///|
/// Condition status in a rule inspection. A condition is not evaluated when
/// the rule's subject, action, or resource selector does not match.
pub(all) enum ConditionStatus {
NotEvaluated
Satisfied
Unsatisfied
Unknown
} derive(Debug, Eq)
///|
/// The selector and condition results for one rule during authorization.
pub(all) struct RuleEvaluation {
id : String
effect : Effect
subject_matches : Bool
action_matches : Bool
resource_matches : Bool
condition_status : ConditionStatus
applied : Bool
} derive(Debug, Eq)
///|
/// A decision accompanied by an inspection of every explicit rule.
pub(all) struct EvaluationReport {
decision : Decision
rules : Array[RuleEvaluation]
} derive(Debug, Eq)
///|
fn condition_status_to_string(status : ConditionStatus) -> String {
match status {
NotEvaluated => "not_evaluated"
Satisfied => "satisfied"
Unsatisfied => "unsatisfied"
Unknown => "unknown"
}
}
///|
fn RuleEvaluation::to_json(self : RuleEvaluation) -> Json {
Json::object({
"id": Json::string(self.id),
"effect": Json::string(
match self.effect {
Allow => "allow"
Deny => "deny"
},
),
"subject_matches": Json::boolean(self.subject_matches),
"action_matches": Json::boolean(self.action_matches),
"resource_matches": Json::boolean(self.resource_matches),
"condition_status": Json::string(
condition_status_to_string(self.condition_status),
),
"applied": Json::boolean(self.applied),
})
}
///|
/// Serialize an explanation as a decision plus ordered rule inspections.
pub fn EvaluationReport::to_json(self : EvaluationReport) -> Json {
Json::object({
"decision": self.decision.to_json(),
"rules": Json::array([ for rule in self.rules => rule.to_json() ]),
})
}
///|
fn inspect_rule(rule : Rule, request : Request) -> RuleEvaluation {
let subject_matches = matcher_matches(rule.subject, request.subject)
let action_matches = matcher_matches(rule.action, request.action)
let resource_matches = matcher_matches(rule.resource, request.resource)
let selector_matches = subject_matches && action_matches && resource_matches
let condition_status = if selector_matches {
match evaluate_condition(rule.condition, request) {
ConditionTrue => Satisfied
ConditionFalse => Unsatisfied
ConditionUnknown => Unknown
}
} else {
NotEvaluated
}
let applied = match (rule.effect, condition_status) {
(_, Satisfied) => true
(Deny, Unknown) => true
_ => false
}
{
id: rule.id,
effect: rule.effect,
subject_matches,
action_matches,
resource_matches,
condition_status,
applied,
}
}
///|
/// Explain each rule's contribution while preserving `authorize` semantics.
/// Explicit deny rules with an unknown condition are marked as applied because
/// MoonPolicy treats them conservatively after their selectors match.
pub fn Policy::explain(self : Policy, request : Request) -> EvaluationReport {
let rules : Array[RuleEvaluation] = []
for rule in self.rules {
rules.push(inspect_rule(rule, request))
}
{ decision: self.authorize(request), rules }
}