Skip to content

Commit 09ff119

Browse files
authored
feat: add temporal role model (#320)
1 parent 177e36b commit 09ff119

20 files changed

+670
-12
lines changed

casbin/core_enforcer.py

+67-8
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
from casbin.persist import Adapter
2121
from casbin.persist.adapters import FileAdapter
2222
from casbin.rbac import default_role_manager
23-
from casbin.util import generate_g_function, SimpleEval, util
23+
from casbin.util import generate_g_function, SimpleEval, util, generate_conditional_g_function
2424
from casbin.util.log import configure_logging
2525

2626

@@ -47,6 +47,7 @@ class CoreEnforcer:
4747
adapter = None
4848
watcher = None
4949
rm_map = None
50+
cond_rm_map = None
5051

5152
enabled = False
5253
auto_save = False
@@ -104,6 +105,7 @@ def init_with_model_and_adapter(self, m, adapter=None):
104105

105106
def _initialize(self):
106107
self.rm_map = dict()
108+
self.cond_rm_map = dict()
107109
self.eft = get_effector(self.model["e"]["e"].value)
108110
self.watcher = None
109111

@@ -192,10 +194,26 @@ def init_rm_map(self):
192194
if "g" in self.model.keys():
193195
for ptype in self.model["g"]:
194196
assertion = self.model["g"][ptype]
195-
if assertion.value.count("_") == 2:
196-
self.rm_map[ptype] = default_role_manager.RoleManager(10)
197-
else:
198-
self.rm_map[ptype] = default_role_manager.DomainManager(10)
197+
if ptype in self.rm_map:
198+
rm = self.rm_map[ptype]
199+
rm.clear()
200+
continue
201+
202+
if len(assertion.tokens) <= 2 and len(assertion.params_tokens) == 0:
203+
assertion.rm = default_role_manager.RoleManager(10)
204+
self.rm_map[ptype] = assertion.rm
205+
206+
if len(assertion.tokens) <= 2 and len(assertion.params_tokens) != 0:
207+
assertion.cond_rm = default_role_manager.ConditionalRoleManager(10)
208+
self.cond_rm_map[ptype] = assertion.cond_rm
209+
210+
if len(assertion.tokens) > 2:
211+
if len(assertion.params_tokens) == 0:
212+
assertion.rm = default_role_manager.DomainManager(10)
213+
self.rm_map[ptype] = assertion.rm
214+
else:
215+
assertion.cond_rm = default_role_manager.ConditionalDomainManager(10)
216+
self.cond_rm_map[ptype] = assertion.cond_rm
199217

200218
def load_policy(self):
201219
"""reloads the policy from file/database."""
@@ -216,8 +234,13 @@ def load_policy(self):
216234
need_to_rebuild = True
217235
for rm in self.rm_map.values():
218236
rm.clear()
237+
if len(self.rm_map) != 0:
238+
new_model.build_role_links(self.rm_map)
219239

220-
new_model.build_role_links(self.rm_map)
240+
for crm in self.cond_rm_map.values():
241+
crm.clear()
242+
if len(self.cond_rm_map) != 0:
243+
new_model.build_conditional_role_links(self.cond_rm_map)
221244

222245
self.model = new_model
223246

@@ -313,6 +336,40 @@ def add_named_domain_matching_func(self, ptype, fn):
313336

314337
return False
315338

339+
def add_named_link_condition_func(self, ptype, user, role, fn):
340+
"""Add condition function fn for Link userName->roleName,
341+
when fn returns true, Link is valid, otherwise invalid"""
342+
if ptype in self.cond_rm_map:
343+
rm = self.cond_rm_map[ptype]
344+
rm.add_link_condition_func(user, role, fn)
345+
return True
346+
return False
347+
348+
def add_named_domain_link_condition_func(self, ptype, user, role, domain, fn):
349+
"""Add condition function fn for Link userName-> {roleName, domain},
350+
when fn returns true, Link is valid, otherwise invalid"""
351+
if ptype in self.cond_rm_map:
352+
rm = self.cond_rm_map[ptype]
353+
rm.add_domain_link_condition_func(user, role, domain, fn)
354+
return True
355+
return False
356+
357+
def set_named_link_condition_func_params(self, ptype, user, role, *params):
358+
"""Sets the parameters of the condition function fn for Link userName->roleName"""
359+
if ptype in self.cond_rm_map:
360+
rm = self.cond_rm_map[ptype]
361+
rm.set_link_condition_func_params(user, role, *params)
362+
return True
363+
return False
364+
365+
def set_named_domain_link_condition_func_params(self, ptype, user, role, domain, *params):
366+
"""Sets the parameters of the condition function fn for Link userName->{roleName, domain}"""
367+
if ptype in self.cond_rm_map:
368+
rm = self.cond_rm_map[ptype]
369+
rm.set_domain_link_condition_func_params(user, role, domain, *params)
370+
return True
371+
return False
372+
316373
def new_enforce_context(self, suffix: str) -> EnforceContext:
317374
return EnforceContext(
318375
rtype="r" + suffix,
@@ -346,8 +403,10 @@ def enforce_ex(self, *rvals):
346403

347404
if "g" in self.model.keys():
348405
for key, ast in self.model["g"].items():
349-
rm = ast.rm
350-
functions[key] = generate_g_function(rm)
406+
if len(self.rm_map) != 0:
407+
functions[key] = generate_g_function(ast.rm)
408+
if len(self.cond_rm_map) != 0:
409+
functions[key] = generate_conditional_g_function(ast.cond_rm)
351410

352411
if len(rvals) != 0:
353412
if isinstance(rvals[0], EnforceContext):

casbin/management_enforcer.py

+4
Original file line numberDiff line numberDiff line change
@@ -238,6 +238,10 @@ def add_named_grouping_policy(self, ptype, *params):
238238

239239
if self.auto_build_role_links:
240240
self.model.build_incremental_role_links(self.rm_map[ptype], PolicyOp.Policy_add, "g", ptype, rules)
241+
if ptype in self.cond_rm_map:
242+
self.model.build_incremental_conditional_role_links(
243+
self.cond_rm_map[ptype], PolicyOp.Policy_add, "g", ptype, rules
244+
)
241245
return rule_added
242246

243247
def add_named_grouping_policies(self, ptype, rules):

casbin/model/assertion.py

+47
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,10 @@ def __init__(self):
2323
self.key = ""
2424
self.value = ""
2525
self.tokens = []
26+
self.params_tokens = []
2627
self.policy = []
2728
self.rm = None
29+
self.cond_rm = None
2830
self.priority_index: int = -1
2931
self.policy_map: dict = {}
3032
self.field_index_map: dict = {}
@@ -62,3 +64,48 @@ def build_incremental_role_links(self, rm, op, rules):
6264
rm.delete_link(rule[0], rule[1], *rule[2:])
6365
else:
6466
raise TypeError("Invalid operation: " + str(op))
67+
68+
def build_incremental_conditional_role_links(self, cond_rm, op, rules):
69+
self.cond_rm = cond_rm
70+
count = self.value.count("_")
71+
if count < 2:
72+
raise RuntimeError('the number of "_" in role definition should be at least 2')
73+
74+
for rule in rules:
75+
if len(rule) < count:
76+
raise TypeError("grouping policy elements do not meet role definition")
77+
if len(rule) > count:
78+
rule = rule[:count]
79+
80+
domain_rule = rule[2 : len(self.tokens)]
81+
82+
if op == PolicyOp.Policy_add:
83+
self.add_conditional_role_link(rule, domain_rule)
84+
elif op == PolicyOp.Policy_remove:
85+
self.cond_rm.delete_link(rule[0], rule[1], *rule[2:])
86+
else:
87+
raise TypeError("Invalid operation: " + str(op))
88+
89+
def build_conditional_role_links(self, cond_rm):
90+
self.cond_rm = cond_rm
91+
count = self.value.count("_")
92+
if count < 2:
93+
raise RuntimeError('the number of "_" in role definition should be at least 2')
94+
for rule in self.policy:
95+
if len(rule) < count:
96+
raise TypeError("grouping policy elements do not meet role definition")
97+
if len(rule) > count:
98+
rule = rule[:count]
99+
100+
domain_rule = rule[2 : len(self.tokens)]
101+
102+
self.add_conditional_role_link(rule, domain_rule)
103+
104+
def add_conditional_role_link(self, rule, domain_rule):
105+
if not domain_rule:
106+
self.cond_rm.add_link(rule[0], rule[1])
107+
self.cond_rm.set_link_condition_func_params(rule[0], rule[1], *rule[len(self.tokens) :])
108+
else:
109+
domain = domain_rule[0]
110+
self.cond_rm.add_link(rule[0], rule[1], domain)
111+
self.cond_rm.set_domain_link_condition_func_params(rule[0], rule[1], domain, *rule[len(self.tokens) :])

casbin/model/function.py

+1
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ def load_function_map():
3535
fm.add_function("regexMatch", util.regex_match_func)
3636
fm.add_function("ipMatch", util.ip_match_func)
3737
fm.add_function("globMatch", util.glob_match_func)
38+
fm.add_function("timeMatch", util.time_match_func)
3839

3940
return fm
4041

casbin/model/model.py

+18
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,15 @@
1111
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1212
# See the License for the specific language governing permissions and
1313
# limitations under the License.
14+
import re
1415

1516
from casbin import util, config
1617
from . import Assertion
1718
from .policy import Policy
1819

1920
DEFAULT_DOMAIN = ""
2021
DEFAULT_SEPARATOR = "::"
22+
PARAMS_REGEX = re.compile(r"\((.*?)\)")
2123

2224

2325
class Model(Policy):
@@ -34,6 +36,18 @@ def _load_assertion(self, cfg, sec, key):
3436

3537
return self.add_def(sec, key, value)
3638

39+
def get_params_token(self, value):
40+
"""get_params_token Get params_token from Assertion.value"""
41+
# Find the matching string using the regular expression
42+
params_string = PARAMS_REGEX.search(value)
43+
44+
if params_string is None:
45+
return []
46+
47+
# Extract the captured group (inside parentheses) and split it by commas
48+
params_string = params_string.group(1)
49+
return [param.strip() for param in params_string.split(",")]
50+
3751
def add_def(self, sec, key, value):
3852
if value == "":
3953
return
@@ -46,6 +60,10 @@ def add_def(self, sec, key, value):
4660
ast.tokens = ast.value.split(",")
4761
for i, token in enumerate(ast.tokens):
4862
ast.tokens[i] = key + "_" + token.strip()
63+
elif "g" == sec:
64+
ast.params_tokens = self.get_params_token(ast.value)
65+
ast.tokens = ast.value.split(",")
66+
ast.tokens = ast.tokens[: len(ast.tokens) - len(ast.params_tokens)]
4967
else:
5068
ast.value = util.remove_comments(util.escape_assertion(ast.value))
5169

casbin/model/policy.py

+14
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,20 @@ def build_incremental_role_links(self, rm, op, sec, ptype, rules):
5151
if sec == "g":
5252
self[sec].get(ptype).build_incremental_role_links(rm, op, rules)
5353

54+
def build_incremental_conditional_role_links(self, cond_rm, op, sec, ptype, rules):
55+
if sec == "g":
56+
return self[sec].get(ptype).build_incremental_conditional_role_links(cond_rm, op, rules)
57+
return None
58+
59+
def build_conditional_role_links(self, cond_rm_map):
60+
if "g" not in self.keys():
61+
return
62+
self.print_policy()
63+
for ptype, ast in self["g"].items():
64+
cond_rm = cond_rm_map.get(ptype)
65+
if cond_rm:
66+
ast.build_conditional_role_links(cond_rm)
67+
5468
def print_policy(self):
5569
"""Log using info"""
5670

casbin/rbac/__init__.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -12,4 +12,4 @@
1212
# See the License for the specific language governing permissions and
1313
# limitations under the License.
1414

15-
from .role_manager import RoleManager
15+
from .role_manager import RoleManager, ConditionalRoleManager

casbin/rbac/default_role_manager/__init__.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -12,4 +12,4 @@
1212
# See the License for the specific language governing permissions and
1313
# limitations under the License.
1414

15-
from .role_manager import RoleManager, DomainManager
15+
from .role_manager import RoleManager, DomainManager, ConditionalRoleManager, ConditionalDomainManager

0 commit comments

Comments
 (0)