Thanks to visit codestin.com
Credit goes to github.com

Skip to content
Open
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
193 changes: 120 additions & 73 deletions django_auth_ldap/backend.py
Original file line number Diff line number Diff line change
Expand Up @@ -247,7 +247,122 @@ def django_to_ldap_username(self, username):
return username


class _LDAPUser:
class _LDAPConnectionMixIn:
_connection = None
_connection_bound = False

def __init__(self, backend):
self.backend = backend

@property
def ldap(self):
return self.backend.ldap

@property
def settings(self):
return self.backend.settings

@property
def connection(self):
if not self._connection_bound:
self._bind()

return self._get_connection()

def _bind(self):
"""
Binds to the LDAP server with AUTH_LDAP_BIND_DN and
AUTH_LDAP_BIND_PASSWORD.
"""
self._bind_as(self.settings.BIND_DN, self.settings.BIND_PASSWORD, sticky=True)

def _bind_as(self, bind_dn, bind_password, sticky=False):
"""
Binds to the LDAP server with the given credentials. This does not trap
exceptions.

If sticky is True, then we will consider the connection to be bound for
the life of this object. If False, then the caller only wishes to test
the credentials, after which the connection will be considered unbound.
"""
self._get_connection().simple_bind_s(bind_dn, bind_password)

self._connection_bound = sticky

def _get_connection(self):
"""
Returns our cached LDAPObject, which may or may not be bound.
"""
if self._connection is None:
uri = self.settings.SERVER_URI
if callable(uri):
if func_supports_parameter(uri, "request"):
uri = uri(self._request)
else:
warnings.warn(
"Update AUTH_LDAP_SERVER_URI callable %s.%s to accept "
"a positional `request` argument. Support for callables "
"accepting no arguments will be removed in a future "
"version." % (uri.__module__, uri.__name__),
DeprecationWarning,
)
uri = uri()

self._connection = self.backend.ldap.initialize(uri, bytes_mode=False)

for opt, value in self.settings.CONNECTION_OPTIONS.items():
self._connection.set_option(opt, value)

if self.settings.START_TLS:
logger.debug("Initiating TLS")
self._connection.start_tls_s()

return self._connection


class LDAPReverseEmailSearch(_LDAPConnectionMixIn):
def __init__(self, backend, email):
super().__init__(backend)

self._email = email
self._ldap_users = []

def search_for_users(self, should_populate=False):
search = self.settings.REVERSE_EMAIL_SEARCH
if search is None:
raise ImproperlyConfigured(
"AUTH_LDAP_REVERSE_EMAIL_SEARCH must be an LDAPSearch instance."
)
USERNAME_ATTR = self.settings.USERNAME_ATTR
if USERNAME_ATTR is None:
raise ImproperlyConfigured(
"AUTH_LDAP_USERNAME_ATTR must be specified to search by email."
)

results = search.execute(self.connection, {"email": self._email})
if not results:
return None

for result in results:
user_dn, user_attrs = result
username = user_attrs[USERNAME_ATTR][0]
ldap_user = _LDAPUser(self.backend, username=username)
ldap_user._user_dn = user_dn
ldap_user._user_attrs = user_attrs
if should_populate:
ldap_user.populate_user()

self._ldap_users.append(ldap_user)

if should_populate:
# Return populated Django Users if populating was requested.
return [ldap_user._user for ldap_user in self._ldap_users]
else:
# Otherwise, just return the found _LDAPUsers.
return self._ldap_users


class _LDAPUser(_LDAPConnectionMixIn):
"""
Represents an LDAP user and ultimately fields all requests that the
backend receives. This class exists for two reasons. First, it's
Expand All @@ -269,8 +384,6 @@ class AuthenticationFailed(Exception):
_user_attrs = None
_groups = None
_group_permissions = None
_connection = None
_connection_bound = False

#
# Initialization
Expand All @@ -282,7 +395,8 @@ def __init__(self, backend, username=None, user=None, request=None):
authenticated User object. If a user is given, the username will be
ignored.
"""
self.backend = backend
super().__init__(backend)

