Browse Source

Add generate subcommand.

Thomas Kerber 1 year ago
parent
commit
6ca0841baa
Signed by: Thomas Kerber <tk@drwx.org> GPG Key ID: 8489B911F9ED617B
7 changed files with 122 additions and 13 deletions
  1. 4
    10
      .drone.yml
  2. 1
    1
      Cargo.lock
  3. 1
    1
      Cargo.toml
  4. 32
    0
      src/args.rs
  5. 81
    0
      src/cmd/generate.rs
  6. 1
    0
      src/cmd/mod.rs
  7. 2
    1
      src/main.rs

+ 4
- 10
.drone.yml View File

@@ -1,23 +1,17 @@
1 1
 pipeline:
2 2
   build:
3 3
     image: drwx/goblin-deps
4
-    environment:
5
-      - CARGO_HOME=/cache/cargo
6
-      - CARGO_TARGET_DIR=/cache/target
7 4
     commands:
8 5
       - rm -rf target/doc
9 6
       - cargo test --all --features weak-scrypt
10 7
       - cargo build
11
-      - cargo doc --all --no-deps
12
-    volumes:
13
-      - /var/cache/drone/goblin:/cache
14 8
   deploy:
15
-    image: debian
9
+    image: drwx/goblin-deps
16 10
     commands:
