From dfd18bba8db7bbad0c4947c1b7eccc5e6c04a537 Mon Sep 17 00:00:00 2001 From: SpiritCroc Date: Thu, 4 Sep 2025 17:09:43 +0200 Subject: [PATCH 01/10] Fix sending redactions on rooms < v11 using the /send endpoint While there is a dedicated API endpoint for redactions, being able to send redactions using the normal send endpoint is useful when using MSC-4140 for sending delayed redactions to replicate expiring messages. Currently this would only work on rooms >= v11 but fail with an internal server error on older room versions. --- synapse/handlers/message.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/synapse/handlers/message.py b/synapse/handlers/message.py index d850b617d83..1ba95c90bbc 100644 --- a/synapse/handlers/message.py +++ b/synapse/handlers/message.py @@ -1024,7 +1024,14 @@ async def create_and_send_nonmember_event( if room_version.updated_redaction_rules: redacts = event_dict["content"].get("redacts") else: - redacts = event_dict.get("redacts") + # Legacy room versions need the "redacts" field outside of the event's + # content. However clients may still send it within the content, so copy + # the field if necessary for compatibility. + redacts = event_dict.get("redacts") or event_dict["content"].get( + "redacts" + ) + if redacts and "redacts" not in event_dict: + event_dict["redacts"] = redacts is_admin_redaction = await self.is_admin_redaction( event_type=event_dict["type"], From 4964d32ff0d880b92453ecfca8e7796f6b4bc564 Mon Sep 17 00:00:00 2001 From: SpiritCroc Date: Mon, 8 Sep 2025 12:49:29 +0200 Subject: [PATCH 02/10] Add changelog --- changelog.d/18898.bugfix | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/18898.bugfix diff --git a/changelog.d/18898.bugfix b/changelog.d/18898.bugfix new file mode 100644 index 00000000000..3823ca84c60 --- /dev/null +++ b/changelog.d/18898.bugfix @@ -0,0 +1 @@ +Fix error trying to send redaction events using the `/send` API in room versions prior to v11 From 888c29cf05f3b8b768bea4b75311d6673f263d12 Mon Sep 17 00:00:00 2001 From: SpiritCroc Date: Mon, 8 Sep 2025 12:56:26 +0200 Subject: [PATCH 03/10] Add credits and period to changelog entry --- changelog.d/18898.bugfix | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/changelog.d/18898.bugfix b/changelog.d/18898.bugfix index 3823ca84c60..0813034df27 100644 --- a/changelog.d/18898.bugfix +++ b/changelog.d/18898.bugfix @@ -1 +1 @@ -Fix error trying to send redaction events using the `/send` API in room versions prior to v11 +Fix error trying to send redaction events using the `/send` API in room versions prior to v11. Contributed by @SpiritCroc @ Beeper. From b42aab29c1307f868b8e867a63e40cea7d6e6c8e Mon Sep 17 00:00:00 2001 From: SpiritCroc Date: Tue, 9 Sep 2025 09:38:00 +0200 Subject: [PATCH 04/10] Move rather than copy "redacts" field Co-authored-by: Tulir Asokan --- synapse/handlers/message.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/synapse/handlers/message.py b/synapse/handlers/message.py index 1ba95c90bbc..4614bcbe5c7 100644 --- a/synapse/handlers/message.py +++ b/synapse/handlers/message.py @@ -1027,7 +1027,7 @@ async def create_and_send_nonmember_event( # Legacy room versions need the "redacts" field outside of the event's # content. However clients may still send it within the content, so copy # the field if necessary for compatibility. - redacts = event_dict.get("redacts") or event_dict["content"].get( + redacts = event_dict.get("redacts") or event_dict["content"].pop( "redacts" ) if redacts and "redacts" not in event_dict: From 89695d8a9a49b0dae2dca0e374096a242fc40ebe Mon Sep 17 00:00:00 2001 From: SpiritCroc Date: Tue, 9 Sep 2025 09:56:13 +0200 Subject: [PATCH 05/10] Fix sending messages that do not redact --- synapse/handlers/message.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/synapse/handlers/message.py b/synapse/handlers/message.py index 4614bcbe5c7..9dd44c392a7 100644 --- a/synapse/handlers/message.py +++ b/synapse/handlers/message.py @@ -1028,7 +1028,7 @@ async def create_and_send_nonmember_event( # content. However clients may still send it within the content, so copy # the field if necessary for compatibility. redacts = event_dict.get("redacts") or event_dict["content"].pop( - "redacts" + "redacts", None ) if redacts and "redacts" not in event_dict: event_dict["redacts"] = redacts From 17989b39f64d7e918195186faefc11e33727c801 Mon Sep 17 00:00:00 2001 From: SpiritCroc Date: Tue, 9 Sep 2025 09:47:31 +0200 Subject: [PATCH 06/10] Make changes an opt-in to MSC-4169 --- changelog.d/18898.bugfix | 1 - changelog.d/18898.feature | 1 + synapse/config/experimental.py | 3 +++ synapse/handlers/message.py | 4 +++- synapse/rest/client/versions.py | 2 ++ 5 files changed, 9 insertions(+), 2 deletions(-) delete mode 100644 changelog.d/18898.bugfix create mode 100644 changelog.d/18898.feature diff --git a/changelog.d/18898.bugfix b/changelog.d/18898.bugfix deleted file mode 100644 index 0813034df27..00000000000 --- a/changelog.d/18898.bugfix +++ /dev/null @@ -1 +0,0 @@ -Fix error trying to send redaction events using the `/send` API in room versions prior to v11. Contributed by @SpiritCroc @ Beeper. diff --git a/changelog.d/18898.feature b/changelog.d/18898.feature new file mode 100644 index 00000000000..bf31dd55d1c --- /dev/null +++ b/changelog.d/18898.feature @@ -0,0 +1 @@ +Support [MSC4169](https://github.com/matrix-org/matrix-spec-proposals/pull/4169) for backwards-compatible redaction sending using the `/send` endpoint. Contributed by @SpiritCroc @ Beeper. diff --git a/synapse/config/experimental.py b/synapse/config/experimental.py index c1631f39e3d..478b775bced 100644 --- a/synapse/config/experimental.py +++ b/synapse/config/experimental.py @@ -556,6 +556,9 @@ def read_config( # MSC4133: Custom profile fields self.msc4133_enabled: bool = experimental.get("msc4133_enabled", False) + # MSC4169: Backwards-compatible redaction sending using `/send` + self.msc4169_enabled: bool = experimental.get("msc4169_enabled", False) + # MSC4210: Remove legacy mentions self.msc4210_enabled: bool = experimental.get("msc4210_enabled", False) diff --git a/synapse/handlers/message.py b/synapse/handlers/message.py index 9dd44c392a7..601fdb8a945 100644 --- a/synapse/handlers/message.py +++ b/synapse/handlers/message.py @@ -1023,7 +1023,7 @@ async def create_and_send_nonmember_event( if room_version.updated_redaction_rules: redacts = event_dict["content"].get("redacts") - else: + elif self.hs.config.experimental.msc4169_enabled: # Legacy room versions need the "redacts" field outside of the event's # content. However clients may still send it within the content, so copy # the field if necessary for compatibility. @@ -1032,6 +1032,8 @@ async def create_and_send_nonmember_event( ) if redacts and "redacts" not in event_dict: event_dict["redacts"] = redacts + else: + redacts = event_dict.get("redacts") is_admin_redaction = await self.is_admin_redaction( event_type=event_dict["type"], diff --git a/synapse/rest/client/versions.py b/synapse/rest/client/versions.py index 7f783795341..abb21cfecc8 100644 --- a/synapse/rest/client/versions.py +++ b/synapse/rest/client/versions.py @@ -179,6 +179,8 @@ async def on_GET(self, request: SynapseRequest) -> Tuple[int, JsonDict]: "org.matrix.msc4155": self.config.experimental.msc4155_enabled, # MSC4306: Support for thread subscriptions "org.matrix.msc4306": self.config.experimental.msc4306_enabled, + # MSC4169: Backwards-compatible redaction sending useing `/send` + "com.beeper.msc4169": self.config.experimental.msc4169_enabled, }, }, ) From 86706c5c0cfef84fdcd7d93d2fbe6779fc09fb89 Mon Sep 17 00:00:00 2001 From: SpiritCroc Date: Tue, 9 Sep 2025 10:31:00 +0200 Subject: [PATCH 07/10] Fix typo in comment --- synapse/rest/client/versions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/synapse/rest/client/versions.py b/synapse/rest/client/versions.py index abb21cfecc8..802851892d4 100644 --- a/synapse/rest/client/versions.py +++ b/synapse/rest/client/versions.py @@ -179,7 +179,7 @@ async def on_GET(self, request: SynapseRequest) -> Tuple[int, JsonDict]: "org.matrix.msc4155": self.config.experimental.msc4155_enabled, # MSC4306: Support for thread subscriptions "org.matrix.msc4306": self.config.experimental.msc4306_enabled, - # MSC4169: Backwards-compatible redaction sending useing `/send` + # MSC4169: Backwards-compatible redaction sending using `/send` "com.beeper.msc4169": self.config.experimental.msc4169_enabled, }, }, From b0f919c66f2f4ea412b9873e5468a6398fc3a427 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Tue, 9 Sep 2025 14:03:48 +0200 Subject: [PATCH 08/10] Refactor to make logic independent of the ratelimit path --- synapse/handlers/message.py | 27 +++++++++++++++++++++------ 1 file changed, 21 insertions(+), 6 deletions(-) diff --git a/synapse/handlers/message.py b/synapse/handlers/message.py index 601fdb8a945..e7ce8c680b7 100644 --- a/synapse/handlers/message.py +++ b/synapse/handlers/message.py @@ -1013,25 +1013,40 @@ async def create_and_send_nonmember_event( await self.clock.sleep(random.randint(1, 10)) raise ShadowBanError() - if ratelimit: + room_version = None + + if ( + event_dict["type"] == EventTypes.Redaction + and "redacts" in event_dict["content"] + and self.hs.config.experimental.msc4169_enabled + ): + room_id = event_dict["room_id"] try: room_version = await self.store.get_room_version(room_id) except NotFoundError: - # The room doesn't exist. raise AuthError(403, f"User {requester.user} not in room {room_id}") - if room_version.updated_redaction_rules: - redacts = event_dict["content"].get("redacts") - elif self.hs.config.experimental.msc4169_enabled: + if not room_version.updated_redaction_rules: # Legacy room versions need the "redacts" field outside of the event's # content. However clients may still send it within the content, so copy # the field if necessary for compatibility. redacts = event_dict.get("redacts") or event_dict["content"].pop( "redacts", None ) - if redacts and "redacts" not in event_dict: + if redacts is not None and "redacts" not in event_dict: event_dict["redacts"] = redacts + + if ratelimit: + if room_version is None: + room_id = event_dict["room_id"] + try: + room_version = await self.store.get_room_version(room_id) + except NotFoundError: + raise AuthError(403, f"User {requester.user} not in room {room_id}") + + if room_version.updated_redaction_rules: + redacts = event_dict["content"].get("redacts") else: redacts = event_dict.get("redacts") From 2bc9d286ef4693c46c06b033aa61430baea47fe4 Mon Sep 17 00:00:00 2001 From: SpiritCroc Date: Tue, 9 Sep 2025 14:35:56 +0200 Subject: [PATCH 09/10] Lint --- synapse/handlers/message.py | 1 - 1 file changed, 1 deletion(-) diff --git a/synapse/handlers/message.py b/synapse/handlers/message.py index e7ce8c680b7..fcee2251737 100644 --- a/synapse/handlers/message.py +++ b/synapse/handlers/message.py @@ -1020,7 +1020,6 @@ async def create_and_send_nonmember_event( and "redacts" in event_dict["content"] and self.hs.config.experimental.msc4169_enabled ): - room_id = event_dict["room_id"] try: room_version = await self.store.get_room_version(room_id) From cf89b25ea36f17f276413b5942fcfc13b094bd7d Mon Sep 17 00:00:00 2001 From: reivilibre Date: Mon, 22 Sep 2025 14:50:30 +0100 Subject: [PATCH 10/10] Update synapse/handlers/message.py --- synapse/handlers/message.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/synapse/handlers/message.py b/synapse/handlers/message.py index fcee2251737..6db031a4b96 100644 --- a/synapse/handlers/message.py +++ b/synapse/handlers/message.py @@ -1028,7 +1028,7 @@ async def create_and_send_nonmember_event( if not room_version.updated_redaction_rules: # Legacy room versions need the "redacts" field outside of the event's - # content. However clients may still send it within the content, so copy + # content. However clients may still send it within the content, so move # the field if necessary for compatibility. redacts = event_dict.get("redacts") or event_dict["content"].pop( "redacts", None