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