Skip to content

Allow to reload DN when using user-bind and DN-template #347

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Aug 8, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 29 additions & 20 deletions django_auth_ldap/backend.py
Original file line number Diff line number Diff line change
Expand Up @@ -484,6 +484,12 @@ def _authenticate_user_dn(self, password):
self._bind_as(self.dn, password, sticky=sticky)
except ldap.INVALID_CREDENTIALS:
raise self.AuthenticationFailed("user DN/password rejected by LDAP server.")
if (
self._using_simple_bind_mode()
and sticky
and self.settings.REFRESH_DN_ON_BIND
):
self._user_dn = self._search_for_user_dn()

def _load_user_attrs(self):
if self.dn is not None:
Expand All @@ -507,15 +513,7 @@ def _load_user_dn(self):
if self._using_simple_bind_mode():
self._user_dn = self._construct_simple_user_dn()
else:
if self.settings.CACHE_TIMEOUT > 0:
cache_key = valid_cache_key(
"django_auth_ldap.user_dn.{}".format(self._username)
)
self._user_dn = cache.get_or_set(
cache_key, self._search_for_user_dn, self.settings.CACHE_TIMEOUT
)
else:
self._user_dn = self._search_for_user_dn()
self._user_dn = self._search_for_user_dn()

def _using_simple_bind_mode(self):
return self.settings.USER_DN_TEMPLATE is not None
Expand All @@ -530,19 +528,30 @@ def _search_for_user_dn(self):
Searches the directory for a user matching AUTH_LDAP_USER_SEARCH.
Populates self._user_dn and self._user_attrs.
"""
search = self.settings.USER_SEARCH
if search is None:
raise ImproperlyConfigured(
"AUTH_LDAP_USER_SEARCH must be an LDAPSearch instance."
)

results = search.execute(self.connection, {"user": self._username})
if results is not None and len(results) == 1:
(user_dn, self._user_attrs) = next(iter(results))
else:
user_dn = None
def _search_for_user():
search = self.settings.USER_SEARCH
if search is None:
raise ImproperlyConfigured(
"AUTH_LDAP_USER_SEARCH must be an LDAPSearch instance."
)

results = search.execute(self.connection, {"user": self._username})
if results is not None and len(results) == 1:
(user_dn, self._user_attrs) = next(iter(results))
else:
user_dn = None

return user_dn

return user_dn
if self.settings.CACHE_TIMEOUT > 0:
cache_key = valid_cache_key(
"django_auth_ldap.user_dn.{}".format(self._username)
)
return cache.get_or_set(
cache_key, _search_for_user, self.settings.CACHE_TIMEOUT
)
return _search_for_user()

def _check_requirements(self):
"""
Expand Down
1 change: 1 addition & 0 deletions django_auth_ldap/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ class LDAPSettings:
"ALWAYS_UPDATE_USER": True,
"AUTHORIZE_ALL_USERS": False,
"BIND_AS_AUTHENTICATING_USER": False,
"REFRESH_DN_ON_BIND": False,
"BIND_DN": "",
"BIND_PASSWORD": "",
"CONNECTION_OPTIONS": {},
Expand Down
16 changes: 16 additions & 0 deletions docs/reference.rst
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,22 @@ requests where the user is authenticated. Thus, the downside to this setting is
that LDAP results may vary based on whether the user was authenticated earlier
in the Django view, which could be surprising to code not directly concerned
with authentication.
Remember to set :setting:`AUTH_LDAP_USER_DN_TEMPLATE` to avoid initial connection
to LDAP with default bind credentials.


.. setting:: AUTH_LDAP_REFRESH_DN_ON_BIND

AUTH_LDAP_REFRESH_DN_ON_BIND
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Default: ``False``

If ``True`` and :setting:`AUTH_LDAP_BIND_AS_AUTHENTICATING_USER` is ``True`` and
:setting:`AUTH_LDAP_USER_DN_TEMPLATE` is set, after performing bind login it refresh
the DN attribute of the user. This is meant for such cases in which users authenticates
via `userPrincipalName` and in which `distinguishedName` is not inferrable by that
attribute.


.. setting:: AUTH_LDAP_BIND_DN
Expand Down
18 changes: 18 additions & 0 deletions tests/tests.ldif
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,24 @@ gidNumber: 50
sn: nobody
homeDirectory: /home/nobody

dn: cn=charlie_cooper,ou=people,o=test
userPrincipalName: charlie@people.test
objectClass: person
objectClass: user
objectClass: organizationalPerson
objectClass: posixAccount
cn: charlie_cooper
uid: charlie
userPassword: password
uidNumber: 1004
gidNumber: 50
givenName: Charlie
sn: Cooper
instanceType: test
nTSecurityDescriptor: test
objectCategory: test
homeDirectory: /home/bob

dn: uid=nonposix,ou=people,o=test
objectClass: person
objectClass: organizationalPerson
Expand Down
28 changes: 28 additions & 0 deletions tests/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,7 @@ def setUpClass(cls):
"cosine.ldif",
"inetorgperson.ldif",
"nis.ldif",
"msuser.ldif",
]
cls.server.start()
with open(os.path.join(here, "tests.ldif")) as fp:
Expand Down Expand Up @@ -653,6 +654,33 @@ def test_bind_as_user(self):
self.assertEqual(user.first_name, "Alice")
self.assertEqual(user.last_name, "Adams")

def test_bind_as_user_with_dn_refetch(self):
self._init_settings(
USER_DN_TEMPLATE="%(user)s@people.test",
USER_SEARCH=LDAPSearch(
"ou=people,o=test", ldap.SCOPE_SUBTREE, "(uid=%(user)s)"
),
USER_ATTR_MAP={"first_name": "givenName", "last_name": "sn"},
BIND_AS_AUTHENTICATING_USER=True,
REFRESH_DN_ON_BIND=True,
)

# need override to mimic Microsoft AD bind
# since openldap does not accepts UPN for login
def _bind_as(_self, bind_dn, bind_password, sticky=False):
_self._get_connection().simple_bind_s(
"cn=charlie_cooper,ou=people,o=test", bind_password
)
_self._connection_bound = sticky

with mock.patch("django_auth_ldap.backend._LDAPUser._bind_as", _bind_as):
user = authenticate(username="charlie", password="password")

self.assertEqual(user.username, "charlie")
self.assertEqual(user.first_name, "Charlie")
self.assertEqual(user.last_name, "Cooper")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
self.assertEqual(user.last_name, "Cooper")
self.assertEqual(user.last_name, "Cooper")
self.assertEqual(user.ldap_user.dn, "cn=charlie_cooper,ou=people,o=test")

self.assertEqual(user.ldap_user.dn, "cn=charlie_cooper,ou=people,o=test")

def test_signal_populate_user(self):
self._init_settings(USER_DN_TEMPLATE="uid=%(user)s,ou=people,o=test")
with catch_signal(populate_user) as handler:
Expand Down
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy