Compare commits

..

No commits in common. "168f832aec6239d7f575f453fb9d4abf791e8b6f" and "af86893acf94f49d40cc2b42ff15987cae21e16f" have entirely different histories.

6 changed files with 48 additions and 73 deletions

View File

@ -366,7 +366,7 @@ pub async fn edit_database_privileges(
let privileges_to_change = if !args.privs.is_empty() { let privileges_to_change = if !args.privs.is_empty() {
parse_privilege_tables_from_args(&args)? parse_privilege_tables_from_args(&args)?
} else { } else {
edit_privileges_with_editor(&privilege_data, args.name.as_deref())? edit_privileges_with_editor(&privilege_data)?
}; };
let diffs = diff_privileges(&privilege_data, &privileges_to_change); let diffs = diff_privileges(&privilege_data, &privileges_to_change);
@ -432,14 +432,13 @@ fn parse_privilege_tables_from_args(
fn edit_privileges_with_editor( fn edit_privileges_with_editor(
privilege_data: &[DatabasePrivilegeRow], privilege_data: &[DatabasePrivilegeRow],
database_name: Option<&str>,
) -> anyhow::Result<Vec<DatabasePrivilegeRow>> { ) -> anyhow::Result<Vec<DatabasePrivilegeRow>> {
let unix_user = User::from_uid(getuid()) let unix_user = User::from_uid(getuid())
.context("Failed to look up your UNIX username") .context("Failed to look up your UNIX username")
.and_then(|u| u.ok_or(anyhow::anyhow!("Failed to look up your UNIX username")))?; .and_then(|u| u.ok_or(anyhow::anyhow!("Failed to look up your UNIX username")))?;
let editor_content = let editor_content =
generate_editor_content_from_privilege_data(privilege_data, &unix_user.name, database_name); generate_editor_content_from_privilege_data(privilege_data, &unix_user.name);
// TODO: handle errors better here // TODO: handle errors better here
let result = Editor::new().extension("tsv").edit(&editor_content)?; let result = Editor::new().extension("tsv").edit(&editor_content)?;

View File

@ -290,14 +290,14 @@ async fn show_users(
"User", "User",
"Password is set", "Password is set",
"Locked", "Locked",
"Databases where user has privileges" // "Databases where user has privileges"
]); ]);
for user in users { for user in users {
table.add_row(row![ table.add_row(row![
user.user, user.user,
user.has_password, user.has_password,
user.is_locked, user.is_locked,
user.databases.join("\n") // user.databases.join("\n")
]); ]);
} }
table.printstd(); table.printstd();

View File

@ -157,12 +157,9 @@ const EDITOR_COMMENT: &str = r#"
pub fn generate_editor_content_from_privilege_data( pub fn generate_editor_content_from_privilege_data(
privilege_data: &[DatabasePrivilegeRow], privilege_data: &[DatabasePrivilegeRow],
unix_user: &str, unix_user: &str,
database_name: Option<&str>,
) -> String { ) -> String {
let example_user = format!("{}_user", unix_user); let example_user = format!("{}_user", unix_user);
let example_db = database_name let example_db = format!("{}_db", unix_user);
.unwrap_or(&format!("{}_db", unix_user))
.to_string();
// NOTE: `.max()`` fails when the iterator is empty. // NOTE: `.max()`` fails when the iterator is empty.
// In this case, we know that the only fields in the // In this case, we know that the only fields in the
@ -542,7 +539,7 @@ pub fn display_privilege_diffs(diffs: &BTreeSet<DatabasePrivilegesDiff>) -> Stri
table.add_row(row![ table.add_row(row![
p.db, p.db,
p.user, p.user,
"(Previously unprivileged)\n".to_string() + &display_new_privileges_list(p) "(New user)\n".to_string() + &display_new_privileges_list(p)
]); ]);
} }
DatabasePrivilegesDiff::Modified(p) => { DatabasePrivilegesDiff::Modified(p) => {
@ -667,7 +664,7 @@ mod tests {
}, },
]; ];
let content = generate_editor_content_from_privilege_data(&permissions, "user", None); let content = generate_editor_content_from_privilege_data(&permissions, "user");
let parsed_permissions = parse_privilege_data_from_editor_content(content).unwrap(); let parsed_permissions = parse_privilege_data_from_editor_content(content).unwrap();

View File

