Fill in for remote invites (out of band, outlier membership)

This commit is contained in:
Eric Eastwood 2024-08-12 18:13:58 -05:00
parent f069659343
commit 53232e6df5
3 changed files with 145 additions and 4 deletions

View file

@ -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:

View file

@ -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,

View file

@ -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