1use base64::{engine::general_purpose::STANDARD, Engine};
2use bitwarden_api_api::models::{CipherLoginModel, CipherLoginUriModel};
3use bitwarden_core::{
4 key_management::{KeyIds, SymmetricKeyId},
5 require,
6};
7use bitwarden_crypto::{CryptoError, Decryptable, EncString, Encryptable, KeyStoreContext};
8use chrono::{DateTime, Utc};
9use serde::{Deserialize, Serialize};
10use serde_repr::{Deserialize_repr, Serialize_repr};
11#[cfg(feature = "wasm")]
12use tsify_next::Tsify;
13#[cfg(feature = "wasm")]
14use wasm_bindgen::prelude::wasm_bindgen;
15
16use super::cipher::CipherKind;
17use crate::{cipher::cipher::CopyableCipherFields, Cipher, VaultParseError};
18
19#[allow(missing_docs)]
20#[derive(Clone, Copy, Serialize_repr, Deserialize_repr, Debug, PartialEq)]
21#[repr(u8)]
22#[serde(rename_all = "camelCase", deny_unknown_fields)]
23#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
24#[cfg_attr(feature = "wasm", wasm_bindgen)]
25pub enum UriMatchType {
26 Domain = 0,
27 Host = 1,
28 StartsWith = 2,
29 Exact = 3,
30 RegularExpression = 4,
31 Never = 5,
32}
33
34#[derive(Serialize, Deserialize, Debug, Clone)]
35#[serde(rename_all = "camelCase", deny_unknown_fields)]
36#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
37#[cfg_attr(feature = "wasm", derive(Tsify), tsify(into_wasm_abi, from_wasm_abi))]
38pub struct LoginUri {
39 pub uri: Option<EncString>,
40 pub r#match: Option<UriMatchType>,
41 pub uri_checksum: Option<EncString>,
42}
43
44#[allow(missing_docs)]
45#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
46#[serde(rename_all = "camelCase", deny_unknown_fields)]
47#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
48#[cfg_attr(feature = "wasm", derive(Tsify), tsify(into_wasm_abi, from_wasm_abi))]
49pub struct LoginUriView {
50 pub uri: Option<String>,
51 pub r#match: Option<UriMatchType>,
52 pub uri_checksum: Option<String>,
53}
54
55impl LoginUriView {
56 pub(crate) fn is_checksum_valid(&self) -> bool {
57 let Some(uri) = &self.uri else {
58 return false;
59 };
60 let Some(cs) = &self.uri_checksum else {
61 return false;
62 };
63 let Ok(cs) = STANDARD.decode(cs) else {
64 return false;
65 };
66
67 use sha2::Digest;
68 let uri_hash = sha2::Sha256::new().chain_update(uri.as_bytes()).finalize();
69
70 uri_hash.as_slice() == cs
71 }
72
73 pub(crate) fn generate_checksum(&mut self) {
74 if let Some(uri) = &self.uri {
75 use sha2::Digest;
76 let uri_hash = sha2::Sha256::new().chain_update(uri.as_bytes()).finalize();
77 let uri_hash = STANDARD.encode(uri_hash.as_slice());
78 self.uri_checksum = Some(uri_hash);
79 }
80 }
81}
82
83#[allow(missing_docs)]
84#[derive(Serialize, Deserialize, Debug, Clone)]
85#[serde(rename_all = "camelCase", deny_unknown_fields)]
86#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
87#[cfg_attr(feature = "wasm", derive(Tsify), tsify(into_wasm_abi, from_wasm_abi))]
88pub struct Fido2Credential {
89 pub credential_id: EncString,
90 pub key_type: EncString,
91 pub key_algorithm: EncString,
92 pub key_curve: EncString,
93 pub key_value: EncString,
94 pub rp_id: EncString,
95 pub user_handle: Option<EncString>,
96 pub user_name: Option<EncString>,
97 pub counter: EncString,
98 pub rp_name: Option<EncString>,
99 pub user_display_name: Option<EncString>,
100 pub discoverable: EncString,
101 pub creation_date: DateTime<Utc>,
102}
103
104#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
105#[serde(rename_all = "camelCase", deny_unknown_fields)]
106#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
107#[cfg_attr(feature = "wasm", derive(Tsify), tsify(into_wasm_abi, from_wasm_abi))]
108pub struct Fido2CredentialListView {
109 pub credential_id: String,
110 pub rp_id: String,
111 pub user_handle: Option<String>,
112 pub user_name: Option<String>,
113 pub user_display_name: Option<String>,
114}
115
116#[allow(missing_docs)]
117#[derive(Serialize, Deserialize, Debug, Clone)]
118#[serde(rename_all = "camelCase", deny_unknown_fields)]
119#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
120#[cfg_attr(feature = "wasm", derive(Tsify), tsify(into_wasm_abi, from_wasm_abi))]
121pub struct Fido2CredentialView {
122 pub credential_id: String,
123 pub key_type: String,
124 pub key_algorithm: String,
125 pub key_curve: String,
126 pub key_value: EncString,
129 pub rp_id: String,
130 pub user_handle: Option<String>,
131 pub user_name: Option<String>,
132 pub counter: String,
133 pub rp_name: Option<String>,
134 pub user_display_name: Option<String>,
135 pub discoverable: String,
136 pub creation_date: DateTime<Utc>,
137}
138
139#[allow(missing_docs)]
142#[derive(Serialize, Deserialize, Debug, Clone)]
143#[serde(rename_all = "camelCase", deny_unknown_fields)]
144pub struct Fido2CredentialFullView {
145 pub credential_id: String,
146 pub key_type: String,
147 pub key_algorithm: String,
148 pub key_curve: String,
149 pub key_value: String,
150 pub rp_id: String,
151 pub user_handle: Option<String>,
152 pub user_name: Option<String>,
153 pub counter: String,
154 pub rp_name: Option<String>,
155 pub user_display_name: Option<String>,
156 pub discoverable: String,
157 pub creation_date: DateTime<Utc>,
158}
159
160#[allow(missing_docs)]
164#[derive(Serialize, Deserialize, Debug, Clone)]
165#[serde(rename_all = "camelCase", deny_unknown_fields)]
166#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
167#[cfg_attr(feature = "wasm", derive(Tsify), tsify(into_wasm_abi, from_wasm_abi))]
168pub struct Fido2CredentialNewView {
169 pub credential_id: String,
170 pub key_type: String,
171 pub key_algorithm: String,
172 pub key_curve: String,
173 pub rp_id: String,
174 pub user_handle: Option<String>,
175 pub user_name: Option<String>,
176 pub counter: String,
177 pub rp_name: Option<String>,
178 pub user_display_name: Option<String>,
179 pub creation_date: DateTime<Utc>,
180}
181
182impl From<Fido2CredentialFullView> for Fido2CredentialNewView {
183 fn from(value: Fido2CredentialFullView) -> Self {
184 Fido2CredentialNewView {
185 credential_id: value.credential_id,
186 key_type: value.key_type,
187 key_algorithm: value.key_algorithm,
188 key_curve: value.key_curve,
189 rp_id: value.rp_id,
190 user_handle: value.user_handle,
191 user_name: value.user_name,
192 counter: value.counter,
193 rp_name: value.rp_name,
194 user_display_name: value.user_display_name,
195 creation_date: value.creation_date,
196 }
197 }
198}
199
200impl Encryptable<KeyIds, SymmetricKeyId, Fido2Credential> for Fido2CredentialFullView {
201 fn encrypt(
202 &self,
203 ctx: &mut KeyStoreContext<KeyIds>,
204 key: SymmetricKeyId,
205 ) -> Result<Fido2Credential, CryptoError> {
206 Ok(Fido2Credential {
207 credential_id: self.credential_id.encrypt(ctx, key)?,
208 key_type: self.key_type.encrypt(ctx, key)?,
209 key_algorithm: self.key_algorithm.encrypt(ctx, key)?,
210 key_curve: self.key_curve.encrypt(ctx, key)?,
211 key_value: self.key_value.encrypt(ctx, key)?,
212 rp_id: self.rp_id.encrypt(ctx, key)?,
213 user_handle: self
214 .user_handle
215 .as_ref()
216 .map(|h| h.encrypt(ctx, key))
217 .transpose()?,
218 user_name: self.user_name.encrypt(ctx, key)?,
219 counter: self.counter.encrypt(ctx, key)?,
220 rp_name: self.rp_name.encrypt(ctx, key)?,
221 user_display_name: self.user_display_name.encrypt(ctx, key)?,
222 discoverable: self.discoverable.encrypt(ctx, key)?,
223 creation_date: self.creation_date,
224 })
225 }
226}
227
228impl Decryptable<KeyIds, SymmetricKeyId, Fido2CredentialFullView> for Fido2Credential {
229 fn decrypt(
230 &self,
231 ctx: &mut KeyStoreContext<KeyIds>,
232 key: SymmetricKeyId,
233 ) -> Result<Fido2CredentialFullView, CryptoError> {
234 Ok(Fido2CredentialFullView {
235 credential_id: self.credential_id.decrypt(ctx, key)?,
236 key_type: self.key_type.decrypt(ctx, key)?,
237 key_algorithm: self.key_algorithm.decrypt(ctx, key)?,
238 key_curve: self.key_curve.decrypt(ctx, key)?,
239 key_value: self.key_value.decrypt(ctx, key)?,
240 rp_id: self.rp_id.decrypt(ctx, key)?,
241 user_handle: self.user_handle.decrypt(ctx, key)?,
242 user_name: self.user_name.decrypt(ctx, key)?,
243 counter: self.counter.decrypt(ctx, key)?,
244 rp_name: self.rp_name.decrypt(ctx, key)?,
245 user_display_name: self.user_display_name.decrypt(ctx, key)?,
246 discoverable: self.discoverable.decrypt(ctx, key)?,
247 creation_date: self.creation_date,
248 })
249 }
250}
251
252impl Decryptable<KeyIds, SymmetricKeyId, Fido2CredentialFullView> for Fido2CredentialView {
253 fn decrypt(
254 &self,
255 ctx: &mut KeyStoreContext<KeyIds>,
256 key: SymmetricKeyId,
257 ) -> Result<Fido2CredentialFullView, CryptoError> {
258 Ok(Fido2CredentialFullView {
259 credential_id: self.credential_id.clone(),
260 key_type: self.key_type.clone(),
261 key_algorithm: self.key_algorithm.clone(),
262 key_curve: self.key_curve.clone(),
263 key_value: self.key_value.decrypt(ctx, key)?,
264 rp_id: self.rp_id.clone(),
265 user_handle: self.user_handle.clone(),
266 user_name: self.user_name.clone(),
267 counter: self.counter.clone(),
268 rp_name: self.rp_name.clone(),
269 user_display_name: self.user_display_name.clone(),
270 discoverable: self.discoverable.clone(),
271 creation_date: self.creation_date,
272 })
273 }
274}
275
276#[allow(missing_docs)]
277#[derive(Serialize, Deserialize, Debug, Clone)]
278#[serde(rename_all = "camelCase", deny_unknown_fields)]
279#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
280#[cfg_attr(feature = "wasm", derive(Tsify), tsify(into_wasm_abi, from_wasm_abi))]
281pub struct Login {
282 pub username: Option<EncString>,
283 pub password: Option<EncString>,
284 pub password_revision_date: Option<DateTime<Utc>>,
285
286 pub uris: Option<Vec<LoginUri>>,
287 pub totp: Option<EncString>,
288 pub autofill_on_page_load: Option<bool>,
289
290 pub fido2_credentials: Option<Vec<Fido2Credential>>,
291}
292
293#[allow(missing_docs)]
294#[derive(Serialize, Deserialize, Debug, Clone)]
295#[serde(rename_all = "camelCase", deny_unknown_fields)]
296#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
297#[cfg_attr(feature = "wasm", derive(Tsify), tsify(into_wasm_abi, from_wasm_abi))]
298pub struct LoginView {
299 pub username: Option<String>,
300 pub password: Option<String>,
301 pub password_revision_date: Option<DateTime<Utc>>,
302
303 pub uris: Option<Vec<LoginUriView>>,
304 pub totp: Option<String>,
305 pub autofill_on_page_load: Option<bool>,
306
307 pub fido2_credentials: Option<Vec<Fido2Credential>>,
309}
310
311#[allow(missing_docs)]
312#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
313#[serde(rename_all = "camelCase", deny_unknown_fields)]
314#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
315#[cfg_attr(feature = "wasm", derive(Tsify), tsify(into_wasm_abi, from_wasm_abi))]
316pub struct LoginListView {
317 pub fido2_credentials: Option<Vec<Fido2CredentialListView>>,
318 pub has_fido2: bool,
319 pub username: Option<String>,
320 pub totp: Option<EncString>,
322 pub uris: Option<Vec<LoginUriView>>,
323}
324
325impl Encryptable<KeyIds, SymmetricKeyId, LoginUri> for LoginUriView {
326 fn encrypt(
327 &self,
328 ctx: &mut KeyStoreContext<KeyIds>,
329 key: SymmetricKeyId,
330 ) -> Result<LoginUri, CryptoError> {
331 Ok(LoginUri {
332 uri: self.uri.encrypt(ctx, key)?,
333 r#match: self.r#match,
334 uri_checksum: self.uri_checksum.encrypt(ctx, key)?,
335 })
336 }
337}
338
339impl Encryptable<KeyIds, SymmetricKeyId, Login> for LoginView {
340 fn encrypt(
341 &self,
342 ctx: &mut KeyStoreContext<KeyIds>,
343 key: SymmetricKeyId,
344 ) -> Result<Login, CryptoError> {
345 Ok(Login {
346 username: self.username.encrypt(ctx, key)?,
347 password: self.password.encrypt(ctx, key)?,
348 password_revision_date: self.password_revision_date,
349 uris: self.uris.encrypt(ctx, key)?,
350 totp: self.totp.encrypt(ctx, key)?,
351 autofill_on_page_load: self.autofill_on_page_load,
352 fido2_credentials: self.fido2_credentials.clone(),
353 })
354 }
355}
356
357impl Decryptable<KeyIds, SymmetricKeyId, LoginUriView> for LoginUri {
358 fn decrypt(
359 &self,
360 ctx: &mut KeyStoreContext<KeyIds>,
361 key: SymmetricKeyId,
362 ) -> Result<LoginUriView, CryptoError> {
363 Ok(LoginUriView {
364 uri: self.uri.decrypt(ctx, key)?,
365 r#match: self.r#match,
366 uri_checksum: self.uri_checksum.decrypt(ctx, key)?,
367 })
368 }
369}
370
371impl Decryptable<KeyIds, SymmetricKeyId, LoginView> for Login {
372 fn decrypt(
373 &self,
374 ctx: &mut KeyStoreContext<KeyIds>,
375 key: SymmetricKeyId,
376 ) -> Result<LoginView, CryptoError> {
377 Ok(LoginView {
378 username: self.username.decrypt(ctx, key).ok().flatten(),
379 password: self.password.decrypt(ctx, key).ok().flatten(),
380 password_revision_date: self.password_revision_date,
381 uris: self.uris.decrypt(ctx, key).ok().flatten(),
382 totp: self.totp.decrypt(ctx, key).ok().flatten(),
383 autofill_on_page_load: self.autofill_on_page_load,
384 fido2_credentials: self.fido2_credentials.clone(),
385 })
386 }
387}
388
389impl Decryptable<KeyIds, SymmetricKeyId, LoginListView> for Login {
390 fn decrypt(
391 &self,
392 ctx: &mut KeyStoreContext<KeyIds>,
393 key: SymmetricKeyId,
394 ) -> Result<LoginListView, CryptoError> {
395 Ok(LoginListView {
396 fido2_credentials: self
397 .fido2_credentials
398 .as_ref()
399 .map(|fido2_credentials| fido2_credentials.decrypt(ctx, key))
400 .transpose()?,
401 has_fido2: self.fido2_credentials.is_some(),
402 username: self.username.decrypt(ctx, key).ok().flatten(),
403 totp: self.totp.clone(),
404 uris: self.uris.decrypt(ctx, key).ok().flatten(),
405 })
406 }
407}
408
409impl Encryptable<KeyIds, SymmetricKeyId, Fido2Credential> for Fido2CredentialView {
410 fn encrypt(
411 &self,
412 ctx: &mut KeyStoreContext<KeyIds>,
413 key: SymmetricKeyId,
414 ) -> Result<Fido2Credential, CryptoError> {
415 Ok(Fido2Credential {
416 credential_id: self.credential_id.encrypt(ctx, key)?,
417 key_type: self.key_type.encrypt(ctx, key)?,
418 key_algorithm: self.key_algorithm.encrypt(ctx, key)?,
419 key_curve: self.key_curve.encrypt(ctx, key)?,
420 key_value: self.key_value.clone(),
421 rp_id: self.rp_id.encrypt(ctx, key)?,
422 user_handle: self
423 .user_handle
424 .as_ref()
425 .map(|h| h.encrypt(ctx, key))
426 .transpose()?,
427 user_name: self
428 .user_name
429 .as_ref()
430 .map(|n| n.encrypt(ctx, key))
431 .transpose()?,
432 counter: self.counter.encrypt(ctx, key)?,
433 rp_name: self.rp_name.encrypt(ctx, key)?,
434 user_display_name: self.user_display_name.encrypt(ctx, key)?,
435 discoverable: self.discoverable.encrypt(ctx, key)?,
436 creation_date: self.creation_date,
437 })
438 }
439}
440
441impl Decryptable<KeyIds, SymmetricKeyId, Fido2CredentialView> for Fido2Credential {
442 fn decrypt(
443 &self,
444 ctx: &mut KeyStoreContext<KeyIds>,
445 key: SymmetricKeyId,
446 ) -> Result<Fido2CredentialView, CryptoError> {
447 Ok(Fido2CredentialView {
448 credential_id: self.credential_id.decrypt(ctx, key)?,
449 key_type: self.key_type.decrypt(ctx, key)?,
450 key_algorithm: self.key_algorithm.decrypt(ctx, key)?,
451 key_curve: self.key_curve.decrypt(ctx, key)?,
452 key_value: self.key_value.clone(),
453 rp_id: self.rp_id.decrypt(ctx, key)?,
454 user_handle: self.user_handle.decrypt(ctx, key)?,
455 user_name: self.user_name.decrypt(ctx, key)?,
456 counter: self.counter.decrypt(ctx, key)?,
457 rp_name: self.rp_name.decrypt(ctx, key)?,
458 user_display_name: self.user_display_name.decrypt(ctx, key)?,
459 discoverable: self.discoverable.decrypt(ctx, key)?,
460 creation_date: self.creation_date,
461 })
462 }
463}
464
465impl Decryptable<KeyIds, SymmetricKeyId, Fido2CredentialListView> for Fido2Credential {
466 fn decrypt(
467 &self,
468 ctx: &mut KeyStoreContext<KeyIds>,
469 key: SymmetricKeyId,
470 ) -> Result<Fido2CredentialListView, CryptoError> {
471 Ok(Fido2CredentialListView {
472 credential_id: self.credential_id.decrypt(ctx, key)?,
473 rp_id: self.rp_id.decrypt(ctx, key)?,
474 user_handle: self.user_handle.decrypt(ctx, key)?,
475 user_name: self.user_name.decrypt(ctx, key)?,
476 user_display_name: self.user_display_name.decrypt(ctx, key)?,
477 })
478 }
479}
480
481impl TryFrom<CipherLoginModel> for Login {
482 type Error = VaultParseError;
483
484 fn try_from(login: CipherLoginModel) -> Result<Self, Self::Error> {
485 Ok(Self {
486 username: EncString::try_from_optional(login.username)?,
487 password: EncString::try_from_optional(login.password)?,
488 password_revision_date: login
489 .password_revision_date
490 .map(|d| d.parse())
491 .transpose()?,
492 uris: login
493 .uris
494 .map(|v| v.into_iter().map(|u| u.try_into()).collect())
495 .transpose()?,
496 totp: EncString::try_from_optional(login.totp)?,
497 autofill_on_page_load: login.autofill_on_page_load,
498 fido2_credentials: login
499 .fido2_credentials
500 .map(|v| v.into_iter().map(|c| c.try_into()).collect())
501 .transpose()?,
502 })
503 }
504}
505
506impl TryFrom<CipherLoginUriModel> for LoginUri {
507 type Error = VaultParseError;
508
509 fn try_from(uri: CipherLoginUriModel) -> Result<Self, Self::Error> {
510 Ok(Self {
511 uri: EncString::try_from_optional(uri.uri)?,
512 r#match: uri.r#match.map(|m| m.into()),
513 uri_checksum: EncString::try_from_optional(uri.uri_checksum)?,
514 })
515 }
516}
517
518impl From<bitwarden_api_api::models::UriMatchType> for UriMatchType {
519 fn from(value: bitwarden_api_api::models::UriMatchType) -> Self {
520 match value {
521 bitwarden_api_api::models::UriMatchType::Domain => Self::Domain,
522 bitwarden_api_api::models::UriMatchType::Host => Self::Host,
523 bitwarden_api_api::models::UriMatchType::StartsWith => Self::StartsWith,
524 bitwarden_api_api::models::UriMatchType::Exact => Self::Exact,
525 bitwarden_api_api::models::UriMatchType::RegularExpression => Self::RegularExpression,
526 bitwarden_api_api::models::UriMatchType::Never => Self::Never,
527 }
528 }
529}
530
531impl TryFrom<bitwarden_api_api::models::CipherFido2CredentialModel> for Fido2Credential {
532 type Error = VaultParseError;
533
534 fn try_from(
535 value: bitwarden_api_api::models::CipherFido2CredentialModel,
536 ) -> Result<Self, Self::Error> {
537 Ok(Self {
538 credential_id: require!(value.credential_id).parse()?,
539 key_type: require!(value.key_type).parse()?,
540 key_algorithm: require!(value.key_algorithm).parse()?,
541 key_curve: require!(value.key_curve).parse()?,
542 key_value: require!(value.key_value).parse()?,
543 rp_id: require!(value.rp_id).parse()?,
544 user_handle: EncString::try_from_optional(value.user_handle)
545 .ok()
546 .flatten(),
547 user_name: EncString::try_from_optional(value.user_name).ok().flatten(),
548 counter: require!(value.counter).parse()?,
549 rp_name: EncString::try_from_optional(value.rp_name).ok().flatten(),
550 user_display_name: EncString::try_from_optional(value.user_display_name)
551 .ok()
552 .flatten(),
553 discoverable: require!(value.discoverable).parse()?,
554 creation_date: value.creation_date.parse()?,
555 })
556 }
557}
558
559impl CipherKind for Login {
560 fn decrypt_subtitle(
561 &self,
562 ctx: &mut KeyStoreContext<KeyIds>,
563 key: SymmetricKeyId,
564 ) -> Result<String, CryptoError> {
565 let username: Option<String> = self.username.decrypt(ctx, key)?;
566
567 Ok(username.unwrap_or_default())
568 }
569
570 fn get_copyable_fields(&self, _: Option<&Cipher>) -> Vec<CopyableCipherFields> {
571 [
572 self.username
573 .as_ref()
574 .map(|_| CopyableCipherFields::LoginUsername),
575 self.password
576 .as_ref()
577 .map(|_| CopyableCipherFields::LoginPassword),
578 self.totp.as_ref().map(|_| CopyableCipherFields::LoginTotp),
579 ]
580 .into_iter()
581 .flatten()
582 .collect()
583 }
584}
585
586#[cfg(test)]
587mod tests {
588 use crate::{
589 cipher::cipher::{CipherKind, CopyableCipherFields},
590 Login,
591 };
592
593 #[test]
594 fn test_valid_checksum() {
595 let uri = super::LoginUriView {
596 uri: Some("https://example.com".to_string()),
597 r#match: Some(super::UriMatchType::Domain),
598 uri_checksum: Some("EAaArVRs5qV39C9S3zO0z9ynVoWeZkuNfeMpsVDQnOk=".to_string()),
599 };
600 assert!(uri.is_checksum_valid());
601 }
602
603 #[test]
604 fn test_invalid_checksum() {
605 let uri = super::LoginUriView {
606 uri: Some("https://example.com".to_string()),
607 r#match: Some(super::UriMatchType::Domain),
608 uri_checksum: Some("UtSgIv8LYfEdOu7yqjF7qXWhmouYGYC8RSr7/ryZg5Q=".to_string()),
609 };
610 assert!(!uri.is_checksum_valid());
611 }
612
613 #[test]
614 fn test_missing_checksum() {
615 let uri = super::LoginUriView {
616 uri: Some("https://example.com".to_string()),
617 r#match: Some(super::UriMatchType::Domain),
618 uri_checksum: None,
619 };
620 assert!(!uri.is_checksum_valid());
621 }
622
623 #[test]
624 fn test_generate_checksum() {
625 let mut uri = super::LoginUriView {
626 uri: Some("https://test.com".to_string()),
627 r#match: Some(super::UriMatchType::Domain),
628 uri_checksum: None,
629 };
630
631 uri.generate_checksum();
632
633 assert_eq!(
634 uri.uri_checksum.unwrap().as_str(),
635 "OWk2vQvwYD1nhLZdA+ltrpBWbDa2JmHyjUEWxRZSS8w="
636 );
637 }
638
639 #[test]
640 fn test_get_copyable_fields_login_password() {
641 let login_with_password = Login {
642 username: None,
643 password: Some("2.38t4E88QbQEkBdK+oZNHFg==|B3BiDcG3ZfEkD2BK+FMytQ==|2Dw1/f+LCfkCmCj4gKOxOu6CRnZj93qaBYUqbzy/reU=".parse().unwrap()),
644 password_revision_date: None,
645 uris: None,
646 totp: None,
647 autofill_on_page_load: None,
648 fido2_credentials: None,
649 };
650
651 let copyable_fields = login_with_password.get_copyable_fields(None);
652 assert_eq!(copyable_fields, vec![CopyableCipherFields::LoginPassword]);
653 }
654
655 #[test]
656 fn test_get_copyable_fields_login_username() {
657 let login_with_username = Login {
658 username: Some("2.38t4E88QbQEkBdK+oZNHFg==|B3BiDcG3ZfEkD2BK+FMytQ==|2Dw1/f+LCfkCmCj4gKOxOu6CRnZj93qaBYUqbzy/reU=".parse().unwrap()),
659 password: None,
660 password_revision_date: None,
661 uris: None,
662 totp: None,
663 autofill_on_page_load: None,
664 fido2_credentials: None,
665 };
666
667 let copyable_fields = login_with_username.get_copyable_fields(None);
668 assert_eq!(copyable_fields, vec![CopyableCipherFields::LoginUsername]);
669 }
670
671 #[test]
672 fn test_get_copyable_fields_login_everything() {
673 let login = Login {
674 username: Some("2.38t4E88QbQEkBdK+oZNHFg==|B3BiDcG3ZfEkD2BK+FMytQ==|2Dw1/f+LCfkCmCj4gKOxOu6CRnZj93qaBYUqbzy/reU=".parse().unwrap()),
675 password: Some("2.38t4E88QbQEkBdK+oZNHFg==|B3BiDcG3ZfEkD2BK+FMytQ==|2Dw1/f+LCfkCmCj4gKOxOu6CRnZj93qaBYUqbzy/reU=".parse().unwrap()),
676 password_revision_date: None,
677 uris: None,
678 totp: Some("2.38t4E88QbQEkBdK+oZNHFg==|B3BiDcG3ZfEkD2BK+FMytQ==|2Dw1/f+LCfkCmCj4gKOxOu6CRnZj93qaBYUqbzy/reU=".parse().unwrap()),
679 autofill_on_page_load: None,
680 fido2_credentials: None,
681 };
682
683 let copyable_fields = login.get_copyable_fields(None);
684 assert_eq!(
685 copyable_fields,
686 vec![
687 CopyableCipherFields::LoginUsername,
688 CopyableCipherFields::LoginPassword,
689 CopyableCipherFields::LoginTotp
690 ]
691 );
692 }
693}