This is an automated email from the ASF dual-hosted git repository.
hsluoyz pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/casbin-pycasbin.git
The following commit(s) were added to refs/heads/master by this push:
new b2a26f6 feat: add policy index for partitioned enforcement
b2a26f6 is described below
commit b2a26f6f66ce91eb9bd51ee7671a69222d8b3926
Author: Yang Luo <[email protected]>
AuthorDate: Sun Nov 1 02:40:33 2026 +0800
feat: add policy index for partitioned enforcement
---
casbin/__init__.py | 1 +
casbin/core_enforcer.py | 110 ++++++++++++++++++++-
casbin/model/assertion.py | 1 +
casbin/model/model.py | 2 +
casbin/model/policy.py | 12 +++
casbin/policy_index.py | 105 ++++++++++++++++++++
casbin/synced_enforcer.py | 15 +++
tests/test_policy_index.py | 239 +++++++++++++++++++++++++++++++++++++++++++++
8 files changed, 484 insertions(+), 1 deletion(-)
diff --git a/casbin/__init__.py b/casbin/__init__.py
index 9c2719e..ff22611 100644
--- a/casbin/__init__.py
+++ b/casbin/__init__.py
@@ -13,6 +13,7 @@
# limitations under the License.
from .enforcer import *
+from .policy_index import PolicyIndex
from .synced_enforcer import SyncedEnforcer
from .distributed_enforcer import DistributedEnforcer
from .fast_enforcer import FastEnforcer
diff --git a/casbin/core_enforcer.py b/casbin/core_enforcer.py
index 0d4ddc8..e904483 100644
--- a/casbin/core_enforcer.py
+++ b/casbin/core_enforcer.py
@@ -17,10 +17,16 @@ import logging
import re
from functools import lru_cache
-from casbin.effect import Effector, AllowAndDenyEffector, get_effector,
effect_to_bool
+from casbin.effect import (
+ AllowAndDenyEffector,
+ Effector,
+ effect_to_bool,
+ get_effector,
+)
from casbin.model import Model, FunctionMap
from casbin.persist import Adapter
from casbin.persist.adapters import FileAdapter
+from casbin.policy_index import PolicyIndex, policy_matches_bucket
from casbin.rbac import default_role_manager
from casbin.util import generate_g_function, SimpleEval, util,
generate_conditional_g_function, key_match
from casbin.util.log import configure_logging, disabled_logging
@@ -62,6 +68,8 @@ class CoreEnforcer:
def __init__(self, model=None, adapter=None, enable_log=False,
logging_config: dict = None):
self.logger = logging.getLogger("casbin.enforcer")
+ self._policy_indexes = {}
+ self._policy_deny_cache = {}
if isinstance(model, str):
if isinstance(adapter, str):
self.init_with_file(model, adapter)
@@ -120,6 +128,7 @@ class CoreEnforcer:
self.auto_build_role_links = True
self.auto_notify_watcher = True
+ self._reset_policy_caches()
self.init_rm_map()
@staticmethod
@@ -143,6 +152,7 @@ class CoreEnforcer:
self.model.load_model(self.model_path)
self.model.print_model()
self.fm = FunctionMap.load_function_map()
+ self._reset_policy_caches()
def get_model(self):
"""gets the current model."""
@@ -154,6 +164,7 @@ class CoreEnforcer:
self.model = m
self.fm = FunctionMap.load_function_map()
+ self._reset_policy_caches()
def get_adapter(self):
"""gets the current adapter."""
@@ -191,6 +202,82 @@ class CoreEnforcer:
"""sets the current effector."""
self.eft = eft
+ def set_policy_index(self, ptype, index):
+ """partitions the policy rules of ptype with a PolicyIndex."""
+ if not isinstance(index, PolicyIndex):
+ raise TypeError("index must be a PolicyIndex")
+ if "p" not in self.model.keys() or ptype not in self.model["p"]:
+ raise ValueError(f"policy type {ptype!r} does not exist")
+ self._policy_indexes[ptype] = index
+
+ def set_policy_index_by_token(self, ptype, request_token, policy_token):
+ """partitions ptype's rules by the string value of two tokens.
+
+ It uses the default request type "r"; use set_policy_index with a
+ custom function when enforcing through an EnforceContext.
+ """
+ if "r" not in self.model.keys() or "r" not in self.model["r"]:
+ raise ValueError('request type "r" does not exist')
+ request_tokens = self.model["r"]["r"].tokens
+ if request_token not in request_tokens:
+ raise ValueError(f"request token {request_token!r} does not exist")
+ request_index = request_tokens.index(request_token)
+
+ if "p" not in self.model.keys() or ptype not in self.model["p"]:
+ raise ValueError(f"policy type {ptype!r} does not exist")
+ policy_tokens = self.model["p"][ptype].tokens
+ if policy_token not in policy_tokens:
+ raise ValueError(f"policy token {policy_token!r} does not exist")
+ policy_index = policy_tokens.index(policy_token)
+
+ def request_key(rvals, index=request_index):
+ if index >= len(rvals):
+ return "", False
+ value = rvals[index]
+ if isinstance(value, str):
+ return value, True
+ return "", False
+
+ def policy_key(rule, index=policy_index):
+ if index >= len(rule):
+ return ""
+ return rule[index]
+
+ self.set_policy_index(ptype, PolicyIndex(request_key, policy_key))
+
+ def remove_policy_index(self, ptype):
+ """removes the policy partition for ptype."""
+ self._policy_indexes.pop(ptype, None)
+
+ def _reset_policy_caches(self):
+ for index in self._policy_indexes.values():
+ index.reset()
+ self._policy_deny_cache.clear()
+
+ def _policy_has_deny(self, ptype, p_tokens):
+ assertion = self.model["p"][ptype]
+ cached = self._policy_deny_cache.get(ptype)
+ if cached is not None and cached[0] == assertion.policy_version and
cached[1] == len(assertion.policy):
+ return cached[2], cached[3]
+
+ eft_token = ptype + "_eft"
+ eft_index = p_tokens.index(eft_token) if eft_token in p_tokens else -1
+ has_deny = False
+ all_valid = True
+ for rule in assertion.policy:
+ if len(rule) != len(p_tokens):
+ all_valid = False
+ if eft_index != -1 and eft_index < len(rule) and rule[eft_index]
== "deny":
+ has_deny = True
+
+ self._policy_deny_cache[ptype] = (
+ assertion.policy_version,
+ len(assertion.policy),
+ has_deny,
+ all_valid,
+ )
+ return has_deny, all_valid
+
def clear_policy(self):
"""clears all policy."""
@@ -271,6 +358,7 @@ class CoreEnforcer:
new_model.build_conditional_role_links(self.cond_rm_map)
self.model = new_model
+ self._reset_policy_caches()
except Exception as e:
if self.auto_build_role_links and need_to_rebuild:
@@ -472,6 +560,19 @@ class CoreEnforcer:
policy_len = len(self.model["p"][ptype].policy)
+ policy_index = self._policy_indexes.get(ptype)
+ bucket_ids = None
+ request_bucket = 0
+ use_policy_index = False
+ if policy_index is not None:
+ bucket_ids = policy_index.ensure_built(self.model["p"][ptype])
+ request_bucket, use_policy_index =
policy_index.request_bucket(rvals)
+
+ shortcut_allow_and_deny = False
+ if policy_len != 0 and type(self.eft) is AllowAndDenyEffector:
+ has_deny, all_valid = self._policy_has_deny(ptype, p_tokens)
+ shortcut_allow_and_deny = all_valid and not has_deny
+
explain_index = -1
allow_index = -1
if not 0 == policy_len:
@@ -479,6 +580,9 @@ class CoreEnforcer:
if len(p_tokens) != len(pvals):
raise RuntimeError("invalid policy size")
+ if use_policy_index and not policy_matches_bucket(bucket_ids,
i, request_bucket):
+ continue
+
p_parameters = dict(zip(p_tokens, pvals))
parameters = dict(r_parameters, **p_parameters)
@@ -514,6 +618,10 @@ class CoreEnforcer:
if allow_index == -1 and Effector.ALLOW in policy_effects:
allow_index = i
+ if shortcut_allow_and_deny and Effector.ALLOW in
policy_effects:
+ explain_index = i
+ break
+
if self.eft.intermediate_effect(policy_effects) !=
Effector.INDETERMINATE:
explain_index = i
break
diff --git a/casbin/model/assertion.py b/casbin/model/assertion.py
index 408e369..3835333 100644
--- a/casbin/model/assertion.py
+++ b/casbin/model/assertion.py
@@ -26,6 +26,7 @@ class Assertion:
self.tokens = []
self.params_tokens = []
self.policy = []
+ self.policy_version = 0
self.rm = None
self.cond_rm = None
self.priority_index: int = -1
diff --git a/casbin/model/model.py b/casbin/model/model.py
index e75572c..681af38 100644
--- a/casbin/model/model.py
+++ b/casbin/model/model.py
@@ -135,6 +135,7 @@ class Model(Policy):
for i, policy in enumerate(assertion.policy):
assertion.policy_map[",".join(policy)] = i
+ assertion.policy_version += 1
return None
@@ -162,6 +163,7 @@ class Model(Policy):
assertion.policy = sorted(assertion.policy, key=compare_policy)
for i, policy in enumerate(assertion.policy):
assertion.policy_map[",".join(policy)] = i
+ assertion.policy_version += 1
def get_subject_hierarchy_map(self, policies):
"""
diff --git a/casbin/model/policy.py b/casbin/model/policy.py
index 65882b8..4437344 100644
--- a/casbin/model/policy.py
+++ b/casbin/model/policy.py
@@ -87,6 +87,7 @@ class Policy:
for key in self[sec].keys():
self[sec][key].policy = []
+ self[sec][key].policy_version += 1
def get_policy(self, sec, ptype):
"""gets all rules in a policy.
@@ -153,6 +154,7 @@ class Policy:
print(e)
assertion.policy_map[DEFAULT_SEP.join(rule)] = len(assertion.policy) -
1
+ assertion.policy_version += 1
return True
def add_policies(self, sec, ptype, rules):
@@ -210,6 +212,8 @@ class Policy:
assertion.policy.append(rule)
assertion.policy_map[DEFAULT_SEP.join(rule)] =
len(assertion.policy) - 1
rules_added = True
+ if rules_added:
+ assertion.policy_version += 1
return rules_added
def update_policy(self, sec, ptype, old_rule, new_rule):
@@ -236,6 +240,7 @@ class Policy:
else:
ast.policy[rule_index] = new_rule
+ ast.policy_version += 1
return True
def update_policies(self, sec, ptype, old_rules, new_rules):
@@ -268,6 +273,7 @@ class Policy:
for idx, old_rule, new_rule in zip(old_rules_index, old_rules,
new_rules):
ast.policy[idx] = new_rule
+ ast.policy_version += 1
return True
def remove_policy(self, sec, ptype, rule):
@@ -276,6 +282,7 @@ class Policy:
return False
self[sec][ptype].policy.remove(rule)
+ self[sec][ptype].policy_version += 1
return rule not in self[sec][ptype].policy
@@ -289,6 +296,7 @@ class Policy:
if rule in self[sec][ptype].policy:
return False
+ self[sec][ptype].policy_version += 1
return True
def remove_policies_with_effected(self, sec, ptype, rules):
@@ -321,6 +329,8 @@ class Policy:
tmp.append(rule)
self[sec][ptype].policy = tmp
+ if effects:
+ self[sec][ptype].policy_version += 1
return effects
@@ -341,6 +351,8 @@ class Policy:
tmp.append(rule)
self[sec][ptype].policy = tmp
+ if res:
+ self[sec][ptype].policy_version += 1
return res
diff --git a/casbin/policy_index.py b/casbin/policy_index.py
new file mode 100644
index 0000000..e77197d
--- /dev/null
+++ b/casbin/policy_index.py
@@ -0,0 +1,105 @@
+# Copyright 2026 The casbin Authors. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import threading
+
+_WILDCARD_POLICY_BUCKET = 2**32 - 1
+_MISSING_POLICY_BUCKET = 2**32 - 2
+
+
+class PolicyIndex:
+ """PolicyIndex partitions the policy rules of one policy type so that
+ enforce() only evaluates the rules in the request's partition.
+
+ request_key receives the request values, with an optional EnforceContext
+ removed, and returns ``(key, True)``. Returning ``(key, False)`` disables
+ the index for that request, so every rule is evaluated.
+
+ policy_key returns the partition key for one policy rule. The key ``"*"``
+ puts the rule in every partition.
+
+ enforce() skips the matcher of a rule whose key differs from the request
+ key. The caller must guarantee that such a rule can never match the
+ request; otherwise the decision changes. This includes matchers that
+ evaluate policy fields with eval(). Rules are still merged by the
+ configured effector, so deny rules must be reachable through the request
+ key or ``"*"``.
+ """
+
+ def __init__(self, request_key, policy_key):
+ if not callable(request_key) or not callable(policy_key):
+ raise TypeError("request_key and policy_key must be callable")
+
+ self.request_key = request_key
+ self.policy_key = policy_key
+ self._lock = threading.Lock()
+ self._built = False
+ self._version = 0
+ self._bucket_ids = []
+ self._names = {}
+
+ def ensure_built(self, assertion):
+ with self._lock:
+ if (
+ self._built
+ and self._version == assertion.policy_version
+ and len(self._bucket_ids) == len(assertion.policy)
+ ):
+ return self._bucket_ids
+
+ names = {}
+ bucket_ids = []
+ next_id = 0
+ for rule in assertion.policy:
+ key = self.policy_key(rule)
+ if key == "*":
+ bucket_ids.append(_WILDCARD_POLICY_BUCKET)
+ continue
+
+ bucket_id = names.get(key)
+ if bucket_id is None:
+ bucket_id = next_id
+ names[key] = bucket_id
+ next_id += 1
+ bucket_ids.append(bucket_id)
+
+ self._bucket_ids = bucket_ids
+ self._names = names
+ self._version = assertion.policy_version
+ self._built = True
+ return bucket_ids
+
+ def request_bucket(self, rvals):
+ key, ok = self.request_key(rvals)
+ if not ok:
+ return 0, False
+
+ with self._lock:
+ bucket_id = self._names.get(key)
+ if bucket_id is None:
+ return _MISSING_POLICY_BUCKET, True
+ return bucket_id, True
+
+ def reset(self):
+ with self._lock:
+ self._built = False
+ self._bucket_ids = []
+ self._names = {}
+
+
+def policy_matches_bucket(bucket_ids, policy_index, bucket):
+ if policy_index < 0 or policy_index >= len(bucket_ids):
+ return True
+ bucket_id = bucket_ids[policy_index]
+ return bucket_id == _WILDCARD_POLICY_BUCKET or bucket_id == bucket
diff --git a/casbin/synced_enforcer.py b/casbin/synced_enforcer.py
index bfd714a..c51f7b7 100644
--- a/casbin/synced_enforcer.py
+++ b/casbin/synced_enforcer.py
@@ -819,6 +819,21 @@ class SyncedEnforcer:
assertion = self._e.model["p"][ptype]
assertion.field_index_map[field] = index
+ def set_policy_index(self, ptype, index):
+ """partitions the policy rules of ptype with a PolicyIndex."""
+ with self._wl:
+ return self._e.set_policy_index(ptype, index)
+
+ def set_policy_index_by_token(self, ptype, request_token, policy_token):
+ """partitions ptype's rules by the string value of two tokens."""
+ with self._wl:
+ return self._e.set_policy_index_by_token(ptype, request_token,
policy_token)
+
+ def remove_policy_index(self, ptype):
+ """removes the policy partition for ptype."""
+ with self._wl:
+ return self._e.remove_policy_index(ptype)
+
def get_all_roles_by_domain(self, domain):
"""gets all roles associated with the domain.
note: Not applicable to Domains with inheritance relationship
(implicit roles)"""
diff --git a/tests/test_policy_index.py b/tests/test_policy_index.py
new file mode 100644
index 0000000..ecd83bf
--- /dev/null
+++ b/tests/test_policy_index.py
@@ -0,0 +1,239 @@
+# Copyright 2026 The casbin Authors. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from unittest import TestCase
+
+import casbin
+
+ALLOW_AND_DENY = "some(where (p.eft == allow)) && !some(where (p.eft == deny))"
+
+
+def new_enforcer(effect=ALLOW_AND_DENY, with_eft=True):
+ model = casbin.Model()
+ model.add_def("r", "r", "sub, obj_type, obj, act")
+ if with_eft:
+ model.add_def("p", "p", "sub, obj_type, obj, act, eft")
+ else:
+ model.add_def("p", "p", "sub, obj_type, obj, act")
+ model.add_def("g", "g", "_, _")
+ model.add_def("e", "e", effect)
+ model.add_def(
+ "m",
+ "m",
+ "g(r.sub, p.sub) && matchObj(p.obj_type, p.obj, r.obj_type, r.obj) &&
r.act == p.act",
+ )
+
+ enforcer = casbin.Enforcer(model)
+ calls = [0]
+
+ def match_obj(p_type, p_obj, r_type, r_obj):
+ calls[0] += 1
+ return (p_type == "*" or p_type == r_type) and p_obj == r_obj
+
+ enforcer.add_function("matchObj", match_obj)
+ return enforcer, calls
+
+
+def add_policies(enforcer, with_eft=True, with_deny=True):
+ rules = [
+ ["alice", "doc", "doc1", "read"],
+ ["alice", "doc", "doc2", "read"],
+ ["alice", "img", "img1", "read"],
+ ["alice", "*", "video1", "read"],
+ ]
+ if with_eft:
+ rules = [rule + ["allow"] for rule in rules]
+ enforcer.add_policies(rules)
+ if with_deny:
+ enforcer.add_policy("alice", "doc", "doc3", "read", "deny")
+
+
+def set_object_type_index(enforcer):
+ enforcer.set_policy_index(
+ "p",
+ casbin.PolicyIndex(
+ request_key=lambda rvals: (rvals[1], True),
+ policy_key=lambda rule: rule[1],
+ ),
+ )
+
+
+class PolicyIndexTest(TestCase):
+ def test_matches_unindexed_decisions(self):
+ indexed, _ = new_enforcer()
+ add_policies(indexed)
+ set_object_type_index(indexed)
+
+ unindexed, _ = new_enforcer()
+ add_policies(unindexed)
+
+ indexed.add_grouping_policy("bob", "alice")
+ unindexed.add_grouping_policy("bob", "alice")
+
+ requests = [
+ ("alice", "doc", "doc1", "read"),
+ ("alice", "doc", "doc2", "read"),
+ ("alice", "doc", "doc3", "read"),
+ ("alice", "img", "img1", "read"),
+ ("alice", "audio", "video1", "read"),
+ ("alice", "audio", "missing", "read"),
+ ("alice", "doc", "doc1", "write"),
+ ("bob", "doc", "doc1", "read"),
+ ("bob", "img", "img1", "read"),
+ ("bob", "audio", "video1", "read"),
+ ]
+ for request in requests:
+ indexed_result, indexed_explain = indexed.enforce_ex(*request)
+ unindexed_result, unindexed_explain =
unindexed.enforce_ex(*request)
+ self.assertEqual(indexed_result, unindexed_result, request)
+ self.assertEqual(indexed_explain, unindexed_explain, request)
+
+ def test_skips_unrelated_rules(self):
+ enforcer, calls = new_enforcer()
+ add_policies(enforcer)
+ set_object_type_index(enforcer)
+
+ for request, expected_calls in [
+ (("alice", "doc", "doc1", "read"), 4),
+ (("alice", "img", "img1", "read"), 2),
+ (("alice", "audio", "video1", "read"), 1),
+ ]:
+ calls[0] = 0
+ enforcer.enforce(*request)
+ self.assertEqual(calls[0], expected_calls, request)
+
+ enforcer.remove_policy_index("p")
+ calls[0] = 0
+ enforcer.enforce("alice", "doc", "doc1", "read")
+ self.assertEqual(calls[0], 5)
+
+ def test_rebuilds_after_policy_changes(self):
+ enforcer, calls = new_enforcer()
+ add_policies(enforcer)
+ set_object_type_index(enforcer)
+
+ self.assertFalse(enforcer.enforce("alice", "doc", "doc4", "read"))
+
+ enforcer.add_policy("alice", "doc", "doc4", "read", "allow")
+ calls[0] = 0
+ self.assertTrue(enforcer.enforce("alice", "doc", "doc4", "read"))
+ self.assertEqual(calls[0], 5)
+
+ enforcer.remove_policy("alice", "doc", "doc4", "read", "allow")
+ self.assertFalse(enforcer.enforce("alice", "doc", "doc4", "read"))
+
+ old_rule = ["alice", "img", "img1", "read", "allow"]
+ new_rule = ["alice", "doc", "doc4", "read", "allow"]
+ self.assertTrue(enforcer.update_policy(old_rule, new_rule))
+ self.assertFalse(enforcer.enforce("alice", "img", "img1", "read"))
+ self.assertTrue(enforcer.enforce("alice", "doc", "doc4", "read"))
+
+ enforcer.clear_policy()
+ self.assertFalse(enforcer.enforce("alice", "doc", "doc1", "read"))
+ enforcer.add_policy("alice", "doc", "doc1", "read", "allow")
+ self.assertTrue(enforcer.enforce("alice", "doc", "doc1", "read"))
+
+ def test_disabled_for_unpartitioned_request(self):
+ enforcer, calls = new_enforcer()
+ add_policies(enforcer)
+ enforcer.set_policy_index(
+ "p",
+ casbin.PolicyIndex(
+ request_key=lambda rvals: ("", False),
+ policy_key=lambda rule: rule[1],
+ ),
+ )
+
+ calls[0] = 0
+ enforcer.enforce("alice", "doc", "doc1", "read")
+ self.assertEqual(calls[0], 5)
+
+ def test_validation(self):
+ enforcer, _ = new_enforcer()
+
+ with self.assertRaises(TypeError):
+ enforcer.set_policy_index("p", ("not", "an", "index"))
+ with self.assertRaises(ValueError):
+ set_object_type_index_to_missing_type(enforcer)
+ with self.assertRaises(ValueError):
+ enforcer.set_policy_index_by_token("p", "r_missing", "p_obj_type")
+ with self.assertRaises(ValueError):
+ enforcer.set_policy_index_by_token("p", "r_obj_type", "p_missing")
+
+ def test_by_token(self):
+ enforcer, calls = new_enforcer()
+ add_policies(enforcer)
+ enforcer.set_policy_index_by_token("p", "r_obj_type", "p_obj_type")
+
+ calls[0] = 0
+ self.assertTrue(enforcer.enforce("alice", "doc", "doc1", "read"))
+ self.assertEqual(calls[0], 4)
+
+ def test_allow_and_deny_short_circuits_without_deny_rules(self):
+ for with_eft in [False, True]:
+ enforcer, calls = new_enforcer(with_eft=with_eft)
+ add_policies(enforcer, with_eft=with_eft, with_deny=False)
+
+ calls[0] = 0
+ self.assertTrue(enforcer.enforce("alice", "doc", "doc1", "read"))
+ self.assertEqual(calls[0], 1, with_eft)
+
+ def test_allow_and_deny_still_evaluates_when_deny_exists(self):
+ enforcer, calls = new_enforcer()
+ add_policies(enforcer)
+
+ calls[0] = 0
+ self.assertTrue(enforcer.enforce("alice", "doc", "doc1", "read"))
+ self.assertEqual(calls[0], 5)
+
+ def test_resets_when_model_changes(self):
+ enforcer, calls = new_enforcer(with_eft=False)
+ enforcer.add_policy("alice", "doc", "doc1", "read")
+ enforcer.add_policy("alice", "img", "img1", "read")
+ set_object_type_index(enforcer)
+
+ self.assertTrue(enforcer.enforce("alice", "img", "img1", "read"))
+
+ replacement = casbin.Model()
+ replacement.add_def("r", "r", "sub, obj_type, obj, act")
+ replacement.add_def("p", "p", "sub, obj_type, obj, act")
+ replacement.add_def("g", "g", "_, _")
+ replacement.add_def("e", "e", ALLOW_AND_DENY)
+ replacement.add_def(
+ "m",
+ "m",
+ "g(r.sub, p.sub) && matchObj(p.obj_type, p.obj, r.obj_type, r.obj)
&& r.act == p.act",
+ )
+ replacement.add_policy("p", "p", ["alice", "img", "img1", "read"])
+ replacement.add_policy("p", "p", ["alice", "doc", "doc1", "read"])
+
+ enforcer.set_model(replacement)
+
+ def match_obj(p_type, p_obj, r_type, r_obj):
+ calls[0] += 1
+ return (p_type == "*" or p_type == r_type) and p_obj == r_obj
+
+ enforcer.add_function("matchObj", match_obj)
+
+ self.assertTrue(enforcer.enforce("alice", "img", "img1", "read"))
+
+
+def set_object_type_index_to_missing_type(enforcer):
+ enforcer.set_policy_index(
+ "missing",
+ casbin.PolicyIndex(
+ request_key=lambda rvals: ("", True),
+ policy_key=lambda rule: rule[0],
+ ),
+ )
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]