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