Implement denylists
All checks were successful
Build and test / check-license (push) Successful in 1m38s
Build and test / check (push) Successful in 1m51s
Build and test / build (push) Successful in 2m40s
Build and test / test (push) Successful in 4m25s
Build and test / docs (push) Successful in 6m1s

This commit is contained in:
2025-12-15 15:17:37 +09:00
parent 45cefb8af4
commit 8b4d549e18
29 changed files with 743 additions and 188 deletions

View File

@@ -17,16 +17,19 @@ pub use create_user::*;
pub use drop_db::*;
pub use drop_user::*;
pub use edit_privs::*;
use futures_util::SinkExt;
use itertools::Itertools;
pub use lock_user::*;
pub use passwd_user::*;
pub use show_db::*;
pub use show_privs::*;
pub use show_user::*;
use tokio_stream::StreamExt;
pub use unlock_user::*;
use clap::Subcommand;
use crate::core::protocol::{ClientToServerMessageStream, Response};
use crate::core::protocol::{ClientToServerMessageStream, Request, Response};
#[derive(Subcommand, Debug, Clone)]
#[command(subcommand_required = true)]
@@ -183,3 +186,23 @@ pub fn erroneous_server_response(
}
}
}
pub async fn print_authorization_owner_hint(
server_connection: &mut ClientToServerMessageStream,
) -> anyhow::Result<()> {
server_connection
.send(Request::ListValidNamePrefixes)
.await?;
let response = match server_connection.next().await {
Some(Ok(Response::ListValidNamePrefixes(prefixes))) => prefixes,
response => return erroneous_server_response(response),
};
println!(
"Note: You are allowed to manage databases and users with the following prefixes:\n{}",
response.into_iter().map(|p| format!(" - {}", p)).join("\n")
);
Ok(())
}

View File

@@ -3,11 +3,12 @@ use futures_util::SinkExt;
use tokio_stream::StreamExt;
use crate::{
client::commands::erroneous_server_response,
client::commands::{erroneous_server_response, print_authorization_owner_hint},
core::{
protocol::{
ClientToServerMessageStream, Request, Response, print_create_databases_output_status,
print_create_databases_output_status_json,
ClientToServerMessageStream, CreateDatabaseError, Request, Response,
print_create_databases_output_status, print_create_databases_output_status_json,
request_validation::ValidationError,
},
types::MySQLDatabase,
},
@@ -40,13 +41,24 @@ pub async fn create_databases(
response => return erroneous_server_response(response),
};
server_connection.send(Request::Exit).await?;
if args.json {
print_create_databases_output_status_json(&result);
} else {
print_create_databases_output_status(&result);
if result.iter().any(|(_, res)| {
matches!(
res,
Err(CreateDatabaseError::ValidationError(
ValidationError::AuthorizationError(_)
))
)
}) {
print_authorization_owner_hint(&mut server_connection).await?
}
}
server_connection.send(Request::Exit).await?;
Ok(())
}

View File

