Skip to main content

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            device_authorization_endpoint,
31            prompt_values_supported,
32        } = helper;
33
34        // Require `code` in `response_types_supported`.
35        if !response_types_supported.contains(&ResponseType::Code) {
36            return Err(de::Error::custom("missing value `code` in `response_types_supported`"));
37        }
38
39        // Require `query` and `fragment` in `response_modes_supported`.
40        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        // If the field is missing, the default value is `["query", "fragment"]`, according to
61        // RFC8414.
62        let response_modes_supported = response_modes_supported
63            .unwrap_or_else(|| [ResponseMode::Query, ResponseMode::Fragment].into());
64
65        // Require `authorization_code` and `refresh_token` in `grant_types_supported`.
66        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        // Require `S256` in `code_challenge_methods_supported`.
86        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::{Value as JsonValue, from_value as from_json_value, value::Map as JsonMap};
133    use url::Url;
134
135    use crate::discovery::get_authorization_server_metadata::{
136        tests::authorization_server_metadata_json,
137        v1::{
138            AccountManagementAction, AuthorizationServerMetadata, CodeChallengeMethod, GrantType,
139            ResponseMode, ResponseType,
140        },
141    };
142
143    /// A valid `AuthorizationServerMetadata` with all fields and values, as a JSON object.
144    fn authorization_server_metadata_object() -> JsonMap<String, JsonValue> {
145        as_variant!(authorization_server_metadata_json(), JsonValue::Object).unwrap()
146    }
147
148    /// Get a mutable reference to the array value with the given key in the given object.
149    ///
150    /// Panics if the property doesn't exist or is not an array.
151    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!(
194            metadata
195                .account_management_actions_supported
196                .contains(&AccountManagementAction::Profile)
197        );
198        assert!(
199            metadata
200                .account_management_actions_supported
201                .contains(&AccountManagementAction::DevicesList)
202        );
203        assert!(
204            metadata
205                .account_management_actions_supported
206                .contains(&AccountManagementAction::DeviceView)
207        );
208        assert!(
209            metadata
210                .account_management_actions_supported
211                .contains(&AccountManagementAction::DeviceDelete)
212        );
213        assert!(
214            metadata
215                .account_management_actions_supported
216                .contains(&AccountManagementAction::AccountDeactivate)
217        );
218        assert!(
219            metadata
220                .account_management_actions_supported
221                .contains(&AccountManagementAction::CrossSigningReset)
222        );
223
224        assert_eq!(
225            metadata.device_authorization_endpoint.as_ref().map(Url::as_str),
226            Some("https://server.local/device")
227        );
228    }
229
230    #[test]
231    fn metadata_no_optional_fields() {
232        let mut metadata_object = authorization_server_metadata_object();
233        assert!(metadata_object.remove("registration_endpoint").is_some());
234        assert!(metadata_object.remove("response_modes_supported").is_some());
235        assert!(metadata_object.remove("account_management_uri").is_some());
236        assert!(metadata_object.remove("account_management_actions_supported").is_some());
237        assert!(metadata_object.remove("device_authorization_endpoint").is_some());
238
239        let metadata =
240            from_json_value::<AuthorizationServerMetadata>(metadata_object.into()).unwrap();
241
242        assert_eq!(metadata.issuer.as_str(), "https://server.local/");
243        assert_eq!(metadata.authorization_endpoint.as_str(), "https://server.local/authorize");
244        assert_eq!(metadata.token_endpoint.as_str(), "https://server.local/token");
245        assert_eq!(metadata.registration_endpoint, None);
246
247        assert_eq!(metadata.response_types_supported.len(), 1);
248        assert!(metadata.response_types_supported.contains(&ResponseType::Code));
249
250        assert_eq!(metadata.response_modes_supported.len(), 2);
251        assert!(metadata.response_modes_supported.contains(&ResponseMode::Query));
252        assert!(metadata.response_modes_supported.contains(&ResponseMode::Fragment));
253
254        assert_eq!(metadata.grant_types_supported.len(), 2);
255        assert!(metadata.grant_types_supported.contains(&GrantType::AuthorizationCode));
256        assert!(metadata.grant_types_supported.contains(&GrantType::RefreshToken));
257
258        assert_eq!(metadata.revocation_endpoint.as_str(), "https://server.local/revoke");
259
260        assert_eq!(metadata.code_challenge_methods_supported.len(), 1);
261        assert!(metadata.code_challenge_methods_supported.contains(&CodeChallengeMethod::S256));
262
263        assert_eq!(metadata.account_management_uri, None);
264        assert_eq!(metadata.account_management_actions_supported.len(), 0);
265
266        assert_eq!(metadata.device_authorization_endpoint, None);
267    }
268
269    #[test]
270    fn metadata_additional_values() {
271        let mut metadata_object = authorization_server_metadata_object();
272        get_mut_array(&mut metadata_object, "response_types_supported").push("custom".into());
273        get_mut_array(&mut metadata_object, "response_modes_supported").push("custom".into());
274        get_mut_array(&mut metadata_object, "grant_types_supported").push("custom".into());
275        get_mut_array(&mut metadata_object, "code_challenge_methods_supported")
276            .push("custom".into());
277        get_mut_array(&mut metadata_object, "account_management_actions_supported")
278            .push("custom".into());
279
280        let metadata =
281            from_json_value::<AuthorizationServerMetadata>(metadata_object.into()).unwrap();
282
283        assert_eq!(metadata.issuer.as_str(), "https://server.local/");
284        assert_eq!(metadata.authorization_endpoint.as_str(), "https://server.local/authorize");
285        assert_eq!(metadata.token_endpoint.as_str(), "https://server.local/token");
286        assert_eq!(
287            metadata.registration_endpoint.as_ref().map(Url::as_str),
288            Some("https://server.local/register")
289        );
290
291        assert_eq!(metadata.response_types_supported.len(), 2);
292        assert!(metadata.response_types_supported.contains(&ResponseType::Code));
293        assert!(metadata.response_types_supported.contains(&ResponseType::from("custom")));
294
295        assert_eq!(metadata.response_modes_supported.len(), 3);
296        assert!(metadata.response_modes_supported.contains(&ResponseMode::Query));
297        assert!(metadata.response_modes_supported.contains(&ResponseMode::Fragment));
298        assert!(metadata.response_modes_supported.contains(&ResponseMode::from("custom")));
299
300        assert_eq!(metadata.grant_types_supported.len(), 3);
301        assert!(metadata.grant_types_supported.contains(&GrantType::AuthorizationCode));
302        assert!(metadata.grant_types_supported.contains(&GrantType::RefreshToken));
303        assert!(metadata.grant_types_supported.contains(&GrantType::from("custom")));
304
305        assert_eq!(metadata.revocation_endpoint.as_str(), "https://server.local/revoke");
306
307        assert_eq!(metadata.code_challenge_methods_supported.len(), 2);
308        assert!(metadata.code_challenge_methods_supported.contains(&CodeChallengeMethod::S256));
309        assert!(
310            metadata
311                .code_challenge_methods_supported
312                .contains(&CodeChallengeMethod::from("custom"))
313        );
314
315        assert_eq!(
316            metadata.account_management_uri.as_ref().map(Url::as_str),
317            Some("https://server.local/account")
318        );
319        assert_eq!(metadata.account_management_actions_supported.len(), 7);
320        assert!(
321            metadata
322                .account_management_actions_supported
323                .contains(&AccountManagementAction::Profile)
324        );
325        assert!(
326            metadata
327                .account_management_actions_supported
328                .contains(&AccountManagementAction::DevicesList)
329        );
330        assert!(
331            metadata
332                .account_management_actions_supported
333                .contains(&AccountManagementAction::DeviceView)
334        );
335        assert!(
336            metadata
337                .account_management_actions_supported
338                .contains(&AccountManagementAction::DeviceDelete)
339        );
340        assert!(
341            metadata
342                .account_management_actions_supported
343                .contains(&AccountManagementAction::AccountDeactivate)
344        );
345        assert!(
346            metadata
347                .account_management_actions_supported
348                .contains(&AccountManagementAction::CrossSigningReset)
349        );
350        assert!(
351            metadata
352                .account_management_actions_supported
353                .contains(&AccountManagementAction::from("custom"))
354        );
355
356        assert_eq!(
357            metadata.device_authorization_endpoint.as_ref().map(Url::as_str),
358            Some("https://server.local/device")
359        );
360    }
361
362    #[test]
363    fn metadata_missing_required_fields() {
364        let original_metadata_object = authorization_server_metadata_object();
365
366        let mut metadata_object = original_metadata_object.clone();
367        assert!(metadata_object.remove("issuer").is_some());
368        from_json_value::<AuthorizationServerMetadata>(metadata_object.into()).unwrap_err();
369
370        let mut metadata_object = original_metadata_object.clone();
371        assert!(metadata_object.remove("authorization_endpoint").is_some());
372        from_json_value::<AuthorizationServerMetadata>(metadata_object.into()).unwrap_err();
373
374        let mut metadata_object = original_metadata_object.clone();
375        assert!(metadata_object.remove("token_endpoint").is_some());
376        from_json_value::<AuthorizationServerMetadata>(metadata_object.into()).unwrap_err();
377
378        let mut metadata_object = original_metadata_object.clone();
379        assert!(metadata_object.remove("response_types_supported").is_some());
380        from_json_value::<AuthorizationServerMetadata>(metadata_object.into()).unwrap_err();
381
382        let mut metadata_object = original_metadata_object.clone();
383        assert!(metadata_object.remove("grant_types_supported").is_some());
384        from_json_value::<AuthorizationServerMetadata>(metadata_object.into()).unwrap_err();
385
386        let mut metadata_object = original_metadata_object.clone();
387        assert!(metadata_object.remove("revocation_endpoint").is_some());
388        from_json_value::<AuthorizationServerMetadata>(metadata_object.into()).unwrap_err();
389
390        let mut metadata_object = original_metadata_object;
391        assert!(metadata_object.remove("code_challenge_methods_supported").is_some());
392        from_json_value::<AuthorizationServerMetadata>(metadata_object.into()).unwrap_err();
393    }
394
395    #[test]
396    fn metadata_missing_required_values() {
397        let original_metadata_object = authorization_server_metadata_object();
398
399        let mut metadata_object = original_metadata_object.clone();
400        get_mut_array(&mut metadata_object, "response_types_supported").clear();
401        from_json_value::<AuthorizationServerMetadata>(metadata_object.into()).unwrap_err();
402
403        let mut metadata_object = original_metadata_object.clone();
404        get_mut_array(&mut metadata_object, "response_modes_supported").clear();
405        from_json_value::<AuthorizationServerMetadata>(metadata_object.into()).unwrap_err();
406
407        let mut metadata_object = original_metadata_object.clone();
408        get_mut_array(&mut metadata_object, "response_modes_supported").remove(0);
409        from_json_value::<AuthorizationServerMetadata>(metadata_object.into()).unwrap_err();
410
411        let mut metadata_object = original_metadata_object.clone();
412        get_mut_array(&mut metadata_object, "response_modes_supported").remove(1);
413        from_json_value::<AuthorizationServerMetadata>(metadata_object.into()).unwrap_err();
414
415        let mut metadata_object = original_metadata_object.clone();
416        get_mut_array(&mut metadata_object, "grant_types_supported").clear();
417        from_json_value::<AuthorizationServerMetadata>(metadata_object.into()).unwrap_err();
418
419        let mut metadata_object = original_metadata_object.clone();
420        get_mut_array(&mut metadata_object, "grant_types_supported").remove(0);
421        from_json_value::<AuthorizationServerMetadata>(metadata_object.into()).unwrap_err();
422
423        let mut metadata_object = original_metadata_object.clone();
424        get_mut_array(&mut metadata_object, "grant_types_supported").remove(1);
425        from_json_value::<AuthorizationServerMetadata>(metadata_object.into()).unwrap_err();
426
427        let mut metadata_object = original_metadata_object;
428        get_mut_array(&mut metadata_object, "code_challenge_methods_supported").clear();
429        from_json_value::<AuthorizationServerMetadata>(metadata_object.into()).unwrap_err();
430    }
431}