From b9ea2285b3cbc1e49be0d45cc46342fa6e167b28 Mon Sep 17 00:00:00 2001 From: Richard van der Hoff <1389908+richvdh@users.noreply.github.com> Date: Fri, 27 Feb 2026 14:47:07 +0000 Subject: [PATCH] Add stable support for MSC4380 invite blocking. (#19431) MSC4380 has now completed FCP, so we can add stable support for it. Co-authored-by: Quentin Gliech --- changelog.d/19431.feature | 1 + synapse/api/constants.py | 4 +- synapse/api/errors.py | 2 +- synapse/config/experimental.py | 3 -- synapse/rest/client/versions.py | 2 +- .../storage/databases/main/account_data.py | 16 +++---- tests/handlers/test_room_member.py | 43 ++++--------------- 7 files changed, 19 insertions(+), 52 deletions(-) create mode 100644 changelog.d/19431.feature diff --git a/changelog.d/19431.feature b/changelog.d/19431.feature new file mode 100644 index 0000000000..0076e5221c --- /dev/null +++ b/changelog.d/19431.feature @@ -0,0 +1 @@ +Add stable support for [MSC4380](https://github.com/matrix-org/matrix-spec-proposals/pull/4380) invite blocking. diff --git a/synapse/api/constants.py b/synapse/api/constants.py index b8ef5dac50..21139a3867 100644 --- a/synapse/api/constants.py +++ b/synapse/api/constants.py @@ -325,9 +325,7 @@ class AccountDataTypes: "org.matrix.msc4155.invite_permission_config" ) # MSC4380: Invite blocking - MSC4380_INVITE_PERMISSION_CONFIG: Final = ( - "org.matrix.msc4380.invite_permission_config" - ) + INVITE_PERMISSION_CONFIG: Final = "m.invite_permission_config" # Synapse-specific behaviour. See "Client-Server API Extensions" documentation # in Admin API for more information. SYNAPSE_ADMIN_CLIENT_CONFIG: Final = "io.element.synapse.admin_client_config" diff --git a/synapse/api/errors.py b/synapse/api/errors.py index c299ca84d9..0c35b4a7ba 100644 --- a/synapse/api/errors.py +++ b/synapse/api/errors.py @@ -138,7 +138,7 @@ class Codes(str, Enum): KEY_TOO_LARGE = "M_KEY_TOO_LARGE" # Part of MSC4155/MSC4380 - INVITE_BLOCKED = "ORG.MATRIX.MSC4155.M_INVITE_BLOCKED" + INVITE_BLOCKED = "M_INVITE_BLOCKED" # Part of MSC4190 APPSERVICE_LOGIN_UNSUPPORTED = "IO.ELEMENT.MSC4190.M_APPSERVICE_LOGIN_UNSUPPORTED" diff --git a/synapse/config/experimental.py b/synapse/config/experimental.py index 05091ca6eb..76e8de35fb 100644 --- a/synapse/config/experimental.py +++ b/synapse/config/experimental.py @@ -606,6 +606,3 @@ class ExperimentalConfig(Config): # Note that sticky events persisted before this feature is enabled will not be # considered sticky by the local homeserver. self.msc4354_enabled: bool = experimental.get("msc4354_enabled", False) - - # MSC4380: Invite blocking - self.msc4380_enabled: bool = experimental.get("msc4380_enabled", False) diff --git a/synapse/rest/client/versions.py b/synapse/rest/client/versions.py index e443629fab..23f5ffeedb 100644 --- a/synapse/rest/client/versions.py +++ b/synapse/rest/client/versions.py @@ -187,7 +187,7 @@ class VersionsRestServlet(RestServlet): # MSC4354: Sticky events "org.matrix.msc4354": self.config.experimental.msc4354_enabled, # MSC4380: Invite blocking - "org.matrix.msc4380": self.config.experimental.msc4380_enabled, + "org.matrix.msc4380.stable": True, }, }, ) diff --git a/synapse/storage/databases/main/account_data.py b/synapse/storage/databases/main/account_data.py index 71182cdab2..538280137b 100644 --- a/synapse/storage/databases/main/account_data.py +++ b/synapse/storage/databases/main/account_data.py @@ -109,7 +109,6 @@ class AccountDataWorkerStore(PushRulesWorkerStore, CacheInvalidationWorkerStore) ) self._msc4155_enabled = hs.config.experimental.msc4155_enabled - self._msc4380_enabled = hs.config.experimental.msc4380_enabled def get_max_account_data_stream_id(self) -> int: """Get the current max stream ID for account data stream @@ -573,14 +572,13 @@ class AccountDataWorkerStore(PushRulesWorkerStore, CacheInvalidationWorkerStore) Args: user_id: The user whose invite configuration should be returned. """ - if self._msc4380_enabled: - data = await self.get_global_account_data_by_type_for_user( - user_id, AccountDataTypes.MSC4380_INVITE_PERMISSION_CONFIG - ) - # If the user has an MSC4380-style config setting, prioritise that - # above an MSC4155 one - if data is not None: - return MSC4380InviteRulesConfig.from_account_data(data) + data = await self.get_global_account_data_by_type_for_user( + user_id, AccountDataTypes.INVITE_PERMISSION_CONFIG + ) + # If the user has an MSC4380-style config setting, prioritise that + # above an MSC4155 one + if data is not None: + return MSC4380InviteRulesConfig.from_account_data(data) if self._msc4155_enabled: data = await self.get_global_account_data_by_type_for_user( diff --git a/tests/handlers/test_room_member.py b/tests/handlers/test_room_member.py index d8d7caaf1b..3890abdbc8 100644 --- a/tests/handlers/test_room_member.py +++ b/tests/handlers/test_room_member.py @@ -503,7 +503,7 @@ class TestMSC4155InviteFiltering(FederatingHomeserverTestCase): SynapseError, ).value self.assertEqual(f.code, 403) - self.assertEqual(f.errcode, "ORG.MATRIX.MSC4155.M_INVITE_BLOCKED") + self.assertEqual(f.errcode, "M_INVITE_BLOCKED") @override_config({"experimental_features": {"msc4155_enabled": False}}) def test_msc4155_disabled_allow_invite_local(self) -> None: @@ -573,7 +573,7 @@ class TestMSC4155InviteFiltering(FederatingHomeserverTestCase): SynapseError, ).value self.assertEqual(f.code, 403) - self.assertEqual(f.errcode, "ORG.MATRIX.MSC4155.M_INVITE_BLOCKED") + self.assertEqual(f.errcode, "M_INVITE_BLOCKED") @override_config({"experimental_features": {"msc4155_enabled": True}}) def test_msc4155_block_invite_remote_server(self) -> None: @@ -619,7 +619,7 @@ class TestMSC4155InviteFiltering(FederatingHomeserverTestCase): SynapseError, ).value self.assertEqual(f.code, 403) - self.assertEqual(f.errcode, "ORG.MATRIX.MSC4155.M_INVITE_BLOCKED") + self.assertEqual(f.errcode, "M_INVITE_BLOCKED") class TestMSC4380InviteBlocking(FederatingHomeserverTestCase): @@ -642,7 +642,6 @@ class TestMSC4380InviteBlocking(FederatingHomeserverTestCase): self.bob = self.register_user("bob", "pass") self.bob_token = self.login("bob", "pass") - @override_config({"experimental_features": {"msc4380_enabled": True}}) def test_misc4380_block_invite_local(self) -> None: """Test that MSC4380 will block a user from being invited to a room""" room_id = self.helper.create_room_as(self.alice, tok=self.alice_token) @@ -650,7 +649,7 @@ class TestMSC4380InviteBlocking(FederatingHomeserverTestCase): self.get_success( self.store.add_account_data_for_user( self.bob, - AccountDataTypes.MSC4380_INVITE_PERMISSION_CONFIG, + AccountDataTypes.INVITE_PERMISSION_CONFIG, { "default_action": "block", }, @@ -667,9 +666,8 @@ class TestMSC4380InviteBlocking(FederatingHomeserverTestCase): SynapseError, ).value self.assertEqual(f.code, 403) - self.assertEqual(f.errcode, "ORG.MATRIX.MSC4155.M_INVITE_BLOCKED") + self.assertEqual(f.errcode, "M_INVITE_BLOCKED") - @override_config({"experimental_features": {"msc4380_enabled": True}}) def test_misc4380_non_string_setting(self) -> None: """Test that `default_action` being set to something non-stringy is the same as "accept".""" room_id = self.helper.create_room_as(self.alice, tok=self.alice_token) @@ -677,7 +675,7 @@ class TestMSC4380InviteBlocking(FederatingHomeserverTestCase): self.get_success( self.store.add_account_data_for_user( self.bob, - AccountDataTypes.MSC4380_INVITE_PERMISSION_CONFIG, + AccountDataTypes.INVITE_PERMISSION_CONFIG, { "default_action": 1, }, @@ -693,31 +691,6 @@ class TestMSC4380InviteBlocking(FederatingHomeserverTestCase): ) ) - @override_config({"experimental_features": {"msc4380_enabled": False}}) - def test_msc4380_disabled_allow_invite_local(self) -> None: - """Test that, when MSC4380 is not enabled, invites are accepted as normal""" - room_id = self.helper.create_room_as(self.alice, tok=self.alice_token) - - self.get_success( - self.store.add_account_data_for_user( - self.bob, - AccountDataTypes.MSC4380_INVITE_PERMISSION_CONFIG, - { - "default_action": "block", - }, - ) - ) - - self.get_success( - self.handler.update_membership( - requester=create_requester(self.alice), - target=UserID.from_string(self.bob), - room_id=room_id, - action=Membership.INVITE, - ), - ) - - @override_config({"experimental_features": {"msc4380_enabled": True}}) def test_msc4380_block_invite_remote(self) -> None: """Test that MSC4380 will block a user from being invited to a room by a remote user.""" # A remote user who sends the invite @@ -727,7 +700,7 @@ class TestMSC4380InviteBlocking(FederatingHomeserverTestCase): self.get_success( self.store.add_account_data_for_user( self.bob, - AccountDataTypes.MSC4380_INVITE_PERMISSION_CONFIG, + AccountDataTypes.INVITE_PERMISSION_CONFIG, {"default_action": "block"}, ) ) @@ -761,4 +734,4 @@ class TestMSC4380InviteBlocking(FederatingHomeserverTestCase): SynapseError, ).value self.assertEqual(f.code, 403) - self.assertEqual(f.errcode, "ORG.MATRIX.MSC4155.M_INVITE_BLOCKED") + self.assertEqual(f.errcode, "M_INVITE_BLOCKED")