1use bitwarden_core::{key_management::KeyIds, require, MissingFieldError};
2use bitwarden_crypto::KeyStore;
3use bitwarden_vault::{
4 CardView, Cipher, CipherType, CipherView, Fido2CredentialFullView, FieldView, FolderView,
5 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),
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),
48 folder_id: view.folder_id,
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<SecureNoteType> for crate::SecureNoteType {
192 fn from(value: SecureNoteType) -> Self {
193 match value {
194 SecureNoteType::Generic => crate::SecureNoteType::Generic,
195 }
196 }
197}
198
199#[cfg(test)]
200mod tests {
201 use bitwarden_core::key_management::create_test_crypto_with_user_key;
202 use bitwarden_crypto::SymmetricCryptoKey;
203 use bitwarden_vault::{CipherRepromptType, LoginView};
204 use chrono::{DateTime, Utc};
205
206 use super::*;
207
208 #[test]
209 fn test_try_from_folder_view() {
210 let test_id: uuid::Uuid = "fd411a1a-fec8-4070-985d-0e6560860e69".parse().unwrap();
211 let view = FolderView {
212 id: Some(test_id),
213 name: "test_name".to_string(),
214 revision_date: "2024-01-30T17:55:36.150Z".parse().unwrap(),
215 };
216
217 let f: crate::Folder = view.try_into().unwrap();
218
219 assert_eq!(f.id, test_id);
220 assert_eq!(f.name, "test_name".to_string());
221 }
222
223 #[test]
224 fn test_from_login() {
225 let key = SymmetricCryptoKey::make_aes256_cbc_hmac_key();
226 let key_store = create_test_crypto_with_user_key(key);
227
228 let test_id: uuid::Uuid = "fd411a1a-fec8-4070-985d-0e6560860e69".parse().unwrap();
229 let view = CipherView {
230 r#type: CipherType::Login,
231 login: Some(LoginView {
232 username: Some("test_username".to_string()),
233 password: Some("test_password".to_string()),
234 password_revision_date: None,
235 uris: None,
236 totp: None,
237 autofill_on_page_load: None,
238 fido2_credentials: None,
239 }),
240 id: Some(test_id),
241 organization_id: None,
242 folder_id: None,
243 collection_ids: vec![],
244 key: None,
245 name: "My login".to_string(),
246 notes: None,
247 identity: None,
248 card: None,
249 secure_note: None,
250 ssh_key: None,
251 favorite: false,
252 reprompt: CipherRepromptType::None,
253 organization_use_totp: true,
254 edit: true,
255 permissions: None,
256 view_password: true,
257 local_data: None,
258 attachments: None,
259 fields: None,
260 password_history: None,
261 creation_date: "2024-01-30T17:55:36.150Z".parse().unwrap(),
262 deleted_date: None,
263 revision_date: "2024-01-30T17:55:36.150Z".parse().unwrap(),
264 };
265
266 let login = from_login(&view, &key_store).unwrap();
267
268 assert_eq!(login.username, Some("test_username".to_string()));
269 assert_eq!(login.password, Some("test_password".to_string()));
270 assert!(login.login_uris.is_empty());
271 assert_eq!(login.totp, None);
272 }
273
274 #[test]
275 fn test_from_cipher_login() {
276 let key = SymmetricCryptoKey::make_aes256_cbc_hmac_key();
277 let key_store = create_test_crypto_with_user_key(key);
278
279 let test_id: uuid::Uuid = "fd411a1a-fec8-4070-985d-0e6560860e69".parse().unwrap();
280 let cipher_view = CipherView {
281 r#type: CipherType::Login,
282 login: Some(LoginView {
283 username: Some("test_username".to_string()),
284 password: Some("test_password".to_string()),
285 password_revision_date: None,
286 uris: None,
287 totp: None,
288 autofill_on_page_load: None,
289 fido2_credentials: None,
290 }),
291 id: Some(test_id),
292 organization_id: None,
293 folder_id: None,
294 collection_ids: vec![],
295 key: None,
296 name: "My login".to_string(),
297 notes: None,
298 identity: None,
299 card: None,
300 secure_note: None,
301 ssh_key: None,
302 favorite: false,
303 reprompt: CipherRepromptType::None,
304 organization_use_totp: true,
305 edit: true,
306 permissions: None,
307 view_password: true,
308 local_data: None,
309 attachments: 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 };
316 let encrypted = key_store.encrypt(cipher_view).unwrap();
317
318 let cipher: crate::Cipher = crate::Cipher::from_cipher(&key_store, encrypted).unwrap();
319
320 assert_eq!(cipher.id, test_id);
321 assert_eq!(cipher.folder_id, None);
322 assert_eq!(cipher.name, "My login".to_string());
323 assert_eq!(cipher.notes, None);
324 assert!(!cipher.favorite);
325 assert_eq!(cipher.reprompt, 0);
326 assert!(cipher.fields.is_empty());
327 assert_eq!(
328 cipher.revision_date,
329 "2024-01-30T17:55:36.150Z".parse::<DateTime<Utc>>().unwrap()
330 );
331 assert_eq!(
332 cipher.creation_date,
333 "2024-01-30T17:55:36.150Z".parse::<DateTime<Utc>>().unwrap()
334 );
335 assert_eq!(cipher.deleted_date, None);
336
337 if let crate::CipherType::Login(l) = cipher.r#type {
338 assert_eq!(l.username, Some("test_username".to_string()));
339 assert_eq!(l.password, Some("test_password".to_string()));
340 assert!(l.login_uris.is_empty());
341 assert_eq!(l.totp, None);
342 } else {
343 panic!("Expected login type");
344 }
345 }
346}