adminroom: simplify codeblock checks

Signed-off-by: strawberry <strawberry@puppygock.gay>
This commit is contained in:
strawberry 2024-06-10 02:57:11 -04:00
parent 14a3471fcb
commit 0e7c3cb338
4 changed files with 306 additions and 299 deletions

View file

@ -3,26 +3,26 @@ use ruma::{api::appservice::Registration, events::room::message::RoomMessageEven
use crate::{escape_html, services, Result}; use crate::{escape_html, services, Result};
pub(crate) async fn register(body: Vec<&str>) -> Result<RoomMessageEventContent> { pub(crate) async fn register(body: Vec<&str>) -> Result<RoomMessageEventContent> {
if body.len() > 2 && body[0].trim().starts_with("```") && body.last().unwrap().trim() == "```" { if body.len() < 2 || !body[0].trim().starts_with("```") || body.last().unwrap_or(&"").trim() != "```" {
let appservice_config = body[1..body.len().checked_sub(1).unwrap()].join("\n"); return Ok(RoomMessageEventContent::text_plain(
let parsed_config = serde_yaml::from_str::<Registration>(&appservice_config);
match parsed_config {
Ok(yaml) => match services().appservice.register_appservice(yaml).await {
Ok(id) => Ok(RoomMessageEventContent::text_plain(format!(
"Appservice registered with ID: {id}."
))),
Err(e) => Ok(RoomMessageEventContent::text_plain(format!(
"Failed to register appservice: {e}"
))),
},
Err(e) => Ok(RoomMessageEventContent::text_plain(format!(
"Could not parse appservice config: {e}"
))),
}
} else {
Ok(RoomMessageEventContent::text_plain(
"Expected code block in command body. Add --help for details.", "Expected code block in command body. Add --help for details.",
)) ));
}
let appservice_config = body[1..body.len().checked_sub(1).unwrap()].join("\n");
let parsed_config = serde_yaml::from_str::<Registration>(&appservice_config);
match parsed_config {
Ok(yaml) => match services().appservice.register_appservice(yaml).await {
Ok(id) => Ok(RoomMessageEventContent::text_plain(format!(
"Appservice registered with ID: {id}."
))),
Err(e) => Ok(RoomMessageEventContent::text_plain(format!(
"Failed to register appservice: {e}"
))),
},
Err(e) => Ok(RoomMessageEventContent::text_plain(format!(
"Could not parse appservice config: {e}"
))),
} }
} }

View file

@ -43,28 +43,30 @@ pub(crate) async fn get_auth_chain(_body: Vec<&str>, event_id: Box<EventId>) ->
} }
pub(crate) async fn parse_pdu(body: Vec<&str>) -> Result<RoomMessageEventContent> { pub(crate) async fn parse_pdu(body: Vec<&str>) -> Result<RoomMessageEventContent> {
if body.len() > 2 && body[0].trim().starts_with("```") && body.last().unwrap().trim() == "```" { if body.len() < 2 || !body[0].trim().starts_with("```") || body.last().unwrap_or(&"").trim() != "```" {
let string = body[1..body.len() - 1].join("\n"); return Ok(RoomMessageEventContent::text_plain(
match serde_json::from_str(&string) { "Expected code block in command body. Add --help for details.",
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")) { let string = body[1..body.len() - 1].join("\n");
Ok(pdu) => Ok(RoomMessageEventContent::text_plain(format!("EventId: {event_id:?}\n{pdu:#?}"))), match serde_json::from_str(&string) {
Err(e) => Ok(RoomMessageEventContent::text_plain(format!( Ok(value) => match ruma::signatures::reference_hash(&value, &RoomVersionId::V6) {
"EventId: {event_id:?}\nCould not parse event: {e}" Ok(hash) => {
))), let event_id = EventId::parse(format!("${hash}"));
}
}, match serde_json::from_value::<PduEvent>(serde_json::to_value(value).expect("value is json")) {
Err(e) => Ok(RoomMessageEventContent::text_plain(format!("Could not parse PDU JSON: {e:?}"))), Ok(pdu) => Ok(RoomMessageEventContent::text_plain(format!("EventId: {event_id:?}\n{pdu:#?}"))),
Err(e) => Ok(RoomMessageEventContent::text_plain(format!(
"EventId: {event_id:?}\nCould not parse event: {e}"
))),
}
}, },
Err(e) => Ok(RoomMessageEventContent::text_plain(format!( Err(e) => Ok(RoomMessageEventContent::text_plain(format!("Could not parse PDU JSON: {e:?}"))),
"Invalid json in command body: {e}" },
))), Err(e) => Ok(RoomMessageEventContent::text_plain(format!(
} "Invalid json in command body: {e}"
} else { ))),
Ok(RoomMessageEventContent::text_plain("Expected code block in command body."))
} }
} }
@ -117,33 +119,40 @@ pub(crate) async fn get_remote_pdu_list(
if server == services().globals.server_name() { if server == services().globals.server_name() {
return Ok(RoomMessageEventContent::text_plain( return Ok(RoomMessageEventContent::text_plain(
"Not allowed to send federation requests to ourselves. Please use `get-pdu` for fetching local PDUs.", "Not allowed to send federation requests to ourselves. Please use `get-pdu` for fetching local PDUs from \
the database.",
)); ));
} }
if body.len() > 2 && body[0].trim().starts_with("```") && body.last().unwrap().trim() == "```" { if body.len() < 2 || !body[0].trim().starts_with("```") || body.last().unwrap_or(&"").trim() != "```" {
let list = body return Ok(RoomMessageEventContent::text_plain(
.clone() "Expected code block in command body. Add --help for details.",
.drain(1..body.len().checked_sub(1).unwrap()) ));
.filter_map(|pdu| EventId::parse(pdu).ok())
.collect::<Vec<_>>();
for pdu in list {
if force {
if let Err(e) = get_remote_pdu(Vec::new(), Box::from(pdu), server.clone()).await {
warn!(%e, "Failed to get remote PDU, ignoring error");
}
} else {
get_remote_pdu(Vec::new(), Box::from(pdu), server.clone()).await?;
}
}
return Ok(RoomMessageEventContent::text_plain("Fetched list of remote PDUs."));
} }
Ok(RoomMessageEventContent::text_plain( let list = body
"Expected code block in command body. Add --help for details.", .clone()
)) .drain(1..body.len().checked_sub(1).unwrap())
.filter_map(|pdu| EventId::parse(pdu).ok())
.collect::<Vec<_>>();
for pdu in list {
if force {
if let Err(e) = get_remote_pdu(Vec::new(), Box::from(pdu), server.clone()).await {
services()
.admin
.send_message(RoomMessageEventContent::text_plain(format!(
"Failed to get remote PDU, ignoring error: {e}"
)))
.await;
warn!(%e, "Failed to get remote PDU, ignoring error");
}
} else {
get_remote_pdu(Vec::new(), Box::from(pdu), server.clone()).await?;
}
}
Ok(RoomMessageEventContent::text_plain("Fetched list of remote PDUs."))
} }
pub(crate) async fn get_remote_pdu( pub(crate) async fn get_remote_pdu(
@ -384,55 +393,55 @@ pub(crate) async fn change_log_level(
} }
pub(crate) async fn sign_json(body: Vec<&str>) -> Result<RoomMessageEventContent> { pub(crate) async fn sign_json(body: Vec<&str>) -> Result<RoomMessageEventContent> {
if body.len() > 2 && body[0].trim().starts_with("```") && body.last().unwrap().trim() == "```" { if body.len() < 2 || !body[0].trim().starts_with("```") || body.last().unwrap_or(&"").trim() != "```" {
let string = body[1..body.len().checked_sub(1).unwrap()].join("\n"); return Ok(RoomMessageEventContent::text_plain(
match serde_json::from_str(&string) {
Ok(mut value) => {
ruma::signatures::sign_json(
services().globals.server_name().as_str(),
services().globals.keypair(),
&mut value,
)
.expect("our request json is what ruma expects");
let json_text = serde_json::to_string_pretty(&value).expect("canonical json is valid json");
Ok(RoomMessageEventContent::text_plain(json_text))
},
Err(e) => Ok(RoomMessageEventContent::text_plain(format!("Invalid json: {e}"))),
}
} else {
Ok(RoomMessageEventContent::text_plain(
"Expected code block in command body. Add --help for details.", "Expected code block in command body. Add --help for details.",
)) ));
}
let string = body[1..body.len().checked_sub(1).unwrap()].join("\n");
match serde_json::from_str(&string) {
Ok(mut value) => {
ruma::signatures::sign_json(
services().globals.server_name().as_str(),
services().globals.keypair(),
&mut value,
)
.expect("our request json is what ruma expects");
let json_text = serde_json::to_string_pretty(&value).expect("canonical json is valid json");
Ok(RoomMessageEventContent::text_plain(json_text))
},
Err(e) => Ok(RoomMessageEventContent::text_plain(format!("Invalid json: {e}"))),
} }
} }
pub(crate) async fn verify_json(body: Vec<&str>) -> Result<RoomMessageEventContent> { pub(crate) async fn verify_json(body: Vec<&str>) -> Result<RoomMessageEventContent> {
if body.len() > 2 && body[0].trim().starts_with("```") && body.last().unwrap().trim() == "```" { if body.len() < 2 || !body[0].trim().starts_with("```") || body.last().unwrap_or(&"").trim() != "```" {
let string = body[1..body.len().checked_sub(1).unwrap()].join("\n"); return Ok(RoomMessageEventContent::text_plain(
match serde_json::from_str(&string) {
Ok(value) => {
let pub_key_map = RwLock::new(BTreeMap::new());
services()
.rooms
.event_handler
.fetch_required_signing_keys([&value], &pub_key_map)
.await?;
let pub_key_map = pub_key_map.read().await;
match ruma::signatures::verify_json(&pub_key_map, &value) {
Ok(()) => Ok(RoomMessageEventContent::text_plain("Signature correct")),
Err(e) => Ok(RoomMessageEventContent::text_plain(format!(
"Signature verification failed: {e}"
))),
}
},
Err(e) => Ok(RoomMessageEventContent::text_plain(format!("Invalid json: {e}"))),
}
} else {
Ok(RoomMessageEventContent::text_plain(
"Expected code block in command body. Add --help for details.", "Expected code block in command body. Add --help for details.",
)) ));
}
let string = body[1..body.len().checked_sub(1).unwrap()].join("\n");
match serde_json::from_str(&string) {
Ok(value) => {
let pub_key_map = RwLock::new(BTreeMap::new());
services()
.rooms
.event_handler
.fetch_required_signing_keys([&value], &pub_key_map)
.await?;
let pub_key_map = pub_key_map.read().await;
match ruma::signatures::verify_json(&pub_key_map, &value) {
Ok(()) => Ok(RoomMessageEventContent::text_plain("Signature correct")),
Err(e) => Ok(RoomMessageEventContent::text_plain(format!(
"Signature verification failed: {e}"
))),
}
},
Err(e) => Ok(RoomMessageEventContent::text_plain(format!("Invalid json: {e}"))),
} }
} }

View file

@ -138,30 +138,30 @@ pub(crate) async fn delete(
} }
pub(crate) async fn delete_list(body: Vec<&str>) -> Result<RoomMessageEventContent> { pub(crate) async fn delete_list(body: Vec<&str>) -> Result<RoomMessageEventContent> {
if body.len() > 2 && body[0].trim().starts_with("```") && body.last().unwrap().trim() == "```" { if body.len() < 2 || !body[0].trim().starts_with("```") || body.last().unwrap_or(&"").trim() != "```" {
let mxc_list = body return Ok(RoomMessageEventContent::text_plain(
.clone() "Expected code block in command body. Add --help for details.",
.drain(1..body.len().checked_sub(1).unwrap()) ));
.collect::<Vec<_>>();
let mut mxc_deletion_count: usize = 0;
for mxc in mxc_list {
debug!("Deleting MXC {mxc} in bulk");
services().media.delete(mxc.to_owned()).await?;
mxc_deletion_count = mxc_deletion_count
.checked_add(1)
.expect("mxc_deletion_count should not get this high");
}
return Ok(RoomMessageEventContent::text_plain(format!(
"Finished bulk MXC deletion, deleted {mxc_deletion_count} total MXCs from our database and the filesystem.",
)));
} }
Ok(RoomMessageEventContent::text_plain( let mxc_list = body
"Expected code block in command body. Add --help for details.", .clone()
)) .drain(1..body.len().checked_sub(1).unwrap())
.collect::<Vec<_>>();
let mut mxc_deletion_count: usize = 0;
for mxc in mxc_list {
debug!("Deleting MXC {mxc} in bulk");
services().media.delete(mxc.to_owned()).await?;
mxc_deletion_count = mxc_deletion_count
.checked_add(1)
.expect("mxc_deletion_count should not get this high");
}
Ok(RoomMessageEventContent::text_plain(format!(
"Finished bulk MXC deletion, deleted {mxc_deletion_count} total MXCs from our database and the filesystem.",
)))
} }
pub(crate) async fn delete_past_remote_media( pub(crate) async fn delete_past_remote_media(

View file

@ -187,135 +187,137 @@ async fn ban_room(
} }
async fn ban_list_of_rooms(body: Vec<&str>, force: bool, disable_federation: bool) -> Result<RoomMessageEventContent> { async fn ban_list_of_rooms(body: Vec<&str>, force: bool, disable_federation: bool) -> Result<RoomMessageEventContent> {
if body.len() > 2 && body[0].trim().starts_with("```") && body.last().unwrap().trim() == "```" { if body.len() < 2 || !body[0].trim().starts_with("```") || body.last().unwrap_or(&"").trim() != "```" {
let rooms_s = body.clone().drain(1..body.len() - 1).collect::<Vec<_>>(); return Ok(RoomMessageEventContent::text_plain(
"Expected code block in command body. Add --help for details.",
));
}
let admin_room_alias: Box<RoomAliasId> = format!("#admins:{}", services().globals.server_name()) let rooms_s = body.clone().drain(1..body.len() - 1).collect::<Vec<_>>();
.try_into()
.expect("#admins:server_name is a valid alias name");
let mut room_ban_count: usize = 0; let admin_room_alias: Box<RoomAliasId> = format!("#admins:{}", services().globals.server_name())
let mut room_ids: Vec<OwnedRoomId> = Vec::new(); .try_into()
.expect("#admins:server_name is a valid alias name");
for &room in &rooms_s { let mut room_ban_count: usize = 0;
match <&RoomOrAliasId>::try_from(room) { let mut room_ids: Vec<OwnedRoomId> = Vec::new();
Ok(room_alias_or_id) => {
if let Some(admin_room_id) = Service::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");
continue;
}
}
if room_alias_or_id.is_room_id() { for &room in &rooms_s {
let room_id = match RoomId::parse(room_alias_or_id) { match <&RoomOrAliasId>::try_from(room) {
Ok(room_id) => room_id, Ok(room_alias_or_id) => {
Err(e) => { if let Some(admin_room_id) = Service::get_admin_room().await? {
if force { if room.to_owned().eq(&admin_room_id) || room.to_owned().eq(&admin_room_alias) {
// ignore rooms we failed to parse if we're force banning info!("User specified admin room in bulk ban list, ignoring");
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}"
)));
},
};
room_ids.push(room_id);
}
if room_alias_or_id.is_room_alias_id() {
match RoomAliasId::parse(room_alias_or_id) {
Ok(room_alias) => {
let room_id =
if let Some(room_id) = services().rooms.alias.resolve_local_alias(&room_alias)? {
room_id
} else {
debug!(
"We don't have this room alias to a room ID locally, attempting to fetch \
room ID over federation"
);
match get_alias_helper(room_alias, None).await {
Ok(response) => {
debug!(
"Got federation response fetching room ID for room {room}: {:?}",
response
);
response.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}"
)));
},
}
};
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}"
)));
},
}
}
},
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; continue;
} }
}
return Ok(RoomMessageEventContent::text_plain(format!( if room_alias_or_id.is_room_id() {
"{room} is not a valid room ID or room alias, please fix the list and try again: {e}" 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}"
)));
},
};
room_ids.push(room_id);
}
if room_alias_or_id.is_room_alias_id() {
match RoomAliasId::parse(room_alias_or_id) {
Ok(room_alias) => {
let room_id =
if let Some(room_id) = services().rooms.alias.resolve_local_alias(&room_alias)? {
room_id
} else {
debug!(
"We don't have this room alias to a room ID locally, attempting to fetch room \
ID over federation"
);
match get_alias_helper(room_alias, None).await {
Ok(response) => {
debug!(
"Got federation response fetching room ID for room {room}: {:?}",
response
);
response.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}"
)));
},
}
};
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}"
)));
},
}
}
},
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}"
)));
},
}
}
for room_id in room_ids {
if services().rooms.metadata.ban_room(&room_id, true).is_ok() {
debug!("Banned {room_id} successfully");
room_ban_count = room_ban_count.saturating_add(1);
} }
for room_id in room_ids { debug!("Making all users leave the room {}", &room_id);
if services().rooms.metadata.ban_room(&room_id, true).is_ok() { if force {
debug!("Banned {room_id} successfully"); for local_user in services()
room_ban_count = room_ban_count.saturating_add(1); .rooms
} .state_cache
.room_members(&room_id)
debug!("Making all users leave the room {}", &room_id); .filter_map(|user| {
if force { user.ok().filter(|local_user| {
for local_user in services() local_user.server_name() == services().globals.server_name()
.rooms
.state_cache
.room_members(&room_id)
.filter_map(|user| {
user.ok().filter(|local_user| {
local_user.server_name() == services().globals.server_name()
// additional wrapped check here is to avoid adding remote users // additional wrapped check here is to avoid adding remote users
// who are in the admin room to the list of local users (would fail auth check) // who are in the admin room to the list of local users (would fail auth check)
&& (local_user.server_name() && (local_user.server_name()
@ -324,31 +326,31 @@ async fn ban_list_of_rooms(body: Vec<&str>, force: bool, disable_federation: boo
.users .users
.is_admin(local_user) .is_admin(local_user)
.unwrap_or(true)) // since this is a .unwrap_or(true)) // since this is a
// force operation, // force operation,
// assume user is // assume user is
// an admin if // an admin if
// somehow this // somehow this
// fails // fails
})
}) })
.collect::<Vec<OwnedUserId>>() })
{ .collect::<Vec<OwnedUserId>>()
debug!( {
"Attempting leave for user {} in room {} (forced, ignoring all errors, evicting admins too)", debug!(
&local_user, room_id "Attempting leave for user {} in room {} (forced, ignoring all errors, evicting admins too)",
); &local_user, room_id
if let Err(e) = leave_room(&local_user, &room_id, None).await { );
warn!(%e, "Failed to leave room"); if let Err(e) = leave_room(&local_user, &room_id, None).await {
} warn!(%e, "Failed to leave room");
} }
} else { }
for local_user in services() } else {
.rooms for local_user in services()
.state_cache .rooms
.room_members(&room_id) .state_cache
.filter_map(|user| { .room_members(&room_id)
user.ok().filter(|local_user| { .filter_map(|user| {
local_user.server_name() == services().globals.server_name() user.ok().filter(|local_user| {
local_user.server_name() == services().globals.server_name()
// additional wrapped check here is to avoid adding remote users // additional wrapped check here is to avoid adding remote users
// who are in the admin room to the list of local users (would fail auth check) // who are in the admin room to the list of local users (would fail auth check)
&& (local_user.server_name() && (local_user.server_name()
@ -357,45 +359,41 @@ async fn ban_list_of_rooms(body: Vec<&str>, force: bool, disable_federation: boo
.users .users
.is_admin(local_user) .is_admin(local_user)
.unwrap_or(false)) .unwrap_or(false))
})
}) })
.collect::<Vec<OwnedUserId>>() })
{ .collect::<Vec<OwnedUserId>>()
debug!("Attempting leave for user {} in room {}", &local_user, &room_id); {
if let Err(e) = leave_room(&local_user, &room_id, None).await { debug!("Attempting leave for user {} in room {}", &local_user, &room_id);
error!( if let Err(e) = leave_room(&local_user, &room_id, None).await {
"Error attempting to make local user {} leave room {} during bulk room banning: {}", error!(
&local_user, &room_id, e "Error attempting to make local user {} leave room {} during bulk 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 \ return Ok(RoomMessageEventContent::text_plain(format!(
banned but not removing any more users and not banning any more rooms): {}\nIf you would \ "Error attempting to make local user {} leave room {} during room banning (room is still \
like to ignore errors, use --force", banned but not removing any more users and not banning any more rooms): {}\nIf you would \
&local_user, &room_id, e like to ignore errors, use --force",
))); &local_user, &room_id, e
} )));
} }
} }
if disable_federation {
services().rooms.metadata.disable_room(&room_id, true)?;
}
} }
if disable_federation { if disable_federation {
return Ok(RoomMessageEventContent::text_plain(format!( services().rooms.metadata.disable_room(&room_id, true)?;
"Finished bulk room ban, banned {room_ban_count} total rooms, evicted all users, and disabled \
incoming federation with the room."
)));
} }
return Ok(RoomMessageEventContent::text_plain(format!(
"Finished bulk room ban, banned {room_ban_count} total rooms and evicted all users."
)));
} }
Ok(RoomMessageEventContent::text_plain( if disable_federation {
"Expected code block in command body. Add --help for details.", 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."
)))
}
} }
async fn unban_room( async fn unban_room(