Skip to content

Commit c577e1d

Browse files
Zxillyabichinger
andauthored
feat: add EnforceEx (#134)
* 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>
1 parent fd624fc commit c577e1d

7 files changed

Lines changed: 154 additions & 69 deletions

File tree

casbin/core_enforcer.py

Lines changed: 21 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import logging
22

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

7171
def _initialize(self):
7272
self.rm_map = dict()
73-
self.eft = DefaultEffector()
73+
self.eft = get_effector(self.model.model["e"]["e"].value)
7474
self.watcher = None
7575

7676
self.enabled = True
@@ -242,6 +242,15 @@ def enforce(self, *rvals):
242242
"""decides whether a "subject" can access a "object" with the operation "action",
243243
input parameters are usually: (sub, obj, act).
244244
"""
245+
result, _ = self.enforceEx(*rvals)
246+
return result
247+
248+
def enforceEx(self, *rvals):
249+
"""decides whether a "subject" can access a "object" with the operation "action",
250+
input parameters are usually: (sub, obj, act).
251+
return judge result with reason
252+
"""
253+
explain_index = -1
245254

246255
if not self.enabled:
247256
return False
@@ -271,12 +280,12 @@ def enforce(self, *rvals):
271280
expression = self._get_expression(exp_string, functions)
272281

273282
policy_effects = set()
274-
matcher_results = set()
275283

276284
r_parameters = dict(zip(r_tokens, rvals))
277285

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

288+
explain_index = -1
280289
if not 0 == policy_len:
281290
for i, pvals in enumerate(self.model.model["p"]["p"].policy):
282291
if len(p_tokens) != len(pvals):
@@ -301,8 +310,6 @@ def enforce(self, *rvals):
301310
if 0 == result:
302311
policy_effects.add(Effector.INDETERMINATE)
303312
continue
304-
else:
305-
matcher_results.add(result)
306313
else:
307314
raise RuntimeError("matcher result should be bool, int or float")
308315

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

320-
if "priority(p_eft) || deny" == self.model.model["e"]["e"].value:
327+
if self.eft.intermediate_effect(policy_effects) != Effector.INDETERMINATE:
328+
explain_index = i
321329
break
322330

323331
else:
@@ -336,7 +344,8 @@ def enforce(self, *rvals):
336344
else:
337345
policy_effects.add(Effector.INDETERMINATE)
338346

339-
result = self.eft.merge_effects(self.model.model["e"]["e"].value, policy_effects, matcher_results)
347+
final_effect = self.eft.final_effect(policy_effects)
348+
result = effect_to_bool(final_effect)
340349

341350
# Log request.
342351

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

353-
return result
362+
explain_rule = []
363+
if explain_index != -1 and explain_index < policy_len:
364+
explain_rule = self.model.model["p"]["p"].policy[explain_index]
365+
366+
return result, explain_rule
354367

355368
@staticmethod
356369
def _get_expression(expr, functions=None):

casbin/effect/__init__.py

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,24 @@
1-
from .default_effector import DefaultEffector
1+
from .default_effectors import AllowOverrideEffector, DenyOverrideEffector, AllowAndDenyEffector, PriorityEffector
22
from .effector import Effector
3+
4+
def get_effector(expr):
5+
''' creates an effector based on the current policy effect expression '''
6+
7+
if expr == "some(where (p_eft == allow))":
8+
return AllowOverrideEffector()
9+
elif expr == "!some(where (p_eft == deny))":
10+
return DenyOverrideEffector()
11+
elif expr == "some(where (p_eft == allow)) && !some(where (p_eft == deny))":
12+
return AllowAndDenyEffector()
13+
elif expr == "priority(p_eft) || deny":
14+
return PriorityEffector()
15+
else:
16+
raise RuntimeError("unsupported effect")
17+
18+
def effect_to_bool(effect):
19+
""" """
20+
if effect == Effector.ALLOW:
21+
return True
22+
if effect == Effector.DENY:
23+
return False
24+
raise RuntimeError("effect can't be converted to boolean")

casbin/effect/default_effector.py

Lines changed: 0 additions & 39 deletions
This file was deleted.

casbin/effect/default_effectors.py

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
from .effector import Effector
2+
3+
class AllowOverrideEffector(Effector):
4+
5+
def intermediate_effect(self, effects):
6+
""" returns a intermediate effect based on the matched effects of the enforcer """
7+
if Effector.ALLOW in effects:
8+
return Effector.ALLOW
9+
return Effector.INDETERMINATE
10+
11+
def final_effect(self, effects):
12+
""" returns the final effect based on the matched effects of the enforcer """
13+
if Effector.ALLOW in effects:
14+
return Effector.ALLOW
15+
return Effector.DENY
16+
17+
class DenyOverrideEffector(Effector):
18+
19+
def intermediate_effect(self, effects):
20+
""" returns a intermediate effect based on the matched effects of the enforcer """
21+
if Effector.DENY in effects:
22+
return Effector.DENY
23+
return Effector.INDETERMINATE
24+
25+
def final_effect(self, effects):
26+
""" returns the final effect based on the matched effects of the enforcer """
27+
if Effector.DENY in effects:
28+
return Effector.DENY
29+
return Effector.ALLOW
30+
31+
class AllowAndDenyEffector(Effector):
32+
33+
def intermediate_effect(self, effects):
34+
""" returns a intermediate effect based on the matched effects of the enforcer """
35+
if Effector.DENY in effects:
36+
return Effector.DENY
37+
return Effector.INDETERMINATE
38+
39+
def final_effect(self, effects):
40+
""" returns the final effect based on the matched effects of the enforcer """
41+
if Effector.DENY in effects or Effector.ALLOW not in effects:
42+
return Effector.DENY
43+
return Effector.ALLOW
44+
45+
class PriorityEffector(Effector):
46+
47+
def intermediate_effect(self, effects):
48+
""" returns a intermediate effect based on the matched effects of the enforcer """
49+
if Effector.ALLOW in effects:
50+
return Effector.ALLOW
51+
if Effector.DENY in effects:
52+
return Effector.DENY
53+
return Effector.INDETERMINATE
54+
55+
def final_effect(self, effects):
56+
""" returns the final effect based on the matched effects of the enforcer """
57+
if Effector.ALLOW in effects:
58+
return Effector.ALLOW
59+
if Effector.DENY in effects:
60+
return Effector.DENY
61+
return Effector.DENY

casbin/effect/effector.py

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,13 @@ class Effector:
77

88
DENY = 2
99

10-
def merge_effects(self, expr, effects, results):
11-
"""merges all matching results collected by the enforcer into a single decision."""
10+
def intermediate_effect(self, effects):
11+
""" returns a intermediate effect based on the matched effects of the enforcer """
1212
pass
13+
14+
def final_effect(self, effects):
15+
""" returns the final effect based on the matched effects of the enforcer """
16+
pass
17+
18+
19+

casbin/synced_enforcer.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,14 @@ def enforce(self, *rvals):
132132
with self._rl:
133133
return self._e.enforce(*rvals)
134134

135+
def enforceEx(self, *rvals):
136+
"""decides whether a "subject" can access a "object" with the operation "action",
137+
input parameters are usually: (sub, obj, act).
138+
return judge result with reason
139+
"""
140+
with self._rl:
141+
return self._e.enforceEx(*rvals)
142+
135143
def get_all_subjects(self):
136144
"""gets the list of subjects that show up in the current policy."""
137145
with self._rl:

0 commit comments

Comments
 (0)