streams: Send event when guest loses access to a user.

This commit adds code to send "realm_user/remove" event
when a guest user loses access to a user due to the user
being unsubscribed from one or more streams.
This commit is contained in:
Sahil Batra 2023-10-24 08:41:34 +05:30 committed by Tim Abbott
parent d394cfc4db
commit 39a31170ee
7 changed files with 111 additions and 2 deletions

View File

@ -41,6 +41,9 @@ format used by the Zulip server that they are interacting with.
* [`GET /events`](/api/get-events): `realm_user` events with `op: "add"` * [`GET /events`](/api/get-events): `realm_user` events with `op: "add"`
are now also sent when a guest user gains access to a user. are now also sent when a guest user gains access to a user.
* [`GET /events`](/api/get-events): `realm_user` events with `op: "remove"`
are now also sent when a guest user loses access to a user.
**Feature level 227** **Feature level 227**
* [`PATCH /realm/user_settings_defaults`](/api/update-realm-user-settings-defaults), * [`PATCH /realm/user_settings_defaults`](/api/update-realm-user-settings-defaults),

View File

@ -33,7 +33,7 @@ DESKTOP_WARNING_VERSION = "5.9.3"
# Changes should be accompanied by documentation explaining what the # Changes should be accompanied by documentation explaining what the
# new level means in api_docs/changelog.md, as well as "**Changes**" # new level means in api_docs/changelog.md, as well as "**Changes**"
# entries in the endpoint's documentation in `zulip.yaml`. # entries in the endpoint's documentation in `zulip.yaml`.
API_FEATURE_LEVEL = 227 API_FEATURE_LEVEL = 228
# Bump the minor PROVISION_VERSION to indicate that folks should provision # 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 # only when going from an old version of the code to a newer version. Bump

View File

