1use bitwarden_core::{MissingFieldError, key_management::KeySlotIds, require};
2use bitwarden_crypto::KeyStore;
3use bitwarden_vault::{
4 CardView, Cipher, CipherType, CipherView, Fido2CredentialFullView, FieldType, FieldView,
5 FolderView, IdentityView, LoginUriView, PasswordHistoryView, SecureNoteType, SecureNoteView,
6 SshKeyView,
7};
8
9impl TryFrom<FolderView> for crate::Folder {
10 type Error = MissingFieldError;
11
12 fn try_from(value: FolderView) -> Result<Self, Self::Error> {
13 Ok(Self {
14 id: require!(value.id).into(),
15 name: value.name,
16 })
17 }
18}
19
20impl crate::Cipher {
21 pub(crate) fn from_cipher(
22 key_store: &KeyStore<KeySlotIds>,
23 cipher: Cipher,
24 ) -> Result<Self, crate::error::ExportError> {
25 let view: CipherView = key_store.decrypt(&cipher)?;
26
27 let r = match view.r#type {
28 CipherType::Login => crate::CipherType::Login(Box::new(from_login(&view, key_store)?)),
29 CipherType::SecureNote => {
30 let s = require!(view.secure_note);
31 crate::CipherType::SecureNote(Box::new(s.into()))
32 }
33 CipherType::Card => {
34 let c = require!(view.card);
35 crate::CipherType::Card(Box::new(c.into()))
36 }
37 CipherType::Identity => {
38 let i = require!(view.identity);
39 crate::CipherType::Identity(Box::new(i.into()))
40 }
41 CipherType::SshKey => {
42 let s = require!(view.ssh_key);
43 crate::CipherType::SshKey(Box::new(s.into()))
44 }
45 CipherType::BankAccount => {
46 crate::CipherType::BankAccount
49 }
50 CipherType::Passport => {
51 crate::CipherType::Passport
54 }
55 CipherType::DriversLicense => {
56 crate::CipherType::DriversLicense
59 }
60 };
61
62 Ok(Self {
63 id: require!(view.id).into(),
64 folder_id: view.folder_id.map(|f| f.into()),
65 name: view.name,
66 notes: view.notes,
67 r#type: r,
68 favorite: view.favorite,
69 reprompt: view.reprompt as u8,
70 fields: view
71 .fields
72 .unwrap_or_default()
73 .into_iter()
74 .map(|f| f.into())
75 .collect(),
76 password_history: view
77 .password_history
78 .map(|h| h.into_iter().map(|p| p.into()).collect()),
79 revision_date: view.revision_date,
80 creation_date: view.creation_date,
81 deleted_date: view.deleted_date,
82 })
83 }
84}
85
86impl From<PasswordHistoryView> for crate::PasswordHistory {
87 fn from(value: PasswordHistoryView) -> Self {
88 Self {
89 password: value.password,
90 last_used_date: value.last_used_date,
91 }
92 }
93}
94
95fn from_login(
97 view: &CipherView,
98 key_store: &KeyStore<KeySlotIds>,
99) -> Result<crate::Login, MissingFieldError> {
100 let l = require!(view.login.clone());
101
102 Ok(crate::Login {
103 username: l.username,
104 password: l.password,
105 login_uris: l
106 .uris
107 .unwrap_or_default()
108 .into_iter()
109 .map(|u| u.into())
110 .collect(),
111 totp: l.totp,
112 fido2_credentials: l.fido2_credentials.as_ref().and_then(|_| {
113 let credentials = view.get_fido2_credentials(&mut key_store.context()).ok()?;
114 if credentials.is_empty() {
115 None
116 } else {
117 Some(credentials.into_iter().map(|c| c.into()).collect())
118 }
119 }),
120 })
121}
122
123impl From<LoginUriView> for crate::LoginUri {
124 fn from(value: LoginUriView) -> Self {
125 Self {
126 r#match: value.r#match.map(|v| v as u8),
127 uri: value.uri,
128 }
129 }
130}
131
132impl From<Fido2CredentialFullView> for crate::Fido2Credential {
133 fn from(value: Fido2CredentialFullView) -> Self {
134 Self {
135 credential_id: value.credential_id,
136 key_type: value.key_type,
137 key_algorithm: value.key_algorithm,
138 key_curve: value.key_curve,
139 key_value: value.key_value,
140 rp_id: value.rp_id,
141 user_handle: value.user_handle,
142 user_name: value.user_name,
143 counter: value.counter.parse().expect("Invalid counter"),
144 rp_name: value.rp_name,
145 user_display_name: value.user_display_name,
146 discoverable: value.discoverable,
147 creation_date: value.creation_date,
148 }
149 }
150}
151
152impl From<SecureNoteView> for crate::SecureNote {
153 fn from(view: SecureNoteView) -> Self {
154 crate::SecureNote {
155 r#type: view.r#type.into(),
156 }
157 }
158}
159
160impl From<CardView> for crate::Card {
161 fn from(view: CardView) -> Self {
162 crate::Card {
163 cardholder_name: view.cardholder_name,
164 exp_month: view.exp_month,
165 exp_year: view.exp_year,
166 code: view.code,
167 brand: view.brand,
168 number: view.number,
169 }
170 }
171}
172
173impl From<IdentityView> for crate::Identity {
174 fn from(view: IdentityView) -> Self {
175 crate::Identity {
176 title: view.title,
177 first_name: view.first_name,
178 middle_name: view.middle_name,
179 last_name: view.last_name,
180 address1: view.address1,
181 address2: view.address2,
182 address3: view.address3,
183 city: view.city,
184 state: view.state,
185 postal_code: view.postal_code,
186 country: view.country,
187 company: view.company,
188 email: view.email,
189 phone: view.phone,
190 ssn: view.ssn,
191 username: view.username,
192 passport_number: view.passport_number,
193 license_number: view.license_number,
194 }
195 }
196}
197
198impl From<SshKeyView> for crate::SshKey {
199 fn from(view: SshKeyView) -> Self {
200 crate::SshKey {
201 private_key: view.private_key,
202 public_key: view.public_key,
203 fingerprint: view.fingerprint,
204 }
205 }
206}
207
208impl From<FieldView> for crate::Field {
209 fn from(value: FieldView) -> Self {
210 Self {
211 name: value.name,
212 value: value.value,
213 r#type: value.r#type as u8,
214 linked_id: value.linked_id.map(|id| id.into()),
215 }
216 }
217}
218
219impl From<crate::Field> for FieldView {
220 fn from(value: crate::Field) -> Self {
221 Self {
222 name: value.name,
223 value: value.value,
224 r#type: value.r#type.try_into().unwrap_or(FieldType::Text),
225 linked_id: value.linked_id.and_then(|id| id.try_into().ok()),
226 }
227 }
228}
229
230impl From<SecureNoteType> for crate::SecureNoteType {
231 fn from(value: SecureNoteType) -> Self {
232 match value {
233 SecureNoteType::Generic => crate::SecureNoteType::Generic,
234 }
235 }
236}
237
238impl From<crate::SecureNoteType> for SecureNoteType {
239 fn from(value: crate::SecureNoteType) -> Self {
240 match value {
241 crate::SecureNoteType::Generic => SecureNoteType::Generic,
242 }
243 }
244}
245
246#[cfg(test)]
247mod tests {
248 use bitwarden_core::key_management::create_test_crypto_with_user_key;
249 use bitwarden_crypto::SymmetricCryptoKey;
250 use bitwarden_vault::{CipherId, CipherRepromptType, FolderId, LoginView};
251 use chrono::{DateTime, Utc};
252
253 use super::*;
254
255 #[test]
256 fn test_try_from_folder_view() {
257 let test_id: uuid::Uuid = "fd411a1a-fec8-4070-985d-0e6560860e69".parse().unwrap();
258 let view = FolderView {
259 id: Some(FolderId::new(test_id)),
260 name: "test_name".to_string(),
261 revision_date: "2024-01-30T17:55:36.150Z".parse().unwrap(),
262 };
263
264 let f: crate::Folder = view.try_into().unwrap();
265
266 assert_eq!(f.id, test_id);
267 assert_eq!(f.name, "test_name".to_string());
268 }
269
270 #[test]
271 fn test_from_login() {
272 let key = SymmetricCryptoKey::make_aes256_cbc_hmac_key();
273 let key_store = create_test_crypto_with_user_key(key);
274
275 let test_id: uuid::Uuid = "fd411a1a-fec8-4070-985d-0e6560860e69".parse().unwrap();
276 let view = CipherView {
277 r#type: CipherType::Login,
278 login: Some(LoginView {
279 username: Some("test_username".to_string()),
280 password: Some("test_password".to_string()),
281 password_revision_date: None,
282 uris: None,
283 totp: None,
284 autofill_on_page_load: None,
285 fido2_credentials: None,
286 }),
287 id: Some(CipherId::new(test_id)),
288 organization_id: None,
289 folder_id: None,
290 collection_ids: vec![],
291 key: None,
292 name: "My login".to_string(),
293 notes: None,
294 identity: None,
295 card: None,
296 secure_note: None,
297 ssh_key: None,
298 bank_account: None,
299 passport: None,
300 drivers_license: None,
301 favorite: false,
302 reprompt: CipherRepromptType::None,
303 organization_use_totp: true,
304 edit: true,
305 permissions: None,
306 view_password: true,
307 local_data: None,
308 attachments: None,
309 attachment_decryption_failures: None,
310 fields: None,
311 password_history: None,
312 creation_date: "2024-01-30T17:55:36.150Z".parse().unwrap(),
313 deleted_date: None,
314 revision_date: "2024-01-30T17:55:36.150Z".parse().unwrap(),
315 archived_date: None,
316 };
317
318 let login = from_login(&view, &key_store).unwrap();
319
320 assert_eq!(login.username, Some("test_username".to_string()));
321 assert_eq!(login.password, Some("test_password".to_string()));
322 assert!(login.login_uris.is_empty());
323 assert_eq!(login.totp, None);
324 }
325
326 #[test]
327 fn test_from_cipher_login() {
328 let key = SymmetricCryptoKey::make_aes256_cbc_hmac_key();
329 let key_store = create_test_crypto_with_user_key(key);
330
331 let test_id: uuid::Uuid = "fd411a1a-fec8-4070-985d-0e6560860e69".parse().unwrap();
332 let cipher_view = CipherView {
333 r#type: CipherType::Login,
334 login: Some(LoginView {
335 username: Some("test_username".to_string()),
336 password: Some("test_password".to_string()),
337 password_revision_date: None,
338 uris: None,
339 totp: None,
340 autofill_on_page_load: None,
341 fido2_credentials: None,
342 }),
343 id: Some(CipherId::new(test_id)),
344 organization_id: None,
345 folder_id: None,
346 collection_ids: vec![],
347 key: None,
348 name: "My login".to_string(),
349 notes: None,
350 identity: None,
351 card: None,
352 secure_note: None,
353 ssh_key: None,
354 bank_account: None,
355 passport: None,
356 drivers_license: None,
357 favorite: false,
358 reprompt: CipherRepromptType::None,
359 organization_use_totp: true,
360 edit: true,
361 permissions: None,
362 view_password: true,
363 local_data: None,
364 attachments: None,
365 attachment_decryption_failures: None,
366 fields: None,
367 password_history: None,
368 creation_date: "2024-01-30T17:55:36.150Z".parse().unwrap(),
369 deleted_date: None,
370 revision_date: "2024-01-30T17:55:36.150Z".parse().unwrap(),
371 archived_date: None,
372 };
373 let encrypted = key_store.encrypt(cipher_view).unwrap();
374
375 let cipher: crate::Cipher = crate::Cipher::from_cipher(&key_store, encrypted).unwrap();
376
377 assert_eq!(cipher.id, test_id);
378 assert_eq!(cipher.folder_id, None);
379 assert_eq!(cipher.name, "My login".to_string());
380 assert_eq!(cipher.notes, None);
381 assert!(!cipher.favorite);
382 assert_eq!(cipher.reprompt, 0);
383 assert!(cipher.fields.is_empty());
384 assert_eq!(
385 cipher.revision_date,
386 "2024-01-30T17:55:36.150Z".parse::<DateTime<Utc>>().unwrap()
387 );
388 assert_eq!(
389 cipher.creation_date,
390 "2024-01-30T17:55:36.150Z".parse::<DateTime<Utc>>().unwrap()
391 );
392 assert_eq!(cipher.deleted_date, None);
393
394 if let crate::CipherType::Login(l) = cipher.r#type {
395 assert_eq!(l.username, Some("test_username".to_string()));
396 assert_eq!(l.password, Some("test_password".to_string()));
397 assert!(l.login_uris.is_empty());
398 assert_eq!(l.totp, None);
399 } else {
400 panic!("Expected login type");
401 }
402 }
403}