From 5ab76a133202e25cbfd46b32faea06d81c2c8551 Mon Sep 17 00:00:00 2001 From: Matthias Ahouansou Date: Fri, 8 Mar 2024 10:50:52 -0500 Subject: [PATCH] update ruma appservice Registration type MR from https://gitlab.com/famedly/conduit/-/merge_requests/583 and fixed panic from blocking async call in timeline/mod.rs Co-authored-by: strawberry Signed-off-by: strawberry --- src/api/client_server/alias.rs | 16 +-- src/database/key_value/rooms/state_cache.rs | 29 +++--- src/database/mod.rs | 8 ++ src/service/admin/mod.rs | 4 +- src/service/appservice/mod.rs | 104 +++++++++++++++++++- src/service/mod.rs | 1 + src/service/rooms/state_cache/data.rs | 5 +- src/service/rooms/state_cache/mod.rs | 6 +- src/service/rooms/timeline/mod.rs | 40 +++----- src/utils/error.rs | 5 + 10 files changed, 156 insertions(+), 62 deletions(-) diff --git a/src/api/client_server/alias.rs b/src/api/client_server/alias.rs index 0e930fc3..be4a8ed0 100644 --- a/src/api/client_server/alias.rs +++ b/src/api/client_server/alias.rs @@ -1,5 +1,4 @@ use rand::seq::SliceRandom; -use regex::Regex; use ruma::{ api::{ appservice, @@ -116,19 +115,12 @@ pub(crate) async fn get_alias_helper(room_alias: OwnedRoomAliasId) -> Result room_id = Some(r), None => { - for (_id, registration) in services().appservice.all()? { - let aliases = registration - .namespaces - .aliases - .iter() - .filter_map(|alias| Regex::new(alias.regex.as_str()).ok()) - .collect::>(); - - if aliases.iter().any(|aliases| aliases.is_match(room_alias.as_str())) + for appservice in services().appservice.registration_info.read().await.values() { + if appservice.aliases.is_match(room_alias.as_str()) && if let Some(opt_result) = services() .sending .send_appservice_request( - registration, + appservice.registration.clone(), appservice::query::query_room_alias::v1::Request { room_alias: room_alias.clone(), }, @@ -144,7 +136,7 @@ pub(crate) async fn get_alias_helper(room_alias: OwnedRoomAliasId) -> Result = Box>)>> + 'a>; @@ -160,19 +162,20 @@ impl service::rooms::state_cache::Data for KeyValueDatabase { } #[tracing::instrument(skip(self, room_id, appservice))] - fn appservice_in_room(&self, room_id: &RoomId, appservice: &(String, Registration)) -> Result { - let maybe = - self.appservice_in_room_cache.read().unwrap().get(room_id).and_then(|map| map.get(&appservice.0)).copied(); + fn appservice_in_room(&self, room_id: &RoomId, appservice: &RegistrationInfo) -> Result { + let maybe = self + .appservice_in_room_cache + .read() + .unwrap() + .get(room_id) + .and_then(|map| map.get(&appservice.registration.id)) + .copied(); if let Some(b) = maybe { Ok(b) } else { - let namespaces = &appservice.1.namespaces; - let users = - namespaces.users.iter().filter_map(|users| Regex::new(users.regex.as_str()).ok()).collect::>(); - let bridge_user_id = UserId::parse_with_server_name( - appservice.1.sender_localpart.as_str(), + appservice.registration.sender_localpart.as_str(), services().globals.server_name(), ) .ok(); @@ -180,14 +183,14 @@ impl service::rooms::state_cache::Data for KeyValueDatabase { let in_room = bridge_user_id.map_or(false, |id| self.is_joined(&id, room_id).unwrap_or(false)) || self .room_members(room_id) - .any(|userid| userid.map_or(false, |userid| users.iter().any(|r| r.is_match(userid.as_str())))); + .any(|userid| userid.map_or(false, |userid| appservice.users.is_match(userid.as_str()))); self.appservice_in_room_cache .write() .unwrap() .entry(room_id.to_owned()) .or_default() - .insert(appservice.0.clone(), in_room); + .insert(appservice.registration.id.clone(), in_room); Ok(in_room) } diff --git a/src/database/mod.rs b/src/database/mod.rs index 7222d1b0..2cb22c90 100644 --- a/src/database/mod.rs +++ b/src/database/mod.rs @@ -967,6 +967,14 @@ impl KeyValueDatabase { ); } + // Inserting registraions into cache + for appservice in services().appservice.all()? { + services().appservice.registration_info.write().await.insert( + appservice.0, + appservice.1.try_into().expect("Should be validated on registration"), + ); + } + services().admin.start_handler(); // Set emergency access for the conduit user diff --git a/src/service/admin/mod.rs b/src/service/admin/mod.rs index f7e27c38..06b00ce4 100644 --- a/src/service/admin/mod.rs +++ b/src/service/admin/mod.rs @@ -567,7 +567,7 @@ impl Service { let appservice_config = body[1..body.len() - 1].join("\n"); let parsed_config = serde_yaml::from_str::(&appservice_config); match parsed_config { - Ok(yaml) => match services().appservice.register_appservice(yaml) { + Ok(yaml) => match services().appservice.register_appservice(yaml).await { Ok(id) => { RoomMessageEventContent::text_plain(format!("Appservice registered with ID: {id}.")) }, @@ -587,7 +587,7 @@ impl Service { }, AppserviceCommand::Unregister { appservice_identifier, - } => match services().appservice.unregister_appservice(&appservice_identifier) { + } => match services().appservice.unregister_appservice(&appservice_identifier).await { Ok(()) => RoomMessageEventContent::text_plain("Appservice unregistered."), Err(e) => RoomMessageEventContent::text_plain(format!("Failed to unregister appservice: {e}")), }, diff --git a/src/service/appservice/mod.rs b/src/service/appservice/mod.rs index 5700731d..5884e4a8 100644 --- a/src/service/appservice/mod.rs +++ b/src/service/appservice/mod.rs @@ -1,24 +1,118 @@ mod data; -pub(crate) use data::Data; -use ruma::api::appservice::Registration; +use std::collections::HashMap; -use crate::Result; +pub(crate) use data::Data; +use regex::RegexSet; +use ruma::api::appservice::{Namespace, Registration}; +use tokio::sync::RwLock; + +use crate::{services, Result}; + +/// Compiled regular expressions for a namespace +pub struct NamespaceRegex { + pub exclusive: Option, + pub non_exclusive: Option, +} + +impl NamespaceRegex { + /// Checks if this namespace has rights to a namespace + pub fn is_match(&self, heystack: &str) -> bool { + if self.is_exclusive_match(heystack) { + return true; + } + + if let Some(non_exclusive) = &self.non_exclusive { + if non_exclusive.is_match(heystack) { + return true; + } + } + false + } + + /// Checks if this namespace has exlusive rights to a namespace + pub fn is_exclusive_match(&self, heystack: &str) -> bool { + if let Some(exclusive) = &self.exclusive { + if exclusive.is_match(heystack) { + return true; + } + } + false + } +} + +impl TryFrom> for NamespaceRegex { + type Error = regex::Error; + + fn try_from(value: Vec) -> Result { + let mut exclusive = vec![]; + let mut non_exclusive = vec![]; + + for namespace in value { + if namespace.exclusive { + exclusive.push(namespace.regex); + } else { + non_exclusive.push(namespace.regex); + } + } + + Ok(NamespaceRegex { + exclusive: if exclusive.is_empty() { + None + } else { + Some(RegexSet::new(exclusive)?) + }, + non_exclusive: if non_exclusive.is_empty() { + None + } else { + Some(RegexSet::new(non_exclusive)?) + }, + }) + } +} + +/// Compiled regular expressions for an appservice +pub struct RegistrationInfo { + pub registration: Registration, + pub users: NamespaceRegex, + pub aliases: NamespaceRegex, + pub rooms: NamespaceRegex, +} + +impl TryFrom for RegistrationInfo { + type Error = regex::Error; + + fn try_from(value: Registration) -> Result { + Ok(RegistrationInfo { + users: value.namespaces.users.clone().try_into()?, + aliases: value.namespaces.aliases.clone().try_into()?, + rooms: value.namespaces.rooms.clone().try_into()?, + registration: value, + }) + } +} pub struct Service { pub db: &'static dyn Data, + pub registration_info: RwLock>, } impl Service { /// Registers an appservice and returns the ID to the caller - pub fn register_appservice(&self, yaml: Registration) -> Result { self.db.register_appservice(yaml) } + pub async fn register_appservice(&self, yaml: Registration) -> Result { + services().appservice.registration_info.write().await.insert(yaml.id.clone(), yaml.clone().try_into()?); + + self.db.register_appservice(yaml) + } /// Remove an appservice registration /// /// # Arguments /// /// * `service_name` - the name you send to register the service previously - pub fn unregister_appservice(&self, service_name: &str) -> Result<()> { + pub async fn unregister_appservice(&self, service_name: &str) -> Result<()> { + services().appservice.registration_info.write().await.remove(service_name); + self.db.unregister_appservice(service_name) } diff --git a/src/service/mod.rs b/src/service/mod.rs index b4e7bb4e..25413647 100644 --- a/src/service/mod.rs +++ b/src/service/mod.rs @@ -57,6 +57,7 @@ impl Services<'_> { Ok(Self { appservice: appservice::Service { db, + registration_info: RwLock::new(HashMap::new()), }, pusher: pusher::Service { db, diff --git a/src/service/rooms/state_cache/data.rs b/src/service/rooms/state_cache/data.rs index 44ae57f8..6e97396f 100644 --- a/src/service/rooms/state_cache/data.rs +++ b/src/service/rooms/state_cache/data.rs @@ -1,13 +1,12 @@ use std::{collections::HashSet, sync::Arc}; use ruma::{ - api::appservice::Registration, events::{AnyStrippedStateEvent, AnySyncStateEvent}, serde::Raw, OwnedRoomId, OwnedServerName, OwnedUserId, RoomId, ServerName, UserId, }; -use crate::Result; +use crate::{service::appservice::RegistrationInfo, Result}; type StrippedStateEventIter<'a> = Box>)>> + 'a>; @@ -25,7 +24,7 @@ pub trait Data: Send + Sync { fn get_our_real_users(&self, room_id: &RoomId) -> Result>>; - fn appservice_in_room(&self, room_id: &RoomId, appservice: &(String, Registration)) -> Result; + fn appservice_in_room(&self, room_id: &RoomId, appservice: &RegistrationInfo) -> Result; /// Makes a user forget a room. fn forget(&self, room_id: &RoomId, user_id: &UserId) -> Result<()>; diff --git a/src/service/rooms/state_cache/mod.rs b/src/service/rooms/state_cache/mod.rs index 7f305712..0ce82e1f 100644 --- a/src/service/rooms/state_cache/mod.rs +++ b/src/service/rooms/state_cache/mod.rs @@ -2,7 +2,7 @@ use std::{collections::HashSet, sync::Arc}; pub use data::Data; use ruma::{ - api::{appservice::Registration, federation}, + api::federation, events::{ direct::DirectEvent, ignored_user_list::IgnoredUserListEvent, @@ -17,7 +17,7 @@ use ruma::{ }; use tracing::warn; -use crate::{services, Error, Result}; +use crate::{service::appservice::RegistrationInfo, services, Error, Result}; mod data; @@ -201,7 +201,7 @@ impl Service { } #[tracing::instrument(skip(self, room_id, appservice))] - pub fn appservice_in_room(&self, room_id: &RoomId, appservice: &(String, Registration)) -> Result { + pub fn appservice_in_room(&self, room_id: &RoomId, appservice: &RegistrationInfo) -> Result { self.db.appservice_in_room(room_id, appservice) } diff --git a/src/service/rooms/timeline/mod.rs b/src/service/rooms/timeline/mod.rs index 95063371..9ffe972e 100644 --- a/src/service/rooms/timeline/mod.rs +++ b/src/service/rooms/timeline/mod.rs @@ -7,7 +7,6 @@ use std::{ }; pub use data::Data; -use regex::Regex; use ruma::{ api::{client::error::ErrorKind, federation}, canonical_json::to_canonical_value, @@ -36,7 +35,10 @@ use tracing::{error, info, warn}; use super::state_compressor::CompressedStateEvent; use crate::{ api::server_server, - service::pdu::{EventHash, PduBuilder}, + service::{ + appservice::NamespaceRegex, + pdu::{EventHash, PduBuilder}, + }, services, utils, Error, PduEvent, Result, }; @@ -506,9 +508,9 @@ impl Service { } } - for appservice in services().appservice.all()? { - if services().rooms.state_cache.appservice_in_room(&pdu.room_id, &appservice)? { - services().sending.send_pdu_appservice(appservice.0, pdu_id.clone())?; + for appservice in services().appservice.registration_info.read().await.values() { + if services().rooms.state_cache.appservice_in_room(&pdu.room_id, appservice)? { + services().sending.send_pdu_appservice(appservice.registration.id.clone(), pdu_id.clone())?; continue; } @@ -518,30 +520,20 @@ impl Service { if let Some(state_key_uid) = &pdu.state_key.as_ref().and_then(|state_key| UserId::parse(state_key.as_str()).ok()) { - let appservice_uid = appservice.1.sender_localpart.as_str(); + let appservice_uid = appservice.registration.sender_localpart.as_str(); if state_key_uid == appservice_uid { - services().sending.send_pdu_appservice(appservice.0, pdu_id.clone())?; + services().sending.send_pdu_appservice(appservice.registration.id.clone(), pdu_id.clone())?; continue; } } } - let namespaces = appservice.1.namespaces; - - // TODO: create some helper function to change from Strings to Regexes - let users = - namespaces.users.iter().filter_map(|user| Regex::new(user.regex.as_str()).ok()).collect::>(); - let aliases = - namespaces.aliases.iter().filter_map(|alias| Regex::new(alias.regex.as_str()).ok()).collect::>(); - let rooms = - namespaces.rooms.iter().filter_map(|room| Regex::new(room.regex.as_str()).ok()).collect::>(); - - let matching_users = |users: &Regex| { - users.is_match(pdu.sender.as_str()) + let matching_users = |users: &NamespaceRegex| { + appservice.users.is_match(pdu.sender.as_str()) || pdu.kind == TimelineEventType::RoomMember && pdu.state_key.as_ref().map_or(false, |state_key| users.is_match(state_key)) }; - let matching_aliases = |aliases: &Regex| { + let matching_aliases = |aliases: &NamespaceRegex| { services() .rooms .alias @@ -550,11 +542,11 @@ impl Service { .any(|room_alias| aliases.is_match(room_alias.as_str())) }; - if aliases.iter().any(matching_aliases) - || rooms.iter().any(|namespace| namespace.is_match(pdu.room_id.as_str())) - || users.iter().any(matching_users) + if matching_aliases(&appservice.aliases) + || appservice.rooms.is_match(pdu.room_id.as_str()) + || matching_users(&appservice.users) { - services().sending.send_pdu_appservice(appservice.0, pdu_id.clone())?; + services().sending.send_pdu_appservice(appservice.registration.id.clone(), pdu_id.clone())?; } } diff --git a/src/utils/error.rs b/src/utils/error.rs index 60209860..4463ffbb 100644 --- a/src/utils/error.rs +++ b/src/utils/error.rs @@ -39,6 +39,11 @@ pub enum Error { #[from] source: reqwest::Error, }, + #[error("Could build regular expression: {source}")] + RegexError { + #[from] + source: regex::Error, + }, #[error("{0}")] FederationError(OwnedServerName, RumaError), #[error("Could not do this io: {source}")]