use std::any::Any;
use std::collections::HashMap;
use std::sync::Arc;
use super::{Entry, Error, Result};
pub trait CredentialApi {
fn set_password(&self, password: &str) -> Result<()> {
self.set_secret(password.as_bytes())
}
fn set_secret(&self, secret: &[u8]) -> Result<()>;
fn get_password(&self) -> Result<String> {
let secret = self.get_secret()?;
super::error::decode_password(secret)
}
fn get_secret(&self) -> Result<Vec<u8>>;
fn get_attributes(&self) -> Result<HashMap<String, String>> {
self.get_secret()?;
Ok(HashMap::new())
}
fn update_attributes(&self, _: &HashMap<&str, &str>) -> Result<()> {
Err(Error::NotSupportedByStore(String::from(
"This store does not allow attribute updates",
)))
}
fn delete_credential(&self) -> Result<()>;
fn get_credential(&self) -> Result<Option<Arc<Credential>>>;
fn get_specifiers(&self) -> Option<(String, String)>;
fn as_any(&self) -> &dyn Any;
fn debug_fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
std::fmt::Debug::fmt(self.as_any(), f)
}
}
pub type Credential = dyn CredentialApi + Send + Sync;
impl std::fmt::Debug for Credential {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.debug_fmt(f)
}
}
#[non_exhaustive]
pub enum CredentialPersistence {
EntryOnly,
ProcessOnly,
UntilLogout,
UntilReboot,
UntilDelete,
Unspecified,
}
pub trait CredentialStoreApi {
fn vendor(&self) -> String;
fn id(&self) -> String;
fn build(
&self,
service: &str,
user: &str,
modifiers: Option<&HashMap<&str, &str>>,
) -> Result<Entry>;
fn search(&self, _spec: &HashMap<&str, &str>) -> Result<Vec<Entry>> {
let msg = "This store does not provide search capabilities";
Err(Error::NotSupportedByStore(msg.to_string()))
}
fn as_any(&self) -> &dyn Any;
fn persistence(&self) -> CredentialPersistence {
CredentialPersistence::UntilDelete
}
fn debug_fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
std::fmt::Debug::fmt(self.as_any(), f)
}
}
impl std::fmt::Debug for CredentialStore {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.debug_fmt(f)
}
}
pub type CredentialStore = dyn CredentialStoreApi + Send + Sync;
#[cfg(test)]
mod tests {
use super::{Any, Arc, CredentialStore, CredentialStoreApi, Entry, Error, HashMap, Result};
struct MinimalStore;
impl CredentialStoreApi for MinimalStore {
fn vendor(&self) -> String {
unimplemented!()
}
fn id(&self) -> String {
unimplemented!()
}
fn build(&self, _: &str, _: &str, mods: Option<&HashMap<&str, &str>>) -> Result<Entry> {
match mods {
Some(mods) => Err(Error::Invalid("mods".to_string(), mods.len().to_string())),
None => Err(Error::Invalid("no-mods".to_string(), "0".to_string())),
}
}
fn as_any(&self) -> &dyn Any {
unimplemented!()
}
}
#[test]
fn test_entry_methods() {
let _ = env_logger::builder().is_test(true).try_init();
assert!(crate::get_default_store().is_none());
assert!(matches!(
Entry::new("test", "test"),
Err(Error::NoDefaultStore)
));
assert!(matches!(
Entry::new_with_modifiers("test", "test", &HashMap::new()),
Err(Error::NoDefaultStore)
));
assert!(matches!(
Entry::search(&HashMap::new()),
Err(Error::NoDefaultStore)
));
let default: Arc<CredentialStore> = Arc::new(MinimalStore);
crate::set_default_store(default.clone());
let current = crate::get_default_store().unwrap();
assert!(Arc::ptr_eq(¤t, &default));
let last = crate::unset_default_store().unwrap();
assert!(Arc::ptr_eq(&last, &default));
assert!(crate::get_default_store().is_none());
crate::set_default_store(default);
assert!(matches!(
Entry::new("test", "test"),
Err(Error::Invalid(x, y)) if x == "no-mods" && y == "0"
));
assert!(matches!(
Entry::new_with_modifiers("test", "test", &HashMap::new()),
Err(Error::Invalid(x, y)) if x == "mods" && y == "0"
));
assert!(matches!(
Entry::new_with_modifiers("test", "test", &HashMap::from([("k1", "v1"), ("k2", "v2")])),
Err(Error::Invalid(x, y)) if x == "mods" && y == "2"
));
assert!(matches!(
Entry::search(&HashMap::new()),
Err(Error::NotSupportedByStore(_))
));
crate::unset_default_store();
assert!(crate::get_default_store().is_none());
}
}