diff --git a/crates/bitwarden-exporters/src/cxf/card.rs b/crates/bitwarden-exporters/src/cxf/card.rs index 0498037e8..22e6c99d8 100644 --- a/crates/bitwarden-exporters/src/cxf/card.rs +++ b/crates/bitwarden-exporters/src/cxf/card.rs @@ -7,7 +7,7 @@ use chrono::Month; use credential_exchange_format::{Credential, CreditCardCredential, EditableFieldYearMonth}; use num_traits::FromPrimitive; -use crate::Card; +use crate::{cxf::editable_field::create_field, Card, Field}; impl From for Vec { fn from(value: Card) -> Self { @@ -57,6 +57,23 @@ impl From<&CreditCardCredential> for Card { } } +pub(super) fn to_card(credential: &CreditCardCredential) -> (Card, Vec) { + let card = credential.into(); + + let fields = [ + credential.pin.as_ref().map(|v| create_field("PIN", v)), + credential + .valid_from + .as_ref() + .map(|v| create_field("Valid From", v)), + ] + .into_iter() + .flatten() + .collect(); + + (card, fields) +} + /// Sanitize credit card brand /// /// Performs a fuzzy match on the string to find a matching brand. By converting to lowercase and @@ -83,6 +100,7 @@ fn sanitize_brand(value: &str) -> Option { #[cfg(test)] mod tests { + use bitwarden_vault::FieldType; use chrono::Month; use credential_exchange_format::EditableFieldYearMonth; @@ -150,7 +168,7 @@ mod tests { full_name: Some("John Doe".to_string().into()), card_type: Some("Visa".to_string().into()), verification_number: Some("123".to_string().into()), - pin: None, + pin: Some("4567".to_string().into()), expiry_date: Some( EditableFieldYearMonth { year: 2025, @@ -158,15 +176,39 @@ mod tests { } .into(), ), - valid_from: None, + valid_from: Some( + EditableFieldYearMonth { + year: 2024, + month: Month::January, + } + .into(), + ), }; - let card: Card = (&credit_card).into(); + let (card, fields) = to_card(&credit_card); assert_eq!(card.cardholder_name, Some("John Doe".to_string())); assert_eq!(card.exp_month, Some("12".to_string())); assert_eq!(card.exp_year, Some("2025".to_string())); assert_eq!(card.code, Some("123".to_string())); assert_eq!(card.brand, Some("Visa".to_string())); assert_eq!(card.number, Some("4111111111111111".to_string())); + + assert_eq!( + fields, + vec![ + Field { + name: Some("PIN".to_string()), + value: Some("4567".to_string()), + r#type: FieldType::Hidden as u8, + linked_id: None, + }, + Field { + name: Some("Valid From".to_string()), + value: Some("2024-01".to_string()), + r#type: FieldType::Text as u8, + linked_id: None, + }, + ] + ) } } diff --git a/crates/bitwarden-exporters/src/cxf/editable_field.rs b/crates/bitwarden-exporters/src/cxf/editable_field.rs index f611d2810..fbf432c08 100644 --- a/crates/bitwarden-exporters/src/cxf/editable_field.rs +++ b/crates/bitwarden-exporters/src/cxf/editable_field.rs @@ -1,7 +1,7 @@ use bitwarden_vault::FieldType; use credential_exchange_format::{ EditableField, EditableFieldBoolean, EditableFieldConcealedString, EditableFieldDate, - EditableFieldString, EditableFieldWifiNetworkSecurityType, + EditableFieldString, EditableFieldWifiNetworkSecurityType, EditableFieldYearMonth, }; use crate::Field; @@ -66,6 +66,18 @@ impl EditableFieldToField for EditableField { } } +impl EditableFieldToField for EditableField { + const FIELD_TYPE: FieldType = FieldType::Text; + + fn field_value(&self) -> String { + format!( + "{:04}-{:02}", + self.value.year, + self.value.month.number_from_month() + ) + } +} + /// Convert WiFi security type enum to human-readable string fn security_type_to_string(security_type: &EditableFieldWifiNetworkSecurityType) -> &str { use EditableFieldWifiNetworkSecurityType::*; @@ -248,4 +260,31 @@ mod tests { } ); } + + #[test] + fn test_create_field_year_month() { + use chrono::Month; + + let editable_field = EditableField { + id: None, + label: None, + value: EditableFieldYearMonth { + year: 2025, + month: Month::December, + }, + extensions: None, + }; + + let field = create_field("Card Expiry", &editable_field); + + assert_eq!( + field, + Field { + name: Some("Card Expiry".to_string()), + value: Some("2025-12".to_string()), + r#type: FieldType::Text as u8, + linked_id: None, + } + ); + } } diff --git a/crates/bitwarden-exporters/src/cxf/import.rs b/crates/bitwarden-exporters/src/cxf/import.rs index 7d420f72f..3b047822e 100644 --- a/crates/bitwarden-exporters/src/cxf/import.rs +++ b/crates/bitwarden-exporters/src/cxf/import.rs @@ -7,6 +7,7 @@ use credential_exchange_format::{ use crate::{ cxf::{ api_key::api_key_to_fields, + card::to_card, login::{to_fields, to_login}, wifi::wifi_to_fields, CxfError, @@ -67,14 +68,16 @@ fn parse_item(value: Item) -> Vec { .first() .expect("Credit card is not empty"); + let (card, fields) = to_card(credit_card); + output.push(ImportingCipher { folder_id: None, // TODO: Handle folders name: value.title.clone(), notes: None, - r#type: CipherType::Card(Box::new(credit_card.into())), + r#type: CipherType::Card(Box::new(card)), favorite: false, reprompt: 0, - fields: scope.map(to_fields).unwrap_or_default(), + fields: [fields, scope.map(to_fields).unwrap_or_default()].concat(), revision_date, creation_date, deleted_date: None,