1use std::cell::RefCell;
41use std::collections::HashMap;
42use std::sync::{Arc, Mutex};
43use std::time::{Duration, SystemTime, UNIX_EPOCH};
44
45use crate::api::{CredentialApi, CredentialStoreApi};
46use crate::{Credential, CredentialPersistence, Entry, Error, Result};
47
48#[derive(Debug)]
53pub struct Cred {
54 pub specifiers: (String, String),
55 pub inner: Mutex<RefCell<CredData>>,
56}
57
58#[derive(Debug, Default)]
66pub struct CredData {
67 pub secret: Option<Vec<u8>>,
68 pub error: Option<Error>,
69}
70
71impl CredentialApi for Cred {
72 fn set_secret(&self, secret: &[u8]) -> Result<()> {
78 let mut inner = self
79 .inner
80 .lock()
81 .expect("Can't access mock data for set_secret: please report a bug!");
82 let data = inner.get_mut();
83 let err = data.error.take();
84 match err {
85 None => {
86 data.secret = Some(secret.to_vec());
87 Ok(())
88 }
89 Some(err) => Err(err),
90 }
91 }
92
93 fn get_secret(&self) -> Result<Vec<u8>> {
99 let mut inner = self
100 .inner
101 .lock()
102 .expect("Can't access mock data for get: please report a bug!");
103 let data = inner.get_mut();
104 let err = data.error.take();
105 match err {
106 None => match &data.secret {
107 None => Err(Error::NoEntry),
108 Some(val) => Ok(val.clone()),
109 },
110 Some(err) => Err(err),
111 }
112 }
113
114 fn delete_credential(&self) -> Result<()> {
119 let mut inner = self
120 .inner
121 .lock()
122 .expect("Can't access mock data for delete: please report a bug!");
123 let data = inner.get_mut();
124 let err = data.error.take();
125 match err {
126 None => match data.secret {
127 Some(_) => {
128 data.secret = None;
129 Ok(())
130 }
131 None => Err(Error::NoEntry),
132 },
133 Some(err) => Err(err),
134 }
135 }
136
137 fn get_credential(&self) -> Result<Option<Arc<Credential>>> {
142 let mut inner = self
143 .inner
144 .lock()
145 .expect("Can't access mock data for get_credential: please report a bug!");
146 let data = inner.get_mut();
147 let err = data.error.take();
148 match err {
149 None => match data.secret {
150 Some(_) => Ok(None),
151 None => Err(Error::NoEntry),
152 },
153 Some(err) => Err(err),
154 }
155 }
156
157 fn get_specifiers(&self) -> Option<(String, String)> {
159 Some(self.specifiers.clone())
160 }
161
162 fn as_any(&self) -> &dyn std::any::Any {
166 self
167 }
168
169 fn debug_fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
171 std::fmt::Debug::fmt(self, f)
172 }
173}
174
175impl Cred {
176 pub fn set_error(&self, err: Error) {
182 let mut inner = self
183 .inner
184 .lock()
185 .expect("Can't access mock data for set_error: please report a bug!");
186 let data = inner.get_mut();
187 data.error = Some(err);
188 }
189}
190
191pub struct Store {
198 pub id: String,
199 pub inner: Mutex<RefCell<Vec<Arc<Cred>>>>,
200}
201
202impl std::fmt::Debug for Store {
203 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
204 f.debug_struct("Store")
205 .field("vendor", &self.vendor())
206 .field("id", &self.id)
207 .finish()
208 }
209}
210
211impl Store {
212 pub fn new() -> Result<Arc<Self>> {
213 Ok(Arc::new(Store {
214 id: format!(
215 "Crate version {}, Instantiated at {}",
216 env!("CARGO_PKG_VERSION"),
217 SystemTime::now()
218 .duration_since(UNIX_EPOCH)
219 .unwrap_or_else(|_| Duration::new(0, 0))
220 .as_secs_f64()
221 ),
222 inner: Mutex::new(RefCell::new(Vec::new())),
223 }))
224 }
225}
226
227impl CredentialStoreApi for Store {
228 fn vendor(&self) -> String {
229 String::from("Mock store, https://crates.io/crates/keyring-core")
230 }
231
232 fn id(&self) -> String {
233 self.id.clone()
234 }
235
236 fn build(
241 &self,
242 service: &str,
243 user: &str,
244 mods: Option<&HashMap<&str, &str>>,
245 ) -> Result<Entry> {
246 if mods.is_some_and(|m| !m.is_empty()) {
247 return Err(Error::NotSupportedByStore(
248 "The mock store doesn't allow modifiers".to_string(),
249 ));
250 }
251 let mut inner = self
252 .inner
253 .lock()
254 .expect("Can't access mock store data: please report a bug!");
255 let creds = inner.get_mut();
256 for cred in creds.iter() {
257 if service == cred.specifiers.0 && user == cred.specifiers.1 {
258 return Ok(Entry {
259 inner: cred.clone(),
260 });
261 }
262 }
263 let cred = Arc::new(Cred {
264 specifiers: (service.to_string(), user.to_string()),
265 inner: Mutex::new(RefCell::new(Default::default())),
266 });
267 creds.push(cred.clone());
268 Ok(Entry { inner: cred })
269 }
270
271 fn search(&self, spec: &HashMap<&str, &str>) -> Result<Vec<Entry>> {
276 let mut result: Vec<Entry> = Vec::new();
277 let svc = spec.get("service").unwrap_or(&"");
278 let usr = spec.get("user").unwrap_or(&"");
279 let mut inner = self
280 .inner
281 .lock()
282 .expect("Can't access mock store data: please report a bug!");
283 let creds = inner.get_mut();
284 for cred in creds.iter() {
285 if !cred.specifiers.0.as_str().contains(svc) {
286 continue;
287 }
288 if !cred.specifiers.1.as_str().contains(usr) {
289 continue;
290 }
291 result.push(Entry {
292 inner: cred.clone(),
293 });
294 }
295 Ok(result)
296 }
297
298 fn as_any(&self) -> &dyn std::any::Any {
300 self
301 }
302
303 fn persistence(&self) -> CredentialPersistence {
305 CredentialPersistence::ProcessOnly
306 }
307
308 fn debug_fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
311 std::fmt::Debug::fmt(self, f)
312 }
313}
314
315#[cfg(test)]
316mod tests {
317 use std::sync::{Arc, Once};
318
319 use super::{Cred, HashMap, Store};
320 use crate::{CredentialPersistence, CredentialStore, Entry, Error, get_default_store};
321
322 static SET_STORE: Once = Once::new();
323
324 fn usually_goes_in_main() {
325 let _ = env_logger::builder().is_test(true).try_init();
326 crate::set_default_store(Store::new().unwrap());
327 }
328
329 #[test]
330 fn test_store_methods() {
331 SET_STORE.call_once(usually_goes_in_main);
332 let store = get_default_store().unwrap();
333 let vendor1 = store.vendor();
334 let id1 = store.id();
335 let vendor2 = store.vendor();
336 let id2 = store.id();
337 assert_eq!(vendor1, vendor2);
338 assert_eq!(id1, id2);
339 let store2: Arc<CredentialStore> = Store::new().unwrap();
340 let vendor3 = store2.vendor();
341 let id3 = store2.id();
342 assert_eq!(vendor1, vendor3);
343 assert_ne!(id1, id3);
344 }
345
346 fn entry_new(service: &str, user: &str) -> Entry {
347 SET_STORE.call_once(usually_goes_in_main);
348 Entry::new(service, user).unwrap_or_else(|err| {
349 panic!("Couldn't create entry (service: {service}, user: {user}): {err:?}")
350 })
351 }
352
353 fn generate_random_string() -> String {
354 use fastrand;
355 use std::iter::repeat_with;
356 repeat_with(fastrand::alphanumeric).take(30).collect()
357 }
358
359 fn generate_random_bytes() -> Vec<u8> {
360 use fastrand;
361 use std::iter::repeat_with;
362 repeat_with(|| fastrand::u8(..)).take(24).collect()
363 }
364
365 fn test_round_trip_no_delete(case: &str, entry: &Entry, in_pass: &str) {
367 entry
368 .set_password(in_pass)
369 .unwrap_or_else(|err| panic!("Can't set password: {case}: {err:?}"));
370 let out_pass = entry
371 .get_password()
372 .unwrap_or_else(|err| panic!("Can't get password: {case}: {err:?}"));
373 assert_eq!(
374 in_pass, out_pass,
375 "Passwords don't match for {case}: set='{in_pass}', get='{out_pass}'",
376 )
377 }
378
379 fn test_round_trip(case: &str, entry: &Entry, in_pass: &str) {
381 test_round_trip_no_delete(case, entry, in_pass);
382 entry
383 .delete_credential()
384 .unwrap_or_else(|err| panic!("Can't delete password: {case}: {err:?}"));
385 let password = entry.get_password();
386 assert!(matches!(password, Err(Error::NoEntry)));
387 }
388
389 pub fn test_round_trip_secret(case: &str, entry: &Entry, in_secret: &[u8]) {
391 entry
392 .set_secret(in_secret)
393 .unwrap_or_else(|err| panic!("Can't set secret for {case}: {err:?}"));
394 let out_secret = entry
395 .get_secret()
396 .unwrap_or_else(|err| panic!("Can't get secret for {case}: {err:?}"));
397 assert_eq!(
398 in_secret, &out_secret,
399 "Secrets don't match for {case}: set='{in_secret:?}', get='{out_secret:?}'",
400 );
401 entry
402 .delete_credential()
403 .unwrap_or_else(|err| panic!("Can't delete credential for {case}: {err:?}"));
404 let secret = entry.get_secret();
405 assert!(matches!(secret, Err(Error::NoEntry)));
406 }
407
408 #[test]
409 fn test_empty_service_and_user() {
410 let name = generate_random_string();
411 let in_pass = "value doesn't matter";
412 test_round_trip("empty user", &entry_new(&name, ""), in_pass);
413 test_round_trip("empty service", &entry_new("", &name), in_pass);
414 test_round_trip("empty service and user", &entry_new("", ""), in_pass);
415 }
416
417 #[test]
418 fn test_empty_password() {
419 let name = generate_random_string();
420 let in_pass = "";
421 test_round_trip("empty password", &entry_new(&name, &name), in_pass);
422 }
423
424 #[test]
425 fn test_missing_entry() {
426 let name = generate_random_string();
427 let entry = entry_new(&name, &name);
428 assert!(matches!(entry.get_password(), Err(Error::NoEntry)))
429 }
430
431 #[test]
432 fn test_round_trip_ascii_password() {
433 let name = generate_random_string();
434 let entry = entry_new(&name, &name);
435 test_round_trip("ascii password", &entry, "test ascii password");
436 }
437
438 #[test]
439 fn test_round_trip_non_ascii_password() {
440 let name = generate_random_string();
441 let entry = entry_new(&name, &name);
442 test_round_trip("non-ascii password", &entry, "このきれいな花は桜です");
443 }
444
445 #[test]
446 fn test_entries_with_same_and_different_specifiers() {
447 let name1 = generate_random_string();
448 let name2 = generate_random_string();
449 let entry1 = entry_new(&name1, &name2);
450 let entry2 = entry_new(&name1, &name2);
451 let entry3 = entry_new(&name2, &name1);
452 entry1.set_password("test password").unwrap();
453 let pw2 = entry2.get_password().unwrap();
454 assert_eq!(pw2, "test password");
455 _ = entry3.get_password().unwrap_err();
456 entry1.delete_credential().unwrap();
457 _ = entry2.get_password().unwrap_err();
458 entry3.delete_credential().unwrap_err();
459 }
460
461 #[test]
462 fn test_get_credential_and_specifiers() {
463 let name = generate_random_string();
464 let entry1 = entry_new(&name, &name);
465 assert!(matches!(entry1.get_credential(), Err(Error::NoEntry)));
466 entry1.set_password("password for entry1").unwrap();
467 let wrapper = entry1.get_credential().unwrap();
468 let (service, user) = wrapper.get_specifiers().unwrap();
469 assert_eq!(service, name);
470 assert_eq!(user, name);
471 wrapper.delete_credential().unwrap();
472 entry1.delete_credential().unwrap_err();
473 wrapper.delete_credential().unwrap_err();
474 }
475
476 #[test]
477 fn test_round_trip_random_secret() {
478 let name = generate_random_string();
479 let entry = entry_new(&name, &name);
480 let secret = generate_random_bytes();
481 test_round_trip_secret("non-ascii password", &entry, secret.as_slice());
482 }
483
484 #[test]
485 fn test_update() {
486 let name = generate_random_string();
487 let entry = entry_new(&name, &name);
488 test_round_trip_no_delete("initial ascii password", &entry, "test ascii password");
489 test_round_trip(
490 "updated non-ascii password",
491 &entry,
492 "このきれいな花は桜です",
493 );
494 }
495
496 #[test]
497 fn test_set_error() {
498 let name = generate_random_string();
499 let entry = entry_new(&name, &name);
500 let password = "test ascii password";
501 let mock: &Cred = entry.inner.as_any().downcast_ref().unwrap();
502 mock.set_error(Error::Invalid(
503 "mock error".to_string(),
504 "is an error".to_string(),
505 ));
506 assert!(matches!(
507 entry.set_password(password),
508 Err(Error::Invalid(_, _))
509 ));
510 entry.set_password(password).unwrap();
511 mock.set_error(Error::NoEntry);
512 assert!(matches!(entry.get_password(), Err(Error::NoEntry)));
513 let stored_password = entry.get_password().unwrap();
514 assert_eq!(stored_password, password);
515 mock.set_error(Error::TooLong("mock".to_string(), 3));
516 assert!(matches!(
517 entry.delete_credential(),
518 Err(Error::TooLong(_, 3))
519 ));
520 entry.delete_credential().unwrap();
521 assert!(matches!(entry.get_password(), Err(Error::NoEntry)))
522 }
523
524 #[test]
525 fn test_search() {
526 let store: Arc<CredentialStore> = Store::new().unwrap();
527 let all = store.search(&HashMap::from([])).unwrap();
528 assert!(all.is_empty());
529 let all = store
530 .search(&HashMap::from([("service", ""), ("user", "")]))
531 .unwrap();
532 assert!(all.is_empty());
533 let e1 = store.build("foo", "bar", None).unwrap();
534 e1.set_password("e1").unwrap();
535 let all = store.search(&HashMap::from([])).unwrap();
536 assert_eq!(all.len(), 1);
537 let all = store
538 .search(&HashMap::from([("service", ""), ("user", "")]))
539 .unwrap();
540 assert_eq!(all.len(), 1);
541 let e2 = store.build("foo", "bam", None).unwrap();
542 e2.set_password("e2").unwrap();
543 let one = store.search(&HashMap::from([("user", "m")])).unwrap();
544 assert_eq!(one.len(), 1);
545 let one = store
546 .search(&HashMap::from([("service", "foo"), ("user", "bar")]))
547 .unwrap();
548 assert_eq!(one.len(), 1);
549 let two = store.search(&HashMap::from([("service", "foo")])).unwrap();
550 assert_eq!(two.len(), 2);
551 let all = store.search(&HashMap::from([("foo", "bar")])).unwrap();
552 assert_eq!(all.len(), 2);
553 }
554
555 #[test]
556 fn test_persistence() {
557 let store: Arc<CredentialStore> = Store::new().unwrap();
558 assert!(matches!(
559 store.persistence(),
560 CredentialPersistence::ProcessOnly
561 ))
562 }
563}