Misc #35

Merged
oysteikt merged 13 commits from misc into main 2024-08-07 17:29:15 +02:00
9 changed files with 216 additions and 183 deletions

97
Cargo.lock generated
View File

@ -269,6 +269,19 @@ dependencies = [
"static_assertions", "static_assertions",
] ]
[[package]]
name = "console"
version = "0.15.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0e1f83fc076bd6dd27517eacdf25fef6c4dfe5f1d7448bafaaf3a26f13b5e4eb"
dependencies = [
"encode_unicode 0.3.6",
"lazy_static",
"libc",
"unicode-width",
"windows-sys 0.52.0",
]
[[package]] [[package]]
name = "const-oid" name = "const-oid"
version = "0.9.6" version = "0.9.6"
@ -381,6 +394,19 @@ dependencies = [
"zeroize", "zeroize",
] ]
[[package]]
name = "dialoguer"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "658bce805d770f407bc62102fca7c2c64ceef2fbcb2b8bd19d2765ce093980de"
dependencies = [
"console",
"shell-words",
"tempfile",
"thiserror",
"zeroize",
]
[[package]] [[package]]
name = "digest" name = "digest"
version = "0.10.7" version = "0.10.7"
@ -420,16 +446,6 @@ version = "0.15.7"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b" checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b"
[[package]]
name = "edit"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f364860e764787163c8c8f58231003839be31276e821e2ad2092ddf496b1aa09"
dependencies = [
"tempfile",
"which",
]
[[package]] [[package]]
name = "either" name = "either"
version = "1.11.0" version = "1.11.0"
@ -439,6 +455,12 @@ dependencies = [
"serde", "serde",
] ]
[[package]]
name = "encode_unicode"
version = "0.3.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f"
[[package]] [[package]]
name = "encode_unicode" name = "encode_unicode"
version = "1.0.0" version = "1.0.0"
@ -885,7 +907,7 @@ version = "0.1.0"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"clap", "clap",
"edit", "dialoguer",
"env_logger", "env_logger",
"indoc", "indoc",
"itertools", "itertools",
@ -893,7 +915,6 @@ dependencies = [
"nix", "nix",
"prettytable", "prettytable",
"ratatui", "ratatui",
"rpassword",
"serde", "serde",
"serde_json", "serde_json",
"sqlx", "sqlx",
@ -970,16 +991,6 @@ dependencies = [
"libm", "libm",
] ]
[[package]]
name = "num_cpus"
version = "1.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43"
dependencies = [
"hermit-abi",
"libc",
]
[[package]] [[package]]
name = "object" name = "object"
version = "0.32.2" version = "0.32.2"
@ -1091,7 +1102,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "46480520d1b77c9a3482d39939fcf96831537a250ec62d4fd8fbdf8e0302e781" checksum = "46480520d1b77c9a3482d39939fcf96831537a250ec62d4fd8fbdf8e0302e781"
dependencies = [ dependencies = [
"csv", "csv",
"encode_unicode", "encode_unicode 1.0.0",
"is-terminal", "is-terminal",
"lazy_static", "lazy_static",
"term", "term",
@ -1230,17 +1241,6 @@ dependencies = [
"windows-sys 0.52.0", "windows-sys 0.52.0",
] ]
[[package]]
name = "rpassword"
version = "7.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "80472be3c897911d0137b2d2b9055faf6eeac5b14e324073d83bc17b191d7e3f"
dependencies = [
"libc",
"rtoolbox",
"windows-sys 0.48.0",
]
[[package]] [[package]]
name = "rsa" name = "rsa"
version = "0.9.6" version = "0.9.6"
@ -1261,16 +1261,6 @@ dependencies = [
"zeroize", "zeroize",
] ]
[[package]]
name = "rtoolbox"
version = "0.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c247d24e63230cdb56463ae328478bd5eac8b8faa8c69461a77e8e323afac90e"
dependencies = [
"libc",
"windows-sys 0.48.0",
]
[[package]] [[package]]
name = "rustc-demangle" name = "rustc-demangle"
version = "0.1.23" version = "0.1.23"
@ -1410,6 +1400,12 @@ dependencies = [
"digest", "digest",
] ]
[[package]]
name = "shell-words"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "24188a676b6ae68c3b2cb3a01be17fbf7240ce009799bb56d5b1409051e78fde"
[[package]] [[package]]
name = "signal-hook" name = "signal-hook"
version = "0.3.17" version = "0.3.17"
@ -1858,7 +1854,6 @@ dependencies = [
"bytes", "bytes",
"libc", "libc",
"mio", "mio",
"num_cpus",
"pin-project-lite", "pin-project-lite",
"socket2", "socket2",
"tokio-macros", "tokio-macros",
@ -2057,18 +2052,6 @@ version = "0.25.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5f20c57d8d7db6d3b86154206ae5d8fba62dd39573114de97c2cb0578251f8e1" checksum = "5f20c57d8d7db6d3b86154206ae5d8fba62dd39573114de97c2cb0578251f8e1"
[[package]]
name = "which"
version = "4.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "87ba24419a2078cd2b0f2ede2691b6c66d8e47836da3b6db8265ebad47afbfc7"
dependencies = [
"either",
"home",
"once_cell",
"rustix",
]
[[package]] [[package]]
name = "whoami" name = "whoami"
version = "1.5.1" version = "1.5.1"

View File

@ -6,7 +6,7 @@ edition = "2021"
[dependencies] [dependencies]
anyhow = "1.0.82" anyhow = "1.0.82"
clap = { version = "4.5.4", features = ["derive"] } clap = { version = "4.5.4", features = ["derive"] }
edit = "0.1.5" dialoguer = "0.11.0"
env_logger = "0.11.3" env_logger = "0.11.3"
indoc = "2.0.5" indoc = "2.0.5"
itertools = "0.12.1" itertools = "0.12.1"
@ -14,11 +14,10 @@ log = "0.4.21"
nix = { version = "0.28.0", features = ["user"] } nix = { version = "0.28.0", features = ["user"] }
prettytable = "0.10.0" prettytable = "0.10.0"
ratatui = { version = "0.26.2", optional = true } ratatui = { version = "0.26.2", optional = true }
rpassword = "7.3.1"
serde = "1.0.198" serde = "1.0.198"
serde_json = "1.0.116" serde_json = "1.0.116"
sqlx = { version = "0.7.4", features = ["runtime-tokio", "mysql", "tls-rustls"] } sqlx = { version = "0.7.4", features = ["runtime-tokio", "mysql", "tls-rustls"] }
tokio = { version = "1.37.0", features = ["rt-multi-thread", "macros"] } tokio = { version = "1.37.0", features = ["rt", "macros"] }
toml = "0.8.12" toml = "0.8.12"
[features] [features]

View File

@ -17,20 +17,24 @@ fn main() -> anyhow::Result<()> {
.ok_or(anyhow!("Could not resolve target profile directory"))? .ok_or(anyhow!("Could not resolve target profile directory"))?
.to_path_buf(); .to_path_buf();
dbg!(&target_profile_dir); if !target_profile_dir.exists() {
std::fs::create_dir_all(&target_profile_dir)?;
}
if !target_profile_dir.join("mysql-useradm").exists() { if !target_profile_dir.join("mysql-useradm").exists() {
symlink( symlink(
target_profile_dir.join("mysqladm"), target_profile_dir.join("mysqladm"),
target_profile_dir.join("mysql-useradm"), target_profile_dir.join("mysql-useradm"),
)?; )
.ok();
} }
if !target_profile_dir.join("mysql-dbadm").exists() { if !target_profile_dir.join("mysql-dbadm").exists() {
symlink( symlink(
target_profile_dir.join("mysqladm"), target_profile_dir.join("mysqladm"),
target_profile_dir.join("mysql-dbadm"), target_profile_dir.join("mysql-dbadm"),
)?; )
.ok();
} }
} }

