1
use std::fs::{
2
  self,
3
  File,
4
};
5
use std::path::Path;
6

            
7
use anyhow::Context as _;
8
use clap::Args;
9
use console::style;
10
use pgp::{
11
  Deserializable as _,
12
  Message,
13
  SignedSecretKey,
14
};
15
use prettytable::format::consts;
16
use prettytable::{
17
  row,
18
  Table,
19
};
20

            
21
use crate::secrets::context::Context;
22
use crate::secrets::vault::{
23
  verify_key,
24
  verify_vault,
25
};
26

            
27
#[derive(Debug, Args)]
28
pub struct ShowSecrets {
29
  #[arg(help = "The secret identifier or prefix to export")]
30
  path: String,
31

            
32
  #[arg(short, long, help = "The path to the secret store")]
33
  vault_location: Option<String>,
34

            
35
  #[arg(long, help = "The keys location")]
36
  keys_location: Option<String>,
37

            
38
  #[arg(short, long, help = "The key name")]
39
  key_name: Option<String>,
40
}
41

            
42
impl ShowSecrets {
43
  pub fn execute(&self, context: &Context) -> anyhow::Result<()> {
44
    let path: &str = &self.path.clone();
45
    let vault_location: &str = &self
46
      .vault_location
47
      .clone()
48
      .unwrap_or_else(|| context.vault_location());
49
    let keys_location: &str = &self
50
      .keys_location
51
      .clone()
52
      .unwrap_or_else(|| context.keys_location());
53
    let key_name: &str = &self.key_name.clone().unwrap_or_else(|| context.key_name());
54

            
55
    assert!(!path.is_empty(), "Path or prefix must be provided");
56
    assert!(!vault_location.is_empty(), "Vault location must be provided");
57
    assert!(!keys_location.is_empty(), "Keys location must be provided");
58
    assert!(!key_name.is_empty(), "Key name must be provided");
59

            
60
    verify_vault(vault_location)?;
61
    verify_key(keys_location, key_name)?;
62

            
63
    // Open the secret key file
64
    let key_name_with_ext = format!("{key_name}.key");
65
    let key_path = Path::new(keys_location).join(key_name_with_ext);
66
    let mut secret_key_string = File::open(key_path)?;
67
    let (signed_secret_key, _) = SignedSecretKey::from_armor_single(&mut secret_key_string)?;
68
    signed_secret_key.verify()?;
69

            
70
    let secret_path = Path::new(vault_location).join(path);
71
    let mut values = Vec::new();
72

            
73
    if secret_path.exists() && secret_path.is_dir() {
74
      // Check for file and subdirectories
75
      let entries = fs::read_dir(secret_path.clone())?
76
        .filter_map(Result::ok)
77
        .collect::<Vec<_>>();
78

            
79
      // Check for data files in the subdirectories
80
      for entry in entries {
81
        let data_path = if entry.path().is_dir() {
82
          entry.path().join("data.asc")
83
        } else {
84
          entry.path()
85
        };
86

            
87
        // Read the data file
88
        if data_path.exists() && data_path.is_file() {
89
          let mut data_file = File::open(data_path)?;
90
          let (message, _) = Message::from_armor_single(&mut data_file)?;
91
          let (decrypted_message, _) = message.decrypt(String::new, &[&signed_secret_key])?;
92
          let value = decrypted_message
93
            .get_literal()
94
            .ok_or_else(|| anyhow::anyhow!("Secret value is not a literal"))?
95
            .to_string()
96
            .context("Failed to read secret value")?;
97

            
98
          values.push(value);
99
        }
100
      }
101

            
102
      if values.is_empty() {
103
        println!("No secrets found for path: {}", path);
104
      } else {
105
        let mut table = Table::new();
106
        table.set_format(*consts::FORMAT_CLEAN);
107
        table.set_titles(row![Fbb->"Name", Fbb->"Value"]);
108
        for value in values {
109
          table.add_row(row![b->&path, Fg->&value]);
110
        }
111
        let msg = style("Available secrets:").bold().cyan();
112
        println!();
113
        println!("{msg}");
114
        println!();
115
        table.printstd();
116
      }
117
    } else {
118
      println!("Path does not exist or is not a directory");
119
    }
120

            
121
    Ok(())
122
  }
123
}