From 887a2efe4f9534593c87682e2e869a816309ef8e Mon Sep 17 00:00:00 2001 From: Jordan Doyle Date: Sun, 5 Jul 2020 17:00:07 +0100 Subject: [PATCH] Propagate errors up out of The 1Password command-line tool provides commands to manage and administer a 1Password account. Sign in to an account to get started. Run `op signin --help` to learn more. HOW TO SPECIFY OBJECTS You can specify all objects by name or UUID. You can also specify some objects by other attributes: * Items: item link * Login or Password items: domain name * Users: email address When you specify an item by name or domain, there may be more than one item that matches. To be more specific, use the `--vault` option to only look in one vault at a time, or use a unique ID (UUID) instead. Usage: op [command] Available Commands: add Grant access to groups or vaults confirm Confirm a user create Create an object delete Remove an object edit Edit an object encode Encode the JSON needed to create an item forget Remove a 1Password account from this device get Get details about an object help Get help for a command. list List objects and events reactivate Reactivate a suspended user remove Revoke access to groups or vaults signin Sign in to a 1Password account signout Sign out of a 1Password account suspend Suspend a user update Check for updates Flags: --account shorthand use the account with this shorthand -h, --help get help for op --session token authenticate with this session token --version version for op Use "op [command] --help" for more information about a command. --- Cargo.lock | 30 ++++++++++++++++++++++++++++++ onep-api-op/src/lib.rs | 102 +++++++++++++++++++++++++++++++++++++----------------------------------------------------------------- onep-api/Cargo.toml | 1 + onep-api/src/lib.rs | 20 +++++++++++++++----- onep-cli/Cargo.toml | 4 +++- onep-cli/src/main.rs | 21 +++++++++++++++------ 6 files changed, 101 insertions(+), 77 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 9b57655..5a20de1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -9,6 +9,11 @@ dependencies = [ ] [[package]] +name = "anyhow" +version = "1.0.31" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] name = "atty" version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -170,6 +175,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "onep-api" version = "0.1.0" +dependencies = [ + "thiserror 1.0.20 (registry+https://github.com/rust-lang/crates.io-index)", +] [[package]] name = "onep-api-op" @@ -185,6 +193,7 @@ dependencies = [ name = "onep-cli" version = "0.1.0" dependencies = [ + "anyhow 1.0.31 (registry+https://github.com/rust-lang/crates.io-index)", "clap 3.0.0-beta.1 (git+https://github.com/clap-rs/clap)", "colored 1.9.3 (registry+https://github.com/rust-lang/crates.io-index)", "itertools 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -364,6 +373,24 @@ dependencies = [ ] [[package]] +name = "thiserror" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "thiserror-impl 1.0.20 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "proc-macro2 1.0.18 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 1.0.33 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] name = "thread_local" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -425,6 +452,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [metadata] "checksum aho-corasick 0.7.13 (registry+https://github.com/rust-lang/crates.io-index)" = "043164d8ba5c4c3035fec9bbee8647c0261d788f3474306f93bb65901cae0e86" +"checksum anyhow 1.0.31 (registry+https://github.com/rust-lang/crates.io-index)" = "85bb70cc08ec97ca5450e6eba421deeea5f172c0fc61f78b5357b2a8e8be195f" "checksum atty 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)" = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" "checksum autocfg 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f8aac770f1885fd7e387acedd76065302551364496e46b3dd00860b2f8359b9d" "checksum bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" @@ -465,6 +493,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum term-table 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "dea05ef801340799f4c96296e9e8cf79cdfa66ef66fe991522591fde77405105" "checksum termcolor 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bb6bfa289a4d7c5766392812c0a1f4c1ba45afa1ad47803c11e1f407d846d75f" "checksum textwrap 0.12.1 (registry+https://github.com/rust-lang/crates.io-index)" = "203008d98caf094106cfaba70acfed15e18ed3ddb7d94e49baec153a2b462789" +"checksum thiserror 1.0.20 (registry+https://github.com/rust-lang/crates.io-index)" = "7dfdd070ccd8ccb78f4ad66bf1982dc37f620ef696c6b5028fe2ed83dd3d0d08" +"checksum thiserror-impl 1.0.20 (registry+https://github.com/rust-lang/crates.io-index)" = "bd80fc12f73063ac132ac92aceea36734f04a1d93c1240c6944e23a3b8841793" "checksum thread_local 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "d40c6d1b69745a6ec6fb1ca717914848da4b44ae29d9b3080cbee91d72a69b14" "checksum unicode-segmentation 1.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e83e153d1053cbb5a118eeff7fd5be06ed99153f00dbcd8ae310c5fb2b22edc0" "checksum unicode-width 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "9337591893a19b88d8d87f2cec1e73fad5cdfd10e5a6f349f498ad6ea2ffb1e3" diff --git a/onep-api-op/src/lib.rs b/onep-api-op/src/lib.rs index 53f0feb..1e2e7fa 100644 --- a/onep-api-op/src/lib.rs +++ b/onep-api-op/src/lib.rs @@ -1,5 +1,6 @@ use serde::Deserialize; use serde_json::Value; +use onep_api::OnePasswordApiError; use std::process::Command; #[derive(Debug, Deserialize)] @@ -118,69 +119,49 @@ impl Into for GetItemSectionField { pub struct OnepasswordOp {} +impl OnepasswordOp { + fn exec(&self, args: &[&str]) -> Result, OnePasswordApiError> { + let cmd = Command::new("op").args(args).output().map_err(OnePasswordApiError::Exec)?; + + if cmd.status.success() { + Ok(cmd.stdout) + } else { + Err(OnePasswordApiError::Backend( + std::str::from_utf8(&cmd.stderr).unwrap().to_string() + )) + } + } +} + impl onep_api::OnePassword for OnepasswordOp { - fn totp(&self, uuid: &str) -> String { - std::str::from_utf8( - &Command::new("op") - .arg("get") - .arg("totp") - .arg(uuid) - .output() - .expect("failed to exec get totp") - .stdout, - ) - .expect("failed to parse get totp output as utf8") - .to_string() + fn totp(&self, uuid: &str) -> Result { + Ok(std::str::from_utf8(&self.exec(&["get", "totp", uuid])?).unwrap().to_string()) } - fn account(&self) -> onep_api::AccountMetadata { - let ret: GetAccount = serde_json::from_slice( - &Command::new("op") - .arg("get") - .arg("account") - .output() - .expect("failed to exec get account") - .stdout, - ) - .expect("failed to parse json"); - - onep_api::AccountMetadata { + fn account(&self) -> Result { + let ret: GetAccount = serde_json::from_slice(&self.exec(&["get", "account"])?).unwrap(); + + Ok(onep_api::AccountMetadata { name: ret.name, domain: ret.domain, - } + }) } - fn vaults(&self) -> Vec { - let ret: Vec = serde_json::from_slice( - &Command::new("op") - .arg("list") - .arg("vaults") - .output() - .expect("failed to exec list vaults") - .stdout, - ) - .expect("failed to parse json"); - - ret.into_iter() + fn vaults(&self) -> Result, OnePasswordApiError> { + let ret: Vec = serde_json::from_slice(&self.exec(&["list", "vaults"])?).unwrap(); + + Ok(ret.into_iter() .map(|v| onep_api::VaultMetadata { uuid: v.uuid, name: v.name, }) - .collect() + .collect()) } - fn search(&self, terms: Option<&str>) -> Vec { - let ret: Vec = serde_json::from_slice( - &Command::new("op") - .arg("list") - .arg("items") - .output() - .expect("failed to exec list items") - .stdout, - ) - .expect("failed to parse json"); - - ret.into_iter() + fn search(&self, terms: Option<&str>) -> Result, OnePasswordApiError> { + let ret: Vec = serde_json::from_slice(&self.exec(&["list", "items"])?).unwrap(); + + Ok(ret.into_iter() .filter(|v| { if let Some(terms) = terms { v.uuid == terms @@ -199,22 +180,13 @@ impl onep_api::OnePassword for OnepasswordOp { uuid: v.uuid, vault_uuid: v.vault_uuid, }) - .collect() + .collect()) } - fn get(&self, uuid: &str) -> Option { - let ret: GetItem = serde_json::from_slice( - &Command::new("op") - .arg("get") - .arg("item") - .arg(uuid) - .output() - .expect("failed to exec get item") - .stdout, - ) - .expect("failed to parse json"); - - Some(onep_api::Item { + fn get(&self, uuid: &str) -> Result, OnePasswordApiError> { + let ret: GetItem = serde_json::from_slice(&self.exec(&["get", "item", uuid])?).unwrap(); + + Ok(Some(onep_api::Item { title: ret.overview.title, fields: ret .details @@ -237,6 +209,6 @@ impl onep_api::OnePassword for OnepasswordOp { .collect(), }) .collect(), - }) + })) } } diff --git a/onep-api/Cargo.toml b/onep-api/Cargo.toml index d45ed62..57b5b99 100644 --- a/onep-api/Cargo.toml +++ b/onep-api/Cargo.toml @@ -7,3 +7,4 @@ edition = "2018" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +thiserror = "1.0" \ No newline at end of file diff --git a/onep-api/src/lib.rs b/onep-api/src/lib.rs index 28e8069..fd6523c 100644 --- a/onep-api/src/lib.rs +++ b/onep-api/src/lib.rs @@ -1,3 +1,13 @@ +use thiserror::Error; + +#[derive(Error, Debug)] +pub enum OnePasswordApiError { + #[error("1password backend returned an error:\n{0}")] + Backend(String), + #[error("failed to exec backend:\n{0}")] + Exec(std::io::Error), +} + #[derive(Debug)] pub struct AccountMetadata { pub name: String, @@ -38,9 +48,9 @@ pub struct ItemSection { } pub trait OnePassword { - fn totp(&self, uuid: &str) -> String; - fn account(&self) -> AccountMetadata; - fn vaults(&self) -> Vec; - fn search(&self, terms: Option<&str>) -> Vec; - fn get(&self, uuid: &str) -> Option; + fn totp(&self, uuid: &str) -> Result; + fn account(&self) -> Result; + fn vaults(&self) -> Result, OnePasswordApiError>; + fn search(&self, terms: Option<&str>) -> Result, OnePasswordApiError>; + fn get(&self, uuid: &str) -> Result, OnePasswordApiError>; } diff --git a/onep-cli/Cargo.toml b/onep-cli/Cargo.toml index 61e6441..581178b 100644 --- a/onep-cli/Cargo.toml +++ b/onep-cli/Cargo.toml @@ -13,4 +13,6 @@ onep-api-op = { path = "../onep-api-op" } clap = { git = "https://github.com/clap-rs/clap" } term-table = "1.3" itertools = "0.9" -colored = "1.9" \ No newline at end of file +colored = "1.9" + +anyhow = "1.0" \ No newline at end of file diff --git a/onep-cli/src/main.rs b/onep-cli/src/main.rs index 454473b..51ff0b0 100644 --- a/onep-cli/src/main.rs +++ b/onep-cli/src/main.rs @@ -30,6 +30,13 @@ enum Opt { } fn main() { + if let Err(e) = run() { + eprintln!("{}", e); + std::process::exit(1); + } +} + +fn run() -> anyhow::Result<()> { let imp = onep_api_op::OnepasswordOp {}; match Opt::parse() { @@ -37,9 +44,9 @@ fn main() { show_uuids, show_account_names, } => { - let account = imp.account(); - let vaults = imp.vaults(); - let results = imp.search(None); + let account = imp.account()?; + let vaults = imp.vaults()?; + let results = imp.search(None)?; let mut results_grouped: Vec<(_, Vec<_>)> = Vec::new(); for (key, group) in &results.into_iter().group_by(|v| v.vault_uuid.clone()) { @@ -108,9 +115,9 @@ fn main() { } } } - Opt::Totp { uuid } => println!("{}", imp.totp(&uuid).trim()), + Opt::Totp { uuid } => println!("{}", imp.totp(&uuid)?.trim()), Opt::Search { terms } => { - for result in imp.search(Some(&terms)) { + for result in imp.search(Some(&terms))? { println!("[{}]", result.title.green()); println!("{}", result.account_info); println!("{}", result.uuid); @@ -118,7 +125,7 @@ fn main() { } } Opt::Show { uuid } => { - let result = imp.get(&uuid).unwrap(); + let result = imp.get(&uuid)?.unwrap(); let mut table = Table::new(); table.style = TableStyle::extended(); @@ -165,4 +172,6 @@ fn main() { } } } + + Ok(()) } -- libgit2 1.7.2