Skip to content

Commit bc28234

Browse files
authored
CXF - Implement missing to view (#429)
We discovered that any non login item were not imported correctly.
1 parent 5658d61 commit bc28234

File tree

2 files changed

+312
-25
lines changed

2 files changed

+312
-25
lines changed

crates/bitwarden-exporters/src/lib.rs

Lines changed: 304 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -98,27 +98,126 @@ pub struct ImportingCipher {
9898
pub deleted_date: Option<DateTime<Utc>>,
9999
}
100100

101+
impl From<Login> for bitwarden_vault::LoginView {
102+
fn from(login: Login) -> Self {
103+
let l: Vec<LoginUriView> = login
104+
.login_uris
105+
.into_iter()
106+
.map(LoginUriView::from)
107+
.collect();
108+
109+
bitwarden_vault::LoginView {
110+
username: login.username,
111+
password: login.password,
112+
password_revision_date: None,
113+
uris: if l.is_empty() { None } else { Some(l) },
114+
totp: login.totp,
115+
autofill_on_page_load: None,
116+
// Fido2Credentials are set by `encrypt_import`.
117+
fido2_credentials: None,
118+
}
119+
}
120+
}
121+
122+
impl From<SecureNote> for bitwarden_vault::SecureNoteView {
123+
fn from(secure_note: SecureNote) -> Self {
124+
bitwarden_vault::SecureNoteView {
125+
r#type: secure_note.r#type.into(),
126+
}
127+
}
128+
}
129+
130+
impl From<Card> for bitwarden_vault::CardView {
131+
fn from(card: Card) -> Self {
132+
bitwarden_vault::CardView {
133+
cardholder_name: card.cardholder_name,
134+
brand: card.brand,
135+
number: card.number,
136+
exp_month: card.exp_month,
137+
exp_year: card.exp_year,
138+
code: card.code,
139+
}
140+
}
141+
}
142+
143+
impl From<Identity> for bitwarden_vault::IdentityView {
144+
fn from(identity: Identity) -> Self {
145+
bitwarden_vault::IdentityView {
146+
title: identity.title,
147+
first_name: identity.first_name,
148+
middle_name: identity.middle_name,
149+
last_name: identity.last_name,
150+
address1: identity.address1,
151+
address2: identity.address2,
152+
address3: identity.address3,
153+
city: identity.city,
154+
state: identity.state,
155+
postal_code: identity.postal_code,
156+
country: identity.country,
157+
company: identity.company,
158+
email: identity.email,
159+
phone: identity.phone,
160+
ssn: identity.ssn,
161+
username: identity.username,
162+
passport_number: identity.passport_number,
163+
license_number: identity.license_number,
164+
}
165+
}
166+
}
167+
168+
impl From<SshKey> for bitwarden_vault::SshKeyView {
169+
fn from(ssh_key: SshKey) -> Self {
170+
bitwarden_vault::SshKeyView {
171+
private_key: ssh_key.private_key,
172+
public_key: ssh_key.public_key,
173+
fingerprint: ssh_key.fingerprint,
174+
}
175+
}
176+
}
177+
101178
impl From<ImportingCipher> for CipherView {
102179
fn from(value: ImportingCipher) -> Self {
103-
let login = match value.r#type {
104-
CipherType::Login(login) => {
105-
let l: Vec<LoginUriView> = login
106-
.login_uris
107-
.into_iter()
108-
.map(LoginUriView::from)
109-
.collect();
110-
111-
Some(bitwarden_vault::LoginView {
112-
username: login.username,
113-
password: login.password,
114-
password_revision_date: None,
115-
uris: if l.is_empty() { None } else { Some(l) },
116-
totp: login.totp,
117-
autofill_on_page_load: None,
118-
fido2_credentials: None,
119-
})
120-
}
121-
_ => None,
180+
let (cipher_type, login, identity, card, secure_note, ssh_key) = match value.r#type {
181+
CipherType::Login(login) => (
182+
bitwarden_vault::CipherType::Login,
183+
Some((*login).into()),
184+
None,
185+
None,
186+
None,
187+
None,
188+
),
189+
CipherType::SecureNote(secure_note) => (
190+
bitwarden_vault::CipherType::SecureNote,
191+
None,
192+
None,
193+
None,
194+
Some((*secure_note).into()),
195+
None,
196+
),
197+
CipherType::Card(card) => (
198+
bitwarden_vault::CipherType::Card,
199+
None,
200+
None,
201+
Some((*card).into()),
202+
None,
203+
None,
204+
),
205+
CipherType::Identity(identity) => (
206+
bitwarden_vault::CipherType::Identity,
207+
None,
208+
Some((*identity).into()),
209+
None,
210+
None,
211+
None,
212+
),
213+
CipherType::SshKey(ssh_key) => (
214+
bitwarden_vault::CipherType::SshKey,
215+
None,
216+
None,
217+
None,
218+
None,
219+
Some((*ssh_key).into()),
220+
),
122221
};
123222

124223
Self {
@@ -128,13 +227,13 @@ impl From<ImportingCipher> for CipherView {
128227
collection_ids: vec![],
129228
key: None,
130229
name: value.name,
131-
notes: None,
132-
r#type: bitwarden_vault::CipherType::Login,
230+
notes: value.notes,
231+
r#type: cipher_type,
133232
login,
134-
identity: None,
135-
card: None,
136-
secure_note: None,
137-
ssh_key: None,
233+
identity,
234+
card,
235+
secure_note,
236+
ssh_key,
138237
favorite: value.favorite,
139238
reprompt: CipherRepromptType::None,
140239
organization_use_totp: true,
@@ -320,3 +419,183 @@ pub struct SshKey {
320419
/// SSH fingerprint using SHA256 in the format: `SHA256:BASE64_ENCODED_FINGERPRINT`
321420
pub fingerprint: String,
322421
}
422+
423+
#[cfg(test)]
424+
mod tests {
425+
use bitwarden_vault::CipherType as VaultCipherType;
426+
use chrono::{DateTime, Utc};
427+
428+
use super::*;
429+
430+
#[test]
431+
fn test_importing_cipher_to_cipher_view_login() {
432+
let test_date: DateTime<Utc> = "2024-01-30T17:55:36.150Z".parse().unwrap();
433+
let test_folder_id = uuid::Uuid::new_v4();
434+
435+
let importing_cipher = ImportingCipher {
436+
folder_id: Some(test_folder_id),
437+
name: "Test Login".to_string(),
438+
notes: Some("Test notes".to_string()),
439+
r#type: CipherType::Login(Box::new(Login {
440+
username: Some("[email protected]".to_string()),
441+
password: Some("password123".to_string()),
442+
login_uris: vec![LoginUri {
443+
uri: Some("https://example.com".to_string()),
444+
r#match: Some(0), // Domain match
445+
}],
446+
totp: Some("otpauth://totp/test".to_string()),
447+
fido2_credentials: None,
448+
})),
449+
favorite: true,
450+
reprompt: 1,
451+
fields: vec![],
452+
revision_date: test_date,
453+
creation_date: test_date,
454+
deleted_date: None,
455+
};
456+
457+
let cipher_view: CipherView = importing_cipher.into();
458+
459+
assert_eq!(cipher_view.id, None);
460+
assert_eq!(cipher_view.organization_id, None);
461+
assert_eq!(
462+
cipher_view.folder_id.unwrap().to_string(),
463+
test_folder_id.to_string()
464+
);
465+
assert_eq!(cipher_view.name, "Test Login");
466+
assert_eq!(cipher_view.notes.unwrap(), "Test notes");
467+
assert_eq!(cipher_view.r#type, VaultCipherType::Login);
468+
assert!(cipher_view.favorite);
469+
assert_eq!(cipher_view.creation_date, test_date);
470+
assert_eq!(cipher_view.revision_date, test_date);
471+
472+
let login = cipher_view.login.expect("Login should be present");
473+
assert_eq!(login.username, Some("[email protected]".to_string()));
474+
assert_eq!(login.password, Some("password123".to_string()));
475+
assert_eq!(login.totp, Some("otpauth://totp/test".to_string()));
476+
477+
let uris = login.uris.expect("URIs should be present");
478+
assert_eq!(uris.len(), 1);
479+
assert_eq!(uris[0].uri, Some("https://example.com".to_string()));
480+
assert_eq!(uris[0].r#match, Some(bitwarden_vault::UriMatchType::Domain));
481+
}
482+
483+
#[test]
484+
fn test_importing_cipher_to_cipher_view_secure_note() {
485+
let test_date: DateTime<Utc> = "2024-01-30T17:55:36.150Z".parse().unwrap();
486+
487+
let importing_cipher = ImportingCipher {
488+
folder_id: None,
489+
name: "My Note".to_string(),
490+
notes: Some("This is a secure note".to_string()),
491+
r#type: CipherType::SecureNote(Box::new(SecureNote {
492+
r#type: SecureNoteType::Generic,
493+
})),
494+
favorite: false,
495+
reprompt: 0,
496+
fields: vec![],
497+
revision_date: test_date,
498+
creation_date: test_date,
499+
deleted_date: None,
500+
};
501+
502+
let cipher_view: CipherView = importing_cipher.into();
503+
504+
// Verify basic fields
505+
assert_eq!(cipher_view.id, None);
506+
assert_eq!(cipher_view.organization_id, None);
507+
assert_eq!(cipher_view.folder_id, None);
508+
assert_eq!(cipher_view.name, "My Note");
509+
assert_eq!(cipher_view.notes, Some("This is a secure note".to_string()));
510+
assert_eq!(cipher_view.r#type, bitwarden_vault::CipherType::SecureNote);
511+
assert!(!cipher_view.favorite);
512+
assert_eq!(cipher_view.creation_date, test_date);
513+
assert_eq!(cipher_view.revision_date, test_date);
514+
515+
// For SecureNote type, secure_note should be populated and others should be None
516+
assert!(cipher_view.login.is_none());
517+
assert!(cipher_view.identity.is_none());
518+
assert!(cipher_view.card.is_none());
519+
assert!(cipher_view.secure_note.is_some());
520+
assert!(cipher_view.ssh_key.is_none());
521+
522+
// Verify the secure note content
523+
let secure_note = cipher_view.secure_note.unwrap();
524+
assert!(matches!(
525+
secure_note.r#type,
526+
bitwarden_vault::SecureNoteType::Generic
527+
));
528+
}
529+
530+
#[test]
531+
fn test_importing_cipher_to_cipher_view_card() {
532+
let test_date: DateTime<Utc> = "2024-01-30T17:55:36.150Z".parse().unwrap();
533+
534+
let importing_cipher = ImportingCipher {
535+
folder_id: None,
536+
name: "My Credit Card".to_string(),
537+
notes: Some("Credit card notes".to_string()),
538+
r#type: CipherType::Card(Box::new(Card {
539+
cardholder_name: Some("John Doe".to_string()),
540+
brand: Some("Visa".to_string()),
541+
number: Some("1234567812345678".to_string()),
542+
exp_month: Some("12".to_string()),
543+
exp_year: Some("2025".to_string()),
544+
code: Some("123".to_string()),
545+
})),
546+
favorite: false,
547+
reprompt: 0,
548+
fields: vec![],
549+
revision_date: test_date,
550+
creation_date: test_date,
551+
deleted_date: None,
552+
};
553+
554+
let cipher_view: CipherView = importing_cipher.into();
555+
556+
assert_eq!(cipher_view.r#type, bitwarden_vault::CipherType::Card);
557+
assert!(cipher_view.card.is_some());
558+
assert!(cipher_view.login.is_none());
559+
560+
let card = cipher_view.card.unwrap();
561+
assert_eq!(card.cardholder_name, Some("John Doe".to_string()));
562+
assert_eq!(card.brand, Some("Visa".to_string()));
563+
assert_eq!(card.number, Some("1234567812345678".to_string()));
564+
}
565+
566+
#[test]
567+
fn test_importing_cipher_to_cipher_view_identity() {
568+
let test_date: DateTime<Utc> = "2024-01-30T17:55:36.150Z".parse().unwrap();
569+
570+
let importing_cipher = ImportingCipher {
571+
folder_id: None,
572+
name: "My Identity".to_string(),
573+
notes: None,
574+
r#type: CipherType::Identity(Box::new(Identity {
575+
title: Some("Dr.".to_string()),
576+
first_name: Some("Jane".to_string()),
577+
last_name: Some("Smith".to_string()),
578+
email: Some("[email protected]".to_string()),
579+
..Default::default()
580+
})),
581+
favorite: false,
582+
reprompt: 0,
583+
fields: vec![],
584+
revision_date: test_date,
585+
creation_date: test_date,
586+
deleted_date: None,
587+
};
588+
589+
let cipher_view: CipherView = importing_cipher.into();
590+
591+
assert_eq!(cipher_view.r#type, bitwarden_vault::CipherType::Identity);
592+
assert!(cipher_view.identity.is_some());
593+
assert!(cipher_view.login.is_none());
594+
595+
let identity = cipher_view.identity.unwrap();
596+
assert_eq!(identity.title, Some("Dr.".to_string()));
597+
assert_eq!(identity.first_name, Some("Jane".to_string()));
598+
assert_eq!(identity.last_name, Some("Smith".to_string()));
599+
assert_eq!(identity.email, Some("[email protected]".to_string()));
600+
}
601+
}

crates/bitwarden-exporters/src/models.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -196,6 +196,14 @@ impl From<SecureNoteType> for crate::SecureNoteType {
196196
}
197197
}
198198

199+
impl From<crate::SecureNoteType> for SecureNoteType {
200+
fn from(value: crate::SecureNoteType) -> Self {
201+
match value {
202+
crate::SecureNoteType::Generic => SecureNoteType::Generic,
203+
}
204+
}
205+
}
206+
199207
#[cfg(test)]
200208
mod tests {
201209
use bitwarden_core::key_management::create_test_crypto_with_user_key;

0 commit comments

Comments
 (0)