From 910d166b5dfdd976cc7c701eae217ad77201b0f3 Mon Sep 17 00:00:00 2001 From: tezlm Date: Tue, 3 Oct 2023 20:42:31 -0700 Subject: [PATCH] Admin room alias commands - room alias set - room alias remove - room alias which - room alias list --- src/database/key_value/rooms/alias.rs | 16 ++++ src/service/admin/mod.rs | 130 ++++++++++++++++++++++++++ src/service/rooms/alias/data.rs | 5 + src/service/rooms/alias/mod.rs | 7 ++ 4 files changed, 158 insertions(+) diff --git a/src/database/key_value/rooms/alias.rs b/src/database/key_value/rooms/alias.rs index 6f230323..9ce35a8b 100644 --- a/src/database/key_value/rooms/alias.rs +++ b/src/database/key_value/rooms/alias.rs @@ -57,4 +57,20 @@ impl service::rooms::alias::Data for KeyValueDatabase { .map_err(|_| Error::bad_database("Invalid alias in aliasid_alias.")) })) } + + fn all_local_aliases<'a>( + &'a self, + ) -> Box> + 'a> { + Box::new(self.alias_roomid.iter().map(|(room_alias_bytes, room_id_bytes)| { + let room_alias_localpart = utils::string_from_bytes(&room_alias_bytes) + .map_err(|_| Error::bad_database("Invalid alias bytes in aliasid_alias."))?; + + let room_id = utils::string_from_bytes(&room_id_bytes) + .map_err(|_| Error::bad_database("Invalid room_id bytes in aliasid_alias."))? + .try_into() + .map_err(|_| Error::bad_database("Invalid room_id in aliasid_alias."))?; + + Ok((room_id, room_alias_localpart)) + })) + } } diff --git a/src/service/admin/mod.rs b/src/service/admin/mod.rs index 49aae9b9..b89e8ea5 100644 --- a/src/service/admin/mod.rs +++ b/src/service/admin/mod.rs @@ -40,6 +40,7 @@ use super::pdu::PduBuilder; #[cfg_attr(test, derive(Debug))] #[derive(Parser)] #[command(name = "@conduit:server.name:", version = env!("CARGO_PKG_VERSION"))] +// TODO: bikeshedding - should command names be singular or plural enum AdminCommand { #[command(subcommand)] /// Commands for managing appservices @@ -168,6 +169,45 @@ enum UserCommand { enum RoomCommand { /// List all rooms the server knows about List, + + #[command(subcommand)] + /// Manage rooms' aliases + Alias(RoomAliasCommand), +} + +#[cfg_attr(test, derive(Debug))] +#[derive(Subcommand)] +enum RoomAliasCommand { + /// Make an alias point to a room. + Set { + #[arg(short, long)] + /// Set the alias even if a room is already using it + force: bool, + + // The room id to set the alias on + room_id: Box, + + // The alias localpart to use (`alias`, not `#alias:servername.tld`) + room_alias_localpart: Box, + }, + + /// Remove an alias + Remove { + /// The alias localpart to remove (`alias`, not `#alias:servername.tld`) + room_alias_localpart: Box, + }, + + /// Show which room is using an alias + Which { + /// The alias localpart to look up (`alias`, not `#alias:servername.tld`) + room_alias_localpart: Box, + }, + + /// List aliases currently being used + List { + /// If set, only list the aliases for this room + room_id: Option>, + }, } #[cfg_attr(test, derive(Debug))] @@ -709,6 +749,96 @@ impl Service { ); RoomMessageEventContent::text_plain(output) } + // TODO: clean up and deduplicate code + RoomCommand::Alias(command) => { + match command { + RoomAliasCommand::Set { ref room_alias_localpart, .. } | RoomAliasCommand::Remove { ref room_alias_localpart } | RoomAliasCommand::Which { ref room_alias_localpart } => { + let room_alias_str = format!("#{}:{}", room_alias_localpart, services().globals.server_name()); + let room_alias = match RoomAliasId::parse_box(room_alias_str) { + Ok(alias) => alias, + Err(err) => return Ok(RoomMessageEventContent::text_plain(format!("Failed to parse alias: {}", err))), + }; + + match command { + RoomAliasCommand::Set { force, room_id, .. } => { + match (force, services().rooms.alias.resolve_local_alias(&room_alias)) { + (true, Ok(Some(id))) => match services().rooms.alias.set_alias(&room_alias, &room_id) { + Ok(()) => RoomMessageEventContent::text_plain(format!("Successfully overwrote alias (formerly {})", id)), + Err(err) => RoomMessageEventContent::text_plain(format!("Failed to remove alias: {}", err)), + } + (false, Ok(Some(id))) => { + RoomMessageEventContent::text_plain(format!("Refusing to overwrite in use alias for {}, use -f or --force to overwrite", id)) + } + (_, Ok(None)) => match services().rooms.alias.set_alias(&room_alias, &room_id) { + Ok(()) => RoomMessageEventContent::text_plain("Successfully set alias"), + Err(err) => RoomMessageEventContent::text_plain(format!("Failed to remove alias: {}", err)), + } + (_, Err(err)) => RoomMessageEventContent::text_plain(format!("Unable to lookup alias: {}", err)), + } + }, + RoomAliasCommand::Remove { .. } => { + match services().rooms.alias.resolve_local_alias(&room_alias) { + Ok(Some(id)) => match services().rooms.alias.remove_alias(&room_alias) { + Ok(()) => RoomMessageEventContent::text_plain(format!("Removed alias from {}", id)), + Err(err) => RoomMessageEventContent::text_plain(format!("Failed to remove alias: {}", err)), + } + Ok(None) => RoomMessageEventContent::text_plain("Alias isn't in use."), + Err(err) => RoomMessageEventContent::text_plain(format!("Unable to lookup alias: {}", err)), + } + }, + RoomAliasCommand::Which { .. } => { + match services().rooms.alias.resolve_local_alias(&room_alias) { + Ok(Some(id)) => RoomMessageEventContent::text_plain(format!("Alias resolves to {}", id)), + Ok(None) => RoomMessageEventContent::text_plain("Alias isn't in use."), + Err(err) => RoomMessageEventContent::text_plain(&format!("Unable to lookup alias: {}", err)), + } + }, + RoomAliasCommand::List { .. } => unreachable!(), + } + } + RoomAliasCommand::List { room_id } => match room_id { + Some(room_id) => { + let aliases: Result, _> = services().rooms.alias.local_aliases_for_room(&room_id).collect(); + match aliases { + Ok(aliases) => { + let plain_list: String = aliases.iter() + .map(|alias| format!("- {}\n", alias)) + .collect(); + + let html_list: String = aliases.iter() + .map(|alias| format!("
  • {}
  • \n", escape_html(&alias.to_string()))) + .collect(); + + let plain = format!("Aliases for {}:\n{}", room_id, plain_list); + let html = format!("Aliases for {}:\n
      {}
    ", room_id, html_list); + RoomMessageEventContent::text_html(plain, html) + }, + Err(err) => RoomMessageEventContent::text_plain(&format!("Unable to list aliases: {}", err)), + } + } + None => { + let aliases: Result, _> = services().rooms.alias.all_local_aliases().collect(); + match aliases { + Ok(aliases) => { + let server_name = services().globals.server_name(); + let plain_list: String = aliases.iter() + .map(|(id, alias)| format!("- #{}:{} -> {}\n", alias, server_name, id)) + .collect(); + + let html_list: String = aliases.iter() + .map(|(id, alias)| format!("
  • #{}:{} -> {}
  • \n", escape_html(&alias.to_string()), server_name, escape_html(&id.to_string()))) + .collect(); + + let plain = format!("Aliases:\n{}", plain_list); + let html = format!("Aliases:\n
      {}
    ", html_list); + RoomMessageEventContent::text_html(plain, html) + }, + Err(err) => RoomMessageEventContent::text_plain(&format!("Unable to list aliases: {}", err)), + } + } + } + } + } } AdminCommand::Federation(command) => match command { FederationCommand::DisableRoom { room_id } => { diff --git a/src/service/rooms/alias/data.rs b/src/service/rooms/alias/data.rs index 629b1ee1..f6b6daaf 100644 --- a/src/service/rooms/alias/data.rs +++ b/src/service/rooms/alias/data.rs @@ -16,4 +16,9 @@ pub trait Data: Send + Sync { &'a self, room_id: &RoomId, ) -> Box> + 'a>; + + /// Returns all local aliases on the server + fn all_local_aliases<'a>( + &'a self, + ) -> Box> + 'a>; } diff --git a/src/service/rooms/alias/mod.rs b/src/service/rooms/alias/mod.rs index d26030c0..34a5732b 100644 --- a/src/service/rooms/alias/mod.rs +++ b/src/service/rooms/alias/mod.rs @@ -32,4 +32,11 @@ impl Service { ) -> Box> + 'a> { self.db.local_aliases_for_room(room_id) } + + #[tracing::instrument(skip(self))] + pub fn all_local_aliases<'a>( + &'a self, + ) -> Box> + 'a> { + self.db.all_local_aliases() + } }