Skip to content

[PM-23645] Update cards to support pin and valid_from #374

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. Weโ€™ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 46 additions & 4 deletions crates/bitwarden-exporters/src/cxf/card.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<Card> for Vec<Credential> {
fn from(value: Card) -> Self {
Expand Down Expand Up @@ -57,6 +57,23 @@ impl From<&CreditCardCredential> for Card {
}
}

pub(super) fn to_card(credential: &CreditCardCredential) -> (Card, Vec<Field>) {
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
Expand All @@ -83,6 +100,7 @@ fn sanitize_brand(value: &str) -> Option<String> {

#[cfg(test)]
mod tests {
use bitwarden_vault::FieldType;
use chrono::Month;
use credential_exchange_format::EditableFieldYearMonth;

Expand Down Expand Up @@ -150,23 +168,47 @@ 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,
month: Month::December,
}
.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,
},
]
)
}
}
41 changes: 40 additions & 1 deletion crates/bitwarden-exporters/src/cxf/editable_field.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use bitwarden_vault::FieldType;
use credential_exchange_format::{
EditableField, EditableFieldBoolean, EditableFieldConcealedString, EditableFieldDate,
EditableFieldString, EditableFieldWifiNetworkSecurityType,
EditableFieldString, EditableFieldWifiNetworkSecurityType, EditableFieldYearMonth,
};

use crate::Field;
Expand Down Expand Up @@ -66,6 +66,18 @@ impl EditableFieldToField for EditableField<EditableFieldDate> {
}
}

impl EditableFieldToField for EditableField<EditableFieldYearMonth> {
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::*;
Expand Down Expand Up @@ -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,
}
);
}
}
7 changes: 5 additions & 2 deletions crates/bitwarden-exporters/src/cxf/import.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -67,14 +68,16 @@ fn parse_item(value: Item) -> Vec<ImportingCipher> {
.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,
Expand Down
Loading