mirror of
https://github.com/zulip/zulip.git
synced 2025-10-23 04:52:12 +00:00
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:
@@ -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
|
||||
|
@@ -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(
|
||||
|
@@ -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:
|
||||
|
@@ -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
|
||||
|
Reference in New Issue
Block a user