Fix rejecting invite when no_longer_in_room (and other non-join transitions)

This commit is contained in:
Eric Eastwood
2024-08-13 14:35:24 -05:00
parent a90f3d4ae2
commit 32ae162278
2 changed files with 155 additions and 60 deletions
+79 -60
View File
@@ -1199,7 +1199,7 @@ class PersistEventsStore:
# We handle `sliding_sync_membership_snapshots` before `current_state_events` so
# we can gather the current state before it might be deleted if we are
# `no_longer_in_room`.
# last ones in the room and now we are `no_longer_in_room`.
#
# We do this regardless of whether the server is `no_longer_in_room` or not
# because we still want a row if a local user was just left/kicked or got banned
@@ -1211,11 +1211,6 @@ class PersistEventsStore:
membership_event_id_to_user_id_map[event_id] = state_key[1]
if len(membership_event_id_to_user_id_map) > 0:
# Map of values to insert/update in the `sliding_sync_membership_snapshots` table
sliding_sync_membership_snapshots_insert_map: Dict[
str, Optional[Union[str, bool]]
] = {}
relevant_state_set = {
(EventTypes.Create, ""),
(EventTypes.RoomEncryption, ""),
@@ -1256,55 +1251,80 @@ class PersistEventsStore:
if state_key in relevant_state_set:
current_state_map[state_key] = event_id
# Fetch the raw event JSON from the database
(
event_id_in_list_clause,
event_id_args,
) = make_in_list_sql_clause(
self.database_engine,
"event_id",
current_state_map.values(),
)
txn.execute(
f"""
SELECT event_id, type, state_key, json FROM event_json
INNER JOIN events USING (event_id)
WHERE {event_id_in_list_clause}
""",
event_id_args,
)
# Map of values to insert/update in the `sliding_sync_membership_snapshots` table
sliding_sync_membership_snapshots_insert_map: Dict[
str, Optional[Union[str, bool]]
] = {}
if current_state_map:
# We have current state to work from
sliding_sync_membership_snapshots_insert_map["has_known_state"] = (
True
)
# Parse the raw event JSON
for row in txn:
event_id, event_type, state_key, json = row
event_json = db_to_json(json)
# Fetch the raw event JSON from the database
(
event_id_in_list_clause,
event_id_args,
) = make_in_list_sql_clause(
self.database_engine,
"event_id",
current_state_map.values(),
)
txn.execute(
f"""
SELECT event_id, type, state_key, json FROM event_json
INNER JOIN events USING (event_id)
WHERE {event_id_in_list_clause}
""",
event_id_args,
)
if event_type == EventTypes.Create:
room_type = event_json.get("content", {}).get(
EventContentFields.ROOM_TYPE
)
sliding_sync_membership_snapshots_insert_map["room_type"] = (
room_type
)
elif event_type == EventTypes.RoomEncryption:
encryption_algorithm = event_json.get("content", {}).get(
EventContentFields.ENCRYPTION_ALGORITHM
)
is_encrypted = encryption_algorithm is not None
sliding_sync_membership_snapshots_insert_map["is_encrypted"] = (
is_encrypted
)
elif event_type == EventTypes.Name:
room_name = event_json.get("content", {}).get(
EventContentFields.ROOM_NAME
)
sliding_sync_membership_snapshots_insert_map["room_name"] = (
room_name
)
else:
raise AssertionError(
f"Unexpected event (we should not be fetching extra events): ({event_type}, {state_key})"
)
# Parse the raw event JSON
for row in txn:
event_id, event_type, state_key, json = row
event_json = db_to_json(json)
if event_type == EventTypes.Create:
room_type = event_json.get("content", {}).get(
EventContentFields.ROOM_TYPE
)
sliding_sync_membership_snapshots_insert_map[
"room_type"
] = room_type
elif event_type == EventTypes.RoomEncryption:
encryption_algorithm = event_json.get("content", {}).get(
EventContentFields.ENCRYPTION_ALGORITHM
)
is_encrypted = encryption_algorithm is not None
sliding_sync_membership_snapshots_insert_map[
"is_encrypted"
] = is_encrypted
elif event_type == EventTypes.Name:
room_name = event_json.get("content", {}).get(
EventContentFields.ROOM_NAME
)
sliding_sync_membership_snapshots_insert_map[
"room_name"
] = room_name
else:
raise AssertionError(
f"Unexpected event (we should not be fetching extra events): ({event_type}, {state_key})"
)
else:
# We don't have any `current_state_events` anymore (previously
# cleared out because of `no_longer_in_room`). This can happen if
# one user is joined and another is invited (some non-join
# membership). If the joined user leaves, we are `no_longer_in_room`
# and `current_state_events` is cleared out. When the invited user
# rejects the invite (leaves the room), we will end up here.
#
# In these cases, we should inherit the meta data from the previous
# snapshot. When using sliding sync filters, this will prevent the
# room from disappearing/appearing just because you left the room.
#
# Ideally, we could additionally assert that we're only here for
# valid non-join membership transitions.
assert delta_state.no_longer_in_room
# Update the `sliding_sync_membership_snapshots` table
#
@@ -1315,20 +1335,20 @@ class PersistEventsStore:
txn.execute_batch(
f"""
INSERT INTO sliding_sync_membership_snapshots
(room_id, user_id, membership_event_id, membership, event_stream_ordering, has_known_state, {", ".join(insert_keys)})
(room_id, user_id, membership_event_id, membership, event_stream_ordering
{"," + (", ".join(insert_keys)) if insert_keys else ""})
VALUES (
?, ?, ?,
(SELECT membership FROM room_memberships WHERE event_id = ?),
(SELECT stream_ordering FROM events WHERE event_id = ?),
?,
{", ".join("?" for _ in insert_values)}
(SELECT stream_ordering FROM events WHERE event_id = ?)
{"," + (", ".join("?" for _ in insert_values)) if insert_values else ""}
)
ON CONFLICT (room_id, user_id)
DO UPDATE SET
membership_event_id = EXCLUDED.membership_event_id,
membership = EXCLUDED.membership,
event_stream_ordering = EXCLUDED.event_stream_ordering,
{", ".join(f"{key} = EXCLUDED.{key}" for key in insert_keys)}
event_stream_ordering = EXCLUDED.event_stream_ordering
{"," + (", ".join(f"{key} = EXCLUDED.{key}" for key in insert_keys)) if insert_keys else ""}
""",
[
[
@@ -1337,7 +1357,6 @@ class PersistEventsStore:
membership_event_id,
membership_event_id,
membership_event_id,
True, # has_known_state
]
+ list(insert_values)
for membership_event_id, user_id in membership_event_id_to_user_id_map.items()
+76
View File
@@ -1553,6 +1553,82 @@ class SlidingSyncPrePopulatedTablesTestCase(HomeserverTestCase):
),
)
def test_non_join_reject_invite_empty_room(self) -> None:
"""
In a room where no one is joined (`no_longer_in_room`), test rejecting an invite.
"""
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")
room_id1 = self.helper.create_room_as(user2_id, tok=user2_tok)
# User1 is invited to the room
self.helper.invite(room_id1, src=user2_id, targ=user1_id, tok=user2_tok)
# User2 leaves the room
user2_leave_response = self.helper.leave(room_id1, user2_id, tok=user2_tok)
user2_leave_event_pos = self.get_success(
self.store.get_position_for_event(user2_leave_response["event_id"])
)
# User1 rejects the invite
user1_leave_response = self.helper.leave(room_id1, user1_id, tok=user1_tok)
user1_leave_event_pos = self.get_success(
self.store.get_position_for_event(user1_leave_response["event_id"])
)
# No one is joined to the room
sliding_sync_joined_rooms_results = self._get_sliding_sync_joined_rooms()
self.assertIncludes(
set(sliding_sync_joined_rooms_results.keys()),
set(),
exact=True,
)
sliding_sync_membership_snapshots_results = (
self._get_sliding_sync_membership_snapshots()
)
self.assertIncludes(
set(sliding_sync_membership_snapshots_results.keys()),
{
(room_id1, user1_id),
(room_id1, user2_id),
},
exact=True,
)
# Holds the info according to the current state when the user left
self.assertEqual(
sliding_sync_membership_snapshots_results.get((room_id1, user1_id)),
_SlidingSyncMembershipSnapshotResult(
room_id=room_id1,
user_id=user1_id,
membership_event_id=user1_leave_response["event_id"],
membership=Membership.LEAVE,
event_stream_ordering=user1_leave_event_pos.stream,
has_known_state=True,
room_type=None,
room_name=None,
is_encrypted=False,
),
)
# Holds the info according to the current state when the left
self.assertEqual(
sliding_sync_membership_snapshots_results.get((room_id1, user2_id)),
_SlidingSyncMembershipSnapshotResult(
room_id=room_id1,
user_id=user2_id,
membership_event_id=user2_leave_response["event_id"],
membership=Membership.LEAVE,
event_stream_ordering=user2_leave_event_pos.stream,
has_known_state=True,
room_type=None,
room_name=None,
is_encrypted=False,
),
)
def test_membership_changing(self) -> None:
"""
Test latest snapshot evolves when membership changes (`sliding_sync_membership_snapshots`).