///|
pub(all) enum DecisionChangeKind {
NewlyAllowed
NewlyDenied
Unchanged
} derive(Debug, Eq)
///|
pub(all) struct DecisionChange {
request : CheckRequest
kind : DecisionChangeKind
before : CheckResult
after : CheckResult
} derive(Debug)
///|
pub(all) struct PolicyImpactReport {
changes : Array[DecisionChange]
newly_allowed : Int
newly_denied : Int
unchanged : Int
} derive(Debug)
///|
/// Compares two policy engines over an explicit, deterministic request set.
///
/// Newly allowed decisions represent privilege expansion and should normally
/// receive security review before deployment.
pub fn analyze_policy_impact(
before : PolicyEngine,
after : PolicyEngine,
requests : Array[CheckRequest],
) -> PolicyImpactReport {
let changes : Array[DecisionChange] = []
let mut newly_allowed = 0
let mut newly_denied = 0
let mut unchanged = 0
for request in requests {
let old_result = before.check(
request.object,
request.permission,
request.principal,
)
let new_result = after.check(
request.object,
request.permission,
request.principal,
)
let kind = if !old_result.allowed && new_result.allowed {
newly_allowed = newly_allowed + 1
NewlyAllowed
} else if old_result.allowed && !new_result.allowed {
newly_denied = newly_denied + 1
NewlyDenied
} else {
unchanged = unchanged + 1
Unchanged
}
changes.push({ request, kind, before: old_result, after: new_result })
}
{ changes, newly_allowed, newly_denied, unchanged }
}
///|
pub fn PolicyImpactReport::has_privilege_expansion(
self : PolicyImpactReport,
) -> Bool {
self.newly_allowed > 0
}
///|
pub fn PolicyImpactReport::to_json(self : PolicyImpactReport) -> String {
"{\"requests\":\{self.changes.length()},\"newly_allowed\":\{self.newly_allowed},\"newly_denied\":\{self.newly_denied},\"unchanged\":\{self.unchanged},\"privilege_expansion\":\{self.has_privilege_expansion()}}"
}