use crate::TokenType;
use crate::core::platform::MaybeSendSync;
pub(crate) fn escape_quoted(s: &str) -> std::borrow::Cow<'_, str> {
if s.contains('"') || s.contains('\\') {
std::borrow::Cow::Owned(s.replace('\\', r"\\").replace('"', r#"\""#))
} else {
std::borrow::Cow::Borrowed(s)
}
}
#[derive(Debug, Clone)]
pub enum ChallengeParam {
Quoted(&'static str, String),
Token(&'static str, String),
}
impl ChallengeParam {
#[must_use]
pub fn format(&self) -> String {
match self {
Self::Quoted(key, value) => format!(r#"{}="{}""#, key, escape_quoted(value)),
Self::Token(key, value) => format!("{}={}", key, value),
}
}
}
#[derive(Debug, Clone)]
pub enum TokenValidationError {
Client(TokenErrorCode),
Server(http::StatusCode),
}
impl TokenValidationError {
#[must_use]
pub fn suggested_status(&self) -> http::StatusCode {
match self {
Self::Client(code) => code.suggested_status(),
Self::Server(status) => *status,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum TokenErrorCode {
InvalidRequest,
InvalidToken,
InsufficientScope,
InvalidDPoPProof,
UseDPoPNonce,
InsufficientUserAuthentication,
}
impl TokenErrorCode {
#[must_use]
pub fn as_str(&self) -> &'static str {
match self {
Self::InvalidRequest => "invalid_request",
Self::InvalidToken => "invalid_token",
Self::InsufficientScope => "insufficient_scope",
Self::InvalidDPoPProof => "invalid_dpop_proof",
Self::UseDPoPNonce => "use_dpop_nonce",
Self::InsufficientUserAuthentication => "insufficient_user_authentication",
}
}
#[must_use]
pub fn suggested_status(&self) -> http::StatusCode {
match self {
Self::InvalidRequest => http::StatusCode::BAD_REQUEST,
Self::InvalidToken
| Self::InvalidDPoPProof
| Self::UseDPoPNonce
| Self::InsufficientUserAuthentication => http::StatusCode::UNAUTHORIZED,
Self::InsufficientScope => http::StatusCode::FORBIDDEN,
}
}
}
#[derive(Debug, Clone, Default)]
pub struct InsufficientScope;
impl ToRfc6750Error for InsufficientScope {
fn attempted_scheme(&self) -> Option<TokenType> {
None
}
fn token_error(&self) -> TokenValidationError {
TokenValidationError::Client(TokenErrorCode::InsufficientScope)
}
fn error_description(&self) -> Option<String> {
Some("The access token has insufficient scope for the requested resource".to_string())
}
}
#[derive(Debug, Clone, Default)]
pub struct InsufficientUserAuthentication {
pub acr_values: Option<String>,
pub max_age: Option<u64>,
}
impl ToRfc6750Error for InsufficientUserAuthentication {
fn attempted_scheme(&self) -> Option<TokenType> {
None
}
fn token_error(&self) -> TokenValidationError {
TokenValidationError::Client(TokenErrorCode::InsufficientUserAuthentication)
}
fn error_description(&self) -> Option<String> {
Some("A higher authentication level is required to access this resource".to_string())
}
fn extra_params(&self) -> Vec<ChallengeParam> {
let mut params = Vec::new();
if let Some(acr) = &self.acr_values {
params.push(ChallengeParam::Quoted("acr_values", acr.clone()));
}
if let Some(max_age) = self.max_age {
params.push(ChallengeParam::Token("max_age", max_age.to_string()));
}
params
}
}
pub trait ToRfc6750Error: MaybeSendSync {
fn attempted_scheme(&self) -> Option<TokenType>;
fn token_error(&self) -> TokenValidationError;
fn error_description(&self) -> Option<String>;
fn extra_params(&self) -> Vec<ChallengeParam> {
Vec::new()
}
}
impl ToRfc6750Error for crate::core::jwt::validator::JwtValidationError {
fn attempted_scheme(&self) -> Option<TokenType> {
None
}
fn token_error(&self) -> TokenValidationError {
TokenValidationError::Client(TokenErrorCode::InvalidToken)
}
fn error_description(&self) -> Option<String> {
use crate::core::jwt::validator::JwtValidationError as E;
match self {
E::Parse { .. } => Some("The access token is malformed".to_string()),
E::Signature { .. } => Some("The access token signature is invalid".to_string()),
E::UnsignedToken => Some("The access token is unsigned".to_string()),
E::DisallowedAlgorithm { .. } => {
Some("The access token uses an unsupported signature algorithm".to_string())
}
E::UnrecognizedCriticalHeader { .. } => Some(
"The access token contains unrecognized critical header parameters".to_string(),
),
E::Expired { .. } => Some("The access token expired".to_string()),
E::NotYetValid { .. } => Some("The access token is not yet valid".to_string()),
E::IssuedInFuture { .. } => {
Some("The access token was issued in the future".to_string())
}
E::TokenTooOld { .. } => Some("The access token is too old".to_string()),
E::InvalidTokenType { .. } => Some("The access token type is invalid".to_string()),
E::ClaimMismatch { claim, .. } => {
Some(format!("The access token '{claim}' claim is invalid"))
}
E::RequiredClaimMissing { claim } => Some(format!(
"The access token is missing the required '{claim}' claim"
)),
E::JtiNotUnique => {
Some("The access token 'jti' claim value was previously seen".to_string())
}
E::JtiCheck { .. } => None,
E::ExtraClaims { .. } => {
Some("The access token does not contain the required claims".to_string())
}
}
}
}