Implement Android TOTP cached discovery and search (#110)
This commit is contained in:
1 parent
0c8366d34f
commit
f4a6edbc68
18 files changed
+1070
-165
No files matched your search
@@ -228,6 +228,14 @@ fn totp_cache_reuses_ciphertext_hashes_and_removes_deleted_entries() -> TestResu
|
||||
assert_eq!(warm_secrets.requests, 0);
|
||||
assert_eq!(warm.progress().cache_hits(), warm.progress().total());
|
||||
assert_eq!(warm_page.rows(), page.rows());
|
||||
let locked = MobileTotpOperation::default();
|
||||
assert_eq!(service.refresh_cached(&cache, &locked)?.rows(), page.rows());
|
||||
assert_eq!(locked.progress().cache_hits(), locked.progress().total());
|
||||
locked.cancel();
|
||||
assert_eq!(
|
||||
locked.progress().phase(),
|
||||
MobileTotpDiscoveryPhase::Complete
|
||||
);
|
||||
assert_eq!(service.cache_status(&cache).0 as usize, page.rows().len());
|
||||
let rebuild = MobileTotpOperation::default();
|
||||
let mut rebuild_secrets = CountingSecrets::all(&fixture);
|
||||
@@ -269,6 +277,17 @@ fn totp_cache_reuses_ciphertext_hashes_and_removes_deleted_entries() -> TestResu
|
||||
"ordinary",
|
||||
b"otpauth://totp/New:new@example.com?secret=GEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQ&issuer=New\n",
|
||||
)?;
|
||||
let preserved = fs::read(&cache)?;
|
||||
let requires_authentication = MobileTotpOperation::default();
|
||||
assert!(matches!(
|
||||
service.refresh_cached(&cache, &requires_authentication),
|
||||
Err(MobileTotpError::AuthenticationRequired)
|
||||
));
|
||||
assert_eq!(
|
||||
requires_authentication.progress().phase(),
|
||||
MobileTotpDiscoveryPhase::Preparing
|
||||
);
|
||||
assert_eq!(fs::read(&cache)?, preserved);
|
||||
let changed = MobileTotpOperation::default();
|
||||
let mut changed_secrets = CountingSecrets::all(&fixture);
|
||||
let changed_page =
|
||||
@@ -327,6 +346,10 @@ fn totp_cache_reuses_ciphertext_hashes_and_removes_deleted_entries() -> TestResu
|
||||
|
||||
let cancelled = MobileTotpOperation::default();
|
||||
cancelled.cancel();
|
||||
assert!(matches!(
|
||||
service.search_cached_page_cancellable(&BTreeSet::new(), &cache, "team", &cancelled),
|
||||
Err(MobileTotpError::Otp(OtpError::Cancelled))
|
||||
));
|
||||
let mut cancelled_secrets = CountingSecrets::all(&fixture);
|
||||
assert!(matches!(
|
||||
service.discover(&BTreeSet::new(), &mut cancelled_secrets, &cache, &cancelled,),
|
||||
@@ -490,6 +513,99 @@ fn cancelled_discovery_checkpoints_completed_entries() -> TestResult {
|
||||
service.discover(&BTreeSet::new(), &mut resumed_secrets, &cache, &resumed)?;
|
||||
assert!(resumed.progress().cache_hits() >= 1);
|
||||
assert!(resumed_secrets.requests < resumed.progress().total() as usize);
|
||||
let deleted = repository
|
||||
.snapshot()?
|
||||
.entries()
|
||||
.next()
|
||||
.unwrap()
|
||||
.path()
|
||||
.clone();
|
||||
repository.remove_entry(&deleted)?;
|
||||
write_plaintext(
|
||||
&repository,
|
||||
&keys,
|
||||
"new-checkpoint",
|
||||
b"otpauth://totp/Test?secret=GEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQ\n",
|
||||
)?;
|
||||
let interrupted = MobileTotpOperation::default();
|
||||
let mut secrets = CancellingSecrets {
|
||||
inner: FixtureSecrets::all(&fixture),
|
||||
operation: &interrupted,
|
||||
requests: 0,
|
||||
};
|
||||
assert!(matches!(
|
||||
service.discover(&BTreeSet::new(), &mut secrets, &cache, &interrupted),
|
||||
Err(MobileTotpError::Otp(OtpError::Cancelled))
|
||||
));
|
||||
let checkpoint: toml::Value = toml::from_str(&fs::read_to_string(&cache)?)?;
|
||||
assert!(checkpoint["entries"].get(deleted.to_string()).is_some());
|
||||
service.discover(
|
||||
&BTreeSet::new(),
|
||||
&mut FixtureSecrets::all(&fixture),
|
||||
&cache,
|
||||
&MobileTotpOperation::default(),
|
||||
)?;
|
||||
let completed: toml::Value = toml::from_str(&fs::read_to_string(&cache)?)?;
|
||||
assert!(completed["entries"].get(deleted.to_string()).is_none());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn large_cached_search_filters_paths_without_reading_secrets() -> TestResult {
|
||||
let fixture = FixtureSet::load()?;
|
||||
let store = fixture.materialize_store("basic")?;
|
||||
let repository = Repository::open(store.path())?;
|
||||
let keys = KeyStore::load(fixture.path("keys"))?;
|
||||
write_plaintext(
|
||||
&repository,
|
||||
&keys,
|
||||
"seed",
|
||||
b"otpauth://totp/Test?secret=GEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQ\n",
|
||||
)?;
|
||||
let directory = tempfile::tempdir()?;
|
||||
let cache = directory.path().join("totp-catalog.toml");
|
||||
let service = MobileTotpService::new(&repository, &keys);
|
||||
service.discover(
|
||||
&BTreeSet::new(),
|
||||
&mut FixtureSecrets::all(&fixture),
|
||||
&cache,
|
||||
&MobileTotpOperation::default(),
|
||||
)?;
|
||||
let ciphertext = repository.read_entry(&EntryPath::parse("seed")?)?;
|
||||
let mut catalog: toml::Value = toml::from_str(&fs::read_to_string(&cache)?)?;
|
||||
let entries = catalog["entries"].as_table_mut().unwrap();
|
||||
let record = entries["seed"].clone();
|
||||
for index in 0..1_000 {
|
||||
let path = format!("large/entry-{index:04}");
|
||||
repository.write_entry(&EntryPath::parse(&path)?, &ciphertext)?;
|
||||
entries.insert(path, record.clone());
|
||||
}
|
||||
fs::write(&cache, toml::to_string(&catalog)?)?;
|
||||
let result = service
|
||||
.search_cached_page_cancellable(
|
||||
&BTreeSet::new(),
|
||||
&cache,
|
||||
" ENTRY-0999 ",
|
||||
&MobileTotpOperation::default(),
|
||||
)?
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
result
|
||||
.rows()
|
||||
.iter()
|
||||
.map(|row| row.path())
|
||||
.collect::<Vec<_>>(),
|
||||
["large/entry-0999"]
|
||||
);
|
||||
let all = service
|
||||
.search_cached_page_cancellable(
|
||||
&BTreeSet::new(),
|
||||
&cache,
|
||||
"large/",
|
||||
&MobileTotpOperation::default(),
|
||||
)?
|
||||
.unwrap();
|
||||
assert_eq!(all.rows().len(), 1_000);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user