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}