ruma_client_api/discovery/get_authorization_server_metadata/
serde.rs1use std::collections::BTreeSet;
2
3use serde::{Deserialize, de};
4use url::Url;
5
6use super::v1::{
7 AccountManagementAction, AuthorizationServerMetadata, CodeChallengeMethod, GrantType, Prompt,
8 ResponseMode, ResponseType,
9};
10
11impl<'de> Deserialize<'de> for AuthorizationServerMetadata {
12 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
13 where
14 D: serde::Deserializer<'de>,
15 {
16 let helper = AuthorizationServerMetadataDeHelper::deserialize(deserializer)?;
17
18 let AuthorizationServerMetadataDeHelper {
19 issuer,
20 authorization_endpoint,
21 token_endpoint,
22 registration_endpoint,
23 response_types_supported,
24 response_modes_supported,
25 grant_types_supported,
26 revocation_endpoint,
27 code_challenge_methods_supported,
28 account_management_uri,
29 account_management_actions_supported,
30 device_authorization_endpoint,
31 prompt_values_supported,
32 } = helper;
33
34 if !response_types_supported.contains(&ResponseType::Code) {
36 return Err(de::Error::custom("missing value `code` in `response_types_supported`"));
37 }
38
39 if let Some(response_modes) = &response_modes_supported {
41 let query_found = response_modes.contains(&ResponseMode::Query);
42 let fragment_found = response_modes.contains(&ResponseMode::Fragment);
43
44 if !query_found && !fragment_found {
45 return Err(de::Error::custom(
46 "missing values `query` and `fragment` in `response_modes_supported`",
47 ));
48 }
49 if !query_found {
50 return Err(de::Error::custom(
51 "missing value `query` in `response_modes_supported`",
52 ));
53 }
54 if !fragment_found {
55 return Err(de::Error::custom(
56 "missing value `fragment` in `response_modes_supported`",
57 ));
58 }
59 }
60 let response_modes_supported = response_modes_supported
63 .unwrap_or_else(|| [ResponseMode::Query, ResponseMode::Fragment].into());
64
65 let authorization_code_found =
67 grant_types_supported.contains(&GrantType::AuthorizationCode);
68 let refresh_token_found = grant_types_supported.contains(&GrantType::RefreshToken);
69 if !authorization_code_found && !refresh_token_found {
70 return Err(de::Error::custom(
71 "missing values `authorization_code` and `refresh_token` in `grant_types_supported`",
72 ));
73 }
74 if !authorization_code_found {
75 return Err(de::Error::custom(
76 "missing value `authorization_code` in `grant_types_supported`",
77 ));
78 }
79 if !refresh_token_found {
80 return Err(de::Error::custom(
81 "missing value `refresh_token` in `grant_types_supported`",
82 ));
83 }
84
85 if !code_challenge_methods_supported.contains(&CodeChallengeMethod::S256) {
87 return Err(de::Error::custom(
88 "missing value `S256` in `code_challenge_methods_supported`",
89 ));
90 }
91
92 Ok(AuthorizationServerMetadata {
93 issuer,
94 authorization_endpoint,
95 token_endpoint,
96 registration_endpoint,
97 response_types_supported,
98 response_modes_supported,
99 grant_types_supported,
100 revocation_endpoint,
101 code_challenge_methods_supported,
102 account_management_uri,
103 account_management_actions_supported,
104 device_authorization_endpoint,
105 prompt_values_supported,
106 })
107 }
108}
109
110#[derive(Deserialize)]
111struct AuthorizationServerMetadataDeHelper {
112 issuer: Url,
113 authorization_endpoint: Url,
114 token_endpoint: Url,
115 registration_endpoint: Option<Url>,
116 response_types_supported: BTreeSet<ResponseType>,
117 response_modes_supported: Option<BTreeSet<ResponseMode>>,
118 grant_types_supported: BTreeSet<GrantType>,
119 revocation_endpoint: Url,
120 code_challenge_methods_supported: BTreeSet<CodeChallengeMethod>,
121 account_management_uri: Option<Url>,
122 #[serde(default)]
123 account_management_actions_supported: BTreeSet<AccountManagementAction>,
124 device_authorization_endpoint: Option<Url>,
125 #[serde(default)]
126 prompt_values_supported: Vec<Prompt>,
127}
128
129#[cfg(test)]
130mod tests {
131 use as_variant::as_variant;
132 use serde_json::{Value as JsonValue, from_value as from_json_value, value::Map as JsonMap};
133 use url::Url;
134
135 use crate::discovery::get_authorization_server_metadata::{
136 tests::authorization_server_metadata_json,
137 v1::{
138 AccountManagementAction, AuthorizationServerMetadata, CodeChallengeMethod, GrantType,
139 ResponseMode, ResponseType,
140 },
141 };
142
143 fn authorization_server_metadata_object() -> JsonMap<String, JsonValue> {
145 as_variant!(authorization_server_metadata_json(), JsonValue::Object).unwrap()
146 }
147
148 fn get_mut_array<'a>(
152 object: &'a mut JsonMap<String, JsonValue>,
153 key: &str,
154 ) -> &'a mut Vec<JsonValue> {
155 object.get_mut(key).unwrap().as_array_mut().unwrap()
156 }
157
158 #[test]
159 fn metadata_all_fields() {
160 let metadata_object = authorization_server_metadata_object();
161 let metadata =
162 from_json_value::<AuthorizationServerMetadata>(metadata_object.into()).unwrap();
163
164 assert_eq!(metadata.issuer.as_str(), "https://server.local/");
165 assert_eq!(metadata.authorization_endpoint.as_str(), "https://server.local/authorize");
166 assert_eq!(metadata.token_endpoint.as_str(), "https://server.local/token");
167 assert_eq!(
168 metadata.registration_endpoint.as_ref().map(Url::as_str),
169 Some("https://server.local/register")
170 );
171
172 assert_eq!(metadata.response_types_supported.len(), 1);
173 assert!(metadata.response_types_supported.contains(&ResponseType::Code));
174
175 assert_eq!(metadata.response_modes_supported.len(), 2);
176 assert!(metadata.response_modes_supported.contains(&ResponseMode::Query));
177 assert!(metadata.response_modes_supported.contains(&ResponseMode::Fragment));
178
179 assert_eq!(metadata.grant_types_supported.len(), 2);
180 assert!(metadata.grant_types_supported.contains(&GrantType::AuthorizationCode));
181 assert!(metadata.grant_types_supported.contains(&GrantType::RefreshToken));
182
183 assert_eq!(metadata.revocation_endpoint.as_str(), "https://server.local/revoke");
184
185 assert_eq!(metadata.code_challenge_methods_supported.len(), 1);
186 assert!(metadata.code_challenge_methods_supported.contains(&CodeChallengeMethod::S256));
187
188 assert_eq!(
189 metadata.account_management_uri.as_ref().map(Url::as_str),
190 Some("https://server.local/account")
191 );
192 assert_eq!(metadata.account_management_actions_supported.len(), 6);
193 assert!(
194 metadata
195 .account_management_actions_supported
196 .contains(&AccountManagementAction::Profile)
197 );
198 assert!(
199 metadata
200 .account_management_actions_supported
201 .contains(&AccountManagementAction::DevicesList)
202 );
203 assert!(
204 metadata
205 .account_management_actions_supported
206 .contains(&AccountManagementAction::DeviceView)
207 );
208 assert!(
209 metadata
210 .account_management_actions_supported
211 .contains(&AccountManagementAction::DeviceDelete)
212 );
213 assert!(
214 metadata
215 .account_management_actions_supported
216 .contains(&AccountManagementAction::AccountDeactivate)
217 );
218 assert!(
219 metadata
220 .account_management_actions_supported
221 .contains(&AccountManagementAction::CrossSigningReset)
222 );
223
224 assert_eq!(
225 metadata.device_authorization_endpoint.as_ref().map(Url::as_str),
226 Some("https://server.local/device")
227 );
228 }
229
230 #[test]
231 fn metadata_no_optional_fields() {
232 let mut metadata_object = authorization_server_metadata_object();
233 assert!(metadata_object.remove("registration_endpoint").is_some());
234 assert!(metadata_object.remove("response_modes_supported").is_some());
235 assert!(metadata_object.remove("account_management_uri").is_some());
236 assert!(metadata_object.remove("account_management_actions_supported").is_some());
237 assert!(metadata_object.remove("device_authorization_endpoint").is_some());
238
239 let metadata =
240 from_json_value::<AuthorizationServerMetadata>(metadata_object.into()).unwrap();
241
242 assert_eq!(metadata.issuer.as_str(), "https://server.local/");
243 assert_eq!(metadata.authorization_endpoint.as_str(), "https://server.local/authorize");
244 assert_eq!(metadata.token_endpoint.as_str(), "https://server.local/token");
245 assert_eq!(metadata.registration_endpoint, None);
246
247 assert_eq!(metadata.response_types_supported.len(), 1);
248 assert!(metadata.response_types_supported.contains(&ResponseType::Code));
249
250 assert_eq!(metadata.response_modes_supported.len(), 2);
251 assert!(metadata.response_modes_supported.contains(&ResponseMode::Query));
252 assert!(metadata.response_modes_supported.contains(&ResponseMode::Fragment));
253
254 assert_eq!(metadata.grant_types_supported.len(), 2);
255 assert!(metadata.grant_types_supported.contains(&GrantType::AuthorizationCode));
256 assert!(metadata.grant_types_supported.contains(&GrantType::RefreshToken));
257
258 assert_eq!(metadata.revocation_endpoint.as_str(), "https://server.local/revoke");
259
260 assert_eq!(metadata.code_challenge_methods_supported.len(), 1);
261 assert!(metadata.code_challenge_methods_supported.contains(&CodeChallengeMethod::S256));
262
263 assert_eq!(metadata.account_management_uri, None);
264 assert_eq!(metadata.account_management_actions_supported.len(), 0);
265
266 assert_eq!(metadata.device_authorization_endpoint, None);
267 }
268
269 #[test]
270 fn metadata_additional_values() {
271 let mut metadata_object = authorization_server_metadata_object();
272 get_mut_array(&mut metadata_object, "response_types_supported").push("custom".into());
273 get_mut_array(&mut metadata_object, "response_modes_supported").push("custom".into());
274 get_mut_array(&mut metadata_object, "grant_types_supported").push("custom".into());
275 get_mut_array(&mut metadata_object, "code_challenge_methods_supported")
276 .push("custom".into());
277 get_mut_array(&mut metadata_object, "account_management_actions_supported")
278 .push("custom".into());
279
280 let metadata =
281 from_json_value::<AuthorizationServerMetadata>(metadata_object.into()).unwrap();
282
283 assert_eq!(metadata.issuer.as_str(), "https://server.local/");
284 assert_eq!(metadata.authorization_endpoint.as_str(), "https://server.local/authorize");
285 assert_eq!(metadata.token_endpoint.as_str(), "https://server.local/token");
286 assert_eq!(
287 metadata.registration_endpoint.as_ref().map(Url::as_str),
288 Some("https://server.local/register")
289 );
290
291 assert_eq!(metadata.response_types_supported.len(), 2);
292 assert!(metadata.response_types_supported.contains(&ResponseType::Code));
293 assert!(metadata.response_types_supported.contains(&ResponseType::from("custom")));
294
295 assert_eq!(metadata.response_modes_supported.len(), 3);
296 assert!(metadata.response_modes_supported.contains(&ResponseMode::Query));
297 assert!(metadata.response_modes_supported.contains(&ResponseMode::Fragment));
298 assert!(metadata.response_modes_supported.contains(&ResponseMode::from("custom")));
299
300 assert_eq!(metadata.grant_types_supported.len(), 3);
301 assert!(metadata.grant_types_supported.contains(&GrantType::AuthorizationCode));
302 assert!(metadata.grant_types_supported.contains(&GrantType::RefreshToken));
303 assert!(metadata.grant_types_supported.contains(&GrantType::from("custom")));
304
305 assert_eq!(metadata.revocation_endpoint.as_str(), "https://server.local/revoke");
306
307 assert_eq!(metadata.code_challenge_methods_supported.len(), 2);
308 assert!(metadata.code_challenge_methods_supported.contains(&CodeChallengeMethod::S256));
309 assert!(
310 metadata
311 .code_challenge_methods_supported
312 .contains(&CodeChallengeMethod::from("custom"))
313 );
314
315 assert_eq!(
316 metadata.account_management_uri.as_ref().map(Url::as_str),
317 Some("https://server.local/account")
318 );
319 assert_eq!(metadata.account_management_actions_supported.len(), 7);
320 assert!(
321 metadata
322 .account_management_actions_supported
323 .contains(&AccountManagementAction::Profile)
324 );
325 assert!(
326 metadata
327 .account_management_actions_supported
328 .contains(&AccountManagementAction::DevicesList)
329 );
330 assert!(
331 metadata
332 .account_management_actions_supported
333 .contains(&AccountManagementAction::DeviceView)
334 );
335 assert!(
336 metadata
337 .account_management_actions_supported
338 .contains(&AccountManagementAction::DeviceDelete)
339 );
340 assert!(
341 metadata
342 .account_management_actions_supported
343 .contains(&AccountManagementAction::AccountDeactivate)
344 );
345 assert!(
346 metadata
347 .account_management_actions_supported
348 .contains(&AccountManagementAction::CrossSigningReset)
349 );
350 assert!(
351 metadata
352 .account_management_actions_supported
353 .contains(&AccountManagementAction::from("custom"))
354 );
355
356 assert_eq!(
357 metadata.device_authorization_endpoint.as_ref().map(Url::as_str),
358 Some("https://server.local/device")
359 );
360 }
361
362 #[test]
363 fn metadata_missing_required_fields() {
364 let original_metadata_object = authorization_server_metadata_object();
365
366 let mut metadata_object = original_metadata_object.clone();
367 assert!(metadata_object.remove("issuer").is_some());
368 from_json_value::<AuthorizationServerMetadata>(metadata_object.into()).unwrap_err();
369
370 let mut metadata_object = original_metadata_object.clone();
371 assert!(metadata_object.remove("authorization_endpoint").is_some());
372 from_json_value::<AuthorizationServerMetadata>(metadata_object.into()).unwrap_err();
373
374 let mut metadata_object = original_metadata_object.clone();
375 assert!(metadata_object.remove("token_endpoint").is_some());
376 from_json_value::<AuthorizationServerMetadata>(metadata_object.into()).unwrap_err();
377
378 let mut metadata_object = original_metadata_object.clone();
379 assert!(metadata_object.remove("response_types_supported").is_some());
380 from_json_value::<AuthorizationServerMetadata>(metadata_object.into()).unwrap_err();
381
382 let mut metadata_object = original_metadata_object.clone();
383 assert!(metadata_object.remove("grant_types_supported").is_some());
384 from_json_value::<AuthorizationServerMetadata>(metadata_object.into()).unwrap_err();
385
386 let mut metadata_object = original_metadata_object.clone();
387 assert!(metadata_object.remove("revocation_endpoint").is_some());
388 from_json_value::<AuthorizationServerMetadata>(metadata_object.into()).unwrap_err();
389
390 let mut metadata_object = original_metadata_object;
391 assert!(metadata_object.remove("code_challenge_methods_supported").is_some());
392 from_json_value::<AuthorizationServerMetadata>(metadata_object.into()).unwrap_err();
393 }
394
395 #[test]
396 fn metadata_missing_required_values() {
397 let original_metadata_object = authorization_server_metadata_object();
398
399 let mut metadata_object = original_metadata_object.clone();
400 get_mut_array(&mut metadata_object, "response_types_supported").clear();
401 from_json_value::<AuthorizationServerMetadata>(metadata_object.into()).unwrap_err();
402
403 let mut metadata_object = original_metadata_object.clone();
404 get_mut_array(&mut metadata_object, "response_modes_supported").clear();
405 from_json_value::<AuthorizationServerMetadata>(metadata_object.into()).unwrap_err();
406
407 let mut metadata_object = original_metadata_object.clone();
408 get_mut_array(&mut metadata_object, "response_modes_supported").remove(0);
409 from_json_value::<AuthorizationServerMetadata>(metadata_object.into()).unwrap_err();
410
411 let mut metadata_object = original_metadata_object.clone();
412 get_mut_array(&mut metadata_object, "response_modes_supported").remove(1);
413 from_json_value::<AuthorizationServerMetadata>(metadata_object.into()).unwrap_err();
414
415 let mut metadata_object = original_metadata_object.clone();
416 get_mut_array(&mut metadata_object, "grant_types_supported").clear();
417 from_json_value::<AuthorizationServerMetadata>(metadata_object.into()).unwrap_err();
418
419 let mut metadata_object = original_metadata_object.clone();
420 get_mut_array(&mut metadata_object, "grant_types_supported").remove(0);
421 from_json_value::<AuthorizationServerMetadata>(metadata_object.into()).unwrap_err();
422
423 let mut metadata_object = original_metadata_object.clone();
424 get_mut_array(&mut metadata_object, "grant_types_supported").remove(1);
425 from_json_value::<AuthorizationServerMetadata>(metadata_object.into()).unwrap_err();
426
427 let mut metadata_object = original_metadata_object;
428 get_mut_array(&mut metadata_object, "code_challenge_methods_supported").clear();
429 from_json_value::<AuthorizationServerMetadata>(metadata_object.into()).unwrap_err();
430 }
431}