oauth2_types/
oidc.rs

1// Copyright 2024 New Vector Ltd.
2// Copyright 2021-2024 The Matrix.org Foundation C.I.C.
3//
4// SPDX-License-Identifier: AGPL-3.0-only
5// Please see LICENSE in the repository root for full details.
6
7//! Types to interact with the [OpenID Connect] specification.
8//!
9//! [OpenID Connect]: https://openid.net/connect/
10
11use std::{fmt, ops::Deref};
12
13use language_tags::LanguageTag;
14use mas_iana::{
15    jose::{JsonWebEncryptionAlg, JsonWebEncryptionEnc, JsonWebSignatureAlg},
16    oauth::{OAuthAccessTokenType, OAuthClientAuthenticationMethod, PkceCodeChallengeMethod},
17};
18use serde::{Deserialize, Serialize};
19use serde_with::{
20    DeserializeFromStr, SerializeDisplay, StringWithSeparator, formats::SpaceSeparator, serde_as,
21    skip_serializing_none,
22};
23use thiserror::Error;
24use url::Url;
25
26use crate::{
27    requests::{Display, GrantType, Prompt, ResponseMode},
28    response_type::ResponseType,
29};
30
31/// An enum for types that accept either an [`OAuthClientAuthenticationMethod`]
32/// or an [`OAuthAccessTokenType`].
33#[derive(SerializeDisplay, DeserializeFromStr, Clone, PartialEq, Eq, Hash, Debug)]
34pub enum AuthenticationMethodOrAccessTokenType {
35    /// An authentication method.
36    AuthenticationMethod(OAuthClientAuthenticationMethod),
37
38    /// An access token type.
39    AccessTokenType(OAuthAccessTokenType),
40
41    /// An unknown value.
42    ///
43    /// Note that this variant should only be used as the result parsing a
44    /// string of unknown type. To build a custom variant, first parse a
45    /// string with the wanted type then use `.into()`.
46    Unknown(String),
47}
48
49impl core::fmt::Display for AuthenticationMethodOrAccessTokenType {
50    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
51        match self {
52            Self::AuthenticationMethod(m) => m.fmt(f),
53            Self::AccessTokenType(t) => t.fmt(f),
54            Self::Unknown(s) => s.fmt(f),
55        }
56    }
57}
58
59impl core::str::FromStr for AuthenticationMethodOrAccessTokenType {
60    type Err = core::convert::Infallible;
61
62    fn from_str(s: &str) -> Result<Self, Self::Err> {
63        match OAuthClientAuthenticationMethod::from_str(s) {
64            Ok(OAuthClientAuthenticationMethod::Unknown(_)) | Err(_) => {}
65            Ok(m) => return Ok(m.into()),
66        }
67
68        match OAuthAccessTokenType::from_str(s) {
69            Ok(OAuthAccessTokenType::Unknown(_)) | Err(_) => {}
70            Ok(m) => return Ok(m.into()),
71        }
72
73        Ok(Self::Unknown(s.to_owned()))
74    }
75}
76
77impl AuthenticationMethodOrAccessTokenType {
78    /// Get the authentication method of this
79    /// `AuthenticationMethodOrAccessTokenType`.
80    #[must_use]
81    pub fn authentication_method(&self) -> Option<&OAuthClientAuthenticationMethod> {
82        match self {
83            Self::AuthenticationMethod(m) => Some(m),
84            _ => None,
85        }
86    }
87
88    /// Get the access token type of this
89    /// `AuthenticationMethodOrAccessTokenType`.
90    #[must_use]
91    pub fn access_token_type(&self) -> Option<&OAuthAccessTokenType> {
92        match self {
93            Self::AccessTokenType(t) => Some(t),
94            _ => None,
95        }
96    }
97}
98
99impl From<OAuthClientAuthenticationMethod> for AuthenticationMethodOrAccessTokenType {
100    fn from(t: OAuthClientAuthenticationMethod) -> Self {
101        Self::AuthenticationMethod(t)
102    }
103}
104
105impl From<OAuthAccessTokenType> for AuthenticationMethodOrAccessTokenType {
106    fn from(t: OAuthAccessTokenType) -> Self {
107        Self::AccessTokenType(t)
108    }
109}
110
111/// The kind of an application.
112#[derive(SerializeDisplay, DeserializeFromStr, Clone, PartialEq, Eq, Hash, Debug)]
113pub enum ApplicationType {
114    /// A web application.
115    Web,
116
117    /// A native application.
118    Native,
119
120    /// An unknown value.
121    Unknown(String),
122}
123
124impl core::fmt::Display for ApplicationType {
125    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
126        match self {
127            Self::Web => f.write_str("web"),
128            Self::Native => f.write_str("native"),
129            Self::Unknown(s) => f.write_str(s),
130        }
131    }
132}
133
134impl core::str::FromStr for ApplicationType {
135    type Err = core::convert::Infallible;
136
137    fn from_str(s: &str) -> Result<Self, Self::Err> {
138        match s {
139            "web" => Ok(Self::Web),
140            "native" => Ok(Self::Native),
141            s => Ok(Self::Unknown(s.to_owned())),
142        }
143    }
144}
145
146/// Subject Identifier types.
147///
148/// A Subject Identifier is a locally unique and never reassigned identifier
149/// within the Issuer for the End-User, which is intended to be consumed by the
150/// Client.
151#[derive(SerializeDisplay, DeserializeFromStr, Clone, PartialEq, Eq, Hash, Debug)]
152pub enum SubjectType {
153    /// This provides the same `sub` (subject) value to all Clients.
154    Public,
155
156    /// This provides a different `sub` value to each Client, so as not to
157    /// enable Clients to correlate the End-User's activities without
158    /// permission.
159    Pairwise,
160
161    /// An unknown value.
162    Unknown(String),
163}
164
165impl core::fmt::Display for SubjectType {
166    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
167        match self {
168            Self::Public => f.write_str("public"),
169            Self::Pairwise => f.write_str("pairwise"),
170            Self::Unknown(s) => f.write_str(s),
171        }
172    }
173}
174
175impl core::str::FromStr for SubjectType {
176    type Err = core::convert::Infallible;
177
178    fn from_str(s: &str) -> Result<Self, Self::Err> {
179        match s {
180            "public" => Ok(Self::Public),
181            "pairwise" => Ok(Self::Pairwise),
182            s => Ok(Self::Unknown(s.to_owned())),
183        }
184    }
185}
186
187/// Claim types.
188#[derive(SerializeDisplay, DeserializeFromStr, Clone, PartialEq, Eq, Hash, Debug)]
189pub enum ClaimType {
190    /// Claims that are directly asserted by the OpenID Provider.
191    Normal,
192
193    /// Claims that are asserted by a Claims Provider other than the OpenID
194    /// Provider but are returned by OpenID Provider.
195    Aggregated,
196
197    /// Claims that are asserted by a Claims Provider other than the OpenID
198    /// Provider but are returned as references by the OpenID Provider.
199    Distributed,
200
201    /// An unknown value.
202    Unknown(String),
203}
204
205impl core::fmt::Display for ClaimType {
206    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
207        match self {
208            Self::Normal => f.write_str("normal"),
209            Self::Aggregated => f.write_str("aggregated"),
210            Self::Distributed => f.write_str("distributed"),
211            Self::Unknown(s) => f.write_str(s),
212        }
213    }
214}
215
216impl core::str::FromStr for ClaimType {
217    type Err = core::convert::Infallible;
218
219    fn from_str(s: &str) -> Result<Self, Self::Err> {
220        match s {
221            "normal" => Ok(Self::Normal),
222            "aggregated" => Ok(Self::Aggregated),
223            "distributed" => Ok(Self::Distributed),
224            s => Ok(Self::Unknown(s.to_owned())),
225        }
226    }
227}
228
229/// An account management action that a user can take.
230///
231/// Source: <https://github.com/matrix-org/matrix-spec-proposals/pull/2965>
232#[derive(
233    SerializeDisplay, DeserializeFromStr, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash,
234)]
235#[non_exhaustive]
236pub enum AccountManagementAction {
237    /// `org.matrix.profile`
238    ///
239    /// The user wishes to view their profile (name, avatar, contact details).
240    Profile,
241
242    /// `org.matrix.sessions_list`
243    ///
244    /// The user wishes to view a list of their sessions.
245    SessionsList,
246
247    /// `org.matrix.session_view`
248    ///
249    /// The user wishes to view the details of a specific session.
250    SessionView,
251
252    /// `org.matrix.session_end`
253    ///
254    /// The user wishes to end/log out of a specific session.
255    SessionEnd,
256
257    /// `org.matrix.account_deactivate`
258    ///
259    /// The user wishes to deactivate their account.
260    AccountDeactivate,
261
262    /// `org.matrix.cross_signing_reset`
263    ///
264    /// The user wishes to reset their cross-signing keys.
265    CrossSigningReset,
266
267    /// An unknown value.
268    Unknown(String),
269}
270
271impl core::fmt::Display for AccountManagementAction {
272    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
273        match self {
274            Self::Profile => write!(f, "org.matrix.profile"),
275            Self::SessionsList => write!(f, "org.matrix.sessions_list"),
276            Self::SessionView => write!(f, "org.matrix.session_view"),
277            Self::SessionEnd => write!(f, "org.matrix.session_end"),
278            Self::AccountDeactivate => write!(f, "org.matrix.account_deactivate"),
279            Self::CrossSigningReset => write!(f, "org.matrix.cross_signing_reset"),
280            Self::Unknown(value) => write!(f, "{value}"),
281        }
282    }
283}
284
285impl core::str::FromStr for AccountManagementAction {
286    type Err = core::convert::Infallible;
287
288    fn from_str(s: &str) -> Result<Self, Self::Err> {
289        match s {
290            "org.matrix.profile" => Ok(Self::Profile),
291            "org.matrix.sessions_list" => Ok(Self::SessionsList),
292            "org.matrix.session_view" => Ok(Self::SessionView),
293            "org.matrix.session_end" => Ok(Self::SessionEnd),
294            "org.matrix.account_deactivate" => Ok(Self::AccountDeactivate),
295            "org.matrix.cross_signing_reset" => Ok(Self::CrossSigningReset),
296            value => Ok(Self::Unknown(value.to_owned())),
297        }
298    }
299}
300
301/// The default value of `response_modes_supported` if it is not set.
302pub static DEFAULT_RESPONSE_MODES_SUPPORTED: &[ResponseMode] =
303    &[ResponseMode::Query, ResponseMode::Fragment];
304
305/// The default value of `grant_types_supported` if it is not set.
306pub static DEFAULT_GRANT_TYPES_SUPPORTED: &[GrantType] =
307    &[GrantType::AuthorizationCode, GrantType::Implicit];
308
309/// The default value of `token_endpoint_auth_methods_supported` if it is not
310/// set.
311pub static DEFAULT_AUTH_METHODS_SUPPORTED: &[OAuthClientAuthenticationMethod] =
312    &[OAuthClientAuthenticationMethod::ClientSecretBasic];
313
314/// The default value of `claim_types_supported` if it is not set.
315pub static DEFAULT_CLAIM_TYPES_SUPPORTED: &[ClaimType] = &[ClaimType::Normal];
316
317/// Authorization server metadata, as described by the [IANA registry].
318///
319/// All the fields with a default value are accessible via methods.
320///
321/// [IANA registry]: https://www.iana.org/assignments/oauth-parameters/oauth-parameters.xhtml#authorization-server-metadata
322#[skip_serializing_none]
323#[derive(Debug, Serialize, Deserialize, Clone, Default)]
324pub struct ProviderMetadata {
325    /// Authorization server's issuer identifier URL.
326    ///
327    /// This field is required. The URL must use a `https` scheme, and must not
328    /// contain a query or fragment. It must match the one used to build the
329    /// well-known URI to query this metadata.
330    pub issuer: Option<String>,
331
332    /// URL of the authorization server's [authorization endpoint].
333    ///
334    /// This field is required. The URL must use a `https` scheme, and must not
335    /// contain a fragment.
336    ///
337    /// [authorization endpoint]: https://www.rfc-editor.org/rfc/rfc6749.html#section-3.1
338    pub authorization_endpoint: Option<Url>,
339
340    /// URL of the authorization server's [token endpoint].
341    ///
342    /// This field is required. The URL must use a `https` scheme, and must not
343    /// contain a fragment.
344    ///
345    /// [token endpoint]: https://www.rfc-editor.org/rfc/rfc6749.html#section-3.2
346    pub token_endpoint: Option<Url>,
347
348    /// URL of the authorization server's [JWK] Set document.
349    ///
350    /// This field is required. The URL must use a `https` scheme.
351    ///
352    /// [JWK]: https://www.rfc-editor.org/rfc/rfc7517.html
353    pub jwks_uri: Option<Url>,
354
355    /// URL of the authorization server's [OAuth 2.0 Dynamic Client
356    /// Registration] endpoint.
357    ///
358    /// If this field is present, the URL must use a `https` scheme.
359    ///
360    /// [OAuth 2.0 Dynamic Client Registration]: https://www.rfc-editor.org/rfc/rfc7591
361    pub registration_endpoint: Option<Url>,
362
363    /// JSON array containing a list of the OAuth 2.0 `scope` values that this
364    /// authorization server supports.
365    ///
366    /// If this field is present, it must contain at least the `openid` scope
367    /// value.
368    pub scopes_supported: Option<Vec<String>>,
369
370    /// JSON array containing a list of the [OAuth 2.0 `response_type` values]
371    /// that this authorization server supports.
372    ///
373    /// This field is required.
374    ///
375    /// [OAuth 2.0 `response_type` values]: https://www.rfc-editor.org/rfc/rfc7591#page-9
376    pub response_types_supported: Option<Vec<ResponseType>>,
377
378    /// JSON array containing a list of the [OAuth 2.0 `response_mode` values]
379    /// that this authorization server supports.
380    ///
381    /// Defaults to [`DEFAULT_RESPONSE_MODES_SUPPORTED`].
382    ///
383    /// [OAuth 2.0 `response_mode` values]: https://openid.net/specs/oauth-v2-multiple-response-types-1_0.html
384    pub response_modes_supported: Option<Vec<ResponseMode>>,
385
386    /// JSON array containing a list of the [OAuth 2.0 `grant_type` values] that
387    /// this authorization server supports.
388    ///
389    /// Defaults to [`DEFAULT_GRANT_TYPES_SUPPORTED`].
390    ///
391    /// [OAuth 2.0 `grant_type` values]: https://www.rfc-editor.org/rfc/rfc7591#page-9
392    pub grant_types_supported: Option<Vec<GrantType>>,
393
394    /// JSON array containing a list of client authentication methods supported
395    /// by this token endpoint.
396    ///
397    /// Defaults to [`DEFAULT_AUTH_METHODS_SUPPORTED`].
398    pub token_endpoint_auth_methods_supported: Option<Vec<OAuthClientAuthenticationMethod>>,
399
400    /// JSON array containing a list of the JWS signing algorithms supported
401    /// by the token endpoint for the signature on the JWT used to
402    /// authenticate the client at the token endpoint.
403    ///
404    /// If this field is present, it must not contain
405    /// [`JsonWebSignatureAlg::None`]. This field is required if
406    /// `token_endpoint_auth_methods_supported` contains
407    /// [`OAuthClientAuthenticationMethod::PrivateKeyJwt`] or
408    /// [`OAuthClientAuthenticationMethod::ClientSecretJwt`].
409    pub token_endpoint_auth_signing_alg_values_supported: Option<Vec<JsonWebSignatureAlg>>,
410
411    /// URL of a page containing human-readable information that developers
412    /// might want or need to know when using the authorization server.
413    pub service_documentation: Option<Url>,
414
415    /// Languages and scripts supported for the user interface, represented as a
416    /// JSON array of language tag values from BCP 47.
417    ///
418    /// If omitted, the set of supported languages and scripts is unspecified.
419    pub ui_locales_supported: Option<Vec<LanguageTag>>,
420
421    /// URL that the authorization server provides to the person registering the
422    /// client to read about the authorization server's requirements on how the
423    /// client can use the data provided by the authorization server.
424    pub op_policy_uri: Option<Url>,
425
426    /// URL that the authorization server provides to the person registering the
427    /// client to read about the authorization server's terms of service.
428    pub op_tos_uri: Option<Url>,
429
430    /// URL of the authorization server's [OAuth 2.0 revocation endpoint].
431    ///
432    /// If this field is present, the URL must use a `https` scheme, and must
433    /// not contain a fragment.
434    ///
435    /// [OAuth 2.0 revocation endpoint]: https://www.rfc-editor.org/rfc/rfc7009
436    pub revocation_endpoint: Option<Url>,
437
438    /// JSON array containing a list of client authentication methods supported
439    /// by this revocation endpoint.
440    ///
441    /// Defaults to [`DEFAULT_AUTH_METHODS_SUPPORTED`].
442    pub revocation_endpoint_auth_methods_supported: Option<Vec<OAuthClientAuthenticationMethod>>,
443
444    /// JSON array containing a list of the JWS signing algorithms supported by
445    /// the revocation endpoint for the signature on the JWT used to
446    /// authenticate the client at the revocation endpoint.
447    ///
448    /// If this field is present, it must not contain
449    /// [`JsonWebSignatureAlg::None`]. This field is required if
450    /// `revocation_endpoint_auth_methods_supported` contains
451    /// [`OAuthClientAuthenticationMethod::PrivateKeyJwt`] or
452    /// [`OAuthClientAuthenticationMethod::ClientSecretJwt`].
453    pub revocation_endpoint_auth_signing_alg_values_supported: Option<Vec<JsonWebSignatureAlg>>,
454
455    /// URL of the authorization server's [OAuth 2.0 introspection endpoint].
456    ///
457    /// If this field is present, the URL must use a `https` scheme.
458    ///
459    /// [OAuth 2.0 introspection endpoint]: https://www.rfc-editor.org/rfc/rfc7662
460    pub introspection_endpoint: Option<Url>,
461
462    /// JSON array containing a list of client authentication methods or token
463    /// types supported by this introspection endpoint.
464    pub introspection_endpoint_auth_methods_supported:
465        Option<Vec<AuthenticationMethodOrAccessTokenType>>,
466
467    /// JSON array containing a list of the JWS signing algorithms supported by
468    /// the introspection endpoint for the signature on the JWT used to
469    /// authenticate the client at the introspection endpoint.
470    ///
471    /// If this field is present, it must not contain
472    /// [`JsonWebSignatureAlg::None`]. This field is required if
473    /// `intospection_endpoint_auth_methods_supported` contains
474    /// [`OAuthClientAuthenticationMethod::PrivateKeyJwt`] or
475    /// [`OAuthClientAuthenticationMethod::ClientSecretJwt`].
476    pub introspection_endpoint_auth_signing_alg_values_supported: Option<Vec<JsonWebSignatureAlg>>,
477
478    /// [PKCE code challenge methods] supported by this authorization server.
479    /// If omitted, the authorization server does not support PKCE.
480    ///
481    /// [PKCE code challenge]: https://www.rfc-editor.org/rfc/rfc7636
482    pub code_challenge_methods_supported: Option<Vec<PkceCodeChallengeMethod>>,
483
484    /// URL of the OP's [UserInfo Endpoint].
485    ///
486    /// [UserInfo Endpoint]: https://openid.net/specs/openid-connect-core-1_0.html#UserInfo
487    pub userinfo_endpoint: Option<Url>,
488
489    /// JSON array containing a list of the Authentication Context Class
490    /// References that this OP supports.
491    pub acr_values_supported: Option<Vec<String>>,
492
493    /// JSON array containing a list of the Subject Identifier types that this
494    /// OP supports.
495    ///
496    /// This field is required.
497    pub subject_types_supported: Option<Vec<SubjectType>>,
498
499    /// JSON array containing a list of the JWS signing algorithms (`alg`
500    /// values) supported by the OP for the ID Token.
501    ///
502    /// This field is required.
503    pub id_token_signing_alg_values_supported: Option<Vec<JsonWebSignatureAlg>>,
504
505    /// JSON array containing a list of the JWE encryption algorithms (`alg`
506    /// values) supported by the OP for the ID Token.
507    pub id_token_encryption_alg_values_supported: Option<Vec<JsonWebEncryptionAlg>>,
508
509    /// JSON array containing a list of the JWE encryption algorithms (`enc`
510    /// values) supported by the OP for the ID Token.
511    pub id_token_encryption_enc_values_supported: Option<Vec<JsonWebEncryptionEnc>>,
512
513    /// JSON array containing a list of the JWS signing algorithms (`alg`
514    /// values) supported by the UserInfo Endpoint.
515    pub userinfo_signing_alg_values_supported: Option<Vec<JsonWebSignatureAlg>>,
516
517    /// JSON array containing a list of the JWE encryption algorithms (`alg`
518    /// values) supported by the UserInfo Endpoint.
519    pub userinfo_encryption_alg_values_supported: Option<Vec<JsonWebEncryptionAlg>>,
520
521    /// JSON array containing a list of the JWE encryption algorithms (`enc`
522    /// values) supported by the UserInfo Endpoint.
523    pub userinfo_encryption_enc_values_supported: Option<Vec<JsonWebEncryptionEnc>>,
524
525    /// JSON array containing a list of the JWS signing algorithms (`alg`
526    /// values) supported by the OP for Request Objects.
527    pub request_object_signing_alg_values_supported: Option<Vec<JsonWebSignatureAlg>>,
528
529    /// JSON array containing a list of the JWE encryption algorithms (`alg`
530    /// values) supported by the OP for Request Objects.
531    pub request_object_encryption_alg_values_supported: Option<Vec<JsonWebEncryptionAlg>>,
532
533    /// JSON array containing a list of the JWE encryption algorithms (`enc`
534    /// values) supported by the OP for Request Objects.
535    pub request_object_encryption_enc_values_supported: Option<Vec<JsonWebEncryptionEnc>>,
536
537    /// JSON array containing a list of the "display" parameter values that the
538    /// OpenID Provider supports.
539    pub display_values_supported: Option<Vec<Display>>,
540
541    /// JSON array containing a list of the Claim Types that the OpenID Provider
542    /// supports.
543    ///
544    /// Defaults to [`DEFAULT_CLAIM_TYPES_SUPPORTED`].
545    pub claim_types_supported: Option<Vec<ClaimType>>,
546
547    /// JSON array containing a list of the Claim Names of the Claims that the
548    /// OpenID Provider MAY be able to supply values for.
549    pub claims_supported: Option<Vec<String>>,
550
551    /// Languages and scripts supported for values in Claims being returned,
552    /// represented as a JSON array of BCP 47 language tag values.
553    pub claims_locales_supported: Option<Vec<LanguageTag>>,
554
555    /// Boolean value specifying whether the OP supports use of the `claims`
556    /// parameter.
557    ///
558    /// Defaults to `false`.
559    pub claims_parameter_supported: Option<bool>,
560
561    /// Boolean value specifying whether the OP supports use of the `request`
562    /// parameter.
563    ///
564    /// Defaults to `false`.
565    pub request_parameter_supported: Option<bool>,
566
567    /// Boolean value specifying whether the OP supports use of the
568    /// `request_uri` parameter.
569    ///
570    /// Defaults to `true`.
571    pub request_uri_parameter_supported: Option<bool>,
572
573    /// Boolean value specifying whether the OP requires any `request_uri`
574    /// values used to be pre-registered.
575    ///
576    /// Defaults to `false`.
577    pub require_request_uri_registration: Option<bool>,
578
579    /// Indicates where authorization request needs to be protected as [Request
580    /// Object] and provided through either request or request_uri parameter.
581    ///
582    /// Defaults to `false`.
583    ///
584    /// [Request Object]: https://www.rfc-editor.org/rfc/rfc9101.html
585    pub require_signed_request_object: Option<bool>,
586
587    /// URL of the authorization server's [pushed authorization request
588    /// endpoint].
589    ///
590    /// [pushed authorization request endpoint]: https://www.rfc-editor.org/rfc/rfc9126.html
591    pub pushed_authorization_request_endpoint: Option<Url>,
592
593    /// Indicates whether the authorization server accepts authorization
594    /// requests only via PAR.
595    ///
596    /// Defaults to `false`.
597    pub require_pushed_authorization_requests: Option<bool>,
598
599    /// Array containing the list of prompt values that this OP supports.
600    ///
601    /// This field can be used to detect if the OP supports the [prompt
602    /// `create`] value.
603    ///
604    /// [prompt `create`]: https://openid.net/specs/openid-connect-prompt-create-1_0.html
605    pub prompt_values_supported: Option<Vec<Prompt>>,
606
607    /// URL of the authorization server's [device authorization endpoint].
608    ///
609    /// [device authorization endpoint]: https://www.rfc-editor.org/rfc/rfc8628
610    pub device_authorization_endpoint: Option<Url>,
611
612    /// URL of the authorization server's [RP-Initiated Logout endpoint].
613    ///
614    /// [RP-Initiated Logout endpoint]: https://openid.net/specs/openid-connect-rpinitiated-1_0.html
615    pub end_session_endpoint: Option<Url>,
616
617    /// URL where the user is able to access the account management capabilities
618    /// of this OP.
619    ///
620    /// This is a Matrix extension introduced in [MSC2965](https://github.com/matrix-org/matrix-spec-proposals/pull/2965).
621    pub account_management_uri: Option<Url>,
622
623    /// Array of actions that the account management URL supports.
624    ///
625    /// This is a Matrix extension introduced in [MSC2965](https://github.com/matrix-org/matrix-spec-proposals/pull/2965).
626    pub account_management_actions_supported: Option<Vec<AccountManagementAction>>,
627}
628
629impl ProviderMetadata {
630    /// Validate this `ProviderMetadata` according to the [OpenID Connect
631    /// Discovery Spec 1.0].
632    ///
633    /// # Parameters
634    ///
635    /// - `issuer`: The issuer that was discovered to get this
636    ///   `ProviderMetadata`.
637    ///
638    /// # Errors
639    ///
640    /// Will return `Err` if validation fails.
641    ///
642    /// [OpenID Connect Discovery Spec 1.0]: https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderMetadata
643    pub fn validate(
644        self,
645        issuer: &str,
646    ) -> Result<VerifiedProviderMetadata, ProviderMetadataVerificationError> {
647        let metadata = self.insecure_verify_metadata()?;
648
649        if metadata.issuer() != issuer {
650            return Err(ProviderMetadataVerificationError::IssuerUrlsDontMatch);
651        }
652
653        validate_url(
654            "issuer",
655            &metadata
656                .issuer()
657                .parse()
658                .map_err(|_| ProviderMetadataVerificationError::IssuerNotUrl)?,
659            ExtraUrlRestrictions::NoQueryOrFragment,
660        )?;
661
662        validate_url(
663            "authorization_endpoint",
664            metadata.authorization_endpoint(),
665            ExtraUrlRestrictions::NoFragment,
666        )?;
667
668        validate_url(
669            "token_endpoint",
670            metadata.token_endpoint(),
671            ExtraUrlRestrictions::NoFragment,
672        )?;
673
674        validate_url("jwks_uri", metadata.jwks_uri(), ExtraUrlRestrictions::None)?;
675
676        if let Some(url) = &metadata.registration_endpoint {
677            validate_url("registration_endpoint", url, ExtraUrlRestrictions::None)?;
678        }
679
680        if let Some(scopes) = &metadata.scopes_supported {
681            if !scopes.iter().any(|s| s == "openid") {
682                return Err(ProviderMetadataVerificationError::ScopesMissingOpenid);
683            }
684        }
685
686        validate_signing_alg_values_supported(
687            "token_endpoint",
688            metadata
689                .token_endpoint_auth_signing_alg_values_supported
690                .iter()
691                .flatten(),
692            metadata
693                .token_endpoint_auth_methods_supported
694                .iter()
695                .flatten(),
696        )?;
697
698        if let Some(url) = &metadata.revocation_endpoint {
699            validate_url("revocation_endpoint", url, ExtraUrlRestrictions::NoFragment)?;
700        }
701
702        validate_signing_alg_values_supported(
703            "revocation_endpoint",
704            metadata
705                .revocation_endpoint_auth_signing_alg_values_supported
706                .iter()
707                .flatten(),
708            metadata
709                .revocation_endpoint_auth_methods_supported
710                .iter()
711                .flatten(),
712        )?;
713
714        if let Some(url) = &metadata.introspection_endpoint {
715            validate_url("introspection_endpoint", url, ExtraUrlRestrictions::None)?;
716        }
717
718        // The list can also contain token types so remove them as we don't need to
719        // check them.
720        let introspection_methods = metadata
721            .introspection_endpoint_auth_methods_supported
722            .as_ref()
723            .map(|v| {
724                v.iter()
725                    .filter_map(AuthenticationMethodOrAccessTokenType::authentication_method)
726                    .collect::<Vec<_>>()
727            });
728        validate_signing_alg_values_supported(
729            "introspection_endpoint",
730            metadata
731                .introspection_endpoint_auth_signing_alg_values_supported
732                .iter()
733                .flatten(),
734            introspection_methods.into_iter().flatten(),
735        )?;
736
737        if let Some(url) = &metadata.userinfo_endpoint {
738            validate_url("userinfo_endpoint", url, ExtraUrlRestrictions::None)?;
739        }
740
741        if let Some(url) = &metadata.pushed_authorization_request_endpoint {
742            validate_url(
743                "pushed_authorization_request_endpoint",
744                url,
745                ExtraUrlRestrictions::None,
746            )?;
747        }
748
749        if let Some(url) = &metadata.end_session_endpoint {
750            validate_url("end_session_endpoint", url, ExtraUrlRestrictions::None)?;
751        }
752
753        Ok(metadata)
754    }
755
756    /// Verify this `ProviderMetadata`.
757    ///
758    /// Contrary to [`ProviderMetadata::validate()`], it only checks that the
759    /// required fields are present.
760    ///
761    /// This can be used during development to test against a local OpenID
762    /// Provider, for example.
763    ///
764    /// # Parameters
765    ///
766    /// - `issuer`: The issuer that was discovered to get this
767    ///   `ProviderMetadata`.
768    ///
769    /// # Errors
770    ///
771    /// Will return `Err` if a required field is missing.
772    ///
773    /// # Warning
774    ///
775    /// It is not recommended to use this method in production as it doesn't
776    /// ensure that the issuer implements the proper security practices.
777    pub fn insecure_verify_metadata(
778        self,
779    ) -> Result<VerifiedProviderMetadata, ProviderMetadataVerificationError> {
780        self.issuer
781            .as_ref()
782            .ok_or(ProviderMetadataVerificationError::MissingIssuer)?;
783
784        self.authorization_endpoint
785            .as_ref()
786            .ok_or(ProviderMetadataVerificationError::MissingAuthorizationEndpoint)?;
787
788        self.token_endpoint
789            .as_ref()
790            .ok_or(ProviderMetadataVerificationError::MissingTokenEndpoint)?;
791
792        self.jwks_uri
793            .as_ref()
794            .ok_or(ProviderMetadataVerificationError::MissingJwksUri)?;
795
796        self.response_types_supported
797            .as_ref()
798            .ok_or(ProviderMetadataVerificationError::MissingResponseTypesSupported)?;
799
800        self.subject_types_supported
801            .as_ref()
802            .ok_or(ProviderMetadataVerificationError::MissingSubjectTypesSupported)?;
803
804        self.id_token_signing_alg_values_supported
805            .as_ref()
806            .ok_or(ProviderMetadataVerificationError::MissingIdTokenSigningAlgValuesSupported)?;
807
808        Ok(VerifiedProviderMetadata { inner: self })
809    }
810
811    /// JSON array containing a list of the OAuth 2.0 `response_mode` values
812    /// that this authorization server supports.
813    ///
814    /// Defaults to [`DEFAULT_RESPONSE_MODES_SUPPORTED`].
815    #[must_use]
816    pub fn response_modes_supported(&self) -> &[ResponseMode] {
817        self.response_modes_supported
818            .as_deref()
819            .unwrap_or(DEFAULT_RESPONSE_MODES_SUPPORTED)
820    }
821
822    /// JSON array containing a list of the OAuth 2.0 grant type values that
823    /// this authorization server supports.
824    ///
825    /// Defaults to [`DEFAULT_GRANT_TYPES_SUPPORTED`].
826    #[must_use]
827    pub fn grant_types_supported(&self) -> &[GrantType] {
828        self.grant_types_supported
829            .as_deref()
830            .unwrap_or(DEFAULT_GRANT_TYPES_SUPPORTED)
831    }
832
833    /// JSON array containing a list of client authentication methods supported
834    /// by the token endpoint.
835    ///
836    /// Defaults to [`DEFAULT_AUTH_METHODS_SUPPORTED`].
837    #[must_use]
838    pub fn token_endpoint_auth_methods_supported(&self) -> &[OAuthClientAuthenticationMethod] {
839        self.token_endpoint_auth_methods_supported
840            .as_deref()
841            .unwrap_or(DEFAULT_AUTH_METHODS_SUPPORTED)
842    }
843
844    /// JSON array containing a list of client authentication methods supported
845    /// by the revocation endpoint.
846    ///
847    /// Defaults to [`DEFAULT_AUTH_METHODS_SUPPORTED`].
848    #[must_use]
849    pub fn revocation_endpoint_auth_methods_supported(&self) -> &[OAuthClientAuthenticationMethod] {
850        self.revocation_endpoint_auth_methods_supported
851            .as_deref()
852            .unwrap_or(DEFAULT_AUTH_METHODS_SUPPORTED)
853    }
854
855    /// JSON array containing a list of the Claim Types that the OpenID Provider
856    /// supports.
857    ///
858    /// Defaults to [`DEFAULT_CLAIM_TYPES_SUPPORTED`].
859    #[must_use]
860    pub fn claim_types_supported(&self) -> &[ClaimType] {
861        self.claim_types_supported
862            .as_deref()
863            .unwrap_or(DEFAULT_CLAIM_TYPES_SUPPORTED)
864    }
865
866    /// Boolean value specifying whether the OP supports use of the `claims`
867    /// parameter.
868    ///
869    /// Defaults to `false`.
870    #[must_use]
871    pub fn claims_parameter_supported(&self) -> bool {
872        self.claims_parameter_supported.unwrap_or(false)
873    }
874
875    /// Boolean value specifying whether the OP supports use of the `request`
876    /// parameter.
877    ///
878    /// Defaults to `false`.
879    #[must_use]
880    pub fn request_parameter_supported(&self) -> bool {
881        self.request_parameter_supported.unwrap_or(false)
882    }
883
884    /// Boolean value specifying whether the OP supports use of the
885    /// `request_uri` parameter.
886    ///
887    /// Defaults to `true`.
888    #[must_use]
889    pub fn request_uri_parameter_supported(&self) -> bool {
890        self.request_uri_parameter_supported.unwrap_or(true)
891    }
892
893    /// Boolean value specifying whether the OP requires any `request_uri`
894    /// values used to be pre-registered.
895    ///
896    /// Defaults to `false`.
897    #[must_use]
898    pub fn require_request_uri_registration(&self) -> bool {
899        self.require_request_uri_registration.unwrap_or(false)
900    }
901
902    /// Indicates where authorization request needs to be protected as Request
903    /// Object and provided through either `request` or `request_uri` parameter.
904    ///
905    /// Defaults to `false`.
906    #[must_use]
907    pub fn require_signed_request_object(&self) -> bool {
908        self.require_signed_request_object.unwrap_or(false)
909    }
910
911    /// Indicates whether the authorization server accepts authorization
912    /// requests only via PAR.
913    ///
914    /// Defaults to `false`.
915    #[must_use]
916    pub fn require_pushed_authorization_requests(&self) -> bool {
917        self.require_pushed_authorization_requests.unwrap_or(false)
918    }
919}
920
921/// The verified authorization server metadata.
922///
923/// All the fields required by the [OpenID Connect Discovery Spec 1.0] or with
924/// a default value are accessible via methods.
925///
926/// To access other fields, use this type's `Deref` implementation.
927///
928/// [OpenID Connect Discovery Spec 1.0]: https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderMetadata
929#[derive(Debug, Clone)]
930pub struct VerifiedProviderMetadata {
931    inner: ProviderMetadata,
932}
933
934impl VerifiedProviderMetadata {
935    /// Authorization server's issuer identifier URL.
936    #[must_use]
937    pub fn issuer(&self) -> &str {
938        match &self.issuer {
939            Some(u) => u,
940            None => unreachable!(),
941        }
942    }
943
944    /// URL of the authorization server's authorization endpoint.
945    #[must_use]
946    pub fn authorization_endpoint(&self) -> &Url {
947        match &self.authorization_endpoint {
948            Some(u) => u,
949            None => unreachable!(),
950        }
951    }
952
953    /// URL of the authorization server's userinfo endpoint.
954    #[must_use]
955    pub fn userinfo_endpoint(&self) -> &Url {
956        match &self.userinfo_endpoint {
957            Some(u) => u,
958            None => unreachable!(),
959        }
960    }
961
962    /// URL of the authorization server's token endpoint.
963    #[must_use]
964    pub fn token_endpoint(&self) -> &Url {
965        match &self.token_endpoint {
966            Some(u) => u,
967            None => unreachable!(),
968        }
969    }
970
971    /// URL of the authorization server's JWK Set document.
972    #[must_use]
973    pub fn jwks_uri(&self) -> &Url {
974        match &self.jwks_uri {
975            Some(u) => u,
976            None => unreachable!(),
977        }
978    }
979
980    /// JSON array containing a list of the OAuth 2.0 `response_type` values
981    /// that this authorization server supports.
982    #[must_use]
983    pub fn response_types_supported(&self) -> &[ResponseType] {
984        match &self.response_types_supported {
985            Some(u) => u,
986            None => unreachable!(),
987        }
988    }
989
990    /// JSON array containing a list of the Subject Identifier types that this
991    /// OP supports.
992    #[must_use]
993    pub fn subject_types_supported(&self) -> &[SubjectType] {
994        match &self.subject_types_supported {
995            Some(u) => u,
996            None => unreachable!(),
997        }
998    }
999
1000    /// JSON array containing a list of the JWS `alg` values supported by the OP
1001    /// for the ID Token.
1002    #[must_use]
1003    pub fn id_token_signing_alg_values_supported(&self) -> &[JsonWebSignatureAlg] {
1004        match &self.id_token_signing_alg_values_supported {
1005            Some(u) => u,
1006            None => unreachable!(),
1007        }
1008    }
1009}
1010
1011impl Deref for VerifiedProviderMetadata {
1012    type Target = ProviderMetadata;
1013
1014    fn deref(&self) -> &Self::Target {
1015        &self.inner
1016    }
1017}
1018
1019/// All errors that can happen when verifying [`ProviderMetadata`]
1020#[derive(Debug, Error)]
1021pub enum ProviderMetadataVerificationError {
1022    /// The issuer is missing.
1023    #[error("issuer is missing")]
1024    MissingIssuer,
1025
1026    /// The issuer is not a valid URL.
1027    #[error("issuer is not a valid URL")]
1028    IssuerNotUrl,
1029
1030    /// The authorization endpoint is missing.
1031    #[error("authorization endpoint is missing")]
1032    MissingAuthorizationEndpoint,
1033
1034    /// The token endpoint is missing.
1035    #[error("token endpoint is missing")]
1036    MissingTokenEndpoint,
1037
1038    /// The JWK Set URI is missing.
1039    #[error("JWK Set URI is missing")]
1040    MissingJwksUri,
1041
1042    /// The supported response types are missing.
1043    #[error("supported response types are missing")]
1044    MissingResponseTypesSupported,
1045
1046    /// The supported subject types are missing.
1047    #[error("supported subject types are missing")]
1048    MissingSubjectTypesSupported,
1049
1050    /// The supported ID token signing algorithm values are missing.
1051    #[error("supported ID token signing algorithm values are missing")]
1052    MissingIdTokenSigningAlgValuesSupported,
1053
1054    /// The URL of the given field doesn't use a `https` scheme.
1055    #[error("{0}'s URL doesn't use a https scheme: {1}")]
1056    UrlNonHttpsScheme(&'static str, Url),
1057
1058    /// The URL of the given field contains a query, but it's not allowed.
1059    #[error("{0}'s URL contains a query: {1}")]
1060    UrlWithQuery(&'static str, Url),
1061
1062    /// The URL of the given field contains a fragment, but it's not allowed.
1063    #[error("{0}'s URL contains a fragment: {1}")]
1064    UrlWithFragment(&'static str, Url),
1065
1066    /// The issuer URL doesn't match the one that was discovered.
1067    #[error("issuer URLs don't match")]
1068    IssuerUrlsDontMatch,
1069
1070    /// `openid` is missing from the supported scopes.
1071    #[error("missing openid scope")]
1072    ScopesMissingOpenid,
1073
1074    /// `code` is missing from the supported response types.
1075    #[error("missing `code` response type")]
1076    ResponseTypesMissingCode,
1077
1078    /// `id_token` is missing from the supported response types.
1079    #[error("missing `id_token` response type")]
1080    ResponseTypesMissingIdToken,
1081
1082    /// `id_token token` is missing from the supported response types.
1083    #[error("missing `id_token token` response type")]
1084    ResponseTypesMissingIdTokenToken,
1085
1086    /// `authorization_code` is missing from the supported grant types.
1087    #[error("missing `authorization_code` grant type")]
1088    GrantTypesMissingAuthorizationCode,
1089
1090    /// `implicit` is missing from the supported grant types.
1091    #[error("missing `implicit` grant type")]
1092    GrantTypesMissingImplicit,
1093
1094    /// The given endpoint is missing auth signing algorithm values, but they
1095    /// are required because it supports at least one of the `client_secret_jwt`
1096    /// or `private_key_jwt` authentication methods.
1097    #[error("{0} missing auth signing algorithm values")]
1098    MissingAuthSigningAlgValues(&'static str),
1099
1100    /// `none` is in the given endpoint's signing algorithm values, but is not
1101    /// allowed.
1102    #[error("{0} signing algorithm values contain `none`")]
1103    SigningAlgValuesWithNone(&'static str),
1104}
1105
1106/// Possible extra restrictions on a URL.
1107#[derive(Debug, Copy, Clone, PartialEq, Eq)]
1108enum ExtraUrlRestrictions {
1109    /// No extra restrictions.
1110    None,
1111
1112    /// The URL must not contain a fragment.
1113    NoFragment,
1114
1115    /// The URL must not contain a query or a fragment.
1116    NoQueryOrFragment,
1117}
1118
1119impl ExtraUrlRestrictions {
1120    fn can_have_fragment(self) -> bool {
1121        self == Self::None
1122    }
1123
1124    fn can_have_query(self) -> bool {
1125        self != Self::NoQueryOrFragment
1126    }
1127}
1128
1129/// Validate the URL of the field with the given extra restrictions.
1130///
1131/// The basic restriction is that the URL must use the `https` scheme.
1132fn validate_url(
1133    field: &'static str,
1134    url: &Url,
1135    restrictions: ExtraUrlRestrictions,
1136) -> Result<(), ProviderMetadataVerificationError> {
1137    if url.scheme() != "https" {
1138        return Err(ProviderMetadataVerificationError::UrlNonHttpsScheme(
1139            field,
1140            url.clone(),
1141        ));
1142    }
1143
1144    if !restrictions.can_have_query() && url.query().is_some() {
1145        return Err(ProviderMetadataVerificationError::UrlWithQuery(
1146            field,
1147            url.clone(),
1148        ));
1149    }
1150
1151    if !restrictions.can_have_fragment() && url.fragment().is_some() {
1152        return Err(ProviderMetadataVerificationError::UrlWithFragment(
1153            field,
1154            url.clone(),
1155        ));
1156    }
1157
1158    Ok(())
1159}
1160
1161/// Validate the algorithm values of the endpoint according to the
1162/// authentication methods.
1163///
1164/// The restrictions are:
1165/// - The algorithm values must not contain `none`,
1166/// - If the `client_secret_jwt` or `private_key_jwt` authentication methods are
1167///   supported, the values must be present.
1168fn validate_signing_alg_values_supported<'a>(
1169    endpoint: &'static str,
1170    values: impl Iterator<Item = &'a JsonWebSignatureAlg>,
1171    mut methods: impl Iterator<Item = &'a OAuthClientAuthenticationMethod>,
1172) -> Result<(), ProviderMetadataVerificationError> {
1173    let mut no_values = true;
1174
1175    for value in values {
1176        if *value == JsonWebSignatureAlg::None {
1177            return Err(ProviderMetadataVerificationError::SigningAlgValuesWithNone(
1178                endpoint,
1179            ));
1180        }
1181
1182        no_values = false;
1183    }
1184
1185    if no_values
1186        && methods.any(|method| {
1187            matches!(
1188                method,
1189                OAuthClientAuthenticationMethod::ClientSecretJwt
1190                    | OAuthClientAuthenticationMethod::PrivateKeyJwt
1191            )
1192        })
1193    {
1194        return Err(ProviderMetadataVerificationError::MissingAuthSigningAlgValues(endpoint));
1195    }
1196
1197    Ok(())
1198}
1199
1200/// The body of a request to the [RP-Initiated Logout Endpoint].
1201///
1202/// [RP-Initiated Logout Endpoint]: https://openid.net/specs/openid-connect-rpinitiated-1_0.html
1203#[skip_serializing_none]
1204#[serde_as]
1205#[derive(Default, Serialize, Deserialize, Clone)]
1206pub struct RpInitiatedLogoutRequest {
1207    /// ID Token previously issued by the OP to the RP.
1208    ///
1209    /// Recommended, used as a hint about the End-User's current authenticated
1210    /// session with the Client.
1211    pub id_token_hint: Option<String>,
1212
1213    /// Hint to the Authorization Server about the End-User that is logging out.
1214    ///
1215    /// The value and meaning of this parameter is left up to the OP's
1216    /// discretion. For instance, the value might contain an email address,
1217    /// phone number, username, or session identifier pertaining to the RP's
1218    /// session with the OP for the End-User.
1219    pub logout_hint: Option<String>,
1220
1221    /// OAuth 2.0 Client Identifier valid at the Authorization Server.
1222    ///
1223    /// The most common use case for this parameter is to specify the Client
1224    /// Identifier when `post_logout_redirect_uri` is used but `id_token_hint`
1225    /// is not. Another use is for symmetrically encrypted ID Tokens used as
1226    /// `id_token_hint` values that require the Client Identifier to be
1227    /// specified by other means, so that the ID Tokens can be decrypted by
1228    /// the OP.
1229    pub client_id: Option<String>,
1230
1231    /// URI to which the RP is requesting that the End-User's User Agent be
1232    /// redirected after a logout has been performed.
1233    ///
1234    /// The value MUST have been previously registered with the OP, using the
1235    /// `post_logout_redirect_uris` registration parameter.
1236    pub post_logout_redirect_uri: Option<Url>,
1237
1238    /// Opaque value used by the RP to maintain state between the logout request
1239    /// and the callback to the endpoint specified by the
1240    /// `post_logout_redirect_uri` parameter.
1241    pub state: Option<String>,
1242
1243    /// End-User's preferred languages and scripts for the user interface,
1244    /// ordered by preference.
1245    #[serde_as(as = "Option<StringWithSeparator::<SpaceSeparator, LanguageTag>>")]
1246    #[serde(default)]
1247    pub ui_locales: Option<Vec<LanguageTag>>,
1248}
1249
1250impl fmt::Debug for RpInitiatedLogoutRequest {
1251    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1252        f.debug_struct("RpInitiatedLogoutRequest")
1253            .field("logout_hint", &self.logout_hint)
1254            .field("post_logout_redirect_uri", &self.post_logout_redirect_uri)
1255            .field("ui_locales", &self.ui_locales)
1256            .finish_non_exhaustive()
1257    }
1258}
1259
1260#[cfg(test)]
1261mod tests {
1262    use assert_matches::assert_matches;
1263    use mas_iana::{
1264        jose::JsonWebSignatureAlg,
1265        oauth::{OAuthAuthorizationEndpointResponseType, OAuthClientAuthenticationMethod},
1266    };
1267    use url::Url;
1268
1269    use super::*;
1270
1271    fn valid_provider_metadata() -> (ProviderMetadata, String) {
1272        let issuer = "https://localhost".to_owned();
1273        let metadata = ProviderMetadata {
1274            issuer: Some(issuer.clone()),
1275            authorization_endpoint: Some(Url::parse("https://localhost/auth").unwrap()),
1276            token_endpoint: Some(Url::parse("https://localhost/token").unwrap()),
1277            jwks_uri: Some(Url::parse("https://localhost/jwks").unwrap()),
1278            response_types_supported: Some(vec![
1279                OAuthAuthorizationEndpointResponseType::Code.into(),
1280            ]),
1281            subject_types_supported: Some(vec![SubjectType::Public]),
1282            id_token_signing_alg_values_supported: Some(vec![JsonWebSignatureAlg::Rs256]),
1283            ..Default::default()
1284        };
1285
1286        (metadata, issuer)
1287    }
1288
1289    #[test]
1290    fn validate_required_metadata() {
1291        let (metadata, issuer) = valid_provider_metadata();
1292        metadata.validate(&issuer).unwrap();
1293    }
1294
1295    #[test]
1296    fn validate_issuer() {
1297        let (mut metadata, issuer) = valid_provider_metadata();
1298
1299        // Err - Missing
1300        metadata.issuer = None;
1301        assert_matches!(
1302            metadata.clone().validate(&issuer),
1303            Err(ProviderMetadataVerificationError::MissingIssuer)
1304        );
1305
1306        // Err - Not an url
1307        metadata.issuer = Some("not-an-url".to_owned());
1308        assert_matches!(
1309            metadata.clone().validate("not-an-url"),
1310            Err(ProviderMetadataVerificationError::IssuerNotUrl)
1311        );
1312
1313        // Err - Wrong issuer
1314        metadata.issuer = Some("https://example.com/".to_owned());
1315        assert_matches!(
1316            metadata.clone().validate(&issuer),
1317            Err(ProviderMetadataVerificationError::IssuerUrlsDontMatch)
1318        );
1319
1320        // Err - Not https
1321        let issuer = "http://localhost/".to_owned();
1322        metadata.issuer = Some(issuer.clone());
1323        let (field, url) = assert_matches!(
1324            metadata.clone().validate(&issuer),
1325            Err(ProviderMetadataVerificationError::UrlNonHttpsScheme(field, url)) => (field, url)
1326        );
1327        assert_eq!(field, "issuer");
1328        assert_eq!(url.as_str(), issuer);
1329
1330        // Err - Query
1331        let issuer = "https://localhost/?query".to_owned();
1332        metadata.issuer = Some(issuer.clone());
1333        let (field, url) = assert_matches!(
1334            metadata.clone().validate(&issuer),
1335            Err(ProviderMetadataVerificationError::UrlWithQuery(field, url)) => (field, url)
1336        );
1337        assert_eq!(field, "issuer");
1338        assert_eq!(url.as_str(), issuer);
1339
1340        // Err - Fragment
1341        let issuer = "https://localhost/#fragment".to_owned();
1342        metadata.issuer = Some(issuer.clone());
1343        let (field, url) = assert_matches!(
1344            metadata.clone().validate(&issuer),
1345            Err(ProviderMetadataVerificationError::UrlWithFragment(field, url)) => (field, url)
1346        );
1347        assert_eq!(field, "issuer");
1348        assert_eq!(url.as_str(), issuer);
1349
1350        // Ok - Path
1351        let issuer = "https://localhost/issuer1".to_owned();
1352        metadata.issuer = Some(issuer.clone());
1353        metadata.validate(&issuer).unwrap();
1354    }
1355
1356    #[test]
1357    fn validate_authorization_endpoint() {
1358        let (mut metadata, issuer) = valid_provider_metadata();
1359
1360        // Err - Missing
1361        metadata.authorization_endpoint = None;
1362        assert_matches!(
1363            metadata.clone().validate(&issuer),
1364            Err(ProviderMetadataVerificationError::MissingAuthorizationEndpoint)
1365        );
1366
1367        // Err - Not https
1368        let endpoint = Url::parse("http://localhost/auth").unwrap();
1369        metadata.authorization_endpoint = Some(endpoint.clone());
1370        let (field, url) = assert_matches!(
1371            metadata.clone().validate(&issuer),
1372            Err(ProviderMetadataVerificationError::UrlNonHttpsScheme(field, url)) => (field, url)
1373        );
1374        assert_eq!(field, "authorization_endpoint");
1375        assert_eq!(url, endpoint);
1376
1377        // Err - Fragment
1378        let endpoint = Url::parse("https://localhost/auth#fragment").unwrap();
1379        metadata.authorization_endpoint = Some(endpoint.clone());
1380        let (field, url) = assert_matches!(
1381            metadata.clone().validate(&issuer),
1382            Err(ProviderMetadataVerificationError::UrlWithFragment(field, url)) => (field, url)
1383        );
1384        assert_eq!(field, "authorization_endpoint");
1385        assert_eq!(url, endpoint);
1386
1387        // Ok - Query
1388        metadata.authorization_endpoint = Some(Url::parse("https://localhost/auth?query").unwrap());
1389        metadata.validate(&issuer).unwrap();
1390    }
1391
1392    #[test]
1393    fn validate_token_endpoint() {
1394        let (mut metadata, issuer) = valid_provider_metadata();
1395
1396        // Err - Missing
1397        metadata.token_endpoint = None;
1398        assert_matches!(
1399            metadata.clone().validate(&issuer),
1400            Err(ProviderMetadataVerificationError::MissingTokenEndpoint)
1401        );
1402
1403        // Err - Not https
1404        let endpoint = Url::parse("http://localhost/token").unwrap();
1405        metadata.token_endpoint = Some(endpoint.clone());
1406        let (field, url) = assert_matches!(
1407            metadata.clone().validate(&issuer),
1408            Err(ProviderMetadataVerificationError::UrlNonHttpsScheme(field, url)) => (field, url)
1409        );
1410        assert_eq!(field, "token_endpoint");
1411        assert_eq!(url, endpoint);
1412
1413        // Err - Fragment
1414        let endpoint = Url::parse("https://localhost/token#fragment").unwrap();
1415        metadata.token_endpoint = Some(endpoint.clone());
1416        let (field, url) = assert_matches!(
1417            metadata.clone().validate(&issuer),
1418            Err(ProviderMetadataVerificationError::UrlWithFragment(field, url)) => (field, url)
1419        );
1420        assert_eq!(field, "token_endpoint");
1421        assert_eq!(url, endpoint);
1422
1423        // Ok - Query
1424        metadata.token_endpoint = Some(Url::parse("https://localhost/token?query").unwrap());
1425        metadata.validate(&issuer).unwrap();
1426    }
1427
1428    #[test]
1429    fn validate_jwks_uri() {
1430        let (mut metadata, issuer) = valid_provider_metadata();
1431
1432        // Err - Missing
1433        metadata.jwks_uri = None;
1434        assert_matches!(
1435            metadata.clone().validate(&issuer),
1436            Err(ProviderMetadataVerificationError::MissingJwksUri)
1437        );
1438
1439        // Err - Not https
1440        let endpoint = Url::parse("http://localhost/jwks").unwrap();
1441        metadata.jwks_uri = Some(endpoint.clone());
1442        let (field, url) = assert_matches!(
1443            metadata.clone().validate(&issuer),
1444            Err(ProviderMetadataVerificationError::UrlNonHttpsScheme(field, url)) => (field, url)
1445        );
1446        assert_eq!(field, "jwks_uri");
1447        assert_eq!(url, endpoint);
1448
1449        // Ok - Query & fragment
1450        metadata.jwks_uri = Some(Url::parse("https://localhost/token?query#fragment").unwrap());
1451        metadata.validate(&issuer).unwrap();
1452    }
1453
1454    #[test]
1455    fn validate_registration_endpoint() {
1456        let (mut metadata, issuer) = valid_provider_metadata();
1457
1458        // Err - Not https
1459        let endpoint = Url::parse("http://localhost/registration").unwrap();
1460        metadata.registration_endpoint = Some(endpoint.clone());
1461        let (field, url) = assert_matches!(
1462            metadata.clone().validate(&issuer),
1463            Err(ProviderMetadataVerificationError::UrlNonHttpsScheme(field, url)) => (field, url)
1464        );
1465        assert_eq!(field, "registration_endpoint");
1466        assert_eq!(url, endpoint);
1467
1468        // Ok - Missing
1469        metadata.registration_endpoint = None;
1470        metadata.clone().validate(&issuer).unwrap();
1471
1472        // Ok - Query & fragment
1473        metadata.registration_endpoint =
1474            Some(Url::parse("https://localhost/registration?query#fragment").unwrap());
1475        metadata.validate(&issuer).unwrap();
1476    }
1477
1478    #[test]
1479    fn validate_scopes_supported() {
1480        let (mut metadata, issuer) = valid_provider_metadata();
1481
1482        // Err - No `openid`
1483        metadata.scopes_supported = Some(vec!["custom".to_owned()]);
1484        assert_matches!(
1485            metadata.clone().validate(&issuer),
1486            Err(ProviderMetadataVerificationError::ScopesMissingOpenid)
1487        );
1488
1489        // Ok - Missing
1490        metadata.scopes_supported = None;
1491        metadata.clone().validate(&issuer).unwrap();
1492
1493        // Ok - With `openid`
1494        metadata.scopes_supported = Some(vec!["openid".to_owned(), "custom".to_owned()]);
1495        metadata.validate(&issuer).unwrap();
1496    }
1497
1498    #[test]
1499    fn validate_response_types_supported() {
1500        let (mut metadata, issuer) = valid_provider_metadata();
1501
1502        // Err - Missing
1503        metadata.response_types_supported = None;
1504        assert_matches!(
1505            metadata.clone().validate(&issuer),
1506            Err(ProviderMetadataVerificationError::MissingResponseTypesSupported)
1507        );
1508
1509        // Ok - Present
1510        metadata.response_types_supported =
1511            Some(vec![OAuthAuthorizationEndpointResponseType::Code.into()]);
1512        metadata.validate(&issuer).unwrap();
1513    }
1514
1515    #[test]
1516    fn validate_token_endpoint_signing_alg_values_supported() {
1517        let (mut metadata, issuer) = valid_provider_metadata();
1518
1519        // Ok - Missing
1520        metadata.token_endpoint_auth_signing_alg_values_supported = None;
1521        metadata.token_endpoint_auth_methods_supported = None;
1522        metadata.clone().validate(&issuer).unwrap();
1523
1524        // Err - With `none`
1525        metadata.token_endpoint_auth_signing_alg_values_supported =
1526            Some(vec![JsonWebSignatureAlg::None]);
1527        let endpoint = assert_matches!(
1528            metadata.clone().validate(&issuer),
1529            Err(ProviderMetadataVerificationError::SigningAlgValuesWithNone(endpoint)) => endpoint
1530        );
1531        assert_eq!(endpoint, "token_endpoint");
1532
1533        // Ok - Other signing alg values.
1534        metadata.token_endpoint_auth_signing_alg_values_supported =
1535            Some(vec![JsonWebSignatureAlg::Rs256, JsonWebSignatureAlg::EdDsa]);
1536        metadata.clone().validate(&issuer).unwrap();
1537
1538        // Err - `client_secret_jwt` without signing alg values.
1539        metadata.token_endpoint_auth_methods_supported =
1540            Some(vec![OAuthClientAuthenticationMethod::ClientSecretJwt]);
1541        metadata.token_endpoint_auth_signing_alg_values_supported = None;
1542        let endpoint = assert_matches!(
1543            metadata.clone().validate(&issuer),
1544            Err(ProviderMetadataVerificationError::MissingAuthSigningAlgValues(endpoint)) => endpoint
1545        );
1546        assert_eq!(endpoint, "token_endpoint");
1547
1548        // Ok - `client_secret_jwt` with signing alg values.
1549        metadata.token_endpoint_auth_signing_alg_values_supported =
1550            Some(vec![JsonWebSignatureAlg::Rs256]);
1551        metadata.clone().validate(&issuer).unwrap();
1552
1553        // Err - `private_key_jwt` without signing alg values.
1554        metadata.token_endpoint_auth_methods_supported =
1555            Some(vec![OAuthClientAuthenticationMethod::PrivateKeyJwt]);
1556        metadata.token_endpoint_auth_signing_alg_values_supported = None;
1557        let endpoint = assert_matches!(
1558            metadata.clone().validate(&issuer),
1559            Err(ProviderMetadataVerificationError::MissingAuthSigningAlgValues(endpoint)) => endpoint
1560        );
1561        assert_eq!(endpoint, "token_endpoint");
1562
1563        // Ok - `private_key_jwt` with signing alg values.
1564        metadata.token_endpoint_auth_signing_alg_values_supported =
1565            Some(vec![JsonWebSignatureAlg::Rs256]);
1566        metadata.clone().validate(&issuer).unwrap();
1567
1568        // Ok - Other auth methods without signing alg values.
1569        metadata.token_endpoint_auth_methods_supported = Some(vec![
1570            OAuthClientAuthenticationMethod::ClientSecretBasic,
1571            OAuthClientAuthenticationMethod::ClientSecretPost,
1572        ]);
1573        metadata.token_endpoint_auth_signing_alg_values_supported = None;
1574        metadata.validate(&issuer).unwrap();
1575    }
1576
1577    #[test]
1578    fn validate_revocation_endpoint() {
1579        let (mut metadata, issuer) = valid_provider_metadata();
1580
1581        // Ok - Missing
1582        metadata.revocation_endpoint = None;
1583        metadata.clone().validate(&issuer).unwrap();
1584
1585        // Err - Not https
1586        let endpoint = Url::parse("http://localhost/revocation").unwrap();
1587        metadata.revocation_endpoint = Some(endpoint.clone());
1588        let (field, url) = assert_matches!(
1589            metadata.clone().validate(&issuer),
1590            Err(ProviderMetadataVerificationError::UrlNonHttpsScheme(field, url)) => (field, url)
1591        );
1592        assert_eq!(field, "revocation_endpoint");
1593        assert_eq!(url, endpoint);
1594
1595        // Err - Fragment
1596        let endpoint = Url::parse("https://localhost/revocation#fragment").unwrap();
1597        metadata.revocation_endpoint = Some(endpoint.clone());
1598        let (field, url) = assert_matches!(
1599            metadata.clone().validate(&issuer),
1600            Err(ProviderMetadataVerificationError::UrlWithFragment(field, url)) => (field, url)
1601        );
1602        assert_eq!(field, "revocation_endpoint");
1603        assert_eq!(url, endpoint);
1604
1605        // Ok - Query
1606        metadata.revocation_endpoint =
1607            Some(Url::parse("https://localhost/revocation?query").unwrap());
1608        metadata.validate(&issuer).unwrap();
1609    }
1610
1611    #[test]
1612    fn validate_revocation_endpoint_signing_alg_values_supported() {
1613        let (mut metadata, issuer) = valid_provider_metadata();
1614
1615        // Only check that this field is validated, algorithm checks are already
1616        // tested for the token endpoint.
1617
1618        // Ok - Missing
1619        metadata.revocation_endpoint_auth_signing_alg_values_supported = None;
1620        metadata.revocation_endpoint_auth_methods_supported = None;
1621        metadata.clone().validate(&issuer).unwrap();
1622
1623        // Err - With `none`
1624        metadata.revocation_endpoint_auth_signing_alg_values_supported =
1625            Some(vec![JsonWebSignatureAlg::None]);
1626        let endpoint = assert_matches!(
1627            metadata.validate(&issuer),
1628            Err(ProviderMetadataVerificationError::SigningAlgValuesWithNone(endpoint)) => endpoint
1629        );
1630        assert_eq!(endpoint, "revocation_endpoint");
1631    }
1632
1633    #[test]
1634    fn validate_introspection_endpoint() {
1635        let (mut metadata, issuer) = valid_provider_metadata();
1636
1637        // Ok - Missing
1638        metadata.introspection_endpoint = None;
1639        metadata.clone().validate(&issuer).unwrap();
1640
1641        // Err - Not https
1642        let endpoint = Url::parse("http://localhost/introspection").unwrap();
1643        metadata.introspection_endpoint = Some(endpoint.clone());
1644        let (field, url) = assert_matches!(
1645            metadata.clone().validate(&issuer),
1646            Err(ProviderMetadataVerificationError::UrlNonHttpsScheme(field, url)) => (field, url)
1647        );
1648        assert_eq!(field, "introspection_endpoint");
1649        assert_eq!(url, endpoint);
1650
1651        // Ok - Query & Fragment
1652        metadata.introspection_endpoint =
1653            Some(Url::parse("https://localhost/introspection?query#fragment").unwrap());
1654        metadata.validate(&issuer).unwrap();
1655    }
1656
1657    #[test]
1658    fn validate_introspection_endpoint_signing_alg_values_supported() {
1659        let (mut metadata, issuer) = valid_provider_metadata();
1660
1661        // Only check that this field is validated, algorithm checks are already
1662        // tested for the token endpoint.
1663
1664        // Ok - Missing
1665        metadata.introspection_endpoint_auth_signing_alg_values_supported = None;
1666        metadata.introspection_endpoint_auth_methods_supported = None;
1667        metadata.clone().validate(&issuer).unwrap();
1668
1669        // Err - With `none`
1670        metadata.introspection_endpoint_auth_signing_alg_values_supported =
1671            Some(vec![JsonWebSignatureAlg::None]);
1672        let endpoint = assert_matches!(
1673            metadata.validate(&issuer),
1674            Err(ProviderMetadataVerificationError::SigningAlgValuesWithNone(endpoint)) => endpoint
1675        );
1676        assert_eq!(endpoint, "introspection_endpoint");
1677    }
1678
1679    #[test]
1680    fn validate_userinfo_endpoint() {
1681        let (mut metadata, issuer) = valid_provider_metadata();
1682
1683        // Ok - Missing
1684        metadata.userinfo_endpoint = None;
1685        metadata.clone().validate(&issuer).unwrap();
1686
1687        // Err - Not https
1688        let endpoint = Url::parse("http://localhost/userinfo").unwrap();
1689        metadata.userinfo_endpoint = Some(endpoint.clone());
1690        let (field, url) = assert_matches!(
1691            metadata.clone().validate(&issuer),
1692            Err(ProviderMetadataVerificationError::UrlNonHttpsScheme(field, url)) => (field, url)
1693        );
1694        assert_eq!(field, "userinfo_endpoint");
1695        assert_eq!(url, endpoint);
1696
1697        // Ok - Query & Fragment
1698        metadata.userinfo_endpoint =
1699            Some(Url::parse("https://localhost/userinfo?query#fragment").unwrap());
1700        metadata.validate(&issuer).unwrap();
1701    }
1702
1703    #[test]
1704    fn validate_subject_types_supported() {
1705        let (mut metadata, issuer) = valid_provider_metadata();
1706
1707        // Err - Missing
1708        metadata.subject_types_supported = None;
1709        assert_matches!(
1710            metadata.clone().validate(&issuer),
1711            Err(ProviderMetadataVerificationError::MissingSubjectTypesSupported)
1712        );
1713
1714        // Ok - Present
1715        metadata.subject_types_supported = Some(vec![SubjectType::Public, SubjectType::Pairwise]);
1716        metadata.validate(&issuer).unwrap();
1717    }
1718
1719    #[test]
1720    fn validate_id_token_signing_alg_values_supported() {
1721        let (mut metadata, issuer) = valid_provider_metadata();
1722
1723        // Err - Missing
1724        metadata.id_token_signing_alg_values_supported = None;
1725        assert_matches!(
1726            metadata.clone().validate(&issuer),
1727            Err(ProviderMetadataVerificationError::MissingIdTokenSigningAlgValuesSupported)
1728        );
1729
1730        // Ok - Present
1731        metadata.id_token_signing_alg_values_supported =
1732            Some(vec![JsonWebSignatureAlg::Rs256, JsonWebSignatureAlg::EdDsa]);
1733        metadata.validate(&issuer).unwrap();
1734    }
1735
1736    #[test]
1737    fn validate_pushed_authorization_request_endpoint() {
1738        let (mut metadata, issuer) = valid_provider_metadata();
1739
1740        // Ok - Missing
1741        metadata.pushed_authorization_request_endpoint = None;
1742        metadata.clone().validate(&issuer).unwrap();
1743
1744        // Err - Not https
1745        let endpoint = Url::parse("http://localhost/par").unwrap();
1746        metadata.pushed_authorization_request_endpoint = Some(endpoint.clone());
1747        let (field, url) = assert_matches!(
1748            metadata.clone().validate(&issuer),
1749            Err(ProviderMetadataVerificationError::UrlNonHttpsScheme(field, url)) => (field, url)
1750        );
1751        assert_eq!(field, "pushed_authorization_request_endpoint");
1752        assert_eq!(url, endpoint);
1753
1754        // Ok - Query & Fragment
1755        metadata.pushed_authorization_request_endpoint =
1756            Some(Url::parse("https://localhost/par?query#fragment").unwrap());
1757        metadata.validate(&issuer).unwrap();
1758    }
1759
1760    #[test]
1761    fn serialize_application_type() {
1762        assert_eq!(
1763            serde_json::to_string(&ApplicationType::Web).unwrap(),
1764            "\"web\""
1765        );
1766        assert_eq!(
1767            serde_json::to_string(&ApplicationType::Native).unwrap(),
1768            "\"native\""
1769        );
1770    }
1771
1772    #[test]
1773    fn deserialize_application_type() {
1774        assert_eq!(
1775            serde_json::from_str::<ApplicationType>("\"web\"").unwrap(),
1776            ApplicationType::Web
1777        );
1778        assert_eq!(
1779            serde_json::from_str::<ApplicationType>("\"native\"").unwrap(),
1780            ApplicationType::Native
1781        );
1782    }
1783
1784    #[test]
1785    fn serialize_subject_type() {
1786        assert_eq!(
1787            serde_json::to_string(&SubjectType::Public).unwrap(),
1788            "\"public\""
1789        );
1790        assert_eq!(
1791            serde_json::to_string(&SubjectType::Pairwise).unwrap(),
1792            "\"pairwise\""
1793        );
1794    }
1795
1796    #[test]
1797    fn deserialize_subject_type() {
1798        assert_eq!(
1799            serde_json::from_str::<SubjectType>("\"public\"").unwrap(),
1800            SubjectType::Public
1801        );
1802        assert_eq!(
1803            serde_json::from_str::<SubjectType>("\"pairwise\"").unwrap(),
1804            SubjectType::Pairwise
1805        );
1806    }
1807
1808    #[test]
1809    fn serialize_claim_type() {
1810        assert_eq!(
1811            serde_json::to_string(&ClaimType::Normal).unwrap(),
1812            "\"normal\""
1813        );
1814        assert_eq!(
1815            serde_json::to_string(&ClaimType::Aggregated).unwrap(),
1816            "\"aggregated\""
1817        );
1818        assert_eq!(
1819            serde_json::to_string(&ClaimType::Distributed).unwrap(),
1820            "\"distributed\""
1821        );
1822    }
1823
1824    #[test]
1825    fn deserialize_claim_type() {
1826        assert_eq!(
1827            serde_json::from_str::<ClaimType>("\"normal\"").unwrap(),
1828            ClaimType::Normal
1829        );
1830        assert_eq!(
1831            serde_json::from_str::<ClaimType>("\"aggregated\"").unwrap(),
1832            ClaimType::Aggregated
1833        );
1834        assert_eq!(
1835            serde_json::from_str::<ClaimType>("\"distributed\"").unwrap(),
1836            ClaimType::Distributed
1837        );
1838    }
1839
1840    #[test]
1841    fn deserialize_auth_method_or_token_type_type() {
1842        assert_eq!(
1843            serde_json::from_str::<AuthenticationMethodOrAccessTokenType>("\"none\"").unwrap(),
1844            AuthenticationMethodOrAccessTokenType::AuthenticationMethod(
1845                OAuthClientAuthenticationMethod::None
1846            )
1847        );
1848        assert_eq!(
1849            serde_json::from_str::<AuthenticationMethodOrAccessTokenType>("\"Bearer\"").unwrap(),
1850            AuthenticationMethodOrAccessTokenType::AccessTokenType(OAuthAccessTokenType::Bearer)
1851        );
1852        assert_eq!(
1853            serde_json::from_str::<AuthenticationMethodOrAccessTokenType>("\"unknown_value\"")
1854                .unwrap(),
1855            AuthenticationMethodOrAccessTokenType::Unknown("unknown_value".to_owned())
1856        );
1857    }
1858}