Skip to content

Commit

Permalink
feat: add prioritizing based on role and user hierarchy
Browse files Browse the repository at this point in the history
  • Loading branch information
basakest committed Sep 29, 2021
1 parent d7b799c commit c0b577c
Show file tree
Hide file tree
Showing 8 changed files with 174 additions and 3 deletions.
14 changes: 14 additions & 0 deletions examples/subject_priority_model.conf
@@ -0,0 +1,14 @@
[request_definition]
r = sub, obj, act

[policy_definition]
p = sub, obj, act, eft

[role_definition]
g = _, _

[policy_effect]
e = subjectPriority(p.eft) || deny

[matchers]
m = g(r.sub, p.sub) && r.obj == p.obj && r.act == p.act
15 changes: 15 additions & 0 deletions examples/subject_priority_model_with_domain.conf
@@ -0,0 +1,15 @@
[request_definition]
r = sub, obj, dom, act

[policy_definition]
# sub can't change position and must be first
p = sub, obj, dom, act, eft

[role_definition]
g = _, _, _

[policy_effect]
e = subjectPriority(p.eft) || deny

[matchers]
m = g(r.sub, p.sub, r.dom) && r.dom == p.dom && r.obj == p.obj && r.act == p.act
16 changes: 16 additions & 0 deletions examples/subject_priority_policy.csv
@@ -0,0 +1,16 @@
p, root, data1, read, deny
p, admin, data1, read, deny

p, editor, data1, read, deny
p, subscriber, data1, deny

p, jane, data1, read, allow
p, alice, data1, read, allow

g, admin, root

g, editor, admin
g, subscriber, admin

g, jane, editor
g, alice, subscriber
7 changes: 7 additions & 0 deletions examples/subject_priority_policy_with_domain.csv
@@ -0,0 +1,7 @@
p, admin, data1, domain1, write, deny
p, alice, data1, domain1, write, allow
p, admin, data2, domain2, write, deny
p, bob, data2, domain2, write, allow

