clippy pedantic fix + get rid of a few unwraps
All checks were successful
All checks were successful
This commit is contained in:
@@ -1,4 +1,9 @@
|
||||
use std::{fs, path::PathBuf, sync::Arc, time::Duration};
|
||||
use std::{
|
||||
fs,
|
||||
path::{Path, PathBuf},
|
||||
sync::Arc,
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
use anyhow::{Context, anyhow};
|
||||
use clap_verbosity_flag::{InfoLevel, Verbosity};
|
||||
@@ -136,7 +141,7 @@ fn connect_to_external_server(
|
||||
Err(e) => match e.kind() {
|
||||
std::io::ErrorKind::NotFound => Err(anyhow::anyhow!("Socket not found")),
|
||||
std::io::ErrorKind::PermissionDenied => Err(anyhow::anyhow!("Permission denied")),
|
||||
_ => Err(anyhow::anyhow!("Failed to connect to socket: {}", e)),
|
||||
_ => Err(anyhow::anyhow!("Failed to connect to socket: {e}")),
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -148,7 +153,7 @@ fn connect_to_external_server(
|
||||
Err(e) => match e.kind() {
|
||||
std::io::ErrorKind::NotFound => Err(anyhow::anyhow!("Socket not found")),
|
||||
std::io::ErrorKind::PermissionDenied => Err(anyhow::anyhow!("Permission denied")),
|
||||
_ => Err(anyhow::anyhow!("Failed to connect to socket: {}", e)),
|
||||
_ => Err(anyhow::anyhow!("Failed to connect to socket: {e}")),
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -192,10 +197,10 @@ fn bootstrap_internal_server_and_drop_privs(
|
||||
}
|
||||
|
||||
tracing::debug!("Starting server with config at {:?}", config_path);
|
||||
let socket = invoke_server_with_config(config_path)?;
|
||||
let socket = invoke_server_with_config(&config_path)?;
|
||||
drop_privs()?;
|
||||
return Ok(socket);
|
||||
};
|
||||
}
|
||||
|
||||
let config_path = PathBuf::from(DEFAULT_CONFIG_PATH);
|
||||
if fs::metadata(&config_path).is_ok() {
|
||||
@@ -203,10 +208,10 @@ fn bootstrap_internal_server_and_drop_privs(
|
||||
anyhow::bail!("Executable is not SUID/SGID - refusing to start internal sever");
|
||||
}
|
||||
tracing::debug!("Starting server with default config at {:?}", config_path);
|
||||
let socket = invoke_server_with_config(config_path)?;
|
||||
let socket = invoke_server_with_config(&config_path)?;
|
||||
drop_privs()?;
|
||||
return Ok(socket);
|
||||
};
|
||||
}
|
||||
|
||||
anyhow::bail!("No config path provided, and no default config found");
|
||||
}
|
||||
@@ -216,7 +221,7 @@ fn bootstrap_internal_server_and_drop_privs(
|
||||
/// Fork a child process to run the server with the provided config.
|
||||
/// The server will exit silently by itself when it is done, and this function
|
||||
/// will only return for the client with the socket for the server.
|
||||
fn invoke_server_with_config(config_path: PathBuf) -> anyhow::Result<StdUnixStream> {
|
||||
fn invoke_server_with_config(config_path: &Path) -> anyhow::Result<StdUnixStream> {
|
||||
let (server_socket, client_socket) = StdUnixStream::pair()?;
|
||||
let unix_user = UnixUser::from_uid(nix::unistd::getuid().as_raw())?;
|
||||
|
||||
@@ -228,18 +233,18 @@ fn invoke_server_with_config(config_path: PathBuf) -> anyhow::Result<StdUnixStre
|
||||
nix::unistd::ForkResult::Child => {
|
||||
tracing::debug!("Running server in child process");
|
||||
|
||||
landlock_restrict_server(Some(config_path.as_path()))
|
||||
landlock_restrict_server(Some(config_path))
|
||||
.context("Failed to apply Landlock restrictions to the server process")?;
|
||||
|
||||
match run_forked_server(config_path, server_socket, unix_user) {
|
||||
match run_forked_server(config_path, server_socket, &unix_user) {
|
||||
Err(e) => Err(e),
|
||||
Ok(_) => unreachable!(),
|
||||
Ok(()) => unreachable!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Construct a MySQL connection pool that consists of exactly one connection.
|
||||
/// Construct a `MySQL` connection pool that consists of exactly one connection.
|
||||
///
|
||||
/// This is used for the internal server in SUID/SGID mode, where the server session
|
||||
/// only ever will get a single client.
|
||||
@@ -273,11 +278,11 @@ async fn construct_single_connection_mysql_pool(
|
||||
/// This function will not return, but will exit the process with a success code.
|
||||
/// The function assumes that it's caller has already forked the process.
|
||||
fn run_forked_server(
|
||||
config_path: PathBuf,
|
||||
config_path: &Path,
|
||||
server_socket: StdUnixStream,
|
||||
unix_user: UnixUser,
|
||||
unix_user: &UnixUser,
|
||||
) -> anyhow::Result<()> {
|
||||
let config = ServerConfig::read_config_from_path(&config_path)
|
||||
let config = ServerConfig::read_config_from_path(config_path)
|
||||
.context("Failed to read server config in forked process")?;
|
||||
|
||||
let group_denylist = if let Some(denylist_path) = &config.authorization.group_denylist_file {
|
||||
@@ -306,7 +311,7 @@ fn run_forked_server(
|
||||
let db_pool = Arc::new(RwLock::new(db_pool));
|
||||
session_handler::session_handler_with_unix_user(
|
||||
socket,
|
||||
&unix_user,
|
||||
unix_user,
|
||||
db_pool,
|
||||
db_is_mariadb,
|
||||
&group_denylist,
|
||||
|
||||
@@ -10,13 +10,13 @@ pub const DEFAULT_CONFIG_PATH: &str = "/etc/muscl/config.toml";
|
||||
pub const DEFAULT_SOCKET_PATH: &str = "/run/muscl/muscl.sock";
|
||||
|
||||
pub const ASCII_BANNER: &str = indoc! {
|
||||
r#"
|
||||
r"
|
||||
__
|
||||
____ ___ __ ____________/ /
|
||||
/ __ `__ \/ / / / ___/ ___/ /
|
||||
/ / / / / / /_/ (__ ) /__/ /
|
||||
/_/ /_/ /_/\__,_/____/\___/_/
|
||||
"#
|
||||
"
|
||||
};
|
||||
|
||||
pub const KIND_REGARDS: &str = concat!(
|
||||
@@ -95,7 +95,7 @@ impl UnixUser {
|
||||
|
||||
Ok(UnixUser {
|
||||
username: libc_user.name,
|
||||
groups: groups.iter().map(|g| g.name.to_owned()).collect(),
|
||||
groups: groups.iter().map(|g| g.name.clone()).collect(),
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@ use crate::{
|
||||
},
|
||||
};
|
||||
|
||||
#[must_use]
|
||||
pub fn mysql_database_completer(current: &std::ffi::OsStr) -> Vec<CompletionCandidate> {
|
||||
match tokio::runtime::Builder::new_current_thread()
|
||||
.enable_all()
|
||||
@@ -20,18 +21,18 @@ pub fn mysql_database_completer(current: &std::ffi::OsStr) -> Vec<CompletionCand
|
||||
Ok(runtime) => match runtime.block_on(mysql_database_completer_(current)) {
|
||||
Ok(completions) => completions,
|
||||
Err(err) => {
|
||||
eprintln!("Error getting MySQL database completions: {}", err);
|
||||
eprintln!("Error getting MySQL database completions: {err}");
|
||||
Vec::new()
|
||||
}
|
||||
},
|
||||
Err(err) => {
|
||||
eprintln!("Error starting Tokio runtime: {}", err);
|
||||
eprintln!("Error starting Tokio runtime: {err}");
|
||||
Vec::new()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Connect to the server to get MySQL database completions.
|
||||
/// Connect to the server to get `MySQL` database completions.
|
||||
async fn mysql_database_completer_(
|
||||
current: &std::ffi::OsStr,
|
||||
) -> anyhow::Result<Vec<CompletionCandidate>> {
|
||||
@@ -44,11 +45,11 @@ async fn mysql_database_completer_(
|
||||
while let Some(Ok(message)) = server_connection.next().await {
|
||||
match message {
|
||||
Response::Error(err) => {
|
||||
anyhow::bail!("{}", err);
|
||||
anyhow::bail!("{err}");
|
||||
}
|
||||
Response::Ready => break,
|
||||
message => {
|
||||
eprintln!("Unexpected message from server: {:?}", message);
|
||||
eprintln!("Unexpected message from server: {message:?}");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -62,7 +63,7 @@ async fn mysql_database_completer_(
|
||||
|
||||
let result = match server_connection.next().await {
|
||||
Some(Ok(Response::CompleteDatabaseName(suggestions))) => suggestions,
|
||||
response => return erroneous_server_response(response).map(|_| vec![]),
|
||||
response => return erroneous_server_response(response).map(|()| vec![]),
|
||||
};
|
||||
|
||||
server_connection.send(Request::Exit).await?;
|
||||
|
||||
@@ -12,6 +12,7 @@ use crate::{
|
||||
},
|
||||
};
|
||||
|
||||
#[must_use]
|
||||
pub fn mysql_user_completer(current: &std::ffi::OsStr) -> Vec<CompletionCandidate> {
|
||||
match tokio::runtime::Builder::new_current_thread()
|
||||
.enable_all()
|
||||
@@ -20,18 +21,18 @@ pub fn mysql_user_completer(current: &std::ffi::OsStr) -> Vec<CompletionCandidat
|
||||
Ok(runtime) => match runtime.block_on(mysql_user_completer_(current)) {
|
||||
Ok(completions) => completions,
|
||||
Err(err) => {
|
||||
eprintln!("Error getting MySQL user completions: {}", err);
|
||||
eprintln!("Error getting MySQL user completions: {err}");
|
||||
Vec::new()
|
||||
}
|
||||
},
|
||||
Err(err) => {
|
||||
eprintln!("Error starting Tokio runtime: {}", err);
|
||||
eprintln!("Error starting Tokio runtime: {err}");
|
||||
Vec::new()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Connect to the server to get MySQL user completions.
|
||||
/// Connect to the server to get `MySQL` user completions.
|
||||
async fn mysql_user_completer_(
|
||||
current: &std::ffi::OsStr,
|
||||
) -> anyhow::Result<Vec<CompletionCandidate>> {
|
||||
@@ -44,11 +45,11 @@ async fn mysql_user_completer_(
|
||||
while let Some(Ok(message)) = server_connection.next().await {
|
||||
match message {
|
||||
Response::Error(err) => {
|
||||
anyhow::bail!("{}", err);
|
||||
anyhow::bail!("{err}");
|
||||
}
|
||||
Response::Ready => break,
|
||||
message => {
|
||||
eprintln!("Unexpected message from server: {:?}", message);
|
||||
eprintln!("Unexpected message from server: {message:?}");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -62,7 +63,7 @@ async fn mysql_user_completer_(
|
||||
|
||||
let result = match server_connection.next().await {
|
||||
Some(Ok(Response::CompleteUserName(suggestions))) => suggestions,
|
||||
response => return erroneous_server_response(response).map(|_| vec![]),
|
||||
response => return erroneous_server_response(response).map(|()| vec![]),
|
||||
};
|
||||
|
||||
server_connection.send(Request::Exit).await?;
|
||||
|
||||
@@ -12,6 +12,7 @@ use crate::{
|
||||
},
|
||||
};
|
||||
|
||||
#[must_use]
|
||||
pub fn prefix_completer(current: &std::ffi::OsStr) -> Vec<CompletionCandidate> {
|
||||
match tokio::runtime::Builder::new_current_thread()
|
||||
.enable_all()
|
||||
@@ -20,18 +21,18 @@ pub fn prefix_completer(current: &std::ffi::OsStr) -> Vec<CompletionCandidate> {
|
||||
Ok(runtime) => match runtime.block_on(prefix_completer_(current)) {
|
||||
Ok(completions) => completions,
|
||||
Err(err) => {
|
||||
eprintln!("Error getting prefix completions: {}", err);
|
||||
eprintln!("Error getting prefix completions: {err}");
|
||||
Vec::new()
|
||||
}
|
||||
},
|
||||
Err(err) => {
|
||||
eprintln!("Error starting Tokio runtime: {}", err);
|
||||
eprintln!("Error starting Tokio runtime: {err}");
|
||||
Vec::new()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Connect to the server to get MySQL user completions.
|
||||
/// Connect to the server to get `MySQL` user completions.
|
||||
async fn prefix_completer_(_current: &std::ffi::OsStr) -> anyhow::Result<Vec<CompletionCandidate>> {
|
||||
let server_connection =
|
||||
bootstrap_server_connection_and_drop_privileges(None, None, Verbosity::new(0, 1))?;
|
||||
@@ -42,11 +43,11 @@ async fn prefix_completer_(_current: &std::ffi::OsStr) -> anyhow::Result<Vec<Com
|
||||
while let Some(Ok(message)) = server_connection.next().await {
|
||||
match message {
|
||||
Response::Error(err) => {
|
||||
anyhow::bail!("{}", err);
|
||||
anyhow::bail!("{err}");
|
||||
}
|
||||
Response::Ready => break,
|
||||
message => {
|
||||
eprintln!("Unexpected message from server: {:?}", message);
|
||||
eprintln!("Unexpected message from server: {message:?}");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -60,7 +61,7 @@ async fn prefix_completer_(_current: &std::ffi::OsStr) -> anyhow::Result<Vec<Com
|
||||
|
||||
let result = match server_connection.next().await {
|
||||
Some(Ok(Response::ListValidNamePrefixes(prefixes))) => prefixes,
|
||||
response => return erroneous_server_response(response).map(|_| vec![]),
|
||||
response => return erroneous_server_response(response).map(|()| vec![]),
|
||||
};
|
||||
|
||||
server_connection.send(Request::Exit).await?;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
//! This module contains some base datastructures and functionality for dealing with
|
||||
//! database privileges in MySQL.
|
||||
//! database privileges in `MySQL`.
|
||||
|
||||
use std::fmt;
|
||||
|
||||
@@ -49,6 +49,7 @@ pub struct DatabasePrivilegeRow {
|
||||
|
||||
impl DatabasePrivilegeRow {
|
||||
/// Gets the value of a privilege by its name as a &str.
|
||||
#[must_use]
|
||||
pub fn get_privilege_by_name(&self, name: &str) -> Option<bool> {
|
||||
match name {
|
||||
"select_priv" => Some(self.select_priv),
|
||||
@@ -83,6 +84,7 @@ impl fmt::Display for DatabasePrivilegeRow {
|
||||
}
|
||||
|
||||
/// Converts a database privilege field name to a human-readable name.
|
||||
#[must_use]
|
||||
pub fn db_priv_field_human_readable_name(name: &str) -> String {
|
||||
match name {
|
||||
"Db" => "Database".to_owned(),
|
||||
@@ -98,12 +100,13 @@ pub fn db_priv_field_human_readable_name(name: &str) -> String {
|
||||
"create_tmp_table_priv" => "Temp".to_owned(),
|
||||
"lock_tables_priv" => "Lock".to_owned(),
|
||||
"references_priv" => "References".to_owned(),
|
||||
_ => format!("Unknown({})", name),
|
||||
_ => format!("Unknown({name})"),
|
||||
}
|
||||
}
|
||||
|
||||
/// Converts a database privilege field name to a single-character name.
|
||||
/// (the characters from the cli privilege editor)
|
||||
#[must_use]
|
||||
pub fn db_priv_field_single_character_name(name: &str) -> &str {
|
||||
match name {
|
||||
"select_priv" => "s",
|
||||
|
||||
@@ -51,9 +51,7 @@ impl DatabasePrivilegeEdit {
|
||||
.map(|c| format!("'{c}'"))
|
||||
.join(", ");
|
||||
anyhow::bail!(
|
||||
"Invalid character(s) in privilege edit entry: {}\n\nValid characters are: {}",
|
||||
invalid_chars,
|
||||
valid_characters,
|
||||
"Invalid character(s) in privilege edit entry: {invalid_chars}\n\nValid characters are: {valid_characters}",
|
||||
);
|
||||
}
|
||||
|
||||
@@ -72,7 +70,7 @@ impl std::fmt::Display for DatabasePrivilegeEdit {
|
||||
DatabasePrivilegeEditEntryType::Remove => write!(f, "-")?,
|
||||
}
|
||||
for priv_char in &self.privileges {
|
||||
write!(f, "{}", priv_char)?;
|
||||
write!(f, "{priv_char}")?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
@@ -99,7 +97,7 @@ impl DatabasePrivilegeEditEntry {
|
||||
/// `database_name:username:[+|-]privileges`
|
||||
///
|
||||
/// where:
|
||||
/// - database_name is the name of the database to edit privileges for
|
||||
/// - `database_name` is the name of the database to edit privileges for
|
||||
/// - username is the name of the user to edit privileges for
|
||||
/// - privileges is a string of characters representing the privileges to add, set or remove
|
||||
/// - the `+` or `-` prefix indicates whether to add or remove the privileges, if omitted the privileges are set directly
|
||||
@@ -107,13 +105,13 @@ impl DatabasePrivilegeEditEntry {
|
||||
pub fn parse_from_str(arg: &str) -> anyhow::Result<Self> {
|
||||
let parts: Vec<&str> = arg.split(':').collect();
|
||||
if parts.len() != 3 {
|
||||
anyhow::bail!("Invalid privilege edit entry format: {}", arg);
|
||||
anyhow::bail!("Invalid privilege edit entry format: {arg}");
|
||||
}
|
||||
|
||||
let (database, user, user_privs) = (parts[0].to_string(), parts[1].to_string(), parts[2]);
|
||||
|
||||
if user.is_empty() {
|
||||
anyhow::bail!("Username cannot be empty in privilege edit entry: {}", arg);
|
||||
anyhow::bail!("Username cannot be empty in privilege edit entry: {arg}");
|
||||
}
|
||||
|
||||
let privilege_edit = DatabasePrivilegeEdit::parse_from_str(user_privs)?;
|
||||
|
||||
@@ -18,6 +18,7 @@ pub enum DatabasePrivilegeChange {
|
||||
}
|
||||
|
||||
impl DatabasePrivilegeChange {
|
||||
#[must_use]
|
||||
pub fn new(p1: bool, p2: bool) -> Option<DatabasePrivilegeChange> {
|
||||
match (p1, p2) {
|
||||
(true, false) => Some(DatabasePrivilegeChange::YesToNo),
|
||||
@@ -49,6 +50,7 @@ pub struct DatabasePrivilegeRowDiff {
|
||||
|
||||
impl DatabasePrivilegeRowDiff {
|
||||
/// Calculates the difference between two [`DatabasePrivilegeRow`] instances.
|
||||
#[must_use]
|
||||
pub fn from_rows(
|
||||
row1: &DatabasePrivilegeRow,
|
||||
row2: &DatabasePrivilegeRow,
|
||||
@@ -56,8 +58,8 @@ impl DatabasePrivilegeRowDiff {
|
||||
debug_assert!(row1.db == row2.db && row1.user == row2.user);
|
||||
|
||||
DatabasePrivilegeRowDiff {
|
||||
db: row1.db.to_owned(),
|
||||
user: row1.user.to_owned(),
|
||||
db: row1.db.clone(),
|
||||
user: row1.user.clone(),
|
||||
select_priv: DatabasePrivilegeChange::new(row1.select_priv, row2.select_priv),
|
||||
insert_priv: DatabasePrivilegeChange::new(row1.insert_priv, row2.insert_priv),
|
||||
update_priv: DatabasePrivilegeChange::new(row1.update_priv, row2.update_priv),
|
||||
@@ -82,6 +84,7 @@ impl DatabasePrivilegeRowDiff {
|
||||
}
|
||||
|
||||
/// Returns true if there are no changes in this diff.
|
||||
#[must_use]
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.select_priv.is_none()
|
||||
&& self.insert_priv.is_none()
|
||||
@@ -113,7 +116,7 @@ impl DatabasePrivilegeRowDiff {
|
||||
"create_tmp_table_priv" => Ok(self.create_tmp_table_priv),
|
||||
"lock_tables_priv" => Ok(self.lock_tables_priv),
|
||||
"references_priv" => Ok(self.references_priv),
|
||||
_ => anyhow::bail!("Unknown privilege name: {}", privilege_name),
|
||||
_ => anyhow::bail!("Unknown privilege name: {privilege_name}"),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -159,7 +162,7 @@ impl DatabasePrivilegeRowDiff {
|
||||
/// Removes any no-op changes from the diff, based on the original privilege row.
|
||||
fn remove_noops(&mut self, from: &DatabasePrivilegeRow) {
|
||||
fn new_value(
|
||||
change: &Option<DatabasePrivilegeChange>,
|
||||
change: Option<&DatabasePrivilegeChange>,
|
||||
from_value: bool,
|
||||
) -> Option<DatabasePrivilegeChange> {
|
||||
change.as_ref().and_then(|c| match c {
|
||||
@@ -173,22 +176,24 @@ impl DatabasePrivilegeRowDiff {
|
||||
})
|
||||
}
|
||||
|
||||
self.select_priv = new_value(&self.select_priv, from.select_priv);
|
||||
self.insert_priv = new_value(&self.insert_priv, from.insert_priv);
|
||||
self.update_priv = new_value(&self.update_priv, from.update_priv);
|
||||
self.delete_priv = new_value(&self.delete_priv, from.delete_priv);
|
||||
self.create_priv = new_value(&self.create_priv, from.create_priv);
|
||||
self.drop_priv = new_value(&self.drop_priv, from.drop_priv);
|
||||
self.alter_priv = new_value(&self.alter_priv, from.alter_priv);
|
||||
self.index_priv = new_value(&self.index_priv, from.index_priv);
|
||||
self.create_tmp_table_priv =
|
||||
new_value(&self.create_tmp_table_priv, from.create_tmp_table_priv);
|
||||
self.lock_tables_priv = new_value(&self.lock_tables_priv, from.lock_tables_priv);
|
||||
self.references_priv = new_value(&self.references_priv, from.references_priv);
|
||||
self.select_priv = new_value(self.select_priv.as_ref(), from.select_priv);
|
||||
self.insert_priv = new_value(self.insert_priv.as_ref(), from.insert_priv);
|
||||
self.update_priv = new_value(self.update_priv.as_ref(), from.update_priv);
|
||||
self.delete_priv = new_value(self.delete_priv.as_ref(), from.delete_priv);
|
||||
self.create_priv = new_value(self.create_priv.as_ref(), from.create_priv);
|
||||
self.drop_priv = new_value(self.drop_priv.as_ref(), from.drop_priv);
|
||||
self.alter_priv = new_value(self.alter_priv.as_ref(), from.alter_priv);
|
||||
self.index_priv = new_value(self.index_priv.as_ref(), from.index_priv);
|
||||
self.create_tmp_table_priv = new_value(
|
||||
self.create_tmp_table_priv.as_ref(),
|
||||
from.create_tmp_table_priv,
|
||||
);
|
||||
self.lock_tables_priv = new_value(self.lock_tables_priv.as_ref(), from.lock_tables_priv);
|
||||
self.references_priv = new_value(self.references_priv.as_ref(), from.references_priv);
|
||||
}
|
||||
|
||||
fn apply(&self, base: &mut DatabasePrivilegeRow) {
|
||||
fn apply_change(change: &Option<DatabasePrivilegeChange>, target: &mut bool) {
|
||||
fn apply_change(change: Option<&DatabasePrivilegeChange>, target: &mut bool) {
|
||||
match change {
|
||||
Some(DatabasePrivilegeChange::YesToNo) => *target = false,
|
||||
Some(DatabasePrivilegeChange::NoToYes) => *target = true,
|
||||
@@ -196,17 +201,20 @@ impl DatabasePrivilegeRowDiff {
|
||||
}
|
||||
}
|
||||
|
||||
apply_change(&self.select_priv, &mut base.select_priv);
|
||||
apply_change(&self.insert_priv, &mut base.insert_priv);
|
||||
apply_change(&self.update_priv, &mut base.update_priv);
|
||||
apply_change(&self.delete_priv, &mut base.delete_priv);
|
||||
apply_change(&self.create_priv, &mut base.create_priv);
|
||||
apply_change(&self.drop_priv, &mut base.drop_priv);
|
||||
apply_change(&self.alter_priv, &mut base.alter_priv);
|
||||
apply_change(&self.index_priv, &mut base.index_priv);
|
||||
apply_change(&self.create_tmp_table_priv, &mut base.create_tmp_table_priv);
|
||||
apply_change(&self.lock_tables_priv, &mut base.lock_tables_priv);
|
||||
apply_change(&self.references_priv, &mut base.references_priv);
|
||||
apply_change(self.select_priv.as_ref(), &mut base.select_priv);
|
||||
apply_change(self.insert_priv.as_ref(), &mut base.insert_priv);
|
||||
apply_change(self.update_priv.as_ref(), &mut base.update_priv);
|
||||
apply_change(self.delete_priv.as_ref(), &mut base.delete_priv);
|
||||
apply_change(self.create_priv.as_ref(), &mut base.create_priv);
|
||||
apply_change(self.drop_priv.as_ref(), &mut base.drop_priv);
|
||||
apply_change(self.alter_priv.as_ref(), &mut base.alter_priv);
|
||||
apply_change(self.index_priv.as_ref(), &mut base.index_priv);
|
||||
apply_change(
|
||||
self.create_tmp_table_priv.as_ref(),
|
||||
&mut base.create_tmp_table_priv,
|
||||
);
|
||||
apply_change(self.lock_tables_priv.as_ref(), &mut base.lock_tables_priv);
|
||||
apply_change(self.references_priv.as_ref(), &mut base.references_priv);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -214,7 +222,7 @@ impl fmt::Display for DatabasePrivilegeRowDiff {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
fn format_change(
|
||||
f: &mut fmt::Formatter<'_>,
|
||||
change: &Option<DatabasePrivilegeChange>,
|
||||
change: Option<DatabasePrivilegeChange>,
|
||||
field_name: &str,
|
||||
) -> fmt::Result {
|
||||
if let Some(change) = change {
|
||||
@@ -233,17 +241,17 @@ impl fmt::Display for DatabasePrivilegeRowDiff {
|
||||
}
|
||||
}
|
||||
|
||||
format_change(f, &self.select_priv, "select_priv")?;
|
||||
format_change(f, &self.insert_priv, "insert_priv")?;
|
||||
format_change(f, &self.update_priv, "update_priv")?;
|
||||
format_change(f, &self.delete_priv, "delete_priv")?;
|
||||
format_change(f, &self.create_priv, "create_priv")?;
|
||||
format_change(f, &self.drop_priv, "drop_priv")?;
|
||||
format_change(f, &self.alter_priv, "alter_priv")?;
|
||||
format_change(f, &self.index_priv, "index_priv")?;
|
||||
format_change(f, &self.create_tmp_table_priv, "create_tmp_table_priv")?;
|
||||
format_change(f, &self.lock_tables_priv, "lock_tables_priv")?;
|
||||
format_change(f, &self.references_priv, "references_priv")?;
|
||||
format_change(f, self.select_priv, "select_priv")?;
|
||||
format_change(f, self.insert_priv, "insert_priv")?;
|
||||
format_change(f, self.update_priv, "update_priv")?;
|
||||
format_change(f, self.delete_priv, "delete_priv")?;
|
||||
format_change(f, self.create_priv, "create_priv")?;
|
||||
format_change(f, self.drop_priv, "drop_priv")?;
|
||||
format_change(f, self.alter_priv, "alter_priv")?;
|
||||
format_change(f, self.index_priv, "index_priv")?;
|
||||
format_change(f, self.create_tmp_table_priv, "create_tmp_table_priv")?;
|
||||
format_change(f, self.lock_tables_priv, "lock_tables_priv")?;
|
||||
format_change(f, self.references_priv, "references_priv")?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -259,6 +267,7 @@ pub enum DatabasePrivilegesDiff {
|
||||
}
|
||||
|
||||
impl DatabasePrivilegesDiff {
|
||||
#[must_use]
|
||||
pub fn get_database_name(&self) -> &MySQLDatabase {
|
||||
match self {
|
||||
DatabasePrivilegesDiff::New(p) => &p.db,
|
||||
@@ -268,6 +277,7 @@ impl DatabasePrivilegesDiff {
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn get_user_name(&self) -> &MySQLUser {
|
||||
match self {
|
||||
DatabasePrivilegesDiff::New(p) => &p.user,
|
||||
@@ -305,7 +315,7 @@ impl DatabasePrivilegesDiff {
|
||||
}
|
||||
|
||||
if matches!(self, DatabasePrivilegesDiff::Noop { .. }) {
|
||||
*self = other.to_owned();
|
||||
other.clone_into(self);
|
||||
return Ok(());
|
||||
} else if matches!(other, DatabasePrivilegesDiff::Noop { .. }) {
|
||||
return Ok(());
|
||||
@@ -327,8 +337,8 @@ impl DatabasePrivilegesDiff {
|
||||
inner_diff.mappend(modified);
|
||||
|
||||
if inner_diff.is_empty() {
|
||||
let db = inner_diff.db.to_owned();
|
||||
let user = inner_diff.user.to_owned();
|
||||
let db = inner_diff.db.clone();
|
||||
let user = inner_diff.user.clone();
|
||||
*self = DatabasePrivilegesDiff::Noop { db, user };
|
||||
}
|
||||
}
|
||||
@@ -352,28 +362,27 @@ pub type DatabasePrivilegeState<'a> = &'a [DatabasePrivilegeRow];
|
||||
/// This function calculates the differences between two sets of database privileges.
|
||||
/// It returns a set of [`DatabasePrivilegesDiff`] that can be used to display or
|
||||
/// apply a set of privilege modifications to the database.
|
||||
#[must_use]
|
||||
pub fn diff_privileges(
|
||||
from: DatabasePrivilegeState<'_>,
|
||||
to: &[DatabasePrivilegeRow],
|
||||
) -> BTreeSet<DatabasePrivilegesDiff> {
|
||||
let from_lookup_table: HashMap<(MySQLDatabase, MySQLUser), DatabasePrivilegeRow> =
|
||||
HashMap::from_iter(
|
||||
from.iter()
|
||||
.cloned()
|
||||
.map(|p| ((p.db.to_owned(), p.user.to_owned()), p)),
|
||||
);
|
||||
let from_lookup_table: HashMap<(MySQLDatabase, MySQLUser), DatabasePrivilegeRow> = from
|
||||
.iter()
|
||||
.cloned()
|
||||
.map(|p| ((p.db.clone(), p.user.clone()), p))
|
||||
.collect();
|
||||
|
||||
let to_lookup_table: HashMap<(MySQLDatabase, MySQLUser), DatabasePrivilegeRow> =
|
||||
HashMap::from_iter(
|
||||
to.iter()
|
||||
.cloned()
|
||||
.map(|p| ((p.db.to_owned(), p.user.to_owned()), p)),
|
||||
);
|
||||
let to_lookup_table: HashMap<(MySQLDatabase, MySQLUser), DatabasePrivilegeRow> = to
|
||||
.iter()
|
||||
.cloned()
|
||||
.map(|p| ((p.db.clone(), p.user.clone()), p))
|
||||
.collect();
|
||||
|
||||
let mut result = BTreeSet::new();
|
||||
|
||||
for p in to {
|
||||
if let Some(old_p) = from_lookup_table.get(&(p.db.to_owned(), p.user.to_owned())) {
|
||||
if let Some(old_p) = from_lookup_table.get(&(p.db.clone(), p.user.clone())) {
|
||||
let diff = DatabasePrivilegeRowDiff::from_rows(old_p, p);
|
||||
if !diff.is_empty() {
|
||||
result.insert(DatabasePrivilegesDiff::Modified(diff));
|
||||
@@ -384,7 +393,7 @@ pub fn diff_privileges(
|
||||
}
|
||||
|
||||
for p in from {
|
||||
if !to_lookup_table.contains_key(&(p.db.to_owned(), p.user.to_owned())) {
|
||||
if !to_lookup_table.contains_key(&(p.db.clone(), p.user.clone())) {
|
||||
result.insert(DatabasePrivilegesDiff::Deleted(p.to_owned()));
|
||||
}
|
||||
}
|
||||
@@ -400,17 +409,16 @@ pub fn create_or_modify_privilege_rows(
|
||||
from: DatabasePrivilegeState<'_>,
|
||||
to: &BTreeSet<DatabasePrivilegeRowDiff>,
|
||||
) -> anyhow::Result<BTreeSet<DatabasePrivilegesDiff>> {
|
||||
let from_lookup_table: HashMap<(MySQLDatabase, MySQLUser), DatabasePrivilegeRow> =
|
||||
HashMap::from_iter(
|
||||
from.iter()
|
||||
.cloned()
|
||||
.map(|p| ((p.db.to_owned(), p.user.to_owned()), p)),
|
||||
);
|
||||
let from_lookup_table: HashMap<(MySQLDatabase, MySQLUser), DatabasePrivilegeRow> = from
|
||||
.iter()
|
||||
.cloned()
|
||||
.map(|p| ((p.db.clone(), p.user.clone()), p))
|
||||
.collect();
|
||||
|
||||
let mut result = BTreeSet::new();
|
||||
|
||||
for diff in to {
|
||||
if let Some(old_p) = from_lookup_table.get(&(diff.db.to_owned(), diff.user.to_owned())) {
|
||||
if let Some(old_p) = from_lookup_table.get(&(diff.db.clone(), diff.user.clone())) {
|
||||
let mut modified_diff = diff.to_owned();
|
||||
modified_diff.remove_noops(old_p);
|
||||
if !modified_diff.is_empty() {
|
||||
@@ -418,8 +426,8 @@ pub fn create_or_modify_privilege_rows(
|
||||
}
|
||||
} else {
|
||||
let mut new_row = DatabasePrivilegeRow {
|
||||
db: diff.db.to_owned(),
|
||||
user: diff.user.to_owned(),
|
||||
db: diff.db.clone(),
|
||||
user: diff.user.clone(),
|
||||
select_priv: false,
|
||||
insert_priv: false,
|
||||
update_priv: false,
|
||||
@@ -450,12 +458,11 @@ pub fn reduce_privilege_diffs(
|
||||
from: DatabasePrivilegeState<'_>,
|
||||
to: BTreeSet<DatabasePrivilegesDiff>,
|
||||
) -> anyhow::Result<BTreeSet<DatabasePrivilegesDiff>> {
|
||||
let from_lookup_table: HashMap<(MySQLDatabase, MySQLUser), DatabasePrivilegeRow> =
|
||||
HashMap::from_iter(
|
||||
from.iter()
|
||||
.cloned()
|
||||
.map(|p| ((p.db.to_owned(), p.user.to_owned()), p)),
|
||||
);
|
||||
let from_lookup_table: HashMap<(MySQLDatabase, MySQLUser), DatabasePrivilegeRow> = from
|
||||
.iter()
|
||||
.cloned()
|
||||
.map(|p| ((p.db.clone(), p.user.clone()), p))
|
||||
.collect();
|
||||
|
||||
let mut result: HashMap<(MySQLDatabase, MySQLUser), DatabasePrivilegesDiff> = from_lookup_table
|
||||
.iter()
|
||||
@@ -481,19 +488,19 @@ pub fn reduce_privilege_diffs(
|
||||
existing_diff.mappend(&diff)?;
|
||||
}
|
||||
Entry::Vacant(vacant_entry) => {
|
||||
vacant_entry.insert(diff.to_owned());
|
||||
vacant_entry.insert(diff.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (key, diff) in result.iter_mut() {
|
||||
for (key, diff) in &mut result {
|
||||
if let Some(from_row) = from_lookup_table.get(key)
|
||||
&& let DatabasePrivilegesDiff::Modified(modified_diff) = diff
|
||||
{
|
||||
modified_diff.remove_noops(from_row);
|
||||
if modified_diff.is_empty() {
|
||||
let db = modified_diff.db.to_owned();
|
||||
let user = modified_diff.user.to_owned();
|
||||
let db = modified_diff.db.clone();
|
||||
let user = modified_diff.user.clone();
|
||||
*diff = DatabasePrivilegesDiff::Noop { db, user };
|
||||
}
|
||||
}
|
||||
@@ -506,6 +513,7 @@ pub fn reduce_privilege_diffs(
|
||||
}
|
||||
|
||||
/// Renders a set of [`DatabasePrivilegesDiff`] into a human-readable formatted table.
|
||||
#[must_use]
|
||||
pub fn display_privilege_diffs(diffs: &BTreeSet<DatabasePrivilegesDiff>) -> String {
|
||||
let mut table = Table::new();
|
||||
table.set_titles(row!["Database", "User", "Privilege diff",]);
|
||||
|
||||
@@ -13,6 +13,7 @@ use itertools::Itertools;
|
||||
use std::cmp::max;
|
||||
|
||||
/// Generates a single row of the privileges table for the editor.
|
||||
#[must_use]
|
||||
pub fn format_privileges_line_for_editor(
|
||||
privs: &DatabasePrivilegeRow,
|
||||
database_name_len: usize,
|
||||
@@ -25,6 +26,7 @@ pub fn format_privileges_line_for_editor(
|
||||
"User" => format!("{:width$}", privs.user, width = username_len),
|
||||
privilege => format!(
|
||||
"{:width$}",
|
||||
// SAFETY: unwrap is safe here because the field names are static
|
||||
yn(privs.get_privilege_by_name(privilege).unwrap()),
|
||||
width = db_priv_field_human_readable_name(privilege).len()
|
||||
),
|
||||
@@ -34,14 +36,14 @@ pub fn format_privileges_line_for_editor(
|
||||
.to_string()
|
||||
}
|
||||
|
||||
const EDITOR_COMMENT: &str = r#"
|
||||
const EDITOR_COMMENT: &str = r"
|
||||
# Welcome to the privilege editor.
|
||||
# Each line defines what privileges a single user has on a single database.
|
||||
# The first two columns respectively represent the database name and the user, and the remaining columns are the privileges.
|
||||
# If the user should have a certain privilege, write 'Y', otherwise write 'N'.
|
||||
#
|
||||
# Lines starting with '#' are comments and will be ignored.
|
||||
"#;
|
||||
";
|
||||
|
||||
/// Generates the content for the privilege editor.
|
||||
///
|
||||
@@ -52,9 +54,9 @@ pub fn generate_editor_content_from_privilege_data(
|
||||
unix_user: &str,
|
||||
database_name: Option<&MySQLDatabase>,
|
||||
) -> String {
|
||||
let example_user = format!("{}_user", unix_user);
|
||||
let example_user = format!("{unix_user}_user");
|
||||
let example_db = database_name
|
||||
.unwrap_or(&format!("{}_db", unix_user).into())
|
||||
.unwrap_or(&format!("{unix_user}_db").into())
|
||||
.to_string();
|
||||
|
||||
// NOTE: `.max()`` fails when the iterator is empty.
|
||||
@@ -114,7 +116,7 @@ pub fn generate_editor_content_from_privilege_data(
|
||||
EDITOR_COMMENT,
|
||||
header.join(" "),
|
||||
if privilege_data.is_empty() {
|
||||
format!("# {}", example_line)
|
||||
format!("# {example_line}")
|
||||
} else {
|
||||
privilege_data
|
||||
.iter()
|
||||
@@ -145,11 +147,8 @@ enum PrivilegeRowParseResult {
|
||||
fn parse_privilege_cell_from_editor(yn: &str, name: &str) -> anyhow::Result<bool> {
|
||||
let human_readable_name = db_priv_field_human_readable_name(name);
|
||||
rev_yn(yn)
|
||||
.ok_or_else(|| anyhow!("Expected Y or N, found {}", yn))
|
||||
.context(format!(
|
||||
"Could not parse '{}' privilege",
|
||||
human_readable_name
|
||||
))
|
||||
.ok_or_else(|| anyhow!("Expected Y or N, found {yn}"))
|
||||
.context(format!("Could not parse '{human_readable_name}' privilege"))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
@@ -272,12 +271,12 @@ fn parse_privilege_row_from_editor(row: &str) -> PrivilegeRowParseResult {
|
||||
}
|
||||
|
||||
pub fn parse_privilege_data_from_editor_content(
|
||||
content: String,
|
||||
content: &str,
|
||||
) -> anyhow::Result<Vec<DatabasePrivilegeRow>> {
|
||||
content
|
||||
.trim()
|
||||
.split('\n')
|
||||
.map(|line| line.trim())
|
||||
.lines()
|
||||
.map(str::trim)
|
||||
.enumerate()
|
||||
.map(|(i, line)| {
|
||||
let mut header: Vec<_> = DATABASE_PRIVILEGE_FIELDS
|
||||
@@ -314,7 +313,7 @@ pub fn parse_privilege_data_from_editor_content(
|
||||
PrivilegeRowParseResult::Empty => Ok(None),
|
||||
}
|
||||
})
|
||||
.filter_map(|result| result.transpose())
|
||||
.filter_map(std::result::Result::transpose)
|
||||
.collect::<anyhow::Result<Vec<DatabasePrivilegeRow>>>()
|
||||
}
|
||||
|
||||
@@ -417,7 +416,7 @@ mod tests {
|
||||
|
||||
let content = generate_editor_content_from_privilege_data(&permissions, "user", None);
|
||||
|
||||
let parsed_permissions = parse_privilege_data_from_editor_content(content).unwrap();
|
||||
let parsed_permissions = parse_privilege_data_from_editor_content(&content).unwrap();
|
||||
|
||||
assert_eq!(permissions, parsed_permissions);
|
||||
}
|
||||
|
||||
@@ -57,10 +57,12 @@ pub fn print_check_authorization_output_status_json(output: &CheckAuthorizationR
|
||||
}
|
||||
|
||||
impl CheckAuthorizationError {
|
||||
#[must_use]
|
||||
pub fn to_error_message(&self, db_or_user: &DbOrUser) -> String {
|
||||
self.0.to_error_message(db_or_user.clone())
|
||||
self.0.to_error_message(db_or_user)
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn error_type(&self) -> String {
|
||||
self.0.error_type()
|
||||
}
|
||||
|
||||
@@ -29,7 +29,7 @@ pub fn print_create_databases_output_status(output: &CreateDatabasesResponse) {
|
||||
for (database_name, result) in output {
|
||||
match result {
|
||||
Ok(()) => {
|
||||
println!("Database '{}' created successfully.", database_name);
|
||||
println!("Database '{database_name}' created successfully.");
|
||||
}
|
||||
Err(err) => {
|
||||
eprintln!("{}", err.to_error_message(database_name));
|
||||
@@ -63,20 +63,22 @@ pub fn print_create_databases_output_status_json(output: &CreateDatabasesRespons
|
||||
}
|
||||
|
||||
impl CreateDatabaseError {
|
||||
#[must_use]
|
||||
pub fn to_error_message(&self, database_name: &MySQLDatabase) -> String {
|
||||
match self {
|
||||
CreateDatabaseError::ValidationError(err) => {
|
||||
err.to_error_message(DbOrUser::Database(database_name.clone()))
|
||||
err.to_error_message(&DbOrUser::Database(database_name.clone()))
|
||||
}
|
||||
CreateDatabaseError::DatabaseAlreadyExists => {
|
||||
format!("Database {} already exists.", database_name)
|
||||
format!("Database {database_name} already exists.")
|
||||
}
|
||||
CreateDatabaseError::MySqlError(err) => {
|
||||
format!("MySQL error: {}", err)
|
||||
format!("MySQL error: {err}")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn error_type(&self) -> String {
|
||||
match self {
|
||||
CreateDatabaseError::ValidationError(err) => err.error_type(),
|
||||
|
||||
@@ -29,7 +29,7 @@ pub fn print_create_users_output_status(output: &CreateUsersResponse) {
|
||||
for (username, result) in output {
|
||||
match result {
|
||||
Ok(()) => {
|
||||
println!("User '{}' created successfully.", username);
|
||||
println!("User '{username}' created successfully.");
|
||||
}
|
||||
Err(err) => {
|
||||
eprintln!("{}", err.to_error_message(username));
|
||||
@@ -63,20 +63,22 @@ pub fn print_create_users_output_status_json(output: &CreateUsersResponse) {
|
||||
}
|
||||
|
||||
impl CreateUserError {
|
||||
#[must_use]
|
||||
pub fn to_error_message(&self, username: &MySQLUser) -> String {
|
||||
match self {
|
||||
CreateUserError::ValidationError(err) => {
|
||||
err.to_error_message(DbOrUser::User(username.clone()))
|
||||
err.to_error_message(&DbOrUser::User(username.clone()))
|
||||
}
|
||||
CreateUserError::UserAlreadyExists => {
|
||||
format!("User '{}' already exists.", username)
|
||||
format!("User '{username}' already exists.")
|
||||
}
|
||||
CreateUserError::MySqlError(err) => {
|
||||
format!("MySQL error: {}", err)
|
||||
format!("MySQL error: {err}")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn error_type(&self) -> String {
|
||||
match self {
|
||||
CreateUserError::ValidationError(err) => err.error_type(),
|
||||
|
||||
@@ -66,20 +66,22 @@ pub fn print_drop_databases_output_status_json(output: &DropDatabasesResponse) {
|
||||
}
|
||||
|
||||
impl DropDatabaseError {
|
||||
#[must_use]
|
||||
pub fn to_error_message(&self, database_name: &MySQLDatabase) -> String {
|
||||
match self {
|
||||
DropDatabaseError::ValidationError(err) => {
|
||||
err.to_error_message(DbOrUser::Database(database_name.clone()))
|
||||
err.to_error_message(&DbOrUser::Database(database_name.clone()))
|
||||
}
|
||||
DropDatabaseError::DatabaseDoesNotExist => {
|
||||
format!("Database {} does not exist.", database_name)
|
||||
format!("Database {database_name} does not exist.")
|
||||
}
|
||||
DropDatabaseError::MySqlError(err) => {
|
||||
format!("MySQL error: {}", err)
|
||||
format!("MySQL error: {err}")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn error_type(&self) -> String {
|
||||
match self {
|
||||
DropDatabaseError::ValidationError(err) => err.error_type(),
|
||||
|
||||
@@ -29,7 +29,7 @@ pub fn print_drop_users_output_status(output: &DropUsersResponse) {
|
||||
for (username, result) in output {
|
||||
match result {
|
||||
Ok(()) => {
|
||||
println!("User '{}' dropped successfully.", username);
|
||||
println!("User '{username}' dropped successfully.");
|
||||
}
|
||||
Err(err) => {
|
||||
eprintln!("{}", err.to_error_message(username));
|
||||
@@ -63,20 +63,22 @@ pub fn print_drop_users_output_status_json(output: &DropUsersResponse) {
|
||||
}
|
||||
|
||||
impl DropUserError {
|
||||
#[must_use]
|
||||
pub fn to_error_message(&self, username: &MySQLUser) -> String {
|
||||
match self {
|
||||
DropUserError::ValidationError(err) => {
|
||||
err.to_error_message(DbOrUser::User(username.clone()))
|
||||
err.to_error_message(&DbOrUser::User(username.clone()))
|
||||
}
|
||||
DropUserError::UserDoesNotExist => {
|
||||
format!("User '{}' does not exist.", username)
|
||||
format!("User '{username}' does not exist.")
|
||||
}
|
||||
DropUserError::MySqlError(err) => {
|
||||
format!("MySQL error: {}", err)
|
||||
format!("MySQL error: {err}")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn error_type(&self) -> String {
|
||||
match self {
|
||||
DropUserError::ValidationError(err) => err.error_type(),
|
||||
|
||||
@@ -12,13 +12,15 @@ pub enum ListAllDatabasesError {
|
||||
}
|
||||
|
||||
impl ListAllDatabasesError {
|
||||
#[must_use]
|
||||
pub fn to_error_message(&self) -> String {
|
||||
match self {
|
||||
ListAllDatabasesError::MySqlError(err) => format!("MySQL error: {}", err),
|
||||
ListAllDatabasesError::MySqlError(err) => format!("MySQL error: {err}"),
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[must_use]
|
||||
pub fn error_type(&self) -> String {
|
||||
match self {
|
||||
ListAllDatabasesError::MySqlError(_) => "mysql-error".to_string(),
|
||||
|
||||
@@ -12,13 +12,15 @@ pub enum ListAllPrivilegesError {
|
||||
}
|
||||
|
||||
impl ListAllPrivilegesError {
|
||||
#[must_use]
|
||||
pub fn to_error_message(&self) -> String {
|
||||
match self {
|
||||
ListAllPrivilegesError::MySqlError(err) => format!("MySQL error: {}", err),
|
||||
ListAllPrivilegesError::MySqlError(err) => format!("MySQL error: {err}"),
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[must_use]
|
||||
pub fn error_type(&self) -> String {
|
||||
match self {
|
||||
ListAllPrivilegesError::MySqlError(_) => "mysql-error".to_string(),
|
||||
|
||||
@@ -12,13 +12,15 @@ pub enum ListAllUsersError {
|
||||
}
|
||||
|
||||
impl ListAllUsersError {
|
||||
#[must_use]
|
||||
pub fn to_error_message(&self) -> String {
|
||||
match self {
|
||||
ListAllUsersError::MySqlError(err) => format!("MySQL error: {}", err),
|
||||
ListAllUsersError::MySqlError(err) => format!("MySQL error: {err}"),
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[must_use]
|
||||
pub fn error_type(&self) -> String {
|
||||
match self {
|
||||
ListAllUsersError::MySqlError(_) => "mysql-error".to_string(),
|
||||
|
||||
@@ -113,20 +113,22 @@ pub fn print_list_databases_output_status_json(output: &ListDatabasesResponse) {
|
||||
}
|
||||
|
||||
impl ListDatabasesError {
|
||||
#[must_use]
|
||||
pub fn to_error_message(&self, database_name: &MySQLDatabase) -> String {
|
||||
match self {
|
||||
ListDatabasesError::ValidationError(err) => {
|
||||
err.to_error_message(DbOrUser::Database(database_name.clone()))
|
||||
err.to_error_message(&DbOrUser::Database(database_name.clone()))
|
||||
}
|
||||
ListDatabasesError::DatabaseDoesNotExist => {
|
||||
format!("Database '{}' does not exist.", database_name)
|
||||
format!("Database '{database_name}' does not exist.")
|
||||
}
|
||||
ListDatabasesError::MySqlError(err) => {
|
||||
format!("MySQL error: {}", err)
|
||||
format!("MySQL error: {err}")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn error_type(&self) -> String {
|
||||
match self {
|
||||
ListDatabasesError::ValidationError(err) => err.error_type(),
|
||||
|
||||
@@ -65,7 +65,7 @@ pub fn print_list_privileges_output_status(output: &ListPrivilegesResponse, long
|
||||
));
|
||||
|
||||
for (_database, rows) in final_privs_map {
|
||||
for row in rows.iter() {
|
||||
for row in &rows {
|
||||
table.add_row(row![
|
||||
row.db,
|
||||
row.user,
|
||||
@@ -129,20 +129,22 @@ pub enum ListPrivilegesError {
|
||||
}
|
||||
|
||||
impl ListPrivilegesError {
|
||||
#[must_use]
|
||||
pub fn to_error_message(&self, database_name: &MySQLDatabase) -> String {
|
||||
match self {
|
||||
ListPrivilegesError::ValidationError(err) => {
|
||||
err.to_error_message(DbOrUser::Database(database_name.clone()))
|
||||
err.to_error_message(&DbOrUser::Database(database_name.clone()))
|
||||
}
|
||||
ListPrivilegesError::DatabaseDoesNotExist => {
|
||||
format!("Database '{}' does not exist.", database_name)
|
||||
format!("Database '{database_name}' does not exist.")
|
||||
}
|
||||
ListPrivilegesError::MySqlError(err) => {
|
||||
format!("MySQL error: {}", err)
|
||||
format!("MySQL error: {err}")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn error_type(&self) -> String {
|
||||
match self {
|
||||
ListPrivilegesError::ValidationError(err) => err.error_type(),
|
||||
|
||||
@@ -97,20 +97,22 @@ pub fn print_list_users_output_status_json(output: &ListUsersResponse) {
|
||||
}
|
||||
|
||||
impl ListUsersError {
|
||||
#[must_use]
|
||||
pub fn to_error_message(&self, username: &MySQLUser) -> String {
|
||||
match self {
|
||||
ListUsersError::ValidationError(err) => {
|
||||
err.to_error_message(DbOrUser::User(username.clone()))
|
||||
err.to_error_message(&DbOrUser::User(username.clone()))
|
||||
}
|
||||
ListUsersError::UserDoesNotExist => {
|
||||
format!("User '{}' does not exist.", username)
|
||||
format!("User '{username}' does not exist.")
|
||||
}
|
||||
ListUsersError::MySqlError(err) => {
|
||||
format!("MySQL error: {}", err)
|
||||
format!("MySQL error: {err}")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn error_type(&self) -> String {
|
||||
match self {
|
||||
ListUsersError::ValidationError(err) => err.error_type(),
|
||||
|
||||
@@ -32,7 +32,7 @@ pub fn print_lock_users_output_status(output: &LockUsersResponse) {
|
||||
for (username, result) in output {
|
||||
match result {
|
||||
Ok(()) => {
|
||||
println!("User '{}' locked successfully.", username);
|
||||
println!("User '{username}' locked successfully.");
|
||||
}
|
||||
Err(err) => {
|
||||
eprintln!("{}", err.to_error_message(username));
|
||||
@@ -66,23 +66,25 @@ pub fn print_lock_users_output_status_json(output: &LockUsersResponse) {
|
||||
}
|
||||
|
||||
impl LockUserError {
|
||||
#[must_use]
|
||||
pub fn to_error_message(&self, username: &MySQLUser) -> String {
|
||||
match self {
|
||||
LockUserError::ValidationError(err) => {
|
||||
err.to_error_message(DbOrUser::User(username.clone()))
|
||||
err.to_error_message(&DbOrUser::User(username.clone()))
|
||||
}
|
||||
LockUserError::UserDoesNotExist => {
|
||||
format!("User '{}' does not exist.", username)
|
||||
format!("User '{username}' does not exist.")
|
||||
}
|
||||
LockUserError::UserIsAlreadyLocked => {
|
||||
format!("User '{}' is already locked.", username)
|
||||
format!("User '{username}' is already locked.")
|
||||
}
|
||||
LockUserError::MySqlError(err) => {
|
||||
format!("MySQL error: {}", err)
|
||||
format!("MySQL error: {err}")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn error_type(&self) -> String {
|
||||
match self {
|
||||
LockUserError::ValidationError(err) => err.error_type(),
|
||||
|
||||
@@ -53,8 +53,7 @@ pub fn print_modify_database_privileges_output_status(output: &ModifyPrivilegesR
|
||||
match result {
|
||||
Ok(()) => {
|
||||
println!(
|
||||
"Privileges for user '{}' on database '{}' modified successfully.",
|
||||
username, database_name
|
||||
"Privileges for user '{username}' on database '{database_name}' modified successfully."
|
||||
);
|
||||
}
|
||||
Err(err) => {
|
||||
@@ -67,19 +66,20 @@ pub fn print_modify_database_privileges_output_status(output: &ModifyPrivilegesR
|
||||
}
|
||||
|
||||
impl ModifyDatabasePrivilegesError {
|
||||
#[must_use]
|
||||
pub fn to_error_message(&self, database_name: &MySQLDatabase, username: &MySQLUser) -> String {
|
||||
match self {
|
||||
ModifyDatabasePrivilegesError::DatabaseValidationError(err) => {
|
||||
err.to_error_message(DbOrUser::Database(database_name.clone()))
|
||||
err.to_error_message(&DbOrUser::Database(database_name.clone()))
|
||||
}
|
||||
ModifyDatabasePrivilegesError::UserValidationError(err) => {
|
||||
err.to_error_message(DbOrUser::User(username.clone()))
|
||||
err.to_error_message(&DbOrUser::User(username.clone()))
|
||||
}
|
||||
ModifyDatabasePrivilegesError::DatabaseDoesNotExist => {
|
||||
format!("Database '{}' does not exist.", database_name)
|
||||
format!("Database '{database_name}' does not exist.")
|
||||
}
|
||||
ModifyDatabasePrivilegesError::UserDoesNotExist => {
|
||||
format!("User '{}' does not exist.", username)
|
||||
format!("User '{username}' does not exist.")
|
||||
}
|
||||
ModifyDatabasePrivilegesError::DiffDoesNotApply(diff) => {
|
||||
format!(
|
||||
@@ -88,12 +88,13 @@ impl ModifyDatabasePrivilegesError {
|
||||
)
|
||||
}
|
||||
ModifyDatabasePrivilegesError::MySqlError(err) => {
|
||||
format!("MySQL error: {}", err)
|
||||
format!("MySQL error: {err}")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[must_use]
|
||||
pub fn error_type(&self) -> String {
|
||||
match self {
|
||||
ModifyDatabasePrivilegesError::DatabaseValidationError(err) => {
|
||||
@@ -113,29 +114,26 @@ impl ModifyDatabasePrivilegesError {
|
||||
}
|
||||
|
||||
impl DiffDoesNotApplyError {
|
||||
#[must_use]
|
||||
pub fn to_error_message(&self) -> String {
|
||||
match self {
|
||||
DiffDoesNotApplyError::RowAlreadyExists(database_name, username) => {
|
||||
format!(
|
||||
"Privileges for user '{}' on database '{}' already exist.",
|
||||
username, database_name
|
||||
"Privileges for user '{username}' on database '{database_name}' already exist."
|
||||
)
|
||||
}
|
||||
DiffDoesNotApplyError::RowDoesNotExist(database_name, username) => {
|
||||
format!(
|
||||
"Privileges for user '{}' on database '{}' do not exist.",
|
||||
username, database_name
|
||||
"Privileges for user '{username}' on database '{database_name}' do not exist."
|
||||
)
|
||||
}
|
||||
DiffDoesNotApplyError::RowPrivilegeChangeDoesNotApply(diff, row) => {
|
||||
format!(
|
||||
"Could not apply privilege change {:?} to row {:?}",
|
||||
diff, row
|
||||
)
|
||||
format!("Could not apply privilege change {diff:?} to row {row:?}")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn error_type(&self) -> String {
|
||||
match self {
|
||||
DiffDoesNotApplyError::RowAlreadyExists(_, _) => "row-already-exists".to_string(),
|
||||
|
||||
@@ -25,7 +25,7 @@ pub enum SetPasswordError {
|
||||
pub fn print_set_password_output_status(output: &SetUserPasswordResponse, username: &MySQLUser) {
|
||||
match output {
|
||||
Ok(()) => {
|
||||
println!("Password for user '{}' set successfully.", username);
|
||||
println!("Password for user '{username}' set successfully.");
|
||||
}
|
||||
Err(err) => {
|
||||
eprintln!("{}", err.to_error_message(username));
|
||||
@@ -35,21 +35,23 @@ pub fn print_set_password_output_status(output: &SetUserPasswordResponse, userna
|
||||
}
|
||||
|
||||
impl SetPasswordError {
|
||||
#[must_use]
|
||||
pub fn to_error_message(&self, username: &MySQLUser) -> String {
|
||||
match self {
|
||||
SetPasswordError::ValidationError(err) => {
|
||||
err.to_error_message(DbOrUser::User(username.clone()))
|
||||
err.to_error_message(&DbOrUser::User(username.clone()))
|
||||
}
|
||||
SetPasswordError::UserDoesNotExist => {
|
||||
format!("User '{}' does not exist.", username)
|
||||
format!("User '{username}' does not exist.")
|
||||
}
|
||||
SetPasswordError::MySqlError(err) => {
|
||||
format!("MySQL error: {}", err)
|
||||
format!("MySQL error: {err}")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[must_use]
|
||||
pub fn error_type(&self) -> String {
|
||||
match self {
|
||||
SetPasswordError::ValidationError(err) => err.error_type(),
|
||||
|
||||
@@ -32,7 +32,7 @@ pub fn print_unlock_users_output_status(output: &UnlockUsersResponse) {
|
||||
for (username, result) in output {
|
||||
match result {
|
||||
Ok(()) => {
|
||||
println!("User '{}' unlocked successfully.", username);
|
||||
println!("User '{username}' unlocked successfully.");
|
||||
}
|
||||
Err(err) => {
|
||||
eprintln!("{}", err.to_error_message(username));
|
||||
@@ -66,23 +66,25 @@ pub fn print_unlock_users_output_status_json(output: &UnlockUsersResponse) {
|
||||
}
|
||||
|
||||
impl UnlockUserError {
|
||||
#[must_use]
|
||||
pub fn to_error_message(&self, username: &MySQLUser) -> String {
|
||||
match self {
|
||||
UnlockUserError::ValidationError(err) => {
|
||||
err.to_error_message(DbOrUser::User(username.clone()))
|
||||
err.to_error_message(&DbOrUser::User(username.clone()))
|
||||
}
|
||||
UnlockUserError::UserDoesNotExist => {
|
||||
format!("User '{}' does not exist.", username)
|
||||
format!("User '{username}' does not exist.")
|
||||
}
|
||||
UnlockUserError::UserIsAlreadyUnlocked => {
|
||||
format!("User '{}' is already unlocked.", username)
|
||||
format!("User '{username}' is already unlocked.")
|
||||
}
|
||||
UnlockUserError::MySqlError(err) => {
|
||||
format!("MySQL error: {}", err)
|
||||
format!("MySQL error: {err}")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn error_type(&self) -> String {
|
||||
match self {
|
||||
UnlockUserError::ValidationError(err) => err.error_type(),
|
||||
|
||||
@@ -22,7 +22,8 @@ pub enum NameValidationError {
|
||||
}
|
||||
|
||||
impl NameValidationError {
|
||||
pub fn to_error_message(self, db_or_user: DbOrUser) -> String {
|
||||
#[must_use]
|
||||
pub fn to_error_message(self, db_or_user: &DbOrUser) -> String {
|
||||
match self {
|
||||
NameValidationError::EmptyString => {
|
||||
format!("{} name can not be empty.", db_or_user.capitalized_noun())
|
||||
@@ -32,15 +33,16 @@ impl NameValidationError {
|
||||
db_or_user.capitalized_noun()
|
||||
),
|
||||
NameValidationError::InvalidCharacters => format!(
|
||||
indoc! {r#"
|
||||
indoc! {r"
|
||||
Invalid characters in {} name: '{}', only A-Z, a-z, 0-9, _ (underscore) and - (dash) are permitted.
|
||||
"#},
|
||||
"},
|
||||
db_or_user.lowercased_noun(),
|
||||
db_or_user.name(),
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn error_type(&self) -> &'static str {
|
||||
match self {
|
||||
NameValidationError::EmptyString => "empty-string",
|
||||
@@ -64,7 +66,8 @@ pub enum AuthorizationError {
|
||||
}
|
||||
|
||||
impl AuthorizationError {
|
||||
pub fn to_error_message(self, db_or_user: DbOrUser) -> String {
|
||||
#[must_use]
|
||||
pub fn to_error_message(self, db_or_user: &DbOrUser) -> String {
|
||||
match self {
|
||||
AuthorizationError::IllegalPrefix => format!(
|
||||
"Illegal {} name prefix: you are not allowed to manage databases or users prefixed with '{}'",
|
||||
@@ -82,6 +85,7 @@ impl AuthorizationError {
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn error_type(&self) -> &'static str {
|
||||
match self {
|
||||
AuthorizationError::IllegalPrefix => "illegal-prefix",
|
||||
@@ -102,7 +106,8 @@ pub enum ValidationError {
|
||||
}
|
||||
|
||||
impl ValidationError {
|
||||
pub fn to_error_message(&self, db_or_user: DbOrUser) -> String {
|
||||
#[must_use]
|
||||
pub fn to_error_message(&self, db_or_user: &DbOrUser) -> String {
|
||||
match self {
|
||||
ValidationError::NameValidationError(err) => err.to_error_message(db_or_user),
|
||||
ValidationError::AuthorizationError(err) => err.to_error_message(db_or_user),
|
||||
@@ -116,6 +121,7 @@ impl ValidationError {
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn error_type(&self) -> String {
|
||||
match self {
|
||||
ValidationError::NameValidationError(err) => {
|
||||
@@ -153,7 +159,7 @@ pub fn validate_authorization_by_unix_user(
|
||||
name: &str,
|
||||
user: &UnixUser,
|
||||
) -> Result<(), AuthorizationError> {
|
||||
let prefixes = std::iter::once(user.username.to_owned())
|
||||
let prefixes = std::iter::once(user.username.clone())
|
||||
.chain(user.groups.iter().cloned())
|
||||
.collect::<Vec<String>>();
|
||||
|
||||
@@ -174,12 +180,12 @@ pub fn validate_authorization_by_prefixes(
|
||||
|
||||
if prefixes
|
||||
.iter()
|
||||
.filter(|p| name.starts_with(&(p.to_string() + "_")))
|
||||
.filter(|p| name.starts_with(&((*p).clone() + "_")))
|
||||
.collect::<Vec<_>>()
|
||||
.is_empty()
|
||||
{
|
||||
return Err(AuthorizationError::IllegalPrefix);
|
||||
};
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -112,6 +112,7 @@ pub enum DbOrUser {
|
||||
}
|
||||
|
||||
impl DbOrUser {
|
||||
#[must_use]
|
||||
pub fn lowercased_noun(&self) -> &'static str {
|
||||
match self {
|
||||
DbOrUser::Database(_) => "database",
|
||||
@@ -119,6 +120,7 @@ impl DbOrUser {
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn capitalized_noun(&self) -> &'static str {
|
||||
match self {
|
||||
DbOrUser::Database(_) => "Database",
|
||||
@@ -126,6 +128,7 @@ impl DbOrUser {
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn name(&self) -> &str {
|
||||
match self {
|
||||
DbOrUser::Database(db) => db.as_str(),
|
||||
@@ -133,6 +136,7 @@ impl DbOrUser {
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn prefix(&self) -> &str {
|
||||
match self {
|
||||
DbOrUser::Database(db) => db.split('_').next().unwrap_or("?"),
|
||||
|
||||
Reference in New Issue
Block a user