use serde::{Deserialize, Serialize};
use super::classification::{ClassifiedData, SecurityClassification};
use super::identity::IdentityContext;
#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize, Deserialize)]
pub enum PolicyDecision {
Allow,
Deny,
Audit,
}
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
pub struct PolicyRule {
pub condition: String,
pub action: String,
pub evidence_required: bool,
}
impl PolicyRule {
#[must_use]
pub fn new(
condition: impl Into<String>,
action: impl Into<String>,
evidence_required: bool,
) -> Self {
Self {
condition: condition.into(),
action: action.into(),
evidence_required,
}
}
}
pub trait PolicyContext {
fn classification(&self) -> Option<SecurityClassification>;
fn identity(&self) -> Option<&IdentityContext>;
}
impl PolicyContext for IdentityContext {
fn classification(&self) -> Option<SecurityClassification> {
None
}
fn identity(&self) -> Option<&IdentityContext> {
Some(self)
}
}
impl PolicyContext for SecurityClassification {
fn classification(&self) -> Option<SecurityClassification> {
Some(*self)
}
fn identity(&self) -> Option<&IdentityContext> {
None
}
}
impl<T> PolicyContext for ClassifiedData<T> {
fn classification(&self) -> Option<SecurityClassification> {
Some(self.classification)
}
fn identity(&self) -> Option<&IdentityContext> {
None
}
}
pub trait PolicyEvaluator {
fn evaluate(&self, context: &dyn PolicyContext, action: &str) -> PolicyDecision;
}
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
pub struct AllowAll;
impl PolicyEvaluator for AllowAll {
fn evaluate(&self, _context: &dyn PolicyContext, _action: &str) -> PolicyDecision {
PolicyDecision::Allow
}
}
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
pub struct DenyAll;
impl PolicyEvaluator for DenyAll {
fn evaluate(&self, _context: &dyn PolicyContext, _action: &str) -> PolicyDecision {
PolicyDecision::Deny
}
}
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
pub struct ClassificationBased {
pub maximum_allowed: SecurityClassification,
#[serde(default)]
pub rules: Vec<PolicyRule>,
}
impl ClassificationBased {
#[must_use]
pub fn new(maximum_allowed: SecurityClassification) -> Self {
Self {
maximum_allowed,
rules: Vec::new(),
}
}
#[must_use]
pub fn from_rules(maximum_allowed: SecurityClassification, rules: Vec<PolicyRule>) -> Self {
Self {
maximum_allowed,
rules,
}
}
#[must_use]
pub fn with_rule(mut self, rule: PolicyRule) -> Self {
self.rules.push(rule);
self
}
pub fn add_rule(&mut self, rule: PolicyRule) {
self.rules.push(rule);
}
}
impl PolicyEvaluator for ClassificationBased {
fn evaluate(&self, context: &dyn PolicyContext, action: &str) -> PolicyDecision {
let Some(classification) = context.classification() else {
return PolicyDecision::Audit;
};
if classification > self.maximum_allowed {
return PolicyDecision::Deny;
}
for rule in &self.rules {
if action_matches(&rule.action, action)
&& condition_matches(&rule.condition, classification)
&& rule.evidence_required
{
return PolicyDecision::Audit;
}
}
PolicyDecision::Allow
}
}
fn action_matches(pattern: &str, action: &str) -> bool {
let pattern = pattern.trim();
pattern == "*"
|| pattern.eq_ignore_ascii_case(action.trim())
|| pattern
.strip_suffix('*')
.is_some_and(|prefix| action.trim().starts_with(prefix))
}
fn condition_matches(condition: &str, classification: SecurityClassification) -> bool {
let condition = condition.trim();
if condition.is_empty() || condition == "*" || condition.eq_ignore_ascii_case("always") {
return true;
}
let (operator, value) = if let Some(value) = condition.strip_prefix("classification>=") {
(">=", value)
} else if let Some(value) = condition.strip_prefix("classification<=") {
("<=", value)
} else if let Some(value) = condition.strip_prefix("classification=") {
("=", value)
} else if let Some(value) = condition.strip_prefix("classification:") {
("=", value)
} else {
("=", condition)
};
let Some(required) = SecurityClassification::parse(value) else {
return false;
};
match operator {
">=" => classification >= required,
"<=" => classification <= required,
"=" => classification == required,
_ => false,
}
}