feat: forbid certain usernames & room aliases

squashed from https://gitlab.com/famedly/conduit/-/merge_requests/582

Signed-off-by: strawberry <strawberry@puppygock.gay>
This commit is contained in:
Matthias Ahouansou 2024-02-08 19:11:48 -05:00 committed by June
parent 784d307425
commit fc93b29abe
8 changed files with 143 additions and 1 deletions

23
Cargo.lock generated
View file

@ -402,6 +402,7 @@ dependencies = [
"hyperlocal", "hyperlocal",
"image", "image",
"ipaddress", "ipaddress",
"itertools 0.12.1",
"jsonwebtoken", "jsonwebtoken",
"lazy_static", "lazy_static",
"lru-cache", "lru-cache",
@ -422,6 +423,7 @@ dependencies = [
"serde", "serde",
"serde_html_form", "serde_html_form",
"serde_json", "serde_json",
"serde_regex",
"serde_yaml", "serde_yaml",
"sha-1", "sha-1",
"sha2", "sha2",
@ -1123,6 +1125,15 @@ dependencies = [
"either", "either",
] ]
[[package]]
name = "itertools"
version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569"
dependencies = [
"either",
]
[[package]] [[package]]
name = "itoa" name = "itoa"
version = "1.0.10" version = "1.0.10"
@ -2191,7 +2202,7 @@ name = "ruma-state-res"
version = "0.10.0" version = "0.10.0"
source = "git+https://github.com/ruma/ruma?rev=68c9bb0930f2195fa8672fbef9633ef62737df5d#68c9bb0930f2195fa8672fbef9633ef62737df5d" source = "git+https://github.com/ruma/ruma?rev=68c9bb0930f2195fa8672fbef9633ef62737df5d#68c9bb0930f2195fa8672fbef9633ef62737df5d"
dependencies = [ dependencies = [
"itertools", "itertools 0.11.0",
"js_int", "js_int",
"ruma-common", "ruma-common",
"ruma-events", "ruma-events",
@ -2404,6 +2415,16 @@ dependencies = [
"serde", "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]] [[package]]
name = "serde_spanned" name = "serde_spanned"
version = "0.6.5" version = "0.6.5"

View file

@ -62,6 +62,9 @@ ring = "0.17.7"
trust-dns-resolver = "0.23.2" trust-dns-resolver = "0.23.2"
# Used to find matching events for appservices # Used to find matching events for appservices
regex = "1.10.3" regex = "1.10.3"
# Used to load forbidden room/user regex from config
serde_regex = "1.1.0"
itertools = "0.12.1"
# jwt jsonwebtokens # jwt jsonwebtokens
jsonwebtoken = "9.2.0" jsonwebtoken = "9.2.0"
# Performance measurements # Performance measurements

View file

@ -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 // TODO add check for appservice namespaces
// If no if check is true we have an username that's available to be used. // 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, ErrorKind::InvalidUsername,
"Username is invalid.", "Username is invalid.",
))?; ))?;
if services().users.exists(&proposed_user_id)? { if services().users.exists(&proposed_user_id)? {
return Err(Error::BadRequest( return Err(Error::BadRequest(
ErrorKind::UserInUse, ErrorKind::UserInUse,
"Desired user ID is already taken.", "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 proposed_user_id
} }
_ => loop { _ => loop {

View file

@ -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() if services()
.rooms .rooms
.alias .alias

View file

@ -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!( let alias = RoomAliasId::parse(format!(
"#{}:{}", "#{}:{}",
localpart, localpart,

View file

@ -7,6 +7,8 @@ use std::{
use figment::Figment; use figment::Figment;
use itertools::Itertools;
use regex::RegexSet;
use ruma::{OwnedServerName, RoomVersionId}; use ruma::{OwnedServerName, RoomVersionId};
use serde::{de::IgnoredAny, Deserialize}; use serde::{de::IgnoredAny, Deserialize};
use tracing::{error, warn}; use tracing::{error, warn};
@ -132,6 +134,14 @@ pub struct Config {
#[serde(default = "default_ip_range_denylist")] #[serde(default = "default_ip_range_denylist")]
pub ip_range_denylist: Vec<String>, 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)] #[serde(flatten)]
pub catchall: BTreeMap<String, IgnoredAny>, pub catchall: BTreeMap<String, IgnoredAny>,
} }
@ -319,6 +329,12 @@ impl fmt::Display for Config {
} }
&lst.join(", ") &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(); let mut msg: String = "Active config values:\n\n".to_owned();

View file

@ -8,6 +8,7 @@ use crate::{
use abstraction::{KeyValueDatabaseEngine, KvTree}; use abstraction::{KeyValueDatabaseEngine, KvTree};
use argon2::{password_hash::SaltString, PasswordHasher, PasswordVerifier}; use argon2::{password_hash::SaltString, PasswordHasher, PasswordVerifier};
use directories::ProjectDirs; use directories::ProjectDirs;
use itertools::Itertools;
use lru_cache::LruCache; use lru_cache::LruCache;
use rand::thread_rng; use rand::thread_rng;
use ruma::{ use ruma::{
@ -971,6 +972,51 @@ impl KeyValueDatabase {
latest_database_version 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!( info!(
"Loaded {} database with version {}", "Loaded {} database with version {}",
services().globals.config.database_backend, services().globals.config.database_backend,

View file

@ -1,6 +1,7 @@
mod data; mod data;
use argon2::Argon2; use argon2::Argon2;
pub use data::Data; pub use data::Data;
use regex::RegexSet;
use ruma::{ use ruma::{
serde::Base64, OwnedDeviceId, OwnedEventId, OwnedRoomId, OwnedServerName, serde::Base64, OwnedDeviceId, OwnedEventId, OwnedRoomId, OwnedServerName,
OwnedServerSigningKeyId, OwnedUserId, OwnedServerSigningKeyId, OwnedUserId,
@ -389,6 +390,14 @@ impl Service<'_> {
&self.config.emergency_password &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 { pub fn allow_local_presence(&self) -> bool {
self.config.allow_local_presence self.config.allow_local_presence
} }