use std::fmt;
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum PolicyDecision {
Allow,
Deny(DenialReason),
}
impl PolicyDecision {
pub fn is_allowed(&self) -> bool {
matches!(self, PolicyDecision::Allow)
}
pub fn is_denied(&self) -> bool {
matches!(self, PolicyDecision::Deny(_))
}
pub fn denial_reason(&self) -> Option<&DenialReason> {
match self {
PolicyDecision::Deny(reason) => Some(reason),
PolicyDecision::Allow => None,
}
}
}
impl fmt::Display for PolicyDecision {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
PolicyDecision::Allow => write!(f, "ALLOW"),
PolicyDecision::Deny(reason) => write!(f, "DENY: {reason}"),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum DenialReason {
Expired,
NotYetValid,
ScopeNotCovered,
Revoked,
RateLimitExceeded {
limit_type: String,
limit: u64,
current: u64,
},
IpBlocked { ip: String, reason: String },
InsufficientTrustLevel { required: String, actual: String },
DelegationDepthExceeded { max_depth: u32, actual_depth: u32 },
GeofenceViolation {
country: String,
allowed: Vec<String>,
},
OutsideTimeWindow,
ConfigAttestationMismatch {
expected: String,
actual: Option<String>,
},
ChainValidationFailed(String),
SignatureInvalid,
Custom(String),
}
impl fmt::Display for DenialReason {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Expired => write!(f, "DAT has expired"),
Self::NotYetValid => write!(f, "DAT is not yet valid"),
Self::ScopeNotCovered => write!(f, "requested scope not covered by DAT"),
Self::Revoked => write!(f, "DAT or delegation chain member has been revoked"),
Self::RateLimitExceeded {
limit_type,
limit,
current,
} => write!(
f,
"rate limit exceeded: {limit_type} limit {limit}, current {current}"
),
Self::IpBlocked { ip, reason } => write!(f, "IP {ip} blocked: {reason}"),
Self::InsufficientTrustLevel { required, actual } => {
write!(f, "trust level {actual} does not meet required {required}")
}
Self::DelegationDepthExceeded {
max_depth,
actual_depth,
} => write!(
f,
"delegation depth {actual_depth} exceeds maximum {max_depth}"
),
Self::GeofenceViolation { country, allowed } => {
write!(f, "country {country} not in allowed list: {allowed:?}")
}
Self::OutsideTimeWindow => write!(f, "request outside allowed time windows"),
Self::ConfigAttestationMismatch { expected, actual } => write!(
f,
"config attestation mismatch: expected {expected}, got {actual:?}"
),
Self::ChainValidationFailed(msg) => write!(f, "chain validation failed: {msg}"),
Self::SignatureInvalid => write!(f, "DAT signature is invalid"),
Self::Custom(msg) => write!(f, "{msg}"),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_policy_decision_allow() {
let d = PolicyDecision::Allow;
assert!(d.is_allowed());
assert!(!d.is_denied());
assert!(d.denial_reason().is_none());
assert_eq!(d.to_string(), "ALLOW");
}
#[test]
fn test_policy_decision_deny() {
let d = PolicyDecision::Deny(DenialReason::Expired);
assert!(!d.is_allowed());
assert!(d.is_denied());
assert_eq!(d.denial_reason(), Some(&DenialReason::Expired));
assert_eq!(d.to_string(), "DENY: DAT has expired");
}
#[test]
fn test_denial_reason_display() {
assert_eq!(
DenialReason::RateLimitExceeded {
limit_type: "hourly".into(),
limit: 100,
current: 101,
}
.to_string(),
"rate limit exceeded: hourly limit 100, current 101"
);
assert_eq!(
DenialReason::InsufficientTrustLevel {
required: "L2".into(),
actual: "L0".into(),
}
.to_string(),
"trust level L0 does not meet required L2"
);
assert_eq!(
DenialReason::DelegationDepthExceeded {
max_depth: 5,
actual_depth: 7,
}
.to_string(),
"delegation depth 7 exceeds maximum 5"
);
}
}