onep-api -> onep-backend-api & onep-api-op -> onep-backend-op
Diff
Cargo.lock | 10 +++++-----
Cargo.toml | 4 ++--
onep-api-op/Cargo.toml | 14 --------------
onep-api/Cargo.toml | 9 ---------
onep-backend-api/Cargo.toml | 9 +++++++++
onep-backend-op/Cargo.toml | 14 ++++++++++++++
onep-cli/Cargo.toml | 4 ++--
onep-api-op/src/lib.rs | 301 --------------------------------------------------------------------------------
onep-api/src/lib.rs | 58 ----------------------------------------------------------
onep-backend-api/src/lib.rs | 58 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
onep-backend-op/src/lib.rs | 302 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
onep-cli/src/main.rs | 9 +++++----
12 files changed, 397 insertions(+), 395 deletions(-)
@@ -173,14 +173,14 @@
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "onep-api"
name = "onep-backend-api"
version = "0.1.0"
[[package]]
name = "onep-api-op"
name = "onep-backend-op"
version = "0.1.0"
dependencies = [
"onep-api 0.1.0",
"onep-backend-api 0.1.0",
"serde 1.0.114 (registry+https://github.com/rust-lang/crates.io-index)",
"serde_json 1.0.56 (registry+https://github.com/rust-lang/crates.io-index)",
"serde_with 1.5.0-alpha.1 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -195,8 +195,8 @@
"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)",
"onep-api 0.1.0",
"onep-api-op 0.1.0",
"onep-backend-api 0.1.0",
"onep-backend-op 0.1.0",
"term-table 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
"thiserror 1.0.20 (registry+https://github.com/rust-lang/crates.io-index)",
]
@@ -1,6 +1,6 @@
[workspace]
members = [
"onep-cli",
"onep-api",
"onep-api-op",
"onep-backend-api",
"onep-backend-op",
]
@@ -1,14 +1,0 @@
[package]
name = "onep-api-op"
version = "0.1.0"
authors = ["Jordan Doyle <jordan@doyle.la>"]
edition = "2018"
[dependencies]
onep-api = { path = "../onep-api" }
serde = { version = "1", features = ["derive"] }
serde_json = "1"
serde_with = "1.5.0-alpha.1"
thiserror = "1.0"
@@ -1,9 +1,0 @@
[package]
name = "onep-api"
version = "0.1.0"
authors = ["Jordan Doyle <jordan@doyle.la>"]
edition = "2018"
[dependencies]
@@ -1,0 +1,9 @@
[package]
name = "onep-backend-api"
version = "0.1.0"
authors = ["Jordan Doyle <jordan@doyle.la>"]
edition = "2018"
[dependencies]
@@ -1,0 +1,14 @@
[package]
name = "onep-backend-op"
version = "0.1.0"
authors = ["Jordan Doyle <jordan@doyle.la>"]
edition = "2018"
[dependencies]
onep-backend-api = { path = "../onep-backend-api" }
serde = { version = "1", features = ["derive"] }
serde_json = "1"
serde_with = "1.5.0-alpha.1"
thiserror = "1.0"
@@ -11,8 +11,8 @@
[dependencies]
onep-api = { path = "../onep-api" }
onep-api-op = { path = "../onep-api-op" }
onep-backend-api = { path = "../onep-backend-api" }
onep-backend-op = { path = "../onep-backend-op" }
clap = { git = "https://github.com/clap-rs/clap" }
term-table = "1.3"
@@ -1,301 +1,0 @@
#![deny(clippy::pedantic)]
use serde::Deserialize;
use serde_json::Value;
use std::borrow::Cow;
use std::process::Command;
#[derive(thiserror::Error, Debug)]
pub enum Error {
#[error("op backend returned an error:\n{0}")]
Backend(String),
#[error("failed to exec backend:\n{0}")]
Exec(std::io::Error),
#[error("failed to parse json from op:\n{0}")]
Json(#[from] serde_json::error::Error),
#[error("failed to convert op response to utf-8:\n{0}")]
Utf8(#[from] std::str::Utf8Error),
}
#[derive(Debug, Deserialize)]
struct GetAccount {
name: String,
domain: String,
}
impl Into<onep_api::AccountMetadata> for GetAccount {
fn into(self) -> onep_api::AccountMetadata {
onep_api::AccountMetadata {
name: self.name,
domain: self.domain,
}
}
}
#[derive(Debug, Deserialize)]
struct ListVault {
uuid: String,
name: String,
}
impl Into<onep_api::VaultMetadata> for ListVault {
fn into(self) -> onep_api::VaultMetadata {
onep_api::VaultMetadata {
uuid: self.uuid,
name: self.name,
}
}
}
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
struct ListItem {
uuid: String,
vault_uuid: String,
created_at: String,
updated_at: String,
overview: ItemOverview,
}
impl Into<onep_api::ItemMetadata> for ListItem {
fn into(self) -> onep_api::ItemMetadata {
onep_api::ItemMetadata {
title: self.overview.title,
account_info: self.overview.account_info,
uuid: self.uuid,
vault_uuid: self.vault_uuid,
}
}
}
#[derive(Debug, Deserialize)]
struct ItemOverview {
#[serde(rename = "URLs", default)]
urls: Vec<ItemOverviewUrl>,
title: String,
url: Option<String>,
#[serde(rename = "ainfo")]
account_info: String,
#[serde(default)]
tags: Vec<String>,
}
#[derive(Debug, Deserialize)]
struct ItemOverviewUrl {
#[serde(rename = "l")]
label: String,
#[serde(rename = "u")]
url: String,
}
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
struct GetItem {
details: GetItemDetails,
overview: ItemOverview,
}
impl Into<onep_api::Item> for GetItem {
fn into(self) -> onep_api::Item {
onep_api::Item {
title: self.overview.title,
fields: self
.details
.fields
.into_iter()
.map(|f| f.into())
.filter(|f: &onep_api::ItemField| !f.value.is_empty())
.collect(),
sections: self
.details
.sections
.into_iter()
.map(|v| onep_api::ItemSection {
name: v.title,
fields: v
.fields
.into_iter()
.map(|f| f.into())
.filter(|f: &onep_api::ItemField| !f.value.is_empty())
.collect(),
})
.collect(),
}
}
}
#[derive(Debug, Deserialize)]
struct GetItemDetails {
#[serde(default)]
fields: Vec<GetItemDetailsField>,
#[serde(default)]
sections: Vec<GetItemSection>,
}
#[derive(Debug, Deserialize)]
struct GetItemDetailsField {
name: String,
#[serde(rename = "designation")]
field_type: String,
value: Value,
}
impl Into<onep_api::ItemField> for GetItemDetailsField {
fn into(self) -> onep_api::ItemField {
onep_api::ItemField {
name: self.field_type,
value: match self.value {
Value::Null => String::new(),
Value::String(v) => v,
Value::Number(v) => format!("{}", v),
Value::Bool(v) => if v { "true" } else { "false" }.to_string(),
_ => panic!("unknown item field type for {}", self.name),
},
}
}
}
#[derive(Debug, Deserialize)]
struct GetItemSection {
title: String,
#[serde(default)]
fields: Vec<GetItemSectionField>,
}
#[derive(Debug, Deserialize)]
struct GetItemSectionField {
#[serde(rename = "k")]
kind: String,
#[serde(rename = "n")]
name: String,
#[serde(rename = "t")]
field_type: String,
#[serde(rename = "v", default)]
value: Value,
}
impl Into<onep_api::ItemField> for GetItemSectionField {
fn into(self) -> onep_api::ItemField {
onep_api::ItemField {
name: self.field_type,
value: match self.value {
Value::Null => String::new(),
Value::String(v) => v,
Value::Number(v) => format!("{}", v),
Value::Bool(v) => if v { "true" } else { "false" }.to_string(),
_ => panic!("unknown item field type for {}", self.name),
},
}
}
}
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
struct CreateItem {
uuid: String,
vault_uuid: String,
}
pub struct OnepasswordOp {}
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()
.map_err(Error::Exec)?;
if cmd.status.success() {
Ok(cmd.stdout)
} else {
Err(Error::Backend(
std::str::from_utf8(&cmd.stderr)?.to_string(),
))
}
}
impl onep_api::OnePassword for OnepasswordOp {
type Error = Error;
fn totp(&self, uuid: &str) -> Result<String, Self::Error> {
Ok(std::str::from_utf8(&exec(&["get", "totp", uuid])?)?.to_string())
}
fn account(&self) -> Result<onep_api::AccountMetadata, Self::Error> {
let ret: GetAccount = serde_json::from_slice(&exec(&["get", "account"])?)?;
Ok(ret.into())
}
fn vaults(&self) -> Result<Vec<onep_api::VaultMetadata>, Self::Error> {
let ret: Vec<ListVault> = serde_json::from_slice(&exec(&["list", "vaults"])?)?;
Ok(ret.into_iter().map(|v| v.into()).collect())
}
#[allow(clippy::filter_map)]
fn search(&self, terms: Option<&str>) -> Result<Vec<onep_api::ItemMetadata>, Self::Error> {
let ret: Vec<ListItem> = serde_json::from_slice(&exec(&["list", "items"])?)?;
Ok(ret
.into_iter()
.filter(|v| {
if let Some(terms) = terms {
v.uuid == terms
|| v.vault_uuid == terms
|| v.overview.urls.iter().any(|v| v.url.contains(terms))
|| v.overview.title.contains(terms)
|| v.overview.account_info.contains(terms)
|| v.overview.tags.iter().any(|v| v.contains(terms))
} else {
true
}
})
.map(|v| v.into())
.collect())
}
fn get(&self, uuid: &str) -> Result<Option<onep_api::Item>, Self::Error> {
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!()))
}
}
@@ -1,58 +1,0 @@
#![deny(clippy::pedantic)]
#![allow(clippy::missing_errors_doc)]
#[derive(Debug)]
pub struct AccountMetadata {
pub name: String,
pub domain: String,
}
#[derive(Debug)]
pub struct VaultMetadata {
pub uuid: String,
pub name: String,
}
#[derive(Debug)]
pub struct ItemMetadata {
pub uuid: String,
pub vault_uuid: String,
pub title: String,
pub account_info: String,
}
#[derive(Debug)]
pub struct Item {
pub title: String,
pub fields: Vec<ItemField>,
pub sections: Vec<ItemSection>,
}
#[derive(Debug)]
pub struct ItemField {
pub name: String,
pub value: String,
}
#[derive(Debug)]
pub struct ItemSection {
pub name: String,
pub fields: Vec<ItemField>,
}
pub trait OnePassword {
type Error;
fn totp(&self, uuid: &str) -> Result<String, Self::Error>;
fn account(&self) -> Result<AccountMetadata, Self::Error>;
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>;
}
@@ -1,0 +1,58 @@
#![deny(clippy::pedantic)]
#![allow(clippy::missing_errors_doc)]
#[derive(Debug)]
pub struct AccountMetadata {
pub name: String,
pub domain: String,
}
#[derive(Debug)]
pub struct VaultMetadata {
pub uuid: String,
pub name: String,
}
#[derive(Debug)]
pub struct ItemMetadata {
pub uuid: String,
pub vault_uuid: String,
pub title: String,
pub account_info: String,
}
#[derive(Debug)]
pub struct Item {
pub title: String,
pub fields: Vec<ItemField>,
pub sections: Vec<ItemSection>,
}
#[derive(Debug)]
pub struct ItemField {
pub name: String,
pub value: String,
}
#[derive(Debug)]
pub struct ItemSection {
pub name: String,
pub fields: Vec<ItemField>,
}
pub trait Backend {
type Error;
fn totp(&self, uuid: &str) -> Result<String, Self::Error>;
fn account(&self) -> Result<AccountMetadata, Self::Error>;
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>;
}
@@ -1,0 +1,302 @@
#![deny(clippy::pedantic)]
use onep_backend_api as api;
use serde::Deserialize;
use serde_json::Value;
use std::borrow::Cow;
use std::process::Command;
#[derive(thiserror::Error, Debug)]
pub enum Error {
#[error("op backend returned an error:\n{0}")]
Backend(String),
#[error("failed to exec backend:\n{0}")]
Exec(std::io::Error),
#[error("failed to parse json from op:\n{0}")]
Json(#[from] serde_json::error::Error),
#[error("failed to convert op response to utf-8:\n{0}")]
Utf8(#[from] std::str::Utf8Error),
}
#[derive(Debug, Deserialize)]
struct GetAccount {
name: String,
domain: String,
}
impl Into<api::AccountMetadata> for GetAccount {
fn into(self) -> api::AccountMetadata {
api::AccountMetadata {
name: self.name,
domain: self.domain,
}
}
}
#[derive(Debug, Deserialize)]
struct ListVault {
uuid: String,
name: String,
}
impl Into<api::VaultMetadata> for ListVault {
fn into(self) -> api::VaultMetadata {
api::VaultMetadata {
uuid: self.uuid,
name: self.name,
}
}
}
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
struct ListItem {
uuid: String,
vault_uuid: String,
created_at: String,
updated_at: String,
overview: ItemOverview,
}
impl Into<api::ItemMetadata> for ListItem {
fn into(self) -> api::ItemMetadata {
api::ItemMetadata {
title: self.overview.title,
account_info: self.overview.account_info,
uuid: self.uuid,
vault_uuid: self.vault_uuid,
}
}
}
#[derive(Debug, Deserialize)]
struct ItemOverview {
#[serde(rename = "URLs", default)]
urls: Vec<ItemOverviewUrl>,
title: String,
url: Option<String>,
#[serde(rename = "ainfo")]
account_info: String,
#[serde(default)]
tags: Vec<String>,
}
#[derive(Debug, Deserialize)]
struct ItemOverviewUrl {
#[serde(rename = "l")]
label: String,
#[serde(rename = "u")]
url: String,
}
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
struct GetItem {
details: GetItemDetails,
overview: ItemOverview,
}
impl Into<api::Item> for GetItem {
fn into(self) -> api::Item {
api::Item {
title: self.overview.title,
fields: self
.details
.fields
.into_iter()
.map(|f| f.into())
.filter(|f: &api::ItemField| !f.value.is_empty())
.collect(),
sections: self
.details
.sections
.into_iter()
.map(|v| api::ItemSection {
name: v.title,
fields: v
.fields
.into_iter()
.map(|f| f.into())
.filter(|f: &api::ItemField| !f.value.is_empty())
.collect(),
})
.collect(),
}
}
}
#[derive(Debug, Deserialize)]
struct GetItemDetails {
#[serde(default)]
fields: Vec<GetItemDetailsField>,
#[serde(default)]
sections: Vec<GetItemSection>,
}
#[derive(Debug, Deserialize)]
struct GetItemDetailsField {
name: String,
#[serde(rename = "designation")]
field_type: String,
value: Value,
}
impl Into<api::ItemField> for GetItemDetailsField {
fn into(self) -> api::ItemField {
api::ItemField {
name: self.field_type,
value: match self.value {
Value::Null => String::new(),
Value::String(v) => v,
Value::Number(v) => format!("{}", v),
Value::Bool(v) => if v { "true" } else { "false" }.to_string(),
_ => panic!("unknown item field type for {}", self.name),
},
}
}
}
#[derive(Debug, Deserialize)]
struct GetItemSection {
title: String,
#[serde(default)]
fields: Vec<GetItemSectionField>,
}
#[derive(Debug, Deserialize)]
struct GetItemSectionField {
#[serde(rename = "k")]
kind: String,
#[serde(rename = "n")]
name: String,
#[serde(rename = "t")]
field_type: String,
#[serde(rename = "v", default)]
value: Value,
}
impl Into<api::ItemField> for GetItemSectionField {
fn into(self) -> api::ItemField {
api::ItemField {
name: self.field_type,
value: match self.value {
Value::Null => String::new(),
Value::String(v) => v,
Value::Number(v) => format!("{}", v),
Value::Bool(v) => if v { "true" } else { "false" }.to_string(),
_ => panic!("unknown item field type for {}", self.name),
},
}
}
}
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
struct CreateItem {
uuid: String,
vault_uuid: String,
}
pub struct OpBackend {}
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()
.map_err(Error::Exec)?;
if cmd.status.success() {
Ok(cmd.stdout)
} else {
Err(Error::Backend(
std::str::from_utf8(&cmd.stderr)?.to_string(),
))
}
}
impl api::Backend for OpBackend {
type Error = Error;
fn totp(&self, uuid: &str) -> Result<String, Self::Error> {
Ok(std::str::from_utf8(&exec(&["get", "totp", uuid])?)?.to_string())
}
fn account(&self) -> Result<api::AccountMetadata, Self::Error> {
let ret: GetAccount = serde_json::from_slice(&exec(&["get", "account"])?)?;
Ok(ret.into())
}
fn vaults(&self) -> Result<Vec<api::VaultMetadata>, Self::Error> {
let ret: Vec<ListVault> = serde_json::from_slice(&exec(&["list", "vaults"])?)?;
Ok(ret.into_iter().map(|v| v.into()).collect())
}
#[allow(clippy::filter_map)]
fn search(&self, terms: Option<&str>) -> Result<Vec<api::ItemMetadata>, Self::Error> {
let ret: Vec<ListItem> = serde_json::from_slice(&exec(&["list", "items"])?)?;
Ok(ret
.into_iter()
.filter(|v| {
if let Some(terms) = terms {
v.uuid == terms
|| v.vault_uuid == terms
|| v.overview.urls.iter().any(|v| v.url.contains(terms))
|| v.overview.title.contains(terms)
|| v.overview.account_info.contains(terms)
|| v.overview.tags.iter().any(|v| v.contains(terms))
} else {
true
}
})
.map(|v| v.into())
.collect())
}
fn get(&self, uuid: &str) -> Result<Option<api::Item>, Self::Error> {
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<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!()))
}
}
@@ -1,9 +1,10 @@
#![deny(clippy::pedantic)]
use clap::Clap;
use colored::Colorize;
use itertools::Itertools;
use onep_api::OnePassword;
use onep_backend_api as api;
use onep_backend_op as backend;
use term_table::{
row::Row,
table_cell::{Alignment, TableCell},
@@ -53,14 +54,14 @@
}
fn main() {
if let Err(e) = run(&onep_api_op::OnepasswordOp {}) {
if let Err(e) = run(&backend::OpBackend {}) {
eprintln!("{}", e);
std::process::exit(1);
}
}
#[allow(clippy::non_ascii_literal)]
fn run<T: OnePassword>(imp: &T) -> anyhow::Result<()>
fn run<T: api::Backend>(imp: &T) -> anyhow::Result<()>
where
T::Error: 'static + std::error::Error + Send + Sync,
{
@@ -167,7 +168,7 @@
Ok(())
}
fn show(item: onep_api::Item) {
fn show(item: api::Item) {
let mut table = Table::new();
table.style = TableStyle::extended();