{client,server}/edit-privs: check for user existence

This commit is contained in:
2025-12-14 01:58:48 +09:00
parent bd23cf693d
commit e56c41cee6
4 changed files with 105 additions and 4 deletions

View File

@@ -1,4 +1,4 @@
use std::collections::BTreeSet;
use std::collections::{BTreeMap, BTreeSet};
use anyhow::Context;
use clap::Parser;
@@ -22,7 +22,7 @@ use crate::{
ClientToServerMessageStream, Request, Response,
print_modify_database_privileges_output_status,
},
types::MySQLDatabase,
types::{MySQLDatabase, MySQLUser},
},
};
@@ -59,6 +59,64 @@ pub struct EditPrivsArgs {
pub yes: bool,
}
async fn users_exist(
server_connection: &mut ClientToServerMessageStream,
privilege_diff: &BTreeSet<DatabasePrivilegesDiff>,
) -> anyhow::Result<BTreeMap<MySQLUser, bool>> {
let user_list = privilege_diff
.iter()
.map(|diff| diff.get_user_name().clone())
.collect();
let message = Request::ListUsers(Some(user_list));
server_connection.send(message).await?;
let result = match server_connection.next().await {
Some(Ok(Response::ListUsers(user_map))) => user_map,
response => {
erroneous_server_response(response)?;
// Unreachable, but needed to satisfy the type checker
BTreeMap::new()
}
};
let result = result
.into_iter()
.map(|(user, user_result)| (user, user_result.is_ok()))
.collect();
Ok(result)
}
async fn databases_exist(
server_connection: &mut ClientToServerMessageStream,
privilege_diff: &BTreeSet<DatabasePrivilegesDiff>,
) -> anyhow::Result<BTreeMap<MySQLDatabase, bool>> {
let database_list = privilege_diff
.iter()
.map(|diff| diff.get_database_name().clone())
.collect();
let message = Request::ListDatabases(Some(database_list));
server_connection.send(message).await?;
let result = match server_connection.next().await {
Some(Ok(Response::ListDatabases(database_map))) => database_map,
response => {
erroneous_server_response(response)?;
// Unreachable, but needed to satisfy the type checker
BTreeMap::new()
}
};
let result = result
.into_iter()
.map(|(database, db_result)| (database, db_result.is_ok()))
.collect();
Ok(result)
}
pub async fn edit_database_privileges(
args: EditPrivsArgs,
mut server_connection: ClientToServerMessageStream,
@@ -100,7 +158,31 @@ pub async fn edit_database_privileges(
edit_privileges_with_editor(&existing_privilege_rows, args.name.as_ref())?;
diff_privileges(&existing_privilege_rows, &privileges_to_change)
};
let diffs = reduce_privilege_diffs(&existing_privilege_rows, diffs)?;
let user_existence_map = users_exist(&mut server_connection, &diffs).await?;
let database_existence_map = databases_exist(&mut server_connection, &diffs).await?;
let diffs = reduce_privilege_diffs(&existing_privilege_rows, diffs)?
.into_iter()
.filter(|diff| {
let database_name = diff.get_database_name();
let username = diff.get_user_name();
if let Some(false) = database_existence_map.get(database_name) {
println!("Database '{}' does not exist.", database_name);
println!("Skipping...");
return false;
}
if let Some(false) = user_existence_map.get(username) {
println!("User '{}' does not exist.", username);
println!("Skipping...");
return false;
}
true
})
.collect::<BTreeSet<_>>();
if diffs.is_empty() {
println!("No changes to make.");

View File

@@ -20,6 +20,7 @@ pub enum ModifyDatabasePrivilegesError {
UserSanitizationError(NameValidationError),
UserOwnershipError(OwnerValidationError),
DatabaseDoesNotExist,
UserDoesNotExist,
DiffDoesNotApply(DiffDoesNotApplyError),
MySqlError(String),
}
@@ -68,6 +69,9 @@ impl ModifyDatabasePrivilegesError {
ModifyDatabasePrivilegesError::DatabaseDoesNotExist => {
format!("Database '{}' does not exist.", database_name)
}
ModifyDatabasePrivilegesError::UserDoesNotExist => {
format!("User '{}' does not exist.", username)
}
ModifyDatabasePrivilegesError::DiffDoesNotApply(diff) => {
format!(
"Could not apply privilege change:\n{}",
@@ -98,6 +102,9 @@ impl ModifyDatabasePrivilegesError {
ModifyDatabasePrivilegesError::DatabaseDoesNotExist => {
"database-does-not-exist".to_string()
}
ModifyDatabasePrivilegesError::UserDoesNotExist => {
"user-does-not-exist".to_string()
}
ModifyDatabasePrivilegesError::DiffDoesNotApply(err) => {
format!("diff-does-not-apply/{}", err.error_type())
}

View File

@@ -38,6 +38,7 @@ use crate::{
common::{create_user_group_matching_regex, try_get_with_binary_fallback},
input_sanitization::{quote_identifier, validate_name, validate_ownership_by_unix_user},
sql::database_operations::unsafe_database_exists,
sql::user_operations::unsafe_user_exists,
},
};
@@ -446,6 +447,17 @@ pub async fn apply_privilege_diffs(
continue;
}
if !unsafe_user_exists(diff.get_user_name(), connection)
.await
.unwrap()
{
results.insert(
key,
Err(ModifyDatabasePrivilegesError::UserDoesNotExist),
);
continue;
}
if let Err(err) = validate_diff(&diff, connection).await {
results.insert(key, Err(err));
continue;

View File

@@ -26,7 +26,7 @@ use crate::{
};
// NOTE: this function is unsafe because it does no input validation.
async fn unsafe_user_exists(
pub(super) async fn unsafe_user_exists(
db_user: &str,
connection: &mut MySqlConnection,
) -> Result<bool, sqlx::Error> {