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