Skip to content

Commit

Permalink
feat: add eval function
Browse files Browse the repository at this point in the history
Signed-off-by: Andreas Bichinger <[email protected]>
  • Loading branch information
abichinger committed Nov 30, 2020
1 parent ff050ae commit 9604fbf
Show file tree
Hide file tree
Showing 8 changed files with 141 additions and 3 deletions.
18 changes: 15 additions & 3 deletions casbin/core_enforcer.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
from casbin.persist.adapters import FileAdapter
from casbin.model import Model, FunctionMap
from casbin.rbac import default_role_manager
from casbin.util import generate_g_function, SimpleEval
from casbin.util import generate_g_function, SimpleEval, util
from casbin.effect import DefaultEffector, Effector


Expand Down Expand Up @@ -225,7 +225,9 @@ def enforce(self, *rvals):
raise RuntimeError("invalid request size")

exp_string = self.model.model["m"]["m"].value
expression = self._get_expression(exp_string, functions)
has_eval = util.has_eval(exp_string)
if not has_eval:
expression = self._get_expression(exp_string, functions)

policy_effects = set()
matcher_results = set()
Expand All @@ -239,7 +241,15 @@ def enforce(self, *rvals):
if len(p_tokens) != len(pvals):
raise RuntimeError("invalid policy size")

parameters = dict(r_parameters, **dict(zip(p_tokens, pvals)))
p_parameters = dict(zip(p_tokens, pvals))
parameters = dict(r_parameters, **p_parameters)

if util.has_eval(exp_string):
rule_names = util.get_eval_value(exp_string)
rules = [util.escape_assertion(p_parameters[rule_name]) for rule_name in rule_names]
exp_with_rule = util.replace_eval(exp_string, rules)
expression = self._get_expression(exp_with_rule, functions)

result = expression.eval(parameters)

if isinstance(result, bool):
Expand Down Expand Up @@ -270,6 +280,8 @@ def enforce(self, *rvals):
break

else:
if has_eval:
raise RuntimeError("please make sure rule exists in policy when using eval() in matcher")

parameters = r_parameters.copy()

Expand Down
23 changes: 23 additions & 0 deletions casbin/util/util.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
from collections import OrderedDict
import re

eval_reg = re.compile(r'\beval\((?P<rule>[^)]*)\)')

def escape_assertion(s):
"""escapes the dots in the assertion, because the expression evaluation doesn't support such variable names."""
Expand Down Expand Up @@ -57,3 +59,24 @@ def set_subtract(a, b):
diff.append(x)

return diff

def has_eval(s):
'''determine whether matcher contains function eval'''
return eval_reg.search(s)

def replace_eval(expr, rules):
''' replace all occurences of function eval with rules '''
pos = 0
match = eval_reg.search(expr, pos)
while match:
rule = "(" + rules.pop(0) + ")"
expr = expr[:match.start()] + rule + expr[match.end():]
pos = match.start() + len(rule)
match = eval_reg.search(expr, pos)

return expr

def get_eval_value(s):
'''returns the parameters of function eval'''
sub_match = eval_reg.findall(s)
return sub_match.copy()
11 changes: 11 additions & 0 deletions examples/abac_multiple_rules_model.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
[request_definition]
r = sub, obj, act

[policy_definition]
p = sub_rule, sub_rule2, obj, act

[policy_effect]
e = some(where (p.eft == allow))

[matchers]
m = r.obj == p.obj && r.act == p.act && eval(p.sub_rule) && eval(p.sub_rule2)
2 changes: 2 additions & 0 deletions examples/abac_multiple_rules_policy.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
p, r.sub.age > 18, r.sub.name == "alice", /data1, read
p, r.sub.age < 60, r.sub.name == "bob", /data2, write
11 changes: 11 additions & 0 deletions examples/abac_rule_model.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
[request_definition]
r = sub, obj, act

[policy_definition]
p = sub_rule, obj, act

[policy_effect]
e = some(where (p.eft == allow))

[matchers]
m = eval(p.sub_rule) && r.obj == p.obj && r.act == p.act
2 changes: 2 additions & 0 deletions examples/abac_rule_policy.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
p, r.sub.age > 18, /data1, read
p, r.sub.age < 60, /data2, write
57 changes: 57 additions & 0 deletions tests/test_enforcer.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,11 @@ def get_examples(path):
examples_path = os.path.split(os.path.realpath(__file__))[0] + "/../examples/"
return os.path.abspath(examples_path + path)

class TestSub():

def __init__(self, name, age):
self.name = name
self.age = age

