From 94b2d856fcfcb3a2fc27064efd821020d530bfd5 Mon Sep 17 00:00:00 2001 From: "Chinmay D. Pai" Date: Thu, 19 Mar 2020 21:44:45 +0530 Subject: [PATCH] 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))