Skip to main content

tuwunel_service/appservice/
mod.rs

1mod append;
2mod keys;
3mod namespace_regex;
4mod ping;
5mod registration_info;
6pub(crate) mod request;
7mod thirdparty;
8
9use std::{
10	collections::BTreeMap,
11	ffi::OsStr,
12	fs::{self, read_dir},
13	sync::Arc,
14};
15
16use async_trait::async_trait;
17use futures::{FutureExt, Stream, TryStreamExt};
18use ruma::{RoomAliasId, RoomId, UserId, api::appservice::Registration};
19use tokio::sync::{RwLock, RwLockReadGuard, SetOnce};
20use tuwunel_core::{Err, Result, defer, err, utils::stream::IterStream};
21use tuwunel_database::Map;
22
23pub use self::{namespace_regex::NamespaceRegex, registration_info::RegistrationInfo};
24
25pub struct Service {
26	registration_info: RwLock<Registrations>,
27	loaded: SetOnce<()>,
28	services: Arc<crate::services::OnceServices>,
29	db: Data,
30}
31
32struct Data {
33	id_appserviceregistrations: Arc<Map>,
34}
35
36type Registrations = BTreeMap<String, RegistrationInfo>;
37
38#[async_trait]
39impl crate::Service for Service {
40	fn build(args: &crate::Args<'_>) -> Result<Arc<Self>> {
41		Ok(Arc::new(Self {
42			registration_info: RwLock::new(BTreeMap::new()),
43			loaded: SetOnce::new(),
44			services: args.services.clone(),
45			db: Data {
46				id_appserviceregistrations: args.db["id_appserviceregistrations"].clone(),
47			},
48		}))
49	}
50
51	async fn worker(self: Arc<Self>) -> Result {
52		defer! {{
53			self.loaded.set(()).ok();
54		}}
55
56		self.load().await
57	}
58
59	fn name(&self) -> &str { crate::service::make_name(std::module_path!()) }
60}
61
62impl Service {
63	/// Loads every registration source into the runtime registry.
64	///
65	/// The configured `appservice` table is read first, then any YAML under
66	/// `appservice_dir`, then the registrations persisted by the admin command.
67	async fn load(&self) -> Result {
68		for (id, mut appservice) in self.services.config.appservice.clone() {
69			if appservice.id.is_empty() {
70				appservice.id = id.clone();
71			}
72
73			if *id != appservice.id {
74				return Err!(Config(
75					"id",
76					"Registration ID {:?} does not match the configured {id:?}",
77					appservice.id
78				));
79			}
80
81			self.load_appservice(appservice.into()).await?;
82		}
83
84		if let Some(appservice_dir) = &self.services.config.appservice_dir {
85			let entries = read_dir(appservice_dir).map_err(|e| {
86				err!(Config("appservice_dir", "Failed to read {appservice_dir:?}: {e}"))
87			})?;
88
89			for dir_entry in entries {
90				let path = dir_entry?.path();
91
92				if !path.is_file()
93					|| !path
94						.extension()
95						.and_then(OsStr::to_str)
96						.is_some_and(|ext| matches!(ext, "yaml" | "yml"))
97				{
98					continue;
99				}
100
101				let bytes = fs::read(path)?;
102				let registration: Registration = serde_yaml::from_slice(&bytes)?;
103
104				self.load_appservice(registration).await?;
105			}
106		}
107
108		self.iter_db_ids()
109			.try_for_each(|registration| self.load_appservice(registration))
110			.await?;
111
112		Ok(())
113	}
114
115	pub async fn load_appservice(&self, registration: Registration) -> Result {
116		//TODO: Check for collisions between exclusive appservice namespaces
117
118		let registration_info =
119			RegistrationInfo::new(registration, self.services.globals.server_name())?;
120
121		let id = &registration_info.registration.id;
122
123		let mut registrations = self.registration_info.write().await;
124
125		for loaded_registration_info in registrations.values() {
126			let loaded_id = &loaded_registration_info.registration.id;
127
128			if loaded_id == id {
129				return Err!("Duplicate id: {id}");
130			}
131
132			if loaded_registration_info.registration.as_token
133				== registration_info.registration.as_token
134			{
135				return Err!("Duplicate as_token: {loaded_id} {id}");
136			}
137		}
138
139		let appservice_user = &registration_info.sender;
140
141		if !self.services.users.exists(appservice_user).await {
142			self.services
143				.users
144				.create(appservice_user, None, None)
145				.await?;
146		}
147
148		registrations.insert(id.clone(), registration_info);
149
150		Ok(())
151	}
152
153	pub async fn register_appservice(&self, registration: Registration) -> Result {
154		self.loaded().await;
155
156		let id = registration.id.clone();
157
158		let appservice_yaml = serde_yaml::to_string(&registration)?;
159
160		self.load_appservice(registration).await?;
161
162		self.db
163			.id_appserviceregistrations
164			.insert(&id, appservice_yaml);
165
166		Ok(())
167	}
168
169	pub async fn unregister_appservice(&self, appservice_id: &str) -> Result {
170		self.loaded().await;
171
172		let mut registrations = self.registration_info.write().await;
173
174		if !registrations.contains_key(appservice_id) {
175			return Err!("Appservice not found");
176		}
177
178		if self
179			.db
180			.id_appserviceregistrations
181			.exists(appservice_id)
182			.await
183			.is_err()
184		{
185			return Err!("Cannot unregister config appservice");
186		}
187
188		// removes the appservice registration info
189		registrations
190			.remove(appservice_id)
191			.ok_or_else(|| err!("Appservice not found"))?;
192
193		// remove the appservice from the database
194		self.db
195			.id_appserviceregistrations
196			.remove(appservice_id);
197
198		// deletes all active requests for the appservice if there are any so we stop
199		// sending to the URL
200		self.services
201			.sending
202			.cleanup_events(Some(appservice_id), None, None)
203			.await
204	}
205
206	pub async fn get_registration(&self, id: &str) -> Option<Registration> {
207		self.registration_info
208			.read()
209			.await
210			.get(id)
211			.cloned()
212			.map(|info| info.registration)
213	}
214
215	/// Retrieve a registration with its compiled namespaces (`sender`,
216	/// `is_user_match`), which a bare `Registration` lacks.
217	pub async fn get_registration_info(&self, id: &str) -> Option<RegistrationInfo> {
218		self.registration_info
219			.read()
220			.await
221			.get(id)
222			.cloned()
223	}
224
225	pub async fn find_from_access_token(&self, token: &str) -> Result<RegistrationInfo> {
226		self.read()
227			.await
228			.values()
229			.find(|info| info.registration.as_token == token)
230			.cloned()
231			.ok_or_else(|| err!(Request(NotFound("Missing or invalid appservice token"))))
232	}
233
234	/// Checks if a given user id matches any exclusive appservice regex
235	pub async fn is_exclusive_user_id(&self, user_id: &UserId) -> bool {
236		self.read()
237			.await
238			.values()
239			.any(|info| info.is_exclusive_user_match(user_id))
240	}
241
242	/// Checks if a given user id matches any appservice's user namespace.
243	pub async fn is_interested_in_user(&self, user_id: &UserId) -> bool {
244		self.read()
245			.await
246			.values()
247			.any(|info| info.is_user_match(user_id))
248	}
249
250	/// Checks if a given room alias matches any exclusive appservice regex
251	pub async fn is_exclusive_alias(&self, alias: &RoomAliasId) -> bool {
252		self.read()
253			.await
254			.values()
255			.any(|info| info.aliases.is_exclusive_match(alias.as_str()))
256	}
257
258	/// Checks if a given room id matches any exclusive appservice regex
259	///
260	/// TODO: use this?
261	pub async fn is_exclusive_room_id(&self, room_id: &RoomId) -> bool {
262		self.read()
263			.await
264			.values()
265			.any(|info| info.rooms.is_exclusive_match(room_id.as_str()))
266	}
267
268	pub fn iter_ids(&self) -> impl Stream<Item = String> + Send {
269		self.read()
270			.map(|info| info.keys().cloned().collect::<Vec<_>>())
271			.map(IntoIterator::into_iter)
272			.map(IterStream::stream)
273			.flatten_stream()
274	}
275
276	pub fn iter_db_ids(&self) -> impl Stream<Item = Result<Registration>> + Send {
277		self.db
278			.id_appserviceregistrations
279			.keys()
280			.and_then(async move |id: &str| Ok(self.get_db_registration(id).await?))
281	}
282
283	pub async fn get_db_registration(&self, id: &str) -> Result<Registration> {
284		self.db
285			.id_appserviceregistrations
286			.get(id)
287			.await
288			.and_then(|ref bytes| serde_yaml::from_slice(bytes).map_err(Into::into))
289			.map_err(|e| err!(Database("Invalid appservice {id:?} registration: {e:?}")))
290	}
291
292	pub fn read(&self) -> impl Future<Output = RwLockReadGuard<'_, Registrations>> + Send {
293		self.registration_info.read()
294	}
295
296	/// Waits for the boot-time registration load to finish.
297	///
298	/// The latch is released on every exit from the worker, a failed or
299	/// panicking load included, so a waiter is never stranded on a load that
300	/// will not complete.
301	#[inline]
302	pub async fn loaded(&self) { self.loaded.wait().await; }
303}