Implement declarative appservices. (closes #67)
Signed-off-by: Jason Volk <jason@zemos.net>
This commit is contained in:
@@ -25,7 +25,11 @@ use url::Url;
|
||||
|
||||
use self::proxy::ProxyConfig;
|
||||
pub use self::{check::check, manager::Manager};
|
||||
use crate::{Result, err, error::Error, utils::sys};
|
||||
use crate::{
|
||||
Result, err,
|
||||
error::Error,
|
||||
utils::{string::EMPTY, sys},
|
||||
};
|
||||
|
||||
/// All the config options for tuwunel.
|
||||
#[allow(clippy::struct_excessive_bools)]
|
||||
@@ -52,7 +56,8 @@ use crate::{Result, err, error::Error, utils::sys};
|
||||
### For more information, see:
|
||||
### https://tuwunel.chat/configuration.html
|
||||
"#,
|
||||
ignore = "catchall well_known tls blurhashing allow_invalid_tls_certificates ldap jwt"
|
||||
ignore = "catchall well_known tls blurhashing allow_invalid_tls_certificates ldap jwt \
|
||||
appservice"
|
||||
)]
|
||||
pub struct Config {
|
||||
/// The server_name is the pretty name of this server. It is used as a
|
||||
@@ -1818,6 +1823,10 @@ pub struct Config {
|
||||
#[serde(default)]
|
||||
pub jwt: JwtConfig,
|
||||
|
||||
// external structure; separate section
|
||||
#[serde(default)]
|
||||
pub appservice: BTreeMap<String, AppService>,
|
||||
|
||||
#[serde(flatten)]
|
||||
#[allow(clippy::zero_sized_map_values)]
|
||||
// this is a catchall, the map shouldn't be zero at runtime
|
||||
@@ -2088,6 +2097,120 @@ pub struct JwtConfig {
|
||||
pub validate_signature: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default, Deserialize)]
|
||||
#[config_example_generator(
|
||||
filename = "tuwunel-example.toml",
|
||||
section = "global.appservice.<ID>",
|
||||
ignore = "id users aliases rooms"
|
||||
)]
|
||||
pub struct AppService {
|
||||
#[serde(default)]
|
||||
pub id: String,
|
||||
|
||||
/// The URL for the application service.
|
||||
///
|
||||
/// Optionally set to `null` if no traffic is required.
|
||||
pub url: Option<String>,
|
||||
|
||||
/// A unique token for application services to use to authenticate requests
|
||||
/// to Homeservers.
|
||||
pub as_token: String,
|
||||
|
||||
/// A unique token for Homeservers to use to authenticate requests to
|
||||
/// application services.
|
||||
pub hs_token: String,
|
||||
|
||||
/// The localpart of the user associated with the application service.
|
||||
pub sender_localpart: Option<String>,
|
||||
|
||||
/// Events which are sent from certain users.
|
||||
#[serde(default)]
|
||||
pub users: Vec<AppServiceNamespace>,
|
||||
|
||||
/// Events which are sent in rooms with certain room aliases.
|
||||
#[serde(default)]
|
||||
pub aliases: Vec<AppServiceNamespace>,
|
||||
|
||||
/// Events which are sent in rooms with certain room IDs.
|
||||
#[serde(default)]
|
||||
pub rooms: Vec<AppServiceNamespace>,
|
||||
|
||||
/// Whether requests from masqueraded users are rate-limited.
|
||||
///
|
||||
/// The sender is excluded.
|
||||
#[serde(default)]
|
||||
pub rate_limited: bool,
|
||||
|
||||
/// The external protocols which the application service provides (e.g.
|
||||
/// IRC).
|
||||
///
|
||||
/// default: []
|
||||
#[serde(default)]
|
||||
pub protocols: Vec<String>,
|
||||
|
||||
/// Whether the application service wants to receive ephemeral data.
|
||||
///
|
||||
/// default: false
|
||||
#[serde(default)]
|
||||
pub receive_ephemeral: bool,
|
||||
|
||||
/// Whether the application service wants to do device management, as part
|
||||
/// of MSC4190.
|
||||
///
|
||||
/// default: false
|
||||
#[serde(default)]
|
||||
pub device_management: bool,
|
||||
}
|
||||
|
||||
impl From<AppService> for ruma::api::appservice::Registration {
|
||||
fn from(conf: AppService) -> Self {
|
||||
use ruma::api::appservice::Namespaces;
|
||||
|
||||
Self {
|
||||
id: conf.id,
|
||||
url: conf.url,
|
||||
as_token: conf.as_token,
|
||||
hs_token: conf.hs_token,
|
||||
receive_ephemeral: conf.receive_ephemeral,
|
||||
device_management: conf.device_management,
|
||||
protocols: conf.protocols.into(),
|
||||
rate_limited: conf.rate_limited.into(),
|
||||
sender_localpart: conf
|
||||
.sender_localpart
|
||||
.unwrap_or_else(|| EMPTY.into()),
|
||||
namespaces: Namespaces {
|
||||
users: conf.users.into_iter().map(Into::into).collect(),
|
||||
aliases: conf.aliases.into_iter().map(Into::into).collect(),
|
||||
rooms: conf.rooms.into_iter().map(Into::into).collect(),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default, Deserialize)]
|
||||
#[config_example_generator(
|
||||
filename = "tuwunel-example.toml",
|
||||
section = "[global.appservice.<ID>.<users|rooms|aliases>]"
|
||||
)]
|
||||
pub struct AppServiceNamespace {
|
||||
/// Whether this application service has exclusive access to events within
|
||||
/// this namespace.
|
||||
#[serde(default)]
|
||||
pub exclusive: bool,
|
||||
|
||||
/// A regular expression defining which values this namespace includes.
|
||||
pub regex: String,
|
||||
}
|
||||
|
||||
impl From<AppServiceNamespace> for ruma::api::appservice::Namespace {
|
||||
fn from(conf: AppServiceNamespace) -> Self {
|
||||
Self {
|
||||
exclusive: conf.exclusive,
|
||||
regex: conf.regex,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Clone, Debug)]
|
||||
#[serde(transparent)]
|
||||
struct ListeningPort {
|
||||
|
||||
@@ -4,10 +4,10 @@ mod registration_info;
|
||||
use std::{collections::BTreeMap, iter::IntoIterator, sync::Arc};
|
||||
|
||||
use async_trait::async_trait;
|
||||
use futures::{Future, FutureExt, Stream, TryStreamExt};
|
||||
use futures::{Future, FutureExt, Stream, StreamExt, TryStreamExt};
|
||||
use ruma::{RoomAliasId, RoomId, UserId, api::appservice::Registration};
|
||||
use tokio::sync::{RwLock, RwLockReadGuard};
|
||||
use tuwunel_core::{Result, err, utils::stream::IterStream};
|
||||
use tuwunel_core::{Err, Result, Server, debug, err, utils::stream::IterStream};
|
||||
use tuwunel_database::Map;
|
||||
|
||||
pub use self::{namespace_regex::NamespaceRegex, registration_info::RegistrationInfo};
|
||||
@@ -21,6 +21,7 @@ pub struct Service {
|
||||
|
||||
struct Services {
|
||||
sending: Dep<sending::Service>,
|
||||
server: Arc<Server>,
|
||||
}
|
||||
|
||||
struct Data {
|
||||
@@ -36,6 +37,7 @@ impl crate::Service for Service {
|
||||
registration_info: RwLock::new(BTreeMap::new()),
|
||||
services: Services {
|
||||
sending: args.depend::<sending::Service>("sending"),
|
||||
server: args.server.clone(),
|
||||
},
|
||||
db: Data {
|
||||
id_appserviceregistrations: args.db["id_appserviceregistrations"].clone(),
|
||||
@@ -44,23 +46,48 @@ impl crate::Service for Service {
|
||||
}
|
||||
|
||||
async fn worker(self: Arc<Self>) -> Result {
|
||||
// Inserting registrations into cache
|
||||
self.iter_db_ids()
|
||||
.try_for_each(async |appservice| {
|
||||
self.registration_info
|
||||
.write()
|
||||
.await
|
||||
.insert(appservice.0, appservice.1.try_into()?);
|
||||
self.init_registrations().await?;
|
||||
|
||||
Ok(())
|
||||
})
|
||||
.await
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn name(&self) -> &str { crate::service::make_name(std::module_path!()) }
|
||||
}
|
||||
|
||||
impl Service {
|
||||
#[tracing::instrument(name = "init", skip(self))]
|
||||
async fn init_registrations(&self) -> Result {
|
||||
// Registrations from configuration file
|
||||
let confs = self
|
||||
.services
|
||||
.server
|
||||
.config
|
||||
.appservice
|
||||
.clone()
|
||||
.into_iter()
|
||||
.stream()
|
||||
.map(|(id, mut reg)| {
|
||||
reg.id.clone_from(&id);
|
||||
reg.sender_localpart
|
||||
.get_or_insert_with(|| id.clone());
|
||||
|
||||
Ok((id, reg))
|
||||
});
|
||||
|
||||
// Registrations from database
|
||||
self.iter_db_ids()
|
||||
.chain(confs.map_ok(|(id, reg)| (id, reg.into())))
|
||||
.try_for_each(async |(id, reg): (_, Registration)| {
|
||||
debug!(?id, ?reg, "appservice registration");
|
||||
self.registration_info
|
||||
.write()
|
||||
.await
|
||||
.insert(id.clone(), reg.try_into()?)
|
||||
.map_or(Ok(()), |_| Err!("Conflicting Appservice ID: {id:?}"))
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
/// Registers an appservice and returns the ID to the caller
|
||||
pub async fn register_appservice(
|
||||
&self,
|
||||
|
||||
@@ -1778,3 +1778,56 @@
|
||||
# Bypass validation for diagnostic/debug use only.
|
||||
#
|
||||
#validate_signature = true
|
||||
|
||||
[global.appservice.<ID>]
|
||||
|
||||
# The URL for the application service.
|
||||
#
|
||||
# Optionally set to `null` if no traffic is required.
|
||||
#
|
||||
#url =
|
||||
|
||||
# A unique token for application services to use to authenticate requests
|
||||
# to Homeservers.
|
||||
#
|
||||
#as_token =
|
||||
|
||||
# A unique token for Homeservers to use to authenticate requests to
|
||||
# application services.
|
||||
#
|
||||
#hs_token =
|
||||
|
||||
# The localpart of the user associated with the application service.
|
||||
#
|
||||
#sender_localpart =
|
||||
|
||||
# Whether requests from masqueraded users are rate-limited.
|
||||
#
|
||||
# The sender is excluded.
|
||||
#
|
||||
#rate_limited = false
|
||||
|
||||
# The external protocols which the application service provides (e.g.
|
||||
# IRC).
|
||||
#
|
||||
#protocols = []
|
||||
|
||||
# Whether the application service wants to receive ephemeral data.
|
||||
#
|
||||
#receive_ephemeral = false
|
||||
|
||||
# Whether the application service wants to do device management, as part
|
||||
# of MSC4190.
|
||||
#
|
||||
#device_management = false
|
||||
|
||||
[[global.appservice.<ID>.<users|rooms|aliases>]]
|
||||
|
||||
# Whether this application service has exclusive access to events within
|
||||
# this namespace.
|
||||
#
|
||||
#exclusive = false
|
||||
|
||||
# A regular expression defining which values this namespace includes.
|
||||
#
|
||||
#regex =
|
||||
|
||||
Reference in New Issue
Block a user