use crate::db::{LoginDb, LoginStore, MigrationMetrics};
use crate::error::*;
use crate::login::Login;
use std::cell::Cell;
use std::path::Path;
use sync15::{
sync_multiple, telemetry, KeyBundle, MemoryCachedState, StoreSyncAssociation,
Sync15StorageClientInit,
};
pub struct PasswordEngine {
pub db: LoginDb,
pub mem_cached_state: Cell<MemoryCachedState>,
}
impl PasswordEngine {
pub fn new(path: impl AsRef<Path>, encryption_key: Option<&str>) -> Result<Self> {
let db = LoginDb::open(path, encryption_key)?;
Ok(Self {
db,
mem_cached_state: Cell::default(),
})
}
pub fn new_with_salt(path: impl AsRef<Path>, encryption_key: &str, salt: &str) -> Result<Self> {
let db = LoginDb::open_with_salt(path, encryption_key, salt)?;
Ok(Self {
db,
mem_cached_state: Cell::default(),
})
}
pub fn new_in_memory(encryption_key: Option<&str>) -> Result<Self> {
let db = LoginDb::open_in_memory(encryption_key)?;
Ok(Self {
db,
mem_cached_state: Cell::default(),
})
}
pub fn list(&self) -> Result<Vec<Login>> {
self.db.get_all()
}
pub fn get(&self, id: &str) -> Result<Option<Login>> {
self.db.get_by_id(id)
}
pub fn get_by_base_domain(&self, base_domain: &str) -> Result<Vec<Login>> {
self.db.get_by_base_domain(base_domain)
}
pub fn potential_dupes_ignoring_username(&self, login: Login) -> Result<Vec<Login>> {
self.db.potential_dupes_ignoring_username(&login)
}
pub fn touch(&self, id: &str) -> Result<()> {
self.db.touch(id)
}
pub fn delete(&self, id: &str) -> Result<bool> {
self.db.delete(id)
}
pub fn wipe(&self) -> Result<()> {
let scope = self.db.begin_interrupt_scope();
self.db.wipe(&scope)?;
Ok(())
}
pub fn wipe_local(&self) -> Result<()> {
self.db.wipe_local()?;
Ok(())
}
pub fn reset(&self) -> Result<()> {
self.db.reset(&StoreSyncAssociation::Disconnected)?;
Ok(())
}
pub fn update(&self, login: Login) -> Result<()> {
self.db.update(login)
}
pub fn add(&self, login: Login) -> Result<String> {
self.db.add(login).map(|record| record.guid.into_string())
}
pub fn import_multiple(&self, logins: &[Login]) -> Result<MigrationMetrics> {
self.db.import_multiple(logins)
}
pub fn disable_mem_security(&self) -> Result<()> {
self.db.disable_mem_security()
}
pub fn rekey_database(&self, new_encryption_key: &str) -> Result<()> {
self.db.rekey_database(new_encryption_key)
}
pub fn conn(&self) -> &rusqlite::Connection {
&self.db.db
}
pub fn new_interrupt_handle(&self) -> sql_support::SqlInterruptHandle {
self.db.new_interrupt_handle()
}
pub fn sync(
&self,
storage_init: &Sync15StorageClientInit,
root_sync_key: &KeyBundle,
) -> Result<telemetry::SyncTelemetryPing> {
self.db.migrate_global_state()?;
let mut disk_cached_state = self.db.get_global_state()?;
let mut mem_cached_state = self.mem_cached_state.take();
let store = LoginStore::new(&self.db);
let mut result = sync_multiple(
&[&store],
&mut disk_cached_state,
&mut mem_cached_state,
storage_init,
root_sync_key,
&store.scope,
None,
);
self.db.set_global_state(&disk_cached_state)?;
if let Err(e) = result.result {
return Err(e.into());
}
match result.engine_results.remove("passwords") {
None | Some(Ok(())) => Ok(result.telemetry),
Some(Err(e)) => Err(e.into()),
}
}
pub fn check_valid_with_no_dupes(&self, login: &Login) -> Result<()> {
self.db.check_valid_with_no_dupes(login)
}
}
#[cfg(test)]
mod test {
use super::*;
use crate::util;
use more_asserts::*;
use std::time::SystemTime;
use sync_guid::Guid;
fn assert_logins_equiv(a: &Login, b: &Login) {
assert_eq!(b.guid, a.guid);
assert_eq!(b.hostname, a.hostname);
assert_eq!(b.form_submit_url, a.form_submit_url);
assert_eq!(b.http_realm, a.http_realm);
assert_eq!(b.username, a.username);
assert_eq!(b.password, a.password);
assert_eq!(b.username_field, a.username_field);
assert_eq!(b.password_field, a.password_field);
}
#[test]
fn test_general() {
let engine = PasswordEngine::new_in_memory(Some("secret")).unwrap();
let list = engine.list().expect("Grabbing Empty list to work");
assert_eq!(list.len(), 0);
let start_us = util::system_time_ms_i64(SystemTime::now());
let a = Login {
guid: "aaaaaaaaaaaa".into(),
hostname: "https://www.example.com".into(),
form_submit_url: Some("https://www.example.com".into()),
username: "coolperson21".into(),
password: "p4ssw0rd".into(),
username_field: "user_input".into(),
password_field: "pass_input".into(),
..Login::default()
};
let b = Login {
hostname: "https://www.example2.com".into(),
http_realm: Some("Some String Here".into()),
username: "asdf".into(),
password: "fdsa".into(),
..Login::default()
};
let a_id = engine.add(a.clone()).expect("added a");
let b_id = engine.add(b.clone()).expect("added b");
assert_eq!(a_id, a.guid);
assert_ne!(b_id, b.guid, "Should generate guid when none provided");
let a_from_db = engine
.get(&a_id)
.expect("Not to error getting a")
.expect("a to exist");
assert_logins_equiv(&a, &a_from_db);
assert_ge!(a_from_db.time_created, start_us);
assert_ge!(a_from_db.time_password_changed, start_us);
assert_ge!(a_from_db.time_last_used, start_us);
assert_eq!(a_from_db.times_used, 1);
let b_from_db = engine
.get(&b_id)
.expect("Not to error getting b")
.expect("b to exist");
assert_logins_equiv(
&b_from_db,
&Login {
guid: Guid::from(b_id.as_str()),
..b.clone()
},
);
assert_ge!(b_from_db.time_created, start_us);
assert_ge!(b_from_db.time_password_changed, start_us);
assert_ge!(b_from_db.time_last_used, start_us);
assert_eq!(b_from_db.times_used, 1);
let mut list = engine.list().expect("Grabbing list to work");
assert_eq!(list.len(), 2);
let mut expect = vec![a_from_db, b_from_db.clone()];
list.sort_by(|a, b| b.guid.cmp(&a.guid));
expect.sort_by(|a, b| b.guid.cmp(&a.guid));
assert_eq!(list, expect);
engine.delete(&a_id).expect("Successful delete");
assert!(engine
.get(&a_id)
.expect("get after delete should still work")
.is_none());
let list = engine.list().expect("Grabbing list to work");
assert_eq!(list.len(), 1);
assert_eq!(list[0], b_from_db);
let list = engine
.get_by_base_domain("example2.com")
.expect("Expect a list for this hostname");
assert_eq!(list.len(), 1);
assert_eq!(list[0], b_from_db);
let list = engine
.get_by_base_domain("www.example.com")
.expect("Expect an empty list");
assert_eq!(list.len(), 0);
let now_us = util::system_time_ms_i64(SystemTime::now());
let b2 = Login {
password: "newpass".into(),
guid: Guid::from(b_id.as_str()),
..b
};
engine.update(b2.clone()).expect("update b should work");
let b_after_update = engine
.get(&b_id)
.expect("Not to error getting b")
.expect("b to exist");
assert_logins_equiv(&b_after_update, &b2);
assert_ge!(b_after_update.time_created, start_us);
assert_le!(b_after_update.time_created, now_us);
assert_ge!(b_after_update.time_password_changed, now_us);
assert_ge!(b_after_update.time_last_used, now_us);
assert_eq!(b_after_update.times_used, 2);
}
#[test]
fn test_rekey() {
let engine = PasswordEngine::new_in_memory(Some("secret")).unwrap();
engine.rekey_database("new_encryption_key").unwrap();
let list = engine.list().expect("Grabbing Empty list to work");
assert_eq!(list.len(), 0);
}
}
#[test]
fn test_send() {
fn ensure_send<T: Send>() {}
ensure_send::<PasswordEngine>();
}