From fc93b29abe50e4f98fa33c9cd51214cdf78827a9 Mon Sep 17 00:00:00 2001 From: Matthias Ahouansou <matthias@ahouansou.cz> Date: Thu, 8 Feb 2024 19:11:48 -0500 Subject: [PATCH] feat: forbid certain usernames & room aliases squashed from https://gitlab.com/famedly/conduit/-/merge_requests/582 Signed-off-by: strawberry <strawberry@puppygock.gay> --- Cargo.lock | 23 +++++++++++++++- Cargo.toml | 3 +++ src/api/client_server/account.rs | 24 +++++++++++++++++ src/api/client_server/alias.rs | 11 ++++++++ src/api/client_server/room.rs | 12 +++++++++ src/config/mod.rs | 16 +++++++++++ src/database/mod.rs | 46 ++++++++++++++++++++++++++++++++ src/service/globals/mod.rs | 9 +++++++ 8 files changed, 143 insertions(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index f653a2d0b..991c7e1b1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -402,6 +402,7 @@ dependencies = [ "hyperlocal", "image", "ipaddress", + "itertools 0.12.1", "jsonwebtoken", "lazy_static", "lru-cache", @@ -422,6 +423,7 @@ dependencies = [ "serde", "serde_html_form", "serde_json", + "serde_regex", "serde_yaml", "sha-1", "sha2", @@ -1123,6 +1125,15 @@ dependencies = [ "either", ] +[[package]] +name = "itertools" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" +dependencies = [ + "either", +] + [[package]] name = "itoa" version = "1.0.10" @@ -2191,7 +2202,7 @@ name = "ruma-state-res" version = "0.10.0" source = "git+https://github.com/ruma/ruma?rev=68c9bb0930f2195fa8672fbef9633ef62737df5d#68c9bb0930f2195fa8672fbef9633ef62737df5d" dependencies = [ - "itertools", + "itertools 0.11.0", "js_int", "ruma-common", "ruma-events", @@ -2404,6 +2415,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 41a5a8b5b..3b79ad692 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -62,6 +62,9 @@ ring = "0.17.7" trust-dns-resolver = "0.23.2" # Used to find matching events for appservices regex = "1.10.3" +# Used to load forbidden room/user regex from config +serde_regex = "1.1.0" +itertools = "0.12.1" # 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 9ba82a58a..2f0a10aab 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. @@ -120,12 +131,25 @@ pub async fn register_route(body: Ruma<register::v3::Request>) -> Result<registe ErrorKind::InvalidUsername, "Username is invalid.", ))?; + if services().users.exists(&proposed_user_id)? { return Err(Error::BadRequest( ErrorKind::UserInUse, "Desired user ID is already taken.", )); } + + if services() + .globals + .forbidden_usernames() + .is_match(proposed_user_id.localpart()) + { + return Err(Error::BadRequest( + ErrorKind::Unknown, + "Username is forbidden.", + )); + } + proposed_user_id } _ => loop { diff --git a/src/api/client_server/alias.rs b/src/api/client_server/alias.rs index bf9034455..1a236449b 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_room_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 28647fa0c..dbd86b66f 100644 --- a/src/api/client_server/room.rs +++ b/src/api/client_server/room.rs @@ -166,6 +166,18 @@ pub async fn create_room_route( )); } + // check if room alias is forbidden + if services() + .globals + .forbidden_room_names() + .is_match(localpart) + { + return Err(Error::BadRequest( + ErrorKind::Unknown, + "Room alias name is forbidden.", + )); + } + let alias = RoomAliasId::parse(format!( "#{}:{}", localpart, diff --git a/src/config/mod.rs b/src/config/mod.rs index d5cecfe27..4beccc680 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -7,6 +7,8 @@ use figment::Figment; +use itertools::Itertools; +use regex::RegexSet; use ruma::{OwnedServerName, RoomVersionId}; use serde::{de::IgnoredAny, Deserialize}; use tracing::{error, warn}; @@ -132,6 +134,14 @@ pub struct Config { #[serde(default = "default_ip_range_denylist")] pub ip_range_denylist: Vec<String>, + #[serde(default = "RegexSet::empty")] + #[serde(with = "serde_regex")] + pub forbidden_room_names: RegexSet, + + #[serde(default = "RegexSet::empty")] + #[serde(with = "serde_regex")] + pub forbidden_usernames: RegexSet, + #[serde(flatten)] pub catchall: BTreeMap<String, IgnoredAny>, } @@ -319,6 +329,12 @@ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { } &lst.join(", ") }), + ("Forbidden usernames", { + &self.forbidden_usernames.patterns().iter().join(", ") + }), + ("Forbidden room names", { + &self.forbidden_room_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 32921b132..e3026377d 100644 --- a/src/database/mod.rs +++ b/src/database/mod.rs @@ -8,6 +8,7 @@ use abstraction::{KeyValueDatabaseEngine, KvTree}; use argon2::{password_hash::SaltString, PasswordHasher, PasswordVerifier}; use directories::ProjectDirs; +use itertools::Itertools; use lru_cache::LruCache; use rand::thread_rng; use ruma::{ @@ -971,6 +972,51 @@ pub async fn load_or_create(config: Config) -> Result<()> { latest_database_version ); + { + let patterns = &services().globals.config.forbidden_usernames; + if !patterns.is_empty() { + for user in services().users.iter() { + let user_id = user?; + 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_room_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 1f90684f7..434a8c4c2 100644 --- a/src/service/globals/mod.rs +++ b/src/service/globals/mod.rs @@ -1,6 +1,7 @@ mod data; use argon2::Argon2; pub use data::Data; +use regex::RegexSet; use ruma::{ serde::Base64, OwnedDeviceId, OwnedEventId, OwnedRoomId, OwnedServerName, OwnedServerSigningKeyId, OwnedUserId, @@ -389,6 +390,14 @@ pub fn emergency_password(&self) -> &Option<String> { &self.config.emergency_password } + pub fn forbidden_room_names(&self) -> &RegexSet { + &self.config.forbidden_room_names + } + + pub fn forbidden_usernames(&self) -> &RegexSet { + &self.config.forbidden_usernames + } + pub fn allow_local_presence(&self) -> bool { self.config.allow_local_presence } -- GitLab