continuwuity/src/api/client/report.rs
Jason Volk 4ec5d1e28e replace additional use tracing::
add log:: to disallowed-macros

Signed-off-by: Jason Volk <jason@zemos.net>
2024-11-14 04:50:07 +00:00

188 lines
5.2 KiB
Rust

use std::time::Duration;
use axum::extract::State;
use axum_client_ip::InsecureClientIp;
use conduit::{info, utils::ReadyExt, Err};
use rand::Rng;
use ruma::{
api::client::{
error::ErrorKind,
room::{report_content, report_room},
},
events::room::message,
int, EventId, RoomId, UserId,
};
use tokio::time::sleep;
use crate::{
debug_info,
service::{pdu::PduEvent, Services},
Error, Result, Ruma,
};
/// # `POST /_matrix/client/v3/rooms/{roomId}/report`
///
/// Reports an abusive room to homeserver admins
#[tracing::instrument(skip_all, fields(%client), name = "report_room")]
pub(crate) async fn report_room_route(
State(services): State<crate::State>, InsecureClientIp(client): InsecureClientIp,
body: Ruma<report_room::v3::Request>,
) -> Result<report_room::v3::Response> {
// user authentication
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
info!(
"Received room report by user {sender_user} for room {} with reason: \"{}\"",
body.room_id,
body.reason.as_deref().unwrap_or("")
);
if body.reason.as_ref().is_some_and(|s| s.len() > 750) {
return Err(Error::BadRequest(
ErrorKind::InvalidParam,
"Reason too long, should be 750 characters or fewer",
));
};
delay_response().await;
if !services
.rooms
.state_cache
.server_in_room(&services.globals.config.server_name, &body.room_id)
.await
{
return Err!(Request(NotFound(
"Room does not exist to us, no local users have joined at all"
)));
}
// send admin room message that we received the report with an @room ping for
// urgency
services
.admin
.send_message(message::RoomMessageEventContent::text_markdown(format!(
"@room Room report received from {} -\n\nRoom ID: {}\n\nReport Reason: {}",
sender_user.to_owned(),
body.room_id,
body.reason.as_deref().unwrap_or("")
)))
.await
.ok();
Ok(report_room::v3::Response {})
}
/// # `POST /_matrix/client/v3/rooms/{roomId}/report/{eventId}`
///
/// Reports an inappropriate event to homeserver admins
#[tracing::instrument(skip_all, fields(%client), name = "report_event")]
pub(crate) async fn report_event_route(
State(services): State<crate::State>, InsecureClientIp(client): InsecureClientIp,
body: Ruma<report_content::v3::Request>,
) -> Result<report_content::v3::Response> {
// user authentication
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
info!(
"Received event report by user {sender_user} for room {} and event ID {}, with reason: \"{}\"",
body.room_id,
body.event_id,
body.reason.as_deref().unwrap_or("")
);
delay_response().await;
// check if we know about the reported event ID or if it's invalid
let Ok(pdu) = services.rooms.timeline.get_pdu(&body.event_id).await else {
return Err!(Request(NotFound("Event ID is not known to us or Event ID is invalid")));
};
is_event_report_valid(
&services,
&pdu.event_id,
&body.room_id,
sender_user,
body.reason.as_ref(),
body.score,
&pdu,
)
.await?;
// send admin room message that we received the report with an @room ping for
// urgency
services
.admin
.send_message(message::RoomMessageEventContent::text_markdown(format!(
"@room Event report received from {} -\n\nEvent ID: {}\nRoom ID: {}\nSent By: {}\n\nReport Score: \
{}\nReport Reason: {}",
sender_user.to_owned(),
pdu.event_id,
pdu.room_id,
pdu.sender,
body.score.unwrap_or_else(|| ruma::Int::from(0)),
body.reason.as_deref().unwrap_or("")
)))
.await
.ok();
Ok(report_content::v3::Response {})
}
/// in the following order:
///
/// check if the room ID from the URI matches the PDU's room ID
/// check if score is in valid range
/// check if report reasoning is less than or equal to 750 characters
/// check if reporting user is in the reporting room
async fn is_event_report_valid(
services: &Services, event_id: &EventId, room_id: &RoomId, sender_user: &UserId, reason: Option<&String>,
score: Option<ruma::Int>, pdu: &std::sync::Arc<PduEvent>,
) -> Result<()> {
debug_info!("Checking if report from user {sender_user} for event {event_id} in room {room_id} is valid");
if room_id != pdu.room_id {
return Err(Error::BadRequest(
ErrorKind::NotFound,
"Event ID does not belong to the reported room",
));
}
if score.is_some_and(|s| s > int!(0) || s < int!(-100)) {
return Err(Error::BadRequest(
ErrorKind::InvalidParam,
"Invalid score, must be within 0 to -100",
));
};
if reason.as_ref().is_some_and(|s| s.len() > 750) {
return Err(Error::BadRequest(
ErrorKind::InvalidParam,
"Reason too long, should be 750 characters or fewer",
));
};
if !services
.rooms
.state_cache
.room_members(room_id)
.ready_any(|user_id| user_id == sender_user)
.await
{
return Err(Error::BadRequest(
ErrorKind::NotFound,
"You are not in the room you are reporting.",
));
}
Ok(())
}
/// even though this is kinda security by obscurity, let's still make a small
/// random delay sending a response per spec suggestion regarding
/// enumerating for potential events existing in our server.
async fn delay_response() {
let time_to_wait = rand::thread_rng().gen_range(2..5);
debug_info!("Got successful /report request, waiting {time_to_wait} seconds before sending successful response.");
sleep(Duration::from_secs(time_to_wait)).await;
}