// effect.mbt — Policy effect expressions and effect merging.
//
// Casbin resolves a request to a decision by evaluating the matcher once
// per policy row and merging the per-row effects through the expression in
// `[policy_effect]`. The five canonical expressions and the merge rules of
// `DefaultEffector.MergeEffects` are reproduced here; the strings match
// Casbin's constants after `p.eft` has been escaped to `p_eft`.
//
// `merge_effects` is called after each row with the current row index and
// the total row count. `Indeterminate` means "no decision yet, keep
// scanning"; the enforcer stops at the first decisive result. The returned
// index explains which row produced the decision, or `-1`.
///|
/// The decision contributed by one policy row, or by the single-evaluation
/// branch when the matcher does not reference the policy.
pub(all) enum Effect {
Allow
/// No decision yet.
Indeterminate
Deny
} derive(Eq, Debug)
///|
/// The five policy effect expressions Casbin supports.
pub(all) enum EffectExpression {
AllowOverride
DenyOverride
AllowAndDeny
Priority
SubjectPriority
} derive(Eq, Debug)
///|
/// Parses the canonical effect strings; the text must already be
/// preprocessed (`p.eft` escaped to `p_eft`).
pub fn EffectExpression::parse(text : String) -> EffectExpression? {
match text {
"some(where (p_eft == allow))" => Some(AllowOverride)
"!some(where (p_eft == deny))" => Some(DenyOverride)
"some(where (p_eft == allow)) && !some(where (p_eft == deny))" =>
Some(AllowAndDeny)
"priority(p_eft) || deny" => Some(Priority)
"subjectPriority(p_eft) || deny" => Some(SubjectPriority)
_ => None
}
}
///|
/// Merges the effects collected so far for the row at `policy_index`,
/// mirroring `DefaultEffector.MergeEffects`.
///
/// `matches[i]` records whether row `i` satisfied the matcher; `effects[i]`
/// is that row's effect. Both arrays have one entry per policy row.
pub fn merge_effects(
expression : EffectExpression,
effects : Array[Effect],
matches : Array[Bool],
policy_index : Int,
policy_length : Int,
) -> (Effect, Int) {
let mut result = Indeterminate
let mut explain_index = -1
match expression {
AllowOverride =>
// Only the current row matters.
if matches[policy_index] && effects[policy_index] == Allow {
result = Allow
explain_index = policy_index
}
DenyOverride =>
// A matched deny decides immediately; when the last row produces no
// deny, everything is allowed.
if matches[policy_index] && effects[policy_index] == Deny {
result = Deny
explain_index = policy_index
} else if policy_index == policy_length - 1 {
result = Allow
}
AllowAndDeny =>
if matches[policy_index] && effects[policy_index] == Deny {
result = Deny
explain_index = policy_index
} else if policy_index == policy_length - 1 {
// Scan everything once at the end and take the first matched allow.
let mut i = 0
while i < effects.length() {
if matches[i] && effects[i] == Allow {
result = Allow
explain_index = i
break
}
i += 1
}
}
Priority | SubjectPriority => {
// Reverse scan of the rows seen so far: the most recent matched row
// with a definite effect wins, which makes policy order the priority
// order.
let mut i = effects.length() - 1
while i >= 0 {
if matches[i] && effects[i] != Indeterminate {
result = if effects[i] == Allow { Allow } else { Deny }
explain_index = i
break
}
i -= 1
}
}
}
(result, explain_index)
}