Skip to content

Commit

Permalink
Merge pull request #21 from duyet/feat/force-password
Browse files Browse the repository at this point in the history
  • Loading branch information
duyet authored Jan 20, 2022
2 parents bb69a5e + e3b704d commit 6c46bd0
Show file tree
Hide file tree
Showing 12 changed files with 111 additions and 41 deletions.
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "grant"
version = "0.0.1-beta.3"
version = "0.0.1-beta.4"
edition = "2021"
authors = ["Duyet Le <me@duyet.net>"]
license = "MIT"
Expand Down
1 change: 1 addition & 0 deletions examples/example.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ users:
- role_table_level
- name: duyet2
password: 1234567890
update_password: true
roles:
- role_database_level
- role_schema_level
Expand Down
7 changes: 7 additions & 0 deletions homebrew.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
name=grant
version=0.0.1-beta.4

cargo build --release
cd target/release
tar -czf $name-$version-x86_64-apple-darwin.tar.gz $name
shasum -a 256 $name-$version-x86_64-apple-darwin.tar.gz
55 changes: 36 additions & 19 deletions src/apply.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,10 @@ use std::path::PathBuf;
/// If the dryrun flag is set, the changes will not be applied.
pub fn apply(target: &PathBuf, dryrun: bool) -> Result<()> {
if target.is_dir() {
return Err(anyhow!("The target is a directory"));
return Err(anyhow!(
"directory is not supported yet ({})",
target.display()
));
}

let config = Config::new(&target)?;
Expand All @@ -22,10 +25,10 @@ pub fn apply(target: &PathBuf, dryrun: bool) -> Result<()> {
let users_in_config = config.users.clone();

// Apply users changes (new users, update password)
apply_users(&mut conn, &users_in_db, &users_in_config, dryrun)?;
create_or_update_users(&mut conn, &users_in_db, &users_in_config, dryrun)?;

// Apply roles privileges to cluster (database role, schema role, table role)
apply_privileges(&mut conn, &config, dryrun)?;
create_or_update_privileges(&mut conn, &config, dryrun)?;

Ok(())
}
Expand Down Expand Up @@ -62,7 +65,7 @@ pub fn apply_all(target: &PathBuf, dryrun: bool) -> Result<()> {
/// If user is in both, compare passwords and update if needed
///
/// Show the summary as table of users created, updated, deleted
fn apply_users(
fn create_or_update_users(
conn: &mut DbConnection,
users_in_db: &[User],
users_in_config: &[UserInConfig],
Expand All @@ -77,31 +80,45 @@ fn apply_users(
match user_in_db {
// User in config and in database
Some(user_in_db) => {
// TODO: Update password if needed, currently we can't compare the password

// Do nothing if user is not changed
summary.push(vec![
user_in_db.name.clone(),
"no action (already exists)".to_string(),
]);
// Update password if `update_password` is set to true
if user.update_password.unwrap_or(false) {
let sql = user.to_sql_update();

if dryrun {
info!("{}: {}", Purple.paint("Dry-run"), Purple.paint(sql));
summary.push(vec![
format!("{}", user.name),
format!("{}", Green.paint("would update password")),
]);
} else {
conn.execute(&sql, &[])?;
info!("{}: {}", Green.paint("Success"), Purple.paint(sql));
summary.push(vec![user.name.clone(), "password updated".to_string()]);
}
} else {
// Do nothing if user is not changed
summary.push(vec![
user_in_db.name.clone(),
"no action (already exists)".to_string(),
]);
}
}

// User in config but not in database
None => {
let sql = user.to_sql_create();

if dryrun {
info!("{}: {}", Purple.paint("Dry-run"), sql);
summary.push(vec![
user.name.clone(),
format!("would create (dryrun) {}", sql),
]);
} else {
conn.execute(&sql, &[])?;
info!("{}: {}", Green.paint("Success"), sql);
summary.push(vec![user.name.clone(), format!("created {}", sql)]);
}

// Update summary
summary.push(vec![user.name.clone(), "created".to_string()]);
}
}
}
Expand All @@ -127,7 +144,11 @@ fn apply_users(
/// If the privileges are not in the database, they will be granted to user.
/// If the privileges are in the database, they will be updated.
/// If the privileges are not in the configuration, they will be revoked from user.
fn apply_privileges(conn: &mut DbConnection, config: &Config, dryrun: bool) -> Result<()> {
fn create_or_update_privileges(
conn: &mut DbConnection,
config: &Config,
dryrun: bool,
) -> Result<()> {
let mut summary = vec![vec![
"User".to_string(),
"Role Name".to_string(),
Expand All @@ -141,10 +162,6 @@ fn apply_privileges(conn: &mut DbConnection, config: &Config, dryrun: bool) -> R
"---".to_string(),
]);

// let database_privileges = conn.get_user_database_privileges()?;
// let schema_privileges = conn.get_user_schema_privileges()?;
// let table_privileges = conn.get_user_table_privileges()?;

// Loop through users in config
// Get the user Role object by the user.roles[*].name
// Apply the Role sql privileges to the cluster
Expand Down
2 changes: 1 addition & 1 deletion src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ pub enum Command {
/// Apply a configuration to a redshift by file name.
/// Yaml format are accepted.
Apply {
/// The path to the file to read
/// The path to the file to read, directory is not supported yet.
#[structopt(short, long, parse(from_os_str))]
file: PathBuf,

Expand Down
8 changes: 3 additions & 5 deletions src/config/connection.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,8 @@ impl Connection {
}
}

// xpaned environtment variables in the `url` field.
// Expand environment variables in the `url` field.
// For example: postgres://user:${PASSWORD}@host:port/database
/// Expand environment variables in the `url` field.
/// For example: postgres://user:${PASSWORD}@host:port/database
pub fn expand_env_vars(&self) -> Result<Self> {
let mut connection = self.clone();

Expand All @@ -61,7 +60,7 @@ impl Connection {
}
}

// Implement default values for connection type and url.
/// Implement default values for connection type and url.
impl Default for Connection {
fn default() -> Self {
Self {
Expand All @@ -71,7 +70,6 @@ impl Default for Connection {
}
}

// Test Connection.
#[cfg(test)]
mod tests {
use super::*;
Expand Down
11 changes: 7 additions & 4 deletions src/config/role_database.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,13 @@ pub struct RoleDatabaseLevel {
}

impl RoleDatabaseLevel {
// { GRANT | REVOKE } { { CREATE | TEMPORARY | TEMP } [,...] | ALL [ PRIVILEGES ] }
// ON DATABASE db_name [, ...]
// TO { username [ WITH GRANT OPTION ] | GROUP group_name | PUBLIC } [, ...]
/// Generate role database to SQL.
///
/// ```sql
/// { GRANT | REVOKE } { { CREATE | TEMPORARY | TEMP } [,...] | ALL [ PRIVILEGES ] }
/// ON DATABASE db_name [, ...]
/// TO { username [ WITH GRANT OPTION ] | GROUP group_name | PUBLIC } [, ...]
/// ```
pub fn to_sql(&self, user: &str) -> String {
// grant all if no grants specified or contains "ALL"
let grants = if self.grants.is_empty() || self.grants.contains(&"ALL".to_string()) {
Expand Down Expand Up @@ -80,7 +84,6 @@ impl RoleValidate for RoleDatabaseLevel {
}
}

// Test
#[cfg(test)]
mod tests {
use super::*;
Expand Down
10 changes: 7 additions & 3 deletions src/config/role_schema.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,13 @@ pub struct RoleSchemaLevel {
}

impl RoleSchemaLevel {
// { GRANT | REVOKE } { { CREATE | USAGE } [,...] | ALL [ PRIVILEGES ] }
// ON SCHEMA schema_name [, ...]
// TO { username [ WITH GRANT OPTION ] | GROUP group_name | PUBLIC } [, ...]
/// Generate role schema to sql.
///
/// ```sql
/// { GRANT | REVOKE } { { CREATE | USAGE } [,...] | ALL [ PRIVILEGES ] }
/// ON SCHEMA schema_name [, ...]
/// TO { username [ WITH GRANT OPTION ] | GROUP group_name | PUBLIC } [, ...]
/// ```
pub fn to_sql(&self, user: &str) -> String {
// grant all privileges if no grants are specified or if grants contains "ALL"
let grants = if self.grants.is_empty() || self.grants.contains(&"ALL".to_string()) {
Expand Down
11 changes: 7 additions & 4 deletions src/config/role_table.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,9 +55,13 @@ impl Table {
}

impl RoleTableLevel {
// {GRANT | REVOKE} { { SELECT | INSERT | UPDATE | DELETE | DROP | REFERENCES } [,...] | ALL [ PRIVILEGES ] }
// ON { [ TABLE ] table_name [, ...] | ALL TABLES IN SCHEMA schema_name [, ...] }
// TO { username [ WITH GRANT OPTION ] | GROUP group_name | PUBLIC } [, ...]
/// Generate role table to sql.
///
/// ```sql
/// {GRANT | REVOKE} { { SELECT | INSERT | UPDATE | DELETE | DROP | REFERENCES } [,...] | ALL [ PRIVILEGES ] }
/// ON { [ TABLE ] table_name [, ...] | ALL TABLES IN SCHEMA schema_name [, ...] }
/// TO { username [ WITH GRANT OPTION ] | GROUP group_name | PUBLIC } [, ...]
/// ```
pub fn to_sql(&self, user: &str) -> String {
let mut sqls = vec![];
let mut tables = self
Expand Down Expand Up @@ -204,7 +208,6 @@ impl RoleValidate for RoleTableLevel {
}
}

// Test
#[cfg(test)]
mod tests {
use super::*;
Expand Down
36 changes: 34 additions & 2 deletions src/config/user.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ pub struct User {
pub name: String,
// password is optional
pub password: Option<String>,
// Need to update password at anytime? by default is false
pub update_password: Option<bool>,
pub roles: Vec<String>,
}

Expand All @@ -19,9 +21,17 @@ impl User {
format!("CREATE USER {}{};", self.name, password)
}

pub fn to_sql_update(&self) -> String {
let password = match &self.password {
Some(p) => format!(" WITH PASSWORD '{}'", p),
None => "".to_string(),
};

format!("ALTER USER {}{};", self.name, password)
}

pub fn to_sql_drop(&self) -> String {
let sql = format!("DROP USER IF EXISTS {};", self.name);
sql
format!("DROP USER IF EXISTS {};", self.name)
}

pub fn validate(&self) -> Result<()> {
Expand Down Expand Up @@ -58,18 +68,33 @@ mod tests {
let user = User {
name: "test".to_string(),
password: Some("test".to_string()),
update_password: Some(true),
roles: vec!["test".to_string()],
};

let sql = user.to_sql_create();
assert_eq!(sql, "CREATE USER test WITH PASSWORD 'test';");
}

#[test]
fn test_user_to_sql_update() {
let user = User {
name: "test".to_string(),
password: Some("test".to_string()),
update_password: Some(true),
roles: vec!["test".to_string()],
};

let sql = user.to_sql_update();
assert_eq!(sql, "ALTER USER test WITH PASSWORD 'test';");
}

#[test]
fn test_user_to_sql_drop() {
let user = User {
name: "test".to_string(),
password: Some("test".to_string()),
update_password: Some(true),
roles: vec!["test".to_string()],
};

Expand All @@ -82,6 +107,7 @@ mod tests {
let user = User {
name: "test".to_string(),
password: Some("test".to_string()),
update_password: Some(true),
roles: vec!["test".to_string()],
};

Expand All @@ -93,6 +119,7 @@ mod tests {
let user = User {
name: "".to_string(),
password: Some("test".to_string()),
update_password: Some(true),
roles: vec!["test".to_string()],
};

Expand All @@ -104,6 +131,7 @@ mod tests {
let user = User {
name: "test".to_string(),
password: None,
update_password: Some(true),
roles: vec!["test".to_string()],
};

Expand All @@ -115,6 +143,7 @@ mod tests {
let user = User {
name: "test".to_string(),
password: Some("test".to_string()),
update_password: Some(true),
roles: vec![],
};

Expand All @@ -126,6 +155,7 @@ mod tests {
let user = User {
name: "test".to_string(),
password: Some("test".to_string()),
update_password: Some(true),
roles: vec!["test".to_string()],
};

Expand All @@ -137,6 +167,7 @@ mod tests {
let user = User {
name: "test".to_string(),
password: Some("test".to_string()),
update_password: Some(true),
roles: vec!["test".to_string()],
};

Expand All @@ -148,6 +179,7 @@ mod tests {
let user = User {
name: "test".to_string(),
password: Some("test".to_string()),
update_password: Some(true),
roles: vec!["test".to_string()],
};

Expand Down
7 changes: 6 additions & 1 deletion src/gen.rs
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,12 @@ mod tests {
gen_password(10, true, Some("test".to_string()), Some("test".to_string()));
gen_password(10, false, None, None);
gen_password(10, false, Some("test".to_string()), None);
gen_password(10, false, Some("test".to_string()), Some("test".to_string()));
gen_password(
10,
false,
Some("test".to_string()),
Some("test".to_string()),
);
}

// Test gen_md5_password
Expand Down
2 changes: 1 addition & 1 deletion tests/cli-apply.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ fn apply_target_is_directory() {
.arg(dir.path())
.assert()
.failure()
.stderr(predicate::str::contains("is a directory"));
.stderr(predicate::str::contains("directory is not supported"));

// cleanup
dir.close().unwrap();
Expand Down

0 comments on commit 6c46bd0

Please sign in to comment.