sopass 0.5.0

command line password manager using SOP
Documentation
//! The `value` sub-commands.

use std::{io::Read, path::PathBuf};

use clap::Parser;
use log::info;

use crate::{
    store::{EncryptedStore, EncryptedStoreError, StoreError},
    Config, LeafCommand,
};

/// List names of values, one per line.
#[derive(Debug, Parser)]
pub struct ListValues {}

impl LeafCommand for ListValues {
    type Error = ValueError;

    fn run(&self, config: &Config) -> Result<(), ValueError> {
        let store = EncryptedStore::new(config.sop(), config.store()).read_store()?;
        info!("loaded store OK");
        let mut names: Vec<&str> = store.iter().map(|(k, _)| k).collect();
        names.sort();
        for name in names {
            println!("{name}");
        }
        Ok(())
    }
}

/// Add a new value.
///
/// The value can be given as a command line argument, read from the
/// standard input, or read from a named file.
#[derive(Debug, Parser)]
pub struct AddValue {
    /// Name of value.
    name: String,

    /// Read value from this file.
    #[clap(long)]
    file: Option<PathBuf>,

    /// Read value from the standard input.
    #[clap(long, default_value = "false", action = clap::ArgAction::SetTrue)]
    stdin: Option<bool>,
}

impl AddValue {
    fn value(&self) -> Result<Vec<u8>, ValueError> {
        match (&&self.file, self.stdin) {
            // Check that exactly one source of value is specified.
            (Some(_), Some(true)) => Err(ValueError::ThereCanBeOnlyOne),
            (None, None) => Err(ValueError::ThereMustBeOne),
            (None, Some(false)) => Err(ValueError::ThereMustBeOne),

            // File.
            (Some(filename), _) => {
                let value = std::fs::read(filename)
                    .map_err(|err| ValueError::ReadFile(filename.into(), err))?;
                Ok(value)
            }

            // Stdin.
            (_, Some(true)) => {
                let mut stdin = std::io::stdin();
                let mut value = vec![];
                stdin
                    .read_to_end(&mut value)
                    .map_err(ValueError::ReadStdin)?;
                Ok(value)
            }
        }
    }
}

impl LeafCommand for AddValue {
    type Error = ValueError;

    fn run(&self, config: &Config) -> Result<(), ValueError> {
        info!("AddValue self={self:#?}");
        let encrypted = EncryptedStore::new(config.sop(), config.store());
        let mut store = encrypted.read_store()?;
        store.insert(&self.name, &self.value()?);
        encrypted.write_store(&store)?;

        Ok(())
    }
}

/// Show a value.
#[derive(Debug, Parser)]
pub struct ShowValue {
    /// Name of value.
    name: String,
}

impl LeafCommand for ShowValue {
    type Error = ValueError;

    fn run(&self, config: &Config) -> Result<(), ValueError> {
        let encrypted = EncryptedStore::new(config.sop(), config.store());
        let store = encrypted.read_store()?;
        info!("get value for {}", self.name);
        if let Some(value) = store.get(&self.name) {
            println!("{}", String::from_utf8_lossy(value));
        } else {
            return Err(ValueError::NoSuchValue(self.name.clone()));
        }

        Ok(())
    }
}

/// Remove a value.
#[derive(Debug, Parser)]
pub struct RemoveValue {
    /// Name of value.
    name: String,
}

impl LeafCommand for RemoveValue {
    type Error = ValueError;

    fn run(&self, config: &Config) -> Result<(), ValueError> {
        info!("remove value for {}", self.name);
        let encrypted = EncryptedStore::new(config.sop(), config.store());
        let mut store = encrypted.read_store()?;
        store.remove(&self.name);
        encrypted.write_store(&store)?;

        Ok(())
    }
}

/// Rename a value.
#[derive(Debug, Parser)]
pub struct RenameValue {
    /// Old name of value.
    old: String,

    /// New name of value.
    new: String,
}

impl LeafCommand for RenameValue {
    type Error = ValueError;

    fn run(&self, config: &Config) -> Result<(), ValueError> {
        info!("rename {} to {}", self.old, self.new);
        let encrypted = EncryptedStore::new(config.sop(), config.store());
        let mut store = encrypted.read_store()?;
        info!("rename in memory");
        store.rename(&self.old, &self.new)?;
        info!("rename in memory OK");
        encrypted.write_store(&store)?;

        Ok(())
    }
}

/// Possible errors from `value` sub-commands.
#[derive(Debug, thiserror::Error)]
pub enum ValueError {
    /// Value does not exist.
    #[error("there is not value named {0}")]
    NoSuchValue(String),

    /// Error from handling encrypted store.
    #[error(transparent)]
    Encrypted(#[from] EncryptedStoreError),

    /// Error from using cleartext store.
    #[error(transparent)]
    Store(#[from] StoreError),

    /// Confused about adding value.
    #[error("conflict: more than one way used to specify value to be added")]
    ThereCanBeOnlyOne,

    /// Confused about not adding value.
    #[error("no value to add specified")]
    ThereMustBeOne,

    /// Can't read value from file.
    #[error("failed to read value from file {0}")]
    ReadFile(PathBuf, #[source] std::io::Error),

    /// Can't read value from stdin.
    #[error("failed to read value from stdin")]
    ReadStdin(#[source] std::io::Error),
}