From f881ad5ea512acbce183ad83d050529fc2c752b0 Mon Sep 17 00:00:00 2001 From: Jordan Doyle Date: Tue, 7 Jul 2020 00:26:01 +0100 Subject: [PATCH] Add option to generate passwords --- onep-api-op/src/lib.rs | 49 ++++++++++++++++++++++++++++++++++++++++++++++++- onep-api/src/lib.rs | 7 +++++++ onep-cli/src/main.rs | 113 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------------------------------------------- 3 files changed, 125 insertions(+), 44 deletions(-) diff --git a/onep-api-op/src/lib.rs b/onep-api-op/src/lib.rs index c121b72..8dda8d6 100644 --- a/onep-api-op/src/lib.rs +++ b/onep-api-op/src/lib.rs @@ -2,6 +2,7 @@ use serde::Deserialize; use serde_json::Value; +use std::borrow::Cow; use std::process::Command; #[derive(thiserror::Error, Debug)] @@ -188,9 +189,20 @@ impl Into for GetItemSectionField { } } +#[derive(Debug, Deserialize)] +#[serde(rename_all = "camelCase")] +struct CreateItem { + uuid: String, + vault_uuid: String, +} + pub struct OnepasswordOp {} -fn exec(args: &[&str]) -> Result, Error> { +fn exec(args: I) -> Result, Error> +where + I: IntoIterator, + S: AsRef, +{ let cmd = Command::new("op") .args(args) .output() @@ -251,4 +263,39 @@ impl onep_api::OnePassword for OnepasswordOp { Ok(Some(ret.into())) } + + fn generate( + &self, + name: &str, + username: Option<&str>, + url: Option<&str>, + tags: Option<&str>, + ) -> Result { + let mut args = Vec::with_capacity(12); + + args.push(Cow::Borrowed("create")); + args.push(Cow::Borrowed("item")); + args.push(Cow::Borrowed("Login")); + args.push(Cow::Borrowed("--generate-password")); + args.push(Cow::Borrowed("--title")); + args.push(Cow::Borrowed(name)); + + if let Some(url) = url { + args.push(Cow::Borrowed("--url")); + args.push(Cow::Borrowed(url)); + } + + if let Some(tags) = tags { + args.push(Cow::Borrowed("--tags")); + args.push(Cow::Borrowed(tags)); + } + + if let Some(username) = username { + args.push(Cow::Owned(format!("username={}", username))); + } + + let ret: CreateItem = serde_json::from_slice(&exec(args.iter().map(Cow::as_ref))?)?; + + Ok(self.get(&ret.uuid)?.unwrap_or_else(|| unreachable!())) + } } diff --git a/onep-api/src/lib.rs b/onep-api/src/lib.rs index 55f78d0..549fa50 100644 --- a/onep-api/src/lib.rs +++ b/onep-api/src/lib.rs @@ -48,4 +48,11 @@ pub trait OnePassword { fn vaults(&self) -> Result, Self::Error>; fn search(&self, terms: Option<&str>) -> Result, Self::Error>; fn get(&self, uuid: &str) -> Result, Self::Error>; + fn generate( + &self, + name: &str, + username: Option<&str>, + url: Option<&str>, + tags: Option<&str>, + ) -> Result; } diff --git a/onep-cli/src/main.rs b/onep-cli/src/main.rs index 9840657..a0e07ed 100644 --- a/onep-cli/src/main.rs +++ b/onep-cli/src/main.rs @@ -21,8 +21,8 @@ enum Error { /// 1password cli for humans enum Opt { /// List all items - #[clap(alias = "list")] - Ls { + #[clap(alias = "ls")] + List { #[clap(long, short = 'i')] show_uuids: bool, #[clap(long, short = 'n')] @@ -35,6 +35,21 @@ enum Opt { /// Show existing password and optionally put it on the clipboard #[clap(alias = "get")] Show { uuid: String }, + /// Generates a new password and stores it in your password store + #[clap(alias = "gen")] + Generate { + /// Name of the login to create + name: String, + /// Username to associate with the login + #[clap(long, short = 'n')] + username: Option, + /// URL to associate with the login + #[clap(long, short = 'u')] + url: Option, + /// Comma-separated list of tags to associate with the login + #[clap(long, short = 't')] + tags: Option, + }, } fn main() { @@ -44,14 +59,13 @@ fn main() { } } -#[warn(clippy::too_many_lines)] #[allow(clippy::non_ascii_literal)] fn run(imp: &T) -> anyhow::Result<()> where T::Error: 'static + std::error::Error + Send + Sync, { match Opt::parse() { - Opt::Ls { + Opt::List { show_uuids, show_account_names, } => { @@ -136,52 +150,65 @@ where } Opt::Show { uuid } => { let result = imp.get(&uuid)?.ok_or(Error::NotFound)?; + show(result); + } + Opt::Generate { + name, + username, + url, + tags, + } => { + let result = + imp.generate(&name, username.as_deref(), url.as_deref(), tags.as_deref())?; + show(result); + } + } - let mut table = Table::new(); - table.style = TableStyle::extended(); - - table.add_row(Row::new(vec![TableCell::new_with_alignment( - result.title, - 2, - Alignment::Center, - )])); - - for field in result.fields { - table.add_row(Row::new(vec![ - TableCell::new(field.name), - TableCell::new_with_alignment(field.value, 1, Alignment::Right), - ])); - } + Ok(()) +} - println!("{}", table.render()); +fn show(item: onep_api::Item) { + let mut table = Table::new(); + table.style = TableStyle::extended(); + + table.add_row(Row::new(vec![TableCell::new_with_alignment( + item.title, + 2, + Alignment::Center, + )])); + + for field in item.fields { + table.add_row(Row::new(vec![ + TableCell::new(field.name), + TableCell::new_with_alignment(field.value, 1, Alignment::Right), + ])); + } - for section in result.sections { - if section.fields.is_empty() { - continue; - } + println!("{}", table.render()); - let mut table = Table::new(); - table.style = TableStyle::extended(); + for section in item.sections { + if section.fields.is_empty() { + continue; + } - if !section.name.is_empty() { - table.add_row(Row::new(vec![TableCell::new_with_alignment( - section.name, - 2, - Alignment::Center, - )])); - } + let mut table = Table::new(); + table.style = TableStyle::extended(); - for field in section.fields { - table.add_row(Row::new(vec![ - TableCell::new(field.name), - TableCell::new_with_alignment(field.value, 1, Alignment::Right), - ])); - } + if !section.name.is_empty() { + table.add_row(Row::new(vec![TableCell::new_with_alignment( + section.name, + 2, + Alignment::Center, + )])); + } - println!("{}", table.render()); - } + for field in section.fields { + table.add_row(Row::new(vec![ + TableCell::new(field.name), + TableCell::new_with_alignment(field.value, 1, Alignment::Right), + ])); } - } - Ok(()) + println!("{}", table.render()); + } } -- libgit2 1.7.2