Rework admin commands to use subcommands.
This commit doesn't add, remove, or change any commands, it only organizes them
This commit is contained in:
parent
5106203d67
commit
3e9c564209
1 changed files with 587 additions and 504 deletions
|
@ -5,7 +5,7 @@ use std::{
|
||||||
time::Instant,
|
time::Instant,
|
||||||
};
|
};
|
||||||
|
|
||||||
use clap::Parser;
|
use clap::{Parser, Subcommand};
|
||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
use ruma::{
|
use ruma::{
|
||||||
events::{
|
events::{
|
||||||
|
@ -41,6 +41,37 @@ use super::pdu::PduBuilder;
|
||||||
#[derive(Parser)]
|
#[derive(Parser)]
|
||||||
#[command(name = "@conduit:server.name:", version = env!("CARGO_PKG_VERSION"))]
|
#[command(name = "@conduit:server.name:", version = env!("CARGO_PKG_VERSION"))]
|
||||||
enum AdminCommand {
|
enum AdminCommand {
|
||||||
|
#[command(subcommand)]
|
||||||
|
/// Commands for managing appservices
|
||||||
|
Appservice(AppserviceCommand),
|
||||||
|
|
||||||
|
#[command(subcommand)]
|
||||||
|
/// Commands for managing local users
|
||||||
|
User(UserCommand),
|
||||||
|
|
||||||
|
#[command(subcommand)]
|
||||||
|
/// Commands for managing rooms
|
||||||
|
Room(RoomCommand),
|
||||||
|
|
||||||
|
#[command(subcommand)]
|
||||||
|
/// Commands for managing federation
|
||||||
|
Federation(FederationCommand),
|
||||||
|
|
||||||
|
#[command(subcommand)]
|
||||||
|
/// Commands for managing the server
|
||||||
|
Server(ServerCommand),
|
||||||
|
|
||||||
|
#[command(subcommand)]
|
||||||
|
// TODO: should i split out debug commands to a separate thing? the
|
||||||
|
// debug commands seem like they could fit in the other categories fine
|
||||||
|
// this is more like a "miscellaneous" category than a debug one
|
||||||
|
/// Commands for debugging things
|
||||||
|
Debug(DebugCommand),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg_attr(test, derive(Debug))]
|
||||||
|
#[derive(Subcommand)]
|
||||||
|
enum AppserviceCommand {
|
||||||
#[command(verbatim_doc_comment)]
|
#[command(verbatim_doc_comment)]
|
||||||
/// Register an appservice using its registration YAML
|
/// Register an appservice using its registration YAML
|
||||||
///
|
///
|
||||||
|
@ -54,33 +85,42 @@ enum AdminCommand {
|
||||||
/// # ```
|
/// # ```
|
||||||
/// # yaml content here
|
/// # yaml content here
|
||||||
/// # ```
|
/// # ```
|
||||||
RegisterAppservice,
|
Register,
|
||||||
|
|
||||||
/// Unregister an appservice using its ID
|
/// Unregister an appservice using its ID
|
||||||
///
|
///
|
||||||
/// You can find the ID using the `list-appservices` command.
|
/// You can find the ID using the `list-appservices` command.
|
||||||
UnregisterAppservice {
|
Unregister {
|
||||||
/// The appservice to unregister
|
/// The appservice to unregister
|
||||||
appservice_identifier: String,
|
appservice_identifier: String,
|
||||||
},
|
},
|
||||||
|
|
||||||
/// List all the currently registered appservices
|
/// List all the currently registered appservices
|
||||||
ListAppservices,
|
List,
|
||||||
|
}
|
||||||
|
|
||||||
/// List all rooms the server knows about
|
#[cfg_attr(test, derive(Debug))]
|
||||||
ListRooms,
|
#[derive(Subcommand)]
|
||||||
|
enum UserCommand {
|
||||||
|
/// Create a new user
|
||||||
|
Create {
|
||||||
|
/// Username of the new user
|
||||||
|
username: String,
|
||||||
|
/// Password of the new user, if unspecified one is generated
|
||||||
|
password: Option<String>,
|
||||||
|
},
|
||||||
|
|
||||||
/// List users in the database
|
/// Reset user password
|
||||||
ListLocalUsers,
|
ResetPassword {
|
||||||
|
/// Username of the user for whom the password should be reset
|
||||||
/// List all rooms we are currently handling an incoming pdu from
|
username: String,
|
||||||
IncomingFederation,
|
},
|
||||||
|
|
||||||
/// Deactivate a user
|
/// Deactivate a user
|
||||||
///
|
///
|
||||||
/// User will not be removed from all rooms by default.
|
/// User will not be removed from all rooms by default.
|
||||||
/// Use --leave-rooms to force the user to leave all rooms
|
/// Use --leave-rooms to force the user to leave all rooms
|
||||||
DeactivateUser {
|
Deactivate {
|
||||||
#[arg(short, long)]
|
#[arg(short, long)]
|
||||||
leave_rooms: bool,
|
leave_rooms: bool,
|
||||||
user_id: Box<UserId>,
|
user_id: Box<UserId>,
|
||||||
|
@ -109,6 +149,49 @@ enum AdminCommand {
|
||||||
force: bool,
|
force: bool,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/// List local users in the database
|
||||||
|
List,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg_attr(test, derive(Debug))]
|
||||||
|
#[derive(Subcommand)]
|
||||||
|
enum RoomCommand {
|
||||||
|
/// List all rooms the server knows about
|
||||||
|
List,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg_attr(test, derive(Debug))]
|
||||||
|
#[derive(Subcommand)]
|
||||||
|
enum FederationCommand {
|
||||||
|
/// List all rooms we are currently handling an incoming pdu from
|
||||||
|
IncomingFederation,
|
||||||
|
|
||||||
|
/// Disables incoming federation handling for a room.
|
||||||
|
DisableRoom { room_id: Box<RoomId> },
|
||||||
|
|
||||||
|
/// Enables incoming federation handling for a room again.
|
||||||
|
EnableRoom { room_id: Box<RoomId> },
|
||||||
|
|
||||||
|
#[command(verbatim_doc_comment)]
|
||||||
|
/// Verify json signatures
|
||||||
|
/// [commandbody]
|
||||||
|
/// # ```
|
||||||
|
/// # json here
|
||||||
|
/// # ```
|
||||||
|
SignJson,
|
||||||
|
|
||||||
|
#[command(verbatim_doc_comment)]
|
||||||
|
/// Verify json signatures
|
||||||
|
/// [commandbody]
|
||||||
|
/// # ```
|
||||||
|
/// # json here
|
||||||
|
/// # ```
|
||||||
|
VerifyJson,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg_attr(test, derive(Debug))]
|
||||||
|
#[derive(Subcommand)]
|
||||||
|
enum DebugCommand {
|
||||||
/// Get the auth_chain of a PDU
|
/// Get the auth_chain of a PDU
|
||||||
GetAuthChain {
|
GetAuthChain {
|
||||||
/// An event ID (the $ character followed by the base64 reference hash)
|
/// An event ID (the $ character followed by the base64 reference hash)
|
||||||
|
@ -132,6 +215,13 @@ enum AdminCommand {
|
||||||
/// An event ID (a $ followed by the base64 reference hash)
|
/// An event ID (a $ followed by the base64 reference hash)
|
||||||
event_id: Box<EventId>,
|
event_id: Box<EventId>,
|
||||||
},
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg_attr(test, derive(Debug))]
|
||||||
|
#[derive(Subcommand)]
|
||||||
|
enum ServerCommand {
|
||||||
|
/// Show configuration values
|
||||||
|
ShowConfig,
|
||||||
|
|
||||||
/// Print database memory usage statistics
|
/// Print database memory usage statistics
|
||||||
MemoryUsage,
|
MemoryUsage,
|
||||||
|
@ -141,42 +231,6 @@ enum AdminCommand {
|
||||||
|
|
||||||
/// Clears all of Conduit's service caches with index smaller than the amount
|
/// Clears all of Conduit's service caches with index smaller than the amount
|
||||||
ClearServiceCaches { amount: u32 },
|
ClearServiceCaches { amount: u32 },
|
||||||
|
|
||||||
/// Show configuration values
|
|
||||||
ShowConfig,
|
|
||||||
|
|
||||||
/// Reset user password
|
|
||||||
ResetPassword {
|
|
||||||
/// Username of the user for whom the password should be reset
|
|
||||||
username: String,
|
|
||||||
},
|
|
||||||
|
|
||||||
/// Create a new user
|
|
||||||
CreateUser {
|
|
||||||
/// Username of the new user
|
|
||||||
username: String,
|
|
||||||
/// Password of the new user, if unspecified one is generated
|
|
||||||
password: Option<String>,
|
|
||||||
},
|
|
||||||
|
|
||||||
/// Disables incoming federation handling for a room.
|
|
||||||
DisableRoom { room_id: Box<RoomId> },
|
|
||||||
/// Enables incoming federation handling for a room again.
|
|
||||||
EnableRoom { room_id: Box<RoomId> },
|
|
||||||
|
|
||||||
/// Verify json signatures
|
|
||||||
/// [commandbody]
|
|
||||||
/// # ```
|
|
||||||
/// # json here
|
|
||||||
/// # ```
|
|
||||||
SignJson,
|
|
||||||
|
|
||||||
/// Verify json signatures
|
|
||||||
/// [commandbody]
|
|
||||||
/// # ```
|
|
||||||
/// # json here
|
|
||||||
/// # ```
|
|
||||||
VerifyJson,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
@ -341,8 +395,11 @@ impl Service {
|
||||||
body: Vec<&str>,
|
body: Vec<&str>,
|
||||||
) -> Result<RoomMessageEventContent> {
|
) -> Result<RoomMessageEventContent> {
|
||||||
let reply_message_content = match command {
|
let reply_message_content = match command {
|
||||||
AdminCommand::RegisterAppservice => {
|
AdminCommand::Appservice(command) => match command {
|
||||||
if body.len() > 2 && body[0].trim() == "```" && body.last().unwrap().trim() == "```"
|
AppserviceCommand::Register => {
|
||||||
|
if body.len() > 2
|
||||||
|
&& body[0].trim() == "```"
|
||||||
|
&& body.last().unwrap().trim() == "```"
|
||||||
{
|
{
|
||||||
let appservice_config = body[1..body.len() - 1].join("\n");
|
let appservice_config = body[1..body.len() - 1].join("\n");
|
||||||
let parsed_config =
|
let parsed_config =
|
||||||
|
@ -366,7 +423,7 @@ impl Service {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
AdminCommand::UnregisterAppservice {
|
AppserviceCommand::Unregister {
|
||||||
appservice_identifier,
|
appservice_identifier,
|
||||||
} => match services()
|
} => match services()
|
||||||
.appservice
|
.appservice
|
||||||
|
@ -377,7 +434,7 @@ impl Service {
|
||||||
"Failed to unregister appservice: {e}"
|
"Failed to unregister appservice: {e}"
|
||||||
)),
|
)),
|
||||||
},
|
},
|
||||||
AdminCommand::ListAppservices => {
|
AppserviceCommand::List => {
|
||||||
if let Ok(appservices) = services()
|
if let Ok(appservices) = services()
|
||||||
.appservice
|
.appservice
|
||||||
.iter_ids()
|
.iter_ids()
|
||||||
|
@ -398,217 +455,18 @@ impl Service {
|
||||||
RoomMessageEventContent::text_plain("Failed to get appservices.")
|
RoomMessageEventContent::text_plain("Failed to get appservices.")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
AdminCommand::ListRooms => {
|
},
|
||||||
let room_ids = services().rooms.metadata.iter_ids();
|
AdminCommand::User(command) => match command {
|
||||||
let output = format!(
|
UserCommand::List => match services().users.list_local_users() {
|
||||||
"Rooms:\n{}",
|
|
||||||
room_ids
|
|
||||||
.filter_map(|r| r.ok())
|
|
||||||
.map(|id| id.to_string()
|
|
||||||
+ "\tMembers: "
|
|
||||||
+ &services()
|
|
||||||
.rooms
|
|
||||||
.state_cache
|
|
||||||
.room_joined_count(&id)
|
|
||||||
.ok()
|
|
||||||
.flatten()
|
|
||||||
.unwrap_or(0)
|
|
||||||
.to_string())
|
|
||||||
.collect::<Vec<_>>()
|
|
||||||
.join("\n")
|
|
||||||
);
|
|
||||||
RoomMessageEventContent::text_plain(output)
|
|
||||||
}
|
|
||||||
AdminCommand::ListLocalUsers => match services().users.list_local_users() {
|
|
||||||
Ok(users) => {
|
Ok(users) => {
|
||||||
let mut msg: String = format!("Found {} local user account(s):\n", users.len());
|
let mut msg: String =
|
||||||
|
format!("Found {} local user account(s):\n", users.len());
|
||||||
msg += &users.join("\n");
|
msg += &users.join("\n");
|
||||||
RoomMessageEventContent::text_plain(&msg)
|
RoomMessageEventContent::text_plain(&msg)
|
||||||
}
|
}
|
||||||
Err(e) => RoomMessageEventContent::text_plain(e.to_string()),
|
Err(e) => RoomMessageEventContent::text_plain(e.to_string()),
|
||||||
},
|
},
|
||||||
AdminCommand::IncomingFederation => {
|
UserCommand::Create { username, password } => {
|
||||||
let map = services()
|
|
||||||
.globals
|
|
||||||
.roomid_federationhandletime
|
|
||||||
.read()
|
|
||||||
.unwrap();
|
|
||||||
let mut msg: String = format!("Handling {} incoming pdus:\n", map.len());
|
|
||||||
|
|
||||||
for (r, (e, i)) in map.iter() {
|
|
||||||
let elapsed = i.elapsed();
|
|
||||||
msg += &format!(
|
|
||||||
"{} {}: {}m{}s\n",
|
|
||||||
r,
|
|
||||||
e,
|
|
||||||
elapsed.as_secs() / 60,
|
|
||||||
elapsed.as_secs() % 60
|
|
||||||
);
|
|
||||||
}
|
|
||||||
RoomMessageEventContent::text_plain(&msg)
|
|
||||||
}
|
|
||||||
AdminCommand::GetAuthChain { event_id } => {
|
|
||||||
let event_id = Arc::<EventId>::from(event_id);
|
|
||||||
if let Some(event) = services().rooms.timeline.get_pdu_json(&event_id)? {
|
|
||||||
let room_id_str = event
|
|
||||||
.get("room_id")
|
|
||||||
.and_then(|val| val.as_str())
|
|
||||||
.ok_or_else(|| Error::bad_database("Invalid event in database"))?;
|
|
||||||
|
|
||||||
let room_id = <&RoomId>::try_from(room_id_str).map_err(|_| {
|
|
||||||
Error::bad_database("Invalid room id field in event in database")
|
|
||||||
})?;
|
|
||||||
let start = Instant::now();
|
|
||||||
let count = services()
|
|
||||||
.rooms
|
|
||||||
.auth_chain
|
|
||||||
.get_auth_chain(room_id, vec![event_id])
|
|
||||||
.await?
|
|
||||||
.count();
|
|
||||||
let elapsed = start.elapsed();
|
|
||||||
RoomMessageEventContent::text_plain(format!(
|
|
||||||
"Loaded auth chain with length {count} in {elapsed:?}"
|
|
||||||
))
|
|
||||||
} else {
|
|
||||||
RoomMessageEventContent::text_plain("Event not found.")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
AdminCommand::ParsePdu => {
|
|
||||||
if body.len() > 2 && body[0].trim() == "```" && body.last().unwrap().trim() == "```"
|
|
||||||
{
|
|
||||||
let string = body[1..body.len() - 1].join("\n");
|
|
||||||
match serde_json::from_str(&string) {
|
|
||||||
Ok(value) => {
|
|
||||||
match ruma::signatures::reference_hash(&value, &RoomVersionId::V6) {
|
|
||||||
Ok(hash) => {
|
|
||||||
let event_id = EventId::parse(format!("${hash}"));
|
|
||||||
|
|
||||||
match serde_json::from_value::<PduEvent>(
|
|
||||||
serde_json::to_value(value).expect("value is json"),
|
|
||||||
) {
|
|
||||||
Ok(pdu) => RoomMessageEventContent::text_plain(format!(
|
|
||||||
"EventId: {event_id:?}\n{pdu:#?}"
|
|
||||||
)),
|
|
||||||
Err(e) => RoomMessageEventContent::text_plain(format!(
|
|
||||||
"EventId: {event_id:?}\nCould not parse event: {e}"
|
|
||||||
)),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Err(e) => RoomMessageEventContent::text_plain(format!(
|
|
||||||
"Could not parse PDU JSON: {e:?}"
|
|
||||||
)),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Err(e) => RoomMessageEventContent::text_plain(format!(
|
|
||||||
"Invalid json in command body: {e}"
|
|
||||||
)),
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
RoomMessageEventContent::text_plain("Expected code block in command body.")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
AdminCommand::GetPdu { event_id } => {
|
|
||||||
let mut outlier = false;
|
|
||||||
let mut pdu_json = services()
|
|
||||||
.rooms
|
|
||||||
.timeline
|
|
||||||
.get_non_outlier_pdu_json(&event_id)?;
|
|
||||||
if pdu_json.is_none() {
|
|
||||||
outlier = true;
|
|
||||||
pdu_json = services().rooms.timeline.get_pdu_json(&event_id)?;
|
|
||||||
}
|
|
||||||
match pdu_json {
|
|
||||||
Some(json) => {
|
|
||||||
let json_text = serde_json::to_string_pretty(&json)
|
|
||||||
.expect("canonical json is valid json");
|
|
||||||
RoomMessageEventContent::text_html(
|
|
||||||
format!(
|
|
||||||
"{}\n```json\n{}\n```",
|
|
||||||
if outlier {
|
|
||||||
"PDU is outlier"
|
|
||||||
} else {
|
|
||||||
"PDU was accepted"
|
|
||||||
},
|
|
||||||
json_text
|
|
||||||
),
|
|
||||||
format!(
|
|
||||||
"<p>{}</p>\n<pre><code class=\"language-json\">{}\n</code></pre>\n",
|
|
||||||
if outlier {
|
|
||||||
"PDU is outlier"
|
|
||||||
} else {
|
|
||||||
"PDU was accepted"
|
|
||||||
},
|
|
||||||
HtmlEscape(&json_text)
|
|
||||||
),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
None => RoomMessageEventContent::text_plain("PDU not found."),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
AdminCommand::MemoryUsage => {
|
|
||||||
let response1 = services().memory_usage();
|
|
||||||
let response2 = services().globals.db.memory_usage();
|
|
||||||
|
|
||||||
RoomMessageEventContent::text_plain(format!(
|
|
||||||
"Services:\n{response1}\n\nDatabase:\n{response2}"
|
|
||||||
))
|
|
||||||
}
|
|
||||||
AdminCommand::ClearDatabaseCaches { amount } => {
|
|
||||||
services().globals.db.clear_caches(amount);
|
|
||||||
|
|
||||||
RoomMessageEventContent::text_plain("Done.")
|
|
||||||
}
|
|
||||||
AdminCommand::ClearServiceCaches { amount } => {
|
|
||||||
services().clear_caches(amount);
|
|
||||||
|
|
||||||
RoomMessageEventContent::text_plain("Done.")
|
|
||||||
}
|
|
||||||
AdminCommand::ShowConfig => {
|
|
||||||
// Construct and send the response
|
|
||||||
RoomMessageEventContent::text_plain(format!("{}", services().globals.config))
|
|
||||||
}
|
|
||||||
AdminCommand::ResetPassword { username } => {
|
|
||||||
let user_id = match UserId::parse_with_server_name(
|
|
||||||
username.as_str().to_lowercase(),
|
|
||||||
services().globals.server_name(),
|
|
||||||
) {
|
|
||||||
Ok(id) => id,
|
|
||||||
Err(e) => {
|
|
||||||
return Ok(RoomMessageEventContent::text_plain(format!(
|
|
||||||
"The supplied username is not a valid username: {e}"
|
|
||||||
)))
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Check if the specified user is valid
|
|
||||||
if !services().users.exists(&user_id)?
|
|
||||||
|| user_id
|
|
||||||
== UserId::parse_with_server_name(
|
|
||||||
"conduit",
|
|
||||||
services().globals.server_name(),
|
|
||||||
)
|
|
||||||
.expect("conduit user exists")
|
|
||||||
{
|
|
||||||
return Ok(RoomMessageEventContent::text_plain(
|
|
||||||
"The specified user does not exist!",
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
let new_password = utils::random_string(AUTO_GEN_PASSWORD_LENGTH);
|
|
||||||
|
|
||||||
match services()
|
|
||||||
.users
|
|
||||||
.set_password(&user_id, Some(new_password.as_str()))
|
|
||||||
{
|
|
||||||
Ok(()) => RoomMessageEventContent::text_plain(format!(
|
|
||||||
"Successfully reset the password for user {user_id}: {new_password}"
|
|
||||||
)),
|
|
||||||
Err(e) => RoomMessageEventContent::text_plain(format!(
|
|
||||||
"Couldn't reset the password for user {user_id}: {e}"
|
|
||||||
)),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
AdminCommand::CreateUser { username, password } => {
|
|
||||||
let password =
|
let password =
|
||||||
password.unwrap_or_else(|| utils::random_string(AUTO_GEN_PASSWORD_LENGTH));
|
password.unwrap_or_else(|| utils::random_string(AUTO_GEN_PASSWORD_LENGTH));
|
||||||
// Validate user id
|
// Validate user id
|
||||||
|
@ -670,15 +528,7 @@ impl Service {
|
||||||
"Created user with user_id: {user_id} and password: {password}"
|
"Created user with user_id: {user_id} and password: {password}"
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
AdminCommand::DisableRoom { room_id } => {
|
UserCommand::Deactivate {
|
||||||
services().rooms.metadata.disable_room(&room_id, true)?;
|
|
||||||
RoomMessageEventContent::text_plain("Room disabled.")
|
|
||||||
}
|
|
||||||
AdminCommand::EnableRoom { room_id } => {
|
|
||||||
services().rooms.metadata.disable_room(&room_id, false)?;
|
|
||||||
RoomMessageEventContent::text_plain("Room enabled.")
|
|
||||||
}
|
|
||||||
AdminCommand::DeactivateUser {
|
|
||||||
leave_rooms,
|
leave_rooms,
|
||||||
user_id,
|
user_id,
|
||||||
} => {
|
} => {
|
||||||
|
@ -703,8 +553,51 @@ impl Service {
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
AdminCommand::DeactivateAll { leave_rooms, force } => {
|
UserCommand::ResetPassword { username } => {
|
||||||
if body.len() > 2 && body[0].trim() == "```" && body.last().unwrap().trim() == "```"
|
let user_id = match UserId::parse_with_server_name(
|
||||||
|
username.as_str().to_lowercase(),
|
||||||
|
services().globals.server_name(),
|
||||||
|
) {
|
||||||
|
Ok(id) => id,
|
||||||
|
Err(e) => {
|
||||||
|
return Ok(RoomMessageEventContent::text_plain(format!(
|
||||||
|
"The supplied username is not a valid username: {e}"
|
||||||
|
)))
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Check if the specified user is valid
|
||||||
|
if !services().users.exists(&user_id)?
|
||||||
|
|| user_id
|
||||||
|
== UserId::parse_with_server_name(
|
||||||
|
"conduit",
|
||||||
|
services().globals.server_name(),
|
||||||
|
)
|
||||||
|
.expect("conduit user exists")
|
||||||
|
{
|
||||||
|
return Ok(RoomMessageEventContent::text_plain(
|
||||||
|
"The specified user does not exist!",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
let new_password = utils::random_string(AUTO_GEN_PASSWORD_LENGTH);
|
||||||
|
|
||||||
|
match services()
|
||||||
|
.users
|
||||||
|
.set_password(&user_id, Some(new_password.as_str()))
|
||||||
|
{
|
||||||
|
Ok(()) => RoomMessageEventContent::text_plain(format!(
|
||||||
|
"Successfully reset the password for user {user_id}: {new_password}"
|
||||||
|
)),
|
||||||
|
Err(e) => RoomMessageEventContent::text_plain(format!(
|
||||||
|
"Couldn't reset the password for user {user_id}: {e}"
|
||||||
|
)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
UserCommand::DeactivateAll { leave_rooms, force } => {
|
||||||
|
if body.len() > 2
|
||||||
|
&& body[0].trim() == "```"
|
||||||
|
&& body.last().unwrap().trim() == "```"
|
||||||
{
|
{
|
||||||
let usernames = body.clone().drain(1..body.len() - 1).collect::<Vec<_>>();
|
let usernames = body.clone().drain(1..body.len() - 1).collect::<Vec<_>>();
|
||||||
|
|
||||||
|
@ -762,8 +655,63 @@ impl Service {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
AdminCommand::SignJson => {
|
},
|
||||||
if body.len() > 2 && body[0].trim() == "```" && body.last().unwrap().trim() == "```"
|
AdminCommand::Room(command) => match command {
|
||||||
|
RoomCommand::List => {
|
||||||
|
let room_ids = services().rooms.metadata.iter_ids();
|
||||||
|
let output = format!(
|
||||||
|
"Rooms:\n{}",
|
||||||
|
room_ids
|
||||||
|
.filter_map(|r| r.ok())
|
||||||
|
.map(|id| id.to_string()
|
||||||
|
+ "\tMembers: "
|
||||||
|
+ &services()
|
||||||
|
.rooms
|
||||||
|
.state_cache
|
||||||
|
.room_joined_count(&id)
|
||||||
|
.ok()
|
||||||
|
.flatten()
|
||||||
|
.unwrap_or(0)
|
||||||
|
.to_string())
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.join("\n")
|
||||||
|
);
|
||||||
|
RoomMessageEventContent::text_plain(output)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
AdminCommand::Federation(command) => match command {
|
||||||
|
FederationCommand::DisableRoom { room_id } => {
|
||||||
|
services().rooms.metadata.disable_room(&room_id, true)?;
|
||||||
|
RoomMessageEventContent::text_plain("Room disabled.")
|
||||||
|
}
|
||||||
|
FederationCommand::EnableRoom { room_id } => {
|
||||||
|
services().rooms.metadata.disable_room(&room_id, false)?;
|
||||||
|
RoomMessageEventContent::text_plain("Room enabled.")
|
||||||
|
}
|
||||||
|
FederationCommand::IncomingFederation => {
|
||||||
|
let map = services()
|
||||||
|
.globals
|
||||||
|
.roomid_federationhandletime
|
||||||
|
.read()
|
||||||
|
.unwrap();
|
||||||
|
let mut msg: String = format!("Handling {} incoming pdus:\n", map.len());
|
||||||
|
|
||||||
|
for (r, (e, i)) in map.iter() {
|
||||||
|
let elapsed = i.elapsed();
|
||||||
|
msg += &format!(
|
||||||
|
"{} {}: {}m{}s\n",
|
||||||
|
r,
|
||||||
|
e,
|
||||||
|
elapsed.as_secs() / 60,
|
||||||
|
elapsed.as_secs() % 60
|
||||||
|
);
|
||||||
|
}
|
||||||
|
RoomMessageEventContent::text_plain(&msg)
|
||||||
|
}
|
||||||
|
FederationCommand::SignJson => {
|
||||||
|
if body.len() > 2
|
||||||
|
&& body[0].trim() == "```"
|
||||||
|
&& body.last().unwrap().trim() == "```"
|
||||||
{
|
{
|
||||||
let string = body[1..body.len() - 1].join("\n");
|
let string = body[1..body.len() - 1].join("\n");
|
||||||
match serde_json::from_str(&string) {
|
match serde_json::from_str(&string) {
|
||||||
|
@ -778,7 +726,9 @@ impl Service {
|
||||||
.expect("canonical json is valid json");
|
.expect("canonical json is valid json");
|
||||||
RoomMessageEventContent::text_plain(json_text)
|
RoomMessageEventContent::text_plain(json_text)
|
||||||
}
|
}
|
||||||
Err(e) => RoomMessageEventContent::text_plain(format!("Invalid json: {e}")),
|
Err(e) => {
|
||||||
|
RoomMessageEventContent::text_plain(format!("Invalid json: {e}"))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
RoomMessageEventContent::text_plain(
|
RoomMessageEventContent::text_plain(
|
||||||
|
@ -786,8 +736,10 @@ impl Service {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
AdminCommand::VerifyJson => {
|
FederationCommand::VerifyJson => {
|
||||||
if body.len() > 2 && body[0].trim() == "```" && body.last().unwrap().trim() == "```"
|
if body.len() > 2
|
||||||
|
&& body[0].trim() == "```"
|
||||||
|
&& body.last().unwrap().trim() == "```"
|
||||||
{
|
{
|
||||||
let string = body[1..body.len() - 1].join("\n");
|
let string = body[1..body.len() - 1].join("\n");
|
||||||
match serde_json::from_str(&string) {
|
match serde_json::from_str(&string) {
|
||||||
|
@ -802,13 +754,17 @@ impl Service {
|
||||||
|
|
||||||
let pub_key_map = pub_key_map.read().unwrap();
|
let pub_key_map = pub_key_map.read().unwrap();
|
||||||
match ruma::signatures::verify_json(&pub_key_map, &value) {
|
match ruma::signatures::verify_json(&pub_key_map, &value) {
|
||||||
Ok(_) => RoomMessageEventContent::text_plain("Signature correct"),
|
Ok(_) => {
|
||||||
|
RoomMessageEventContent::text_plain("Signature correct")
|
||||||
|
}
|
||||||
Err(e) => RoomMessageEventContent::text_plain(format!(
|
Err(e) => RoomMessageEventContent::text_plain(format!(
|
||||||
"Signature verification failed: {e}"
|
"Signature verification failed: {e}"
|
||||||
)),
|
)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(e) => RoomMessageEventContent::text_plain(format!("Invalid json: {e}")),
|
Err(e) => {
|
||||||
|
RoomMessageEventContent::text_plain(format!("Invalid json: {e}"))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
RoomMessageEventContent::text_plain(
|
RoomMessageEventContent::text_plain(
|
||||||
|
@ -816,6 +772,133 @@ impl Service {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
AdminCommand::Server(command) => match command {
|
||||||
|
ServerCommand::ShowConfig => {
|
||||||
|
// Construct and send the response
|
||||||
|
RoomMessageEventContent::text_plain(format!("{}", services().globals.config))
|
||||||
|
}
|
||||||
|
ServerCommand::MemoryUsage => {
|
||||||
|
let response1 = services().memory_usage();
|
||||||
|
let response2 = services().globals.db.memory_usage();
|
||||||
|
|
||||||
|
RoomMessageEventContent::text_plain(format!(
|
||||||
|
"Services:\n{response1}\n\nDatabase:\n{response2}"
|
||||||
|
))
|
||||||
|
}
|
||||||
|
ServerCommand::ClearDatabaseCaches { amount } => {
|
||||||
|
services().globals.db.clear_caches(amount);
|
||||||
|
|
||||||
|
RoomMessageEventContent::text_plain("Done.")
|
||||||
|
}
|
||||||
|
ServerCommand::ClearServiceCaches { amount } => {
|
||||||
|
services().clear_caches(amount);
|
||||||
|
|
||||||
|
RoomMessageEventContent::text_plain("Done.")
|
||||||
|
}
|
||||||
|
},
|
||||||
|
AdminCommand::Debug(command) => match command {
|
||||||
|
DebugCommand::GetAuthChain { event_id } => {
|
||||||
|
let event_id = Arc::<EventId>::from(event_id);
|
||||||
|
if let Some(event) = services().rooms.timeline.get_pdu_json(&event_id)? {
|
||||||
|
let room_id_str = event
|
||||||
|
.get("room_id")
|
||||||
|
.and_then(|val| val.as_str())
|
||||||
|
.ok_or_else(|| Error::bad_database("Invalid event in database"))?;
|
||||||
|
|
||||||
|
let room_id = <&RoomId>::try_from(room_id_str).map_err(|_| {
|
||||||
|
Error::bad_database("Invalid room id field in event in database")
|
||||||
|
})?;
|
||||||
|
let start = Instant::now();
|
||||||
|
let count = services()
|
||||||
|
.rooms
|
||||||
|
.auth_chain
|
||||||
|
.get_auth_chain(room_id, vec![event_id])
|
||||||
|
.await?
|
||||||
|
.count();
|
||||||
|
let elapsed = start.elapsed();
|
||||||
|
RoomMessageEventContent::text_plain(format!(
|
||||||
|
"Loaded auth chain with length {count} in {elapsed:?}"
|
||||||
|
))
|
||||||
|
} else {
|
||||||
|
RoomMessageEventContent::text_plain("Event not found.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
DebugCommand::ParsePdu => {
|
||||||
|
if body.len() > 2
|
||||||
|
&& body[0].trim() == "```"
|
||||||
|
&& body.last().unwrap().trim() == "```"
|
||||||
|
{
|
||||||
|
let string = body[1..body.len() - 1].join("\n");
|
||||||
|
match serde_json::from_str(&string) {
|
||||||
|
Ok(value) => {
|
||||||
|
match ruma::signatures::reference_hash(&value, &RoomVersionId::V6) {
|
||||||
|
Ok(hash) => {
|
||||||
|
let event_id = EventId::parse(format!("${hash}"));
|
||||||
|
|
||||||
|
match serde_json::from_value::<PduEvent>(
|
||||||
|
serde_json::to_value(value).expect("value is json"),
|
||||||
|
) {
|
||||||
|
Ok(pdu) => RoomMessageEventContent::text_plain(
|
||||||
|
format!("EventId: {event_id:?}\n{pdu:#?}"),
|
||||||
|
),
|
||||||
|
Err(e) => RoomMessageEventContent::text_plain(format!(
|
||||||
|
"EventId: {event_id:?}\nCould not parse event: {e}"
|
||||||
|
)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(e) => RoomMessageEventContent::text_plain(format!(
|
||||||
|
"Could not parse PDU JSON: {e:?}"
|
||||||
|
)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(e) => RoomMessageEventContent::text_plain(format!(
|
||||||
|
"Invalid json in command body: {e}"
|
||||||
|
)),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
RoomMessageEventContent::text_plain("Expected code block in command body.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
DebugCommand::GetPdu { event_id } => {
|
||||||
|
let mut outlier = false;
|
||||||
|
let mut pdu_json = services()
|
||||||
|
.rooms
|
||||||
|
.timeline
|
||||||
|
.get_non_outlier_pdu_json(&event_id)?;
|
||||||
|
if pdu_json.is_none() {
|
||||||
|
outlier = true;
|
||||||
|
pdu_json = services().rooms.timeline.get_pdu_json(&event_id)?;
|
||||||
|
}
|
||||||
|
match pdu_json {
|
||||||
|
Some(json) => {
|
||||||
|
let json_text = serde_json::to_string_pretty(&json)
|
||||||
|
.expect("canonical json is valid json");
|
||||||
|
RoomMessageEventContent::text_html(
|
||||||
|
format!(
|
||||||
|
"{}\n```json\n{}\n```",
|
||||||
|
if outlier {
|
||||||
|
"PDU is outlier"
|
||||||
|
} else {
|
||||||
|
"PDU was accepted"
|
||||||
|
},
|
||||||
|
json_text
|
||||||
|
),
|
||||||
|
format!(
|
||||||
|
"<p>{}</p>\n<pre><code class=\"language-json\">{}\n</code></pre>\n",
|
||||||
|
if outlier {
|
||||||
|
"PDU is outlier"
|
||||||
|
} else {
|
||||||
|
"PDU was accepted"
|
||||||
|
},
|
||||||
|
HtmlEscape(&json_text)
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
None => RoomMessageEventContent::text_plain("PDU not found."),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(reply_message_content)
|
Ok(reply_message_content)
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue