Files
continuwuity/src/admin/room/moderation.rs
T

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

471 lines
12 KiB
Rust
Raw Normal View History

use api::client::leave_room;
2024-07-27 00:11:41 +00:00
use clap::Subcommand;
2024-12-14 21:58:01 -05:00
use conduwuit::{
2025-07-01 23:14:41 +01:00
Err, Result, debug, info,
2024-08-08 17:18:30 +00:00
utils::{IterStream, ReadyExt},
warn,
2024-08-08 17:18:30 +00:00
};
2025-04-26 08:23:57 +00:00
use futures::{FutureExt, StreamExt};
use ruma::{OwnedRoomId, OwnedRoomOrAliasId, RoomAliasId, RoomId, RoomOrAliasId};
2024-03-22 03:37:55 -07:00
2024-07-27 00:11:41 +00:00
use crate::{admin_command, admin_command_dispatch, get_room_info};
#[admin_command_dispatch]
#[derive(Debug, Subcommand)]
2025-05-24 00:28:09 +01:00
pub enum RoomModerationCommand {
/// Bans a room from local users joining and evicts all our local users
/// (including server
/// admins)
2024-07-27 00:11:41 +00:00
/// from the room. Also blocks any invites (local and remote) for the
/// banned room, and disables federation entirely with it.
2024-07-27 00:11:41 +00:00
BanRoom {
/// The room in the format of `!roomid:example.com` or a room alias in
/// the format of `#roomalias:example.com`
room: OwnedRoomOrAliasId,
2024-07-27 00:11:41 +00:00
},
/// Bans a list of rooms (room IDs and room aliases) from a newline
/// delimited codeblock similar to `user deactivate-all`. Applies the same
/// steps as ban-room
BanListOfRooms,
2024-07-27 00:11:41 +00:00
/// Unbans a room to allow local users to join again
2024-07-27 00:11:41 +00:00
UnbanRoom {
/// The room in the format of `!roomid:example.com` or a room alias in
/// the format of `#roomalias:example.com`
room: OwnedRoomOrAliasId,
2024-07-27 00:11:41 +00:00
},
/// List of all rooms we have banned
ListBannedRooms {
#[arg(long)]
/// Whether to only output room IDs without supplementary room
/// information
no_details: bool,
},
2024-06-09 04:40:19 +00:00
}
2024-03-22 03:37:55 -07:00
2024-07-27 00:11:41 +00:00
#[admin_command]
async fn ban_room(&self, room: OwnedRoomOrAliasId) -> Result {
2024-06-09 04:40:19 +00:00
debug!("Got room alias or ID: {}", room);
2024-03-22 03:37:55 -07:00
2024-07-27 00:11:41 +00:00
let admin_room_alias = &self.services.globals.admin_alias;
2024-03-22 03:37:55 -07:00
2024-08-08 17:18:30 +00:00
if let Ok(admin_room_id) = self.services.admin.get_admin_room().await {
if room.to_string().eq(&admin_room_id) || room.to_string().eq(admin_room_alias) {
return Err!("Not allowed to ban the admin room.");
2024-06-09 04:40:19 +00:00
}
}
2024-03-22 03:37:55 -07:00
2024-06-09 04:40:19 +00:00
let room_id = if room.is_room_id() {
let room_id = match RoomId::parse(&room) {
| Ok(room_id) => room_id,
2025-03-02 23:15:05 -05:00
| Err(e) => {
return Err!(
"Failed to parse room ID {room}. Please note that this requires a full room \
ID (`!awIh6gGInaS5wLQJwa:example.com`) or a room alias \
(`#roomalias:example.com`): {e}"
);
2025-03-02 23:15:05 -05:00
},
2024-06-09 04:40:19 +00:00
};
2024-03-22 03:37:55 -07:00
2024-06-09 04:40:19 +00:00
debug!("Room specified is a room ID, banning room ID");
2024-03-22 03:37:55 -07:00
2024-12-28 23:31:24 +00:00
room_id.to_owned()
2024-06-09 04:40:19 +00:00
} else if room.is_room_alias_id() {
let room_alias = match RoomAliasId::parse(&room) {
| Ok(room_alias) => room_alias,
2025-03-02 23:15:05 -05:00
| Err(e) => {
return Err!(
"Failed to parse room ID {room}. Please note that this requires a full room \
ID (`!awIh6gGInaS5wLQJwa:example.com`) or a room alias \
(`#roomalias:example.com`): {e}"
);
2025-03-02 23:15:05 -05:00
},
2024-06-09 04:40:19 +00:00
};
debug!(
"Room specified is not a room ID, attempting to resolve room alias to a room ID \
locally, if not using get_alias_helper to fetch room ID remotely"
2024-06-09 04:40:19 +00:00
);
2025-07-01 23:14:41 +01:00
match self
2024-08-08 17:18:30 +00:00
.services
.rooms
.alias
2025-07-01 23:14:41 +01:00
.resolve_alias(room_alias, None)
2024-08-08 17:18:30 +00:00
.await
{
2025-07-01 23:14:41 +01:00
| Ok((room_id, servers)) => {
debug!(
2026-01-04 03:04:37 +00:00
%room_id,
2025-07-01 23:14:41 +01:00
?servers,
"Got federation response fetching room ID for room {room}"
);
2025-07-01 23:14:41 +01:00
room_id
},
2025-07-01 23:14:41 +01:00
| Err(e) => {
return Err!("Failed to resolve room alias {room} to a room ID: {e}");
},
}
2024-06-09 04:40:19 +00:00
} else {
return Err!(
"Room specified is not a room ID or room alias. Please note that this requires a \
full room ID (`!awIh6gGInaS5wLQJwa:example.com`) or a room alias \
(`#roomalias:example.com`)",
);
2024-06-09 04:40:19 +00:00
};
2025-07-01 23:14:41 +01:00
info!("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();
2024-08-08 17:18:30 +00:00
while let Some(ref user_id) = users.next().await {
2025-07-01 23:14:41 +01:00
info!(
"Attempting leave for user {user_id} in room {room_id} (ignoring all errors, \
evicting admins too)",
);
2024-06-09 04:40:19 +00:00
2025-04-26 08:23:57 +00:00
if let Err(e) = leave_room(self.services, user_id, &room_id, None)
.boxed()
.await
{
warn!("Failed to leave room: {e}");
2024-06-09 04:40:19 +00:00
}
2024-08-08 17:18:30 +00:00
self.services.rooms.state_cache.forget(&room_id, user_id);
2024-06-09 04:40:19 +00:00
}
2024-03-22 03:37:55 -07:00
self.services
.rooms
.alias
.local_aliases_for_room(&room_id)
2024-08-08 17:18:30 +00:00
.map(ToOwned::to_owned)
.for_each(|local_alias| async move {
self.services
.rooms
.alias
.remove_alias(&local_alias, &self.services.globals.server_user)
.await
.ok();
})
.await;
2025-07-01 23:14:41 +01:00
self.services.rooms.directory.set_not_public(&room_id); // remove from the room directory
self.services.rooms.metadata.ban_room(&room_id, true); // prevent further joins
self.services.rooms.metadata.disable_room(&room_id, true); // disable federation
2024-06-09 04:40:19 +00:00
self.write_str(
"Room banned, removed all our local users, and disabled incoming federation with room.",
)
.await
2024-06-09 04:40:19 +00:00
}
2024-03-22 03:37:55 -07:00
2024-07-27 00:11:41 +00:00
#[admin_command]
async fn ban_list_of_rooms(&self) -> Result {
if self.body.len() < 2
|| !self.body[0].trim().starts_with("```")
|| self.body.last().unwrap_or(&"").trim() != "```"
2024-07-27 00:11:41 +00:00
{
return Err!("Expected code block in command body. Add --help for details.",);
2024-06-10 02:57:11 -04:00
}
2024-03-22 03:37:55 -07:00
2024-07-27 00:11:41 +00:00
let rooms_s = self
.body
.to_vec()
.drain(1..self.body.len().saturating_sub(1))
2024-07-07 04:46:16 +00:00
.collect::<Vec<_>>();
2024-06-10 02:57:11 -04:00
2024-07-27 00:11:41 +00:00
let admin_room_alias = &self.services.globals.admin_alias;
2024-06-09 04:40:19 +00:00
2024-06-10 02:57:11 -04:00
let mut room_ban_count: usize = 0;
let mut room_ids: Vec<OwnedRoomId> = Vec::new();
2024-06-09 04:40:19 +00:00
2024-06-10 02:57:11 -04:00
for &room in &rooms_s {
match <&RoomOrAliasId>::try_from(room) {
| Ok(room_alias_or_id) => {
2024-08-08 17:18:30 +00:00
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)
{
warn!("User specified admin room in bulk ban list, ignoring");
2024-06-10 02:57:11 -04:00
continue;
2024-06-09 04:40:19 +00:00
}
2024-06-10 02:57:11 -04:00
}
2024-03-22 03:37:55 -07:00
2024-06-10 02:57:11 -04:00
if room_alias_or_id.is_room_id() {
let room_id = match RoomId::parse(room_alias_or_id) {
| Ok(room_id) => room_id,
| Err(e) => {
// ignore rooms we failed to parse
warn!(
"Error parsing room \"{room}\" during bulk room banning, \
ignoring error and logging here: {e}"
);
continue;
2024-06-10 02:57:11 -04:00
},
};
2024-12-28 23:31:24 +00:00
room_ids.push(room_id.to_owned());
2024-06-10 02:57:11 -04:00
}
if room_alias_or_id.is_room_alias_id() {
match RoomAliasId::parse(room_alias_or_id) {
| Ok(room_alias) => {
let room_id = match self
2024-08-08 17:18:30 +00:00
.services
.rooms
.alias
2024-12-28 23:31:24 +00:00
.resolve_local_alias(room_alias)
2024-08-08 17:18:30 +00:00
.await
{
| Ok(room_id) => room_id,
| _ => {
debug!(
"We don't have this room alias to a room ID locally, \
attempting to fetch room ID over federation"
);
match self
.services
.rooms
.alias
.resolve_alias(room_alias, None)
.await
{
| Ok((room_id, servers)) => {
debug!(
2026-01-04 03:04:37 +00:00
%room_id,
?servers,
"Got federation response fetching room ID for \
{room}",
);
room_id
},
| Err(e) => {
warn!(
"Failed to resolve room alias {room} to a room \
ID: {e}"
);
continue;
},
}
},
2024-08-08 17:18:30 +00:00
};
2024-06-10 02:57:11 -04:00
room_ids.push(room_id);
},
| Err(e) => {
warn!(
"Error parsing room \"{room}\" during bulk room banning, \
ignoring error and logging here: {e}"
);
continue;
2024-06-10 02:57:11 -04:00
},
2024-06-09 04:40:19 +00:00
}
2024-06-10 02:57:11 -04:00
}
},
| Err(e) => {
warn!(
"Error parsing room \"{room}\" during bulk room banning, ignoring error and \
logging here: {e}"
);
continue;
2024-06-10 02:57:11 -04:00
},
2024-06-09 04:40:19 +00:00
}
2024-06-10 02:57:11 -04:00
}
2024-06-09 04:40:19 +00:00
2024-06-10 02:57:11 -04:00
for room_id in room_ids {
2024-08-08 17:18:30 +00:00
debug!("Banned {room_id} successfully");
room_ban_count = room_ban_count.saturating_add(1);
2024-06-09 04:40:19 +00:00
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();
2024-08-08 17:18:30 +00:00
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)",
);
2024-08-08 17:18:30 +00:00
2025-04-26 08:23:57 +00:00
if let Err(e) = leave_room(self.services, user_id, &room_id, None)
.boxed()
.await
{
warn!("Failed to leave room: {e}");
2024-06-10 02:57:11 -04:00
}
2024-08-08 17:18:30 +00:00
self.services.rooms.state_cache.forget(&room_id, user_id);
2024-06-09 04:40:19 +00:00
}
// remove any local aliases, ignore errors
2024-08-08 17:18:30 +00:00
self.services
.rooms
.alias
.local_aliases_for_room(&room_id)
2024-08-08 17:18:30 +00:00
.map(ToOwned::to_owned)
.for_each(|local_alias| async move {
self.services
.rooms
.alias
.remove_alias(&local_alias, &self.services.globals.server_user)
.await
.ok();
})
.await;
2025-07-01 23:14:41 +01:00
self.services.rooms.metadata.ban_room(&room_id, true);
// unpublish from room directory, ignore errors
2024-08-08 17:18:30 +00:00
self.services.rooms.directory.set_not_public(&room_id);
self.services.rooms.metadata.disable_room(&room_id, true);
2024-06-09 04:40:19 +00:00
}
2024-03-22 03:37:55 -07:00
self.write_str(&format!(
"Finished bulk room ban, banned {room_ban_count} total rooms, evicted all users, and \
disabled incoming federation with the room."
))
.await
2024-06-09 04:40:19 +00:00
}
2024-07-27 00:11:41 +00:00
#[admin_command]
async fn unban_room(&self, room: OwnedRoomOrAliasId) -> Result {
2024-06-09 04:40:19 +00:00
let room_id = if room.is_room_id() {
let room_id = match RoomId::parse(&room) {
| Ok(room_id) => room_id,
2025-03-02 23:15:05 -05:00
| Err(e) => {
return Err!(
"Failed to parse room ID {room}. Please note that this requires a full room \
ID (`!awIh6gGInaS5wLQJwa:example.com`) or a room alias \
(`#roomalias:example.com`): {e}"
);
2025-03-02 23:15:05 -05:00
},
2024-06-09 04:40:19 +00:00
};
2024-06-09 04:40:19 +00:00
debug!("Room specified is a room ID, unbanning room ID");
2024-12-28 23:31:24 +00:00
self.services.rooms.metadata.ban_room(room_id, false);
2024-03-22 03:37:55 -07:00
2024-12-28 23:31:24 +00:00
room_id.to_owned()
2024-06-09 04:40:19 +00:00
} else if room.is_room_alias_id() {
let room_alias = match RoomAliasId::parse(&room) {
| Ok(room_alias) => room_alias,
2025-03-02 23:15:05 -05:00
| Err(e) => {
return Err!(
"Failed to parse room ID {room}. Please note that this requires a full room \
ID (`!awIh6gGInaS5wLQJwa:example.com`) or a room alias \
(`#roomalias:example.com`): {e}"
);
2025-03-02 23:15:05 -05:00
},
2024-06-09 04:40:19 +00:00
};
debug!(
"Room specified is not a room ID, attempting to resolve room alias to a room ID \
locally, if not using get_alias_helper to fetch room ID remotely"
2024-06-09 04:40:19 +00:00
);
let room_id = match self
2024-08-08 17:18:30 +00:00
.services
.rooms
.alias
2024-12-28 23:31:24 +00:00
.resolve_local_alias(room_alias)
2024-08-08 17:18:30 +00:00
.await
{
| Ok(room_id) => room_id,
| _ => {
debug!(
"We don't have this room alias to a room ID locally, attempting to fetch \
room ID over federation"
);
2024-06-09 04:40:19 +00:00
match self
.services
.rooms
.alias
.resolve_alias(room_alias, None)
.await
{
| Ok((room_id, servers)) => {
debug!(
2026-01-04 03:04:37 +00:00
%room_id,
?servers,
"Got federation response fetching room ID for room {room}"
);
room_id
},
| Err(e) => {
return Err!("Failed to resolve room alias {room} to a room ID: {e}");
},
}
},
2024-06-09 04:40:19 +00:00
};
2024-03-22 03:37:55 -07:00
2024-08-08 17:18:30 +00:00
self.services.rooms.metadata.ban_room(&room_id, false);
2024-03-22 03:37:55 -07:00
2024-06-09 04:40:19 +00:00
room_id
} else {
return Err!(
"Room specified is not a room ID or room alias. Please note that this requires a \
full room ID (`!awIh6gGInaS5wLQJwa:example.com`) or a room alias \
(`#roomalias:example.com`)",
);
2024-06-09 04:40:19 +00:00
};
2024-03-22 03:37:55 -07:00
self.services.rooms.metadata.disable_room(&room_id, false);
self.write_str("Room unbanned and federation re-enabled.")
.await
2024-06-09 04:40:19 +00:00
}
2024-03-22 03:37:55 -07:00
2024-07-27 00:11:41 +00:00
#[admin_command]
async fn list_banned_rooms(&self, no_details: bool) -> Result {
2024-10-01 02:47:39 +00:00
let room_ids: Vec<OwnedRoomId> = self
2024-07-27 00:11:41 +00:00
.services
2024-06-09 04:40:19 +00:00
.rooms
.metadata
.list_banned_rooms()
2024-08-08 17:18:30 +00:00
.map(Into::into)
2024-10-01 02:47:39 +00:00
.collect()
2024-08-08 17:18:30 +00:00
.await;
2024-06-09 04:40:19 +00:00
2024-08-08 17:18:30 +00:00
if room_ids.is_empty() {
return Err!("No rooms are banned.");
2024-03-22 03:37:55 -07:00
}
2024-08-08 17:18:30 +00:00
let mut rooms = room_ids
.iter()
.stream()
.then(|room_id| get_room_info(self.services, room_id))
.collect::<Vec<_>>()
.await;
rooms.sort_by_key(|r| r.1);
rooms.reverse();
let num = rooms.len();
let body = rooms
.iter()
.map(|(id, members, name)| {
if no_details {
2024-08-08 17:18:30 +00:00
format!("{id}")
} else {
format!("{id}\tMembers: {members}\tName: {name}")
}
})
.collect::<Vec<_>>()
.join("\n");
2024-08-08 17:18:30 +00:00
self.write_str(&format!("Rooms Banned ({num}):\n```\n{body}\n```",))
.await
2024-03-22 03:37:55 -07:00
}