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