1
0
mirror of https://github.com/matrix-org/matrix-authentication-service.git synced 2025-07-28 11:02:02 +03:00

Make sure all types of oauth2-types are documented

This commit is contained in:
Kévin Commaille
2022-11-30 12:42:13 +01:00
committed by Quentin Gliech
parent c02f59bbaf
commit 66055b044e
9 changed files with 152 additions and 3 deletions

View File

@ -12,6 +12,8 @@
// See the License for the specific language governing permissions and
// limitations under the License.
//! Error types returned by an authorization server.
use std::borrow::Cow;
use parse_display::{Display, FromStr};
@ -56,6 +58,7 @@ impl From<ClientErrorCode> for ClientError {
}
}
/// Client error codes defined in OAuth2.0, OpenID Connect and their extensions.
#[derive(Debug, Clone, PartialEq, Eq, Display, FromStr, SerializeDisplay, DeserializeFromStr)]
#[display(style = "snake_case")]
pub enum ClientErrorCode {

View File

@ -12,8 +12,21 @@
// See the License for the specific language governing permissions and
// limitations under the License.
//! [OAuth 2.0] and [OpenID Connect] types.
//!
//! This is part of the [Matrix Authentication Service] project.
//!
//! [OAuth 2.0]: https://oauth.net/2/
//! [OpenID Connect]: https://openid.net/connect/
//! [Matrix Authentication Service]: https://github.com/matrix-org/matrix-authentication-service
#![forbid(unsafe_code)]
#![deny(clippy::all, clippy::str_to_string, rustdoc::broken_intra_doc_links)]
#![deny(
clippy::all,
clippy::str_to_string,
rustdoc::broken_intra_doc_links,
missing_docs
)]
#![warn(clippy::pedantic)]
#![allow(clippy::module_name_repetitions)]
@ -26,6 +39,7 @@ pub mod response_type;
pub mod scope;
pub mod webfinger;
/// Traits intended for blanket imports.
pub mod prelude {
pub use crate::pkce::CodeChallengeMethodExt;
}

View File

@ -12,6 +12,10 @@
// See the License for the specific language governing permissions and
// limitations under the License.
//! Types to interact with the [OpenID Connect] specification.
//!
//! [OpenID Connect]: https://openid.net/connect/
use std::{ops::Deref, str::FromStr};
use language_tags::LanguageTag;
@ -107,43 +111,63 @@ impl From<OAuthAccessTokenType> for AuthenticationMethodOrAccessTokenType {
}
}
/// The kind of an application.
#[derive(
SerializeDisplay, DeserializeFromStr, Clone, Copy, PartialEq, Eq, Hash, Debug, Display, FromStr,
)]
#[display(style = "lowercase")]
pub enum ApplicationType {
/// A web application.
Web,
/// A native application.
Native,
}
/// Subject Identifier types.
///
/// A Subject Identifier is a locally unique and never reassigned identifier within the Issuer for the End-User, which is intended to be consumed by the Client.
#[derive(
SerializeDisplay, DeserializeFromStr, Clone, Copy, PartialEq, Eq, Hash, Debug, Display, FromStr,
)]
#[display(style = "lowercase")]
pub enum SubjectType {
/// This provides the same `sub` (subject) value to all Clients.
Public,
/// This provides a different `sub` value to each Client, so as not to enable Clients to correlate the End-User's activities without permission.
Pairwise,
}
/// Claim types.
#[derive(
SerializeDisplay, DeserializeFromStr, Clone, Copy, PartialEq, Eq, Hash, Debug, Display, FromStr,
)]
#[display(style = "lowercase")]
pub enum ClaimType {
/// Claims that are directly asserted by the OpenID Provider.
Normal,
/// Claims that are asserted by a Claims Provider other than the OpenID Provider but are returned by OpenID Provider.
Aggregated,
/// Claims that are asserted by a Claims Provider other than the OpenID Provider but are returned as references by the OpenID Provider.
Distributed,
}
/// The default value of `response_modes_supported` if it is not set.
pub static DEFAULT_RESPONSE_MODES_SUPPORTED: &[ResponseMode] =
&[ResponseMode::Query, ResponseMode::Fragment];
/// The default value of `grant_types_supported` if it is not set.
pub static DEFAULT_GRANT_TYPES_SUPPORTED: &[GrantType] =
&[GrantType::AuthorizationCode, GrantType::Implicit];
/// The default value of `token_endpoint_auth_methods_supported` if it is not set.
pub static DEFAULT_AUTH_METHODS_SUPPORTED: &[OAuthClientAuthenticationMethod] =
&[OAuthClientAuthenticationMethod::ClientSecretBasic];
/// The default value of `claim_types_supported` if it is not set.
pub static DEFAULT_CLAIM_TYPES_SUPPORTED: &[ClaimType] = &[ClaimType::Normal];
/// Authorization server metadata, as described by the [IANA registry].

