autofill/sync/credit_card/
mod.rs1pub mod incoming;
7pub mod outgoing;
8
9use super::engine::{ConfigSyncEngine, EngineConfig, SyncEngineStorageImpl};
10use super::{
11 MergeResult, Metadata, ProcessIncomingRecordImpl, ProcessOutgoingRecordImpl, SyncRecord,
12 UnknownFields,
13};
14use crate::db::models::credit_card::InternalCreditCard;
15use crate::encryption::EncryptorDecryptor;
16use crate::error::*;
17use crate::sync_merge_field_check;
18use incoming::IncomingCreditCardsImpl;
19use outgoing::OutgoingCreditCardsImpl;
20use rusqlite::Transaction;
21use serde::{Deserialize, Serialize};
22use std::sync::Arc;
23use sync_guid::Guid;
24use types::Timestamp;
25
26pub(crate) fn create_engine(store: Arc<crate::Store>) -> ConfigSyncEngine<InternalCreditCard> {
28 ConfigSyncEngine::new(
29 EngineConfig {
30 namespace: "credit_cards".to_string(),
31 collection: "creditcards".into(),
32 },
33 store,
34 Box::new(CreditCardsEngineStorageImpl {}),
35 )
36}
37
38pub(super) struct CreditCardsEngineStorageImpl {}
39
40impl SyncEngineStorageImpl<InternalCreditCard> for CreditCardsEngineStorageImpl {
41 fn get_incoming_impl(
42 &self,
43 enc_key: &Option<String>,
44 ) -> Result<Box<dyn ProcessIncomingRecordImpl<Record = InternalCreditCard>>> {
45 let enc_key = match enc_key {
46 None => return Err(Error::MissingEncryptionKey),
47 Some(enc_key) => enc_key,
48 };
49 let encdec = EncryptorDecryptor::new(enc_key)?;
50 Ok(Box::new(IncomingCreditCardsImpl { encdec }))
51 }
52
53 fn reset_storage(&self, tx: &Transaction<'_>) -> Result<()> {
54 tx.execute_batch(
55 "DELETE FROM credit_cards_mirror;
56 DELETE FROM credit_cards_tombstones;",
57 )?;
58 Ok(())
59 }
60
61 fn get_outgoing_impl(
62 &self,
63 enc_key: &Option<String>,
64 ) -> Result<Box<dyn ProcessOutgoingRecordImpl<Record = InternalCreditCard>>> {
65 let enc_key = match enc_key {
66 None => return Err(Error::MissingEncryptionKey),
67 Some(enc_key) => enc_key,
68 };
69 let encdec = EncryptorDecryptor::new(enc_key)?;
70 Ok(Box::new(OutgoingCreditCardsImpl { encdec }))
71 }
72}
73
74#[derive(Default, Debug, Deserialize, Serialize)]
77pub(crate) struct CreditCardPayload {
78 id: Guid,
79
80 pub(super) entry: PayloadEntry,
83}
84
85#[derive(Default, Debug, Deserialize, Serialize)]
92#[serde(default, rename_all = "kebab-case")]
93pub(super) struct PayloadEntry {
94 pub cc_name: String,
95 pub cc_number: String,
96 pub cc_exp_month: i64,
97 pub cc_exp_year: i64,
98 pub cc_type: String,
99 #[serde(rename = "timeCreated")]
101 pub time_created: Timestamp,
102 #[serde(rename = "timeLastUsed")]
103 pub time_last_used: Timestamp,
104 #[serde(rename = "timeLastModified")]
105 pub time_last_modified: Timestamp,
106 #[serde(rename = "timesUsed")]
107 pub times_used: i64,
108 pub version: u32, #[serde(flatten)]
112 pub unknown_fields: UnknownFields,
113}
114
115impl InternalCreditCard {
116 fn from_payload(p: CreditCardPayload, encdec: &EncryptorDecryptor) -> Result<Self> {
117 if p.entry.version != 3 {
118 return Err(Error::InvalidSyncPayload(format!(
121 "invalid version - {}",
122 p.entry.version
123 )));
124 }
125 let cc_number_enc = encdec.encrypt(&p.entry.cc_number)?;
127 let cc_number_last_4 = get_last_4(&p.entry.cc_number);
128
129 Ok(InternalCreditCard {
130 guid: p.id,
131 cc_name: p.entry.cc_name,
132 cc_number_enc,
133 cc_number_last_4,
134 cc_exp_month: p.entry.cc_exp_month,
135 cc_exp_year: p.entry.cc_exp_year,
136 cc_type: p.entry.cc_type,
137 metadata: Metadata {
138 time_created: p.entry.time_created,
139 time_last_used: p.entry.time_last_used,
140 time_last_modified: p.entry.time_last_modified,
141 times_used: p.entry.times_used,
142 sync_change_counter: 0,
143 },
144 })
145 }
146
147 pub(crate) fn into_payload(self, encdec: &EncryptorDecryptor) -> Result<CreditCardPayload> {
148 let cc_number = encdec.decrypt(&self.cc_number_enc)?;
149 Ok(CreditCardPayload {
150 id: self.guid,
151 entry: PayloadEntry {
152 cc_name: self.cc_name,
153 cc_number,
154 cc_exp_month: self.cc_exp_month,
155 cc_exp_year: self.cc_exp_year,
156 cc_type: self.cc_type,
157 time_created: self.metadata.time_created,
158 time_last_used: self.metadata.time_last_used,
159 time_last_modified: self.metadata.time_last_modified,
160 times_used: self.metadata.times_used,
161 version: 3,
162 unknown_fields: Default::default(),
163 },
164 })
165 }
166}
167
168impl SyncRecord for InternalCreditCard {
169 fn record_name() -> &'static str {
170 "CreditCard"
171 }
172
173 fn id(&self) -> &Guid {
174 &self.guid
175 }
176
177 fn metadata(&self) -> &Metadata {
178 &self.metadata
179 }
180
181 fn metadata_mut(&mut self) -> &mut Metadata {
182 &mut self.metadata
183 }
184
185 #[allow(clippy::cognitive_complexity)] fn merge(incoming: &Self, local: &Self, mirror: &Option<Self>) -> MergeResult<Self> {
193 let mut merged_record: Self = Default::default();
194 assert_eq!(incoming.guid, local.guid);
196
197 if let Some(m) = mirror {
198 assert_eq!(incoming.guid, m.guid)
199 };
200
201 merged_record.guid = incoming.guid.clone();
202
203 sync_merge_field_check!(cc_name, incoming, local, mirror, merged_record);
204 sync_merge_field_check!(cc_number_enc, incoming, local, mirror, merged_record);
208 sync_merge_field_check!(cc_number_last_4, incoming, local, mirror, merged_record);
209 sync_merge_field_check!(cc_exp_month, incoming, local, mirror, merged_record);
210 sync_merge_field_check!(cc_exp_year, incoming, local, mirror, merged_record);
211 sync_merge_field_check!(cc_type, incoming, local, mirror, merged_record);
212
213 merged_record.metadata = incoming.metadata;
214 merged_record
215 .metadata
216 .merge(&local.metadata, mirror.as_ref().map(|m| m.metadata()));
217
218 MergeResult::Merged {
219 merged: merged_record,
220 }
221 }
222}
223
224fn get_forked_record(local_record: InternalCreditCard) -> InternalCreditCard {
227 let mut local_record_data = local_record;
228 local_record_data.guid = Guid::random();
229 local_record_data.metadata.time_created = Timestamp::now();
230 local_record_data.metadata.time_last_used = Timestamp::now();
231 local_record_data.metadata.time_last_modified = Timestamp::now();
232 local_record_data.metadata.times_used = 0;
233 local_record_data.metadata.sync_change_counter = 1;
234
235 local_record_data
236}
237
238fn get_last_4(v: &str) -> String {
241 v.chars()
242 .rev()
243 .take(4)
244 .collect::<Vec<_>>()
245 .into_iter()
246 .rev()
247 .collect::<String>()
248}
249#[test]
250fn test_last_4() {
251 assert_eq!(get_last_4("testing"), "ting".to_string());
252 assert_eq!(get_last_4("abc"), "abc".to_string());
253 assert_eq!(get_last_4(""), "".to_string());
254}
255
256#[test]
257fn test_to_from_payload() {
258 nss::ensure_initialized();
259 let key = crate::encryption::create_autofill_key().unwrap();
260 let cc_number = "1234567812345678";
261 let cc_number_enc =
262 crate::encryption::encrypt_string(key.clone(), cc_number.to_string()).unwrap();
263 let cc = InternalCreditCard {
264 cc_name: "Shaggy".to_string(),
265 cc_number_enc,
266 cc_number_last_4: "5678".to_string(),
267 cc_exp_month: 12,
268 cc_exp_year: 2021,
269 cc_type: "foo".to_string(),
270 ..Default::default()
271 };
272 let encdec = EncryptorDecryptor::new(&key).unwrap();
273 let payload: CreditCardPayload = cc.clone().into_payload(&encdec).unwrap();
274
275 assert_eq!(payload.id, cc.guid);
276 assert_eq!(payload.entry.cc_name, "Shaggy".to_string());
277 assert_eq!(payload.entry.cc_number, cc_number.to_string());
278 assert_eq!(payload.entry.cc_exp_month, 12);
279 assert_eq!(payload.entry.cc_exp_year, 2021);
280 assert_eq!(payload.entry.cc_type, "foo".to_string());
281
282 let cc2 = InternalCreditCard::from_payload(payload, &encdec).unwrap();
284 assert_eq!(cc2.guid, cc.guid);
287 assert_eq!(cc2.cc_name, "Shaggy".to_string());
288 assert_eq!(cc2.cc_number_last_4, cc.cc_number_last_4);
289 assert_eq!(cc2.cc_exp_month, cc.cc_exp_month);
290 assert_eq!(cc2.cc_exp_year, cc.cc_exp_year);
291 assert_eq!(cc2.cc_type, cc.cc_type);
292 assert_eq!(
294 crate::encryption::decrypt_string(key, cc2.cc_number_enc.clone()).unwrap(),
295 cc_number
296 );
297 assert_ne!(cc2.cc_number_enc, cc.cc_number_enc);
299}