diff --git a/crates/bitwarden-exporters/resources/dashlane_export.json b/crates/bitwarden-exporters/resources/dashlane_export.json new file mode 100644 index 000000000..515a30147 --- /dev/null +++ b/crates/bitwarden-exporters/resources/dashlane_export.json @@ -0,0 +1,308 @@ +{ + "items": [ + { + "id": "ezEwRjNCNEIzLUZFOEYtNDBFMi1BRkVELUMyNkUyOUMxNzIwNX0", + "creationAt": 2735237468, + "modifiedAt": 2735237468, + "title": "", + "credentials": [ + { + "type": "credit-card", + "number": { + "id": "e2Y0ZTdiMThiLTNhZGMtNGJlYS05ZmJjLTIxYjU0MWYxMDhlY30", + "fieldType": "concealed-string", + "value": "4111111111111111" + }, + "fullName": { + "id": "ezFiYmZmNjdlLTUyODEtNGZhZC04ZmRjLWFlYzYwNjJmM2ZkYn0", + "fieldType": "string", + "value": "Dashlane CC" + }, + "verificationNumber": { + "id": "e2JmODAyZjA0LTdjMzItNDY1MC05NTZjLTIzM2MxODc4MDZkMX0", + "fieldType": "concealed-string", + "value": "999" + }, + "expiryDate": { + "id": "ezRhOTYyY2QzLWUxNzktNGM1My1iMTBmLTNiNzM2OThlMGRjYn0", + "fieldType": "year-month", + "value": "2028-10" + } + } + ] + }, + { + "id": "ezM0NzhERkE1LTI0RjktNEI5MS1CNzM0LUMwQTUxRDlFNzMxNX0", + "creationAt": 2735237468, + "modifiedAt": 2735237468, + "title": "adobe.com", + "scope": { + "urls": [ + "adobe.com" + ], + "androidApps": [] + }, + "credentials": [ + { + "type": "basic-auth", + "username": { + "id": "ezY2OGE4NzA3LTQ3MGItNDU0YS05YzgwLTdkYjc4ZWRmNjhmM30", + "fieldType": "string", + "value": "dashlane@dashlane.com" + }, + "password": { + "id": "ezE2YWE0MzNhLTU2ZDktNDA3Yy04ZWNlLTYzMjRlYTExZTI0Mn0", + "fieldType": "concealed-string", + "value": "asdfgh" + } + }, + { + "type": "totp", + "secret": "JBSWY3DPEHPK3PXP", + "period": 30, + "digits": 6, + "algorithm": "sha1" + } + ] + }, + { + "id": "ezM0Nzk5NTI3LUYwQTktNDlBOS04NTRELUNFNTFBQkQzRDI2M30", + "creationAt": 2735237468, + "modifiedAt": 2735237468, + "title": "Dashlane note", + "credentials": [ + { + "type": "note", + "content": { + "id": "ezg4MWQ4MjJiLTczODQtNGU5YS05MjY4LWZlYTg1ZTFiYWQ3YX0", + "fieldType": "string", + "value": "Dashlane note content" + } + } + ] + }, + { + "id": "e0Q5ODYzMUNBLUE1RDUtNDVDQS05MDY1LUI1MDI0NkY4QkFEMX0", + "creationAt": 2735237468, + "modifiedAt": 2735237468, + "title": "Dashlane address", + "credentials": [ + { + "type": "address", + "streetAddress": { + "id": "e2IzODRlMjRlLWRlMDktNDYzMS04MDkyLWJjOTlkOGVlMDM3Mn0", + "fieldType": "string", + "value": "Addy1" + }, + "postalCode": { + "id": "ezUxZmVmMDMxLWE1NmQtNDFhOS04ZDAxLTBhNGE3ZWY4NjhjZH0", + "fieldType": "string", + "value": "12345" + }, + "city": { + "id": "ezI5ODFkZDA3LWQ1MmUtNGZjMy1hYjVkLWY4YzAzYTY1NWQ2Nn0", + "fieldType": "string", + "value": "City" + } + } + ] + }, + { + "id": "ezI2RkE1MUI4LUU0MEUtNEU3NC04NDUzLUQwQTU4MjQ2MkRDOH0", + "creationAt": 2735237468, + "modifiedAt": 2735237468, + "title": "", + "credentials": [ + { + "type": "identity-document", + "documentNumber": { + "id": "ezdlODUxM2I0LWQ2MTQtNDIyOS1iZjEyLWFlYWVmZDYxYWUwNH0", + "fieldType": "string", + "value": "LicenseNumber" + }, + "issueDate": { + "id": "e2U3YTdkZjIzLTU5NjYtNDE4YS1hYjMwLTlhMzIzZjkyYTA2OH0", + "fieldType": "date", + "value": "2025-10-09" + }, + "expiryDate": { + "id": "ezk2M2U3ZjhkLTkxOWYtNDNiNi1hYjQ0LWIyYTdmMTYyNmI5OH0", + "fieldType": "date", + "value": "2025-10-10" + } + } + ] + }, + { + "id": "e0MwQTQzRTA0LTUxNjEtNDBFNi04RjJBLTgxNThGMjQ2MEQ3NH0", + "creationAt": 2735237468, + "modifiedAt": 2735237468, + "title": "", + "credentials": [ + { + "type": "passport", + "passportNumber": { + "id": "ezM1ZGZiMjBiLWMxMzAtNGVlNy04ZTFhLTdlZTE5YTdjZGRhZH0", + "fieldType": "string", + "value": "PassportNumber" + }, + "issueDate": { + "id": "e2RmMTU4ZjlhLTM3NmItNDcyZC05NjJiLTk4MTFlYTlkYmU2NH0", + "fieldType": "date", + "value": "2025-10-09" + }, + "expiryDate": { + "id": "e2YxYTMyMjMzLWMxMWYtNDlmOS1iZGQ3LWI1M2M0NDAwNjI0Yn0", + "fieldType": "date", + "value": "2025-10-10" + }, + "issuingAuthority": { + "id": "e2U0YWRlMDYwLTQ5ZjQtNGVmMy1iMzJlLTZlMjk2MWU2ODA5Yn0", + "fieldType": "string", + "value": "PassportIssuer" + } + } + ] + }, + { + "id": "ezA0NTBCMUQxLUNEOTctNDk0Qy05NzM0LTM4MUJBNjk2QzcwQ30", + "creationAt": 2735237468, + "modifiedAt": 2735237468, + "title": "", + "credentials": [ + { + "type": "drivers-license", + "issueDate": { + "id": "e2Y2N2IxYWMwLTkyMmItNDI0My05NGM0LTA0MTdhNDVkOTUzOX0", + "fieldType": "date", + "value": "2025-10-09" + }, + "expiryDate": { + "id": "e2MzYjYyZDVlLWI2YjItNDMzMi04N2M5LTYxZjgxMTM4NTQ3MX0", + "fieldType": "date", + "value": "2025-10-10" + }, + "licenseNumber": { + "id": "ezAyYWRhN2UwLTM4NzUtNGE5YS04MTIyLWFjZDFjZTc2MWZlZn0", + "fieldType": "string", + "value": "LicenseNumber" + } + } + ] + }, + { + "id": "ezc2NTRDQTQ2LTc4MzMtNDU1Qy05MUU0LTE0NUU1MzI0NjUxMX0", + "creationAt": 2735237468, + "modifiedAt": 2735237468, + "title": "", + "credentials": [ + { + "type": "identity-document", + "identificationNumber": { + "id": "e2FhYmI3MDlhLWQ0NDAtNGQzNS04MjFlLWE5Mjk4ZjBmNDIyY30", + "fieldType": "string", + "value": "SSN" + } + } + ] + }, + { + "id": "e0VDOTM4NEE5LUZBMzktNEQ4Mi1CQTBDLUE3NDFGRjdENTVFOX0", + "creationAt": 2735237468, + "modifiedAt": 2735237468, + "title": "", + "credentials": [ + { + "type": "identity-document", + "documentNumber": { + "id": "ezZmYWQ0ZGQwLWUwMjQtNDEzZi1hOWY4LWQ4OWFkMmJlYTdiY30", + "fieldType": "string", + "value": "TaxNumber" + } + } + ] + }, + { + "id": "ezY1Q0Q0Mzk4LTZFQTMtNDEzMi1COTQyLUQwMzVFRjM1N0RCQ30", + "creationAt": 2735237468, + "modifiedAt": 2735237468, + "title": "WifiOrg", + "credentials": [ + { + "type": "wifi", + "ssid": { + "id": "ezI5YmE4ZjY0LTg0MjEtNDkwOC05ZTNhLTljY2NlZGUyY2I5Nn0", + "fieldType": "string", + "value": "Dashlane WiFi" + }, + "passphrase": { + "id": "ezY2MGFlOWZmLThiZmUtNDY2Mi1hZDE0LWM5ODljNGZmZWI4MH0", + "fieldType": "concealed-string", + "value": "123456" + }, + "hidden": { + "id": "e2M4MGQ1N2I3LTU4ZGMtNGY3OS1hOWU4LWVhNTZhZjI3ZDE5NX0", + "fieldType": "boolean", + "value": "false" + } + } + ] + }, + { + "id": "ezkxQkRFREM3LTQ5NTYtNDI5RS1BMTJFLUQxMUVENjRFQkRCMn0", + "creationAt": 2735237468, + "modifiedAt": 2735237468, + "title": "Dashlane company", + "credentials": [ + { + "type": "identity-document", + "fullName": { + "id": "e2I2N2Y3MTRhLWYzYjUtNGQ3NS1iNTMwLTQ5NDU5NTVhOWVhZH0", + "fieldType": "string", + "value": "Dashlane company" + }, + "issuingAuthority": { + "id": "ezQ2ODA5NTE2LTAzYzItNDMyYi1iZDhiLWFlNmQ0ZTY2OWI4ZX0", + "fieldType": "string", + "value": "Company title" + } + } + ] + }, + { + "id": "ezZCMkI4MDZFLUUyQUUtNDFDQi04NUY1LTlCOTg5MDE3Q0YwRH0", + "creationAt": 2735237468, + "modifiedAt": 2735237468, + "title": "Dashlane Person", + "credentials": [ + { + "type": "person-name", + "title": { + "id": "ezQwYTM5ZDExLTcyYmItNGFhMS04NzYxLTYwNjA0M2ExZTA0OH0", + "fieldType": "string", + "value": "mr" + }, + "given": { + "id": "ezE2NzNjMjA3LWJhNTEtNDMwOS1hM2NjLTlmODA3ZGIzNjI2OX0", + "fieldType": "string", + "value": "Dashlane Person" + }, + "givenInformal": { + "id": "ezI5ZjIyMTE0LTgzYzEtNDA3Yi05NTVlLTc5ZWFmNWEwZjUxMX0", + "fieldType": "string", + "value": "DashlaneUsername" + }, + "given2": { + "id": "e2Y0Mzc5M2U2LWIyOWYtNDRiNy1iMWYzLTczZmRjMjYyMjVjYX0", + "fieldType": "string", + "value": "M" + } + } + ] + } + ], + "id": "e2I5OWM4MTgxLWU3MTQtNGM1MS05ZGM1LTMyZTJlNzllZDQwOX0", + "username": "dashlane@dashlane.com", + "email": "dashlane@dashlane.com", + "collections": [] +} diff --git a/crates/bitwarden-exporters/src/cxf/import.rs b/crates/bitwarden-exporters/src/cxf/import.rs index e2b2498c1..7dd0a1b6b 100644 --- a/crates/bitwarden-exporters/src/cxf/import.rs +++ b/crates/bitwarden-exporters/src/cxf/import.rs @@ -88,10 +88,11 @@ pub(super) fn parse_item(value: Item) -> Vec { let note_content = grouped.note.first().map(extract_note_content); // Helper to add ciphers with consistent boilerplate - let mut add_item = |t: CipherType, fields: Vec| { + let mut add_item = |t: CipherType, fields: Vec, custom_name: Option| { + let name = custom_name.unwrap_or_else(|| value.title.clone()); output.push(ImportingCipher { folder_id: None, // TODO: Handle folders - name: value.title.clone(), + name, notes: note_content.clone(), r#type: t, favorite: false, @@ -110,14 +111,23 @@ pub(super) fn parse_item(value: Item) -> Vec { let totp = grouped.totp.first(); let login = to_login(creation_date, basic_auth, passkey, totp, scope); - add_item(CipherType::Login(Box::new(login)), vec![]); + add_item(CipherType::Login(Box::new(login)), vec![], None); } // Credit Card credentials if let Some(credit_card) = grouped.credit_card.first() { let (card, fields) = to_card(credit_card); - add_item(CipherType::Card(Box::new(card)), fields); + // Use cardholder name if title is empty or blank + let card_name = if value.title.trim().is_empty() { + card.cardholder_name + .clone() + .unwrap_or_else(|| "Untitled Card".to_string()) + } else { + value.title.clone() + }; + + add_item(CipherType::Card(Box::new(card)), fields, Some(card_name)); } // Helper for creating SecureNote cipher type @@ -130,13 +140,13 @@ pub(super) fn parse_item(value: Item) -> Vec { // API Key credentials -> Secure Note if let Some(api_key) = grouped.api_key.first() { let fields = api_key_to_fields(api_key); - add_item(secure_note_type(), fields); + add_item(secure_note_type(), fields, None); } // WiFi credentials -> Secure Note if let Some(wifi) = grouped.wifi.first() { let fields = wifi_to_fields(wifi); - add_item(secure_note_type(), fields); + add_item(secure_note_type(), fields, None); } // Identity credentials (address, passport, person name, drivers license, identity document) @@ -165,13 +175,17 @@ pub(super) fn parse_item(value: Item) -> Vec { .into_iter() .flatten() .for_each(|(identity, custom_fields)| { - add_item(CipherType::Identity(Box::new(identity)), custom_fields); + add_item( + CipherType::Identity(Box::new(identity)), + custom_fields, + None, + ); }); // SSH Key credentials if let Some(ssh) = grouped.ssh.first() { match to_ssh(ssh) { - Ok((ssh_key, fields)) => add_item(CipherType::SshKey(Box::new(ssh_key)), fields), + Ok((ssh_key, fields)) => add_item(CipherType::SshKey(Box::new(ssh_key)), fields, None), Err(_) => { // Include information about the failed items, or import as note? } @@ -181,7 +195,7 @@ pub(super) fn parse_item(value: Item) -> Vec { // CustomFields credentials -> Secure Note if let Some(custom_fields) = grouped.custom_fields.first() { let fields = custom_fields_to_fields(custom_fields); - add_item(secure_note_type(), fields); + add_item(secure_note_type(), fields, None); } // Standalone Note credentials -> Secure Note (only if no other credentials exist) @@ -858,6 +872,143 @@ mod tests { assert!(!cipher.fields.is_empty()); } + #[test] + fn test_credit_card_empty_title_uses_cardholder_name() { + let item = Item { + id: [0, 1, 2, 3, 4, 5, 6].as_ref().into(), + creation_at: Some(1706613834), + modified_at: Some(1706623773), + title: "".to_string(), // Empty title + subtitle: None, + favorite: None, + credentials: vec![Credential::CreditCard(Box::new(CreditCardCredential { + number: Some("1234 5678 9012 3456".to_string().into()), + full_name: Some("Jane Smith".to_string().into()), // Cardholder name + card_type: Some("Visa".to_string().into()), + verification_number: Some("456".to_string().into()), + pin: None, + expiry_date: Some( + EditableFieldYearMonth { + year: 2027, + month: Month::March, + } + .into(), + ), + valid_from: None, + }))], + tags: None, + extensions: None, + scope: None, + }; + + let ciphers: Vec = parse_item(item); + assert_eq!(ciphers.len(), 1); + let cipher = ciphers.first().unwrap(); + + // Should use cardholder name since title is empty + assert_eq!(cipher.name, "Jane Smith"); + + let card = match &cipher.r#type { + CipherType::Card(card) => card, + _ => panic!("Expected card"), + }; + + assert_eq!(card.cardholder_name, Some("Jane Smith".to_string())); + } + + #[test] + fn test_credit_card_blank_title_uses_cardholder_name() { + let item = Item { + id: [0, 1, 2, 3, 4, 5, 6].as_ref().into(), + creation_at: Some(1706613834), + modified_at: Some(1706623773), + title: " ".to_string(), // Blank/whitespace title + subtitle: None, + favorite: None, + credentials: vec![Credential::CreditCard(Box::new(CreditCardCredential { + number: Some("1234 5678 9012 3456".to_string().into()), + full_name: Some("John Doe".to_string().into()), + card_type: Some("Mastercard".to_string().into()), + verification_number: Some("789".to_string().into()), + pin: None, + expiry_date: None, + valid_from: None, + }))], + tags: None, + extensions: None, + scope: None, + }; + + let ciphers: Vec = parse_item(item); + assert_eq!(ciphers.len(), 1); + let cipher = ciphers.first().unwrap(); + + // Should use cardholder name since title is just whitespace + assert_eq!(cipher.name, "John Doe"); + } + + #[test] + fn test_credit_card_empty_title_no_cardholder_uses_fallback() { + let item = Item { + id: [0, 1, 2, 3, 4, 5, 6].as_ref().into(), + creation_at: Some(1706613834), + modified_at: Some(1706623773), + title: "".to_string(), // Empty title + subtitle: None, + favorite: None, + credentials: vec![Credential::CreditCard(Box::new(CreditCardCredential { + number: Some("1234 5678 9012 3456".to_string().into()), + full_name: None, // No cardholder name + card_type: Some("Visa".to_string().into()), + verification_number: Some("123".to_string().into()), + pin: None, + expiry_date: None, + valid_from: None, + }))], + tags: None, + extensions: None, + scope: None, + }; + + let ciphers: Vec = parse_item(item); + assert_eq!(ciphers.len(), 1); + let cipher = ciphers.first().unwrap(); + + // Should use fallback since both title and cardholder name are missing + assert_eq!(cipher.name, "Untitled Card"); + } + + #[test] + fn test_credit_card_with_title_ignores_cardholder_name() { + let item = Item { + id: [0, 1, 2, 3, 4, 5, 6].as_ref().into(), + creation_at: Some(1706613834), + modified_at: Some(1706623773), + title: "My Business Card".to_string(), // Has title + subtitle: None, + favorite: None, + credentials: vec![Credential::CreditCard(Box::new(CreditCardCredential { + number: Some("1234 5678 9012 3456".to_string().into()), + full_name: Some("Jane Smith".to_string().into()), + card_type: Some("Visa".to_string().into()), + verification_number: Some("456".to_string().into()), + pin: None, + expiry_date: None, + valid_from: None, + }))], + tags: None, + extensions: None, + scope: None, + }; + + let ciphers: Vec = parse_item(item); + assert_eq!(ciphers.len(), 1); + let cipher = ciphers.first().unwrap(); + + // Should use title since it exists, not cardholder name + assert_eq!(cipher.name, "My Business Card"); + } + #[test] fn test_note_as_part_of_identity() { use credential_exchange_format::{AddressCredential, Credential, Item, NoteCredential}; diff --git a/crates/bitwarden-exporters/src/cxf/tests/dashlane_import_test.rs b/crates/bitwarden-exporters/src/cxf/tests/dashlane_import_test.rs new file mode 100644 index 000000000..dc7257e19 --- /dev/null +++ b/crates/bitwarden-exporters/src/cxf/tests/dashlane_import_test.rs @@ -0,0 +1,436 @@ +//! Sample file integration tests for CXF import functionality +//! +//! These tests validate the parsing of a Dashlane export. + +#[cfg(test)] +mod tests { + use crate::{ + Card, CipherType, Field, Identity, ImportingCipher, Login, LoginUri, SecureNote, + SecureNoteType, + cxf::{CxfError, parse_cxf}, + }; + + fn load_file() -> Result, CxfError> { + use std::fs; + + // Read teh actual Dashlane example file + let cxf_data = fs::read_to_string("resources/dashlane_export.json").unwrap(); + + let items = parse_cxf(cxf_data)?; + + Ok(items) + } + + #[test] + fn test_import_dashlane() { + let result = load_file().unwrap(); + + assert_eq!( + result, + vec![ + // Credit Card + ImportingCipher { + folder_id: None, + name: "Dashlane CC".to_string(), + notes: None, + r#type: CipherType::Card(Box::new(Card { + cardholder_name: Some("Dashlane CC".to_string()), + exp_month: Some("10".to_string()), + exp_year: Some("2028".to_string()), + code: Some("999".to_string()), + brand: None, + number: Some("4111111111111111".to_string()), + })), + favorite: false, + reprompt: 0, + fields: vec![], + revision_date: "2056-09-03T20:11:08Z".parse().unwrap(), + creation_date: "2056-09-03T20:11:08Z".parse().unwrap(), + deleted_date: None, + }, + // Basic Auth w/ TOTP + ImportingCipher { + folder_id: None, + name: "adobe.com".to_string(), + notes: None, + r#type: CipherType::Login(Box::new(Login { + username: Some("dashlane@dashlane.com".to_string()), + password: Some("asdfgh".to_string()), + login_uris: vec![LoginUri { + uri: Some("adobe.com".to_string()), + r#match: None + }], + totp: Some("otpauth://totp?secret=JBSWY3DPEHPK3PXP".to_string()), + fido2_credentials: None, + })), + favorite: false, + reprompt: 0, + fields: vec![], + revision_date: "2056-09-03T20:11:08Z".parse().unwrap(), + creation_date: "2056-09-03T20:11:08Z".parse().unwrap(), + deleted_date: None, + }, + // Secure note + ImportingCipher { + folder_id: None, + name: "Dashlane note".to_string(), + notes: Some("Dashlane note content".to_string()), + r#type: CipherType::SecureNote(Box::new(SecureNote { + r#type: SecureNoteType::Generic, + })), + favorite: false, + reprompt: 0, + fields: vec![], + revision_date: "2056-09-03T20:11:08Z".parse().unwrap(), + creation_date: "2056-09-03T20:11:08Z".parse().unwrap(), + deleted_date: None, + }, + // Address + ImportingCipher { + folder_id: None, + name: "Dashlane address".to_string(), + notes: None, + r#type: CipherType::Identity(Box::new(Identity { + title: None, + first_name: None, + middle_name: None, + last_name: None, + address1: Some("Addy1".to_string()), + address2: None, + address3: None, + city: Some("City".to_string()), + state: None, + postal_code: Some("12345".to_string()), + country: None, + company: None, + email: None, + phone: None, + ssn: None, + username: None, + passport_number: None, + license_number: None, + })), + favorite: false, + reprompt: 0, + fields: vec![], + revision_date: "2056-09-03T20:11:08Z".parse().unwrap(), + creation_date: "2056-09-03T20:11:08Z".parse().unwrap(), + deleted_date: None, + }, + // ID Card + ImportingCipher { + folder_id: None, + name: "".to_string(), + notes: None, + r#type: CipherType::Identity(Box::new(Identity { + title: None, + first_name: None, + middle_name: None, + last_name: None, + address1: None, + address2: None, + address3: None, + city: None, + state: None, + postal_code: None, + country: None, + company: None, + email: None, + phone: None, + ssn: None, + username: None, + passport_number: Some("LicenseNumber".to_string()), + license_number: None, + })), + favorite: false, + reprompt: 0, + fields: vec![ + Field { + name: Some("Issue Date".to_string()), + value: Some("2025-10-09".to_string()), + r#type: 0, + linked_id: None, + }, + Field { + name: Some("Expiry Date".to_string()), + value: Some("2025-10-10".to_string()), + r#type: 0, + linked_id: None, + } + ], + revision_date: "2056-09-03T20:11:08Z".parse().unwrap(), + creation_date: "2056-09-03T20:11:08Z".parse().unwrap(), + deleted_date: None, + }, + // Passport + ImportingCipher { + folder_id: None, + name: "".to_string(), + notes: None, + r#type: CipherType::Identity(Box::new(Identity { + title: None, + first_name: None, + middle_name: None, + last_name: None, + address1: None, + address2: None, + address3: None, + city: None, + state: None, + postal_code: None, + country: None, + company: None, + email: None, + phone: None, + ssn: None, + username: None, + passport_number: Some("PassportNumber".to_string()), + license_number: None, + })), + favorite: false, + reprompt: 0, + fields: vec![ + Field { + name: Some("Issue Date".to_string()), + value: Some("2025-10-09".to_string()), + r#type: 0, + linked_id: None, + }, + Field { + name: Some("Expiry Date".to_string()), + value: Some("2025-10-10".to_string()), + r#type: 0, + linked_id: None, + }, + Field { + name: Some("Issuing Authority".to_string()), + value: Some("PassportIssuer".to_string()), + r#type: 0, + linked_id: None, + }, + ], + revision_date: "2056-09-03T20:11:08Z".parse().unwrap(), + creation_date: "2056-09-03T20:11:08Z".parse().unwrap(), + deleted_date: None, + }, + // Drivers license + ImportingCipher { + folder_id: None, + name: "".to_string(), + notes: None, + r#type: CipherType::Identity(Box::new(Identity { + title: None, + first_name: None, + middle_name: None, + last_name: None, + address1: None, + address2: None, + address3: None, + city: None, + state: None, + postal_code: None, + country: None, + company: None, + email: None, + phone: None, + ssn: None, + username: None, + passport_number: None, + license_number: Some("LicenseNumber".to_string()), + })), + favorite: false, + reprompt: 0, + fields: vec![ + Field { + name: Some("Issue Date".to_string()), + value: Some("2025-10-09".to_string()), + r#type: 0, + linked_id: None, + }, + Field { + name: Some("Expiry Date".to_string()), + value: Some("2025-10-10".to_string()), + r#type: 0, + linked_id: None, + }, + ], + revision_date: "2056-09-03T20:11:08Z".parse().unwrap(), + creation_date: "2056-09-03T20:11:08Z".parse().unwrap(), + deleted_date: None, + }, + // SSN + ImportingCipher { + folder_id: None, + name: "".to_string(), + notes: None, + r#type: CipherType::Identity(Box::new(Identity { + title: None, + first_name: None, + middle_name: None, + last_name: None, + address1: None, + address2: None, + address3: None, + city: None, + state: None, + postal_code: None, + country: None, + company: None, + email: None, + phone: None, + ssn: Some("SSN".to_string()), + username: None, + passport_number: None, + license_number: None, + })), + favorite: false, + reprompt: 0, + fields: vec![], + revision_date: "2056-09-03T20:11:08Z".parse().unwrap(), + creation_date: "2056-09-03T20:11:08Z".parse().unwrap(), + deleted_date: None, + }, + // Tax ID + ImportingCipher { + folder_id: None, + name: "".to_string(), + notes: None, + r#type: CipherType::Identity(Box::new(Identity { + title: None, + first_name: None, + middle_name: None, + last_name: None, + address1: None, + address2: None, + address3: None, + city: None, + state: None, + postal_code: None, + country: None, + company: None, + email: None, + phone: None, + ssn: None, + username: None, + passport_number: Some("TaxNumber".to_string()), + license_number: None, + })), + favorite: false, + reprompt: 0, + fields: vec![], + revision_date: "2056-09-03T20:11:08Z".parse().unwrap(), + creation_date: "2056-09-03T20:11:08Z".parse().unwrap(), + deleted_date: None, + }, + // Wifi + ImportingCipher { + folder_id: None, + name: "WifiOrg".to_string(), + notes: None, + r#type: CipherType::SecureNote(Box::new(SecureNote { + r#type: SecureNoteType::Generic + })), + favorite: false, + reprompt: 0, + fields: vec![ + Field { + name: Some("SSID".to_string()), + value: Some("Dashlane WiFi".to_string()), + r#type: 0, + linked_id: None, + }, + Field { + name: Some("Passphrase".to_string()), + value: Some("123456".to_string()), + r#type: 1, + linked_id: None, + }, + Field { + name: Some("Hidden".to_string()), + value: Some("false".to_string()), + r#type: 2, + linked_id: None, + } + ], + revision_date: "2056-09-03T20:11:08Z".parse().unwrap(), + creation_date: "2056-09-03T20:11:08Z".parse().unwrap(), + deleted_date: None, + }, + // Company + ImportingCipher { + folder_id: None, + name: "Dashlane company".to_string(), + notes: None, + r#type: CipherType::Identity(Box::new(Identity { + title: None, + first_name: Some("Dashlane".to_string()), + middle_name: None, + last_name: Some("company".to_string()), + address1: None, + address2: None, + address3: None, + city: None, + state: None, + postal_code: None, + country: None, + company: None, + email: None, + phone: None, + ssn: None, + username: None, + passport_number: None, + license_number: None, + })), + favorite: false, + reprompt: 0, + fields: vec![Field { + name: Some("Issuing Authority".to_string()), + value: Some("Company title".to_string()), + r#type: 0, + linked_id: None, + }], + revision_date: "2056-09-03T20:11:08Z".parse().unwrap(), + creation_date: "2056-09-03T20:11:08Z".parse().unwrap(), + deleted_date: None, + }, + // Person name + ImportingCipher { + folder_id: None, + name: "Dashlane Person".to_string(), + notes: None, + r#type: CipherType::Identity(Box::new(Identity { + title: Some("mr".to_string()), + first_name: Some("Dashlane Person".to_string()), + middle_name: Some("M".to_string()), + last_name: None, + address1: None, + address2: None, + address3: None, + city: None, + state: None, + postal_code: None, + country: None, + company: None, + email: None, + phone: None, + ssn: None, + username: None, + passport_number: None, + license_number: None, + })), + favorite: false, + reprompt: 0, + fields: vec![Field { + name: Some("Informal Given Name".to_string()), + value: Some("DashlaneUsername".to_string()), + r#type: 0, + linked_id: None, + }], + revision_date: "2056-09-03T20:11:08Z".parse().unwrap(), + creation_date: "2056-09-03T20:11:08Z".parse().unwrap(), + deleted_date: None, + } + ] + ) + } +} diff --git a/crates/bitwarden-exporters/src/cxf/tests/mod.rs b/crates/bitwarden-exporters/src/cxf/tests/mod.rs index 4e002d4a0..baacac0f9 100644 --- a/crates/bitwarden-exporters/src/cxf/tests/mod.rs +++ b/crates/bitwarden-exporters/src/cxf/tests/mod.rs @@ -1,2 +1,3 @@ +mod dashlane_import_test; mod one_password_import_test; mod sample_import_test;