2022-08-07 19:42:22 +02:00
|
|
|
mod data;
|
2022-10-08 13:04:55 +02:00
|
|
|
|
2024-03-22 17:51:15 +00:00
|
|
|
use std::collections::BTreeMap;
|
2023-12-27 13:22:21 +00:00
|
|
|
|
2022-08-07 19:42:22 +02:00
|
|
|
pub use data::Data;
|
|
|
|
|
2024-03-22 18:27:14 +00:00
|
|
|
use futures_util::Future;
|
2023-12-27 13:22:21 +00:00
|
|
|
use regex::RegexSet;
|
2024-04-16 15:53:38 +01:00
|
|
|
use ruma::{
|
|
|
|
api::appservice::{Namespace, Registration},
|
|
|
|
RoomAliasId, RoomId, UserId,
|
|
|
|
};
|
2023-12-27 13:22:21 +00:00
|
|
|
use tokio::sync::RwLock;
|
|
|
|
|
|
|
|
use crate::{services, Result};
|
|
|
|
|
2024-03-22 08:52:39 +01:00
|
|
|
/// Compiled regular expressions for a namespace.
|
|
|
|
#[derive(Clone, Debug)]
|
2023-12-27 13:22:21 +00:00
|
|
|
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 {
|
|
|
|
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)?)
|
|
|
|
},
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
type Error = regex::Error;
|
|
|
|
}
|
|
|
|
|
2024-03-22 08:52:39 +01:00
|
|
|
/// Appservice registration combined with its compiled regular expressions.
|
|
|
|
#[derive(Clone, Debug)]
|
2023-12-27 13:22:21 +00:00
|
|
|
pub struct RegistrationInfo {
|
|
|
|
pub registration: Registration,
|
|
|
|
pub users: NamespaceRegex,
|
|
|
|
pub aliases: NamespaceRegex,
|
|
|
|
pub rooms: NamespaceRegex,
|
|
|
|
}
|
|
|
|
|
2024-04-16 15:53:38 +01:00
|
|
|
impl RegistrationInfo {
|
2024-04-27 20:41:28 +01:00
|
|
|
/// Checks if a given user ID matches either the users namespace or the localpart specified in the appservice registration
|
2024-04-16 15:53:38 +01:00
|
|
|
pub fn is_user_match(&self, user_id: &UserId) -> bool {
|
|
|
|
self.users.is_match(user_id.as_str())
|
|
|
|
|| self.registration.sender_localpart == user_id.localpart()
|
|
|
|
}
|
|
|
|
|
2024-04-27 20:41:28 +01:00
|
|
|
/// Checks if a given user ID exclusively matches either the users namespace or the localpart specified in the appservice registration
|
2024-04-16 15:53:38 +01:00
|
|
|
pub fn is_exclusive_user_match(&self, user_id: &UserId) -> bool {
|
|
|
|
self.users.is_exclusive_match(user_id.as_str())
|
|
|
|
|| self.registration.sender_localpart == user_id.localpart()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-12-27 13:22:21 +00:00
|
|
|
impl TryFrom<Registration> for RegistrationInfo {
|
|
|
|
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,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
type Error = regex::Error;
|
|
|
|
}
|
2022-08-07 19:42:22 +02:00
|
|
|
|
2022-10-05 12:45:54 +02:00
|
|
|
pub struct Service {
|
2022-10-08 13:02:52 +02:00
|
|
|
pub db: &'static dyn Data,
|
2024-03-22 17:51:15 +00:00
|
|
|
registration_info: RwLock<BTreeMap<String, RegistrationInfo>>,
|
2022-08-07 19:42:22 +02:00
|
|
|
}
|
|
|
|
|
2022-10-05 12:45:54 +02:00
|
|
|
impl Service {
|
2024-03-22 08:52:39 +01:00
|
|
|
pub fn build(db: &'static dyn Data) -> Result<Self> {
|
2024-03-22 17:51:15 +00:00
|
|
|
let mut registration_info = BTreeMap::new();
|
2024-03-22 08:52:39 +01:00
|
|
|
// Inserting registrations into cache
|
|
|
|
for appservice in db.all()? {
|
|
|
|
registration_info.insert(
|
|
|
|
appservice.0,
|
|
|
|
appservice
|
|
|
|
.1
|
|
|
|
.try_into()
|
|
|
|
.expect("Should be validated on registration"),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
Ok(Self {
|
|
|
|
db,
|
|
|
|
registration_info: RwLock::new(registration_info),
|
|
|
|
})
|
|
|
|
}
|
|
|
|
/// Registers an appservice and returns the ID to the caller.
|
2023-12-27 13:22:21 +00:00
|
|
|
pub async fn register_appservice(&self, yaml: Registration) -> Result<String> {
|
2024-04-16 15:53:38 +01:00
|
|
|
//TODO: Check for collisions between exclusive appservice namespaces
|
2023-12-27 13:22:21 +00:00
|
|
|
services()
|
|
|
|
.appservice
|
|
|
|
.registration_info
|
|
|
|
.write()
|
|
|
|
.await
|
|
|
|
.insert(yaml.id.clone(), yaml.clone().try_into()?);
|
|
|
|
|
2022-08-07 19:42:22 +02:00
|
|
|
self.db.register_appservice(yaml)
|
|
|
|
}
|
|
|
|
|
2024-03-22 08:52:39 +01:00
|
|
|
/// Removes an appservice registration.
|
2022-08-07 19:42:22 +02:00
|
|
|
///
|
|
|
|
/// # Arguments
|
|
|
|
///
|
|
|
|
/// * `service_name` - the name you send to register the service previously
|
2023-12-27 13:22:21 +00:00
|
|
|
pub async fn unregister_appservice(&self, service_name: &str) -> Result<()> {
|
|
|
|
services()
|
|
|
|
.appservice
|
|
|
|
.registration_info
|
|
|
|
.write()
|
|
|
|
.await
|
2024-03-31 13:25:04 +01:00
|
|
|
.remove(service_name)
|
|
|
|
.ok_or_else(|| crate::Error::AdminCommand("Appservice not found"))?;
|
2023-12-27 13:22:21 +00:00
|
|
|
|
2022-08-07 19:42:22 +02:00
|
|
|
self.db.unregister_appservice(service_name)
|
|
|
|
}
|
|
|
|
|
2024-03-22 18:27:14 +00:00
|
|
|
pub async fn get_registration(&self, id: &str) -> Option<Registration> {
|
|
|
|
self.registration_info
|
|
|
|
.read()
|
|
|
|
.await
|
|
|
|
.get(id)
|
|
|
|
.cloned()
|
|
|
|
.map(|info| info.registration)
|
2022-08-07 19:42:22 +02:00
|
|
|
}
|
|
|
|
|
2024-03-22 18:27:14 +00:00
|
|
|
pub async fn iter_ids(&self) -> Vec<String> {
|
2024-03-22 08:52:39 +01:00
|
|
|
self.registration_info
|
|
|
|
.read()
|
|
|
|
.await
|
2024-03-22 18:27:14 +00:00
|
|
|
.keys()
|
2024-03-22 08:52:39 +01:00
|
|
|
.cloned()
|
|
|
|
.collect()
|
2022-08-07 19:42:22 +02:00
|
|
|
}
|
2024-03-22 18:27:14 +00:00
|
|
|
|
|
|
|
pub async fn find_from_token(&self, token: &str) -> Option<RegistrationInfo> {
|
|
|
|
self.read()
|
|
|
|
.await
|
|
|
|
.values()
|
|
|
|
.find(|info| info.registration.as_token == token)
|
|
|
|
.cloned()
|
|
|
|
}
|
|
|
|
|
2024-04-16 15:53:38 +01:00
|
|
|
// Checks if a given user id matches any exclusive appservice regex
|
|
|
|
pub async fn is_exclusive_user_id(&self, user_id: &UserId) -> bool {
|
|
|
|
self.read()
|
|
|
|
.await
|
|
|
|
.values()
|
|
|
|
.any(|info| info.is_exclusive_user_match(user_id))
|
|
|
|
}
|
|
|
|
|
|
|
|
// Checks if a given room alias matches any exclusive appservice regex
|
|
|
|
pub async fn is_exclusive_alias(&self, alias: &RoomAliasId) -> bool {
|
|
|
|
self.read()
|
|
|
|
.await
|
|
|
|
.values()
|
|
|
|
.any(|info| info.aliases.is_exclusive_match(alias.as_str()))
|
|
|
|
}
|
|
|
|
|
|
|
|
// Checks if a given room id matches any exclusive appservice regex
|
|
|
|
pub async fn is_exclusive_room_id(&self, room_id: &RoomId) -> bool {
|
|
|
|
self.read()
|
|
|
|
.await
|
|
|
|
.values()
|
|
|
|
.any(|info| info.rooms.is_exclusive_match(room_id.as_str()))
|
|
|
|
}
|
|
|
|
|
2024-03-22 18:27:14 +00:00
|
|
|
pub fn read(
|
|
|
|
&self,
|
|
|
|
) -> impl Future<Output = tokio::sync::RwLockReadGuard<'_, BTreeMap<String, RegistrationInfo>>>
|
|
|
|
{
|
|
|
|
self.registration_info.read()
|
|
|
|
}
|
2022-08-07 19:42:22 +02:00
|
|
|
}
|