Add option to generate passwords
Diff
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(-)
@@ -1,7 +1,8 @@
#![deny(clippy::pedantic)]
use serde::Deserialize;
use serde_json::Value;
use std::borrow::Cow;
use std::process::Command;
#[derive(thiserror::Error, Debug)]
@@ -188,9 +189,20 @@
}
}
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
struct CreateItem {
uuid: String,
vault_uuid: String,
}
pub struct OnepasswordOp {}
fn exec(args: &[&str]) -> Result<Vec<u8>, Error> {
fn exec<I, S>(args: I) -> Result<Vec<u8>, Error>
where
I: IntoIterator<Item = S>,
S: AsRef<std::ffi::OsStr>,
{
let cmd = Command::new("op")
.args(args)
.output()
@@ -250,5 +262,40 @@
let ret: GetItem = serde_json::from_slice(&exec(&["get", "item", uuid])?)?;
Ok(Some(ret.into()))
}
fn generate(
&self,
name: &str,
username: Option<&str>,
url: Option<&str>,
tags: Option<&str>,
) -> Result<onep_api::Item, Self::Error> {
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!()))
}
}
@@ -48,4 +48,11 @@
fn vaults(&self) -> Result<Vec<VaultMetadata>, Self::Error>;
fn search(&self, terms: Option<&str>) -> Result<Vec<ItemMetadata>, Self::Error>;
fn get(&self, uuid: &str) -> Result<Option<Item>, Self::Error>;
fn generate(
&self,
name: &str,
username: Option<&str>,
url: Option<&str>,
tags: Option<&str>,
) -> Result<Item, Self::Error>;
}
@@ -21,8 +21,8 @@
enum Opt {
#[clap(alias = "list")]
Ls {
#[clap(alias = "ls")]
List {
#[clap(long, short = 'i')]
show_uuids: bool,
#[clap(long, short = 'n')]
@@ -35,6 +35,21 @@
#[clap(alias = "get")]
Show { uuid: String },
#[clap(alias = "gen")]
Generate {
name: String,
#[clap(long, short = 'n')]
username: Option<String>,
#[clap(long, short = 'u')]
url: Option<String>,
#[clap(long, short = 't')]
tags: Option<String>,
},
}
fn main() {
@@ -44,14 +59,13 @@
}
}
#[warn(clippy::too_many_lines)]
#[allow(clippy::non_ascii_literal)]
fn run<T: OnePassword>(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 @@
}
Opt::Show { uuid } => {
let result = imp.get(&uuid)?.ok_or(Error::NotFound)?;
let mut table = Table::new();
table.style = TableStyle::extended();
show(result);
}
Opt::Generate {
name,
username,
url,
tags,
} => {
let result =
imp.generate(&name, username.as_deref(), url.as_deref(), tags.as_deref())?;
show(result);
}
}
table.add_row(Row::new(vec![TableCell::new_with_alignment(
result.title,
2,
Alignment::Center,
)]));
Ok(())
}
for field in result.fields {
table.add_row(Row::new(vec![
TableCell::new(field.name),
TableCell::new_with_alignment(field.value, 1, Alignment::Right),
]));
}
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),
]));
}
println!("{}", table.render());
println!("{}", table.render());
for section in result.sections {
if section.fields.is_empty() {
continue;
}
for section in item.sections {
if section.fields.is_empty() {
continue;
}
let mut table = Table::new();
table.style = TableStyle::extended();
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());
}
}