ruma_client_api/discovery/get_authorization_server_metadata/
serde.rs1use std::collections::BTreeSet;
2
3use serde::{de, Deserialize};
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::{from_value as from_json_value, value::Map as JsonMap, Value as JsonValue};
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!(metadata
207 .account_management_actions_supported
208 .contains(&AccountManagementAction::Profile));
209 assert!(metadata
210 .account_management_actions_supported
211 .contains(&AccountManagementAction::SessionsList));
212 assert!(metadata
213 .account_management_actions_supported
214 .contains(&AccountManagementAction::SessionView));
215 assert!(metadata
216 .account_management_actions_supported
217 .contains(&AccountManagementAction::SessionEnd));
218 assert!(metadata
219 .account_management_actions_supported
220 .contains(&AccountManagementAction::AccountDeactivate));
221 assert!(metadata
222 .account_management_actions_supported
223 .contains(&AccountManagementAction::CrossSigningReset));
224 }
225
226 #[cfg(feature = "unstable-msc4108")]
227 assert_eq!(
228 metadata.device_authorization_endpoint.as_ref().map(Url::as_str),
229 Some("https://server.local/device")
230 );
231 }
232
233 #[test]
234 fn metadata_no_optional_fields() {
235 let mut metadata_object = authorization_server_metadata_object();
236 assert!(metadata_object.remove("registration_endpoint").is_some());
237 assert!(metadata_object.remove("response_modes_supported").is_some());
238 assert!(metadata_object.remove("account_management_uri").is_some());
239 assert!(metadata_object.remove("account_management_actions_supported").is_some());
240 assert!(metadata_object.remove("device_authorization_endpoint").is_some());
241
242 let metadata =
243 from_json_value::<AuthorizationServerMetadata>(metadata_object.into()).unwrap();
244
245 assert_eq!(metadata.issuer.as_str(), "https://server.local/");
246 assert_eq!(metadata.authorization_endpoint.as_str(), "https://server.local/authorize");
247 assert_eq!(metadata.token_endpoint.as_str(), "https://server.local/token");
248 assert_eq!(metadata.registration_endpoint, None);
249
250 assert_eq!(metadata.response_types_supported.len(), 1);
251 assert!(metadata.response_types_supported.contains(&ResponseType::Code));
252
253 assert_eq!(metadata.response_modes_supported.len(), 2);
254 assert!(metadata.response_modes_supported.contains(&ResponseMode::Query));
255 assert!(metadata.response_modes_supported.contains(&ResponseMode::Fragment));
256
257 assert_eq!(metadata.grant_types_supported.len(), 2);
258 assert!(metadata.grant_types_supported.contains(&GrantType::AuthorizationCode));
259 assert!(metadata.grant_types_supported.contains(&GrantType::RefreshToken));
260
261 assert_eq!(metadata.revocation_endpoint.as_str(), "https://server.local/revoke");
262
263 assert_eq!(metadata.code_challenge_methods_supported.len(), 1);
264 assert!(metadata.code_challenge_methods_supported.contains(&CodeChallengeMethod::S256));
265
266 #[cfg(feature = "unstable-msc4191")]
267 {
268 assert_eq!(metadata.account_management_uri, None);
269 assert_eq!(metadata.account_management_actions_supported.len(), 0);
270 }
271
272 #[cfg(feature = "unstable-msc4108")]
273 assert_eq!(metadata.device_authorization_endpoint, None);
274 }
275
276 #[test]
277 fn metadata_additional_values() {
278 let mut metadata_object = authorization_server_metadata_object();
279 get_mut_array(&mut metadata_object, "response_types_supported").push("custom".into());
280 get_mut_array(&mut metadata_object, "response_modes_supported").push("custom".into());
281 get_mut_array(&mut metadata_object, "grant_types_supported").push("custom".into());
282 get_mut_array(&mut metadata_object, "code_challenge_methods_supported")
283 .push("custom".into());
284 get_mut_array(&mut metadata_object, "account_management_actions_supported")
285 .push("custom".into());
286
287 let metadata =
288 from_json_value::<AuthorizationServerMetadata>(metadata_object.into()).unwrap();
289
290 assert_eq!(metadata.issuer.as_str(), "https://server.local/");
291 assert_eq!(metadata.authorization_endpoint.as_str(), "https://server.local/authorize");
292 assert_eq!(metadata.token_endpoint.as_str(), "https://server.local/token");
293 assert_eq!(
294 metadata.registration_endpoint.as_ref().map(Url::as_str),
295 Some("https://server.local/register")
296 );
297
298 assert_eq!(metadata.response_types_supported.len(), 2);
299 assert!(metadata.response_types_supported.contains(&ResponseType::Code));
300 assert!(metadata.response_types_supported.contains(&ResponseType::from("custom")));
301
302 assert_eq!(metadata.response_modes_supported.len(), 3);
303 assert!(metadata.response_modes_supported.contains(&ResponseMode::Query));
304 assert!(metadata.response_modes_supported.contains(&ResponseMode::Fragment));
305 assert!(metadata.response_modes_supported.contains(&ResponseMode::from("custom")));
306
307 assert_eq!(metadata.grant_types_supported.len(), 3);
308 assert!(metadata.grant_types_supported.contains(&GrantType::AuthorizationCode));
309 assert!(metadata.grant_types_supported.contains(&GrantType::RefreshToken));
310 assert!(metadata.grant_types_supported.contains(&GrantType::from("custom")));
311
312 assert_eq!(metadata.revocation_endpoint.as_str(), "https://server.local/revoke");
313
314 assert_eq!(metadata.code_challenge_methods_supported.len(), 2);
315 assert!(metadata.code_challenge_methods_supported.contains(&CodeChallengeMethod::S256));
316 assert!(metadata
317 .code_challenge_methods_supported
318 .contains(&CodeChallengeMethod::from("custom")));
319
320 #[cfg(feature = "unstable-msc4191")]
321 {
322 assert_eq!(
323 metadata.account_management_uri.as_ref().map(Url::as_str),
324 Some("https://server.local/account")
325 );
326 assert_eq!(metadata.account_management_actions_supported.len(), 7);
327 assert!(metadata
328 .account_management_actions_supported
329 .contains(&AccountManagementAction::Profile));
330 assert!(metadata
331 .account_management_actions_supported
332 .contains(&AccountManagementAction::SessionsList));
333 assert!(metadata
334 .account_management_actions_supported
335 .contains(&AccountManagementAction::SessionView));
336 assert!(metadata
337 .account_management_actions_supported
338 .contains(&AccountManagementAction::SessionEnd));
339 assert!(metadata
340 .account_management_actions_supported
341 .contains(&AccountManagementAction::AccountDeactivate));
342 assert!(metadata
343 .account_management_actions_supported
344 .contains(&AccountManagementAction::CrossSigningReset));
345 assert!(metadata
346 .account_management_actions_supported
347 .contains(&AccountManagementAction::from("custom")));
348 }
349
350 #[cfg(feature = "unstable-msc4108")]
351 assert_eq!(
352 metadata.device_authorization_endpoint.as_ref().map(Url::as_str),
353 Some("https://server.local/device")
354 );
355 }
356
357 #[test]
358 fn metadata_missing_required_fields() {
359 let original_metadata_object = authorization_server_metadata_object();
360
361 let mut metadata_object = original_metadata_object.clone();
362 assert!(metadata_object.remove("issuer").is_some());
363 from_json_value::<AuthorizationServerMetadata>(metadata_object.into()).unwrap_err();
364
365 let mut metadata_object = original_metadata_object.clone();
366 assert!(metadata_object.remove("authorization_endpoint").is_some());
367 from_json_value::<AuthorizationServerMetadata>(metadata_object.into()).unwrap_err();
368
369 let mut metadata_object = original_metadata_object.clone();
370 assert!(metadata_object.remove("token_endpoint").is_some());
371 from_json_value::<AuthorizationServerMetadata>(metadata_object.into()).unwrap_err();
372
373 let mut metadata_object = original_metadata_object.clone();
374 assert!(metadata_object.remove("response_types_supported").is_some());
375 from_json_value::<AuthorizationServerMetadata>(metadata_object.into()).unwrap_err();
376
377 let mut metadata_object = original_metadata_object.clone();
378 assert!(metadata_object.remove("grant_types_supported").is_some());
379 from_json_value::<AuthorizationServerMetadata>(metadata_object.into()).unwrap_err();
380
381 let mut metadata_object = original_metadata_object.clone();
382 assert!(metadata_object.remove("revocation_endpoint").is_some());
383 from_json_value::<AuthorizationServerMetadata>(metadata_object.into()).unwrap_err();
384
385 let mut metadata_object = original_metadata_object;
386 assert!(metadata_object.remove("code_challenge_methods_supported").is_some());
387 from_json_value::<AuthorizationServerMetadata>(metadata_object.into()).unwrap_err();
388 }
389
390 #[test]
391 fn metadata_missing_required_values() {
392 let original_metadata_object = authorization_server_metadata_object();
393
394 let mut metadata_object = original_metadata_object.clone();
395 get_mut_array(&mut metadata_object, "response_types_supported").clear();
396 from_json_value::<AuthorizationServerMetadata>(metadata_object.into()).unwrap_err();
397
398 let mut metadata_object = original_metadata_object.clone();
399 get_mut_array(&mut metadata_object, "response_modes_supported").clear();
400 from_json_value::<AuthorizationServerMetadata>(metadata_object.into()).unwrap_err();
401
402 let mut metadata_object = original_metadata_object.clone();
403 get_mut_array(&mut metadata_object, "response_modes_supported").remove(0);
404 from_json_value::<AuthorizationServerMetadata>(metadata_object.into()).unwrap_err();
405
406 let mut metadata_object = original_metadata_object.clone();
407 get_mut_array(&mut metadata_object, "response_modes_supported").remove(1);
408 from_json_value::<AuthorizationServerMetadata>(metadata_object.into()).unwrap_err();
409
410 let mut metadata_object = original_metadata_object.clone();
411 get_mut_array(&mut metadata_object, "grant_types_supported").clear();
412 from_json_value::<AuthorizationServerMetadata>(metadata_object.into()).unwrap_err();
413
414 let mut metadata_object = original_metadata_object.clone();
415 get_mut_array(&mut metadata_object, "grant_types_supported").remove(0);
416 from_json_value::<AuthorizationServerMetadata>(metadata_object.into()).unwrap_err();
417
418 let mut metadata_object = original_metadata_object.clone();
419 get_mut_array(&mut metadata_object, "grant_types_supported").remove(1);
420 from_json_value::<AuthorizationServerMetadata>(metadata_object.into()).unwrap_err();
421
422 let mut metadata_object = original_metadata_object;
423 get_mut_array(&mut metadata_object, "code_challenge_methods_supported").clear();
424 from_json_value::<AuthorizationServerMetadata>(metadata_object.into()).unwrap_err();
425 }
426}