From d625d265bb7f488c65885edcc4592fba3764b913 Mon Sep 17 00:00:00 2001 From: Matthias Ahouansou Date: Thu, 23 Nov 2023 20:03:19 +0000 Subject: [PATCH] feat: forbid usernames & room aliases via regex --- Cargo.lock | 12 ++++++++ Cargo.toml | 3 ++ src/api/client_server/account.rs | 21 +++++++++++++ src/api/client_server/alias.rs | 11 +++++++ src/api/client_server/room.rs | 10 +++++++ src/config/mod.rs | 17 +++++++++++ src/database/mod.rs | 51 ++++++++++++++++++++++++++++++++ src/service/globals/mod.rs | 9 ++++++ 8 files changed, 134 insertions(+) diff --git a/Cargo.lock b/Cargo.lock index 4d294c08..e57e43f2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -386,6 +386,7 @@ dependencies = [ "http", "hyper", "image", + "itertools 0.12.1", "jsonwebtoken", "lazy_static", "lru-cache", @@ -407,6 +408,7 @@ dependencies = [ "serde", "serde_html_form", "serde_json", + "serde_regex", "serde_yaml", "sha-1", "thiserror", @@ -2420,6 +2422,16 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_regex" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8136f1a4ea815d7eac4101cfd0b16dc0cb5e1fe1b8609dfd728058656b7badf" +dependencies = [ + "regex", + "serde", +] + [[package]] name = "serde_spanned" version = "0.6.5" diff --git a/Cargo.toml b/Cargo.toml index ac334728..d641fc67 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -78,6 +78,9 @@ ring = "0.17.7" trust-dns-resolver = "0.22.0" # Used to find matching events for appservices regex = "1.8.1" +# Used to load forbidden room/user regex from config +serde_regex = "1.1.0" +itertools = "0.12.0" # jwt jsonwebtokens jsonwebtoken = "9.2.0" # Performance measurements diff --git a/src/api/client_server/account.rs b/src/api/client_server/account.rs index d4529a40..d6ab6a5e 100644 --- a/src/api/client_server/account.rs +++ b/src/api/client_server/account.rs @@ -54,6 +54,17 @@ pub async fn get_register_available_route( )); } + if services() + .globals + .forbidden_usernames() + .is_match(user_id.localpart()) + { + return Err(Error::BadRequest( + ErrorKind::Unknown, + "Username is forbidden.", + )); + } + // TODO add check for appservice namespaces // If no if check is true we have an username that's available to be used. @@ -107,6 +118,16 @@ pub async fn register_route(body: Ruma) -> Result loop { diff --git a/src/api/client_server/alias.rs b/src/api/client_server/alias.rs index 7660ca2f..482a923c 100644 --- a/src/api/client_server/alias.rs +++ b/src/api/client_server/alias.rs @@ -26,6 +26,17 @@ pub async fn create_alias_route( )); } + if services() + .globals + .forbidden_alias_names() + .is_match(body.room_alias.alias()) + { + return Err(Error::BadRequest( + ErrorKind::Unknown, + "Room alias is forbidden.", + )); + } + if services() .rooms .alias diff --git a/src/api/client_server/room.rs b/src/api/client_server/room.rs index 5d4cbe8a..bd053b30 100644 --- a/src/api/client_server/room.rs +++ b/src/api/client_server/room.rs @@ -81,6 +81,16 @@ pub async fn create_room_route( body.room_alias_name .as_ref() .map_or(Ok(None), |localpart| { + if services() + .globals + .forbidden_alias_names() + .is_match(localpart) + { + return Err(Error::BadRequest( + ErrorKind::Unknown, + "Room alias is forbidden.", + )); + } // TODO: Check for invalid characters and maximum length let alias = RoomAliasId::parse(format!( "#{}:{}", diff --git a/src/config/mod.rs b/src/config/mod.rs index 4605855f..dcfe8c24 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -1,3 +1,6 @@ +use itertools::Itertools; +use regex::RegexSet; + use std::{ collections::BTreeMap, fmt, @@ -84,6 +87,14 @@ pub struct Config { #[serde(flatten)] pub catchall: BTreeMap, + + #[serde(default = "RegexSet::empty")] + #[serde(with = "serde_regex")] + pub forbidden_alias_names: RegexSet, + + #[serde(default = "RegexSet::empty")] + #[serde(with = "serde_regex")] + pub forbidden_usernames: RegexSet, } #[derive(Clone, Debug, Deserialize)] @@ -195,6 +206,12 @@ impl fmt::Display for Config { } &lst.join(", ") }), + ("Forbidden usernames", { + &self.forbidden_usernames.patterns().iter().join(", ") + }), + ("Forbidden room names", { + &self.forbidden_alias_names.patterns().iter().join(", ") + }), ]; let mut msg: String = "Active config values:\n\n".to_owned(); diff --git a/src/database/mod.rs b/src/database/mod.rs index 0960dc96..7b6ecc56 100644 --- a/src/database/mod.rs +++ b/src/database/mod.rs @@ -7,6 +7,7 @@ use crate::{ }; use abstraction::{KeyValueDatabaseEngine, KvTree}; use directories::ProjectDirs; +use itertools::Itertools; use lru_cache::LruCache; use ruma::{ events::{ @@ -943,6 +944,56 @@ impl KeyValueDatabase { latest_database_version ); + { + let patterns = &services().globals.config.forbidden_usernames; + if !patterns.is_empty() { + for user_id in services() + .users + .iter() + .filter_map(std::result::Result::ok) + .filter(|user| !services().users.is_deactivated(user).unwrap_or(true)) + .filter(|user| user.server_name() == services().globals.server_name()) + { + let matches = patterns.matches(user_id.localpart()); + if matches.matched_any() { + warn!( + "User {} matches the following forbidden username patterns: {}", + user_id.to_string(), + matches + .into_iter() + .map(|x| &patterns.patterns()[x]) + .join(", ") + ) + } + } + } + } + + { + let patterns = &services().globals.config.forbidden_alias_names; + if !patterns.is_empty() { + for address in services().rooms.metadata.iter_ids() { + let room_id = address?; + let room_aliases = services().rooms.alias.local_aliases_for_room(&room_id); + for room_alias_result in room_aliases { + let room_alias = room_alias_result?; + let matches = patterns.matches(room_alias.alias()); + if matches.matched_any() { + warn!( + "Room with alias {} ({}) matches the following forbidden room name patterns: {}", + room_alias, + &room_id, + matches + .into_iter() + .map(|x| &patterns.patterns()[x]) + .join(", ") + ) + } + } + } + } + } + info!( "Loaded {} database with version {}", services().globals.config.database_backend, diff --git a/src/service/globals/mod.rs b/src/service/globals/mod.rs index 22dc6959..7207b3c3 100644 --- a/src/service/globals/mod.rs +++ b/src/service/globals/mod.rs @@ -1,5 +1,6 @@ mod data; pub use data::Data; +use regex::RegexSet; use ruma::{ serde::Base64, OwnedDeviceId, OwnedEventId, OwnedRoomId, OwnedServerName, OwnedServerSigningKeyId, OwnedUserId, @@ -352,6 +353,14 @@ impl Service { &self.config.emergency_password } + pub fn forbidden_alias_names(&self) -> &RegexSet { + &self.config.forbidden_alias_names + } + + pub fn forbidden_usernames(&self) -> &RegexSet { + &self.config.forbidden_usernames + } + pub fn supported_room_versions(&self) -> Vec { let mut room_versions: Vec = vec![]; room_versions.extend(self.stable_room_versions.clone());