From 977130807234d75a32187673254d0ba1e007f033 Mon Sep 17 00:00:00 2001 From: crossxcell99 Date: Wed, 28 Jun 2017 18:02:20 +0100 Subject: [PATCH] Check if user role on login, return otpauth uri --- frappe/auth.py | 70 ++++++++++++++++++++++-- frappe/core/doctype/role/role.json | 33 ++++++++++- frappe/core/doctype/user/user.json | 64 +++++++++++++++++++++- frappe/core/doctype/user/user.py | 9 +++ frappe/exceptions.py | 3 + frappe/templates/includes/login/login.js | 46 +++++++++++++++- frappe/www/login.py | 2 +- 7 files changed, 217 insertions(+), 10 deletions(-) diff --git a/frappe/auth.py b/frappe/auth.py index 3612b98f04..0a9a7ade5f 100644 --- a/frappe/auth.py +++ b/frappe/auth.py @@ -19,6 +19,8 @@ from frappe.core.doctype.authentication_log.authentication_log import add_authen from urllib import quote +import pyotp + class HTTPRequest: def __init__(self): # Get Environment variables @@ -116,15 +118,73 @@ class LoginManager: def login(self): # clear cache frappe.clear_cache(user = frappe.form_dict.get('usr')) - self.authenticate() - self.post_login() + otp = frappe.form_dict.get('otp') + if not otp: + self.authenticate() + # after authenticate, self.user is set (from check_password() call) + user_info = frappe.db.get_value('User', self.user, ['two_factor_auth','two_factor_setup'], as_dict=1) + if user_info.two_factor_auth: + + if user_info.two_factor_setup: + frappe.local.response['verification'] = {'setup_completed':True} + raise frappe.RequestToken + otp_secret = frappe.db.get_default(self.user + '_otpsecret') + else: + import os + import base64 + otp_secret = base64.b32encode(os.urandom(10)).decode('utf-8') + frappe.db.set_default(self.user + '_otpsecret', otp_secret) + # set two_factor_setup as 1 meaning user has copied otpsecret + frappe.db.set_value("User", self.user, 'two_factor_setup', 1) + frappe.db.commit() + totp_uri = pyotp.totp.TOTP(otp_secret).provisioning_uri(self.user, issuer_name="Estate Manager") + frappe.local.response['verification'] = {'setup_completed':False, 'totp_uri':totp_uri} + + tmp_id = frappe.generate_hash(length=8) + usr = frappe.form_dict.get('usr') + pwd = frappe.form_dict.get('pwd') + frappe.cache().hset('token',tmp_id,{'usr':usr,'pwd':pwd,'otp_secret':otp_secret}) + frappe.local.response['tmp_id'] = tmp_id + + raise frappe.RequestToken + + else: + self.post_login(no_two_auth=True) - def post_login(self): + else: + try: + tmp_info = frappe.cache().hget('token', frappe.form_dict.get('tmp_id')) + self.authenticate(user=tmp_info['usr'], pwd=tmp_info['pwd']) + except: + frappe.log_error(frappe.get_traceback(),"AUTHENTICATION PROBLEM") + #frappe.respond_as_web_page("Logged Out", """

You have been logged out.

Back to Home

