Skip to content

Commit

Permalink
Improve error handling for Error::ClaimValidation (#131)
Browse files Browse the repository at this point in the history
  • Loading branch information
jpramosi authored Aug 28, 2024
1 parent fb2a0f3 commit 77b5f5c
Show file tree
Hide file tree
Showing 2 changed files with 94 additions and 37 deletions.
84 changes: 48 additions & 36 deletions src/claims.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#![cfg_attr(docsrs, doc(cfg(feature = "std")))]

use crate::errors::Error;
use crate::errors::{ClaimValidationError, Error};
use core::convert::TryFrom;
use serde_json::Value;
use std::collections::HashMap;
Expand Down Expand Up @@ -325,79 +325,91 @@ impl ClaimsValidationRules {
/// separately.
pub fn validate_claims(&self, claims: &Claims) -> Result<(), Error> {
if self.validate_currently_valid {
match (claims.list_of.get("iat"), claims.list_of.get("nbf")) {
(Some(iat), Some(nbf)) => match (iat.as_str(), nbf.as_str()) {
(Some(iat), Some(nbf)) => {
let iat = OffsetDateTime::parse(iat, &Rfc3339)
.map_err(|_| Error::ClaimValidation)?;
let nbf = OffsetDateTime::parse(nbf, &Rfc3339)
.map_err(|_| Error::ClaimValidation)?;
let current_time = OffsetDateTime::now_utc();

if current_time < nbf || current_time < iat {
return Err(Error::ClaimValidation);
}
let current_time = OffsetDateTime::now_utc();

if let Some(nbf) = claims.list_of.get("nbf") {
if let Some(nbf) = nbf.as_str() {
let nbf = OffsetDateTime::parse(nbf, &Rfc3339)
.map_err(|_| Error::ClaimValidation(ClaimValidationError::ParseNbf))?;
if current_time < nbf {
return Err(Error::ClaimValidation(ClaimValidationError::Nbf));
}
_ => return Err(Error::ClaimValidation),
},
_ => return Err(Error::ClaimValidation),
} else {
return Err(Error::ClaimValidation(ClaimValidationError::NoStrNbf));
}
} else {
return Err(Error::ClaimValidation(ClaimValidationError::NoNbf));
}

if let Some(iat) = claims.list_of.get("iat") {
if let Some(iat) = iat.as_str() {
let iat = OffsetDateTime::parse(iat, &Rfc3339)
.map_err(|_| Error::ClaimValidation(ClaimValidationError::ParseIat))?;
if current_time < iat {
return Err(Error::ClaimValidation(ClaimValidationError::Iat));
}
} else {
return Err(Error::ClaimValidation(ClaimValidationError::NoStrIat));
}
} else {
return Err(Error::ClaimValidation(ClaimValidationError::NoIat));
}
}

if let Some(exp) = claims.list_of.get("exp") {
if let Some(exp) = exp.as_str() {
let exp =
OffsetDateTime::parse(exp, &Rfc3339).map_err(|_| Error::ClaimValidation)?;
let exp = OffsetDateTime::parse(exp, &Rfc3339)
.map_err(|_| Error::ClaimValidation(ClaimValidationError::ParseExp))?;
let current_time = OffsetDateTime::now_utc();

if current_time > exp {
return Err(Error::ClaimValidation);
return Err(Error::ClaimValidation(ClaimValidationError::Exp));
}
} else {
return Err(Error::ClaimValidation);
return Err(Error::ClaimValidation(ClaimValidationError::NoStrExp));
}
} else if !self.allow_non_expiring {
// We didn't explicitly allow non-expiring tokens so we expect `exp` claim.
return Err(Error::ClaimValidation);
return Err(Error::ClaimValidation(ClaimValidationError::NoExp));
}

if let Some(expected_issuer) = &self.validate_issuer {
if let Some(actual_issuer) = claims.list_of.get("iss") {
if expected_issuer != actual_issuer {
return Err(Error::ClaimValidation);
return Err(Error::ClaimValidation(ClaimValidationError::Iss));
}
} else {
return Err(Error::ClaimValidation);
return Err(Error::ClaimValidation(ClaimValidationError::NoIss));
}
}

if let Some(expected_subject) = &self.validate_subject {
if let Some(actual_subject) = claims.list_of.get("sub") {
if expected_subject != actual_subject {
return Err(Error::ClaimValidation);
return Err(Error::ClaimValidation(ClaimValidationError::Sub));
}
} else {
return Err(Error::ClaimValidation);
return Err(Error::ClaimValidation(ClaimValidationError::NoSub));
}
}

if let Some(expected_audience) = &self.validate_audience {
if let Some(actual_audience) = claims.list_of.get("aud") {
if expected_audience != actual_audience {
return Err(Error::ClaimValidation);
return Err(Error::ClaimValidation(ClaimValidationError::Aud));
}
} else {
return Err(Error::ClaimValidation);
return Err(Error::ClaimValidation(ClaimValidationError::NoAud));
}
}

if let Some(expected_token_identifier) = &self.validate_token_identifier {
if let Some(actual_token_identifier) = claims.list_of.get("jti") {
if expected_token_identifier != actual_token_identifier {
return Err(Error::ClaimValidation);
return Err(Error::ClaimValidation(ClaimValidationError::Jti));
}
} else {
return Err(Error::ClaimValidation);
return Err(Error::ClaimValidation(ClaimValidationError::NoJti));
}
}

Expand Down Expand Up @@ -623,7 +635,7 @@ mod test {
claims_validation
.validate_claims(&outdated_claims)
.unwrap_err(),
Error::ClaimValidation
Error::ClaimValidation(ClaimValidationError::Exp)
);
outdated_claims.non_expiring();
let mut claims_validation_allow_expiry = claims_validation.clone();
Expand Down Expand Up @@ -657,7 +669,7 @@ mod test {
claims_validation
.validate_claims(&future_claims)
.unwrap_err(),
Error::ClaimValidation
Error::ClaimValidation(ClaimValidationError::Iat)
);
future_claims.issued_at(old_iat.as_str().unwrap()).unwrap();
assert!(claims_validation.validate_claims(&future_claims).is_ok());
Expand All @@ -670,7 +682,7 @@ mod test {
claims_validation
.validate_claims(&future_claims)
.unwrap_err(),
Error::ClaimValidation
Error::ClaimValidation(ClaimValidationError::Nbf)
);
future_claims.not_before(old_nbf.as_str().unwrap()).unwrap();
assert!(claims_validation.validate_claims(&future_claims).is_ok());
Expand All @@ -682,7 +694,7 @@ mod test {
claims_validation
.validate_claims(&incomplete_claims)
.unwrap_err(),
Error::ClaimValidation
Error::ClaimValidation(ClaimValidationError::NoIat)
);

let mut incomplete_claims = claims.clone();
Expand All @@ -691,7 +703,7 @@ mod test {
claims_validation
.validate_claims(&incomplete_claims)
.unwrap_err(),
Error::ClaimValidation
Error::ClaimValidation(ClaimValidationError::NoExp)
);

let mut incomplete_claims = claims;
Expand All @@ -700,7 +712,7 @@ mod test {
claims_validation
.validate_claims(&incomplete_claims)
.unwrap_err(),
Error::ClaimValidation
Error::ClaimValidation(ClaimValidationError::NoNbf)
);
}

Expand Down Expand Up @@ -773,7 +785,7 @@ mod test {
// Expired
assert_eq!(
claims_validation.validate_claims(&claims).unwrap_err(),
Error::ClaimValidation
Error::ClaimValidation(ClaimValidationError::Exp)
);
}
}
47 changes: 46 additions & 1 deletion src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ pub enum Error {
/// Error for attempting to create an invalid claim.
InvalidClaim,
/// Claim validation error. See [`crate::claims::ClaimsValidationRules::validate_claims`].
ClaimValidation,
ClaimValidation(ClaimValidationError),
/// Error for attempting to parse a Claim but found invalid UTF-8 sequence.
ClaimInvalidUtf8,
/// Error for attempting to parse a Claim but found invalid JSON sequence.
Expand All @@ -39,6 +39,51 @@ pub enum Error {
FooterParsing,
}

#[derive(Debug, PartialEq, Eq)]
/// Errors for claim validation operations.
pub enum ClaimValidationError {
/// `Audience` claim doesn't match.
Aud,
/// `Expiration` claim expired.
Exp,
/// `Issued at` claim has date set in the future.
Iat,
/// `Issuer` claim doesn't match.
Iss,
/// `Token Identifier` doesn't match.
Jti,
/// `Not before` claim date not reached.
Nbf,
/// `Subject` claim doesn't match.
Sub,
/// No `Audience` claim was set.
NoAud,
/// No `Expiration` claim was set.
NoExp,
/// No `Issued at` claim was set.
NoIat,
/// No `Issuer` claim was set.
NoIss,
/// No `Token Identifier` claim was set.
NoJti,
/// No `Not before` claim was set.
NoNbf,
/// No `Subject` claim was set.
NoSub,
/// Claim `Expiration` is no string.
NoStrExp,
/// Claim `Issued at` is no string.
NoStrIat,
/// Claim `Not before` is no string.
NoStrNbf,
/// Error during parsing of `Expiration` claim.
ParseExp,
/// Error during parsing of `Issued at` claim.
ParseIat,
/// Error during parsing of `Not before` claim.
ParseNbf,
}

#[cfg(feature = "std")]
impl std::error::Error for Error {}

Expand Down

0 comments on commit 77b5f5c

Please sign in to comment.