Преглед на файлове

use OTP App, SMS or Email to authenticate

version-14
crossxcell99 преди 8 години
родител
ревизия
9741ca7dcf
променени са 8 файла, в които са добавени 507 реда и са изтрити 114 реда
  1. +134
    -21
      frappe/auth.py
  2. +2
    -2
      frappe/core/doctype/role/role.json
  3. +34
    -1
      frappe/core/doctype/system_settings/system_settings.json
  4. +22
    -0
      frappe/core/doctype/user/user.js
  5. +4
    -65
      frappe/core/doctype/user/user.json
  6. +98
    -1
      frappe/core/doctype/user/user.py
  7. +198
    -24
      frappe/templates/includes/login/login.js
  8. +15
    -0
      frappe/www/login.py

+ 134
- 21
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", """<p>You have been logged out.</p><p><a href='index'>Back to Home</a></p>""")
#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='<p>Your verification code is {}</p>'.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


+ 2
- 2
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",


+ 34
- 1
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 &gt; 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",


+ 22
- 0
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) {


+ 4
- 65
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",


+ 98
- 1
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)
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='<p>Your verification code is {0}</p>'.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()

+ 198
- 24
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($('<div>').attr({'id':'otp_div'}).html('<form class="form-verify"><div class="page-card-head">\

var continue_otp = function(setup_completed,method_prompt){

$('.login-content').empty().append($('<div>').attr({'id':'otp_div'}).html('<form class="form-verify"><div class="page-card-head">\
<span class="indicator blue" data-text="Verification">Verification</span></div>\
<input type="text" id="login_token" class="form-control" placeholder="Verification Code" required="" autofocus="">\
<input type="text" id="login_token" class="form-control" placeholder="Verification Code" required="" autocomplete="off" autofocus="">\
<button class="btn btn-sm btn-primary btn-block" id="verify_token">Verify</button></form>'));
if (!data.verification.setup_completed){
var qrcode = $('<div>').attr('id','qrcode_div');
var direction = $('<div>').attr('id','qr_info').text('Scan QR Code and enter the resulting code displayed');
var qrcanvas = $('<canvas>');
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 = $('<div>').attr('id','qrcode_div');

var direction = $('<div>').attr('id','qr_info').text(method_prompt || 'Scan QR Code and enter the resulting code displayed');

var qrcanvas = $('<canvas>');
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 = $('<div>').attr('id','qrcode_div');
var direction = $('<div>').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($('<div>').attr({'id':'otp_div'}).html(
'<form class="form-verify">\
<div class="page-card-head">\
<span class="indicator blue" data-text="Verification">Verification</span>\
</div>\
<input type="text" id="login_token" class="form-control" placeholder="Verification Code" required="" autofocus="">\
<button class="btn btn-sm btn-primary btn-block" id="verify_token">Verify</button>\
</form>'));

verify_token();

if (!setup_completed){
var sms_div = $('<div>').attr({'id':'sms_div','style':'margin-bottom: 20px;'});
var direction = $('<div>').attr({'id':'sms_info','style':'margin-bottom: 15px;'}).text('Enter phone number to send verification code');
sms_div.append(direction);
sms_div.append($('<div>').attr({'id':'sms_code_div'}).html(
'<div class="form-group text-center">\
<input type="text" id="phone_no" class="form-control" placeholder="2347001234567" required="" autofocus="">\
<button class="btn btn-sm btn-primary" id="submit_phone_no" >Send SMS</button>\
</div><hr>'));

$('#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(
'<p class="lead">SMS sent.<br><small><small>Enter verification code received</small></small></p><hr>'
);
} else {
$('#sms_div').empty().append(
'<p class="lead">SMS not sent</p><hr>'
);
}
}
});
})
} else {
var smscode = $('<div>').attr('id','smscode_div');
var direction = $('<div>').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($('<div>').attr({'id':'otp_div'}).html(
'<form class="form-verify">\
<div class="page-card-head">\
<span class="indicator blue" data-text="Verification">Verification</span>\
</div>\
<input type="text" id="login_token" class="form-control" placeholder="Verification Code" required="" autofocus="">\
<button class="btn btn-sm btn-primary btn-block" id="verify_token">Verify</button>\
</form>'));

verify_token();

if (!setup_completed){
var email_div = $('<div>').attr({'id':'email_div','style':'margin-bottom: 20px;'});
email_div.append('<p>Verification code email will be sent to registered email address. Enter code received below</p>')

$('#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(
'<p>Email not sent</p><hr>'
);
}
}
});
} else {
if (method_prompt){
var emailcode = $('<div>').attr('id','emailcode_div');
var direction = $('<div>').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 = $('<div>').attr('id','emailcode_div');
var direction = $('<div>').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('<div id="verification_method">\
<div>\
<p class="lead">Select verification Method <br>\
<small><small><small class="text-muted">method may be changed later in settings</small></small></small></p>\
</div>\
<div class="form-check">\
<label class="form-check-label">\
<input class="form-check-input" type="radio" name="method" value="OTP App" checked>\
OTP App\
</label>\
</div>\
<div class="form-check">\
<label class="form-check-label">\
<input class="form-check-input" type="radio" name="method" value="SMS">\
SMS\
</label>\
</div>\
<div class="form-check disabled">\
<label class="form-check-label">\
<input class="form-check-input" type="radio" name="method" value="Email">\
Email\
</label>\
</div>\
<button id="submit_method" class="btn btn-sm btn-primary">Continue</button>\
</div>')

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 = $('<div>').attr('id','qrcode_div');
var direction = $('<div>').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;


+ 15
- 0
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



Зареждане…
Отказ
Запис