use std::process::Command;
use crate::{
error::{GlovesError, Result},
types::SecretValue,
};
#[derive(Debug, Clone)]
pub struct PassOutput {
pub status_code: i32,
pub stdout: String,
pub stderr: String,
}
pub trait PassExecutor: Send + Sync {
fn exec(&self, args: &[&str]) -> Result<PassOutput>;
}
pub struct SystemPassExecutor {
binary: String,
}
impl SystemPassExecutor {
pub fn new() -> Self {
Self {
binary: "pass".to_owned(),
}
}
pub fn with_binary(binary: impl Into<String>) -> Self {
Self {
binary: binary.into(),
}
}
}
impl Default for SystemPassExecutor {
fn default() -> Self {
Self::new()
}
}
impl PassExecutor for SystemPassExecutor {
fn exec(&self, args: &[&str]) -> Result<PassOutput> {
let output = Command::new(&self.binary).args(args).output()?;
Ok(PassOutput {
status_code: output.status.code().unwrap_or(1),
stdout: String::from_utf8(output.stdout)?,
stderr: String::from_utf8(output.stderr)?,
})
}
}
pub struct HumanBackend {
executor: Box<dyn PassExecutor>,
}
impl HumanBackend {
pub fn new() -> Self {
Self {
executor: Box::new(SystemPassExecutor::new()),
}
}
pub fn with_executor(executor: Box<dyn PassExecutor>) -> Self {
Self { executor }
}
pub fn get(&self, secret_name: &str) -> Result<SecretValue> {
let output = self.executor.exec(&["show", secret_name])?;
if output.status_code == 0 {
let parsed = output.stdout.trim_end_matches('\n').as_bytes().to_vec();
return Ok(SecretValue::new(parsed));
}
if output.stderr.contains("is not in the password store") {
return Err(GlovesError::NotFound);
}
if output.stderr.contains("decryption failed") {
return Err(GlovesError::GpgDenied);
}
Err(GlovesError::Crypto(output.stderr))
}
pub fn exists(&self, secret_name: &str) -> Result<bool> {
let output = self.executor.exec(&["show", secret_name])?;
Ok(output.status_code == 0)
}
}
impl Default for HumanBackend {
fn default() -> Self {
Self::new()
}
}