diff --git a/aleksis/core/settings.py b/aleksis/core/settings.py index c3705b3cacd498a849f05009ad72c610bfb2b894..142e2c83723c8fb41d2eae464fd9d01cfee8e1ae 100644 --- a/aleksis/core/settings.py +++ b/aleksis/core/settings.py @@ -234,7 +234,7 @@ if _settings.get("ldap.uri", None): ) # Enable Django's integration to LDAP - AUTHENTICATION_BACKENDS.append("django_auth_ldap.backend.LDAPBackend") + AUTHENTICATION_BACKENDS.append("aleksis.core.util.ldap.LDAPBackend") AUTH_LDAP_SERVER_URI = _settings.get("ldap.uri") @@ -243,6 +243,13 @@ if _settings.get("ldap.uri", None): AUTH_LDAP_BIND_DN = _settings.get("ldap.bind.dn") AUTH_LDAP_BIND_PASSWORD = _settings.get("ldap.bind.password") + # Keep local password for users to be required to proveide their old password on change + AUTH_LDAP_SET_USABLE_PASSWORD = True + + # Keep bound as the authenticating user + # Ensures proper read permissions, and ability to change password without admin + AUTH_LDAP_BIND_AS_AUTHENTICATING_USER = True + # The TOML config might contain either one table or an array of tables _AUTH_LDAP_USER_SETTINGS = _settings.get("ldap.users.search") if not isinstance(_AUTH_LDAP_USER_SETTINGS, list): diff --git a/aleksis/core/util/ldap.py b/aleksis/core/util/ldap.py new file mode 100644 index 0000000000000000000000000000000000000000..5a17cbbb5aea91030fab467c21897826f74eb6aa --- /dev/null +++ b/aleksis/core/util/ldap.py @@ -0,0 +1,34 @@ +"""Utilities and extensions for django_auth_ldap.""" + +from django.core.exceptions import PermissionDenied + +from django_auth_ldap.backend import LDAPBackend as _LDAPBackend + + +class LDAPBackend(_LDAPBackend): + default_settings = {"SET_USABLE_PASSWORD": False} + + def authenticate_ldap_user(self, ldap_user, password): + """Authenticate user against LDAP and set local password if successful. + + Having a local password is needed to make changing passwords easier. In + order to catch password changes in a universal way and forward them to + backends (like LDAP, in this case), getting the old password first is + necessary to authenticate as that user to LDAP. + + We buy the small insecurity of having a hash of the password in the + Django database in order to not require it to have global admin permissions + on the LDAP directory. + """ + user = ldap_user.authenticate(password) + + if not user: + # Fail early and do not try other backends + raise PermissionDenied("LDAP failed to authenticate user") + + if self.settings.SET_USABLE_PASSWORD: + # Set a usable password so users can change their LDAP password + user.set_password(password) + user.save() + + return user