diff --git a/src/service/rooms/event_handler/mod.rs b/src/service/rooms/event_handler/mod.rs index e2cd5066..813b8d4a 100644 --- a/src/service/rooms/event_handler/mod.rs +++ b/src/service/rooms/event_handler/mod.rs @@ -19,7 +19,7 @@ use ruma::{ }, events::{ room::{create::RoomCreateEventContent, server_acl::RoomServerAclEventContent}, - StateEventType, + StateEventType, TimelineEventType, }, int, serde::Base64, @@ -525,7 +525,18 @@ impl Service { let soft_fail = !state_res::event_auth::auth_check(&room_version, &incoming_pdu, None::, |k, s| { auth_events.get(&(k.clone(), s.to_owned())) }) - .map_err(|_e| Error::BadRequest(ErrorKind::forbidden(), "Auth check failed."))?; + .map_err(|_e| Error::BadRequest(ErrorKind::forbidden(), "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 ab290cbd..06293ea3 100644 --- a/src/service/rooms/state_accessor/mod.rs +++ b/src/service/rooms/state_accessor/mod.rs @@ -16,6 +16,7 @@ use ruma::{ history_visibility::{HistoryVisibility, RoomHistoryVisibilityEventContent}, member::{MembershipState, RoomMemberEventContent}, name::RoomNameEventContent, + power_levels::{RoomPowerLevels, RoomPowerLevelsEventContent}, topic::RoomTopicEventContent, }, StateEventType, @@ -355,4 +356,49 @@ 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 + 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_or_else( + || { + // Falling back on m.room.create to judge power level + 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", + )) + } + }, + |event| { + serde_json::from_str(event.content.get()) + .map(|content: RoomPowerLevelsEventContent| content.into()) + .map(|event: RoomPowerLevels| { + event.user_can_redact_event_of_other(sender) + || event.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")) + }, + ) + } } diff --git a/src/service/rooms/timeline/mod.rs b/src/service/rooms/timeline/mod.rs index ed369334..7712ab90 100644 --- a/src/service/rooms/timeline/mod.rs +++ b/src/service/rooms/timeline/mod.rs @@ -397,21 +397,36 @@ impl Service { | RoomVersionId::V9 | RoomVersionId::V10 => { if let Some(redact_id) = &pdu.redacts { - self.redact_pdu(redact_id, pdu, shortroomid)?; + if services().rooms.state_accessor.user_can_redact( + redact_id, + &pdu.sender, + &pdu.room_id, + false, + )? { + self.redact_pdu(redact_id, pdu, shortroomid)?; + } } }, RoomVersionId::V11 => { let content = serde_json::from_str::(pdu.content.get()).map_err(|e| { warn!("Invalid content in redaction pdu: {e}"); - Error::bad_database("Invalid content in redaction pdu.") + Error::bad_database("Invalid content in redaction pdu") })?; + if let Some(redact_id) = &content.redacts { - self.redact_pdu(redact_id, pdu, shortroomid)?; + if services().rooms.state_accessor.user_can_redact( + redact_id, + &pdu.sender, + &pdu.room_id, + false, + )? { + self.redact_pdu(redact_id, pdu, shortroomid)?; + } } }, _ => { - warn!("Unexpected or unsupported room version {}", room_version_id); + warn!("Unexpected or unsupported room version {room_version_id}"); return Err(Error::BadRequest( ErrorKind::BadJson, "Unexpected or unsupported room version found", @@ -703,7 +718,7 @@ impl Service { // Hash and sign let mut pdu_json = utils::to_canonical_object(&pdu).map_err(|e| { - error!("Failed to convert PDU to canonical JSON: {}", e); + error!("Failed to convert PDU to canonical JSON: {e}"); Error::bad_database("Failed to convert PDU to canonical JSON.") })?; @@ -778,7 +793,7 @@ impl Service { warn!("Encryption is not allowed in the admins room"); return Err(Error::BadRequest( ErrorKind::forbidden(), - "Encryption is not allowed in the admins room.", + "Encryption is not allowed in the admins room", )); }, TimelineEventType::RoomMember => { @@ -789,14 +804,14 @@ impl Service { let server_user = &services().globals.server_user.to_string(); let content = serde_json::from_str::(pdu.content.get()) - .map_err(|_| Error::bad_database("Invalid content in pdu."))?; + .map_err(|_| Error::bad_database("Invalid content in pdu"))?; if content.membership == MembershipState::Leave { if target == server_user { - warn!("Conduit user cannot leave from admins room"); + warn!("Server user cannot leave from admins room"); return Err(Error::BadRequest( ErrorKind::forbidden(), - "Conduit user cannot leave from admins room.", + "Server user cannot leave from admins room.", )); } @@ -818,10 +833,10 @@ impl Service { if content.membership == MembershipState::Ban && pdu.state_key().is_some() { if target == server_user { - warn!("Conduit user cannot be banned in admins room"); + warn!("Server user cannot be banned in admins room"); return Err(Error::BadRequest( ErrorKind::forbidden(), - "Conduit user cannot be banned in admins room.", + "Server user cannot be banned in admins room.", )); } @@ -846,6 +861,18 @@ 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.