MSC4311: Use full PDU's in stripped state (like invite_room_state) over federation and always include m.room.create event#19723
Conversation
/sync should still use stripped events with MSC4311/sync should still use stripped events
/sync should still use stripped events/sync should still use stripped events
| and event.get_state_key() == "" | ||
| ): | ||
| return event.get_pdu_json() | ||
|
|
|
|
||
| return [strip_event(e) for e in state_to_include.values()] | ||
|
|
||
| async def get_stripped_room_state_ids_from_event_context( |
There was a problem hiding this comment.
Split some logic out of get_stripped_room_state_from_event_context(...) into get_stripped_room_state_ids_from_event_context(...)
This way we can use the same event selection logic but fetch/format them however we see fit.
| # TODO(paul): assert that room_id/event_id parsed from path actually | ||
| # match those given in content |
There was a problem hiding this comment.
Removed as we actually do this now in FederationServer.on_invite_request(...) (added in this PR)
/sync should still use stripped eventsinvite_room_state) over federation and always include m.room.create event
…al-implementation Conflicts: rust/src/room_versions.rs
| # Add the invite itself | ||
| # | ||
| # FIXME: Doesn't seem to be in the spec | ||
| invited_state.append(strip_event(room.invite)) |
There was a problem hiding this comment.
Found that we were accidentally providing the full invite event in this case. This is just something that's been happening since inception (2015)
The Complement tests check to make sure the client sees stripped state which is how I caught this.
Additionally, just documenting this existing behavior.
There was a problem hiding this comment.
To note, the stripped state event fields cover the use cases described in matrix-org/synapse#6739 (comment). The client has the invite sender to show who invited you.
… of deriving from `invite_room_state` We don't even calculate `knock_room_state` to do the same pattern and it's a bunch of extra complexity.
…al-implementation
| # the client with: | ||
| # | ||
| # * A knock state event that they can use for easier internal tracking | ||
| # * The rough timestamp of when the knock occurred contained within the event |
There was a problem hiding this comment.
Removed the note about the timestamp as a stripped state event doesn't have origin_server_ts like the full PDU would.
There was a problem hiding this comment.
There is some previous discussion in matrix-org/synapse#6739 (comment) where this is both argued for and against.
The timestamp use case seems to be theoretical vs something the client actually does so I'm minded to just to align with the spec. If it's useful, it deserves its own MSC.
| invited_state.append(invite) | ||
| # Add the invite itself | ||
| # | ||
| # FIXME: Doesn't seem to be in the spec |
There was a problem hiding this comment.
As a note, in the spec /sync example under invite_state: it does actually include the m.room.member invite:
{
// ...
"rooms": {
"invite": {
"!696r7674:example.com": {
"invite_state": {
"events": [
{
"content": {
"name": "My Room Name"
},
"sender": "@alice:example.com",
"state_key": "",
"type": "m.room.name"
},
{
"content": {
"membership": "invite"
},
"sender": "@alice:example.com",
"state_key": "@bob:example.com",
"type": "m.room.member"
}
]
}
}
},
// ...
}But m.room.member is not mentioned in the list stripped state event types in https://spec.matrix.org/v1.18/client-server-api/#stripped-state
And my guess is that the example is just copy-pasted from Synapse though so I don't think it's a great beacon to hang our hat on. The idea makes sense and should get its own MSC.
| @@ -0,0 +1 @@ | |||
| Remove flawed [MSC4311](https://github.com/matrix-org/matrix-spec-proposals/pull/4311) partial implementation: Client-side API's like `/sync` should still use stripped events. | |||
There was a problem hiding this comment.
The Complement tests are expected to fail ❌ as we removed the flawed partial implementation in this PR.
The Complement tests have been updated in matrix-org/complement#796 and pass locally. We will merge both PRs at the same time.
| # Ensure the event has been stripped | ||
| self.assertNotIn("signatures", event) |
There was a problem hiding this comment.
Removed as these can be full PDU's now (that's the whole point of MSC4311)
| "(either one could be at fault).", | ||
| Codes.UNKNOWN, | ||
| additional_fields={ | ||
| "cause": err.msg, |
There was a problem hiding this comment.
I don't think we have to worry about leaking anything by passing this error message along. Calling this out so the reviewer can sanity check this assumption
There was a problem hiding this comment.
Pull request overview
This PR implements MSC4311 handling for federation invite/knock room state while keeping client APIs on stripped-state formats. It adds full-PDU federation payloads, inbound validation/sanitization, and a room-version capability flag for version 12+ behavior.
Changes:
- Restores client stripped-state serialization and updates
/syncinvite/knock rendering. - Sends full PDUs for federation
invite_room_state/knock_room_stateand validates received state. - Adds storage helpers, room-version metadata, changelog, and test updates for new call signatures/behavior.
Reviewed changes
Copilot reviewed 17 out of 17 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
changelog.d/19723.bugfix |
Records the MSC4311 client stripped-state bugfix. |
rust/src/room_versions.rs |
Adds the MSC4311 room-version capability flag. |
synapse/events/utils.py |
Removes full-PDU special casing from strip_event and adds stripped-state serialization. |
synapse/federation/federation_client.py |
Builds federation invite room state as full PDUs and maps MSC4311 remote errors. |
synapse/federation/federation_server.py |
Validates invite path IDs and returns full-PDU knock room state. |
synapse/federation/transport/server/federation.py |
Passes expected invite path IDs through transport handlers. |
synapse/handlers/federation.py |
Adds inbound invite/knock room-state validation and sanitization. |
synapse/handlers/message.py |
Passes invite event context into federation invite sending. |
synapse/rest/client/sync.py |
Ensures client invite/knock events remain stripped in sync responses. |
synapse/storage/databases/main/events_worker.py |
Splits stripped-state event ID retrieval from stripped event serialization. |
synapse/synapse_rust/room_versions.pyi |
Exposes the new room-version flag to Python typing. |
tests/events/test_auto_accept_invites.py |
Updates invite handler calls to keyword arguments. |
tests/federation/transport/test_knocking.py |
Updates knock federation expectations for full-PDU state. |
tests/handlers/test_federation.py |
Updates invite handler calls to keyword arguments. |
tests/handlers/test_room_member.py |
Updates invite handler calls to keyword arguments. |
tests/handlers/test_room_summary.py |
Updates invite handler calls to keyword arguments. |
tests/test_visibility.py |
Updates federation invite test to pass expected path IDs. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| _, content = await self.transport_layer.send_invite_v1( | ||
| destination=destination, | ||
| room_id=pdu.room_id, | ||
| event_id=pdu.event_id, | ||
| content=pdu.get_pdu_json(time_now), |
| # validation above. The only reason this is here is because the validation | ||
| # can fail for non-compliant servers but we should still use stripped state. | ||
| stripped_room_state_for_client = self._minimal_parse_stripped_room_state( | ||
| stripped_room_state=event.unsigned.get("knock_room_state"), |
| raise SynapseError( | ||
| 500, | ||
| f"Received {HTTPStatus.BAD_REQUEST} {Codes.MISSING_PARAM} response from remote homeserver " | ||
| "while trying to send the invite over federation. This indicates a compatability problem " |
| raise SynapseError( | ||
| 500, | ||
| f"Received {HTTPStatus.BAD_REQUEST} {Codes.MISSING_PARAM} response from remote homeserver " | ||
| "while trying to send the invite over federation. This indicates a compatability problem " |
| # Find the full events based on the state at the time of the invite | ||
| state_ids = await self.store.get_stripped_room_state_ids_from_event_context( | ||
| context, self._room_prejoin_state_types |
…c/test_rooms_invites.SlidingSyncRoomsInvitesTestCase to use 'stripped state' to compare against responses instead of fleshing out a full PDU
Background
This PR was originally just trying to remove the flawed MSC4311 partial implementation as client side API's like
/syncshould still use stripped events. But it turns out we were just re-using the client logic for the federation side and things might break if we didn't include the fullm.room.createevent so this PR now introduces MSC4311 support to use full PDU's in theinvite_room_state/knock_room_statein the federation API's.The flawed implementation was originally introduced in 0eb7252 (no PR I assume because part of Hydra security fix)
Spawning from reviewing #19722 and noticing that we have
TestMSC4311FullCreateEventOnStrippedStatein Complement which already passes even though that test looks flawed:What does this PR do?
invite_room_state/knock_room_stateover federationinvite_room_state/knock_room_stateover federationComplement tests: matrix-org/complement#796
Part of #19414
Dev notes
Todo
TestMSC4311FullCreateEventOnStrippedStateComplement test so it doesn't look for the full PDU in/sync.knock_room_statein this PRknock_room_statesenderinstead ofuser_idfor any stripped state event usages matrix-org/sytest#1425senderinstead ofuser_idfor any stripped state event usages matrix-org/sytest#1425Pull Request Checklist
EventStoretoEventWorkerStore.".code blocks.