From c71db93e225b44f15c652f4fbe0befaad508e48e Mon Sep 17 00:00:00 2001 From: strawberry Date: Mon, 28 Oct 2024 18:28:56 -0400 Subject: [PATCH] implement admin command to force join list of local users Signed-off-by: strawberry --- src/admin/user/commands.rs | 139 ++++++++++++++++++++++++++++++++++++- src/admin/user/mod.rs | 15 ++++ 2 files changed, 153 insertions(+), 1 deletion(-) diff --git a/src/admin/user/commands.rs b/src/admin/user/commands.rs index fb6d2bf1..d6946b4e 100644 --- a/src/admin/user/commands.rs +++ b/src/admin/user/commands.rs @@ -1,7 +1,11 @@ use std::{collections::BTreeMap, fmt::Write as _}; use api::client::{full_user_deactivate, join_room_by_id_helper, leave_room}; -use conduit::{error, info, is_equal_to, utils, warn, PduBuilder, Result}; +use conduit::{ + debug_warn, error, info, is_equal_to, + utils::{self, ReadyExt}, + warn, PduBuilder, Result, +}; use conduit_api::client::{leave_all_rooms, update_avatar_url, update_displayname}; use futures::StreamExt; use ruma::{ @@ -376,6 +380,139 @@ pub(super) async fn list_joined_rooms(&self, user_id: String) -> Result Result { + const REASON: &str = "Bulk force joining this room as initiated by the server admin."; + + if self.body.len() < 2 || !self.body[0].trim().starts_with("```") || self.body.last().unwrap_or(&"").trim() != "```" + { + return Ok(RoomMessageEventContent::text_plain( + "Expected code block in command body. Add --help for details.", + )); + } + + if !yes_i_want_to_do_this { + return Ok(RoomMessageEventContent::notice_markdown( + "You must pass the --yes-i-want-to-do-this-flag to ensure you really want to force bulk join all \ + specified local users.", + )); + } + + let Ok(admin_room) = self.services.admin.get_admin_room().await else { + return Ok(RoomMessageEventContent::notice_markdown( + "There is not an admin room to check for server admins.", + )); + }; + + let (room_id, servers) = self + .services + .rooms + .alias + .resolve_with_servers(&room_id, None) + .await?; + + if !self + .services + .rooms + .state_cache + .server_in_room(self.services.globals.server_name(), &room_id) + .await + { + return Ok(RoomMessageEventContent::notice_markdown("We are not joined in this room.")); + } + + let server_admins: Vec<_> = self + .services + .rooms + .state_cache + .active_local_users_in_room(&admin_room) + .map(ToOwned::to_owned) + .collect() + .await; + + if !self + .services + .rooms + .state_cache + .room_members(&room_id) + .ready_any(|user_id| server_admins.contains(&user_id.to_owned())) + .await + { + return Ok(RoomMessageEventContent::notice_markdown( + "There is not a single server admin in the room.", + )); + } + + let usernames = self + .body + .to_vec() + .drain(1..self.body.len().saturating_sub(1)) + .collect::>(); + + let mut user_ids: Vec = Vec::with_capacity(usernames.len()); + + for username in usernames { + match parse_active_local_user_id(self.services, username).await { + Ok(user_id) => { + // don't make the server service account join + if user_id == self.services.globals.server_user { + self.services + .admin + .send_message(RoomMessageEventContent::text_plain(format!( + "{username} is the server service account, skipping over" + ))) + .await + .ok(); + continue; + } + + user_ids.push(user_id); + }, + Err(e) => { + self.services + .admin + .send_message(RoomMessageEventContent::text_plain(format!( + "{username} is not a valid username, skipping over: {e}" + ))) + .await + .ok(); + continue; + }, + } + } + + let mut failed_joins: usize = 0; + let mut successful_joins: usize = 0; + + for user_id in user_ids { + match join_room_by_id_helper( + self.services, + &user_id, + &room_id, + Some(String::from(REASON)), + &servers, + None, + &None, + ) + .await + { + Ok(_res) => { + successful_joins = successful_joins.saturating_add(1); + }, + Err(e) => { + debug_warn!("Failed force joining {user_id} to {room_id} during bulk join: {e}"); + failed_joins = failed_joins.saturating_add(1); + }, + }; + } + + Ok(RoomMessageEventContent::notice_markdown(format!( + "{successful_joins} local users have been joined to {room_id}. {failed_joins} joins failed.", + ))) +} + #[admin_command] pub(super) async fn force_join_room( &self, user_id: String, room_id: OwnedRoomOrAliasId, diff --git a/src/admin/user/mod.rs b/src/admin/user/mod.rs index e7bb5c73..e1568269 100644 --- a/src/admin/user/mod.rs +++ b/src/admin/user/mod.rs @@ -124,4 +124,19 @@ pub(super) enum UserCommand { RedactEvent { event_id: Box, }, + + /// - Force joins a specified list of local users to join the specified + /// room. + /// + /// Specify a codeblock of usernames. + /// + /// At least 1 server admin must be in the room to prevent abuse. + /// + /// Requires the `--yes-i-want-to-do-this` flag. + ForceJoinListOfLocalUsers { + room_id: OwnedRoomOrAliasId, + + #[arg(long)] + yes_i_want_to_do_this: bool, + }, }