mirror of https://github.com/zulip/zulip.git
ldap: Add support for two field mapping of full name.
Tests for `sync_full_name_from_ldap()` are pending and will be added in a separate commit. Fixes: #11039.
This commit is contained in:
parent
348f370b79
commit
05ad6a357b
|
@ -2342,6 +2342,17 @@ class TestLDAP(ZulipTestCase):
|
|||
with self.assertRaisesRegex(Exception, 'LDAP user doesn\'t have the needed email attribute'):
|
||||
backend.get_or_build_user(email, _LDAPUser())
|
||||
|
||||
@override_settings(AUTHENTICATION_BACKENDS=('zproject.backends.ZulipLDAPAuthBackend',))
|
||||
def test_get_or_build_user_when_ldap_has_no_full_name_mapping(self) -> None:
|
||||
class _LDAPUser:
|
||||
attrs = {'fn': ['Full Name'], 'sn': ['Short Name']}
|
||||
|
||||
with self.settings(AUTH_LDAP_USER_ATTR_MAP={}):
|
||||
backend = self.backend
|
||||
email = 'nonexisting@zulip.com'
|
||||
with self.assertRaisesRegex(Exception, "Missing required mapping for user's full name"):
|
||||
backend.get_or_build_user(email, _LDAPUser())
|
||||
|
||||
@override_settings(AUTHENTICATION_BACKENDS=('zproject.backends.ZulipLDAPAuthBackend',))
|
||||
def test_django_to_ldap_username_when_domain_does_not_match(self) -> None:
|
||||
backend = self.backend
|
||||
|
@ -2449,6 +2460,27 @@ class TestLDAP(ZulipTestCase):
|
|||
result = self.client_get(avatar_url(user_profile))
|
||||
self.assertEqual(result.status_code, 200)
|
||||
|
||||
@override_settings(AUTHENTICATION_BACKENDS=('zproject.backends.ZulipLDAPAuthBackend',))
|
||||
def test_login_success_when_user_does_not_exist_with_split_full_name_mapping(self) -> None:
|
||||
self.mock_ldap.directory = {
|
||||
'uid=nonexisting,ou=users,dc=acme,dc=com': {
|
||||
'fn': ['Non', ],
|
||||
'ln': ['Existing', ],
|
||||
'userPassword': 'testing',
|
||||
}
|
||||
}
|
||||
with self.settings(
|
||||
LDAP_APPEND_DOMAIN='acme.com',
|
||||
AUTH_LDAP_BIND_PASSWORD='',
|
||||
AUTH_LDAP_USER_DN_TEMPLATE='uid=%(user)s,ou=users,dc=acme,dc=com',
|
||||
AUTH_LDAP_USER_ATTR_MAP={'first_name': 'fn', 'last_name': 'ln'}):
|
||||
user_profile = self.backend.authenticate('nonexisting@acme.com', 'testing',
|
||||
realm=get_realm('zulip'))
|
||||
assert(user_profile is not None)
|
||||
self.assertEqual(user_profile.email, 'nonexisting@acme.com')
|
||||
self.assertEqual(user_profile.full_name, 'Non Existing')
|
||||
self.assertEqual(user_profile.realm.string_id, 'zulip')
|
||||
|
||||
class TestZulipLDAPUserPopulator(ZulipTestCase):
|
||||
def test_authenticate(self) -> None:
|
||||
backend = ZulipLDAPUserPopulator()
|
||||
|
|
|
@ -312,10 +312,40 @@ class ZulipLDAPAuthBackendBase(ZulipAuthMixin, LDAPBackend):
|
|||
ldap_disabled = bool(int(account_control_value) & LDAP_USER_ACCOUNT_CONTROL_DISABLED_MASK)
|
||||
return ldap_disabled
|
||||
|
||||
def get_mapped_name(self, ldap_user: _LDAPUser) -> Tuple[str, str]:
|
||||
if "full_name" in settings.AUTH_LDAP_USER_ATTR_MAP:
|
||||
full_name_attr = settings.AUTH_LDAP_USER_ATTR_MAP["full_name"]
|
||||
short_name = full_name = ldap_user.attrs[full_name_attr][0]
|
||||
elif all(key in settings.AUTH_LDAP_USER_ATTR_MAP for key in {"first_name", "last_name"}):
|
||||
first_name_attr = settings.AUTH_LDAP_USER_ATTR_MAP["first_name"]
|
||||
last_name_attr = settings.AUTH_LDAP_USER_ATTR_MAP["last_name"]
|
||||
short_name = ldap_user.attrs[first_name_attr][0]
|
||||
full_name = short_name + ' ' + ldap_user.attrs[last_name_attr][0]
|
||||
else:
|
||||
raise ZulipLDAPException("Missing required mapping for user's full name")
|
||||
|
||||
if "short_name" in settings.AUTH_LDAP_USER_ATTR_MAP:
|
||||
short_name_attr = settings.AUTH_LDAP_USER_ATTR_MAP["short_name"]
|
||||
short_name = ldap_user.attrs[short_name_attr][0]
|
||||
|
||||
return full_name, short_name
|
||||
|
||||
def sync_full_name_from_ldap(self, user_profile: UserProfile,
|
||||
ldap_user: _LDAPUser) -> None: # nocoverage
|
||||
from zerver.lib.actions import do_change_full_name
|
||||
full_name, _ = self.get_mapped_name(ldap_user)
|
||||
if full_name != user_profile.full_name:
|
||||
try:
|
||||
full_name = check_full_name(full_name)
|
||||
except JsonableError as e:
|
||||
raise ZulipLDAPException(e.msg)
|
||||
do_change_full_name(user_profile, full_name, None)
|
||||
|
||||
def get_or_build_user(self, username: str,
|
||||
ldap_user: _LDAPUser) -> Tuple[UserProfile, bool]: # nocoverage
|
||||
(user, built) = super().get_or_build_user(username, ldap_user)
|
||||
self.sync_avatar_from_ldap(user, ldap_user)
|
||||
self.sync_full_name_from_ldap(user, ldap_user)
|
||||
if 'userAccountControl' in settings.AUTH_LDAP_USER_ATTR_MAP:
|
||||
user_disabled_in_ldap = self.is_account_control_disabled_user(ldap_user)
|
||||
if user_disabled_in_ldap and user.is_active:
|
||||
|
@ -393,15 +423,11 @@ class ZulipLDAPAuthBackend(ZulipLDAPAuthBackendBase):
|
|||
raise ZulipLDAPException("Realm has been deactivated")
|
||||
|
||||
# We have valid LDAP credentials; time to create an account.
|
||||
full_name_attr = settings.AUTH_LDAP_USER_ATTR_MAP["full_name"]
|
||||
short_name = full_name = ldap_user.attrs[full_name_attr][0]
|
||||
full_name, short_name = self.get_mapped_name(ldap_user)
|
||||
try:
|
||||
full_name = check_full_name(full_name)
|
||||
except JsonableError as e:
|
||||
raise ZulipLDAPException(e.msg)
|
||||
if "short_name" in settings.AUTH_LDAP_USER_ATTR_MAP:
|
||||
short_name_attr = settings.AUTH_LDAP_USER_ATTR_MAP["short_name"]
|
||||
short_name = ldap_user.attrs[short_name_attr][0]
|
||||
|
||||
user_profile = do_create_user(username, None, self._realm, full_name, short_name)
|
||||
self.sync_avatar_from_ldap(user_profile, ldap_user)
|
||||
|
|
|
@ -462,7 +462,12 @@ LDAP_EMAIL_ATTR = None # type: Optional[str]
|
|||
# LDAP database uses for the same concept.
|
||||
AUTH_LDAP_USER_ATTR_MAP = {
|
||||
# full_name is required; common values include "cn" or "displayName".
|
||||
# If names are encoded in your LDAP directory as first and last
|
||||
# name, you can instead specify first_name and last_name, and
|
||||
# Zulip will combine those to construct a full_name automatically.
|
||||
"full_name": "cn",
|
||||
# "first_name": "fn",
|
||||
# "last_name": "ln",
|
||||
|
||||
# User avatars can be pulled from the LDAP "thumbnailPhoto"/"jpegPhoto" field.
|
||||
# "avatar": "thumbnailPhoto",
|
||||
|
|
Loading…
Reference in New Issue