keyring_core/sample/
credential.rs

1use std::any::Any;
2use std::collections::HashMap;
3use std::sync::Arc;
4
5use dashmap::DashMap;
6use serde::{Deserialize, Serialize};
7use uuid::Uuid;
8
9use super::store::{CredValue, Store};
10use crate::attributes::parse_attributes;
11use crate::{Credential, Entry, Error, Result, api::CredentialApi};
12
13/// Credentials are specified by a pair of service name and username.
14#[derive(Debug, Clone, Eq, PartialEq, Hash, Serialize, Deserialize)]
15pub struct CredId {
16    pub service: String,
17    pub user: String,
18}
19
20/// Each of these keys specifies a specific credential in the store.
21///
22/// For each credential ID, the store maintains a list of all the
23/// credentials associated with that ID. The first in that list
24/// (element 0) is the credential _specified_ by the ID, so it's
25/// the one that's auto-created if there are no credentials with
26/// that ID in the store and a password is set. All keys with
27/// indices higher than 0 are wrappers for a specific credential,
28/// but they do not _specify_ a credential.
29#[derive(Debug, Clone)]
30pub struct CredKey {
31    pub store: Arc<Store>,
32    pub id: CredId,
33    pub uuid: Option<String>,
34}
35
36impl CredKey {
37    /// This is the boilerplate for all credential-reading/updating calls.
38    ///
39    /// It makes sure there is just one credential and, if so, it reads/updates it.
40    /// If there is no credential, it returns a NoEntry error.
41    /// If there are multiple credentials, it returns an ambiguous error.
42    ///
43    /// It knows about the difference between specifiers and wrappers
44    /// and acts accordingly.
45    pub fn with_unique_pair<T, F>(&self, f: F) -> Result<T>
46    where
47        F: FnOnce(&String, &mut CredValue) -> T,
48    {
49        match self.uuid.as_ref() {
50            // this is a wrapper, look for the cred, and if found get it, else fail
51            Some(key) => match self.store.creds.get(&self.id) {
52                None => Err(Error::NoEntry),
53                Some(pair) => match pair.value().get_mut(key) {
54                    None => Err(Error::NoEntry),
55                    Some(mut cred) => {
56                        let (key, val) = cred.pair_mut();
57                        Ok(f(key, val))
58                    }
59                },
60            },
61            // this is a specifier
62            None => {
63                match self.store.creds.get(&self.id) {
64                    // there are no creds: create the only one and set it
65                    None => Err(Error::NoEntry),
66                    // this is a specifier: check for ambiguity and get if not
67                    Some(pair) => {
68                        let creds = pair.value();
69                        match creds.len() {
70                            // no matching cred, can't read or update
71                            0 => Err(Error::NoEntry),
72                            // just one current cred, get it
73                            1 => {
74                                let mut first = creds.iter_mut().next().unwrap();
75                                let (key, val) = first.pair_mut();
76                                Ok(f(key, val))
77                            }
78                            // more than one cred - ambiguous!
79                            _ => {
80                                let mut entries: Vec<Entry> = vec![];
81                                for cred in creds.iter() {
82                                    let key = CredKey {
83                                        store: self.store.clone(),
84                                        id: self.id.clone(),
85                                        uuid: Some(cred.key().clone()),
86                                    };
87                                    entries.push(Entry::new_with_credential(Arc::new(key)));
88                                }
89                                Err(Error::Ambiguous(entries))
90                            }
91                        }
92                    }
93                }
94            }
95        }
96    }
97
98    /// A simpler form of boilerplate which just looks at the cred's value
99    pub fn with_unique_cred<T, F>(&self, f: F) -> Result<T>
100    where
101        F: FnOnce(&mut CredValue) -> T,
102    {
103        self.with_unique_pair(|_, cred| f(cred))
104    }
105
106    /// This returns the UUID of the sole credential for this cred.
107    pub fn get_uuid(&self) -> Result<String> {
108        self.with_unique_pair(|uuid, _| uuid.to_string())
109    }
110
111    /// This returns the comment of the sole credential for this cred.
112    pub fn get_comment(&self) -> Result<Option<String>> {
113        self.with_unique_pair(|_, cred| cred.comment.clone())
114    }
115}
116
117impl CredentialApi for CredKey {
118    /// See the API docs.
119    fn set_secret(&self, secret: &[u8]) -> Result<()> {
120        let result = self.with_unique_cred(|cred| cred.secret = secret.to_vec());
121        match result {
122            Ok(_) => Ok(()),
123            // a specifier with no credential: create the cred
124            Err(Error::NoEntry) if self.uuid.is_none() => {
125                let value = CredValue::new(secret);
126                let creds = DashMap::new();
127                creds.insert(Uuid::new_v4().to_string(), value);
128                self.store.creds.insert(self.id.clone(), creds);
129                Ok(())
130            }
131            // a wrapper with no cred or an ambiguous spec
132            Err(e) => Err(e),
133        }
134    }
135
136    /// See the API docs.
137    fn get_secret(&self) -> Result<Vec<u8>> {
138        self.with_unique_cred(|cred| cred.secret.clone())
139    }
140
141    /// See the API docs.
142    ///
143    /// The possible attributes on credentials in this store are `uuid`, `comment`,
144    /// and `creation-date`.
145    fn get_attributes(&self) -> Result<HashMap<String, String>> {
146        self.with_unique_pair(|uuid, cred| get_attrs(uuid, cred))
147    }
148
149    /// See the API docs.
150    ///
151    /// Only the `comment` attribute can be updated.
152    fn update_attributes(&self, attrs: &HashMap<&str, &str>) -> Result<()> {
153        parse_attributes(&["comment"], Some(attrs))?;
154        self.with_unique_cred(|cred| update_attrs(cred, attrs))
155    }
156
157    /// See the API docs.
158    fn delete_credential(&self) -> Result<()> {
159        let result = self.with_unique_cred(|_| ());
160        match result {
161            // there is exactly one matching cred, delete it
162            Ok(_) => {
163                match self.uuid.as_ref() {
164                    // this is a wrapper, delete the credential key from the map
165                    Some(uuid) => {
166                        self.store.creds.get(&self.id).unwrap().value().remove(uuid);
167                        Ok(())
168                    }
169                    // this is a specifier, and there's only credential, delete the map
170                    None => {
171                        self.store.creds.remove(&self.id);
172                        Ok(())
173                    }
174                }
175            }
176            // there's no cred or many creds, return the error
177            Err(e) => Err(e),
178        }
179    }
180
181    /// See the API docs.
182    ///
183    /// This always returns a new wrapper, even if this is already a wrapper,
184    /// because that's just as easy to do once we've checked the error conditions.
185    fn get_credential(&self) -> Result<Option<Arc<Credential>>> {
186        let result = self.get_uuid();
187        match result {
188            Ok(uuid) => Ok(Some(Arc::new(CredKey {
189                store: self.store.clone(),
190                id: self.id.clone(),
191                uuid: Some(uuid),
192            }))),
193            Err(e) => Err(e),
194        }
195    }
196
197    /// See the API docs.
198    fn get_specifiers(&self) -> Option<(String, String)> {
199        Some((self.id.service.clone(), self.id.user.clone()))
200    }
201
202    /// See the API docs.
203    fn as_any(&self) -> &dyn Any {
204        self
205    }
206
207    /// See the API docs.
208    fn debug_fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
209        std::fmt::Debug::fmt(self, f)
210    }
211}
212
213/// get the attributes on a credential
214///
215/// This is a helper function used by get_attributes
216pub fn get_attrs(uuid: &str, cred: &CredValue) -> HashMap<String, String> {
217    let mut attrs = HashMap::new();
218    attrs.insert("uuid".to_string(), uuid.to_string());
219    if cred.creation_date.is_some() {
220        attrs.insert(
221            "creation-date".to_string(),
222            cred.creation_date.as_ref().unwrap().to_string(),
223        );
224    }
225    if cred.comment.is_some() {
226        attrs.insert(
227            "comment".to_string(),
228            cred.comment.as_ref().unwrap().to_string(),
229        );
230    };
231    attrs
232}
233
234/// update the attributes on a credential
235///
236/// This is a helper function used by update_attributes
237pub fn update_attrs(cred: &mut CredValue, attrs: &HashMap<&str, &str>) {
238    if let Some(comment) = attrs.get("comment") {
239        cred.comment = Some(comment.to_string());
240    }
241}