use serde::Deserialize;
use serde_json::Value;
use std::process::Command;
#[derive(Debug, Deserialize)]
struct GetAccount {
name: String,
domain: String,
}
#[derive(Debug, Deserialize)]
struct ListVault {
uuid: String,
name: String,
}
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
struct ListItem {
uuid: String,
vault_uuid: String,
created_at: String,
updated_at: String,
overview: ItemOverview,
}
#[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,
}
#[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),
},
}
}
}
pub struct OnepasswordOp {}
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 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 {
name: ret.name,
domain: ret.domain,
}
}
fn vaults(&self) -> Vec<onep_api::VaultMetadata> {
let ret: Vec<ListVault> = 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()
.map(|v| onep_api::VaultMetadata {
uuid: v.uuid,
name: v.name,
})
.collect()
}
fn search(&self, terms: Option<&str>) -> Vec<onep_api::ItemMetadata> {
let ret: Vec<ListItem> = 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()
.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| onep_api::ItemMetadata {
title: v.overview.title,
account_info: v.overview.account_info,
uuid: v.uuid,
vault_uuid: v.vault_uuid,
})
.collect()
}
fn get(&self, uuid: &str) -> Option<onep_api::Item> {
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 {
title: ret.overview.title,
fields: ret
.details
.fields
.into_iter()
.map(|f| f.into())
.filter(|f: &onep_api::ItemField| !f.value.is_empty())
.collect(),
sections: ret
.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(),
})
}
}