Skip to content

Commit 6c46bd0

Browse files
authored
Merge pull request #21 from duyet/feat/force-password
2 parents bb69a5e + e3b704d commit 6c46bd0

12 files changed

+111
-41
lines changed

Cargo.toml

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "grant"
3-
version = "0.0.1-beta.3"
3+
version = "0.0.1-beta.4"
44
edition = "2021"
55
authors = ["Duyet Le <[email protected]>"]
66
license = "MIT"

examples/example.yaml

+1
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ users:
4141
- role_table_level
4242
- name: duyet2
4343
password: 1234567890
44+
update_password: true
4445
roles:
4546
- role_database_level
4647
- role_schema_level

homebrew.sh

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
name=grant
2+
version=0.0.1-beta.4
3+
4+
cargo build --release
5+
cd target/release
6+
tar -czf $name-$version-x86_64-apple-darwin.tar.gz $name
7+
shasum -a 256 $name-$version-x86_64-apple-darwin.tar.gz

src/apply.rs

+36-19
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,10 @@ use std::path::PathBuf;
1010
/// If the dryrun flag is set, the changes will not be applied.
1111
pub fn apply(target: &PathBuf, dryrun: bool) -> Result<()> {
1212
if target.is_dir() {
13-
return Err(anyhow!("The target is a directory"));
13+
return Err(anyhow!(
14+
"directory is not supported yet ({})",
15+
target.display()
16+
));
1417
}
1518

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

2427
// Apply users changes (new users, update password)
25-
apply_users(&mut conn, &users_in_db, &users_in_config, dryrun)?;
28+
create_or_update_users(&mut conn, &users_in_db, &users_in_config, dryrun)?;
2629

2730
// Apply roles privileges to cluster (database role, schema role, table role)
28-
apply_privileges(&mut conn, &config, dryrun)?;
31+
create_or_update_privileges(&mut conn, &config, dryrun)?;
2932

3033
Ok(())
3134
}
@@ -62,7 +65,7 @@ pub fn apply_all(target: &PathBuf, dryrun: bool) -> Result<()> {
6265
/// If user is in both, compare passwords and update if needed
6366
///
6467
/// Show the summary as table of users created, updated, deleted
65-
fn apply_users(
68+
fn create_or_update_users(
6669
conn: &mut DbConnection,
6770
users_in_db: &[User],
6871
users_in_config: &[UserInConfig],
@@ -77,31 +80,45 @@ fn apply_users(
7780
match user_in_db {
7881
// User in config and in database
7982
Some(user_in_db) => {
80-
// TODO: Update password if needed, currently we can't compare the password
81-
82-
// Do nothing if user is not changed
83-
summary.push(vec![
84-
user_in_db.name.clone(),
85-
"no action (already exists)".to_string(),
86-
]);
83+
// Update password if `update_password` is set to true
84+
if user.update_password.unwrap_or(false) {
85+
let sql = user.to_sql_update();
86+
87+
if dryrun {
88+
info!("{}: {}", Purple.paint("Dry-run"), Purple.paint(sql));
89+
summary.push(vec![
90+
format!("{}", user.name),
91+
format!("{}", Green.paint("would update password")),
92+
]);
93+
} else {
94+
conn.execute(&sql, &[])?;
95+
info!("{}: {}", Green.paint("Success"), Purple.paint(sql));
96+
summary.push(vec![user.name.clone(), "password updated".to_string()]);
97+
}
98+
} else {
99+
// Do nothing if user is not changed
100+
summary.push(vec![
101+
user_in_db.name.clone(),
102+
"no action (already exists)".to_string(),
103+
]);
104+
}
87105
}
88106

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

93111
if dryrun {
112+
info!("{}: {}", Purple.paint("Dry-run"), sql);
94113
summary.push(vec![
95114
user.name.clone(),
96115
format!("would create (dryrun) {}", sql),
97116
]);
98117
} else {
99118
conn.execute(&sql, &[])?;
119+
info!("{}: {}", Green.paint("Success"), sql);
100120
summary.push(vec![user.name.clone(), format!("created {}", sql)]);
101121
}
102-
103-
// Update summary
104-
summary.push(vec![user.name.clone(), "created".to_string()]);
105122
}
106123
}
107124
}
@@ -127,7 +144,11 @@ fn apply_users(
127144
/// If the privileges are not in the database, they will be granted to user.
128145
/// If the privileges are in the database, they will be updated.
129146
/// If the privileges are not in the configuration, they will be revoked from user.
130-
fn apply_privileges(conn: &mut DbConnection, config: &Config, dryrun: bool) -> Result<()> {
147+
fn create_or_update_privileges(
148+
conn: &mut DbConnection,
149+
config: &Config,
150+
dryrun: bool,
151+
) -> Result<()> {
131152
let mut summary = vec![vec![
132153
"User".to_string(),
133154
"Role Name".to_string(),
@@ -141,10 +162,6 @@ fn apply_privileges(conn: &mut DbConnection, config: &Config, dryrun: bool) -> R
141162
"---".to_string(),
142163
]);
143164

144-
// let database_privileges = conn.get_user_database_privileges()?;
145-
// let schema_privileges = conn.get_user_schema_privileges()?;
146-
// let table_privileges = conn.get_user_table_privileges()?;
147-
148165
// Loop through users in config
149166
// Get the user Role object by the user.roles[*].name
150167
// Apply the Role sql privileges to the cluster

src/cli.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ pub enum Command {
3939
/// Apply a configuration to a redshift by file name.
4040
/// Yaml format are accepted.
4141
Apply {
42-
/// The path to the file to read
42+
/// The path to the file to read, directory is not supported yet.
4343
#[structopt(short, long, parse(from_os_str))]
4444
file: PathBuf,
4545

src/config/connection.rs

+3-5
Original file line numberDiff line numberDiff line change
@@ -35,9 +35,8 @@ impl Connection {
3535
}
3636
}
3737

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

@@ -61,7 +60,7 @@ impl Connection {
6160
}
6261
}
6362

64-
// Implement default values for connection type and url.
63+
/// Implement default values for connection type and url.
6564
impl Default for Connection {
6665
fn default() -> Self {
6766
Self {
@@ -71,7 +70,6 @@ impl Default for Connection {
7170
}
7271
}
7372

74-
// Test Connection.
7573
#[cfg(test)]
7674
mod tests {
7775
use super::*;

src/config/role_database.rs

+7-4
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,13 @@ pub struct RoleDatabaseLevel {
2525
}
2626

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

83-
// Test
8487
#[cfg(test)]
8588
mod tests {
8689
use super::*;

src/config/role_schema.rs

+7-3
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,13 @@ pub struct RoleSchemaLevel {
2727
}
2828

2929
impl RoleSchemaLevel {
30-
// { GRANT | REVOKE } { { CREATE | USAGE } [,...] | ALL [ PRIVILEGES ] }
31-
// ON SCHEMA schema_name [, ...]
32-
// TO { username [ WITH GRANT OPTION ] | GROUP group_name | PUBLIC } [, ...]
30+
/// Generate role schema to sql.
31+
///
32+
/// ```sql
33+
/// { GRANT | REVOKE } { { CREATE | USAGE } [,...] | ALL [ PRIVILEGES ] }
34+
/// ON SCHEMA schema_name [, ...]
35+
/// TO { username [ WITH GRANT OPTION ] | GROUP group_name | PUBLIC } [, ...]
36+
/// ```
3337
pub fn to_sql(&self, user: &str) -> String {
3438
// grant all privileges if no grants are specified or if grants contains "ALL"
3539
let grants = if self.grants.is_empty() || self.grants.contains(&"ALL".to_string()) {

src/config/role_table.rs

+7-4
Original file line numberDiff line numberDiff line change
@@ -55,9 +55,13 @@ impl Table {
5555
}
5656

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

207-
// Test
208211
#[cfg(test)]
209212
mod tests {
210213
use super::*;

src/config/user.rs

+34-2
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ pub struct User {
66
pub name: String,
77
// password is optional
88
pub password: Option<String>,
9+
// Need to update password at anytime? by default is false
10+
pub update_password: Option<bool>,
911
pub roles: Vec<String>,
1012
}
1113

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

24+
pub fn to_sql_update(&self) -> String {
25+
let password = match &self.password {
26+
Some(p) => format!(" WITH PASSWORD '{}'", p),
27+
None => "".to_string(),
28+
};
29+
30+
format!("ALTER USER {}{};", self.name, password)
31+
}
32+
2233
pub fn to_sql_drop(&self) -> String {
23-
let sql = format!("DROP USER IF EXISTS {};", self.name);
24-
sql
34+
format!("DROP USER IF EXISTS {};", self.name)
2535
}
2636

2737
pub fn validate(&self) -> Result<()> {
@@ -58,18 +68,33 @@ mod tests {
5868
let user = User {
5969
name: "test".to_string(),
6070
password: Some("test".to_string()),
71+
update_password: Some(true),
6172
roles: vec!["test".to_string()],
6273
};
6374

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

79+
#[test]
80+
fn test_user_to_sql_update() {
81+
let user = User {
82+
name: "test".to_string(),
83+
password: Some("test".to_string()),
84+
update_password: Some(true),
85+
roles: vec!["test".to_string()],
86+
};
87+
88+
let sql = user.to_sql_update();
89+
assert_eq!(sql, "ALTER USER test WITH PASSWORD 'test';");
90+
}
91+
6892
#[test]
6993
fn test_user_to_sql_drop() {
7094
let user = User {
7195
name: "test".to_string(),
7296
password: Some("test".to_string()),
97+
update_password: Some(true),
7398
roles: vec!["test".to_string()],
7499
};
75100

@@ -82,6 +107,7 @@ mod tests {
82107
let user = User {
83108
name: "test".to_string(),
84109
password: Some("test".to_string()),
110+
update_password: Some(true),
85111
roles: vec!["test".to_string()],
86112
};
87113

@@ -93,6 +119,7 @@ mod tests {
93119
let user = User {
94120
name: "".to_string(),
95121
password: Some("test".to_string()),
122+
update_password: Some(true),
96123
roles: vec!["test".to_string()],
97124
};
98125

@@ -104,6 +131,7 @@ mod tests {
104131
let user = User {
105132
name: "test".to_string(),
106133
password: None,
134+
update_password: Some(true),
107135
roles: vec!["test".to_string()],
108136
};
109137

@@ -115,6 +143,7 @@ mod tests {
115143
let user = User {
116144
name: "test".to_string(),
117145
password: Some("test".to_string()),
146+
update_password: Some(true),
118147
roles: vec![],
119148
};
120149

@@ -126,6 +155,7 @@ mod tests {
126155
let user = User {
127156
name: "test".to_string(),
128157
password: Some("test".to_string()),
158+
update_password: Some(true),
129159
roles: vec!["test".to_string()],
130160
};
131161

@@ -137,6 +167,7 @@ mod tests {
137167
let user = User {
138168
name: "test".to_string(),
139169
password: Some("test".to_string()),
170+
update_password: Some(true),
140171
roles: vec!["test".to_string()],
141172
};
142173

@@ -148,6 +179,7 @@ mod tests {
148179
let user = User {
149180
name: "test".to_string(),
150181
password: Some("test".to_string()),
182+
update_password: Some(true),
151183
roles: vec!["test".to_string()],
152184
};
153185

src/gen.rs

+6-1
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,12 @@ mod tests {
9898
gen_password(10, true, Some("test".to_string()), Some("test".to_string()));
9999
gen_password(10, false, None, None);
100100
gen_password(10, false, Some("test".to_string()), None);
101-
gen_password(10, false, Some("test".to_string()), Some("test".to_string()));
101+
gen_password(
102+
10,
103+
false,
104+
Some("test".to_string()),
105+
Some("test".to_string()),
106+
);
102107
}
103108

104109
// Test gen_md5_password

tests/cli-apply.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ fn apply_target_is_directory() {
3434
.arg(dir.path())
3535
.assert()
3636
.failure()
37-
.stderr(predicate::str::contains("is a directory"));
37+
.stderr(predicate::str::contains("directory is not supported"));
3838

3939
// cleanup
4040
dir.close().unwrap();

0 commit comments

Comments
 (0)