ruma_client_api/discovery/
get_authorization_server_metadata.rs

1//! `GET /_matrix/client/*/auth_metadata`
2//!
3//! Get the metadata of the authorization server that is trusted by the homeserver.
4
5mod serde;
6
7pub mod v1 {
8    //! `v1` ([spec])
9    //!
10    //! [spec]: https://spec.matrix.org/latest/client-server-api/#get_matrixclientv1auth_metadata
11
12    use std::collections::BTreeSet;
13
14    use ruma_common::{
15        DeviceId,
16        api::{auth_scheme::NoAuthentication, request, response},
17        metadata,
18        serde::{Raw, StringEnum},
19    };
20    use serde::Serialize;
21    use url::Url;
22
23    use crate::PrivOwnedStr;
24
25    metadata! {
26        method: GET,
27        rate_limited: false,
28        authentication: NoAuthentication,
29        history: {
30            unstable => "/_matrix/client/unstable/org.matrix.msc2965/auth_metadata",
31            1.15 => "/_matrix/client/v1/auth_metadata",
32        }
33    }
34
35    /// Request type for the `auth_metadata` endpoint.
36    #[request(error = crate::Error)]
37    #[derive(Default)]
38    pub struct Request {}
39
40    /// Response type for the `auth_metadata` endpoint.
41    #[response(error = crate::Error)]
42    pub struct Response {
43        /// The authorization server metadata as defined in [RFC 8414].
44        ///
45        /// [RFC 8414]: https://datatracker.ietf.org/doc/html/rfc8414
46        #[ruma_api(body)]
47        pub metadata: Raw<AuthorizationServerMetadata>,
48    }
49
50    impl Request {
51        /// Creates a new empty `Request`.
52        pub fn new() -> Self {
53            Self {}
54        }
55    }
56
57    impl Response {
58        /// Creates a new `Response` with the given serialized authorization server metadata.
59        pub fn new(metadata: Raw<AuthorizationServerMetadata>) -> Self {
60            Self { metadata }
61        }
62    }
63
64    /// Metadata describing the configuration of the authorization server.
65    ///
66    /// While the metadata properties and their values are declared for OAuth 2.0 in [RFC 8414] and
67    /// other RFCs, this type only supports properties and values that are used for Matrix, as
68    /// specified in [MSC3861] and its dependencies.
69    ///
70    /// This type is validated to have at least all the required values during deserialization. The
71    /// URLs are not validated during deserialization, to validate them use
72    /// [`AuthorizationServerMetadata::validate_urls()`] or
73    /// [`AuthorizationServerMetadata::insecure_validate_urls()`].
74    ///
75    /// This type has no constructor, it should be sent as raw JSON directly.
76    ///
77    /// [RFC 8414]: https://datatracker.ietf.org/doc/html/rfc8414
78    /// [MSC3861]: https://github.com/matrix-org/matrix-spec-proposals/pull/3861
79    #[derive(Debug, Clone, Serialize)]
80    #[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
81    pub struct AuthorizationServerMetadata {
82        /// The authorization server's issuer identifier.
83        ///
84        /// This should be a URL with no query or fragment components.
85        pub issuer: Url,
86
87        /// URL of the authorization server's authorization endpoint ([RFC 6749]).
88        ///
89        /// [RFC 6749]: https://datatracker.ietf.org/doc/html/rfc6749
90        pub authorization_endpoint: Url,
91
92        /// URL of the authorization server's token endpoint ([RFC 6749]).
93        ///
94        /// [RFC 6749]: https://datatracker.ietf.org/doc/html/rfc6749
95        pub token_endpoint: Url,
96
97        /// URL of the authorization server's OAuth 2.0 Dynamic Client Registration endpoint
98        /// ([RFC 7591]).
99        ///
100        /// [RFC 7591]: https://datatracker.ietf.org/doc/html/rfc7591
101        #[serde(skip_serializing_if = "Option::is_none")]
102        pub registration_endpoint: Option<Url>,
103
104        /// List of the OAuth 2.0 `response_type` values that this authorization server supports.
105        ///
106        /// Those values are the same as those used with the `response_types` parameter defined by
107        /// OAuth 2.0 Dynamic Client Registration ([RFC 7591]).
108        ///
109        /// This field must include [`ResponseType::Code`].
110        ///
111        /// [RFC 7591]: https://datatracker.ietf.org/doc/html/rfc7591
112        pub response_types_supported: BTreeSet<ResponseType>,
113
114        /// List of the OAuth 2.0 `response_mode` values that this authorization server supports.
115        ///
116        /// Those values are specified in [OAuth 2.0 Multiple Response Type Encoding Practices].
117        ///
118        /// This field must include [`ResponseMode::Query`] and [`ResponseMode::Fragment`].
119        ///
120        /// [OAuth 2.0 Multiple Response Type Encoding Practices]: https://openid.net/specs/oauth-v2-multiple-response-types-1_0.html
121        pub response_modes_supported: BTreeSet<ResponseMode>,
122
123        /// List of the OAuth 2.0 `grant_type` values that this authorization server supports.
124        ///
125        /// Those values are the same as those used with the `grant_types` parameter defined by
126        /// OAuth 2.0 Dynamic Client Registration ([RFC 7591]).
127        ///
128        /// This field must include [`GrantType::AuthorizationCode`] and
129        /// [`GrantType::RefreshToken`].
130        ///
131        /// [RFC 7591]: https://datatracker.ietf.org/doc/html/rfc7591
132        pub grant_types_supported: BTreeSet<GrantType>,
133
134        /// URL of the authorization server's OAuth 2.0 revocation endpoint ([RFC 7009]).
135        ///
136        /// [RFC 7009]: https://datatracker.ietf.org/doc/html/rfc7009
137        pub revocation_endpoint: Url,
138
139        /// List of Proof Key for Code Exchange (PKCE) code challenge methods supported by this
140        /// authorization server ([RFC 7636]).
141        ///
142        /// This field must include [`CodeChallengeMethod::S256`].
143        ///
144        /// [RFC 7636]: https://datatracker.ietf.org/doc/html/rfc7636
145        pub code_challenge_methods_supported: BTreeSet<CodeChallengeMethod>,
146
147        /// URL where the user is able to access the account management capabilities of the
148        /// authorization server ([MSC4191]).
149        ///
150        /// [MSC4191]: https://github.com/matrix-org/matrix-spec-proposals/pull/4191
151        #[serde(skip_serializing_if = "Option::is_none")]
152        pub account_management_uri: Option<Url>,
153
154        /// List of actions that the account management URL supports ([MSC4191]).
155        ///
156        /// [MSC4191]: https://github.com/matrix-org/matrix-spec-proposals/pull/4191
157        #[serde(skip_serializing_if = "BTreeSet::is_empty")]
158        pub account_management_actions_supported: BTreeSet<AccountManagementAction>,
159
160        /// URL of the authorization server's device authorization endpoint ([RFC 8628]).
161        ///
162        /// [RFC 8628]: https://datatracker.ietf.org/doc/html/rfc8628
163        #[cfg(feature = "unstable-msc4108")]
164        #[serde(skip_serializing_if = "Option::is_none")]
165        pub device_authorization_endpoint: Option<Url>,
166
167        /// The [`Prompt`] values supported by the authorization server ([Initiating User
168        /// Registration via OpenID Connect 1.0]).
169        ///
170        /// [Initiating User Registration via OpenID Connect 1.0]: https://openid.net/specs/openid-connect-prompt-create-1_0.html
171        #[serde(skip_serializing_if = "Vec::is_empty")]
172        pub prompt_values_supported: Vec<Prompt>,
173    }
174
175    impl AuthorizationServerMetadata {
176        /// Strict validation of the URLs in this `AuthorizationServerMetadata`.
177        ///
178        /// This checks that:
179        ///
180        /// * The `issuer` is a valid URL using an `https` scheme and without a query or fragment.
181        ///
182        /// * All the URLs use an `https` scheme.
183        pub fn validate_urls(&self) -> Result<(), AuthorizationServerMetadataUrlError> {
184            self.validate_urls_inner(false)
185        }
186
187        /// Weak validation the URLs `AuthorizationServerMetadata` are all absolute URLs.
188        ///
189        /// This only checks that the `issuer` is a valid URL without a query or fragment.
190        ///
191        /// In production, you should prefer [`AuthorizationServerMetadata`] that also check if the
192        /// URLs use an `https` scheme. This method is meant for development purposes, when
193        /// interacting with a local authorization server.
194        pub fn insecure_validate_urls(&self) -> Result<(), AuthorizationServerMetadataUrlError> {
195            self.validate_urls_inner(true)
196        }
197
198        /// Get an iterator over the URLs of this `AuthorizationServerMetadata`, except the
199        /// `issuer`.
200        fn validate_urls_inner(
201            &self,
202            insecure: bool,
203        ) -> Result<(), AuthorizationServerMetadataUrlError> {
204            if self.issuer.query().is_some() || self.issuer.fragment().is_some() {
205                return Err(AuthorizationServerMetadataUrlError::IssuerHasQueryOrFragment);
206            }
207
208            if insecure {
209                // No more checks.
210                return Ok(());
211            }
212
213            let required_urls = &[
214                ("issuer", &self.issuer),
215                ("authorization_endpoint", &self.authorization_endpoint),
216                ("token_endpoint", &self.token_endpoint),
217                ("revocation_endpoint", &self.revocation_endpoint),
218            ];
219            let optional_urls = &[
220                self.registration_endpoint.as_ref().map(|string| ("registration_endpoint", string)),
221                self.account_management_uri
222                    .as_ref()
223                    .map(|string| ("account_management_uri", string)),
224                #[cfg(feature = "unstable-msc4108")]
225                self.device_authorization_endpoint
226                    .as_ref()
227                    .map(|string| ("device_authorization_endpoint", string)),
228            ];
229
230            for (field, url) in required_urls.iter().chain(optional_urls.iter().flatten()) {
231                if url.scheme() != "https" {
232                    return Err(AuthorizationServerMetadataUrlError::NotHttpsScheme(field));
233                }
234            }
235
236            Ok(())
237        }
238
239        /// Whether the given account management action is advertised as supported by the server.
240        ///
241        /// This function tries to be backwards compatible with unstable implementations by checking
242        /// both the stable and unstable values of the given action, if they differ.
243        pub fn is_action_management_action_supported(
244            &self,
245            action: &AccountManagementAction,
246        ) -> bool {
247            match action {
248                AccountManagementAction::UnstableSessionsList
249                | AccountManagementAction::DevicesList => {
250                    self.account_management_actions_supported
251                        .contains(&AccountManagementAction::DevicesList)
252                        || self
253                            .account_management_actions_supported
254                            .contains(&AccountManagementAction::UnstableSessionsList)
255                }
256                AccountManagementAction::UnstableSessionView
257                | AccountManagementAction::DeviceView => {
258                    self.account_management_actions_supported
259                        .contains(&AccountManagementAction::DeviceView)
260                        || self
261                            .account_management_actions_supported
262                            .contains(&AccountManagementAction::UnstableSessionView)
263                }
264                AccountManagementAction::UnstableSessionEnd
265                | AccountManagementAction::DeviceDelete => {
266                    self.account_management_actions_supported
267                        .contains(&AccountManagementAction::DeviceDelete)
268                        || self
269                            .account_management_actions_supported
270                            .contains(&AccountManagementAction::UnstableSessionEnd)
271                }
272                action => self.account_management_actions_supported.contains(action),
273            }
274        }
275
276        /// Build the action management URL with the given action.
277        ///
278        /// This function tries to be backwards compatible with unstable implementations by
279        /// selecting the proper action value to add to the URL (stable or unstable) given
280        /// the supported actions advertised in this metadata. If the action is not present
281        /// in the metadata, the stable value is used.
282        ///
283        /// Returns `None` if the `action_management_url` is `None`.
284        pub fn action_management_url_with_action(
285            &self,
286            action: AccountManagementActionData<'_>,
287        ) -> Option<Url> {
288            const QUERY_NAME_ACTION: &str = "action";
289            const QUERY_NAME_DEVICE_ID: &str = "device_id";
290
291            let mut url = self.account_management_uri.clone()?;
292
293            match action {
294                AccountManagementActionData::Profile => {
295                    url.query_pairs_mut()
296                        .append_pair(QUERY_NAME_ACTION, AccountManagementAction::Profile.as_str());
297                }
298                AccountManagementActionData::DevicesList => {
299                    // Prefer the stable action if it is supported, and default to the stable action
300                    // if no actions are advertised in the metadata.
301                    let action = if self
302                        .account_management_actions_supported
303                        .contains(&AccountManagementAction::DevicesList)
304                    {
305                        AccountManagementAction::DevicesList
306                    } else if self
307                        .account_management_actions_supported
308                        .contains(&AccountManagementAction::UnstableSessionsList)
309                    {
310                        AccountManagementAction::UnstableSessionsList
311                    } else {
312                        AccountManagementAction::DevicesList
313                    };
314
315                    url.query_pairs_mut().append_pair(QUERY_NAME_ACTION, action.as_str());
316                }
317                AccountManagementActionData::DeviceView(DeviceViewData { device_id }) => {
318                    // Prefer the stable action if it is supported, and default to the stable action
319                    // if no actions are advertised in the metadata.
320                    let action = if self
321                        .account_management_actions_supported
322                        .contains(&AccountManagementAction::DeviceView)
323                    {
324                        AccountManagementAction::DeviceView
325                    } else if self
326                        .account_management_actions_supported
327                        .contains(&AccountManagementAction::UnstableSessionView)
328                    {
329                        AccountManagementAction::UnstableSessionView
330                    } else {
331                        AccountManagementAction::DeviceView
332                    };
333
334                    url.query_pairs_mut()
335                        .append_pair(QUERY_NAME_ACTION, action.as_str())
336                        .append_pair(QUERY_NAME_DEVICE_ID, device_id.as_str());
337                }
338                AccountManagementActionData::DeviceDelete(DeviceDeleteData { device_id }) => {
339                    // Prefer the stable action if it is supported, and default to the stable action
340                    // if no actions are advertised in the metadata.
341                    let action = if self
342                        .account_management_actions_supported
343                        .contains(&AccountManagementAction::DeviceDelete)
344                    {
345                        AccountManagementAction::DeviceDelete
346                    } else if self
347                        .account_management_actions_supported
348                        .contains(&AccountManagementAction::UnstableSessionEnd)
349                    {
350                        AccountManagementAction::UnstableSessionEnd
351                    } else {
352                        AccountManagementAction::DeviceDelete
353                    };
354
355                    url.query_pairs_mut()
356                        .append_pair(QUERY_NAME_ACTION, action.as_str())
357                        .append_pair(QUERY_NAME_DEVICE_ID, device_id.as_str());
358                }
359                AccountManagementActionData::AccountDeactivate => {
360                    url.query_pairs_mut().append_pair(
361                        QUERY_NAME_ACTION,
362                        AccountManagementAction::AccountDeactivate.as_str(),
363                    );
364                }
365                AccountManagementActionData::CrossSigningReset => {
366                    url.query_pairs_mut().append_pair(
367                        QUERY_NAME_ACTION,
368                        AccountManagementAction::CrossSigningReset.as_str(),
369                    );
370                }
371            }
372
373            Some(url)
374        }
375    }
376
377    /// The method to use at the authorization endpoint.
378    #[doc = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/src/doc/string_enum.md"))]
379    #[derive(Clone, StringEnum)]
380    #[ruma_enum(rename_all = "lowercase")]
381    #[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
382    pub enum ResponseType {
383        /// Use the authorization code grant flow ([RFC 6749]).
384        ///
385        /// [RFC 6749]: https://datatracker.ietf.org/doc/html/rfc6749
386        Code,
387
388        #[doc(hidden)]
389        _Custom(PrivOwnedStr),
390    }
391
392    /// The mechanism to be used for returning authorization response parameters from the
393    /// authorization endpoint.
394    ///
395    /// The values are specified in [OAuth 2.0 Multiple Response Type Encoding Practices].
396    #[doc = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/src/doc/string_enum.md"))]
397    ///
398    /// [OAuth 2.0 Multiple Response Type Encoding Practices]: https://openid.net/specs/oauth-v2-multiple-response-types-1_0.html
399    #[derive(Clone, StringEnum)]
400    #[ruma_enum(rename_all = "lowercase")]
401    #[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
402    pub enum ResponseMode {
403        /// Authorization Response parameters are encoded in the fragment added to the
404        /// `redirect_uri` when redirecting back to the client.
405        Query,
406
407        /// Authorization Response parameters are encoded in the query string added to the
408        /// `redirect_uri` when redirecting back to the client.
409        Fragment,
410
411        #[doc(hidden)]
412        _Custom(PrivOwnedStr),
413    }
414
415    /// The grant type to use at the token endpoint.
416    #[doc = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/src/doc/string_enum.md"))]
417    #[derive(Clone, StringEnum)]
418    #[ruma_enum(rename_all = "snake_case")]
419    #[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
420    pub enum GrantType {
421        /// The authorization code grant type ([RFC 6749]).
422        ///
423        /// [RFC 6749]: https://datatracker.ietf.org/doc/html/rfc6749
424        AuthorizationCode,
425
426        /// The refresh token grant type ([RFC 6749]).
427        ///
428        /// [RFC 6749]: https://datatracker.ietf.org/doc/html/rfc6749
429        RefreshToken,
430
431        /// The device code grant type ([RFC 8628]).
432        ///
433        /// [RFC 8628]: https://datatracker.ietf.org/doc/html/rfc8628
434        #[cfg(feature = "unstable-msc4108")]
435        #[ruma_enum(rename = "urn:ietf:params:oauth:grant-type:device_code")]
436        DeviceCode,
437
438        #[doc(hidden)]
439        _Custom(PrivOwnedStr),
440    }
441
442    /// The code challenge method to use at the authorization endpoint.
443    #[doc = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/src/doc/string_enum.md"))]
444    #[derive(Clone, StringEnum)]
445    #[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
446    pub enum CodeChallengeMethod {
447        /// Use a SHA-256, base64url-encoded code challenge ([RFC 7636]).
448        ///
449        /// [RFC 7636]: https://datatracker.ietf.org/doc/html/rfc7636
450        S256,
451
452        #[doc(hidden)]
453        _Custom(PrivOwnedStr),
454    }
455
456    /// The action that the user wishes to do at the account management URL.
457    ///
458    /// This enum supports both the values that were first specified in [MSC4191] and the values
459    /// that replaced them in the Matrix specification, for backwards compatibility with unstable
460    /// implementations. The variants that were replaced all use an `Unstable` prefix.
461    #[doc = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/src/doc/string_enum.md"))]
462    ///
463    /// [MSC4191]: https://github.com/matrix-org/matrix-spec-proposals/pull/4191
464    #[derive(Clone, StringEnum)]
465    #[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
466    pub enum AccountManagementAction {
467        /// The user wishes to view or edit their profile (name, avatar, contact details).
468        #[ruma_enum(rename = "org.matrix.profile")]
469        Profile,
470
471        /// The unstable version of [`AccountManagementAction::DevicesList`].
472        ///
473        /// This uses the `org.matrix.sessions_list` string that was first specified in [MSC4191]
474        /// before being replaced by `org.matrix.devices_list`.
475        ///
476        /// [MSC4191]: https://github.com/matrix-org/matrix-spec-proposals/pull/4191
477        #[ruma_enum(rename = "org.matrix.sessions_list")]
478        UnstableSessionsList,
479
480        /// The user wishes to view a list of their devices.
481        #[ruma_enum(rename = "org.matrix.devices_list")]
482        DevicesList,
483
484        /// The unstable version of [`AccountManagementAction::DeviceView`].
485        ///
486        /// This uses the `org.matrix.session_view` string that was first specified in [MSC4191]
487        /// before being replaced by `org.matrix.device_view`.
488        ///
489        /// [MSC4191]: https://github.com/matrix-org/matrix-spec-proposals/pull/4191
490        #[ruma_enum(rename = "org.matrix.session_view")]
491        UnstableSessionView,
492
493        /// The user wishes to view the details of a specific device.
494        #[ruma_enum(rename = "org.matrix.device_view")]
495        DeviceView,
496
497        /// The unstable version of [`AccountManagementAction::DeviceDelete`].
498        ///
499        /// This uses the `org.matrix.session_end` string that was first specified in [MSC4191]
500        /// before being replaced by `org.matrix.device_delete`.
501        ///
502        /// [MSC4191]: https://github.com/matrix-org/matrix-spec-proposals/pull/4191
503        #[ruma_enum(rename = "org.matrix.session_end")]
504        UnstableSessionEnd,
505
506        /// The user wishes to delete/log out a specific device.
507        #[ruma_enum(rename = "org.matrix.device_delete")]
508        DeviceDelete,
509
510        /// The user wishes to deactivate their account.
511        #[ruma_enum(rename = "org.matrix.account_deactivate")]
512        AccountDeactivate,
513
514        /// The user wishes to reset their cross-signing keys.
515        ///
516        /// Servers should use this action in the URL of the [`m.oauth`] UIA type.
517        ///
518        /// [`m.oauth`]: https://spec.matrix.org/latest/client-server-api/#oauth-authentication
519        #[ruma_enum(rename = "org.matrix.cross_signing_reset")]
520        CrossSigningReset,
521
522        #[doc(hidden)]
523        _Custom(PrivOwnedStr),
524    }
525
526    /// The action that the user wishes to do at the account management URL with its associated
527    /// data.
528    ///
529    /// [MSC 4191]: https://github.com/matrix-org/matrix-spec-proposals/pull/4191
530    #[derive(Debug, Clone)]
531    #[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
532    pub enum AccountManagementActionData<'a> {
533        /// The user wishes to view or edit their profile (name, avatar, contact details).
534        Profile,
535
536        /// The user wishes to view a list of their devices.
537        DevicesList,
538
539        /// The user wishes to view the details of a specific device.
540        DeviceView(DeviceViewData<'a>),
541
542        /// The user wishes to delete/log out a specific device.
543        DeviceDelete(DeviceDeleteData<'a>),
544
545        /// The user wishes to deactivate their account.
546        AccountDeactivate,
547
548        /// The user wishes to reset their cross-signing keys.
549        CrossSigningReset,
550    }
551
552    /// The data associated with [`AccountManagementAction::DeviceView`].
553    #[derive(Debug, Clone)]
554    #[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
555    pub struct DeviceViewData<'a> {
556        /// The ID of the device to view.
557        pub device_id: &'a DeviceId,
558    }
559
560    impl<'a> DeviceViewData<'a> {
561        /// Construct a new `DeviceViewData` with the given device ID.
562        fn new(device_id: &'a DeviceId) -> Self {
563            Self { device_id }
564        }
565    }
566
567    impl<'a> From<&'a DeviceId> for DeviceViewData<'a> {
568        fn from(device_id: &'a DeviceId) -> Self {
569            Self::new(device_id)
570        }
571    }
572
573    /// The data associated with [`AccountManagementAction::DeviceDelete`].
574    #[derive(Debug, Clone)]
575    #[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
576    pub struct DeviceDeleteData<'a> {
577        /// The ID of the device to delete/log out.
578        pub device_id: &'a DeviceId,
579    }
580
581    impl<'a> DeviceDeleteData<'a> {
582        /// Construct a new `DeviceDeleteData` with the given device ID.
583        fn new(device_id: &'a DeviceId) -> Self {
584            Self { device_id }
585        }
586    }
587
588    impl<'a> From<&'a DeviceId> for DeviceDeleteData<'a> {
589        fn from(device_id: &'a DeviceId) -> Self {
590            Self::new(device_id)
591        }
592    }
593
594    /// The possible errors when validating URLs of [`AuthorizationServerMetadata`].
595    #[derive(Debug, Clone, thiserror::Error)]
596    #[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
597    pub enum AuthorizationServerMetadataUrlError {
598        /// The URL of the field does not use the `https` scheme.
599        #[error("URL in `{0}` must use the `https` scheme")]
600        NotHttpsScheme(&'static str),
601
602        /// The `issuer` URL has a query or fragment component.
603        #[error("URL in `issuer` cannot have a query or fragment component")]
604        IssuerHasQueryOrFragment,
605    }
606
607    /// The desired user experience when using the authorization endpoint.
608    #[derive(Clone, StringEnum)]
609    #[ruma_enum(rename_all = "lowercase")]
610    #[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
611    pub enum Prompt {
612        /// The user wants to create a new account ([Initiating User Registration via OpenID
613        /// Connect 1.0]).
614        ///
615        /// [Initiating User Registration via OpenID Connect 1.0]: https://openid.net/specs/openid-connect-prompt-create-1_0.html
616        Create,
617
618        #[doc(hidden)]
619        _Custom(PrivOwnedStr),
620    }
621}
622
623#[cfg(test)]
624mod tests {
625    use ruma_common::device_id;
626    use serde_json::{Value as JsonValue, from_value as from_json_value, json};
627    use url::Url;
628
629    use super::v1::{
630        AccountManagementAction, AccountManagementActionData, AuthorizationServerMetadata,
631    };
632
633    /// A valid `AuthorizationServerMetadata` with all fields and values, as a JSON object.
634    pub(super) fn authorization_server_metadata_json() -> JsonValue {
635        json!({
636            "issuer": "https://server.local/",
637            "authorization_endpoint": "https://server.local/authorize",
638            "token_endpoint": "https://server.local/token",
639            "registration_endpoint": "https://server.local/register",
640            "response_types_supported": ["code"],
641            "response_modes_supported": ["query", "fragment"],
642            "grant_types_supported": ["authorization_code", "refresh_token"],
643            "revocation_endpoint": "https://server.local/revoke",
644            "code_challenge_methods_supported": ["S256"],
645            "account_management_uri": "https://server.local/account",
646            "account_management_actions_supported": [
647                "org.matrix.profile",
648                "org.matrix.devices_list",
649                "org.matrix.device_view",
650                "org.matrix.device_delete",
651                "org.matrix.account_deactivate",
652                "org.matrix.cross_signing_reset",
653            ],
654            "device_authorization_endpoint": "https://server.local/device",
655        })
656    }
657
658    /// A valid `AuthorizationServerMetadata`, with valid URLs.
659    fn authorization_server_metadata() -> AuthorizationServerMetadata {
660        from_json_value(authorization_server_metadata_json()).unwrap()
661    }
662
663    #[test]
664    fn metadata_valid_urls() {
665        let metadata = authorization_server_metadata();
666        metadata.validate_urls().unwrap();
667        metadata.insecure_validate_urls().unwrap();
668    }
669
670    #[test]
671    fn metadata_invalid_or_insecure_issuer() {
672        let original_metadata = authorization_server_metadata();
673
674        // URL with query string.
675        let mut metadata = original_metadata.clone();
676        metadata.issuer = Url::parse("https://server.local/?session=1er45elp").unwrap();
677        metadata.validate_urls().unwrap_err();
678        metadata.insecure_validate_urls().unwrap_err();
679
680        // URL with fragment.
681        let mut metadata = original_metadata.clone();
682        metadata.issuer = Url::parse("https://server.local/#session").unwrap();
683        metadata.validate_urls().unwrap_err();
684        metadata.insecure_validate_urls().unwrap_err();
685
686        // Insecure URL.
687        let mut metadata = original_metadata;
688        metadata.issuer = Url::parse("http://server.local/").unwrap();
689        metadata.validate_urls().unwrap_err();
690        metadata.insecure_validate_urls().unwrap();
691    }
692
693    #[test]
694    fn metadata_insecure_urls() {
695        let original_metadata = authorization_server_metadata();
696
697        let mut metadata = original_metadata.clone();
698        metadata.authorization_endpoint = Url::parse("http://server.local/authorize").unwrap();
699        metadata.validate_urls().unwrap_err();
700        metadata.insecure_validate_urls().unwrap();
701
702        let mut metadata = original_metadata.clone();
703        metadata.token_endpoint = Url::parse("http://server.local/token").unwrap();
704        metadata.validate_urls().unwrap_err();
705        metadata.insecure_validate_urls().unwrap();
706
707        let mut metadata = original_metadata.clone();
708        metadata.registration_endpoint = Some(Url::parse("http://server.local/register").unwrap());
709        metadata.validate_urls().unwrap_err();
710        metadata.insecure_validate_urls().unwrap();
711
712        let mut metadata = original_metadata.clone();
713        metadata.revocation_endpoint = Url::parse("http://server.local/revoke").unwrap();
714        metadata.validate_urls().unwrap_err();
715        metadata.insecure_validate_urls().unwrap();
716
717        let mut metadata = original_metadata.clone();
718        metadata.account_management_uri = Some(Url::parse("http://server.local/account").unwrap());
719        metadata.validate_urls().unwrap_err();
720        metadata.insecure_validate_urls().unwrap();
721
722        #[cfg(feature = "unstable-msc4108")]
723        {
724            let mut metadata = original_metadata.clone();
725            metadata.device_authorization_endpoint =
726                Some(Url::parse("http://server.local/device").unwrap());
727            metadata.validate_urls().unwrap_err();
728            metadata.insecure_validate_urls().unwrap();
729        }
730    }
731
732    #[test]
733    fn metadata_is_account_management_action_supported() {
734        let mut original_metadata = authorization_server_metadata();
735
736        // View profile.
737        assert!(
738            original_metadata
739                .is_action_management_action_supported(&AccountManagementAction::Profile)
740        );
741
742        // Remove it.
743        original_metadata
744            .account_management_actions_supported
745            .remove(&AccountManagementAction::Profile);
746        assert!(
747            !original_metadata
748                .is_action_management_action_supported(&AccountManagementAction::Profile)
749        );
750
751        // View devices list.
752        assert!(
753            original_metadata
754                .is_action_management_action_supported(&AccountManagementAction::DevicesList)
755        );
756        assert!(
757            original_metadata.is_action_management_action_supported(
758                &AccountManagementAction::UnstableSessionsList
759            )
760        );
761
762        // Remove it.
763        original_metadata
764            .account_management_actions_supported
765            .remove(&AccountManagementAction::DevicesList);
766        assert!(
767            !original_metadata
768                .is_action_management_action_supported(&AccountManagementAction::DevicesList)
769        );
770        assert!(
771            !original_metadata.is_action_management_action_supported(
772                &AccountManagementAction::UnstableSessionsList
773            )
774        );
775
776        // View device.
777        assert!(
778            original_metadata
779                .is_action_management_action_supported(&AccountManagementAction::DeviceView)
780        );
781        assert!(
782            original_metadata.is_action_management_action_supported(
783                &AccountManagementAction::UnstableSessionView
784            )
785        );
786
787        // Remove it.
788        original_metadata
789            .account_management_actions_supported
790            .remove(&AccountManagementAction::DeviceView);
791        assert!(
792            !original_metadata
793                .is_action_management_action_supported(&AccountManagementAction::DeviceView)
794        );
795        assert!(
796            !original_metadata.is_action_management_action_supported(
797                &AccountManagementAction::UnstableSessionView
798            )
799        );
800
801        // Delete device.
802        assert!(
803            original_metadata
804                .is_action_management_action_supported(&AccountManagementAction::DeviceDelete)
805        );
806        assert!(
807            original_metadata.is_action_management_action_supported(
808                &AccountManagementAction::UnstableSessionEnd
809            )
810        );
811
812        // Remove it.
813        original_metadata
814            .account_management_actions_supported
815            .remove(&AccountManagementAction::DeviceDelete);
816        assert!(
817            !original_metadata
818                .is_action_management_action_supported(&AccountManagementAction::DeviceDelete)
819        );
820        assert!(
821            !original_metadata.is_action_management_action_supported(
822                &AccountManagementAction::UnstableSessionEnd
823            )
824        );
825    }
826
827    #[test]
828    fn metadata_account_management_url_with_action() {
829        let mut original_metadata = authorization_server_metadata();
830        let device_id = device_id!("DEVICE");
831
832        // View profile.
833        let url = original_metadata
834            .action_management_url_with_action(AccountManagementActionData::Profile)
835            .unwrap();
836        assert_eq!(url.query().unwrap(), "action=org.matrix.profile");
837
838        // View devices list, with only the stable action advertised.
839        let url = original_metadata
840            .action_management_url_with_action(AccountManagementActionData::DevicesList)
841            .unwrap();
842        assert_eq!(url.query().unwrap(), "action=org.matrix.devices_list");
843
844        // View devices list, with both stable and unstable actions advertised.
845        original_metadata
846            .account_management_actions_supported
847            .insert(AccountManagementAction::UnstableSessionsList);
848        let url = original_metadata
849            .action_management_url_with_action(AccountManagementActionData::DevicesList)
850            .unwrap();
851        assert_eq!(url.query().unwrap(), "action=org.matrix.devices_list");
852
853        // View devices list, with only the unstable action advertised.
854        original_metadata
855            .account_management_actions_supported
856            .remove(&AccountManagementAction::DevicesList);
857        let url = original_metadata
858            .action_management_url_with_action(AccountManagementActionData::DevicesList)
859            .unwrap();
860        assert_eq!(url.query().unwrap(), "action=org.matrix.sessions_list");
861
862        // View devices list, with no actions advertised.
863        original_metadata
864            .account_management_actions_supported
865            .remove(&AccountManagementAction::UnstableSessionsList);
866        let url = original_metadata
867            .action_management_url_with_action(AccountManagementActionData::DevicesList)
868            .unwrap();
869        assert_eq!(url.query().unwrap(), "action=org.matrix.devices_list");
870
871        // View device, with only the stable action advertised.
872        let url = original_metadata
873            .action_management_url_with_action(AccountManagementActionData::DeviceView(
874                device_id.into(),
875            ))
876            .unwrap();
877        assert_eq!(url.query().unwrap(), "action=org.matrix.device_view&device_id=DEVICE");
878
879        // View device, with both stable and unstable actions advertised.
880        original_metadata
881            .account_management_actions_supported
882            .insert(AccountManagementAction::UnstableSessionView);
883        let url = original_metadata
884            .action_management_url_with_action(AccountManagementActionData::DeviceView(
885                device_id.into(),
886            ))
887            .unwrap();
888        assert_eq!(url.query().unwrap(), "action=org.matrix.device_view&device_id=DEVICE");
889
890        // View device, with only the unstable action advertised.
891        original_metadata
892            .account_management_actions_supported
893            .remove(&AccountManagementAction::DeviceView);
894        let url = original_metadata
895            .action_management_url_with_action(AccountManagementActionData::DeviceView(
896                device_id.into(),
897            ))
898            .unwrap();
899        assert_eq!(url.query().unwrap(), "action=org.matrix.session_view&device_id=DEVICE");
900
901        // View device, with no actions advertised.
902        original_metadata
903            .account_management_actions_supported
904            .remove(&AccountManagementAction::UnstableSessionView);
905        let url = original_metadata
906            .action_management_url_with_action(AccountManagementActionData::DeviceView(
907                device_id.into(),
908            ))
909            .unwrap();
910        assert_eq!(url.query().unwrap(), "action=org.matrix.device_view&device_id=DEVICE");
911
912        // Delete device, with only the stable action advertised.
913        let url = original_metadata
914            .action_management_url_with_action(AccountManagementActionData::DeviceDelete(
915                device_id.into(),
916            ))
917            .unwrap();
918        assert_eq!(url.query().unwrap(), "action=org.matrix.device_delete&device_id=DEVICE");
919
920        // Delete device, with both stable and unstable actions advertised.
921        original_metadata
922            .account_management_actions_supported
923            .insert(AccountManagementAction::UnstableSessionEnd);
924        let url = original_metadata
925            .action_management_url_with_action(AccountManagementActionData::DeviceDelete(
926                device_id.into(),
927            ))
928            .unwrap();
929        assert_eq!(url.query().unwrap(), "action=org.matrix.device_delete&device_id=DEVICE");
930
931        // Delete device, with only the unstable action advertised.
932        original_metadata
933            .account_management_actions_supported
934            .remove(&AccountManagementAction::DeviceDelete);
935        let url = original_metadata
936            .action_management_url_with_action(AccountManagementActionData::DeviceDelete(
937                device_id.into(),
938            ))
939            .unwrap();
940        assert_eq!(url.query().unwrap(), "action=org.matrix.session_end&device_id=DEVICE");
941
942        // Delete device, with no actions advertised.
943        original_metadata
944            .account_management_actions_supported
945            .remove(&AccountManagementAction::UnstableSessionEnd);
946        let url = original_metadata
947            .action_management_url_with_action(AccountManagementActionData::DeviceDelete(
948                device_id.into(),
949            ))
950            .unwrap();
951        assert_eq!(url.query().unwrap(), "action=org.matrix.device_delete&device_id=DEVICE");
952    }
953}