Skip to content

Commit

Permalink
feat: add EnforceEx (#134)
Browse files Browse the repository at this point in the history
* feat: add EnforceEx

Signed-off-by: Zxilly <zhouxinyu1001@gmail.com>

* fix: wrong implement

Signed-off-by: Zxilly <zhouxinyu1001@gmail.com>

* feat: new effector interface

BREAKING CHANGE: Custom effectors will need a rewrite

Signed-off-by: Andreas Bichinger <andreas.bichinger@gmail.com>

Co-authored-by: Andreas Bichinger <andreas.bichinger@gmail.com>
  • Loading branch information
Zxilly and abichinger authored Apr 6, 2021
1 parent fd624fc commit c577e1d
Show file tree
Hide file tree
Showing 7 changed files with 154 additions and 69 deletions.
29 changes: 21 additions & 8 deletions casbin/core_enforcer.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import logging

from casbin.effect import DefaultEffector, Effector
from casbin.effect import Effector, get_effector, effect_to_bool
from casbin.model import Model, FunctionMap
from casbin.persist import Adapter
from casbin.persist.adapters import FileAdapter
Expand Down Expand Up @@ -70,7 +70,7 @@ def init_with_model_and_adapter(self, m, adapter=None):

def _initialize(self):
self.rm_map = dict()
self.eft = DefaultEffector()
self.eft = get_effector(self.model.model["e"]["e"].value)
self.watcher = None

self.enabled = True
Expand Down Expand Up @@ -242,6 +242,15 @@ def enforce(self, *rvals):
"""decides whether a "subject" can access a "object" with the operation "action",
input parameters are usually: (sub, obj, act).
"""
result, _ = self.enforceEx(*rvals)
return result

def enforceEx(self, *rvals):
"""decides whether a "subject" can access a "object" with the operation "action",
input parameters are usually: (sub, obj, act).
return judge result with reason
"""
explain_index = -1

if not self.enabled:
return False
Expand Down Expand Up @@ -271,12 +280,12 @@ def enforce(self, *rvals):
expression = self._get_expression(exp_string, functions)

policy_effects = set()
matcher_results = set()

r_parameters = dict(zip(r_tokens, rvals))

policy_len = len(self.model.model["p"]["p"].policy)

explain_index = -1
if not 0 == policy_len:
for i, pvals in enumerate(self.model.model["p"]["p"].policy):
if len(p_tokens) != len(pvals):
Expand All @@ -301,8 +310,6 @@ def enforce(self, *rvals):
if 0 == result:
policy_effects.add(Effector.INDETERMINATE)
continue
else:
matcher_results.add(result)
else:
raise RuntimeError("matcher result should be bool, int or float")

Expand All @@ -317,7 +324,8 @@ def enforce(self, *rvals):
else:
policy_effects.add(Effector.ALLOW)

if "priority(p_eft) || deny" == self.model.model["e"]["e"].value:
if self.eft.intermediate_effect(policy_effects) != Effector.INDETERMINATE:
explain_index = i
break

else:
Expand All @@ -336,7 +344,8 @@ def enforce(self, *rvals):
else:
policy_effects.add(Effector.INDETERMINATE)

result = self.eft.merge_effects(self.model.model["e"]["e"].value, policy_effects, matcher_results)
final_effect = self.eft.final_effect(policy_effects)
result = effect_to_bool(final_effect)

# Log request.

Expand All @@ -350,7 +359,11 @@ def enforce(self, *rvals):
# leaving this in error for now, if it's very noise this can be changed to info or debug
self.logger.error(req_str)

return result
explain_rule = []
if explain_index != -1 and explain_index < policy_len:
explain_rule = self.model.model["p"]["p"].policy[explain_index]

return result, explain_rule

@staticmethod
def _get_expression(expr, functions=None):
Expand Down
24 changes: 23 additions & 1 deletion casbin/effect/__init__.py
Original file line number Diff line number Diff line change
@@ -1,2 +1,24 @@
from .default_effector import DefaultEffector
from .default_effectors import AllowOverrideEffector, DenyOverrideEffector, AllowAndDenyEffector, PriorityEffector
from .effector import Effector

def get_effector(expr):
''' creates an effector based on the current policy effect expression '''

if expr == "some(where (p_eft == allow))":
return AllowOverrideEffector()
elif expr == "!some(where (p_eft == deny))":
return DenyOverrideEffector()
elif expr == "some(where (p_eft == allow)) && !some(where (p_eft == deny))":
return AllowAndDenyEffector()
elif expr == "priority(p_eft) || deny":
return PriorityEffector()
else:
raise RuntimeError("unsupported effect")

def effect_to_bool(effect):
""" """
if effect == Effector.ALLOW:
return True
if effect == Effector.DENY:
return False
raise RuntimeError("effect can't be converted to boolean")
39 changes: 0 additions & 39 deletions casbin/effect/default_effector.py

This file was deleted.

61 changes: 61 additions & 0 deletions casbin/effect/default_effectors.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
from .effector import Effector

class AllowOverrideEffector(Effector):

def intermediate_effect(self, effects):
""" returns a intermediate effect based on the matched effects of the enforcer """
if Effector.ALLOW in effects:
return Effector.ALLOW
return Effector.INDETERMINATE

def final_effect(self, effects):
""" returns the final effect based on the matched effects of the enforcer """
if Effector.ALLOW in effects:
return Effector.ALLOW
return Effector.DENY

class DenyOverrideEffector(Effector):

def intermediate_effect(self, effects):
""" returns a intermediate effect based on the matched effects of the enforcer """
if Effector.DENY in effects:
return Effector.DENY
return Effector.INDETERMINATE

def final_effect(self, effects):
""" returns the final effect based on the matched effects of the enforcer """
if Effector.DENY in effects:
return Effector.DENY
return Effector.ALLOW

class AllowAndDenyEffector(Effector):

def intermediate_effect(self, effects):
""" returns a intermediate effect based on the matched effects of the enforcer """
if Effector.DENY in effects:
return Effector.DENY
return Effector.INDETERMINATE

def final_effect(self, effects):
""" returns the final effect based on the matched effects of the enforcer """
if Effector.DENY in effects or Effector.ALLOW not in effects:
return Effector.DENY
return Effector.ALLOW

class PriorityEffector(Effector):

def intermediate_effect(self, effects):
""" returns a intermediate effect based on the matched effects of the enforcer """
if Effector.ALLOW in effects:
return Effector.ALLOW
if Effector.DENY in effects:
return Effector.DENY
return Effector.INDETERMINATE

def final_effect(self, effects):
""" returns the final effect based on the matched effects of the enforcer """
if Effector.ALLOW in effects:
return Effector.ALLOW
if Effector.DENY in effects:
return Effector.DENY
return Effector.DENY
11 changes: 9 additions & 2 deletions casbin/effect/effector.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,13 @@ class Effector:

DENY = 2

def merge_effects(self, expr, effects, results):
"""merges all matching results collected by the enforcer into a single decision."""
def intermediate_effect(self, effects):
""" returns a intermediate effect based on the matched effects of the enforcer """
pass

def final_effect(self, effects):
""" returns the final effect based on the matched effects of the enforcer """
pass



8 changes: 8 additions & 0 deletions casbin/synced_enforcer.py
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,14 @@ def enforce(self, *rvals):
with self._rl:
return self._e.enforce(*rvals)

def enforceEx(self, *rvals):
"""decides whether a "subject" can access a "object" with the operation "action",
input parameters are usually: (sub, obj, act).
return judge result with reason
"""
with self._rl:
return self._e.enforceEx(*rvals)

def get_all_subjects(self):
"""gets the list of subjects that show up in the current policy."""
with self._rl:
Expand Down
Loading

0 comments on commit c577e1d

Please sign in to comment.