diff --git a/cmd/notify/main.go b/cmd/notify/main.go index 8b5a58fd14..0a733e2679 100644 --- a/cmd/notify/main.go +++ b/cmd/notify/main.go @@ -1,3 +1,17 @@ +// Copyright 2019 Yunion +// +// 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. + package main import ( diff --git a/pkg/cloudcommon/policy/policy.go b/pkg/cloudcommon/policy/policy.go index 93a71bb27c..780f8fc372 100644 --- a/pkg/cloudcommon/policy/policy.go +++ b/pkg/cloudcommon/policy/policy.go @@ -38,13 +38,13 @@ import ( const ( PolicyDelegation = "delegate" - PolicyActionList = "list" - PolicyActionGet = "get" - PolicyActionUpdate = "update" - PolicyActionPatch = "patch" - PolicyActionCreate = "create" - PolicyActionDelete = "delete" - PolicyActionPerform = "perform" + PolicyActionList = rbacutils.ActionList + PolicyActionGet = rbacutils.ActionGet + PolicyActionUpdate = rbacutils.ActionUpdate + PolicyActionPatch = rbacutils.ActionPatch + PolicyActionCreate = rbacutils.ActionCreate + PolicyActionDelete = rbacutils.ActionDelete + PolicyActionPerform = rbacutils.ActionPerform ) type PolicyFetchFunc func() (map[rbacutils.TRbacScope]map[string]*rbacutils.SRbacPolicy, error) @@ -307,25 +307,53 @@ func (manager *SPolicyManager) findPolicyByName(scope rbacutils.TRbacScope, name return nil } +func getMatchedPolicyNames(policies map[string]*rbacutils.SRbacPolicy, userCred rbacutils.IRbacIdentity) []string { + matchNames := make([]string, 0) + maxMatchWeight := 0 + for k := range policies { + isMatched, matchWeight := policies[k].Match(userCred) + if !isMatched || matchWeight < maxMatchWeight { + continue + } + if maxMatchWeight < matchWeight { + maxMatchWeight = matchWeight + matchNames = matchNames[:0] + } + matchNames = append(matchNames, k) + } + return matchNames +} + +func getMatchedPolicyRules(policies map[string]*rbacutils.SRbacPolicy, userCred rbacutils.IRbacIdentity, service string, resource string, action string, extra ...string) ([]rbacutils.SRbacRule, bool) { + matchRules := make([]rbacutils.SRbacRule, 0) + findMatchPolicy := false + maxMatchWeight := 0 + for k := range policies { + isMatched, matchWeight := policies[k].Match(userCred) + if !isMatched || matchWeight < maxMatchWeight { + continue + } + if maxMatchWeight < matchWeight { + maxMatchWeight = matchWeight + matchRules = matchRules[:0] + } + findMatchPolicy = true + rule := policies[k].GetMatchRule(service, resource, action, extra...) + if rule != nil { + matchRules = append(matchRules, *rule) + } + } + return matchRules, findMatchPolicy +} + func (manager *SPolicyManager) allowWithoutCache(scope rbacutils.TRbacScope, userCred mcclient.TokenCredential, service string, resource string, action string, extra ...string) rbacutils.TRbacResult { matchRules := make([]rbacutils.SRbacRule, 0) - findMatchPolicy := false - policies, ok := manager.policies[scope] if !ok { log.Warningf("no policies fetched for scope %s", scope) } else { - for i := range policies { - if !policies[i].Match(userCred) { - continue - } - findMatchPolicy = true - rule := policies[i].GetMatchRule(service, resource, action, extra...) - if rule != nil { - matchRules = append(matchRules, *rule) - } - } + matchRules, findMatchPolicy = getMatchedPolicyRules(policies, userCred, service, resource, action, extra...) } scopedDeny := false @@ -358,7 +386,8 @@ func (manager *SPolicyManager) allowWithoutCache(scope rbacutils.TRbacScope, use defaultPolicies, ok := manager.defaultPolicies[scope] if ok { for i := range defaultPolicies { - if !defaultPolicies[i].Match(userCred) { + isMatched, _ := defaultPolicies[i].Match(userCred) + if !isMatched { continue } rule := defaultPolicies[i].GetMatchRule(service, resource, action, extra...) @@ -476,10 +505,9 @@ func (manager *SPolicyManager) IsScopeCapable(userCred mcclient.TokenCredential, } if policies, ok := manager.policies[scope]; ok { - for _, p := range policies { - if p.Match(userCred) { - return true - } + pnames := getMatchedPolicyNames(policies, userCred) + if len(pnames) > 0 { + return true } } return false @@ -491,12 +519,7 @@ func (manager *SPolicyManager) MatchedPolicies(scope rbacutils.TRbacScope, userC if !ok { return ret } - for k, p := range policies { - if p.Match(userCred) { - ret = append(ret, k) - } - } - return ret + return getMatchedPolicyNames(policies, userCred) } func (manager *SPolicyManager) AllPolicies() map[string][]string { diff --git a/pkg/mcclient/modules/mod_notify.go b/pkg/mcclient/modules/mod_notify.go index 1827b05200..0b009f1607 100644 --- a/pkg/mcclient/modules/mod_notify.go +++ b/pkg/mcclient/modules/mod_notify.go @@ -1,3 +1,17 @@ +// Copyright 2019 Yunion +// +// 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. + package modules type ConfigsManager struct { diff --git a/pkg/notify/models/initdb.go b/pkg/notify/models/initdb.go index f14d07b4e4..20ef68febe 100644 --- a/pkg/notify/models/initdb.go +++ b/pkg/notify/models/initdb.go @@ -1,3 +1,17 @@ +// Copyright 2019 Yunion +// +// 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. + package models import ( diff --git a/pkg/util/rbacutils/actions.go b/pkg/util/rbacutils/actions.go new file mode 100644 index 0000000000..9d5ab8cd68 --- /dev/null +++ b/pkg/util/rbacutils/actions.go @@ -0,0 +1,43 @@ +// Copyright 2019 Yunion +// +// 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. + +package rbacutils + +import ( + "yunion.io/x/onecloud/pkg/util/stringutils2" +) + +const ( + ActionList = "list" + ActionGet = "get" + ActionUpdate = "update" + ActionCreate = "create" + ActionDelete = "delete" + ActionPerform = "perform" + + ActionPatch = "patch" +) + +var ( + AllActions = []string{ + ActionList, + ActionGet, + ActionUpdate, + ActionCreate, + ActionDelete, + ActionPerform, + } + + AllSortedActions = stringutils2.NewSortedStrings(AllActions) +) diff --git a/pkg/util/rbacutils/rbac.go b/pkg/util/rbacutils/rbac.go index f98732779a..e213a97d99 100644 --- a/pkg/util/rbacutils/rbac.go +++ b/pkg/util/rbacutils/rbac.go @@ -76,6 +76,10 @@ func (r1 TRbacResult) StricterThan(r2 TRbacResult) bool { return r1.Strictness() < r2.Strictness() } +func (r1 TRbacResult) LooserThan(r2 TRbacResult) bool { + return r1.Strictness() > r2.Strictness() +} + func (s1 TRbacScope) HigherEqual(s2 TRbacScope) bool { return scopeScore[s1] >= scopeScore[s2] } @@ -113,6 +117,15 @@ type SRbacRule struct { Result TRbacResult } +func (r SRbacRule) clone() SRbacRule { + nr := r + nr.Extra = make([]string, len(r.Extra)) + if len(r.Extra) > 0 { + copy(nr.Extra, r.Extra) + } + return nr +} + func isWildMatch(str string) bool { return len(str) == 0 || str == WILD_MATCH } @@ -144,6 +157,10 @@ func (rule *SRbacRule) stricterThan(r2 *SRbacRule) bool { return rule.Result.StricterThan(r2.Result) } +func (rule *SRbacRule) looserThan(r2 *SRbacRule) bool { + return rule.Result.LooserThan(r2.Result) +} + func (rule *SRbacRule) match(service string, resource string, action string, extra ...string) (bool, int, int) { matched := 0 weight := 0 @@ -215,7 +232,7 @@ func GetMatchRule(rules []SRbacRule, service string, resource string, action str match, matchCnt, weight := rules[i].match(service, resource, action, extra...) if match && (maxMatchCnt < matchCnt || (maxMatchCnt == matchCnt && minWeight > weight) || - (maxMatchCnt == matchCnt && minWeight == weight && matchRule.stricterThan(&rules[i]))) { + (maxMatchCnt == matchCnt && minWeight == weight && matchRule.looserThan(&rules[i]))) { maxMatchCnt = matchCnt minWeight = weight matchRule = &rules[i] @@ -228,7 +245,7 @@ func CompactRules(rules []SRbacRule) []SRbacRule { if len(rules) == 0 { return nil } - output := make([]SRbacRule, 1) + /*output := make([]SRbacRule, 1) output[0] = rules[0] for i := 1; i < len(rules); i += 1 { isContains := false @@ -246,8 +263,8 @@ func CompactRules(rules []SRbacRule) []SRbacRule { if !isContains { output = append(output, rules[i]) } - } - return output + }*/ + return reduceRules(rules) } var ( @@ -332,9 +349,10 @@ func (policy *SRbacPolicy) Decode(policyJson jsonutils.JSONObject) error { return err } - rules, err := decode(ruleJson, SRbacRule{}, levelService) + /*rules, err := decode(ruleJson, SRbacRule{}, levelService)*/ + rules, err := json2Rules(ruleJson) if err != nil { - return err + return errors.Wrap(err, "json2Rules") } if len(rules) == 0 { @@ -459,14 +477,16 @@ func addRule2Json(nodeJson *jsonutils.JSONDict, keys []string, result TRbacResul } func (policy *SRbacPolicy) Encode() (jsonutils.JSONObject, error) { - rules := jsonutils.NewDict() + /*rules := jsonutils.NewDict() for i := 0; i < len(policy.Rules); i += 1 { keys := policy.Rules[i].toStringArray() err := addRule2Json(rules, keys, policy.Rules[i].Result) if err != nil { return nil, errors.Wrap(err, "addRule2Json") } - } + }*/ + + rules := rules2Json(policy.Rules) ret := jsonutils.NewDict() // ret.Add(jsonutils.NewString(policy.Condition), "condition") @@ -564,20 +584,44 @@ func (policy *SRbacPolicy) IsSystemWidePolicy() bool { return len(policy.Roles) == 0 && len(policy.Projects) == 0 } -func (policy *SRbacPolicy) Match(userCred IRbacIdentity) bool { - if !policy.Auth && len(policy.Projects) == 0 && len(policy.Roles) == 0 && len(policy.Ips) == 0 { - return true +// check whether policy maches a userCred +// return value +// bool isMatched +// int match weight, the higher the value, the more exact the match +// the more exact match wins +func (policy *SRbacPolicy) Match(userCred IRbacIdentity) (bool, int) { + if !policy.Auth && len(policy.Roles) == 0 && len(policy.Projects) == 0 && len(policy.Ips) == 0 { + return true, 1 } if userCred == nil { - return false + return false, 0 } - if !policy.IsPublic && len(policy.DomainId) > 0 && policy.DomainId != userCred.GetProjectDomainId() { - return false + weight := 0 + if policy.IsPublic || len(policy.DomainId) == 0 || policy.DomainId == userCred.GetProjectDomainId() { + if len(policy.DomainId) > 0 { + weight += 10 + } + if !policy.IsPublic { + weight += 10 + } + if len(policy.Roles) == 0 || intersect(policy.Roles, userCred.GetRoles()) { + if len(policy.Roles) != 0 { + weight += 100 + } + if len(policy.Projects) == 0 || contains(policy.Projects, userCred.GetProjectName()) { + if len(policy.Projects) > 0 { + weight += 1000 + } + if len(policy.Ips) == 0 || containsIp(policy.Ips, userCred.GetLoginIp()) { + if len(policy.Ips) > 0 { + weight += 10000 + } + return true, weight + } + } + } } - if (len(policy.Projects) == 0 || contains(policy.Projects, userCred.GetProjectName())) && (len(policy.Roles) == 0 || intersect(policy.Roles, userCred.GetRoles())) && (len(policy.Ips) == 0 || containsIp(policy.Ips, userCred.GetLoginIp())) { - return true - } - return false + return false, 0 } func (policy *SRbacPolicy) MatchRole(roleName string) bool { diff --git a/pkg/util/rbacutils/rbac_test.go b/pkg/util/rbacutils/rbac_test.go index ec09f1cd97..c0a7527ac5 100644 --- a/pkg/util/rbacutils/rbac_test.go +++ b/pkg/util/rbacutils/rbac_test.go @@ -475,10 +475,10 @@ func TestSRbacPolicyMatch(t *testing.T) { true, }, } - for _, c := range cases { - got := c.policy.Match(c.userCred) + for i, c := range cases { + got, _ := c.policy.Match(c.userCred) if got != c.want { - t.Errorf("%#v %#v got %v want %v", c.policy, c.userCred, got, c.want) + t.Errorf("[%d]: %#v %#v got %v want %v", i, c.policy, c.userCred, got, c.want) } } } diff --git a/pkg/util/rbacutils/reduce.go b/pkg/util/rbacutils/reduce.go new file mode 100644 index 0000000000..13894d4978 --- /dev/null +++ b/pkg/util/rbacutils/reduce.go @@ -0,0 +1,309 @@ +// Copyright 2019 Yunion +// +// 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. + +package rbacutils + +import ( + "sort" + + "yunion.io/x/jsonutils" + "yunion.io/x/log" + "yunion.io/x/pkg/errors" + + "yunion.io/x/onecloud/pkg/util/stringutils2" +) + +type sRbacNode struct { + defNode *sRbacNode + downStream map[string]*sRbacNode + result *TRbacResult + level int +} + +func newRbacNode(level int) *sRbacNode { + return &sRbacNode{ + downStream: make(map[string]*sRbacNode), + level: level, + } +} + +func (n *sRbacNode) AddRule(rule SRbacRule) { + n.addRule(rule, levelService) +} + +func (n *sRbacNode) addRule(rule SRbacRule, level int) { + if level <= levelAction || len(rule.Extra) > level-levelExtra { + if n.result != nil { + // node is a leaf + if n.defNode != nil { + log.Fatalf("illegal state, result != nil and defNode != nil") + } + n.defNode = newRbacNode(n.level + 1) + n.defNode.result = n.result + n.result = nil + } + var key string + if level == levelService { + key = rule.Service + } else if level == levelResource { + key = rule.Resource + } else if level == levelAction { + key = rule.Action + } else { + key = rule.Extra[level-levelExtra] + } + if len(key) == 0 || key == WILD_MATCH { + next := n.defNode + if n.defNode == nil { + next = newRbacNode(level + 1) + n.defNode = next + } + next.addRule(rule, level+1) + } else { + next, ok := n.downStream[key] + if !ok { + next = newRbacNode(level + 1) + n.downStream[key] = next + } + next.addRule(rule, level+1) + } + } else { + if n.result != nil { + log.Warningf("node has been occupide!!!") + } + n.result = &rule.Result + } +} + +func (n *sRbacNode) isLeaf() bool { + return n.result != nil && n.defNode == nil && len(n.downStream) == 0 +} + +func (n *sRbacNode) reduceDownstream() { + allowKey := make([]string, 0) + denyKey := make([]string, 0) + skipKey := make([]string, 0) + for k, v := range n.downStream { + if v.result == nil { + skipKey = append(skipKey, k) + continue + } + if *v.result == Allow { + allowKey = append(allowKey, k) + } else { + denyKey = append(denyKey, k) + } + } + if len(allowKey)+len(denyKey) > 0 { + var result TRbacResult + var keys []string + if n.defNode != nil { + if n.defNode.isLeaf() { + if *n.defNode.result == Allow { + keys = allowKey + result = Allow + } else { + keys = denyKey + result = Deny + } + } + } else { + if len(allowKey) >= len(denyKey) { + keys = allowKey + result = Allow + } else { + keys = denyKey + result = Deny + } + if len(allowKey) == 0 || len(denyKey) == 0 { + // reduce whole downStream + needBranch := false + if n.level == levelAction { + sort.Strings(keys) + if !stringutils2.Contains(stringutils2.SSortedStrings(keys), AllSortedActions) { + // not a complete set of action, cancel reduce + needBranch = true + } + } else if n.level >= levelExtra { + if len(keys) <= 1 { + needBranch = true + } + } + + if needBranch { + keys = nil + if result == Allow { + result = Deny + } else { + result = Allow + } + // add a default branch + n.defNode = newRbacNode(n.level + 1) + n.defNode.result = &result + } + } + } + if len(keys) > 0 { + for _, k := range keys { + delete(n.downStream, k) + } + if n.defNode == nil { + n.defNode = newRbacNode(n.level + 1) + } + n.defNode.result = &result + } + } + if len(n.downStream) == 0 && n.defNode != nil && n.defNode.isLeaf() { + n.result = n.defNode.result + n.defNode = nil + } +} + +func (n *sRbacNode) reduce() { + if n.defNode != nil { + n.defNode.reduce() + } + for k := range n.downStream { + n.downStream[k].reduce() + } + n.reduceDownstream() +} + +func (n *sRbacNode) GetRules() []SRbacRule { + return n.getRules(SRbacRule{Service: WILD_MATCH}, levelService) +} + +func (n *sRbacNode) getRules(seed SRbacRule, level int) []SRbacRule { + result := make([]SRbacRule, 0) + if n.result != nil { + rule := seed.clone() + rule.Result = *n.result + return []SRbacRule{rule} + } else { + if n.defNode != nil { + rule := seed.clone() + if level == levelService { + rule.Service = WILD_MATCH + } else if level == levelResource { + rule.Resource = WILD_MATCH + } else if level == levelAction { + rule.Action = WILD_MATCH + } else if level >= levelExtra { + rule.Extra = append(rule.Extra, WILD_MATCH) + } + newRules := n.defNode.getRules(rule, level+1) + result = append(result, newRules...) + } + for k := range n.downStream { + rule := seed.clone() + if level == levelService { + rule.Service = k + } else if level == levelResource { + rule.Resource = k + } else if level == levelAction { + rule.Action = k + } else if level >= levelExtra { + rule.Extra = append(rule.Extra, k) + } + newRules := n.downStream[k].getRules(rule, level+1) + result = append(result, newRules...) + } + } + return result +} + +func reduceRules(rules []SRbacRule) []SRbacRule { + root := newRbacNode(levelService) + for _, r := range rules { + root.AddRule(r) + } + root.reduce() + return root.GetRules() +} + +func rules2Json(rules []SRbacRule) jsonutils.JSONObject { + root := newRbacNode(levelService) + for _, r := range rules { + root.AddRule(r) + } + root.reduce() + return root.json() +} + +func json2Rules(json jsonutils.JSONObject) ([]SRbacRule, error) { + root := newRbacNode(levelService) + err := root.parseJson(json) + if err != nil { + return nil, errors.Wrap(err, "root.parseJson") + } + root.reduce() + return root.GetRules(), nil +} + +func (n *sRbacNode) json() jsonutils.JSONObject { + var result jsonutils.JSONObject + if n.result != nil { + return jsonutils.NewString(string(*n.result)) + } else { + result = jsonutils.NewDict() + if n.defNode != nil { + result.(*jsonutils.JSONDict).Add(n.defNode.json(), "*") + } + for k := range n.downStream { + result.(*jsonutils.JSONDict).Add(n.downStream[k].json(), k) + } + } + return result +} + +func (n *sRbacNode) parseJson(input jsonutils.JSONObject) error { + switch val := input.(type) { + case *jsonutils.JSONString: + ruleStr, err := val.GetString() + if err != nil { + return errors.Wrap(err, "val.GetString") + } + var result TRbacResult + switch ruleStr { + case string(Allow), string(AdminAllow), string(OwnerAllow), string(UserAllow), string(GuestAllow): + result = Allow + default: + result = Deny + } + n.result = &result + case *jsonutils.JSONDict: + ruleJsonDict, err := val.GetMap() + if err != nil { + return errors.Wrap(err, "val.GetMap") + } + for key, ruleJson := range ruleJsonDict { + if key == WILD_MATCH { + n.defNode = newRbacNode(n.level + 1) + err := n.defNode.parseJson(ruleJson) + if err != nil { + return errors.Wrap(err, "n.defNode.parseJson") + } + } else { + n.downStream[key] = newRbacNode(n.level + 1) + err := n.downStream[key].parseJson(ruleJson) + if err != nil { + return errors.Wrap(err, "n.downStream[key].parseJson") + } + } + } + default: + return errors.Wrap(ErrUnsuportRuleData, input.String()) + } + return nil +} diff --git a/pkg/util/rbacutils/reduce_test.go b/pkg/util/rbacutils/reduce_test.go new file mode 100644 index 0000000000..c64ba104b9 --- /dev/null +++ b/pkg/util/rbacutils/reduce_test.go @@ -0,0 +1,163 @@ +// Copyright 2019 Yunion +// +// 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. + +package rbacutils + +import ( + "testing" +) + +func TestReduce(t *testing.T) { + cases := map[string]struct { + in []SRbacRule + want []SRbacRule + }{ + "merge1": { + in: []SRbacRule{ + { + Service: "compute", + Resource: "servers", + Action: "list", + Result: Allow, + }, + { + Service: "compute", + Resource: "servers", + Action: "get", + Result: Allow, + }, + { + Service: "compute", + Resource: "servers", + Action: "delete", + Result: Allow, + }, + }, + want: []SRbacRule{ + { + Service: "compute", + Resource: "servers", + Result: Allow, + }, + }, + }, + "merge2": { + in: []SRbacRule{ + { + Service: "compute", + Resource: "servers", + Action: "get", + Result: Allow, + }, + { + Service: "compute", + Resource: "servers", + Action: "create", + Result: Allow, + }, + { + Service: "compute", + Resource: "networks", + Action: "get", + Result: Allow, + }, + { + Service: "compute", + Resource: "networks", + Action: "create", + Result: Deny, + }, + }, + want: []SRbacRule{ + { + Service: "compute", + Resource: "servers", + Action: "", + Result: Allow, + }, + { + Service: "compute", + Resource: "networks", + Action: "get", + Result: Allow, + }, + { + Service: "compute", + Resource: "networks", + Action: "create", + Result: Deny, + }, + }, + }, + "merge3": { + in: []SRbacRule{ + { + Service: "compute", + Resource: "servers", + Action: "get", + Result: Allow, + }, + { + Service: "compute", + Resource: "servers", + Action: "get", + Extra: []string{"vnc"}, + Result: Deny, + }, + { + Service: "compute", + Resource: "servers", + Action: "create", + Result: Allow, + }, + { + Service: "compute", + Resource: "servers", + Action: "update", + Result: Allow, + }, + { + Service: "compute", + Resource: "servers", + Action: "delete", + Result: Deny, + }, + }, + want: []SRbacRule{ + { + Service: "compute", + Resource: "servers", + Result: Allow, + }, + { + Service: "compute", + Resource: "servers", + Action: "get", + Extra: []string{"vnc"}, + Result: Deny, + }, + { + Service: "compute", + Resource: "servers", + Action: "delete", + Result: Deny, + }, + }, + }, + } + for name, c := range cases { + got := reduceRules(c.in) + t.Logf("[%s]: want: %s got: %s", name, c.want, got) + } +} diff --git a/pkg/util/stringutils2/sortedstrings.go b/pkg/util/stringutils2/sortedstrings.go index 17eed8d07d..aba792ad8c 100644 --- a/pkg/util/stringutils2/sortedstrings.go +++ b/pkg/util/stringutils2/sortedstrings.go @@ -84,6 +84,24 @@ func (ss SSortedStrings) ContainsAll(needles ...string) bool { return true } +func Contains(a, b SSortedStrings) bool { + _, _, bNoA := Split(a, b) + if len(bNoA) == 0 { + return true + } else { + return false + } +} + +func Equals(a, b SSortedStrings) bool { + aNoB, _, bNoA := Split(a, b) + if len(aNoB) == 0 && len(bNoA) == 0 { + return true + } else { + return false + } +} + func Split(a, b SSortedStrings) (aNoB SSortedStrings, aAndB SSortedStrings, bNoA SSortedStrings) { a_b := make([]string, 0) b_a := make([]string, 0)