From f006601d651ae3ccd41d5b21153e6c041aac962e Mon Sep 17 00:00:00 2001 From: Georg Pfuetzenreuter Date: Mon, 4 Aug 2025 14:50:00 +0200 Subject: [PATCH 1/2] Allow for association with session user In environments with multiple authentication backends it can be desirable to link an LDAP login with an existing user. Signed-off-by: Georg Pfuetzenreuter --- README.rst | 5 +++++ django_python3_ldap/conf.py | 5 +++++ django_python3_ldap/ldap.py | 24 +++++++++++++++--------- 3 files changed, 25 insertions(+), 9 deletions(-) diff --git a/README.rst b/README.rst index 9ec6b4e..7e98c86 100644 --- a/README.rst +++ b/README.rst @@ -109,6 +109,11 @@ Available settings # Set connection pool `active` parameter on the underlying `ldap3` library. LDAP_AUTH_POOL_ACTIVE = True + # Whether an LDAP login as part of an existing user session (for example due to a prior login using a different + # authentication backend) should update the existing user object and preserve the user in the session instead + # of creating a new user object. + LDAP_AUTH_ASSOCIATE_EXISTING_USER = False + Microsoft Active Directory support ---------------------------------- diff --git a/django_python3_ldap/conf.py b/django_python3_ldap/conf.py index a548c47..c851fae 100644 --- a/django_python3_ldap/conf.py +++ b/django_python3_ldap/conf.py @@ -161,5 +161,10 @@ def __init__(self, settings): default=True ) + LDAP_AUTH_ASSOCIATE_EXISTING_USER = LazySetting( + name="LDAP_AUTH_ASSOCIATE_EXISTING_USER", + default=False + ) + settings = LazySettings(settings) diff --git a/django_python3_ldap/ldap.py b/django_python3_ldap/ldap.py index 54d0bb4..cd6a07c 100644 --- a/django_python3_ldap/ldap.py +++ b/django_python3_ldap/ldap.py @@ -30,7 +30,7 @@ def __init__(self, connection): """ self._connection = connection - def _get_or_create_user(self, user_data): + def _get_or_create_user(self, user_data, request=None): """ Returns a Django user for the given LDAP user data. @@ -62,11 +62,17 @@ def _get_or_create_user(self, user_data): for field_name in settings.LDAP_AUTH_USER_LOOKUP_FIELDS } + # Update or create the user. - user, created = User.objects.update_or_create( - defaults=user_fields, - **user_lookup - ) + if settings.LDAP_AUTH_ASSOCIATE_EXISTING_USER and request.user.is_authenticated: + user = request.user + created = False + else: + user, created = User.objects.update_or_create( + defaults=user_fields, + **user_lookup + ) + # If the user was created, set them an unusable password. if created: user.set_unusable_password() @@ -108,7 +114,7 @@ def iter_users(self): if entry["type"] == "searchResEntry" )) - def get_user(self, **kwargs): + def get_user(self, request, **kwargs): """ Returns the user with the given identifier. @@ -117,7 +123,7 @@ def get_user(self, **kwargs): """ # Search the LDAP database. if self.has_user(**kwargs): - return self._get_or_create_user(self._connection.response[0]) + return self._get_or_create_user(self._connection.response[0], request) logger.warning("LDAP user lookup failed") return None @@ -242,7 +248,7 @@ def connection(**kwargs): c.unbind() -def authenticate(*args, **kwargs): +def authenticate(request, *args, **kwargs): """ Authenticates with the LDAP server, and returns the corresponding Django user instance. @@ -265,4 +271,4 @@ def authenticate(*args, **kwargs): with connection(password=password, **ldap_kwargs) as c: if c is None: return None - return c.get_user(**ldap_kwargs) + return c.get_user(request, **ldap_kwargs) From ab04e18b904b7e1dd5ed7da0b65d6ea87cb9ce1a Mon Sep 17 00:00:00 2001 From: Georg Pfuetzenreuter Date: Mon, 4 Aug 2025 15:37:45 +0200 Subject: [PATCH 2/2] Allow sync relation function to return user If existing users are associated, it can be useful to similarly compare an LDAP login with a model containing users created through different backends, and further to use a found user object instead of creating a new one. Signed-off-by: Georg Pfuetzenreuter --- README.rst | 2 ++ django_python3_ldap/ldap.py | 4 +++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/README.rst b/README.rst index 7e98c86..7a52680 100644 --- a/README.rst +++ b/README.rst @@ -176,6 +176,8 @@ The parameters are:- - ``connection`` - the LDAP connection object (optional keyword only parameter) - ``dn`` - the DN (Distinguished Name) of the LDAP matched user (optional keyword only parameter) +The function can optionally return a user object to forward to ``authenticate()`` instead of the original user. +This is useful in combination with ``LDAP_AUTH_ASSOCIATE_EXISTING_USER``. Clean User ---------- diff --git a/django_python3_ldap/ldap.py b/django_python3_ldap/ldap.py index cd6a07c..85454a2 100644 --- a/django_python3_ldap/ldap.py +++ b/django_python3_ldap/ldap.py @@ -89,7 +89,9 @@ def _get_or_create_user(self, user_data, request=None): else: raise TypeError(f"Unknown kw argument {argname} in signature for LDAP_AUTH_SYNC_USER_RELATIONS") # call sync_user_relations_func() with original args plus supported named extras - sync_user_relations_func(user, attributes, **args) + sync_user = sync_user_relations_func(user, attributes, **args) + if sync_user is not None: + user = sync_user # All done! logger.info("LDAP user lookup succeeded") return user