feature: initial implementation of LDAP/AD sync
This commit is contained in:
parent
5a67c042e2
commit
b783ff6689
3 changed files with 267 additions and 212 deletions
|
|
@ -21,28 +21,22 @@ RhodeCode authentication plugin for LDAP
|
|||
"""
|
||||
|
||||
import logging
|
||||
import traceback
|
||||
|
||||
import colander
|
||||
|
||||
from rhodecode.authentication.plugins.services.ldap_dao import LdapDao
|
||||
from rhodecode.translation import _
|
||||
from rhodecode.authentication.base import RhodeCodeExternalAuthPlugin, AuthLdapBase, hybrid_property
|
||||
from rhodecode.authentication.base import RhodeCodeExternalAuthPlugin, hybrid_property
|
||||
from rhodecode.authentication.schema import AuthnPluginSettingsSchemaBase, TwoFactorAuthnPluginSettingsSchemaMixin
|
||||
from rhodecode.authentication.routes import AuthnPluginResourceBase
|
||||
from rhodecode.lib.colander_utils import strip_whitespace
|
||||
from rhodecode.lib.exceptions import LdapConnectionError, LdapUsernameError, LdapPasswordError, LdapImportError
|
||||
from rhodecode.lib.exceptions import LdapUsernameError, LdapPasswordError, LdapImportError
|
||||
from rhodecode.lib.str_utils import safe_str
|
||||
from rhodecode.model.db import User
|
||||
from rhodecode.model.validators import Missing
|
||||
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
try:
|
||||
import ldap
|
||||
except ImportError:
|
||||
# means that python-ldap is not installed, we use Missing object to mark
|
||||
# ldap lib is Missing
|
||||
ldap = Missing
|
||||
|
||||
|
||||
class LdapError(Exception):
|
||||
pass
|
||||
|
|
@ -61,188 +55,6 @@ class LdapAuthnResource(AuthnPluginResourceBase):
|
|||
pass
|
||||
|
||||
|
||||
class AuthLdap(AuthLdapBase):
|
||||
default_tls_cert_dir = "/etc/openldap/cacerts"
|
||||
|
||||
scope_labels = {
|
||||
ldap.SCOPE_BASE: "SCOPE_BASE",
|
||||
ldap.SCOPE_ONELEVEL: "SCOPE_ONELEVEL",
|
||||
ldap.SCOPE_SUBTREE: "SCOPE_SUBTREE",
|
||||
}
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
server,
|
||||
base_dn,
|
||||
port=389,
|
||||
bind_dn="",
|
||||
bind_pass="",
|
||||
tls_kind="PLAIN",
|
||||
tls_reqcert="DEMAND",
|
||||
tls_cert_file=None,
|
||||
tls_cert_dir=None,
|
||||
ldap_version=3,
|
||||
search_scope="SUBTREE",
|
||||
attr_login="uid",
|
||||
ldap_filter="",
|
||||
timeout=None,
|
||||
):
|
||||
if ldap == Missing:
|
||||
raise LdapImportError("Missing or incompatible ldap library")
|
||||
|
||||
self.debug = False
|
||||
self.timeout = timeout or 60 * 5
|
||||
self.ldap_version = ldap_version
|
||||
self.ldap_server_type = "ldap"
|
||||
|
||||
self.TLS_KIND = tls_kind
|
||||
|
||||
if self.TLS_KIND == "LDAPS":
|
||||
port = port or 636
|
||||
self.ldap_server_type += "s"
|
||||
|
||||
OPT_X_TLS_DEMAND = 2
|
||||
self.TLS_REQCERT = getattr(ldap, "OPT_X_TLS_%s" % tls_reqcert, OPT_X_TLS_DEMAND)
|
||||
self.TLS_CERT_FILE = tls_cert_file or ""
|
||||
self.TLS_CERT_DIR = tls_cert_dir or self.default_tls_cert_dir
|
||||
|
||||
# split server into list
|
||||
self.SERVER_ADDRESSES = self._get_server_list(server)
|
||||
self.LDAP_SERVER_PORT = port
|
||||
|
||||
# USE FOR READ ONLY BIND TO LDAP SERVER
|
||||
self.attr_login = attr_login
|
||||
|
||||
self.LDAP_BIND_DN = safe_str(bind_dn)
|
||||
self.LDAP_BIND_PASS = safe_str(bind_pass)
|
||||
|
||||
self.SEARCH_SCOPE = getattr(ldap, "SCOPE_%s" % search_scope)
|
||||
self.BASE_DN = safe_str(base_dn)
|
||||
self.LDAP_FILTER = safe_str(ldap_filter)
|
||||
|
||||
def _get_ldap_conn(self):
|
||||
if self.debug:
|
||||
ldap.set_option(ldap.OPT_DEBUG_LEVEL, 255)
|
||||
|
||||
if self.TLS_CERT_FILE and hasattr(ldap, "OPT_X_TLS_CACERTFILE"):
|
||||
ldap.set_option(ldap.OPT_X_TLS_CACERTFILE, self.TLS_CERT_FILE)
|
||||
|
||||
elif hasattr(ldap, "OPT_X_TLS_CACERTDIR"):
|
||||
ldap.set_option(ldap.OPT_X_TLS_CACERTDIR, self.TLS_CERT_DIR)
|
||||
|
||||
if self.TLS_KIND != "PLAIN":
|
||||
ldap.set_option(ldap.OPT_X_TLS_REQUIRE_CERT, self.TLS_REQCERT)
|
||||
|
||||
ldap.set_option(ldap.OPT_REFERRALS, ldap.OPT_OFF)
|
||||
ldap.set_option(ldap.OPT_RESTART, ldap.OPT_ON)
|
||||
|
||||
# init connection now
|
||||
ldap_servers = self._build_servers(self.ldap_server_type, self.SERVER_ADDRESSES, self.LDAP_SERVER_PORT)
|
||||
log.debug("initializing LDAP connection to:%s", ldap_servers)
|
||||
ldap_conn = ldap.initialize(ldap_servers)
|
||||
ldap_conn.set_option(ldap.OPT_NETWORK_TIMEOUT, self.timeout)
|
||||
ldap_conn.set_option(ldap.OPT_TIMEOUT, self.timeout)
|
||||
ldap_conn.timeout = self.timeout
|
||||
|
||||
if self.ldap_version == 2:
|
||||
ldap_conn.protocol = ldap.VERSION2
|
||||
else:
|
||||
ldap_conn.protocol = ldap.VERSION3
|
||||
|
||||
if self.TLS_KIND == "START_TLS":
|
||||
ldap_conn.start_tls_s()
|
||||
|
||||
if self.LDAP_BIND_DN and self.LDAP_BIND_PASS:
|
||||
log.debug("Trying simple_bind with password and given login DN: %r", self.LDAP_BIND_DN)
|
||||
ldap_conn.simple_bind_s(self.LDAP_BIND_DN, self.LDAP_BIND_PASS)
|
||||
log.debug("simple_bind successful")
|
||||
return ldap_conn
|
||||
|
||||
def fetch_attrs_from_simple_bind(self, ldap_conn, dn, username, password):
|
||||
scope = ldap.SCOPE_BASE
|
||||
scope_label = self.scope_labels.get(scope)
|
||||
ldap_filter = "(objectClass=*)"
|
||||
|
||||
try:
|
||||
log.debug(
|
||||
"Trying authenticated search bind with dn: %r SCOPE: %s (and filter: %s)", dn, scope_label, ldap_filter
|
||||
)
|
||||
ldap_conn.simple_bind_s(dn, safe_str(password))
|
||||
response = ldap_conn.search_ext_s(dn, scope, ldap_filter, attrlist=["*", "+"])
|
||||
|
||||
if not response:
|
||||
log.error("search bind returned empty results: %r", response)
|
||||
return {}
|
||||
else:
|
||||
_dn, attrs = response[0]
|
||||
return attrs
|
||||
|
||||
except ldap.INVALID_CREDENTIALS:
|
||||
log.debug("LDAP rejected password for user '%s': %s, org_exc:", username, dn, exc_info=True)
|
||||
|
||||
def authenticate_ldap(self, username, password):
|
||||
"""
|
||||
Authenticate a user via LDAP and return his/her LDAP properties.
|
||||
|
||||
Raises AuthenticationError if the credentials are rejected, or
|
||||
EnvironmentError if the LDAP server can't be reached.
|
||||
|
||||
:param username: username
|
||||
:param password: password
|
||||
"""
|
||||
|
||||
uid = self.get_uid(username, self.SERVER_ADDRESSES)
|
||||
user_attrs = {}
|
||||
dn = ""
|
||||
|
||||
self.validate_password(username, password)
|
||||
self.validate_username(username)
|
||||
scope_label = self.scope_labels.get(self.SEARCH_SCOPE)
|
||||
|
||||
ldap_conn = None
|
||||
try:
|
||||
ldap_conn = self._get_ldap_conn()
|
||||
filter_ = "(&{}({}={}))".format(self.LDAP_FILTER, self.attr_login, username)
|
||||
log.debug("Authenticating %r filter %s and scope: %s", self.BASE_DN, filter_, scope_label)
|
||||
|
||||
ldap_objects = ldap_conn.search_ext_s(self.BASE_DN, self.SEARCH_SCOPE, filter_, attrlist=["*", "+"])
|
||||
|
||||
if not ldap_objects:
|
||||
log.debug("No matching LDAP objects for authentication of UID:'%s' username:(%s)", uid, username)
|
||||
raise ldap.NO_SUCH_OBJECT()
|
||||
|
||||
log.debug("Found %s matching ldap object[s], trying to authenticate on each one now...", len(ldap_objects))
|
||||
for dn, _attrs in ldap_objects:
|
||||
if dn is None:
|
||||
continue
|
||||
|
||||
user_attrs = self.fetch_attrs_from_simple_bind(ldap_conn, dn, username, password)
|
||||
|
||||
if user_attrs:
|
||||
log.debug("Got authenticated user attributes from DN:%s", dn)
|
||||
break
|
||||
else:
|
||||
raise LdapPasswordError(f"Failed to authenticate user `{username}` with given password")
|
||||
|
||||
except ldap.NO_SUCH_OBJECT:
|
||||
log.debug("LDAP says no such user '%s' (%s), org_exc:", uid, username, exc_info=True)
|
||||
raise LdapUsernameError("Unable to find user")
|
||||
except ldap.SERVER_DOWN:
|
||||
org_exc = traceback.format_exc()
|
||||
raise LdapConnectionError("LDAP can't access authentication server, org_exc:%s" % org_exc)
|
||||
finally:
|
||||
if ldap_conn:
|
||||
log.debug("ldap: connection release")
|
||||
try:
|
||||
ldap_conn.unbind_s()
|
||||
except Exception:
|
||||
# for any reason this can raise exception we must catch it
|
||||
# to not crush the server
|
||||
pass
|
||||
|
||||
return dn, user_attrs
|
||||
|
||||
|
||||
class LdapSettingsSchema(TwoFactorAuthnPluginSettingsSchemaMixin, AuthnPluginSettingsSchemaBase):
|
||||
tls_kind_choices = ["PLAIN", "LDAPS", "START_TLS"]
|
||||
tls_reqcert_choices = ["NEVER", "ALLOW", "TRY", "DEMAND", "HARD"]
|
||||
|
|
@ -338,7 +150,7 @@ class LdapSettingsSchema(TwoFactorAuthnPluginSettingsSchemaMixin, AuthnPluginSet
|
|||
)
|
||||
tls_cert_dir = colander.SchemaNode(
|
||||
colander.String(),
|
||||
default=AuthLdap.default_tls_cert_dir,
|
||||
default=LdapDao.default_tls_cert_dir,
|
||||
description=_(
|
||||
"This specifies the path of a directory that contains individual CA certificates in separate files."
|
||||
),
|
||||
|
|
@ -420,6 +232,38 @@ class LdapSettingsSchema(TwoFactorAuthnPluginSettingsSchemaMixin, AuthnPluginSet
|
|||
title=_("Last Name Attribute"),
|
||||
widget="string",
|
||||
)
|
||||
sync_active_directory_users = colander.SchemaNode(
|
||||
colander.Bool(),
|
||||
default=False,
|
||||
description=_(
|
||||
"A cron job that periodically retrieves all users from an LDAP-based Active Directory server and syncs "
|
||||
"them with the RhodeCode database.\n"
|
||||
"Note: This feature is specific to Active Directory. Enabling it for other types of LDAP servers will have no effect."
|
||||
),
|
||||
missing=False,
|
||||
preparer=strip_whitespace,
|
||||
title=_("Sync Active Directory Users"),
|
||||
widget="bool",
|
||||
)
|
||||
|
||||
|
||||
def get_ldap_args(settings: dict):
|
||||
return {
|
||||
"server": settings.get("host", ""),
|
||||
"base_dn": settings.get("base_dn", ""),
|
||||
"port": settings.get("port"),
|
||||
"bind_dn": settings.get("dn_user"),
|
||||
"bind_pass": settings.get("dn_pass"),
|
||||
"tls_kind": settings.get("tls_kind"),
|
||||
"tls_reqcert": settings.get("tls_reqcert"),
|
||||
"tls_cert_file": settings.get("tls_cert_file"),
|
||||
"tls_cert_dir": settings.get("tls_cert_dir"),
|
||||
"search_scope": settings.get("search_scope"),
|
||||
"attr_login": settings.get("attr_login"),
|
||||
"ldap_version": 3,
|
||||
"ldap_filter": settings.get("filter"),
|
||||
"timeout": settings.get("timeout"),
|
||||
}
|
||||
|
||||
|
||||
class RhodeCodeAuthPlugin(RhodeCodeExternalAuthPlugin):
|
||||
|
|
@ -509,30 +353,15 @@ class RhodeCodeAuthPlugin(RhodeCodeExternalAuthPlugin):
|
|||
log.debug("Empty username or password skipping...")
|
||||
return None
|
||||
|
||||
ldap_args = {
|
||||
"server": settings.get("host", ""),
|
||||
"base_dn": settings.get("base_dn", ""),
|
||||
"port": settings.get("port"),
|
||||
"bind_dn": settings.get("dn_user"),
|
||||
"bind_pass": settings.get("dn_pass"),
|
||||
"tls_kind": settings.get("tls_kind"),
|
||||
"tls_reqcert": settings.get("tls_reqcert"),
|
||||
"tls_cert_file": settings.get("tls_cert_file"),
|
||||
"tls_cert_dir": settings.get("tls_cert_dir"),
|
||||
"search_scope": settings.get("search_scope"),
|
||||
"attr_login": settings.get("attr_login"),
|
||||
"ldap_version": 3,
|
||||
"ldap_filter": settings.get("filter"),
|
||||
"timeout": settings.get("timeout"),
|
||||
}
|
||||
ldap_args = get_ldap_args(settings)
|
||||
|
||||
ldap_attrs = self.try_dynamic_binding(username, password, ldap_args)
|
||||
|
||||
log.debug("Checking for ldap authentication.")
|
||||
|
||||
try:
|
||||
auth_ldap = AuthLdap(**ldap_args)
|
||||
(user_dn, ldap_attrs) = auth_ldap.authenticate_ldap(username, password)
|
||||
ldap_dao = LdapDao(**ldap_args)
|
||||
(user_dn, ldap_attrs) = ldap_dao.authenticate_ldap(username, password)
|
||||
log.debug("Got ldap DN response %s", user_dn)
|
||||
|
||||
def get_ldap_attr(k) -> str:
|
||||
|
|
|
|||
0
rhodecode/authentication/plugins/services/__init__.py
Normal file
0
rhodecode/authentication/plugins/services/__init__.py
Normal file
226
rhodecode/authentication/plugins/services/ldap_dao.py
Normal file
226
rhodecode/authentication/plugins/services/ldap_dao.py
Normal file
|
|
@ -0,0 +1,226 @@
|
|||
import logging
|
||||
import traceback
|
||||
from dataclasses import dataclass
|
||||
from typing import List, Optional
|
||||
|
||||
from rhodecode.model.validators import Missing
|
||||
from rhodecode.lib.exceptions import LdapConnectionError, LdapUsernameError, LdapPasswordError, LdapImportError
|
||||
from rhodecode.authentication.base import AuthLdapBase
|
||||
from rhodecode.lib.str_utils import safe_str
|
||||
|
||||
try:
|
||||
import ldap
|
||||
except ImportError:
|
||||
# means that python-ldap is not installed, we use Missing object to mark
|
||||
# ldap lib is Missing
|
||||
ldap = Missing
|
||||
|
||||
|
||||
@dataclass
|
||||
class UserType:
|
||||
DEFAULT: str = "DEFAULT"
|
||||
ACTIVE_DIRECTORY: str = "ACTIVE_DIRECTORY"
|
||||
|
||||
|
||||
class LdapDao(AuthLdapBase):
|
||||
default_tls_cert_dir = "/etc/openldap/cacerts"
|
||||
|
||||
scope_labels = {
|
||||
ldap.SCOPE_BASE: "SCOPE_BASE",
|
||||
ldap.SCOPE_ONELEVEL: "SCOPE_ONELEVEL",
|
||||
ldap.SCOPE_SUBTREE: "SCOPE_SUBTREE",
|
||||
}
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
server,
|
||||
base_dn,
|
||||
port=389,
|
||||
bind_dn="",
|
||||
bind_pass="",
|
||||
tls_kind="PLAIN",
|
||||
tls_reqcert="DEMAND",
|
||||
tls_cert_file=None,
|
||||
tls_cert_dir=None,
|
||||
ldap_version=3,
|
||||
search_scope="SUBTREE",
|
||||
attr_login="uid",
|
||||
ldap_filter="",
|
||||
timeout=None,
|
||||
):
|
||||
if ldap == Missing:
|
||||
raise LdapImportError("Missing or incompatible ldap library")
|
||||
|
||||
self.log = logging.getLogger(LdapDao.__name__)
|
||||
|
||||
self.debug = False
|
||||
self.timeout = timeout or 60 * 5
|
||||
self.ldap_version = ldap_version
|
||||
self.ldap_server_type = "ldap"
|
||||
|
||||
self.TLS_KIND = tls_kind
|
||||
|
||||
if self.TLS_KIND == "LDAPS":
|
||||
port = port or 636
|
||||
self.ldap_server_type += "s"
|
||||
|
||||
OPT_X_TLS_DEMAND = 2
|
||||
self.TLS_REQCERT = getattr(ldap, "OPT_X_TLS_%s" % tls_reqcert, OPT_X_TLS_DEMAND)
|
||||
self.TLS_CERT_FILE = tls_cert_file or ""
|
||||
self.TLS_CERT_DIR = tls_cert_dir or self.default_tls_cert_dir
|
||||
|
||||
# split server into list
|
||||
self.SERVER_ADDRESSES = self._get_server_list(server)
|
||||
self.LDAP_SERVER_PORT = port
|
||||
|
||||
# USE FOR READ ONLY BIND TO LDAP SERVER
|
||||
self.attr_login = attr_login
|
||||
|
||||
self.LDAP_BIND_DN = safe_str(bind_dn)
|
||||
self.LDAP_BIND_PASS = safe_str(bind_pass)
|
||||
|
||||
self.SEARCH_SCOPE = getattr(ldap, "SCOPE_%s" % search_scope)
|
||||
self.BASE_DN = safe_str(base_dn)
|
||||
self.LDAP_FILTER = safe_str(ldap_filter)
|
||||
|
||||
def _get_ldap_conn(self):
|
||||
if self.debug:
|
||||
ldap.set_option(ldap.OPT_DEBUG_LEVEL, 255)
|
||||
|
||||
if self.TLS_CERT_FILE and hasattr(ldap, "OPT_X_TLS_CACERTFILE"):
|
||||
ldap.set_option(ldap.OPT_X_TLS_CACERTFILE, self.TLS_CERT_FILE)
|
||||
|
||||
elif hasattr(ldap, "OPT_X_TLS_CACERTDIR"):
|
||||
ldap.set_option(ldap.OPT_X_TLS_CACERTDIR, self.TLS_CERT_DIR)
|
||||
|
||||
if self.TLS_KIND != "PLAIN":
|
||||
ldap.set_option(ldap.OPT_X_TLS_REQUIRE_CERT, self.TLS_REQCERT)
|
||||
|
||||
ldap.set_option(ldap.OPT_REFERRALS, ldap.OPT_OFF)
|
||||
ldap.set_option(ldap.OPT_RESTART, ldap.OPT_ON)
|
||||
|
||||
# init connection now
|
||||
ldap_servers = self._build_servers(self.ldap_server_type, self.SERVER_ADDRESSES, self.LDAP_SERVER_PORT)
|
||||
self.log.debug("initializing LDAP connection to:%s", ldap_servers)
|
||||
ldap_conn = ldap.initialize(ldap_servers)
|
||||
ldap_conn.set_option(ldap.OPT_NETWORK_TIMEOUT, self.timeout)
|
||||
ldap_conn.set_option(ldap.OPT_TIMEOUT, self.timeout)
|
||||
ldap_conn.timeout = self.timeout
|
||||
|
||||
if self.ldap_version == 2:
|
||||
ldap_conn.protocol = ldap.VERSION2
|
||||
else:
|
||||
ldap_conn.protocol = ldap.VERSION3
|
||||
|
||||
if self.TLS_KIND == "START_TLS":
|
||||
ldap_conn.start_tls_s()
|
||||
|
||||
if self.LDAP_BIND_DN and self.LDAP_BIND_PASS:
|
||||
self.log.debug("Trying simple_bind with password and given login DN: %r", self.LDAP_BIND_DN)
|
||||
ldap_conn.simple_bind_s(self.LDAP_BIND_DN, self.LDAP_BIND_PASS)
|
||||
self.log.debug("simple_bind successful")
|
||||
return ldap_conn
|
||||
|
||||
def fetch_all(self, ldap_filter: str = "(objectClass=*)", attributes: List[str] = None) -> Optional[List[dict]]:
|
||||
# TODO: add pagination
|
||||
ldap_conn = None
|
||||
try:
|
||||
if attributes is None:
|
||||
attributes = ["*", "+"]
|
||||
|
||||
ldap_conn = self._get_ldap_conn()
|
||||
self.log.debug("fetching users for DN: %s", self.BASE_DN)
|
||||
ldap_objects = ldap_conn.search_ext_s(self.BASE_DN, self.SEARCH_SCOPE, ldap_filter, attrlist=attributes)
|
||||
return [attrs for _, attrs in ldap_objects]
|
||||
except Exception as e:
|
||||
self.log.error("Error fetching users for DN: %s. Error: %s", self.BASE_DN, str(e))
|
||||
return None
|
||||
finally:
|
||||
self._releease_connection(ldap_conn)
|
||||
|
||||
def _releease_connection(self, ldap_conn):
|
||||
if ldap_conn:
|
||||
self.log.debug("ldap: connection release")
|
||||
try:
|
||||
ldap_conn.unbind_s()
|
||||
except Exception as e:
|
||||
# for any reason this can raise exception we must catch it
|
||||
# to not crush the server
|
||||
self.log.warning("unbind_s failed, error: %s", str(e))
|
||||
|
||||
def _fetch_attrs_from_simple_bind(self, ldap_conn, dn, username, password):
|
||||
scope = ldap.SCOPE_BASE
|
||||
scope_label = self.scope_labels.get(scope)
|
||||
ldap_filter = "(objectClass=*)"
|
||||
|
||||
try:
|
||||
self.log.debug(
|
||||
"Trying authenticated search bind with dn: %r SCOPE: %s (and filter: %s)", dn, scope_label, ldap_filter
|
||||
)
|
||||
ldap_conn.simple_bind_s(dn, safe_str(password))
|
||||
response = ldap_conn.search_ext_s(dn, scope, ldap_filter, attrlist=["*", "+"])
|
||||
|
||||
if not response:
|
||||
self.log.error("search bind returned empty results: %r", response)
|
||||
return {}
|
||||
else:
|
||||
_dn, attrs = response[0]
|
||||
return attrs
|
||||
|
||||
except ldap.INVALID_CREDENTIALS:
|
||||
self.log.debug("LDAP rejected password for user '%s': %s, org_exc:", username, dn, exc_info=True)
|
||||
|
||||
def authenticate_ldap(self, username, password):
|
||||
"""
|
||||
Authenticate a user via LDAP and return his/her LDAP properties.
|
||||
|
||||
Raises AuthenticationError if the credentials are rejected, or
|
||||
EnvironmentError if the LDAP server can't be reached.
|
||||
|
||||
:param username: username
|
||||
:param password: password
|
||||
"""
|
||||
|
||||
uid = self.get_uid(username, self.SERVER_ADDRESSES)
|
||||
|
||||
self.validate_password(username, password)
|
||||
self.validate_username(username)
|
||||
scope_label = self.scope_labels.get(self.SEARCH_SCOPE)
|
||||
|
||||
ldap_conn = None
|
||||
try:
|
||||
ldap_conn = self._get_ldap_conn()
|
||||
filter_ = "(&{}({}={}))".format(self.LDAP_FILTER, self.attr_login, username)
|
||||
self.log.debug("Authenticating %r filter %s and scope: %s", self.BASE_DN, filter_, scope_label)
|
||||
|
||||
ldap_objects = ldap_conn.search_ext_s(self.BASE_DN, self.SEARCH_SCOPE, filter_, attrlist=["*", "+"])
|
||||
|
||||
if not ldap_objects:
|
||||
self.log.debug("No matching LDAP objects for authentication of UID:'%s' username:(%s)", uid, username)
|
||||
raise ldap.NO_SUCH_OBJECT()
|
||||
|
||||
self.log.debug(
|
||||
"Found %s matching ldap object[s], trying to authenticate on each one now...", len(ldap_objects)
|
||||
)
|
||||
for dn, _attrs in ldap_objects:
|
||||
if dn is None:
|
||||
continue
|
||||
|
||||
user_attrs = self._fetch_attrs_from_simple_bind(ldap_conn, dn, username, password)
|
||||
|
||||
if user_attrs:
|
||||
self.log.debug("Got authenticated user attributes from DN:%s", dn)
|
||||
break
|
||||
else:
|
||||
raise LdapPasswordError(f"Failed to authenticate user `{username}` with given password")
|
||||
|
||||
except ldap.NO_SUCH_OBJECT:
|
||||
self.log.debug("LDAP says no such user '%s' (%s), org_exc:", uid, username, exc_info=True)
|
||||
raise LdapUsernameError("Unable to find user")
|
||||
except ldap.SERVER_DOWN:
|
||||
org_exc = traceback.format_exc()
|
||||
raise LdapConnectionError("LDAP can't access authentication server, org_exc:%s" % org_exc)
|
||||
finally:
|
||||
self._releease_connection(ldap_conn)
|
||||
|
||||
return dn, user_attrs
|
||||
Loading…
Add table
Add a link
Reference in a new issue