1
0
mirror of https://github.com/matrix-org/matrix-authentication-service.git synced 2025-07-09 10:01:45 +03:00

Simplify OAuth2 error types

This commit is contained in:
Quentin Gliech
2022-04-07 10:08:10 +02:00
parent 54170cac6f
commit bbcd03fa73
2 changed files with 86 additions and 326 deletions

View File

@ -1,4 +1,4 @@
// Copyright 2021 The Matrix.org Foundation C.I.C.
// Copyright 2021, 2022 The Matrix.org Foundation C.I.C.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@ -12,356 +12,127 @@
// See the License for the specific language governing permissions and
// limitations under the License.
use http::status::StatusCode;
use serde::ser::{Serialize, SerializeMap};
use url::Url;
#[derive(serde::Serialize)]
pub struct ClientError {
pub error: &'static str,
pub error_description: &'static str,
}
pub trait OAuth2Error: std::fmt::Debug + Send + Sync {
/// A single ASCII error code.
///
/// Maps to the required "error" field.
fn error(&self) -> &'static str;
/// Human-readable ASCII text providing additional information, used to
/// assist the client developer in understanding the error that
/// occurred.
///
/// Maps to the optional `error_description` field.
fn description(&self) -> Option<String> {
None
}
/// A URI identifying a human-readable web page with information about the
/// error, used to provide the client developer with additional
/// information about the error.
///
/// Maps to the optional `error_uri` field.
fn uri(&self) -> Option<Url> {
None
}
/// Wraps the error with an `ErrorResponse` to help serializing.
fn into_response(self) -> ErrorResponse
where
Self: Sized + 'static,
{
ErrorResponse(Box::new(self))
}
}
pub trait OAuth2ErrorCode: OAuth2Error + 'static {
/// The HTTP status code that must be returned by this error
fn status(&self) -> StatusCode;
}
impl OAuth2Error for &Box<dyn OAuth2ErrorCode> {
fn error(&self) -> &'static str {
self.as_ref().error()
}
fn description(&self) -> Option<String> {
self.as_ref().description()
}
fn uri(&self) -> Option<Url> {
self.as_ref().uri()
}
}
#[derive(Debug)]
pub struct ErrorResponse(Box<dyn OAuth2Error>);
impl From<Box<dyn OAuth2Error>> for ErrorResponse {
fn from(b: Box<dyn OAuth2Error>) -> Self {
Self(b)
}
}
impl OAuth2Error for ErrorResponse {
fn error(&self) -> &'static str {
self.0.error()
}
fn description(&self) -> Option<String> {
self.0.description()
}
fn uri(&self) -> Option<Url> {
self.0.uri()
}
}
impl Serialize for ErrorResponse {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
let error = self.0.error();
let description = self.0.description();
let uri = self.0.uri();
// Count the number of fields to serialize
let len = {
let mut x = 1;
if description.is_some() {
x += 1;
}
if uri.is_some() {
x += 1;
}
x
};
let mut map = serializer.serialize_map(Some(len))?;
map.serialize_entry("error", error)?;
if let Some(ref description) = description {
map.serialize_entry("error_description", description)?;
impl ClientError {
#[must_use]
pub const fn new(error: &'static str, error_description: &'static str) -> Self {
Self {
error,
error_description,
}
if let Some(ref uri) = uri {
map.serialize_entry("error_uri", uri)?;
}
map.end()
}
}
macro_rules! oauth2_error_def {
($name:ident) => {
#[derive(Debug, Clone)]
pub struct $name;
};
}
macro_rules! oauth2_error_status {
($name:ident, $code:ident) => {
impl $crate::errors::OAuth2ErrorCode for $name {
fn status(&self) -> ::http::status::StatusCode {
::http::status::StatusCode::$code
}
}
};
}
macro_rules! oauth2_error_error {
($err:literal) => {
fn error(&self) -> &'static str {
$err
}
};
}
macro_rules! oauth2_error_const {
($const:ident, $err:literal, $description:expr) => {
pub const $const: ClientError = ClientError {
error: $err,
error_description: $description,
};
};
}
macro_rules! oauth2_error_description {
($description:expr) => {
fn description(&self) -> Option<String> {
Some(($description).to_string())
}
};
}
macro_rules! oauth2_error {
($name:ident, $const:ident, $err:literal => $description:expr) => {
oauth2_error_const!($const, $err, $description);
oauth2_error_def!($name);
impl $crate::errors::OAuth2Error for $name {
oauth2_error_error!($err);
oauth2_error_description!(indoc::indoc! {$description});
}
};
($name:ident, $const:ident, $err:literal) => {
oauth2_error_def!($name);
impl $crate::errors::OAuth2Error for $name {
oauth2_error_error!($err);
}
};
($name:ident, $const:ident, code: $code:ident, $err:literal => $description:expr) => {
oauth2_error!($name, $const, $err => $description);
oauth2_error_status!($name, $code);
};
($name:ident, $const:ident, code: $code:ident, $err:literal) => {
oauth2_error!($name, $const, $err);
oauth2_error_status!($name, $code);
};
}
pub mod rfc6749 {
use super::ClientError;
oauth2_error! {
InvalidRequest,
INVALID_REQUEST,
code: BAD_REQUEST,
"invalid_request" =>
"The request is missing a required parameter, includes an invalid parameter value, \
includes a parameter more than once, or is otherwise malformed."
}
pub const INVALID_REQUEST: ClientError = ClientError::new(
"invalid_request",
"The request is missing a required parameter, \
includes an invalid parameter value, \
includes a parameter more than once, \
or is otherwise malformed.",
);
oauth2_error! {
InvalidClient,
INVALID_CLIENT,
code: BAD_REQUEST,
"invalid_client" =>
"Client authentication failed."
}
pub const INVALID_CLIENT: ClientError =
ClientError::new("invalid_client", "Client authentication failed.");
oauth2_error! {
InvalidGrant,
INVALID_GRANT,
code: BAD_REQUEST,
"invalid_grant" =>
"The provided access grant is invalid, expired, or revoked."
}
pub const INVALID_GRANT: ClientError = ClientError::new(
"invalid_grant",
"The provided access grant is invalid, expired, or revoked.",
);
oauth2_error! {
UnauthorizedClient,
UNAUTHORIZED_CLIENT,
code: BAD_REQUEST,
"unauthorized_client" =>
"The client is not authorized to request an access token using this method."
}
pub const UNAUTHORIZED_CLIENT: ClientError = ClientError::new(
"unauthorized_client",
"The client is not authorized to request an access token using this method.",
);
oauth2_error! {
UnsupportedGrantType,
UNSUPPORTED_GRANT_TYPE,
code: BAD_REQUEST,
"unsupported_grant_type" =>
"The authorization grant type is not supported by the authorization server."
}
pub const UNSUPPORTED_GRANT_TYPE: ClientError = ClientError::new(
"unsupported_grant_type",
"The authorization grant type is not supported by the authorization server.",
);
oauth2_error! {
AccessDenied,
ACCESS_DENIED,
"access_denied" =>
"The resource owner or authorization server denied the request."
}
pub const ACCESS_DENIED: ClientError = ClientError::new(
"access_denied",
"The resource owner or authorization server denied the request.",
);
oauth2_error! {
UnsupportedResponseType,
UNSUPPORTED_RESPONSE_TYPE,
"unsupported_response_type" =>
"The authorization server does not support obtaining an access token using this method."
}
pub const UNSUPPORTED_RESPONSE_TYPE: ClientError = ClientError::new(
"unsupported_response_type",
"The authorization server does not support obtaining an access token using this method.",
);
oauth2_error! {
InvalidScope,
INVALID_SCOPE,
code: BAD_REQUEST,
"invalid_scope" =>
"The requested scope is invalid, unknown, or malformed."
}
pub const INVALID_SCOPE: ClientError = ClientError::new(
"invalid_scope",
"The requested scope is invalid, unknown, or malformed.",
);
oauth2_error! {
ServerError,
SERVER_ERROR,
code: INTERNAL_SERVER_ERROR,
"server_error" =>
"The authorization server encountered an unexpected \
condition that prevented it from fulfilling the request."
}
pub const SERVER_ERROR: ClientError = ClientError::new(
"server_error",
"The authorization server encountered an unexpected condition \
that prevented it from fulfilling the request.",
);
oauth2_error! {
TemporarilyUnavailable,
TEMPORARILY_UNAVAILABLE,
"temporarily_unavailable" =>
"The authorization server is currently unable to handle \
the request due to a temporary overloading or maintenance \
of the server."
}
pub const TEMPORARILY_UNAVAILABLE: ClientError = ClientError::new(
"temporarily_unavailable",
"The authorization server is currently unable to handle the request \
due to a temporary overloading or maintenance of the server.",
);
}
pub mod oidc_core {
use super::ClientError;
oauth2_error! {
InteractionRequired,
INTERACTION_REQUIRED,
"interaction_required" =>
"The Authorization Server requires End-User interaction of some form to proceed."
}
pub const INTERACTION_REQUIRED: ClientError = ClientError::new(
"interaction_required",
"The Authorization Server requires End-User interaction of some form to proceed.",
);
oauth2_error! {
LoginRequired,
LOGIN_REQUIRED,
"login_required" =>
"The Authorization Server requires End-User authentication."
}
pub const LOGIN_REQUIRED: ClientError = ClientError::new(
"login_required",
"The Authorization Server requires End-User authentication.",
);
oauth2_error! {
AccountSelectionRequired,
ACCOUNT_SELECTION_REQUIRED,
"account_selection_required"
}
pub const ACCOUNT_SELECTION_REQUIRED: ClientError = ClientError::new(
"account_selection_required",
"The End-User is REQUIRED to select a session at the Authorization Server.",
);
oauth2_error! {
ConsentRequired,
CONSENT_REQUIRED,
"consent_required"
}
pub const CONSENT_REQUIRED: ClientError = ClientError::new(
"consent_required",
"The Authorization Server requires End-User consent.",
);
oauth2_error! {
InvalidRequestUri,
INVALID_REQUEST_URI,
"invalid_request_uri" =>
"The request_uri in the Authorization Request returns an error or contains invalid data. "
}
pub const INVALID_REQUEST_URI: ClientError = ClientError::new(
"invalid_request_uri",
"The request_uri in the Authorization Request returns an error or contains invalid data. ",
);
oauth2_error! {
InvalidRequestObject,
INVALID_REQUEST_OBJECT,
"invalid_request_object" =>
"The request parameter contains an invalid Request Object."
}
pub const INVALID_REQUEST_OBJECT: ClientError = ClientError::new(
"invalid_request_object",
"The request parameter contains an invalid Request Object.",
);
oauth2_error! {
RequestNotSupported,
REQUEST_NOT_SUPPORTED,
"request_not_supported" =>
"The provider does not support use of the request parameter."
}
pub const REQUEST_NOT_SUPPORTED: ClientError = ClientError::new(
"request_not_supported",
"The provider does not support use of the request parameter.",
);
oauth2_error! {
RequestUriNotSupported,
REQUEST_URI_NOT_SUPPORTED,
"request_uri_not_supported" =>
"The provider does not support use of the request_uri parameter."
}
pub const REQUEST_URI_NOT_SUPPORTED: ClientError = ClientError::new(
"request_uri_not_supported",
"The provider does not support use of the request_uri parameter.",
);
oauth2_error! {
RegistrationNotSupported,
REGISTRATION_NOT_SUPPORTED,
"registration_not_supported" =>
"The provider does not support use of the registration parameter."
}
pub const REGISTRATION_NOT_SUPPORTED: ClientError = ClientError::new(
"registration_not_supported",
"The provider does not support use of the registration parameter.",
);
}
pub use oidc_core::*;
pub use rfc6749::*;
#[cfg(test)]
mod tests {
use serde_json::json;
use super::*;
#[test]
fn serialize_error() {
let expected = json!({
"error": "invalid_grant",
"error_description": "The provided access grant is invalid, expired, or revoked."
});
let actual = serde_json::to_value(InvalidGrant.into_response()).unwrap();
assert_eq!(expected, actual);
}
}