View File

@ -12,6 +12,10 @@
// See the License for the specific language governing permissions and
// limitations under the License.
//! Types for the [Proof Key for Code Exchange].
//!
//! [Proof Key for Code Exchange]: https://www.rfc-editor.org/rfc/rfc7636
use std::borrow::Cow;
use data_encoding::BASE64URL_NOPAD;
@ -20,20 +24,26 @@ use serde::{Deserialize, Serialize};
use sha2::{Digest, Sha256};
use thiserror::Error;
/// Errors that can occur when verifying a code challenge.
#[derive(Debug, Error, PartialEq, Eq)]
pub enum CodeChallengeError {
/// The code verifier should be at least 43 characters long.
#[error("code_verifier should be at least 43 characters long")]
TooShort,
/// The code verifier should be at most 128 characters long.
#[error("code_verifier should be at most 128 characters long")]
TooLong,
/// The code verifier contains invalid characters.
#[error("code_verifier contains invalid characters")]
InvalidCharacters,
/// The challenge verification failed.
#[error("challenge verification failed")]
VerificationFailed,
/// The challenge method is unsupported.
#[error("unknown challenge method")]
UnknownChallengeMethod,
}
@ -57,6 +67,7 @@ fn validate_verifier(verifier: &str) -> Result<(), CodeChallengeError> {
Ok(())
}
/// Helper trait to compute and verify code challenges.
pub trait CodeChallengeMethodExt {
/// Compute the challenge for a given verifier
///
@ -105,14 +116,20 @@ impl CodeChallengeMethodExt for PkceCodeChallengeMethod {
}
}
/// The code challenge data added to an authorization request.
#[derive(Clone, Serialize, Deserialize)]
pub struct AuthorizationRequest {
/// The code challenge method.
pub code_challenge_method: PkceCodeChallengeMethod,
/// The code challenge computed from the verifier and the method.
pub code_challenge: String,
}
/// The code challenge data added to a token request.
#[derive(Clone, Serialize, Deserialize)]
pub struct TokenRequest {
/// The code challenge verifier.
pub code_challenge_verifier: String,
}

View File

