Skip to content
Draft
1 change: 1 addition & 0 deletions changelog.d/19723.bugfix
Original file line number Diff line number Diff line change
@@ -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.
37 changes: 37 additions & 0 deletions rust/src/room_versions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,25 @@ pub struct RoomVersion {
/// This is similar to how doubly-linked lists can potentially not refer to previous items correctly
/// without verifying the list's integrity, but doing it on every insert is too expensive.
pub msc4242_state_dags: bool,
/// Whether the `m.room.create` event is required in
/// `invite_room_state`/`knock_room_state` when receiving invites/knocks over the
/// federation API's.
///
/// Also determines whether we expect full PDU's in the
/// `invite_room_state`/`knock_room_state` in the federation API. The client API
/// still uses stripped state.
///
/// According to MSC4311:
/// > If any of the events are not a PDU, not for the room ID specified, or fail
/// > signature checks, or the `m.room.create` event is missing, the receiving
/// > server MAY respond to invites with a `400 M_MISSING_PARAM` standard Matrix
/// > error (new to the endpoint). For invites to room version 12+ rooms, servers
/// > SHOULD rather than MAY respond to such requests with `400 M_MISSING_PARAM`.
///
/// This does *not* determine whether we should include the `m.room.create` event in
/// stripped state or use full PDU's in stripped state over federation. We should
/// always do this.
pub msc4311_stripped_state: bool,
}

