1use crate::db::{LoginDb, LoginsDeletionMetrics};
5use crate::encryption::EncryptorDecryptor;
6use crate::error::*;
7use crate::login::{BulkResultEntry, EncryptedLogin, Login, LoginEntry, LoginEntryWithMeta};
8use crate::LoginsSyncEngine;
9use parking_lot::Mutex;
10use sql_support::run_maintenance;
11use std::path::Path;
12use std::sync::{Arc, Weak};
13use sync15::{
14 engine::{EngineSyncAssociation, SyncEngine, SyncEngineId},
15 ServerTimestamp,
16};
17
18#[derive(uniffi::Enum)]
19pub enum LoginOrErrorMessage {
20 Login,
21 String,
22}
23
24lazy_static::lazy_static! {
26 static ref STORE_FOR_MANAGER: Mutex<Weak<LoginStore>> = Mutex::new(Weak::new());
29}
30
31pub fn get_registered_sync_engine(engine_id: &SyncEngineId) -> Option<Box<dyn SyncEngine>> {
34 let weak = STORE_FOR_MANAGER.lock();
35 match weak.upgrade() {
36 None => None,
37 Some(store) => match create_sync_engine(store, engine_id) {
38 Ok(engine) => Some(engine),
39 Err(e) => {
40 report_error!("logins-sync-engine-create-error", "{e}");
41 None
42 }
43 },
44 }
45}
46
47fn create_sync_engine(
48 store: Arc<LoginStore>,
49 engine_id: &SyncEngineId,
50) -> Result<Box<dyn SyncEngine>> {
51 match engine_id {
52 SyncEngineId::Passwords => Ok(Box::new(LoginsSyncEngine::new(Arc::clone(&store))?)),
53 _ => unreachable!("can't provide unknown engine: {}", engine_id),
56 }
57}
58
59fn map_bulk_result_entry(
60 enc_login: Result<EncryptedLogin>,
61 encdec: &dyn EncryptorDecryptor,
62) -> BulkResultEntry {
63 match enc_login {
64 Ok(enc_login) => match enc_login.decrypt(encdec) {
65 Ok(login) => BulkResultEntry::Success { login },
66 Err(error) => {
67 warn!("Login could not be decrypted. This indicates a fundamental problem with the encryption key.");
68 BulkResultEntry::Error {
69 message: error.to_string(),
70 }
71 }
72 },
73 Err(error) => BulkResultEntry::Error {
74 message: error.to_string(),
75 },
76 }
77}
78
79pub struct LoginStore {
80 pub db: Mutex<Option<LoginDb>>,
81}
82
83impl LoginStore {
84 #[handle_error(Error)]
85 pub fn new(path: impl AsRef<Path>, encdec: Arc<dyn EncryptorDecryptor>) -> ApiResult<Self> {
86 let db = Mutex::new(Some(LoginDb::open(path, encdec)?));
87 Ok(Self { db })
88 }
89
90 pub fn new_from_db(db: LoginDb) -> Self {
91 let db = Mutex::new(Some(db));
92 Self { db }
93 }
94
95 #[cfg(test)]
97 pub fn new_in_memory() -> Self {
98 let db = Mutex::new(Some(LoginDb::open_in_memory()));
99 Self { db }
100 }
101
102 pub fn lock_db(&self) -> Result<parking_lot::MappedMutexGuard<'_, LoginDb>> {
103 parking_lot::MutexGuard::try_map(self.db.lock(), |db| db.as_mut())
104 .map_err(|_| Error::DatabaseClosed)
105 }
106
107 #[handle_error(Error)]
108 pub fn is_empty(&self) -> ApiResult<bool> {
109 Ok(self.lock_db()?.count_all()? == 0)
110 }
111
112 #[handle_error(Error)]
113 pub fn list(&self) -> ApiResult<Vec<Login>> {
114 let db = self.lock_db()?;
115 db.get_all().and_then(|logins| {
116 logins
117 .into_iter()
118 .map(|login| login.decrypt(db.encdec.as_ref()))
119 .collect()
120 })
121 }
122
123 #[handle_error(Error)]
124 pub fn count(&self) -> ApiResult<i64> {
125 self.lock_db()?.count_all()
126 }
127
128 #[handle_error(Error)]
129 pub fn count_by_origin(&self, origin: &str) -> ApiResult<i64> {
130 self.lock_db()?.count_by_origin(origin)
131 }
132
133 #[handle_error(Error)]
134 pub fn count_by_form_action_origin(&self, form_action_origin: &str) -> ApiResult<i64> {
135 self.lock_db()?
136 .count_by_form_action_origin(form_action_origin)
137 }
138
139 #[handle_error(Error)]
140 pub fn get(&self, id: &str) -> ApiResult<Option<Login>> {
141 let db = self.lock_db()?;
142 match db.get_by_id(id) {
143 Ok(result) => match result {
144 Some(enc_login) => enc_login.decrypt(db.encdec.as_ref()).map(Some),
145 None => Ok(None),
146 },
147 Err(err) => Err(err),
148 }
149 }
150
151 #[handle_error(Error)]
152 pub fn get_by_base_domain(&self, base_domain: &str) -> ApiResult<Vec<Login>> {
153 let db = self.lock_db()?;
154 db.get_by_base_domain(base_domain).and_then(|logins| {
155 logins
156 .into_iter()
157 .map(|login| login.decrypt(db.encdec.as_ref()))
158 .collect()
159 })
160 }
161
162 #[handle_error(Error)]
163 pub fn has_logins_by_base_domain(&self, base_domain: &str) -> ApiResult<bool> {
164 self.lock_db()?
165 .get_by_base_domain(base_domain)
166 .map(|logins| !logins.is_empty())
167 }
168
169 #[handle_error(Error)]
170 pub fn find_login_to_update(&self, entry: LoginEntry) -> ApiResult<Option<Login>> {
171 let db = self.lock_db()?;
172 db.find_login_to_update(entry, db.encdec.as_ref())
173 }
174
175 #[handle_error(Error)]
176 pub fn touch(&self, id: &str) -> ApiResult<()> {
177 self.lock_db()?.touch(id)
178 }
179
180 #[handle_error(Error)]
181 pub fn are_potentially_vulnerable_passwords(&self, ids: Vec<String>) -> ApiResult<Vec<String>> {
182 let db = self.lock_db()?;
184 let ids: Vec<&str> = ids.iter().map(|id| &**id).collect();
185 db.are_potentially_vulnerable_passwords(&ids, db.encdec.as_ref())
186 }
187
188 #[handle_error(Error)]
189 pub fn is_potentially_vulnerable_password(&self, id: &str) -> ApiResult<bool> {
190 let db = self.lock_db()?;
191 db.is_potentially_vulnerable_password(id, db.encdec.as_ref())
192 }
193
194 #[handle_error(Error)]
195 pub fn record_potentially_vulnerable_passwords(&self, passwords: Vec<String>) -> ApiResult<()> {
196 let db = self.lock_db()?;
197 db.record_potentially_vulnerable_passwords(passwords, db.encdec.as_ref())
198 }
199
200 #[handle_error(Error)]
201 pub fn reset_all_breaches(&self) -> ApiResult<()> {
202 self.lock_db()?.reset_all_breaches()
203 }
204
205 #[handle_error(Error)]
206 pub fn record_breach_alert_dismissal(&self, id: &str) -> ApiResult<()> {
207 self.lock_db()?.record_breach_alert_dismissal(id)
208 }
209
210 #[handle_error(Error)]
211 pub fn record_breach_alert_dismissal_time(&self, id: &str, timestamp: i64) -> ApiResult<()> {
212 self.lock_db()?
213 .record_breach_alert_dismissal_time(id, timestamp)
214 }
215
216 #[handle_error(Error)]
217 pub fn delete(&self, id: &str) -> ApiResult<bool> {
218 self.lock_db()?.delete(id)
219 }
220
221 #[handle_error(Error)]
222 pub fn delete_many(&self, ids: Vec<String>) -> ApiResult<Vec<bool>> {
223 let ids: Vec<&str> = ids.iter().map(|id| &**id).collect();
226 self.lock_db()?.delete_many(ids)
227 }
228
229 #[handle_error(Error)]
230 pub fn delete_undecryptable_records_for_remote_replacement(
231 self: Arc<Self>,
232 ) -> ApiResult<LoginsDeletionMetrics> {
233 let engine = LoginsSyncEngine::new(Arc::clone(&self))?;
239
240 let db = self.lock_db()?;
241 let deletion_stats =
242 db.delete_undecryptable_records_for_remote_replacement(db.encdec.as_ref())?;
243 engine.set_last_sync(&db, ServerTimestamp(0))?;
244 Ok(deletion_stats)
245 }
246
247 #[handle_error(Error)]
248 pub fn wipe_local(&self) -> ApiResult<()> {
249 self.lock_db()?.wipe_local()?;
250 Ok(())
251 }
252
253 #[handle_error(Error)]
254 pub fn reset(self: Arc<Self>) -> ApiResult<()> {
255 let engine = LoginsSyncEngine::new(Arc::clone(&self))?;
259 engine.do_reset(&EngineSyncAssociation::Disconnected)?;
260 Ok(())
261 }
262
263 #[handle_error(Error)]
264 pub fn update(&self, id: &str, entry: LoginEntry) -> ApiResult<Login> {
265 let db = self.lock_db()?;
266 db.update(id, entry, db.encdec.as_ref())
267 .and_then(|enc_login| enc_login.decrypt(db.encdec.as_ref()))
268 }
269
270 #[handle_error(Error)]
271 pub fn add(&self, entry: LoginEntry) -> ApiResult<Login> {
272 let db = self.lock_db()?;
273 db.add(entry, db.encdec.as_ref())
274 .and_then(|enc_login| enc_login.decrypt(db.encdec.as_ref()))
275 }
276
277 #[handle_error(Error)]
278 pub fn add_many(&self, entries: Vec<LoginEntry>) -> ApiResult<Vec<BulkResultEntry>> {
279 let db = self.lock_db()?;
280 db.add_many(entries, db.encdec.as_ref()).map(|enc_logins| {
281 enc_logins
282 .into_iter()
283 .map(|enc_login| map_bulk_result_entry(enc_login, db.encdec.as_ref()))
284 .collect()
285 })
286 }
287
288 #[handle_error(Error)]
292 pub fn add_with_meta(&self, entry_with_meta: LoginEntryWithMeta) -> ApiResult<Login> {
293 let db = self.lock_db()?;
294 db.add_with_meta(entry_with_meta, db.encdec.as_ref())
295 .and_then(|enc_login| enc_login.decrypt(db.encdec.as_ref()))
296 }
297
298 #[handle_error(Error)]
299 pub fn add_many_with_meta(
300 &self,
301 entries_with_meta: Vec<LoginEntryWithMeta>,
302 ) -> ApiResult<Vec<BulkResultEntry>> {
303 let db = self.lock_db()?;
304 db.add_many_with_meta(entries_with_meta, db.encdec.as_ref())
305 .map(|enc_logins| {
306 enc_logins
307 .into_iter()
308 .map(|enc_login| map_bulk_result_entry(enc_login, db.encdec.as_ref()))
309 .collect()
310 })
311 }
312
313 #[handle_error(Error)]
314 pub fn add_or_update(&self, entry: LoginEntry) -> ApiResult<Login> {
315 let db = self.lock_db()?;
316 db.add_or_update(entry, db.encdec.as_ref())
317 .and_then(|enc_login| enc_login.decrypt(db.encdec.as_ref()))
318 }
319
320 #[handle_error(Error)]
321 pub fn run_maintenance(&self, options: Option<RunMaintenanceOptions>) -> ApiResult<()> {
322 let conn = self.lock_db()?;
323 let options = options.unwrap_or_default();
324 run_maintenance(&conn)?;
325 if options.delete_undecryptable_records_for_remote_replacement {
326 conn.delete_undecryptable_records_for_remote_replacement(conn.encdec.as_ref())?;
327 }
328 Ok(())
329 }
330
331 pub fn shutdown(&self) {
332 if let Some(db) = self.db.lock().take() {
333 let _ = db.shutdown();
334 }
335 }
336
337 pub fn register_with_sync_manager(self: Arc<Self>) {
343 let mut state = STORE_FOR_MANAGER.lock();
344 *state = Arc::downgrade(&self);
345 }
346
347 #[handle_error(Error)]
354 pub fn create_logins_sync_engine(self: Arc<Self>) -> ApiResult<Box<dyn SyncEngine>> {
355 Ok(Box::new(LoginsSyncEngine::new(self)?) as Box<dyn SyncEngine>)
356 }
357}
358
359pub struct RunMaintenanceOptions {
360 pub delete_undecryptable_records_for_remote_replacement: bool,
361}
362
363impl Default for RunMaintenanceOptions {
364 fn default() -> Self {
365 Self {
366 delete_undecryptable_records_for_remote_replacement: true,
367 }
368 }
369}
370
371#[cfg(not(feature = "keydb"))]
372#[cfg(test)]
373mod tests {
374 use super::*;
375 use crate::encryption::test_utils::TEST_ENCDEC;
376 use crate::util;
377 use nss_as::ensure_initialized;
378 use std::cmp::Reverse;
379 use std::time::SystemTime;
380
381 fn assert_logins_equiv(a: &LoginEntry, b: &Login) {
382 assert_eq!(a.origin, b.origin);
383 assert_eq!(a.form_action_origin, b.form_action_origin);
384 assert_eq!(a.http_realm, b.http_realm);
385 assert_eq!(a.username_field, b.username_field);
386 assert_eq!(a.password_field, b.password_field);
387 assert_eq!(b.username, a.username);
388 assert_eq!(b.password, a.password);
389 }
390
391 #[test]
392 fn test_general() {
393 ensure_initialized();
394
395 let store = LoginStore::new_in_memory();
396 let list = store.list().expect("Grabbing Empty list to work");
397 assert_eq!(list.len(), 0);
398 let start_us = util::system_time_ms_i64(SystemTime::now());
399
400 let a = LoginEntry {
401 origin: "https://www.example.com".into(),
402 form_action_origin: Some("https://www.example.com".into()),
403 username_field: "user_input".into(),
404 password_field: "pass_input".into(),
405 username: "coolperson21".into(),
406 password: "p4ssw0rd".into(),
407 ..Default::default()
408 };
409
410 let b = LoginEntry {
411 origin: "https://www.example2.com".into(),
412 http_realm: Some("Some String Here".into()),
413 username: "asdf".into(),
414 password: "fdsa".into(),
415 ..Default::default()
416 };
417 let a_id = store.add(a.clone()).expect("added a").id;
418 let b_id = store.add(b.clone()).expect("added b").id;
419
420 let a_from_db = store
421 .get(&a_id)
422 .expect("Not to error getting a")
423 .expect("a to exist");
424
425 assert_logins_equiv(&a, &a_from_db);
426 assert!(a_from_db.time_created >= start_us);
427 assert!(a_from_db.time_password_changed >= start_us);
428 assert!(a_from_db.time_last_used >= start_us);
429 assert_eq!(a_from_db.times_used, 1);
430
431 let b_from_db = store
432 .get(&b_id)
433 .expect("Not to error getting b")
434 .expect("b to exist");
435
436 assert_logins_equiv(&LoginEntry { ..b.clone() }, &b_from_db);
437 assert!(b_from_db.time_created >= start_us);
438 assert!(b_from_db.time_password_changed >= start_us);
439 assert!(b_from_db.time_last_used >= start_us);
440 assert_eq!(b_from_db.times_used, 1);
441
442 let mut list = store.list().expect("Grabbing list to work");
443 assert_eq!(list.len(), 2);
444
445 let mut expect = vec![a_from_db, b_from_db.clone()];
446
447 list.sort_by_key(|b| Reverse(b.guid()));
448 expect.sort_by_key(|b| Reverse(b.guid()));
449 assert_eq!(list, expect);
450
451 store.delete(&a_id).expect("Successful delete");
452 assert!(store
453 .get(&a_id)
454 .expect("get after delete should still work")
455 .is_none());
456
457 let list = store.list().expect("Grabbing list to work");
458 assert_eq!(list.len(), 1);
459 assert_eq!(list[0], b_from_db);
460
461 let has_logins = store
462 .has_logins_by_base_domain("example2.com")
463 .expect("Expect a result for this origin");
464 assert!(has_logins);
465
466 let list = store
467 .get_by_base_domain("example2.com")
468 .expect("Expect a list for this origin");
469 assert_eq!(list.len(), 1);
470 assert_eq!(list[0], b_from_db);
471
472 let has_logins = store
473 .has_logins_by_base_domain("www.example.com")
474 .expect("Expect a result for this origin");
475 assert!(!has_logins);
476
477 let list = store
478 .get_by_base_domain("www.example.com")
479 .expect("Expect an empty list");
480 assert_eq!(list.len(), 0);
481
482 let now_us = util::system_time_ms_i64(SystemTime::now());
483 let b2 = LoginEntry {
484 username: b.username.to_owned(),
485 password: "newpass".into(),
486 ..b
487 };
488
489 store
490 .update(&b_id, b2.clone())
491 .expect("update b should work");
492
493 let b_after_update = store
494 .get(&b_id)
495 .expect("Not to error getting b")
496 .expect("b to exist");
497
498 assert_logins_equiv(&b2, &b_after_update);
499 assert!(b_after_update.time_created >= start_us);
500 assert!(b_after_update.time_created <= now_us);
501 assert!(b_after_update.time_password_changed >= now_us);
502 assert!(b_after_update.time_last_used >= now_us);
503 assert_eq!(b_after_update.times_used, 2);
505 }
506
507 #[test]
508 fn test_sync_manager_registration() {
509 ensure_initialized();
510 let store = Arc::new(LoginStore::new_in_memory());
511 assert_eq!(Arc::strong_count(&store), 1);
512 assert_eq!(Arc::weak_count(&store), 0);
513 Arc::clone(&store).register_with_sync_manager();
514 assert_eq!(Arc::strong_count(&store), 1);
515 assert_eq!(Arc::weak_count(&store), 1);
516 let registered = STORE_FOR_MANAGER.lock().upgrade().expect("should upgrade");
517 assert!(Arc::ptr_eq(&store, ®istered));
518 drop(registered);
519 assert_eq!(Arc::strong_count(&store), 1);
521 assert_eq!(Arc::weak_count(&store), 1);
522 drop(store);
524 assert!(STORE_FOR_MANAGER.lock().upgrade().is_none());
525 }
526
527 #[test]
528 fn test_wipe_local_on_a_fresh_database_is_a_noop() {
529 ensure_initialized();
530 let db = LoginDb::open_in_memory();
532 db.add_or_update(
533 LoginEntry {
534 origin: "https://www.example.com".into(),
535 form_action_origin: Some("https://www.example.com".into()),
536 username_field: "user_input".into(),
537 password_field: "pass_input".into(),
538 username: "coolperson21".into(),
539 password: "p4ssw0rd".into(),
540 ..Default::default()
541 },
542 &TEST_ENCDEC.clone(),
543 )
544 .unwrap();
545 assert!(db.wipe_local().unwrap() > 0);
546
547 let db = LoginDb::open_in_memory();
549 assert_eq!(db.wipe_local().unwrap(), 0);
550 }
551
552 #[test]
553 fn test_shutdown() {
554 ensure_initialized();
555 let store = LoginStore::new_in_memory();
556 store.shutdown();
557 assert!(matches!(
558 store.list(),
559 Err(LoginsApiError::UnexpectedLoginsApiError { reason: _ })
560 ));
561 assert!(store.db.lock().is_none());
562 }
563
564 #[test]
565 fn test_delete_undecryptable_records_for_remote_replacement() {
566 ensure_initialized();
567 let store = Arc::new(LoginStore::new_in_memory());
568 store
570 .delete_undecryptable_records_for_remote_replacement()
571 .unwrap();
572 }
573}
574
575#[test]
576fn test_send() {
577 fn ensure_send<T: Send>() {}
578 ensure_send::<LoginStore>();
579}
580
581#[cfg(feature = "keydb")]
582#[cfg(test)]
583mod tests_keydb {
584 use super::*;
585 use crate::{ManagedEncryptorDecryptor, NSSKeyManager, PrimaryPasswordAuthenticator};
586 use async_trait::async_trait;
587 use nss_as::ensure_initialized_with_profile_dir;
588 use std::path::PathBuf;
589
590 struct MockPrimaryPasswordAuthenticator {
591 password: String,
592 }
593
594 #[async_trait]
595 impl PrimaryPasswordAuthenticator for MockPrimaryPasswordAuthenticator {
596 async fn get_primary_password(&self) -> ApiResult<String> {
597 Ok(self.password.clone())
598 }
599 async fn on_authentication_success(&self) -> ApiResult<()> {
600 Ok(())
601 }
602 async fn on_authentication_failure(&self) -> ApiResult<()> {
603 Ok(())
604 }
605 }
606
607 fn profile_path() -> PathBuf {
608 std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"))
609 .join("../support/rc_crypto/nss/fixtures/profile")
610 }
611
612 #[test]
613 fn decrypting_logins_with_primary_password() {
614 ensure_initialized_with_profile_dir(profile_path());
615
616 let primary_password_authenticator = MockPrimaryPasswordAuthenticator {
618 password: "password".to_string(),
619 };
620 let key_manager = NSSKeyManager::new(Arc::new(primary_password_authenticator));
621 let encdec = ManagedEncryptorDecryptor::new(Arc::new(key_manager));
622 let store = LoginStore::new(profile_path().join("logins.db"), Arc::new(encdec))
623 .expect("store from fixtures");
624 let list = store.list().expect("Grabbing list to work");
625
626 assert_eq!(list.len(), 1);
627
628 assert_eq!(list[0].origin, "https://www.example.com");
629 assert_eq!(list[0].username, "test");
630 assert_eq!(list[0].password, "test");
631 }
632}