diff --git a/api_docs/changelog.md b/api_docs/changelog.md index 7a40431142..690206ffe3 100644 --- a/api_docs/changelog.md +++ b/api_docs/changelog.md @@ -20,6 +20,12 @@ format used by the Zulip server that they are interacting with. ## Changes in Zulip 11.0 +**Feature level 377** + +* [`GET /events`](/api/get-events): When a user is deactivate, send + `peer_remove` event to all the subscribers of the streams that the + user was subscribed to. + Feature levels 373-376 reserved for future use in 10.x maintenance releases. diff --git a/version.py b/version.py index 253f8c8191..0d1528a401 100644 --- a/version.py +++ b/version.py @@ -34,7 +34,7 @@ DESKTOP_WARNING_VERSION = "5.9.3" # new level means in api_docs/changelog.md, as well as "**Changes**" # entries in the endpoint's documentation in `zulip.yaml`. -API_FEATURE_LEVEL = 372 # Last bumped to interpret "(no topic)" as empty string. +API_FEATURE_LEVEL = 377 # Last bumped to sending peer_remove on user deactivation. # Bump the minor PROVISION_VERSION to indicate that folks should provision # only when going from an old version of the code to a newer version. Bump diff --git a/zerver/actions/users.py b/zerver/actions/users.py index 2a9f0cccc2..c6474fcb86 100644 --- a/zerver/actions/users.py +++ b/zerver/actions/users.py @@ -13,6 +13,7 @@ from django.utils.http import urlsafe_base64_encode from django.utils.timezone import now as timezone_now from django.utils.translation import get_language +from zerver.actions.streams import send_peer_remove_events from zerver.actions.user_groups import ( do_send_user_group_members_update_event, update_users_in_full_members_system_group, @@ -383,6 +384,19 @@ def send_update_events_for_anonymous_group_settings( def send_events_for_user_deactivation(user_profile: UserProfile) -> None: + subscribed_streams = get_streams_for_user( + user_profile, + include_public=False, + include_subscribed=True, + ) + altered_user_dict: dict[int, set[int]] = defaultdict(set) + streams: list[Stream] = [] + for stream in subscribed_streams: + altered_user_dict[stream.id].add(user_profile.id) + streams.append(stream) + + send_peer_remove_events(user_profile.realm, streams, altered_user_dict) + event_deactivate_user = dict( type="realm_user", op="update", diff --git a/zerver/openapi/zulip.yaml b/zerver/openapi/zulip.yaml index 4ea27e954f..724b08a739 100644 --- a/zerver/openapi/zulip.yaml +++ b/zerver/openapi/zulip.yaml @@ -916,6 +916,16 @@ paths: The IDs of the channels from which the users have been unsubscribed from. + When a user is deactivated, the server will send this event + removing the user's subscriptions before the `realm_user` event + for the user's deactivation. + + **Changes**: Before Zulip 10.0 (feature level 377), this event + was not sent on user deactivation. Clients supporting older + server versions and maintaining peer subscriber data need to + remove all channel subscriptions for a user when processing the + `realm_user` event with `op="remove"`. + **Changes**: New in Zulip 4.0 (feature level 35), replacing the `stream_id` integer. items: diff --git a/zerver/tests/test_events.py b/zerver/tests/test_events.py index cc18c410af..6191ca26ff 100644 --- a/zerver/tests/test_events.py +++ b/zerver/tests/test_events.py @@ -1464,7 +1464,7 @@ class NormalActionsTest(BaseAction): invite_expires_in_minutes=invite_expires_in_minutes, ) - with self.verify_action(num_events=2) as events: + with self.verify_action(num_events=3) as events: do_deactivate_user(user_profile, acting_user=None) check_invites_changed("events[0]", events[0]) @@ -3484,9 +3484,10 @@ class NormalActionsTest(BaseAction): hamletcharacters_group, "can_mention_group", setting_group, acting_user=None ) - with self.verify_action(num_events=1) as events: + with self.verify_action(num_events=2) as events: do_deactivate_user(user_profile, acting_user=None) - check_realm_user_update("events[0]", events[0], "is_active") + check_subscription_peer_remove("events[0]", events[0]) + check_realm_user_update("events[1]", events[1], "is_active") do_reactivate_user(user_profile, acting_user=None) self.set_up_db_for_testing_user_access() @@ -3495,9 +3496,10 @@ class NormalActionsTest(BaseAction): # Test that users who can access the deactivated user # do not receive the 'user_group/remove_members' event. user_profile = self.example_user("cordelia") - with self.verify_action(num_events=1) as events: + with self.verify_action(num_events=2) as events: do_deactivate_user(user_profile, acting_user=None) - check_realm_user_update("events[0]", events[0], "is_active") + check_subscription_peer_remove("events[0]", events[0]) + check_realm_user_update("events[1]", events[1], "is_active") do_reactivate_user(user_profile, acting_user=None) @@ -3567,15 +3569,16 @@ class NormalActionsTest(BaseAction): # Guest loses access to deactivated user if the user # was not involved in DMs. user_profile = self.example_user("hamlet") - with self.verify_action(num_events=5) as events: + with self.verify_action(num_events=6) as events: do_deactivate_user(user_profile, acting_user=None) - check_user_group_remove_members("events[0]", events[0]) + check_subscription_peer_remove("events[0]", events[0]) check_user_group_remove_members("events[1]", events[1]) check_user_group_remove_members("events[2]", events[2]) - check_user_group_update("events[3]", events[3], {"can_mention_group"}) - check_realm_user_remove("events[4]]", events[4]) + check_user_group_remove_members("events[3]", events[3]) + check_user_group_update("events[4]", events[4], {"can_mention_group"}) + check_realm_user_remove("events[5]]", events[5]) self.assertEqual( - events[3]["data"]["can_mention_group"], + events[4]["data"]["can_mention_group"], UserGroupMembersDict(direct_members=[], direct_subgroups=[members_group.id]), )