2026-09-13 19:19:16 +02:00
|
|
|
use axum::extract::{Query, State};
|
|
|
|
|
use conduwuit::{Err, Error, Event, PduEvent, Result, debug_warn, err};
|
|
|
|
|
use futures::{FutureExt, TryFutureExt, future::try_join};
|
|
|
|
|
use http::StatusCode;
|
|
|
|
|
use ruma::{
|
|
|
|
|
UserId,
|
|
|
|
|
api::{client::room::get_room_event, error::ErrorKind},
|
|
|
|
|
};
|
|
|
|
|
use serde::Deserialize;
|
|
|
|
|
use serde_json::json;
|
2024-11-06 18:27:40 +00:00
|
|
|
|
2025-02-23 01:17:45 -05:00
|
|
|
use crate::{Ruma, client::is_ignored_pdu};
|
2024-11-06 18:27:40 +00:00
|
|
|
|
2026-09-13 19:19:16 +02:00
|
|
|
#[derive(Deserialize)]
|
|
|
|
|
pub(crate) struct Msc2815Params {
|
|
|
|
|
#[serde(rename = "fi.mau.msc2815.include_unredacted_content", default)]
|
|
|
|
|
include_unredacted_content: Option<String>,
|
|
|
|
|
}
|
|
|
|
|
|
2024-11-06 18:27:40 +00:00
|
|
|
/// # `GET /_matrix/client/r0/rooms/{roomId}/event/{eventId}`
|
|
|
|
|
///
|
|
|
|
|
/// Gets a single event.
|
|
|
|
|
pub(crate) async fn get_room_event_route(
|
2025-02-06 10:23:17 +00:00
|
|
|
State(ref services): State<crate::State>,
|
2026-09-13 19:19:16 +02:00
|
|
|
Query(params): Query<Msc2815Params>,
|
2024-12-15 00:05:47 -05:00
|
|
|
ref body: Ruma<get_room_event::v3::Request>,
|
2024-11-06 18:27:40 +00:00
|
|
|
) -> Result<get_room_event::v3::Response> {
|
2026-05-27 12:15:16 -04:00
|
|
|
let sender_user = body.identity.expect_sender_user()?;
|
2025-02-06 10:23:17 +00:00
|
|
|
let event_id = &body.event_id;
|
|
|
|
|
let room_id = &body.room_id;
|
|
|
|
|
|
2026-09-21 23:36:44 +02:00
|
|
|
let mut event = match services.rooms.timeline.get_pdu(event_id).await {
|
|
|
|
|
| Ok(event) => event,
|
2026-09-22 00:11:38 +01:00
|
|
|
| Err(e) if e.is_not_found() => {
|
2026-09-21 23:36:44 +02:00
|
|
|
// Only fetch over federation for users who could see the event.
|
2026-09-22 00:11:38 +01:00
|
|
|
let (joined, world_readable) = tokio::join!(
|
|
|
|
|
services.rooms.state_cache.is_joined(sender_user, room_id),
|
|
|
|
|
services.rooms.state_accessor.is_world_readable(room_id)
|
|
|
|
|
);
|
|
|
|
|
if !joined && !world_readable {
|
2026-11-10 20:41:38 +00:00
|
|
|
return Err!(Request(NotFound("Event {event_id} not found.")));
|
2026-09-21 23:36:44 +02:00
|
|
|
}
|
2024-12-04 18:33:12 -05:00
|
|
|
|
2026-09-21 23:36:44 +02:00
|
|
|
services
|
|
|
|
|
.rooms
|
|
|
|
|
.timeline
|
|
|
|
|
.get_remote_pdu(room_id, event_id)
|
|
|
|
|
.await
|
2026-11-10 20:41:38 +00:00
|
|
|
.map_err(|_| err!(Request(NotFound("Event {event_id} not found."))))?
|
2026-09-21 23:36:44 +02:00
|
|
|
},
|
2026-09-22 00:11:38 +01:00
|
|
|
| Err(e) => return Err(e),
|
2026-09-21 23:36:44 +02:00
|
|
|
};
|
2026-11-10 20:41:38 +00:00
|
|
|
if !event.room_id_or_hash().eq(room_id) {
|
|
|
|
|
// Return not found instead of invalid param to avoid revealing the
|
|
|
|
|
// existence of events the user might not be allowed to know about.
|
|
|
|
|
return Err!(Request(NotFound("Event {event_id} not found.")));
|
|
|
|
|
}
|
2026-09-21 23:36:44 +02:00
|
|
|
|
|
|
|
|
// NOTE: checked after fetching, as visibility of an unknown event cannot
|
|
|
|
|
// be determined (user_can_see_event returns true for those).
|
2024-12-04 18:33:12 -05:00
|
|
|
let visible = services
|
|
|
|
|
.rooms
|
|
|
|
|
.state_accessor
|
2026-05-27 12:15:16 -04:00
|
|
|
.user_can_see_event(sender_user, room_id, event_id)
|
2026-09-21 23:36:44 +02:00
|
|
|
.await;
|
2024-12-04 18:33:12 -05:00
|
|
|
|
2026-05-27 12:15:16 -04:00
|
|
|
if !visible || is_ignored_pdu(services, &event, sender_user).await? {
|
2024-12-04 18:33:12 -05:00
|
|
|
return Err!(Request(Forbidden("You don't have permission to view this event.")));
|
|
|
|
|
}
|
|
|
|
|
|
2026-09-13 19:19:16 +02:00
|
|
|
if services.config.allow_moderators_to_view_redacted_content
|
|
|
|
|
&& params.include_unredacted_content.as_deref() == Some("true")
|
|
|
|
|
&& event.is_redacted()
|
|
|
|
|
{
|
|
|
|
|
unredact(services, sender_user, &mut event).await?;
|
|
|
|
|
}
|
|
|
|
|
|
2025-06-04 00:11:09 +01:00
|
|
|
if let Err(e) = services
|
|
|
|
|
.rooms
|
|
|
|
|
.pdu_metadata
|
2026-05-27 12:15:16 -04:00
|
|
|
.add_bundled_aggregations_to_pdu(sender_user, &mut event)
|
2025-06-04 00:11:09 +01:00
|
|
|
.await
|
|
|
|
|
{
|
|
|
|
|
debug_warn!("Failed to add bundled aggregations to event: {e}");
|
|
|
|
|
}
|
|
|
|
|
|
2026-09-21 00:32:02 +01:00
|
|
|
let ctx = services
|
|
|
|
|
.rooms
|
|
|
|
|
.timeline
|
|
|
|
|
.get_unsigned_context(&event, Some(sender_user))
|
|
|
|
|
.await;
|
2026-09-27 17:22:02 +01:00
|
|
|
event.set_unsigned(Some(ctx))?;
|
2024-12-04 18:33:12 -05:00
|
|
|
|
2026-04-10 12:52:42 -04:00
|
|
|
Ok(get_room_event::v3::Response::new(event.into_format()))
|
2024-11-06 18:27:40 +00:00
|
|
|
}
|
2026-09-13 19:19:16 +02:00
|
|
|
|
|
|
|
|
async fn unredact(services: &crate::State, sender_user: &UserId, event: &mut PduEvent) -> Result {
|
|
|
|
|
let power_levels = services
|
|
|
|
|
.rooms
|
|
|
|
|
.state_accessor
|
|
|
|
|
.get_room_power_levels(&event.room_id_or_hash())
|
|
|
|
|
.await;
|
|
|
|
|
|
|
|
|
|
if power_levels.for_user(sender_user) < power_levels.redact
|
|
|
|
|
&& !services.users.is_admin(sender_user).await
|
|
|
|
|
{
|
|
|
|
|
return Err!(Request(Forbidden(
|
|
|
|
|
"You don't have permission to view redacted content in this room."
|
|
|
|
|
)));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let Ok(pdu_id) = services.rooms.timeline.get_pdu_id(event.event_id()).await else {
|
|
|
|
|
return Err(content_unavailable());
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
let Ok(content) = services
|
|
|
|
|
.rooms
|
|
|
|
|
.timeline
|
|
|
|
|
.get_unredacted_content(&pdu_id)
|
|
|
|
|
.await
|
|
|
|
|
else {
|
|
|
|
|
return Err(content_unavailable());
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
event.content = content;
|
|
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn content_unavailable() -> Error {
|
|
|
|
|
let kind = serde_json::from_value::<ErrorKind>(
|
|
|
|
|
json!({ "errcode": "FI.MAU.MSC2815_UNREDACTED_CONTENT_DELETED" }),
|
|
|
|
|
)
|
|
|
|
|
.expect("errcode is a valid custom error kind");
|
|
|
|
|
|
|
|
|
|
Error::Request(
|
|
|
|
|
kind,
|
|
|
|
|
"The unredacted content of this event is not available.".into(),
|
|
|
|
|
StatusCode::NOT_FOUND,
|
|
|
|
|
)
|
|
|
|
|
}
|