diff --git a/go.mod b/go.mod index 28836a0..ec2918e 100644 --- a/go.mod +++ b/go.mod @@ -6,6 +6,7 @@ require ( git.frostfs.info/TrueCloudLab/frostfs-contract v0.18.1-0.20231129062201-a1b61d394958 github.com/nspcc-dev/neo-go v0.103.0 github.com/stretchr/testify v1.8.4 + golang.org/x/exp v0.0.0-20230817173708-d852ddb80c63 ) require ( diff --git a/go.sum b/go.sum index 0453bd3..96722c1 100644 --- a/go.sum +++ b/go.sum @@ -31,6 +31,8 @@ github.com/virtuald/go-ordered-json v0.0.0-20170621173500-b18e6e673d74/go.mod h1 go.etcd.io/bbolt v1.3.7 h1:j+zJOnnEjF/kyHlDDgGnVL/AIqIJPq8UoB2GSNfkUfQ= golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc= golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= +golang.org/x/exp v0.0.0-20230817173708-d852ddb80c63 h1:m64FZMko/V45gv0bNmrNYoDEq8U5YUhetc9cBWKS1TQ= +golang.org/x/exp v0.0.0-20230817173708-d852ddb80c63/go.mod h1:0v4NqG35kSWCMzLaMeX+IQrlSnVE/bqGSyC2cz/9Le8= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E= golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= diff --git a/iam/converter.go b/iam/converter.go index 53b581e..957cbc3 100644 --- a/iam/converter.go +++ b/iam/converter.go @@ -50,6 +50,9 @@ const ( CondArnLike string = "ArnLike" CondArnNotEquals string = "ArnNotEquals" CondArnNotLike string = "ArnNotLike" + + // Custom condition operators. + CondSliceContains string = "SliceContains" ) const ( @@ -191,6 +194,8 @@ func getConditionTypeAndConverter(op string) (chain.ConditionType, convertFuncti return chain.CondStringLike, noConvertFunction, nil case op == CondNotIPAddress: return chain.CondStringNotLike, noConvertFunction, nil + case op == CondSliceContains: + return chain.CondSliceContains, noConvertFunction, nil default: return 0, nil, fmt.Errorf("unsupported condition operator: '%s'", op) } diff --git a/pkg/chain/chain.go b/pkg/chain/chain.go index d241ca8..24c0afc 100644 --- a/pkg/chain/chain.go +++ b/pkg/chain/chain.go @@ -7,6 +7,7 @@ import ( "git.frostfs.info/TrueCloudLab/policy-engine/pkg/resource" "git.frostfs.info/TrueCloudLab/policy-engine/util" + "golang.org/x/exp/slices" ) // ID is the ID of rule chain. @@ -103,6 +104,8 @@ const ( CondNumericLessThanEquals CondNumericGreaterThan CondNumericGreaterThanEquals + + CondSliceContains ) func (c ConditionType) String() string { @@ -139,11 +142,21 @@ func (c ConditionType) String() string { return "NumericGreaterThan" case CondNumericGreaterThanEquals: return "NumericGreaterThanEquals" + case CondSliceContains: + return "SliceContains" default: return "unknown condition type" } } +const condSliceContainsDelimiter = "\x00" + +// FormCondSliceContainsValue builds value for ObjectResource or ObjectRequest property +// that can be matched by CondSliceContains condition. +func FormCondSliceContainsValue(values []string) string { + return strings.Join(values, condSliceContainsDelimiter) +} + func (c *Condition) Match(req resource.Request) bool { var val string switch c.Object { @@ -178,6 +191,8 @@ func (c *Condition) Match(req resource.Request) bool { return val > c.Value case CondStringGreaterThanEquals: return val >= c.Value + case CondSliceContains: + return slices.Contains(strings.Split(val, condSliceContainsDelimiter), c.Value) } } diff --git a/pkg/chain/chain_test.go b/pkg/chain/chain_test.go index 911daa4..8e0c706 100644 --- a/pkg/chain/chain_test.go +++ b/pkg/chain/chain_test.go @@ -4,6 +4,7 @@ import ( "testing" "git.frostfs.info/TrueCloudLab/policy-engine/pkg/resource/testutil" + "git.frostfs.info/TrueCloudLab/policy-engine/schema/common" "git.frostfs.info/TrueCloudLab/policy-engine/schema/native" "github.com/stretchr/testify/require" ) @@ -73,3 +74,65 @@ func TestReturnFirstMatch(t *testing.T) { require.Equal(t, Allow, st) }) } + +func TestCondSliceContainsMatch(t *testing.T) { + propKey := common.PropertyKeyFrostFSIDGroupID + groupID := "1" + + ch := Chain{Rules: []Rule{{ + Status: Allow, + Actions: Actions{Names: []string{native.MethodPutObject}}, + Resources: Resources{Names: []string{native.ResourceFormatRootContainers}}, + Condition: []Condition{{ + Op: CondSliceContains, + Object: ObjectRequest, + Key: propKey, + Value: groupID, + }}, + }}} + + for _, tc := range []struct { + name string + value string + status Status + }{ + { + name: "simple value", + value: groupID, + status: Allow, + }, + { + name: "simple value by func", + value: FormCondSliceContainsValue([]string{groupID}), + status: Allow, + }, + { + name: "multiple values by func", + value: FormCondSliceContainsValue([]string{groupID, "2", "3"}), + status: Allow, + }, + { + name: "simple mismatched", + value: "3", + status: NoRuleFound, + }, + { + name: "multiple mismatched", + value: FormCondSliceContainsValue([]string{"11", "12"}), + status: NoRuleFound, + }, + { + name: "comma correct handling mismatched", + value: "1,11", + status: NoRuleFound, + }, + } { + t.Run(tc.name, func(t *testing.T) { + resource := testutil.NewResource(native.ResourceFormatRootContainers, nil) + request := testutil.NewRequest(native.MethodPutObject, resource, map[string]string{propKey: tc.value}) + + st, _ := ch.Match(request) + require.Equal(t, tc.status.String(), st.String()) + }) + } +} diff --git a/schema/common/consts.go b/schema/common/consts.go new file mode 100644 index 0000000..03ed340 --- /dev/null +++ b/schema/common/consts.go @@ -0,0 +1,5 @@ +package common + +const ( + PropertyKeyFrostFSIDGroupID = "frostfsid:groupID" +)