use std::fmt;
#[derive(Debug, PartialEq, Clone)]
pub enum CombineOp {
And,
Or,
Not,
Delegate,
}
impl fmt::Display for CombineOp {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
CombineOp::And => write!(f, "AND"),
CombineOp::Or => write!(f, "OR"),
CombineOp::Not => write!(f, "NOT"),
CombineOp::Delegate => write!(f, "DELEGATE"),
}
}
}
#[derive(Debug, Clone)]
pub enum PolicyEvalResult {
Granted {
policy_type: String,
reason: Option<String>,
},
Denied {
policy_type: String,
reason: String,
},
Combined {
policy_type: String,
operation: CombineOp,
children: Vec<PolicyEvalResult>,
outcome: bool,
},
}
#[derive(Debug, Clone)]
pub enum AccessEvaluation {
Granted {
policy_type: String,
reason: Option<String>,
trace: EvalTrace,
},
Denied {
trace: EvalTrace,
reason: String,
},
}
impl AccessEvaluation {
pub fn is_granted(&self) -> bool {
matches!(self, Self::Granted { .. })
}
pub fn to_result<E>(&self, error_fn: impl FnOnce(&str) -> E) -> Result<(), E> {
match self {
Self::Granted { .. } => Ok(()),
Self::Denied { reason, .. } => Err(error_fn(reason)),
}
}
pub fn display_trace(&self) -> String {
let trace = match self {
AccessEvaluation::Granted {
policy_type: _,
reason: _,
trace,
} => trace,
AccessEvaluation::Denied { reason: _, trace } => trace,
};
let trace_str = trace.format();
if trace_str == "No evaluation trace available" {
format!("{}\n(No evaluation trace available)", self)
} else {
format!("{}\nEvaluation Trace:\n{}", self, trace_str)
}
}
}
impl fmt::Display for AccessEvaluation {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Granted {
policy_type,
reason,
trace: _,
} => {
match reason {
Some(r) => write!(f, "[GRANTED] by {} - {}", policy_type, r),
None => write!(f, "[GRANTED] by {}", policy_type),
}
}
Self::Denied { reason, trace: _ } => {
write!(f, "[Denied] - {}", reason)
}
}
}
}
#[derive(Debug, Clone, Default)]
pub struct EvalTrace {
root: Option<PolicyEvalResult>,
}
impl EvalTrace {
pub fn new() -> Self {
Self { root: None }
}
pub fn with_root(result: PolicyEvalResult) -> Self {
Self { root: Some(result) }
}
pub fn set_root(&mut self, result: PolicyEvalResult) {
self.root = Some(result);
}
pub fn root(&self) -> Option<&PolicyEvalResult> {
self.root.as_ref()
}
pub fn format(&self) -> String {
match &self.root {
Some(root) => root.format(0),
None => "No evaluation trace available".to_string(),
}
}
}
impl PolicyEvalResult {
pub fn is_granted(&self) -> bool {
match self {
Self::Granted { .. } => true,
Self::Denied { .. } => false,
Self::Combined { outcome, .. } => *outcome,
}
}
pub fn reason(&self) -> Option<String> {
match self {
Self::Granted { reason, .. } => reason.clone(),
Self::Denied { reason, .. } => Some(reason.clone()),
Self::Combined { .. } => None,
}
}
pub fn format(&self, indent: usize) -> String {
let indent_str = " ".repeat(indent);
match self {
Self::Granted {
policy_type,
reason,
} => {
let reason_text = reason
.as_ref()
.map_or("".to_string(), |r| format!(": {}", r));
format!("{}✔ {} GRANTED{}", indent_str, policy_type, reason_text)
}
Self::Denied {
policy_type,
reason,
} => {
format!("{}✘ {} DENIED: {}", indent_str, policy_type, reason)
}
Self::Combined {
policy_type,
operation,
children,
outcome,
} => {
let outcome_char = if *outcome { "✔" } else { "✘" };
let mut result = format!(
"{}{} {} ({})",
indent_str, outcome_char, policy_type, operation
);
for child in children {
result.push_str(&format!("\n{}", child.format(indent + 2)));
}
result
}
}
}
}
impl fmt::Display for PolicyEvalResult {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let tree = self.format(0);
write!(f, "{}", tree)
}
}