1use super::{Fido2CredentialDataV1, Fido2CredentialFullView, LoginUriDataV1, LoginUriView};
2
3impl From<&LoginUriView> for LoginUriDataV1 {
6 fn from(view: &LoginUriView) -> Self {
7 Self {
8 uri: view.uri.clone(),
9 r#match: view.r#match,
10 }
11 }
12}
13
14impl From<&LoginUriDataV1> for LoginUriView {
15 fn from(data: &LoginUriDataV1) -> Self {
16 Self {
17 uri: data.uri.clone(),
18 r#match: data.r#match,
19 uri_checksum: None,
20 }
21 }
22}
23
24impl From<&Fido2CredentialFullView> for Fido2CredentialDataV1 {
27 fn from(view: &Fido2CredentialFullView) -> Self {
28 Self {
29 credential_id: view.credential_id.clone(),
30 key_type: view.key_type.clone(),
31 key_algorithm: view.key_algorithm.clone(),
32 key_curve: view.key_curve.clone(),
33 key_value: view.key_value.clone(),
34 rp_id: view.rp_id.clone(),
35 user_handle: view.user_handle.clone(),
36 user_name: view.user_name.clone(),
37 counter: view.counter.parse().unwrap_or(0),
38 rp_name: view.rp_name.clone(),
39 user_display_name: view.user_display_name.clone(),
40 discoverable: view.discoverable == "true",
41 creation_date: view.creation_date,
42 }
43 }
44}
45
46impl From<&Fido2CredentialDataV1> for Fido2CredentialFullView {
47 fn from(data: &Fido2CredentialDataV1) -> Self {
48 Self {
49 credential_id: data.credential_id.clone(),
50 key_type: data.key_type.clone(),
51 key_algorithm: data.key_algorithm.clone(),
52 key_curve: data.key_curve.clone(),
53 key_value: data.key_value.clone(),
54 rp_id: data.rp_id.clone(),
55 user_handle: data.user_handle.clone(),
56 user_name: data.user_name.clone(),
57 counter: data.counter.to_string(),
58 rp_name: data.rp_name.clone(),
59 user_display_name: data.user_display_name.clone(),
60 discoverable: data.discoverable.to_string(),
61 creation_date: data.creation_date,
62 }
63 }
64}
65
66#[cfg(test)]
67mod tests {
68 use bitwarden_crypto::{CompositeEncryptable, Decryptable};
69 use chrono::{TimeZone, Utc};
70
71 use super::super::{CipherBlobV1, CipherTypeDataV1, LoginUriDataV1, test_support::*};
72 use crate::cipher::{
73 cipher::CipherType,
74 field::{FieldType, FieldView},
75 linked_id::{LinkedIdType, LoginLinkedIdType},
76 login::{Fido2Credential, Fido2CredentialFullView, LoginUriView, LoginView, UriMatchType},
77 };
78
79 #[test]
80 fn test_login_uri_view_to_data_drops_checksum() {
81 let view = LoginUriView {
82 uri: Some("https://example.com".to_string()),
83 r#match: Some(UriMatchType::Domain),
84 uri_checksum: Some("some-checksum-value".to_string()),
85 };
86
87 let data = LoginUriDataV1::from(&view);
88
89 assert_eq!(data.uri, Some("https://example.com".to_string()));
90 assert_eq!(data.r#match, Some(UriMatchType::Domain));
91 }
92
93 #[test]
94 fn test_login_uri_data_to_view_sets_checksum_none() {
95 let data = LoginUriDataV1 {
96 uri: Some("https://example.com".to_string()),
97 r#match: Some(UriMatchType::Domain),
98 };
99
100 let view = LoginUriView::from(&data);
101
102 assert_eq!(view.uri, Some("https://example.com".to_string()));
103 assert_eq!(view.r#match, Some(UriMatchType::Domain));
104 assert_eq!(view.uri_checksum, None);
105 }
106
107 #[test]
108 fn test_fido2_counter_parsing() {
109 let full_view = Fido2CredentialFullView {
110 credential_id: "cred-id".to_string(),
111 key_type: "public-key".to_string(),
112 key_algorithm: "ECDSA".to_string(),
113 key_curve: "P-256".to_string(),
114 key_value: "key-value".to_string(),
115 rp_id: "example.com".to_string(),
116 user_handle: None,
117 user_name: None,
118 counter: "42".to_string(),
119 rp_name: None,
120 user_display_name: None,
121 discoverable: "true".to_string(),
122 creation_date: Utc.with_ymd_and_hms(2024, 1, 1, 0, 0, 0).unwrap(),
123 };
124
125 let data = super::Fido2CredentialDataV1::from(&full_view);
126 assert_eq!(data.counter, 42);
127 assert!(data.discoverable);
128
129 let round_tripped = Fido2CredentialFullView::from(&data);
130 assert_eq!(round_tripped.counter, "42");
131 assert_eq!(round_tripped.discoverable, "true");
132 }
133
134 #[test]
135 fn test_fido2_counter_zero() {
136 let full_view = Fido2CredentialFullView {
137 credential_id: "cred-id".to_string(),
138 key_type: "public-key".to_string(),
139 key_algorithm: "ECDSA".to_string(),
140 key_curve: "P-256".to_string(),
141 key_value: "key-value".to_string(),
142 rp_id: "example.com".to_string(),
143 user_handle: None,
144 user_name: None,
145 counter: "0".to_string(),
146 rp_name: None,
147 user_display_name: None,
148 discoverable: "false".to_string(),
149 creation_date: Utc.with_ymd_and_hms(2024, 1, 1, 0, 0, 0).unwrap(),
150 };
151
152 let data = super::Fido2CredentialDataV1::from(&full_view);
153 assert_eq!(data.counter, 0);
154 assert!(!data.discoverable);
155
156 let round_tripped = Fido2CredentialFullView::from(&data);
157 assert_eq!(round_tripped.counter, "0");
158 assert_eq!(round_tripped.discoverable, "false");
159 }
160
161 #[test]
162 fn test_fido2_counter_invalid_defaults_to_zero() {
163 let full_view = Fido2CredentialFullView {
164 credential_id: "cred-id".to_string(),
165 key_type: "public-key".to_string(),
166 key_algorithm: "ECDSA".to_string(),
167 key_curve: "P-256".to_string(),
168 key_value: "key-value".to_string(),
169 rp_id: "example.com".to_string(),
170 user_handle: None,
171 user_name: None,
172 counter: "not-a-number".to_string(),
173 rp_name: None,
174 user_display_name: None,
175 discoverable: "true".to_string(),
176 creation_date: Utc.with_ymd_and_hms(2024, 1, 1, 0, 0, 0).unwrap(),
177 };
178
179 let data = super::Fido2CredentialDataV1::from(&full_view);
180 assert_eq!(data.counter, 0);
181 }
182
183 #[test]
184 fn test_login_cipher_round_trip() {
185 let (key_store, key_id) = create_test_key_store();
186 let mut ctx = key_store.context_mut();
187
188 let fido2_full = Fido2CredentialFullView {
190 credential_id: "cred-123".to_string(),
191 key_type: "public-key".to_string(),
192 key_algorithm: "ECDSA".to_string(),
193 key_curve: "P-256".to_string(),
194 key_value: "key-value-base64".to_string(),
195 rp_id: "example.com".to_string(),
196 user_handle: Some("user-handle".to_string()),
197 user_name: Some("testuser".to_string()),
198 counter: "42".to_string(),
199 rp_name: Some("Example".to_string()),
200 user_display_name: Some("Test User".to_string()),
201 discoverable: "true".to_string(),
202 creation_date: Utc.with_ymd_and_hms(2024, 6, 1, 10, 30, 0).unwrap(),
203 };
204 let encrypted_fido2: Fido2Credential =
205 fido2_full.encrypt_composite(&mut ctx, key_id).unwrap();
206
207 let original = crate::CipherView {
208 name: "My Login".to_string(),
209 notes: Some("Login notes".to_string()),
210 r#type: CipherType::Login,
211 login: Some(LoginView {
212 username: Some("[email protected]".to_string()),
213 password: Some("p@ssw0rd123".to_string()),
214 password_revision_date: Some(Utc.with_ymd_and_hms(2024, 1, 15, 12, 0, 0).unwrap()),
215 uris: Some(vec![LoginUriView {
216 uri: Some("https://example.com/login".to_string()),
217 r#match: Some(UriMatchType::Domain),
218 uri_checksum: Some("some-checksum".to_string()),
219 }]),
220 totp: Some("otpauth://totp/test?secret=JBSWY3DPEHPK3PXP".to_string()),
221 autofill_on_page_load: Some(true),
222 fido2_credentials: Some(vec![encrypted_fido2]),
223 }),
224 fields: Some(vec![FieldView {
225 name: Some("Custom Field".to_string()),
226 value: Some("custom-value".to_string()),
227 r#type: FieldType::Linked,
228 linked_id: Some(LinkedIdType::Login(LoginLinkedIdType::Username)),
229 }]),
230 password_history: Some(vec![crate::PasswordHistoryView {
231 password: "old-password-1".to_string(),
232 last_used_date: Utc.with_ymd_and_hms(2023, 12, 1, 8, 0, 0).unwrap(),
233 }]),
234 ..create_shell_cipher_view(CipherType::Login)
235 };
236
237 let blob = CipherBlobV1::from_cipher_view(&original, &mut ctx, key_id).unwrap();
238
239 assert_eq!(blob.name, "My Login");
241 assert_eq!(blob.notes, Some("Login notes".to_string()));
242 if let CipherTypeDataV1::Login(ref login_data) = blob.type_data {
243 assert_eq!(
244 login_data.username,
245 Some("[email protected]".to_string())
246 );
247 assert_eq!(login_data.fido2_credentials.len(), 1);
248 assert_eq!(login_data.fido2_credentials[0].counter, 42);
249 assert!(login_data.fido2_credentials[0].discoverable);
250 assert_eq!(login_data.uris.len(), 1);
252 } else {
253 panic!("Expected Login type data");
254 }
255
256 let mut restored = create_shell_cipher_view(CipherType::Login);
258 blob.apply_to_cipher_view(&mut restored, &mut ctx, key_id)
259 .unwrap();
260
261 assert_eq!(restored.name, "My Login");
262 assert_eq!(restored.notes, Some("Login notes".to_string()));
263 assert_eq!(restored.r#type, CipherType::Login);
264
265 let login = restored.login.unwrap();
266 assert_eq!(login.username, Some("[email protected]".to_string()));
267 assert_eq!(login.password, Some("p@ssw0rd123".to_string()));
268 assert_eq!(
269 login.totp,
270 Some("otpauth://totp/test?secret=JBSWY3DPEHPK3PXP".to_string())
271 );
272 assert_eq!(login.autofill_on_page_load, Some(true));
273
274 let uris = login.uris.unwrap();
276 assert_eq!(uris.len(), 1);
277 assert_eq!(uris[0].uri, Some("https://example.com/login".to_string()));
278 assert_eq!(uris[0].r#match, Some(UriMatchType::Domain));
279 assert_eq!(uris[0].uri_checksum, None);
280
281 let fido2 = login.fido2_credentials.unwrap();
283 assert_eq!(fido2.len(), 1);
284 let decrypted: Fido2CredentialFullView = fido2[0].decrypt(&mut ctx, key_id).unwrap();
286 assert_eq!(decrypted.credential_id, "cred-123");
287 assert_eq!(decrypted.counter, "42");
288 assert_eq!(decrypted.discoverable, "true");
289 assert_eq!(decrypted.rp_id, "example.com");
290
291 assert_eq!(restored.fields.as_ref().unwrap().len(), 1);
293 assert_eq!(restored.password_history.as_ref().unwrap().len(), 1);
294
295 assert!(restored.card.is_none());
296 assert!(restored.identity.is_none());
297 assert!(restored.secure_note.is_none());
298 assert!(restored.ssh_key.is_none());
299 }
300}