diff --git a/data/users/__init__.py b/data/users/__init__.py index e093f6993..70f736c1f 100644 --- a/data/users/__init__.py +++ b/data/users/__init__.py @@ -44,10 +44,11 @@ def get_users_handler(config, config_provider, override_config_dir): user_rdn = config.get('LDAP_USER_RDN', []) uid_attr = config.get('LDAP_UID_ATTR', 'uid') email_attr = config.get('LDAP_EMAIL_ATTR', 'mail') + secondary_user_rds = config.get('LDAP_SECONDARY_USER_RDNS', []) allow_tls_fallback = config.get('LDAP_ALLOW_INSECURE_FALLBACK', False) return LDAPUsers(ldap_uri, base_dn, admin_dn, admin_passwd, user_rdn, uid_attr, email_attr, - allow_tls_fallback) + allow_tls_fallback, secondary_user_rds=secondary_user_rds) if authentication_type == 'JWT': verify_url = config.get('JWT_VERIFY_ENDPOINT') diff --git a/data/users/externalldap.py b/data/users/externalldap.py index 561891438..ad885b8eb 100644 --- a/data/users/externalldap.py +++ b/data/users/externalldap.py @@ -48,17 +48,21 @@ class LDAPUsers(FederatedUsers): _LDAPResult = namedtuple('LDAPResult', ['dn', 'attrs']) def __init__(self, ldap_uri, base_dn, admin_dn, admin_passwd, user_rdn, uid_attr, email_attr, - allow_tls_fallback=False): + allow_tls_fallback=False, secondary_user_rdns=None): super(LDAPUsers, self).__init__('ldap') self._ldap = LDAPConnectionBuilder(ldap_uri, admin_dn, admin_passwd, allow_tls_fallback) self._ldap_uri = ldap_uri - self._base_dn = base_dn - self._user_rdn = user_rdn self._uid_attr = uid_attr self._email_attr = email_attr self._allow_tls_fallback = allow_tls_fallback + # Note: user_rdn is a list of RDN pieces (for historical reasons), and secondary_user_rds + # is a list of RDN strings. + relative_user_dns = [','.join(user_rdn)] + (secondary_user_rdns or []) + self._user_dns = [','.join(relative_dn.split(',') + base_dn) + for relative_dn in relative_user_dns] + def _get_ldap_referral_dn(self, referral_exception): logger.debug('Got referral: %s', referral_exception.args[0]) if not referral_exception.args[0] or not referral_exception.args[0].get('info'): @@ -78,6 +82,28 @@ class LDAPUsers(FederatedUsers): referral_dn = referral_uri[len('ldap:///'):] return referral_dn + def _ldap_user_search_with_rdn(self, conn, username_or_email, user_search_dn): + query = u'(|({0}={2})({1}={2}))'.format(self._uid_attr, self._email_attr, + username_or_email) + logger.debug('Conducting user search: %s under %s', query, user_search_dn) + try: + return (conn.search_s(user_search_dn, ldap.SCOPE_SUBTREE, query.encode('utf-8')), None) + except ldap.REFERRAL as re: + referral_dn = self._get_ldap_referral_dn(re) + if not referral_dn: + return (None, 'Failed to follow referral when looking up username') + + try: + subquery = u'(%s=%s)' % (self._uid_attr, username_or_email) + return (conn.search_s(referral_dn, ldap.SCOPE_BASE, subquery), None) + except ldap.LDAPError: + logger.exception('LDAP referral search exception') + return (None, 'Username not found') + + except ldap.LDAPError: + logger.exception('LDAP search exception') + return (None, 'Username not found') + def _ldap_user_search(self, username_or_email): # Verify the admin connection works first. We do this here to avoid wrapping # the entire block in the INVALID CREDENTIALS check. @@ -89,31 +115,16 @@ class LDAPUsers(FederatedUsers): with self._ldap.get_connection() as conn: logger.debug('Incoming username or email param: %s', username_or_email.__repr__()) - user_search_dn = ','.join(self._user_rdn + self._base_dn) - query = u'(|({0}={2})({1}={2}))'.format(self._uid_attr, self._email_attr, - username_or_email) - logger.debug('Conducting user search: %s under %s', query, user_search_dn) - try: - pairs = conn.search_s(user_search_dn, ldap.SCOPE_SUBTREE, query.encode('utf-8')) - except ldap.REFERRAL as re: - referral_dn = self._get_ldap_referral_dn(re) - if not referral_dn: - return (None, 'Failed to follow referral when looking up username') + for user_search_dn in self._user_dns: + (pairs, err_msg) = self._ldap_user_search_with_rdn(conn, username_or_email, user_search_dn) + if pairs is not None and len(pairs) > 0: + break - try: - subquery = u'(%s=%s)' % (self._uid_attr, username_or_email) - pairs = conn.search_s(referral_dn, ldap.SCOPE_BASE, subquery) - except ldap.LDAPError: - logger.exception('LDAP referral search exception') - return (None, 'Username not found') - - except ldap.LDAPError: - logger.exception('LDAP search exception') - return (None, 'Username not found') + if err_msg is not None: + return (None, err_msg) logger.debug('Found matching pairs: %s', pairs) - results = [LDAPUsers._LDAPResult(*pair) for pair in pairs] # Filter out pairs without DNs. Some LDAP impls will return such diff --git a/static/css/core-ui.css b/static/css/core-ui.css index 667fcc781..8a6f1ae08 100644 --- a/static/css/core-ui.css +++ b/static/css/core-ui.css @@ -467,6 +467,10 @@ a:focus { width: 400px; } +.config-setup-tool-element .config-table > tbody > tr > td .config-string-list-field-element { + width: 400px; +} + .config-map-field-element table { margin-bottom: 10px; } diff --git a/static/directives/config/config-setup-tool.html b/static/directives/config/config-setup-tool.html index 8e07f1521..9afaf4a23 100644 --- a/static/directives/config/config-setup-tool.html +++ b/static/directives/config/config-setup-tool.html @@ -595,23 +595,36 @@