Browse Source

Add generate subcommand.

tags/v0.3.1^0
Thomas Kerber 2 years 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 @@
pipeline:
build:
image: drwx/goblin-deps
environment:
- CARGO_HOME=/cache/cargo
- CARGO_TARGET_DIR=/cache/target
commands:
- rm -rf target/doc
- cargo test --all --features weak-scrypt
- cargo build
- cargo doc --all --no-deps
volumes:
- /var/cache/drone/goblin:/cache
deploy:
image: debian
image: drwx/goblin-deps
commands:
- rm -rf /doc/*
- cp -a /cache/target/doc/* /doc/
- cargo doc --all --no-deps
- rm -rf /doc/*
- cp -a target/doc/* /doc/
volumes:
- /var/cache/drone/goblin:/cache
- /var/www/doc.drwx.org/goblin:/doc
when:
branch: master

+ 1
- 1
Cargo.lock View File

@@ -249,7 +249,7 @@ dependencies = [

[[package]]
name = "goblin"
version = "0.3.0"
version = "0.3.1"
dependencies = [
"atty 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
"chrono 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",

+ 1
- 1
Cargo.toml View File

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

+ 32
- 0
src/args.rs View File

@@ -30,6 +30,7 @@ pub fn goblin() -> App<'static, 'static> {
.subcommand(merge())
.subcommand(commit())
.subcommand(discard())
.subcommand(generate())
}

pub fn daemon() -> App<'static, 'static> {
@@ -115,6 +116,37 @@ pub fn insert() -> App<'static, 'static> {
))
}

pub fn generate() -> App<'static, 'static> {
authenticated(subcommand("generate"))
.about(
"Randomly generate a new password. Generates from lowercase, \
uppercase, and digits by default.",
)
.display_order(0)
.arg(nocommit())
.arg(
Arg::from_usage(
"[LENGTH] 'The length of the password to generate. \
Defaults to 20'",
).validator(validate_usize),
)
.arg(Arg::from_usage("[PATH] 'The path to insert into'"))
.arg(Arg::from_usage(
"-l --lowercase 'Select from characters a-z'",
))
.arg(Arg::from_usage(
"-u --uppercase 'Select from characters A-Z'",
))
.arg(Arg::from_usage("-d --digit 'Select from characters 0-9'"))
.arg(Arg::from_usage(
"-s --symbol 'Select from common ascii symbols'",
))
.arg(Arg::from_usage(
"-C --custom [CHARACTERS] 'Select from a custom string of \
characters'",
))
}

pub fn import() -> App<'static, 'static> {
subcommand("import").arg(askpass()).about(
"Imports a JSON map representing the keyring, piped to STDIN",

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

@@ -0,0 +1,81 @@
use cfg::Config;
use clap::ArgMatches;
use err::Result;
use goblin_core::{Access, Value, ZeroBox};
use rand::Rng;
use rand::os::OsRng;
use std::borrow::Cow;
use term::Stylable;
use util::{ClientExt, require_client, should_autocommit};

const LOWERCASE: &'static str = "abcdefghijklmnopqrstuvwxyz";
const UPPERCASE: &'static str = "ABCDEFGHIJKLMNOPQRSTUVWXZY";
const DIGITS: &'static str = "0123456789";
const SYMBOLS: &'static str = "!@#$%^&*()-_+=\\|[]{}<>;:'\"`";

pub fn main(matches: &ArgMatches) -> Result<()> {
let cfg = Config::init(matches)?;


let mut syms: Vec<Cow<str>> = Vec::new();
if matches.is_present("lowercase") {
syms.push(LOWERCASE.into());
}
if matches.is_present("uppercase") {
syms.push(UPPERCASE.into());
}
if matches.is_present("digit") {
syms.push(DIGITS.into());
}
if matches.is_present("symbol") {
syms.push(SYMBOLS.into());
}
if let Some(s) = matches.value_of("custom") {
syms.push(s.into());
}
let mut pool = syms.iter().flat_map(|s| s.chars()).collect::<Vec<_>>();
// If no flags were specified, default to -lud
if pool.len() == 0 {
pool.extend(LOWERCASE.chars());
pool.extend(UPPERCASE.chars());
pool.extend(DIGITS.chars());
}
pool.sort();
pool.dedup();
let mut rng = OsRng::new()?;
let len = if let Some(l) = matches.value_of("LENGTH") {
l.parse::<usize>().unwrap()
} else {
20
};
let pass: String =
(0..len).map(|_| rng.choose(&pool[..]).unwrap()).collect();
if let Some(path) = matches.value_of("PATH") {
let mut cli = require_client(&cfg)?;
let value = Value::new(pass.into());
cli.require(
&Access::path(&ZeroBox::new(path.to_owned()), true),
&cfg,
)?;
let commit = should_autocommit(&mut cli, matches)?;
let _: () = cli.call("goblin.keyring.insert", (path, value))?;
if commit {
let _: () = cli.call("goblin.keyring.commit", ())?;
let _: () = cli.call("goblin.vault.flush", ())?;
}
if cfg.verbosity >= 0 {
println!("Generated {}.", path.bold());
}
} else {
if cfg.verbosity >= 0 {
print!("Your new password is: ");
}
println!("{}", pass.green().bold());
if cfg.verbosity >= 0 {
println!(
"Note: you can specify a path to store the password under directly."
);
}
}
Ok(())
}

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

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

+ 2
- 1
src/main.rs View File

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

Loading…
Cancel
Save