Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Sliding sync: Ignore invites from ignored users #17729

Merged
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions changelog.d/17729.bugfix
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Ignore invites from ignored users in Sliding Sync.
30 changes: 29 additions & 1 deletion synapse/handlers/sliding_sync/room_lists.py
Original file line number Diff line number Diff line change
Expand Up @@ -224,15 +224,31 @@ async def _compute_interested_rooms_new_tables(
user_id
)

# Remove invites from ignored users
ignored_users = await self.store.ignored_users(user_id)
if ignored_users:
# TODO: It would be nice to avoid these copies
room_membership_for_user_map = dict(room_membership_for_user_map)
Comment on lines +230 to +231
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another reason to potentially add SoftDeleteChainMap (context #17725 (comment))

Would tackle in a separate PR though.

# Make a copy so we don't run into an error: `dictionary changed size during
# iteration`, when we remove items
for room_id in list(room_membership_for_user_map.keys()):
room_for_user_sliding_sync = room_membership_for_user_map[room_id]
if (
room_for_user_sliding_sync.membership == Membership.INVITE
and room_for_user_sliding_sync.sender in ignored_users
):
room_membership_for_user_map.pop(room_id, None)

changes = await self._get_rewind_changes_to_current_membership_to_token(
sync_config.user, room_membership_for_user_map, to_token=to_token
)
if changes:
# TODO: It would be nice to avoid these copies
room_membership_for_user_map = dict(room_membership_for_user_map)
for room_id, change in changes.items():
if change is None:
# Remove rooms that the user joined after the `to_token`
room_membership_for_user_map.pop(room_id)
room_membership_for_user_map.pop(room_id, None)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unrelated but added a default to the .pop(...) so we don't run into a KeyError if the room_id is already removed from the dict.

continue

existing_room = room_membership_for_user_map.get(room_id)
Expand Down Expand Up @@ -926,6 +942,18 @@ async def get_room_membership_for_user_at_to_token(
excluded_rooms=self.rooms_to_exclude_globally,
)

# Remove invites from ignored users
ignored_users = await self.store.ignored_users(user_id)
if ignored_users:
room_for_user_list = [
room_for_user
for room_for_user in room_for_user_list
if not (
room_for_user.membership == Membership.INVITE
and room_for_user.sender in ignored_users
)
]

# If the user has never joined any rooms before, we can just return an empty list
if not room_for_user_list:
return {}, set(), set()
Expand Down
113 changes: 112 additions & 1 deletion tests/rest/client/sliding_sync/test_sliding_sync.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
from synapse.api.room_versions import RoomVersions
from synapse.events import EventBase, StrippedStateEvent, make_event_from_dict
from synapse.events.snapshot import EventContext
from synapse.rest.client import devices, login, receipts, room, sync
from synapse.rest.client import account_data, devices, login, receipts, room, sync
from synapse.server import HomeServer
from synapse.types import (
JsonDict,
Expand Down Expand Up @@ -413,6 +413,7 @@ class SlidingSyncTestCase(SlidingSyncBase):
sync.register_servlets,
devices.register_servlets,
receipts.register_servlets,
account_data.register_servlets,
]

def prepare(self, reactor: MemoryReactor, clock: Clock, hs: HomeServer) -> None:
Expand Down Expand Up @@ -670,6 +671,116 @@ def test_forgotten_up_to_date(self) -> None:
exact=True,
)

def test_ignored_user_invites_initial_sync(self) -> None:
"""
Make sure we ignore invites if they are from one of the `m.ignored_user_list` on
initial sync.
"""
user1_id = self.register_user("user1", "pass")
user1_tok = self.login(user1_id, "pass")
user2_id = self.register_user("user2", "pass")
user2_tok = self.login(user2_id, "pass")

# Create a room that user1 is already in
room_id1 = self.helper.create_room_as(user1_id, tok=user1_tok)

# Create a room that user2 is already in
room_id2 = self.helper.create_room_as(user2_id, tok=user2_tok)

# User1 is invited to room_id2
self.helper.invite(room_id2, src=user2_id, targ=user1_id, tok=user2_tok)

# Sync once before we ignore to make sure the rooms can show up
sync_body = {
"lists": {
"foo-list": {
"ranges": [[0, 99]],
"required_state": [],
"timeline_limit": 0,
},
}
}
response_body, _ = self.do_sync(sync_body, tok=user1_tok)
# room_id2 shows up because we haven't ignored the user yet
self.assertIncludes(
set(response_body["lists"]["foo-list"]["ops"][0]["room_ids"]),
{room_id1, room_id2},
exact=True,
)

# User1 ignores user2
channel = self.make_request(
"PUT",
f"/_matrix/client/v3/user/{user1_id}/account_data/{AccountDataTypes.IGNORED_USER_LIST}",
content={"ignored_users": {user2_id: {}}},
access_token=user1_tok,
)
self.assertEqual(channel.code, 200, channel.result)

# Sync again (initial sync)
response_body, _ = self.do_sync(sync_body, tok=user1_tok)
# The invite for room_id2 should no longer show up because user2 is ignored
self.assertIncludes(
set(response_body["lists"]["foo-list"]["ops"][0]["room_ids"]),
{room_id1},
exact=True,
)

def test_ignored_user_invites_incremental_sync(self) -> None:
"""
Make sure we ignore invites if they are from one of the `m.ignored_user_list` on
incremental sync.
"""
user1_id = self.register_user("user1", "pass")
user1_tok = self.login(user1_id, "pass")
user2_id = self.register_user("user2", "pass")
user2_tok = self.login(user2_id, "pass")

# Create a room that user1 is already in
room_id1 = self.helper.create_room_as(user1_id, tok=user1_tok)

# Create a room that user2 is already in
room_id2 = self.helper.create_room_as(user2_id, tok=user2_tok)

# User1 ignores user2
channel = self.make_request(
"PUT",
f"/_matrix/client/v3/user/{user1_id}/account_data/{AccountDataTypes.IGNORED_USER_LIST}",
content={"ignored_users": {user2_id: {}}},
access_token=user1_tok,
)
self.assertEqual(channel.code, 200, channel.result)

# Sync once before we ignore to make sure the rooms can show up
MadLittleMods marked this conversation as resolved.
Show resolved Hide resolved
sync_body = {
"lists": {
"foo-list": {
"ranges": [[0, 99]],
"required_state": [],
"timeline_limit": 0,
},
}
}
response_body, from_token = self.do_sync(sync_body, tok=user1_tok)
# User1 only has membership in room_id1 at this point
self.assertIncludes(
set(response_body["lists"]["foo-list"]["ops"][0]["room_ids"]),
{room_id1},
exact=True,
)

# User1 is invited to room_id2 after the initial sync
self.helper.invite(room_id2, src=user2_id, targ=user1_id, tok=user2_tok)

# Sync again (incremental sync)
response_body, _ = self.do_sync(sync_body, since=from_token, tok=user1_tok)
# The invite for room_id2 doesn't show up because user2 is ignored
self.assertIncludes(
set(response_body["lists"]["foo-list"]["ops"][0]["room_ids"]),
{room_id1},
exact=True,
)

def test_sort_list(self) -> None:
"""
Test that the `lists` are sorted by `stream_ordering`
Expand Down
Loading