diff --git a/frappe/data/Framework.sql b/frappe/data/Framework.sql index 4e97b0bfca..071145b6f5 100644 --- a/frappe/data/Framework.sql +++ b/frappe/data/Framework.sql @@ -215,7 +215,6 @@ CREATE TABLE `__Auth` ( `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; diff --git a/frappe/patches.txt b/frappe/patches.txt index 9d355b35d2..954c233666 100644 --- a/frappe/patches.txt +++ b/frappe/patches.txt @@ -1,4 +1,4 @@ -execute:frappe.db.sql("""update `tabPatch Log` set patch=replace(patch, '.4_0.', '.v4_0.')""") #2014-05-12 + frappe.patches.v5_0.convert_to_barracuda_and_utf8mb4 execute:frappe.utils.global_search.setup_global_search_table() frappe.patches.v8_0.update_global_search_table @@ -205,4 +205,212 @@ frappe.patches.v10_0.refactor_social_login_keys frappe.patches.v10_0.enable_chat_by_default_within_system_settings frappe.patches.v10_0.remove_custom_field_for_disabled_domain execute:frappe.delete_doc("Page", "chat") + +frappe.patches.v5_0.convert_to_barracuda_and_utf8mb4 +execute:frappe.utils.global_search.setup_global_search_table() +frappe.patches.v8_0.update_global_search_table +frappe.patches.v7_0.update_auth +frappe.patches.v8_0.drop_in_dialog #2017-09-22 +frappe.patches.v7_2.remove_in_filter +execute:frappe.reload_doc('core', 'doctype', 'doctype', force=True) #2017-09-22 +execute:frappe.reload_doc('core', 'doctype', 'docfield', force=True) #2018-02-20 +execute:frappe.reload_doc('core', 'doctype', 'docperm') #2017-03-03 +execute:frappe.reload_doc('core', 'doctype', 'module_def') #2017-09-22 +execute:frappe.reload_doc('core', 'doctype', 'version') #2017-04-01 +execute:frappe.reload_doc('core', 'doctype', 'activity_log') +frappe.patches.v7_1.rename_scheduler_log_to_error_log +frappe.patches.v6_1.rename_file_data +frappe.patches.v7_0.re_route #2016-06-27 +frappe.patches.v8_0.drop_is_custom_from_docperm +frappe.patches.v8_0.update_records_in_global_search #11-05-2017 +frappe.patches.v8_0.update_published_in_global_search +execute:frappe.reload_doc('core', 'doctype', 'custom_docperm') +execute:frappe.reload_doc('core', 'doctype', 'deleted_document') +execute:frappe.reload_doc('core', 'doctype', 'domain_settings') +frappe.patches.v8_0.rename_page_role_to_has_role #2017-03-16 +frappe.patches.v7_2.setup_custom_perms #2017-01-19 +frappe.patches.v8_0.set_user_permission_for_page_and_report #2017-03-20 +execute:frappe.reload_doc('core', 'doctype', 'role') #2017-05-23 +execute:frappe.reload_doc('core', 'doctype', 'user') #2017-10-27 +execute:frappe.reload_doc('custom', 'doctype', 'custom_field') #2015-10-19 +execute:frappe.reload_doc('core', 'doctype', 'page') #2013-13-26 +execute:frappe.reload_doc('core', 'doctype', 'report') #2014-06-03 +execute:frappe.reload_doc('core', 'doctype', 'translation') #2016-03-03 +execute:frappe.reload_doc('email', 'doctype', 'email_alert') #2014-07-15 +execute:frappe.reload_doc('desk', 'doctype', 'todo') #2014-12-31-1 +execute:frappe.reload_doc('custom', 'doctype', 'property_setter') #2014-12-31-1 +execute:frappe.reload_doc('core', 'doctype', 'patch_log') #2016-10-31 +execute:frappe.reload_doctype("File") # 2015-10-19 +execute:frappe.reload_doc('core', 'doctype', 'error_snapshot') +execute:frappe.clear_cache() +frappe.patches.v7_1.rename_scheduler_log_to_error_log +frappe.patches.v7_1.sync_language_doctype +frappe.patches.v7_0.rename_bulk_email_to_email_queue +frappe.patches.v7_1.rename_chinese_language_codes + +execute:frappe.db.sql("alter table `tabSessions` modify `user` varchar(255), engine=InnoDB") +execute:frappe.db.sql("delete from `tabDocField` where parent='0'") +frappe.patches.v4_0.change_varchar_length +frappe.patches.v6_4.reduce_varchar_length +frappe.patches.v5_2.change_checks_to_not_null +frappe.patches.v6_9.int_float_not_null #2015-11-25 +frappe.patches.v5_0.v4_to_v5 + +frappe.patches.v5_0.remove_shopping_cart_app +frappe.patches.v4_0.webnotes_to_frappe +execute:frappe.permissions.reset_perms("Module Def") +execute:import frappe.installer;frappe.installer.make_site_dirs() #2014-02-19 +frappe.patches.v4_0.rename_profile_to_user +frappe.patches.v4_0.deprecate_control_panel +frappe.patches.v4_0.remove_old_parent +frappe.patches.v4_0.rename_sitemap_to_route +frappe.patches.v4_0.website_sitemap_hierarchy +frappe.patches.v4_0.remove_index_sitemap +frappe.patches.v4_0.set_website_route_idx +frappe.patches.v4_0.add_delete_permission +frappe.patches.v4_0.set_todo_checked_as_closed +frappe.patches.v4_0.private_backups +frappe.patches.v4_0.set_module_in_report +frappe.patches.v4_0.update_datetime +frappe.patches.v4_0.file_manager_hooks +execute:frappe.get_doc("User", "Guest").save() +frappe.patches.v4_0.update_custom_field_insert_after +frappe.patches.v4_0.deprecate_link_selects +frappe.patches.v4_0.set_user_gravatar +frappe.patches.v4_0.set_user_permissions +frappe.patches.v4_0.create_custom_field_for_owner_match +frappe.patches.v4_0.enable_scheduler_in_system_settings +execute:frappe.db.sql("update tabReport set apply_user_permissions=1") #2014-06-03 +frappe.patches.v4_0.replace_deprecated_timezones +execute:import frappe.website.render; frappe.website.render.clear_cache("login"); #2014-06-10 +frappe.patches.v4_0.fix_attach_field_file_url +execute:frappe.permissions.reset_perms("User") #2015-03-24 +execute:frappe.db.sql("""delete from `tabUserRole` where ifnull(parentfield, '')='' or ifnull(`role`, '')=''""") #2014-08-18 +frappe.patches.v4_0.remove_user_owner_custom_field +execute:frappe.delete_doc("DocType", "Website Template") +execute:frappe.db.sql("""update `tabProperty Setter` set property_type='Text' where property in ('options', 'default')""") #2014-06-20 +frappe.patches.v4_1.enable_outgoing_email_settings +execute:frappe.db.sql("""update `tabSingles` set `value`=`doctype` where `field`='name'""") #2014-07-04 +frappe.patches.v4_1.enable_print_as_pdf #2014-06-17 +execute:frappe.db.sql("""update `tabDocPerm` set email=1 where parent='User' and permlevel=0 and `role`='All' and `read`=1 and apply_user_permissions=1""") #2014-07-15 +execute:frappe.db.sql("""update `tabPrint Format` set print_format_type='Client' where ifnull(print_format_type, '')=''""") #2014-07-28 +frappe.patches.v4_1.file_manager_fix +frappe.patches.v4_2.print_with_letterhead +execute:frappe.delete_doc("DocType", "Control Panel", force=1) +execute:frappe.reload_doc('website', 'doctype', 'web_form') #2014-09-04 +execute:frappe.reload_doc('website', 'doctype', 'web_form_field') #2014-09-04 +frappe.patches.v4_2.refactor_website_routing +frappe.patches.v4_2.set_assign_in_doc +frappe.patches.v4_3.remove_allow_on_submit_customization +frappe.patches.v5_0.rename_table_fieldnames +frappe.patches.v5_0.communication_parent +frappe.patches.v5_0.clear_website_group_and_notifications +execute:frappe.db.sql("""update tabComment set comment = substr(comment, 6, locate(":", comment)-6) where comment_type in ("Assigned", "Assignment Completed")""") +execute:frappe.db.sql("update `tabComment` set comment_type='Comment' where comment_doctype='Blog Post' and ifnull(comment_type, '')=''") +frappe.patches.v5_0.update_shared +execute:frappe.reload_doc("core", "doctype", "docshare") #2015-07-21 +frappe.patches.v6_19.comment_feed_communication +frappe.patches.v6_16.star_to_like +frappe.patches.v5_0.bookmarks_to_stars +frappe.patches.v5_0.style_settings_to_website_theme +frappe.patches.v5_0.rename_ref_type_fieldnames +frappe.patches.v5_0.fix_email_alert +frappe.patches.v5_0.fix_null_date_datetime +frappe.patches.v5_0.force_sync_website +execute:frappe.delete_doc("DocType", "Tag") +execute:frappe.db.sql("delete from `tabProperty Setter` where `property` in ('idx', '_idx')") +frappe.patches.v5_0.move_scheduler_last_event_to_system_settings +execute:frappe.db.sql("update tabUser set new_password='' where ifnull(new_password, '')!=''") +frappe.patches.v5_0.fix_text_editor_file_urls +frappe.patches.v5_0.modify_session +frappe.patches.v5_0.expire_old_scheduler_logs +execute:frappe.permissions.reset_perms("DocType") +execute:frappe.db.sql("delete from `tabProperty Setter` where `property` = 'idx'") +frappe.patches.v6_0.communication_status_and_permission +frappe.patches.v6_0.make_task_log_folder +frappe.patches.v6_0.document_type_rename +frappe.patches.v6_0.fix_ghana_currency +frappe.patches.v6_2.ignore_user_permissions_if_missing +execute:frappe.db.sql("delete from tabSessions where user is null") +frappe.patches.v6_2.rename_backup_manager +execute:frappe.delete_doc("DocType", "Backup Manager") +execute:frappe.db.sql("""update `tabCommunication` set parenttype=null, parent=null, parentfield=null""") #2015-10-22 +execute:frappe.permissions.reset_perms("Web Page") +frappe.patches.v6_6.user_last_active +frappe.patches.v6_6.fix_file_url +frappe.patches.v6_11.rename_field_in_email_account +frappe.patches.v7_0.create_private_file_folder +frappe.patches.v6_15.remove_property_setter_for_previous_field #2015-12-29 +frappe.patches.v6_15.set_username +execute:frappe.permissions.reset_perms("Error Snapshot") +frappe.patches.v6_16.feed_doc_owner +frappe.patches.v6_21.print_settings_repeat_header_footer +frappe.patches.v6_24.set_language_as_code +frappe.patches.v6_20x.update_insert_after +finally:frappe.patches.v6_24.sync_desktop_icons +frappe.patches.v6_20x.set_allow_draft_for_print +frappe.patches.v6_20x.remove_roles_from_website_user +frappe.patches.v7_0.set_user_fullname +frappe.patches.v7_0.desktop_icons_hidden_by_admin_as_blocked +frappe.patches.v7_0.add_communication_in_doc +frappe.patches.v7_0.update_send_after_in_bulk_email +execute:frappe.db.sql('''delete from `tabSingles` where doctype="Email Settings"''') # 2016-06-13 +execute:frappe.db.sql("delete from `tabWeb Page` where ifnull(template_path, '')!=''") +frappe.patches.v7_0.rename_newsletter_list_to_email_group +frappe.patches.v7_0.replace_upgrade_link_limit +frappe.patches.v7_0.set_email_group +frappe.patches.v7_1.setup_integration_services #2016-10-27 +frappe.patches.v7_1.rename_chinese_language_codes +execute:frappe.core.doctype.language.language.update_language_names() # 2017-04-12 +execute:frappe.db.set_value("Print Settings", "Print Settings", "add_draft_heading", 1) +frappe.patches.v7_0.cleanup_list_settings +execute:frappe.db.set_default('language', '') +frappe.patches.v7_1.refactor_integration_broker +frappe.patches.v7_1.set_backup_limit +frappe.patches.v7_2.set_doctype_engine +frappe.patches.v7_2.merge_knowledge_base +frappe.patches.v7_0.update_report_builder_json +frappe.patches.v7_2.set_in_standard_filter_property #1 +frappe.patches.v8_0.drop_unwanted_indexes +execute:frappe.db.sql("update tabCommunication set communication_date = creation where time(communication_date) = 0") +frappe.patches.v7_2.fix_email_queue_recipient +frappe.patches.v7_2.update_feedback_request # 2017-02-27 +execute:frappe.rename_doc('Country', 'Macedonia, Republic of', 'Macedonia', ignore_if_exists=True) +execute:frappe.rename_doc('Country', 'Iran, Islamic Republic of', 'Iran', ignore_if_exists=True) +execute:frappe.rename_doc('Country', 'Tanzania, United Republic of', 'Tanzania', ignore_if_exists=True) +execute:frappe.rename_doc('Country', 'Syrian Arab Republic', 'Syria', ignore_if_exists=True) +frappe.patches.v8_0.rename_listsettings_to_usersettings +frappe.patches.v7_2.update_communications +frappe.patches.v8_0.deprecate_integration_broker +frappe.patches.v8_0.update_gender_and_salutation +frappe.patches.v8_0.setup_email_inbox #2017-03-29 +frappe.patches.v8_0.newsletter_childtable_migrate +execute:frappe.db.sql("delete from `tabDesktop Icon` where module_name='Communication'") +execute:frappe.db.sql("update `tabDesktop Icon` set type='list' where _doctype='Communication'") +frappe.patches.v8_0.fix_non_english_desktop_icons # 2017-04-12 +frappe.patches.v8_0.set_doctype_values_in_custom_role +frappe.patches.v8_0.install_new_build_system_requirements +frappe.patches.v8_0.set_currency_field_precision # 2017-05-09 +frappe.patches.v8_0.rename_print_to_printing +frappe.patches.v7_1.disabled_print_settings_for_custom_print_format +frappe.patches.v8_0.update_desktop_icons +execute:frappe.db.sql('update tabReport set module="Desk" where name="ToDo"') +frappe.patches.v8_1.enable_allow_error_traceback_in_system_settings +frappe.patches.v8_1.update_format_options_in_auto_email_report +frappe.patches.v8_1.delete_custom_docperm_if_doctype_not_exists +frappe.patches.v8_5.delete_email_group_member_with_invalid_emails +frappe.patches.v8_x.update_user_permission +frappe.patches.v8_5.patch_event_colors +frappe.patches.v8_10.delete_static_web_page_from_global_search +frappe.patches.v9_1.add_sms_sender_name_as_parameters +frappe.patches.v9_1.resave_domain_settings +frappe.patches.v9_1.revert_domain_settings +frappe.patches.v9_1.move_feed_to_activity_log +execute:frappe.delete_doc('Page', 'data-import-tool', ignore_missing=True) +frappe.patches.v10_0.reload_countries_and_currencies +frappe.patches.v10_0.refactor_social_login_keys +frappe.patches.v10_0.enable_chat_by_default_within_system_settings +frappe.patches.v10_0.remove_custom_field_for_disabled_domain +execute:frappe.delete_doc("Page", "chat") +frappe.patches.v10_0.migrate_passwords_passlib frappe.patches.v11_0.drop_column_apply_user_permissions diff --git a/frappe/patches/v10_0/migrate_passwords_passlib.py b/frappe/patches/v10_0/migrate_passwords_passlib.py new file mode 100644 index 0000000000..ada50e46f6 --- /dev/null +++ b/frappe/patches/v10_0/migrate_passwords_passlib.py @@ -0,0 +1,21 @@ +import frappe +from frappe.utils.password import LegacyPassword + + +def execute(): + all_auths = frappe.db.sql("""SELECT `name`, `password`, `salt` FROM `__Auth` + WHERE doctype='User' AND `fieldname`='password'""", + as_dict=True + ) + + for auth in all_auths: + if auth.salt and auth.salt != "": + pwd = LegacyPassword.hash(auth.password, salt=auth.salt.encode('UTF-8')) + frappe.db.sql("""UPDATE `__Auth` SET `password`=%(pwd)s, `salt`=NULL + WHERE `doctype`='User' AND `fieldname`='password' AND `name`=%(user)s""", + {'pwd': pwd, 'user': auth.name} + ) + + frappe.reload_doctype("User") + + frappe.db.sql_ddl("""ALTER TABLE `__Auth` DROP COLUMN `salt`""") diff --git a/frappe/tests/test_password.py b/frappe/tests/test_password.py index 0bf9bbfb37..8b429e4270 100644 --- a/frappe/tests/test_password.py +++ b/frappe/tests/test_password.py @@ -3,7 +3,7 @@ from __future__ import unicode_literals import frappe import unittest -from frappe.utils.password import update_password, check_password +from frappe.utils.password import update_password, check_password, passlibctx class TestPassword(unittest.TestCase): def setUp(self): @@ -52,14 +52,14 @@ class TestPassword(unittest.TestCase): update_password(user, new_password) - auth = frappe.db.sql('''select `password`, `salt` from `__Auth` + auth = frappe.db.sql('''select `password` from `__Auth` where doctype='User' and name=%s and fieldname="password"''', user, as_dict=True)[0] + # is not plain text self.assertTrue(auth.password != new_password) - self.assertTrue(auth.salt) - # stored password = password(plain_text_password + salt) - self.assertEqual(frappe.db.sql('select password(concat(%s, %s))', (new_password, auth.salt))[0][0], auth.password) + # is valid hashing + self.assertTrue(passlibctx.verify(new_password, auth.password)) self.assertTrue(check_password(user, new_password)) diff --git a/frappe/utils/password.py b/frappe/utils/password.py index 182b1fb011..d7dbea06b3 100644 --- a/frappe/utils/password.py +++ b/frappe/utils/password.py @@ -2,10 +2,41 @@ # MIT License. See license.txt from __future__ import unicode_literals +import string import frappe from frappe import _ from frappe.utils import cstr, encode from cryptography.fernet import Fernet, InvalidToken +from passlib.hash import pbkdf2_sha256, mysql41 +from passlib.registry import register_crypt_handler +from passlib.context import CryptContext + + +class LegacyPassword(pbkdf2_sha256): + name = "frappe_legacy" + ident = "$frappel$" + + def _calc_checksum(self, secret): + # check if this is a mysql hash + # it is possible that we will generate a false positive if the users password happens to be 40 hex chars proceeded + # by an * char, but this seems highly unlikely + if not (secret[0] == "*" and len(secret) == 41 and all(c in string.hexdigits for c in secret[1:])): + secret = mysql41.hash(secret + self.salt) + return super(LegacyPassword, self)._calc_checksum(secret) + + +register_crypt_handler(LegacyPassword, force=True) +passlibctx = CryptContext( + schemes=[ + "pbkdf2_sha256", + "argon2", + "frappe_legacy", + ], + deprecated=[ + "frappe_legacy", + ], +) + def get_decrypted_password(doctype, name, fieldname='password', raise_exception=True): auth = frappe.db.sql('''select `password` from `__Auth` @@ -24,27 +55,24 @@ def set_encrypted_password(doctype, name, pwd, fieldname='password'): 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'): +def check_password(auth, 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) + auth = frappe.db.sql("""select name, `password` from `__Auth` + where doctype=%(doctype)s and name=%(name)s and fieldname=%(fieldname)s and encrypted=0""", + {'doctype': doctype, 'name': auth, 'fieldname': fieldname}, + 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) + if not auth or not passlibctx.verify(pwd, auth[0].password): + raise frappe.AuthenticationError(_('Incorrect User or Password')) # lettercase agnostic user = auth[0].name + if not passlibctx.needs_update(auth[0].password): + update_password(user, pwd, doctype, fieldname) + return user def update_password(user, pwd, doctype='User', fieldname='password', logout_all_sessions=False): @@ -57,12 +85,12 @@ def update_password(user, pwd, doctype='User', fieldname='password', logout_all_ :param fieldname: fieldname (in given doctype) (for encryption) :param logout_all_session: delete all other session ''' - 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) + hashPwd = passlibctx.hash(pwd) + frappe.db.sql("""insert into __Auth (doctype, name, fieldname, `password`, encrypted) + values (%(doctype)s, %(name)s, %(fieldname)s, %(pwd)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 }) + `password`=%(pwd)s, encrypted=0""", + {'doctype': doctype, 'name': user, 'fieldname': fieldname, 'pwd': hashPwd}) # clear all the sessions except current if logout_all_sessions: @@ -95,7 +123,6 @@ def create_auth_table(): `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""") diff --git a/requirements.txt b/requirements.txt index 884acfd59d..ef9203659d 100644 --- a/requirements.txt +++ b/requirements.txt @@ -48,6 +48,7 @@ googlemaps mycli braintree future +passlib google-api-python-client google-auth google-auth-httplib2