Skip to main content

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