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