@ -12,6 +12,10 @@
// See the License for the specific language governing permissions and
// limitations under the License.
//! Types for [Dynamic Client Registration].
//!
//! [Dynamic Client Registration]: https://openid.net/specs/openid-connect-registration-1_0.html
use std::{collections::HashMap, ops::Deref};
use chrono::{DateTime, Duration, Utc};
@ -35,18 +39,24 @@ use crate::{
mod client_metadata_serde;
use client_metadata_serde::ClientMetadataSerdeHelper;
/// The default value of `response_types` if it is not set.
pub const DEFAULT_RESPONSE_TYPES: [OAuthAuthorizationEndpointResponseType; 1] =
[OAuthAuthorizationEndpointResponseType::Code];
/// The default value of `grant_types` if it is not set.
pub const DEFAULT_GRANT_TYPES: &[GrantType] = &[GrantType::AuthorizationCode];
/// The default value of `application_type` if it is not set.
pub const DEFAULT_APPLICATION_TYPE: ApplicationType = ApplicationType::Web;
/// The default value of `token_endpoint_auth_method` if it is not set.
pub const DEFAULT_TOKEN_AUTH_METHOD: &OAuthClientAuthenticationMethod =
&OAuthClientAuthenticationMethod::ClientSecretBasic;
/// The default value of `id_token_signed_response_alg` if it is not set.
pub const DEFAULT_SIGNING_ALGORITHM: &JsonWebSignatureAlg = &JsonWebSignatureAlg::Rs256;
/// The default value of `id_token_encrypted_response_enc` if it is not set.
pub const DEFAULT_ENCRYPTION_ENC_ALGORITHM: &JsonWebEncryptionEnc =
&JsonWebEncryptionEnc::A128CbcHs256;
@ -841,19 +851,26 @@ pub enum ClientMetadataVerificationError {
MissingEncryptionAlg(&'static str),
}
/// The issuer response to dynamic client registration.
#[serde_as]
#[skip_serializing_none]
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq)]
pub struct ClientRegistrationResponse {
/// A unique client identifier.
pub client_id: String,
/// A client secret, if the `token_endpoint_auth_method` requires one.
#[serde(default)]
pub client_secret: Option<String>,
/// Time at which the Client Identifier was issued.
#[serde(default)]
#[serde_as(as = "Option<TimestampSeconds<i64>>")]
pub client_id_issued_at: Option<DateTime<Utc>>,
/// Time at which the client_secret will expire or 0 if it will not expire.
///
/// Required if `client_secret` is issued.
#[serde(default)]
#[serde_as(as = "Option<TimestampSeconds<i64>>")]
pub client_secret_expires_at: Option<DateTime<Utc>>,

View File

@ -12,6 +12,10 @@
// See the License for the specific language governing permissions and
// limitations under the License.
//! Requests and response types to interact with the [OAuth 2.0] specification.
//!
//! [OAuth 2.0]: https://oauth.net/2/
use std::{collections::HashSet, fmt, hash::Hash, num::NonZeroU32};
use chrono::{DateTime, Duration, Utc};
@ -323,7 +327,10 @@ impl fmt::Debug for AuthorizationRequest {
#[skip_serializing_none]
#[derive(Serialize, Deserialize, Default, Clone)]
pub struct AuthorizationResponse<R> {
/// The authorization code generated by the authorization server.
pub code: Option<String>,
/// Other fields of the response.
#[serde(flatten)]
pub response: R,
}
@ -345,6 +352,7 @@ pub struct DeviceAuthorizationRequest {
pub scope: Option<Scope>,
}
/// The default value of the `interval` between polling requests, if it is not set.
pub const DEFAULT_DEVICE_AUTHORIZATION_INTERVAL_SECONDS: i64 = 5;
/// A successful response from the [Device Authorization Endpoint].
@ -384,7 +392,7 @@ pub struct DeviceAuthorizationResponse {
}
impl DeviceAuthorizationResponse {
///The minimum amount of time in seconds that the client should wait
/// The minimum amount of time in seconds that the client should wait
/// between polling requests to the token endpoint.
///
/// Defaults to [`DEFAULT_DEVICE_AUTHORIZATION_INTERVAL_SECONDS`].
@ -535,11 +543,20 @@ pub enum GrantType {
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
#[serde(tag = "grant_type", rename_all = "snake_case")]
pub enum AccessTokenRequest {
/// A request in the Authorization Code flow.
AuthorizationCode(AuthorizationCodeGrant),
/// A request to refresh an access token.
RefreshToken(RefreshTokenGrant),
/// A request in the Client Credentials flow.
ClientCredentials(ClientCredentialsGrant),
/// A request in the Device Code flow.
#[serde(rename = "urn:ietf:params:oauth:grant-type:device_code")]
DeviceCode(DeviceCodeGrant),
/// An unsupported request.
#[serde(skip, other)]
Unsupported,
}

View File

@ -12,6 +12,10 @@
// See the License for the specific language governing permissions and
// limitations under the License.
//! [Response types] in the OpenID Connect specification.
//!
//! [Response types]: https://openid.net/specs/openid-connect-core-1_0.html#Authentication
#![allow(clippy::module_name_repetitions)]
use std::{collections::BTreeSet, fmt, iter::FromIterator, str::FromStr};

View File

@ -12,6 +12,10 @@
// See the License for the specific language governing permissions and
// limitations under the License.
//! Types to define an [access token's scope].
//!
//! [access token's scope]: https://www.rfc-editor.org/rfc/rfc6749#section-3.3
#![allow(clippy::module_name_repetitions)]
use std::{borrow::Cow, collections::BTreeSet, iter::FromIterator, ops::Deref, str::FromStr};
@ -20,10 +24,12 @@ use itertools::Itertools;
use serde::{Deserialize, Serialize};
use thiserror::Error;
/// The error type returned when a scope is invalid.
#[derive(Debug, Error, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[error("Invalid scope format")]
pub struct InvalidScope;
/// A scope token or scope value.
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct ScopeToken(Cow<'static, str>);
@ -36,11 +42,34 @@ impl ScopeToken {
}
}
/// `openid`.
///
/// Must be included in OpenID Connect requests.
pub const OPENID: ScopeToken = ScopeToken::from_static("openid");
/// `profile`.
///
/// Requests access to the End-User's default profile Claims.
pub const PROFILE: ScopeToken = ScopeToken::from_static("profile");
/// `email`.
///
/// Requests access to the `email` and `email_verified` Claims.
pub const EMAIL: ScopeToken = ScopeToken::from_static("email");
/// `address`.
///
/// Requests access to the `address` Claim.
pub const ADDRESS: ScopeToken = ScopeToken::from_static("address");
/// `phone`.
///
/// Requests access to the `phone_number` and `phone_number_verified` Claims.
pub const PHONE: ScopeToken = ScopeToken::from_static("phone");
/// `offline_access`.
///
/// Requests that an OAuth 2.0 Refresh Token be issued that can be used to obtain an Access Token that grants access to the End-User's Userinfo Endpoint even when the End-User is not present (not logged in).
pub const OFFLINE_ACCESS: ScopeToken = ScopeToken::from_static("offline_access");
// As per RFC6749 appendix A:
@ -81,6 +110,7 @@ impl ToString for ScopeToken {
}
}
/// A scope.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Scope(BTreeSet<ScopeToken>);
@ -108,17 +138,20 @@ impl FromStr for Scope {
}
impl Scope {
/// Whether this `Scope` is empty.
#[must_use]
pub fn is_empty(&self) -> bool {
// This should never be the case?
self.0.is_empty()
}
/// The number of tokens in the `Scope`.
#[must_use]
pub fn len(&self) -> usize {
self.0.len()
}
/// Whether this `Scope` contains the given value.
#[must_use]
pub fn contains(&self, token: &str) -> bool {
ScopeToken::from_str(token)
@ -126,6 +159,9 @@ impl Scope {
.unwrap_or(false)
}
/// Inserts the given token in this `Scope`.
///
/// Returns whether the token was newly inserted.
pub fn insert(&mut self, value: ScopeToken) -> bool {
self.0.insert(value)
}

View File

@ -12,16 +12,25 @@
// See the License for the specific language governing permissions and
// limitations under the License.
//! Types for provider discovery using [Webfinger].
//!
//! [Webfinger]: https://www.rfc-editor.org/rfc/rfc7033
use serde::{Deserialize, Serialize};
use url::Url;
/// The response of the Webfinger endpoint.
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq)]
pub struct WebFingerResponse {
/// A URI that identifies the entity described by the response.
subject: String,
/// Links that describe the subject.
links: Vec<WebFingerLink>,
}
impl WebFingerResponse {
/// Creates a new `WebFingerResponse` with the given subject.
#[must_use]
pub const fn new(subject: String) -> Self {
Self {
@ -30,26 +39,34 @@ impl WebFingerResponse {
}
}
/// Adds the given link to this `WebFingerResponse`.
#[must_use]
pub fn with_link(mut self, link: WebFingerLink) -> Self {
self.links.push(link);
self
}
/// Adds the given issuer to this `WebFingerResponse`.
#[must_use]
pub fn with_issuer(self, issuer: Url) -> Self {
self.with_link(WebFingerLink::issuer(issuer))
}
}
/// A link in a Webfinger response.
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq)]
#[serde(tag = "rel")]
pub enum WebFingerLink {
/// An OpenID Connect issuer.
#[serde(rename = "http://openid.net/specs/connect/1.0/issuer")]
OidcIssuer { href: Url },
OidcIssuer {
/// The URL of the issuer.
href: Url,
},
}
impl WebFingerLink {
/// Creates a new `WebFingerLink` for an OpenID Connect issuer.
#[must_use]
pub const fn issuer(href: Url) -> Self {
Self::OidcIssuer { href }