""") + #frappe.throw("+++++ YOUR LOGIN WAS SUCCESSFUL, CONGRATS +++++") + #frappe.website.render('/404.html') + self.post_login() + + def post_login(self,no_two_auth=False): self.run_trigger('on_login') self.validate_ip_address() self.validate_hour() - self.make_session() - self.set_user_info() + if frappe.form_dict.get('otp') and not no_two_auth: + self.confirm_token(otp=frappe.form_dict.get('otp'), tmp_id=frappe.form_dict.get('tmp_id')) + self.make_session() + self.set_user_info() + else: + self.make_session() + self.set_user_info() + + def confirm_token(self,otp=None, tmp_id=None): + try: + otp_secret = frappe.cache().hget('token',tmp_id).get('otp_secret') + except AttributeError: + return False + totp = pyotp.TOTP(otp_secret) + if totp.verify(otp): + frappe.cache().hdel('token', tmp_id) + return True + else: + self.fail('Incorrect Verification code', user=frappe.cache().hget('token',tmp_id).get('usr')) def set_user_info(self, resume=False): # set sid again diff --git a/frappe/core/doctype/role/role.json b/frappe/core/doctype/role/role.json index 104ee7d53c..a97def1910 100644 --- a/frappe/core/doctype/role/role.json +++ b/frappe/core/doctype/role/role.json @@ -105,6 +105,37 @@ "set_only_once": 0, "unique": 0 }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "default": "0", + "fieldname": "two_factor_auth", + "fieldtype": "Check", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Two Factor Authenticaction", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, { "allow_bulk_edit": 0, "allow_on_submit": 0, @@ -148,7 +179,7 @@ "issingle": 0, "istable": 0, "max_attachments": 0, - "modified": "2017-05-04 11:03:41.533058", + "modified": "2017-06-28 13:29:49.915545", "modified_by": "Administrator", "module": "Core", "name": "Role", diff --git a/frappe/core/doctype/user/user.json b/frappe/core/doctype/user/user.json index 3eda403272..809a9ea2e7 100644 --- a/frappe/core/doctype/user/user.json +++ b/frappe/core/doctype/user/user.json @@ -1956,6 +1956,68 @@ "search_index": 0, "set_only_once": 0, "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "default": "0", + "fieldname": "two_factor_auth", + "fieldtype": "Check", + "hidden": 1, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Two Factor Authentication", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "default": "0", + "fieldname": "two_factor_setup", + "fieldtype": "Check", + "hidden": 1, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Two Factor Initial Setup Completed", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 } ], "has_web_view": 0, @@ -1971,7 +2033,7 @@ "istable": 0, "max_attachments": 5, "menu_index": 0, - "modified": "2017-05-19 09:12:35.697915", + "modified": "2017-06-28 14:40:26.616254", "modified_by": "Administrator", "module": "Core", "name": "User", diff --git a/frappe/core/doctype/user/user.py b/frappe/core/doctype/user/user.py index c91c876680..321b4f9304 100644 --- a/frappe/core/doctype/user/user.py +++ b/frappe/core/doctype/user/user.py @@ -57,6 +57,7 @@ class User(Document): self.validate_email_type(self.name) self.add_system_manager_role() self.set_system_user() + self.set_two_factor_auth() self.set_full_name() self.check_enable_disable() self.ensure_unique_roles() @@ -146,6 +147,14 @@ class User(Document): else: self.user_type = 'Website User' + def set_two_factor_auth(self): + '''Set two factor authentication for user''' + if (len(frappe.db.sql("""select name + from `tabRole` where two_factor_auth=1 + and name in ({0}) limit 1""".format(', '.join(['%s'] * len(self.roles))), + [d.role for d in self.roles]))): + self.two_factor_auth = 1 + def has_desk_access(self): '''Return true if any of the set roles has desk access''' if not self.roles: diff --git a/frappe/exceptions.py b/frappe/exceptions.py index 723c602496..ae9fca7e7a 100644 --- a/frappe/exceptions.py +++ b/frappe/exceptions.py @@ -37,6 +37,9 @@ class SessionStopped(Exception): class UnsupportedMediaType(Exception): http_status_code = 415 +class RequestToken(Exception): + http_status_code = 200 + class Redirect(Exception): http_status_code = 301 diff --git a/frappe/templates/includes/login/login.js b/frappe/templates/includes/login/login.js index 249487333e..afbfc8656f 100644 --- a/frappe/templates/includes/login/login.js +++ b/frappe/templates/includes/login/login.js @@ -5,11 +5,14 @@ window.disable_signup = {{ disable_signup and "true" or "false" }}; window.login = {}; +window.verify = {}; + login.bind_events = function() { $(window).on("hashchange", function() { login.route(); }); + $(".form-login").on("submit", function(event) { event.preventDefault(); var args = {}; @@ -90,6 +93,11 @@ login.login = function() { $(".for-login").toggle(true); } +login.steptwo = function() { + login.reset_sections(); + $(".for-login").toggle(true); +} + login.forgot = function() { login.reset_sections(); $(".for-forgot").toggle(true); @@ -148,7 +156,17 @@ login.login_handlers = (function() { var login_handlers = { 200: function(data) { - if(data.message=="Logged In") { + console.log(data); + if(data.token) { + login.set_indicator("{{ _("Success") }}", 'green'); + $('.login-content').empty().append($('
').html('
\ + Verification
\ + \ +
')); + document.cookie = "tmp_id="+data.tmp_id; + verify_token(); + return false; + } else if(data.message == 'Logged In'){ login.set_indicator("{{ _("Success") }}", 'green'); window.location.href = get_url_arg("redirect-to") || data.home_page; } else if(data.message=="No App") { @@ -194,10 +212,14 @@ login.login_handlers = (function() { }; return login_handlers; -})(); +} )(); frappe.ready(function() { + + login.bind_events(); + console.log("Why"); + if (!window.location.hash) { window.location.hash = "#login"; @@ -208,3 +230,23 @@ frappe.ready(function() { $(".form-signup, .form-forgot").removeClass("hide"); $(document).trigger('login_rendered'); }); + +var verify_token = function(event) { + $('#verify_token').bind("click", function() { + console.log("Why XX2"); + //eventx.preventDefault(); + var args = {}; + args.cmd = "login"; + args.otp = $("#login_token").val(); + console.log("LLLLLLLLLLLLLLLLLLL"); + args.tmp_id = frappe.get_cookie('tmp_id'); + if(!args.otp) { + frappe.msgprint('{{ _("Login token required") }}'); + return false; + } + console.log("Button Clicked") + console.log(args) + login.call(args); + return false; + }); +} diff --git a/frappe/www/login.py b/frappe/www/login.py index cc149abbec..dbfc6f6fe5 100644 --- a/frappe/www/login.py +++ b/frappe/www/login.py @@ -14,7 +14,7 @@ no_cache = True def get_context(context): if frappe.session.user != "Guest" and frappe.session.data.user_type=="System User": - frappe.local.flags.redirect_location = "/desk" + frappe.local.flags.redirect_location = "/testpayment" raise frappe.Redirect # get settings from site config