mirror of
https://github.com/zulip/zulip.git
synced 2025-11-02 04:53:36 +00:00
zerver: Implement ldap group synchronization.
Fixes #9957. Co-authored-by: Mateusz Mandera <mateusz.mandera@zulip.com>
This commit is contained in:
committed by
Tim Abbott
parent
2bfbbf0035
commit
b6a25840a1
@@ -80,6 +80,10 @@ from zxcvbn import zxcvbn
|
||||
|
||||
from zerver.actions.create_user import do_create_user, do_reactivate_user
|
||||
from zerver.actions.custom_profile_fields import do_update_user_custom_profile_data_if_changed
|
||||
from zerver.actions.user_groups import (
|
||||
bulk_add_members_to_user_groups,
|
||||
bulk_remove_members_from_user_groups,
|
||||
)
|
||||
from zerver.actions.user_settings import do_regenerate_api_key
|
||||
from zerver.actions.users import do_deactivate_user
|
||||
from zerver.lib.avatar import avatar_url, is_avatar_new
|
||||
@@ -105,6 +109,8 @@ from zerver.models import (
|
||||
PreregistrationRealm,
|
||||
PreregistrationUser,
|
||||
Realm,
|
||||
UserGroup,
|
||||
UserGroupMembership,
|
||||
UserProfile,
|
||||
custom_profile_fields_for_realm,
|
||||
get_realm,
|
||||
@@ -910,6 +916,78 @@ class ZulipLDAPAuthBackendBase(ZulipAuthMixin, LDAPBackend):
|
||||
except SyncUserError as e:
|
||||
raise ZulipLDAPError(str(e)) from e
|
||||
|
||||
def sync_groups_from_ldap(self, user_profile: UserProfile, ldap_user: _LDAPUser) -> None:
|
||||
"""
|
||||
For the groups set up for syncing for the realm in LDAP_SYNCHRONIZED_GROUPS_BY_REALM:
|
||||
|
||||
(1) Makes sure the user has membership in the Zulip UserGroups corresponding
|
||||
to the LDAP groups ldap_user belongs to.
|
||||
(2) Makes sure the user doesn't have membership in the Zulip UserGroups corresponding
|
||||
to the LDAP groups ldap_user doesn't belong to.
|
||||
"""
|
||||
|
||||
if user_profile.realm.string_id not in settings.LDAP_SYNCHRONIZED_GROUPS_BY_REALM:
|
||||
# no groups to sync for this realm
|
||||
return
|
||||
|
||||
configured_ldap_group_names_for_sync = set(
|
||||
settings.LDAP_SYNCHRONIZED_GROUPS_BY_REALM[user_profile.realm.string_id]
|
||||
)
|
||||
|
||||
try:
|
||||
ldap_logger.debug("Syncing groups for user: %s", user_profile.id)
|
||||
intended_group_name_set_for_user = set(ldap_user.group_names).intersection(
|
||||
configured_ldap_group_names_for_sync
|
||||
)
|
||||
|
||||
existing_group_name_set_for_user = set(
|
||||
UserGroupMembership.objects.filter(
|
||||
user_group__realm=user_profile.realm,
|
||||
user_group__name__in=set(
|
||||
settings.LDAP_SYNCHRONIZED_GROUPS_BY_REALM[user_profile.realm.string_id]
|
||||
),
|
||||
user_profile=user_profile,
|
||||
).values_list("user_group__name", flat=True)
|
||||
)
|
||||
|
||||
ldap_logger.debug(
|
||||
"intended groups: %s; zulip groups: %s",
|
||||
repr(intended_group_name_set_for_user),
|
||||
repr(existing_group_name_set_for_user),
|
||||
)
|
||||
|
||||
new_groups = UserGroup.objects.filter(
|
||||
name__in=intended_group_name_set_for_user.difference(
|
||||
existing_group_name_set_for_user
|
||||
),
|
||||
realm=user_profile.realm,
|
||||
)
|
||||
if new_groups:
|
||||
ldap_logger.debug(
|
||||
"add %s to %s", user_profile.id, [group.name for group in new_groups]
|
||||
)
|
||||
bulk_add_members_to_user_groups(new_groups, [user_profile.id], acting_user=None)
|
||||
|
||||
group_names_for_membership_deletion = existing_group_name_set_for_user.difference(
|
||||
intended_group_name_set_for_user
|
||||
)
|
||||
groups_for_membership_deletion = UserGroup.objects.filter(
|
||||
name__in=group_names_for_membership_deletion, realm=user_profile.realm
|
||||
)
|
||||
|
||||
if group_names_for_membership_deletion:
|
||||
ldap_logger.debug(
|
||||
"removing groups %s from %s",
|
||||
group_names_for_membership_deletion,
|
||||
user_profile.id,
|
||||
)
|
||||
bulk_remove_members_from_user_groups(
|
||||
groups_for_membership_deletion, [user_profile.id], acting_user=None
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
raise ZulipLDAPError(str(e)) from e
|
||||
|
||||
|
||||
class ZulipLDAPAuthBackend(ZulipLDAPAuthBackendBase):
|
||||
REALM_IS_NONE_ERROR = 1
|
||||
@@ -1136,6 +1214,7 @@ class ZulipLDAPUserPopulator(ZulipLDAPAuthBackendBase):
|
||||
self.sync_avatar_from_ldap(user, ldap_user)
|
||||
self.sync_full_name_from_ldap(user, ldap_user)
|
||||
self.sync_custom_profile_fields_from_ldap(user, ldap_user)
|
||||
self.sync_groups_from_ldap(user, ldap_user)
|
||||
return (user, built)
|
||||
|
||||
|
||||
|
||||
@@ -2,6 +2,8 @@ import os
|
||||
from email.headerregistry import Address
|
||||
from typing import TYPE_CHECKING, Any, Dict, List, Literal, Optional, Tuple
|
||||
|
||||
from django_auth_ldap.config import GroupOfUniqueNamesType, LDAPGroupType
|
||||
|
||||
from scripts.lib.zulip_tools import deport
|
||||
from zproject.settings_types import JwtAuthKey, OIDCIdPConfigDict, SAMLIdPConfigDict
|
||||
|
||||
@@ -69,6 +71,8 @@ AUTH_LDAP_ALWAYS_UPDATE_USER = False
|
||||
FAKE_LDAP_MODE: Optional[str] = None
|
||||
FAKE_LDAP_NUM_USERS = 8
|
||||
AUTH_LDAP_ADVANCED_REALM_ACCESS_CONTROL: Optional[Dict[str, Any]] = None
|
||||
LDAP_SYNCHRONIZED_GROUPS_BY_REALM: Dict[str, List[str]] = {}
|
||||
AUTH_LDAP_GROUP_TYPE: LDAPGroupType = GroupOfUniqueNamesType()
|
||||
|
||||
# Social auth; we support providing values for some of these
|
||||
# settings in zulip-secrets.conf instead of settings.py in development.
|
||||
|
||||
@@ -266,6 +266,20 @@ AUTH_LDAP_USER_ATTR_MAP = {
|
||||
# ]
|
||||
# }
|
||||
|
||||
|
||||
## LDAP group sync configuration.
|
||||
## See: https://zulip.readthedocs.io/en/latest/production/authentication-methods.html#synchronizing-groups
|
||||
# AUTH_LDAP_GROUP_TYPE = GroupOfUniqueNamesType()
|
||||
# AUTH_LDAP_GROUP_SEARCH = LDAPSearch(
|
||||
# "ou=groups,dc=www,dc=example,dc=com", ldap.SCOPE_SUBTREE, "(objectClass=groupOfUniqueNames)"
|
||||
# )
|
||||
# LDAP_SYNCHRONIZED_GROUPS_BY_REALM = {
|
||||
# "subdomain1" : [
|
||||
# "group1",
|
||||
# "group2",
|
||||
# ]
|
||||
# }
|
||||
|
||||
########
|
||||
## Google OAuth.
|
||||
##
|
||||
|
||||
Reference in New Issue
Block a user