You've already forked authentication-service
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:
@ -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);
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user