1use crate::db::{LoginDb, LoginsDeletionMetrics};
5use crate::encryption::EncryptorDecryptor;
6use crate::error::*;
7use crate::login::{
8 BulkResultEntry, EncryptedLogin, Login, LoginCandidate, LoginEntry, LoginEntryWithMeta,
9};
10use crate::LoginsSyncEngine;
11use parking_lot::Mutex;
12use sql_support::run_maintenance;
13use std::path::Path;
14use std::sync::{Arc, Weak};
15use sync15::{
16 engine::{EngineSyncAssociation, SyncEngine, SyncEngineId},
17 ServerTimestamp,
18};
19
20#[derive(uniffi::Enum)]
21pub enum LoginOrErrorMessage {
22 Login,
23 String,
24}
25
26lazy_static::lazy_static! {
28 static ref STORE_FOR_MANAGER: Mutex<Weak<LoginStore>> = Mutex::new(Weak::new());
31}
32
33pub fn get_registered_sync_engine(engine_id: &SyncEngineId) -> Option<Box<dyn SyncEngine>> {
36 let weak = STORE_FOR_MANAGER.lock();
37 match weak.upgrade() {
38 None => None,
39 Some(store) => match create_sync_engine(store, engine_id) {
40 Ok(engine) => Some(engine),
41 Err(e) => {
42 report_error!("logins-sync-engine-create-error", "{e}");
43 None
44 }
45 },
46 }
47}
48
49fn create_sync_engine(
50 store: Arc<LoginStore>,
51 engine_id: &SyncEngineId,
52) -> Result<Box<dyn SyncEngine>> {
53 match engine_id {
54 SyncEngineId::Passwords => Ok(Box::new(LoginsSyncEngine::new(Arc::clone(&store))?)),
55 _ => unreachable!("can't provide unknown engine: {}", engine_id),
58 }
59}
60
61fn map_bulk_result_entry(
62 enc_login: Result<EncryptedLogin>,
63 encdec: &dyn EncryptorDecryptor,
64) -> BulkResultEntry {
65 match enc_login {
66 Ok(enc_login) => match enc_login.decrypt(encdec) {
67 Ok(login) => BulkResultEntry::Success { login },
68 Err(error) => {
69 warn!("Login could not be decrypted. This indicates a fundamental problem with the encryption key.");
70 BulkResultEntry::Error {
71 message: error.to_string(),
72 }
73 }
74 },
75 Err(error) => BulkResultEntry::Error {
76 message: error.to_string(),
77 },
78 }
79}
80
81pub struct LoginStore {
82 pub db: Mutex<Option<LoginDb>>,
83}
84
85impl LoginStore {
86 #[handle_error(Error)]
87 pub fn new(path: impl AsRef<Path>, encdec: Arc<dyn EncryptorDecryptor>) -> ApiResult<Self> {
88 let db = Mutex::new(Some(LoginDb::open(path, encdec)?));
89 Ok(Self { db })
90 }
91
92 pub fn new_from_db(db: LoginDb) -> Self {
93 let db = Mutex::new(Some(db));
94 Self { db }
95 }
96
97 #[cfg(test)]
99 pub fn new_in_memory() -> Self {
100 let db = Mutex::new(Some(LoginDb::open_in_memory()));
101 Self { db }
102 }
103
104 pub fn lock_db(&self) -> Result<parking_lot::MappedMutexGuard<'_, LoginDb>> {
105 parking_lot::MutexGuard::try_map(self.db.lock(), |db| db.as_mut())
106 .map_err(|_| Error::DatabaseClosed)
107 }
108
109 #[handle_error(Error)]
110 pub fn is_empty(&self) -> ApiResult<bool> {
111 Ok(self.lock_db()?.count_all()? == 0)
112 }
113
114 #[handle_error(Error)]
115 pub fn list(&self) -> ApiResult<Vec<Login>> {
116 let db = self.lock_db()?;
117 db.get_all().and_then(|logins| {
118 logins
119 .into_iter()
120 .map(|login| login.decrypt(db.encdec.as_ref()))
121 .collect()
122 })
123 }
124
125 #[handle_error(Error)]
131 pub fn list_candidates(&self) -> ApiResult<Vec<LoginCandidate>> {
132 Ok(self
133 .lock_db()?
134 .get_all()?
135 .into_iter()
136 .map(LoginCandidate::from)
137 .collect())
138 }
139
140 #[handle_error(Error)]
141 pub fn count(&self) -> ApiResult<i64> {
142 self.lock_db()?.count_all()
143 }
144
145 #[handle_error(Error)]
146 pub fn count_by_origin(&self, origin: &str) -> ApiResult<i64> {
147 self.lock_db()?.count_by_origin(origin)
148 }
149
150 #[handle_error(Error)]
151 pub fn count_by_form_action_origin(&self, form_action_origin: &str) -> ApiResult<i64> {
152 self.lock_db()?
153 .count_by_form_action_origin(form_action_origin)
154 }
155
156 #[handle_error(Error)]
157 pub fn get(&self, id: &str) -> ApiResult<Option<Login>> {
158 let db = self.lock_db()?;
159 match db.get_by_id(id) {
160 Ok(result) => match result {
161 Some(enc_login) => enc_login.decrypt(db.encdec.as_ref()).map(Some),
162 None => Ok(None),
163 },
164 Err(err) => Err(err),
165 }
166 }
167
168 #[handle_error(Error)]
174 pub fn get_many(&self, ids: Vec<String>) -> ApiResult<Vec<Login>> {
175 let db = self.lock_db()?;
176 db.get_many(&ids)?
177 .into_iter()
178 .map(|login| login.decrypt(db.encdec.as_ref()))
179 .collect()
180 }
181
182 #[handle_error(Error)]
183 pub fn get_by_base_domain(&self, base_domain: &str) -> ApiResult<Vec<Login>> {
184 let db = self.lock_db()?;
185 db.get_by_base_domain(base_domain).and_then(|logins| {
186 logins
187 .into_iter()
188 .map(|login| login.decrypt(db.encdec.as_ref()))
189 .collect()
190 })
191 }
192
193 #[handle_error(Error)]
194 pub fn has_logins_by_base_domain(&self, base_domain: &str) -> ApiResult<bool> {
195 self.lock_db()?
196 .get_by_base_domain(base_domain)
197 .map(|logins| !logins.is_empty())
198 }
199
200 #[handle_error(Error)]
201 pub fn find_login_to_update(&self, entry: LoginEntry) -> ApiResult<Option<Login>> {
202 let db = self.lock_db()?;
203 db.find_login_to_update(entry)
204 }
205
206 #[handle_error(Error)]
207 pub fn touch(&self, id: &str) -> ApiResult<()> {
208 self.lock_db()?.touch(id)
209 }
210
211 #[handle_error(Error)]
212 pub fn are_potentially_vulnerable_passwords(&self, ids: Vec<String>) -> ApiResult<Vec<String>> {
213 let db = self.lock_db()?;
215 let ids: Vec<&str> = ids.iter().map(|id| &**id).collect();
216 db.are_potentially_vulnerable_passwords(&ids)
217 }
218
219 #[handle_error(Error)]
220 pub fn is_potentially_vulnerable_password(&self, id: &str) -> ApiResult<bool> {
221 let db = self.lock_db()?;
222 db.is_potentially_vulnerable_password(id)
223 }
224
225 #[handle_error(Error)]
226 pub fn record_potentially_vulnerable_passwords(&self, passwords: Vec<String>) -> ApiResult<()> {
227 let db = self.lock_db()?;
228 db.record_potentially_vulnerable_passwords(passwords)
229 }
230
231 #[handle_error(Error)]
232 pub fn reset_all_breaches(&self) -> ApiResult<()> {
233 self.lock_db()?.reset_all_breaches()
234 }
235
236 #[handle_error(Error)]
237 pub fn record_breach_alert_dismissal(&self, id: &str) -> ApiResult<()> {
238 self.lock_db()?.record_breach_alert_dismissal(id)
239 }
240
241 #[handle_error(Error)]
242 pub fn record_breach_alert_dismissal_time(&self, id: &str, timestamp: i64) -> ApiResult<()> {
243 self.lock_db()?
244 .record_breach_alert_dismissal_time(id, timestamp)
245 }
246
247 #[handle_error(Error)]
248 pub fn delete(&self, id: &str) -> ApiResult<bool> {
249 self.lock_db()?.delete(id)
250 }
251
252 #[handle_error(Error)]
253 pub fn delete_many(&self, ids: Vec<String>) -> ApiResult<Vec<bool>> {
254 let ids: Vec<&str> = ids.iter().map(|id| &**id).collect();
257 self.lock_db()?.delete_many(ids)
258 }
259
260 #[handle_error(Error)]
261 pub fn delete_all(&self) -> ApiResult<Vec<String>> {
262 self.lock_db()?.delete_all()
263 }
264
265 #[handle_error(Error)]
266 pub fn delete_all_except_fxa(&self) -> ApiResult<Vec<String>> {
267 self.lock_db()?.delete_all_except_fxa()
268 }
269
270 #[handle_error(Error)]
271 pub fn delete_undecryptable_records_for_remote_replacement(
272 self: Arc<Self>,
273 ) -> ApiResult<LoginsDeletionMetrics> {
274 let engine = LoginsSyncEngine::new(Arc::clone(&self))?;
280
281 let db = self.lock_db()?;
282 let deletion_stats = db.delete_undecryptable_records_for_remote_replacement()?;
283 engine.set_last_sync(&db, ServerTimestamp(0))?;
284 Ok(deletion_stats)
285 }
286
287 #[handle_error(Error)]
288 pub fn wipe_local(&self) -> ApiResult<()> {
289 self.lock_db()?.wipe_local()?;
290 Ok(())
291 }
292
293 #[handle_error(Error)]
294 pub fn wipe_local_except_fxa(&self) -> ApiResult<()> {
295 self.lock_db()?.wipe_local_except_fxa()?;
296 Ok(())
297 }
298
299 #[handle_error(Error)]
300 pub fn reset(self: Arc<Self>) -> ApiResult<()> {
301 let engine = LoginsSyncEngine::new(Arc::clone(&self))?;
305 engine.do_reset(&EngineSyncAssociation::Disconnected)?;
306 Ok(())
307 }
308
309 #[handle_error(Error)]
310 pub fn update(&self, id: &str, entry: LoginEntry) -> ApiResult<Login> {
311 let db = self.lock_db()?;
312 db.update(id, entry)
313 .and_then(|enc_login| enc_login.decrypt(db.encdec.as_ref()))
314 }
315
316 #[handle_error(Error)]
317 pub fn add(&self, entry: LoginEntry) -> ApiResult<Login> {
318 let db = self.lock_db()?;
319 db.add(entry)
320 .and_then(|enc_login| enc_login.decrypt(db.encdec.as_ref()))
321 }
322
323 #[handle_error(Error)]
324 pub fn add_many(&self, entries: Vec<LoginEntry>) -> ApiResult<Vec<BulkResultEntry>> {
325 let db = self.lock_db()?;
326 let encdec = db.encdec.as_ref();
327 db.add_many(entries).map(|enc_logins| {
328 enc_logins
329 .into_iter()
330 .map(|enc_login| map_bulk_result_entry(enc_login, encdec))
331 .collect()
332 })
333 }
334
335 #[handle_error(Error)]
339 pub fn add_with_meta(&self, entry_with_meta: LoginEntryWithMeta) -> ApiResult<Login> {
340 let db = self.lock_db()?;
341 db.add_with_meta(entry_with_meta)
342 .and_then(|enc_login| enc_login.decrypt(db.encdec.as_ref()))
343 }
344
345 #[handle_error(Error)]
346 pub fn add_many_with_meta(
347 &self,
348 entries_with_meta: Vec<LoginEntryWithMeta>,
349 ) -> ApiResult<Vec<BulkResultEntry>> {
350 let db = self.lock_db()?;
351 let encdec = db.encdec.as_ref();
352 db.add_many_with_meta(entries_with_meta).map(|enc_logins| {
353 enc_logins
354 .into_iter()
355 .map(|enc_login| map_bulk_result_entry(enc_login, encdec))
356 .collect()
357 })
358 }
359
360 #[handle_error(Error)]
361 pub fn add_or_update(&self, entry: LoginEntry) -> ApiResult<Login> {
362 let db = self.lock_db()?;
363 db.add_or_update(entry)
364 .and_then(|enc_login| enc_login.decrypt(db.encdec.as_ref()))
365 }
366
367 #[handle_error(Error)]
368 pub fn run_maintenance(&self, options: Option<RunMaintenanceOptions>) -> ApiResult<()> {
369 let conn = self.lock_db()?;
370 let options = options.unwrap_or_default();
371 run_maintenance(&conn)?;
372 if options.delete_undecryptable_records_for_remote_replacement {
373 conn.delete_undecryptable_records_for_remote_replacement()?;
374 }
375 Ok(())
376 }
377
378 pub fn shutdown(&self) {
379 if let Some(db) = self.db.lock().take() {
380 let _ = db.shutdown();
381 }
382 }
383
384 pub fn register_with_sync_manager(self: Arc<Self>) {
390 let mut state = STORE_FOR_MANAGER.lock();
391 *state = Arc::downgrade(&self);
392 }
393
394 #[handle_error(Error)]
401 pub fn create_logins_sync_engine(self: Arc<Self>) -> ApiResult<Box<dyn SyncEngine>> {
402 Ok(Box::new(LoginsSyncEngine::new(self)?) as Box<dyn SyncEngine>)
403 }
404}
405
406pub struct RunMaintenanceOptions {
407 pub delete_undecryptable_records_for_remote_replacement: bool,
408}
409
410impl Default for RunMaintenanceOptions {
411 fn default() -> Self {
412 Self {
413 delete_undecryptable_records_for_remote_replacement: true,
414 }
415 }
416}
417
418#[cfg(not(feature = "keydb"))]
419#[cfg(test)]
420mod tests {
421 use super::*;
422 use crate::encryption::{create_key, KeyManager, ManagedEncryptorDecryptor};
423 use crate::util;
424 use nss_as::ensure_initialized;
425 use std::cmp::Reverse;
426 use std::sync::atomic::{AtomicUsize, Ordering};
427 use std::time::SystemTime;
428
429 fn assert_logins_equiv(a: &LoginEntry, b: &Login) {
430 assert_eq!(a.origin, b.origin);
431 assert_eq!(a.form_action_origin, b.form_action_origin);
432 assert_eq!(a.http_realm, b.http_realm);
433 assert_eq!(a.username_field, b.username_field);
434 assert_eq!(a.password_field, b.password_field);
435 assert_eq!(b.username, a.username);
436 assert_eq!(b.password, a.password);
437 }
438
439 #[test]
440 fn test_general() {
441 ensure_initialized();
442
443 let store = LoginStore::new_in_memory();
444 let list = store.list().expect("Grabbing Empty list to work");
445 assert_eq!(list.len(), 0);
446 let start_us = util::system_time_ms_i64(SystemTime::now());
447
448 let a = LoginEntry {
449 origin: "https://www.example.com".into(),
450 form_action_origin: Some("https://www.example.com".into()),
451 username_field: "user_input".into(),
452 password_field: "pass_input".into(),
453 username: "user".into(),
454 password: "password".into(),
455 ..Default::default()
456 };
457
458 let b = LoginEntry {
459 origin: "https://www.example2.com".into(),
460 http_realm: Some("Some String Here".into()),
461 username: "asdf".into(),
462 password: "fdsa".into(),
463 ..Default::default()
464 };
465 let a_id = store.add(a.clone()).expect("added a").id;
466 let b_id = store.add(b.clone()).expect("added b").id;
467
468 let a_from_db = store
469 .get(&a_id)
470 .expect("Not to error getting a")
471 .expect("a to exist");
472
473 assert_logins_equiv(&a, &a_from_db);
474 assert!(a_from_db.time_created >= start_us);
475 assert!(a_from_db.time_password_changed >= start_us);
476 assert!(a_from_db.time_last_used >= start_us);
477 assert_eq!(a_from_db.times_used, 1);
478
479 let b_from_db = store
480 .get(&b_id)
481 .expect("Not to error getting b")
482 .expect("b to exist");
483
484 assert_logins_equiv(&LoginEntry { ..b.clone() }, &b_from_db);
485 assert!(b_from_db.time_created >= start_us);
486 assert!(b_from_db.time_password_changed >= start_us);
487 assert!(b_from_db.time_last_used >= start_us);
488 assert_eq!(b_from_db.times_used, 1);
489
490 let mut list = store.list().expect("Grabbing list to work");
491 assert_eq!(list.len(), 2);
492
493 let mut expect = vec![a_from_db, b_from_db.clone()];
494
495 list.sort_by_key(|b| Reverse(b.guid()));
496 expect.sort_by_key(|b| Reverse(b.guid()));
497 assert_eq!(list, expect);
498
499 store.delete(&a_id).expect("Successful delete");
500 assert!(store
501 .get(&a_id)
502 .expect("get after delete should still work")
503 .is_none());
504
505 let list = store.list().expect("Grabbing list to work");
506 assert_eq!(list.len(), 1);
507 assert_eq!(list[0], b_from_db);
508
509 let has_logins = store
510 .has_logins_by_base_domain("example2.com")
511 .expect("Expect a result for this origin");
512 assert!(has_logins);
513
514 let list = store
515 .get_by_base_domain("example2.com")
516 .expect("Expect a list for this origin");
517 assert_eq!(list.len(), 1);
518 assert_eq!(list[0], b_from_db);
519
520 let has_logins = store
521 .has_logins_by_base_domain("www.example.com")
522 .expect("Expect a result for this origin");
523 assert!(!has_logins);
524
525 let list = store
526 .get_by_base_domain("www.example.com")
527 .expect("Expect an empty list");
528 assert_eq!(list.len(), 0);
529
530 let now_us = util::system_time_ms_i64(SystemTime::now());
531 let b2 = LoginEntry {
532 username: b.username.to_owned(),
533 password: "newpass".into(),
534 ..b
535 };
536
537 store
538 .update(&b_id, b2.clone())
539 .expect("update b should work");
540
541 let b_after_update = store
542 .get(&b_id)
543 .expect("Not to error getting b")
544 .expect("b to exist");
545
546 assert_logins_equiv(&b2, &b_after_update);
547 assert!(b_after_update.time_created >= start_us);
548 assert!(b_after_update.time_created <= now_us);
549 assert!(b_after_update.time_password_changed >= now_us);
550 assert_eq!(b_after_update.time_last_used, b_from_db.time_last_used);
552 assert_eq!(b_after_update.times_used, 1);
553 }
554
555 #[test]
556 fn test_sync_manager_registration() {
557 ensure_initialized();
558 let store = Arc::new(LoginStore::new_in_memory());
559 assert_eq!(Arc::strong_count(&store), 1);
560 assert_eq!(Arc::weak_count(&store), 0);
561 Arc::clone(&store).register_with_sync_manager();
562 assert_eq!(Arc::strong_count(&store), 1);
563 assert_eq!(Arc::weak_count(&store), 1);
564 let registered = STORE_FOR_MANAGER.lock().upgrade().expect("should upgrade");
565 assert!(Arc::ptr_eq(&store, ®istered));
566 drop(registered);
567 assert_eq!(Arc::strong_count(&store), 1);
569 assert_eq!(Arc::weak_count(&store), 1);
570 drop(store);
572 assert!(STORE_FOR_MANAGER.lock().upgrade().is_none());
573 }
574
575 #[test]
576 fn test_wipe_local_on_a_fresh_database_is_a_noop() {
577 ensure_initialized();
578 let db = LoginDb::open_in_memory();
580 db.add_or_update(LoginEntry {
581 origin: "https://www.example.com".into(),
582 form_action_origin: Some("https://www.example.com".into()),
583 username_field: "user_input".into(),
584 password_field: "pass_input".into(),
585 username: "coolperson21".into(),
586 password: "p4ssw0rd".into(),
587 ..Default::default()
588 })
589 .unwrap();
590 assert!(db.wipe_local().unwrap() > 0);
591
592 let db = LoginDb::open_in_memory();
594 assert_eq!(db.wipe_local().unwrap(), 0);
595 }
596
597 #[test]
598 fn test_shutdown() {
599 ensure_initialized();
600 let store = LoginStore::new_in_memory();
601 store.shutdown();
602 assert!(matches!(
603 store.list(),
604 Err(LoginsApiError::UnexpectedLoginsApiError { reason: _ })
605 ));
606 assert!(store.db.lock().is_none());
607 }
608
609 struct CountingKeyManager {
612 key: String,
613 calls: AtomicUsize,
614 }
615
616 impl KeyManager for CountingKeyManager {
617 fn get_key(&self) -> ApiResult<Vec<u8>> {
618 self.calls.fetch_add(1, Ordering::SeqCst);
619 Ok(self.key.as_bytes().into())
620 }
621 }
622
623 fn store_with_encdec(encdec: Arc<dyn EncryptorDecryptor>) -> LoginStore {
624 let conn = rusqlite::Connection::open_in_memory().unwrap();
625 LoginStore::new_from_db(LoginDb::with_connection(conn, encdec).unwrap())
626 }
627
628 fn test_entry(origin: &str, username: &str) -> LoginEntry {
629 LoginEntry {
630 origin: origin.into(),
631 http_realm: Some("Some Realm".into()),
632 username: username.into(),
633 password: "p4ssw0rd".into(),
634 ..Default::default()
635 }
636 }
637
638 #[test]
639 fn test_list_candidates_does_not_need_the_key() {
640 ensure_initialized();
641
642 let key_manager = Arc::new(CountingKeyManager {
643 key: create_key().unwrap(),
644 calls: AtomicUsize::new(0),
645 });
646 let store = store_with_encdec(Arc::new(ManagedEncryptorDecryptor::new(
647 key_manager.clone(),
648 )));
649
650 let a = store
651 .add(test_entry("https://www.a.com", "a-user"))
652 .unwrap();
653 let b = store
654 .add(test_entry("https://www.b.com", "b-user"))
655 .unwrap();
656
657 assert!(key_manager.calls.load(Ordering::SeqCst) > 0);
659 key_manager.calls.store(0, Ordering::SeqCst);
660
661 let mut candidates = store.list_candidates().unwrap();
662 assert_eq!(key_manager.calls.load(Ordering::SeqCst), 0);
663
664 candidates.sort_by(|l, r| l.origin.cmp(&r.origin));
665 assert_eq!(candidates.len(), 2);
666 for (candidate, login) in candidates.iter().zip([&a, &b]) {
667 assert_eq!(candidate.id, login.id);
668 assert_eq!(candidate.origin, login.origin);
669 assert_eq!(candidate.http_realm, login.http_realm);
670 assert_eq!(candidate.form_action_origin, login.form_action_origin);
671 assert_eq!(candidate.username_field, login.username_field);
672 assert_eq!(candidate.password_field, login.password_field);
673 assert_eq!(candidate.times_used, login.times_used);
674 assert_eq!(candidate.time_created, login.time_created);
675 assert_eq!(candidate.time_last_used, login.time_last_used);
676 assert_eq!(candidate.time_password_changed, login.time_password_changed);
677 assert_eq!(
678 candidate.time_last_breach_alert_dismissed,
679 login.time_last_breach_alert_dismissed
680 );
681 }
682 }
683
684 #[test]
685 fn test_get_many() {
686 ensure_initialized();
687
688 let store = LoginStore::new_in_memory();
689 let a = store
690 .add(test_entry("https://www.a.com", "a-user"))
691 .unwrap();
692 let b = store
693 .add(test_entry("https://www.b.com", "b-user"))
694 .unwrap();
695 store
696 .add(test_entry("https://www.c.com", "c-user"))
697 .unwrap();
698
699 assert_eq!(store.get_many(vec![]).unwrap(), vec![]);
700
701 let mut got = store
704 .get_many(vec![b.id.clone(), "no-such-guid".to_string(), a.id.clone()])
705 .unwrap();
706 got.sort_by(|l, r| l.origin.cmp(&r.origin));
707 assert_eq!(got, vec![a, b]);
708 }
709
710 #[test]
711 fn test_get_many_with_an_undecryptable_login() {
712 ensure_initialized();
713
714 let store = LoginStore::new_in_memory();
715 let a = store
716 .add(test_entry("https://www.a.com", "a-user"))
717 .unwrap();
718 let b = store
719 .add(test_entry("https://www.b.com", "b-user"))
720 .unwrap();
721
722 store
723 .lock_db()
724 .unwrap()
725 .db
726 .execute(
727 "UPDATE loginsL SET secFields = 'not-a-ciphertext' WHERE guid = ?",
728 [&b.id],
729 )
730 .unwrap();
731
732 assert!(matches!(
734 store.get_many(vec![a.id.clone(), b.id]),
735 Err(LoginsApiError::UnexpectedLoginsApiError { .. })
736 ));
737 }
738
739 #[test]
740 fn test_delete_undecryptable_records_for_remote_replacement() {
741 ensure_initialized();
742 let store = Arc::new(LoginStore::new_in_memory());
743 store
745 .delete_undecryptable_records_for_remote_replacement()
746 .unwrap();
747 }
748}
749
750#[test]
751fn test_send() {
752 fn ensure_send<T: Send>() {}
753 ensure_send::<LoginStore>();
754}
755
756#[cfg(feature = "keydb")]
757#[cfg(test)]
758mod tests_keydb {
759 use super::*;
760 use crate::{ManagedEncryptorDecryptor, NSSKeyManager, PrimaryPasswordAuthenticator};
761 use async_trait::async_trait;
762 use nss_as::ensure_initialized_with_profile_dir;
763 use std::path::PathBuf;
764
765 struct MockPrimaryPasswordAuthenticator {
766 password: String,
767 }
768
769 #[async_trait]
770 impl PrimaryPasswordAuthenticator for MockPrimaryPasswordAuthenticator {
771 async fn get_primary_password(&self) -> ApiResult<String> {
772 Ok(self.password.clone())
773 }
774 async fn on_authentication_success(&self) -> ApiResult<()> {
775 Ok(())
776 }
777 async fn on_authentication_failure(&self) -> ApiResult<()> {
778 Ok(())
779 }
780 }
781
782 fn profile_path() -> PathBuf {
783 std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"))
784 .join("../support/rc_crypto/nss/fixtures/profile")
785 }
786
787 #[test]
788 fn decrypting_logins_with_primary_password() {
789 ensure_initialized_with_profile_dir(profile_path());
790
791 let primary_password_authenticator = MockPrimaryPasswordAuthenticator {
793 password: "password".to_string(),
794 };
795 let key_manager = NSSKeyManager::new(Arc::new(primary_password_authenticator));
796 let encdec = ManagedEncryptorDecryptor::new(Arc::new(key_manager));
797 let store = LoginStore::new(profile_path().join("logins.db"), Arc::new(encdec))
798 .expect("store from fixtures");
799 let list = store.list().expect("Grabbing list to work");
800
801 assert_eq!(list.len(), 1);
802
803 assert_eq!(list[0].origin, "https://www.example.com");
804 assert_eq!(list[0].username, "test");
805 assert_eq!(list[0].password, "test");
806 }
807}