Files
continuwuity/src/service/rooms/event_handler/policy_server.rs
T

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

145 lines
4.0 KiB
Rust
Raw Normal View History

2025-07-20 01:03:18 +01:00
//! Policy server integration for event spam checking in Matrix rooms.
//!
//! This module implements a check against a room-specific policy server, as
//! described in the relevant Matrix spec proposal (see: https://github.com/matrix-org/matrix-spec-proposals/pull/4284).
use std::time::Duration;
use conduwuit::{Err, Event, PduEvent, Result, debug, debug_info, implement, trace, warn};
use ruma::{
CanonicalJsonObject, RoomId, ServerName,
api::federation::room::policy::v1::Request as PolicyRequest,
events::{StateEventType, room::policy::RoomPolicyEventContent},
};
/// Asks a remote policy server if the event is allowed.
///
/// If the event is the `org.matrix.msc4284.policy` configuration state event,
/// this check is skipped. Similarly, if there is no policy server configured in
/// the PDU's room, or the configured server is not present in the room, the
/// check is also skipped.
///
/// If the policy server marks the event as spam, Ok(false) is returned,
/// otherwise Ok(true) allows the event. If the policy server cannot be
/// contacted for whatever reason, Err(e) is returned, which generally is a
/// fail-open operation.
#[implement(super::Service)]
#[tracing::instrument(skip_all, level = "debug")]
pub async fn ask_policy_server(
&self,
pdu: &PduEvent,
pdu_json: &CanonicalJsonObject,
room_id: &RoomId,
) -> Result<bool> {
if !self.services.server.config.enable_msc4284_policy_servers {
return Ok(true); // don't ever contact policy servers
}
if self.services.server.config.policy_server_check_own_events
&& pdu.origin.is_some()
&& self
.services
.server
.is_ours(pdu.origin.as_ref().unwrap().as_str())
{
return Ok(true); // don't contact policy servers for locally generated events
}
2025-07-19 23:54:07 +01:00
if *pdu.event_type() == StateEventType::RoomPolicy.into() {
2025-07-20 01:03:18 +01:00
debug!(
room_id = %room_id,
event_type = ?pdu.event_type(),
"Skipping spam check for policy server meta-event"
);
return Ok(true);
}
let Ok(policyserver) = self
.services
.state_accessor
.room_state_get_content(room_id, &StateEventType::RoomPolicy, "")
.await
.map(|c: RoomPolicyEventContent| c)
else {
return Ok(true);
};
let via = match policyserver.via {
| Some(ref via) => ServerName::parse(via)?,
| None => {
trace!("No policy server configured for room {room_id}");
return Ok(true);
},
};
if via.is_empty() {
trace!("Policy server is empty for room {room_id}, skipping spam check");
return Ok(true);
}
if !self.services.state_cache.server_in_room(via, room_id).await {
2025-07-20 01:03:18 +01:00
debug!(
room_id = %room_id,
via = %via,
"Policy server is not in the room, skipping spam check"
);
return Ok(true);
}
let outgoing = self
.services
.sending
.convert_to_outgoing_federation_event(pdu_json.clone())
.await;
debug_info!(
2025-07-20 01:03:18 +01:00
room_id = %room_id,
via = %via,
outgoing = ?pdu_json,
2025-07-20 01:03:18 +01:00
"Checking event for spam with policy server"
);
let response = tokio::time::timeout(
Duration::from_secs(self.services.server.config.policy_server_request_timeout),
self.services
.sending
.send_federation_request(via, PolicyRequest {
event_id: pdu.event_id().to_owned(),
pdu: Some(outgoing),
}),
)
.await;
let response = match response {
| Ok(Ok(response)) => {
debug!("Response from policy server: {:?}", response);
response
},
| Ok(Err(e)) => {
2025-07-19 20:50:47 +01:00
warn!(
via = %via,
event_id = %pdu.event_id(),
room_id = %room_id,
"Failed to contact policy server: {e}"
);
2025-07-20 01:03:18 +01:00
// Network or policy server errors are treated as non-fatal: event is allowed by
// default.
return Err(e);
},
| Err(elapsed) => {
warn!(
%via,
event_id = %pdu.event_id(),
%room_id,
%elapsed,
"Policy server request timed out after 10 seconds"
);
return Err!("Request to policy server timed out");
},
};
trace!("Recommendation from policy server was {}", response.recommendation);
if response.recommendation == "spam" {
warn!(
2025-07-19 20:50:47 +01:00
via = %via,
event_id = %pdu.event_id(),
room_id = %room_id,
"Event was marked as spam by policy server",
);
return Ok(false);
2025-07-19 20:34:34 +01:00
}
Ok(true)
}