/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
use crate::auth::TestClient;
use crate::testing::TestGroup;
use anyhow::Result;
use autofill::{
db::{
credit_cards::CreditCardsDeletionMetrics,
models::address::{Address, UpdatableAddressFields},
models::credit_card::{CreditCard, UpdatableCreditCardFields},
store::Store as AutofillStore,
},
encryption::{create_autofill_key, decrypt_string, encrypt_string},
error::ApiResult as AutofillResult,
};
use std::{
collections::{hash_map::RandomState, HashMap},
sync::Arc,
};
pub fn sync_addresses(client: &mut TestClient) -> Result<()> {
client.sync(&["addresses".to_string()], HashMap::new())?;
Ok(())
}
pub fn add_address(s: &AutofillStore, a: UpdatableAddressFields) -> AutofillResult
{
let id = s.add_address(a)?.guid;
Ok(s.get_address(id).expect("Address has been added"))
}
pub fn delete_address(s: &AutofillStore, a: Address) -> AutofillResult<()> {
s.delete_address(a.guid)?;
Ok(())
}
pub fn verify_address(s: &AutofillStore, a: &Address) {
let equivalent = s
.get_address(a.guid.clone())
.expect("get_address() to succeed");
assert_address_equiv(&equivalent, a);
}
pub fn verify_address_removal(s: &AutofillStore) {
let a = s.get_all_addresses().expect("no returned addresses");
assert!(a.is_empty());
}
pub fn assert_address_equiv(a: &Address, b: &Address) {
assert_eq!(a.name, b.name, "name mismatch");
assert_eq!(
a.street_address, b.street_address,
"street_address mismatch"
);
assert_eq!(
a.address_level2, b.address_level2,
"address_level2 mismatch"
);
assert_eq!(a.postal_code, b.postal_code, "postal_code mismatch");
assert_eq!(a.country, b.country, "country mismatch");
}
pub fn sync_credit_cards(client: &mut TestClient, local_enc_key: String) -> Result<()> {
let engine_name = "creditcards";
let mut local_encryption_keys = HashMap::new();
local_encryption_keys.insert(engine_name.to_string(), local_enc_key);
client.sync(&[engine_name.to_string()], local_encryption_keys)?;
Ok(())
}
pub fn sync_credit_cards_with_failure(
client: &mut TestClient,
local_enc_key: String,
) -> Result> {
let engine_name = "creditcards";
let mut local_encryption_keys = HashMap::new();
local_encryption_keys.insert(engine_name.to_string(), local_enc_key);
client.sync_with_failure(&[engine_name.to_string()], local_encryption_keys)
}
pub fn add_credit_card(
s: &AutofillStore,
c: UpdatableCreditCardFields,
) -> AutofillResult {
let id = s.add_credit_card(c)?.guid;
Ok(s.get_credit_card(id).expect("Credit card has been added"))
}
pub fn scrub_credit_card(s: Arc) -> AutofillResult<()> {
AutofillStore::scrub_encrypted_data(s).expect("scrub_encrypted_data() to succeed");
Ok(())
}
pub fn scrub_undecryptable_credit_card_data_for_remote_replacement(
s: Arc,
local_enc_key: String,
) -> AutofillResult {
Ok(
AutofillStore::scrub_undecryptable_credit_card_data_for_remote_replacement(
s,
local_enc_key,
)
.expect("scrub_undecryptable_credit_card_data_for_remote_replacement() to succeed"),
)
}
pub fn get_credit_card(s: &AutofillStore, guid: String) -> CreditCard {
s.get_credit_card(guid)
.expect("stored credit card to be retrieved")
}
pub fn delete_credit_card(s: &AutofillStore, c: CreditCard) -> AutofillResult<()> {
s.delete_credit_card(c.guid)?;
Ok(())
}
pub fn verify_credit_card(s: &AutofillStore, c: &CreditCard, key: String) {
let equivalent = s
.get_credit_card(c.guid.clone())
.expect("get_credit_card() to succeed");
assert_credit_cards_equiv(&equivalent, c, key.clone(), key);
}
pub fn verify_credit_card_with_two_keys(
s: &AutofillStore,
c: &CreditCard,
stored_record_key: String,
c_key: String,
) {
let equivalent = s
.get_credit_card(c.guid.clone())
.expect("get_credit_card() to succeed");
assert_credit_cards_equiv(&equivalent, c, stored_record_key, c_key);
}
pub fn verify_credit_card_removal(s: &AutofillStore) {
let c = s.get_all_credit_cards().expect("no returned credit cards");
assert!(c.is_empty());
}
pub fn assert_credit_cards_equiv(a: &CreditCard, b: &CreditCard, key_a: String, key_b: String) {
assert_eq!(a.cc_name, b.cc_name, "cc_name mismatch");
assert_eq!(
decrypt_string(key_a, a.cc_number_enc.clone()).expect("to decrypt a.cc_number_enc"),
decrypt_string(key_b, b.cc_number_enc.clone()).expect("to decrypt b.cc_number_enc"),
"cc_number_enc mismatch",
);
assert_eq!(
a.cc_number_last_4, b.cc_number_last_4,
"cc_number_last_4 mismatch"
);
assert_eq!(a.cc_exp_month, b.cc_exp_month, "cc_exp_month mismatch");
assert_eq!(a.cc_exp_year, b.cc_exp_year, "cc_exp_year mismatch");
assert_eq!(a.cc_type, b.cc_type, "cc_type mismatch");
}
// Actual tests
fn test_autofill_credit_cards_general(c0: &mut TestClient, c1: &mut TestClient) {
log::info!("Add some credit cards to client0");
let key = create_autofill_key().expect("encryption key created");
let cc1 = add_credit_card(
&c0.autofill_store,
UpdatableCreditCardFields {
cc_name: "jane doe".to_string(),
cc_number_enc: encrypt_string(key.clone(), "2222222222221234".to_string())
.expect("encrypted cc number for cc1"),
cc_number_last_4: "1234".to_string(),
cc_exp_month: 3,
cc_exp_year: 2022,
cc_type: "visa".to_string(),
},
)
.expect("add cc1");
let cc2 = add_credit_card(
&c0.autofill_store,
UpdatableCreditCardFields {
cc_name: "john deer".to_string(),
cc_number_enc: encrypt_string(key.clone(), "9999999999996543".to_string())
.expect("encrypted cc number for cc2"),
cc_number_last_4: "6543".to_string(),
cc_exp_month: 10,
cc_exp_year: 2025,
cc_type: "mastercard".to_string(),
},
)
.expect("add cc2");
log::info!("Syncing client0");
sync_credit_cards(c0, key.clone()).expect("c0 sync to work");
log::info!("Syncing client1");
sync_credit_cards(c1, key.clone()).expect("c1 sync to work");
log::info!("Check state");
verify_credit_card(&c1.autofill_store, &cc1, key.clone());
verify_credit_card(&c1.autofill_store, &cc2, key.clone());
// clear records
delete_credit_card(&c0.autofill_store, cc1).expect("cc1 to be deleted from c0");
delete_credit_card(&c0.autofill_store, cc2).expect("cc2 to be deleted from c0");
sync_credit_cards(c0, key.clone()).expect("c0 sync to work");
sync_credit_cards(c1, key).expect("c1 sync to work");
verify_credit_card_removal(&c0.autofill_store);
verify_credit_card_removal(&c1.autofill_store);
}
fn test_autofill_credit_cards_with_scrubbed_cards(c0: &mut TestClient, c1: &mut TestClient) {
let key = create_autofill_key().expect("encryption key created");
log::info!("Add a credit card to client0");
let cc3 = add_credit_card(
&c0.autofill_store,
UpdatableCreditCardFields {
cc_name: "jane deer".to_string(),
cc_number_enc: encrypt_string(key.clone(), "88888888888888".to_string())
.expect("encrypted cc number for cc3"),
cc_number_last_4: "6789".to_string(),
cc_exp_month: 12,
cc_exp_year: 2027,
cc_type: "visa".to_string(),
},
)
.expect("add cc3");
log::info!("CC3 GUID: {}", cc3.clone().guid);
log::info!("Scrub the credit cards on client0");
let _ = scrub_credit_card(c0.autofill_store.clone());
log::info!("Syncing client0");
sync_credit_cards(c0, key.clone()).expect("c0 sync to work");
// clear records
delete_credit_card(&c0.autofill_store, cc3.clone()).expect("cc3 to be deleted from c0");
sync_credit_cards(c0, key.clone()).expect("c0 sync to work");
verify_credit_card_removal(&c0.autofill_store);
verify_credit_card_removal(&c1.autofill_store);
}
fn test_autofill_addresses_general(c0: &mut TestClient, c1: &mut TestClient) {
log::info!("Add some addresses to client0");
let a1 = add_address(
&c0.autofill_store,
UpdatableAddressFields {
name: "jane elliott doe".to_string(),
street_address: "123 Second Avenue".to_string(),
address_level2: "Chicago, IL".to_string(),
postal_code: "60007".to_string(),
country: "United States".to_string(),
..UpdatableAddressFields::default()
},
)
.expect("add a1");
let a2 = add_address(
&c0.autofill_store,
UpdatableAddressFields {
name: "john elliott doe".to_string(),
street_address: "1300 Broadway".to_string(),
address_level2: "New York, NY".to_string(),
postal_code: "10001".to_string(),
country: "United States".to_string(),
..UpdatableAddressFields::default()
},
)
.expect("add a2");
log::info!("Syncing client0");
sync_addresses(c0).expect("c0 sync to work");
log::info!("Syncing client1");
sync_addresses(c1).expect("c1 sync to work");
log::info!("Check state");
verify_address(&c1.autofill_store, &a1);
verify_address(&c1.autofill_store, &a2);
// clear records
delete_address(&c0.autofill_store, a1).expect("a1 to be deleted from c0");
delete_address(&c0.autofill_store, a2).expect("a2 to be deleted from c0");
sync_addresses(c0).expect("c0 sync to work");
sync_addresses(c1).expect("c1 sync to work");
verify_address_removal(&c0.autofill_store);
verify_address_removal(&c1.autofill_store);
}
fn test_undecryptable_record_prevents_syncing(c0: &mut TestClient, c1: &mut TestClient) {
log::info!("Add a credit card to client0");
let old_key = create_autofill_key().expect("encryption key created");
// Add a credit card
let credit_card0 = add_credit_card(
&c0.autofill_store,
UpdatableCreditCardFields {
cc_name: "john deer".to_string(),
cc_number_enc: encrypt_string(old_key.clone(), "88888888888888".to_string())
.expect("encrypted cc number for credit_card0"),
cc_number_last_4: "8888".to_string(),
cc_exp_month: 10,
cc_exp_year: 2025,
cc_type: "mastercard".to_string(),
},
)
.expect("add credit_card0");
log::info!("Verifying credit_card0 on c0");
// Check that the corrupted credit card exists on first device
verify_credit_card(&c0.autofill_store, &credit_card0, old_key.clone());
log::info!("Syncing client0 with corrupted record");
// In order to simulate syncing a corrupted credit card created with a key we no longer have, we are syncing
// with a newly created key.
let new_key = create_autofill_key().expect("second encryption key created");
let failures = sync_credit_cards_with_failure(c0, new_key.clone())
.expect("sync to complete with failures");
let credit_card_failures = failures.get("creditcards");
assert!(credit_card_failures.is_some());
assert!(credit_card_failures.unwrap().contains("Crypto Error"));
// clear records
delete_credit_card(&c0.autofill_store, credit_card0)
.expect("credit_card0 to be deleted from c0");
verify_credit_card_removal(&c0.autofill_store);
verify_credit_card_removal(&c1.autofill_store);
}
fn test_scrub_undecryptable_records_for_remote_replacement(
c0: &mut TestClient,
c1: &mut TestClient,
) {
log::info!("Adding a credit card to client0");
let key = create_autofill_key().expect("encryption key created");
let cc_number = "88888888888888".to_string();
// Add a credit card
let credit_card0 = add_credit_card(
&c0.autofill_store,
UpdatableCreditCardFields {
cc_name: "john deer".to_string(),
cc_number_enc: encrypt_string(key.clone(), cc_number.clone())
.expect("encrypted cc number for credit_card0"),
cc_number_last_4: "8888".to_string(),
cc_exp_month: 10,
cc_exp_year: 2025,
cc_type: "mastercard".to_string(),
},
)
.expect("add credit_card0 to c0");
let cc0id = credit_card0.clone().guid;
log::info!("Verifying credit_card0 on c0");
verify_credit_card(&c0.autofill_store, &credit_card0, key.clone());
// Here we're checking that c1 doesn't have the record yet. This is to validate
// that the devices are not sharing a store reference.
verify_credit_card_removal(&c1.autofill_store);
// Sync the first device where the credit card was added
log::info!("Syncing client0 -- inital sync");
sync_credit_cards(c0, key.clone()).expect("c0 sync to work");
// Sync the second device
log::info!("Syncing client1 -- inital sync");
sync_credit_cards(c1, key.clone()).expect("c1 sync to work");
log::info!("Verifying the synced record on both devices");
// Verify that both devices store the credit card record after syncing and that
// the decrypted value of the cc_number_enc field is the original card number
verify_credit_card(&c0.autofill_store, &credit_card0, key.clone());
verify_credit_card(&c1.autofill_store, &credit_card0, key.clone());
log::info!("Scrubbing the credit card on c0");
let new_key = create_autofill_key().expect("encryption key created");
let metrics = scrub_undecryptable_credit_card_data_for_remote_replacement(
c0.autofill_store.clone(),
new_key.clone(),
)
.expect("c0 credit card to be scrubbed");
assert_eq!(metrics.total_scrubbed_records, 1);
log::info!("Verifying that the record on c0 has been scrubbed");
let c0_scrubbed_record = get_credit_card(&c0.autofill_store, cc0id.clone());
assert!(c0_scrubbed_record.cc_number_enc.is_empty());
// Being super cautious and checking that the scrub didn't impact the second device
verify_credit_card(&c1.autofill_store, &credit_card0, key.clone());
// Sync the first device after scrubbing
log::info!("Syncing client0 -- after scrubbing");
sync_credit_cards(c0, new_key.clone()).expect("c0 post-scrub sync to work");
// Sync the second device after scrubbing
log::info!("Syncing client1 -- after scrubbing");
sync_credit_cards(c1, key.clone()).expect("c1 post-scrub sync to work");
log::info!("Verifying that c0 has the restored record");
// We are passing two keys here, `new_key` is what we introduced on device c0 after simulating
// a lost or corrupted key and `key` is what we used to encrypt the credit card number for `credit_card0`.
// We need both to ensure that we still have the same credit card number after decryption.
verify_credit_card_with_two_keys(
&c0.autofill_store,
&credit_card0,
new_key.clone(),
key.clone(),
);
// Again this check is done out of an abundance of caution
verify_credit_card(&c1.autofill_store, &credit_card0, key.clone());
// clear records
delete_credit_card(&c0.autofill_store, credit_card0.clone())
.expect("credit_card0 to be deleted from c0");
delete_credit_card(&c1.autofill_store, credit_card0)
.expect("credit_card0 to be deleted from c1");
verify_credit_card_removal(&c0.autofill_store);
verify_credit_card_removal(&c1.autofill_store);
}
pub fn get_test_group() -> TestGroup {
TestGroup::new(
"autofill",
vec![
(
"test_autofill_addresses_general",
test_autofill_addresses_general,
),
(
"test_autofill_credit_cards_general",
test_autofill_credit_cards_general,
),
(
"test_autofill_credit_cards_with_scrubbed_cards",
test_autofill_credit_cards_with_scrubbed_cards,
),
(
"test_undecryptable_record_prevents_syncing",
test_undecryptable_record_prevents_syncing,
),
(
"test_scrub_undecryptable_records_for_remote_replacement",
test_scrub_undecryptable_records_for_remote_replacement,
),
],
)
}