17
-       - rm -rf /doc/*
18
-       - cp -a /cache/target/doc/* /doc/
11
+      - cargo doc --all --no-deps
12
+      - rm -rf /doc/*
13
+      - cp -a target/doc/* /doc/
19 14
     volumes:
20
-      - /var/cache/drone/goblin:/cache
21 15
       - /var/www/doc.drwx.org/goblin:/doc
22 16
     when:
23 17
       branch: master

+ 1
- 1
Cargo.lock View File

@@ -249,7 +249,7 @@ dependencies = [
249 249
 
250 250
 [[package]]
251 251
 name = "goblin"
252
-version = "0.3.0"
252
+version = "0.3.1"
253 253
 dependencies = [
254 254
  "atty 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
255 255
  "chrono 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",

+ 1
- 1
Cargo.toml View File

@@ -1,6 +1,6 @@
1 1
 [package]
2 2
 name = "goblin"
3
-version = "0.3.0"
3
+version = "0.3.1"
4 4
 authors = ["Thomas Kerber <tk@drwx.org>"]
5 5
 description = "Version controlled password management."
6 6
 publish = false

+ 32
- 0
src/args.rs View File

@@ -30,6 +30,7 @@ pub fn goblin() -> App<'static, 'static> {
30 30
         .subcommand(merge())
31 31
         .subcommand(commit())
32 32
         .subcommand(discard())
33
+        .subcommand(generate())
33 34
 }
34 35
 
35 36
 pub fn daemon() -> App<'static, 'static> {
@@ -115,6 +116,37 @@ pub fn insert() -> App<'static, 'static> {
115 116
         ))
116 117
 }
117 118
 
119
+pub fn generate() -> App<'static, 'static> {
120
+    authenticated(subcommand("generate"))
121
+        .about(
122
+            "Randomly generate a new password. Generates from lowercase, \
123
+            uppercase, and digits by default.",
124
+        )
125
+        .display_order(0)
126
+        .arg(nocommit())
127
+        .arg(
128
+            Arg::from_usage(
129
+                "[LENGTH] 'The length of the password to generate. \
130
+                Defaults to 20'",
131
+            ).validator(validate_usize),
132
+        )
133
+        .arg(Arg::from_usage("[PATH] 'The path to insert into'"))
134
+        .arg(Arg::from_usage(
135
+            "-l --lowercase 'Select from characters a-z'",
136
+        ))
137
+        .arg(Arg::from_usage(
138
+            "-u --uppercase 'Select from characters A-Z'",
139
+        ))
140
+        .arg(Arg::from_usage("-d --digit 'Select from characters 0-9'"))
141
+        .arg(Arg::from_usage(
142
+            "-s --symbol 'Select from common ascii symbols'",
143
+        ))
144
+        .arg(Arg::from_usage(
145
+            "-C --custom [CHARACTERS] 'Select from a custom string of \
146
+            characters'",
147
+        ))
148
+}
149
+
118 150
 pub fn import() -> App<'static, 'static> {
119 151
     subcommand("import").arg(askpass()).about(
120 152
         "Imports a JSON map representing the keyring, piped to STDIN",

+ 81
- 0
src/cmd/generate.rs View File

@@ -0,0 +1,81 @@
1
+use cfg::Config;
2
+use clap::ArgMatches;
3
+use err::Result;
4
+use goblin_core::{Access, Value, ZeroBox};
5
+use rand::Rng;
6
+use rand::os::OsRng;
7
+use std::borrow::Cow;
8
+use term::Stylable;
9
+use util::{ClientExt, require_client, should_autocommit};
10
+
11
+const LOWERCASE: &'static str = "abcdefghijklmnopqrstuvwxyz";
12
+const UPPERCASE: &'static str = "ABCDEFGHIJKLMNOPQRSTUVWXZY";
13
+const DIGITS: &'static str = "0123456789";
14
+const SYMBOLS: &'static str = "!@#$%^&*()-_+=\\|[]{}<>;:'\"`";
15
+
16
+pub fn main(matches: &ArgMatches) -> Result<()> {
17
+    let cfg = Config::init(matches)?;
18
+
19
+
20
+    let mut syms: Vec<Cow<str>> = Vec::new();
21
+    if matches.is_present("lowercase") {
22
+        syms.push(LOWERCASE.into());
23
+    }
24
+    if matches.is_present("uppercase") {
25
+        syms.push(UPPERCASE.into());
26
+    }
27
+    if matches.is_present("digit") {
28
+        syms.push(DIGITS.into());
29
+    }
30
+    if matches.is_present("symbol") {
31
+        syms.push(SYMBOLS.into());
32
+    }
33
+    if let Some(s) = matches.value_of("custom") {
34
+        syms.push(s.into());
35
+    }
36
+    let mut pool = syms.iter().flat_map(|s| s.chars()).collect::<Vec<_>>();
37
+    // If no flags were specified, default to -lud
38
+    if pool.len() == 0 {
39
+        pool.extend(LOWERCASE.chars());
40
+        pool.extend(UPPERCASE.chars());
41
+        pool.extend(DIGITS.chars());
42
+    }
43
+    pool.sort();
44
+    pool.dedup();
45
+    let mut rng = OsRng::new()?;
46
+    let len = if let Some(l) = matches.value_of("LENGTH") {
47
+        l.parse::<usize>().unwrap()
48
+    } else {
49
+        20
50
+    };
51
+    let pass: String =
52
+        (0..len).map(|_| rng.choose(&pool[..]).unwrap()).collect();
53
+    if let Some(path) = matches.value_of("PATH") {
54
+        let mut cli = require_client(&cfg)?;
55
+        let value = Value::new(pass.into());
56
+        cli.require(
57
+            &Access::path(&ZeroBox::new(path.to_owned()), true),
58
+            &cfg,
59
+        )?;
60
+        let commit = should_autocommit(&mut cli, matches)?;
61
+        let _: () = cli.call("goblin.keyring.insert", (path, value))?;
62
+        if commit {
63
+            let _: () = cli.call("goblin.keyring.commit", ())?;
64
+            let _: () = cli.call("goblin.vault.flush", ())?;
65
+        }
66
+        if cfg.verbosity >= 0 {
67
+            println!("Generated {}.", path.bold());
68
+        }
69
+    } else {
70
+        if cfg.verbosity >= 0 {
71
+            print!("Your new password is: ");
72
+        }
73
+        println!("{}", pass.green().bold());
74
+        if cfg.verbosity >= 0 {
75
+            println!(
76
+                "Note: you can specify a path to store the password under directly."
77
+            );
78
+        }
79
+    }
80
+    Ok(())
81
+}

+ 1
- 0
src/cmd/mod.rs View File

@@ -20,3 +20,4 @@ pub mod lock;
20 20
 pub mod merge;
21 21
 pub mod commit;
22 22
 pub mod discard;
23
+pub mod generate;

+ 2
- 1
src/main.rs View File

@@ -70,7 +70,8 @@ fn main_err(matches: &ArgMatches) -> Result<()> {
70 70
         checkout,
71 71
         merge,
72 72
         commit,
73
-        discard
73
+        discard,
74
+        generate
74 75
     );
75 76
     let (cmd, subm) = matches.subcommand();
76 77
     // We are requiring a subcommand.

Loading…
Cancel
Save