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 <strawberry@puppygock.gay>
Signed-off-by: strawberry <strawberry@puppygock.gay>
This commit is contained in:
Matthias Ahouansou 2024-03-08 10:50:52 -05:00 committed by June
parent 019a82850d
commit 5ab76a1332
10 changed files with 156 additions and 62 deletions

View file

@ -1,5 +1,4 @@
use rand::seq::SliceRandom; use rand::seq::SliceRandom;
use regex::Regex;
use ruma::{ use ruma::{
api::{ api::{
appservice, appservice,
@ -116,19 +115,12 @@ pub(crate) async fn get_alias_helper(room_alias: OwnedRoomAliasId) -> Result<get
match services().rooms.alias.resolve_local_alias(&room_alias)? { match services().rooms.alias.resolve_local_alias(&room_alias)? {
Some(r) => room_id = Some(r), Some(r) => room_id = Some(r),
None => { None => {
for (_id, registration) in services().appservice.all()? { for appservice in services().appservice.registration_info.read().await.values() {
let aliases = registration if appservice.aliases.is_match(room_alias.as_str())
.namespaces
.aliases
.iter()
.filter_map(|alias| Regex::new(alias.regex.as_str()).ok())
.collect::<Vec<_>>();
if aliases.iter().any(|aliases| aliases.is_match(room_alias.as_str()))
&& if let Some(opt_result) = services() && if let Some(opt_result) = services()
.sending .sending
.send_appservice_request( .send_appservice_request(
registration, appservice.registration.clone(),
appservice::query::query_room_alias::v1::Request { appservice::query::query_room_alias::v1::Request {
room_alias: room_alias.clone(), room_alias: room_alias.clone(),
}, },
@ -144,7 +136,7 @@ pub(crate) async fn get_alias_helper(room_alias: OwnedRoomAliasId) -> Result<get
.rooms .rooms
.alias .alias
.resolve_local_alias(&room_alias)? .resolve_local_alias(&room_alias)?
.ok_or_else(|| Error::bad_config("Appservice lied to us. Room does not exist."))?, .ok_or_else(|| Error::bad_config("Room does not exist."))?,
); );
break; break;
} }

View file

@ -1,14 +1,16 @@
use std::{collections::HashSet, sync::Arc}; use std::{collections::HashSet, sync::Arc};
use regex::Regex;
use ruma::{ use ruma::{
api::appservice::Registration,
events::{AnyStrippedStateEvent, AnySyncStateEvent}, events::{AnyStrippedStateEvent, AnySyncStateEvent},
serde::Raw, serde::Raw,
OwnedRoomId, OwnedServerName, OwnedUserId, RoomId, ServerName, UserId, OwnedRoomId, OwnedServerName, OwnedUserId, RoomId, ServerName, UserId,
}; };
use crate::{database::KeyValueDatabase, service, services, utils, Error, Result}; use crate::{
database::KeyValueDatabase,
service::{self, appservice::RegistrationInfo},
services, utils, Error, Result,
};
type StrippedStateEventIter<'a> = Box<dyn Iterator<Item = Result<(OwnedRoomId, Vec<Raw<AnyStrippedStateEvent>>)>> + 'a>; type StrippedStateEventIter<'a> = Box<dyn Iterator<Item = Result<(OwnedRoomId, Vec<Raw<AnyStrippedStateEvent>>)>> + 'a>;
@ -160,19 +162,20 @@ impl service::rooms::state_cache::Data for KeyValueDatabase {
} }
#[tracing::instrument(skip(self, room_id, appservice))] #[tracing::instrument(skip(self, room_id, appservice))]
fn appservice_in_room(&self, room_id: &RoomId, appservice: &(String, Registration)) -> Result<bool> { fn appservice_in_room(&self, room_id: &RoomId, appservice: &RegistrationInfo) -> Result<bool> {
let maybe = let maybe = self
self.appservice_in_room_cache.read().unwrap().get(room_id).and_then(|map| map.get(&appservice.0)).copied(); .appservice_in_room_cache
.read()
.unwrap()
.get(room_id)
.and_then(|map| map.get(&appservice.registration.id))
.copied();
if let Some(b) = maybe { if let Some(b) = maybe {
Ok(b) Ok(b)
} else { } else {
let namespaces = &appservice.1.namespaces;
let users =
namespaces.users.iter().filter_map(|users| Regex::new(users.regex.as_str()).ok()).collect::<Vec<_>>();
let bridge_user_id = UserId::parse_with_server_name( 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(), services().globals.server_name(),
) )
.ok(); .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)) let in_room = bridge_user_id.map_or(false, |id| self.is_joined(&id, room_id).unwrap_or(false))
|| self || self
.room_members(room_id) .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 self.appservice_in_room_cache
.write() .write()
.unwrap() .unwrap()
.entry(room_id.to_owned()) .entry(room_id.to_owned())
.or_default() .or_default()
.insert(appservice.0.clone(), in_room); .insert(appservice.registration.id.clone(), in_room);
Ok(in_room) Ok(in_room)
} }

View file

@ -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(); services().admin.start_handler();
// Set emergency access for the conduit user // Set emergency access for the conduit user

View file

@ -567,7 +567,7 @@ impl Service {
let appservice_config = body[1..body.len() - 1].join("\n"); let appservice_config = body[1..body.len() - 1].join("\n");
let parsed_config = serde_yaml::from_str::<Registration>(&appservice_config); let parsed_config = serde_yaml::from_str::<Registration>(&appservice_config);
match parsed_config { match parsed_config {
Ok(yaml) => match services().appservice.register_appservice(yaml) { Ok(yaml) => match services().appservice.register_appservice(yaml).await {
Ok(id) => { Ok(id) => {
RoomMessageEventContent::text_plain(format!("Appservice registered with ID: {id}.")) RoomMessageEventContent::text_plain(format!("Appservice registered with ID: {id}."))
}, },
@ -587,7 +587,7 @@ impl Service {
}, },
AppserviceCommand::Unregister { AppserviceCommand::Unregister {
appservice_identifier, appservice_identifier,
} => match services().appservice.unregister_appservice(&appservice_identifier) { } => match services().appservice.unregister_appservice(&appservice_identifier).await {
Ok(()) => RoomMessageEventContent::text_plain("Appservice unregistered."), Ok(()) => RoomMessageEventContent::text_plain("Appservice unregistered."),
Err(e) => RoomMessageEventContent::text_plain(format!("Failed to unregister appservice: {e}")), Err(e) => RoomMessageEventContent::text_plain(format!("Failed to unregister appservice: {e}")),
}, },

View file

@ -1,24 +1,118 @@
mod data; mod data;
pub(crate) use data::Data; use std::collections::HashMap;
use ruma::api::appservice::Registration;
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<RegexSet>,
pub non_exclusive: Option<RegexSet>,
}
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<Vec<Namespace>> for NamespaceRegex {
type Error = regex::Error;
fn try_from(value: Vec<Namespace>) -> Result<Self, regex::Error> {
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<Registration> for RegistrationInfo {
type Error = regex::Error;
fn try_from(value: Registration) -> Result<RegistrationInfo, regex::Error> {
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 struct Service {
pub db: &'static dyn Data, pub db: &'static dyn Data,
pub registration_info: RwLock<HashMap<String, RegistrationInfo>>,
} }
impl Service { impl Service {
/// Registers an appservice and returns the ID to the caller /// Registers an appservice and returns the ID to the caller
pub fn register_appservice(&self, yaml: Registration) -> Result<String> { self.db.register_appservice(yaml) } pub async fn register_appservice(&self, yaml: Registration) -> Result<String> {
services().appservice.registration_info.write().await.insert(yaml.id.clone(), yaml.clone().try_into()?);
self.db.register_appservice(yaml)
}
/// Remove an appservice registration /// Remove an appservice registration
/// ///
/// # Arguments /// # Arguments
/// ///
/// * `service_name` - the name you send to register the service previously /// * `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) self.db.unregister_appservice(service_name)
} }

View file

@ -57,6 +57,7 @@ impl Services<'_> {
Ok(Self { Ok(Self {
appservice: appservice::Service { appservice: appservice::Service {
db, db,
registration_info: RwLock::new(HashMap::new()),
}, },
pusher: pusher::Service { pusher: pusher::Service {
db, db,

View file

@ -1,13 +1,12 @@
use std::{collections::HashSet, sync::Arc}; use std::{collections::HashSet, sync::Arc};
use ruma::{ use ruma::{
api::appservice::Registration,
events::{AnyStrippedStateEvent, AnySyncStateEvent}, events::{AnyStrippedStateEvent, AnySyncStateEvent},
serde::Raw, serde::Raw,
OwnedRoomId, OwnedServerName, OwnedUserId, RoomId, ServerName, UserId, OwnedRoomId, OwnedServerName, OwnedUserId, RoomId, ServerName, UserId,
}; };
use crate::Result; use crate::{service::appservice::RegistrationInfo, Result};
type StrippedStateEventIter<'a> = Box<dyn Iterator<Item = Result<(OwnedRoomId, Vec<Raw<AnyStrippedStateEvent>>)>> + 'a>; type StrippedStateEventIter<'a> = Box<dyn Iterator<Item = Result<(OwnedRoomId, Vec<Raw<AnyStrippedStateEvent>>)>> + 'a>;
@ -25,7 +24,7 @@ pub trait Data: Send + Sync {
fn get_our_real_users(&self, room_id: &RoomId) -> Result<Arc<HashSet<OwnedUserId>>>; fn get_our_real_users(&self, room_id: &RoomId) -> Result<Arc<HashSet<OwnedUserId>>>;
fn appservice_in_room(&self, room_id: &RoomId, appservice: &(String, Registration)) -> Result<bool>; fn appservice_in_room(&self, room_id: &RoomId, appservice: &RegistrationInfo) -> Result<bool>;
/// Makes a user forget a room. /// Makes a user forget a room.
fn forget(&self, room_id: &RoomId, user_id: &UserId) -> Result<()>; fn forget(&self, room_id: &RoomId, user_id: &UserId) -> Result<()>;

View file

@ -2,7 +2,7 @@ use std::{collections::HashSet, sync::Arc};
pub use data::Data; pub use data::Data;
use ruma::{ use ruma::{
api::{appservice::Registration, federation}, api::federation,
events::{ events::{
direct::DirectEvent, direct::DirectEvent,
ignored_user_list::IgnoredUserListEvent, ignored_user_list::IgnoredUserListEvent,
@ -17,7 +17,7 @@ use ruma::{
}; };
use tracing::warn; use tracing::warn;
use crate::{services, Error, Result}; use crate::{service::appservice::RegistrationInfo, services, Error, Result};
mod data; mod data;
@ -201,7 +201,7 @@ impl Service {
} }
#[tracing::instrument(skip(self, room_id, appservice))] #[tracing::instrument(skip(self, room_id, appservice))]
pub fn appservice_in_room(&self, room_id: &RoomId, appservice: &(String, Registration)) -> Result<bool> { pub fn appservice_in_room(&self, room_id: &RoomId, appservice: &RegistrationInfo) -> Result<bool> {
self.db.appservice_in_room(room_id, appservice) self.db.appservice_in_room(room_id, appservice)
} }

View file

@ -7,7 +7,6 @@ use std::{
}; };
pub use data::Data; pub use data::Data;
use regex::Regex;
use ruma::{ use ruma::{
api::{client::error::ErrorKind, federation}, api::{client::error::ErrorKind, federation},
canonical_json::to_canonical_value, canonical_json::to_canonical_value,
@ -36,7 +35,10 @@ use tracing::{error, info, warn};
use super::state_compressor::CompressedStateEvent; use super::state_compressor::CompressedStateEvent;
use crate::{ use crate::{
api::server_server, api::server_server,
service::pdu::{EventHash, PduBuilder}, service::{
appservice::NamespaceRegex,
pdu::{EventHash, PduBuilder},
},
services, utils, Error, PduEvent, Result, services, utils, Error, PduEvent, Result,
}; };
@ -506,9 +508,9 @@ impl Service {
} }
} }
for appservice in services().appservice.all()? { for appservice in services().appservice.registration_info.read().await.values() {
if services().rooms.state_cache.appservice_in_room(&pdu.room_id, &appservice)? { if services().rooms.state_cache.appservice_in_room(&pdu.room_id, appservice)? {
services().sending.send_pdu_appservice(appservice.0, pdu_id.clone())?; services().sending.send_pdu_appservice(appservice.registration.id.clone(), pdu_id.clone())?;
continue; continue;
} }
@ -518,30 +520,20 @@ impl Service {
if let Some(state_key_uid) = if let Some(state_key_uid) =
&pdu.state_key.as_ref().and_then(|state_key| UserId::parse(state_key.as_str()).ok()) &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 { 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; continue;
} }
} }
} }
let namespaces = appservice.1.namespaces; let matching_users = |users: &NamespaceRegex| {
appservice.users.is_match(pdu.sender.as_str())
// 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::<Vec<_>>();
let aliases =
namespaces.aliases.iter().filter_map(|alias| Regex::new(alias.regex.as_str()).ok()).collect::<Vec<_>>();
let rooms =
namespaces.rooms.iter().filter_map(|room| Regex::new(room.regex.as_str()).ok()).collect::<Vec<_>>();
let matching_users = |users: &Regex| {
users.is_match(pdu.sender.as_str())
|| pdu.kind == TimelineEventType::RoomMember || pdu.kind == TimelineEventType::RoomMember
&& pdu.state_key.as_ref().map_or(false, |state_key| users.is_match(state_key)) && 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() services()
.rooms .rooms
.alias .alias
@ -550,11 +542,11 @@ impl Service {
.any(|room_alias| aliases.is_match(room_alias.as_str())) .any(|room_alias| aliases.is_match(room_alias.as_str()))
}; };
if aliases.iter().any(matching_aliases) if matching_aliases(&appservice.aliases)
|| rooms.iter().any(|namespace| namespace.is_match(pdu.room_id.as_str())) || appservice.rooms.is_match(pdu.room_id.as_str())
|| users.iter().any(matching_users) || 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())?;
} }
} }

View file

@ -39,6 +39,11 @@ pub enum Error {
#[from] #[from]
source: reqwest::Error, source: reqwest::Error,
}, },
#[error("Could build regular expression: {source}")]
RegexError {
#[from]
source: regex::Error,
},
#[error("{0}")] #[error("{0}")]
FederationError(OwnedServerName, RumaError), FederationError(OwnedServerName, RumaError),
#[error("Could not do this io: {source}")] #[error("Could not do this io: {source}")]