Implement iPhone TOTP tab and Watch sharing
This commit is contained in:
117
crates/storage/tests/mobile_totp.rs
Normal file
117
crates/storage/tests/mobile_totp.rs
Normal file
@@ -0,0 +1,117 @@
|
||||
#![forbid(unsafe_code)]
|
||||
|
||||
mod support;
|
||||
|
||||
use std::collections::{BTreeMap, BTreeSet};
|
||||
|
||||
use ironstorage::{
|
||||
crypto::{KeyInfo, KeyStore, SecretProvider, SecretProviderError},
|
||||
mobile_totp::{MobileTotpService, MobileWatchSnapshotState},
|
||||
recipient::RecipientPolicyManager,
|
||||
repository::{EntryPath, Repository, SecretBytes},
|
||||
};
|
||||
use support::compatibility::{FixtureSet, TestResult};
|
||||
|
||||
struct FixtureSecrets(BTreeMap<String, Vec<u8>>);
|
||||
|
||||
impl FixtureSecrets {
|
||||
fn all(fixture: &FixtureSet) -> Self {
|
||||
Self(
|
||||
fixture
|
||||
.generated
|
||||
.keys
|
||||
.iter()
|
||||
.map(|key| {
|
||||
(
|
||||
key.primary_fingerprint.clone(),
|
||||
key.passphrase.as_bytes().to_vec(),
|
||||
)
|
||||
})
|
||||
.collect(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl SecretProvider for FixtureSecrets {
|
||||
fn secret_for(&mut self, key: &KeyInfo) -> Result<SecretBytes, SecretProviderError> {
|
||||
self.0
|
||||
.get(key.fingerprint().as_str())
|
||||
.cloned()
|
||||
.map(SecretBytes::new)
|
||||
.ok_or(SecretProviderError::Unavailable)
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mobile_totp_catalog_details_and_watch_selection_are_storage_owned() -> 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,
|
||||
"otp/alice",
|
||||
b"password\notpauth://totp/Acme:alice@example.com?secret=GEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQ&issuer=Acme&digits=8&period=30\n",
|
||||
)?;
|
||||
write_plaintext(
|
||||
&repository,
|
||||
&keys,
|
||||
"otp/counter",
|
||||
b"otpauth://hotp/Counter?secret=GEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQ&counter=0\n",
|
||||
)?;
|
||||
write_plaintext(&repository, &keys, "ordinary", b"password\nlogin: alice\n")?;
|
||||
|
||||
let service = MobileTotpService::new(&repository, &keys);
|
||||
let mut secrets = FixtureSecrets::all(&fixture);
|
||||
let page = service.page(&BTreeSet::new(), &mut secrets)?;
|
||||
let alice = page
|
||||
.rows()
|
||||
.iter()
|
||||
.find(|row| row.path() == "otp/alice")
|
||||
.expect("inserted TOTP row");
|
||||
assert_eq!(alice.issuer(), Some("Acme"));
|
||||
assert_eq!(alice.account(), "alice@example.com");
|
||||
assert!(!alice.shared_with_watch());
|
||||
assert!(page.rows().iter().all(|row| row.path() != "otp/counter"));
|
||||
assert_eq!(page.watch().state(), MobileWatchSnapshotState::Unavailable);
|
||||
assert!(!format!("{page:?}").contains("94287082"));
|
||||
|
||||
let detail = service.detail("otp/alice", 59, &BTreeSet::new(), &mut secrets)?;
|
||||
assert_eq!(detail.code().expose(), b"94287082");
|
||||
assert_eq!(detail.valid_until(), 60);
|
||||
assert_eq!(detail.period(), 30);
|
||||
assert!(!format!("{detail:?}").contains("94287082"));
|
||||
|
||||
let selected = BTreeSet::from([EntryPath::parse("otp/alice")?]);
|
||||
let page = service.page(&selected, &mut secrets)?;
|
||||
assert!(
|
||||
page.rows()
|
||||
.iter()
|
||||
.find(|row| row.path() == "otp/alice")
|
||||
.expect("selected TOTP row")
|
||||
.shared_with_watch()
|
||||
);
|
||||
assert_eq!(page.watch().state(), MobileWatchSnapshotState::Pending);
|
||||
let detail = service.detail("otp/alice", 60, &selected, &mut secrets)?;
|
||||
assert!(detail.shared_with_watch());
|
||||
assert_eq!(detail.valid_until(), 90);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn write_plaintext(
|
||||
repository: &Repository,
|
||||
keys: &KeyStore,
|
||||
path: &str,
|
||||
plaintext: &[u8],
|
||||
) -> TestResult {
|
||||
let path = EntryPath::parse(path)?;
|
||||
let recipients =
|
||||
RecipientPolicyManager::new(repository, keys).resolve_for_entry(&path, None)?;
|
||||
let encrypted = keys.encrypt(
|
||||
SecretBytes::new(plaintext.to_vec()),
|
||||
recipients.recipients(),
|
||||
)?;
|
||||
repository.write_entry(&path, &encrypted)?;
|
||||
Ok(())
|
||||
}
|
||||
Reference in New Issue
Block a user