mirror of
https://forgejo.ellis.link/continuwuation/continuwuity.git
synced 2026-05-26 20:49:55 +00:00
d189004d65
Adds two new toggles to the configuration, the first of which allows disabling the policy server checks entirely, and the second of which allows disabling checking events created locally. They're both enabled by default for maximum PS efficacy but allowing them to be disabled allows people who frequently cannot contact policy servers, for example those in censored countries, to be able to still use rooms with pace, allows single-user/trusted-only homeservers to disable the preliminary check on their own events, and also gives an escape hatch in case an issue like #1060 happens again, especially with MSCs not in FCP being moving targets. In future, I think we should gate all MSC implementations behind config flags, even if they default to on. Reviewed-on: https://forgejo.ellis.link/continuwuation/continuwuity/pulls/1127 Reviewed-by: Jade Ellis <jade@ellis.link> Co-authored-by: timedout <git@nexy7574.co.uk> Co-committed-by: timedout <git@nexy7574.co.uk>
145 lines
4.0 KiB
Rust
145 lines
4.0 KiB
Rust
//! 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
|
|
}
|
|
|
|
if *pdu.event_type() == StateEventType::RoomPolicy.into() {
|
|
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 {
|
|
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!(
|
|
room_id = %room_id,
|
|
via = %via,
|
|
outgoing = ?pdu_json,
|
|
"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)) => {
|
|
warn!(
|
|
via = %via,
|
|
event_id = %pdu.event_id(),
|
|
room_id = %room_id,
|
|
"Failed to contact policy server: {e}"
|
|
);
|
|
// 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!(
|
|
via = %via,
|
|
event_id = %pdu.event_id(),
|
|
room_id = %room_id,
|
|
"Event was marked as spam by policy server",
|
|
);
|
|
return Ok(false);
|
|
}
|
|
|
|
Ok(true)
|
|
}
|