@@ -4,11 +4,15 @@ use futures_util::SinkExt;
use tokio_stream::StreamExt;
use crate::{
client::commands::{erroneous_server_response, read_password_from_stdin_with_double_check},
client::commands::{
erroneous_server_response, print_authorization_owner_hint,
read_password_from_stdin_with_double_check,
},
core::{
protocol::{
ClientToServerMessageStream, Request, Response, print_create_users_output_status,
print_create_users_output_status_json, print_set_password_output_status,
ClientToServerMessageStream, CreateUserError, Request, Response,
print_create_users_output_status, print_create_users_output_status_json,
print_set_password_output_status, request_validation::ValidationError,
},
types::MySQLUser,
},
@@ -55,6 +59,17 @@ pub async fn create_users(
} else {
print_create_users_output_status(&result);
if result.iter().any(|(_, res)| {
matches!(
res,
Err(CreateUserError::ValidationError(
ValidationError::AuthorizationError(_)
))
)
}) {
print_authorization_owner_hint(&mut server_connection).await?
}
let successfully_created_users = result
.iter()
.filter_map(|(username, result)| result.as_ref().ok().map(|_| username))

View File

@@ -5,12 +5,13 @@ use futures_util::SinkExt;
use tokio_stream::StreamExt;
use crate::{
client::commands::erroneous_server_response,
client::commands::{erroneous_server_response, print_authorization_owner_hint},
core::{
completion::mysql_database_completer,
protocol::{
ClientToServerMessageStream, Request, Response, print_drop_databases_output_status,
print_drop_databases_output_status_json,
ClientToServerMessageStream, DropDatabaseError, Request, Response,
print_drop_databases_output_status, print_drop_databases_output_status_json,
request_validation::ValidationError,
},
types::MySQLDatabase,
},
@@ -66,13 +67,24 @@ pub async fn drop_databases(
response => return erroneous_server_response(response),
};
server_connection.send(Request::Exit).await?;
if args.json {
print_drop_databases_output_status_json(&result);
} else {
print_drop_databases_output_status(&result);
if result.iter().any(|(_, res)| {
matches!(
res,
Err(DropDatabaseError::ValidationError(
ValidationError::AuthorizationError(_)
))
)
}) {
print_authorization_owner_hint(&mut server_connection).await?
}
};
server_connection.send(Request::Exit).await?;
Ok(())
}

View File

@@ -5,12 +5,13 @@ use futures_util::SinkExt;
use tokio_stream::StreamExt;
use crate::{
client::commands::erroneous_server_response,
client::commands::{erroneous_server_response, print_authorization_owner_hint},
core::{
completion::mysql_user_completer,
protocol::{
ClientToServerMessageStream, Request, Response, print_drop_users_output_status,
print_drop_users_output_status_json,
ClientToServerMessageStream, DropUserError, Request, Response,
print_drop_users_output_status, print_drop_users_output_status_json,
request_validation::ValidationError,
},
types::MySQLUser,
},
@@ -70,13 +71,24 @@ pub async fn drop_users(
response => return erroneous_server_response(response),
};
server_connection.send(Request::Exit).await?;
if args.json {
print_drop_users_output_status_json(&result);
} else {
print_drop_users_output_status(&result);
if result.iter().any(|(_, res)| {
matches!(
res,
Err(DropUserError::ValidationError(
ValidationError::AuthorizationError(_)
))
)
}) {
print_authorization_owner_hint(&mut server_connection).await?
}
}
server_connection.send(Request::Exit).await?;
Ok(())
}

View File

@@ -9,7 +9,7 @@ use nix::unistd::{User, getuid};
use tokio_stream::StreamExt;
use crate::{
client::commands::erroneous_server_response,
client::commands::{erroneous_server_response, print_authorization_owner_hint},
core::{
completion::{mysql_database_completer, mysql_user_completer},
database_privileges::{
@@ -19,8 +19,8 @@ use crate::{
parse_privilege_data_from_editor_content, reduce_privilege_diffs,
},
protocol::{
ClientToServerMessageStream, Request, Response,
print_modify_database_privileges_output_status,
ClientToServerMessageStream, ModifyDatabasePrivilegesError, Request, Response,
print_modify_database_privileges_output_status, request_validation::ValidationError,
},
types::{MySQLDatabase, MySQLUser},
},
@@ -219,6 +219,8 @@ pub async fn edit_database_privileges(
diff_privileges(&existing_privilege_rows, &privileges_to_change)
};
// TODO: validate authorization before existence
let user_existence_map = users_exist(&mut server_connection, &diffs).await?;
let database_existence_map = databases_exist(&mut server_connection, &diffs).await?;
@@ -274,6 +276,19 @@ pub async fn edit_database_privileges(
print_modify_database_privileges_output_status(&result);
if result.iter().any(|(_, res)| {
matches!(
res,
Err(ModifyDatabasePrivilegesError::UserValidationError(
ValidationError::AuthorizationError(_)
) | ModifyDatabasePrivilegesError::DatabaseValidationError(
ValidationError::AuthorizationError(_)
))
)
}) {
print_authorization_owner_hint(&mut server_connection).await?
}
server_connection.send(Request::Exit).await?;
Ok(())

View File

@@ -4,12 +4,13 @@ use futures_util::SinkExt;
use tokio_stream::StreamExt;
use crate::{
client::commands::erroneous_server_response,
client::commands::{erroneous_server_response, print_authorization_owner_hint},
core::{
completion::mysql_user_completer,
protocol::{
ClientToServerMessageStream, Request, Response, print_lock_users_output_status,
print_lock_users_output_status_json,
ClientToServerMessageStream, LockUserError, Request, Response,
print_lock_users_output_status, print_lock_users_output_status_json,
request_validation::ValidationError,
},
types::MySQLUser,
},
@@ -47,13 +48,24 @@ pub async fn lock_users(
response => return erroneous_server_response(response),
};
server_connection.send(Request::Exit).await?;
if args.json {
print_lock_users_output_status_json(&result);
} else {
print_lock_users_output_status(&result);
if result.iter().any(|(_, res)| {
matches!(
res,
Err(LockUserError::ValidationError(
ValidationError::AuthorizationError(_)
))
)
}) {
print_authorization_owner_hint(&mut server_connection).await?
}
}
server_connection.send(Request::Exit).await?;
Ok(())
}

View File

@@ -8,12 +8,12 @@ use futures_util::SinkExt;
use tokio_stream::StreamExt;
use crate::{
client::commands::erroneous_server_response,
client::commands::{erroneous_server_response, print_authorization_owner_hint},
core::{
completion::mysql_user_completer,
protocol::{
ClientToServerMessageStream, ListUsersError, Request, Response,
print_set_password_output_status,
ClientToServerMessageStream, ListUsersError, Request, Response, SetPasswordError,
print_set_password_output_status, request_validation::ValidationError,
},
types::MySQLUser,
},
@@ -103,9 +103,18 @@ pub async fn passwd_user(
response => return erroneous_server_response(response),
};
server_connection.send(Request::Exit).await?;
print_set_password_output_status(&result, &args.username);
if matches!(
result,
Err(SetPasswordError::ValidationError(
ValidationError::AuthorizationError(_)
))
) {
print_authorization_owner_hint(&mut server_connection).await?
}
server_connection.send(Request::Exit).await?;
Ok(())
}

View File

@@ -4,12 +4,13 @@ use futures_util::SinkExt;
use tokio_stream::StreamExt;
use crate::{
client::commands::erroneous_server_response,
client::commands::{erroneous_server_response, print_authorization_owner_hint},
core::{
completion::mysql_database_completer,
protocol::{
ClientToServerMessageStream, Request, Response, print_list_databases_output_status,
print_list_databases_output_status_json,
ClientToServerMessageStream, ListDatabasesError, Request, Response,
print_list_databases_output_status, print_list_databases_output_status_json,
request_validation::ValidationError,
},
types::MySQLDatabase,
},
@@ -60,14 +61,25 @@ pub async fn show_databases(
response => return erroneous_server_response(response),
};
server_connection.send(Request::Exit).await?;
if args.json {
print_list_databases_output_status_json(&databases);
} else {
print_list_databases_output_status(&databases);
if databases.iter().any(|(_, res)| {
matches!(
res,
Err(ListDatabasesError::ValidationError(
ValidationError::AuthorizationError(_)
))
)
}) {
print_authorization_owner_hint(&mut server_connection).await?
}
}
server_connection.send(Request::Exit).await?;
if args.fail && databases.values().any(|res| res.is_err()) {
std::process::exit(1);
}

View File

@@ -5,12 +5,13 @@ use itertools::Itertools;
use tokio_stream::StreamExt;
use crate::{
client::commands::erroneous_server_response,
client::commands::{erroneous_server_response, print_authorization_owner_hint},
core::{
completion::mysql_database_completer,
protocol::{
ClientToServerMessageStream, Request, Response, print_list_privileges_output_status,
print_list_privileges_output_status_json,
ClientToServerMessageStream, GetDatabasesPrivilegeDataError, Request, Response,
print_list_privileges_output_status, print_list_privileges_output_status_json,
request_validation::ValidationError,
},
types::MySQLDatabase,
},
@@ -68,14 +69,25 @@ pub async fn show_database_privileges(
response => return erroneous_server_response(response),
};
server_connection.send(Request::Exit).await?;
if args.json {
print_list_privileges_output_status_json(&privilege_data);
} else {
print_list_privileges_output_status(&privilege_data, args.long);
if privilege_data.iter().any(|(_, res)| {
matches!(
res,
Err(GetDatabasesPrivilegeDataError::ValidationError(
ValidationError::AuthorizationError(_)
))
)
}) {
print_authorization_owner_hint(&mut server_connection).await?
}
}
server_connection.send(Request::Exit).await?;
if args.fail && privilege_data.values().any(|res| res.is_err()) {
std::process::exit(1);
}

View File

@@ -4,12 +4,13 @@ use futures_util::SinkExt;
use tokio_stream::StreamExt;
use crate::{
client::commands::erroneous_server_response,
client::commands::{erroneous_server_response, print_authorization_owner_hint},
core::{
completion::mysql_user_completer,
protocol::{
ClientToServerMessageStream, Request, Response, print_list_users_output_status,
print_list_users_output_status_json,
ClientToServerMessageStream, ListUsersError, Request, Response,
print_list_users_output_status, print_list_users_output_status_json,
request_validation::ValidationError,
},
types::MySQLUser,
},
@@ -63,14 +64,25 @@ pub async fn show_users(
response => return erroneous_server_response(response),
};
server_connection.send(Request::Exit).await?;
if args.json {
print_list_users_output_status_json(&users);
} else {
print_list_users_output_status(&users);
if users.iter().any(|(_, res)| {
matches!(
res,
Err(ListUsersError::ValidationError(
ValidationError::AuthorizationError(_)
))
)
}) {
print_authorization_owner_hint(&mut server_connection).await?
}
}
server_connection.send(Request::Exit).await?;
if args.fail && users.values().any(|result| result.is_err()) {
std::process::exit(1);
}

View File

@@ -4,12 +4,13 @@ use futures_util::SinkExt;
use tokio_stream::StreamExt;
use crate::{
client::commands::erroneous_server_response,
client::commands::{erroneous_server_response, print_authorization_owner_hint},
core::{
completion::mysql_user_completer,
protocol::{
ClientToServerMessageStream, Request, Response, print_unlock_users_output_status,
print_unlock_users_output_status_json,
ClientToServerMessageStream, Request, Response, UnlockUserError,
print_unlock_users_output_status, print_unlock_users_output_status_json,
request_validation::ValidationError,
},
types::MySQLUser,
},
@@ -47,13 +48,24 @@ pub async fn unlock_users(
response => return erroneous_server_response(response),
};
server_connection.send(Request::Exit).await?;
if args.json {
print_unlock_users_output_status_json(&result);
} else {
print_unlock_users_output_status(&result);
if result.iter().any(|(_, res)| {
matches!(
res,
Err(UnlockUserError::ValidationError(
ValidationError::AuthorizationError(_)
))
)
}) {
print_authorization_owner_hint(&mut server_connection).await?
}
}
server_connection.send(Request::Exit).await?;
Ok(())
}