class TestConfig(TestCase):
def test_enforcer_basic(self):
Expand Down Expand Up @@ -189,3 +194,55 @@ def test_enforce_abac_log_enabled(self):
sub = 'alice'
obj = {'Owner': 'alice', 'id': 'data1'}
self.assertTrue(e.enforce(sub, obj, 'write'))

def test_abac_with_sub_rule(self):
e = get_enforcer(get_examples("abac_rule_model.conf"),
get_examples("abac_rule_policy.csv"))

sub1 = TestSub("alice", 16)
sub2 = TestSub("bob", 20)
sub3 = TestSub("alice", 65)

self.assertFalse(e.enforce(sub1, "/data1", "read"))
self.assertFalse(e.enforce(sub1, "/data2", "read"))
self.assertFalse(e.enforce(sub1, "/data1", "write"))
self.assertTrue(e.enforce(sub1, "/data2", "write"))

self.assertTrue(e.enforce(sub2, "/data1", "read"))
self.assertFalse(e.enforce(sub2, "/data2", "read"))
self.assertFalse(e.enforce(sub2, "/data1", "write"))
self.assertTrue(e.enforce(sub2, "/data2", "write"))

self.assertTrue(e.enforce(sub3, "/data1", "read"))
self.assertFalse(e.enforce(sub3, "/data2", "read"))
self.assertFalse(e.enforce(sub3, "/data1", "write"))
self.assertFalse(e.enforce(sub3, "/data2", "write"))

def test_abac_with_multiple_sub_rules(self):
e = get_enforcer(get_examples("abac_multiple_rules_model.conf"),
get_examples("abac_multiple_rules_policy.csv"))

sub1 = TestSub("alice", 16)
sub2 = TestSub("alice", 20)
sub3 = TestSub("bob", 65)
sub4 = TestSub("bob", 35)

self.assertFalse(e.enforce(sub1, "/data1", "read"))
self.assertFalse(e.enforce(sub1, "/data2", "read"))
self.assertFalse(e.enforce(sub1, "/data1", "write"))
self.assertFalse(e.enforce(sub1, "/data2", "write"))

self.assertTrue(e.enforce(sub2, "/data1", "read"))
self.assertFalse(e.enforce(sub2, "/data2", "read"))
self.assertFalse(e.enforce(sub2, "/data1", "write"))
self.assertFalse(e.enforce(sub2, "/data2", "write"))

self.assertFalse(e.enforce(sub3, "/data1", "read"))
self.assertFalse(e.enforce(sub3, "/data2", "read"))
self.assertFalse(e.enforce(sub3, "/data1", "write"))
self.assertFalse(e.enforce(sub3, "/data2", "write"))

self.assertFalse(e.enforce(sub4, "/data1", "read"))
self.assertFalse(e.enforce(sub4, "/data2", "read"))
self.assertFalse(e.enforce(sub4, "/data1", "write"))
self.assertTrue(e.enforce(sub4, "/data2", "write"))
20 changes: 20 additions & 0 deletions tests/util/test_util.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,3 +24,23 @@ def test_array_to_string(self):

def test_params_to_string(self):
self.assertEqual(util.params_to_string('data', 'data1', 'data2', 'data3'), "data, data1, data2, data3")

def test_has_eval(self):
self.assertTrue(util.has_eval("eval() && a && b && c"))
self.assertTrue(util.has_eval("a && b && eval(c)"))
self.assertFalse(util.has_eval("eval) && a && b && c"))
self.assertFalse(util.has_eval("eval)( && a && b && c"))
self.assertTrue(util.has_eval("eval(c * (a + b)) && a && b && c"))
self.assertFalse(util.has_eval("xeval() && a && b && c"))
self.assertTrue(util.has_eval("eval(a) && eval(b) && a && b && c"))

def test_replace_eval(self):
self.assertEqual(util.replace_eval("eval(a) && eval(b) && c", ["a", "b"]), "(a) && (b) && c")
self.assertEqual(util.replace_eval("a && eval(b) && eval(c)", ["b", "c"]), "a && (b) && (c)")

def test_get_eval_value(self):
self.assertEqual(util.get_eval_value("eval(a) && a && b && c"), ["a"])
self.assertEqual(util.get_eval_value("a && eval(a) && b && c"), ["a"])
self.assertEqual(util.get_eval_value("eval(a) && eval(b) && a && b && c"), ["a", "b"])
self.assertEqual(util.get_eval_value("a && eval(a) && eval(b) && b && c"), ["a", "b"])
self.assertEqual(util.get_eval_value("eval(p.sub_rule) || p.obj == r.obj && eval(p.domain_rule)"), ["p.sub_rule", "p.domain_rule"])

0 comments on commit 9604fbf

Please sign in to comment.