From db6756657f18948ff44f205490e1bef8cff75fcc Mon Sep 17 00:00:00 2001 From: Richard van der Hoff Date: Tue, 3 Feb 2026 18:42:56 +0000 Subject: [PATCH 1/2] Add stable support for MSC4380 invite blocking. MSC4380 has now completed FCP, so we can add stable support for it. --- 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 00000000000..feaaddc31af --- /dev/null +++ b/changelog.d/19431.feature @@ -0,0 +1 @@ +Add stable support for MSC4380 invite blocking. diff --git a/synapse/api/constants.py b/synapse/api/constants.py index 9b6a68e929c..91bdeac20db 100644 --- a/synapse/api/constants.py +++ b/synapse/api/constants.py @@ -321,9 +321,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 c299ca84d94..0c35b4a7ba5 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 1bd70ead09a..8a162148686 100644 --- a/synapse/config/experimental.py +++ b/synapse/config/experimental.py @@ -582,6 +582,3 @@ def read_config( # MSC4306: Thread Subscriptions # (and MSC4308: Thread Subscriptions extension to Sliding Sync) self.msc4306_enabled: bool = experimental.get("msc4306_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 75f27c98dea..821bba6af0d 100644 --- a/synapse/rest/client/versions.py +++ b/synapse/rest/client/versions.py @@ -183,7 +183,7 @@ async def on_GET(self, request: SynapseRequest) -> tuple[int, JsonDict]: # MSC4169: Backwards-compatible redaction sending using `/send` "com.beeper.msc4169": self.config.experimental.msc4169_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 71182cdab2b..538280137b0 100644 --- a/synapse/storage/databases/main/account_data.py +++ b/synapse/storage/databases/main/account_data.py @@ -109,7 +109,6 @@ def __init__( ) 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 @@ async def get_invite_config_for_user(self, user_id: str) -> InviteRulesConfig: 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 d8d7caaf1bd..3890abdbc83 100644 --- a/tests/handlers/test_room_member.py +++ b/tests/handlers/test_room_member.py @@ -503,7 +503,7 @@ def test_misc4155_block_invite_local(self) -> None: 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 @@ def test_msc4155_block_invite_remote(self) -> None: 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 @@ def test_msc4155_block_invite_remote_server(self) -> None: 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 @@ def prepare(self, reactor: MemoryReactor, clock: Clock, hs: HomeServer) -> None: 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 @@ def test_misc4380_block_invite_local(self) -> None: 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 @@ def test_misc4380_block_invite_local(self) -> None: 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 @@ def test_misc4380_non_string_setting(self) -> None: 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 @@ def test_misc4380_non_string_setting(self) -> None: ) ) - @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 @@ def test_msc4380_block_invite_remote(self) -> None: 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 @@ def test_msc4380_block_invite_remote(self) -> None: SynapseError, ).value self.assertEqual(f.code, 403) - self.assertEqual(f.errcode, "ORG.MATRIX.MSC4155.M_INVITE_BLOCKED") + self.assertEqual(f.errcode, "M_INVITE_BLOCKED") From 9dc0f891760188a29d38bb2be64214524347f252 Mon Sep 17 00:00:00 2001 From: Richard van der Hoff <1389908+richvdh@users.noreply.github.com> Date: Wed, 25 Feb 2026 14:16:05 +0000 Subject: [PATCH 2/2] Apply suggestion from @sandhose Co-authored-by: Quentin Gliech --- changelog.d/19431.feature | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/changelog.d/19431.feature b/changelog.d/19431.feature index feaaddc31af..0076e5221c6 100644 --- a/changelog.d/19431.feature +++ b/changelog.d/19431.feature @@ -1 +1 @@ -Add stable support for MSC4380 invite blocking. +Add stable support for [MSC4380](https://github.com/matrix-org/matrix-spec-proposals/pull/4380) invite blocking.