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 chrono::{TimeZone, Utc};
69
70 use super::super::{CipherBlobV1, CipherTypeDataV1, LoginUriDataV1, test_support::*};
71 use crate::cipher::{
72 cipher::CipherType,
73 field::{FieldType, FieldView},
74 linked_id::{LinkedIdType, LoginLinkedIdType},
75 login::{
76 Fido2CredentialFullView, Fido2CredentialView, LoginUriView, LoginView, UriMatchType,
77 },
78 };
79
80 #[test]
81 fn test_login_uri_view_to_data_drops_checksum() {
82 let view = LoginUriView {
83 uri: Some("https://example.com".to_string()),
84 r#match: Some(UriMatchType::Domain),
85 uri_checksum: Some("some-checksum-value".to_string()),
86 };
87
88 let data = LoginUriDataV1::from(&view);
89
90 assert_eq!(data.uri, Some("https://example.com".to_string()));
91 assert_eq!(data.r#match, Some(UriMatchType::Domain));
92 }
93
94 #[test]
95 fn test_login_uri_data_to_view_sets_checksum_none() {
96 let data = LoginUriDataV1 {
97 uri: Some("https://example.com".to_string()),
98 r#match: Some(UriMatchType::Domain),
99 };
100
101 let view = LoginUriView::from(&data);
102
103 assert_eq!(view.uri, Some("https://example.com".to_string()));
104 assert_eq!(view.r#match, Some(UriMatchType::Domain));
105 assert_eq!(view.uri_checksum, None);
106 }
107
108 #[test]
109 fn test_fido2_counter_parsing() {
110 let full_view = Fido2CredentialFullView {
111 credential_id: "cred-id".to_string(),
112 key_type: "public-key".to_string(),
113 key_algorithm: "ECDSA".to_string(),
114 key_curve: "P-256".to_string(),
115 key_value: "key-value".to_string(),
116 rp_id: "example.com".to_string(),
117 user_handle: None,
118 user_name: None,
119 counter: "42".to_string(),
120 rp_name: None,
121 user_display_name: None,
122 discoverable: "true".to_string(),
123 creation_date: Utc.with_ymd_and_hms(2024, 1, 1, 0, 0, 0).unwrap(),
124 };
125
126 let data = super::Fido2CredentialDataV1::from(&full_view);
127 assert_eq!(data.counter, 42);
128 assert!(data.discoverable);
129
130 let round_tripped = Fido2CredentialFullView::from(&data);
131 assert_eq!(round_tripped.counter, "42");
132 assert_eq!(round_tripped.discoverable, "true");
133 }
134
135 #[test]
136 fn test_fido2_counter_zero() {
137 let full_view = Fido2CredentialFullView {
138 credential_id: "cred-id".to_string(),
139 key_type: "public-key".to_string(),
140 key_algorithm: "ECDSA".to_string(),
141 key_curve: "P-256".to_string(),
142 key_value: "key-value".to_string(),
143 rp_id: "example.com".to_string(),
144 user_handle: None,
145 user_name: None,
146 counter: "0".to_string(),
147 rp_name: None,
148 user_display_name: None,
149 discoverable: "false".to_string(),
150 creation_date: Utc.with_ymd_and_hms(2024, 1, 1, 0, 0, 0).unwrap(),
151 };
152
153 let data = super::Fido2CredentialDataV1::from(&full_view);
154 assert_eq!(data.counter, 0);
155 assert!(!data.discoverable);
156
157 let round_tripped = Fido2CredentialFullView::from(&data);
158 assert_eq!(round_tripped.counter, "0");
159 assert_eq!(round_tripped.discoverable, "false");
160 }
161
162 #[test]
163 fn test_fido2_counter_invalid_defaults_to_zero() {
164 let full_view = Fido2CredentialFullView {
165 credential_id: "cred-id".to_string(),
166 key_type: "public-key".to_string(),
167 key_algorithm: "ECDSA".to_string(),
168 key_curve: "P-256".to_string(),
169 key_value: "key-value".to_string(),
170 rp_id: "example.com".to_string(),
171 user_handle: None,
172 user_name: None,
173 counter: "not-a-number".to_string(),
174 rp_name: None,
175 user_display_name: None,
176 discoverable: "true".to_string(),
177 creation_date: Utc.with_ymd_and_hms(2024, 1, 1, 0, 0, 0).unwrap(),
178 };
179
180 let data = super::Fido2CredentialDataV1::from(&full_view);
181 assert_eq!(data.counter, 0);
182 }
183
184 #[test]
185 fn test_login_cipher_round_trip() {
186 let fido2_view = Fido2CredentialView {
188 credential_id: "cred-123".to_string(),
189 key_type: "public-key".to_string(),
190 key_algorithm: "ECDSA".to_string(),
191 key_curve: "P-256".to_string(),
192 key_value: "key-value-base64".to_string(),
193 rp_id: "example.com".to_string(),
194 user_handle: Some("user-handle".to_string()),
195 user_name: Some("testuser".to_string()),
196 counter: "42".to_string(),
197 rp_name: Some("Example".to_string()),
198 user_display_name: Some("Test User".to_string()),
199 discoverable: "true".to_string(),
200 creation_date: Utc.with_ymd_and_hms(2024, 6, 1, 10, 30, 0).unwrap(),
201 };
202
203 let original = crate::CipherView {
204 name: "My Login".to_string(),
205 notes: Some("Login notes".to_string()),
206 r#type: CipherType::Login,
207 login: Some(LoginView {
208 username: Some("[email protected]".to_string()),
209 password: Some("p@ssw0rd123".to_string()),
210 password_revision_date: Some(Utc.with_ymd_and_hms(2024, 1, 15, 12, 0, 0).unwrap()),
211 uris: Some(vec![LoginUriView {
212 uri: Some("https://example.com/login".to_string()),
213 r#match: Some(UriMatchType::Domain),
214 uri_checksum: Some("some-checksum".to_string()),
215 }]),
216 totp: Some("otpauth://totp/test?secret=JBSWY3DPEHPK3PXP".to_string()),
217 autofill_on_page_load: Some(true),
218 fido2_credentials: Some(vec![fido2_view]),
219 }),
220 fields: Some(vec![FieldView {
221 name: Some("Custom Field".to_string()),
222 value: Some("custom-value".to_string()),
223 r#type: FieldType::Linked,
224 linked_id: Some(LinkedIdType::Login(LoginLinkedIdType::Username)),
225 }]),
226 password_history: Some(vec![crate::PasswordHistoryView {
227 password: "old-password-1".to_string(),
228 last_used_date: Utc.with_ymd_and_hms(2023, 12, 1, 8, 0, 0).unwrap(),
229 }]),
230 ..create_shell_cipher_view(CipherType::Login)
231 };
232
233 let blob = CipherBlobV1::from_cipher_view(&original).unwrap();
234
235 assert_eq!(blob.name, "My Login");
237 assert_eq!(blob.notes, Some("Login notes".to_string()));
238 if let CipherTypeDataV1::Login(ref login_data) = blob.type_data {
239 assert_eq!(
240 login_data.username,
241 Some("[email protected]".to_string())
242 );
243 assert_eq!(login_data.fido2_credentials.len(), 1);
244 assert_eq!(login_data.fido2_credentials[0].counter, 42);
245 assert!(login_data.fido2_credentials[0].discoverable);
246 assert_eq!(login_data.uris.len(), 1);
248 } else {
249 panic!("Expected Login type data");
250 }
251
252 let mut restored = create_shell_cipher_view(CipherType::Login);
254 blob.apply_to_cipher_view(&mut restored).unwrap();
255
256 assert_eq!(restored.name, "My Login");
257 assert_eq!(restored.notes, Some("Login notes".to_string()));
258 assert_eq!(restored.r#type, CipherType::Login);
259
260 let login = restored.login.unwrap();
261 assert_eq!(login.username, Some("[email protected]".to_string()));
262 assert_eq!(login.password, Some("p@ssw0rd123".to_string()));
263 assert_eq!(
264 login.totp,
265 Some("otpauth://totp/test?secret=JBSWY3DPEHPK3PXP".to_string())
266 );
267 assert_eq!(login.autofill_on_page_load, Some(true));
268
269 let uris = login.uris.unwrap();
271 assert_eq!(uris.len(), 1);
272 assert_eq!(uris[0].uri, Some("https://example.com/login".to_string()));
273 assert_eq!(uris[0].r#match, Some(UriMatchType::Domain));
274 assert_eq!(uris[0].uri_checksum, None);
275
276 let fido2 = login.fido2_credentials.unwrap();
278 assert_eq!(fido2.len(), 1);
279 assert_eq!(fido2[0].credential_id, "cred-123");
280 assert_eq!(fido2[0].counter, "42");
281 assert_eq!(fido2[0].discoverable, "true");
282 assert_eq!(fido2[0].rp_id, "example.com");
283
284 assert_eq!(restored.fields.as_ref().unwrap().len(), 1);
286 assert_eq!(restored.password_history.as_ref().unwrap().len(), 1);
287
288 assert!(restored.card.is_none());
289 assert!(restored.identity.is_none());
290 assert!(restored.secure_note.is_none());
291 assert!(restored.ssh_key.is_none());
292 }
293}