Keep iPhone TOTP cache across installs (#78)
This commit is contained in:
@@ -348,7 +348,7 @@ impl<'a> MobileTotpService<'a> {
|
|||||||
progress.total = u32::try_from(initial_inventory.len()).unwrap_or(u32::MAX);
|
progress.total = u32::try_from(initial_inventory.len()).unwrap_or(u32::MAX);
|
||||||
});
|
});
|
||||||
|
|
||||||
let store = store_identity(self.repository.root_path());
|
let store = store_identity(self.repository.root_path(), cache_path);
|
||||||
let (cached, mut cache_notice) = load_cache(cache_path, &store);
|
let (cached, mut cache_notice) = load_cache(cache_path, &store);
|
||||||
let mut records = BTreeMap::new();
|
let mut records = BTreeMap::new();
|
||||||
let mut rows = Vec::new();
|
let mut rows = Vec::new();
|
||||||
@@ -424,7 +424,7 @@ impl<'a> MobileTotpService<'a> {
|
|||||||
shared: &BTreeSet<EntryPath>,
|
shared: &BTreeSet<EntryPath>,
|
||||||
cache_path: &Path,
|
cache_path: &Path,
|
||||||
) -> Option<MobileTotpPage> {
|
) -> Option<MobileTotpPage> {
|
||||||
let store = store_identity(self.repository.root_path());
|
let store = store_identity(self.repository.root_path(), cache_path);
|
||||||
let (catalog, _) = load_cache(cache_path, &store);
|
let (catalog, _) = load_cache(cache_path, &store);
|
||||||
let mut rows: Vec<_> = catalog?
|
let mut rows: Vec<_> = catalog?
|
||||||
.entries
|
.entries
|
||||||
@@ -460,7 +460,7 @@ impl<'a> MobileTotpService<'a> {
|
|||||||
ciphertext_hash: digest(ciphertext.as_bytes()),
|
ciphertext_hash: digest(ciphertext.as_bytes()),
|
||||||
is_totp: uri.is_some(),
|
is_totp: uri.is_some(),
|
||||||
};
|
};
|
||||||
let store = store_identity(self.repository.root_path());
|
let store = store_identity(self.repository.root_path(), cache_path);
|
||||||
let cache_notice =
|
let cache_notice =
|
||||||
upsert_cache_record(cache_path, &store, document.path().to_string(), record);
|
upsert_cache_record(cache_path, &store, document.path().to_string(), record);
|
||||||
let detail = match (uri, unix_seconds) {
|
let detail = match (uri, unix_seconds) {
|
||||||
@@ -667,8 +667,15 @@ fn set_private_permissions(_temporary: &TempFile<'_>) -> Result<(), MobileTotpCa
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn store_identity(root: &Path) -> String {
|
fn store_identity(root: &Path, cache_path: &Path) -> String {
|
||||||
digest(root.to_string_lossy().as_bytes())
|
let cache_parent = cache_path
|
||||||
|
.parent()
|
||||||
|
.and_then(|parent| fs::canonicalize(parent).ok());
|
||||||
|
let stable_root = cache_parent
|
||||||
|
.as_deref()
|
||||||
|
.and_then(|parent| root.strip_prefix(parent).ok())
|
||||||
|
.unwrap_or(root);
|
||||||
|
digest(stable_root.to_string_lossy().as_bytes())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn digest(bytes: &[u8]) -> String {
|
fn digest(bytes: &[u8]) -> String {
|
||||||
|
|||||||
@@ -306,6 +306,47 @@ fn totp_cache_reuses_ciphertext_hashes_and_removes_deleted_entries() -> TestResu
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn totp_cache_survives_application_container_relocation() -> TestResult {
|
||||||
|
let fixture = FixtureSet::load()?;
|
||||||
|
let application = tempfile::tempdir()?;
|
||||||
|
let installed = application.path().join("installed");
|
||||||
|
fs::create_dir(&installed)?;
|
||||||
|
let vault = installed.join("vault");
|
||||||
|
fs::rename(fixture.materialize_store("basic")?.keep(), &vault)?;
|
||||||
|
let cache = installed.join("totp-catalog.toml");
|
||||||
|
let keys = KeyStore::load(fixture.path("keys"))?;
|
||||||
|
let expected = {
|
||||||
|
let repository = Repository::open(&vault)?;
|
||||||
|
let service = MobileTotpService::new(&repository, &keys);
|
||||||
|
let operation = MobileTotpOperation::default();
|
||||||
|
let mut secrets = CountingSecrets::all(&fixture);
|
||||||
|
service.discover(&BTreeSet::new(), &mut secrets, &cache, &operation)?
|
||||||
|
};
|
||||||
|
|
||||||
|
let updated = application.path().join("updated");
|
||||||
|
fs::rename(&installed, &updated)?;
|
||||||
|
let repository = Repository::open(updated.join("vault"))?;
|
||||||
|
let service = MobileTotpService::new(&repository, &keys);
|
||||||
|
let relocated_cache = updated.join("totp-catalog.toml");
|
||||||
|
assert_eq!(
|
||||||
|
service
|
||||||
|
.cached_page(&BTreeSet::new(), &relocated_cache)
|
||||||
|
.expect("relocated cache remains valid")
|
||||||
|
.rows(),
|
||||||
|
expected.rows()
|
||||||
|
);
|
||||||
|
let operation = MobileTotpOperation::default();
|
||||||
|
let mut secrets = CountingSecrets::all(&fixture);
|
||||||
|
service.discover(&BTreeSet::new(), &mut secrets, &relocated_cache, &operation)?;
|
||||||
|
assert_eq!(secrets.requests, 0);
|
||||||
|
assert_eq!(
|
||||||
|
operation.progress().cache_hits(),
|
||||||
|
operation.progress().total()
|
||||||
|
);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn decrypted_entry_details_reconcile_totp_cache_without_persisting_secrets() -> TestResult {
|
fn decrypted_entry_details_reconcile_totp_cache_without_persisting_secrets() -> TestResult {
|
||||||
let fixture = FixtureSet::load()?;
|
let fixture = FixtureSet::load()?;
|
||||||
|
|||||||
Reference in New Issue
Block a user