backends.py: Enable auth with any ldap attributes as username.

This commit enables user to authenticate with any attribute set in
AUTH_LDAP_USER_SEARCH given that LDAP_EMAIL_ATTR is set to an email
attributes in the ldap server. Thus email and username can be
completely unrelated.

With some tweaks by tabbott to squash in the documentation and make it
work on older servers.
This commit is contained in:
Supermanu 2017-09-10 17:25:24 +02:00 committed by Tim Abbott
parent fb39e884c8
commit 28beddfd76
4 changed files with 53 additions and 5 deletions

View File

@ -1896,6 +1896,23 @@ class TestLDAP(ZulipTestCase):
assert(user_profile is not None)
self.assertEqual(user_profile.email, self.example_email("hamlet"))
@override_settings(AUTHENTICATION_BACKENDS=('zproject.backends.ZulipLDAPAuthBackend',))
def test_login_success_with_email_attr(self):
# type: () -> None
self.mock_ldap.directory = {
'uid=letham,ou=users,dc=zulip,dc=com': {
'userPassword': 'testing',
'email': ['hamlet@zulip.com'],
}
}
with self.settings(LDAP_EMAIL_ATTR='email',
AUTH_LDAP_BIND_PASSWORD='',
AUTH_LDAP_USER_DN_TEMPLATE='uid=%(user)s,ou=users,dc=zulip,dc=com'):
user_profile = self.backend.authenticate("letham", 'testing')
assert (user_profile is not None)
self.assertEqual(user_profile.email, self.example_email("hamlet"))
@override_settings(AUTHENTICATION_BACKENDS=('zproject.backends.ZulipLDAPAuthBackend',))
def test_login_failure_due_to_wrong_password(self):
# type: () -> None
@ -2023,6 +2040,19 @@ class TestLDAP(ZulipTestCase):
with self.assertRaisesRegex(Exception, 'Realm is None'):
backend.get_or_create_user(email, _LDAPUser())
@override_settings(AUTHENTICATION_BACKENDS=('zproject.backends.ZulipLDAPAuthBackend',))
def test_get_or_create_user_when_ldap_has_no_email_attr(self):
# type: () -> None
class _LDAPUser(object):
attrs = {'fn': ['Full Name'], 'sn': ['Short Name']}
nonexisting_attr = 'email'
with self.settings(LDAP_EMAIL_ATTR=nonexisting_attr):
backend = self.backend
email = 'nonexisting@zulip.com'
with self.assertRaisesRegex(Exception, 'LDAP user doesn\'t have the needed email attribute'):
backend.get_or_create_user(email, _LDAPUser())
@override_settings(AUTHENTICATION_BACKENDS=('zproject.backends.ZulipLDAPAuthBackend',))
def test_django_to_ldap_username_when_domain_does_not_match(self):
# type: () -> None

View File

@ -416,7 +416,7 @@ class ZulipLDAPAuthBackend(ZulipLDAPAuthBackendBase):
try:
if settings.REALMS_HAVE_SUBDOMAINS:
self._realm = get_realm(realm_subdomain)
else:
elif settings.LDAP_EMAIL_ATTR is not None:
self._realm = get_realm_by_email_domain(username)
username = self.django_to_ldap_username(username)
user_profile = ZulipLDAPAuthBackendBase.authenticate(self, username, password)
@ -433,6 +433,14 @@ class ZulipLDAPAuthBackend(ZulipLDAPAuthBackendBase):
def get_or_create_user(self, username, ldap_user):
# type: (str, _LDAPUser) -> Tuple[UserProfile, bool]
try:
if settings.LDAP_EMAIL_ATTR is not None:
# Get email from ldap attributes.
if settings.LDAP_EMAIL_ATTR not in ldap_user.attrs:
raise ZulipLDAPException("LDAP user doesn't have the needed %s attribute" % (settings.LDAP_EMAIL_ATTR,))
username = ldap_user.attrs[settings.LDAP_EMAIL_ATTR][0]
self._realm = get_realm_by_email_domain(username)
user_profile = get_user_profile_by_email(username)
if not user_profile.is_active or user_profile.realm.deactivated:
raise ZulipLDAPException("Realm has been deactivated")

View File

@ -313,11 +313,10 @@ EMAIL_GATEWAY_IMAP_FOLDER = "INBOX"
# * Fill in the LDAP configuration options below so that Zulip can
# connect to your LDAP server
#
# * Setup the mapping between email addresses (used as login names in
# Zulip) and LDAP usernames. There are two supported ways to setup
# the username mapping:
# * Setup the mapping between LDAP attributes and Zulip.
# There are three supported ways to setup the username and/or email mapping:
#
# (A) If users' email addresses are in LDAP, set
# (A) If users' email addresses are in LDAP and used as username, set
# LDAP_APPEND_DOMAIN = None
# AUTH_LDAP_USER_SEARCH to lookup users by email address
#
@ -326,6 +325,12 @@ EMAIL_GATEWAY_IMAP_FOLDER = "INBOX"
# LDAP_APPEND_DOMAIN = example.com and
# AUTH_LDAP_USER_SEARCH to lookup users by username
#
# (C) If LDAP username are completely unrelated to email addresses,
# you should set:
# LDAP_EMAIL_ATTR = "email"
# LDAP_APPEND_DOMAIN = None
# AUTH_LDAP_USER_SEARCH to lookup users by username
#
# You can quickly test whether your configuration works by running:
# ./manage.py query_ldap username@example.com
# From the root of your Zulip installation; if your configuration is working
@ -365,6 +370,10 @@ AUTH_LDAP_USER_SEARCH = LDAPSearch("ou=users,dc=example,dc=com",
# address, specify the domain to append here.
LDAP_APPEND_DOMAIN = None # type: Optional[str]
# If username and email are two different LDAP attributes, specify the
# attribute to get the user's email address from LDAP here.
LDAP_EMAIL_ATTR = None # type: Optional[str]
# This map defines how to populate attributes of a Zulip user from LDAP.
AUTH_LDAP_USER_ATTR_MAP = {
# Populate the Django user's name from the LDAP directory.

View File

@ -169,6 +169,7 @@ DEFAULT_SETTINGS = {'TWITTER_CONSUMER_KEY': '',
'ENABLE_GRAVATAR': True,
'DEFAULT_AVATAR_URI': '/static/images/default-avatar.png',
'AUTH_LDAP_SERVER_URI': "",
'LDAP_EMAIL_ATTR': None,
'EXTERNAL_URI_SCHEME': "https://",
'ZULIP_COM': False,
'SHOW_OSS_ANNOUNCEMENT': False,