Repository: matrix-org/synapse

Issue:
Invites from ignored users may not be ignored
Midway down the digestive system of Synapse's sync code, we fetch all membership changes for a user across the sync period.

After 1709, `event` will be the last element in the list of events, or it'll be unset if there were no such events. They're fetched in ascending stream ordering order, so `event` will be the membership change with highest stream ordering.

https://github.com/matrix-org/synapse/blob/d26808dd854006bd26a2366c675428ce0737238c/synapse/handlers/sync.py#L1703-L1709

There are no other writes to `event`, but there is a read:

https://github.com/matrix-org/synapse/blob/d26808dd854006bd26a2366c675428ce0737238c/synapse/handlers/sync.py#L1794-L1800

I smell a bug. 

- Suppose my `membership_change_events` consists of three events in this order:
   - I'm invited to room A by someone I've ignored
   - I'm invited to room B by someone I've not ignord.
- `event` will be the invite event in room B.
- mem_change_events_by_room_id will be the dictionary `{A: [invite_A], B: [invite_B]}`.
- We start looping over the keys of this dictionary.
- When I process room A, we test `event.sender not in ignored users`
- **`event` refers `invite_B`, whose sender is not ignored. We then push invite_A down to the user.

Well this is just embarrassing. Should be an easy fix though.


Discussion:
Note the [spec](https://spec.matrix.org/v1.1/client-server-api/#client-behaviour-22) says:

> Invites to new rooms by ignored users will not be sent to the client.

Resolving patch:
diff --git a/synapse/handlers/sync.py b/synapse/handlers/sync.py
--- a/synapse/handlers/sync.py
+++ b/synapse/handlers/sync.py
@@ -1771,6 +1771,7 @@ async def _get_rooms_changed(
 
             if not non_joins:
                 continue
+            last_non_join = non_joins[-1]
 
             # Check if we have left the room. This can either be because we were
             # joined before *or* that we since joined and then left.
@@ -1792,18 +1793,18 @@ async def _get_rooms_changed(
                         newly_left_rooms.append(room_id)
 
             # Only bother if we're still currently invited
-            should_invite = non_joins[-1].membership == Membership.INVITE
+            should_invite = last_non_join.membership == Membership.INVITE
             if should_invite:
-                if event.sender not in ignored_users:
-                    invite_room_sync = InvitedSyncResult(room_id, invite=non_joins[-1])
+                if last_non_join.sender not in ignored_users:
+                    invite_room_sync = InvitedSyncResult(room_id, invite=last_non_join)
                     if invite_room_sync:
                         invited.append(invite_room_sync)
 
             # Only bother if our latest membership in the room is knock (and we haven't
             # been accepted/rejected in the meantime).
-            should_knock = non_joins[-1].membership == Membership.KNOCK
+            should_knock = last_non_join.membership == Membership.KNOCK
             if should_knock:
-                knock_room_sync = KnockedSyncResult(room_id, knock=non_joins[-1])
+                knock_room_sync = KnockedSyncResult(room_id, knock=last_non_join)
                 if knock_room_sync:
                     knocked.append(knock_room_sync)
 


Test patch: