redaction fixes

This commit is contained in:
strawberry 2024-06-22 21:21:16 +00:00 committed by Jason Volk
parent cbb97b4fdf
commit 5f46623371
3 changed files with 97 additions and 13 deletions

View file

@ -19,7 +19,7 @@ use ruma::{
}, },
events::{ events::{
room::{create::RoomCreateEventContent, server_acl::RoomServerAclEventContent}, room::{create::RoomCreateEventContent, server_acl::RoomServerAclEventContent},
StateEventType, StateEventType, TimelineEventType,
}, },
int, int,
serde::Base64, serde::Base64,
@ -525,7 +525,18 @@ impl Service {
let soft_fail = !state_res::event_auth::auth_check(&room_version, &incoming_pdu, None::<PduEvent>, |k, s| { let soft_fail = !state_res::event_auth::auth_check(&room_version, &incoming_pdu, None::<PduEvent>, |k, s| {
auth_events.get(&(k.clone(), s.to_owned())) 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 // 13. Use state resolution to find new room state

View file

@ -16,6 +16,7 @@ use ruma::{
history_visibility::{HistoryVisibility, RoomHistoryVisibilityEventContent}, history_visibility::{HistoryVisibility, RoomHistoryVisibilityEventContent},
member::{MembershipState, RoomMemberEventContent}, member::{MembershipState, RoomMemberEventContent},
name::RoomNameEventContent, name::RoomNameEventContent,
power_levels::{RoomPowerLevels, RoomPowerLevelsEventContent},
topic::RoomTopicEventContent, topic::RoomTopicEventContent,
}, },
StateEventType, 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<bool> {
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"))
},
)
}
} }

View file

@ -397,21 +397,36 @@ impl Service {
| RoomVersionId::V9 | RoomVersionId::V9
| RoomVersionId::V10 => { | RoomVersionId::V10 => {
if let Some(redact_id) = &pdu.redacts { if let Some(redact_id) = &pdu.redacts {
if services().rooms.state_accessor.user_can_redact(
redact_id,
&pdu.sender,
&pdu.room_id,
false,
)? {
self.redact_pdu(redact_id, pdu, shortroomid)?; self.redact_pdu(redact_id, pdu, shortroomid)?;
} }
}
}, },
RoomVersionId::V11 => { RoomVersionId::V11 => {
let content = let content =
serde_json::from_str::<RoomRedactionEventContent>(pdu.content.get()).map_err(|e| { serde_json::from_str::<RoomRedactionEventContent>(pdu.content.get()).map_err(|e| {
warn!("Invalid content in redaction pdu: {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 { if let Some(redact_id) = &content.redacts {
if services().rooms.state_accessor.user_can_redact(
redact_id,
&pdu.sender,
&pdu.room_id,
false,
)? {
self.redact_pdu(redact_id, pdu, shortroomid)?; 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( return Err(Error::BadRequest(
ErrorKind::BadJson, ErrorKind::BadJson,
"Unexpected or unsupported room version found", "Unexpected or unsupported room version found",
@ -703,7 +718,7 @@ impl Service {
// Hash and sign // Hash and sign
let mut pdu_json = utils::to_canonical_object(&pdu).map_err(|e| { 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.") 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"); warn!("Encryption is not allowed in the admins room");
return Err(Error::BadRequest( return Err(Error::BadRequest(
ErrorKind::forbidden(), ErrorKind::forbidden(),
"Encryption is not allowed in the admins room.", "Encryption is not allowed in the admins room",
)); ));
}, },
TimelineEventType::RoomMember => { TimelineEventType::RoomMember => {
@ -789,14 +804,14 @@ impl Service {
let server_user = &services().globals.server_user.to_string(); let server_user = &services().globals.server_user.to_string();
let content = serde_json::from_str::<RoomMemberEventContent>(pdu.content.get()) let content = serde_json::from_str::<RoomMemberEventContent>(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 content.membership == MembershipState::Leave {
if target == server_user { if target == server_user {
warn!("Conduit user cannot leave from admins room"); warn!("Server user cannot leave from admins room");
return Err(Error::BadRequest( return Err(Error::BadRequest(
ErrorKind::forbidden(), 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 content.membership == MembershipState::Ban && pdu.state_key().is_some() {
if target == server_user { 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( return Err(Error::BadRequest(
ErrorKind::forbidden(), 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 // 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 // time with the pdu without it's state. This is okay because append_pdu can't
// fail. // fail.