diff --git a/changelog.d/19722.feature b/changelog.d/19722.feature new file mode 100644 index 00000000000..30104b7e74d --- /dev/null +++ b/changelog.d/19722.feature @@ -0,0 +1 @@ +Partial [MSC4311](https://github.com/matrix-org/matrix-spec-proposals/pull/4311) implementation: `m.room.create` is now a required part of stripped `invite_state`/`knock_state` . Contributed by @FrenchGithubUser @Famedly. diff --git a/synapse/config/api.py b/synapse/config/api.py index 03b92249a96..b5979a089d2 100644 --- a/synapse/config/api.py +++ b/synapse/config/api.py @@ -48,6 +48,9 @@ def _get_prejoin_state_entries( self, config: JsonDict ) -> Iterable[tuple[str, str | None]]: """Get the event types and state keys to include in the prejoin state.""" + # MSC4311: the create event must always be included in invite/knock state. + yield EventTypes.Create, "" + room_prejoin_state_config = config.get("room_prejoin_state") or {} # backwards-compatibility support for room_invite_state_types diff --git a/tests/config/test_api.py b/tests/config/test_api.py index e6cc3e21ed3..d4fa398a47e 100644 --- a/tests/config/test_api.py +++ b/tests/config/test_api.py @@ -5,7 +5,6 @@ from synapse.config import ConfigError from synapse.config._base import RootConfig from synapse.config.api import ApiConfig -from synapse.types.state import StateFilter DEFAULT_PREJOIN_STATE_PAIRS = { ("m.room.join_rules", ""), @@ -38,7 +37,11 @@ def test_disable_default_event_types(self) -> None: disable_default_event_types: true """ ) - self.assertEqual(config.room_prejoin_state, StateFilter.none()) + # MSC4311: m.room.create is always included even when defaults are disabled + self.assertEqual( + set(config.room_prejoin_state.concrete_types()), + {("m.room.create", "")}, + ) def test_event_without_state_key(self) -> None: config = self.read_config( @@ -50,7 +53,11 @@ def test_event_without_state_key(self) -> None: """ ) self.assertEqual(config.room_prejoin_state.wildcard_types(), ["foo"]) - self.assertEqual(config.room_prejoin_state.concrete_types(), []) + # MSC4311: m.room.create is always included + self.assertEqual( + set(config.room_prejoin_state.concrete_types()), + {("m.room.create", "")}, + ) def test_event_with_specific_state_key(self) -> None: config = self.read_config( @@ -62,9 +69,10 @@ def test_event_with_specific_state_key(self) -> None: """ ) self.assertFalse(config.room_prejoin_state.has_wildcards()) + # MSC4311: m.room.create is always included self.assertEqual( set(config.room_prejoin_state.concrete_types()), - {("foo", "bar")}, + {("foo", "bar"), ("m.room.create", "")}, ) def test_repeated_event_with_specific_state_key(self) -> None: @@ -78,9 +86,10 @@ def test_repeated_event_with_specific_state_key(self) -> None: """ ) self.assertFalse(config.room_prejoin_state.has_wildcards()) + # MSC4311: m.room.create is always included self.assertEqual( set(config.room_prejoin_state.concrete_types()), - {("foo", "bar"), ("foo", "baz")}, + {("foo", "bar"), ("foo", "baz"), ("m.room.create", "")}, ) def test_no_specific_state_key_overrides_specific_state_key(self) -> None: @@ -94,7 +103,11 @@ def test_no_specific_state_key_overrides_specific_state_key(self) -> None: """ ) self.assertEqual(config.room_prejoin_state.wildcard_types(), ["foo"]) - self.assertEqual(config.room_prejoin_state.concrete_types(), []) + # MSC4311: m.room.create is always included + self.assertEqual( + set(config.room_prejoin_state.concrete_types()), + {("m.room.create", "")}, + ) config = self.read_config( """ @@ -106,7 +119,11 @@ def test_no_specific_state_key_overrides_specific_state_key(self) -> None: """ ) self.assertEqual(config.room_prejoin_state.wildcard_types(), ["foo"]) - self.assertEqual(config.room_prejoin_state.concrete_types(), []) + # MSC4311: m.room.create is always included + self.assertEqual( + set(config.room_prejoin_state.concrete_types()), + {("m.room.create", "")}, + ) def test_bad_event_type_entry_raises(self) -> None: with self.assertRaises(ConfigError): diff --git a/tests/rest/client/test_sync.py b/tests/rest/client/test_sync.py index e6ada1adb27..74a8678ae99 100644 --- a/tests/rest/client/test_sync.py +++ b/tests/rest/client/test_sync.py @@ -29,6 +29,7 @@ from synapse.api.constants import ( EventContentFields, EventTypes, + JoinRules, ReceiptTypes, RelationTypes, ) @@ -394,6 +395,69 @@ def test_knock_room_state(self) -> None: ) +class SyncCreateEventInPrejoinStateTestCase(unittest.HomeserverTestCase): + """MSC4311: Tests that m.room.create is present in invite_state and knock_state""" + + servlets = [ + synapse.rest.admin.register_servlets, + login.register_servlets, + room.register_servlets, + sync.register_servlets, + knock.register_servlets, + ] + + def default_config(self) -> JsonDict: + config = super().default_config() + return config + + def test_create_event_present_in_invite_state(self) -> None: + """m.room.create must appear in invite_state.""" + inviter = self.register_user("inviter", "pass") + inviter_tok = self.login("inviter", "pass") + invitee = self.register_user("invitee", "pass") + invitee_tok = self.login("invitee", "pass") + + room_id = self.helper.create_room_as(inviter, tok=inviter_tok) + self.helper.invite(room=room_id, src=inviter, targ=invitee, tok=inviter_tok) + + channel = self.make_request("GET", "/sync", access_token=invitee_tok) + self.assertEqual(channel.code, 200, channel.json_body) + + invite_state_events = channel.json_body["rooms"]["invite"][room_id][ + "invite_state" + ]["events"] + event_types = {stripped_event["type"] for stripped_event in invite_state_events} + self.assertIn(EventTypes.Create, event_types) + + def test_create_event_present_in_knock_state(self) -> None: + """m.room.create must appear in knock_state.""" + host = self.register_user("host", "pass") + host_tok = self.login("host", "pass") + knocker = self.register_user("knocker", "pass") + knocker_tok = self.login("knocker", "pass") + + room_id = self.helper.create_room_as( + host, is_public=False, room_version="7", tok=host_tok + ) + self.helper.send_state( + room_id, + EventTypes.JoinRules, + {"join_rule": JoinRules.KNOCK}, + tok=host_tok, + ) + + self.helper.knock(room_id, knocker, tok=knocker_tok) + + channel = self.make_request("GET", "/sync", access_token=knocker_tok) + self.assertEqual(channel.code, 200, channel.json_body) + + knock_state_events = channel.json_body["rooms"]["knock"][room_id][ + "knock_state" + ]["events"] + event_types = {stripped_event["type"] for stripped_event in knock_state_events} + self.assertIn(EventTypes.Create, event_types) + + class UnreadMessagesTestCase(unittest.HomeserverTestCase): servlets = [ synapse.rest.admin.register_servlets,