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