@ -95,7 +95,8 @@ impl OwnerValidationError {
.join("\n"), .join("\n"),
) )
.to_owned(), .to_owned(),
OwnerValidationError::StringEmpty => format!(
_ => format!(
"'{}' is not a valid {} name.", "'{}' is not a valid {} name.",
name, name,
db_or_user.lowercased() db_or_user.lowercased()
@ -112,6 +113,12 @@ pub enum OwnerValidationError {
// The name is empty, which is invalid // The name is empty, which is invalid
StringEmpty, StringEmpty,
// The name is in the format "_<postfix>", which is invalid
MissingPrefix,
// The name is in the format "<prefix>_", which is invalid
MissingPostfix,
} }
pub type CreateDatabasesOutput = BTreeMap<String, Result<(), CreateDatabaseError>>; pub type CreateDatabasesOutput = BTreeMap<String, Result<(), CreateDatabaseError>>;

View File

@ -43,15 +43,19 @@ pub fn validate_ownership_by_prefixes(
return Err(OwnerValidationError::StringEmpty); return Err(OwnerValidationError::StringEmpty);
} }
if prefixes if name.starts_with('_') {
.iter() return Err(OwnerValidationError::MissingPrefix);
.filter(|p| name.starts_with(*p)) }
.collect::<Vec<_>>()
.is_empty() let (prefix, _) = match name.split_once('_') {
{ Some(pair) => pair,
return Err(OwnerValidationError::NoMatch); None => return Err(OwnerValidationError::MissingPostfix),
}; };
if !prefixes.iter().any(|g| g == prefix) {
return Err(OwnerValidationError::NoMatch);
}
Ok(()) Ok(())
} }
@ -111,6 +115,24 @@ mod tests {
Err(OwnerValidationError::StringEmpty) Err(OwnerValidationError::StringEmpty)
); );
assert_eq!(
validate_ownership_by_prefixes("user", &prefixes),
Err(OwnerValidationError::MissingPostfix)
);
assert_eq!(
validate_ownership_by_prefixes("something", &prefixes),
Err(OwnerValidationError::MissingPostfix)
);
assert_eq!(
validate_ownership_by_prefixes("user-testdb", &prefixes),
Err(OwnerValidationError::MissingPostfix)
);
assert_eq!(
validate_ownership_by_prefixes("_testdb", &prefixes),
Err(OwnerValidationError::MissingPrefix)
);
assert_eq!( assert_eq!(
validate_ownership_by_prefixes("user_testdb", &prefixes), validate_ownership_by_prefixes("user_testdb", &prefixes),
Ok(()) Ok(())

View File

@ -1,6 +1,4 @@
use itertools::Itertools;
use std::collections::BTreeMap; use std::collections::BTreeMap;
use indoc::formatdoc;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
@ -22,8 +20,6 @@ use crate::{
}, },
}; };
use super::database_privilege_operations::DATABASE_PRIVILEGE_FIELDS;
// NOTE: this function is unsafe because it does no input validation. // NOTE: this function is unsafe because it does no input validation.
async fn unsafe_user_exists( async fn unsafe_user_exists(
db_user: &str, db_user: &str,
@ -313,9 +309,6 @@ pub struct DatabaseUser {
#[sqlx(rename = "is_locked")] #[sqlx(rename = "is_locked")]
pub is_locked: bool, pub is_locked: bool,
#[sqlx(skip)]
pub databases: Vec<String>,
} }
const DB_USER_SELECT_STATEMENT: &str = r#" const DB_USER_SELECT_STATEMENT: &str = r#"
@ -351,17 +344,13 @@ pub async fn list_database_users(
continue; continue;
} }
let mut result = sqlx::query_as::<_, DatabaseUser>( let result = sqlx::query_as::<_, DatabaseUser>(
&(DB_USER_SELECT_STATEMENT.to_string() + "WHERE `mysql`.`user`.`User` = ?"), &(DB_USER_SELECT_STATEMENT.to_string() + "WHERE `mysql`.`user`.`User` = ?"),
) )
.bind(&db_user) .bind(&db_user)
.fetch_optional(&mut *connection) .fetch_optional(&mut *connection)
.await; .await;
if let Ok(Some(user)) = result.as_mut() {
append_databases_where_user_has_privileges(user, &mut *connection).await;
}
match result { match result {
Ok(Some(user)) => results.insert(db_user, Ok(user)), Ok(Some(user)) => results.insert(db_user, Ok(user)),
Ok(None) => results.insert(db_user, Err(ListUsersError::UserDoesNotExist)), Ok(None) => results.insert(db_user, Err(ListUsersError::UserDoesNotExist)),
@ -376,50 +365,11 @@ pub async fn list_all_database_users_for_unix_user(
unix_user: &UnixUser, unix_user: &UnixUser,
connection: &mut MySqlConnection, connection: &mut MySqlConnection,
) -> ListAllUsersOutput { ) -> ListAllUsersOutput {
let mut result = sqlx::query_as::<_, DatabaseUser>( sqlx::query_as::<_, DatabaseUser>(
&(DB_USER_SELECT_STATEMENT.to_string() + "WHERE `mysql`.`user`.`User` REGEXP ?"), &(DB_USER_SELECT_STATEMENT.to_string() + "WHERE `mysql`.`user`.`User` REGEXP ?"),
) )
.bind(create_user_group_matching_regex(unix_user)) .bind(create_user_group_matching_regex(unix_user))
.fetch_all(&mut *connection) .fetch_all(connection)
.await .await
.map_err(|err| ListAllUsersError::MySqlError(err.to_string())); .map_err(|err| ListAllUsersError::MySqlError(err.to_string()))
if let Ok(users) = result.as_mut() {
for user in users {
append_databases_where_user_has_privileges(user, &mut *connection).await;
}
}
result
}
pub async fn append_databases_where_user_has_privileges(
database_user: &mut DatabaseUser,
connection: &mut MySqlConnection,
) {
let database_list = 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(database_user.user.clone())
.fetch_all(&mut *connection)
.await;
database_user.databases = database_list
.map(|rows| {
rows.into_iter()
.map(|row| row.get::<String, _>("database"))
.collect()
})
.unwrap_or_default();
} }