@ -34,6 +34,7 @@ from zerver.lib.stream_subscription import (
get_active_subscriptions_for_stream_id, get_active_subscriptions_for_stream_id,
get_bulk_stream_subscriber_info, get_bulk_stream_subscriber_info,
get_used_colors_for_user_ids, get_used_colors_for_user_ids,
get_user_ids_for_streams,
get_users_for_streams, get_users_for_streams,
) )
from zerver.lib.stream_traffic import get_streams_traffic from zerver.lib.stream_traffic import get_streams_traffic
@ -896,6 +897,67 @@ def send_subscription_remove_events(
) )
def send_user_remove_events_on_removing_subscriptions(
realm: Realm, altered_user_dict: Dict[UserProfile, Set[int]]
) -> None:
altered_stream_ids: Set[int] = set()
altered_users = list(altered_user_dict.keys())
for stream_ids in altered_user_dict.values():
altered_stream_ids |= stream_ids
users_involved_in_dms = get_users_involved_in_dms_with_target_users(altered_users, realm)
subscribers_of_altered_user_subscriptions = get_subscribers_of_target_user_subscriptions(
altered_users
)
non_guest_user_ids = active_non_guest_user_ids(realm.id)
subscribers_dict = get_user_ids_for_streams(altered_stream_ids)
for user in altered_users:
users_in_unsubscribed_streams: Set[int] = set()
for stream_id in altered_user_dict[user]:
users_in_unsubscribed_streams = (
users_in_unsubscribed_streams | subscribers_dict[stream_id]
)
users_who_can_access_altered_user = (
set(non_guest_user_ids)
| subscribers_of_altered_user_subscriptions[user.id]
| users_involved_in_dms[user.id]
| {user.id}
)
subscribers_without_access_to_altered_user = (
users_in_unsubscribed_streams - users_who_can_access_altered_user
)
if subscribers_without_access_to_altered_user:
event_remove_user = dict(
type="realm_user",
op="remove",
person=dict(user_id=user.id, full_name=str(UserProfile.INACCESSIBLE_USER_NAME)),
)
send_event_on_commit(
realm, event_remove_user, list(subscribers_without_access_to_altered_user)
)
if user.is_guest:
users_inaccessible_to_altered_user = users_in_unsubscribed_streams - (
subscribers_of_altered_user_subscriptions[user.id]
| users_involved_in_dms[user.id]
| {user.id}
)
for user_id in users_inaccessible_to_altered_user:
event_remove_user = dict(
type="realm_user",
op="remove",
person=dict(user_id=user_id, full_name=str(UserProfile.INACCESSIBLE_USER_NAME)),
)
send_event_on_commit(realm, event_remove_user, [user.id])
def bulk_remove_subscriptions( def bulk_remove_subscriptions(
realm: Realm, realm: Realm,
users: Iterable[UserProfile], users: Iterable[UserProfile],
@ -978,6 +1040,12 @@ def bulk_remove_subscriptions(
removed_sub_tuples = [(sub_info.user, sub_info.stream) for sub_info in subs_to_deactivate] removed_sub_tuples = [(sub_info.user, sub_info.stream) for sub_info in subs_to_deactivate]
send_subscription_remove_events(realm, users, streams, removed_sub_tuples) send_subscription_remove_events(realm, users, streams, removed_sub_tuples)
if realm.can_access_all_users_group.name != SystemGroups.EVERYONE:
altered_user_dict: Dict[UserProfile, Set[int]] = defaultdict(set)
for user, stream in removed_sub_tuples:
altered_user_dict[user].add(stream.id)
send_user_remove_events_on_removing_subscriptions(realm, altered_user_dict)
new_vacant_streams = set(streams_to_unsubscribe) - set(occupied_streams_after) new_vacant_streams = set(streams_to_unsubscribe) - set(occupied_streams_after)
new_vacant_private_streams = [stream for stream in new_vacant_streams if stream.invite_only] new_vacant_private_streams = [stream for stream in new_vacant_streams if stream.invite_only]

View File

@ -1206,6 +1206,16 @@ def check_realm_user_update(
) )
realm_user_remove_event = event_dict_type(
required_keys=[
("type", Equals("realm_user")),
("op", Equals("remove")),
("person", removed_user_type),
],
)
check_realm_user_remove = make_checker(realm_user_remove_event)
restart_event = event_dict_type( restart_event = event_dict_type(
required_keys=[ required_keys=[
("type", Equals("restart")), ("type", Equals("restart")),

View File

@ -63,6 +63,7 @@ from zerver.lib.user_status import get_user_status_dict
from zerver.lib.user_topics import get_topic_mutes, get_user_topics from zerver.lib.user_topics import get_topic_mutes, get_user_topics
from zerver.lib.users import ( from zerver.lib.users import (
get_cross_realm_dicts, get_cross_realm_dicts,
get_data_for_inaccessible_user,
get_users_for_api, get_users_for_api,
is_administrator_role, is_administrator_role,
max_message_id_for_user, max_message_id_for_user,
@ -989,6 +990,12 @@ def apply_event(
sub["subscribers"] = [ sub["subscribers"] = [
user_id for user_id in sub["subscribers"] if user_id != person_user_id user_id for user_id in sub["subscribers"] if user_id != person_user_id
] ]
elif event["op"] == "remove":
if person_user_id in state["raw_users"]:
inaccessible_user_dict = get_data_for_inaccessible_user(
user_profile.realm, person_user_id
)
state["raw_users"][person_user_id] = inaccessible_user_dict
else: else:
raise AssertionError("Unexpected event type {type}/{op}".format(**event)) raise AssertionError("Unexpected event type {type}/{op}".format(**event))
elif event["type"] == "realm_bot": elif event["type"] == "realm_bot":

View File

@ -1085,7 +1085,9 @@ paths:
} }
- type: object - type: object
description: | description: |
No longer sent by modern Zulip servers. Event sent to guest users when they lose access to a user.
**Changes**: Prior to Zulip 8.0 (feature level 228), this event was deprecated.
**Changes**: Deprecated and no longer sent since Zulip 8.0 (feature level 222). **Changes**: Deprecated and no longer sent since Zulip 8.0 (feature level 222).

View File

@ -166,6 +166,7 @@ from zerver.lib.event_schema import (
check_realm_update, check_realm_update,
check_realm_update_dict, check_realm_update_dict,
check_realm_user_add, check_realm_user_add,
check_realm_user_remove,
check_realm_user_update, check_realm_user_update,
check_scheduled_message_add, check_scheduled_message_add,
check_scheduled_message_remove, check_scheduled_message_remove,
@ -4078,6 +4079,15 @@ class SubscribeActionTest(BaseAction):
check_subscription_peer_add("events[1]", events[1]) check_subscription_peer_add("events[1]", events[1])
self.assertEqual(set(events[1]["user_ids"]), {iago.id, othello.id}) self.assertEqual(set(events[1]["user_ids"]), {iago.id, othello.id})
unsubscribe_action = lambda: bulk_remove_subscriptions(
realm, [othello, iago], [stream], acting_user=None
)
events = self.verify_action(unsubscribe_action, num_events=2)
check_subscription_peer_remove("events[0]", events[0])
self.assertEqual(set(events[0]["user_ids"]), {iago.id, othello.id})
check_realm_user_remove("events[1]", events[1])
self.assertEqual(events[1]["person"]["user_id"], othello.id)
def test_user_access_events_on_changing_subscriptions_for_guests(self) -> None: def test_user_access_events_on_changing_subscriptions_for_guests(self) -> None:
self.set_up_db_for_testing_user_access() self.set_up_db_for_testing_user_access()
polonius = self.example_user("polonius") polonius = self.example_user("polonius")
@ -4094,6 +4104,15 @@ class SubscribeActionTest(BaseAction):
check_realm_user_add("events[2]", events[2]) check_realm_user_add("events[2]", events[2])
self.assertEqual(events[2]["person"]["user_id"], othello.id) self.assertEqual(events[2]["person"]["user_id"], othello.id)
unsubscribe_action = lambda: bulk_remove_subscriptions(
realm, [polonius, self.example_user("iago")], [stream], acting_user=None
)
events = self.verify_action(unsubscribe_action, num_events=3)
check_subscription_remove("events[0]", events[0])
check_stream_delete("events[1]", events[1])
check_realm_user_remove("events[2]", events[2])
self.assertEqual(events[2]["person"]["user_id"], othello.id)
class DraftActionTest(BaseAction): class DraftActionTest(BaseAction):
def do_enable_drafts_synchronization(self, user_profile: UserProfile) -> None: def do_enable_drafts_synchronization(self, user_profile: UserProfile) -> None: