ruma_client_api/discovery/get_authorization_server_metadata/
serde.rs

1use 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        // Require `code` in `response_types_supported`.
36        if !response_types_supported.contains(&ResponseType::Code) {
37            return Err(de::Error::custom("missing value `code` in `response_types_supported`"));
38        }
39
40        // Require `query` and `fragment` in `response_modes_supported`.
41        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        // If the field is missing, the default value is `["query", "fragment"]`, according to
62        // RFC8414.
63        let response_modes_supported = response_modes_supported
64            .unwrap_or_else(|| [ResponseMode::Query, ResponseMode::Fragment].into());
65
66        // Require `authorization_code` and `refresh_token` in `grant_types_supported`.
67        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        // Require `S256` in `code_challenge_methods_supported`.
87        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    /// A valid `AuthorizationServerMetadata` with all fields and values, as a JSON object.
147    fn authorization_server_metadata_object() -> JsonMap<String, JsonValue> {
148        as_variant!(authorization_server_metadata_json(), JsonValue::Object).unwrap()
149    }
150
151    /// Get a mutable reference to the array value with the given key in the given object.
152    ///
153    /// Panics if the property doesn't exist or is not an array.
154    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}