feat: Consolidate antispam checks into a service
Also adds support for the spam checker join rule, and Draupnir callbacks
This commit is contained in:
parent
c249dd992e
commit
5ac82f36f3
13 changed files with 355 additions and 136 deletions
35
Cargo.lock
generated
35
Cargo.lock
generated
|
|
@ -1632,6 +1632,16 @@ dependencies = [
|
|||
"litrs",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "draupnir-antispam"
|
||||
version = "0.1.0"
|
||||
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=46e31bd6439eccbd3a1762f710c17fc15168c15e#46e31bd6439eccbd3a1762f710c17fc15168c15e"
|
||||
dependencies = [
|
||||
"ruma-common",
|
||||
"serde",
|
||||
"serde_json",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dtor"
|
||||
version = "0.1.0"
|
||||
|
|
@ -2985,7 +2995,7 @@ checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273"
|
|||
[[package]]
|
||||
name = "meowlnir-antispam"
|
||||
version = "0.1.0"
|
||||
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=377d801fa035480b772c640b430097c1ec0ddb16#377d801fa035480b772c640b430097c1ec0ddb16"
|
||||
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=46e31bd6439eccbd3a1762f710c17fc15168c15e#46e31bd6439eccbd3a1762f710c17fc15168c15e"
|
||||
dependencies = [
|
||||
"ruma-common",
|
||||
"serde",
|
||||
|
|
@ -4075,9 +4085,10 @@ checksum = "88f8660c1ff60292143c98d08fc6e2f654d722db50410e3f3797d40baaf9d8f3"
|
|||
[[package]]
|
||||
name = "ruma"
|
||||
version = "0.10.1"
|
||||
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=377d801fa035480b772c640b430097c1ec0ddb16#377d801fa035480b772c640b430097c1ec0ddb16"
|
||||
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=46e31bd6439eccbd3a1762f710c17fc15168c15e#46e31bd6439eccbd3a1762f710c17fc15168c15e"
|
||||
dependencies = [
|
||||
"assign",
|
||||
"draupnir-antispam",
|
||||
"js_int",
|
||||
"js_option",
|
||||
"meowlnir-antispam",
|
||||
|
|
@ -4096,7 +4107,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "ruma-appservice-api"
|
||||
version = "0.10.0"
|
||||
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=377d801fa035480b772c640b430097c1ec0ddb16#377d801fa035480b772c640b430097c1ec0ddb16"
|
||||
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=46e31bd6439eccbd3a1762f710c17fc15168c15e#46e31bd6439eccbd3a1762f710c17fc15168c15e"
|
||||
dependencies = [
|
||||
"js_int",
|
||||
"ruma-common",
|
||||
|
|
@ -4108,7 +4119,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "ruma-client-api"
|
||||
version = "0.18.0"
|
||||
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=377d801fa035480b772c640b430097c1ec0ddb16#377d801fa035480b772c640b430097c1ec0ddb16"
|
||||
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=46e31bd6439eccbd3a1762f710c17fc15168c15e#46e31bd6439eccbd3a1762f710c17fc15168c15e"
|
||||
dependencies = [
|
||||
"as_variant",
|
||||
"assign",
|
||||
|
|
@ -4131,7 +4142,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "ruma-common"
|
||||
version = "0.13.0"
|
||||
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=377d801fa035480b772c640b430097c1ec0ddb16#377d801fa035480b772c640b430097c1ec0ddb16"
|
||||
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=46e31bd6439eccbd3a1762f710c17fc15168c15e#46e31bd6439eccbd3a1762f710c17fc15168c15e"
|
||||
dependencies = [
|
||||
"as_variant",
|
||||
"base64 0.22.1",
|
||||
|
|
@ -4163,7 +4174,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "ruma-events"
|
||||
version = "0.28.1"
|
||||
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=377d801fa035480b772c640b430097c1ec0ddb16#377d801fa035480b772c640b430097c1ec0ddb16"
|
||||
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=46e31bd6439eccbd3a1762f710c17fc15168c15e#46e31bd6439eccbd3a1762f710c17fc15168c15e"
|
||||
dependencies = [
|
||||
"as_variant",
|
||||
"indexmap",
|
||||
|
|
@ -4188,7 +4199,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "ruma-federation-api"
|
||||
version = "0.9.0"
|
||||
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=377d801fa035480b772c640b430097c1ec0ddb16#377d801fa035480b772c640b430097c1ec0ddb16"
|
||||
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=46e31bd6439eccbd3a1762f710c17fc15168c15e#46e31bd6439eccbd3a1762f710c17fc15168c15e"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"headers",
|
||||
|
|
@ -4210,7 +4221,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "ruma-identifiers-validation"
|
||||
version = "0.9.5"
|
||||
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=377d801fa035480b772c640b430097c1ec0ddb16#377d801fa035480b772c640b430097c1ec0ddb16"
|
||||
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=46e31bd6439eccbd3a1762f710c17fc15168c15e#46e31bd6439eccbd3a1762f710c17fc15168c15e"
|
||||
dependencies = [
|
||||
"js_int",
|
||||
"thiserror 2.0.17",
|
||||
|
|
@ -4219,7 +4230,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "ruma-identity-service-api"
|
||||
version = "0.9.0"
|
||||
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=377d801fa035480b772c640b430097c1ec0ddb16#377d801fa035480b772c640b430097c1ec0ddb16"
|
||||
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=46e31bd6439eccbd3a1762f710c17fc15168c15e#46e31bd6439eccbd3a1762f710c17fc15168c15e"
|
||||
dependencies = [
|
||||
"js_int",
|
||||
"ruma-common",
|
||||
|
|
@ -4229,7 +4240,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "ruma-macros"
|
||||
version = "0.13.0"
|
||||
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=377d801fa035480b772c640b430097c1ec0ddb16#377d801fa035480b772c640b430097c1ec0ddb16"
|
||||
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=46e31bd6439eccbd3a1762f710c17fc15168c15e#46e31bd6439eccbd3a1762f710c17fc15168c15e"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"proc-macro-crate",
|
||||
|
|
@ -4244,7 +4255,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "ruma-push-gateway-api"
|
||||
version = "0.9.0"
|
||||
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=377d801fa035480b772c640b430097c1ec0ddb16#377d801fa035480b772c640b430097c1ec0ddb16"
|
||||
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=46e31bd6439eccbd3a1762f710c17fc15168c15e#46e31bd6439eccbd3a1762f710c17fc15168c15e"
|
||||
dependencies = [
|
||||
"js_int",
|
||||
"ruma-common",
|
||||
|
|
@ -4256,7 +4267,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "ruma-signatures"
|
||||
version = "0.15.0"
|
||||
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=377d801fa035480b772c640b430097c1ec0ddb16#377d801fa035480b772c640b430097c1ec0ddb16"
|
||||
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=46e31bd6439eccbd3a1762f710c17fc15168c15e#46e31bd6439eccbd3a1762f710c17fc15168c15e"
|
||||
dependencies = [
|
||||
"base64 0.22.1",
|
||||
"ed25519-dalek",
|
||||
|
|
|
|||
|
|
@ -351,7 +351,7 @@ version = "0.1.2"
|
|||
# Used for matrix spec type definitions and helpers
|
||||
[workspace.dependencies.ruma]
|
||||
git = "https://forgejo.ellis.link/continuwuation/ruwuma"
|
||||
rev = "377d801fa035480b772c640b430097c1ec0ddb16"
|
||||
rev = "46e31bd6439eccbd3a1762f710c17fc15168c15e"
|
||||
features = [
|
||||
"compat",
|
||||
"rand",
|
||||
|
|
|
|||
|
|
@ -1647,7 +1647,7 @@
|
|||
|
||||
# Enable the tokio-console. This option is only relevant to developers.
|
||||
#
|
||||
# For more information, see:
|
||||
# For more information, see:
|
||||
# https://continuwuity.org/development.html#debugging-with-tokio-console
|
||||
#
|
||||
#tokio_console = false
|
||||
|
|
@ -1757,10 +1757,6 @@
|
|||
#
|
||||
#ldap = false
|
||||
|
||||
# Configuration for antispam support
|
||||
#
|
||||
#antispam = false
|
||||
|
||||
[global.tls]
|
||||
|
||||
# Path to a valid TLS certificate file.
|
||||
|
|
@ -1930,7 +1926,7 @@
|
|||
|
||||
[global.antispam.meowlnir]
|
||||
|
||||
# The base URL on which to contact meowlnir (before /_meowlnir/antispam).
|
||||
# The base URL on which to contact Meowlnir (before /_meowlnir/antispam).
|
||||
#
|
||||
# Example: "http://127.0.0.1:29339"
|
||||
#
|
||||
|
|
@ -1944,3 +1940,24 @@
|
|||
# The management room for which to send requests
|
||||
#
|
||||
#management_room =
|
||||
|
||||
# If enabled run all federated join attempts (both federated and local)
|
||||
# through the Meowlnir anti-spam checks.
|
||||
#
|
||||
# By default, only join attempts for rooms with the `fi.mau.spam_checker`
|
||||
# restricted join rule are checked.
|
||||
#
|
||||
#check_all_joins = false
|
||||
|
||||
[global.antispam.draupnir]
|
||||
|
||||
# The base URL on which to contact Draupnir (before /api/).
|
||||
#
|
||||
# Example: "http://127.0.0.1:29339"
|
||||
#
|
||||
#base_url =
|
||||
|
||||
# The authentication secret defined in
|
||||
# web->synapseHTTPAntispam->authorization
|
||||
#
|
||||
#secret =
|
||||
|
|
|
|||
|
|
@ -1,11 +1,9 @@
|
|||
use axum::extract::State;
|
||||
use axum_client_ip::InsecureClientIp;
|
||||
use conduwuit::{
|
||||
Err, Result,
|
||||
config::Antispam,
|
||||
debug_error, err, info,
|
||||
Err, Result, debug_error, err, info,
|
||||
matrix::{event::gen_event_id_canonical_json, pdu::PduBuilder},
|
||||
trace,
|
||||
warn,
|
||||
};
|
||||
use futures::FutureExt;
|
||||
use ruma::{
|
||||
|
|
@ -15,7 +13,6 @@ use ruma::{
|
|||
invite_permission_config::FilterLevel,
|
||||
room::member::{MembershipState, RoomMemberEventContent},
|
||||
},
|
||||
meowlnir_antispam::user_may_invite,
|
||||
};
|
||||
use service::Services;
|
||||
|
||||
|
|
@ -128,24 +125,16 @@ pub(crate) async fn invite_helper(
|
|||
return Err!(Request(Forbidden("Invites are not allowed on this server.")));
|
||||
}
|
||||
|
||||
trace!("maybe ask meowlnir");
|
||||
if let Some(Antispam { meowlnir: Some(cfg) }) = &services.config.antispam {
|
||||
trace!("asking meowlnir");
|
||||
services
|
||||
.sending
|
||||
.send_meowlnir_antispam_request(
|
||||
cfg,
|
||||
user_may_invite::v1::Request::new(
|
||||
cfg.management_room.clone(),
|
||||
sender_user.to_owned(),
|
||||
recipient_user.to_owned(),
|
||||
),
|
||||
)
|
||||
.await
|
||||
.inspect(|_| trace!("meowlnir :D"))
|
||||
.inspect_err(|e| debug_error!("meowlnir sad: {e}"))?;
|
||||
} else {
|
||||
trace!("no meowlnir configured");
|
||||
if let Err(e) = services
|
||||
.antispam
|
||||
.user_may_invite(sender_user.to_owned(), recipient_user.to_owned(), room_id.to_owned())
|
||||
.await
|
||||
{
|
||||
warn!(
|
||||
"Invite from {} to {} in room {} blocked by antispam: {e:?}",
|
||||
sender_user, recipient_user, room_id
|
||||
);
|
||||
return Err!(Request(Forbidden("Invite blocked by antispam service.")));
|
||||
}
|
||||
|
||||
if !services.globals.user_is_local(recipient_user) {
|
||||
|
|
|
|||
|
|
@ -3,9 +3,7 @@ use std::{borrow::Borrow, collections::HashMap, iter::once, sync::Arc};
|
|||
use axum::extract::State;
|
||||
use axum_client_ip::InsecureClientIp;
|
||||
use conduwuit::{
|
||||
Err, Result,
|
||||
config::Antispam,
|
||||
debug, debug_info, debug_warn, err, error, info,
|
||||
Err, Result, debug, debug_info, debug_warn, err, error, info,
|
||||
matrix::{
|
||||
StateKey,
|
||||
event::{gen_event_id, gen_event_id_canonical_json},
|
||||
|
|
@ -39,7 +37,6 @@ use ruma::{
|
|||
member::{MembershipState, RoomMemberEventContent},
|
||||
},
|
||||
},
|
||||
meowlnir_antispam::user_may_join_room,
|
||||
};
|
||||
use service::{
|
||||
Services,
|
||||
|
|
@ -82,6 +79,26 @@ pub(crate) async fn join_room_by_id_route(
|
|||
)
|
||||
.await?;
|
||||
|
||||
if let Err(e) = services
|
||||
.antispam
|
||||
.user_may_join_room(
|
||||
sender_user.to_owned(),
|
||||
body.room_id.clone(),
|
||||
services
|
||||
.rooms
|
||||
.state_cache
|
||||
.is_invited(sender_user, &body.room_id)
|
||||
.await,
|
||||
)
|
||||
.await
|
||||
{
|
||||
warn!(
|
||||
"Antispam prevented user {} from joining room {}: {}",
|
||||
sender_user, body.room_id, e
|
||||
);
|
||||
return Err!(Request(Forbidden("You are not allowed to join this room.")));
|
||||
}
|
||||
|
||||
// There is no body.server_name for /roomId/join
|
||||
let mut servers: Vec<_> = services
|
||||
.rooms
|
||||
|
|
@ -350,20 +367,6 @@ pub async fn join_room_by_id_helper(
|
|||
.boxed()
|
||||
.await?;
|
||||
}
|
||||
|
||||
if let Some(Antispam { meowlnir: Some(cfg) }) = &services.config.antispam {
|
||||
services
|
||||
.sending
|
||||
.send_meowlnir_antispam_request(
|
||||
cfg,
|
||||
user_may_join_room::v1::Request::new(
|
||||
cfg.management_room.clone(),
|
||||
sender_user.to_owned(),
|
||||
room_id.to_owned(),
|
||||
),
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
Ok(join_room_by_id::v3::Response::new(room_id.to_owned()))
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -2,9 +2,7 @@ use axum::extract::State;
|
|||
use axum_client_ip::InsecureClientIp;
|
||||
use base64::{Engine as _, engine::general_purpose};
|
||||
use conduwuit::{
|
||||
Err, Error, PduEvent, Result,
|
||||
config::Antispam,
|
||||
err,
|
||||
Err, Error, PduEvent, Result, err,
|
||||
matrix::{Event, event::gen_event_id},
|
||||
utils::{self, hash::sha256},
|
||||
warn,
|
||||
|
|
@ -13,7 +11,6 @@ use ruma::{
|
|||
CanonicalJsonValue, OwnedUserId, UserId,
|
||||
api::{client::error::ErrorKind, federation::membership::create_invite},
|
||||
events::room::member::{MembershipState, RoomMemberEventContent},
|
||||
meowlnir_antispam::user_may_invite,
|
||||
serde::JsonObject,
|
||||
};
|
||||
|
||||
|
|
@ -151,18 +148,13 @@ pub(crate) async fn create_invite_route(
|
|||
return Err!(Request(Forbidden("This server does not allow room invites.")));
|
||||
}
|
||||
|
||||
if let Some(Antispam { meowlnir: Some(cfg) }) = &services.config.antispam {
|
||||
services
|
||||
.sending
|
||||
.send_meowlnir_antispam_request(
|
||||
cfg,
|
||||
user_may_invite::v1::Request::new(
|
||||
cfg.management_room.clone(),
|
||||
sender_user.to_owned(),
|
||||
recipient_user.clone(),
|
||||
),
|
||||
)
|
||||
.await?;
|
||||
if let Err(e) = services
|
||||
.antispam
|
||||
.user_may_invite(sender_user.to_owned(), recipient_user.clone(), body.room_id.clone())
|
||||
.await
|
||||
{
|
||||
warn!("Antispam rejected invite: {e:?}");
|
||||
return Err!(Request(Forbidden("Invite rejected by antispam service.")));
|
||||
}
|
||||
|
||||
let mut invite_state = body.invite_room_state.clone();
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
use std::borrow::ToOwned;
|
||||
|
||||
use axum::extract::State;
|
||||
use conduwuit::{
|
||||
Err, Error, Result, debug_info, info, matrix::pdu::PduBuilder, utils::IterStream, warn,
|
||||
};
|
||||
use conduwuit::{Err, Error, Result, debug, debug_info, info, matrix::pdu::PduBuilder, warn};
|
||||
use conduwuit_service::Services;
|
||||
use futures::StreamExt;
|
||||
use ruma::{
|
||||
|
|
@ -136,7 +136,6 @@ pub(crate) async fn create_join_event_template_route(
|
|||
&state_lock,
|
||||
)
|
||||
.await?;
|
||||
|
||||
drop(state_lock);
|
||||
|
||||
// room v3 and above removed the "event_id" field from remote PDU format
|
||||
|
|
@ -192,25 +191,52 @@ pub(crate) async fn user_can_perform_restricted_join(
|
|||
return Ok(false);
|
||||
}
|
||||
|
||||
if r.allow
|
||||
.iter()
|
||||
.filter_map(|rule| {
|
||||
if let AllowRule::RoomMembership(membership) = rule {
|
||||
Some(membership)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.stream()
|
||||
.any(|m| services.rooms.state_cache.is_joined(user_id, &m.room_id))
|
||||
.await
|
||||
{
|
||||
Ok(true)
|
||||
} else {
|
||||
Err!(Request(UnableToAuthorizeJoin(
|
||||
"Joining user is not known to be in any required room."
|
||||
)))
|
||||
for allow_rule in &r.allow {
|
||||
match allow_rule {
|
||||
| AllowRule::RoomMembership(membership) => {
|
||||
if services
|
||||
.rooms
|
||||
.state_cache
|
||||
.is_joined(user_id, &membership.room_id)
|
||||
.await
|
||||
{
|
||||
debug!(
|
||||
"User {} is allowed to join room {} via membership in room {}",
|
||||
user_id, room_id, membership.room_id
|
||||
);
|
||||
return Ok(true);
|
||||
}
|
||||
},
|
||||
| AllowRule::UnstableSpamChecker => {
|
||||
match services
|
||||
.antispam
|
||||
.meowlnir_accept_make_join(room_id.to_owned(), user_id.to_owned())
|
||||
.await
|
||||
{
|
||||
| Ok(()) => {
|
||||
return Ok(true);
|
||||
},
|
||||
| Err(e) => {
|
||||
info!(
|
||||
"meowlnir rejected restricted join for user {} into room {}: {e:?}",
|
||||
user_id, room_id
|
||||
);
|
||||
},
|
||||
}
|
||||
},
|
||||
| _ => {
|
||||
debug_info!(
|
||||
"Unsupported allow rule in restricted join for room {}: {:?}",
|
||||
room_id,
|
||||
allow_rule
|
||||
);
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
Err!(Request(UnableToAuthorizeJoin(
|
||||
"Joining user is not known to be in any required room."
|
||||
)))
|
||||
}
|
||||
|
||||
pub(crate) fn maybe_strip_event_id(
|
||||
|
|
|
|||
|
|
@ -53,7 +53,8 @@ use crate::{Result, err, error::Error, utils::sys};
|
|||
### For more information, see:
|
||||
### https://continuwuity.org/configuration.html
|
||||
"#,
|
||||
ignore = "config_paths catchall well_known tls blurhashing allow_invalid_tls_certificates_yes_i_know_what_the_fuck_i_am_doing_with_this_and_i_know_this_is_insecure"
|
||||
ignore = "config_paths catchall well_known tls blurhashing \
|
||||
allow_invalid_tls_certificates_yes_i_know_what_the_fuck_i_am_doing_with_this_and_i_know_this_is_insecure antispam"
|
||||
)]
|
||||
pub struct Config {
|
||||
// Paths to config file(s). Not supposed to be set manually in the config file,
|
||||
|
|
@ -1887,7 +1888,7 @@ pub struct Config {
|
|||
|
||||
/// Enable the tokio-console. This option is only relevant to developers.
|
||||
///
|
||||
/// For more information, see:
|
||||
/// For more information, see:
|
||||
/// https://continuwuity.org/development.html#debugging-with-tokio-console
|
||||
#[serde(default)]
|
||||
pub tokio_console: bool,
|
||||
|
|
@ -2247,6 +2248,7 @@ struct ListeningAddr {
|
|||
#[derive(Clone, Debug, Deserialize)]
|
||||
pub struct Antispam {
|
||||
pub meowlnir: Option<MeowlnirConfig>,
|
||||
pub draupnir: Option<DraupnirConfig>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize)]
|
||||
|
|
@ -2255,7 +2257,7 @@ pub struct Antispam {
|
|||
section = "global.antispam.meowlnir"
|
||||
)]
|
||||
pub struct MeowlnirConfig {
|
||||
/// The base URL on which to contact meowlnir (before /_meowlnir/antispam).
|
||||
/// The base URL on which to contact Meowlnir (before /_meowlnir/antispam).
|
||||
///
|
||||
/// Example: "http://127.0.0.1:29339"
|
||||
pub base_url: Url,
|
||||
|
|
@ -2266,6 +2268,32 @@ pub struct MeowlnirConfig {
|
|||
|
||||
/// The management room for which to send requests
|
||||
pub management_room: OwnedRoomId,
|
||||
|
||||
/// If enabled run all federated join attempts (both federated and local)
|
||||
/// through the Meowlnir anti-spam checks.
|
||||
///
|
||||
/// By default, only join attempts for rooms with the `fi.mau.spam_checker`
|
||||
/// restricted join rule are checked.
|
||||
#[serde(default)]
|
||||
pub check_all_joins: bool,
|
||||
}
|
||||
|
||||
// TODO: the DraupnirConfig and MeowlnirConfig are basically identical.
|
||||
// Maybe management_room could just become an Option<> and these structs merged?
|
||||
#[derive(Clone, Debug, Deserialize)]
|
||||
#[config_example_generator(
|
||||
filename = "conduwuit-example.toml",
|
||||
section = "global.antispam.draupnir"
|
||||
)]
|
||||
pub struct DraupnirConfig {
|
||||
/// The base URL on which to contact Draupnir (before /api/).
|
||||
///
|
||||
/// Example: "http://127.0.0.1:29339"
|
||||
pub base_url: Url,
|
||||
|
||||
/// The authentication secret defined in
|
||||
/// web->synapseHTTPAntispam->authorization
|
||||
pub secret: String,
|
||||
}
|
||||
|
||||
const DEPRECATED_KEYS: &[&str; 9] = &[
|
||||
|
|
|
|||
172
src/service/antispam/mod.rs
Normal file
172
src/service/antispam/mod.rs
Normal file
|
|
@ -0,0 +1,172 @@
|
|||
use std::sync::Arc;
|
||||
|
||||
use async_trait::async_trait;
|
||||
use conduwuit::{Result, config::Antispam, debug};
|
||||
use ruma::{OwnedRoomId, OwnedUserId, draupnir_antispam, meowlnir_antispam};
|
||||
|
||||
use crate::{client, config, sending, service::Dep};
|
||||
|
||||
struct Services {
|
||||
config: Dep<config::Service>,
|
||||
client: Dep<client::Service>,
|
||||
}
|
||||
|
||||
pub struct Service {
|
||||
services: Services,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl crate::Service for Service {
|
||||
fn build(args: crate::Args<'_>) -> Result<Arc<Self>> {
|
||||
Ok(Arc::new(Self {
|
||||
services: Services {
|
||||
client: args.depend::<client::Service>("client"),
|
||||
config: args.depend::<config::Service>("config"),
|
||||
},
|
||||
}))
|
||||
}
|
||||
|
||||
fn name(&self) -> &str { crate::service::make_name(std::module_path!()) }
|
||||
}
|
||||
|
||||
impl Service {
|
||||
async fn send_antispam_request<T>(
|
||||
&self,
|
||||
base_url: &str,
|
||||
secret: &str,
|
||||
request: T,
|
||||
) -> Result<T::IncomingResponse>
|
||||
where
|
||||
T: ruma::api::OutgoingRequest + std::fmt::Debug + Send,
|
||||
{
|
||||
sending::antispam::send_antispam_request(
|
||||
&self.services.client.appservice,
|
||||
base_url,
|
||||
secret,
|
||||
request,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
/// Checks with the antispam service whether `inviter` may invite `invitee`
|
||||
/// to `room_id`.
|
||||
///
|
||||
/// If no antispam service is configured, this always returns `Ok(())`.
|
||||
/// If an error is returned, the invite should be blocked - the antispam
|
||||
/// service was unreachable, or refused the invite.
|
||||
pub async fn user_may_invite(
|
||||
&self,
|
||||
inviter: OwnedUserId,
|
||||
invitee: OwnedUserId,
|
||||
room_id: OwnedRoomId,
|
||||
) -> Result<()> {
|
||||
if let Some(config) = &self.services.config.antispam {
|
||||
let result = if let Some(meowlnir) = &config.meowlnir {
|
||||
debug!("Asking meowlnir for user_may_invite");
|
||||
self.send_antispam_request(
|
||||
meowlnir.base_url.as_str(),
|
||||
&meowlnir.secret,
|
||||
meowlnir_antispam::user_may_invite::v1::Request::new(
|
||||
meowlnir.management_room.clone(),
|
||||
inviter,
|
||||
invitee,
|
||||
room_id,
|
||||
),
|
||||
)
|
||||
.await
|
||||
.inspect(|_| debug!("meowlnir allowed the invite"))
|
||||
.inspect_err(|e| debug!("meowlnir denied the invite: {e:?}"))
|
||||
.map(|_| ())
|
||||
} else if let Some(draupnir) = &config.draupnir {
|
||||
debug!("Asking draupnir for user_may_invite");
|
||||
self.send_antispam_request(
|
||||
draupnir.base_url.as_str(),
|
||||
&draupnir.secret,
|
||||
draupnir_antispam::user_may_invite::v1::Request::new(
|
||||
room_id, inviter, invitee,
|
||||
),
|
||||
)
|
||||
.await
|
||||
.inspect(|_| debug!("draupnir allowed the invite"))
|
||||
.inspect_err(|e| debug!("draupnir denied the invite: {e:?}"))
|
||||
.map(|_| ())
|
||||
} else {
|
||||
Ok(())
|
||||
};
|
||||
return result;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Checks with the antispam service whether `user_id` may join `room_id`.
|
||||
pub async fn user_may_join_room(
|
||||
&self,
|
||||
user_id: OwnedUserId,
|
||||
room_id: OwnedRoomId,
|
||||
is_invited: bool,
|
||||
) -> Result<()> {
|
||||
if let Some(config) = &self.services.config.antispam {
|
||||
let result = if let Some(meowlnir) = &config.meowlnir {
|
||||
debug!("Asking meowlnir for user_may_join_room");
|
||||
self.send_antispam_request(
|
||||
meowlnir.base_url.as_str(),
|
||||
&meowlnir.secret,
|
||||
meowlnir_antispam::user_may_join_room::v1::Request::new(
|
||||
meowlnir.management_room.clone(),
|
||||
user_id,
|
||||
room_id,
|
||||
is_invited,
|
||||
),
|
||||
)
|
||||
.await
|
||||
.inspect(|_| debug!("meowlnir allowed the join"))
|
||||
.inspect_err(|e| debug!("meowlnir denied the join: {e:?}"))
|
||||
.map(|_| ())
|
||||
} else if let Some(draupnir) = &config.draupnir {
|
||||
debug!("Asking draupnir for user_may_join_room");
|
||||
self.send_antispam_request(
|
||||
draupnir.base_url.as_str(),
|
||||
&draupnir.secret,
|
||||
draupnir_antispam::user_may_join_room::v1::Request::new(
|
||||
user_id, room_id, is_invited,
|
||||
),
|
||||
)
|
||||
.await
|
||||
.inspect(|_| debug!("draupnir allowed the join"))
|
||||
.inspect_err(|e| debug!("draupnir denied the join: {e:?}"))
|
||||
.map(|_| ())
|
||||
} else {
|
||||
Ok(())
|
||||
};
|
||||
return result;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Checks with Meowlnir whether the incoming federated `make_join` request
|
||||
/// should be allowed. Applies the `fi.mau.spam_checker` join rule.
|
||||
pub async fn meowlnir_accept_make_join(
|
||||
&self,
|
||||
room_id: OwnedRoomId,
|
||||
user_id: OwnedUserId,
|
||||
) -> Result<()> {
|
||||
if let Some(Antispam { meowlnir: Some(meowlnir), .. }) = &self.services.config.antispam {
|
||||
debug!("Asking meowlnir for meowlnir_accept_make_join");
|
||||
self.send_antispam_request(
|
||||
meowlnir.base_url.as_str(),
|
||||
&meowlnir.secret,
|
||||
meowlnir_antispam::accept_make_join::v1::Request::new(
|
||||
meowlnir.management_room.clone(),
|
||||
user_id,
|
||||
room_id,
|
||||
),
|
||||
)
|
||||
.await
|
||||
.inspect(|_| debug!("meowlnir allowed the make_join"))
|
||||
.inspect_err(|e| debug!("meowlnir denied the make_join: {e:?}"))
|
||||
.map(|_| ())
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,8 @@
|
|||
#![type_length_limit = "8192"]
|
||||
#![allow(refining_impl_trait)]
|
||||
|
||||
extern crate conduwuit_core as conduwuit;
|
||||
extern crate conduwuit_database as database;
|
||||
mod manager;
|
||||
mod migrations;
|
||||
mod service;
|
||||
|
|
@ -10,6 +12,7 @@ pub mod state;
|
|||
pub mod account_data;
|
||||
pub mod admin;
|
||||
pub mod announcements;
|
||||
pub mod antispam;
|
||||
pub mod appservice;
|
||||
pub mod client;
|
||||
pub mod config;
|
||||
|
|
@ -30,9 +33,6 @@ pub mod transaction_ids;
|
|||
pub mod uiaa;
|
||||
pub mod users;
|
||||
|
||||
extern crate conduwuit_core as conduwuit;
|
||||
extern crate conduwuit_database as database;
|
||||
|
||||
use ctor::{ctor, dtor};
|
||||
pub(crate) use service::{Args, Dep, Service};
|
||||
|
||||
|
|
|
|||
|
|
@ -1,30 +1,23 @@
|
|||
use std::{fmt::Debug, mem};
|
||||
|
||||
use bytes::BytesMut;
|
||||
use conduwuit::{Err, Result, config::MeowlnirConfig, debug_error, err, utils, warn};
|
||||
use conduwuit::{Err, Result, debug_error, err, utils, warn};
|
||||
use reqwest::Client;
|
||||
use ruma::api::{IncomingResponse, MatrixVersion, OutgoingRequest, SendAccessToken};
|
||||
|
||||
/// Sends a request to an antispam service
|
||||
pub(crate) async fn send_meowlnir_request<T>(
|
||||
pub(crate) async fn send_antispam_request<T>(
|
||||
client: &Client,
|
||||
config: &MeowlnirConfig,
|
||||
base_url: &str,
|
||||
secret: &str,
|
||||
request: T,
|
||||
) -> Result<Option<T::IncomingResponse>>
|
||||
) -> Result<T::IncomingResponse>
|
||||
where
|
||||
T: OutgoingRequest + Debug + Send,
|
||||
{
|
||||
const VERSIONS: [MatrixVersion; 1] = [MatrixVersion::V1_15];
|
||||
if config.secret.is_empty() {
|
||||
return Ok(None);
|
||||
}
|
||||
let secret = config.secret.as_str();
|
||||
let http_request = request
|
||||
.try_into_http_request::<BytesMut>(
|
||||
config.base_url.as_str(),
|
||||
SendAccessToken::Always(secret),
|
||||
&VERSIONS,
|
||||
)?
|
||||
.try_into_http_request::<BytesMut>(base_url, SendAccessToken::Always(secret), &VERSIONS)?
|
||||
.map(BytesMut::freeze);
|
||||
let reqwest_request = reqwest::Request::try_from(http_request)?;
|
||||
|
||||
|
|
@ -64,7 +57,7 @@ where
|
|||
.expect("reqwest body is valid http body"),
|
||||
);
|
||||
|
||||
response.map(Some).map_err(|e| {
|
||||
response.map_err(|e| {
|
||||
err!(BadServerResponse(warn!(
|
||||
"Antispam returned invalid/malformed response bytes: {e}",
|
||||
)))
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
mod antispam;
|
||||
pub mod antispam;
|
||||
mod appservice;
|
||||
mod data;
|
||||
mod dest;
|
||||
|
|
@ -13,9 +13,7 @@ use std::{
|
|||
|
||||
use async_trait::async_trait;
|
||||
use conduwuit::{
|
||||
Result, Server,
|
||||
config::MeowlnirConfig,
|
||||
debug, debug_warn, err, error,
|
||||
Result, Server, debug, debug_warn, err, error,
|
||||
smallvec::SmallVec,
|
||||
utils::{ReadyExt, TryReadyExt, available_parallelism, math::usize_from_u64_truncated},
|
||||
warn,
|
||||
|
|
@ -337,18 +335,6 @@ impl Service {
|
|||
appservice::send_request(client, registration, request).await
|
||||
}
|
||||
|
||||
/// Sends a request to the chosen antispam configuration
|
||||
pub async fn send_meowlnir_antispam_request<T>(
|
||||
&self,
|
||||
config: &MeowlnirConfig,
|
||||
request: T,
|
||||
) -> Result<Option<T::IncomingResponse>>
|
||||
where
|
||||
T: OutgoingRequest + Debug + Send,
|
||||
{
|
||||
antispam::send_meowlnir_request(&self.services.client.appservice, config, request).await
|
||||
}
|
||||
|
||||
/// Clean up queued sending event data
|
||||
///
|
||||
/// Used after we remove an appservice registration or a user deletes a push
|
||||
|
|
|
|||
|
|
@ -8,8 +8,8 @@ use futures::{Stream, StreamExt, TryStreamExt};
|
|||
use tokio::sync::Mutex;
|
||||
|
||||
use crate::{
|
||||
account_data, admin, announcements, appservice, client, config, emergency, federation,
|
||||
globals, key_backups,
|
||||
account_data, admin, announcements, antispam, appservice, client, config, emergency,
|
||||
federation, globals, key_backups,
|
||||
manager::Manager,
|
||||
media, moderation, presence, pusher, resolver, rooms, sending, server_keys, service,
|
||||
service::{Args, Map, Service},
|
||||
|
|
@ -39,6 +39,7 @@ pub struct Services {
|
|||
pub users: Arc<users::Service>,
|
||||
pub moderation: Arc<moderation::Service>,
|
||||
pub announcements: Arc<announcements::Service>,
|
||||
pub antispam: Arc<antispam::Service>,
|
||||
|
||||
manager: Mutex<Option<Arc<Manager>>>,
|
||||
pub(crate) service: Arc<Map>,
|
||||
|
|
@ -107,6 +108,7 @@ impl Services {
|
|||
users: build!(users::Service),
|
||||
moderation: build!(moderation::Service),
|
||||
announcements: build!(announcements::Service),
|
||||
antispam: build!(antispam::Service),
|
||||
|
||||
manager: Mutex::new(None),
|
||||
service,
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue