From af33e4053977ef19148e4d1b0bdeb0dbff246d7a Mon Sep 17 00:00:00 2001 From: nexy7574 Date: Wed, 30 Jul 2025 19:19:32 +0100 Subject: [PATCH 1/4] feat: Force leave remote rooms admin command --- src/admin/user/commands.rs | 30 ++++++++++++++++++++++++++++-- src/admin/user/mod.rs | 6 ++++++ src/api/client/membership/leave.rs | 2 +- src/api/client/membership/mod.rs | 2 +- 4 files changed, 36 insertions(+), 4 deletions(-) diff --git a/src/admin/user/commands.rs b/src/admin/user/commands.rs index 56864a32..37ab030c 100644 --- a/src/admin/user/commands.rs +++ b/src/admin/user/commands.rs @@ -1,8 +1,8 @@ use std::{collections::BTreeMap, fmt::Write as _}; use api::client::{ - full_user_deactivate, join_room_by_id_helper, leave_all_rooms, leave_room, update_avatar_url, - update_displayname, + full_user_deactivate, join_room_by_id_helper, leave_all_rooms, leave_room, remote_leave_room, + update_avatar_url, update_displayname, }; use conduwuit::{ Err, Result, debug, debug_warn, error, info, is_equal_to, @@ -926,3 +926,29 @@ pub(super) async fn redact_event(&self, event_id: OwnedEventId) -> Result { )) .await } + +#[admin_command] +pub(super) async fn force_leave_remote_room( + &self, + user_id: String, + room_id: OwnedRoomOrAliasId, +) -> Result { + let user_id = parse_local_user_id(self.services, &user_id)?; + let (room_id, _) = self + .services + .rooms + .alias + .resolve_with_servers(&room_id, None) + .await?; + + assert!( + self.services.globals.user_is_local(&user_id), + "Parsed user_id must be a local user" + ); + remote_leave_room(self.services, &user_id, &room_id, None) + .boxed() + .await?; + + self.write_str(&format!("{user_id} has been joined to {room_id}.",)) + .await +} diff --git a/src/admin/user/mod.rs b/src/admin/user/mod.rs index 656cacaf..366f7dd5 100644 --- a/src/admin/user/mod.rs +++ b/src/admin/user/mod.rs @@ -103,6 +103,12 @@ pub enum UserCommand { room_id: OwnedRoomOrAliasId, }, + /// - Manually leave a remote room for a local user. + ForceLeaveRemoteRoom { + user_id: String, + room_id: OwnedRoomOrAliasId, + }, + /// - Forces the specified user to drop their power levels to the room /// default, if their permissions allow and the auth check permits ForceDemote { diff --git a/src/api/client/membership/leave.rs b/src/api/client/membership/leave.rs index f4f1666b..0aadd833 100644 --- a/src/api/client/membership/leave.rs +++ b/src/api/client/membership/leave.rs @@ -215,7 +215,7 @@ pub async fn leave_room( Ok(()) } -async fn remote_leave_room( +pub async fn remote_leave_room( services: &Services, user_id: &UserId, room_id: &RoomId, diff --git a/src/api/client/membership/mod.rs b/src/api/client/membership/mod.rs index 7a6f19ad..691419f6 100644 --- a/src/api/client/membership/mod.rs +++ b/src/api/client/membership/mod.rs @@ -29,7 +29,7 @@ pub(crate) use self::{ }; pub use self::{ join::join_room_by_id_helper, - leave::{leave_all_rooms, leave_room}, + leave::{leave_all_rooms, leave_room, remote_leave_room}, }; use crate::{Ruma, client::full_user_deactivate}; From 5f35ebbce0ecd12c7ea7312bf5567a4fbe2b00d4 Mon Sep 17 00:00:00 2001 From: nexy7574 Date: Wed, 30 Jul 2025 19:29:33 +0100 Subject: [PATCH 2/4] fix: Make remote leave helper a public fn --- src/api/client/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/api/client/mod.rs b/src/api/client/mod.rs index e4be20b7..be335cba 100644 --- a/src/api/client/mod.rs +++ b/src/api/client/mod.rs @@ -55,7 +55,7 @@ pub(super) use keys::*; pub(super) use media::*; pub(super) use media_legacy::*; pub(super) use membership::*; -pub use membership::{join_room_by_id_helper, leave_all_rooms, leave_room}; +pub use membership::{join_room_by_id_helper, leave_all_rooms, leave_room, remote_leave_room}; pub(super) use message::*; pub(super) use openid::*; pub(super) use presence::*; From e903390b980397a2af28a9ea0044693f25214402 Mon Sep 17 00:00:00 2001 From: nexy7574 Date: Thu, 31 Jul 2025 17:48:30 +0100 Subject: [PATCH 3/4] feat: Only inject vias when manual ones aren't provided during join --- src/api/client/membership/join.rs | 49 ++++++++++++++++--------------- 1 file changed, 26 insertions(+), 23 deletions(-) diff --git a/src/api/client/membership/join.rs b/src/api/client/membership/join.rs index dc170cbf..f3434bf5 100644 --- a/src/api/client/membership/join.rs +++ b/src/api/client/membership/join.rs @@ -156,31 +156,34 @@ pub(crate) async fn join_room_by_id_or_alias_route( .await?; let mut servers = body.via.clone(); - servers.extend( - services - .rooms - .state_cache - .servers_invite_via(&room_id) - .map(ToOwned::to_owned) - .collect::>() - .await, - ); + if servers.is_empty() { + debug!("No via servers provided for join, injecting some."); + servers.extend( + services + .rooms + .state_cache + .servers_invite_via(&room_id) + .map(ToOwned::to_owned) + .collect::>() + .await, + ); - servers.extend( - services - .rooms - .state_cache - .invite_state(sender_user, &room_id) - .await - .unwrap_or_default() - .iter() - .filter_map(|event| event.get_field("sender").ok().flatten()) - .filter_map(|sender: &str| UserId::parse(sender).ok()) - .map(|user| user.server_name().to_owned()), - ); + servers.extend( + services + .rooms + .state_cache + .invite_state(sender_user, &room_id) + .await + .unwrap_or_default() + .iter() + .filter_map(|event| event.get_field("sender").ok().flatten()) + .filter_map(|sender: &str| UserId::parse(sender).ok()) + .map(|user| user.server_name().to_owned()), + ); - if let Some(server) = room_id.server_name() { - servers.push(server.to_owned()); + if let Some(server) = room_id.server_name() { + servers.push(server.to_owned()); + } } servers.sort_unstable(); From 15d3e2c47639fbd0b3822c0b9507c1b6df20b7d1 Mon Sep 17 00:00:00 2001 From: nexy7574 Date: Sat, 30 Aug 2025 21:31:23 +0100 Subject: [PATCH 4/4] chore(PR956): Update admin docs --- docs/admin_reference.md | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/docs/admin_reference.md b/docs/admin_reference.md index 18e039e4..1c9d4fb0 100644 --- a/docs/admin_reference.md +++ b/docs/admin_reference.md @@ -21,6 +21,7 @@ This document contains the help content for the `admin` command-line program. * [`admin users list-joined-rooms`↴](#admin-users-list-joined-rooms) * [`admin users force-join-room`↴](#admin-users-force-join-room) * [`admin users force-leave-room`↴](#admin-users-force-leave-room) +* [`admin users force-leave-remote-room`↴](#admin-users-force-leave-remote-room) * [`admin users force-demote`↴](#admin-users-force-demote) * [`admin users make-user-admin`↴](#admin-users-make-user-admin) * [`admin users put-room-tag`↴](#admin-users-put-room-tag) @@ -295,6 +296,7 @@ You can find the ID using the `list-appservices` command. * `list-joined-rooms` — - Lists all the rooms (local and remote) that the specified user is joined in * `force-join-room` — - Manually join a local user to a room * `force-leave-room` — - Manually leave a local user from a room +* `force-leave-remote-room` — - Manually leave a remote room for a local user * `force-demote` — - Forces the specified user to drop their power levels to the room default, if their permissions allow and the auth check permits * `make-user-admin` — - Grant server-admin privileges to a user * `put-room-tag` — - Puts a room tag for the specified user and room ID @@ -449,6 +451,19 @@ Reverses the effects of the `suspend` command, allowing the user to send message +## `admin users force-leave-remote-room` + +- Manually leave a remote room for a local user + +**Usage:** `admin users force-leave-remote-room ` + +###### **Arguments:** + +* `` +* `` + + + ## `admin users force-demote` - Forces the specified user to drop their power levels to the room default, if their permissions allow and the auth check permits