grant/
gen.rs

1use crate::config::Config;
2use ansi_term::Colour::Green;
3use anyhow::{Context, Result};
4use log::info;
5use md5::compute;
6use rand::{thread_rng, Rng};
7use std::fs;
8use std::path::Path;
9
10/// Generate project template to given target
11pub fn gen(target: &Path) -> Result<()> {
12    let target = target.to_path_buf();
13
14    // Skip if target already exists
15    if target.exists() {
16        info!("target already exists");
17        return Ok(());
18    }
19
20    fs::create_dir_all(&target).context(format!("Failed to create directory {:?}", &target))?;
21    info!("creating path: {:?}", target);
22
23    let config = Config::default();
24    let config_str = serde_yaml::to_string(&config)
25        .context("Failed to serialize default configuration to YAML")?;
26
27    // Write config_str to target/config.yml
28    let config_path = target.join("config.yml");
29    fs::write(&config_path, config_str)
30        .context(format!("Failed to write config to {:?}", config_path))?;
31    info!("Generated: {:?}", config_path);
32
33    Ok(())
34}
35
36/// Generating password with given length
37pub fn gen_password(
38    length: u8,
39    no_special: bool,
40    username: Option<String>,
41    password: Option<String>,
42) {
43    // If not password is given, generate random password
44    let password = match password {
45        Some(p) => p,
46        None => {
47            let chars: &[u8] = if no_special {
48                b"ABCDEFGHIJKLMNOPQRSTUVWXYZ\
49                  abcdefghijklmnopqrstuvwxyz\
50                  0123456789"
51            } else {
52                b"ABCDEFGHIJKLMNOPQRSTUVWXYZ\
53                  abcdefghijklmnopqrstuvwxyz\
54                  0123456789)(*&^%#!~"
55            };
56
57            // Use thread_rng for random number generation
58            let mut rng = thread_rng();
59            let password: String = (0..length)
60                .map(|_| {
61                    let idx = rng.gen_range(0..chars.len());
62                    chars[idx] as char
63                })
64                .collect();
65
66            password
67        }
68    };
69
70    println!("Generated password: {}", Green.paint(password.clone()));
71
72    if let Some(username) = username {
73        let password_hash = gen_md5_password(&password, &username);
74        println!(
75            "Generated MD5 (user: {}): {}",
76            username,
77            Green.paint(password_hash)
78        );
79        println!("\nHint: https://docs.aws.amazon.com/redshift/latest/dg/r_CREATE_USER.html");
80    } else {
81        println!("\nHint: Please provide --username to generate MD5");
82    }
83}
84
85/// Generate md5 password hash from username and password
86/// 1. Concatenate the password and username
87/// 2. Hash the concatenated string
88/// 3. Concatenate 'md5' in front of the MD5 hash string
89/// https://docs.aws.amazon.com/redshift/latest/dg/r_CREATE_USER.html
90fn gen_md5_password(password: &str, username: &str) -> String {
91    format!(
92        "md5{:x}",
93        compute(format!("{}{}", password, username).as_bytes())
94    )
95}
96
97#[cfg(test)]
98mod tests {
99    use super::*;
100
101    // Test gen_password
102    #[test]
103    fn test_gen_password() {
104        gen_password(10, true, None, None);
105        gen_password(10, true, Some("test".to_string()), None);
106        gen_password(10, true, Some("test".to_string()), Some("test".to_string()));
107        gen_password(10, false, None, None);
108        gen_password(10, false, Some("test".to_string()), None);
109        gen_password(
110            10,
111            false,
112            Some("test".to_string()),
113            Some("test".to_string()),
114        );
115    }
116
117    // Test gen_md5_password
118    #[test]
119    fn test_gen_md5_password() {
120        assert_eq!(
121            gen_md5_password("test", "test"),
122            "md505a671c66aefea124cc08b76ea6d30bb"
123        );
124    }
125}