zerver: Implement ldap group synchronization.

Fixes #9957.

Co-authored-by: Mateusz Mandera <mateusz.mandera@zulip.com>
This commit is contained in:
Simon Michalke
2023-07-15 22:25:36 +02:00
committed by Tim Abbott
parent 2bfbbf0035
commit b6a25840a1
6 changed files with 309 additions and 0 deletions

View File

@@ -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)