Files
continuwuity/src/service/rooms/alias/mod.rs
T

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

314 lines
8.4 KiB
Rust
Raw Normal View History

mod remote;
2022-10-08 13:04:55 +02:00
2024-05-09 15:59:08 -07:00
use std::sync::Arc;
2024-12-14 21:58:01 -05:00
use conduwuit::{
Err, Event, Result, Server, err,
utils::{ReadyExt, stream::TryIgnore},
2024-08-08 17:18:30 +00:00
};
use database::{Deserialized, Ignore, Interfix, Map};
2024-12-18 03:32:58 +00:00
use futures::{Stream, StreamExt, TryFutureExt};
2024-06-12 01:42:39 -04:00
use ruma::{
OwnedRoomId, OwnedServerName, OwnedUserId, RoomAliasId, RoomId, RoomOrAliasId, UserId,
2024-06-12 01:42:39 -04:00
events::{
StateEventType,
room::power_levels::{RoomPowerLevels, RoomPowerLevelsEventContent},
2024-06-12 01:42:39 -04:00
},
};
2022-09-07 13:25:51 +02:00
use crate::{Dep, admin, appservice, appservice::RegistrationInfo, globals, rooms, sending};
2024-05-09 15:59:08 -07:00
pub struct Service {
2024-06-28 22:51:39 +00:00
db: Data,
2024-07-18 06:37:47 +00:00
services: Services,
}
2024-08-08 17:18:30 +00:00
struct Data {
alias_userid: Arc<Map>,
alias_roomid: Arc<Map>,
aliasid_alias: Arc<Map>,
}
2024-07-18 06:37:47 +00:00
struct Services {
server: Arc<Server>,
2024-07-18 06:37:47 +00:00
admin: Dep<admin::Service>,
appservice: Dep<appservice::Service>,
globals: Dep<globals::Service>,
sending: Dep<sending::Service>,
state_accessor: Dep<rooms::state_accessor::Service>,
}
2024-07-04 03:26:19 +00:00
impl crate::Service for Service {
fn build(args: crate::Args<'_>) -> Result<Arc<Self>> {
Ok(Arc::new(Self {
2024-08-08 17:18:30 +00:00
db: Data {
alias_userid: args.db["alias_userid"].clone(),
alias_roomid: args.db["alias_roomid"].clone(),
aliasid_alias: args.db["aliasid_alias"].clone(),
},
2024-07-18 06:37:47 +00:00
services: Services {
server: args.server.clone(),
2024-07-18 06:37:47 +00:00
admin: args.depend::<admin::Service>("admin"),
appservice: args.depend::<appservice::Service>("appservice"),
globals: args.depend::<globals::Service>("globals"),
sending: args.depend::<sending::Service>("sending"),
state_accessor: args
.depend::<rooms::state_accessor::Service>("rooms::state_accessor"),
2024-07-18 06:37:47 +00:00
},
2024-07-04 03:26:19 +00:00
}))
2024-05-27 03:17:20 +00:00
}
2024-07-04 03:26:19 +00:00
fn name(&self) -> &str { crate::service::make_name(std::module_path!()) }
}
impl Service {
2022-09-06 23:15:09 +02:00
#[tracing::instrument(skip(self))]
pub fn set_alias(
&self,
alias: &RoomAliasId,
room_id: &RoomId,
user_id: &UserId,
) -> Result<()> {
if alias == self.services.globals.admin_alias
&& user_id != self.services.globals.server_user
{
2024-12-18 03:32:58 +00:00
return Err!(Request(Forbidden("Only the server user can set this alias")));
}
2024-08-08 17:18:30 +00:00
// Comes first as we don't want a stuck alias
self.db
.alias_userid
.insert(alias.alias().as_bytes(), user_id.as_bytes());
self.db
.alias_roomid
.insert(alias.alias().as_bytes(), room_id.as_bytes());
let mut aliasid = room_id.as_bytes().to_vec();
aliasid.push(0xFF);
aliasid.extend_from_slice(&self.services.globals.next_count()?.to_be_bytes());
self.db.aliasid_alias.insert(&aliasid, alias.as_bytes());
Ok(())
2024-06-12 01:42:39 -04:00
}
2024-03-05 19:48:54 -05:00
2022-09-06 23:15:09 +02:00
#[tracing::instrument(skip(self))]
2024-06-12 01:42:39 -04:00
pub async fn remove_alias(&self, alias: &RoomAliasId, user_id: &UserId) -> Result<()> {
2024-08-08 17:18:30 +00:00
if !self.user_can_remove_alias(alias, user_id).await? {
return Err!(Request(Forbidden("User is not permitted to remove this alias.")));
2024-06-12 01:42:39 -04:00
}
2024-08-08 17:18:30 +00:00
let alias = alias.alias();
let Ok(room_id) = self.db.alias_roomid.get(&alias).await else {
2024-08-08 17:18:30 +00:00
return Err!(Request(NotFound("Alias does not exist or is invalid.")));
};
let prefix = (&room_id, Interfix);
self.db
.aliasid_alias
.keys_prefix_raw(&prefix)
2024-08-08 17:18:30 +00:00
.ignore_err()
2024-10-04 17:17:10 +00:00
.ready_for_each(|key| self.db.aliasid_alias.remove(key))
2024-08-08 17:18:30 +00:00
.await;
self.db.alias_roomid.remove(alias.as_bytes());
self.db.alias_userid.remove(alias.as_bytes());
Ok(())
2024-06-12 01:42:39 -04:00
}
2024-03-05 19:48:54 -05:00
#[inline]
2024-06-23 02:57:32 +00:00
pub async fn resolve(&self, room: &RoomOrAliasId) -> Result<OwnedRoomId> {
self.resolve_with_servers(room, None)
.await
.map(|(room_id, _)| room_id)
}
pub async fn resolve_with_servers(
&self,
room: &RoomOrAliasId,
servers: Option<Vec<OwnedServerName>>,
) -> Result<(OwnedRoomId, Vec<OwnedServerName>)> {
2024-06-23 02:57:32 +00:00
if room.is_room_id() {
2024-12-28 23:31:24 +00:00
let room_id: &RoomId = room.try_into().expect("valid RoomId");
Ok((room_id.to_owned(), servers.unwrap_or_default()))
2024-06-23 02:57:32 +00:00
} else {
2024-12-28 23:31:24 +00:00
let alias: &RoomAliasId = room.try_into().expect("valid RoomAliasId");
self.resolve_alias(alias, servers).await
2024-06-23 02:57:32 +00:00
}
}
#[tracing::instrument(skip(self), name = "resolve")]
pub async fn resolve_alias(
&self,
room_alias: &RoomAliasId,
servers: Option<Vec<OwnedServerName>>,
) -> Result<(OwnedRoomId, Vec<OwnedServerName>)> {
let server_name = room_alias.server_name();
let server_is_ours = self.services.globals.server_is_ours(server_name);
let servers_contains_ours = || {
servers
.as_ref()
2025-01-25 23:41:39 +00:00
.is_some_and(|servers| servers.contains(&self.services.server.name))
};
if !server_is_ours && !servers_contains_ours() {
return self
.remote_resolve(room_alias, servers.unwrap_or_default())
.await;
}
let room_id = match self.resolve_local_alias(room_alias).await {
| Ok(r) => Some(r),
| Err(_) => self.resolve_appservice_alias(room_alias).await?,
};
room_id.map_or_else(
|| Err!(Request(NotFound("Room with alias not found."))),
|room_id| Ok((room_id, Vec::new())),
)
}
2024-07-07 19:03:15 +00:00
#[tracing::instrument(skip(self), level = "debug")]
2024-08-08 17:18:30 +00:00
pub async fn resolve_local_alias(&self, alias: &RoomAliasId) -> Result<OwnedRoomId> {
self.db.alias_roomid.get(alias.alias()).await.deserialized()
2020-05-25 23:24:13 +02:00
}
2024-03-05 19:48:54 -05:00
2024-07-07 19:03:15 +00:00
#[tracing::instrument(skip(self), level = "debug")]
pub fn local_aliases_for_room<'a>(
&'a self,
room_id: &'a RoomId,
) -> impl Stream<Item = &'a RoomAliasId> + Send + 'a {
2024-08-08 17:18:30 +00:00
let prefix = (room_id, Interfix);
self.db
.aliasid_alias
.stream_prefix(&prefix)
.ignore_err()
2024-10-04 17:17:10 +00:00
.map(|(_, alias): (Ignore, &RoomAliasId)| alias)
2020-05-25 23:24:13 +02:00
}
2024-03-05 19:48:54 -05:00
2024-07-07 19:03:15 +00:00
#[tracing::instrument(skip(self), level = "debug")]
pub fn all_local_aliases<'a>(
&'a self,
) -> impl Stream<Item = (&'a RoomId, &'a str)> + Send + 'a {
2024-08-08 17:18:30 +00:00
self.db
.alias_roomid
.stream()
.ignore_err()
.map(|(alias_localpart, room_id): (&str, &RoomId)| (room_id, alias_localpart))
2023-10-03 20:42:31 -07:00
}
2024-06-12 01:42:39 -04:00
async fn user_can_remove_alias(&self, alias: &RoomAliasId, user_id: &UserId) -> Result<bool> {
2024-08-08 17:18:30 +00:00
let room_id = self
.resolve_local_alias(alias)
.await
.map_err(|_| err!(Request(NotFound("Alias not found."))))?;
2024-06-12 01:42:39 -04:00
2024-07-18 06:37:47 +00:00
let server_user = &self.services.globals.server_user;
2024-06-12 01:42:39 -04:00
// The creator of an alias can remove it
if self
2024-08-08 17:18:30 +00:00
.who_created_alias(alias).await
.is_ok_and(|user| user == user_id)
2024-06-12 01:42:39 -04:00
// Server admins can remove any local alias
2024-08-08 17:18:30 +00:00
|| self.services.admin.user_is_admin(user_id).await
2024-06-12 01:42:39 -04:00
// Always allow the server service account to remove the alias, since there may not be an admin room
|| server_user == user_id
{
return Ok(true);
}
// Checking whether the user is able to change canonical aliases of the room
2024-12-18 03:32:58 +00:00
if let Ok(power_levels) = self
2024-08-08 17:18:30 +00:00
.services
.state_accessor
.room_state_get_content::<RoomPowerLevelsEventContent>(
&room_id,
&StateEventType::RoomPowerLevels,
"",
)
2024-12-18 03:32:58 +00:00
.map_ok(RoomPowerLevels::from)
2024-08-08 17:18:30 +00:00
.await
2024-06-12 01:42:39 -04:00
{
2024-12-18 03:32:58 +00:00
return Ok(
power_levels.user_can_send_state(user_id, StateEventType::RoomCanonicalAlias)
);
}
2024-06-12 01:42:39 -04:00
// If there is no power levels event, only the room creator can change
// canonical aliases
if let Ok(event) = self
2024-08-08 17:18:30 +00:00
.services
.state_accessor
.room_state_get(&room_id, &StateEventType::RoomCreate, "")
.await
2024-06-12 01:42:39 -04:00
{
return Ok(event.sender() == user_id);
2024-06-12 01:42:39 -04:00
}
Err!(Database("Room has no m.room.create event"))
2024-06-12 01:42:39 -04:00
}
2024-08-08 17:18:30 +00:00
async fn who_created_alias(&self, alias: &RoomAliasId) -> Result<OwnedUserId> {
self.db.alias_userid.get(alias.alias()).await.deserialized()
2024-08-08 17:18:30 +00:00
}
async fn resolve_appservice_alias(
&self,
room_alias: &RoomAliasId,
) -> Result<Option<OwnedRoomId>> {
2024-07-18 06:37:47 +00:00
use ruma::api::appservice::query::query_room_alias;
for appservice in self.services.appservice.read().await.values() {
if appservice.aliases.is_match(room_alias.as_str())
&& matches!(
2024-07-18 06:37:47 +00:00
self.services
.sending
.send_appservice_request(
appservice.registration.clone(),
query_room_alias::v1::Request { room_alias: room_alias.to_owned() },
)
.await,
Ok(Some(_opt_result))
) {
2024-08-08 17:18:30 +00:00
return self
.resolve_local_alias(room_alias)
.await
.map_err(|_| err!(Request(NotFound("Room does not exist."))))
.map(Some);
}
}
Ok(None)
}
2024-07-18 06:37:47 +00:00
pub async fn appservice_checks(
&self,
room_alias: &RoomAliasId,
appservice_info: &Option<RegistrationInfo>,
2024-07-18 06:37:47 +00:00
) -> Result<()> {
2024-07-22 07:43:51 +00:00
if !self
.services
.globals
.server_is_ours(room_alias.server_name())
{
2024-12-18 03:32:58 +00:00
return Err!(Request(InvalidParam("Alias is from another server.")));
2024-07-18 06:37:47 +00:00
}
2024-12-18 03:32:58 +00:00
if let Some(info) = appservice_info {
2024-07-18 06:37:47 +00:00
if !info.aliases.is_match(room_alias.as_str()) {
2024-12-18 03:32:58 +00:00
return Err!(Request(Exclusive("Room alias is not in namespace.")));
2024-07-18 06:37:47 +00:00
}
} else if self
.services
.appservice
.is_exclusive_alias(room_alias)
.await
{
2024-12-18 03:32:58 +00:00
return Err!(Request(Exclusive("Room alias reserved by appservice.")));
}
2024-07-18 06:37:47 +00:00
Ok(())
}
}