From 5c916550fcabde87b4234a460b738304313b1c0c Mon Sep 17 00:00:00 2001 From: Jade Ellis Date: Wed, 28 May 2025 01:04:00 +0100 Subject: [PATCH 01/10] feat: Store the original content of redacted PDUs --- src/database/maps.rs | 9 +++++++++ src/service/rooms/timeline/data.rs | 13 +++++++++++++ src/service/rooms/timeline/mod.rs | 28 ++++++++++++++++++++++++++-- 3 files changed, 48 insertions(+), 2 deletions(-) diff --git a/src/database/maps.rs b/src/database/maps.rs index 19f9ced4..c72ed414 100644 --- a/src/database/maps.rs +++ b/src/database/maps.rs @@ -121,6 +121,15 @@ pub(super) static MAPS: &[Descriptor] = &[ index_size: 512, ..descriptor::SEQUENTIAL }, + Descriptor { + name: "pduid_originalcontent", + cache_disp: CacheDisp::SharedWith("pduid_pdu"), + key_size_hint: Some(16), + val_size_hint: Some(1520), + block_size: 2048, + index_size: 512, + ..descriptor::RANDOM + }, Descriptor { name: "publicroomids", ..descriptor::RANDOM_SMALL diff --git a/src/service/rooms/timeline/data.rs b/src/service/rooms/timeline/data.rs index 94c78bb0..a0b407d6 100644 --- a/src/service/rooms/timeline/data.rs +++ b/src/service/rooms/timeline/data.rs @@ -19,6 +19,8 @@ pub(super) struct Data { pduid_pdu: Arc, userroomid_highlightcount: Arc, userroomid_notificationcount: Arc, + /// Stores the original content of redacted PDUs. + pduid_originalcontent: Arc, pub(super) db: Arc, services: Services, } @@ -38,6 +40,7 @@ impl Data { pduid_pdu: db["pduid_pdu"].clone(), userroomid_highlightcount: db["userroomid_highlightcount"].clone(), userroomid_notificationcount: db["userroomid_notificationcount"].clone(), + pduid_originalcontent: db["pduid_originalcontent"].clone(), // Initialize new table db: args.db.clone(), services: Services { short: args.depend::("rooms::short"), @@ -177,6 +180,16 @@ impl Data { self.pduid_pdu.get(pdu_id).await.deserialized() } + /// Stores the original content of a PDU that is about to be redacted. + pub(super) async fn store_redacted_pdu_content( + &self, + pdu_id: &RawPduId, + pdu_json: &CanonicalJsonObject, + ) -> Result<()> { + self.pduid_originalcontent.raw_put(pdu_id, Json(pdu_json)); + Ok(()) + } + pub(super) async fn append_pdu( &self, pdu_id: &RawPduId, diff --git a/src/service/rooms/timeline/mod.rs b/src/service/rooms/timeline/mod.rs index 4b2f3cb2..dee12a41 100644 --- a/src/service/rooms/timeline/mod.rs +++ b/src/service/rooms/timeline/mod.rs @@ -260,6 +260,16 @@ impl Service { self.db.replace_pdu(pdu_id, pdu_json, pdu).await } + /// Stores the content of a to-be redacted pdu. + #[tracing::instrument(skip(self), level = "debug")] + pub async fn store_redacted_pdu_content( + &self, + pdu_id: &RawPduId, + pdu_json: &CanonicalJsonObject, + ) -> Result<()> { + self.db.store_redacted_pdu_content(pdu_id, pdu_json).await + } + /// Creates a new persisted data unit and adds it to a room. /// /// By this point the incoming event should be fully authenticated, no auth @@ -472,7 +482,7 @@ impl Service { .user_can_redact(redact_id, &pdu.sender, &pdu.room_id, false) .await? { - self.redact_pdu(redact_id, pdu, shortroomid).await?; + self.redact_pdu(redact_id, pdu, shortroomid, true).await?; } } }, @@ -485,7 +495,7 @@ impl Service { .user_can_redact(redact_id, &pdu.sender, &pdu.room_id, false) .await? { - self.redact_pdu(redact_id, pdu, shortroomid).await?; + self.redact_pdu(redact_id, pdu, shortroomid, true).await?; } } }, @@ -1033,6 +1043,7 @@ impl Service { event_id: &EventId, reason: &PduEvent, shortroomid: ShortRoomId, + keep_original_content: bool, ) -> Result { // TODO: Don't reserialize, keep original json let Ok(pdu_id) = self.get_pdu_id(event_id).await else { @@ -1054,6 +1065,19 @@ impl Service { let room_version_id = self.services.state.get_room_version(&pdu.room_id).await?; + if keep_original_content { + let original_pdu_json = utils::to_canonical_object(&pdu).map_err(|e| { + err!(Database(error!( + ?event_id, + ?e, + "Failed to convert PDU to canonical JSON for original content storage" + ))) + })?; + self.db + .store_redacted_pdu_content(&pdu_id, &original_pdu_json) + .await?; + } + pdu.redact(&room_version_id, reason)?; let obj = utils::to_canonical_object(&pdu).map_err(|e| { From 8f7b3bceb0d1f915a192a7b264c447ec595968ca Mon Sep 17 00:00:00 2001 From: Jade Ellis Date: Wed, 28 May 2025 02:07:56 +0100 Subject: [PATCH 02/10] feat: Allow retrieving redacted message content (msc2815) Still to do: - Handling the difference between content that we have deleted and content we never received - Deleting the original content on command or expiry Another question is if we have to store the full original content? Can we get by with just storing the 'content' field? --- src/api/client/room/event.rs | 51 ++++++++++++++++++++++++++++-- src/api/client/unversioned.rs | 1 + src/service/rooms/timeline/data.rs | 8 +++++ src/service/rooms/timeline/mod.rs | 9 ++++++ 4 files changed, 67 insertions(+), 2 deletions(-) diff --git a/src/api/client/room/event.rs b/src/api/client/room/event.rs index 2b115b5c..61f8b88c 100644 --- a/src/api/client/room/event.rs +++ b/src/api/client/room/event.rs @@ -1,7 +1,7 @@ use axum::extract::State; -use conduwuit::{Err, Event, Result, err}; +use conduwuit::{Err, Event, PduEvent, Result, err}; use futures::{FutureExt, TryFutureExt, future::try_join}; -use ruma::api::client::room::get_room_event; +use ruma::api::client::{error::ErrorKind, room::get_room_event}; use crate::{Ruma, client::is_ignored_pdu}; @@ -14,6 +14,7 @@ pub(crate) async fn get_room_event_route( ) -> Result { let event_id = &body.event_id; let room_id = &body.room_id; + let sender_user = body.sender_user(); let event = services .rooms @@ -33,6 +34,52 @@ pub(crate) async fn get_room_event_route( return Err!(Request(Forbidden("You don't have permission to view this event."))); } + let include_unredacted_content = body + .include_unredacted_content // User's file has this field name + .unwrap_or(false); + + if include_unredacted_content && event.is_redacted() { + let is_server_admin = services + .users + .is_admin(sender_user) + .map(|is_admin| Ok(is_admin)); + let can_redact_privilege = services + .rooms + .state_accessor + .user_can_redact(event_id, sender_user, room_id, false) // federation=false for local check + ; + let (is_server_admin, can_redact_privilege) = + try_join(is_server_admin, can_redact_privilege).await?; + + if !is_server_admin && !can_redact_privilege { + return Err!(Request(Forbidden( + "You don't have permission to view redacted content.", + ))); + } + + let pdu_id = match services.rooms.timeline.get_pdu_id(event_id).await { + | Ok(id) => id, + | Err(e) => { + return Err(e); + }, + }; + let original_content = services + .rooms + .timeline + .get_original_pdu_content(&pdu_id) + .await?; + if let Some(original_content) = original_content { + // If the original content is available, we can return it. + // event.content = to_raw_value(&original_content)?; + event = PduEvent::from_id_val(event_id, original_content)?; + } else { + return Err(conduwuit::Error::BadRequest( + ErrorKind::UnredactedContentDeleted { content_keep_ms: None }, + "The original unredacted content is not in the database.", + )); + } + } + debug_assert!( event.event_id() == event_id && event.room_id() == room_id, "Fetched PDU must match requested" diff --git a/src/api/client/unversioned.rs b/src/api/client/unversioned.rs index 232d5b28..98976522 100644 --- a/src/api/client/unversioned.rs +++ b/src/api/client/unversioned.rs @@ -40,6 +40,7 @@ pub(crate) async fn get_supported_versions_route( "v1.11".to_owned(), ], unstable_features: BTreeMap::from_iter([ + ("fi.mau.msc2815".to_owned(), true), ("org.matrix.e2e_cross_signing".to_owned(), true), ("org.matrix.msc2285.stable".to_owned(), true), /* private read receipts (https://github.com/matrix-org/matrix-spec-proposals/pull/2285) */ ("uk.half-shot.msc2666.query_mutual_rooms".to_owned(), true), /* query mutual rooms (https://github.com/matrix-org/matrix-spec-proposals/pull/2666) */ diff --git a/src/service/rooms/timeline/data.rs b/src/service/rooms/timeline/data.rs index a0b407d6..e5baf3b3 100644 --- a/src/service/rooms/timeline/data.rs +++ b/src/service/rooms/timeline/data.rs @@ -190,6 +190,14 @@ impl Data { Ok(()) } + /// Returns the original content of a redacted PDU. + pub(super) async fn get_original_pdu_content( + &self, + pdu_id: &RawPduId, + ) -> Result> { + self.pduid_originalcontent.get(pdu_id).await.deserialized() + } + pub(super) async fn append_pdu( &self, pdu_id: &RawPduId, diff --git a/src/service/rooms/timeline/mod.rs b/src/service/rooms/timeline/mod.rs index dee12a41..a09f5ec6 100644 --- a/src/service/rooms/timeline/mod.rs +++ b/src/service/rooms/timeline/mod.rs @@ -270,6 +270,15 @@ impl Service { self.db.store_redacted_pdu_content(pdu_id, pdu_json).await } + /// Returns the original content of a redacted PDU. + #[tracing::instrument(skip(self), level = "debug")] + pub async fn get_original_pdu_content( + &self, + pdu_id: &RawPduId, + ) -> Result> { + self.db.get_original_pdu_content(pdu_id).await + } + /// Creates a new persisted data unit and adds it to a room. /// /// By this point the incoming event should be fully authenticated, no auth From be52d495c9af1f6474e2f537036776dcc80538dd Mon Sep 17 00:00:00 2001 From: Jade Ellis Date: Wed, 28 May 2025 02:10:02 +0100 Subject: [PATCH 03/10] fix: Don't store events that have already been redacted This prevents clobbering --- src/service/rooms/timeline/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/service/rooms/timeline/mod.rs b/src/service/rooms/timeline/mod.rs index a09f5ec6..07bf9b7a 100644 --- a/src/service/rooms/timeline/mod.rs +++ b/src/service/rooms/timeline/mod.rs @@ -1074,7 +1074,7 @@ impl Service { let room_version_id = self.services.state.get_room_version(&pdu.room_id).await?; - if keep_original_content { + if keep_original_content && !pdu.is_redacted() { let original_pdu_json = utils::to_canonical_object(&pdu).map_err(|e| { err!(Database(error!( ?event_id, From 1d45e0b68cd86a72393bf3cb3f866d5943056018 Mon Sep 17 00:00:00 2001 From: Jade Ellis Date: Fri, 13 Jun 2025 13:39:50 +0100 Subject: [PATCH 04/10] feat: Add warning when admin users will be exposed as support contacts --- src/core/config/check.rs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/core/config/check.rs b/src/core/config/check.rs index ded9533d..3dc45e2f 100644 --- a/src/core/config/check.rs +++ b/src/core/config/check.rs @@ -219,6 +219,15 @@ pub fn check(config: &Config) -> Result { )); } + // Check if support contact information is configured + if config.well_known.support_email.is_none() && config.well_known.support_mxid.is_none() { + warn!( + "No support contact information (support_email or support_mxid) is configured in \ + the well_known section. Users in the admin room will be automatically listed as \ + support contacts in the /.well-known/matrix/support endpoint." + ); + } + if config .url_preview_domain_contains_allowlist .contains(&"*".to_owned()) From d7514178ab3aaf594bc2b55bb115842f5ba9f2ca Mon Sep 17 00:00:00 2001 From: Jade Ellis Date: Fri, 13 Jun 2025 14:29:14 +0100 Subject: [PATCH 05/10] ci: Fix extra bracket in commit shorthash --- .forgejo/workflows/release-image.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.forgejo/workflows/release-image.yml b/.forgejo/workflows/release-image.yml index 92a5b7c4..55b303b2 100644 --- a/.forgejo/workflows/release-image.yml +++ b/.forgejo/workflows/release-image.yml @@ -180,7 +180,7 @@ jobs: file: "docker/Dockerfile" build-args: | GIT_COMMIT_HASH=${{ github.sha }}) - GIT_COMMIT_HASH_SHORT=${{ env.COMMIT_SHORT_SHA }}) + GIT_COMMIT_HASH_SHORT=${{ env.COMMIT_SHORT_SHA }} GIT_REMOTE_URL=${{github.event.repository.html_url }} GIT_REMOTE_COMMIT_URL=${{github.event.head_commit.url }} platforms: ${{ matrix.platform }} From 44e60d0ea60fec116eb1535239cc8007d73992f0 Mon Sep 17 00:00:00 2001 From: Jade Ellis Date: Fri, 30 May 2025 23:50:29 +0100 Subject: [PATCH 06/10] docs: Tiny phrasing changes to the security policy --- SECURITY.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/SECURITY.md b/SECURITY.md index a9aa183e..2869ce58 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -20,10 +20,10 @@ We may backport fixes to the previous release at our discretion, but we don't gu We appreciate the efforts of security researchers and the community in identifying and reporting vulnerabilities. To ensure that potential vulnerabilities are addressed properly, please follow these guidelines: -1. Contact members of the team over E2EE private message. +1. **Contact members of the team directly** over E2EE private message. - [@jade:ellis.link](https://matrix.to/#/@jade:ellis.link) - [@nex:nexy7574.co.uk](https://matrix.to/#/@nex:nexy7574.co.uk) -2. **Email the security team** directly at [security@continuwuity.org](mailto:security@continuwuity.org). This is not E2EE, so don't include sensitive details. +2. **Email the security team** at [security@continuwuity.org](mailto:security@continuwuity.org). This is not E2EE, so don't include sensitive details. 3. **Do not disclose the vulnerability publicly** until it has been addressed 4. **Provide detailed information** about the vulnerability, including: - A clear description of the issue @@ -48,7 +48,7 @@ When you report a security vulnerability: When security vulnerabilities are identified: -1. We will develop and test fixes in a private branch +1. We will develop and test fixes in a private fork 2. Security updates will be released as soon as possible 3. Release notes will include information about the vulnerabilities, avoiding details that could facilitate exploitation where possible 4. Critical security updates may be backported to the previous stable release From 5d44653e3a9bc278f9dbe3084e2e34a9a0799e1f Mon Sep 17 00:00:00 2001 From: Jade Ellis Date: Sat, 14 Jun 2025 16:28:57 +0100 Subject: [PATCH 07/10] fix: Incorrect command descriptions --- src/admin/debug/mod.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/admin/debug/mod.rs b/src/admin/debug/mod.rs index 9b86f18c..1fd4e263 100644 --- a/src/admin/debug/mod.rs +++ b/src/admin/debug/mod.rs @@ -125,13 +125,13 @@ pub(super) enum DebugCommand { reset: bool, }, - /// - Verify json signatures + /// - Sign JSON blob /// /// This command needs a JSON blob provided in a Markdown code block below /// the command. SignJson, - /// - Verify json signatures + /// - Verify JSON signatures /// /// This command needs a JSON blob provided in a Markdown code block below /// the command. From 88ecf61d49945a3e82307b9f940cdc5e39eafb70 Mon Sep 17 00:00:00 2001 From: Jade Ellis Date: Wed, 28 May 2025 01:04:00 +0100 Subject: [PATCH 08/10] feat: Store the original content of redacted PDUs --- src/database/maps.rs | 9 +++++++++ src/service/rooms/timeline/data.rs | 13 +++++++++++++ src/service/rooms/timeline/mod.rs | 28 ++++++++++++++++++++++++++-- 3 files changed, 48 insertions(+), 2 deletions(-) diff --git a/src/database/maps.rs b/src/database/maps.rs index 19f9ced4..c72ed414 100644 --- a/src/database/maps.rs +++ b/src/database/maps.rs @@ -121,6 +121,15 @@ pub(super) static MAPS: &[Descriptor] = &[ index_size: 512, ..descriptor::SEQUENTIAL }, + Descriptor { + name: "pduid_originalcontent", + cache_disp: CacheDisp::SharedWith("pduid_pdu"), + key_size_hint: Some(16), + val_size_hint: Some(1520), + block_size: 2048, + index_size: 512, + ..descriptor::RANDOM + }, Descriptor { name: "publicroomids", ..descriptor::RANDOM_SMALL diff --git a/src/service/rooms/timeline/data.rs b/src/service/rooms/timeline/data.rs index 94c78bb0..a0b407d6 100644 --- a/src/service/rooms/timeline/data.rs +++ b/src/service/rooms/timeline/data.rs @@ -19,6 +19,8 @@ pub(super) struct Data { pduid_pdu: Arc, userroomid_highlightcount: Arc, userroomid_notificationcount: Arc, + /// Stores the original content of redacted PDUs. + pduid_originalcontent: Arc, pub(super) db: Arc, services: Services, } @@ -38,6 +40,7 @@ impl Data { pduid_pdu: db["pduid_pdu"].clone(), userroomid_highlightcount: db["userroomid_highlightcount"].clone(), userroomid_notificationcount: db["userroomid_notificationcount"].clone(), + pduid_originalcontent: db["pduid_originalcontent"].clone(), // Initialize new table db: args.db.clone(), services: Services { short: args.depend::("rooms::short"), @@ -177,6 +180,16 @@ impl Data { self.pduid_pdu.get(pdu_id).await.deserialized() } + /// Stores the original content of a PDU that is about to be redacted. + pub(super) async fn store_redacted_pdu_content( + &self, + pdu_id: &RawPduId, + pdu_json: &CanonicalJsonObject, + ) -> Result<()> { + self.pduid_originalcontent.raw_put(pdu_id, Json(pdu_json)); + Ok(()) + } + pub(super) async fn append_pdu( &self, pdu_id: &RawPduId, diff --git a/src/service/rooms/timeline/mod.rs b/src/service/rooms/timeline/mod.rs index 4b2f3cb2..dee12a41 100644 --- a/src/service/rooms/timeline/mod.rs +++ b/src/service/rooms/timeline/mod.rs @@ -260,6 +260,16 @@ impl Service { self.db.replace_pdu(pdu_id, pdu_json, pdu).await } + /// Stores the content of a to-be redacted pdu. + #[tracing::instrument(skip(self), level = "debug")] + pub async fn store_redacted_pdu_content( + &self, + pdu_id: &RawPduId, + pdu_json: &CanonicalJsonObject, + ) -> Result<()> { + self.db.store_redacted_pdu_content(pdu_id, pdu_json).await + } + /// Creates a new persisted data unit and adds it to a room. /// /// By this point the incoming event should be fully authenticated, no auth @@ -472,7 +482,7 @@ impl Service { .user_can_redact(redact_id, &pdu.sender, &pdu.room_id, false) .await? { - self.redact_pdu(redact_id, pdu, shortroomid).await?; + self.redact_pdu(redact_id, pdu, shortroomid, true).await?; } } }, @@ -485,7 +495,7 @@ impl Service { .user_can_redact(redact_id, &pdu.sender, &pdu.room_id, false) .await? { - self.redact_pdu(redact_id, pdu, shortroomid).await?; + self.redact_pdu(redact_id, pdu, shortroomid, true).await?; } } }, @@ -1033,6 +1043,7 @@ impl Service { event_id: &EventId, reason: &PduEvent, shortroomid: ShortRoomId, + keep_original_content: bool, ) -> Result { // TODO: Don't reserialize, keep original json let Ok(pdu_id) = self.get_pdu_id(event_id).await else { @@ -1054,6 +1065,19 @@ impl Service { let room_version_id = self.services.state.get_room_version(&pdu.room_id).await?; + if keep_original_content { + let original_pdu_json = utils::to_canonical_object(&pdu).map_err(|e| { + err!(Database(error!( + ?event_id, + ?e, + "Failed to convert PDU to canonical JSON for original content storage" + ))) + })?; + self.db + .store_redacted_pdu_content(&pdu_id, &original_pdu_json) + .await?; + } + pdu.redact(&room_version_id, reason)?; let obj = utils::to_canonical_object(&pdu).map_err(|e| { From 46b1eeb2c87f4a83264f17f2508f98293e74ba62 Mon Sep 17 00:00:00 2001 From: Jade Ellis Date: Wed, 28 May 2025 02:07:56 +0100 Subject: [PATCH 09/10] feat: Allow retrieving redacted message content (msc2815) Still to do: - Handling the difference between content that we have deleted and content we never received - Deleting the original content on command or expiry Another question is if we have to store the full original content? Can we get by with just storing the 'content' field? --- src/api/client/room/event.rs | 51 ++++++++++++++++++++++++++++-- src/api/client/unversioned.rs | 1 + src/service/rooms/timeline/data.rs | 8 +++++ src/service/rooms/timeline/mod.rs | 9 ++++++ 4 files changed, 67 insertions(+), 2 deletions(-) diff --git a/src/api/client/room/event.rs b/src/api/client/room/event.rs index 2b115b5c..61f8b88c 100644 --- a/src/api/client/room/event.rs +++ b/src/api/client/room/event.rs @@ -1,7 +1,7 @@ use axum::extract::State; -use conduwuit::{Err, Event, Result, err}; +use conduwuit::{Err, Event, PduEvent, Result, err}; use futures::{FutureExt, TryFutureExt, future::try_join}; -use ruma::api::client::room::get_room_event; +use ruma::api::client::{error::ErrorKind, room::get_room_event}; use crate::{Ruma, client::is_ignored_pdu}; @@ -14,6 +14,7 @@ pub(crate) async fn get_room_event_route( ) -> Result { let event_id = &body.event_id; let room_id = &body.room_id; + let sender_user = body.sender_user(); let event = services .rooms @@ -33,6 +34,52 @@ pub(crate) async fn get_room_event_route( return Err!(Request(Forbidden("You don't have permission to view this event."))); } + let include_unredacted_content = body + .include_unredacted_content // User's file has this field name + .unwrap_or(false); + + if include_unredacted_content && event.is_redacted() { + let is_server_admin = services + .users + .is_admin(sender_user) + .map(|is_admin| Ok(is_admin)); + let can_redact_privilege = services + .rooms + .state_accessor + .user_can_redact(event_id, sender_user, room_id, false) // federation=false for local check + ; + let (is_server_admin, can_redact_privilege) = + try_join(is_server_admin, can_redact_privilege).await?; + + if !is_server_admin && !can_redact_privilege { + return Err!(Request(Forbidden( + "You don't have permission to view redacted content.", + ))); + } + + let pdu_id = match services.rooms.timeline.get_pdu_id(event_id).await { + | Ok(id) => id, + | Err(e) => { + return Err(e); + }, + }; + let original_content = services + .rooms + .timeline + .get_original_pdu_content(&pdu_id) + .await?; + if let Some(original_content) = original_content { + // If the original content is available, we can return it. + // event.content = to_raw_value(&original_content)?; + event = PduEvent::from_id_val(event_id, original_content)?; + } else { + return Err(conduwuit::Error::BadRequest( + ErrorKind::UnredactedContentDeleted { content_keep_ms: None }, + "The original unredacted content is not in the database.", + )); + } + } + debug_assert!( event.event_id() == event_id && event.room_id() == room_id, "Fetched PDU must match requested" diff --git a/src/api/client/unversioned.rs b/src/api/client/unversioned.rs index 232d5b28..98976522 100644 --- a/src/api/client/unversioned.rs +++ b/src/api/client/unversioned.rs @@ -40,6 +40,7 @@ pub(crate) async fn get_supported_versions_route( "v1.11".to_owned(), ], unstable_features: BTreeMap::from_iter([ + ("fi.mau.msc2815".to_owned(), true), ("org.matrix.e2e_cross_signing".to_owned(), true), ("org.matrix.msc2285.stable".to_owned(), true), /* private read receipts (https://github.com/matrix-org/matrix-spec-proposals/pull/2285) */ ("uk.half-shot.msc2666.query_mutual_rooms".to_owned(), true), /* query mutual rooms (https://github.com/matrix-org/matrix-spec-proposals/pull/2666) */ diff --git a/src/service/rooms/timeline/data.rs b/src/service/rooms/timeline/data.rs index a0b407d6..e5baf3b3 100644 --- a/src/service/rooms/timeline/data.rs +++ b/src/service/rooms/timeline/data.rs @@ -190,6 +190,14 @@ impl Data { Ok(()) } + /// Returns the original content of a redacted PDU. + pub(super) async fn get_original_pdu_content( + &self, + pdu_id: &RawPduId, + ) -> Result> { + self.pduid_originalcontent.get(pdu_id).await.deserialized() + } + pub(super) async fn append_pdu( &self, pdu_id: &RawPduId, diff --git a/src/service/rooms/timeline/mod.rs b/src/service/rooms/timeline/mod.rs index dee12a41..a09f5ec6 100644 --- a/src/service/rooms/timeline/mod.rs +++ b/src/service/rooms/timeline/mod.rs @@ -270,6 +270,15 @@ impl Service { self.db.store_redacted_pdu_content(pdu_id, pdu_json).await } + /// Returns the original content of a redacted PDU. + #[tracing::instrument(skip(self), level = "debug")] + pub async fn get_original_pdu_content( + &self, + pdu_id: &RawPduId, + ) -> Result> { + self.db.get_original_pdu_content(pdu_id).await + } + /// Creates a new persisted data unit and adds it to a room. /// /// By this point the incoming event should be fully authenticated, no auth From aa29b81ef6ae404c71cf7f99ec8665e44d94d9b6 Mon Sep 17 00:00:00 2001 From: Jade Ellis Date: Wed, 28 May 2025 02:10:02 +0100 Subject: [PATCH 10/10] fix: Don't store events that have already been redacted This prevents clobbering --- src/service/rooms/timeline/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/service/rooms/timeline/mod.rs b/src/service/rooms/timeline/mod.rs index a09f5ec6..07bf9b7a 100644 --- a/src/service/rooms/timeline/mod.rs +++ b/src/service/rooms/timeline/mod.rs @@ -1074,7 +1074,7 @@ impl Service { let room_version_id = self.services.state.get_room_version(&pdu.room_id).await?; - if keep_original_content { + if keep_original_content && !pdu.is_redacted() { let original_pdu_json = utils::to_canonical_object(&pdu).map_err(|e| { err!(Database(error!( ?event_id,