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        // 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::{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    /// 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!(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}