From 9741ca7dcfe348d70cd83e93ede76bef2cd47bd1 Mon Sep 17 00:00:00 2001 From: crossxcell99 Date: Thu, 6 Jul 2017 18:46:26 +0100 Subject: [PATCH] use OTP App, SMS or Email to authenticate --- frappe/auth.py | 155 ++++++++++-- frappe/core/doctype/role/role.json | 4 +- .../system_settings/system_settings.json | 35 ++- frappe/core/doctype/user/user.js | 22 ++ frappe/core/doctype/user/user.json | 69 +----- frappe/core/doctype/user/user.py | 99 +++++++- frappe/templates/includes/login/login.js | 222 ++++++++++++++++-- frappe/www/login.py | 15 ++ 8 files changed, 507 insertions(+), 114 deletions(-) diff --git a/frappe/auth.py b/frappe/auth.py index 9a387666d9..1b43096014 100644 --- a/frappe/auth.py +++ b/frappe/auth.py @@ -17,6 +17,8 @@ from frappe.translate import get_lang_code from frappe.utils.password import check_password from frappe.core.doctype.authentication_log.authentication_log import add_authentication_log +from erpnext.setup.doctype.sms_settings.sms_settings import send_request + from urllib import quote import pyotp @@ -127,25 +129,90 @@ class LoginManager: 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 == 1: - - if user_info.two_factor_setup: - frappe.local.response['verification'] = {'setup_completed':True} - otp_secret = frappe.db.get_default(self.user + '_otpsecret') + user_obj = frappe.get_doc('User', self.user) + two_factor_auth_user = len(frappe.db.sql("""select name from `tabRole` where two_factor_auth=1 + and name in ({0}) limit 1""".format(', '.join(['%s'] * len(user_obj.roles))), + [d.role for d in user_obj.roles])) + if two_factor_auth_user == 1: + + otp_secret = frappe.db.get_default(self.user + '_otpsecret') + + restrict_method = frappe.db.get_value('System Settings', None, 'fix_2fa_method') + verification_meth = frappe.db.get_value('User', self.user, 'two_factor_method') + + if restrict_method: + try: + fixed_method = frappe.db.sql('''SELECT DEFAULT(two_factor_method) AS 'default_method' FROM + (SELECT 1) AS dummy LEFT JOIN tabUser on True LIMIT 1;''', as_dict=1) + except OperationalError: + fixed_method = [frappe._dict()] + + if not verification_meth: + verification_method = fixed_method[0].default_method or 'OTP App' + else: + verification_method = fixed_method[0].default_method or verification_meth + + if otp_secret: + + + token = int(pyotp.TOTP(otp_secret).now()) + + if verification_method == 'SMS': + user_phone = frappe.db.get_value('User', self.user, ['phone','mobile_no'], as_dict=1) + usr_phone = user_phone.mobile_no or user_phone.phone + status = self.send_token_via_sms(token=token, phone_no=usr_phone, otpsecret=otp_secret) + verification_obj = {'token_delivery': status, + 'prompt': status and 'Enter verification code sent to {}'.format(usr_phone[:4] + '******' + usr_phone[-3:]), + 'method': 'SMS'} + elif verification_method == 'OTP App': + totp_uri = False + + if frappe.db.get_default(self.user + '_otpsecret', otp_secret): + totp_uri = pyotp.TOTP(otp_secret).provisioning_uri(self.user, issuer_name="Estate Manager") + + verification_obj = {'token_delivery': True, + 'prompt': False, + 'totp_uri': totp_uri, + 'method': 'OTP App'} + elif verification_method == 'Email': + status = self.send_token_via_email(token=token,otpsecret=otp_secret) + verification_obj = {'token_delivery': status, + 'prompt': status and 'Enter verification code sent to your registered email address', + 'method': 'Email'} + frappe.local.response['verification'] = verification_obj else: import os import base64 otp_secret = base64.b32encode(os.urandom(10)).decode('utf-8') - frappe.db.set_default(self.user + '_otpsecret', otp_secret) - 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} + + # not actual token but counter ( hotp.at(counter) gives token) + token = int(pyotp.TOTP(otp_secret).now()) + + frappe.local.response['verification'] = { + 'method_first_time': True, + 'token_delivery': True, + 'prompt': False, + 'totp_uri': totp_uri, + 'restrict_method': fixed_method[0].default_method or 'OTP App' + } 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}) + + if verification_method in ['SMS', 'Email']: + frappe.cache().set(tmp_id + '_token',token) + frappe.cache().expire(tmp_id + '_token',300) + + frappe.cache().set(tmp_id + '_usr', usr) + frappe.cache().set(tmp_id + '_pwd', pwd) + frappe.cache().set(tmp_id + '_otp_secret', otp_secret) + frappe.cache().set(tmp_id + '_user', self.user) + + for field in [tmp_id + nm for nm in ['_usr', '_pwd', '_otp_secret', '_user']]: + frappe.cache().expire(field,120) + frappe.local.response['tmp_id'] = tmp_id raise frappe.RequestToken @@ -155,13 +222,14 @@ class LoginManager: else: try: - tmp_info = frappe.cache().hget('token', frappe.form_dict.get('tmp_id')) + tmp_info = { + 'usr': frappe.cache().get(frappe.form_dict.get('tmp_id')+'_usr'), + 'pwd': frappe.cache().get(frappe.form_dict.get('tmp_id')+'_pwd') + } 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): @@ -169,27 +237,42 @@ class LoginManager: self.validate_ip_address() self.validate_hour() 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')) + hotp_token = frappe.cache().get(frappe.form_dict.get('tmp_id') + '_token') + self.confirm_token(otp=frappe.form_dict.get('otp'), tmp_id=frappe.form_dict.get('tmp_id'), hotp_token=hotp_token) self.make_session() self.set_user_info() else: self.make_session() self.set_user_info() - def confirm_token(self,otp=None, tmp_id=None): + def confirm_token(self,otp=None, tmp_id=None, hotp_token=False): try: - otp_secret = frappe.cache().hget('token',tmp_id).get('otp_secret') + otp_secret = frappe.cache().get(tmp_id + '_otp_secret') or frappe.db.get_default(self.user + '_otpsecret') + if not otp_secret: + return False except AttributeError: return False + + if hotp_token: + u_hotp = pyotp.HOTP(otp_secret) + if u_hotp.verify(otp, int(hotp_token)): + if not frappe.db.get_default(self.user + '_otpsecret'): + frappe.db.set_default(self.user + '_otpsecret', otp_secret) + + frappe.cache().delete(tmp_id + '_token') + return True + else: + self.fail('Incorrect Verification code', self.user) + totp = pyotp.TOTP(otp_secret) if totp.verify(otp): - frappe.cache().hdel('token', tmp_id) # show qr code only once - frappe.db.set_value("User", self.user, 'two_factor_setup', 1) - frappe.db.commit() + if not frappe.db.get_default(self.user + '_otpsecret'): + frappe.db.set_default(self.user + '_otpsecret', otp_secret) + frappe.db.set_default(self.user + '_otplogin', 1) return True else: - self.fail('Incorrect Verification code', user=frappe.cache().hget('token',tmp_id).get('usr')) + self.fail('Incorrect Verification code', self.user) def set_user_info(self, resume=False): # set sid again @@ -333,6 +416,35 @@ class LoginManager: def clear_cookies(self): clear_cookies() + def send_token_via_sms(self,otpsecret,token=None,phone_no=None): + ss = frappe.get_doc('SMS Settings', 'SMS Settings') + if not ss.sms_gateway_url: + return False + hotp = pyotp.HOTP(otpsecret) + args = {ss.message_parameter: 'verification code is {}'.format(hotp.at(int(token)))} + for d in ss.get("parameters"): + args[d.parameter] = d.value + + if not phone_no: + return False + args[ss.receiver_parameter] = phone_no + + status = send_request(ss.sms_gateway_url, args) + + if 200 <= status < 300: + return True + else: + return False + + def send_token_via_email(self,token,otpsecret): + user_email = frappe.db.get_value('User',self.user, 'email') + if not user_email: + return False + hotp = pyotp.HOTP(otpsecret) + frappe.sendmail(recipients=user_email, sender=None, subject='Verification Code', + message='

