use std::any::Any;
use std::collections::HashMap;
use std::sync::Arc;
use dashmap::DashMap;
use serde::{Deserialize, Serialize};
use uuid::Uuid;
use super::store::{CredValue, Store};
use crate::attributes::parse_attributes;
use crate::{Credential, Entry, Error, Result, api::CredentialApi};
#[derive(Debug, Clone, Eq, PartialEq, Hash, Serialize, Deserialize)]
pub struct CredId {
pub service: String,
pub user: String,
}
#[derive(Clone)]
pub struct CredKey {
pub store: Arc<Store>,
pub id: CredId,
pub uuid: Option<String>,
}
impl std::fmt::Debug for CredKey {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("CredKey")
.field("id", &self.id)
.field("uuid", &self.uuid)
.finish()
}
}
impl CredKey {
pub fn with_unique_pair<T, F>(&self, f: F) -> Result<T>
where
F: FnOnce(&String, &mut CredValue) -> T,
{
match self.uuid.as_ref() {
Some(key) => match self.store.creds.get(&self.id) {
None => Err(Error::NoEntry),
Some(pair) => match pair.value().get_mut(key) {
None => Err(Error::NoEntry),
Some(mut cred) => {
let (key, val) = cred.pair_mut();
Ok(f(key, val))
}
},
},
None => {
match self.store.creds.get(&self.id) {
None => Err(Error::NoEntry),
Some(pair) => {
let creds = pair.value();
match creds.len() {
0 => Err(Error::NoEntry),
1 => {
let mut first = creds.iter_mut().next().unwrap();
let (key, val) = first.pair_mut();
Ok(f(key, val))
}
_ => {
let mut entries: Vec<Entry> = vec![];
for cred in creds.iter() {
let key = CredKey {
store: self.store.clone(),
id: self.id.clone(),
uuid: Some(cred.key().clone()),
};
entries.push(Entry::new_with_credential(Arc::new(key)));
}
Err(Error::Ambiguous(entries))
}
}
}
}
}
}
}
pub fn with_unique_cred<T, F>(&self, f: F) -> Result<T>
where
F: FnOnce(&mut CredValue) -> T,
{
self.with_unique_pair(|_, cred| f(cred))
}
pub fn get_uuid(&self) -> Result<String> {
self.with_unique_pair(|uuid, _| uuid.to_string())
}
pub fn get_comment(&self) -> Result<Option<String>> {
self.with_unique_pair(|_, cred| cred.comment.clone())
}
}
impl CredentialApi for CredKey {
fn set_secret(&self, secret: &[u8]) -> Result<()> {
let result = self.with_unique_cred(|cred| cred.secret = secret.to_vec());
match result {
Ok(_) => Ok(()),
Err(Error::NoEntry) if self.uuid.is_none() => {
let value = CredValue::new(secret);
let creds = DashMap::new();
creds.insert(Uuid::new_v4().to_string(), value);
self.store.creds.insert(self.id.clone(), creds);
Ok(())
}
Err(e) => Err(e),
}
}
fn get_secret(&self) -> Result<Vec<u8>> {
self.with_unique_cred(|cred| cred.secret.clone())
}
fn get_attributes(&self) -> Result<HashMap<String, String>> {
self.with_unique_pair(|uuid, cred| get_attrs(uuid, cred))
}
fn update_attributes(&self, attrs: &HashMap<&str, &str>) -> Result<()> {
parse_attributes(&["comment"], Some(attrs))?;
self.with_unique_cred(|cred| update_attrs(cred, attrs))
}
fn delete_credential(&self) -> Result<()> {
let result = self.with_unique_cred(|_| ());
match result {
Ok(_) => {
match self.uuid.as_ref() {
Some(uuid) => {
self.store.creds.get(&self.id).unwrap().value().remove(uuid);
Ok(())
}
None => {
self.store.creds.remove(&self.id);
Ok(())
}
}
}
Err(e) => Err(e),
}
}
fn get_credential(&self) -> Result<Option<Arc<Credential>>> {
let result = self.get_uuid();
match result {
Ok(uuid) => Ok(Some(Arc::new(CredKey {
store: self.store.clone(),
id: self.id.clone(),
uuid: Some(uuid),
}))),
Err(e) => Err(e),
}
}
fn get_specifiers(&self) -> Option<(String, String)> {
Some((self.id.service.clone(), self.id.user.clone()))
}
fn as_any(&self) -> &dyn Any {
self
}
fn debug_fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
std::fmt::Debug::fmt(self, f)
}
}
pub fn get_attrs(uuid: &str, cred: &CredValue) -> HashMap<String, String> {
let mut attrs = HashMap::new();
attrs.insert("uuid".to_string(), uuid.to_string());
if let Some(date) = &cred.creation_date {
attrs.insert("creation-date".to_string(), date.to_string());
}
if let Some(comment) = &cred.comment {
attrs.insert("comment".to_string(), comment.to_string());
};
attrs
}
pub fn update_attrs(cred: &mut CredValue, attrs: &HashMap<&str, &str>) {
if let Some(comment) = attrs.get("comment") {
cred.comment = Some(comment.to_string());
}
}