# 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 frappe.utils import cstr, encode from cryptography.fernet import Fernet def get_decrypted_password(doctype, name, fieldname='password', raise_exception=True): auth = frappe.db.sql('''select `password` from `__Auth` where doctype=%(doctype)s and name=%(name)s and fieldname=%(fieldname)s and encrypted=1''', { 'doctype': doctype, 'name': name, 'fieldname': fieldname }) if auth and auth[0][0]: return decrypt(auth[0][0]) elif raise_exception: frappe.throw(_('Password not found'), frappe.AuthenticationError) def set_encrypted_password(doctype, name, pwd, fieldname='password'): frappe.db.sql("""insert into __Auth (doctype, name, fieldname, `password`, encrypted) values (%(doctype)s, %(name)s, %(fieldname)s, %(pwd)s, 1) on duplicate key update `password`=%(pwd)s, encrypted=1""", { 'doctype': doctype, 'name': name, 'fieldname': fieldname, 'pwd': encrypt(pwd) }) def check_password(user, pwd, doctype='User', fieldname='password'): '''Checks if user and password are correct, else raises frappe.AuthenticationError''' auth = frappe.db.sql("""select name, `password`, salt from `__Auth` where doctype=%(doctype)s and name=%(name)s and fieldname=%(fieldname)s and encrypted=0 and ( (salt is null and `password`=password(%(pwd)s)) or `password`=password(concat(%(pwd)s, salt)) )""",{ 'doctype': doctype, 'name': user, 'fieldname': fieldname, 'pwd': pwd }, as_dict=True) if not auth: raise frappe.AuthenticationError('Incorrect User or Password') salt = auth[0].salt if not salt: # sets salt and updates password update_password(user, pwd, doctype, fieldname) # lettercase agnostic user = auth[0].name return user def update_password(user, pwd, doctype='User', fieldname='password'): salt = frappe.generate_hash() frappe.db.sql("""insert into __Auth (doctype, name, fieldname, `password`, salt, encrypted) values (%(doctype)s, %(name)s, %(fieldname)s, password(concat(%(pwd)s, %(salt)s)), %(salt)s, 0) on duplicate key update `password`=password(concat(%(pwd)s, %(salt)s)), salt=%(salt)s, encrypted=0""", { 'doctype': doctype, 'name': user, 'fieldname': fieldname, 'pwd': pwd, 'salt': salt }) def delete_all_passwords_for(doctype, name): try: frappe.db.sql("""delete from __Auth where doctype=%(doctype)s and name=%(name)s""", { 'doctype': doctype, 'name': name }) except Exception, e: if e.args[0]!=1054: raise def rename_password(doctype, old_name, new_name): # NOTE: fieldname is not considered, since the document is renamed frappe.db.sql("""update __Auth set name=%(new_name)s where doctype=%(doctype)s and name=%(old_name)s""", { 'doctype': doctype, 'new_name': new_name, 'old_name': old_name }) def rename_password_field(doctype, old_fieldname, new_fieldname): frappe.db.sql('''update `__Auth` set fieldname=%(new_fieldname)s where doctype=%(doctype)s and fieldname=%(old_fieldname)s''', { 'doctype': doctype, 'old_fieldname': old_fieldname, 'new_fieldname': new_fieldname }) def create_auth_table(): # same as Framework.sql frappe.db.sql_ddl("""create table if not exists __Auth ( `doctype` VARCHAR(140) NOT NULL, `name` VARCHAR(255) NOT NULL, `fieldname` VARCHAR(140) NOT NULL, `password` VARCHAR(255) NOT NULL, `salt` VARCHAR(140), `encrypted` INT(1) NOT NULL DEFAULT 0, PRIMARY KEY (`doctype`, `name`, `fieldname`) ) ENGINE=InnoDB ROW_FORMAT=COMPRESSED CHARACTER SET=utf8mb4 COLLATE=utf8mb4_unicode_ci""") def encrypt(pwd): if len(pwd) > 100: # encrypting > 100 chars will lead to truncation frappe.throw(_('Password cannot be more than 100 characters long')) cipher_suite = Fernet(encode(get_encryption_key())) cipher_text = cstr(cipher_suite.encrypt(encode(pwd))) return cipher_text def decrypt(pwd): cipher_suite = Fernet(encode(get_encryption_key())) plain_text = cstr(cipher_suite.decrypt(encode(pwd))) return plain_text def get_encryption_key(): from frappe.installer import update_site_config if 'encryption_key' not in frappe.local.conf: encryption_key = Fernet.generate_key() update_site_config('encryption_key', encryption_key) frappe.local.conf.encryption_key = encryption_key return frappe.local.conf.encryption_key