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
Signed-off-by: tangyang9464 <tangyang9464@163.com>
  • Loading branch information
tangyang9464 committed Jul 8, 2021
1 parent ecc6d90 commit 6091b85
Show file tree
Hide file tree
Showing 8 changed files with 173 additions and 2 deletions.
4 changes: 2 additions & 2 deletions effector/default_effector.go
Expand Up @@ -32,7 +32,7 @@ func (e *DefaultEffector) MergeEffects(expr string, effects []Effect, matches []
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, nil
Expand Down Expand Up @@ -87,7 +87,7 @@ func (e *DefaultEffector) MergeEffects(expr string, effects []Effect, matches []
break
}
}
} else if expr == "priority(p_eft) || deny" {
} else if expr == "priority(p_eft) || deny" || expr == "subjectPriority(p_eft) || deny" {
result = Indeterminate
for i, eft := range effects {
if matches[i] == 0 {
Expand Down
4 changes: 4 additions & 0 deletions enforcer.go
Expand Up @@ -290,6 +290,10 @@ func (e *Enforcer) LoadPolicy() error {
return err
}

if err = e.model.SortPoliciesBySubjectHierarchy(); err != nil {
return err
}

if err = e.model.SortPoliciesByPriority(); err != nil {
return err
}
Expand Down
20 changes: 20 additions & 0 deletions enforcer_test.go
Expand Up @@ -499,6 +499,26 @@ func TestBatchEnforce(t *testing.T) {
testBatchEnforce(t, e, [][]interface{}{{"alice", "data1", "read"}, {"bob", "data2", "write"}, {"jack", "data3", "read"}}, results)
}

func TestSubjectPriority(t *testing.T) {
e, _ := NewEnforcer("examples/subject_priority_model.conf", "examples/subject_priority_policy.csv")
testBatchEnforce(t, e, [][]interface{}{
{"jane", "data1", "read"},
{"alice", "data1", "read"},
}, []bool{
true, true,
})
}

func TestSubjectPriorityWithDomain(t *testing.T) {
e, _ := NewEnforcer("examples/subject_priority_model_with_domain.conf", "examples/subject_priority_policy_with_domain.csv")
testBatchEnforce(t, e, [][]interface{}{
{"alice", "data1", "domain1", "write"},
{"bob", "data2", "domain2", "write"},
}, []bool{
true, true,
})
}

func TestPriorityExplicit(t *testing.T) {
e, _ := NewEnforcer("examples/priority_model_explicit.conf", "examples/priority_policy_explicit.csv")
testBatchEnforce(t, e, [][]interface{}{
Expand Down
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
14 changes: 14 additions & 0 deletions examples/subject_priority_model_with_domain.conf
@@ -0,0 +1,14 @@
[request_definition]
r = sub, obj, dom, act

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

[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
96 changes: 96 additions & 0 deletions model/model.go
Expand Up @@ -15,6 +15,8 @@
package model

import (
"container/list"
"errors"
"fmt"
"regexp"
"sort"
Expand All @@ -32,6 +34,9 @@ type Model map[string]AssertionMap
// AssertionMap is the collection of assertions, can be "r", "p", "g", "e", "m".
type AssertionMap map[string]*Assertion

const defaultDomain string = ""
const defaultSeparator = "::"

var sectionNameMap = map[string]string{
"r": "request_definition",
"p": "policy_definition",
Expand Down Expand Up @@ -206,6 +211,97 @@ func (model Model) PrintModel() {
model.GetLogger().LogModel(modelInfo)
}

func (model Model) SortPoliciesBySubjectHierarchy() error {
if model["e"]["e"].Value != "subjectPriority(p_eft) || deny" {
return nil
}
subIndex := 0
domainIndex := -1
for ptype, assertion := range model["p"] {
for index, token := range assertion.Tokens {
if token == fmt.Sprintf("%s_dom", ptype) {
domainIndex = index
break
}
}
policies := assertion.Policy
subjectHierarchyMap, err := getSubjectHierarchyMap(model["g"]["g"].Policy)
if err != nil {
return err
}
sort.SliceStable(policies, func(i, j int) bool {
domain1, domain2 := defaultDomain, defaultDomain
if domainIndex != -1 {
domain1 = policies[i][domainIndex]
domain2 = policies[j][domainIndex]
}
name1, name2 := getNameWithDomain(domain1, policies[i][subIndex]), getNameWithDomain(domain2, policies[j][subIndex])
p1 := subjectHierarchyMap[name1]
p2 := subjectHierarchyMap[name2]
return p1 > p2
})
for i, policy := range assertion.Policy {
assertion.PolicyMap[strings.Join(policy, ",")] = i
}
}
return nil
}

func getSubjectHierarchyMap(policies [][]string) (map[string]int, error) {
subjectHierarchyMap := make(map[string]int)
// Tree structure of role
policyMap := make(map[string][]string)
for _, policy := range policies {
if len(policy) < 2 {
return nil, errors.New("policy g expect 2 more params")
}
domain := defaultDomain
if len(policy) != 2 {
domain = policy[2]
}
child := getNameWithDomain(domain, policy[0])
parent := getNameWithDomain(domain, policy[1])
policyMap[parent] = append(policyMap[parent], child)
if _, ok := subjectHierarchyMap[child]; !ok {
subjectHierarchyMap[child] = 0
}
if _, ok := subjectHierarchyMap[parent]; !ok {
subjectHierarchyMap[parent] = 0
}
subjectHierarchyMap[child] = 1
}
// Use queues for levelOrder
queue := list.New()
for k, v := range subjectHierarchyMap {
root := k
if v != 0 {
continue
}
lv := 0
queue.PushBack(root)
for queue.Len() != 0 {
sz := queue.Len()
for i := 0; i < sz; i++ {
node := queue.Front()
queue.Remove(node)
nodeValue := node.Value.(string)
subjectHierarchyMap[nodeValue] = lv
if _, ok := policyMap[nodeValue]; ok {
for _, child := range policyMap[nodeValue] {
queue.PushBack(child)
}
}
}
lv++
}
}
return subjectHierarchyMap, nil
}

func getNameWithDomain(domain string, name string) string {
return domain + defaultSeparator + name
}

func (model Model) SortPoliciesByPriority() error {
for ptype, assertion := range model["p"] {
for index, token := range assertion.Tokens {
Expand Down

0 comments on commit 6091b85

Please sign in to comment.