use crate::error::jwt_error::JWTError;
use crate::model::claim_validation_rules::ScriptClaimRule;
use crate::model::claims::JWTClaims;
use chrono::offset::Utc;
use chrono::DateTime;
use pdk_core::logger::debug;
use pdk_script::AttributesBinding;
use serde_json::Value;
use std::borrow::Cow;
use std::collections::{HashSet, LinkedList};
pub const CLAIMS_SET: &str = "claimSet";
#[derive(Clone, PartialEq)]
pub enum ClaimValidator {
Aud {
mandatory: bool,
audiences: HashSet<String>,
},
Nb {
mandatory: bool,
current_time: DateTime<Utc>,
},
Exp {
mandatory: bool,
current_time: DateTime<Utc>,
},
}
pub trait ClaimValidation {
fn validate(&self, claims: &JWTClaims) -> Result<(), JWTError>;
}
pub trait ScriptClaimValidation {
fn validate(&self, claims: &JWTClaims, handler: &dyn AttributesBinding)
-> Result<(), JWTError>;
}
impl ClaimValidation for ClaimValidator {
fn validate(&self, claims: &JWTClaims) -> Result<(), JWTError> {
debug!("JwtLib: Validating standard claim rule");
match self {
ClaimValidator::Aud {
mandatory,
audiences,
} => {
let claim: Option<Result<Vec<String>, JWTError>> = claims.audience();
match claim {
None => fail_if_mandatory(*mandatory, JWTError::ClaimAudNotPresent),
Some(extracted_auds) => extracted_auds.and_then(|mut aud_value| {
let validation = aud_value.iter_mut().any(|a| audiences.contains(a));
validation
.then_some(())
.ok_or(JWTError::ClaimAudValidationFailed)
}),
}
}
ClaimValidator::Nb {
mandatory,
current_time,
} => {
let claim = claims.not_before();
match claim {
None => fail_if_mandatory(*mandatory, JWTError::ClaimNbNotPresent),
Some(claim_not_before) => {
let valid = claim_not_before <= *current_time;
valid.then_some(()).ok_or(JWTError::ClaimNbValidationFailed)
}
}
}
ClaimValidator::Exp {
mandatory,
current_time,
} => {
let claim = claims.expiration();
match claim {
None => fail_if_mandatory(*mandatory, JWTError::ClaimExpNotPresent),
Some(claim_expiration) => {
let non_expired = claim_expiration >= *current_time;
non_expired
.then_some(())
.ok_or(JWTError::ClaimExpValidationFailed)
}
}
}
}
}
}
fn fail_if_mandatory(mandatory: bool, error: JWTError) -> Result<(), JWTError> {
if mandatory {
Err(error)
} else {
Ok(())
}
}
#[derive(Clone, Default)]
pub struct ClaimsValidator {
claims_validators: LinkedList<ClaimValidator>,
}
impl ClaimsValidator {
pub fn new(claims_validators: LinkedList<ClaimValidator>) -> Self {
ClaimsValidator { claims_validators }
}
}
impl ClaimValidation for ClaimsValidator {
fn validate(&self, claims: &JWTClaims) -> Result<(), JWTError> {
self.claims_validators
.iter()
.try_fold((), |_, validator| validator.validate(claims))
}
}
pub struct ScriptClaimValidator {
mandatory_custom_claims: Vec<ScriptClaimRule>,
non_mandatory_custom_claims: Vec<ScriptClaimRule>,
}
impl ScriptClaimValidator {
pub fn new(
mandatory_custom_claims: Vec<ScriptClaimRule>,
non_mandatory_custom_claims: Vec<ScriptClaimRule>,
) -> ScriptClaimValidator {
ScriptClaimValidator {
mandatory_custom_claims,
non_mandatory_custom_claims,
}
}
}
impl ScriptClaimValidation for ScriptClaimValidator {
fn validate(
&self,
claims: &JWTClaims,
handler: &dyn AttributesBinding,
) -> Result<(), JWTError> {
debug!("JwtLib: Validating custom script claim rule");
let claim_set: Value = claims.to_serde()?;
if self.mandatory_custom_claims.is_empty() && self.non_mandatory_custom_claims.is_empty() {
return Ok(());
}
let mandatory_validation = validate_script_rules(
&self.mandatory_custom_claims,
true,
claims,
&claim_set,
handler,
);
let non_mandatory_validation = validate_script_rules(
&self.non_mandatory_custom_claims,
false,
claims,
&claim_set,
handler,
);
mandatory_validation.and(non_mandatory_validation)
}
}
fn validate_script_rules(
rules: &[ScriptClaimRule],
mandatory: bool,
claims: &JWTClaims,
claim_set: &Value,
req_ops: &dyn AttributesBinding,
) -> Result<(), JWTError> {
let validation = rules.iter().all(|rule| {
let claim_value = claims.get_claim::<Value>(rule.claim_name());
claim_value
.as_ref()
.map(|value| value_match(value, rule, claim_set, req_ops))
.unwrap_or(!mandatory)
});
validation.then_some(()).ok_or({
if mandatory {
JWTError::MandatoryCustomClaimMismatchOrNotPresent
} else {
JWTError::NonMandatoryCustomClaimMismatch
}
})
}
fn value_match(
claim_value: &Value,
rule: &ScriptClaimRule,
claim_set: &Value,
req_ops: &dyn AttributesBinding,
) -> bool {
let mut rule = rule.evaluator();
rule.bind_vars(CLAIMS_SET, claim_set.clone());
rule.bind_attributes(req_ops);
match rule.eval() {
Err(e) => {
debug!("Error parsing PEL expression {e:?}");
false
}
Ok(result) => match result {
pdk_script::Value::Bool(val) => val,
pdk_script::Value::String(val) => as_string_matching(&val, claim_value),
_ => false,
},
}
}
fn as_string_matching(result: &str, claim_value: &Value) -> bool {
let str_value = match claim_value {
Value::String(s) => Cow::Borrowed(s.as_str()),
Value::Bool(true) => Cow::Borrowed("true"),
Value::Bool(false) => Cow::Borrowed("false"),
value => Cow::Owned(value.to_string()),
};
result.eq(&str_value)
}
#[cfg(test)]
mod tests {
use super::ClaimValidation;
use super::ScriptClaimValidation;
use crate::error::jwt_error::JWTError;
use crate::error::jwt_error::JWTErrorEnumKind;
use crate::model::claims::JWTClaims;
use mockall::mock;
use pdk_script::{Expression, HandlerAttributesBinding, Script, ScriptingEngine};
mod tests_accessors {
use super::super::{ClaimValidator, ClaimsValidator};
use std::collections::LinkedList;
impl ClaimsValidator {
pub fn claims_validators(&self) -> &LinkedList<ClaimValidator> {
&self.claims_validators
}
}
}
mock! {
pub HeadersHandler {}
impl pdk_core::classy::hl::HeadersHandler for HeadersHandler{
fn headers(&self) -> Vec<(String, String)>;
fn header(&self, name: &str) -> Option<String>;
fn add_header(&self, name: &str, value: &str);
fn set_header(&self, name: &str, value: &str);
fn set_headers<'a>(&self, headers: Vec<(&'a str, &'a str)>);
fn remove_header(&self, name: &str);
}
}
fn get_expression(pel: &str) -> Script {
let exp: Expression = serde_json::from_str(pel).unwrap();
ScriptingEngine::script(&exp).compile().unwrap()
}
fn get_expression_fake_str(value: &str) -> Script {
let interpolated = format!(r#""P[[\":str\", \"0-10\", \"{value}\"]]""#);
get_expression(interpolated.as_str())
}
fn get_expression_fake_nbr(value: &str) -> Script {
let interpolated = format!(r#""P[[\":nbr\", \"33-34\", \"{value}\"]]""#);
get_expression(interpolated.as_str())
}
fn get_expression_fake_true() -> Script {
let pel = r#""P[[\":bool\", \"0-10\", \"true\"]]""#;
get_expression(pel)
}
fn get_expression_fake_false() -> Script {
let pel = r#""P[[\":bool\", \"0-10\", \"false\"]]""#;
get_expression(pel)
}
fn get_mock() -> MockHeadersHandler {
MockHeadersHandler::new()
}
fn assert_error_kind<T>(validation: &Result<T, JWTError>, kind: JWTErrorEnumKind)
where
T: std::fmt::Debug,
{
assert!(validation.is_err());
assert_eq!(
JWTErrorEnumKind::from(validation.as_ref().unwrap_err()),
kind
);
}
fn assert_claims_valid(validator: &mut impl ClaimValidation, token_claims: &JWTClaims) {
let validation = validator.validate(token_claims);
assert!(validation.is_ok());
}
fn assert_claims_matching_fail(
validator: &mut impl ClaimValidation,
token_claims: &JWTClaims,
error: JWTErrorEnumKind,
) {
let validation = validator.validate(token_claims);
assert_error_kind(&validation, error);
}
fn assert_expression_claims_valid(
validator: &mut impl ScriptClaimValidation,
token_claims: &JWTClaims,
) {
let ops = get_mock();
let validation = validator.validate(token_claims, &HandlerAttributesBinding::partial(&ops));
assert!(validation.is_ok());
}
fn assert_expression_claims_matching_fail(
validator: &mut impl ScriptClaimValidation,
token_claims: &JWTClaims,
error: JWTErrorEnumKind,
) {
let ops = get_mock();
let validation = validator.validate(token_claims, &HandlerAttributesBinding::partial(&ops));
assert_error_kind(&validation, error);
}
mod exp_validator_tests {
use super::super::{ClaimValidation, ClaimValidator};
use super::assert_error_kind;
use crate::error::jwt_error::JWTErrorEnumKind;
use crate::model::claims::JWTClaims;
use chrono::{DateTime, Duration, Utc};
#[test]
fn fails_on_missing_expiration_claim_when_mandatory() {
let validator = ClaimValidator::Exp {
mandatory: true,
current_time: Utc::now(),
};
let token_claims = JWTClaims::empty().unwrap();
let validation = validator.validate(&token_claims);
assert_error_kind(&validation, JWTErrorEnumKind::ClaimExpNotPresent);
}
#[test]
fn succeeds_on_present_claim_and_non_expired_when_mandatory() {
let now: DateTime<Utc> = Utc::now();
let validator = ClaimValidator::Exp {
mandatory: true,
current_time: now,
};
let expiration = now + Duration::try_days(1).unwrap();
let token_claims = JWTClaims::just_expiration(expiration).unwrap();
let validation = validator.validate(&token_claims);
assert!(validation.is_ok())
}
#[test]
fn doesnt_fail_on_missing_expiration_claim_when_non_mandatory() {
let validator = ClaimValidator::Exp {
mandatory: false,
current_time: Utc::now(),
};
let token_claims = JWTClaims::empty().unwrap();
let validation = validator.validate(&token_claims);
assert!(validation.is_ok())
}
#[test]
fn fails_on_past_expiration_date_for_non_mandatory() {
let now: DateTime<Utc> = Utc::now();
let validator = ClaimValidator::Exp {
mandatory: false,
current_time: now,
};
let expiration = now - Duration::try_days(1).unwrap();
let token_claims = JWTClaims::just_expiration(expiration).unwrap();
let validation = validator.validate(&token_claims);
assert_error_kind(&validation, JWTErrorEnumKind::ClaimExpValidationFailed);
}
#[test]
fn fails_on_past_expiration_date_for_mandatory() {
let now = Utc::now();
let validator = ClaimValidator::Exp {
mandatory: true,
current_time: now,
};
let expiration = now - Duration::try_days(1).unwrap();
let token_claims = JWTClaims::just_expiration(expiration).unwrap();
let validation = validator.validate(&token_claims);
assert_error_kind(&validation, JWTErrorEnumKind::ClaimExpValidationFailed);
}
#[test]
fn succeeds_if_it_is_right_in_the_limit() {
let now = Utc::now();
let validator = ClaimValidator::Exp {
mandatory: false,
current_time: now,
};
let expiration = now;
let token_claims = JWTClaims::just_expiration(expiration).unwrap();
let validation = validator.validate(&token_claims);
assert!(validation.is_ok())
}
#[test]
fn succeeds_if_expiration_time_is_in_the_future() {
let now = Utc::now();
let validator = ClaimValidator::Exp {
mandatory: false,
current_time: now,
};
let expiration = now + Duration::try_days(1).unwrap();
let token_claims = JWTClaims::just_expiration(expiration).unwrap();
let validation = validator.validate(&token_claims);
assert!(validation.is_ok())
}
}
mod nb_validator_tests {
use super::super::{ClaimValidation, ClaimValidator};
use super::assert_error_kind;
use crate::error::jwt_error::JWTErrorEnumKind;
use crate::model::claims::JWTClaims;
use chrono::{Duration, Utc};
#[test]
fn fails_on_missing_non_before_claim_for_mandatory() {
let validator = ClaimValidator::Nb {
mandatory: true,
current_time: Utc::now(),
};
let token_claims = JWTClaims::empty().unwrap();
let validation = validator.validate(&token_claims);
assert_error_kind(&validation, JWTErrorEnumKind::ClaimNbNotPresent);
}
#[test]
fn succeeds_on_past_non_before_date_for_mandatory() {
let now = Utc::now();
let validator = ClaimValidator::Nb {
mandatory: true,
current_time: now,
};
let not_before = now - Duration::try_days(1).unwrap();
let token_claims = JWTClaims::just_not_before(not_before).unwrap();
let validation = validator.validate(&token_claims);
assert!(validation.is_ok())
}
#[test]
fn succeeds_if_it_is_right_in_the_limit_for_non_mandatory() {
let now = Utc::now();
let validator = ClaimValidator::Nb {
mandatory: false,
current_time: now,
};
let not_before = now;
let token_claims = JWTClaims::just_not_before(not_before).unwrap();
let validation = validator.validate(&token_claims);
assert!(validation.is_ok())
}
#[test]
fn succeeds_on_present_claim_and_non_before_when_mandatory() {
let now = Utc::now();
let validator = ClaimValidator::Nb {
mandatory: true,
current_time: now,
};
let not_before = now - Duration::try_days(1).unwrap();
let token_claims = JWTClaims::just_not_before(not_before).unwrap();
let validation = validator.validate(&token_claims);
assert!(validation.is_ok());
}
#[test]
fn doesnt_fail_on_missing_non_before_claim_when_non_mandatory() {
let validator = ClaimValidator::Nb {
mandatory: false,
current_time: Utc::now(),
};
let token_claims = JWTClaims::empty().unwrap();
let validation = validator.validate(&token_claims);
assert!(validation.is_ok());
}
#[test]
fn fails_on_future_non_before_date_for_non_mandatory() {
let now = Utc::now();
let validator = ClaimValidator::Nb {
mandatory: false,
current_time: now,
};
let not_before = now + Duration::try_days(1).unwrap();
let token_claims = JWTClaims::just_not_before(not_before).unwrap();
let validation = validator.validate(&token_claims);
assert_error_kind(&validation, JWTErrorEnumKind::ClaimNbValidationFailed);
}
#[test]
fn fails_if_non_before_time_is_in_the_future_for_non_mandatory() {
let now = Utc::now();
let validator = ClaimValidator::Nb {
mandatory: false,
current_time: now,
};
let not_before = now + Duration::try_days(1).unwrap();
let token_claims = JWTClaims::just_not_before(not_before).unwrap();
let validation = validator.validate(&token_claims);
assert_error_kind(&validation, JWTErrorEnumKind::ClaimNbValidationFailed);
}
}
mod aud_validator_tests {
use super::super::ClaimValidator;
use super::{assert_claims_matching_fail, assert_claims_valid};
use crate::error::jwt_error::JWTErrorEnumKind;
use crate::model::claims::{claim_names::AUD_CLAIM, JWTClaims};
use serde_json::Value::*;
use std::collections::{HashMap, HashSet};
use std::string::String;
#[test]
fn doesnt_fail_on_missing_aud_claim_when_non_mandatory() {
let mut validator = ClaimValidator::Aud {
mandatory: false,
audiences: HashSet::default(),
};
let token_claims = JWTClaims::empty().unwrap();
assert_claims_valid(&mut validator, &token_claims);
}
#[test]
fn fails_on_missing_aud_claim_when_mandatory() {
let mut validator = ClaimValidator::Aud {
mandatory: true,
audiences: HashSet::default(),
};
let token_claims = JWTClaims::empty().unwrap();
assert_claims_matching_fail(
&mut validator,
&token_claims,
JWTErrorEnumKind::ClaimAudNotPresent,
);
}
#[test]
fn succeeds_on_string_type_matching_audience() {
let token_claims = JWTClaims::from_custom_claims(HashMap::from([(
AUD_CLAIM.to_string(),
String(String::from("an_audience")),
)]))
.unwrap();
let mut validator = ClaimValidator::Aud {
mandatory: true,
audiences: HashSet::from(["an_audience".to_string()]),
};
assert_claims_valid(&mut validator, &token_claims);
let mut validator = ClaimValidator::Aud {
mandatory: false,
audiences: HashSet::from(["an_audience".to_string()]),
};
assert_claims_valid(&mut validator, &token_claims);
}
#[test]
fn fails_on_string_type_not_matching_audience() {
let token_claims = JWTClaims::from_custom_claims(HashMap::from([(
AUD_CLAIM.to_string(),
String(String::from("another_audience")),
)]))
.unwrap();
let mut validator = ClaimValidator::Aud {
mandatory: true,
audiences: HashSet::from(["an_audience".to_string()]),
};
assert_claims_matching_fail(
&mut validator,
&token_claims,
JWTErrorEnumKind::ClaimAudValidationFailed,
);
let mut validator = ClaimValidator::Aud {
mandatory: false,
audiences: HashSet::from(["an_audience".to_string()]),
};
assert_claims_matching_fail(
&mut validator,
&token_claims,
JWTErrorEnumKind::ClaimAudValidationFailed,
);
}
#[test]
fn succeeds_on_list_type_matching_audience() {
let token_claims = JWTClaims::from_custom_claims(HashMap::from([(
AUD_CLAIM.to_string(),
String(String::from("an_audience")),
)]))
.unwrap();
let mut validator = ClaimValidator::Aud {
mandatory: true,
audiences: HashSet::from(["an_audience".to_string()]),
};
assert_claims_valid(&mut validator, &token_claims);
let mut validator = ClaimValidator::Aud {
mandatory: false,
audiences: HashSet::from(["an_audience".to_string()]),
};
assert_claims_valid(&mut validator, &token_claims);
}
#[test]
fn fails_on_list_type_not_matching_audience() {
let token_claims = JWTClaims::from_custom_claims(HashMap::from([(
AUD_CLAIM.to_string(),
String(String::from("another_audience")),
)]))
.unwrap();
let mut validator = ClaimValidator::Aud {
mandatory: true,
audiences: HashSet::from(["an_audience".to_string()]),
};
assert_claims_matching_fail(
&mut validator,
&token_claims,
JWTErrorEnumKind::ClaimAudValidationFailed,
);
let mut validator = ClaimValidator::Aud {
mandatory: true,
audiences: HashSet::from(["an_audience".to_string()]),
};
assert_claims_matching_fail(
&mut validator,
&token_claims,
JWTErrorEnumKind::ClaimAudValidationFailed,
);
}
#[test]
fn succeeds_on_list_type_matching_one_audience() {
let mut validator = ClaimValidator::Aud {
mandatory: true,
audiences: HashSet::from([
"an_audience1".to_string(),
"an_audience2".to_string(),
"an_audience3".to_string(),
]),
};
let token_claims = JWTClaims::from_custom_claims(HashMap::from([(
AUD_CLAIM.to_string(),
Array(vec![
String(String::from("an_audience1")),
String(String::from("an_audience4")),
String(String::from("an_audience5")),
]),
)]))
.unwrap();
assert_claims_valid(&mut validator, &token_claims);
}
#[test]
fn succeeds_on_list_type_matching_several_audiences() {
let mut validator = ClaimValidator::Aud {
mandatory: true,
audiences: HashSet::from([
"an_audience1".to_string(),
"an_audience2".to_string(),
"an_audience3".to_string(),
]),
};
let token_claims = JWTClaims::from_custom_claims(HashMap::from([(
AUD_CLAIM.to_string(),
Array(vec![
String(String::from("an_audience1")),
String(String::from("an_audience4")),
String(String::from("an_audience5")),
]),
)]))
.unwrap();
assert_claims_valid(&mut validator, &token_claims);
}
#[test]
fn succeeds_on_list_type_matching_all_audiences() {
let mut validator = ClaimValidator::Aud {
mandatory: true,
audiences: HashSet::from([
"an_audience1".to_string(),
"an_audience2".to_string(),
"an_audience3".to_string(),
]),
};
let token_claims = JWTClaims::from_custom_claims(HashMap::from([(
AUD_CLAIM.to_string(),
Array(vec![
String(String::from("an_audience1")),
String(String::from("an_audience4")),
String(String::from("an_audience5")),
]),
)]))
.unwrap();
assert_claims_valid(&mut validator, &token_claims);
}
#[test]
fn fails_on_list_type_with_no_audiences_permitted() {
let mut validator = ClaimValidator::Aud {
mandatory: true,
audiences: HashSet::default(),
};
let token_claims = JWTClaims::from_custom_claims(HashMap::from([(
AUD_CLAIM.to_string(),
Array(vec![
String(String::from("an_audience1")),
String(String::from("an_audience4")),
String(String::from("an_audience5")),
]),
)]))
.unwrap();
assert_claims_matching_fail(
&mut validator,
&token_claims,
JWTErrorEnumKind::ClaimAudValidationFailed,
);
}
#[test]
fn succeeds_on_list_type_matching_one_url_audience() {
let mut validator = ClaimValidator::Aud {
mandatory: true,
audiences: HashSet::from([
"an_audience1".to_string(),
"an_audience2".to_string(),
"http://localhost:8080".to_string(),
]),
};
let token_claims = JWTClaims::from_custom_claims(HashMap::from([(
AUD_CLAIM.to_string(),
Array(vec![
String(String::from("an_audience1")),
String(String::from("http://localhost:8080")),
]),
)]))
.unwrap();
assert_claims_valid(&mut validator, &token_claims);
}
}
mod custom_claims_validator_tests {
use crate::error::jwt_error::JWTErrorEnumKind;
use crate::model::{claim_validation_rules::ScriptClaimRule, claims::JWTClaims};
use crate::validator::claim_validator::ScriptClaimValidator;
use serde_json::{Map, Number, Value};
use std::collections::HashMap;
use super::{
assert_expression_claims_matching_fail, assert_expression_claims_valid, get_expression,
get_expression_fake_false, get_expression_fake_nbr, get_expression_fake_str,
get_expression_fake_true,
};
#[test]
fn succeeds_on_empty_claims() {
let mut validator = ScriptClaimValidator::new(vec![], vec![]);
let token_claims = JWTClaims::empty().unwrap();
assert_expression_claims_valid(&mut validator, &token_claims);
}
#[test]
fn fails_on_empty_claims_when_there_are_mandatory_claims() {
let mut validator = ScriptClaimValidator::new(
vec![ScriptClaimRule::new_owned(
"a_claim".to_string(),
get_expression_fake_str("a_rule"),
)],
vec![],
);
let token_claims = JWTClaims::empty().unwrap();
assert_expression_claims_matching_fail(
&mut validator,
&token_claims,
JWTErrorEnumKind::MandatoryCustomClaimMismatchOrNotPresent,
);
}
#[test]
fn succeeds_on_empty_claims_when_there_are_non_mandatory_claims() {
let mut validator = ScriptClaimValidator::new(
vec![],
vec![ScriptClaimRule::new_owned(
"a_claim".to_string(),
get_expression_fake_str("a_rule"),
)],
);
let token_claims = JWTClaims::empty().unwrap();
assert_expression_claims_valid(&mut validator, &token_claims);
}
#[test]
fn fails_on_empty_claims_when_there_are_mandatory_and_non_mandatory_claims() {
let mut validator = ScriptClaimValidator::new(
vec![ScriptClaimRule::new_owned(
"a_claim".to_string(),
get_expression_fake_str("a_rule"),
)],
vec![ScriptClaimRule::new_owned(
"a_claim2".to_string(),
get_expression_fake_str("a_rule2"),
)],
);
let token_claims = JWTClaims::empty().unwrap();
assert_expression_claims_matching_fail(
&mut validator,
&token_claims,
JWTErrorEnumKind::MandatoryCustomClaimMismatchOrNotPresent,
);
}
#[test]
fn validation_should_honor_a_dw_expression_resolving_to_true() {
let mut validator = ScriptClaimValidator::new(
vec![ScriptClaimRule::new_owned(
"a_mandatory_claim".to_string(),
get_expression_fake_true(),
)],
vec![ScriptClaimRule::new_owned(
"a_claim".to_string(),
get_expression_fake_true(),
)],
);
let token_claims = JWTClaims::from_custom_claims(HashMap::from([
(
"a_mandatory_claim".to_string(),
Value::String("some_random_value".to_string()),
),
(
"a_claim".to_string(),
Value::String("some_another_random_value".to_string()),
),
]))
.unwrap();
assert_expression_claims_valid(&mut validator, &token_claims);
}
#[test]
fn validation_should_honor_a_dw_expression_resolving_to_false_on_a_mandatory_rule() {
let mut validator = ScriptClaimValidator::new(
vec![ScriptClaimRule::new_owned(
"a_claim".to_string(),
get_expression_fake_false(),
)],
vec![],
);
let token_claims = JWTClaims::from_custom_claims(HashMap::from([(
"a_claim".to_string(),
Value::String("some random value".to_string()),
)]))
.unwrap();
assert_expression_claims_matching_fail(
&mut validator,
&token_claims,
JWTErrorEnumKind::MandatoryCustomClaimMismatchOrNotPresent,
);
}
#[test]
fn validation_should_honor_a_dw_expression_resolving_to_false_on_a_non_mandatory_rule() {
let mut validator = ScriptClaimValidator::new(
vec![],
vec![ScriptClaimRule::new_owned(
"a_claim".to_string(),
get_expression_fake_false(),
)],
);
let token_claims = JWTClaims::from_custom_claims(HashMap::from([(
"a_claim".to_string(),
Value::String("some random value".to_string()),
)]))
.unwrap();
assert_expression_claims_matching_fail(
&mut validator,
&token_claims,
JWTErrorEnumKind::NonMandatoryCustomClaimMismatch,
);
}
#[test]
fn when_a_non_mandatory_claim_is_not_present_the_dw_expression_should_not_be_honored() {
let mut validator = ScriptClaimValidator::new(
vec![],
vec![ScriptClaimRule::new_owned(
"a_claim".to_string(),
get_expression_fake_false(),
)],
);
let token_claims = JWTClaims::empty().unwrap();
assert_expression_claims_valid(&mut validator, &token_claims);
}
#[test]
fn dw_expression_should_only_be_honored_if_it_resolves_to_string_or_bool() {
let mut validator = ScriptClaimValidator::new(
vec![],
vec![
ScriptClaimRule::new_owned(
"a_string_claim".to_string(),
get_expression_fake_nbr("1"),
),
ScriptClaimRule::new_owned(
"a_number_claim".to_string(),
get_expression_fake_nbr("1"),
),
ScriptClaimRule::new_owned(
"a_float_claim".to_string(),
get_expression_fake_nbr("1"),
),
],
);
let token_claims = JWTClaims::from_custom_claims(HashMap::from([
("a_string_claim".to_string(), Value::String("1".to_string())),
(
"a_number_claim".to_string(),
Value::Number(Number::from(1u32)),
),
(
"a_float_claim".to_string(),
Value::Number(Number::from_f64(1.0).unwrap()),
),
]))
.unwrap();
assert_expression_claims_matching_fail(
&mut validator,
&token_claims,
JWTErrorEnumKind::NonMandatoryCustomClaimMismatch,
);
}
#[test]
fn fails_when_there_are_only_non_mandatory_claims_mismatching() {
let mut validator = ScriptClaimValidator::new(
vec![],
vec![ScriptClaimRule::new_owned(
"a_claim".to_string(),
get_expression_fake_str("a_rule"),
)],
);
let token_claims = JWTClaims::from_custom_claims(HashMap::from([(
"a_claim".to_string(),
Value::String("some_random_value".to_string()),
)]))
.unwrap();
assert_expression_claims_matching_fail(
&mut validator,
&token_claims,
JWTErrorEnumKind::NonMandatoryCustomClaimMismatch,
);
}
#[test]
fn succeeds_when_there_are_non_mandatory_claims_that_are_not_present() {
let mut validator = ScriptClaimValidator::new(
vec![],
vec![
ScriptClaimRule::new_owned(
"a_claim".to_string(),
get_expression_fake_str("a_rule"),
),
ScriptClaimRule::new_owned(
"a_claim2".to_string(),
get_expression_fake_str("a_rule2"),
),
],
);
let token_claims = JWTClaims::from_custom_claims(HashMap::from([(
"a_claim".to_string(),
Value::String("a_rule".to_string()),
)]))
.unwrap();
assert_expression_claims_valid(&mut validator, &token_claims);
}
#[test]
fn fails_when_there_are_mandatory_claims_that_are_not_present() {
let mut validator = ScriptClaimValidator::new(
vec![
ScriptClaimRule::new_owned(
"a_claim".to_string(),
get_expression_fake_str("a_rule"),
),
ScriptClaimRule::new_owned(
"a_claim2".to_string(),
get_expression_fake_str("a_rule2"),
),
],
vec![],
);
let token_claims = JWTClaims::from_custom_claims(HashMap::from([(
"a_claim".to_string(),
Value::String("a_rule".to_string()),
)]))
.unwrap();
assert_expression_claims_matching_fail(
&mut validator,
&token_claims,
JWTErrorEnumKind::MandatoryCustomClaimMismatchOrNotPresent,
);
}
#[test]
fn fails_when_there_are_mandatory_claims_mismatching() {
let mut validator = ScriptClaimValidator::new(
vec![
ScriptClaimRule::new_owned(
"a_claim".to_string(),
get_expression_fake_str("a_rule"),
),
ScriptClaimRule::new_owned(
"a_claim2".to_string(),
get_expression_fake_str("another_value"),
),
],
vec![],
);
let token_claims = JWTClaims::from_custom_claims(HashMap::from([
("a_claim".to_string(), Value::String("a_value".to_string())),
(
"a_claim2".to_string(),
Value::String("yet_another_value".to_string()),
),
]))
.unwrap();
assert_expression_claims_matching_fail(
&mut validator,
&token_claims,
JWTErrorEnumKind::MandatoryCustomClaimMismatchOrNotPresent,
);
}
#[test]
fn succeeds_when_there_are_only_non_mandatory_claims_matching() {
let mut validator = ScriptClaimValidator::new(
vec![],
vec![ScriptClaimRule::new_owned(
"a_claim".to_string(),
get_expression_fake_str("a_rule"),
)],
);
let token_claims = JWTClaims::from_custom_claims(HashMap::from([
("a_claim".to_string(), Value::String("a_rule".to_string())),
("a_claim2".to_string(), Value::String("a_rule".to_string())),
]))
.unwrap();
assert_expression_claims_valid(&mut validator, &token_claims);
}
#[test]
fn succeeds_when_there_are_only_several_non_mandatory_claims_matching() {
let mut validator = ScriptClaimValidator::new(
vec![],
vec![
ScriptClaimRule::new_owned(
"a_claim1".to_string(),
get_expression_fake_str("a_claim_value"),
),
ScriptClaimRule::new_owned(
"a_claim2".to_string(),
get_expression_fake_str("another_claim_value"),
),
ScriptClaimRule::new_owned(
"a_claim3".to_string(),
get_expression_fake_str("another_claim_value"),
),
],
);
let token_claims = JWTClaims::from_custom_claims(HashMap::from([
(
"a_claim1".to_string(),
Value::String("a_claim_value".to_string()),
),
(
"a_claim2".to_string(),
Value::String("another_claim_value".to_string()),
),
(
"a_claim3".to_string(),
Value::String("another_claim_value".to_string()),
),
(
"a_claim4".to_string(),
Value::String("another_claim_value".to_string()),
),
]))
.unwrap();
assert_expression_claims_valid(&mut validator, &token_claims);
}
#[test]
fn succeeds_when_there_are_several_non_mandatory_claims_matching_with_different_types() {
let mut validator = ScriptClaimValidator::new(
vec![],
vec![
ScriptClaimRule::new_owned(
"a_claim1".to_string(),
get_expression_fake_str("a_claim_value"),
),
ScriptClaimRule::new_owned(
"a_claim2".to_string(),
get_expression_fake_str("2"),
),
ScriptClaimRule::new_owned(
"a_claim3".to_string(),
get_expression_fake_str("false"),
),
ScriptClaimRule::new_owned(
"a_claim4".to_string(),
get_expression_fake_str("{}"),
),
ScriptClaimRule::new_owned(
"a_claim5".to_string(),
get_expression(
r#""P[[\":str\", \"0-10\", \"[\\\"a string in an array\\\",1,true]\"]]""#,
),
),
],
);
let token_claims = JWTClaims::from_custom_claims(HashMap::from([
(
"a_claim1".to_string(),
Value::String("a_claim_value".to_string()),
),
("a_claim2".to_string(), Value::Number(Number::from(2u64))),
("a_claim3".to_string(), Value::Bool(false)),
("a_claim4".to_string(), Value::Object(Map::new())),
(
"a_claim5".to_string(),
Value::Array(vec![
Value::String("a string in an array".to_string()),
Value::Number(Number::from(1u64)),
Value::Bool(true),
]),
),
]))
.unwrap();
assert_expression_claims_valid(&mut validator, &token_claims);
}
#[test]
fn succeeds_when_there_are_only_several_mandatory_claims_matching_with_different_types() {
let mut validator = ScriptClaimValidator::new(
vec![
ScriptClaimRule::new_owned(
"a_claim1".to_string(),
get_expression_fake_str("aValue"),
),
ScriptClaimRule::new_owned(
"a_claim2".to_string(),
get_expression_fake_str("2"),
),
ScriptClaimRule::new_owned(
"a_claim3".to_string(),
get_expression_fake_str("false"),
),
ScriptClaimRule::new_owned(
"a_claim4".to_string(),
get_expression_fake_str("{}"),
),
ScriptClaimRule::new_owned(
"a_claim5".to_string(),
get_expression(
r#""P[[\":str\", \"0-10\", \"[\\\"a string in an array\\\",1,true]\"]]""#,
),
),
],
vec![],
);
let token_claims = JWTClaims::from_custom_claims(HashMap::from([
("a_claim1".to_string(), Value::String("aValue".to_string())),
("a_claim2".to_string(), Value::Number(Number::from(2u64))),
("a_claim3".to_string(), Value::Bool(false)),
("a_claim4".to_string(), Value::Object(Map::new())),
(
"a_claim5".to_string(),
Value::Array(vec![
Value::String("a string in an array".to_string()),
Value::Number(Number::from(1u64)),
Value::Bool(true),
]),
),
]))
.unwrap();
assert_expression_claims_valid(&mut validator, &token_claims);
}
}
mod claims_validator {
use super::super::{ClaimValidation, ClaimValidator, ClaimsValidator};
use super::assert_error_kind;
use crate::error::jwt_error::JWTErrorEnumKind;
use crate::model::claims::{claim_names::AUD_CLAIM, JWTClaims};
use chrono::{Duration, Utc};
use serde_json::{Number, Value};
use std::collections::{HashMap, HashSet, LinkedList};
#[test]
fn when_all_validators_are_ok_general_validation_is_ok() {
let now = Utc::now();
let exp_validator = ClaimValidator::Exp {
mandatory: true,
current_time: now,
};
let nbf_validator = ClaimValidator::Nb {
mandatory: true,
current_time: now,
};
let aud_validator = ClaimValidator::Aud {
mandatory: true,
audiences: HashSet::from(["some_audience".to_string()]),
};
let claims_validator = ClaimsValidator::new(LinkedList::from([
exp_validator,
nbf_validator,
aud_validator,
]));
let token_claims = JWTClaims::new(
Some(now),
Some(now),
None,
HashMap::from([
(
AUD_CLAIM.to_string(),
Value::String("some_audience".to_string()),
),
(
"a_claim1".to_string(),
Value::String("a_claim_value".to_string()),
),
("a_claim2".to_string(), Value::Number(Number::from(2u64))),
(
"a_claim3".to_string(),
Value::String("another_claim_value".to_string()),
),
]),
HashMap::default(),
)
.unwrap();
let validation = claims_validator.validate(&token_claims);
assert!(validation.is_ok());
}
#[test]
fn when_some_validator_returns_error_then_general_validation_returns_error() {
let now = Utc::now();
let exp_validator = ClaimValidator::Exp {
mandatory: true,
current_time: now,
};
let nbf_validator = ClaimValidator::Nb {
mandatory: true,
current_time: now,
};
let aud_validator = ClaimValidator::Aud {
mandatory: true,
audiences: HashSet::from(["some_audience".to_string()]),
};
let claims_validator = ClaimsValidator::new(LinkedList::from([
exp_validator,
nbf_validator,
aud_validator,
]));
let token_claims = JWTClaims::new(
Some(now - Duration::try_seconds(10).unwrap()), Some(now - Duration::try_days(1).unwrap()),
None,
HashMap::from([(
AUD_CLAIM.to_string(),
Value::String("some_audience".to_string()),
)]),
HashMap::default(),
)
.unwrap();
let validation = claims_validator.validate(&token_claims);
assert_error_kind(&validation, JWTErrorEnumKind::ClaimExpValidationFailed);
}
}
}