use serde::Deserialize;
use std::{error, fmt};
#[derive(Deserialize, Debug, PartialEq, Eq)]
pub struct OAuth2Error {
pub error: OAuth2ErrorCode,
pub error_description: Option<String>,
pub error_uri: Option<String>,
}
impl fmt::Display for OAuth2Error {
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
write!(f, "{:?}", self.error)?;
if let Some(ref description) = self.error_description {
write!(f, ": {}", description)?;
}
if let Some(ref uri) = self.error_uri {
write!(f, " ({})", uri)?;
}
Ok(())
}
}
impl error::Error for OAuth2Error {
fn description(&self) -> &str {
"OAuth 2.0 API error"
}
}
#[derive(Deserialize, Debug, Clone, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum OAuth2ErrorCode {
InvalidRequest,
InvalidClient,
InvalidGrant,
UnauthorizedClient,
UnsupportedGrantType,
InvalidScope,
Unrecognized(String),
}
impl<'a> From<&'a str> for OAuth2ErrorCode {
fn from(s: &str) -> OAuth2ErrorCode {
match s {
"invalid_request" => OAuth2ErrorCode::InvalidRequest,
"invalid_client" => OAuth2ErrorCode::InvalidClient,
"invalid_grant" => OAuth2ErrorCode::InvalidGrant,
"unauthorized_client" => OAuth2ErrorCode::UnauthorizedClient,
"unsupported_grant_type" => OAuth2ErrorCode::UnsupportedGrantType,
"invalid_scope" => OAuth2ErrorCode::InvalidScope,
s => OAuth2ErrorCode::Unrecognized(s.to_owned()),
}
}
}
#[derive(Debug)]
pub enum ClientError {
Io(std::io::Error),
Url(url::ParseError),
Reqwest(reqwest::Error),
Json(serde_json::Error),
OAuth2(OAuth2Error),
#[cfg(feature = "uma2")]
Uma2(Uma2Error),
}
impl fmt::Display for ClientError {
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
match *self {
ClientError::Io(ref err) => write!(f, "{}", err),
ClientError::Url(ref err) => write!(f, "{}", err),
ClientError::Reqwest(ref err) => write!(f, "{}", err),
ClientError::Json(ref err) => write!(f, "{}", err),
ClientError::OAuth2(ref err) => write!(f, "{}", err),
#[cfg(feature = "uma2")]
ClientError::Uma2(ref err) => write!(f, "{}", err),
}
}
}
impl std::error::Error for ClientError {
fn cause(&self) -> Option<&dyn std::error::Error> {
match *self {
ClientError::Io(ref err) => Some(err),
ClientError::Url(ref err) => Some(err),
ClientError::Reqwest(ref err) => Some(err),
ClientError::Json(ref err) => Some(err),
ClientError::OAuth2(ref err) => Some(err),
#[cfg(feature = "uma2")]
ClientError::Uma2(ref err) => Some(err),
}
}
}
macro_rules! impl_from {
($v:path, $t:ty) => {
impl From<$t> for ClientError {
fn from(err: $t) -> Self {
$v(err)
}
}
};
}
impl_from!(ClientError::Io, std::io::Error);
impl_from!(ClientError::Url, url::ParseError);
impl_from!(ClientError::Reqwest, reqwest::Error);
impl_from!(ClientError::Json, serde_json::Error);
impl_from!(ClientError::OAuth2, OAuth2Error);
pub use biscuit::errors::Error as Jose;
pub use reqwest::Error as Http;
pub use serde_json::Error as Json;
use thiserror::Error;
#[cfg(feature = "uma2")]
use crate::uma2::Uma2Error;
#[derive(Debug, Error)]
pub enum Error {
#[error(transparent)]
Jose(#[from] Jose),
#[error(transparent)]
Http(#[from] Http),
#[error(transparent)]
Json(#[from] Json),
#[error(transparent)]
Decode(#[from] Decode),
#[error(transparent)]
Validation(#[from] Validation),
#[error(transparent)]
Userinfo(#[from] Userinfo),
#[error(transparent)]
Introspection(#[from] Introspection),
#[error("Url must use TLS: '{0}'")]
Insecure(::reqwest::Url),
#[error("Scope must contain Openid")]
MissingOpenidScope,
#[error("Url: Path segments is cannot-be-a-base")]
CannotBeABase,
#[error(transparent)]
ClientError(#[from] ClientError),
}
#[derive(Debug, Error)]
pub enum Decode {
#[error("Token Missing a Key Id when the key set has multiple keys")]
MissingKid,
#[error("Token wants this key id not in the key set: {0}")]
MissingKey(String),
#[error("JWK Set is empty")]
EmptySet,
#[error("No support for EC keys yet")]
UnsupportedEllipticCurve,
#[error("No support for Octet key pair yet")]
UnsupportedOctetKeyPair,
}
#[derive(Debug, Error)]
pub enum Validation {
#[error(transparent)]
Mismatch(#[from] Mismatch),
#[error(transparent)]
Missing(#[from] Missing),
#[error(transparent)]
Expired(#[from] Expiry),
}
#[derive(Debug, Error)]
pub enum Mismatch {
#[error("Client ID and Token authorized party mismatch: '{expected}', '{actual}'")]
AuthorizedParty { expected: String, actual: String },
#[error("Configured issuer and token issuer mismatch: '{expected}', '{actual}'")]
Issuer { expected: String, actual: String },
#[error("Given nonce does not match token nonce: '{expected}', '{actual}'")]
Nonce { expected: String, actual: String },
}
#[derive(Debug, Error)]
pub enum Missing {
#[error("Token missing Audience")]
Audience,
#[error("Token missing AZP")]
AuthorizedParty,
#[error("Token missing Auth Time")]
AuthTime,
#[error("Token missing Nonce")]
Nonce,
}
#[derive(Debug, Error)]
pub enum Expiry {
#[error("Token expired at: {0}")]
Expires(::chrono::DateTime<::chrono::Utc>),
#[error("Token is too old: {0}")]
MaxAge(::chrono::Duration),
#[error("Token exp is not valid UNIX timestamp: {0}")]
NotUnix(i64),
}
#[derive(Debug, Error)]
pub enum Userinfo {
#[error("Config has no userinfo url")]
NoUrl,
#[error("The UserInfo Endpoint MUST return a content-type header to indicate which format is being returned")]
MissingContentType,
#[error("Not parsable content type header: {content_type}")]
ParseContentType { content_type: String },
#[error("Wrong content type header: {content_type}. The following are accepted content types: application/json, application/jwt")]
WrongContentType { content_type: String, body: Vec<u8> },
#[error("Token and Userinfo Subjects mismatch: '{expected}', '{actual}'")]
MismatchSubject { expected: String, actual: String },
#[error(transparent)]
MissingSubject(#[from] StandardClaimsSubjectMissing),
}
#[derive(Debug, Error)]
#[error("The sub (subject) Claim MUST always be returned in the UserInfo Response")]
pub struct StandardClaimsSubjectMissing;
#[derive(Debug, Error)]
pub enum Introspection {
#[error("Config has no introspection url")]
NoUrl,
#[error("The Introspection Endpoint MUST return a content-type header to indicate which format is being returned")]
MissingContentType,
#[error("Not parsable content type header: {content_type}")]
ParseContentType { content_type: String },
#[error("Wrong content type header: {content_type}. The following are accepted content types: application/json")]
WrongContentType { content_type: String, body: Vec<u8> },
}
#[cfg(test)]
mod tests {
use serde_json::json;
use super::*;
#[test]
fn it_deserializes_error() {
let error_json = json!({
"error": "invalid_request",
"error_description": "Only resources with owner managed accessed can have policies",
});
let error: OAuth2Error = serde_json::from_value(error_json).unwrap();
assert_eq!(
error,
OAuth2Error {
error: OAuth2ErrorCode::InvalidRequest,
error_description: Some(
"Only resources with owner managed accessed can have policies".to_string()
),
error_uri: None,
}
);
}
}