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