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