From ebb94341c8796fdd6467454e1e72d4742f3ae0e9 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 | 24 +++ src/service/admin/mod.rs | 216 +++++++++++++++++++++++++- src/service/rooms/alias/data.rs | 5 + src/service/rooms/alias/mod.rs | 7 + 4 files changed, 245 insertions(+), 7 deletions(-) diff --git a/src/database/key_value/rooms/alias.rs b/src/database/key_value/rooms/alias.rs index 6f230323..0d3e2b10 100644 --- a/src/database/key_value/rooms/alias.rs +++ b/src/database/key_value/rooms/alias.rs @@ -57,4 +57,28 @@ 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 1a10cf4b..3ee2e5ea 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))] @@ -444,17 +484,19 @@ impl Service { "Failed to unregister appservice: {e}" )), }, - AppserviceCommand::Show { appservice_identifier } => { + AppserviceCommand::Show { + appservice_identifier, + } => { match services() .appservice - .get_registration(&appservice_identifier) { + .get_registration(&appservice_identifier) + { Ok(Some(config)) => { let config_str = serde_yaml::to_string(&config) .expect("config should've been validated on register"); let output = format!( "Config for {}:\n\n```yaml\n{}\n```", - appservice_identifier, - config_str, + appservice_identifier, config_str, ); let output_html = format!( "Config for {}:\n\n
{}
", @@ -462,8 +504,10 @@ impl Service { escape_html(&config_str), ); RoomMessageEventContent::text_html(output, output_html) - }, - Ok(None) => RoomMessageEventContent::text_plain("Appservice does not exist."), + } + Ok(None) => { + RoomMessageEventContent::text_plain("Appservice does not exist.") + } Err(_) => RoomMessageEventContent::text_plain("Failed to get appservice."), } } @@ -711,6 +755,162 @@ 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 } => { @@ -1387,7 +1587,9 @@ impl Service { } fn escape_html(s: &str) -> String { - s.replace('&', "&").replace('<', "<").replace('>', ">") + s.replace('&', "&") + .replace('<', "<") + .replace('>', ">") } #[cfg(test)] diff --git a/src/service/rooms/alias/data.rs b/src/service/rooms/alias/data.rs index 629b1ee1..e2647ffc 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() + } }