diff --git a/docker/complement/conf/workers-shared-extra.yaml.j2 b/docker/complement/conf/workers-shared-extra.yaml.j2 index fdc891055e..1af3a34ec9 100644 --- a/docker/complement/conf/workers-shared-extra.yaml.j2 +++ b/docker/complement/conf/workers-shared-extra.yaml.j2 @@ -143,6 +143,7 @@ experimental_features: msc4354_enabled: true # `/sync` `state_after` msc4222_enabled: true + # State DAGs msc4242_enabled: true server_notices: diff --git a/scripts-dev/complement.sh b/scripts-dev/complement.sh index d8e553f446..ec2e26961c 100755 --- a/scripts-dev/complement.sh +++ b/scripts-dev/complement.sh @@ -287,6 +287,7 @@ main() { ./tests/msc4306 ./tests/msc4222 ./tests/msc4429 + ./tests/msc4242 ) # Export the list of test packages as a space-separated environment variable, so other diff --git a/synapse/events/builder.py b/synapse/events/builder.py index 5057ee72de..26edf36ca7 100644 --- a/synapse/events/builder.py +++ b/synapse/events/builder.py @@ -158,6 +158,8 @@ class EventBuilder: if self.room_version.msc4242_state_dags: assert prev_state_events is not None calculated_auth_event_ids: list[str] = [] + # We can't use internal_metadata.out_of_band_membership as it isn't set pre-build time, + # only the .outlier is. if self.internal_metadata.outlier: # An out-of-band membership (e.g. a locally generated invite rejection): # we have no state at its `prev_state_events`, and it isn't auth checked. diff --git a/synapse/handlers/federation_event.py b/synapse/handlers/federation_event.py index 311201fd1d..55a9f5a647 100644 --- a/synapse/handlers/federation_event.py +++ b/synapse/handlers/federation_event.py @@ -2065,8 +2065,9 @@ class FederationEventHandler: # persister does another round of deduplication. seen_remotes = await self._store.have_seen_events(room_id, event_map.keys()) if is_state_dag_room: - # Out-of-band memberships were persisted without state; now that we have - # their `prev_state_events` process them again so they gain some. + # Pretend we haven't seen events which lack state groups. Events which lack state groups + # in state DAG rooms are exclusively out-of-band memberships. This ensures we calculate + # state for out-of-band events we have previously persisted. seen_remotes = await self._store.get_events_with_state_groups(seen_remotes) for s in seen_remotes: event_map.pop(s, None) @@ -3172,21 +3173,23 @@ class FederationEventHandler: raise SynapseError(HTTPStatus.BAD_REQUEST, "Too many auth_events") -def rescinds_invite(pdu: EventBase, invite_event: EventBase) -> bool: +def rescinds_invite(leave_event: EventBase, invite_event: EventBase) -> bool: """Returns True if the given leave event references the given invite. We are not in the room, so we cannot authorise the leave event: the most we can do is check that it names the invite we are holding. Without this an old leave event could be replayed by anyone to cancel a newer invite. """ - if supports_msc4242_state_dag(pdu): + assert leave_event.membership == Membership.LEAVE + assert invite_event.membership == Membership.INVITE + if supports_msc4242_state_dag(leave_event): # MSC4242 state DAG events don't list their auth events: they are calculated from # the state DAG, which we cannot do as we are not in the room. Instead, a leave - # event which rescinds an invite must name the invite in its prev_state_events. - return invite_event.event_id in pdu.prev_state_events + # event which rescinds an invite must name the invite in its prev_state_events, see MSC4242. + return invite_event.event_id in leave_event.prev_state_events # The invite should be in the auth events of the rescission. - return invite_event.event_id in pdu.auth_event_ids() + return invite_event.event_id in leave_event.auth_event_ids() def is_state_dag_connected(state_dag: Collection[MSC4242Event]) -> bool: