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;
|
use self::proxy::ProxyConfig;
|
||||||
pub use self::{check::check, manager::Manager};
|
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.
|
/// All the config options for tuwunel.
|
||||||
#[allow(clippy::struct_excessive_bools)]
|
#[allow(clippy::struct_excessive_bools)]
|
||||||
@@ -52,7 +56,8 @@ use crate::{Result, err, error::Error, utils::sys};
|
|||||||
### For more information, see:
|
### For more information, see:
|
||||||
### https://tuwunel.chat/configuration.html
|
### 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 {
|
pub struct Config {
|
||||||
/// The server_name is the pretty name of this server. It is used as a
|
/// The server_name is the pretty name of this server. It is used as a
|
||||||
@@ -1818,6 +1823,10 @@ pub struct Config {
|
|||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub jwt: JwtConfig,
|
pub jwt: JwtConfig,
|
||||||
|
|
||||||
|
// external structure; separate section
|
||||||
|
#[serde(default)]
|
||||||
|
pub appservice: BTreeMap<String, AppService>,
|
||||||
|
|
||||||
#[serde(flatten)]
|
#[serde(flatten)]
|
||||||
#[allow(clippy::zero_sized_map_values)]
|
#[allow(clippy::zero_sized_map_values)]
|
||||||
// this is a catchall, the map shouldn't be zero at runtime
|
// this is a catchall, the map shouldn't be zero at runtime
|
||||||
@@ -2088,6 +2097,120 @@ pub struct JwtConfig {
|
|||||||
pub validate_signature: bool,
|
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)]
|
#[derive(Deserialize, Clone, Debug)]
|
||||||
#[serde(transparent)]
|
#[serde(transparent)]
|
||||||
struct ListeningPort {
|
struct ListeningPort {
|
||||||
|
|||||||
@@ -4,10 +4,10 @@ mod registration_info;
|
|||||||
use std::{collections::BTreeMap, iter::IntoIterator, sync::Arc};
|
use std::{collections::BTreeMap, iter::IntoIterator, sync::Arc};
|
||||||
|
|
||||||
use async_trait::async_trait;
|
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 ruma::{RoomAliasId, RoomId, UserId, api::appservice::Registration};
|
||||||
use tokio::sync::{RwLock, RwLockReadGuard};
|
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;
|
use tuwunel_database::Map;
|
||||||
|
|
||||||
pub use self::{namespace_regex::NamespaceRegex, registration_info::RegistrationInfo};
|
pub use self::{namespace_regex::NamespaceRegex, registration_info::RegistrationInfo};
|
||||||
@@ -21,6 +21,7 @@ pub struct Service {
|
|||||||
|
|
||||||
struct Services {
|
struct Services {
|
||||||
sending: Dep<sending::Service>,
|
sending: Dep<sending::Service>,
|
||||||
|
server: Arc<Server>,
|
||||||
}
|
}
|
||||||
|
|
||||||
struct Data {
|
struct Data {
|
||||||
@@ -36,6 +37,7 @@ impl crate::Service for Service {
|
|||||||
registration_info: RwLock::new(BTreeMap::new()),
|
registration_info: RwLock::new(BTreeMap::new()),
|
||||||
services: Services {
|
services: Services {
|
||||||
sending: args.depend::<sending::Service>("sending"),
|
sending: args.depend::<sending::Service>("sending"),
|
||||||
|
server: args.server.clone(),
|
||||||
},
|
},
|
||||||
db: Data {
|
db: Data {
|
||||||
id_appserviceregistrations: args.db["id_appserviceregistrations"].clone(),
|
id_appserviceregistrations: args.db["id_appserviceregistrations"].clone(),
|
||||||
@@ -44,23 +46,48 @@ impl crate::Service for Service {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async fn worker(self: Arc<Self>) -> Result {
|
async fn worker(self: Arc<Self>) -> Result {
|
||||||
// Inserting registrations into cache
|
self.init_registrations().await?;
|
||||||
self.iter_db_ids()
|
|
||||||
.try_for_each(async |appservice| {
|
|
||||||
self.registration_info
|
|
||||||
.write()
|
|
||||||
.await
|
|
||||||
.insert(appservice.0, appservice.1.try_into()?);
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
})
|
|
||||||
.await
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn name(&self) -> &str { crate::service::make_name(std::module_path!()) }
|
fn name(&self) -> &str { crate::service::make_name(std::module_path!()) }
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Service {
|
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
|
/// Registers an appservice and returns the ID to the caller
|
||||||
pub async fn register_appservice(
|
pub async fn register_appservice(
|
||||||
&self,
|
&self,
|
||||||
|
|||||||
@@ -1778,3 +1778,56 @@
|
|||||||
# Bypass validation for diagnostic/debug use only.
|
# Bypass validation for diagnostic/debug use only.
|
||||||
#
|
#
|
||||||
#validate_signature = true
|
#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