g, alice, admin, domain1
g, bob, admin, domain2
1 change: 1 addition & 0 deletions src/CoreEnforcer.php
Expand Up @@ -362,6 +362,7 @@ public function loadPolicy(): void
try {
$this->adapter->loadPolicy($newModel);
$newModel->printPolicy();
$newModel->sortPoliciesBySubjectHierarchy();
$newModel->sortPoliciesByPriority();

if ($this->autoBuildRoleLinks) {
Expand Down
4 changes: 2 additions & 2 deletions src/Effector/DefaultEffector.php
Expand Up @@ -32,7 +32,7 @@ public function mergeEffects(string $expr, array $effects, array $matches, int $
$explainIndex = -1;

// short-circuit some effects in the middle
if ($expr != 'priority(p_eft) || deny') {
if ($expr != 'priority(p_eft) || deny' && $expr != 'subjectPriority(p_eft) || deny') {
if ($policyIndex < $policyLength - 1) {
// choose not to short-circuit
return [$result, $explainIndex];
Expand Down Expand Up @@ -82,7 +82,7 @@ public function mergeEffects(string $expr, array $effects, array $matches, int $
break;
}
}
} elseif ($expr == 'priority(p_eft) || deny') {
} elseif ($expr == 'priority(p_eft) || deny' || $expr == 'subjectPriority(p_eft) || deny') {
$result = Effector::INDETERMINATE;
foreach ($effects as $i => $eft) {
if ($matches[$i] == 0) {
Expand Down
104 changes: 104 additions & 0 deletions src/Model/Model.php
Expand Up @@ -19,6 +19,9 @@
*/
class Model extends Policy
{
const DEFAULT_DOMAIN = '';
const DEFAULT_SEPARATOR = '::';

/**
* @var array<string, string>
*/
Expand Down Expand Up @@ -230,6 +233,107 @@ public static function loadFunctionMap(): FunctionMap
return FunctionMap::loadFunctionMap();
}

public function getNameWithDomain(string $domain, string $name): string
{
return $domain . self::DEFAULT_SEPARATOR . $name;
}

public function getSubjectHierarchyMap(array $policies): array
{
$subjectHierarchyMap = [];
// Tree structure of role
$policyMap = [];
foreach ($policies as $policy) {
if (count($policy) < 2) {
throw new CasbinException('policy g expect 2 more params');
}
$domain = self::DEFAULT_DOMAIN;
if (count($policy) != 2) {
$domain = $policy[2];
}
$child = $this->getNameWithDomain($domain, $policy[0]);
$parent = $this->getNameWithDomain($domain, $policy[1]);
$policyMap[$parent][] = $child;
if (!isset($subjectHierarchyMap[$child])) {
$subjectHierarchyMap[$child] = 0;
}
if (!isset($subjectHierarchyMap[$parent])) {
$subjectHierarchyMap[$parent] = 0;
}
$subjectHierarchyMap[$child] = 1;
}
// Use queues for levelOrder
$queue = [];
foreach ($subjectHierarchyMap as $k => $v) {
$root = $k;
if ($v != 0) {
continue;
}
$lv = 0;
$queue[] = $root;
while (count($queue) != 0) {
$sz = count($queue);
for ($i = 0; $i < $sz; $i++) {
$node = $queue[array_key_first($queue)];
unset($queue[array_key_first($queue)]);

$nodeValue = $node;
$subjectHierarchyMap[$nodeValue] = $lv;
if (isset($policyMap[$nodeValue])) {
foreach ($policyMap[$nodeValue] as $child) {
$queue[] = $child;
}
}
}
$lv++;
}
}

return $subjectHierarchyMap;
}

public function sortPoliciesBySubjectHierarchy(): void
{
if ($this->items['e']['e']->value != 'subjectPriority(p_eft) || deny') {
return;
}
$subIndex = 0;
$domainIndex = -1;
foreach ($this->items['p'] as $ptype => $assertion) {
foreach ($assertion->tokens as $index => $token) {
if ($token == sprintf('%s_dom', $ptype)) {
$domainIndex = $index;
break;
}
}
$policies = &$assertion->policy;
$subjectHierarchyMap = $this->getSubjectHierarchyMap($this->items['g']['g']->policy);

usort($policies, function ($i, $j) use ($subIndex, $domainIndex, $subjectHierarchyMap): int {
$domain1 = self::DEFAULT_DOMAIN;
$domain2 = self::DEFAULT_DOMAIN;
if ($domainIndex != -1) {
$domain1 = $i[$domainIndex];
$domain2 = $j[$domainIndex];
}
$name1 = $this->getNameWithDomain($domain1, $i[$subIndex]);
$name2 = $this->getNameWithDomain($domain2, $j[$subIndex]);

$p1 = $subjectHierarchyMap[$name1];
$p2 = $subjectHierarchyMap[$name2];

if ($p1 == $p2) {
return 0;
}
return ($p1 > $p2) ? -1 : 1;
});

foreach ($assertion->policy as $i => $policy) {
$assertion->policyMap[implode(',', $policy)] = $i;
}
}
}

public function sortPoliciesByPriority(): void
{
foreach ($this->items['p'] as $ptype => $assertion) {
Expand Down
16 changes: 15 additions & 1 deletion tests/Unit/EnforcerTest.php
Expand Up @@ -368,7 +368,7 @@ public function testFailedToLoadPolicy()
$this->assertTrue($e->enforce('alice', '/pen/1', 'GET'));
$this->assertTrue($e->enforce('alice', '/pen2/1', 'GET'));
}

public function testReloadPolicyWithFunc()
{
$e = new Enforcer($this->modelAndPolicyPath . '/rbac_with_pattern_model.conf', $this->modelAndPolicyPath . '/rbac_with_pattern_policy.csv');
Expand All @@ -394,6 +394,20 @@ public function testBatchEnforce()
$this->assertEquals([true, true, false], $res);
}

public function testSubjectPriority()
{
$e = new Enforcer($this->modelAndPolicyPath . '/subject_priority_model.conf', $this->modelAndPolicyPath . '/subject_priority_policy.csv');
$this->assertTrue($e->enforce('jane', 'data1', 'read'));
$this->assertTrue($e->enforce('alice', 'data1', 'read'));
}

public function testSubjectPriorityWithDomain()
{
$e = new Enforcer($this->modelAndPolicyPath . '/subject_priority_model_with_domain.conf', $this->modelAndPolicyPath . '/subject_priority_policy_with_domain.csv');
$this->assertTrue($e->enforce('alice', 'data1', 'domain1', 'write'));
$this->assertTrue($e->enforce('bob', 'data2', 'domain2', 'write'));
}

public function testDeleteAllUsersByDomain()
{
$e = new Enforcer($this->modelAndPolicyPath . '/rbac_with_domains_model.conf', $this->modelAndPolicyPath . '/rbac_with_domains_policy.csv');
Expand Down

0 comments on commit c0b577c

Please sign in to comment.