From 6091b851dfb802db0d5c6df381a73f805dcaff44 Mon Sep 17 00:00:00 2001 From: tangyang9464 Date: Tue, 6 Jul 2021 12:41:29 +0800 Subject: [PATCH] feat: add prioritizing based on role and user hierarchy Signed-off-by: tangyang9464 --- effector/default_effector.go | 4 +- enforcer.go | 4 + enforcer_test.go | 20 ++++ examples/subject_priority_model.conf | 14 +++ .../subject_priority_model_with_domain.conf | 14 +++ examples/subject_priority_policy.csv | 16 ++++ .../subject_priority_policy_with_domain.csv | 7 ++ model/model.go | 96 +++++++++++++++++++ 8 files changed, 173 insertions(+), 2 deletions(-) create mode 100644 examples/subject_priority_model.conf create mode 100644 examples/subject_priority_model_with_domain.conf create mode 100644 examples/subject_priority_policy.csv create mode 100644 examples/subject_priority_policy_with_domain.csv diff --git a/effector/default_effector.go b/effector/default_effector.go index 20f574b9..465293d4 100644 --- a/effector/default_effector.go +++ b/effector/default_effector.go @@ -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 @@ -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 { diff --git a/enforcer.go b/enforcer.go index 0d108083..7719e1e0 100644 --- a/enforcer.go +++ b/enforcer.go @@ -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 } diff --git a/enforcer_test.go b/enforcer_test.go index a1d000b2..251c23b0 100644 --- a/enforcer_test.go +++ b/enforcer_test.go @@ -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{}{ diff --git a/examples/subject_priority_model.conf b/examples/subject_priority_model.conf new file mode 100644 index 00000000..77b8c4eb --- /dev/null +++ b/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 \ No newline at end of file diff --git a/examples/subject_priority_model_with_domain.conf b/examples/subject_priority_model_with_domain.conf new file mode 100644 index 00000000..84ec518c --- /dev/null +++ b/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 \ No newline at end of file diff --git a/examples/subject_priority_policy.csv b/examples/subject_priority_policy.csv new file mode 100644 index 00000000..1b3672f6 --- /dev/null +++ b/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 \ No newline at end of file diff --git a/examples/subject_priority_policy_with_domain.csv b/examples/subject_priority_policy_with_domain.csv new file mode 100644 index 00000000..c4859ecd --- /dev/null +++ b/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 \ No newline at end of file diff --git a/model/model.go b/model/model.go index 8a6afdc6..4c36c839 100644 --- a/model/model.go +++ b/model/model.go @@ -15,6 +15,8 @@ package model import ( + "container/list" + "errors" "fmt" "regexp" "sort" @@ -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", @@ -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 {