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)
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)
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)
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)
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_all(&self) -> ApiResult<Vec<String>> {
231 self.lock_db()?.delete_all()
232 }
233
234 #[handle_error(Error)]
235 pub fn delete_all_except_fxa(&self) -> ApiResult<Vec<String>> {
236 self.lock_db()?.delete_all_except_fxa()
237 }
238
239 #[handle_error(Error)]
240 pub fn delete_undecryptable_records_for_remote_replacement(
241 self: Arc<Self>,
242 ) -> ApiResult<LoginsDeletionMetrics> {
243 let engine = LoginsSyncEngine::new(Arc::clone(&self))?;
249
250 let db = self.lock_db()?;
251 let deletion_stats = db.delete_undecryptable_records_for_remote_replacement()?;
252 engine.set_last_sync(&db, ServerTimestamp(0))?;
253 Ok(deletion_stats)
254 }
255
256 #[handle_error(Error)]
257 pub fn wipe_local(&self) -> ApiResult<()> {
258 self.lock_db()?.wipe_local()?;
259 Ok(())
260 }
261
262 #[handle_error(Error)]
263 pub fn wipe_local_except_fxa(&self) -> ApiResult<()> {
264 self.lock_db()?.wipe_local_except_fxa()?;
265 Ok(())
266 }
267
268 #[handle_error(Error)]
269 pub fn reset(self: Arc<Self>) -> ApiResult<()> {
270 let engine = LoginsSyncEngine::new(Arc::clone(&self))?;
274 engine.do_reset(&EngineSyncAssociation::Disconnected)?;
275 Ok(())
276 }
277
278 #[handle_error(Error)]
279 pub fn update(&self, id: &str, entry: LoginEntry) -> ApiResult<Login> {
280 let db = self.lock_db()?;
281 db.update(id, entry)
282 .and_then(|enc_login| enc_login.decrypt(db.encdec.as_ref()))
283 }
284
285 #[handle_error(Error)]
286 pub fn add(&self, entry: LoginEntry) -> ApiResult<Login> {
287 let db = self.lock_db()?;
288 db.add(entry)
289 .and_then(|enc_login| enc_login.decrypt(db.encdec.as_ref()))
290 }
291
292 #[handle_error(Error)]
293 pub fn add_many(&self, entries: Vec<LoginEntry>) -> ApiResult<Vec<BulkResultEntry>> {
294 let db = self.lock_db()?;
295 let encdec = db.encdec.as_ref();
296 db.add_many(entries).map(|enc_logins| {
297 enc_logins
298 .into_iter()
299 .map(|enc_login| map_bulk_result_entry(enc_login, encdec))
300 .collect()
301 })
302 }
303
304 #[handle_error(Error)]
308 pub fn add_with_meta(&self, entry_with_meta: LoginEntryWithMeta) -> ApiResult<Login> {
309 let db = self.lock_db()?;
310 db.add_with_meta(entry_with_meta)
311 .and_then(|enc_login| enc_login.decrypt(db.encdec.as_ref()))
312 }
313
314 #[handle_error(Error)]
315 pub fn add_many_with_meta(
316 &self,
317 entries_with_meta: Vec<LoginEntryWithMeta>,
318 ) -> ApiResult<Vec<BulkResultEntry>> {
319 let db = self.lock_db()?;
320 let encdec = db.encdec.as_ref();
321 db.add_many_with_meta(entries_with_meta).map(|enc_logins| {
322 enc_logins
323 .into_iter()
324 .map(|enc_login| map_bulk_result_entry(enc_login, encdec))
325 .collect()
326 })
327 }
328
329 #[handle_error(Error)]
330 pub fn add_or_update(&self, entry: LoginEntry) -> ApiResult<Login> {
331 let db = self.lock_db()?;
332 db.add_or_update(entry)
333 .and_then(|enc_login| enc_login.decrypt(db.encdec.as_ref()))
334 }
335
336 #[handle_error(Error)]
337 pub fn run_maintenance(&self, options: Option<RunMaintenanceOptions>) -> ApiResult<()> {
338 let conn = self.lock_db()?;
339 let options = options.unwrap_or_default();
340 run_maintenance(&conn)?;
341 if options.delete_undecryptable_records_for_remote_replacement {
342 conn.delete_undecryptable_records_for_remote_replacement()?;
343 }
344 Ok(())
345 }
346
347 pub fn shutdown(&self) {
348 if let Some(db) = self.db.lock().take() {
349 let _ = db.shutdown();
350 }
351 }
352
353 pub fn register_with_sync_manager(self: Arc<Self>) {
359 let mut state = STORE_FOR_MANAGER.lock();
360 *state = Arc::downgrade(&self);
361 }
362
363 #[handle_error(Error)]
370 pub fn create_logins_sync_engine(self: Arc<Self>) -> ApiResult<Box<dyn SyncEngine>> {
371 Ok(Box::new(LoginsSyncEngine::new(self)?) as Box<dyn SyncEngine>)
372 }
373}
374
375pub struct RunMaintenanceOptions {
376 pub delete_undecryptable_records_for_remote_replacement: bool,
377}
378
379impl Default for RunMaintenanceOptions {
380 fn default() -> Self {
381 Self {
382 delete_undecryptable_records_for_remote_replacement: true,
383 }
384 }
385}
386
387#[cfg(not(feature = "keydb"))]
388#[cfg(test)]
389mod tests {
390 use super::*;
391 use crate::util;
392 use nss_as::ensure_initialized;
393 use std::cmp::Reverse;
394 use std::time::SystemTime;
395
396 fn assert_logins_equiv(a: &LoginEntry, b: &Login) {
397 assert_eq!(a.origin, b.origin);
398 assert_eq!(a.form_action_origin, b.form_action_origin);
399 assert_eq!(a.http_realm, b.http_realm);
400 assert_eq!(a.username_field, b.username_field);
401 assert_eq!(a.password_field, b.password_field);
402 assert_eq!(b.username, a.username);
403 assert_eq!(b.password, a.password);
404 }
405
406 #[test]
407 fn test_general() {
408 ensure_initialized();
409
410 let store = LoginStore::new_in_memory();
411 let list = store.list().expect("Grabbing Empty list to work");
412 assert_eq!(list.len(), 0);
413 let start_us = util::system_time_ms_i64(SystemTime::now());
414
415 let a = LoginEntry {
416 origin: "https://www.example.com".into(),
417 form_action_origin: Some("https://www.example.com".into()),
418 username_field: "user_input".into(),
419 password_field: "pass_input".into(),
420 username: "coolperson21".into(),
421 password: "p4ssw0rd".into(),
422 ..Default::default()
423 };
424
425 let b = LoginEntry {
426 origin: "https://www.example2.com".into(),
427 http_realm: Some("Some String Here".into()),
428 username: "asdf".into(),
429 password: "fdsa".into(),
430 ..Default::default()
431 };
432 let a_id = store.add(a.clone()).expect("added a").id;
433 let b_id = store.add(b.clone()).expect("added b").id;
434
435 let a_from_db = store
436 .get(&a_id)
437 .expect("Not to error getting a")
438 .expect("a to exist");
439
440 assert_logins_equiv(&a, &a_from_db);
441 assert!(a_from_db.time_created >= start_us);
442 assert!(a_from_db.time_password_changed >= start_us);
443 assert!(a_from_db.time_last_used >= start_us);
444 assert_eq!(a_from_db.times_used, 1);
445
446 let b_from_db = store
447 .get(&b_id)
448 .expect("Not to error getting b")
449 .expect("b to exist");
450
451 assert_logins_equiv(&LoginEntry { ..b.clone() }, &b_from_db);
452 assert!(b_from_db.time_created >= start_us);
453 assert!(b_from_db.time_password_changed >= start_us);
454 assert!(b_from_db.time_last_used >= start_us);
455 assert_eq!(b_from_db.times_used, 1);
456
457 let mut list = store.list().expect("Grabbing list to work");
458 assert_eq!(list.len(), 2);
459
460 let mut expect = vec![a_from_db, b_from_db.clone()];
461
462 list.sort_by_key(|b| Reverse(b.guid()));
463 expect.sort_by_key(|b| Reverse(b.guid()));
464 assert_eq!(list, expect);
465
466 store.delete(&a_id).expect("Successful delete");
467 assert!(store
468 .get(&a_id)
469 .expect("get after delete should still work")
470 .is_none());
471
472 let list = store.list().expect("Grabbing list to work");
473 assert_eq!(list.len(), 1);
474 assert_eq!(list[0], b_from_db);
475
476 let has_logins = store
477 .has_logins_by_base_domain("example2.com")
478 .expect("Expect a result for this origin");
479 assert!(has_logins);
480
481 let list = store
482 .get_by_base_domain("example2.com")
483 .expect("Expect a list for this origin");
484 assert_eq!(list.len(), 1);
485 assert_eq!(list[0], b_from_db);
486
487 let has_logins = store
488 .has_logins_by_base_domain("www.example.com")
489 .expect("Expect a result for this origin");
490 assert!(!has_logins);
491
492 let list = store
493 .get_by_base_domain("www.example.com")
494 .expect("Expect an empty list");
495 assert_eq!(list.len(), 0);
496
497 let now_us = util::system_time_ms_i64(SystemTime::now());
498 let b2 = LoginEntry {
499 username: b.username.to_owned(),
500 password: "newpass".into(),
501 ..b
502 };
503
504 store
505 .update(&b_id, b2.clone())
506 .expect("update b should work");
507
508 let b_after_update = store
509 .get(&b_id)
510 .expect("Not to error getting b")
511 .expect("b to exist");
512
513 assert_logins_equiv(&b2, &b_after_update);
514 assert!(b_after_update.time_created >= start_us);
515 assert!(b_after_update.time_created <= now_us);
516 assert!(b_after_update.time_password_changed >= now_us);
517 assert_eq!(b_after_update.time_last_used, b_from_db.time_last_used);
519 assert_eq!(b_after_update.times_used, 1);
520 }
521
522 #[test]
523 fn test_sync_manager_registration() {
524 ensure_initialized();
525 let store = Arc::new(LoginStore::new_in_memory());
526 assert_eq!(Arc::strong_count(&store), 1);
527 assert_eq!(Arc::weak_count(&store), 0);
528 Arc::clone(&store).register_with_sync_manager();
529 assert_eq!(Arc::strong_count(&store), 1);
530 assert_eq!(Arc::weak_count(&store), 1);
531 let registered = STORE_FOR_MANAGER.lock().upgrade().expect("should upgrade");
532 assert!(Arc::ptr_eq(&store, ®istered));
533 drop(registered);
534 assert_eq!(Arc::strong_count(&store), 1);
536 assert_eq!(Arc::weak_count(&store), 1);
537 drop(store);
539 assert!(STORE_FOR_MANAGER.lock().upgrade().is_none());
540 }
541
542 #[test]
543 fn test_wipe_local_on_a_fresh_database_is_a_noop() {
544 ensure_initialized();
545 let db = LoginDb::open_in_memory();
547 db.add_or_update(LoginEntry {
548 origin: "https://www.example.com".into(),
549 form_action_origin: Some("https://www.example.com".into()),
550 username_field: "user_input".into(),
551 password_field: "pass_input".into(),
552 username: "coolperson21".into(),
553 password: "p4ssw0rd".into(),
554 ..Default::default()
555 })
556 .unwrap();
557 assert!(db.wipe_local().unwrap() > 0);
558
559 let db = LoginDb::open_in_memory();
561 assert_eq!(db.wipe_local().unwrap(), 0);
562 }
563
564 #[test]
565 fn test_shutdown() {
566 ensure_initialized();
567 let store = LoginStore::new_in_memory();
568 store.shutdown();
569 assert!(matches!(
570 store.list(),
571 Err(LoginsApiError::UnexpectedLoginsApiError { reason: _ })
572 ));
573 assert!(store.db.lock().is_none());
574 }
575
576 #[test]
577 fn test_delete_undecryptable_records_for_remote_replacement() {
578 ensure_initialized();
579 let store = Arc::new(LoginStore::new_in_memory());
580 store
582 .delete_undecryptable_records_for_remote_replacement()
583 .unwrap();
584 }
585}
586
587#[test]
588fn test_send() {
589 fn ensure_send<T: Send>() {}
590 ensure_send::<LoginStore>();
591}
592
593#[cfg(feature = "keydb")]
594#[cfg(test)]
595mod tests_keydb {
596 use super::*;
597 use crate::{ManagedEncryptorDecryptor, NSSKeyManager, PrimaryPasswordAuthenticator};
598 use async_trait::async_trait;
599 use nss_as::ensure_initialized_with_profile_dir;
600 use std::path::PathBuf;
601
602 struct MockPrimaryPasswordAuthenticator {
603 password: String,
604 }
605
606 #[async_trait]
607 impl PrimaryPasswordAuthenticator for MockPrimaryPasswordAuthenticator {
608 async fn get_primary_password(&self) -> ApiResult<String> {
609 Ok(self.password.clone())
610 }
611 async fn on_authentication_success(&self) -> ApiResult<()> {
612 Ok(())
613 }
614 async fn on_authentication_failure(&self) -> ApiResult<()> {
615 Ok(())
616 }
617 }
618
619 fn profile_path() -> PathBuf {
620 std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"))
621 .join("../support/rc_crypto/nss/fixtures/profile")
622 }
623
624 #[test]
625 fn decrypting_logins_with_primary_password() {
626 ensure_initialized_with_profile_dir(profile_path());
627
628 let primary_password_authenticator = MockPrimaryPasswordAuthenticator {
630 password: "password".to_string(),
631 };
632 let key_manager = NSSKeyManager::new(Arc::new(primary_password_authenticator));
633 let encdec = ManagedEncryptorDecryptor::new(Arc::new(key_manager));
634 let store = LoginStore::new(profile_path().join("logins.db"), Arc::new(encdec))
635 .expect("store from fixtures");
636 let list = store.list().expect("Grabbing list to work");
637
638 assert_eq!(list.len(), 1);
639
640 assert_eq!(list[0].origin, "https://www.example.com");
641 assert_eq!(list[0].username, "test");
642 assert_eq!(list[0].password, "test");
643 }
644}