use std::collections::HashMap;
use color_eyre::eyre::Report;
use keyring::{Entry, Error as KeyringError};
use rpassword::prompt_password;
use tracing::{debug, instrument};
use crate::next::{
resolve::{merge_and_resolve, SecretArguments, SecretScopeArguments},
state::{create_resolve_state, InferContext},
};
use super::{
config::ConfigVar,
errors::{SecretError, SecretKeyringError, StateError, WrapStateErr},
resolve::SecretScope,
state::{AddressIn, StateOptions},
};
fn get_keyring_secret(key: &str, service: &str) -> Result<Option<String>, KeyringError> {
debug!(
"Trying to get keyring password with key {} for service {}",
key, service
);
let entry = Entry::new(service, key)?;
match entry.get_password() {
Ok(pw) => Ok(Some(pw)),
Err(err) => match err {
KeyringError::NoEntry => {
debug!("No entry found!");
Ok(None)
}
_ => Err(err),
},
}
}
fn set_keyring_secret(secret: &str, key: &str, service: &str) -> Result<(), KeyringError> {
let entry = Entry::new(service, key)?;
entry.set_password(secret)?;
debug!(
"Set keyring password with key {} for service {}",
key, service
);
Ok(())
}
fn key_from_scope(scope: &SecretScope, key: &str, require_hash_match: bool) -> String {
let hash = if require_hash_match {
&scope.hash
} else {
"tidploy_default_hash"
};
format!("{}::{}::{}:{}", scope.name, scope.sub, hash, key)
}
#[instrument(name = "get_secret", level = "debug", skip_all)]
pub(crate) fn get_secret(scope: &SecretScope, key: &str) -> Result<String, SecretError> {
debug!("Getting secret with key {}", key);
let store_key = key_from_scope(scope, key, true);
match get_keyring_secret(&store_key, &scope.service).map_err(|e| SecretKeyringError {
msg: format!("Failed to get key {}", &store_key),
source: e,
})? {
Some(password) => Ok(password),
None => Err(SecretError::NoPassword(store_key)),
}
}
fn secret_prompt(
scope: &SecretScope,
key: &str,
prompt: Option<String>,
) -> Result<String, SecretError> {
let password = if let Some(prompt) = prompt {
prompt
} else {
prompt_password("Enter secret:\n")?
};
let store_key = key_from_scope(scope, key, true);
set_keyring_secret(&password, &store_key, &scope.service).map_err(|e| SecretKeyringError {
msg: format!("Failed to set key {}", &store_key),
source: e,
})?;
let store_key_default_hash = key_from_scope(scope, key, false);
set_keyring_secret(&password, &store_key_default_hash, &scope.service).map_err(|e| {
SecretKeyringError {
msg: format!("Failed to set key {}", &store_key),
source: e,
}
})?;
Ok(store_key)
}
pub(crate) fn secret_command(
addr_in: AddressIn,
cwd_infer: bool,
state_options: Option<StateOptions>,
service: Option<String>,
key: String,
prompt: Option<String>,
) -> Result<String, Report> {
debug!(
"Secret command called with in_addr {:?}, key {:?} and prompt {:?}",
addr_in, key, prompt
);
let scope_args = SecretScopeArguments {
service,
..Default::default()
};
let secret_args = SecretArguments { key, scope_args };
let infer_ctx = if cwd_infer {
InferContext::Cwd
} else {
InferContext::Git
};
let resolve_state =
create_resolve_state(addr_in, infer_ctx, state_options.unwrap_or_default())?;
let secret_resolved = merge_and_resolve(secret_args, resolve_state)?;
let store_key = secret_prompt(&secret_resolved.scope, &secret_resolved.key, prompt)?;
println!("Set secret with store key {}!", &store_key);
Ok(store_key)
}
pub(crate) fn secret_vars_to_envs(
scope: &SecretScope,
vars: Vec<ConfigVar>,
) -> Result<HashMap<String, String>, StateError> {
let mut envs = HashMap::<String, String>::new();
for e in vars {
debug!("Getting pass for {:?}", e);
let pass = get_secret(scope, &e.key)
.to_state_err("Getting secret for config var to create env map.".to_owned())?;
envs.insert(e.env_name, pass);
}
Ok(envs)
}