1use crate::db::{
7 models::{
8 credit_card::{
9 CreditCardMeta, InternalCreditCard, UpdatableCreditCardFields,
10 UpdatableCreditCardFieldsWithMeta,
11 },
12 Metadata,
13 },
14 schema::{CREDIT_CARD_COMMON_COLS, CREDIT_CARD_COMMON_VALS},
15 timestamp_from_millis, with_savepoint, CounterUpdate,
16};
17use crate::error::*;
18
19use jwcrypto::EncryptorDecryptor;
20use rusqlite::{Connection, Transaction};
21use sync_guid::Guid;
22use types::Timestamp;
23
24pub struct CreditCardsDeletionMetrics {
25 pub total_scrubbed_records: u64,
26}
27
28pub(crate) fn add_credit_card(
29 conn: &Connection,
30 new_credit_card_fields: UpdatableCreditCardFields,
31) -> Result<InternalCreditCard> {
32 let now = Timestamp::now();
33
34 let credit_card = InternalCreditCard {
37 guid: Guid::random(),
38 cc_name: new_credit_card_fields.cc_name,
39 cc_number_enc: new_credit_card_fields.cc_number_enc,
40 cc_number_last_4: new_credit_card_fields.cc_number_last_4,
41 cc_exp_month: new_credit_card_fields.cc_exp_month,
42 cc_exp_year: new_credit_card_fields.cc_exp_year,
43 cc_type: new_credit_card_fields.cc_type,
46 metadata: Metadata {
47 time_created: now,
48 time_last_modified: now,
49 ..Default::default()
50 },
51 };
52
53 let tx = conn.unchecked_transaction()?;
54 add_internal_credit_card(&tx, &credit_card)?;
55 tx.commit()?;
56 Ok(credit_card)
57}
58
59pub(crate) fn add_credit_card_with_meta(
68 conn: &Connection,
69 fields: UpdatableCreditCardFields,
70 meta: CreditCardMeta,
71) -> Result<InternalCreditCard> {
72 let tx = conn.unchecked_transaction()?;
73 let card = internal_credit_card_from_meta(fields, &meta);
74 add_internal_credit_card(&tx, &card)?;
75 tx.commit()?;
76 Ok(card)
77}
78
79pub(crate) fn add_many_credit_cards_with_meta(
83 conn: &Connection,
84 entries: Vec<UpdatableCreditCardFieldsWithMeta>,
85) -> Result<Vec<std::result::Result<InternalCreditCard, String>>> {
86 let tx = conn.unchecked_transaction()?;
87 let mut results = Vec::with_capacity(entries.len());
88 for entry in entries {
89 let card = internal_credit_card_from_meta(entry.fields, &entry.meta);
90 match with_savepoint(&tx, || add_internal_credit_card(&tx, &card))? {
91 Ok(()) => results.push(Ok(card)),
92 Err(e) => results.push(Err(e.to_string())),
93 }
94 }
95 tx.commit()?;
96 Ok(results)
97}
98
99pub(crate) fn delete_all_credit_cards(conn: &Connection) -> Result<()> {
107 let tx = conn.unchecked_transaction()?;
108 tx.execute("DELETE FROM credit_cards_data", [])?;
109 tx.execute("DELETE FROM credit_cards_tombstones", [])?;
111 tx.commit()?;
112 Ok(())
113}
114
115pub(crate) fn add_many_credit_card_tombstones(
121 conn: &Connection,
122 tombstones: Vec<(String, i64)>,
123) -> Result<Vec<std::result::Result<String, String>>> {
124 let tx = conn.unchecked_transaction()?;
125 let mut results = Vec::with_capacity(tombstones.len());
126 for (guid, time_deleted) in tombstones {
127 let inserted = with_savepoint(&tx, || {
128 tx.execute(
129 "INSERT INTO credit_cards_tombstones (guid, time_deleted)
130 VALUES (:guid, :time_deleted)",
131 rusqlite::named_params! {
132 ":guid": &guid,
133 ":time_deleted": timestamp_from_millis(time_deleted),
134 },
135 )?;
136 Ok(())
137 })?;
138 match inserted {
139 Ok(()) => results.push(Ok(guid)),
140 Err(e) => results.push(Err(e.to_string())),
141 }
142 }
143 tx.commit()?;
144 Ok(results)
145}
146
147fn internal_credit_card_from_meta(
148 fields: UpdatableCreditCardFields,
149 meta: &CreditCardMeta,
150) -> InternalCreditCard {
151 InternalCreditCard {
152 guid: Guid::new(&meta.guid),
153 cc_name: fields.cc_name,
154 cc_number_enc: fields.cc_number_enc,
155 cc_number_last_4: fields.cc_number_last_4,
156 cc_exp_month: fields.cc_exp_month,
157 cc_exp_year: fields.cc_exp_year,
158 cc_type: fields.cc_type,
159 metadata: Metadata {
160 time_created: timestamp_from_millis(meta.time_created),
161 time_last_used: timestamp_from_millis(meta.time_last_used.unwrap_or(0)),
162 time_last_modified: timestamp_from_millis(meta.time_last_modified),
163 times_used: meta.times_used,
164 sync_change_counter: meta.sync_change_counter,
165 },
166 }
167}
168
169pub(crate) fn update_credit_card_with_meta(
175 conn: &Connection,
176 fields: UpdatableCreditCardFields,
177 meta: CreditCardMeta,
178) -> Result<()> {
179 let tx = conn.unchecked_transaction()?;
180
181 let card = internal_credit_card_from_meta(fields, &meta);
182 let exists: bool = tx.query_row(
185 "SELECT EXISTS(SELECT 1 FROM credit_cards_data WHERE guid = :guid)",
186 rusqlite::named_params! { ":guid": card.guid },
187 |row| row.get(0),
188 )?;
189 if !exists {
190 return Err(Error::NoSuchRecord(card.guid.to_string()));
191 }
192 update_internal_credit_card(
193 &tx,
194 &card,
195 CounterUpdate::Set(card.metadata.sync_change_counter),
196 )?;
197 tx.commit()?;
198 Ok(())
199}
200
201pub(crate) fn add_internal_credit_card(
202 tx: &Transaction<'_>,
203 card: &InternalCreditCard,
204) -> Result<()> {
205 tx.execute(
206 &format!(
207 "INSERT INTO credit_cards_data (
208 {common_cols},
209 sync_change_counter
210 ) VALUES (
211 {common_vals},
212 :sync_change_counter
213 )",
214 common_cols = CREDIT_CARD_COMMON_COLS,
215 common_vals = CREDIT_CARD_COMMON_VALS,
216 ),
217 rusqlite::named_params! {
218 ":guid": card.guid,
219 ":cc_name": card.cc_name,
220 ":cc_number_enc": card.cc_number_enc,
221 ":cc_number_last_4": card.cc_number_last_4,
222 ":cc_exp_month": card.cc_exp_month,
223 ":cc_exp_year": card.cc_exp_year,
224 ":cc_type": card.cc_type,
225 ":time_created": card.metadata.time_created,
226 ":time_last_used": card.metadata.time_last_used,
227 ":time_last_modified": card.metadata.time_last_modified,
228 ":times_used": card.metadata.times_used,
229 ":sync_change_counter": card.metadata.sync_change_counter,
230 },
231 )?;
232 Ok(())
233}
234
235pub(crate) fn get_credit_card(conn: &Connection, guid: &Guid) -> Result<InternalCreditCard> {
236 let sql = format!(
237 "SELECT
238 {common_cols},
239 sync_change_counter
240 FROM credit_cards_data
241 WHERE guid = :guid",
242 common_cols = CREDIT_CARD_COMMON_COLS
243 );
244
245 conn.query_row(&sql, [guid], InternalCreditCard::from_row)
246 .map_err(|e| match e {
247 rusqlite::Error::QueryReturnedNoRows => Error::NoSuchRecord(guid.to_string()),
248 e => e.into(),
249 })
250}
251
252pub(crate) fn get_all_credit_cards(conn: &Connection) -> Result<Vec<InternalCreditCard>> {
253 let sql = format!(
254 "SELECT
255 {common_cols},
256 sync_change_counter
257 FROM credit_cards_data",
258 common_cols = CREDIT_CARD_COMMON_COLS
259 );
260
261 let mut stmt = conn.prepare(&sql)?;
262 let credit_cards = stmt
263 .query_map([], InternalCreditCard::from_row)?
264 .collect::<std::result::Result<Vec<InternalCreditCard>, _>>()?;
265 Ok(credit_cards)
266}
267
268pub(crate) fn count_all_credit_cards(conn: &Connection) -> Result<i64> {
269 let sql = "SELECT COUNT(*)
270 FROM credit_cards_data";
271
272 let mut stmt = conn.prepare(sql)?;
273 let count: i64 = stmt.query_row([], |row| row.get(0))?;
274 Ok(count)
275}
276
277pub fn update_credit_card(
278 conn: &Connection,
279 guid: &Guid,
280 credit_card: &UpdatableCreditCardFields,
281) -> Result<()> {
282 let tx = conn.unchecked_transaction()?;
283 tx.execute(
284 "UPDATE credit_cards_data
285 SET cc_name = :cc_name,
286 cc_number_enc = :cc_number_enc,
287 cc_number_last_4 = :cc_number_last_4,
288 cc_exp_month = :cc_exp_month,
289 cc_exp_year = :cc_exp_year,
290 cc_type = :cc_type,
291 time_last_modified = :time_last_modified,
292 sync_change_counter = sync_change_counter + 1
293 WHERE guid = :guid",
294 rusqlite::named_params! {
295 ":cc_name": credit_card.cc_name,
296 ":cc_number_enc": credit_card.cc_number_enc,
297 ":cc_number_last_4": credit_card.cc_number_last_4,
298 ":cc_exp_month": credit_card.cc_exp_month,
299 ":cc_exp_year": credit_card.cc_exp_year,
300 ":cc_type": credit_card.cc_type,
301 ":time_last_modified": Timestamp::now(),
302 ":guid": guid,
303 },
304 )?;
305
306 tx.commit()?;
307 Ok(())
308}
309
310pub(crate) fn update_internal_credit_card(
313 tx: &Transaction<'_>,
314 card: &InternalCreditCard,
315 counter: CounterUpdate,
316) -> Result<()> {
317 let (counter_sql, counter_value) = counter.as_sql();
318 tx.execute(
319 &format!(
320 "UPDATE credit_cards_data
321 SET cc_name = :cc_name,
322 cc_number_enc = :cc_number_enc,
323 cc_number_last_4 = :cc_number_last_4,
324 cc_exp_month = :cc_exp_month,
325 cc_exp_year = :cc_exp_year,
326 cc_type = :cc_type,
327 time_created = :time_created,
328 time_last_used = :time_last_used,
329 time_last_modified = :time_last_modified,
330 times_used = :times_used,
331 sync_change_counter = {counter_sql}
332 WHERE guid = :guid"
333 ),
334 rusqlite::named_params! {
335 ":cc_name": card.cc_name,
336 ":cc_number_enc": card.cc_number_enc,
337 ":cc_number_last_4": card.cc_number_last_4,
338 ":cc_exp_month": card.cc_exp_month,
339 ":cc_exp_year": card.cc_exp_year,
340 ":cc_type": card.cc_type,
341 ":time_created": card.metadata.time_created,
342 ":time_last_used": card.metadata.time_last_used,
343 ":time_last_modified": card.metadata.time_last_modified,
344 ":times_used": card.metadata.times_used,
345 ":counter": counter_value,
346 ":guid": card.guid,
347 },
348 )?;
349 Ok(())
350}
351
352pub fn delete_credit_card(conn: &Connection, guid: &Guid) -> Result<bool> {
353 let tx = conn.unchecked_transaction()?;
354
355 let exists = tx.execute(
357 "DELETE FROM credit_cards_data
358 WHERE guid = :guid",
359 rusqlite::named_params! {
360 ":guid": guid.as_str(),
361 },
362 )? != 0;
363
364 tx.commit()?;
365 Ok(exists)
366}
367
368pub fn scrub_encrypted_credit_card_data(conn: &Connection) -> Result<()> {
369 let tx = conn.unchecked_transaction()?;
370 tx.execute("UPDATE credit_cards_data SET cc_number_enc = ''", [])?;
371 tx.commit()?;
372 Ok(())
373}
374
375pub fn scrub_undecryptable_credit_card_data_for_remote_replacement(
376 conn: &Connection,
377 local_encryption_key: String,
378) -> Result<CreditCardsDeletionMetrics> {
379 let tx = conn.unchecked_transaction()?;
380 let mut scrubbed_records = 0;
381 let encdec = EncryptorDecryptor::new(local_encryption_key.as_str()).unwrap();
382
383 let undecryptable_record_ids = get_all_credit_cards(conn)?
384 .into_iter()
385 .filter(|credit_card| encdec.decrypt(&credit_card.cc_number_enc).is_err())
386 .map(|credit_card| credit_card.guid)
387 .collect::<Vec<_>>();
388
389 sql_support::each_chunk(&undecryptable_record_ids, |chunk, _| -> Result<()> {
392 let scrubbed = tx.execute(
393 &format!(
394 "UPDATE credit_cards_data
395 SET cc_number_enc = '',
396 time_created = 0,
397 time_last_used = 0,
398 time_last_modified = 0,
399 times_used = 0,
400 sync_change_counter = 0
401 WHERE guid IN ({})",
402 sql_support::repeat_sql_values(chunk.len())
403 ),
404 rusqlite::params_from_iter(chunk),
405 )?;
406 scrubbed_records += scrubbed;
407 Ok(())
408 })?;
409
410 tx.commit()?;
411 Ok(CreditCardsDeletionMetrics {
412 total_scrubbed_records: scrubbed_records as u64,
413 })
414}
415
416pub fn touch(conn: &Connection, guid: &Guid) -> Result<()> {
417 let tx = conn.unchecked_transaction()?;
418 let now_ms = Timestamp::now();
419
420 tx.execute(
421 "UPDATE credit_cards_data
422 SET time_last_used = :time_last_used,
423 times_used = times_used + 1,
424 sync_change_counter = sync_change_counter + 1
425 WHERE guid = :guid",
426 rusqlite::named_params! {
427 ":time_last_used": now_ms,
428 ":guid": guid.as_str(),
429 },
430 )?;
431
432 tx.commit()?;
433 Ok(())
434}
435
436#[cfg(test)]
437pub(crate) mod tests {
438 use super::*;
439 use crate::db::test::new_mem_db;
440 use crate::encryption::EncryptorDecryptor;
441 use nss_as::ensure_initialized;
442 use sync15::bso::IncomingBso;
443
444 fn meta_test_fields(cc_name: &str) -> UpdatableCreditCardFields {
445 UpdatableCreditCardFields {
446 cc_name: cc_name.to_string(),
447 cc_number_enc: "0123456789012345678901234567890".to_string(),
450 cc_number_last_4: "1234".to_string(),
451 cc_exp_month: 4,
452 cc_exp_year: 2030,
453 cc_type: "visa".to_string(),
454 }
455 }
456
457 fn meta_test_meta(guid: &str, sync_change_counter: i64) -> CreditCardMeta {
458 CreditCardMeta {
459 guid: guid.to_string(),
460 time_created: 1000,
461 time_last_used: Some(2000),
462 time_last_modified: 3000,
463 times_used: 4,
464 sync_change_counter,
465 }
466 }
467
468 fn count_cc_tombstones(conn: &Connection, guid: &str) -> Result<i64> {
469 Ok(conn.query_row(
470 "SELECT COUNT(*) FROM credit_cards_tombstones WHERE guid = :guid",
471 rusqlite::named_params! { ":guid": guid },
472 |row| row.get(0),
473 )?)
474 }
475
476 #[test]
477 fn test_credit_card_add_with_meta() -> Result<()> {
478 let db = new_mem_db();
479
480 let saved =
481 add_credit_card_with_meta(&db, meta_test_fields("Jane Doe"), meta_test_meta("abc", 2))?;
482
483 assert_eq!(saved.guid.as_str(), "abc");
485
486 let retrieved = get_credit_card(&db, &Guid::new("abc"))?;
487 assert_eq!(retrieved.cc_name, "Jane Doe");
488 assert_eq!(retrieved.metadata.time_created.as_millis(), 1000);
489 assert_eq!(retrieved.metadata.time_last_used.as_millis(), 2000);
490 assert_eq!(retrieved.metadata.time_last_modified.as_millis(), 3000);
491 assert_eq!(retrieved.metadata.times_used, 4);
492 assert_eq!(retrieved.metadata.sync_change_counter, 2);
493
494 Ok(())
495 }
496
497 #[test]
498 fn test_credit_card_add_with_meta_sanitizes_out_of_range_timestamps() -> Result<()> {
499 let db = new_mem_db();
500
501 for (guid, out_of_range) in [("abc", -1), ("def", 18446744071857664)] {
506 let meta = CreditCardMeta {
507 guid: guid.to_string(),
508 time_created: out_of_range,
509 time_last_used: Some(out_of_range),
510 time_last_modified: out_of_range,
511 times_used: 0,
512 sync_change_counter: 0,
513 };
514 add_credit_card_with_meta(&db, meta_test_fields("Jane Doe"), meta)?;
515
516 let retrieved = get_credit_card(&db, &Guid::new(guid))?;
517 assert_eq!(
518 retrieved.metadata.time_created.as_millis(),
519 0,
520 "{out_of_range} survived"
521 );
522 assert_eq!(retrieved.metadata.time_last_used.as_millis(), 0);
523 assert_eq!(retrieved.metadata.time_last_modified.as_millis(), 0);
524 }
525
526 Ok(())
527 }
528
529 #[test]
532 fn test_credit_card_from_row_sanitizes_corrupt_timestamps() -> Result<()> {
533 let db = new_mem_db();
534
535 let card = add_credit_card(&db, meta_test_fields("Jane Doe"))?;
536 db.execute(
537 "UPDATE credit_cards_data
540 SET time_created = 18446744071857664,
541 time_last_used = -1,
542 time_last_modified = 8640000000000001
543 WHERE guid = :guid",
544 rusqlite::named_params! { ":guid": card.guid },
545 )?;
546
547 let retrieved = get_credit_card(&db, &card.guid)?;
548 assert_eq!(retrieved.metadata.time_created.as_millis(), 0);
549 assert_eq!(retrieved.metadata.time_last_used.as_millis(), 0);
550 assert_eq!(retrieved.metadata.time_last_modified.as_millis(), 0);
551
552 Ok(())
553 }
554
555 #[test]
556 fn test_credit_card_update_with_meta_keeps_supplied_counter() -> Result<()> {
557 let db = new_mem_db();
558
559 add_credit_card_with_meta(&db, meta_test_fields("Jane Doe"), meta_test_meta("abc", 0))?;
560
561 update_credit_card_with_meta(
563 &db,
564 meta_test_fields("Jane Q. Doe"),
565 meta_test_meta("abc", 1),
566 )?;
567
568 let retrieved = get_credit_card(&db, &Guid::new("abc"))?;
569 assert_eq!(retrieved.cc_name, "Jane Q. Doe");
570 assert_eq!(retrieved.metadata.sync_change_counter, 1);
571
572 update_credit_card_with_meta(
574 &db,
575 meta_test_fields("Jane Q. Doe"),
576 meta_test_meta("abc", 0),
577 )?;
578 assert_eq!(
579 get_credit_card(&db, &Guid::new("abc"))?
580 .metadata
581 .sync_change_counter,
582 0
583 );
584
585 Ok(())
586 }
587
588 #[test]
589 fn test_credit_card_update_with_meta_errors_when_missing() -> Result<()> {
590 let db = new_mem_db();
591
592 let result = update_credit_card_with_meta(
593 &db,
594 meta_test_fields("Jane Doe"),
595 meta_test_meta("abc", 3),
596 );
597 assert!(matches!(result, Err(Error::NoSuchRecord(guid)) if guid == "abc"));
598 assert!(get_credit_card(&db, &Guid::new("abc")).is_err());
599
600 Ok(())
601 }
602
603 #[test]
604 fn test_credit_card_add_many_with_meta_isolates_failures() -> Result<()> {
605 let db = new_mem_db();
606
607 let results = add_many_credit_cards_with_meta(
610 &db,
611 vec![
612 UpdatableCreditCardFieldsWithMeta {
613 fields: meta_test_fields("One"),
614 meta: meta_test_meta("aaa", 1),
615 },
616 UpdatableCreditCardFieldsWithMeta {
617 fields: meta_test_fields("Two"),
618 meta: meta_test_meta("", 1),
619 },
620 UpdatableCreditCardFieldsWithMeta {
621 fields: meta_test_fields("Three"),
622 meta: meta_test_meta("ccc", 1),
623 },
624 ],
625 )?;
626
627 assert_eq!(results.len(), 3);
628 assert!(results[0].is_ok());
629 assert!(results[1].is_err());
630 assert!(results[2].is_ok());
631 assert_eq!(get_all_credit_cards(&db)?.len(), 2);
632
633 Ok(())
634 }
635
636 #[test]
637 fn test_delete_all_credit_cards_allows_a_reimport() -> Result<()> {
638 let db = new_mem_db();
639
640 add_many_credit_card_tombstones(&db, vec![("gone".to_string(), 1234)])?;
643 let card = add_credit_card(&db, meta_test_fields("Jane Doe"))?;
644
645 delete_all_credit_cards(&db)?;
646 assert_eq!(get_all_credit_cards(&db)?.len(), 0);
647 let tombstones: i64 =
648 db.query_row("SELECT COUNT(*) FROM credit_cards_tombstones", [], |row| {
649 row.get(0)
650 })?;
651 assert_eq!(tombstones, 0, "tombstones are cleared with the records");
652
653 let results = add_many_credit_cards_with_meta(
656 &db,
657 vec![
658 UpdatableCreditCardFieldsWithMeta {
659 fields: meta_test_fields("Jane Doe"),
660 meta: CreditCardMeta {
661 guid: card.guid.to_string(),
662 ..Default::default()
663 },
664 },
665 UpdatableCreditCardFieldsWithMeta {
666 fields: meta_test_fields("Gone"),
667 meta: CreditCardMeta {
668 guid: "gone".to_string(),
669 ..Default::default()
670 },
671 },
672 ],
673 )?;
674 assert!(
675 results.iter().all(|r| r.is_ok()),
676 "a previously tombstoned guid can be re-imported: {results:?}"
677 );
678
679 Ok(())
680 }
681
682 #[test]
683 fn test_credit_card_add_many_tombstones() -> Result<()> {
684 let db = new_mem_db();
685
686 let results = add_many_credit_card_tombstones(&db, vec![("aaa".to_string(), 1234)])?;
687 assert_eq!(results.len(), 1);
688 assert!(results[0].is_ok());
689
690 let time_deleted: i64 = db.query_row(
692 "SELECT time_deleted FROM credit_cards_tombstones WHERE guid = 'aaa'",
693 [],
694 |row| row.get(0),
695 )?;
696 assert_eq!(time_deleted, 1234);
697
698 Ok(())
699 }
700
701 #[test]
702 fn test_credit_card_add_many_tombstones_rejects_live_guid() -> Result<()> {
703 let db = new_mem_db();
704
705 add_credit_card_with_meta(&db, meta_test_fields("Jane Doe"), meta_test_meta("abc", 0))?;
706
707 let results = add_many_credit_card_tombstones(
711 &db,
712 vec![("abc".to_string(), 1234), ("ddd".to_string(), 5678)],
713 )?;
714
715 assert_eq!(results.len(), 2);
716 assert!(results[0].is_err());
717 assert!(results[1].is_ok());
718
719 assert_eq!(count_cc_tombstones(&db, "abc")?, 0);
722 assert!(get_credit_card(&db, &Guid::new("abc")).is_ok());
723 assert_eq!(count_cc_tombstones(&db, "ddd")?, 1);
724
725 Ok(())
726 }
727
728 #[test]
729 fn test_credit_card_add_many_with_meta_rejects_deleted_guid() -> Result<()> {
730 let db = new_mem_db();
731
732 add_many_credit_card_tombstones(&db, vec![("aaa".to_string(), 1234)])?;
733
734 let results = add_many_credit_cards_with_meta(
738 &db,
739 vec![
740 UpdatableCreditCardFieldsWithMeta {
741 fields: meta_test_fields("One"),
742 meta: meta_test_meta("aaa", 1),
743 },
744 UpdatableCreditCardFieldsWithMeta {
745 fields: meta_test_fields("Two"),
746 meta: meta_test_meta("bbb", 1),
747 },
748 ],
749 )?;
750
751 assert_eq!(results.len(), 2);
752 assert!(results[0].is_err());
753 assert!(results[1].is_ok());
754
755 assert!(get_credit_card(&db, &Guid::new("aaa")).is_err());
756 assert_eq!(get_all_credit_cards(&db)?.len(), 1);
757
758 Ok(())
759 }
760
761 pub fn get_all(
762 conn: &Connection,
763 table_name: String,
764 ) -> rusqlite::Result<Vec<String>, rusqlite::Error> {
765 let mut stmt = conn.prepare(&format!(
766 "SELECT guid FROM {table_name}",
767 table_name = table_name
768 ))?;
769 let rows = stmt.query_map([], |row| row.get(0))?;
770
771 let mut guids = Vec::new();
772 for guid_result in rows {
773 guids.push(guid_result?);
774 }
775
776 Ok(guids)
777 }
778
779 pub fn insert_tombstone_record(
780 conn: &Connection,
781 guid: String,
782 ) -> rusqlite::Result<usize, rusqlite::Error> {
783 conn.execute(
784 "INSERT INTO credit_cards_tombstones (
785 guid,
786 time_deleted
787 ) VALUES (
788 :guid,
789 :time_deleted
790 )",
791 rusqlite::named_params! {
792 ":guid": guid,
793 ":time_deleted": Timestamp::now(),
794 },
795 )
796 }
797
798 pub(crate) fn test_insert_mirror_record(conn: &Connection, bso: IncomingBso) {
799 conn.execute(
804 "INSERT INTO credit_cards_mirror (guid, payload)
805 VALUES (:guid, :payload)",
806 rusqlite::named_params! {
807 ":guid": &bso.envelope.id,
808 ":payload": &bso.payload,
809 },
810 )
811 .expect("should insert");
812 }
813
814 #[test]
815 fn test_credit_card_create_and_read() -> Result<()> {
816 ensure_initialized();
817 let db = new_mem_db();
818
819 let saved_credit_card = add_credit_card(
820 &db,
821 UpdatableCreditCardFields {
822 cc_name: "jane doe".to_string(),
823 cc_number_enc: "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX".to_string(),
824 cc_number_last_4: "1234".to_string(),
825 cc_exp_month: 3,
826 cc_exp_year: 2022,
827 cc_type: "visa".to_string(),
828 },
829 )?;
830
831 assert_ne!(Guid::default(), saved_credit_card.guid);
833
834 assert_ne!(0, saved_credit_card.metadata.time_created.as_millis());
836 assert_ne!(0, saved_credit_card.metadata.time_last_modified.as_millis());
837
838 assert_eq!(0, saved_credit_card.metadata.sync_change_counter);
840
841 let retrieved_credit_card = get_credit_card(&db, &saved_credit_card.guid)?;
843
844 assert_eq!(saved_credit_card.guid, retrieved_credit_card.guid);
845 assert_eq!(saved_credit_card.cc_name, retrieved_credit_card.cc_name);
846 assert_eq!(
847 saved_credit_card.cc_number_enc,
848 retrieved_credit_card.cc_number_enc
849 );
850 assert_eq!(
851 saved_credit_card.cc_number_last_4,
852 retrieved_credit_card.cc_number_last_4
853 );
854 assert_eq!(
855 saved_credit_card.cc_exp_month,
856 retrieved_credit_card.cc_exp_month
857 );
858 assert_eq!(
859 saved_credit_card.cc_exp_year,
860 retrieved_credit_card.cc_exp_year
861 );
862 assert_eq!(saved_credit_card.cc_type, retrieved_credit_card.cc_type);
863
864 let delete_result = delete_credit_card(&db, &saved_credit_card.guid);
866 assert!(delete_result.is_ok());
867 assert!(delete_result?);
868
869 assert!(get_credit_card(&db, &saved_credit_card.guid).is_err());
870
871 Ok(())
872 }
873
874 #[test]
875 fn test_credit_card_missing_guid() {
876 ensure_initialized();
877 let db = new_mem_db();
878 let guid = Guid::random();
879 let result = get_credit_card(&db, &guid);
880
881 assert_eq!(
882 result.unwrap_err().to_string(),
883 Error::NoSuchRecord(guid.to_string()).to_string()
884 );
885 }
886
887 #[test]
888 fn test_credit_card_read_all() -> Result<()> {
889 ensure_initialized();
890 let db = new_mem_db();
891
892 let saved_credit_card = add_credit_card(
893 &db,
894 UpdatableCreditCardFields {
895 cc_name: "jane doe".to_string(),
896 cc_number_enc: "YYYYYYYYYYYYYYYYYYYYYYYYYYYYY".to_string(),
897 cc_number_last_4: "4321".to_string(),
898 cc_exp_month: 3,
899 cc_exp_year: 2022,
900 cc_type: "visa".to_string(),
901 },
902 )?;
903
904 let saved_credit_card2 = add_credit_card(
905 &db,
906 UpdatableCreditCardFields {
907 cc_name: "john deer".to_string(),
908 cc_number_enc: "ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ".to_string(),
909 cc_number_last_4: "6543".to_string(),
910 cc_exp_month: 10,
911 cc_exp_year: 2025,
912 cc_type: "mastercard".to_string(),
913 },
914 )?;
915
916 let saved_credit_card3 = add_credit_card(
918 &db,
919 UpdatableCreditCardFields {
920 cc_name: "abraham lincoln".to_string(),
921 cc_number_enc: "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA".to_string(),
922 cc_number_last_4: "9876".to_string(),
923 cc_exp_month: 1,
924 cc_exp_year: 2024,
925 cc_type: "amex".to_string(),
926 },
927 )?;
928
929 let delete_result = delete_credit_card(&db, &saved_credit_card3.guid);
930 assert!(delete_result.is_ok());
931 assert!(delete_result?);
932
933 let retrieved_credit_cards = get_all_credit_cards(&db)?;
934
935 assert!(!retrieved_credit_cards.is_empty());
936 let expected_number_of_credit_cards = 2;
937 assert_eq!(
938 expected_number_of_credit_cards,
939 retrieved_credit_cards.len()
940 );
941
942 let credit_card_count = count_all_credit_cards(&db)?;
943 assert_eq!(expected_number_of_credit_cards, credit_card_count as usize);
944
945 let retrieved_credit_card_guids = [
946 retrieved_credit_cards[0].guid.as_str(),
947 retrieved_credit_cards[1].guid.as_str(),
948 ];
949 assert!(retrieved_credit_card_guids.contains(&saved_credit_card.guid.as_str()));
950 assert!(retrieved_credit_card_guids.contains(&saved_credit_card2.guid.as_str()));
951
952 Ok(())
953 }
954
955 #[test]
956 fn test_credit_card_update() -> Result<()> {
957 ensure_initialized();
958 let db = new_mem_db();
959
960 let saved_credit_card = add_credit_card(
961 &db,
962 UpdatableCreditCardFields {
963 cc_name: "john deer".to_string(),
964 cc_number_enc: "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA".to_string(),
965 cc_number_last_4: "4321".to_string(),
966 cc_exp_month: 10,
967 cc_exp_year: 2025,
968 cc_type: "mastercard".to_string(),
969 },
970 )?;
971
972 let expected_cc_name = "john doe".to_string();
973 let update_result = update_credit_card(
974 &db,
975 &saved_credit_card.guid,
976 &UpdatableCreditCardFields {
977 cc_name: expected_cc_name.clone(),
978 cc_number_enc: "BBBBBBBBBBBBBBBBBBBBBBBBBBBBBB".to_string(),
979 cc_number_last_4: "1234".to_string(),
980 cc_type: "mastercard".to_string(),
981 cc_exp_month: 10,
982 cc_exp_year: 2025,
983 },
984 );
985 assert!(update_result.is_ok());
986
987 let updated_credit_card = get_credit_card(&db, &saved_credit_card.guid)?;
988
989 assert_eq!(saved_credit_card.guid, updated_credit_card.guid);
990 assert_eq!(expected_cc_name, updated_credit_card.cc_name);
991
992 assert_eq!(1, updated_credit_card.metadata.sync_change_counter);
994
995 Ok(())
996 }
997
998 #[test]
999 fn test_credit_card_update_internal_credit_card() -> Result<()> {
1000 ensure_initialized();
1001 let mut db = new_mem_db();
1002 let tx = db.transaction()?;
1003
1004 let guid = Guid::random();
1005 add_internal_credit_card(
1006 &tx,
1007 &InternalCreditCard {
1008 guid: guid.clone(),
1009 cc_name: "john deer".to_string(),
1010 cc_number_enc: "BBBBBBBBBBBBBBBBBBBBBBBBBBBBBB".to_string(),
1011 cc_number_last_4: "1234".to_string(),
1012 cc_exp_month: 10,
1013 cc_exp_year: 2025,
1014 cc_type: "mastercard".to_string(),
1015 ..Default::default()
1016 },
1017 )?;
1018
1019 let expected_cc_exp_month = 11;
1020 update_internal_credit_card(
1021 &tx,
1022 &InternalCreditCard {
1023 guid: guid.clone(),
1024 cc_name: "john deer".to_string(),
1025 cc_number_enc: "BBBBBBBBBBBBBBBBBBBBBBBBBBBBBB".to_string(),
1026 cc_number_last_4: "1234".to_string(),
1027 cc_exp_month: expected_cc_exp_month,
1028 cc_exp_year: 2025,
1029 cc_type: "mastercard".to_string(),
1030 ..Default::default()
1031 },
1032 CounterUpdate::Leave,
1033 )?;
1034
1035 let record_exists: bool = tx.query_row(
1036 "SELECT EXISTS (
1037 SELECT 1
1038 FROM credit_cards_data
1039 WHERE guid = :guid
1040 AND cc_exp_month = :cc_exp_month
1041 AND sync_change_counter = 0
1042 )",
1043 [&guid.to_string(), &expected_cc_exp_month.to_string()],
1044 |row| row.get(0),
1045 )?;
1046 assert!(record_exists);
1047
1048 Ok(())
1049 }
1050
1051 #[test]
1052 fn test_credit_card_delete() -> Result<()> {
1053 ensure_initialized();
1054 let db = new_mem_db();
1055 let encdec = EncryptorDecryptor::new_with_random_key().unwrap();
1056
1057 let saved_credit_card = add_credit_card(
1058 &db,
1059 UpdatableCreditCardFields {
1060 cc_name: "john deer".to_string(),
1061 cc_number_enc: encdec.encrypt("1234567812345678")?,
1062 cc_number_last_4: "5678".to_string(),
1063 cc_exp_month: 10,
1064 cc_exp_year: 2025,
1065 cc_type: "mastercard".to_string(),
1066 },
1067 )?;
1068
1069 let delete_result = delete_credit_card(&db, &saved_credit_card.guid);
1070 assert!(delete_result.is_ok());
1071 assert!(delete_result?);
1072
1073 let saved_credit_card2 = add_credit_card(
1074 &db,
1075 UpdatableCreditCardFields {
1076 cc_name: "john doe".to_string(),
1077 cc_number_enc: encdec.encrypt("1234123412341234")?,
1078 cc_number_last_4: "1234".to_string(),
1079 cc_exp_month: 5,
1080 cc_exp_year: 2024,
1081 cc_type: "visa".to_string(),
1082 },
1083 )?;
1084
1085 let cc2_guid = saved_credit_card2.guid.clone();
1087 let payload = saved_credit_card2.into_test_incoming_bso(&encdec, Default::default());
1088
1089 test_insert_mirror_record(&db, payload);
1090
1091 let delete_result2 = delete_credit_card(&db, &cc2_guid);
1092 assert!(delete_result2.is_ok());
1093 assert!(delete_result2?);
1094
1095 let tombstone_exists: bool = db.query_row(
1097 "SELECT EXISTS (
1098 SELECT 1
1099 FROM credit_cards_tombstones
1100 WHERE guid = :guid
1101 )",
1102 [&cc2_guid],
1103 |row| row.get(0),
1104 )?;
1105 assert!(tombstone_exists);
1106
1107 db.execute(
1109 "DELETE FROM credit_cards_tombstones
1110 WHERE guid = :guid",
1111 rusqlite::named_params! {
1112 ":guid": cc2_guid,
1113 },
1114 )?;
1115
1116 Ok(())
1117 }
1118
1119 #[test]
1120 fn test_scrub_encrypted_credit_card_data() -> Result<()> {
1121 ensure_initialized();
1122 let db = new_mem_db();
1123 let encdec = EncryptorDecryptor::new_with_random_key().unwrap();
1124 let mut saved_credit_cards = Vec::with_capacity(10);
1125 for _ in 0..5 {
1126 saved_credit_cards.push(add_credit_card(
1127 &db,
1128 UpdatableCreditCardFields {
1129 cc_name: "john deer".to_string(),
1130 cc_number_enc: encdec.encrypt("1234567812345678")?,
1131 cc_number_last_4: "5678".to_string(),
1132 cc_exp_month: 10,
1133 cc_exp_year: 2025,
1134 cc_type: "mastercard".to_string(),
1135 },
1136 )?);
1137 }
1138
1139 scrub_encrypted_credit_card_data(&db)?;
1140 for saved_credit_card in saved_credit_cards.into_iter() {
1141 let retrieved_credit_card = get_credit_card(&db, &saved_credit_card.guid)?;
1142 assert_eq!(retrieved_credit_card.cc_number_enc, "");
1143 }
1144
1145 Ok(())
1146 }
1147
1148 #[test]
1149 fn test_scrub_undecryptable_credit_card_date_for_remote_replacement() -> Result<()> {
1150 ensure_initialized();
1151 let db = new_mem_db();
1152 let old_key = EncryptorDecryptor::create_key()?;
1153 let old_encdec = EncryptorDecryptor::new(&old_key)?;
1154 let key = EncryptorDecryptor::create_key()?;
1155 let encdec = EncryptorDecryptor::new(&key)?;
1156
1157 let undecryptable_credit_card = add_credit_card(
1158 &db,
1159 UpdatableCreditCardFields {
1160 cc_name: "jane doe".to_string(),
1161 cc_number_enc: old_encdec.encrypt("2345678923456789")?,
1162 cc_number_last_4: "6789".to_string(),
1163 cc_exp_month: 9,
1164 cc_exp_year: 2027,
1165 cc_type: "visa".to_string(),
1166 },
1167 )?;
1168
1169 let encrypted_cc_number = encdec.encrypt("567812345678123456781")?;
1170 let credit_card = add_credit_card(
1171 &db,
1172 UpdatableCreditCardFields {
1173 cc_name: "john deer".to_string(),
1174 cc_number_enc: encrypted_cc_number.clone(),
1175 cc_number_last_4: "6781".to_string(),
1176 cc_exp_month: 10,
1177 cc_exp_year: 2025,
1178 cc_type: "mastercard".to_string(),
1179 },
1180 )?;
1181
1182 let metrics = scrub_undecryptable_credit_card_data_for_remote_replacement(&db.writer, key)?;
1183 assert_eq!(metrics.total_scrubbed_records, 1);
1184
1185 let credit_cards = get_all_credit_cards(&db)?;
1186 assert_eq!(credit_cards.len(), 2);
1187
1188 let retrieved_credit_card = get_credit_card(&db, &undecryptable_credit_card.guid)?;
1189 assert_eq!(retrieved_credit_card.cc_number_enc, "");
1190
1191 let retrieved_credit_card2 = get_credit_card(&db, &credit_card.guid)?;
1192 assert_eq!(retrieved_credit_card2.cc_number_enc, encrypted_cc_number);
1193
1194 Ok(())
1195 }
1196
1197 #[test]
1198 fn test_credit_card_trigger_on_create() -> Result<()> {
1199 ensure_initialized();
1200 let db = new_mem_db();
1201 let tx = db.unchecked_transaction()?;
1202 let guid = Guid::random();
1203
1204 insert_tombstone_record(&db, guid.to_string())?;
1206
1207 let credit_card = InternalCreditCard {
1209 guid,
1210 cc_name: "john deer".to_string(),
1211 cc_number_enc: "WWWWWWWWWWWWWWWWWWWWWWWWWWWWWWW".to_string(),
1212 cc_number_last_4: "6543".to_string(),
1213 cc_exp_month: 10,
1214 cc_exp_year: 2025,
1215 cc_type: "mastercard".to_string(),
1216
1217 ..Default::default()
1218 };
1219
1220 let add_credit_card_result = add_internal_credit_card(&tx, &credit_card);
1221 assert!(add_credit_card_result.is_err());
1222
1223 let expected_error_message = "guid exists in `credit_cards_tombstones`";
1224 assert!(add_credit_card_result
1225 .unwrap_err()
1226 .to_string()
1227 .contains(expected_error_message));
1228
1229 Ok(())
1230 }
1231
1232 #[test]
1233 fn test_credit_card_trigger_on_delete() -> Result<()> {
1234 ensure_initialized();
1235 let db = new_mem_db();
1236 let tx = db.unchecked_transaction()?;
1237 let guid = Guid::random();
1238
1239 let credit_card = InternalCreditCard {
1241 guid,
1242 cc_name: "jane doe".to_string(),
1243 cc_number_enc: "WWWWWWWWWWWWWWWWWWWWWWWWWWWWWWW".to_string(),
1244 cc_number_last_4: "6543".to_string(),
1245 cc_exp_month: 3,
1246 cc_exp_year: 2022,
1247 cc_type: "visa".to_string(),
1248 ..Default::default()
1249 };
1250 add_internal_credit_card(&tx, &credit_card)?;
1251
1252 let tombstone_result = insert_tombstone_record(&db, credit_card.guid.to_string());
1254
1255 let expected_error_message = "guid exists in `credit_cards_data`";
1256 assert!(tombstone_result
1257 .unwrap_err()
1258 .to_string()
1259 .contains(expected_error_message));
1260
1261 Ok(())
1262 }
1263
1264 #[test]
1265 fn test_credit_card_touch() -> Result<()> {
1266 ensure_initialized();
1267 let db = new_mem_db();
1268 let saved_credit_card = add_credit_card(
1269 &db,
1270 UpdatableCreditCardFields {
1271 cc_name: "john doe".to_string(),
1272 cc_number_enc: "WWWWWWWWWWWWWWWWWWWWWWWWWWWWWWW".to_string(),
1273 cc_number_last_4: "6543".to_string(),
1274 cc_exp_month: 5,
1275 cc_exp_year: 2024,
1276 cc_type: "visa".to_string(),
1277 },
1278 )?;
1279
1280 assert_eq!(saved_credit_card.metadata.sync_change_counter, 0);
1281 assert_eq!(saved_credit_card.metadata.times_used, 0);
1282
1283 touch(&db, &saved_credit_card.guid)?;
1284
1285 let touched_credit_card = get_credit_card(&db, &saved_credit_card.guid)?;
1286
1287 assert_eq!(touched_credit_card.metadata.sync_change_counter, 1);
1288 assert_eq!(touched_credit_card.metadata.times_used, 1);
1289
1290 Ok(())
1291 }
1292}