From e393b282dfb9a46ff3c9b19bcc13097ef64e18da Mon Sep 17 00:00:00 2001 From: Jason Robinson Date: Tue, 21 Jul 2026 23:21:33 +0300 Subject: [PATCH] Limit initial sync response to rooms within the scope of the sliding sync request --- synapse/handlers/sliding_sync/extensions.py | 5 +- synapse/storage/databases/main/roommember.py | 18 ++++- .../sliding_sync/test_extension_profiles.py | 74 +++++++++++++++++++ 3 files changed, 94 insertions(+), 3 deletions(-) diff --git a/synapse/handlers/sliding_sync/extensions.py b/synapse/handlers/sliding_sync/extensions.py index 34d96df8ba..bfed95099f 100644 --- a/synapse/handlers/sliding_sync/extensions.py +++ b/synapse/handlers/sliding_sync/extensions.py @@ -1082,6 +1082,7 @@ class SlidingSyncExtensionHandler: self, user_id: UserID, fields: set[str], + rooms: set[str], ) -> dict[str, JsonDict | None]: """ Build an initial sync response for the profiles extension. @@ -1089,15 +1090,16 @@ class SlidingSyncExtensionHandler: Args: user_id: The syncing user UserID fields: A set of fields to include in the response. + rooms: A set of rooms to limit the user profiles for. Returns: A dictionary containing the profile updates in an `updated` dictionary. """ response: dict[str, JsonDict | None] = {} - # TODO should be filtered for the rooms for this sync profile_user_ids = await self.store.get_local_users_who_share_room_with_user( user_id.to_string(), + limit_to_rooms=rooms, ) # Ensure we're in the list even if we don't belong to any rooms profile_user_ids.add(user_id.to_string()) @@ -1164,6 +1166,7 @@ class SlidingSyncExtensionHandler: users=await self._get_profiles_extension_initial_sync_response( user_id=sync_config.user, fields=fields, + rooms=all_interested_room_ids, ), ) diff --git a/synapse/storage/databases/main/roommember.py b/synapse/storage/databases/main/roommember.py index 952de88908..8ab2e739a3 100644 --- a/synapse/storage/databases/main/roommember.py +++ b/synapse/storage/databases/main/roommember.py @@ -1035,12 +1035,26 @@ class RoomMemberWorkerStore(EventsWorkerStore, CacheInvalidationWorkerStore): return user_who_share_room - async def get_local_users_who_share_room_with_user(self, user_id: str) -> set[str]: - """Returns the set of local users who share a room with `user_id`. + async def get_local_users_who_share_room_with_user( + self, + user_id: str, + limit_to_rooms: set[str] | None = None, + ) -> set[str]: + """ + Returns the set of local users who share a room with `user_id`. This also includes the `user_id` themselves. + + Args: + user_id: The user ID to find the local users who share rooms. + limit_to_rooms: Optional set of rooms to limit to. + + Returns: + Set of local user ID's who share a room with the given user. """ room_ids = await self.get_rooms_for_user(user_id) + if limit_to_rooms: + room_ids = room_ids.intersection(limit_to_rooms) user_who_share_room: set[str] = set() for room_id in room_ids: diff --git a/tests/rest/client/sliding_sync/test_extension_profiles.py b/tests/rest/client/sliding_sync/test_extension_profiles.py index 829af6daf4..e7405e3c33 100644 --- a/tests/rest/client/sliding_sync/test_extension_profiles.py +++ b/tests/rest/client/sliding_sync/test_extension_profiles.py @@ -292,6 +292,80 @@ class SlidingSyncProfilesTestCase(SlidingSyncBase): response_body["extensions"].get("org.matrix.msc4262.profiles") ) + @parameterized.expand( + [ + True, + False, + ] + ) + @override_config({"include_profile_updates_in_sync": True}) + def test_updated_fields_are_not_if_not_in_requested_rooms( + self, is_initial: bool + ) -> None: + """ + Test that profile extension response respects the room subscriptions, by: + * for initial sync returning updates for only those users in the given rooms + * for incremental sync returning all updates in shared rooms + """ + new_room = self.helper.create_room_as(self.user, tok=self.tok) + if is_initial: + self.get_success( + self.profile_handler.set_field( + target_user=UserID.from_string(self.other_user), + requester=create_requester(self.other_user), + field_name="field", + new_value="value", + ) + ) + # Make an initial Sliding Sync request with the profiles extension enabled + sync_body = { + "lists": {}, + "room_subscriptions": { + new_room: { + "required_state": [], + "timeline_limit": 10, + }, + }, + "extensions": { + "org.matrix.msc4262.profiles": { + "enabled": True, + "fields": ["field"], + }, + }, + } + response_body, from_token = self.do_sync(sync_body, tok=self.tok) + if is_initial: + # Nothing returned since even though user and other_user share a room, + # we didn't ask for that room. + self.assertIsNone( + response_body["extensions"].get("org.matrix.msc4262.profiles") + ) + + if not is_initial: + self.get_success( + self.profile_handler.set_field( + target_user=UserID.from_string(self.other_user), + requester=create_requester(self.other_user), + field_name="field", + new_value="value", + ) + ) + # Make an incremental Sliding Sync request + response_body, _ = self.do_sync(sync_body, since=from_token, tok=self.tok) + # Even though we only asked for a room other_user is not in, + # since these users share a room, updates are always sent via incremental + # sync. + self.assertEqual( + response_body["extensions"]["org.matrix.msc4262.profiles"]["users"][ + "@other_user:test" + ], + { + "updated": { + "field": "value", + } + }, + ) + @parameterized.expand( [ True,