use crate::compiled::CompiledPolicy;
use crate::context::EvalContext;
use crate::decision::{Decision, Outcome};
use crate::eval::{evaluate_strict, evaluate3};
#[derive(Debug, Clone)]
pub struct Divergence {
pub primary: Decision,
pub shadow: Decision,
}
impl Divergence {
pub fn shadow_would_deny(&self) -> bool {
self.primary.outcome == Outcome::Allow && self.shadow.outcome == Outcome::Deny
}
pub fn shadow_would_allow(&self) -> bool {
self.primary.outcome == Outcome::Deny && self.shadow.outcome == Outcome::Allow
}
}
pub fn enforce<F>(
primary: &CompiledPolicy,
shadow: Option<&CompiledPolicy>,
ctx: &EvalContext,
on_divergence: F,
) -> Decision
where
F: FnOnce(Divergence),
{
let decision = evaluate_strict(primary, ctx);
if let Some(shadow_policy) = shadow {
let shadow_decision = evaluate3(shadow_policy, ctx);
if decision.outcome != shadow_decision.outcome {
on_divergence(Divergence {
primary: decision.clone(),
shadow: shadow_decision,
});
}
}
decision
}
pub fn enforce_simple(policy: &CompiledPolicy, ctx: &EvalContext) -> Decision {
evaluate_strict(policy, ctx)
}
#[cfg(test)]
#[allow(clippy::disallowed_methods)]
mod tests {
use super::*;
use crate::compile::compile;
use crate::expr::Expr;
use crate::types::CanonicalDid;
use chrono::Utc;
use std::cell::RefCell;
fn did(s: &str) -> CanonicalDid {
CanonicalDid::parse(s).unwrap()
}
fn base_ctx() -> EvalContext {
EvalContext::new(Utc::now(), did("did:keri:issuer"), did("did:keri:subject"))
}
#[test]
fn enforce_no_shadow() {
let policy = compile(&Expr::True).unwrap();
let ctx = base_ctx();
let called = RefCell::new(false);
let decision = enforce(&policy, None, &ctx, |_| {
*called.borrow_mut() = true;
});
assert!(decision.is_allowed());
assert!(!*called.borrow()); }
#[test]
fn enforce_shadow_agrees() {
let primary = compile(&Expr::True).unwrap();
let shadow = compile(&Expr::True).unwrap();
let ctx = base_ctx();
let called = RefCell::new(false);
let decision = enforce(&primary, Some(&shadow), &ctx, |_| {
*called.borrow_mut() = true;
});
assert!(decision.is_allowed());
assert!(!*called.borrow()); }
#[test]
fn enforce_shadow_diverges() {
let primary = compile(&Expr::True).unwrap();
let shadow = compile(&Expr::False).unwrap();
let ctx = base_ctx();
let divergence = RefCell::new(None);
let decision = enforce(&primary, Some(&shadow), &ctx, |div| {
*divergence.borrow_mut() = Some(div);
});
assert!(decision.is_allowed()); let div = divergence.borrow();
let div = div.as_ref().expect("divergence should be captured");
assert!(div.shadow_would_deny());
assert!(!div.shadow_would_allow());
}
#[test]
fn enforce_shadow_would_allow() {
let primary = compile(&Expr::False).unwrap();
let shadow = compile(&Expr::True).unwrap();
let ctx = base_ctx();
let divergence = RefCell::new(None);
let decision = enforce(&primary, Some(&shadow), &ctx, |div| {
*divergence.borrow_mut() = Some(div);
});
assert!(decision.is_denied()); let div = divergence.borrow();
let div = div.as_ref().expect("divergence should be captured");
assert!(!div.shadow_would_deny());
assert!(div.shadow_would_allow());
}
#[test]
fn enforce_simple_works() {
let policy = compile(&Expr::NotRevoked).unwrap();
let ctx = base_ctx().revoked(false);
let decision = enforce_simple(&policy, &ctx);
assert!(decision.is_allowed());
}
#[test]
fn enforce_strict_converts_indeterminate() {
let primary = compile(&Expr::RepoIs("org/repo".into())).unwrap();
let ctx = base_ctx();
let decision = enforce_simple(&primary, &ctx);
assert!(decision.is_denied()); }
#[test]
fn divergence_indeterminate_vs_allow() {
let primary = compile(&Expr::RepoIs("org/repo".into())).unwrap();
let shadow = compile(&Expr::True).unwrap();
let ctx = base_ctx();
let divergence = RefCell::new(None);
let decision = enforce(&primary, Some(&shadow), &ctx, |div| {
*divergence.borrow_mut() = Some(div);
});
assert!(decision.is_denied()); let div = divergence.borrow();
let div = div.as_ref().expect("divergence should be captured");
assert!(div.shadow_would_allow()); }
}