self._username = username
self._request = request

Expand Down Expand Up @@ -328,14 +442,6 @@ def _set_authenticated_user(self, user):
user.ldap_user = self
user.ldap_username = self._username

@property
def ldap(self):
return self.backend.ldap

@property
def settings(self):
return self.backend.settings

#
# Entry points
#
Expand Down Expand Up @@ -460,13 +566,6 @@ def group_dns(self):
def group_names(self):
return self._get_groups().get_group_names()

@property
def connection(self):
if not self._connection_bound:
self._bind()

return self._get_connection()

#
# Authentication
#
Expand Down Expand Up @@ -811,60 +910,6 @@ def _get_groups(self):

return self._groups

#
# LDAP connection
#

def _bind(self):
"""
Binds to the LDAP server with AUTH_LDAP_BIND_DN and
AUTH_LDAP_BIND_PASSWORD.
"""
self._bind_as(self.settings.BIND_DN, self.settings.BIND_PASSWORD, sticky=True)

def _bind_as(self, bind_dn, bind_password, sticky=False):
"""
Binds to the LDAP server with the given credentials. This does not trap
exceptions.

If sticky is True, then we will consider the connection to be bound for
the life of this object. If False, then the caller only wishes to test
the credentials, after which the connection will be considered unbound.
"""
self._get_connection().simple_bind_s(bind_dn, bind_password)

self._connection_bound = sticky

def _get_connection(self):
"""
Returns our cached LDAPObject, which may or may not be bound.
"""
if self._connection is None:
uri = self.settings.SERVER_URI
if callable(uri):
if func_supports_parameter(uri, "request"):
uri = uri(self._request)
else:
warnings.warn(
"Update AUTH_LDAP_SERVER_URI callable %s.%s to accept "
"a positional `request` argument. Support for callables "
"accepting no arguments will be removed in a future "
"version." % (uri.__module__, uri.__name__),
DeprecationWarning,
)
uri = uri()

self._connection = self.backend.ldap.initialize(uri, bytes_mode=False)

for opt, value in self.settings.CONNECTION_OPTIONS.items():
self._connection.set_option(opt, value)

if self.settings.START_TLS:
logger.debug("Initiating TLS")
self._connection.start_tls_s()

return self._connection


class _LDAPUserGroups:
"""
Expand Down Expand Up @@ -1023,6 +1068,8 @@ class LDAPSettings:
"USER_DN_TEMPLATE": None,
"USER_FLAGS_BY_GROUP": {},
"USER_SEARCH": None,
"REVERSE_EMAIL_SEARCH": None,
"USERNAME_ATTR": None,
}

def __init__(self, prefix="AUTH_LDAP_", defaults={}):
Expand Down
18 changes: 18 additions & 0 deletions tests/tests.ldif
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ objectClass: inetOrgPerson
objectClass: posixAccount
cn: bob
uid: bob
mail: [email protected]
userPassword: password
uidNumber: 1001
gidNumber: 50
Expand All @@ -54,6 +55,7 @@ objectClass: inetOrgPerson
objectClass: posixAccount
cn: dreßler
uid: dreßler
mail: [email protected]
userPassword: password
uidNumber: 1002
gidNumber: 50
Expand All @@ -68,18 +70,34 @@ objectClass: inetOrgPerson
objectClass: posixAccount
cn: nobody
uid: nobody
mail: [email protected]
userPassword: password
uidNumber: 1003
gidNumber: 50
sn: nobody
homeDirectory: /home/nobody

dn: uid=nobody2,ou=people,o=test
objectClass: person
objectClass: organizationalPerson
objectClass: inetOrgPerson
objectClass: posixAccount
cn: nobody2
uid: nobody2
mail: [email protected]
userPassword: password
uidNumber: 1003
gidNumber: 50
sn: nobody2
homeDirectory: /home/nobody2

dn: uid=nonposix,ou=people,o=test
objectClass: person
objectClass: organizationalPerson
objectClass: inetOrgPerson
cn: nonposix
uid: nonposix
mail: [email protected]
userPassword: password
sn: nonposix

Expand Down
Loading