Compare commits
2 Commits
319a9bd5e7
...
7025982d44
Author | SHA1 | Date |
---|---|---|
Oystein Kristoffer Tveit | 7025982d44 | |
Oystein Kristoffer Tveit | 71c712dce0 |
|
@ -277,7 +277,7 @@ async fn show_databases(
|
||||||
}
|
}
|
||||||
|
|
||||||
/// See documentation for `DatabaseCommand::EditPerm`.
|
/// See documentation for `DatabaseCommand::EditPerm`.
|
||||||
fn parse_permission_table_cli_arg(arg: &str) -> anyhow::Result<DatabasePrivileges> {
|
fn parse_permission_table_cli_arg(arg: &str) -> anyhow::Result<DatabasePrivilegeRow> {
|
||||||
let parts: Vec<&str> = arg.split(':').collect();
|
let parts: Vec<&str> = arg.split(':').collect();
|
||||||
if parts.len() != 3 {
|
if parts.len() != 3 {
|
||||||
anyhow::bail!("Invalid argument format. See `edit-perm --help` for more information.");
|
anyhow::bail!("Invalid argument format. See `edit-perm --help` for more information.");
|
||||||
|
@ -287,7 +287,7 @@ fn parse_permission_table_cli_arg(arg: &str) -> anyhow::Result<DatabasePrivilege
|
||||||
let user = parts[1].to_string();
|
let user = parts[1].to_string();
|
||||||
let privs = parts[2].to_string();
|
let privs = parts[2].to_string();
|
||||||
|
|
||||||
let mut result = DatabasePrivileges {
|
let mut result = DatabasePrivilegeRow {
|
||||||
db,
|
db,
|
||||||
user,
|
user,
|
||||||
select_priv: false,
|
select_priv: false,
|
||||||
|
@ -344,7 +344,7 @@ fn parse_permission(yn: &str) -> anyhow::Result<bool> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_permission_data_from_editor(content: String) -> anyhow::Result<Vec<DatabasePrivileges>> {
|
fn parse_permission_data_from_editor(content: String) -> anyhow::Result<Vec<DatabasePrivilegeRow>> {
|
||||||
content
|
content
|
||||||
.trim()
|
.trim()
|
||||||
.split('\n')
|
.split('\n')
|
||||||
|
@ -357,7 +357,7 @@ fn parse_permission_data_from_editor(content: String) -> anyhow::Result<Vec<Data
|
||||||
anyhow::bail!("")
|
anyhow::bail!("")
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(DatabasePrivileges {
|
Ok(DatabasePrivilegeRow {
|
||||||
db: (*line_parts.first().unwrap()).to_owned(),
|
db: (*line_parts.first().unwrap()).to_owned(),
|
||||||
user: (*line_parts.get(1).unwrap()).to_owned(),
|
user: (*line_parts.get(1).unwrap()).to_owned(),
|
||||||
select_priv: parse_permission(line_parts.get(2).unwrap())
|
select_priv: parse_permission(line_parts.get(2).unwrap())
|
||||||
|
@ -384,11 +384,11 @@ fn parse_permission_data_from_editor(content: String) -> anyhow::Result<Vec<Data
|
||||||
.context("Could not parse REFERENCES privilege")?,
|
.context("Could not parse REFERENCES privilege")?,
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
.collect::<anyhow::Result<Vec<DatabasePrivileges>>>()
|
.collect::<anyhow::Result<Vec<DatabasePrivilegeRow>>>()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn format_privileges_line(
|
fn format_privileges_line(
|
||||||
privs: &DatabasePrivileges,
|
privs: &DatabasePrivilegeRow,
|
||||||
username_len: usize,
|
username_len: usize,
|
||||||
database_name_len: usize,
|
database_name_len: usize,
|
||||||
) -> String {
|
) -> String {
|
||||||
|
@ -430,7 +430,7 @@ pub async fn edit_permissions(
|
||||||
parse_permission_table_cli_arg(&format!("{}:{}", name, &perm))
|
parse_permission_table_cli_arg(&format!("{}:{}", name, &perm))
|
||||||
.context(format!("Failed parsing database permissions: `{}`", &perm))
|
.context(format!("Failed parsing database permissions: `{}`", &perm))
|
||||||
})
|
})
|
||||||
.collect::<anyhow::Result<Vec<DatabasePrivileges>>>()?
|
.collect::<anyhow::Result<Vec<DatabasePrivilegeRow>>>()?
|
||||||
} else {
|
} else {
|
||||||
args.perm
|
args.perm
|
||||||
.iter()
|
.iter()
|
||||||
|
@ -438,7 +438,7 @@ pub async fn edit_permissions(
|
||||||
parse_permission_table_cli_arg(perm)
|
parse_permission_table_cli_arg(perm)
|
||||||
.context(format!("Failed parsing database permissions: `{}`", &perm))
|
.context(format!("Failed parsing database permissions: `{}`", &perm))
|
||||||
})
|
})
|
||||||
.collect::<anyhow::Result<Vec<DatabasePrivileges>>>()?
|
.collect::<anyhow::Result<Vec<DatabasePrivilegeRow>>>()?
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
let comment = indoc! {r#"
|
let comment = indoc! {r#"
|
||||||
|
@ -476,7 +476,7 @@ pub async fn edit_permissions(
|
||||||
header[1] = format!("{:width$}", header[1], width = longest_username);
|
header[1] = format!("{:width$}", header[1], width = longest_username);
|
||||||
|
|
||||||
let example_line = format_privileges_line(
|
let example_line = format_privileges_line(
|
||||||
&DatabasePrivileges {
|
&DatabasePrivilegeRow {
|
||||||
db: example_db,
|
db: example_db,
|
||||||
user: example_user,
|
user: example_user,
|
||||||
select_priv: true,
|
select_priv: true,
|
||||||
|
|
|
@ -1,3 +1,18 @@
|
||||||
|
//! Database privilege operations
|
||||||
|
//!
|
||||||
|
//! This module contains functions for querying, modifying,
|
||||||
|
//! displaying and comparing database privileges.
|
||||||
|
//!
|
||||||
|
//! A lot of the complexity comes from two core components:
|
||||||
|
//!
|
||||||
|
//! - The permission editor that needs to be able to print
|
||||||
|
//! an editable table of privileges and reparse the content
|
||||||
|
//! after the user has made manual changes.
|
||||||
|
//!
|
||||||
|
//! - The comparison functionality that tells the user what
|
||||||
|
//! changes will be made when applying a set of changes
|
||||||
|
//! to the list of database privileges.
|
||||||
|
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
use anyhow::Context;
|
use anyhow::Context;
|
||||||
|
@ -48,8 +63,9 @@ pub fn db_priv_field_human_readable_name(name: &str) -> String {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// This struct represents the set of privileges for a single user on a single database.
|
||||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||||
pub struct DatabasePrivileges {
|
pub struct DatabasePrivilegeRow {
|
||||||
pub db: String,
|
pub db: String,
|
||||||
pub user: String,
|
pub user: String,
|
||||||
pub select_priv: bool,
|
pub select_priv: bool,
|
||||||
|
@ -65,7 +81,7 @@ pub struct DatabasePrivileges {
|
||||||
pub references_priv: bool,
|
pub references_priv: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DatabasePrivileges {
|
impl DatabasePrivilegeRow {
|
||||||
pub fn get_privilege_by_name(&self, name: &str) -> bool {
|
pub fn get_privilege_by_name(&self, name: &str) -> bool {
|
||||||
match name {
|
match name {
|
||||||
"select_priv" => self.select_priv,
|
"select_priv" => self.select_priv,
|
||||||
|
@ -82,17 +98,18 @@ impl DatabasePrivileges {
|
||||||
_ => false,
|
_ => false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn diff(&self, other: &DatabasePrivileges) -> DatabasePrivilegeDiffList {
|
|
||||||
|
pub fn diff(&self, other: &DatabasePrivilegeRow) -> DatabasePrivilegeRowDiff {
|
||||||
debug_assert!(self.db == other.db && self.user == other.user);
|
debug_assert!(self.db == other.db && self.user == other.user);
|
||||||
|
|
||||||
DatabasePrivilegeDiffList {
|
DatabasePrivilegeRowDiff {
|
||||||
db: self.db.clone(),
|
db: self.db.clone(),
|
||||||
user: self.user.clone(),
|
user: self.user.clone(),
|
||||||
diff: DATABASE_PRIVILEGE_FIELDS
|
diff: DATABASE_PRIVILEGE_FIELDS
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.skip(2)
|
.skip(2)
|
||||||
.filter_map(|field| {
|
.filter_map(|field| {
|
||||||
diff_single_priv(
|
DatabasePrivilegeChange::new(
|
||||||
self.get_privilege_by_name(field),
|
self.get_privilege_by_name(field),
|
||||||
other.get_privilege_by_name(field),
|
other.get_privilege_by_name(field),
|
||||||
field,
|
field,
|
||||||
|
@ -103,7 +120,7 @@ impl DatabasePrivileges {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FromRow<'_, MySqlRow> for DatabasePrivileges {
|
impl FromRow<'_, MySqlRow> for DatabasePrivilegeRow {
|
||||||
fn from_row(row: &MySqlRow) -> Result<Self, sqlx::Error> {
|
fn from_row(row: &MySqlRow) -> Result<Self, sqlx::Error> {
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
db: row.try_get("db")?,
|
db: row.try_get("db")?,
|
||||||
|
@ -126,11 +143,11 @@ impl FromRow<'_, MySqlRow> for DatabasePrivileges {
|
||||||
pub async fn get_database_privileges(
|
pub async fn get_database_privileges(
|
||||||
database_name: &str,
|
database_name: &str,
|
||||||
conn: &mut MySqlConnection,
|
conn: &mut MySqlConnection,
|
||||||
) -> anyhow::Result<Vec<DatabasePrivileges>> {
|
) -> anyhow::Result<Vec<DatabasePrivilegeRow>> {
|
||||||
let unix_user = get_current_unix_user()?;
|
let unix_user = get_current_unix_user()?;
|
||||||
validate_database_name(database_name, &unix_user)?;
|
validate_database_name(database_name, &unix_user)?;
|
||||||
|
|
||||||
let result = sqlx::query_as::<_, DatabasePrivileges>(&format!(
|
let result = sqlx::query_as::<_, DatabasePrivilegeRow>(&format!(
|
||||||
"SELECT {} FROM `db` WHERE `db` = ?",
|
"SELECT {} FROM `db` WHERE `db` = ?",
|
||||||
DATABASE_PRIVILEGE_FIELDS
|
DATABASE_PRIVILEGE_FIELDS
|
||||||
.iter()
|
.iter()
|
||||||
|
@ -147,10 +164,10 @@ pub async fn get_database_privileges(
|
||||||
|
|
||||||
pub async fn get_all_database_privileges(
|
pub async fn get_all_database_privileges(
|
||||||
conn: &mut MySqlConnection,
|
conn: &mut MySqlConnection,
|
||||||
) -> anyhow::Result<Vec<DatabasePrivileges>> {
|
) -> anyhow::Result<Vec<DatabasePrivilegeRow>> {
|
||||||
let unix_user = get_current_unix_user()?;
|
let unix_user = get_current_unix_user()?;
|
||||||
|
|
||||||
let result = sqlx::query_as::<_, DatabasePrivileges>(&format!(
|
let result = sqlx::query_as::<_, DatabasePrivilegeRow>(&format!(
|
||||||
indoc! {r#"
|
indoc! {r#"
|
||||||
SELECT {} FROM `db` WHERE `db` IN
|
SELECT {} FROM `db` WHERE `db` IN
|
||||||
(SELECT DISTINCT `SCHEMA_NAME` AS `database`
|
(SELECT DISTINCT `SCHEMA_NAME` AS `database`
|
||||||
|
@ -171,45 +188,56 @@ pub async fn get_all_database_privileges(
|
||||||
Ok(result)
|
Ok(result)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*******************/
|
||||||
|
/* PRIVILEGE DIFFS */
|
||||||
|
/*******************/
|
||||||
|
|
||||||
|
/// This struct represents encapsulates the differences between two
|
||||||
|
/// instances of privilege sets for a single user on a single database.
|
||||||
|
///
|
||||||
|
/// The `User` and `Database` are the same for both instances.
|
||||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||||
pub struct DatabasePrivilegeDiffList {
|
pub struct DatabasePrivilegeRowDiff {
|
||||||
pub db: String,
|
pub db: String,
|
||||||
pub user: String,
|
pub user: String,
|
||||||
pub diff: Vec<DatabasePrivilegeDiff>,
|
pub diff: Vec<DatabasePrivilegeChange>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// This enum represents a change in a single privilege.
|
||||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||||
pub enum DatabasePrivilegeDiff {
|
pub enum DatabasePrivilegeChange {
|
||||||
YesToNo(String),
|
YesToNo(String),
|
||||||
NoToYes(String),
|
NoToYes(String),
|
||||||
}
|
}
|
||||||
|
|
||||||
fn diff_single_priv(p1: bool, p2: bool, name: &str) -> Option<DatabasePrivilegeDiff> {
|
impl DatabasePrivilegeChange {
|
||||||
match (p1, p2) {
|
pub fn new(p1: bool, p2: bool, name: &str) -> Option<DatabasePrivilegeChange> {
|
||||||
(true, false) => Some(DatabasePrivilegeDiff::YesToNo(name.to_owned())),
|
match (p1, p2) {
|
||||||
(false, true) => Some(DatabasePrivilegeDiff::NoToYes(name.to_owned())),
|
(true, false) => Some(DatabasePrivilegeChange::YesToNo(name.to_owned())),
|
||||||
_ => None,
|
(false, true) => Some(DatabasePrivilegeChange::NoToYes(name.to_owned())),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||||
pub enum DatabasePrivilegesDiff {
|
pub enum DatabasePrivilegesDiff {
|
||||||
New(DatabasePrivileges),
|
New(DatabasePrivilegeRow),
|
||||||
Modified(DatabasePrivilegeDiffList),
|
Modified(DatabasePrivilegeRowDiff),
|
||||||
Deleted(DatabasePrivileges),
|
Deleted(DatabasePrivilegeRow),
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn diff_permissions(
|
pub async fn diff_permissions(
|
||||||
from: Vec<DatabasePrivileges>,
|
from: Vec<DatabasePrivilegeRow>,
|
||||||
to: &[DatabasePrivileges],
|
to: &[DatabasePrivilegeRow],
|
||||||
) -> Vec<DatabasePrivilegesDiff> {
|
) -> Vec<DatabasePrivilegesDiff> {
|
||||||
let from_lookup_table: HashMap<(String, String), DatabasePrivileges> = HashMap::from_iter(
|
let from_lookup_table: HashMap<(String, String), DatabasePrivilegeRow> = HashMap::from_iter(
|
||||||
from.iter()
|
from.iter()
|
||||||
.cloned()
|
.cloned()
|
||||||
.map(|p| ((p.db.clone(), p.user.clone()), p)),
|
.map(|p| ((p.db.clone(), p.user.clone()), p)),
|
||||||
);
|
);
|
||||||
|
|
||||||
let to_lookup_table: HashMap<(String, String), DatabasePrivileges> = HashMap::from_iter(
|
let to_lookup_table: HashMap<(String, String), DatabasePrivilegeRow> = HashMap::from_iter(
|
||||||
to.iter()
|
to.iter()
|
||||||
.cloned()
|
.cloned()
|
||||||
.map(|p| ((p.db.clone(), p.user.clone()), p)),
|
.map(|p| ((p.db.clone(), p.user.clone()), p)),
|
||||||
|
@ -277,8 +305,8 @@ pub async fn apply_permission_diffs(
|
||||||
.diff
|
.diff
|
||||||
.iter()
|
.iter()
|
||||||
.map(|diff| match diff {
|
.map(|diff| match diff {
|
||||||
DatabasePrivilegeDiff::YesToNo(name) => format!("`{}` = 'N'", name),
|
DatabasePrivilegeChange::YesToNo(name) => format!("`{}` = 'N'", name),
|
||||||
DatabasePrivilegeDiff::NoToYes(name) => format!("`{}` = 'Y'", name),
|
DatabasePrivilegeChange::NoToYes(name) => format!("`{}` = 'Y'", name),
|
||||||
})
|
})
|
||||||
.join(",");
|
.join(",");
|
||||||
|
|
||||||
|
@ -307,22 +335,22 @@ mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_diff_single_priv() {
|
fn test_database_privilege_change_creation() {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
diff_single_priv(true, false, "test"),
|
DatabasePrivilegeChange::new(true, false, "test"),
|
||||||
Some(DatabasePrivilegeDiff::YesToNo("test".to_owned()))
|
Some(DatabasePrivilegeChange::YesToNo("test".to_owned()))
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
diff_single_priv(false, true, "test"),
|
DatabasePrivilegeChange::new(false, true, "test"),
|
||||||
Some(DatabasePrivilegeDiff::NoToYes("test".to_owned()))
|
Some(DatabasePrivilegeChange::NoToYes("test".to_owned()))
|
||||||
);
|
);
|
||||||
assert_eq!(diff_single_priv(true, true, "test"), None);
|
assert_eq!(DatabasePrivilegeChange::new(true, true, "test"), None);
|
||||||
assert_eq!(diff_single_priv(false, false, "test"), None);
|
assert_eq!(DatabasePrivilegeChange::new(false, false, "test"), None);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_diff_permissions() {
|
async fn test_diff_permissions() {
|
||||||
let from = vec![DatabasePrivileges {
|
let from = vec![DatabasePrivilegeRow {
|
||||||
db: "db".to_owned(),
|
db: "db".to_owned(),
|
||||||
user: "user".to_owned(),
|
user: "user".to_owned(),
|
||||||
select_priv: true,
|
select_priv: true,
|
||||||
|
@ -338,7 +366,7 @@ mod tests {
|
||||||
references_priv: true,
|
references_priv: true,
|
||||||
}];
|
}];
|
||||||
|
|
||||||
let to = vec![DatabasePrivileges {
|
let to = vec![DatabasePrivilegeRow {
|
||||||
db: "db".to_owned(),
|
db: "db".to_owned(),
|
||||||
user: "user".to_owned(),
|
user: "user".to_owned(),
|
||||||
select_priv: false,
|
select_priv: false,
|
||||||
|
@ -358,13 +386,11 @@ mod tests {
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
diffs,
|
diffs,
|
||||||
vec![DatabasePrivilegesDiff::Modified(
|
vec![DatabasePrivilegesDiff::Modified(DatabasePrivilegeRowDiff {
|
||||||
DatabasePrivilegeDiffList {
|
db: "db".to_owned(),
|
||||||
db: "db".to_owned(),
|
user: "user".to_owned(),
|
||||||
user: "user".to_owned(),
|
diff: vec![DatabasePrivilegeChange::YesToNo("select_priv".to_owned())],
|
||||||
diff: vec![DatabasePrivilegeDiff::YesToNo("select_priv".to_owned())],
|
})]
|
||||||
}
|
|
||||||
)]
|
|
||||||
);
|
);
|
||||||
|
|
||||||
assert!(matches!(&diffs[0], DatabasePrivilegesDiff::Modified(_)));
|
assert!(matches!(&diffs[0], DatabasePrivilegesDiff::Modified(_)));
|
||||||
|
|
|
@ -100,6 +100,7 @@ pub struct DatabaseUser {
|
||||||
#[sqlx(rename = "User")]
|
#[sqlx(rename = "User")]
|
||||||
pub user: String,
|
pub user: String,
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
#[serde(skip)]
|
#[serde(skip)]
|
||||||
#[sqlx(rename = "Host")]
|
#[sqlx(rename = "Host")]
|
||||||
pub host: String,
|
pub host: String,
|
||||||
|
|
Loading…
Reference in New Issue