const ROOM_VERSION_V1: RoomVersion = RoomVersion {
Expand All @@ -182,6 +201,7 @@ const ROOM_VERSION_V1: RoomVersion = RoomVersion {
msc4291_room_ids_as_hashes: false,
strict_event_byte_limits_room_versions: false,
msc4242_state_dags: false,
msc4311_stripped_state: false,
};

const ROOM_VERSION_V2: RoomVersion = RoomVersion {
Expand All @@ -207,6 +227,7 @@ const ROOM_VERSION_V2: RoomVersion = RoomVersion {
msc4291_room_ids_as_hashes: false,
strict_event_byte_limits_room_versions: false,
msc4242_state_dags: false,
msc4311_stripped_state: false,
};

const ROOM_VERSION_V3: RoomVersion = RoomVersion {
Expand All @@ -232,6 +253,7 @@ const ROOM_VERSION_V3: RoomVersion = RoomVersion {
msc4291_room_ids_as_hashes: false,
strict_event_byte_limits_room_versions: false,
msc4242_state_dags: false,
msc4311_stripped_state: false,
};

const ROOM_VERSION_V4: RoomVersion = RoomVersion {
Expand All @@ -257,6 +279,7 @@ const ROOM_VERSION_V4: RoomVersion = RoomVersion {
msc4291_room_ids_as_hashes: false,
strict_event_byte_limits_room_versions: false,
msc4242_state_dags: false,
msc4311_stripped_state: false,
};

const ROOM_VERSION_V5: RoomVersion = RoomVersion {
Expand All @@ -282,6 +305,7 @@ const ROOM_VERSION_V5: RoomVersion = RoomVersion {
msc4291_room_ids_as_hashes: false,
strict_event_byte_limits_room_versions: false,
msc4242_state_dags: false,
msc4311_stripped_state: false,
};

const ROOM_VERSION_V6: RoomVersion = RoomVersion {
Expand All @@ -307,6 +331,7 @@ const ROOM_VERSION_V6: RoomVersion = RoomVersion {
msc4291_room_ids_as_hashes: false,
strict_event_byte_limits_room_versions: false,
msc4242_state_dags: false,
msc4311_stripped_state: false,
};

const ROOM_VERSION_V7: RoomVersion = RoomVersion {
Expand All @@ -332,6 +357,7 @@ const ROOM_VERSION_V7: RoomVersion = RoomVersion {
msc4291_room_ids_as_hashes: false,
strict_event_byte_limits_room_versions: false,
msc4242_state_dags: false,
msc4311_stripped_state: false,
};

const ROOM_VERSION_V8: RoomVersion = RoomVersion {
Expand All @@ -357,6 +383,7 @@ const ROOM_VERSION_V8: RoomVersion = RoomVersion {
msc4291_room_ids_as_hashes: false,
strict_event_byte_limits_room_versions: false,
msc4242_state_dags: false,
msc4311_stripped_state: false,
};

const ROOM_VERSION_V9: RoomVersion = RoomVersion {
Expand All @@ -382,6 +409,7 @@ const ROOM_VERSION_V9: RoomVersion = RoomVersion {
msc4291_room_ids_as_hashes: false,
strict_event_byte_limits_room_versions: false,
msc4242_state_dags: false,
msc4311_stripped_state: false,
};

const ROOM_VERSION_V10: RoomVersion = RoomVersion {
Expand All @@ -407,6 +435,7 @@ const ROOM_VERSION_V10: RoomVersion = RoomVersion {
msc4291_room_ids_as_hashes: false,
strict_event_byte_limits_room_versions: false,
msc4242_state_dags: false,
msc4311_stripped_state: false,
};

/// MSC3389 (Redaction changes for events with a relation) based on room version "10".
Expand All @@ -433,6 +462,7 @@ const ROOM_VERSION_MSC3389V10: RoomVersion = RoomVersion {
msc4291_room_ids_as_hashes: false,
strict_event_byte_limits_room_versions: true,
msc4242_state_dags: false,
msc4311_stripped_state: false,
};

/// MSC1767 (Extensible Events) based on room version "10".
Expand All @@ -459,6 +489,7 @@ const ROOM_VERSION_MSC1767V10: RoomVersion = RoomVersion {
msc4291_room_ids_as_hashes: false,
strict_event_byte_limits_room_versions: false,
msc4242_state_dags: false,
msc4311_stripped_state: false,
};

/// MSC3757 (Restricting who can overwrite a state event) based on room version "10".
Expand All @@ -485,6 +516,7 @@ const ROOM_VERSION_MSC3757V10: RoomVersion = RoomVersion {
msc4291_room_ids_as_hashes: false,
strict_event_byte_limits_room_versions: false,
msc4242_state_dags: false,
msc4311_stripped_state: false,
};

const ROOM_VERSION_V11: RoomVersion = RoomVersion {
Expand All @@ -510,6 +542,7 @@ const ROOM_VERSION_V11: RoomVersion = RoomVersion {
msc4291_room_ids_as_hashes: false,
strict_event_byte_limits_room_versions: true, // Changed from v10
msc4242_state_dags: false,
msc4311_stripped_state: false,
};

/// MSC3757 (Restricting who can overwrite a state event) based on room version "11".
Expand All @@ -536,6 +569,7 @@ const ROOM_VERSION_MSC3757V11: RoomVersion = RoomVersion {
msc4291_room_ids_as_hashes: false,
strict_event_byte_limits_room_versions: true,
msc4242_state_dags: false,
msc4311_stripped_state: false,
};

const ROOM_VERSION_HYDRA_V11: RoomVersion = RoomVersion {
Expand All @@ -561,6 +595,7 @@ const ROOM_VERSION_HYDRA_V11: RoomVersion = RoomVersion {
msc4291_room_ids_as_hashes: true, // Changed from v11
strict_event_byte_limits_room_versions: true,
msc4242_state_dags: false,
msc4311_stripped_state: false,
};

const ROOM_VERSION_V12: RoomVersion = RoomVersion {
Expand All @@ -586,6 +621,7 @@ const ROOM_VERSION_V12: RoomVersion = RoomVersion {
msc4291_room_ids_as_hashes: true, // Changed from v11
strict_event_byte_limits_room_versions: true,
msc4242_state_dags: false,
msc4311_stripped_state: true,
};

const ROOM_VERSION_MSC4242V12: RoomVersion = RoomVersion {
Expand All @@ -611,6 +647,7 @@ const ROOM_VERSION_MSC4242V12: RoomVersion = RoomVersion {
msc4291_room_ids_as_hashes: true,
strict_event_byte_limits_room_versions: true,
msc4242_state_dags: true,
msc4311_stripped_state: true,
};

/// Helper class for managing the known room versions, and providing dict-like
Expand Down
18 changes: 9 additions & 9 deletions synapse/events/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -1019,15 +1019,6 @@ def strip_event(event: EventBase) -> JsonDict:
Stripped state events can only have the `sender`, `type`, `state_key` and `content`
properties present.
"""
# MSC4311: Ensure the create event is available on invites and knocks.
# TODO: Implement the rest of MSC4311
if (
event.room_version.msc4291_room_ids_as_hashes
and event.type == EventTypes.Create
and event.get_state_key() == ""
):
return event.get_pdu_json()

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removing the flawed MSC4311 partial implementation

return {
"type": event.type,
"state_key": event.state_key,
Expand Down Expand Up @@ -1061,3 +1052,12 @@ def parse_stripped_state_event(raw_stripped_event: Any) -> StrippedStateEvent |
)

return None


def serialize_stripped_state_event(stripped_event: StrippedStateEvent) -> JsonDict:
return {
"type": stripped_event.type,
"state_key": stripped_event.state_key,
"sender": stripped_event.sender,
"content": stripped_event.content,
}
62 changes: 57 additions & 5 deletions synapse/federation/federation_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,8 @@
RoomVersions,
)
from synapse.events import EventBase, builder, make_event_from_dict
from synapse.events.snapshot import EventContext
from synapse.events.utils import parse_stripped_state_event
from synapse.federation.federation_base import (
FederationBase,
InvalidEventSignatureError,
Expand All @@ -71,7 +73,13 @@
from synapse.http.types import QueryParams
from synapse.logging.opentracing import SynapseTags, log_kv, set_tag, tag_args, trace
from synapse.metrics import SERVER_NAME_LABEL
from synapse.types import JsonDict, StrCollection, UserID, get_domain_from_id
from synapse.types import (
JsonDict,
StrCollection,
UserID,
get_domain_from_id,
)
from synapse.types.state import StateFilter
from synapse.util.async_helpers import concurrently_execute
from synapse.util.caches.expiringcache import ExpiringCache
from synapse.util.duration import Duration
Expand Down Expand Up @@ -1309,12 +1317,12 @@ async def send_invite(
self,
destination: str,
room_id: str,
event_id: str,
pdu: EventBase,
context: EventContext,
) -> EventBase:
room_version = await self.store.get_room_version(room_id)

content = await self._do_send_invite(destination, pdu, room_version)
content = await self._do_send_invite(destination, pdu, context, room_version)

pdu_dict = content["event"]

Expand All @@ -1335,7 +1343,11 @@ async def send_invite(
return pdu

async def _do_send_invite(
self, destination: str, pdu: EventBase, room_version: RoomVersion
self,
destination: str,
pdu: EventBase,
context: EventContext,
room_version: RoomVersion,
) -> JsonDict:
"""Actually sends the invite, first trying v2 API and falling back to
v1 API if necessary.
Expand All @@ -1350,6 +1362,34 @@ async def _do_send_invite(
"""
time_now = self._clock.time_msec()

# MSC4311: For the federation API, format events in `invite_room_state` as full
# PDU's
#
# First get all of the expected stripped state events that should be included.
# We will derive these from the `unsigned` part of the PDU but this doesn't
# include any event ID information so we need to look it up based on the state
# at the time of the invite.
stripped_state_types = []
for raw_stripped_event in pdu.unsigned.get("invite_room_state", []):
stripped_state_event = parse_stripped_state_event(raw_stripped_event)
# Since this is our own invite, it should always be well-formed
assert stripped_state_event is not None, (
"Unable to parse one of the evnts from the `invite_room_state` as a stripped state event"
)
stripped_state_types.append(
(stripped_state_event.type, stripped_state_event.state_key)
)

# Find the full events based on the state at the time of the invite
state_filter = StateFilter.from_types(stripped_state_types)
state_ids = await self.store.get_stripped_room_state_ids_from_event_context(
context, state_filter
)
state_events = await self.store.get_events(state_ids)
assert set(state_ids) == set(state_events.keys()), (
"We should have all events available that were set as stripped state."
)

try:
return await self.transport_layer.send_invite_v2(
destination=destination,
Expand All @@ -1358,10 +1398,18 @@ async def _do_send_invite(
content={
"event": pdu.get_pdu_json(time_now),
"room_version": room_version.identifier,
"invite_room_state": pdu.unsigned.get("invite_room_state", []),
"invite_room_state": [
state_event.get_pdu_json(time_now)
for state_event in state_events.values()
],
},
)
except HttpResponseException as e:
# TODO: MSC4311: The 400 `M_MISSING_PARAM` error SHOULD be translated to a 5xx
# error by the sending server over the Client-Server API. This is done
# because there's nothing the client can materially do differently to make
# the request succeed.

# If an error is received that is due to an unrecognised endpoint,
# fallback to the v1 endpoint if the room uses old-style event IDs.
# Otherwise, consider it a legitimate error and raise.
Expand All @@ -1385,6 +1433,10 @@ async def _do_send_invite(
event_id=pdu.event_id,
content=pdu.get_pdu_json(time_now),
)
# TODO: MSC4311: The 400 `M_MISSING_PARAM` error SHOULD be translated to a 5xx
# error by the sending server over the Client-Server API. This is done
# because there's nothing the client can materially do differently to make
# the request succeed.
return content

async def send_leave(self, destinations: Iterable[str], pdu: EventBase) -> None:
Expand Down
37 changes: 34 additions & 3 deletions synapse/federation/federation_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -772,8 +772,22 @@ async def on_make_join_request(
return {"event": pdu.get_templated_pdu_json(), "room_version": room_version}

async def on_invite_request(
self, origin: str, content: JsonDict, room_version_id: str
self,
*,
origin: str,
expected_room_id: str,
expected_event_id: str,
event_json: JsonDict,
room_version_id: str,
) -> dict[str, Any]:
"""
Args:
origin:
expected_room_id: The room ID specified in the
`/_matrix/federation/v1/invite/{roomId}/{eventId}` request that we expect to
match in the actual event itself.
"""

room_version = KNOWN_ROOM_VERSIONS.get(room_version_id)
if not room_version:
raise SynapseError(
Expand All @@ -782,9 +796,21 @@ async def on_invite_request(
Codes.UNSUPPORTED_ROOM_VERSION,
)

pdu = event_from_pdu_json(content, room_version)
pdu = event_from_pdu_json(event_json, room_version)
origin_host, _ = parse_server_name(origin)
await self.check_server_matches_acl(origin_host, pdu.room_id)
if pdu.event_id != expected_event_id:
raise SynapseError(
400,
Codes.INVALID_PARAM,
"Invite event ID must match event ID specified in the federation `/invite` request",
)
if pdu.room_id != expected_room_id:
raise SynapseError(
400,
Codes.INVALID_PARAM,
"The room_id specified in the invite event must match room ID specified in the federation `/invite` request",
)
if await self._spam_checker_module_callbacks.should_drop_federated_event(pdu):
logger.info(
"Federated event contains spam, dropping %s",
Expand All @@ -797,7 +823,11 @@ async def on_invite_request(
errmsg = f"event id {pdu.event_id}: {e}"
logger.warning("%s", errmsg)
raise SynapseError(403, errmsg, Codes.FORBIDDEN)
ret_pdu = await self.handler.on_invite_request(origin, pdu, room_version)
ret_pdu = await self.handler.on_invite_request(
origin=origin,
event=pdu,
room_version=room_version,
)
time_now = self._clock.time_msec()
return {"event": ret_pdu.get_pdu_json(time_now)}

Expand Down Expand Up @@ -965,6 +995,7 @@ async def on_send_knock_request(
# server. This will allow the remote server's clients to display information
# related to the room while the knock request is pending.
stripped_room_state = (
# TODO: Implement MSC4311 and use full PDUs here
await self.store.get_stripped_room_state_from_event_context(
context, self._room_prejoin_state_types
)
Expand Down
Loading
Loading