Your verification code is {}

'.format(hotp.at(int(token))),delayed=False, retry=3) + return True + class CookieManager: def __init__(self): self.cookies = {} @@ -367,6 +479,7 @@ class CookieManager: for key in set(self.to_delete): response.set_cookie(key, "", expires=expires) + @frappe.whitelist() def get_logged_user(): return frappe.session.user diff --git a/frappe/core/doctype/role/role.json b/frappe/core/doctype/role/role.json index a97def1910..1eebb71a36 100644 --- a/frappe/core/doctype/role/role.json +++ b/frappe/core/doctype/role/role.json @@ -121,7 +121,7 @@ "in_global_search": 0, "in_list_view": 0, "in_standard_filter": 0, - "label": "Two Factor Authenticaction", + "label": "Two Factor Authentication", "length": 0, "no_copy": 0, "permlevel": 0, @@ -179,7 +179,7 @@ "issingle": 0, "istable": 0, "max_attachments": 0, - "modified": "2017-06-28 13:29:49.915545", + "modified": "2017-07-06 12:42:57.097914", "modified_by": "Administrator", "module": "Core", "name": "Role", diff --git a/frappe/core/doctype/system_settings/system_settings.json b/frappe/core/doctype/system_settings/system_settings.json index 240da9b303..33130389f3 100644 --- a/frappe/core/doctype/system_settings/system_settings.json +++ b/frappe/core/doctype/system_settings/system_settings.json @@ -710,6 +710,39 @@ "set_only_once": 0, "unique": 0 }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "default": "0", + "depends_on": "eval:doc.enable_two_factor_auth==1", + "description": "If this is checked, the default 2FA method in User > two_factor_method will be used", + "fieldname": "fix_2fa_method", + "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": "Fix authentication method", + "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, @@ -996,7 +1029,7 @@ "issingle": 1, "istable": 0, "max_attachments": 0, - "modified": "2017-06-29 18:01:46.292635", + "modified": "2017-07-06 14:44:04.601775", "modified_by": "Administrator", "module": "Core", "name": "System Settings", diff --git a/frappe/core/doctype/user/user.js b/frappe/core/doctype/user/user.js index aa7f7940e3..14918a8c8a 100644 --- a/frappe/core/doctype/user/user.js +++ b/frappe/core/doctype/user/user.js @@ -107,6 +107,28 @@ frappe.ui.form.on('User', { } cur_frm.dirty(); } + + frappe.call({ + method: "get_2fa_params", + doc:frm.doc, + callback: function(r) { + if (r.message){ + frm.toggle_display('two_factor_method', r.message.show_method_field == true); + if (r.message.restrict_method){ + $("select[data-fieldname=two_factor_method] > option").each(function() { + if ($(this).val() != r.message.restrict_method){ + $(this).attr('disabled',''); + } else { + $(this).removeAttr('disabled') + } + }); + //frm.set_df_property('two_factor_method', 'options', [r.message.restrict_method]); + //frm.set_value('two_factor_method',r.message.restrict_method) + //frm.refresh_field('two_factor_method'); + } + } + } + }); }, validate: function(frm) { if(frm.roles_editor) { diff --git a/frappe/core/doctype/user/user.json b/frappe/core/doctype/user/user.json index 37449fd072..6d809a9292 100644 --- a/frappe/core/doctype/user/user.json +++ b/frappe/core/doctype/user/user.json @@ -1729,9 +1729,10 @@ "bold": 0, "collapsible": 0, "columns": 0, + "default": "OTP App", "fieldname": "two_factor_method", "fieldtype": "Select", - "hidden": 1, + "hidden": 0, "ignore_user_permissions": 0, "ignore_xss_filter": 0, "in_filter": 0, @@ -1741,7 +1742,7 @@ "label": "Two Factor Authentication Method", "length": 0, "no_copy": 0, - "options": "OTP App\nSMS", + "options": "OTP App\nSMS\nEmail", "permlevel": 0, "precision": "", "print_hide": 0, @@ -1987,68 +1988,6 @@ "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, @@ -2064,7 +2003,7 @@ "istable": 0, "max_attachments": 5, "menu_index": 0, - "modified": "2017-06-30 16:26:06.481438", + "modified": "2017-07-04 15:53:25.877843", "modified_by": "Administrator", "module": "Core", "name": "User", diff --git a/frappe/core/doctype/user/user.py b/frappe/core/doctype/user/user.py index 9ea6f4cccd..5b4679a486 100644 --- a/frappe/core/doctype/user/user.py +++ b/frappe/core/doctype/user/user.py @@ -489,6 +489,38 @@ class User(Document): if len(email_accounts) != len(set(email_accounts)): frappe.throw(_("Email Account added multiple times")) + def get_2fa_params(self, twoFA_method=None,user=None): + show_method_field = frappe.db.get_value('System Settings', 'System Settings', 'enable_two_factor_auth') == unicode(1) + try: + two_factor_auth_user = 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])) + except Exception as e: + return {'show_method_field' : False} + + restrict_method = frappe.db.get_value('System Settings', None, 'fix_2fa_method') + if int(restrict_method): + try: + a = frappe.db.sql('''SELECT DEFAULT(two_factor_method) AS 'default_method' FROM + (SELECT 1) AS dummy LEFT JOIN tabUser on True LIMIT 1;''', as_dict=1) + restrict_method = a[0].default_method + except OperationalError: + a = [frappe._dict()] + restrict_method = False + else: + restrict_method = False + + return {'show_method_field' : (two_factor_auth_user == 1) and show_method_field, 'restrict_method': restrict_method} + #if not twoFA_method: + #else: + # if twoFA_method == 'Email': + # if not self.email: + # frappe.throw(_('No User Email Found')) + # elif twoFA_method == 'SMS': + # #user_no = frappe.db.get_values('User', user, ['mobile_no', 'phone'], as_dict=1) + # if not (self.phone or self.mobile_no): + # frappe.throw(_('No User Phone Number Found')) + @frappe.whitelist() def get_timezones(): import pytz @@ -903,4 +935,69 @@ def handle_password_test_fail(result): def update_gravatar(name): gravatar = has_gravatar(name) if gravatar: - frappe.db.set_value('User', name, 'user_image', gravatar) \ No newline at end of file + frappe.db.set_value('User', name, 'user_image', gravatar) + +@frappe.whitelist(allow_guest=True) +def send_token_via_sms(tmp_id,phone_no=None,user=None): + from erpnext.setup.doctype.sms_settings.sms_settings import send_request + + if not frappe.cache().ttl(tmp_id + '_token'): + return False + + token = frappe.cache().get(tmp_id + '_token') + + ss = frappe.get_doc('SMS Settings', 'SMS Settings') + if not ss.sms_gateway_url: + return False + + args = {ss.message_parameter: 'verification code is {}'.format(token)} + + for d in ss.get("parameters"): + args[d.parameter] = d.value + + if user: + user_phone = frappe.db.get_value('User', user, ['phone','mobile_no'], as_dict=1) + usr_phone = user_phone.mobile_no or user_phone.phone + if not usr_phone: + return False + else: + if phone_no: + usr_phone = phone_no + else: + return False + + args[ss.receiver_parameter] = usr_phone + + status = send_request(ss.sms_gateway_url, args) + + if 200 <= status < 300: + frappe.cache().delete(tmp_id + '_token') + return True + else: + return False + +@frappe.whitelist(allow_guest=True) +def send_token_via_email(tmp_id,token=None): + import pyotp + + user = frappe.cache().get(tmp_id + '_user') + count = token or frappe.cache().get(tmp_id + '_token') + if ((not user) or (user == 'None') or (not count)): + return False + + otpsecret = frappe.cache().get(tmp_id + '_otp_secret') + hotp = pyotp.HOTP(otpsecret) + user_email = frappe.db.get_value('User',user, 'email') + if not user_email: + return False + frappe.sendmail(recipients=user_email, sender=None, subject='Verification Code', + message='

Your verification code is {0}

'.format(hotp.at(int(count))),delayed=False, retry=3) + return True + +@frappe.whitelist(allow_guest=True) +def set_verification_method(tmp_id,method=None): + user = frappe.cache().get(tmp_id + '_user') + if ((not user) or (user == 'None') or (not method)): + return False + frappe.db.set_value('User', user, 'two_factor_method', method) + frappe.db.commit() \ No newline at end of file diff --git a/frappe/templates/includes/login/login.js b/frappe/templates/includes/login/login.js index 85b12b980d..f5e0f860a0 100644 --- a/frappe/templates/includes/login/login.js +++ b/frappe/templates/includes/login/login.js @@ -159,37 +159,211 @@ login.login_handlers = (function() { console.log(data); if(data.verification) { login.set_indicator("{{ _("Success") }}", 'green'); - $('.login-content').empty().append($('
').attr({'id':'otp_div'}).html('
\ + + var continue_otp = function(setup_completed,method_prompt){ + + $('.login-content').empty().append($('
').attr({'id':'otp_div'}).html('
\ Verification
\ - \ + \ ')); - if (!data.verification.setup_completed){ - var qrcode = $('
').attr('id','qrcode_div'); - var direction = $('
').attr('id','qr_info').text('Scan QR Code and enter the resulting code displayed'); - var qrcanvas = $(''); - qrcanvas.attr('id','qrcanvass'); - qrcode.append(direction); - qrcode.append(qrcanvas); - $('#otp_div').prepend(qrcode) - qr = new QRious({ - element: document.getElementById('qrcanvass'), - value: data.verification.totp_uri, - background: 'white', // background color - foreground: 'black', // foreground color - level: 'L', // Error correction level of the QR code - mime: 'image/png', // MIME type used to render - size: 200 + + verify_token(); + + if (!setup_completed){ + var qrcode = $('
').attr('id','qrcode_div'); + + var direction = $('
').attr('id','qr_info').text(method_prompt || 'Scan QR Code and enter the resulting code displayed'); + + var qrcanvas = $(''); + qrcanvas.attr('id','qrcanvass'); + qrcode.append(direction); + qrcode.append(qrcanvas); + $('#otp_div').prepend(qrcode) + qr = new QRious({ + element: document.getElementById('qrcanvass'), + value: data.verification.totp_uri, + background: 'white', // background color + foreground: 'black', // foreground color + level: 'L', // Error correction level of the QR code + mime: 'image/png', // MIME type used to render + size: 200 + }); + } else { + var qrcode = $('
').attr('id','qrcode_div'); + var direction = $('
').attr('id','qr_info').text(method_prompt || 'Enter Code displayed in OTP App'); + direction.attr('style','padding-bottom:10px;'); + qrcode.append(direction); + $('#otp_div').prepend(qrcode) + } + } + + var continue_sms = function(setup_completed,method_prompt){ + + $('.login-content').empty().append($('
').attr({'id':'otp_div'}).html( + '
\ +
\ + Verification\ +
\ + \ + \ +
')); + + verify_token(); + + if (!setup_completed){ + var sms_div = $('
').attr({'id':'sms_div','style':'margin-bottom: 20px;'}); + var direction = $('
').attr({'id':'sms_info','style':'margin-bottom: 15px;'}).text('Enter phone number to send verification code'); + sms_div.append(direction); + sms_div.append($('
').attr({'id':'sms_code_div'}).html( + '
\ + \ + \ +

')); + + $('#otp_div').prepend(sms_div); + + $('#submit_phone_no').on('click',function(){ + frappe.call({ + method: "frappe.core.doctype.user.user.send_token_via_sms", + args: {'phone_no': $('#phone_no').val(), 'tmp_id':data.tmp_id }, + freeze: true, + callback: function(r) { + if (r.message){ + $('#sms_div').empty().append( + '

SMS sent.
Enter verification code received


' + ); + } else { + $('#sms_div').empty().append( + '

SMS not sent


' + ); + } + } }); + }) + } else { + var smscode = $('
').attr('id','smscode_div'); + var direction = $('
').attr('id','qr_info').text(method_prompt || 'Enter verification code sent to registered phone number'); + direction.attr('style','padding-bottom:10px;'); + smscode.append(direction); + $('#otp_div').prepend(smscode) + } + } + + var continue_email = function(setup_completed,method_prompt){ + + $('.login-content').empty().append($('
').attr({'id':'otp_div'}).html( + '
\ +
\ + Verification\ +
\ + \ + \ +
')); + + verify_token(); + + if (!setup_completed){ + var email_div = $('
').attr({'id':'email_div','style':'margin-bottom: 20px;'}); + email_div.append('

Verification code email will be sent to registered email address. Enter code received below

') + + $('#otp_div').prepend(email_div); + + frappe.call({ + method: "frappe.core.doctype.user.user.send_token_via_email", + args: {'tmp_id':data.tmp_id }, + callback: function(r) { + if (r.message){ + } else { + $('#email_div').empty().append( + '

Email not sent


' + ); + } + } + }); + } else { + if (method_prompt){ + var emailcode = $('
').attr('id','emailcode_div'); + var direction = $('
').attr('id','qr_info').text(method_prompt || 'Verification code email will be sent to registered email address. Enter code received below'); + direction.attr('style','padding-bottom:10px;'); + emailcode.append(direction); + $('#otp_div').prepend(emailcode); + } else { + var emailcode = $('
').attr('id','emailcode_div'); + var direction = $('
').attr('id','qr_info').text('Verification code email not sent'); + direction.attr('style','padding-bottom:10px;'); + emailcode.append(direction); + $('#otp_div').prepend(emailcode) + } + + } + } + + if (data.verification.method_first_time){ + $('.login-content').empty().append('
\ +
\ +

Select verification Method
\ + method may be changed later in settings

\ +
\ +
\ + \ +
\ +
\ + \ +
\ +
\ + \ +
\ + \ +
') + + if (data.verification.restrict_method){ + $('input[name=method]').each(function(){ + if ($(this).val() != data.verification.restrict_method){ + $(this).attr('disabled',true) + } + }) + } + $('#submit_method').on('click',function(event){ + if ($('input[name=method]:checked').val() == 'OTP App'){ + continue_otp(setup_completed=false); + } else if ($('input[name=method]:checked').val() == 'SMS'){ + continue_sms(setup_completed=false); + console.log('SMS'); + } else if ($('input[name=method]:checked').val() == 'Email'){ + continue_email(setup_completed=false); + } + + frappe.call({ + method: "frappe.core.doctype.user.user.set_verification_method", + args: {'tmp_id':data.tmp_id, 'method': $('input[name=method]:checked').val()}, + callback: function(r) { } + }); + }); } else { - var qrcode = $('
').attr('id','qrcode_div'); - var direction = $('
').attr('id','qr_info').text('Enter the code displayed in otp app under the appropriate account'); - direction.attr('style','padding-bottom:10px;'); - qrcode.append(direction); - $('#otp_div').prepend(qrcode) + if (data.verification.method == 'OTP App'){ + console.log(data.verification.totp_uri) + continue_otp(setup_completed = !data.verification.totp_uri); + } else if (data.verification.method == 'SMS'){ + continue_sms(setup_completed=true, method_prompt=data.verification.prompt); + console.log('SMS'); + } else if (data.verification.method == 'Email'){ + continue_sms(setup_completed=true, method_prompt=data.verification.prompt); + } } + document.cookie = "tmp_id="+data.tmp_id; - verify_token(); + //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; diff --git a/frappe/www/login.py b/frappe/www/login.py index dbfc6f6fe5..e81c7bcb78 100644 --- a/frappe/www/login.py +++ b/frappe/www/login.py @@ -12,6 +12,20 @@ from frappe.integrations.doctype.ldap_settings.ldap_settings import get_ldap_set no_cache = True +import pyqrcode +from StringIO import StringIO +from werkzeug.wrappers import Response + +def get_qr_code(): + + url = pyqrcode.create('http://www.google.com') + stream = StringIO() + url.svg(stream, scale=5) + responses = Response(stream.getvalue().encode('utf-8')) + responses.status_code = 200 + responses.headers['content-type'] = 'image/svg+xml; charset=utf-8' + return responses + def get_context(context): if frappe.session.user != "Guest" and frappe.session.data.user_type=="System User": frappe.local.flags.redirect_location = "/testpayment" @@ -30,6 +44,7 @@ def get_context(context): ldap_settings = get_ldap_settings() context["ldap_settings"] = ldap_settings + context['qqrcode'] = frappe.render_template(get_qr_code()) return context