v0.2.6-pre.006
This commit is contained in:
96
crates/ksp-app-wallet-desk/src/wallet_secrets.rs
Normal file
96
crates/ksp-app-wallet-desk/src/wallet_secrets.rs
Normal file
@@ -0,0 +1,96 @@
|
||||
// file: crates/ksp-app-wallet-desk/src/wallet_secrets.rs
|
||||
// version: 1
|
||||
|
||||
//! Config-owned Wallet password candidate discovery for explicit unlock operations.
|
||||
|
||||
const WALLET_SECRET_PREFIX: &str = "KSP_SECRET_WALLET_PASS_";
|
||||
|
||||
/// One Config-owned secret candidate retained only inside Rust.
|
||||
pub(crate) struct WalletSecretCandidate {
|
||||
variable_name: String,
|
||||
}
|
||||
|
||||
impl WalletSecretCandidate {
|
||||
/// Explicitly reveals this candidate through Config's privileged management boundary.
|
||||
pub(crate) fn reveal(self, management: &ksp_config_lib::ConfigManagement) -> ksp_core_lib::Result<std::option::Option<String>> {
|
||||
return management.reveal_effective_environment_value(self.variable_name.as_str());
|
||||
}
|
||||
}
|
||||
|
||||
/// Discovers configured Wallet password candidates without revealing values.
|
||||
pub(crate) fn discover_wallet_secret_candidates(
|
||||
management: &ksp_config_lib::ConfigManagement,
|
||||
wallet_id: &str,
|
||||
) -> ksp_core_lib::Result<std::vec::Vec<WalletSecretCandidate>> {
|
||||
let reports = management.environment_report();
|
||||
let reports = match reports {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let mut names = reports
|
||||
.into_iter()
|
||||
.filter(|report| {
|
||||
return report.sensitivity() == ksp_config_lib::ConfigSensitivity::Secret
|
||||
&& report.effective_source().is_some()
|
||||
&& report.variable_name().starts_with(WALLET_SECRET_PREFIX)
|
||||
&& report.variable_name().len() > WALLET_SECRET_PREFIX.len();
|
||||
})
|
||||
.map(|report| return report.variable_name().to_owned())
|
||||
.collect::<std::vec::Vec<_>>();
|
||||
order_wallet_secret_candidate_names(wallet_id, names.as_mut_slice());
|
||||
return std::result::Result::Ok(names.into_iter().map(|variable_name| return WalletSecretCandidate { variable_name }).collect());
|
||||
}
|
||||
|
||||
/// Returns the number of effective configured Wallet password candidates without revealing names or values.
|
||||
pub(crate) fn wallet_secret_candidate_count(management: &ksp_config_lib::ConfigManagement, wallet_id: &str) -> ksp_core_lib::Result<usize> {
|
||||
let candidates = discover_wallet_secret_candidates(management, wallet_id);
|
||||
return candidates.map(|values| return values.len());
|
||||
}
|
||||
|
||||
/// Normalizes a native Wallet filename to the deterministic Config secret label candidate.
|
||||
#[must_use]
|
||||
fn normalize_wallet_secret_label(wallet_id: &str) -> String {
|
||||
let stem = wallet_id.strip_suffix(crate::WALLET_FILE_SUFFIX).unwrap_or(wallet_id);
|
||||
let mut output = String::new();
|
||||
let mut previous_separator = false;
|
||||
for byte in stem.bytes() {
|
||||
let normalized = if byte.is_ascii_alphanumeric() { byte.to_ascii_uppercase() as char } else { '_' };
|
||||
if normalized == '_' {
|
||||
if output.is_empty() || previous_separator {
|
||||
continue;
|
||||
}
|
||||
output.push('_');
|
||||
previous_separator = true;
|
||||
} else {
|
||||
output.push(normalized);
|
||||
previous_separator = false;
|
||||
}
|
||||
}
|
||||
while output.ends_with('_') {
|
||||
output.pop();
|
||||
}
|
||||
return output;
|
||||
}
|
||||
|
||||
fn order_wallet_secret_candidate_names(wallet_id: &str, names: &mut [String]) {
|
||||
let normalized = normalize_wallet_secret_label(wallet_id);
|
||||
names.sort_by(|left, right| {
|
||||
return wallet_secret_sort_key(left.as_str(), normalized.as_str()).cmp(&wallet_secret_sort_key(right.as_str(), normalized.as_str()));
|
||||
});
|
||||
}
|
||||
|
||||
fn wallet_secret_sort_key(variable_name: &str, normalized_filename: &str) -> (u8, u64, String) {
|
||||
let suffix = variable_name.strip_prefix(WALLET_SECRET_PREFIX).unwrap_or(variable_name);
|
||||
if suffix == normalized_filename && !normalized_filename.is_empty() {
|
||||
return (0, 0, suffix.to_owned());
|
||||
}
|
||||
if !suffix.is_empty() && suffix.bytes().all(|byte| return byte.is_ascii_digit()) {
|
||||
let parsed = suffix.parse::<u64>().unwrap_or(u64::MAX);
|
||||
return (1, parsed, suffix.to_owned());
|
||||
}
|
||||
return (2, 0, suffix.to_owned());
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[path = "../unit_tests/wallet_secrets.rs"]
|
||||
mod tests;
|
||||
Reference in New Issue
Block a user