Compare commits
No commits in common. "168f832aec6239d7f575f453fb9d4abf791e8b6f" and "af86893acf94f49d40cc2b42ff15987cae21e16f" have entirely different histories.
168f832aec
...
af86893acf
|
@ -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)?;
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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();
|
||||||
|
|
||||||
|
|
|
@ -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>>;
|
||||||
|
|
|
@ -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(())
|
||||||
|
|
|
@ -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();
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue