diff --git a/synapse/storage/databases/main/events.py b/synapse/storage/databases/main/events.py index ae43ad0fc6..984a54bf2e 100644 --- a/synapse/storage/databases/main/events.py +++ b/synapse/storage/databases/main/events.py @@ -42,11 +42,17 @@ import attr from prometheus_client import Counter import synapse.metrics -from synapse.api.constants import EventContentFields, EventTypes, RelationTypes +from synapse.api.constants import ( + EventContentFields, + EventTypes, + Membership, + RelationTypes, +) from synapse.api.errors import PartialStateConflictError from synapse.api.room_versions import RoomVersions -from synapse.events import EventBase, relation_from_event +from synapse.events import EventBase, StrippedStateEvent, relation_from_event from synapse.events.snapshot import EventContext +from synapse.events.utils import parse_stripped_state_event from synapse.logging.opentracing import trace from synapse.storage._base import db_to_json, make_in_list_sql_clause from synapse.storage.database import ( @@ -1312,11 +1318,12 @@ class PersistEventsStore: txn.execute_batch( f""" INSERT INTO sliding_sync_membership_snapshots - (room_id, user_id, membership_event_id, membership, event_stream_ordering, {", ".join(insert_keys)}) + (room_id, user_id, membership_event_id, membership, event_stream_ordering, has_known_state, {", ".join(insert_keys)}) VALUES ( ?, ?, ?, (SELECT membership FROM room_memberships WHERE event_id = ?), (SELECT stream_ordering FROM events WHERE event_id = ?), + ?, {", ".join("?" for _ in insert_values)} ) ON CONFLICT (room_id, user_id) @@ -1333,6 +1340,7 @@ 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() @@ -2304,6 +2312,7 @@ class PersistEventsStore: ) for event in events: + # Sanity check that we're working with persisted events assert event.internal_metadata.stream_ordering is not None # We update the local_current_membership table only if the event is @@ -2318,6 +2327,16 @@ class PersistEventsStore: and event.internal_metadata.is_outlier() and event.internal_metadata.is_out_of_band_membership() ): + # The only sort of out-of-band-membership events we expect to see here + # are remote invites/knocks and LEAVE events corresponding to + # rejected/retracted invites and rescinded knocks. + assert event.type == EventTypes.Member + assert event.membership in ( + Membership.INVITE, + Membership.KNOCK, + Membership.LEAVE, + ) + self.db_pool.simple_upsert_txn( txn, table="local_current_membership", @@ -2329,6 +2348,115 @@ class PersistEventsStore: }, ) + # Update the `sliding_sync_membership_snapshots` table + # + raw_stripped_state_events = None + if event.membership == Membership.INVITE: + invite_room_state = event.unsigned.get("invite_room_state") + raw_stripped_state_events = invite_room_state + elif event.membership == Membership.KNOCK: + knock_room_state = event.unsigned.get("knock_room_state") + raw_stripped_state_events = knock_room_state + + insert_values = { + "membership_event_id": event.event_id, + "membership": event.membership, + "event_stream_ordering": event.internal_metadata.stream_ordering, + } + if raw_stripped_state_events is not None: + stripped_state_map: MutableStateMap[StrippedStateEvent] = {} + if isinstance(raw_stripped_state_events, list): + for raw_stripped_event in raw_stripped_state_events: + stripped_state_event = parse_stripped_state_event( + raw_stripped_event + ) + if stripped_state_event is not None: + stripped_state_map[ + ( + stripped_state_event.type, + stripped_state_event.state_key, + ) + ] = stripped_state_event + + # If there is some stripped state, we assume the remote server passed *all* + # of the potential stripped state events for the room. + create_stripped_event = stripped_state_map.get( + (EventTypes.Create, "") + ) + # Sanity check that we at-least have the create event + if create_stripped_event is not None: + # Find the room_type + insert_values["room_type"] = ( + create_stripped_event.content.get( + EventContentFields.ROOM_TYPE + ) + if create_stripped_event is not None + else None + ) + + # Find whether the room is_encrypted + encryption_stripped_event = stripped_state_map.get( + (EventTypes.RoomEncryption, "") + ) + encryption = ( + encryption_stripped_event.content.get( + EventContentFields.ENCRYPTION_ALGORITHM + ) + if encryption_stripped_event is not None + else None + ) + insert_values["is_encrypted"] = encryption is not None + + # Find the room_name + room_name_stripped_event = stripped_state_map.get( + (EventTypes.Name, "") + ) + insert_values["room_name"] = ( + room_name_stripped_event.content.get( + EventContentFields.ROOM_NAME + ) + if room_name_stripped_event is not None + else None + ) + + else: + # No strip state provided + insert_values["has_known_state"] = False + insert_values["room_type"] = None + insert_values["room_name"] = None + insert_values["is_encrypted"] = False + else: + if event.membership == Membership.LEAVE: + # Inherit the meta data from the remote invite/knock. When using + # sliding sync filters, this will prevent the room from + # disappearing/appearing just because you left the room. + pass + elif event.membership in (Membership.INVITE, Membership.KNOCK): + # No strip state provided + insert_values["has_known_state"] = False + insert_values["room_type"] = None + insert_values["room_name"] = None + insert_values["is_encrypted"] = False + else: + # We don't know how to handle this type of membership yet + # + # FIXME: We should use `assert_never` here but for some reason + # the exhaustive matching doesn't recognize the `Never` here. + # assert_never(event.membership) + raise AssertionError( + f"Unexpected out-of-band membership {event.membership} ({event.event_id}) that we don't know how to handle yet" + ) + + self.db_pool.simple_upsert_txn( + txn, + table="sliding_sync_membership_snapshots", + keyvalues={ + "room_id": event.room_id, + "user_id": event.state_key, + }, + values=insert_values, + ) + def _handle_event_relations( self, txn: LoggingTransaction, event: EventBase ) -> None: diff --git a/synapse/storage/schema/main/delta/87/01_sliding_sync_memberships.sql b/synapse/storage/schema/main/delta/87/01_sliding_sync_memberships.sql index 4e8fd0cd2d..81f5e271f3 100644 --- a/synapse/storage/schema/main/delta/87/01_sliding_sync_memberships.sql +++ b/synapse/storage/schema/main/delta/87/01_sliding_sync_memberships.sql @@ -46,7 +46,14 @@ CREATE TABLE IF NOT EXISTS sliding_sync_joined_rooms( -- to find all membership for a given user and shares the same semantics as -- `local_current_membership`. And we get to avoid some table maintenance; if we only -- stored non-joins, we would have to delete the row for the user when the user joins --- the room. +-- the room. Stripped state doesn't include the `m.room.tombstone` event, so we just +-- assume that the room doesn't have a tombstone. +-- +-- For remote invite/knocks where the server is not participating in the room, we will +-- use stripped state events to populate this table. We assume that if any stripped +-- state is given, it will include all possible stripped state events types. For +-- example, if stripped state is given but `m.room.encryption` isn't included, we will +-- assume that the room is not encrypted. -- -- We don't include `bump_stamp` here because we can just use the `stream_ordering` from -- the membership event itself as the `bump_stamp`. @@ -57,6 +64,11 @@ CREATE TABLE IF NOT EXISTS sliding_sync_membership_snapshots( membership TEXT NOT NULL, -- `stream_ordering` of the `membership_event_id` event_stream_ordering BIGINT NOT NULL REFERENCES events(stream_ordering), + -- For remote invites/knocks that don't include any stripped state, we want to be + -- able to distinguish between a room with `None` as valid value for some state and + -- room where the state is completely unknown. Basically, this should be True unless + -- no stripped state was provided for a remote invite/knock (False). + has_known_state BOOLEAN DEFAULT 0 NOT NULL, -- `m.room.create` -> `content.type` (according to the current state at the time of -- the membership) room_type TEXT, diff --git a/tests/storage/test_events.py b/tests/storage/test_events.py index 9a51baffab..19b17fe4c8 100644 --- a/tests/storage/test_events.py +++ b/tests/storage/test_events.py @@ -1407,6 +1407,7 @@ class SlidingSyncPrePopulatedTablesTestCase(HomeserverTestCase): ) # TODO: Test remote invite + # TODO: Test rejection of a remote invite # TODO Test for non-join membership changing