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)?)),
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(view: &CipherView) -> Result<crate::Login, MissingFieldError> {
97 let l = require!(view.login.clone());
98
99 Ok(crate::Login {
100 username: l.username,
101 password: l.password,
102 login_uris: l
103 .uris
104 .unwrap_or_default()
105 .into_iter()
106 .map(|u| u.into())
107 .collect(),
108 totp: l.totp,
109 fido2_credentials: l.fido2_credentials.as_ref().and_then(|_| {
110 let credentials = view.get_fido2_credentials();
111 if credentials.is_empty() {
112 None
113 } else {
114 Some(
115 credentials
116 .into_iter()
117 .map(|c| Fido2CredentialFullView::from(c).into())
118 .collect(),
119 )
120 }
121 }),
122 })
123}
124
125impl From<LoginUriView> for crate::LoginUri {
126 fn from(value: LoginUriView) -> Self {
127 Self {
128 r#match: value.r#match.map(|v| v as u8),
129 uri: value.uri,
130 }
131 }
132}
133
134impl From<Fido2CredentialFullView> for crate::Fido2Credential {
135 fn from(value: Fido2CredentialFullView) -> Self {
136 Self {
137 credential_id: value.credential_id,
138 key_type: value.key_type,
139 key_algorithm: value.key_algorithm,
140 key_curve: value.key_curve,
141 key_value: value.key_value,
142 rp_id: value.rp_id,
143 user_handle: value.user_handle,
144 user_name: value.user_name,
145 counter: value.counter.parse().expect("Invalid counter"),
146 rp_name: value.rp_name,
147 user_display_name: value.user_display_name,
148 discoverable: value.discoverable,
149 creation_date: value.creation_date,
150 }
151 }
152}
153
154impl From<SecureNoteView> for crate::SecureNote {
155 fn from(view: SecureNoteView) -> Self {
156 crate::SecureNote {
157 r#type: view.r#type.into(),
158 }
159 }
160}
161
162impl From<CardView> for crate::Card {
163 fn from(view: CardView) -> Self {
164 crate::Card {
165 cardholder_name: view.cardholder_name,
166 exp_month: view.exp_month,
167 exp_year: view.exp_year,
168 code: view.code,
169 brand: view.brand,
170 number: view.number,
171 }
172 }
173}
174
175impl From<IdentityView> for crate::Identity {
176 fn from(view: IdentityView) -> Self {
177 crate::Identity {
178 title: view.title,
179 first_name: view.first_name,
180 middle_name: view.middle_name,
181 last_name: view.last_name,
182 address1: view.address1,
183 address2: view.address2,
184 address3: view.address3,
185 city: view.city,
186 state: view.state,
187 postal_code: view.postal_code,
188 country: view.country,
189 company: view.company,
190 email: view.email,
191 phone: view.phone,
192 ssn: view.ssn,
193 username: view.username,
194 passport_number: view.passport_number,
195 license_number: view.license_number,
196 }
197 }
198}
199
200impl From<SshKeyView> for crate::SshKey {
201 fn from(view: SshKeyView) -> Self {
202 crate::SshKey {
203 private_key: view.private_key,
204 public_key: view.public_key,
205 fingerprint: view.fingerprint,
206 }
207 }
208}
209
210impl From<FieldView> for crate::Field {
211 fn from(value: FieldView) -> Self {
212 Self {
213 name: value.name,
214 value: value.value,
215 r#type: value.r#type as u8,
216 linked_id: value.linked_id.map(|id| id.into()),
217 }
218 }
219}
220
221impl From<crate::Field> for FieldView {
222 fn from(value: crate::Field) -> Self {
223 Self {
224 name: value.name,
225 value: value.value,
226 r#type: value.r#type.try_into().unwrap_or(FieldType::Text),
227 linked_id: value.linked_id.and_then(|id| id.try_into().ok()),
228 }
229 }
230}
231
232impl From<SecureNoteType> for crate::SecureNoteType {
233 fn from(value: SecureNoteType) -> Self {
234 match value {
235 SecureNoteType::Generic => crate::SecureNoteType::Generic,
236 }
237 }
238}
239
240impl From<crate::SecureNoteType> for SecureNoteType {
241 fn from(value: crate::SecureNoteType) -> Self {
242 match value {
243 crate::SecureNoteType::Generic => SecureNoteType::Generic,
244 }
245 }
246}
247
248#[cfg(test)]
249mod tests {
250 use bitwarden_core::key_management::create_test_crypto_with_user_key;
251 use bitwarden_crypto::{SymmetricCryptoKey, SymmetricKeyAlgorithm};
252 use bitwarden_vault::{CipherId, CipherRepromptType, EncryptMode, FolderId, LoginView};
253 use chrono::{DateTime, Utc};
254
255 use super::*;
256
257 #[test]
258 fn test_try_from_folder_view() {
259 let test_id: uuid::Uuid = "fd411a1a-fec8-4070-985d-0e6560860e69".parse().unwrap();
260 let view = FolderView {
261 id: Some(FolderId::new(test_id)),
262 name: "test_name".to_string(),
263 revision_date: "2024-01-30T17:55:36.150Z".parse().unwrap(),
264 };
265
266 let f: crate::Folder = view.try_into().unwrap();
267
268 assert_eq!(f.id, test_id);
269 assert_eq!(f.name, "test_name".to_string());
270 }
271
272 #[test]
273 fn test_from_login() {
274 let test_id: uuid::Uuid = "fd411a1a-fec8-4070-985d-0e6560860e69".parse().unwrap();
275 let view = CipherView {
276 partial: false,
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).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(SymmetricKeyAlgorithm::Aes256CbcHmac);
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 partial: false,
334 r#type: CipherType::Login,
335 login: Some(LoginView {
336 username: Some("test_username".to_string()),
337 password: Some("test_password".to_string()),
338 password_revision_date: None,
339 uris: None,
340 totp: None,
341 autofill_on_page_load: None,
342 fido2_credentials: None,
343 }),
344 id: Some(CipherId::new(test_id)),
345 organization_id: None,
346 folder_id: None,
347 collection_ids: vec![],
348 key: None,
349 name: "My login".to_string(),
350 notes: None,
351 identity: None,
352 card: None,
353 secure_note: None,
354 ssh_key: None,
355 bank_account: None,
356 passport: None,
357 drivers_license: None,
358 favorite: false,
359 reprompt: CipherRepromptType::None,
360 organization_use_totp: true,
361 edit: true,
362 permissions: None,
363 view_password: true,
364 local_data: None,
365 attachments: None,
366 attachment_decryption_failures: None,
367 fields: None,
368 password_history: None,
369 creation_date: "2024-01-30T17:55:36.150Z".parse().unwrap(),
370 deleted_date: None,
371 revision_date: "2024-01-30T17:55:36.150Z".parse().unwrap(),
372 archived_date: None,
373 };
374 let encrypted = key_store.encrypt(EncryptMode::Legacy(cipher_view)).unwrap();
375
376 let cipher: crate::Cipher = crate::Cipher::from_cipher(&key_store, encrypted).unwrap();
377
378 assert_eq!(cipher.id, test_id);
379 assert_eq!(cipher.folder_id, None);
380 assert_eq!(cipher.name, "My login".to_string());
381 assert_eq!(cipher.notes, None);
382 assert!(!cipher.favorite);
383 assert_eq!(cipher.reprompt, 0);
384 assert!(cipher.fields.is_empty());
385 assert_eq!(
386 cipher.revision_date,
387 "2024-01-30T17:55:36.150Z".parse::<DateTime<Utc>>().unwrap()
388 );
389 assert_eq!(
390 cipher.creation_date,
391 "2024-01-30T17:55:36.150Z".parse::<DateTime<Utc>>().unwrap()
392 );
393 assert_eq!(cipher.deleted_date, None);
394
395 if let crate::CipherType::Login(l) = cipher.r#type {
396 assert_eq!(l.username, Some("test_username".to_string()));
397 assert_eq!(l.password, Some("test_password".to_string()));
398 assert!(l.login_uris.is_empty());
399 assert_eq!(l.totp, None);
400 } else {
401 panic!("Expected login type");
402 }
403 }
404}