From d39d30008a81a0149279eef01e2e2969d1a10078 Mon Sep 17 00:00:00 2001 From: Val Lorentz Date: Fri, 1 Dec 2023 15:11:26 +0100 Subject: [PATCH 01/19] Remove "creator" property from rooms >= v11 --- src/api/client_server/room.rs | 71 +++++++++++++++++++++++++------ src/service/admin/mod.rs | 17 +++++++- src/service/pdu.rs | 20 ++++++++- src/service/rooms/timeline/mod.rs | 51 ++++++++++++---------- 4 files changed, 119 insertions(+), 40 deletions(-) diff --git a/src/api/client_server/room.rs b/src/api/client_server/room.rs index 0e2d9326..6b824040 100644 --- a/src/api/client_server/room.rs +++ b/src/api/client_server/room.rs @@ -23,7 +23,7 @@ use ruma::{ }, int, serde::JsonObject, - CanonicalJsonObject, OwnedRoomAliasId, RoomAliasId, RoomId, + CanonicalJsonObject, OwnedRoomAliasId, RoomAliasId, RoomId, RoomVersionId, }; use serde_json::{json, value::to_raw_value}; use std::{cmp::max, collections::BTreeMap, sync::Arc}; @@ -127,12 +127,28 @@ pub async fn create_room_route( let mut content = content .deserialize_as::() .expect("Invalid creation content"); - content.insert( - "creator".into(), - json!(&sender_user).try_into().map_err(|_| { - Error::BadRequest(ErrorKind::BadJson, "Invalid creation content") - })?, - ); + + match room_version { + RoomVersionId::V1 + | RoomVersionId::V2 + | RoomVersionId::V3 + | RoomVersionId::V4 + | RoomVersionId::V5 + | RoomVersionId::V6 + | RoomVersionId::V7 + | RoomVersionId::V8 + | RoomVersionId::V9 + | RoomVersionId::V10 => { + content.insert( + "creator".into(), + json!(&sender_user).try_into().map_err(|_| { + Error::BadRequest(ErrorKind::BadJson, "Invalid creation content") + })?, + ); + } + _ => {} // V11 removed the "creator" key + } + content.insert( "room_version".into(), json!(room_version.as_str()).try_into().map_err(|_| { @@ -143,8 +159,21 @@ pub async fn create_room_route( } None => { // TODO: Add correct value for v11 + let content = match room_version { + RoomVersionId::V1 + | RoomVersionId::V2 + | RoomVersionId::V3 + | RoomVersionId::V4 + | RoomVersionId::V5 + | RoomVersionId::V6 + | RoomVersionId::V7 + | RoomVersionId::V8 + | RoomVersionId::V9 + | RoomVersionId::V10 => RoomCreateEventContent::new_v1(sender_user.clone()), + _ => RoomCreateEventContent::new_v11(), + }; let mut content = serde_json::from_str::( - to_raw_value(&RoomCreateEventContent::new_v1(sender_user.clone())) + to_raw_value(&content) .map_err(|_| Error::BadRequest(ErrorKind::BadJson, "Invalid creation content"))? .get(), ) @@ -580,12 +609,26 @@ pub async fn upgrade_room_route( )); // Send a m.room.create event containing a predecessor field and the applicable room_version - create_event_content.insert( - "creator".into(), - json!(&sender_user) - .try_into() - .map_err(|_| Error::BadRequest(ErrorKind::BadJson, "Error forming creation event"))?, - ); + match body.new_version { + RoomVersionId::V1 + | RoomVersionId::V2 + | RoomVersionId::V3 + | RoomVersionId::V4 + | RoomVersionId::V5 + | RoomVersionId::V6 + | RoomVersionId::V7 + | RoomVersionId::V8 + | RoomVersionId::V9 + | RoomVersionId::V10 => { + create_event_content.insert( + "creator".into(), + json!(&sender_user).try_into().map_err(|_| { + Error::BadRequest(ErrorKind::BadJson, "Error forming creation event") + })?, + ); + } + _ => {} // V11 removed the "creator" key + } create_event_content.insert( "room_version".into(), json!(&body.new_version) diff --git a/src/service/admin/mod.rs b/src/service/admin/mod.rs index b22f8ed4..292943a3 100644 --- a/src/service/admin/mod.rs +++ b/src/service/admin/mod.rs @@ -932,10 +932,23 @@ impl Service { services().users.create(&conduit_user, None)?; - let mut content = RoomCreateEventContent::new_v1(conduit_user.clone()); + let room_version = services().globals.default_room_version(); + let mut content = match room_version { + RoomVersionId::V1 + | RoomVersionId::V2 + | RoomVersionId::V3 + | RoomVersionId::V4 + | RoomVersionId::V5 + | RoomVersionId::V6 + | RoomVersionId::V7 + | RoomVersionId::V8 + | RoomVersionId::V9 + | RoomVersionId::V10 => RoomCreateEventContent::new_v1(conduit_user.clone()), + _ => RoomCreateEventContent::new_v11(), + }; content.federate = true; content.predecessor = None; - content.room_version = services().globals.default_room_version(); + content.room_version = room_version; // 1. The room create event services().rooms.timeline.build_and_append_pdu( diff --git a/src/service/pdu.rs b/src/service/pdu.rs index 4a170bc2..9e7f6d1d 100644 --- a/src/service/pdu.rs +++ b/src/service/pdu.rs @@ -49,12 +49,28 @@ pub struct PduEvent { impl PduEvent { #[tracing::instrument(skip(self))] - pub fn redact(&mut self, reason: &PduEvent) -> crate::Result<()> { + pub fn redact( + &mut self, + room_version_id: RoomVersionId, + reason: &PduEvent, + ) -> crate::Result<()> { self.unsigned = None; let allowed: &[&str] = match self.kind { TimelineEventType::RoomMember => &["join_authorised_via_users_server", "membership"], - TimelineEventType::RoomCreate => &["creator"], + TimelineEventType::RoomCreate => match room_version_id { + RoomVersionId::V1 + | RoomVersionId::V2 + | RoomVersionId::V3 + | RoomVersionId::V4 + | RoomVersionId::V5 + | RoomVersionId::V6 + | RoomVersionId::V7 + | RoomVersionId::V8 + | RoomVersionId::V9 + | RoomVersionId::V10 => &["creator"], + _ => &[], // V11 removed the creator key + }, TimelineEventType::RoomJoinRules => &["join_rule"], TimelineEventType::RoomPowerLevels => &[ "ban", diff --git a/src/service/rooms/timeline/mod.rs b/src/service/rooms/timeline/mod.rs index 25e1c54d..4f3b0e59 100644 --- a/src/service/rooms/timeline/mod.rs +++ b/src/service/rooms/timeline/mod.rs @@ -28,7 +28,7 @@ use ruma::{ state_res, state_res::{Event, RoomVersion}, uint, user_id, CanonicalJsonObject, CanonicalJsonValue, EventId, OwnedEventId, OwnedRoomId, - OwnedServerName, RoomAliasId, RoomId, ServerName, UserId, + OwnedServerName, RoomAliasId, RoomId, RoomVersionId, ServerName, UserId, }; use serde::Deserialize; use serde_json::value::{to_raw_value, RawValue as RawJsonValue}; @@ -128,6 +128,27 @@ impl Service { self.db.get_pdu_count(event_id) } + /// Returns the version of a room, if known + pub fn get_room_version(&self, room_id: &RoomId) -> Result> { + let create_event = services().rooms.state_accessor.room_state_get( + room_id, + &StateEventType::RoomCreate, + "", + )?; + + let create_event_content: Option = create_event + .as_ref() + .map(|create_event| { + serde_json::from_str(create_event.content.get()).map_err(|e| { + warn!("Invalid create event: {}", e); + Error::bad_database("Invalid create event in db.") + }) + }) + .transpose()?; + + Ok(create_event_content.map(|content| content.room_version)) + } + // TODO Is this the same as the function above? /* #[tracing::instrument(skip(self))] @@ -645,28 +666,11 @@ impl Service { .take(20) .collect(); - let create_event = services().rooms.state_accessor.room_state_get( - room_id, - &StateEventType::RoomCreate, - "", - )?; - - let create_event_content: Option = create_event - .as_ref() - .map(|create_event| { - serde_json::from_str(create_event.content.get()).map_err(|e| { - warn!("Invalid create event: {}", e); - Error::bad_database("Invalid create event in db.") - }) - }) - .transpose()?; - // If there was no create event yet, assume we are creating a room with the default // version right now - let room_version_id = create_event_content - .map_or(services().globals.default_room_version(), |create_event| { - create_event.room_version - }); + let room_version_id = self + .get_room_version(room_id)? + .unwrap_or_else(|| services().globals.default_room_version()); let room_version = RoomVersion::new(&room_version_id).expect("room version is supported"); let auth_events = services().rooms.state.get_auth_events( @@ -1034,7 +1038,10 @@ impl Service { let mut pdu = self .get_pdu_from_id(&pdu_id)? .ok_or_else(|| Error::bad_database("PDU ID points to invalid PDU."))?; - pdu.redact(reason)?; + let room_version_id = self.get_room_version(&pdu.room_id)?.ok_or_else(|| { + Error::bad_database("Trying to redact PDU in in room of unknown version") + })?; + pdu.redact(room_version_id, reason)?; self.replace_pdu( &pdu_id, &utils::to_canonical_object(&pdu).expect("PDU is an object"), From a3b8eea9b4885bccdfad7279f809c472ddd89a44 Mon Sep 17 00:00:00 2001 From: Val Lorentz Date: Fri, 1 Dec 2023 17:46:50 +0100 Subject: [PATCH 02/19] Move "redacts" key to "content" in redaction events in v11 rooms --- src/service/rooms/timeline/mod.rs | 35 ++++++++++++++++++++++++++++--- 1 file changed, 32 insertions(+), 3 deletions(-) diff --git a/src/service/rooms/timeline/mod.rs b/src/service/rooms/timeline/mod.rs index 4f3b0e59..4b06acb7 100644 --- a/src/service/rooms/timeline/mod.rs +++ b/src/service/rooms/timeline/mod.rs @@ -404,9 +404,38 @@ impl Service { match pdu.kind { TimelineEventType::RoomRedaction => { - if let Some(redact_id) = &pdu.redacts { - self.redact_pdu(redact_id, pdu)?; - } + let room_version_id = self + .get_room_version(&pdu.room_id)? + .expect("Got RoomRedaction in a room of unknown version"); + match room_version_id { + RoomVersionId::V1 + | RoomVersionId::V2 + | RoomVersionId::V3 + | RoomVersionId::V4 + | RoomVersionId::V5 + | RoomVersionId::V6 + | RoomVersionId::V7 + | RoomVersionId::V8 + | RoomVersionId::V9 + | RoomVersionId::V10 => { + if let Some(redact_id) = &pdu.redacts { + self.redact_pdu(redact_id, pdu)?; + } + } + _ => { + #[derive(Deserialize)] + struct Redaction { + redacts: Option, + } + let content = serde_json::from_str::(pdu.content.get()) + .map_err(|_| { + Error::bad_database("Invalid content in redaction pdu.") + })?; + if let Some(redact_id) = &content.redacts { + self.redact_pdu(redact_id, pdu)?; + } + } + }; } TimelineEventType::SpaceChild => { if let Some(_state_key) = &pdu.state_key { From 18bfd79ef2d4a0bb18622bb25b3da23e05a04481 Mon Sep 17 00:00:00 2001 From: Val Lorentz Date: Fri, 1 Dec 2023 18:15:20 +0100 Subject: [PATCH 03/19] Remove "creator" key when upgrading rooms to v11 --- src/api/client_server/room.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/api/client_server/room.rs b/src/api/client_server/room.rs index 6b824040..5f62e9fa 100644 --- a/src/api/client_server/room.rs +++ b/src/api/client_server/room.rs @@ -627,7 +627,10 @@ pub async fn upgrade_room_route( })?, ); } - _ => {} // V11 removed the "creator" key + _ => { + // "creator" key no longer exists in V11 rooms + create_event_content.remove("creator"); + } } create_event_content.insert( "room_version".into(), From fac995036ab02fe30fca6a2dfc682612c4cf43d7 Mon Sep 17 00:00:00 2001 From: Val Lorentz Date: Fri, 1 Dec 2023 18:15:57 +0100 Subject: [PATCH 04/19] create_hash_and_sign_event: Use actual version of RoomCreate events, instead of the default --- src/service/rooms/timeline/mod.rs | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/src/service/rooms/timeline/mod.rs b/src/service/rooms/timeline/mod.rs index 4b06acb7..a6e9f87a 100644 --- a/src/service/rooms/timeline/mod.rs +++ b/src/service/rooms/timeline/mod.rs @@ -695,11 +695,20 @@ impl Service { .take(20) .collect(); - // If there was no create event yet, assume we are creating a room with the default - // version right now - let room_version_id = self - .get_room_version(room_id)? - .unwrap_or_else(|| services().globals.default_room_version()); + // If there was no create event yet, assume we are creating a room + let room_version_id = self.get_room_version(room_id)?.unwrap_or_else(|| { + if event_type != TimelineEventType::RoomCreate { + panic!("non-create event for room of unknown version"); + } + #[derive(Deserialize)] + struct RoomCreate { + room_version: RoomVersionId, + } + let content = serde_json::from_str::(content.get()) + .expect("Invalid content in RoomCreate pdu."); + content.room_version + }); + let room_version = RoomVersion::new(&room_version_id).expect("room version is supported"); let auth_events = services().rooms.state.get_auth_events( From 9646439a94f35a7ab46274c1465ee1192215724a Mon Sep 17 00:00:00 2001 From: Val Lorentz Date: Fri, 1 Dec 2023 18:16:18 +0100 Subject: [PATCH 05/19] Enable support for room v11 --- src/service/globals/mod.rs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/service/globals/mod.rs b/src/service/globals/mod.rs index 9bce8a2c..1aa123ff 100644 --- a/src/service/globals/mod.rs +++ b/src/service/globals/mod.rs @@ -139,7 +139,12 @@ impl Service { RoomVersionId::V10, ]; // Experimental, partially supported room versions - let unstable_room_versions = vec![RoomVersionId::V3, RoomVersionId::V4, RoomVersionId::V5]; + let unstable_room_versions = vec![ + RoomVersionId::V3, + RoomVersionId::V4, + RoomVersionId::V5, + RoomVersionId::V11, + ]; let mut s = Self { db, From 520806d41385cddee20ec8d8a83bc1c491a61280 Mon Sep 17 00:00:00 2001 From: Val Lorentz Date: Fri, 1 Dec 2023 19:08:24 +0100 Subject: [PATCH 06/19] Use Ruma's redact_content_in_place instead of custom implementation --- src/service/pdu.rs | 48 ++++++---------------------------------------- src/utils/error.rs | 2 ++ 2 files changed, 8 insertions(+), 42 deletions(-) diff --git a/src/service/pdu.rs b/src/service/pdu.rs index 9e7f6d1d..c3e8d59c 100644 --- a/src/service/pdu.rs +++ b/src/service/pdu.rs @@ -1,5 +1,6 @@ use crate::Error; use ruma::{ + canonical_json::redact_content_in_place, events::{ room::member::RoomMemberEventContent, space::child::HierarchySpaceChildEvent, AnyEphemeralRoomEvent, AnyMessageLikeEvent, AnyStateEvent, AnyStrippedStateEvent, @@ -56,53 +57,16 @@ impl PduEvent { ) -> crate::Result<()> { self.unsigned = None; - let allowed: &[&str] = match self.kind { - TimelineEventType::RoomMember => &["join_authorised_via_users_server", "membership"], - TimelineEventType::RoomCreate => match room_version_id { - RoomVersionId::V1 - | RoomVersionId::V2 - | RoomVersionId::V3 - | RoomVersionId::V4 - | RoomVersionId::V5 - | RoomVersionId::V6 - | RoomVersionId::V7 - | RoomVersionId::V8 - | RoomVersionId::V9 - | RoomVersionId::V10 => &["creator"], - _ => &[], // V11 removed the creator key - }, - TimelineEventType::RoomJoinRules => &["join_rule"], - TimelineEventType::RoomPowerLevels => &[ - "ban", - "events", - "events_default", - "kick", - "redact", - "state_default", - "users", - "users_default", - ], - TimelineEventType::RoomHistoryVisibility => &["history_visibility"], - _ => &[], - }; - - let mut old_content: BTreeMap = - serde_json::from_str(self.content.get()) - .map_err(|_| Error::bad_database("PDU in db has invalid content."))?; - - let mut new_content = serde_json::Map::new(); - - for key in allowed { - if let Some(value) = old_content.remove(*key) { - new_content.insert((*key).to_owned(), value); - } - } + let mut content = serde_json::from_str(self.content.get()) + .map_err(|_| Error::bad_database("PDU in db has invalid content."))?; + redact_content_in_place(&mut content, &room_version_id, self.kind.to_string()) + .map_err(|e| Error::RedactionError(self.sender.server_name().to_owned(), e))?; self.unsigned = Some(to_raw_value(&json!({ "redacted_because": serde_json::to_value(reason).expect("to_value(PduEvent) always works") })).expect("to string always works")); - self.content = to_raw_value(&new_content).expect("to string always works"); + self.content = to_raw_value(&content).expect("to string always works"); Ok(()) } diff --git a/src/utils/error.rs b/src/utils/error.rs index 6e88cf59..83ef3095 100644 --- a/src/utils/error.rs +++ b/src/utils/error.rs @@ -80,6 +80,8 @@ pub enum Error { #[cfg(feature = "conduit_bin")] #[error("{0}")] PathError(#[from] axum::extract::rejection::PathRejection), + #[error("from {0}: {1}")] + RedactionError(OwnedServerName, ruma::canonical_json::RedactionError), } impl Error { From 5a7bb1e8f104b66e65a16e447d930b478640383a Mon Sep 17 00:00:00 2001 From: Val Lorentz Date: Sat, 2 Dec 2023 17:51:19 +0100 Subject: [PATCH 07/19] Return error instead of panic when first event is not m.room.create --- src/service/rooms/timeline/mod.rs | 33 ++++++++++++++++++++----------- src/utils/error.rs | 2 ++ 2 files changed, 23 insertions(+), 12 deletions(-) diff --git a/src/service/rooms/timeline/mod.rs b/src/service/rooms/timeline/mod.rs index a6e9f87a..06deb069 100644 --- a/src/service/rooms/timeline/mod.rs +++ b/src/service/rooms/timeline/mod.rs @@ -696,18 +696,27 @@ impl Service { .collect(); // If there was no create event yet, assume we are creating a room - let room_version_id = self.get_room_version(room_id)?.unwrap_or_else(|| { - if event_type != TimelineEventType::RoomCreate { - panic!("non-create event for room of unknown version"); - } - #[derive(Deserialize)] - struct RoomCreate { - room_version: RoomVersionId, - } - let content = serde_json::from_str::(content.get()) - .expect("Invalid content in RoomCreate pdu."); - content.room_version - }); + let room_version_id = self + .get_room_version(room_id)? + .or_else(|| { + if event_type == TimelineEventType::RoomCreate { + #[derive(Deserialize)] + struct RoomCreate { + room_version: RoomVersionId, + } + let content = serde_json::from_str::(content.get()) + .expect("Invalid content in RoomCreate pdu."); + Some(content.room_version) + } else { + None + } + }) + .ok_or_else(|| { + Error::InconsistentRoomState( + "non-create event for room of unknown version", + room_id.to_owned(), + ) + })?; let room_version = RoomVersion::new(&room_version_id).expect("room version is supported"); diff --git a/src/utils/error.rs b/src/utils/error.rs index 83ef3095..568ad8a0 100644 --- a/src/utils/error.rs +++ b/src/utils/error.rs @@ -82,6 +82,8 @@ pub enum Error { PathError(#[from] axum::extract::rejection::PathRejection), #[error("from {0}: {1}")] RedactionError(OwnedServerName, ruma::canonical_json::RedactionError), + #[error("{0} in {1}")] + InconsistentRoomState(&'static str, ruma::OwnedRoomId), } impl Error { From eb7ac91cd5a8c62fc5dbd96527712b9750394a70 Mon Sep 17 00:00:00 2001 From: Val Lorentz Date: Sun, 24 Dec 2023 19:02:03 +0100 Subject: [PATCH 08/19] Reuse existing get_room_version --- src/service/rooms/timeline/mod.rs | 53 +++++++++---------------------- 1 file changed, 15 insertions(+), 38 deletions(-) diff --git a/src/service/rooms/timeline/mod.rs b/src/service/rooms/timeline/mod.rs index 06deb069..b0f6cb5e 100644 --- a/src/service/rooms/timeline/mod.rs +++ b/src/service/rooms/timeline/mod.rs @@ -128,27 +128,6 @@ impl Service { self.db.get_pdu_count(event_id) } - /// Returns the version of a room, if known - pub fn get_room_version(&self, room_id: &RoomId) -> Result> { - let create_event = services().rooms.state_accessor.room_state_get( - room_id, - &StateEventType::RoomCreate, - "", - )?; - - let create_event_content: Option = create_event - .as_ref() - .map(|create_event| { - serde_json::from_str(create_event.content.get()).map_err(|e| { - warn!("Invalid create event: {}", e); - Error::bad_database("Invalid create event in db.") - }) - }) - .transpose()?; - - Ok(create_event_content.map(|content| content.room_version)) - } - // TODO Is this the same as the function above? /* #[tracing::instrument(skip(self))] @@ -404,9 +383,7 @@ impl Service { match pdu.kind { TimelineEventType::RoomRedaction => { - let room_version_id = self - .get_room_version(&pdu.room_id)? - .expect("Got RoomRedaction in a room of unknown version"); + let room_version_id = services().rooms.state.get_room_version(&pdu.room_id)?; match room_version_id { RoomVersionId::V1 | RoomVersionId::V2 @@ -696,9 +673,11 @@ impl Service { .collect(); // If there was no create event yet, assume we are creating a room - let room_version_id = self - .get_room_version(room_id)? - .or_else(|| { + let room_version_id = services() + .rooms + .state + .get_room_version(room_id) + .or_else(|_| { if event_type == TimelineEventType::RoomCreate { #[derive(Deserialize)] struct RoomCreate { @@ -706,16 +685,13 @@ impl Service { } let content = serde_json::from_str::(content.get()) .expect("Invalid content in RoomCreate pdu."); - Some(content.room_version) + Ok(content.room_version) } else { - None + Err(Error::InconsistentRoomState( + "non-create event for room of unknown version", + room_id.to_owned(), + )) } - }) - .ok_or_else(|| { - Error::InconsistentRoomState( - "non-create event for room of unknown version", - room_id.to_owned(), - ) })?; let room_version = RoomVersion::new(&room_version_id).expect("room version is supported"); @@ -1085,9 +1061,10 @@ impl Service { let mut pdu = self .get_pdu_from_id(&pdu_id)? .ok_or_else(|| Error::bad_database("PDU ID points to invalid PDU."))?; - let room_version_id = self.get_room_version(&pdu.room_id)?.ok_or_else(|| { - Error::bad_database("Trying to redact PDU in in room of unknown version") - })?; + let room_version_id = services() + .rooms + .state + .get_room_version(&pdu.room_id)?; pdu.redact(room_version_id, reason)?; self.replace_pdu( &pdu_id, From 8175bc124629bff5808270c496d7e1f32ee1cdcb Mon Sep 17 00:00:00 2001 From: Val Lorentz Date: Sun, 24 Dec 2023 19:04:48 +0100 Subject: [PATCH 09/19] Explicitly match RoomVersionId::V11 --- src/api/client_server/room.rs | 9 ++++++--- src/service/admin/mod.rs | 3 ++- src/service/rooms/timeline/mod.rs | 3 ++- 3 files changed, 10 insertions(+), 5 deletions(-) diff --git a/src/api/client_server/room.rs b/src/api/client_server/room.rs index 5f62e9fa..128f84f9 100644 --- a/src/api/client_server/room.rs +++ b/src/api/client_server/room.rs @@ -146,7 +146,8 @@ pub async fn create_room_route( })?, ); } - _ => {} // V11 removed the "creator" key + RoomVersionId::V11 => {} // V11 removed the "creator" key + _ => panic!("Unexpected room version {}", room_version), } content.insert( @@ -170,7 +171,8 @@ pub async fn create_room_route( | RoomVersionId::V8 | RoomVersionId::V9 | RoomVersionId::V10 => RoomCreateEventContent::new_v1(sender_user.clone()), - _ => RoomCreateEventContent::new_v11(), + RoomVersionId::V11 => RoomCreateEventContent::new_v11(), + _ => panic!("Unexpected room version {}", room_version), }; let mut content = serde_json::from_str::( to_raw_value(&content) @@ -627,10 +629,11 @@ pub async fn upgrade_room_route( })?, ); } - _ => { + RoomVersionId::V11 => { // "creator" key no longer exists in V11 rooms create_event_content.remove("creator"); } + _ => panic!("Unexpected room version {}", body.new_version) } create_event_content.insert( "room_version".into(), diff --git a/src/service/admin/mod.rs b/src/service/admin/mod.rs index 292943a3..08b6e621 100644 --- a/src/service/admin/mod.rs +++ b/src/service/admin/mod.rs @@ -944,7 +944,8 @@ impl Service { | RoomVersionId::V8 | RoomVersionId::V9 | RoomVersionId::V10 => RoomCreateEventContent::new_v1(conduit_user.clone()), - _ => RoomCreateEventContent::new_v11(), + RoomVersionId::V11 => RoomCreateEventContent::new_v11(), + _ => panic!("Unexpected room version {}", room_version), }; content.federate = true; content.predecessor = None; diff --git a/src/service/rooms/timeline/mod.rs b/src/service/rooms/timeline/mod.rs index b0f6cb5e..d0943c9a 100644 --- a/src/service/rooms/timeline/mod.rs +++ b/src/service/rooms/timeline/mod.rs @@ -399,7 +399,7 @@ impl Service { self.redact_pdu(redact_id, pdu)?; } } - _ => { + RoomVersionId::V11 => { #[derive(Deserialize)] struct Redaction { redacts: Option, @@ -412,6 +412,7 @@ impl Service { self.redact_pdu(redact_id, pdu)?; } } + _ => panic!("Unexpected room version {}", room_version_id) }; } TimelineEventType::SpaceChild => { From e88d137bd71232d0cc9793757cf3d79a8fd8c998 Mon Sep 17 00:00:00 2001 From: Matthias Ahouansou Date: Thu, 11 Apr 2024 17:19:42 +0000 Subject: [PATCH 10/19] Replace panic!() with unreachable!() --- src/api/client_server/room.rs | 6 +++--- src/service/rooms/timeline/mod.rs | 12 ++---------- 2 files changed, 5 insertions(+), 13 deletions(-) diff --git a/src/api/client_server/room.rs b/src/api/client_server/room.rs index 128f84f9..ee89439d 100644 --- a/src/api/client_server/room.rs +++ b/src/api/client_server/room.rs @@ -147,7 +147,7 @@ pub async fn create_room_route( ); } RoomVersionId::V11 => {} // V11 removed the "creator" key - _ => panic!("Unexpected room version {}", room_version), + _ => unreachable!("Validity of room version already checked"), } content.insert( @@ -172,7 +172,7 @@ pub async fn create_room_route( | RoomVersionId::V9 | RoomVersionId::V10 => RoomCreateEventContent::new_v1(sender_user.clone()), RoomVersionId::V11 => RoomCreateEventContent::new_v11(), - _ => panic!("Unexpected room version {}", room_version), + _ => unreachable!("Validity of room version already checked"), }; let mut content = serde_json::from_str::( to_raw_value(&content) @@ -633,7 +633,7 @@ pub async fn upgrade_room_route( // "creator" key no longer exists in V11 rooms create_event_content.remove("creator"); } - _ => panic!("Unexpected room version {}", body.new_version) + _ => unreachable!("Validity of room version already checked") } create_event_content.insert( "room_version".into(), diff --git a/src/service/rooms/timeline/mod.rs b/src/service/rooms/timeline/mod.rs index d0943c9a..cb2e6494 100644 --- a/src/service/rooms/timeline/mod.rs +++ b/src/service/rooms/timeline/mod.rs @@ -400,11 +400,7 @@ impl Service { } } RoomVersionId::V11 => { - #[derive(Deserialize)] - struct Redaction { - redacts: Option, - } - let content = serde_json::from_str::(pdu.content.get()) + let content = serde_json::from_str::(pdu.content.get()) .map_err(|_| { Error::bad_database("Invalid content in redaction pdu.") })?; @@ -680,11 +676,7 @@ impl Service { .get_room_version(room_id) .or_else(|_| { if event_type == TimelineEventType::RoomCreate { - #[derive(Deserialize)] - struct RoomCreate { - room_version: RoomVersionId, - } - let content = serde_json::from_str::(content.get()) + let content = serde_json::from_str::(content.get()) .expect("Invalid content in RoomCreate pdu."); Ok(content.room_version) } else { From 9e6ce8326f88d12b6fe3d5cd7bfcc440fd684f9b Mon Sep 17 00:00:00 2001 From: Matthias Ahouansou Date: Thu, 11 Apr 2024 17:21:00 +0000 Subject: [PATCH 11/19] Remove TODO --- src/api/client_server/room.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/api/client_server/room.rs b/src/api/client_server/room.rs index ee89439d..df325059 100644 --- a/src/api/client_server/room.rs +++ b/src/api/client_server/room.rs @@ -159,7 +159,6 @@ pub async fn create_room_route( content } None => { - // TODO: Add correct value for v11 let content = match room_version { RoomVersionId::V1 | RoomVersionId::V2 From ab8592526fe6f061756f730c349a0e575072f627 Mon Sep 17 00:00:00 2001 From: Matthias Ahouansou Date: Fri, 12 Apr 2024 05:14:39 +0000 Subject: [PATCH 12/19] Replace panic!() with unreachable!() --- src/service/admin/mod.rs | 2 +- src/service/globals/mod.rs | 2 +- src/service/rooms/timeline/mod.rs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/service/admin/mod.rs b/src/service/admin/mod.rs index c5f014cd..484fc134 100644 --- a/src/service/admin/mod.rs +++ b/src/service/admin/mod.rs @@ -978,7 +978,7 @@ impl Service { | RoomVersionId::V9 | RoomVersionId::V10 => RoomCreateEventContent::new_v1(conduit_user.clone()), RoomVersionId::V11 => RoomCreateEventContent::new_v11(), - _ => panic!("Unexpected room version {}", room_version), + _ => unreachable!("Validity of room version already checked"), }; content.federate = true; content.predecessor = None; diff --git a/src/service/globals/mod.rs b/src/service/globals/mod.rs index cab61c66..3eb49e10 100644 --- a/src/service/globals/mod.rs +++ b/src/service/globals/mod.rs @@ -178,13 +178,13 @@ impl Service { RoomVersionId::V8, RoomVersionId::V9, RoomVersionId::V10, + RoomVersionId::V11, ]; // Experimental, partially supported room versions let unstable_room_versions = vec![ RoomVersionId::V3, RoomVersionId::V4, RoomVersionId::V5, - RoomVersionId::V11, ]; let mut s = Self { diff --git a/src/service/rooms/timeline/mod.rs b/src/service/rooms/timeline/mod.rs index dff727af..2aeffa60 100644 --- a/src/service/rooms/timeline/mod.rs +++ b/src/service/rooms/timeline/mod.rs @@ -407,7 +407,7 @@ impl Service { self.redact_pdu(redact_id, pdu)?; } } - _ => panic!("Unexpected room version {}", room_version_id) + _ => unreachable!("Validity of room version already checked") }; } TimelineEventType::SpaceChild => { From 92817213d5c828c7dc95920ded1562ec4b2003f3 Mon Sep 17 00:00:00 2001 From: Matthias Ahouansou Date: Fri, 12 Apr 2024 05:15:37 +0000 Subject: [PATCH 13/19] Add missing import --- 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 2aeffa60..2887fdd7 100644 --- a/src/service/rooms/timeline/mod.rs +++ b/src/service/rooms/timeline/mod.rs @@ -15,7 +15,7 @@ use ruma::{ push_rules::PushRulesEvent, room::{ create::RoomCreateEventContent, encrypted::Relation, member::MembershipState, - power_levels::RoomPowerLevelsEventContent, + power_levels::RoomPowerLevelsEventContent, redaction::RoomRedactionEventContent, }, GlobalAccountDataEventType, StateEventType, TimelineEventType, }, From 7a7c09785e4895336bb3559c05f8accf0c4c23ac Mon Sep 17 00:00:00 2001 From: Matthias Ahouansou Date: Tue, 16 Apr 2024 22:45:04 +0100 Subject: [PATCH 14/19] feat(pdu): copy top level redact to content and vice versa --- src/api/client_server/room.rs | 2 +- src/service/globals/mod.rs | 6 +---- src/service/pdu.rs | 40 ++++++++++++++++++++++++------- src/service/rooms/timeline/mod.rs | 16 ++++++------- 4 files changed, 41 insertions(+), 23 deletions(-) diff --git a/src/api/client_server/room.rs b/src/api/client_server/room.rs index 203a8b70..6f92e9f6 100644 --- a/src/api/client_server/room.rs +++ b/src/api/client_server/room.rs @@ -671,7 +671,7 @@ pub async fn upgrade_room_route( // "creator" key no longer exists in V11 rooms create_event_content.remove("creator"); } - _ => unreachable!("Validity of room version already checked") + _ => unreachable!("Validity of room version already checked"), } create_event_content.insert( "room_version".into(), diff --git a/src/service/globals/mod.rs b/src/service/globals/mod.rs index 3eb49e10..798c725a 100644 --- a/src/service/globals/mod.rs +++ b/src/service/globals/mod.rs @@ -181,11 +181,7 @@ impl Service { RoomVersionId::V11, ]; // Experimental, partially supported room versions - let unstable_room_versions = vec![ - RoomVersionId::V3, - RoomVersionId::V4, - RoomVersionId::V5, - ]; + let unstable_room_versions = vec![RoomVersionId::V3, RoomVersionId::V4, RoomVersionId::V5]; let mut s = Self { db, diff --git a/src/service/pdu.rs b/src/service/pdu.rs index 0a9ea861..a10573be 100644 --- a/src/service/pdu.rs +++ b/src/service/pdu.rs @@ -2,7 +2,8 @@ use crate::Error; use ruma::{ canonical_json::redact_content_in_place, events::{ - room::member::RoomMemberEventContent, space::child::HierarchySpaceChildEvent, + room::{member::RoomMemberEventContent, redaction::RoomRedactionEventContent}, + space::child::HierarchySpaceChildEvent, AnyEphemeralRoomEvent, AnyMessageLikeEvent, AnyStateEvent, AnyStrippedStateEvent, AnySyncStateEvent, AnySyncTimelineEvent, AnyTimelineEvent, StateEvent, TimelineEventType, }, @@ -25,7 +26,7 @@ pub struct EventHash { pub sha256: String, } -#[derive(Clone, Deserialize, Serialize, Debug)] +#[derive(Clone, Deserialize, Debug, Serialize)] pub struct PduEvent { pub event_id: Arc, pub room_id: OwnedRoomId, @@ -96,10 +97,31 @@ impl PduEvent { Ok(()) } + pub fn copy_redacts(&self) -> (Option>, Box) { + if self.kind == TimelineEventType::RoomRedaction { + if let Ok(mut content) = + serde_json::from_str::(self.content.get()) + { + if let Some(redacts) = content.redacts { + return (Some(redacts.into()), self.content.clone()); + } else if let Some(redacts) = self.redacts.clone() { + content.redacts = Some(redacts.into()); + return ( + self.redacts.clone(), + to_raw_value(&content).expect("Must be valid, we only added redacts field"), + ); + } + } + } + + (self.redacts.clone(), self.content.clone()) + } + #[tracing::instrument(skip(self))] pub fn to_sync_room_event(&self) -> Raw { + let (redacts, content) = self.copy_redacts(); let mut json = json!({ - "content": self.content, + "content": content, "type": self.kind, "event_id": self.event_id, "sender": self.sender, @@ -112,7 +134,7 @@ impl PduEvent { if let Some(state_key) = &self.state_key { json["state_key"] = json!(state_key); } - if let Some(redacts) = &self.redacts { + if let Some(redacts) = &redacts { json["redacts"] = json!(redacts); } @@ -146,8 +168,9 @@ impl PduEvent { #[tracing::instrument(skip(self))] pub fn to_room_event(&self) -> Raw { + let (redacts, content) = self.copy_redacts(); let mut json = json!({ - "content": self.content, + "content": content, "type": self.kind, "event_id": self.event_id, "sender": self.sender, @@ -161,7 +184,7 @@ impl PduEvent { if let Some(state_key) = &self.state_key { json["state_key"] = json!(state_key); } - if let Some(redacts) = &self.redacts { + if let Some(redacts) = &redacts { json["redacts"] = json!(redacts); } @@ -170,8 +193,9 @@ impl PduEvent { #[tracing::instrument(skip(self))] pub fn to_message_like_event(&self) -> Raw { + let (redacts, content) = self.copy_redacts(); let mut json = json!({ - "content": self.content, + "content": content, "type": self.kind, "event_id": self.event_id, "sender": self.sender, @@ -185,7 +209,7 @@ impl PduEvent { if let Some(state_key) = &self.state_key { json["state_key"] = json!(state_key); } - if let Some(redacts) = &self.redacts { + if let Some(redacts) = &redacts { json["redacts"] = json!(redacts); } diff --git a/src/service/rooms/timeline/mod.rs b/src/service/rooms/timeline/mod.rs index 2887fdd7..8f713c45 100644 --- a/src/service/rooms/timeline/mod.rs +++ b/src/service/rooms/timeline/mod.rs @@ -399,15 +399,16 @@ impl Service { } } RoomVersionId::V11 => { - let content = serde_json::from_str::(pdu.content.get()) - .map_err(|_| { - Error::bad_database("Invalid content in redaction pdu.") - })?; + let content = + serde_json::from_str::(pdu.content.get()) + .map_err(|_| { + Error::bad_database("Invalid content in redaction pdu.") + })?; if let Some(redact_id) = &content.redacts { self.redact_pdu(redact_id, pdu)?; } } - _ => unreachable!("Validity of room version already checked") + _ => unreachable!("Validity of room version already checked"), }; } TimelineEventType::SpaceChild => { @@ -1015,10 +1016,7 @@ impl Service { let mut pdu = self .get_pdu_from_id(&pdu_id)? .ok_or_else(|| Error::bad_database("PDU ID points to invalid PDU."))?; - let room_version_id = services() - .rooms - .state - .get_room_version(&pdu.room_id)?; + let room_version_id = services().rooms.state.get_room_version(&pdu.room_id)?; pdu.redact(room_version_id, reason)?; self.replace_pdu( &pdu_id, From eb6801290b90e400b2c00c9275d6f90a2d3baad4 Mon Sep 17 00:00:00 2001 From: Valentin Lorentz Date: Wed, 17 Apr 2024 19:37:32 +0200 Subject: [PATCH 15/19] Document copy_redacts --- src/service/pdu.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/service/pdu.rs b/src/service/pdu.rs index a10573be..b5c95981 100644 --- a/src/service/pdu.rs +++ b/src/service/pdu.rs @@ -97,6 +97,14 @@ impl PduEvent { Ok(()) } + /// Copies the `redacts` property of the event to the `content` dict + /// + /// This follows the specification's + /// [recommendation](https://spec.matrix.org/v1.10/rooms/v11/#moving-the-redacts-property-of-mroomredaction-events-to-a-content-property): + /// + /// > For backwards-compatibility with older clients, servers should add a redacts + /// > property to the top level of m.room.redaction events in when serving such events + /// > over the Client-Server API. pub fn copy_redacts(&self) -> (Option>, Box) { if self.kind == TimelineEventType::RoomRedaction { if let Ok(mut content) = From 2d8c551cd5f23c8ab7ab24b0bdde9dd413a5fa20 Mon Sep 17 00:00:00 2001 From: Valentin Lorentz Date: Wed, 17 Apr 2024 19:41:38 +0200 Subject: [PATCH 16/19] Fix doc --- src/service/pdu.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/service/pdu.rs b/src/service/pdu.rs index b5c95981..a51d7ec5 100644 --- a/src/service/pdu.rs +++ b/src/service/pdu.rs @@ -97,7 +97,7 @@ impl PduEvent { Ok(()) } - /// Copies the `redacts` property of the event to the `content` dict + /// Copies the `redacts` property of the event to the `content` dict and vice-versa. /// /// This follows the specification's /// [recommendation](https://spec.matrix.org/v1.10/rooms/v11/#moving-the-redacts-property-of-mroomredaction-events-to-a-content-property): @@ -105,6 +105,10 @@ impl PduEvent { /// > For backwards-compatibility with older clients, servers should add a redacts /// > property to the top level of m.room.redaction events in when serving such events /// > over the Client-Server API. + /// > + /// > For improved compatibility with newer clients, servers should add a redacts property + /// > to the content of m.room.redaction events in older room versions when serving + /// > such events over the Client-Server API. pub fn copy_redacts(&self) -> (Option>, Box) { if self.kind == TimelineEventType::RoomRedaction { if let Ok(mut content) = From 00d6aeddb6c43660326277a675fadb08a1617c10 Mon Sep 17 00:00:00 2001 From: Matthias Ahouansou Date: Tue, 23 Apr 2024 21:42:01 +0100 Subject: [PATCH 17/19] refactor(redactions): move checks inside conduit ruma was already accidentally performing these checks for us, but this shouldn't be the case --- src/service/rooms/event_handler/mod.rs | 15 +++++++- src/service/rooms/state_accessor/mod.rs | 51 +++++++++++++++++++++++++ src/service/rooms/timeline/mod.rs | 35 ++++++++++++++++- 3 files changed, 97 insertions(+), 4 deletions(-) diff --git a/src/service/rooms/event_handler/mod.rs b/src/service/rooms/event_handler/mod.rs index 1547d406..ada289fb 100644 --- a/src/service/rooms/event_handler/mod.rs +++ b/src/service/rooms/event_handler/mod.rs @@ -24,7 +24,7 @@ use ruma::{ }, events::{ room::{create::RoomCreateEventContent, server_acl::RoomServerAclEventContent}, - StateEventType, + StateEventType, TimelineEventType, }, int, serde::Base64, @@ -796,7 +796,18 @@ impl Service { None::, |k, s| auth_events.get(&(k.clone(), s.to_owned())), ) - .map_err(|_e| Error::BadRequest(ErrorKind::InvalidParam, "Auth check failed."))?; + .map_err(|_e| Error::BadRequest(ErrorKind::InvalidParam, "Auth check failed."))? + || if let Some(redact_id) = &incoming_pdu.redacts { + incoming_pdu.kind == TimelineEventType::RoomRedaction + && !services().rooms.state_accessor.user_can_redact( + redact_id, + &incoming_pdu.sender, + &incoming_pdu.room_id, + true, + )? + } else { + false + }; // 13. Use state resolution to find new room state diff --git a/src/service/rooms/state_accessor/mod.rs b/src/service/rooms/state_accessor/mod.rs index c287edc5..8ca1b778 100644 --- a/src/service/rooms/state_accessor/mod.rs +++ b/src/service/rooms/state_accessor/mod.rs @@ -13,9 +13,11 @@ use ruma::{ history_visibility::{HistoryVisibility, RoomHistoryVisibilityEventContent}, member::{MembershipState, RoomMemberEventContent}, name::RoomNameEventContent, + power_levels::{RoomPowerLevels, RoomPowerLevelsEventContent}, }, StateEventType, }, + state_res::Event, EventId, JsOption, OwnedServerName, OwnedUserId, RoomId, ServerName, UserId, }; use serde_json::value::to_raw_value; @@ -351,4 +353,53 @@ impl Service { .map_err(|_| Error::bad_database("Invalid room member event in database.")) }) } + + /// Checks if a given user can redact a given event + /// + /// If federation is true, it allows redaction events from any user of the same server as the original event sender + pub fn user_can_redact( + &self, + redacts: &EventId, + sender: &UserId, + room_id: &RoomId, + federation: bool, + ) -> Result { + self.room_state_get(room_id, &StateEventType::RoomPowerLevels, "")? + .map(|e| { + serde_json::from_str(e.content.get()) + .map(|c: RoomPowerLevelsEventContent| c.into()) + .map(|e: RoomPowerLevels| { + e.user_can_redact_event_of_other(sender) + || e.user_can_redact_own_event(sender) + && if let Ok(Some(pdu)) = services().rooms.timeline.get_pdu(redacts) + { + if federation { + pdu.sender().server_name() == sender.server_name() + } else { + pdu.sender == sender + } + } else { + false + } + }) + .map_err(|_| { + Error::bad_database("Invalid m.room.power_levels event in database") + }) + }) + // Falling back on m.room.create to judge power levels + .unwrap_or_else(|| { + if let Some(pdu) = self.room_state_get(room_id, &StateEventType::RoomCreate, "")? { + Ok(pdu.sender == sender + || if let Ok(Some(pdu)) = services().rooms.timeline.get_pdu(redacts) { + pdu.sender == sender + } else { + false + }) + } else { + Err(Error::bad_database( + "No m.room.power_levels or m.room.create events in database for room", + )) + } + }) + } } diff --git a/src/service/rooms/timeline/mod.rs b/src/service/rooms/timeline/mod.rs index 8f713c45..2752abe4 100644 --- a/src/service/rooms/timeline/mod.rs +++ b/src/service/rooms/timeline/mod.rs @@ -395,7 +395,14 @@ impl Service { | RoomVersionId::V9 | RoomVersionId::V10 => { if let Some(redact_id) = &pdu.redacts { - self.redact_pdu(redact_id, pdu)?; + if services().rooms.state_accessor.user_can_redact( + redact_id, + &pdu.sender, + &pdu.room_id, + false, + )? { + self.redact_pdu(redact_id, pdu)?; + } } } RoomVersionId::V11 => { @@ -405,7 +412,14 @@ impl Service { Error::bad_database("Invalid content in redaction pdu.") })?; if let Some(redact_id) = &content.redacts { - self.redact_pdu(redact_id, pdu)?; + if services().rooms.state_accessor.user_can_redact( + redact_id, + &pdu.sender, + &pdu.room_id, + false, + )? { + self.redact_pdu(redact_id, pdu)?; + } } } _ => unreachable!("Validity of room version already checked"), @@ -885,6 +899,23 @@ impl Service { } } + // If redaction event is not authorized, do not append it to the timeline + if let Some(redact_id) = &pdu.redacts { + if pdu.kind == TimelineEventType::RoomRedaction + && !services().rooms.state_accessor.user_can_redact( + redact_id, + &pdu.sender, + &pdu.room_id, + false, + )? + { + return Err(Error::BadRequest( + ErrorKind::Forbidden, + "User cannot redact this event.", + )); + } + }; + // We append to state before appending the pdu, so we don't have a moment in time with the // pdu without it's state. This is okay because append_pdu can't fail. let statehashid = services().rooms.state.append_to_state(&pdu)?; From 89c1c2109cba0cef29b09201d6aa188889c2cfdd Mon Sep 17 00:00:00 2001 From: Valentin Lorentz Date: Wed, 24 Apr 2024 08:29:47 +0200 Subject: [PATCH 18/19] Link to the specification from user_can_redact's documentation --- src/service/rooms/state_accessor/mod.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/service/rooms/state_accessor/mod.rs b/src/service/rooms/state_accessor/mod.rs index 8ca1b778..9a3580dd 100644 --- a/src/service/rooms/state_accessor/mod.rs +++ b/src/service/rooms/state_accessor/mod.rs @@ -356,7 +356,9 @@ impl Service { /// Checks if a given user can redact a given event /// - /// If federation is true, it allows redaction events from any user of the same server as the original event sender + /// If `federation` is `true`, it allows redaction events from any user of the same server + /// as the original event sender, [as required by room versions >= + /// v3](https://spec.matrix.org/v1.10/rooms/v11/#handling-redactions) pub fn user_can_redact( &self, redacts: &EventId, From 1c4ae8d268dc58829b5114ac2f0441c6f72e499d Mon Sep 17 00:00:00 2001 From: Matthias Ahouansou Date: Wed, 24 Apr 2024 09:28:16 +0100 Subject: [PATCH 19/19] fix(redaction): use content.redacts when checking v11 events --- src/service/rooms/event_handler/mod.rs | 60 ++++++++++++++++++----- src/service/rooms/timeline/mod.rs | 68 ++++++++++++++++++++------ 2 files changed, 102 insertions(+), 26 deletions(-) diff --git a/src/service/rooms/event_handler/mod.rs b/src/service/rooms/event_handler/mod.rs index ada289fb..b7817e50 100644 --- a/src/service/rooms/event_handler/mod.rs +++ b/src/service/rooms/event_handler/mod.rs @@ -23,7 +23,10 @@ use ruma::{ }, }, events::{ - room::{create::RoomCreateEventContent, server_acl::RoomServerAclEventContent}, + room::{ + create::RoomCreateEventContent, redaction::RoomRedactionEventContent, + server_acl::RoomServerAclEventContent, + }, StateEventType, TimelineEventType, }, int, @@ -797,17 +800,50 @@ impl Service { |k, s| auth_events.get(&(k.clone(), s.to_owned())), ) .map_err(|_e| Error::BadRequest(ErrorKind::InvalidParam, "Auth check failed."))? - || if let Some(redact_id) = &incoming_pdu.redacts { - incoming_pdu.kind == TimelineEventType::RoomRedaction - && !services().rooms.state_accessor.user_can_redact( - redact_id, - &incoming_pdu.sender, - &incoming_pdu.room_id, - true, - )? - } else { - false - }; + || incoming_pdu.kind == TimelineEventType::RoomRedaction + && match room_version_id { + RoomVersionId::V1 + | RoomVersionId::V2 + | RoomVersionId::V3 + | RoomVersionId::V4 + | RoomVersionId::V5 + | RoomVersionId::V6 + | RoomVersionId::V7 + | RoomVersionId::V8 + | RoomVersionId::V9 + | RoomVersionId::V10 => { + if let Some(redact_id) = &incoming_pdu.redacts { + !services().rooms.state_accessor.user_can_redact( + redact_id, + &incoming_pdu.sender, + &incoming_pdu.room_id, + true, + )? + } else { + false + } + } + RoomVersionId::V11 => { + let content = serde_json::from_str::( + incoming_pdu.content.get(), + ) + .map_err(|_| Error::bad_database("Invalid content in redaction pdu."))?; + + if let Some(redact_id) = &content.redacts { + !services().rooms.state_accessor.user_can_redact( + redact_id, + &incoming_pdu.sender, + &incoming_pdu.room_id, + true, + )? + } else { + false + } + } + _ => { + unreachable!("Validity of room version already checked") + } + }; // 13. Use state resolution to find new room state diff --git a/src/service/rooms/timeline/mod.rs b/src/service/rooms/timeline/mod.rs index 2752abe4..acb00d01 100644 --- a/src/service/rooms/timeline/mod.rs +++ b/src/service/rooms/timeline/mod.rs @@ -900,21 +900,61 @@ impl Service { } // If redaction event is not authorized, do not append it to the timeline - if let Some(redact_id) = &pdu.redacts { - if pdu.kind == TimelineEventType::RoomRedaction - && !services().rooms.state_accessor.user_can_redact( - redact_id, - &pdu.sender, - &pdu.room_id, - false, - )? - { - return Err(Error::BadRequest( - ErrorKind::Forbidden, - "User cannot redact this event.", - )); + if pdu.kind == TimelineEventType::RoomRedaction { + match services().rooms.state.get_room_version(&pdu.room_id)? { + RoomVersionId::V1 + | RoomVersionId::V2 + | RoomVersionId::V3 + | RoomVersionId::V4 + | RoomVersionId::V5 + | RoomVersionId::V6 + | RoomVersionId::V7 + | RoomVersionId::V8 + | RoomVersionId::V9 + | RoomVersionId::V10 => { + if let Some(redact_id) = &pdu.redacts { + if !services().rooms.state_accessor.user_can_redact( + redact_id, + &pdu.sender, + &pdu.room_id, + false, + )? { + return Err(Error::BadRequest( + ErrorKind::Forbidden, + "User cannot redact this event.", + )); + } + }; + } + RoomVersionId::V11 => { + let content = + serde_json::from_str::(pdu.content.get()) + .map_err(|_| { + Error::bad_database("Invalid content in redaction pdu.") + })?; + + if let Some(redact_id) = &content.redacts { + if !services().rooms.state_accessor.user_can_redact( + redact_id, + &pdu.sender, + &pdu.room_id, + false, + )? { + return Err(Error::BadRequest( + ErrorKind::Forbidden, + "User cannot redact this event.", + )); + } + } + } + _ => { + return Err(Error::BadRequest( + ErrorKind::UnsupportedRoomVersion, + "Unsupported room version", + )); + } } - }; + } // We append to state before appending the pdu, so we don't have a moment in time with the // pdu without it's state. This is okay because append_pdu can't fail.