View File

@ -1,5 +1,6 @@
use anyhow::{anyhow, Context}; use anyhow::{anyhow, Context};
use clap::Parser; use clap::Parser;
use dialoguer::Editor;
use indoc::indoc; use indoc::indoc;
use itertools::Itertools; use itertools::Itertools;
use prettytable::{Cell, Row, Table}; use prettytable::{Cell, Row, Table};
@ -7,7 +8,7 @@ use sqlx::{Connection, MySqlConnection};
use crate::core::{ use crate::core::{
self, self,
common::get_current_unix_user, common::{close_database_connection, get_current_unix_user},
database_operations::{ database_operations::{
apply_permission_diffs, db_priv_field_human_readable_name, diff_permissions, yn, apply_permission_diffs, db_priv_field_human_readable_name, diff_permissions, yn,
DatabasePrivileges, DATABASE_PRIVILEGE_FIELDS, DatabasePrivileges, DATABASE_PRIVILEGE_FIELDS,
@ -15,9 +16,6 @@ use crate::core::{
user_operations::user_exists, user_operations::user_exists,
}; };
// TODO: Support batch creation/dropping,showing of databases,
// using a comma-separated list of database names.
#[derive(Parser)] #[derive(Parser)]
// #[command(next_help_heading = Some(DATABASE_COMMAND_HEADER))] // #[command(next_help_heading = Some(DATABASE_COMMAND_HEADER))]
pub enum DatabaseCommand { pub enum DatabaseCommand {
@ -147,15 +145,21 @@ pub async fn handle_command(
command: DatabaseCommand, command: DatabaseCommand,
mut conn: MySqlConnection, mut conn: MySqlConnection,
) -> anyhow::Result<()> { ) -> anyhow::Result<()> {
let result = match command { let result = conn
DatabaseCommand::CreateDb(args) => create_databases(args, &mut conn).await, .transaction(|txn| {
DatabaseCommand::DropDb(args) => drop_databases(args, &mut conn).await, Box::pin(async move {
DatabaseCommand::ListDb(args) => list_databases(args, &mut conn).await, match command {
DatabaseCommand::ShowDbPerm(args) => show_databases(args, &mut conn).await, DatabaseCommand::CreateDb(args) => create_databases(args, txn).await,
DatabaseCommand::EditDbPerm(args) => edit_permissions(args, &mut conn).await, DatabaseCommand::DropDb(args) => drop_databases(args, txn).await,
}; DatabaseCommand::ListDb(args) => list_databases(args, txn).await,
DatabaseCommand::ShowDbPerm(args) => show_databases(args, txn).await,
DatabaseCommand::EditDbPerm(args) => edit_permissions(args, txn).await,
}
})
})
.await;
conn.close().await?; close_database_connection(conn).await;
result result
} }
@ -494,27 +498,32 @@ pub async fn edit_permissions(
longest_database_name, longest_database_name,
); );
let result = edit::edit_with_builder( // TODO: handle errors better here
format!( let result = Editor::new()
"{}\n{}\n{}", .extension("tsv")
comment, .edit(
header.join(" "), format!(
if permission_data.is_empty() { "{}\n{}\n{}",
format!("# {}", example_line) comment,
} else { header.join(" "),
permission_data if permission_data.is_empty() {
.iter() format!("# {}", example_line)
.map(|perm| { } else {
format_privileges_line(perm, longest_username, longest_database_name) permission_data
}) .iter()
.join("\n") .map(|perm| {
} format_privileges_line(
), perm,
edit::Builder::new() longest_username,
.prefix("database-permissions") longest_database_name,
.suffix(".tsv") )
.rand_bytes(10), })
)?; .join("\n")
}
)
.as_str(),
)?
.unwrap();
parse_permission_data_from_editor(result) parse_permission_data_from_editor(result)
.context("Could not parse permission data from editor")? .context("Could not parse permission data from editor")?

View File

@ -7,11 +7,11 @@ use crate::{
user_command, user_command,
}, },
core::{ core::{
common::get_current_unix_user, common::{close_database_connection, get_current_unix_user},
config::{get_config, mysql_connection_from_config, GlobalConfigArgs}, config::{get_config, mysql_connection_from_config, GlobalConfigArgs},
user_operations::{ user_operations::{
create_database_user, delete_database_user, get_all_database_users_for_unix_user, create_database_user, delete_database_user, get_all_database_users_for_unix_user,
password_is_set_for_database_user, set_password_for_database_user, user_exists, get_database_user_for_user, set_password_for_database_user, user_exists,
}, },
}, },
}; };
@ -107,14 +107,16 @@ pub async fn main() -> anyhow::Result<()> {
delete_database_user(&name, &mut connection).await?; delete_database_user(&name, &mut connection).await?;
} }
} }
Command::Passwd(args) => passwd(args, connection).await?, Command::Passwd(args) => passwd(args, &mut connection).await?,
Command::Show(args) => show(args, connection).await?, Command::Show(args) => show(args, &mut connection).await?,
} }
close_database_connection(connection).await;
Ok(()) Ok(())
} }
async fn passwd(args: PasswdArgs, mut connection: MySqlConnection) -> anyhow::Result<()> { async fn passwd(args: PasswdArgs, connection: &mut MySqlConnection) -> anyhow::Result<()> {
let filtered_names = filter_db_or_user_names(args.name, DbOrUser::User)?; let filtered_names = filter_db_or_user_names(args.name, DbOrUser::User)?;
// NOTE: this gets doubly checked during the call to `set_password_for_database_user`. // NOTE: this gets doubly checked during the call to `set_password_for_database_user`.
@ -123,7 +125,7 @@ async fn passwd(args: PasswdArgs, mut connection: MySqlConnection) -> anyhow::Re
// have entered the password twice. // have entered the password twice.
let mut better_filtered_names = Vec::with_capacity(filtered_names.len()); let mut better_filtered_names = Vec::with_capacity(filtered_names.len());
for name in filtered_names.into_iter() { for name in filtered_names.into_iter() {
if !user_exists(&name, &mut connection).await? { if !user_exists(&name, connection).await? {
println!( println!(
"{}: User '{}' does not exist. You must create it first.", "{}: User '{}' does not exist. You must create it first.",
std::env::args() std::env::args()
@ -138,32 +140,34 @@ async fn passwd(args: PasswdArgs, mut connection: MySqlConnection) -> anyhow::Re
for name in better_filtered_names { for name in better_filtered_names {
let password = user_command::read_password_from_stdin_with_double_check(&name)?; let password = user_command::read_password_from_stdin_with_double_check(&name)?;
set_password_for_database_user(&name, &password, &mut connection).await?; set_password_for_database_user(&name, &password, connection).await?;
println!("Password updated for user '{}'.", name); println!("Password updated for user '{}'.", name);
} }
Ok(()) Ok(())
} }
async fn show(args: ShowArgs, mut connection: MySqlConnection) -> anyhow::Result<()> { async fn show(args: ShowArgs, connection: &mut MySqlConnection) -> anyhow::Result<()> {
let users = if args.name.is_empty() { let users = if args.name.is_empty() {
let unix_user = get_current_unix_user()?; let unix_user = get_current_unix_user()?;
get_all_database_users_for_unix_user(&unix_user, &mut connection) get_all_database_users_for_unix_user(&unix_user, connection).await?
.await?
.into_iter()
.map(|u| u.user)
.collect()
} else { } else {
filter_db_or_user_names(args.name, DbOrUser::User)? let filtered_usernames = filter_db_or_user_names(args.name, DbOrUser::User)?;
let mut result = Vec::with_capacity(filtered_usernames.len());
for username in filtered_usernames.iter() {
// TODO: fetch all users in one query
if let Some(user) = get_database_user_for_user(username, connection).await? {
result.push(user)
}
}
result
}; };
for user in users { for user in users {
let password_is_set = password_is_set_for_database_user(&user, &mut connection).await?; if user.has_password {
println!("User '{}': password set.", user.user);
match password_is_set { } else {
Some(true) => println!("User '{}': password set.", user), println!("User '{}': no password set.", user.user);
Some(false) => println!("User '{}': no password set.", user),
None => {}
} }
} }

View File

@ -2,9 +2,10 @@ use std::vec;
use anyhow::Context; use anyhow::Context;
use clap::Parser; use clap::Parser;
use dialoguer::{Confirm, Password};
use sqlx::{Connection, MySqlConnection}; use sqlx::{Connection, MySqlConnection};
use crate::core::user_operations::validate_user_name; use crate::core::{common::close_database_connection, user_operations::validate_user_name};
#[derive(Parser)] #[derive(Parser)]
pub struct UserArgs { pub struct UserArgs {
@ -37,6 +38,10 @@ pub enum UserCommand {
pub struct UserCreateArgs { pub struct UserCreateArgs {
#[arg(num_args = 1..)] #[arg(num_args = 1..)]
username: Vec<String>, username: Vec<String>,
/// Do not ask for a password, leave it unset
#[clap(long)]
no_password: bool,
} }
#[derive(Parser)] #[derive(Parser)]
@ -57,22 +62,30 @@ pub struct UserPasswdArgs {
pub struct UserShowArgs { pub struct UserShowArgs {
#[arg(num_args = 0..)] #[arg(num_args = 0..)]
username: Vec<String>, username: Vec<String>,
#[clap(short, long)]
json: bool,
} }
pub async fn handle_command(command: UserCommand, mut conn: MySqlConnection) -> anyhow::Result<()> { pub async fn handle_command(command: UserCommand, mut conn: MySqlConnection) -> anyhow::Result<()> {
let result = match command { let result = conn
UserCommand::CreateUser(args) => create_users(args, &mut conn).await, .transaction(|txn| {
UserCommand::DropUser(args) => drop_users(args, &mut conn).await, Box::pin(async move {
UserCommand::PasswdUser(args) => change_password_for_user(args, &mut conn).await, match command {
UserCommand::ShowUser(args) => show_users(args, &mut conn).await, UserCommand::CreateUser(args) => create_users(args, txn).await,
}; UserCommand::DropUser(args) => drop_users(args, txn).await,
UserCommand::PasswdUser(args) => change_password_for_user(args, txn).await,
UserCommand::ShowUser(args) => show_users(args, txn).await,
}
})
})
.await;
conn.close().await?; close_database_connection(conn).await;
result result
} }
// TODO: provide a better error message when the user already exists
async fn create_users(args: UserCreateArgs, conn: &mut MySqlConnection) -> anyhow::Result<()> { async fn create_users(args: UserCreateArgs, conn: &mut MySqlConnection) -> anyhow::Result<()> {
if args.username.is_empty() { if args.username.is_empty() {
anyhow::bail!("No usernames provided"); anyhow::bail!("No usernames provided");
@ -81,13 +94,34 @@ async fn create_users(args: UserCreateArgs, conn: &mut MySqlConnection) -> anyho
for username in args.username { for username in args.username {
if let Err(e) = crate::core::user_operations::create_database_user(&username, conn).await { if let Err(e) = crate::core::user_operations::create_database_user(&username, conn).await {
eprintln!("{}", e); eprintln!("{}", e);
eprintln!("Skipping..."); eprintln!("Skipping...\n");
continue;
} else {
println!("User '{}' created.", username);
} }
if !args.no_password
&& Confirm::new()
.with_prompt(format!(
"Do you want to set a password for user '{}'?",
username
))
.interact()?
{
change_password_for_user(
UserPasswdArgs {
username,
password_file: None,
},
conn,
)
.await?;
}
println!();
} }
Ok(()) Ok(())
} }
// TODO: provide a better error message when the user does not exist
async fn drop_users(args: UserDeleteArgs, conn: &mut MySqlConnection) -> anyhow::Result<()> { async fn drop_users(args: UserDeleteArgs, conn: &mut MySqlConnection) -> anyhow::Result<()> {
if args.username.is_empty() { if args.username.is_empty() {
anyhow::bail!("No usernames provided"); anyhow::bail!("No usernames provided");
@ -103,20 +137,14 @@ async fn drop_users(args: UserDeleteArgs, conn: &mut MySqlConnection) -> anyhow:
} }
pub fn read_password_from_stdin_with_double_check(username: &str) -> anyhow::Result<String> { pub fn read_password_from_stdin_with_double_check(username: &str) -> anyhow::Result<String> {
let pass1 = rpassword::prompt_password(format!("New MySQL password for user '{}': ", username)) Password::new()
.context("Failed to read password")?; .with_prompt(format!("New MySQL password for user '{}'", username))
.with_confirmation(
let pass2 = rpassword::prompt_password(format!( format!("Retype new MySQL password for user '{}'", username),
"Retype new MySQL password for user '{}': ", "Passwords do not match",
username )
)) .interact()
.context("Failed to read password")?; .map_err(Into::into)
if pass1 != pass2 {
anyhow::bail!("Passwords do not match");
}
Ok(pass1)
} }
async fn change_password_for_user( async fn change_password_for_user(
@ -168,16 +196,20 @@ async fn show_users(args: UserShowArgs, conn: &mut MySqlConnection) -> anyhow::R
result result
}; };
for user in users { if args.json {
println!( println!("{}", serde_json::to_string_pretty(&users)?);
"User '{}': {}", } else {
&user.user, for user in users {
if !(user.authentication_string.is_empty() && user.password.is_empty()) { println!(
"password set." "User '{}': {}",
} else { &user.user,
"no password set." if user.has_password {
} "password set."
); } else {
"no password set."
}
);
}
} }
Ok(()) Ok(())

View File

@ -2,6 +2,7 @@ use anyhow::Context;
use indoc::indoc; use indoc::indoc;
use itertools::Itertools; use itertools::Itertools;
use nix::unistd::{getuid, Group, User}; use nix::unistd::{getuid, Group, User};
use sqlx::{Connection, MySqlConnection};
#[cfg(not(target_os = "macos"))] #[cfg(not(target_os = "macos"))]
use std::ffi::CString; use std::ffi::CString;
@ -140,10 +141,23 @@ pub fn validate_ownership_by_user_prefix<'a>(
Ok(prefix) Ok(prefix)
} }
pub async fn close_database_connection(conn: MySqlConnection) {
if let Err(e) = conn
.close()
.await
.context("Failed to close connection properly")
{
eprintln!("{}", e);
eprintln!("Ignoring...");
}
}
#[inline]
pub fn quote_literal(s: &str) -> String { pub fn quote_literal(s: &str) -> String {
format!("'{}'", s.replace('\'', r"\'")) format!("'{}'", s.replace('\'', r"\'"))
} }
#[inline]
pub fn quote_identifier(s: &str) -> String { pub fn quote_identifier(s: &str) -> String {
format!("`{}`", s.replace('`', r"\`")) format!("`{}`", s.replace('`', r"\`"))
} }

View File

@ -93,30 +93,6 @@ pub async fn set_password_for_database_user(
Ok(()) Ok(())
} }
/// Helper struct to deserialize the query made in `password_is_set_for_database_user`.
#[derive(sqlx::FromRow)]
#[sqlx(transparent)]
struct PasswordIsSet(bool);
/// This function checks if a database user has a password set.
/// It returns `Ok(None)` if the user does not exist.
pub async fn password_is_set_for_database_user(
db_user: &str,
conn: &mut MySqlConnection,
) -> anyhow::Result<Option<bool>> {
let unix_user = crate::core::common::get_current_unix_user()?;
validate_user_name(db_user, &unix_user)?;
let user_has_password = sqlx::query_as::<_, PasswordIsSet>(
"SELECT authentication_string != '' FROM mysql.user WHERE User = ?",
)
.bind(db_user)
.fetch_optional(conn)
.await?;
Ok(user_has_password.map(|PasswordIsSet(is_set)| is_set))
}
/// This struct contains information about a database user. /// This struct contains information about a database user.
/// This can be extended if we need more information in the future. /// This can be extended if we need more information in the future.
#[derive(Debug, Clone, FromRow, Serialize, Deserialize)] #[derive(Debug, Clone, FromRow, Serialize, Deserialize)]
@ -124,13 +100,12 @@ pub struct DatabaseUser {
#[sqlx(rename = "User")] #[sqlx(rename = "User")]
pub user: String, pub user: String,
#[serde(skip)]
#[sqlx(rename = "Host")] #[sqlx(rename = "Host")]
pub host: String, pub host: String,
#[sqlx(rename = "Password")] #[sqlx(rename = "`Password` != '' OR `authentication_string` != ''")]
pub password: String, pub has_password: bool,
pub authentication_string: String,
} }
/// This function fetches all database users that have a prefix matching the /// This function fetches all database users that have a prefix matching the
@ -141,7 +116,10 @@ pub async fn get_all_database_users_for_unix_user(
) -> anyhow::Result<Vec<DatabaseUser>> { ) -> anyhow::Result<Vec<DatabaseUser>> {
let users = sqlx::query_as::<_, DatabaseUser>( let users = sqlx::query_as::<_, DatabaseUser>(
r#" r#"
SELECT `User`, `Host`, `Password`, `authentication_string` SELECT
`User`,
`Host`,
`Password` != '' OR `authentication_string` != ''
FROM `mysql`.`user` FROM `mysql`.`user`
WHERE `User` REGEXP ? WHERE `User` REGEXP ?
"#, "#,
@ -160,7 +138,10 @@ pub async fn get_database_user_for_user(
) -> anyhow::Result<Option<DatabaseUser>> { ) -> anyhow::Result<Option<DatabaseUser>> {
let user = sqlx::query_as::<_, DatabaseUser>( let user = sqlx::query_as::<_, DatabaseUser>(
r#" r#"
SELECT `User`, `Host`, `Password`, `authentication_string` SELECT
`User`,
`Host`,
`Password` != '' OR `authentication_string` != ''
FROM `mysql`.`user` FROM `mysql`.`user`
WHERE `User` = ? WHERE `User` = ?
"#, "#,

View File

@ -44,7 +44,7 @@ enum Command {
User(cli::user_command::UserCommand), User(cli::user_command::UserCommand),
} }
#[tokio::main] #[tokio::main(flavor = "current_thread")]
async fn main() -> anyhow::Result<()> { async fn main() -> anyhow::Result<()> {
env_logger::init(); env_logger::init();
@ -67,8 +67,15 @@ async fn main() -> anyhow::Result<()> {
let config = core::config::get_config(args.config_overrides)?; let config = core::config::get_config(args.config_overrides)?;
let connection = core::config::mysql_connection_from_config(config).await?; let connection = core::config::mysql_connection_from_config(config).await?;
match args.command { let result = match args.command {
Command::Db(command) => cli::database_command::handle_command(command, connection).await, Command::Db(command) => cli::database_command::handle_command(command, connection).await,
Command::User(user_args) => cli::user_command::handle_command(user_args, connection).await, Command::User(user_args) => cli::user_command::handle_command(user_args, connection).await,
};
match result {
Ok(_) => println!("Changes committed to database"),
Err(_) => println!("Changes reverted due to error"),
} }
result
} }