tuwunel_service/rooms/alias/
mod.rs1#[cfg(test)]
2mod tests;
3
4use std::sync::Arc;
5
6use futures::{Stream, StreamExt};
7use ruma::{
8 OwnedRoomId, OwnedServerName, OwnedUserId, RoomAliasId, RoomId, RoomOrAliasId, UserId,
9 api::federation::query::get_room_information::v1::Request, events::StateEventType,
10};
11use tuwunel_core::{
12 Err, Result, err, implement,
13 matrix::Event,
14 utils::{ReadyExt, stream::TryIgnore},
15};
16use tuwunel_database::{Deserialized, Ignore, Interfix, Map};
17
18use crate::appservice::RegistrationInfo;
19
20pub struct Service {
21 db: Data,
22 services: Arc<crate::services::OnceServices>,
23}
24
25struct Data {
26 alias_userid: Arc<Map>,
27 alias_roomid: Arc<Map>,
28 aliasid_alias: Arc<Map>,
29}
30
31impl crate::Service for Service {
32 fn build(args: &crate::Args<'_>) -> Result<Arc<Self>> {
33 Ok(Arc::new(Self {
34 db: Data {
35 alias_userid: args.db["alias_userid"].clone(),
36 alias_roomid: args.db["alias_roomid"].clone(),
37 aliasid_alias: args.db["aliasid_alias"].clone(),
38 },
39 services: args.services.clone(),
40 }))
41 }
42
43 fn name(&self) -> &str { crate::service::make_name(std::module_path!()) }
44}
45
46#[implement(Service)]
51#[tracing::instrument(skip(self, appservice_info), level = "trace")]
52pub async fn creation_check(
53 &self,
54 user_id: &UserId,
55 appservice_info: Option<&RegistrationInfo>,
56) -> Result {
57 if self.services.config.allow_room_alias_creation
58 || appservice_info.is_some()
59 || self.services.admin.user_is_admin(user_id).await
60 {
61 return Ok(());
62 }
63
64 Err!(Request(Forbidden("Room alias creation has been disabled.")))
65}
66
67impl Service {
68 pub fn set_alias(&self, alias: &RoomAliasId, room_id: &RoomId) -> Result {
69 self.check_alias_local(alias)?;
70
71 self.set_alias_by(alias, room_id, &self.services.globals.server_user)
72 }
73
74 #[tracing::instrument(skip(self))]
75 pub fn set_alias_by(
76 &self,
77 alias: &RoomAliasId,
78 room_id: &RoomId,
79 user_id: &UserId,
80 ) -> Result {
81 self.check_alias_local(alias)?;
82
83 if alias == self.services.admin.admin_alias
84 && user_id != self.services.globals.server_user
85 {
86 return Err!(Request(Forbidden("Only the server user can set this alias")));
87 }
88
89 let count = self.services.globals.next_count();
90
91 let localpart = alias.alias();
92
93 self.db.alias_userid.insert(localpart, user_id);
95
96 self.db.alias_roomid.insert(localpart, room_id);
97
98 self.db
99 .aliasid_alias
100 .put_raw((room_id, *count), alias);
101
102 Ok(())
103 }
104
105 pub async fn remove_alias_by(
110 &self,
111 alias: &RoomAliasId,
112 user_id: &UserId,
113 ) -> Result<OwnedRoomId> {
114 if !self.user_can_remove_alias(alias, user_id).await? {
115 return Err!(Request(Forbidden("User is not permitted to remove this alias.")));
116 }
117
118 self.remove_alias(alias).await
119 }
120
121 #[tracing::instrument(skip(self))]
127 pub async fn remove_alias(&self, alias: &RoomAliasId) -> Result<OwnedRoomId> {
128 let localpart = alias.alias();
129 let Ok(room_id) = self
130 .db
131 .alias_roomid
132 .get(&localpart)
133 .await
134 .deserialized()
135 else {
136 return Err!(Request(NotFound("Alias does not exist or is invalid.")));
137 };
138
139 let prefix = (&room_id, Interfix);
140
141 self.db
144 .aliasid_alias
145 .stream_prefix_raw(&prefix)
146 .ignore_err()
147 .ready_filter_map(|(key, stored)| stored.eq(alias.as_bytes()).then_some(key))
148 .ready_for_each(|key| self.db.aliasid_alias.remove(key))
149 .await;
150
151 self.db.alias_roomid.remove(localpart.as_bytes());
152 self.db.alias_userid.remove(localpart.as_bytes());
153
154 Ok(room_id)
155 }
156
157 #[inline]
158 pub async fn maybe_resolve(&self, room: &RoomOrAliasId) -> Result<OwnedRoomId> {
159 match <&RoomId>::try_from(room) {
160 | Ok(room_id) => Ok(room_id.to_owned()),
161 | Err(alias) => Ok(self.resolve_alias(alias).await?.0),
162 }
163 }
164
165 pub async fn maybe_resolve_with_servers(
166 &self,
167 room: &RoomOrAliasId,
168 servers: Option<&[OwnedServerName]>,
169 ) -> Result<(OwnedRoomId, Vec<OwnedServerName>)> {
170 match <&RoomId>::try_from(room) {
171 | Ok(room_id) => Ok((room_id.to_owned(), Vec::from(servers.unwrap_or_default()))),
172 | Err(alias) => self.resolve_alias(alias).await,
173 }
174 }
175
176 #[tracing::instrument(skip(self), name = "resolve")]
177 pub async fn resolve_alias(
178 &self,
179 room_alias: &RoomAliasId,
180 ) -> Result<(OwnedRoomId, Vec<OwnedServerName>)> {
181 if self.services.globals.alias_is_local(room_alias) {
182 if let Ok(room_id) = self.resolve_local_alias(room_alias).await {
183 return Ok((room_id, Vec::new()));
184 }
185
186 if let Ok(room_id) = self.resolve_appservice_alias(room_alias).await {
187 return Ok((room_id, Vec::new()));
188 }
189
190 return Err!(Request(NotFound("Room with alias not found.")));
191 }
192
193 return self.remote_resolve(room_alias).await;
194 }
195
196 async fn remote_resolve(
197 &self,
198 room_alias: &RoomAliasId,
199 ) -> Result<(OwnedRoomId, Vec<OwnedServerName>)> {
200 let server = room_alias.server_name();
201
202 let request = Request { room_alias: room_alias.to_owned() };
203
204 let response = self
205 .services
206 .federation
207 .execute(server, request)
208 .await?;
209
210 Ok((response.room_id, response.servers))
211 }
212
213 #[tracing::instrument(skip(self), level = "trace")]
214 pub async fn resolve_local_alias(&self, alias: &RoomAliasId) -> Result<OwnedRoomId> {
215 self.check_alias_local(alias)?;
216 self.db
217 .alias_roomid
218 .get(alias.alias())
219 .await
220 .deserialized()
221 }
222
223 #[tracing::instrument(skip(self), level = "debug")]
224 pub fn local_aliases_for_room<'a>(
225 &'a self,
226 room_id: &'a RoomId,
227 ) -> impl Stream<Item = &RoomAliasId> + Send + 'a {
228 let prefix = (room_id, Interfix);
229 self.db
230 .aliasid_alias
231 .stream_prefix(&prefix)
232 .ignore_err()
233 .map(|(_, alias): (Ignore, &RoomAliasId)| alias)
234 }
235
236 #[tracing::instrument(skip(self), level = "debug")]
237 pub fn all_local_aliases(&self) -> impl Stream<Item = (&RoomId, &str)> + Send + '_ {
238 self.db
239 .alias_roomid
240 .stream()
241 .ignore_err()
242 .map(|(alias_localpart, room_id): (&str, &RoomId)| (room_id, alias_localpart))
243 }
244
245 async fn user_can_remove_alias(&self, alias: &RoomAliasId, user_id: &UserId) -> Result<bool> {
246 self.check_alias_local(alias)?;
247
248 let room_id = self
249 .resolve_local_alias(alias)
250 .await
251 .map_err(|_| err!(Request(NotFound("Alias not found."))))?;
252
253 if self
255 .who_created_alias(alias).await
256 .is_ok_and(|user| user == user_id)
257 || self.services.admin.user_is_admin(user_id).await
259 {
260 return Ok(true);
261 }
262
263 if let Ok(power_levels) = self
265 .services
266 .state_accessor
267 .get_power_levels(&room_id)
268 .await
269 {
270 return Ok(
271 power_levels.user_can_send_state(user_id, StateEventType::RoomCanonicalAlias)
272 );
273 }
274
275 if let Ok(event) = self
278 .services
279 .state_accessor
280 .room_state_get(&room_id, &StateEventType::RoomCreate, "")
281 .await
282 {
283 return Ok(event.sender() == user_id);
284 }
285
286 Err!(Database("Room has no m.room.create event"))
287 }
288
289 async fn who_created_alias(&self, alias: &RoomAliasId) -> Result<OwnedUserId> {
290 self.check_alias_local(alias)?;
291
292 self.db
293 .alias_userid
294 .get(alias.alias())
295 .await
296 .deserialized()
297 }
298
299 async fn resolve_appservice_alias(&self, room_alias: &RoomAliasId) -> Result<OwnedRoomId> {
300 use ruma::api::appservice::query::query_room_alias;
301
302 self.check_alias_local(room_alias)?;
303
304 for appservice in self.services.appservice.read().await.values() {
305 if appservice.aliases.is_match(room_alias.as_str())
306 && matches!(
307 self.services
308 .appservice
309 .send_request(
310 appservice.registration.clone(),
311 query_room_alias::v1::Request { room_alias: room_alias.to_owned() },
312 )
313 .await,
314 Ok(Some(_opt_result))
315 ) {
316 return self
317 .resolve_local_alias(room_alias)
318 .await
319 .map_err(|_| err!(Request(NotFound("Room does not exist."))));
320 }
321 }
322
323 Err!(Request(NotFound("Room does not exist.")))
324 }
325
326 fn check_alias_local(&self, alias: &RoomAliasId) -> Result {
327 if !self.services.globals.alias_is_local(alias) {
328 return Err!(Request(InvalidParam("Alias is from another server.")));
329 }
330
331 Ok(())
332 }
333
334 #[tracing::instrument(skip(self, appservice_info), level = "trace")]
335 pub async fn appservice_checks(
336 &self,
337 room_alias: &RoomAliasId,
338 appservice_info: &Option<RegistrationInfo>,
339 ) -> Result {
340 self.check_alias_local(room_alias)?;
341 if let Some(info) = appservice_info {
342 if !info.aliases.is_match(room_alias.as_str()) {
343 return Err!(Request(Exclusive("Room alias is not in namespace.")));
344 }
345 } else if self
346 .services
347 .appservice
348 .is_exclusive_alias(room_alias)
349 .await
350 {
351 return Err!(Request(Exclusive("Room alias reserved by appservice.")));
352 }
353
354 Ok(())
355 }
356}