diff --git a/frappe/core/doctype/system_settings/system_settings.json b/frappe/core/doctype/system_settings/system_settings.json index 77f207cb10..02e5eda0e5 100644 --- a/frappe/core/doctype/system_settings/system_settings.json +++ b/frappe/core/doctype/system_settings/system_settings.json @@ -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:240", + "description": "Time in seconds to retain QR code image on server. Min:240", "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", diff --git a/frappe/twofactor.py b/frappe/twofactor.py index a70f3d1985..3bcf8c5ce1 100644 --- a/frappe/twofactor.py +++ b/frappe/twofactor.py @@ -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 = '''
Please scan the barcode for One Time Password
-Your verification code is {}.
'.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: diff --git a/frappe/www/qrcode.html b/frappe/www/qrcode.html new file mode 100644 index 0000000000..3edd1f7e18 --- /dev/null +++ b/frappe/www/qrcode.html @@ -0,0 +1,12 @@ +