From bbcd03fa73aaaee8ff58b96f75d313c1f3dfb948 Mon Sep 17 00:00:00 2001 From: Quentin Gliech Date: Thu, 7 Apr 2022 10:08:10 +0200 Subject: [PATCH] Simplify OAuth2 error types --- crates/oauth2-types/src/errors.rs | 401 +++++++----------------------- crates/templates/src/context.rs | 11 - 2 files changed, 86 insertions(+), 326 deletions(-) diff --git a/crates/oauth2-types/src/errors.rs b/crates/oauth2-types/src/errors.rs index 1de8ed5c..195f4242 100644 --- a/crates/oauth2-types/src/errors.rs +++ b/crates/oauth2-types/src/errors.rs @@ -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 { - 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 { - 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 { - fn error(&self) -> &'static str { - self.as_ref().error() - } - fn description(&self) -> Option { - self.as_ref().description() - } - - fn uri(&self) -> Option { - self.as_ref().uri() - } -} - -#[derive(Debug)] -pub struct ErrorResponse(Box); - -impl From> for ErrorResponse { - fn from(b: Box) -> Self { - Self(b) - } -} - -impl OAuth2Error for ErrorResponse { - fn error(&self) -> &'static str { - self.0.error() - } - - fn description(&self) -> Option { - self.0.description() - } - - fn uri(&self) -> Option { - self.0.uri() - } -} - -impl Serialize for ErrorResponse { - fn serialize(&self, serializer: S) -> Result - 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 { - 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); - } -} diff --git a/crates/templates/src/context.rs b/crates/templates/src/context.rs index 2b7cf71f..fc4a71a6 100644 --- a/crates/templates/src/context.rs +++ b/crates/templates/src/context.rs @@ -19,7 +19,6 @@ use mas_data_model::{ errors::ErroredForm, AuthorizationGrant, BrowserSession, StorageBackend, User, UserEmail, }; -use oauth2_types::errors::OAuth2Error; use serde::{ser::SerializeStruct, Serialize}; use url::Url; @@ -599,13 +598,3 @@ impl ErrorContext { self } } - -impl From> for ErrorContext { - fn from(err: Box) -> Self { - let mut ctx = ErrorContext::new().with_code(err.error()); - if let Some(desc) = err.description() { - ctx = ctx.with_description(desc); - } - ctx - } -}