always disable fed, evict admins, and forget the room when banning a room

Signed-off-by: June Clementine Strawberry <june@3.dog>
This commit is contained in:
June Clementine Strawberry 2025-03-08 15:48:23 -05:00
parent ef96e7afac
commit 5efe804a20
No known key found for this signature in database
4 changed files with 109 additions and 259 deletions

View file

@ -1,7 +1,7 @@
use api::client::leave_room;
use clap::Subcommand;
use conduwuit::{
Result, debug, error, info,
Result, debug,
utils::{IterStream, ReadyExt},
warn,
};
@ -17,51 +17,23 @@ use crate::{admin_command, admin_command_dispatch, get_room_info};
#[derive(Debug, Subcommand)]
pub(crate) enum RoomModerationCommand {
/// - Bans a room from local users joining and evicts all our local users
/// (including server
/// admins)
/// from the room. Also blocks any invites (local and remote) for the
/// banned room.
///
/// Server admins (users in the conduwuit admin room) will not be evicted
/// and server admins can still join the room. To evict admins too, use
/// --force (also ignores errors) To disable incoming federation of the
/// room, use --disable-federation
/// banned room, and disables federation entirely with it.
BanRoom {
#[arg(short, long)]
/// Evicts admins out of the room and ignores any potential errors when
/// making our local users leave the room
force: bool,
#[arg(long)]
/// Disables incoming federation of the room after banning and evicting
/// users
disable_federation: bool,
/// The room in the format of `!roomid:example.com` or a room alias in
/// the format of `#roomalias:example.com`
room: Box<RoomOrAliasId>,
},
/// - Bans a list of rooms (room IDs and room aliases) from a newline
/// delimited codeblock similar to `user deactivate-all`
BanListOfRooms {
#[arg(short, long)]
/// Evicts admins out of the room and ignores any potential errors when
/// making our local users leave the room
force: bool,
#[arg(long)]
/// Disables incoming federation of the room after banning and evicting
/// users
disable_federation: bool,
},
/// delimited codeblock similar to `user deactivate-all`. Applies the same
/// steps as ban-room
BanListOfRooms,
/// - Unbans a room to allow local users to join again
///
/// To re-enable incoming federation of the room, use --enable-federation
UnbanRoom {
#[arg(long)]
/// Enables incoming federation of the room after unbanning
enable_federation: bool,
/// The room in the format of `!roomid:example.com` or a room alias in
/// the format of `#roomalias:example.com`
room: Box<RoomOrAliasId>,
@ -77,12 +49,7 @@ pub(crate) enum RoomModerationCommand {
}
#[admin_command]
async fn ban_room(
&self,
force: bool,
disable_federation: bool,
room: Box<RoomOrAliasId>,
) -> Result<RoomMessageEventContent> {
async fn ban_room(&self, room: Box<RoomOrAliasId>) -> Result<RoomMessageEventContent> {
debug!("Got room alias or ID: {}", room);
let admin_room_alias = &self.services.globals.admin_alias;
@ -175,98 +142,56 @@ async fn ban_room(
));
};
debug!("Making all users leave the room {}", &room);
if force {
let mut users = self
.services
.rooms
.state_cache
.room_members(&room_id)
.ready_filter(|user| self.services.globals.user_is_local(user))
.boxed();
debug!("Making all users leave the room {room_id} and forgetting it");
let mut users = self
.services
.rooms
.state_cache
.room_members(&room_id)
.map(ToOwned::to_owned)
.ready_filter(|user| self.services.globals.user_is_local(user))
.boxed();
while let Some(local_user) = users.next().await {
debug!(
"Attempting leave for user {local_user} in room {room_id} (forced, ignoring all \
errors, evicting admins too)",
);
while let Some(ref user_id) = users.next().await {
debug!(
"Attempting leave for user {user_id} in room {room_id} (ignoring all errors, \
evicting admins too)",
);
if let Err(e) = leave_room(self.services, local_user, &room_id, None).await {
warn!(%e, "Failed to leave room");
}
if let Err(e) = leave_room(self.services, user_id, &room_id, None).await {
warn!("Failed to leave room: {e}");
}
} else {
let mut users = self
.services
.rooms
.state_cache
.room_members(&room_id)
.ready_filter(|user| self.services.globals.user_is_local(user))
.boxed();
while let Some(local_user) = users.next().await {
if self.services.users.is_admin(local_user).await {
continue;
}
debug!("Attempting leave for user {} in room {}", &local_user, &room_id);
if let Err(e) = leave_room(self.services, local_user, &room_id, None).await {
error!(
"Error attempting to make local user {} leave room {} during room banning: \
{}",
&local_user, &room_id, e
);
return Ok(RoomMessageEventContent::text_plain(format!(
"Error attempting to make local user {} leave room {} during room banning \
(room is still banned but not removing any more users): {}\nIf you would \
like to ignore errors, use --force",
&local_user, &room_id, e
)));
}
}
self.services.rooms.state_cache.forget(&room_id, user_id);
}
// remove any local aliases, ignore errors
for local_alias in &self
.services
self.services
.rooms
.alias
.local_aliases_for_room(&room_id)
.map(ToOwned::to_owned)
.collect::<Vec<_>>()
.await
{
_ = self
.services
.rooms
.alias
.remove_alias(local_alias, &self.services.globals.server_user)
.await;
}
.for_each(|local_alias| async move {
self.services
.rooms
.alias
.remove_alias(&local_alias, &self.services.globals.server_user)
.await
.ok();
})
.await;
// unpublish from room directory, ignore errors
// unpublish from room directory
self.services.rooms.directory.set_not_public(&room_id);
if disable_federation {
self.services.rooms.metadata.disable_room(&room_id, true);
return Ok(RoomMessageEventContent::text_plain(
"Room banned, removed all our local users, and disabled incoming federation with \
room.",
));
}
self.services.rooms.metadata.disable_room(&room_id, true);
Ok(RoomMessageEventContent::text_plain(
"Room banned and removed all our local users, use `!admin federation disable-room` to \
stop receiving new inbound federation events as well if needed.",
"Room banned, removed all our local users, and disabled incoming federation with room.",
))
}
#[admin_command]
async fn ban_list_of_rooms(
&self,
force: bool,
disable_federation: bool,
) -> Result<RoomMessageEventContent> {
async fn ban_list_of_rooms(&self) -> Result<RoomMessageEventContent> {
if self.body.len() < 2
|| !self.body[0].trim().starts_with("```")
|| self.body.last().unwrap_or(&"").trim() != "```"
@ -293,7 +218,7 @@ async fn ban_list_of_rooms(
if let Ok(admin_room_id) = self.services.admin.get_admin_room().await {
if room.to_owned().eq(&admin_room_id) || room.to_owned().eq(admin_room_alias)
{
info!("User specified admin room in bulk ban list, ignoring");
warn!("User specified admin room in bulk ban list, ignoring");
continue;
}
}
@ -302,19 +227,12 @@ async fn ban_list_of_rooms(
let room_id = match RoomId::parse(room_alias_or_id) {
| Ok(room_id) => room_id,
| Err(e) => {
if force {
// ignore rooms we failed to parse if we're force banning
warn!(
"Error parsing room \"{room}\" during bulk room banning, \
ignoring error and logging here: {e}"
);
continue;
}
return Ok(RoomMessageEventContent::text_plain(format!(
"{room} is not a valid room ID or room alias, please fix the \
list and try again: {e}"
)));
// ignore rooms we failed to parse
warn!(
"Error parsing room \"{room}\" during bulk room banning, \
ignoring error and logging here: {e}"
);
continue;
},
};
@ -355,21 +273,11 @@ async fn ban_list_of_rooms(
room_id
},
| Err(e) => {
// don't fail if force blocking
if force {
warn!(
"Failed to resolve room alias {room} to a \
room ID: {e}"
);
continue;
}
return Ok(RoomMessageEventContent::text_plain(
format!(
"Failed to resolve room alias {room} to a \
room ID: {e}"
),
));
warn!(
"Failed to resolve room alias {room} to a room \
ID: {e}"
);
continue;
},
}
},
@ -378,37 +286,21 @@ async fn ban_list_of_rooms(
room_ids.push(room_id);
},
| Err(e) => {
if force {
// ignore rooms we failed to parse if we're force deleting
error!(
"Error parsing room \"{room}\" during bulk room banning, \
ignoring error and logging here: {e}"
);
continue;
}
return Ok(RoomMessageEventContent::text_plain(format!(
"{room} is not a valid room ID or room alias, please fix the \
list and try again: {e}"
)));
warn!(
"Error parsing room \"{room}\" during bulk room banning, \
ignoring error and logging here: {e}"
);
continue;
},
}
}
},
| Err(e) => {
if force {
// ignore rooms we failed to parse if we're force deleting
error!(
"Error parsing room \"{room}\" during bulk room banning, ignoring error \
and logging here: {e}"
);
continue;
}
return Ok(RoomMessageEventContent::text_plain(format!(
"{room} is not a valid room ID or room alias, please fix the list and try \
again: {e}"
)));
warn!(
"Error parsing room \"{room}\" during bulk room banning, ignoring error and \
logging here: {e}"
);
continue;
},
}
}
@ -419,56 +311,27 @@ async fn ban_list_of_rooms(
debug!("Banned {room_id} successfully");
room_ban_count = room_ban_count.saturating_add(1);
debug!("Making all users leave the room {}", &room_id);
if force {
let mut users = self
.services
.rooms
.state_cache
.room_members(&room_id)
.ready_filter(|user| self.services.globals.user_is_local(user))
.boxed();
debug!("Making all users leave the room {room_id} and forgetting it");
let mut users = self
.services
.rooms
.state_cache
.room_members(&room_id)
.map(ToOwned::to_owned)
.ready_filter(|user| self.services.globals.user_is_local(user))
.boxed();
while let Some(local_user) = users.next().await {
debug!(
"Attempting leave for user {local_user} in room {room_id} (forced, ignoring \
all errors, evicting admins too)",
);
while let Some(ref user_id) = users.next().await {
debug!(
"Attempting leave for user {user_id} in room {room_id} (ignoring all errors, \
evicting admins too)",
);
if let Err(e) = leave_room(self.services, local_user, &room_id, None).await {
warn!(%e, "Failed to leave room");
}
if let Err(e) = leave_room(self.services, user_id, &room_id, None).await {
warn!("Failed to leave room: {e}");
}
} else {
let mut users = self
.services
.rooms
.state_cache
.room_members(&room_id)
.ready_filter(|user| self.services.globals.user_is_local(user))
.boxed();
while let Some(local_user) = users.next().await {
if self.services.users.is_admin(local_user).await {
continue;
}
debug!("Attempting leave for user {local_user} in room {room_id}");
if let Err(e) = leave_room(self.services, local_user, &room_id, None).await {
error!(
"Error attempting to make local user {local_user} leave room {room_id} \
during bulk room banning: {e}",
);
return Ok(RoomMessageEventContent::text_plain(format!(
"Error attempting to make local user {} leave room {} during room \
banning (room is still banned but not removing any more users and not \
banning any more rooms): {}\nIf you would like to ignore errors, use \
--force",
&local_user, &room_id, e
)));
}
}
self.services.rooms.state_cache.forget(&room_id, user_id);
}
// remove any local aliases, ignore errors
@ -490,29 +353,17 @@ async fn ban_list_of_rooms(
// unpublish from room directory, ignore errors
self.services.rooms.directory.set_not_public(&room_id);
if disable_federation {
self.services.rooms.metadata.disable_room(&room_id, true);
}
self.services.rooms.metadata.disable_room(&room_id, true);
}
if disable_federation {
Ok(RoomMessageEventContent::text_plain(format!(
"Finished bulk room ban, banned {room_ban_count} total rooms, evicted all users, \
and disabled incoming federation with the room."
)))
} else {
Ok(RoomMessageEventContent::text_plain(format!(
"Finished bulk room ban, banned {room_ban_count} total rooms and evicted all users."
)))
}
Ok(RoomMessageEventContent::text_plain(format!(
"Finished bulk room ban, banned {room_ban_count} total rooms, evicted all users, and \
disabled incoming federation with the room."
)))
}
#[admin_command]
async fn unban_room(
&self,
enable_federation: bool,
room: Box<RoomOrAliasId>,
) -> Result<RoomMessageEventContent> {
async fn unban_room(&self, room: Box<RoomOrAliasId>) -> Result<RoomMessageEventContent> {
let room_id = if room.is_room_id() {
let room_id = match RoomId::parse(&room) {
| Ok(room_id) => room_id,
@ -595,15 +446,8 @@ async fn unban_room(
));
};
if enable_federation {
self.services.rooms.metadata.disable_room(&room_id, false);
return Ok(RoomMessageEventContent::text_plain("Room unbanned."));
}
Ok(RoomMessageEventContent::text_plain(
"Room unbanned, you may need to re-enable federation with the room using enable-room if \
this is a remote room to make it fully functional.",
))
self.services.rooms.metadata.disable_room(&room_id, false);
Ok(RoomMessageEventContent::text_plain("Room unbanned and federation re-enabled."))
}
#[admin_command]