Merge branch 'twofactor' of https://github.com/manqala/frappe into twofactor
This commit is contained in:
commit
4cf4bb38ea
4 changed files with 252 additions and 17 deletions
|
|
@ -751,7 +751,7 @@
|
|||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"depends_on": "eval:doc.enable_two_factor_auth==1 && doc.two_factor_method == \"OTP App\" && doc.send_barcode_as_email==1",
|
||||
"description": "Time in seconds to retain barcode image on server. Min:<strong>240</strong>",
|
||||
"description": "Time in seconds to retain QR code image on server. Min:<strong>240</strong>",
|
||||
"fieldname": "lifespan_barcode_image",
|
||||
"fieldtype": "Int",
|
||||
"hidden": 0,
|
||||
|
|
@ -761,7 +761,7 @@
|
|||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Delete Barcode Image On server",
|
||||
"label": "Delete QR Code Image On server",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
|
|
@ -1010,7 +1010,131 @@
|
|||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Send Barcode as Email",
|
||||
"label": "Send QR Code as email",
|
||||
"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,
|
||||
"depends_on": "eval:doc.enable_two_factor_auth==1 && doc.two_factor_method == \"OTP App\" && doc.send_barcode_as_email==1",
|
||||
"fieldname": "qr_code_email_subject",
|
||||
"fieldtype": "Data",
|
||||
"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": "QR Code Email Subject",
|
||||
"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,
|
||||
"depends_on": "eval:doc.enable_two_factor_auth==1 && doc.two_factor_method == \"OTP App\" && doc.send_barcode_as_email==1",
|
||||
"fieldname": "qr_code_email_body",
|
||||
"fieldtype": "Small Text",
|
||||
"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": "QR Code Email Body",
|
||||
"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,
|
||||
"depends_on": "eval:doc.enable_two_factor_auth==1 && doc.two_factor_method == \"Email\"",
|
||||
"fieldname": "two_factor_email_subject",
|
||||
"fieldtype": "Data",
|
||||
"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 Email Subject",
|
||||
"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,
|
||||
"depends_on": "eval:doc.enable_two_factor_auth==1 && doc.two_factor_method == \"Email\"",
|
||||
"fieldname": "two_factor_email_body",
|
||||
"fieldtype": "Small Text",
|
||||
"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 Email Body",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
|
|
@ -1157,7 +1281,7 @@
|
|||
"issingle": 1,
|
||||
"istable": 0,
|
||||
"max_attachments": 0,
|
||||
"modified": "2017-07-27 12:23:01.135841",
|
||||
"modified": "2017-07-28 07:21:12.520227",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Core",
|
||||
"name": "System Settings",
|
||||
|
|
|
|||
|
|
@ -7,19 +7,20 @@ import frappe
|
|||
from frappe import _
|
||||
import pyotp,base64,os
|
||||
from frappe.utils.background_jobs import enqueue
|
||||
from jinja2 import Template
|
||||
from pyqrcode import create as qrcreate
|
||||
from StringIO import StringIO
|
||||
from base64 import b64encode,b32encode
|
||||
from frappe.utils import get_url, get_datetime, time_diff_in_seconds
|
||||
|
||||
|
||||
class ExpiredLoginExpection(Exception):pass
|
||||
class ExpiredLoginException(Exception):pass
|
||||
|
||||
def should_run_2fa(user):
|
||||
'''Check if 2fa should run.'''
|
||||
site_otp_enabled = frappe.db.get_value('System Settings', 'System Settings', 'enable_two_factor_auth')
|
||||
user_otp_enabled = two_factor_is_enabled_for_(user)
|
||||
#Don't validate for Admin of if not enabled
|
||||
#Don't validate for Admin or if not enabled
|
||||
if user =='Administrator' or not site_otp_enabled or not user_otp_enabled:
|
||||
return False
|
||||
return True
|
||||
|
|
@ -102,7 +103,7 @@ def confirm_otp_token(login_manager,otp=None,tmp_id=None):
|
|||
hotp_token = frappe.cache().get(tmp_id + '_token')
|
||||
otp_secret = frappe.cache().get(tmp_id + '_otp_secret')
|
||||
if not otp_secret:
|
||||
raise ExpiredLoginExpection(_('Login session expired, refresh page to retry'))
|
||||
raise ExpiredLoginException(_('Login session expired, refresh page to retry'))
|
||||
hotp = pyotp.HOTP(otp_secret)
|
||||
if hotp_token:
|
||||
if hotp.verify(otp, int(hotp_token)):
|
||||
|
|
@ -119,7 +120,7 @@ def confirm_otp_token(login_manager,otp=None,tmp_id=None):
|
|||
delete_qrimage(login_manager.user)
|
||||
return True
|
||||
else:
|
||||
login_manager.fail('Incorrect Verification code', login_manager.user)
|
||||
login_manager.fail(_('Incorrect Verification code'), login_manager.user)
|
||||
|
||||
|
||||
def get_verification_obj(user,token,otp_secret):
|
||||
|
|
@ -166,20 +167,71 @@ def process_2fa_for_email(user,token,otp_secret,otp_issuer,method='email'):
|
|||
'''Process Email method for 2fa.'''
|
||||
message = None
|
||||
status = True
|
||||
# TODO SVG don't display in email
|
||||
if method == 'otp_app' and not frappe.db.get_default(user + '_otplogin'):
|
||||
totp_uri = pyotp.TOTP(otp_secret).provisioning_uri(user, issuer_name=otp_issuer)
|
||||
message = '''<p>Please scan the barcode for One Time Password</p>
|
||||
<img src="{}"
|
||||
style':'width:150px;height:150px;>'''.format(qrcode_as_png(user,totp_uri))
|
||||
qrcode_link = get_link_for_qrcode(user,totp_uri)
|
||||
message = get_email_body_for_qr_code({'qrcode_link':qrcode_link})
|
||||
subject = get_email_subject_for_qr_code({'qrcode_link':qrcode_link})
|
||||
if method == 'email' or message:
|
||||
status = send_token_via_email(user,token,otp_secret,otp_issuer,message=message)
|
||||
status = send_token_via_email(user,token,otp_secret,otp_issuer,subject=subject,message=message)
|
||||
verification_obj = {'token_delivery': status,
|
||||
'prompt': status and 'Verification code has been sent to your registered email address',
|
||||
'method': 'Email'}
|
||||
return verification_obj
|
||||
|
||||
def get_email_subject_for_2fa(kwargs_dict):
|
||||
'''Get email subject for 2fa.'''
|
||||
subject_template = 'Verifcation Code from Frappe Framework'
|
||||
template = frappe.get_value('System Settings','System Settings','two_factor_email_subject')
|
||||
if not template == '':
|
||||
subject_template = template
|
||||
subject = render_string_template(subject_template,kwargs_dict)
|
||||
return subject
|
||||
|
||||
def get_email_body_for_2fa(kwargs_dict):
|
||||
'''Get email body for 2fa.'''
|
||||
body_template = 'Use this token to login <br> {{otp}}'
|
||||
template = frappe.get_value('System Settings','System Settings','two_factor_email_body')
|
||||
if not template == '':
|
||||
subject_template = template
|
||||
body = render_string_template(body_template,kwargs_dict)
|
||||
return body
|
||||
|
||||
def get_email_subject_for_qr_code(kwargs_dict):
|
||||
'''Get QRCode email subject.'''
|
||||
subject_template = 'Verification Code from Frappe Framework'
|
||||
template = frappe.get_value('System Settings','System Settings','qr_code_email_subject')
|
||||
if not template == '':
|
||||
subject_template = template
|
||||
subject = render_string_template(subject_template,kwargs_dict)
|
||||
return subject
|
||||
|
||||
def get_email_body_for_qr_code(kwargs_dict):
|
||||
'''Get QRCode email body.'''
|
||||
body_template = 'Scan the QRCode on this link to get token <br> {{qrcode_link}}'
|
||||
template = frappe.get_value('System Settings','System Settings','qr_code_email_body')
|
||||
if not template == '':
|
||||
body_template = template
|
||||
body = render_string_template(body_template,kwargs_dict)
|
||||
return body
|
||||
|
||||
def render_string_template(_str,kwargs_dict):
|
||||
'''Render string with jinja.'''
|
||||
s = Template(_str)
|
||||
s = s.render(**kwargs_dict)
|
||||
return s
|
||||
|
||||
def get_link_for_qrcode(user,totp_uri):
|
||||
'''Get link to temporary page showing QRCode.'''
|
||||
key = frappe.generate_hash(length=20)
|
||||
key_user = "{}_user".format(key)
|
||||
key_uri = "{}_uri".format(key)
|
||||
lifespan = int(frappe.db.get_value('System Settings', 'System Settings', 'lifespan_barcode_image'))
|
||||
if lifespan<=0:
|
||||
lifespan = 240
|
||||
frappe.cache().set_value(key_uri,totp_uri,expires_in_sec=lifespan)
|
||||
frappe.cache().set_value(key_user,user,expires_in_sec=lifespan)
|
||||
return get_url('/qrcode?k={}'.format(key))
|
||||
|
||||
def send_token_via_sms(otpsecret, token=None, phone_no=None):
|
||||
'''Send token as sms to user.'''
|
||||
|
|
@ -207,16 +259,20 @@ def send_token_via_sms(otpsecret, token=None, phone_no=None):
|
|||
enqueue(method=send_request, queue='short', timeout=300, event=None, async=True, job_name=None, now=False, **sms_args)
|
||||
return True
|
||||
|
||||
def send_token_via_email(user, token, otp_secret, otp_issuer,message=None):
|
||||
def send_token_via_email(user, token, otp_secret, otp_issuer,subject=None,message=None):
|
||||
'''Send token to user as email.'''
|
||||
user_email = frappe.db.get_value('User', user, 'email')
|
||||
if not user_email:
|
||||
return False
|
||||
hotp = pyotp.HOTP(otp_secret)
|
||||
otp = hotp.at(int(token))
|
||||
template_args = {'otp':otp,'otp_issuer':otp_issuer}
|
||||
if not subject:
|
||||
subject = get_email_subject_for_2fa(template_args)
|
||||
if not message:
|
||||
message = '<p>Your verification code is {}.</p>'.format(hotp.at(int(token)))
|
||||
message = get_email_body_for_2fa(template_args)
|
||||
email_args = {
|
||||
'recipients':user_email, 'sender':None, 'subject':'Verification Code from {}'.format(otp_issuer or "Frappe Framework"),
|
||||
'recipients':user_email, 'sender':None, 'subject':subject,
|
||||
'message':message,
|
||||
'delayed':False, 'retry':3 }
|
||||
|
||||
|
|
@ -236,7 +292,7 @@ def get_qr_svg_code(totp_uri):
|
|||
svg = ''
|
||||
stream = StringIO()
|
||||
try:
|
||||
url.svg(stream, scale=3)
|
||||
url.svg(stream, scale=4, background="#eee", module_color="#222")
|
||||
svg = stream.getvalue().replace('\n','')
|
||||
svg = b64encode(bytes(svg))
|
||||
finally:
|
||||
|
|
|
|||
12
frappe/www/qrcode.html
Normal file
12
frappe/www/qrcode.html
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
<div>
|
||||
<div style="text-align:center">
|
||||
<div style="width:400px;margin:auto;text-align:center;">
|
||||
<strong style="padding:10px;">Hi {{qr_code_user.first_name}}, Please scan QR Code and enter the resulting code displayed.
|
||||
You can use apps such as Google Authenticator, Lastpass Authenticator, Authy, Duo Mobile and others.
|
||||
</strong>
|
||||
</div>
|
||||
<div style="margin-top:10px;">
|
||||
<img src="data:image/svg+xml;base64,{{qrcode_svg}}">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
43
frappe/www/qrcode.py
Normal file
43
frappe/www/qrcode.py
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# MIT License. See license.txt
|
||||
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import frappe
|
||||
from frappe import _
|
||||
from urlparse import parse_qs
|
||||
from frappe.twofactor import get_qr_svg_code
|
||||
|
||||
no_cache = 1
|
||||
|
||||
|
||||
def get_context(context):
|
||||
context.qr_code_user,context.qrcode_svg = get_user_svg_from_cache()
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
def get_query_key():
|
||||
'''Return query string arg.'''
|
||||
query_string = frappe.local.request.query_string
|
||||
query = parse_qs(query_string)
|
||||
if not 'k' in query.keys():
|
||||
frappe.throw(_('Not Permitted'),frappe.PermissionError)
|
||||
query = (query['k'][0]).strip()
|
||||
if False in [i.isalpha() or i.isdigit() for i in query]:
|
||||
frappe.throw(_('Not Permitted'),frappe.PermissionError)
|
||||
return query
|
||||
|
||||
def get_user_svg_from_cache():
|
||||
'''Get User and SVG code from cache.'''
|
||||
key = get_query_key()
|
||||
totp_uri = frappe.cache().get_value("{}_uri".format(key))
|
||||
user = frappe.cache().get_value("{}_user".format(key))
|
||||
if not totp_uri or not user:
|
||||
frappe.throw(_('Page has expired!'),frappe.PermissionError)
|
||||
if not frappe.db.exists('User',user):
|
||||
frappe.throw(_('Not Permitted'), frappe.PermissionError)
|
||||
user = frappe.get_doc('User',user)
|
||||
svg = get_qr_svg_code(totp_uri)
|
||||
return (user,svg)
|
||||
Loading…
Add table
Reference in a new issue