ldap: Add option to limit user access to certain realms.

This adds an option for restricting a ldap user
to only be allowed to login into certain realms.
This is done by configuring an attribute mapping of "org_membership"
to an ldap attribute that will contain the list of subdomains the ldap
user is allowed to access. This is analogous to how it's done in SAML.

Co-authored-by: Mateusz Mandera <mateusz.mandera@zulip.com>
This commit is contained in:
Strifel
2020-08-15 18:33:16 +02:00
committed by Tim Abbott
parent e6ee1b0760
commit 209c89be10
4 changed files with 71 additions and 0 deletions

View File

@@ -284,6 +284,34 @@ details.
[upstream-ldap-groups]: https://django-auth-ldap.readthedocs.io/en/latest/groups.html#limiting-access
### Restricting LDAP user access to specific organizations
If you're hosting multiple Zulip organizations, you can restrict which
users have access to which organizations.
This is done by setting `org_membership` in `AUTH_LDAP_USER_ATTR_MAP` to the name of
the LDAP attribute which will contain a list of subdomains that the
user should be allowed to access.
For the root subdomain, `www` in the list will work, or any other of
`settings.ROOT_SUBDOMAIN_ALIASES`.
For example, with `org_membership` set to `department`, a user with
the following attributes will have access to the root and `engineering` subdomains:
```
...
department: engineering
department: www
...
```
```eval_rst
.. warning::
Restricting access using this mechanism only affects authentication via LDAP,
and won't prevent users from accessing the organization using any other
authentication backends that are enabled for the organization.
```
### Troubleshooting
Most issues with LDAP authentication are caused by misconfigurations of

View File

@@ -3782,6 +3782,35 @@ class FetchAPIKeyTest(ZulipTestCase):
)
self.assert_json_success(result)
@override_settings(
AUTHENTICATION_BACKENDS=("zproject.backends.ZulipLDAPAuthBackend",),
AUTH_LDAP_USER_ATTR_MAP={"full_name": "cn", "org_membership": "department"},
)
def test_ldap_auth_email_auth_organization_restriction(self) -> None:
self.init_default_ldap_database()
# We do test two combinations here:
# The first user has no (department) attribute set
# The second user has one set, but to a different value
result = self.client_post(
"/api/v1/fetch_api_key",
dict(username=self.example_email("hamlet"), password=self.ldap_password("hamlet")),
)
self.assert_json_error(result, "Your username or password is incorrect.", 403)
self.change_ldap_user_attr("hamlet", "department", "testWrongRealm")
result = self.client_post(
"/api/v1/fetch_api_key",
dict(username=self.example_email("hamlet"), password=self.ldap_password("hamlet")),
)
self.assert_json_error(result, "Your username or password is incorrect.", 403)
self.change_ldap_user_attr("hamlet", "department", "zulip")
result = self.client_post(
"/api/v1/fetch_api_key",
dict(username=self.example_email("hamlet"), password=self.ldap_password("hamlet")),
)
self.assert_json_success(result)
def test_inactive_user(self) -> None:
do_deactivate_user(self.user_profile)
result = self.client_post(

View File

@@ -631,6 +631,14 @@ class ZulipLDAPAuthBackendBase(ZulipAuthMixin, LDAPBackend):
ldap_disabled = bool(int(account_control_value) & LDAP_USER_ACCOUNT_CONTROL_DISABLED_MASK)
return ldap_disabled
def is_account_realm_access_forbidden(self, ldap_user: _LDAPUser, realm: Realm) -> bool:
if "org_membership" not in settings.AUTH_LDAP_USER_ATTR_MAP:
return False
org_membership_attr = settings.AUTH_LDAP_USER_ATTR_MAP["org_membership"]
allowed_orgs: List[str] = ldap_user.attrs.get(org_membership_attr, [])
return not is_subdomain_in_allowed_subdomains_list(realm.subdomain, allowed_orgs)
@classmethod
def get_mapped_name(cls, ldap_user: _LDAPUser) -> str:
"""Constructs the user's Zulip full_name from the LDAP data"""
@@ -767,6 +775,9 @@ class ZulipLDAPAuthBackend(ZulipLDAPAuthBackendBase):
username = self.user_email_from_ldapuser(username, ldap_user)
if self.is_account_realm_access_forbidden(ldap_user, self._realm):
raise ZulipLDAPException("User not allowed to access realm")
if "userAccountControl" in settings.AUTH_LDAP_USER_ATTR_MAP: # nocoverage
ldap_disabled = self.is_account_control_disabled_user(ldap_user)
if ldap_disabled:

View File

@@ -236,6 +236,9 @@ AUTH_LDAP_USER_ATTR_MAP = {
## who are disabled in LDAP/Active Directory (and reactivate users who are not).
## See docs for usage details and precise semantics.
# "userAccountControl": "userAccountControl",
## Restrict access to organizations using an LDAP attribute.
## See https://zulip.readthedocs.io/en/latest/production/authentication-methods.html#restricting-ldap-user-access-to-specific-organizations
# "org_membership": "department",
}
## Whether to automatically deactivate users not found in LDAP. If LDAP