show-user: add databases field, report when no users

This commit is contained in:
Oystein Kristoffer Tveit 2024-08-07 20:30:16 +02:00
parent a3c894061c
commit 833251a1a2
Signed by: oysteikt
GPG Key ID: 9F2F7D8250F35146
4 changed files with 79 additions and 13 deletions

1
Cargo.lock generated
View File

@ -1364,6 +1364,7 @@ version = "1.0.116"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3e17db7126d17feb94eb3fad46bf1a96b034e8aacbc2e775fe81505f8b0b2813" checksum = "3e17db7126d17feb94eb3fad46bf1a96b034e8aacbc2e775fe81505f8b0b2813"
dependencies = [ dependencies = [
"indexmap",
"itoa", "itoa",
"ryu", "ryu",
"serde", "serde",

View File

@ -15,7 +15,7 @@ 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 }
serde = "1.0.198" serde = "1.0.198"
serde_json = "1.0.116" serde_json = { version = "1.0.116", features = ["preserve_order"] }
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", "macros"] } tokio = { version = "1.37.0", features = ["rt", "macros"] }
toml = "0.8.12" toml = "0.8.12"

View File

@ -1,11 +1,18 @@
use std::collections::BTreeMap;
use std::vec; use std::vec;
use anyhow::Context; use anyhow::Context;
use clap::Parser; use clap::Parser;
use dialoguer::{Confirm, Password}; use dialoguer::{Confirm, Password};
use prettytable::Table;
use serde_json::json;
use sqlx::{Connection, MySqlConnection}; use sqlx::{Connection, MySqlConnection};
use crate::core::{common::close_database_connection, user_operations::validate_user_name}; use crate::core::{
common::close_database_connection,
database_operations::get_databases_where_user_has_privileges,
user_operations::validate_user_name,
};
#[derive(Parser)] #[derive(Parser)]
pub struct UserArgs { pub struct UserArgs {
@ -196,20 +203,47 @@ async fn show_users(args: UserShowArgs, conn: &mut MySqlConnection) -> anyhow::R
result result
}; };
let mut user_databases: BTreeMap<String, Vec<String>> = BTreeMap::new();
for user in users.iter() {
user_databases.insert(
user.user.clone(),
get_databases_where_user_has_privileges(&user.user, conn).await?,
);
}
if args.json { if args.json {
println!("{}", serde_json::to_string_pretty(&users)?); let users_json = users
.into_iter()
.map(|user| {
json!({
"user": user.user,
"has_password": user.has_password,
"databases": user_databases.get(&user.user).unwrap_or(&vec![]),
})
})
.collect::<serde_json::Value>();
println!(
"{}",
serde_json::to_string_pretty(&users_json)
.context("Failed to serialize users to JSON")?
);
} else if users.is_empty() {
println!("No users found.");
} else { } else {
let mut table = Table::new();
table.add_row(row![
"User",
"Password is set",
"Databases where user has privileges"
]);
for user in users { for user in users {
println!( table.add_row(row![
"User '{}': {}", user.user,
&user.user, user.has_password,
if user.has_password { user_databases.get(&user.user).unwrap_or(&vec![]).join("\n")
"password set." ]);
} else {
"no password set."
}
);
} }
table.printstd();
} }
Ok(()) Ok(())

View File

@ -1,7 +1,7 @@
use std::collections::HashMap; use std::collections::HashMap;
use anyhow::Context; use anyhow::Context;
use indoc::indoc; use indoc::{formatdoc, indoc};
use itertools::Itertools; use itertools::Itertools;
use nix::unistd::User; use nix::unistd::User;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
@ -77,6 +77,37 @@ pub async fn get_database_list(conn: &mut MySqlConnection) -> anyhow::Result<Vec
Ok(databases.into_iter().map(|d| d.database).collect()) Ok(databases.into_iter().map(|d| d.database).collect())
} }
pub async fn get_databases_where_user_has_privileges(
username: &str,
conn: &mut MySqlConnection,
) -> anyhow::Result<Vec<String>> {
let result = sqlx::query(
formatdoc!(
r#"
SELECT `db` AS `database`
FROM `db`
WHERE `user` = ?
AND ({})
"#,
DATABASE_PRIVILEGE_FIELDS
.iter()
.map(|field| format!("`{}` = 'Y'", field))
.join(" OR "),
)
.as_str(),
)
.bind(username)
.fetch_all(conn)
.await?
.into_iter()
.map(|databases| {
databases.try_get::<String, _>("database").unwrap()
})
.collect();
Ok(result)
}
pub const DATABASE_PRIVILEGE_FIELDS: [&str; 13] = [ pub const DATABASE_PRIVILEGE_FIELDS: [&str; 13] = [
"db", "db",
"user", "user",