From dcee43f646f251c36bfd9ab49cdf61d2d034f0e2 Mon Sep 17 00:00:00 2001 From: B H Boma Date: Thu, 27 Jul 2017 17:59:43 +0100 Subject: [PATCH] Settings to send Qrcode as email to user --- .../system_settings/system_settings.json | 36 ++++++++++- frappe/hooks.py | 3 +- frappe/templates/includes/login/login.js | 2 +- frappe/twofactor.py | 61 ++++++++++++++++--- requirements.txt | 1 + 5 files changed, 92 insertions(+), 11 deletions(-) diff --git a/frappe/core/doctype/system_settings/system_settings.json b/frappe/core/doctype/system_settings/system_settings.json index d8a4de1d9d..77f207cb10 100644 --- a/frappe/core/doctype/system_settings/system_settings.json +++ b/frappe/core/doctype/system_settings/system_settings.json @@ -744,6 +744,38 @@ "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", + "description": "Time in seconds to retain barcode image on server. Min:240", + "fieldname": "lifespan_barcode_image", + "fieldtype": "Int", + "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": "Delete Barcode Image On server", + "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, @@ -968,7 +1000,7 @@ "bold": 0, "collapsible": 0, "columns": 0, - "depends_on": "eval:doc.two_factor_method == \"OTP App\"", + "depends_on": "eval:doc.enable_two_factor_auth==1 && doc.two_factor_method == \"OTP App\"", "fieldname": "send_barcode_as_email", "fieldtype": "Check", "hidden": 0, @@ -1125,7 +1157,7 @@ "issingle": 1, "istable": 0, "max_attachments": 0, - "modified": "2017-07-26 18:31:27.992012", + "modified": "2017-07-27 12:23:01.135841", "modified_by": "Administrator", "module": "Core", "name": "System Settings", diff --git a/frappe/hooks.py b/frappe/hooks.py index 49ec772175..bf990a9f72 100755 --- a/frappe/hooks.py +++ b/frappe/hooks.py @@ -128,7 +128,8 @@ scheduler_events = { "frappe.email.doctype.email_account.email_account.pull", "frappe.email.doctype.email_account.email_account.notify_unreplied", "frappe.oauth.delete_oauth2_data", - "frappe.integrations.doctype.razorpay_settings.razorpay_settings.capture_payment" + "frappe.integrations.doctype.razorpay_settings.razorpay_settings.capture_payment", + "frappe.twofactor.delete_all_barcodes_for_users" ], "hourly": [ "frappe.model.utils.link_count.update_link_count", diff --git a/frappe/templates/includes/login/login.js b/frappe/templates/includes/login/login.js index e3e0537b14..1e59e40175 100644 --- a/frappe/templates/includes/login/login.js +++ b/frappe/templates/includes/login/login.js @@ -198,7 +198,7 @@ login.login_handlers = (function() { } //OTP verification - if(data.verification) { + if(data.verification && data.message != 'Logged In') { login.set_indicator("{{ _("Success") }}", 'green'); document.cookie = "tmp_id="+data.tmp_id; diff --git a/frappe/twofactor.py b/frappe/twofactor.py index 4ddfc9b981..ffae19a548 100644 --- a/frappe/twofactor.py +++ b/frappe/twofactor.py @@ -10,6 +10,7 @@ from frappe.utils.background_jobs import enqueue 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 @@ -114,7 +115,8 @@ def confirm_otp_token(login_manager,otp=None,tmp_id=None): if totp.verify(otp): # show qr code only once if not frappe.db.get_default(login_manager.user + '_otplogin'): - frappe.db.set_default(login_manager.user + '_otplogin', 1) + # frappe.db.set_default(login_manager.user + '_otplogin', 1) + delete_qrimage(login_manager.user) return True else: login_manager.fail('Incorrect Verification code', login_manager.user) @@ -168,8 +170,8 @@ def process_2fa_for_email(user,token,otp_secret,otp_issuer,method='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

- '''.format(get_qr_svg_code(totp_uri)) + '''.format(qrcode_as_png(user,totp_uri)) if method == 'email' or message: status = send_token_via_email(user,token,otp_secret,otp_issuer,message=message) verification_obj = {'token_delivery': status, @@ -227,10 +229,6 @@ def should_send_barcode_as_email(): return True return False -def send_barcode_as_email(user,svg_code): - pass - - def get_qr_svg_code(totp_uri): '''Get SVG code to display Qrcode for OTP.''' @@ -245,7 +243,56 @@ def get_qr_svg_code(totp_uri): stream.close() return svg +def qrcode_as_png(user,totp_uri): + '''Save temporary Qrcode to server.''' + from frappe.utils.file_manager import save_file + folder = create_barcode_folder() + png_file_name = '{}.png'.format(frappe.generate_hash(length=20)) + file_obj = save_file(png_file_name,png_file_name,'User',user,folder=folder) + frappe.db.commit() + file_url = get_url(file_obj.file_url) + file_path = os.path.join(frappe.get_site_path('public', 'files'),file_obj.file_name) + url = qrcreate(totp_uri) + with open(file_path,'w') as png_file: + url.png(png_file,scale=8, module_color=[0, 0, 0, 180], background=[0xff, 0xff, 0xcc]) + return file_url +def create_barcode_folder(): + '''Get Barcodes folder.''' + folder_name = 'Barcodes' + folder = frappe.db.exists('File',{'file_name':folder_name}) + if folder: + return folder + folder = frappe.get_doc({ + 'doctype':'File', + 'file_name':folder_name, + 'is_folder':1, + 'folder':'Home' + }) + folder.insert(ignore_permissions=True) + return folder.name +def delete_qrimage(user,check_expiry=False): + '''Delete Qrimage when user logs in.''' + user_barcodes = frappe.get_all('File',{'attached_to_doctype':'User', + 'attached_to_name':user,'folder':'Home/Barcodes'}) + for barcode in user_barcodes: + if check_expiry and not should_remove_barcode_image(barcode):continue + barcode = frappe.get_doc('File',barcode.name) + frappe.delete_doc('File',barcode.name,ignore_permissions=True) +def delete_all_barcodes_for_users(): + '''Task to delete all barcodes for user.''' + users = frappe.get_all('User',{'enabled':1}) + for user in users: + delete_qrimage(user.name,check_expiry=True) + +def should_remove_barcode_image(barcode): + '''Check if it's time to delete barcode image from server. ''' + if isinstance(barcode, basestring): + barcode = frappe.get_doc('File',barcode) + lifespan = frappe.db.get_value('System Settings', 'System Settings', 'lifespan_barcode_image') + if time_diff_in_seconds(get_datetime(),barcode.creation) > int(lifespan): + return True + return False diff --git a/requirements.txt b/requirements.txt index 01054a3870..0f6a4ef421 100644 --- a/requirements.txt +++ b/requirements.txt @@ -43,5 +43,6 @@ pypdf openpyxl pyotp pyqrcode +pypng premailer