use crate::{Arn, Context, ContextValue, EvaluationError, IAMOperator, OperatorType};
use chrono::{DateTime, Utc};
use ipnet::IpNet;
#[derive(Copy, Debug, Clone, PartialEq, Eq, Hash)]
enum SetOperatorType {
ForAnyValue,
ForAllValues,
None,
}
impl SetOperatorType {
fn from_operator(operator: &IAMOperator) -> Self {
match operator.to_string().as_str() {
s if s.starts_with("ForAnyValue:") => SetOperatorType::ForAnyValue,
s if s.starts_with("ForAllValues:") => SetOperatorType::ForAllValues,
_ => SetOperatorType::None,
}
}
}
type Predicate<T> = Box<dyn Fn(T, T) -> bool>;
type DatePredicate<T> = Box<dyn for<'a, 'b> Fn(&'a T, &'b T) -> bool>;
type O = IAMOperator;
#[allow(clippy::too_many_lines)]
pub(super) fn evaluate_condition(
ctx: &Context,
operator: &IAMOperator,
key: &str,
value: &serde_json::Value,
) -> Result<bool, EvaluationError> {
let if_exists = operator.is_if_exists_operator();
let set_operator = SetOperatorType::from_operator(operator);
let mut predicate_str: Predicate<String> =
Box::new(|_a, _b| unreachable!("predicate_str invoked for non-string operator category"));
let mut predicate_num: Predicate<f64> =
Box::new(|_a, _b| unreachable!("predicate_num invoked for non-numeric operator category"));
let mut predicate_date: DatePredicate<DateTime<Utc>> =
Box::new(|_a, _b| unreachable!("predicate_date invoked for non-date operator category"));
let mut predicate_bool: Predicate<bool> =
Box::new(|_a, _b| unreachable!("predicate_bool invoked for non-boolean operator category"));
let mut predicate_ip: Predicate<IpNet> =
Box::new(|_a, _b| unreachable!("predicate_ip invoked for non-IP operator category"));
match operator {
O::StringEquals
| O::ForAllValuesStringEquals
| O::ForAnyValueStringEquals
| O::StringEqualsIfExists
| O::ArnEquals
| O::ForAllValuesArnEquals
| O::ForAnyValueArnEquals
| O::ArnEqualsIfExists => predicate_str = Box::new(|a, b| a == b),
O::StringNotEquals
| O::ForAllValuesStringNotEquals
| O::ForAnyValueStringNotEquals
| O::StringNotEqualsIfExists
| O::ArnNotEquals
| O::ForAllValuesArnNotEquals
| O::ForAnyValueArnNotEquals
| O::ArnNotEqualsIfExists => predicate_str = Box::new(|a, b| a != b),
O::StringEqualsIgnoreCase
| O::ForAllValuesStringEqualsIgnoreCase
| O::ForAnyValueStringEqualsIgnoreCase
| O::StringEqualsIgnoreCaseIfExists => {
predicate_str = Box::new(|a, b| a.eq_ignore_ascii_case(&b));
}
O::StringNotEqualsIgnoreCase
| O::ForAllValuesStringNotEqualsIgnoreCase
| O::ForAnyValueStringNotEqualsIgnoreCase
| O::StringNotEqualsIgnoreCaseIfExists => {
predicate_str = Box::new(|a, b| !a.eq_ignore_ascii_case(&b));
}
O::StringLike
| O::ForAllValuesStringLike
| O::ForAnyValueStringLike
| O::StringLikeIfExists
| O::ArnLike
| O::ForAllValuesArnLike
| O::ForAnyValueArnLike
| O::ArnLikeIfExists => predicate_str = Box::new(|a, b| wildcard_match(&a, &b)),
O::StringNotLike
| O::ForAllValuesStringNotLike
| O::ForAnyValueStringNotLike
| O::StringNotLikeIfExists
| O::ArnNotLike
| O::ForAllValuesArnNotLike
| O::ForAnyValueArnNotLike
| O::ArnNotLikeIfExists => predicate_str = Box::new(|a, b| !wildcard_match(&a, &b)),
O::NumericEquals | O::NumericEqualsIfExists => {
predicate_num = Box::new(|a, b| (a - b).abs() < f64::EPSILON);
}
O::NumericNotEquals | O::NumericNotEqualsIfExists => {
predicate_num = Box::new(|a, b| (a - b).abs() >= f64::EPSILON);
}
O::NumericLessThan | O::NumericLessThanIfExists => predicate_num = Box::new(|a, b| a < b),
O::NumericLessThanEquals | O::NumericLessThanEqualsIfExists => {
predicate_num = Box::new(|a, b| a <= b);
}
O::NumericGreaterThan | O::NumericGreaterThanIfExists => {
predicate_num = Box::new(|a, b| a > b);
}
O::NumericGreaterThanEquals | O::NumericGreaterThanEqualsIfExists => {
predicate_num = Box::new(|a, b| a >= b);
}
O::DateEquals | O::DateEqualsIfExists => predicate_date = Box::new(|a, b| a == b),
O::DateNotEquals | O::DateNotEqualsIfExists => predicate_date = Box::new(|a, b| a != b),
O::DateLessThan | O::DateLessThanIfExists => predicate_date = Box::new(|a, b| a < b),
O::DateLessThanEquals | O::DateLessThanEqualsIfExists => {
predicate_date = Box::new(|a, b| a <= b);
}
O::DateGreaterThan | O::DateGreaterThanIfExists => predicate_date = Box::new(|a, b| a > b),
O::DateGreaterThanEquals | O::DateGreaterThanEqualsIfExists => {
predicate_date = Box::new(|a, b| a >= b);
}
O::Bool | O::ForAllValuesBool | O::ForAnyValueBool | O::BoolIfExists => {
predicate_bool = Box::new(|a, b| a == b);
}
O::BinaryEquals | O::BinaryEqualsIfExists => {
predicate_str = Box::new(|a, b| {
a.to_lowercase().trim_end_matches('=') == b.to_lowercase().trim_end_matches('=')
});
}
O::IpAddress | O::IpAddressIfExists => predicate_ip = Box::new(|a, b| b.contains(&a)),
O::NotIpAddress | O::NotIpAddressIfExists => {
predicate_ip = Box::new(|a, b| !b.contains(&a));
}
O::Null => {
}
}
let values = match value {
serde_json::Value::Array(arr) => arr,
_ => std::slice::from_ref(value),
};
for value in values {
let result = match operator.category() {
OperatorType::String | OperatorType::Arn | OperatorType::Binary => {
ev_str(ctx, key, value, &predicate_str, if_exists, set_operator)?
}
OperatorType::Numeric => ev_numeric(ctx, key, value, &predicate_num, if_exists)?,
OperatorType::Date => ev_date(ctx, key, value, &predicate_date, if_exists)?,
OperatorType::Boolean => {
ev_bool(ctx, key, value, &predicate_bool, if_exists, set_operator)?
}
OperatorType::IpAddress => ev_ip(ctx, key, value, &predicate_ip, if_exists)?,
OperatorType::Null => {
match value {
serde_json::Value::Bool(should_be_null) => {
let is_null = ctx.get(key).is_none();
return Ok(is_null == *should_be_null);
}
_ => {
return Err(EvaluationError::ConditionError(
"Null operator requires boolean value".to_string(),
));
}
}
}
};
if result {
return Ok(true);
}
}
Ok(false)
}
fn ev_str(
ctx: &Context,
key: &str,
value: &serde_json::Value,
predicate: &Predicate<String>,
if_exists: bool,
set_operator: SetOperatorType,
) -> Result<bool, EvaluationError> {
let value = value.as_str().ok_or_else(|| {
EvaluationError::ConditionError("String condition value must be a string".to_string())
})?;
match ctx.get(key) {
Some(ContextValue::String(s)) => Ok(predicate(s.clone(), value.to_string())),
Some(ContextValue::StringList(list)) => match set_operator {
SetOperatorType::ForAnyValue => Ok(list
.iter()
.any(|val| predicate(val.clone(), value.to_string()))),
SetOperatorType::ForAllValues => Ok(list
.iter()
.all(|val| predicate(val.clone(), value.to_string()))),
SetOperatorType::None => Err(EvaluationError::ConditionError(
"Multivalued context keys require a condition set operator.".to_string(),
)),
},
Some(_) => Ok(false), None => Ok(if_exists), }
}
fn ev_numeric(
ctx: &Context,
key: &str,
value: &serde_json::Value,
predicate: &Predicate<f64>,
if_exists: bool,
) -> Result<bool, EvaluationError> {
let value = value
.to_string()
.trim_matches('"') .parse::<f64>()
.map_err(|err| {
EvaluationError::ConditionError(format!(
"Numeric condition value must be a number, was {value}. ParseFloatError: {err}",
))
})?;
let context_value = match ctx.get(key) {
Some(ContextValue::Number(n)) => *n,
Some(ContextValue::String(s)) => s.parse::<f64>().map_err(|_| {
EvaluationError::ConditionError("Invalid numeric context value".to_string())
})?,
Some(_) => return Ok(false), None => return Ok(if_exists), };
Ok(predicate(context_value, value))
}
fn parse_date(value: &str) -> Result<DateTime<Utc>, EvaluationError> {
DateTime::parse_from_rfc3339(value)
.map(|dt| dt.with_timezone(&Utc))
.or_else(|_| {
value
.parse::<i64>()
.map_err(|_| EvaluationError::ConditionError("Invalid date value".to_string()))
.and_then(|epoch| {
DateTime::<Utc>::from_timestamp(epoch, 0).ok_or_else(|| {
EvaluationError::ConditionError("Invalid epoch time".to_string())
})
})
})
}
fn ev_date(
ctx: &Context,
key: &str,
value: &serde_json::Value,
predicate: &DatePredicate<DateTime<Utc>>,
if_exists: bool,
) -> Result<bool, EvaluationError> {
let value = value.as_str().ok_or_else(|| {
EvaluationError::ConditionError(format!(
"Date condition value must be a string, got {value}"
))
})?;
let value: DateTime<Utc> = parse_date(value)
.map_err(|_| EvaluationError::ConditionError("Invalid date condition value".to_string()))?;
let context_value: DateTime<Utc> = match ctx.get(key) {
Some(ContextValue::DateTime(dt)) => *dt,
Some(ContextValue::Number(epoch)) => parse_date(&epoch.to_string()).map_err(|_| {
EvaluationError::ConditionError("Invalid epoch context value".to_string())
})?,
Some(ContextValue::String(s)) => parse_date(s).map_err(|_| {
EvaluationError::ConditionError("Invalid date context value".to_string())
})?,
Some(_) => return Ok(false), None => return Ok(if_exists), };
Ok(predicate(&context_value, &value))
}
fn ev_bool(
ctx: &Context,
key: &str,
value: &serde_json::Value,
predicate: &Predicate<bool>,
if_exists: bool,
set_operator: SetOperatorType,
) -> Result<bool, EvaluationError> {
let value = value
.to_string()
.trim_matches('"') .parse::<bool>()
.map_err(|err| {
EvaluationError::ConditionError(format!(
"Boolean condition value must be a boolean, got {value}. Error: {err}",
))
})?;
match ctx.get(key) {
Some(ContextValue::Boolean(b)) => Ok(predicate(*b, value)),
Some(ContextValue::BooleanList(list)) => match set_operator {
SetOperatorType::ForAnyValue => Ok(list.iter().any(|&val| predicate(val, value))),
SetOperatorType::ForAllValues => Ok(list.iter().all(|&val| predicate(val, value))),
SetOperatorType::None => Err(EvaluationError::ConditionError(
"Multivalued context keys require a condition set operator.".to_string(),
)),
},
Some(_) => Ok(false), None => Ok(if_exists), }
}
fn ev_ip(
ctx: &Context,
key: &str,
value: &serde_json::Value,
predicate: &Predicate<IpNet>,
if_exists: bool,
) -> Result<bool, EvaluationError> {
fn ip_subnet(ip: &str) -> String {
match ip {
ip if ip.contains('/') => ip.to_string(),
ip if ip.contains(':') => format!("{ip}/128"),
ip => format!("{ip}/32"),
}
}
let value = value.as_str().ok_or_else(|| {
EvaluationError::ConditionError("IP condition value must be a string".to_string())
})?;
let value: IpNet = ip_subnet(value)
.parse()
.map_err(|_| EvaluationError::ConditionError("Invalid IP condition value".to_string()))?;
let context_value = match ctx.get(key) {
Some(ContextValue::String(ip_addr)) => ip_subnet(ip_addr)
.parse::<IpNet>()
.map_err(|_| EvaluationError::ConditionError("Invalid IP context value".to_string()))?,
Some(_) => return Ok(false), None => return Ok(if_exists), };
Ok(predicate(context_value, value))
}
#[must_use]
pub(super) fn wildcard_match(text: &str, pattern: &str) -> bool {
Arn::wildcard_match(text, pattern)
}
#[cfg(test)]
mod tests {
use super::*;
use chrono::DateTime;
fn create_test_context() -> Context {
let mut ctx = Context::new()
.with_string("string_key", "test_value")
.with_string("ip_key", "192.168.1.1")
.with_string("date_key", "2024-01-01T00:00:00Z")
.with_string("numeric_string", "42.5")
.with_number("numeric_key", 42.0)
.with_boolean("bool_key", true);
ctx.insert(
"datetime_key".to_string(),
ContextValue::DateTime(
DateTime::parse_from_rfc3339("2024-01-01T00:00:00Z")
.unwrap()
.with_timezone(&Utc),
),
);
ctx.insert(
"string_list".to_string(),
ContextValue::StringList(vec![
"value1".to_string(),
"value2".to_string(),
"value3".to_string(),
]),
);
ctx.insert(
"bool_list".to_string(),
ContextValue::BooleanList(vec![true, false, true]),
);
ctx
}
#[test]
fn test_evaluate_condition_string_equals() {
let ctx = create_test_context();
let result = evaluate_condition(
&ctx,
&IAMOperator::StringEquals,
"string_key",
&serde_json::Value::String("test_value".to_string()),
)
.unwrap();
assert!(result);
let result = evaluate_condition(
&ctx,
&IAMOperator::StringEquals,
"string_key",
&serde_json::Value::String("different_value".to_string()),
)
.unwrap();
assert!(!result);
}
#[test]
fn test_evaluate_condition_string_not_equals() {
let ctx = create_test_context();
let result = evaluate_condition(
&ctx,
&IAMOperator::StringNotEquals,
"string_key",
&serde_json::Value::String("different_value".to_string()),
)
.unwrap();
assert!(result);
let result = evaluate_condition(
&ctx,
&IAMOperator::StringNotEquals,
"string_key",
&serde_json::Value::String("test_value".to_string()),
)
.unwrap();
assert!(!result);
}
#[test]
fn test_evaluate_condition_string_equals_ignore_case() {
let ctx = create_test_context();
let result = evaluate_condition(
&ctx,
&IAMOperator::StringEqualsIgnoreCase,
"string_key",
&serde_json::Value::String("TEST_VALUE".to_string()),
)
.unwrap();
assert!(result);
let result = evaluate_condition(
&ctx,
&IAMOperator::StringEqualsIgnoreCase,
"string_key",
&serde_json::Value::String("different_value".to_string()),
)
.unwrap();
assert!(!result);
}
#[test]
fn test_evaluate_condition_string_like() {
let ctx = create_test_context();
let result = evaluate_condition(
&ctx,
&IAMOperator::StringLike,
"string_key",
&serde_json::Value::String("test_*".to_string()),
)
.unwrap();
assert!(result);
let result = evaluate_condition(
&ctx,
&IAMOperator::StringLike,
"string_key",
&serde_json::Value::String("other_*".to_string()),
)
.unwrap();
assert!(!result);
}
#[test]
fn test_evaluate_condition_numeric_operators() {
let ctx = create_test_context();
let result = evaluate_condition(
&ctx,
&IAMOperator::NumericEquals,
"numeric_key",
&serde_json::Value::Number(serde_json::Number::from_f64(42.0).unwrap()),
)
.unwrap();
assert!(result);
let result = evaluate_condition(
&ctx,
&IAMOperator::NumericLessThan,
"numeric_key",
&serde_json::Value::Number(serde_json::Number::from_f64(50.0).unwrap()),
)
.unwrap();
assert!(result);
let result = evaluate_condition(
&ctx,
&IAMOperator::NumericGreaterThan,
"numeric_key",
&serde_json::Value::Number(serde_json::Number::from_f64(30.0).unwrap()),
)
.unwrap();
assert!(result);
let result = evaluate_condition(
&ctx,
&IAMOperator::NumericEquals,
"numeric_string",
&serde_json::Value::Number(serde_json::Number::from_f64(42.5).unwrap()),
)
.unwrap();
assert!(result);
}
#[test]
fn test_evaluate_condition_boolean() {
let ctx = create_test_context();
let result = evaluate_condition(
&ctx,
&IAMOperator::Bool,
"bool_key",
&serde_json::Value::Bool(true),
)
.unwrap();
assert!(result);
let result = evaluate_condition(
&ctx,
&IAMOperator::Bool,
"bool_key",
&serde_json::Value::Bool(false),
)
.unwrap();
assert!(!result);
}
#[test]
fn test_evaluate_condition_date_operators() {
let ctx = create_test_context();
let result = evaluate_condition(
&ctx,
&IAMOperator::DateEquals,
"datetime_key",
&serde_json::Value::String("2024-01-01T00:00:00Z".to_string()),
)
.unwrap();
assert!(result);
let result = evaluate_condition(
&ctx,
&IAMOperator::DateLessThan,
"datetime_key",
&serde_json::Value::String("2024-12-31T23:59:59Z".to_string()),
)
.unwrap();
assert!(result);
let result = evaluate_condition(
&ctx,
&IAMOperator::DateEquals,
"date_key",
&serde_json::Value::String("2024-01-01T00:00:00Z".to_string()),
)
.unwrap();
assert!(result);
let result = evaluate_condition(
&ctx,
&IAMOperator::DateEquals,
"datetime_key",
&serde_json::Value::String("1704067200".to_string()), )
.unwrap();
assert!(result);
}
#[test]
fn test_evaluate_condition_ip_address() {
let ctx = create_test_context();
let result = evaluate_condition(
&ctx,
&IAMOperator::IpAddress,
"ip_key",
&serde_json::Value::String("192.168.1.0/24".to_string()),
)
.unwrap();
assert!(result);
let result = evaluate_condition(
&ctx,
&IAMOperator::NotIpAddress,
"ip_key",
&serde_json::Value::String("10.0.0.0/8".to_string()),
)
.unwrap();
assert!(result);
}
#[test]
fn test_evaluate_condition_null() {
let ctx = create_test_context();
let result = evaluate_condition(
&ctx,
&IAMOperator::Null,
"string_key",
&serde_json::Value::Bool(true),
)
.unwrap();
assert!(!result);
let result = evaluate_condition(
&ctx,
&IAMOperator::Null,
"nonexistent_key",
&serde_json::Value::Bool(true),
)
.unwrap();
assert!(result);
let result = evaluate_condition(
&ctx,
&IAMOperator::Null,
"string_key",
&serde_json::Value::Bool(false),
)
.unwrap();
assert!(result);
}
#[test]
fn test_evaluate_condition_if_exists_operators() {
let ctx = create_test_context();
let result = evaluate_condition(
&ctx,
&IAMOperator::StringEqualsIfExists,
"string_key",
&serde_json::Value::String("test_value".to_string()),
)
.unwrap();
assert!(result);
let result = evaluate_condition(
&ctx,
&IAMOperator::StringEqualsIfExists,
"nonexistent_key",
&serde_json::Value::String("any_value".to_string()),
)
.unwrap();
assert!(result);
let result = evaluate_condition(
&ctx,
&IAMOperator::StringEqualsIfExists,
"string_key",
&serde_json::Value::String("different_value".to_string()),
)
.unwrap();
assert!(!result);
}
#[test]
fn test_evaluate_condition_for_any_value() {
let ctx = create_test_context();
let result = evaluate_condition(
&ctx,
&IAMOperator::ForAnyValueStringEquals,
"string_list",
&serde_json::Value::String("value2".to_string()),
)
.unwrap();
assert!(result);
let result = evaluate_condition(
&ctx,
&IAMOperator::ForAnyValueStringEquals,
"string_list",
&serde_json::Value::String("nonexistent".to_string()),
)
.unwrap();
assert!(!result);
let result = evaluate_condition(
&ctx,
&IAMOperator::ForAnyValueBool,
"bool_list",
&serde_json::Value::Bool(false),
)
.unwrap();
assert!(result);
}
#[test]
fn test_evaluate_condition_for_all_values() {
let ctx = create_test_context();
let mut ctx_all_same = Context::new();
ctx_all_same.insert(
"all_same".to_string(),
ContextValue::StringList(vec![
"same".to_string(),
"same".to_string(),
"same".to_string(),
]),
);
let result = evaluate_condition(
&ctx_all_same,
&IAMOperator::ForAllValuesStringEquals,
"all_same",
&serde_json::Value::String("same".to_string()),
)
.unwrap();
assert!(result);
let result = evaluate_condition(
&ctx,
&IAMOperator::ForAllValuesStringEquals,
"string_list",
&serde_json::Value::String("value1".to_string()),
)
.unwrap();
assert!(!result);
}
#[test]
fn test_evaluate_condition_array_values() {
let ctx = create_test_context();
let array_value = serde_json::Value::Array(vec![
serde_json::Value::String("test_value".to_string()),
serde_json::Value::String("other_value".to_string()),
]);
let result =
evaluate_condition(&ctx, &IAMOperator::StringEquals, "string_key", &array_value)
.unwrap();
assert!(result); }
#[test]
fn test_evaluate_condition_type_mismatches() {
let ctx = create_test_context();
let result = evaluate_condition(
&ctx,
&IAMOperator::StringEquals,
"numeric_key",
&serde_json::Value::String("test".to_string()),
)
.unwrap();
assert!(!result);
let ctx_with_unparseable = Context::new().with_string("unparseable_string", "not_a_number");
let result = evaluate_condition(
&ctx_with_unparseable,
&IAMOperator::NumericEquals,
"unparseable_string",
&serde_json::Value::Number(serde_json::Number::from(42)),
);
assert!(result.is_err());
let result = evaluate_condition(
&ctx,
&IAMOperator::Bool,
"string_key",
&serde_json::Value::Bool(true),
)
.unwrap();
assert!(!result);
}
#[test]
fn test_evaluate_condition_errors() {
let ctx = create_test_context();
let result = evaluate_condition(
&ctx,
&IAMOperator::StringEquals,
"string_key",
&serde_json::Value::Number(serde_json::Number::from(42)),
);
assert!(result.is_err());
let result = evaluate_condition(
&ctx,
&IAMOperator::NumericEquals,
"numeric_key",
&serde_json::Value::String("not_a_number".to_string()),
);
assert!(result.is_err());
let result = evaluate_condition(
&ctx,
&IAMOperator::Bool,
"bool_key",
&serde_json::Value::String("not_a_bool".to_string()),
);
assert!(result.is_err());
let result = evaluate_condition(
&ctx,
&IAMOperator::DateEquals,
"date_key",
&serde_json::Value::String("invalid_date".to_string()),
);
assert!(result.is_err());
let result = evaluate_condition(
&ctx,
&IAMOperator::IpAddress,
"ip_key",
&serde_json::Value::String("invalid_ip".to_string()),
);
assert!(result.is_err());
let result = evaluate_condition(
&ctx,
&IAMOperator::Null,
"string_key",
&serde_json::Value::String("not_a_bool".to_string()),
);
assert!(result.is_err());
let result = evaluate_condition(
&ctx,
&IAMOperator::StringEquals,
"string_list",
&serde_json::Value::String("value1".to_string()),
);
assert!(result.is_err());
}
#[test]
fn test_evaluate_condition_binary_equals() {
let ctx = Context::new().with_string("binary_key", "SGVsbG8gV29ybGQ=");
let result = evaluate_condition(
&ctx,
&IAMOperator::BinaryEquals,
"binary_key",
&serde_json::Value::String("SGVsbG8gV29ybGQ=".to_string()),
)
.unwrap();
assert!(result);
let result = evaluate_condition(
&ctx,
&IAMOperator::BinaryEquals,
"binary_key",
&serde_json::Value::String("sGVsbG8gV29ybGQ".to_string()), )
.unwrap();
assert!(result);
}
#[test]
fn test_parse_date() {
let result = parse_date("2024-01-01T00:00:00Z");
assert!(result.is_ok());
let result = parse_date("1704067200"); assert!(result.is_ok());
let result = parse_date("invalid_date");
assert!(result.is_err());
}
#[test]
fn test_wildcard_match() {
assert!(wildcard_match("hello", "hello"));
assert!(wildcard_match("hello", "h*"));
assert!(wildcard_match("hello", "*llo"));
assert!(wildcard_match("hello", "h*o"));
assert!(!wildcard_match("hello", "world"));
assert!(!wildcard_match("hello", "h*x"));
}
#[test]
fn test_set_operator_type_detection() {
let ctx = create_test_context();
let result = evaluate_condition(
&ctx,
&IAMOperator::ForAnyValueStringEquals,
"string_list",
&serde_json::Value::String("value1".to_string()),
)
.unwrap();
assert!(result);
let mut ctx_all_same = Context::new();
ctx_all_same.insert(
"all_same".to_string(),
ContextValue::StringList(vec!["same".to_string(), "same".to_string()]),
);
let result = evaluate_condition(
&ctx_all_same,
&IAMOperator::ForAllValuesStringEquals,
"all_same",
&serde_json::Value::String("same".to_string()),
)
.unwrap();
assert!(result);
}
#[test]
fn test_arn_operators() {
let ctx = Context::new().with_string("arn_key", "arn:aws:s3:::my-bucket/*");
let result = evaluate_condition(
&ctx,
&IAMOperator::ArnEquals,
"arn_key",
&serde_json::Value::String("arn:aws:s3:::my-bucket/*".to_string()),
)
.unwrap();
assert!(result);
let result = evaluate_condition(
&ctx,
&IAMOperator::ArnLike,
"arn_key",
&serde_json::Value::String("arn:aws:s3:::my-bucket*".to_string()),
)
.unwrap();
assert!(result);
let result = evaluate_condition(
&ctx,
&IAMOperator::ArnNotEquals,
"arn_key",
&serde_json::Value::String("arn:aws:s3:::other-bucket/*".to_string()),
)
.unwrap();
assert!(result);
}
#[test]
fn test_context_value_parsing_edge_cases() {
let ctx = Context::new()
.with_string("invalid_numeric", "not_a_number")
.with_string("invalid_ip", "not_an_ip")
.with_string("invalid_date", "not_a_date");
let result = evaluate_condition(
&ctx,
&IAMOperator::NumericEquals,
"invalid_numeric",
&serde_json::Value::Number(serde_json::Number::from(42)),
);
assert!(result.is_err());
let result = evaluate_condition(
&ctx,
&IAMOperator::IpAddress,
"invalid_ip",
&serde_json::Value::String("192.168.1.0/24".to_string()),
);
assert!(result.is_err());
let result = evaluate_condition(
&ctx,
&IAMOperator::DateEquals,
"invalid_date",
&serde_json::Value::String("2024-01-01T00:00:00Z".to_string()),
);
assert!(result.is_err());
}
#[test]
fn test_missing_context_keys_non_if_exists() {
let ctx = Context::new();
let result = evaluate_condition(
&ctx,
&IAMOperator::StringEquals,
"missing_key",
&serde_json::Value::String("any_value".to_string()),
)
.unwrap();
assert!(!result);
let _result = evaluate_condition(
&ctx,
&IAMOperator::StringNotEquals,
"missing_key",
&serde_json::Value::String("any_value".to_string()),
)
.unwrap();
}
}