#[cfg(test)]
mod bind_lifecycle_tests {
use keeper_secrets_manager_core::config_keys::ConfigKeys;
use keeper_secrets_manager_core::core::{ClientOptions, SecretsManager};
use keeper_secrets_manager_core::crypto::CryptoUtils;
use keeper_secrets_manager_core::custom_error::KSMRError;
use keeper_secrets_manager_core::dto::{EncryptedPayload, KsmHttpResponse, TransmissionKey};
use keeper_secrets_manager_core::enums::KvStoreType;
use keeper_secrets_manager_core::storage::{InMemoryKeyValueStorage, KeyValueStorage};
const MOCK_TOKEN: &str = "US:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";
fn bound_storage() -> Result<KvStoreType, KSMRError> {
let storage = InMemoryKeyValueStorage::new(None)?;
let mut kv = KvStoreType::InMemory(storage);
let priv_der = CryptoUtils::generate_private_key_der()?;
let priv_b64 = keeper_secrets_manager_core::utils::bytes_to_base64(&priv_der);
let priv_key = CryptoUtils::generate_private_key_ecc()?;
let pub_b64 = keeper_secrets_manager_core::utils::bytes_to_base64(
&CryptoUtils::public_key_ecc(&priv_key),
);
kv.set(ConfigKeys::KeyClientId, "TEST_CLIENT_ID".to_string())?;
kv.set(
ConfigKeys::KeyAppKey,
"dGVzdF9hcHBfa2V5X2Jhc2U2NF9lbmNvZGVkX3ZhbHVlAAAAAAAAAAAA".to_string(),
)?;
kv.set(ConfigKeys::KeyOwnerPublicKey, pub_b64)?;
kv.set(ConfigKeys::KeyPrivateKey, priv_b64)?;
kv.set(ConfigKeys::KeyServerPublicKeyId, "10".to_string())?;
kv.set(
ConfigKeys::KeyHostname,
"fake.keepersecurity.com".to_string(),
)?;
Ok(kv)
}
fn mock_no_network(
_url: String,
_tk: TransmissionKey,
_ep: EncryptedPayload,
) -> Result<KsmHttpResponse, KSMRError> {
Err(KSMRError::HTTPError(
"KSM-973 test: unexpected network call".to_string(),
))
}
#[test]
fn test_new_does_not_perform_network_call() -> Result<(), KSMRError> {
let storage = InMemoryKeyValueStorage::new(None)?;
let kv = KvStoreType::InMemory(storage);
let mut options = ClientOptions::new_client_options_with_token(MOCK_TOKEN.to_string(), kv);
options.set_custom_post_function(|_url, _tk, _ep| {
panic!(
"KSM-973: SecretsManager::new() must not make a network call. \
The token must only be redeemed on the first get_secrets() call."
);
});
let _sm = SecretsManager::new(options)?;
Ok(())
}
#[test]
fn test_storage_is_unbound_after_new() -> Result<(), KSMRError> {
let storage = InMemoryKeyValueStorage::new(None)?;
let kv = KvStoreType::InMemory(storage);
let mut options = ClientOptions::new_client_options_with_token(MOCK_TOKEN.to_string(), kv);
options.set_custom_post_function(mock_no_network);
let sm = SecretsManager::new(options)?;
let has_app_key = match &sm.config {
KvStoreType::InMemory(s) => s.contains(ConfigKeys::KeyAppKey).unwrap_or(false),
KvStoreType::File(s) => s.contains(ConfigKeys::KeyAppKey).unwrap_or(false),
_ => false,
};
assert!(
!has_app_key,
"KSM-973: appKey must NOT be present before the first get_secrets() call. \
Persisting config at this point produces an unbound (non-reloadable) config."
);
let has_owner_key = match &sm.config {
KvStoreType::InMemory(s) => s.contains(ConfigKeys::KeyOwnerPublicKey).unwrap_or(false),
KvStoreType::File(s) => s.contains(ConfigKeys::KeyOwnerPublicKey).unwrap_or(false),
_ => false,
};
assert!(
!has_owner_key,
"KSM-973: appOwnerPublicKey must NOT be present before bind completes."
);
Ok(())
}
#[test]
fn test_bound_config_can_be_reloaded_without_token() -> Result<(), KSMRError> {
let bound_kv = bound_storage()?;
let mut options = ClientOptions::new_client_options(bound_kv);
options.set_custom_post_function(mock_no_network);
SecretsManager::new(options)
.expect("KSM-973: a bound config must be reloadable without a token");
Ok(())
}
#[test]
fn test_pre_bind_keys_present_after_new() -> Result<(), KSMRError> {
let storage = InMemoryKeyValueStorage::new(None)?;
let kv = KvStoreType::InMemory(storage);
let mut options = ClientOptions::new_client_options_with_token(MOCK_TOKEN.to_string(), kv);
options.set_custom_post_function(mock_no_network);
let sm = SecretsManager::new(options)?;
for key in &[
ConfigKeys::KeyClientId,
ConfigKeys::KeyPrivateKey,
ConfigKeys::KeyHostname,
] {
let present = match &sm.config {
KvStoreType::InMemory(s) => s.contains(key.clone()).unwrap_or(false),
KvStoreType::File(s) => s.contains(key.clone()).unwrap_or(false),
_ => false,
};
assert!(
present,
"KSM-973: {:?} must be present in storage after new() — \
it is written locally without any network call.",
key
);
}
Ok(())
}
}