From 94b2d856fcfcb3a2fc27064efd821020d530bfd5 Mon Sep 17 00:00:00 2001 From: "Chinmay D. Pai" Date: Thu, 19 Mar 2020 21:44:45 +0530 Subject: [PATCH 1/6] feat(ldap): allow resetting ldap password from user settings currently, there is no way to reset password for those logging in through ldap. i understand that this shouldn't really be handled by erpnext, but there are people that have requested resetting the ldap password from with the instance itself, and hence, we'll let that happen now. Signed-off-by: Chinmay D. Pai --- frappe/core/doctype/user/user.js | 42 +++++++++++++++++++ .../doctype/ldap_settings/ldap_settings.py | 42 +++++++++++++++++-- 2 files changed, 81 insertions(+), 3 deletions(-) diff --git a/frappe/core/doctype/user/user.js b/frappe/core/doctype/user/user.js index c4873ee40e..46332e1893 100644 --- a/frappe/core/doctype/user/user.js +++ b/frappe/core/doctype/user/user.js @@ -97,6 +97,48 @@ frappe.ui.form.on('User', { }); }, __("Password")); + frappe.db.get_single_value("LDAP Settings", "enabled").then((value) => { + if (value === 1 && frm.name != "Administrator") { + frm.add_custom_button(__("Reset LDAP Password"), function(){ + const d = new frappe.ui.Dialog({ + title: __("Reset LDAP Password"), + fields: [ + { + label: __("New Password"), + fieldtype: "Password", + fieldname: "new_password", + reqd: 1 + }, + { + label: __("Confirm New Password"), + fieldtype: "Password", + fieldname: "confirm_password", + reqd: 1 + }, + { + label: __("Logout All Sessions"), + fieldtype: "Check", + fieldname: "logout_sessions" + } + ], + primary_action: (values) => { + d.hide(); + if(values.new_password !== values.confirm_password) { + frappe.throw(__("Passwords do not match!")); + } + frappe.call( + "frappe.integrations.doctype.ldap_settings.ldap_settings.reset_password", { + user: frm.doc.email, + password: values.new_password, + logout: values.logout_sessions + }).then(() => done()); + } + }); + d.show(); + }, __("Password")); + } + }); + frm.add_custom_button(__("Reset OTP Secret"), function() { frappe.call({ method: "frappe.core.doctype.user.user.reset_otp_secret", diff --git a/frappe/integrations/doctype/ldap_settings/ldap_settings.py b/frappe/integrations/doctype/ldap_settings/ldap_settings.py index c0f12df04a..63e0b8ff55 100644 --- a/frappe/integrations/doctype/ldap_settings/ldap_settings.py +++ b/frappe/integrations/doctype/ldap_settings/ldap_settings.py @@ -4,7 +4,7 @@ from __future__ import unicode_literals import frappe -from frappe import _ +from frappe import _, safe_encode from frappe.model.document import Document @@ -19,7 +19,7 @@ class LDAPSettings(Document): else: frappe.throw(_("LDAP Search String needs to end with a placeholder, eg sAMAccountName={0}")) - def connect_to_ldap(self, base_dn, password): + def connect_to_ldap(self, base_dn, password, read_only=True): try: import ldap3 import ssl @@ -44,7 +44,7 @@ class LDAPSettings(Document): user=base_dn, password=password, auto_bind=bind_type, - read_only=True, + read_only=read_only, raise_exceptions=True) return conn @@ -170,6 +170,34 @@ class LDAPSettings(Document): else: frappe.throw(_("Invalid username or password")) + def reset_password(self, user, password, logout_sessions=False): + from ldap3 import HASHED_SALTED_SHA, MODIFY_REPLACE + from ldap3.utils.hashed import hashed + + search_filter = "({0}={1})".format(self.ldap_email_field, user) + + conn = self.connect_to_ldap(self.base_dn, self.get_password(raise_exception=False), + read_only=False) + + if conn.search( + search_base=self.organizational_unit, + search_filter=search_filter, + attributes=self.get_ldap_attributes() + ): + if conn.entries and conn.entries[0]: + entry_dn = conn.entries[0].entry_dn + hashed_password = hashed(HASHED_SALTED_SHA, safe_encode(password)) + changes = {'userPassword': [(MODIFY_REPLACE, [hashed_password])]} + if conn.modify(entry_dn, changes=changes): + if logout_sessions: + from frappe.sessions import clear_sessions + clear_sessions(user=user, force=True) + frappe.msgprint(_("Password changed successfully.")) + else: + frappe.throw(_("Failed to change password.")) + else: + frappe.throw(_("LDAP User does not exist!")) + def convert_ldap_entry_to_dict(self, user_entry): # support multiple email values @@ -211,3 +239,11 @@ def login(): # because of a GET request! frappe.db.commit() + + +@frappe.whitelist() +def reset_password(user, password, logout): + ldap = frappe.get_doc("LDAP Settings") + if not ldap.enabled: + frappe.throw(_("LDAP is not enabled.")) + ldap.reset_password(user, password, logout_sessions=int(logout)) From bc77455d132704f8838ebfa50aee9ff01026661b Mon Sep 17 00:00:00 2001 From: "Chinmay D. Pai" Date: Thu, 19 Mar 2020 21:49:58 +0530 Subject: [PATCH 2/6] chore: correct indentation for ldap exceptions Signed-off-by: Chinmay D. Pai --- frappe/integrations/doctype/ldap_settings/ldap_settings.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/frappe/integrations/doctype/ldap_settings/ldap_settings.py b/frappe/integrations/doctype/ldap_settings/ldap_settings.py index 63e0b8ff55..f9dce6800c 100644 --- a/frappe/integrations/doctype/ldap_settings/ldap_settings.py +++ b/frappe/integrations/doctype/ldap_settings/ldap_settings.py @@ -196,7 +196,9 @@ class LDAPSettings(Document): else: frappe.throw(_("Failed to change password.")) else: - frappe.throw(_("LDAP User does not exist!")) + frappe.throw(_("No Entry for the User {0} found within LDAP!").format(user)) + else: + frappe.throw(_("LDAP User does not exist!")) def convert_ldap_entry_to_dict(self, user_entry): From 5fbf1f8c08249a3c6d724b9832efa5f6264af31a Mon Sep 17 00:00:00 2001 From: "Chinmay D. Pai" Date: Thu, 19 Mar 2020 21:53:39 +0530 Subject: [PATCH 3/6] chore: make the exception read slightly better Signed-off-by: Chinmay D. Pai --- frappe/integrations/doctype/ldap_settings/ldap_settings.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/integrations/doctype/ldap_settings/ldap_settings.py b/frappe/integrations/doctype/ldap_settings/ldap_settings.py index f9dce6800c..558f7117c0 100644 --- a/frappe/integrations/doctype/ldap_settings/ldap_settings.py +++ b/frappe/integrations/doctype/ldap_settings/ldap_settings.py @@ -198,7 +198,7 @@ class LDAPSettings(Document): else: frappe.throw(_("No Entry for the User {0} found within LDAP!").format(user)) else: - frappe.throw(_("LDAP User does not exist!")) + frappe.throw(_("No LDAP User found for email: {0}").format(user)) def convert_ldap_entry_to_dict(self, user_entry): From 3b8d76e5a7dc6798c67afef38d38970c489bf602 Mon Sep 17 00:00:00 2001 From: "Chinmay D. Pai" Date: Thu, 19 Mar 2020 22:02:02 +0530 Subject: [PATCH 4/6] chore: check the correct variable for docname Signed-off-by: Chinmay D. Pai --- frappe/core/doctype/user/user.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/core/doctype/user/user.js b/frappe/core/doctype/user/user.js index 46332e1893..0f474c85f2 100644 --- a/frappe/core/doctype/user/user.js +++ b/frappe/core/doctype/user/user.js @@ -98,7 +98,7 @@ frappe.ui.form.on('User', { }, __("Password")); frappe.db.get_single_value("LDAP Settings", "enabled").then((value) => { - if (value === 1 && frm.name != "Administrator") { + if (value === 1 && frm.doc.name != "Administrator") { frm.add_custom_button(__("Reset LDAP Password"), function(){ const d = new frappe.ui.Dialog({ title: __("Reset LDAP Password"), From 15801f870880e9b5a7457fb42a988db0325f7da8 Mon Sep 17 00:00:00 2001 From: "Chinmay D. Pai" Date: Thu, 19 Mar 2020 22:15:01 +0530 Subject: [PATCH 5/6] chore: codacy fixes Signed-off-by: Chinmay D. Pai --- frappe/core/doctype/user/user.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frappe/core/doctype/user/user.js b/frappe/core/doctype/user/user.js index 0f474c85f2..de4dc0c272 100644 --- a/frappe/core/doctype/user/user.js +++ b/frappe/core/doctype/user/user.js @@ -99,7 +99,7 @@ frappe.ui.form.on('User', { frappe.db.get_single_value("LDAP Settings", "enabled").then((value) => { if (value === 1 && frm.doc.name != "Administrator") { - frm.add_custom_button(__("Reset LDAP Password"), function(){ + frm.add_custom_button(__("Reset LDAP Password"), function() { const d = new frappe.ui.Dialog({ title: __("Reset LDAP Password"), fields: [ @@ -123,7 +123,7 @@ frappe.ui.form.on('User', { ], primary_action: (values) => { d.hide(); - if(values.new_password !== values.confirm_password) { + if (values.new_password !== values.confirm_password) { frappe.throw(__("Passwords do not match!")); } frappe.call( From 1f3f8b924606cfaa0db3c2b48ac9bf961c620b1c Mon Sep 17 00:00:00 2001 From: "Chinmay D. Pai" Date: Fri, 3 Apr 2020 09:57:11 +0530 Subject: [PATCH 6/6] chore: fix codacy issues Signed-off-by: Chinmay D. Pai --- frappe/core/doctype/user/user.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/core/doctype/user/user.js b/frappe/core/doctype/user/user.js index de4dc0c272..b17548d994 100644 --- a/frappe/core/doctype/user/user.js +++ b/frappe/core/doctype/user/user.js @@ -131,7 +131,7 @@ frappe.ui.form.on('User', { user: frm.doc.email, password: values.new_password, logout: values.logout_sessions - }).then(() => done()); + }); } }); d.show();