mirror of
https://github.com/minio/minio.git
synced 2025-11-07 04:42:56 -05:00
Rename pkg/{tagging,lifecycle} to pkg/bucket sub-directory (#8892)
Rename to allow for more such features to come in a more proper hierarchical manner.
This commit is contained in:
committed by
kannappanr
parent
4cb6ebcfa2
commit
0cbebf0f57
139
pkg/bucket/policy/condition/binaryequalsfunc.go
Normal file
139
pkg/bucket/policy/condition/binaryequalsfunc.go
Normal file
@@ -0,0 +1,139 @@
|
||||
/*
|
||||
* MinIO Cloud Storage, (C) 2018 MinIO, Inc.
|
||||
*
|
||||
* 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 condition
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"sort"
|
||||
|
||||
"github.com/minio/minio-go/v6/pkg/s3utils"
|
||||
"github.com/minio/minio-go/v6/pkg/set"
|
||||
)
|
||||
|
||||
func toBinaryEqualsFuncString(n name, key Key, values set.StringSet) string {
|
||||
valueStrings := values.ToSlice()
|
||||
sort.Strings(valueStrings)
|
||||
|
||||
return fmt.Sprintf("%v:%v:%v", n, key, valueStrings)
|
||||
}
|
||||
|
||||
// binaryEqualsFunc - String equals function. It checks whether value by Key in given
|
||||
// values map is in condition values.
|
||||
// For example,
|
||||
// - if values = ["mybucket/foo"], at evaluate() it returns whether string
|
||||
// in value map for Key is in values.
|
||||
type binaryEqualsFunc struct {
|
||||
k Key
|
||||
values set.StringSet
|
||||
}
|
||||
|
||||
// evaluate() - evaluates to check whether value by Key in given values is in
|
||||
// condition values.
|
||||
func (f binaryEqualsFunc) evaluate(values map[string][]string) bool {
|
||||
requestValue, ok := values[http.CanonicalHeaderKey(f.k.Name())]
|
||||
if !ok {
|
||||
requestValue = values[f.k.Name()]
|
||||
}
|
||||
|
||||
fvalues := f.values.ApplyFunc(substFuncFromValues(values))
|
||||
return !fvalues.Intersection(set.CreateStringSet(requestValue...)).IsEmpty()
|
||||
}
|
||||
|
||||
// key() - returns condition key which is used by this condition function.
|
||||
func (f binaryEqualsFunc) key() Key {
|
||||
return f.k
|
||||
}
|
||||
|
||||
// name() - returns "BinaryEquals" condition name.
|
||||
func (f binaryEqualsFunc) name() name {
|
||||
return binaryEquals
|
||||
}
|
||||
|
||||
func (f binaryEqualsFunc) String() string {
|
||||
return toBinaryEqualsFuncString(binaryEquals, f.k, f.values)
|
||||
}
|
||||
|
||||
// toMap - returns map representation of this function.
|
||||
func (f binaryEqualsFunc) toMap() map[Key]ValueSet {
|
||||
if !f.k.IsValid() {
|
||||
return nil
|
||||
}
|
||||
|
||||
values := NewValueSet()
|
||||
for _, value := range f.values.ToSlice() {
|
||||
values.Add(NewStringValue(base64.StdEncoding.EncodeToString([]byte(value))))
|
||||
}
|
||||
|
||||
return map[Key]ValueSet{
|
||||
f.k: values,
|
||||
}
|
||||
}
|
||||
|
||||
func validateBinaryEqualsValues(n name, key Key, values set.StringSet) error {
|
||||
vslice := values.ToSlice()
|
||||
for _, s := range vslice {
|
||||
sbytes, err := base64.StdEncoding.DecodeString(s)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
values.Remove(s)
|
||||
s = string(sbytes)
|
||||
switch key {
|
||||
case S3XAmzCopySource:
|
||||
bucket, object := path2BucketAndObject(s)
|
||||
if object == "" {
|
||||
return fmt.Errorf("invalid value '%v' for '%v' for %v condition", s, S3XAmzCopySource, n)
|
||||
}
|
||||
if err = s3utils.CheckValidBucketName(bucket); err != nil {
|
||||
return err
|
||||
}
|
||||
case S3XAmzServerSideEncryption, S3XAmzServerSideEncryptionCustomerAlgorithm:
|
||||
if s != "AES256" {
|
||||
return fmt.Errorf("invalid value '%v' for '%v' for %v condition", s, S3XAmzServerSideEncryption, n)
|
||||
}
|
||||
case S3XAmzMetadataDirective:
|
||||
if s != "COPY" && s != "REPLACE" {
|
||||
return fmt.Errorf("invalid value '%v' for '%v' for %v condition", s, S3XAmzMetadataDirective, n)
|
||||
}
|
||||
}
|
||||
values.Add(s)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// newBinaryEqualsFunc - returns new BinaryEquals function.
|
||||
func newBinaryEqualsFunc(key Key, values ValueSet) (Function, error) {
|
||||
valueStrings, err := valuesToStringSlice(binaryEquals, values)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return NewBinaryEqualsFunc(key, valueStrings...)
|
||||
}
|
||||
|
||||
// NewBinaryEqualsFunc - returns new BinaryEquals function.
|
||||
func NewBinaryEqualsFunc(key Key, values ...string) (Function, error) {
|
||||
sset := set.CreateStringSet(values...)
|
||||
if err := validateBinaryEqualsValues(binaryEquals, key, sset); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &binaryEqualsFunc{key, sset}, nil
|
||||
}
|
||||
382
pkg/bucket/policy/condition/binaryequalsfunc_test.go
Normal file
382
pkg/bucket/policy/condition/binaryequalsfunc_test.go
Normal file
@@ -0,0 +1,382 @@
|
||||
/*
|
||||
* MinIO Cloud Storage, (C) 2018 MinIO, Inc.
|
||||
*
|
||||
* 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 condition
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestBinaryEqualsFuncEvaluate(t *testing.T) {
|
||||
case1Function, err := newBinaryEqualsFunc(S3XAmzCopySource,
|
||||
NewValueSet(NewStringValue(base64.StdEncoding.EncodeToString([]byte("mybucket/myobject")))))
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error. %v\n", err)
|
||||
}
|
||||
|
||||
case2Function, err := newBinaryEqualsFunc(S3XAmzServerSideEncryption,
|
||||
NewValueSet(NewStringValue(base64.StdEncoding.EncodeToString([]byte("AES256")))))
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error. %v\n", err)
|
||||
}
|
||||
|
||||
case3Function, err := newBinaryEqualsFunc(S3XAmzMetadataDirective,
|
||||
NewValueSet(NewStringValue(base64.StdEncoding.EncodeToString([]byte("REPLACE")))))
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error. %v\n", err)
|
||||
}
|
||||
|
||||
case4Function, err := newBinaryEqualsFunc(S3LocationConstraint,
|
||||
NewValueSet(NewStringValue(base64.StdEncoding.EncodeToString([]byte("eu-west-1")))))
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error. %v\n", err)
|
||||
}
|
||||
|
||||
testCases := []struct {
|
||||
function Function
|
||||
values map[string][]string
|
||||
expectedResult bool
|
||||
}{
|
||||
{case1Function, map[string][]string{"x-amz-copy-source": {"mybucket/myobject"}}, true},
|
||||
{case1Function, map[string][]string{"x-amz-copy-source": {"yourbucket/myobject"}}, false},
|
||||
{case1Function, map[string][]string{}, false},
|
||||
{case1Function, map[string][]string{"delimiter": {"/"}}, false},
|
||||
|
||||
{case2Function, map[string][]string{"x-amz-server-side-encryption": {"AES256"}}, true},
|
||||
{case2Function, map[string][]string{}, false},
|
||||
{case2Function, map[string][]string{"delimiter": {"/"}}, false},
|
||||
|
||||
{case3Function, map[string][]string{"x-amz-metadata-directive": {"REPLACE"}}, true},
|
||||
{case3Function, map[string][]string{"x-amz-metadata-directive": {"COPY"}}, false},
|
||||
{case3Function, map[string][]string{}, false},
|
||||
{case3Function, map[string][]string{"delimiter": {"/"}}, false},
|
||||
|
||||
{case4Function, map[string][]string{"LocationConstraint": {"eu-west-1"}}, true},
|
||||
{case4Function, map[string][]string{"LocationConstraint": {"us-east-1"}}, false},
|
||||
{case4Function, map[string][]string{}, false},
|
||||
{case4Function, map[string][]string{"delimiter": {"/"}}, false},
|
||||
}
|
||||
|
||||
for i, testCase := range testCases {
|
||||
result := testCase.function.evaluate(testCase.values)
|
||||
|
||||
if result != testCase.expectedResult {
|
||||
t.Fatalf("case %v: expected: %v, got: %v\n", i+1, testCase.expectedResult, result)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestBinaryEqualsFuncKey(t *testing.T) {
|
||||
case1Function, err := newBinaryEqualsFunc(S3XAmzCopySource,
|
||||
NewValueSet(NewStringValue(base64.StdEncoding.EncodeToString([]byte("mybucket/myobject")))))
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error. %v\n", err)
|
||||
}
|
||||
|
||||
case2Function, err := newBinaryEqualsFunc(S3XAmzServerSideEncryption,
|
||||
NewValueSet(NewStringValue(base64.StdEncoding.EncodeToString([]byte("AES256")))))
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error. %v\n", err)
|
||||
}
|
||||
|
||||
case3Function, err := newBinaryEqualsFunc(S3XAmzMetadataDirective,
|
||||
NewValueSet(NewStringValue(base64.StdEncoding.EncodeToString([]byte("REPLACE")))))
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error. %v\n", err)
|
||||
}
|
||||
|
||||
case4Function, err := newBinaryEqualsFunc(S3LocationConstraint,
|
||||
NewValueSet(NewStringValue(base64.StdEncoding.EncodeToString([]byte("eu-west-1")))))
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error. %v\n", err)
|
||||
}
|
||||
|
||||
testCases := []struct {
|
||||
function Function
|
||||
expectedResult Key
|
||||
}{
|
||||
{case1Function, S3XAmzCopySource},
|
||||
{case2Function, S3XAmzServerSideEncryption},
|
||||
{case3Function, S3XAmzMetadataDirective},
|
||||
{case4Function, S3LocationConstraint},
|
||||
}
|
||||
|
||||
for i, testCase := range testCases {
|
||||
result := testCase.function.key()
|
||||
|
||||
if result != testCase.expectedResult {
|
||||
t.Fatalf("case %v: expected: %v, got: %v\n", i+1, testCase.expectedResult, result)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestBinaryEqualsFuncToMap(t *testing.T) {
|
||||
case1Function, err := newBinaryEqualsFunc(S3XAmzCopySource,
|
||||
NewValueSet(NewStringValue(base64.StdEncoding.EncodeToString([]byte("mybucket/myobject")))))
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error. %v\n", err)
|
||||
}
|
||||
|
||||
case1Result := map[Key]ValueSet{
|
||||
S3XAmzCopySource: NewValueSet(NewStringValue(base64.StdEncoding.EncodeToString([]byte("mybucket/myobject")))),
|
||||
}
|
||||
|
||||
case2Function, err := newBinaryEqualsFunc(S3XAmzCopySource,
|
||||
NewValueSet(
|
||||
NewStringValue(base64.StdEncoding.EncodeToString([]byte("mybucket/myobject"))),
|
||||
NewStringValue(base64.StdEncoding.EncodeToString([]byte("yourbucket/myobject"))),
|
||||
),
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error. %v\n", err)
|
||||
}
|
||||
|
||||
case2Result := map[Key]ValueSet{
|
||||
S3XAmzCopySource: NewValueSet(
|
||||
NewStringValue(base64.StdEncoding.EncodeToString([]byte("mybucket/myobject"))),
|
||||
NewStringValue(base64.StdEncoding.EncodeToString([]byte("yourbucket/myobject"))),
|
||||
),
|
||||
}
|
||||
|
||||
case3Function, err := newBinaryEqualsFunc(S3XAmzServerSideEncryption,
|
||||
NewValueSet(NewStringValue(base64.StdEncoding.EncodeToString([]byte("AES256")))))
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error. %v\n", err)
|
||||
}
|
||||
|
||||
case3Result := map[Key]ValueSet{
|
||||
S3XAmzServerSideEncryption: NewValueSet(NewStringValue(base64.StdEncoding.EncodeToString([]byte("AES256")))),
|
||||
}
|
||||
|
||||
case4Function, err := newBinaryEqualsFunc(S3XAmzServerSideEncryption,
|
||||
NewValueSet(
|
||||
NewStringValue(base64.StdEncoding.EncodeToString([]byte("AES256"))),
|
||||
),
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error. %v\n", err)
|
||||
}
|
||||
|
||||
case4Result := map[Key]ValueSet{
|
||||
S3XAmzServerSideEncryption: NewValueSet(
|
||||
NewStringValue(base64.StdEncoding.EncodeToString([]byte("AES256"))),
|
||||
),
|
||||
}
|
||||
|
||||
case5Function, err := newBinaryEqualsFunc(S3XAmzMetadataDirective,
|
||||
NewValueSet(NewStringValue(base64.StdEncoding.EncodeToString([]byte("REPLACE")))))
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error. %v\n", err)
|
||||
}
|
||||
|
||||
case5Result := map[Key]ValueSet{
|
||||
S3XAmzMetadataDirective: NewValueSet(NewStringValue(base64.StdEncoding.EncodeToString([]byte("REPLACE")))),
|
||||
}
|
||||
|
||||
case6Function, err := newBinaryEqualsFunc(S3XAmzMetadataDirective,
|
||||
NewValueSet(
|
||||
NewStringValue(base64.StdEncoding.EncodeToString([]byte("REPLACE"))),
|
||||
NewStringValue(base64.StdEncoding.EncodeToString([]byte("COPY"))),
|
||||
),
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error. %v\n", err)
|
||||
}
|
||||
|
||||
case6Result := map[Key]ValueSet{
|
||||
S3XAmzMetadataDirective: NewValueSet(
|
||||
NewStringValue(base64.StdEncoding.EncodeToString([]byte("REPLACE"))),
|
||||
NewStringValue(base64.StdEncoding.EncodeToString([]byte("COPY"))),
|
||||
),
|
||||
}
|
||||
|
||||
case7Function, err := newBinaryEqualsFunc(S3LocationConstraint,
|
||||
NewValueSet(NewStringValue(base64.StdEncoding.EncodeToString([]byte("eu-west-1")))))
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error. %v\n", err)
|
||||
}
|
||||
|
||||
case7Result := map[Key]ValueSet{
|
||||
S3LocationConstraint: NewValueSet(NewStringValue(base64.StdEncoding.EncodeToString([]byte("eu-west-1")))),
|
||||
}
|
||||
|
||||
case8Function, err := newBinaryEqualsFunc(S3LocationConstraint,
|
||||
NewValueSet(
|
||||
NewStringValue(base64.StdEncoding.EncodeToString([]byte("eu-west-1"))),
|
||||
NewStringValue(base64.StdEncoding.EncodeToString([]byte("us-west-1"))),
|
||||
),
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error. %v\n", err)
|
||||
}
|
||||
|
||||
case8Result := map[Key]ValueSet{
|
||||
S3LocationConstraint: NewValueSet(
|
||||
NewStringValue(base64.StdEncoding.EncodeToString([]byte("eu-west-1"))),
|
||||
NewStringValue(base64.StdEncoding.EncodeToString([]byte("us-west-1"))),
|
||||
),
|
||||
}
|
||||
|
||||
testCases := []struct {
|
||||
f Function
|
||||
expectedResult map[Key]ValueSet
|
||||
}{
|
||||
{case1Function, case1Result},
|
||||
{case2Function, case2Result},
|
||||
{case3Function, case3Result},
|
||||
{case4Function, case4Result},
|
||||
{case5Function, case5Result},
|
||||
{case6Function, case6Result},
|
||||
{case7Function, case7Result},
|
||||
{case8Function, case8Result},
|
||||
{&binaryEqualsFunc{}, nil},
|
||||
}
|
||||
|
||||
for i, testCase := range testCases {
|
||||
result := testCase.f.toMap()
|
||||
|
||||
if !reflect.DeepEqual(result, testCase.expectedResult) {
|
||||
t.Fatalf("case %v: result: expected: %v, got: %v\n", i+1, testCase.expectedResult, result)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewBinaryEqualsFunc(t *testing.T) {
|
||||
case1Function, err := newBinaryEqualsFunc(S3XAmzCopySource,
|
||||
NewValueSet(NewStringValue(base64.StdEncoding.EncodeToString([]byte("mybucket/myobject")))))
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error. %v\n", err)
|
||||
}
|
||||
|
||||
case2Function, err := newBinaryEqualsFunc(S3XAmzCopySource,
|
||||
NewValueSet(
|
||||
NewStringValue(base64.StdEncoding.EncodeToString([]byte("mybucket/myobject"))),
|
||||
NewStringValue(base64.StdEncoding.EncodeToString([]byte("yourbucket/myobject"))),
|
||||
),
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error. %v\n", err)
|
||||
}
|
||||
|
||||
case3Function, err := newBinaryEqualsFunc(S3XAmzServerSideEncryption,
|
||||
NewValueSet(NewStringValue(base64.StdEncoding.EncodeToString([]byte("AES256")))))
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error. %v\n", err)
|
||||
}
|
||||
|
||||
case4Function, err := newBinaryEqualsFunc(S3XAmzServerSideEncryption,
|
||||
NewValueSet(
|
||||
NewStringValue(base64.StdEncoding.EncodeToString([]byte("AES256"))),
|
||||
),
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error. %v\n", err)
|
||||
}
|
||||
|
||||
case5Function, err := newBinaryEqualsFunc(S3XAmzMetadataDirective,
|
||||
NewValueSet(NewStringValue(base64.StdEncoding.EncodeToString([]byte("REPLACE")))))
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error. %v\n", err)
|
||||
}
|
||||
|
||||
case6Function, err := newBinaryEqualsFunc(S3XAmzMetadataDirective,
|
||||
NewValueSet(
|
||||
NewStringValue(base64.StdEncoding.EncodeToString([]byte("REPLACE"))),
|
||||
NewStringValue(base64.StdEncoding.EncodeToString([]byte("COPY"))),
|
||||
),
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error. %v\n", err)
|
||||
}
|
||||
|
||||
case7Function, err := newBinaryEqualsFunc(S3LocationConstraint,
|
||||
NewValueSet(NewStringValue(base64.StdEncoding.EncodeToString([]byte("eu-west-1")))))
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error. %v\n", err)
|
||||
}
|
||||
|
||||
case8Function, err := newBinaryEqualsFunc(S3LocationConstraint,
|
||||
NewValueSet(
|
||||
NewStringValue(base64.StdEncoding.EncodeToString([]byte("eu-west-1"))),
|
||||
NewStringValue(base64.StdEncoding.EncodeToString([]byte("us-west-1"))),
|
||||
),
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error. %v\n", err)
|
||||
}
|
||||
|
||||
testCases := []struct {
|
||||
key Key
|
||||
values ValueSet
|
||||
expectedResult Function
|
||||
expectErr bool
|
||||
}{
|
||||
{S3XAmzCopySource, NewValueSet(NewStringValue(base64.StdEncoding.EncodeToString([]byte("mybucket/myobject")))), case1Function, false},
|
||||
{S3XAmzCopySource,
|
||||
NewValueSet(
|
||||
NewStringValue(base64.StdEncoding.EncodeToString([]byte("mybucket/myobject"))),
|
||||
NewStringValue(base64.StdEncoding.EncodeToString([]byte("yourbucket/myobject"))),
|
||||
), case2Function, false},
|
||||
|
||||
{S3XAmzServerSideEncryption, NewValueSet(NewStringValue(base64.StdEncoding.EncodeToString([]byte("AES256")))), case3Function, false},
|
||||
{S3XAmzServerSideEncryption,
|
||||
NewValueSet(
|
||||
NewStringValue(base64.StdEncoding.EncodeToString([]byte("AES256"))),
|
||||
), case4Function, false},
|
||||
|
||||
{S3XAmzMetadataDirective, NewValueSet(NewStringValue(base64.StdEncoding.EncodeToString([]byte("REPLACE")))), case5Function, false},
|
||||
{S3XAmzMetadataDirective,
|
||||
NewValueSet(
|
||||
NewStringValue(base64.StdEncoding.EncodeToString([]byte("REPLACE"))),
|
||||
NewStringValue(base64.StdEncoding.EncodeToString([]byte("COPY"))),
|
||||
), case6Function, false},
|
||||
|
||||
{S3LocationConstraint, NewValueSet(NewStringValue(base64.StdEncoding.EncodeToString([]byte("eu-west-1")))), case7Function, false},
|
||||
{S3LocationConstraint,
|
||||
NewValueSet(
|
||||
NewStringValue(base64.StdEncoding.EncodeToString([]byte("eu-west-1"))),
|
||||
NewStringValue(base64.StdEncoding.EncodeToString([]byte("us-west-1"))),
|
||||
), case8Function, false},
|
||||
|
||||
// Unsupported value error.
|
||||
{S3XAmzCopySource, NewValueSet(NewStringValue(base64.StdEncoding.EncodeToString([]byte("mybucket/myobject"))), NewIntValue(7)), nil, true},
|
||||
{S3XAmzServerSideEncryption, NewValueSet(NewStringValue(base64.StdEncoding.EncodeToString([]byte("AES256"))), NewIntValue(7)), nil, true},
|
||||
{S3XAmzMetadataDirective, NewValueSet(NewStringValue(base64.StdEncoding.EncodeToString([]byte("REPLACE"))), NewIntValue(7)), nil, true},
|
||||
{S3LocationConstraint, NewValueSet(NewStringValue(base64.StdEncoding.EncodeToString([]byte("eu-west-1"))), NewIntValue(7)), nil, true},
|
||||
|
||||
// Invalid value error.
|
||||
{S3XAmzCopySource, NewValueSet(NewStringValue(base64.StdEncoding.EncodeToString([]byte("mybucket")))), nil, true},
|
||||
{S3XAmzServerSideEncryption, NewValueSet(NewStringValue(base64.StdEncoding.EncodeToString([]byte("SSE-C")))), nil, true},
|
||||
{S3XAmzMetadataDirective, NewValueSet(NewStringValue(base64.StdEncoding.EncodeToString([]byte("DUPLICATE")))), nil, true},
|
||||
}
|
||||
|
||||
for i, testCase := range testCases {
|
||||
result, err := newBinaryEqualsFunc(testCase.key, testCase.values)
|
||||
expectErr := (err != nil)
|
||||
|
||||
if expectErr != testCase.expectErr {
|
||||
t.Fatalf("case %v: error: expected: %v, got: %v\n", i+1, testCase.expectErr, expectErr)
|
||||
}
|
||||
|
||||
if !testCase.expectErr {
|
||||
if !reflect.DeepEqual(result, testCase.expectedResult) {
|
||||
t.Fatalf("case %v: result: expected: %v, got: %v\n", i+1, testCase.expectedResult, result)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
105
pkg/bucket/policy/condition/boolfunc.go
Normal file
105
pkg/bucket/policy/condition/boolfunc.go
Normal file
@@ -0,0 +1,105 @@
|
||||
/*
|
||||
* MinIO Cloud Storage, (C) 2018 MinIO, Inc.
|
||||
*
|
||||
* 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 condition
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"reflect"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
// booleanFunc - Bool condition function. It checks whether Key is true or false.
|
||||
// https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_elements_condition_operators.html#Conditions_Boolean
|
||||
type booleanFunc struct {
|
||||
k Key
|
||||
value string
|
||||
}
|
||||
|
||||
// evaluate() - evaluates to check whether Key is present in given values or not.
|
||||
// Depending on condition boolean value, this function returns true or false.
|
||||
func (f booleanFunc) evaluate(values map[string][]string) bool {
|
||||
requestValue, ok := values[http.CanonicalHeaderKey(f.k.Name())]
|
||||
if !ok {
|
||||
requestValue = values[f.k.Name()]
|
||||
}
|
||||
|
||||
return f.value == requestValue[0]
|
||||
}
|
||||
|
||||
// key() - returns condition key which is used by this condition function.
|
||||
func (f booleanFunc) key() Key {
|
||||
return f.k
|
||||
}
|
||||
|
||||
// name() - returns "Bool" condition name.
|
||||
func (f booleanFunc) name() name {
|
||||
return boolean
|
||||
}
|
||||
|
||||
func (f booleanFunc) String() string {
|
||||
return fmt.Sprintf("%v:%v:%v", boolean, f.k, f.value)
|
||||
}
|
||||
|
||||
// toMap - returns map representation of this function.
|
||||
func (f booleanFunc) toMap() map[Key]ValueSet {
|
||||
if !f.k.IsValid() {
|
||||
return nil
|
||||
}
|
||||
|
||||
return map[Key]ValueSet{
|
||||
f.k: NewValueSet(NewStringValue(f.value)),
|
||||
}
|
||||
}
|
||||
|
||||
func newBooleanFunc(key Key, values ValueSet) (Function, error) {
|
||||
if key != AWSSecureTransport {
|
||||
return nil, fmt.Errorf("only %v key is allowed for %v condition", AWSSecureTransport, boolean)
|
||||
}
|
||||
|
||||
if len(values) != 1 {
|
||||
return nil, fmt.Errorf("only one value is allowed for boolean condition")
|
||||
}
|
||||
|
||||
var value Value
|
||||
for v := range values {
|
||||
value = v
|
||||
switch v.GetType() {
|
||||
case reflect.Bool:
|
||||
if _, err := v.GetBool(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
case reflect.String:
|
||||
s, err := v.GetString()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if _, err = strconv.ParseBool(s); err != nil {
|
||||
return nil, fmt.Errorf("value must be a boolean string for boolean condition")
|
||||
}
|
||||
default:
|
||||
return nil, fmt.Errorf("value must be a boolean for boolean condition")
|
||||
}
|
||||
}
|
||||
|
||||
return &booleanFunc{key, value.String()}, nil
|
||||
}
|
||||
|
||||
// NewBoolFunc - returns new Bool function.
|
||||
func NewBoolFunc(key Key, value string) (Function, error) {
|
||||
return &booleanFunc{key, value}, nil
|
||||
}
|
||||
152
pkg/bucket/policy/condition/boolfunc_test.go
Normal file
152
pkg/bucket/policy/condition/boolfunc_test.go
Normal file
@@ -0,0 +1,152 @@
|
||||
/*
|
||||
* MinIO Cloud Storage, (C) 2018 MinIO, Inc.
|
||||
*
|
||||
* 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 condition
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestBooleanFuncEvaluate(t *testing.T) {
|
||||
case1Function, err := newBooleanFunc(AWSSecureTransport, NewValueSet(NewBoolValue(true)))
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error. %v\n", err)
|
||||
}
|
||||
|
||||
case2Function, err := newBooleanFunc(AWSSecureTransport, NewValueSet(NewBoolValue(false)))
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error. %v\n", err)
|
||||
}
|
||||
|
||||
testCases := []struct {
|
||||
function Function
|
||||
values map[string][]string
|
||||
expectedResult bool
|
||||
}{
|
||||
{case1Function, map[string][]string{"SecureTransport": {"true"}}, true},
|
||||
{case2Function, map[string][]string{"SecureTransport": {"false"}}, true},
|
||||
}
|
||||
|
||||
for i, testCase := range testCases {
|
||||
result := testCase.function.evaluate(testCase.values)
|
||||
|
||||
if result != testCase.expectedResult {
|
||||
t.Errorf("case %v: expected: %v, got: %v\n", i+1, testCase.expectedResult, result)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestBooleanFuncKey(t *testing.T) {
|
||||
case1Function, err := newBooleanFunc(AWSSecureTransport, NewValueSet(NewBoolValue(true)))
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error. %v\n", err)
|
||||
}
|
||||
|
||||
testCases := []struct {
|
||||
function Function
|
||||
expectedResult Key
|
||||
}{
|
||||
{case1Function, AWSSecureTransport},
|
||||
}
|
||||
|
||||
for i, testCase := range testCases {
|
||||
result := testCase.function.key()
|
||||
|
||||
if result != testCase.expectedResult {
|
||||
t.Fatalf("case %v: expected: %v, got: %v\n", i+1, testCase.expectedResult, result)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestBooleanFuncToMap(t *testing.T) {
|
||||
case1Function, err := newBooleanFunc(AWSSecureTransport, NewValueSet(NewBoolValue(true)))
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error. %v\n", err)
|
||||
}
|
||||
|
||||
case1Result := map[Key]ValueSet{
|
||||
AWSSecureTransport: NewValueSet(NewStringValue("true")),
|
||||
}
|
||||
|
||||
case2Function, err := newBooleanFunc(AWSSecureTransport, NewValueSet(NewBoolValue(false)))
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error. %v\n", err)
|
||||
}
|
||||
|
||||
case2Result := map[Key]ValueSet{
|
||||
AWSSecureTransport: NewValueSet(NewStringValue("false")),
|
||||
}
|
||||
|
||||
testCases := []struct {
|
||||
f Function
|
||||
expectedResult map[Key]ValueSet
|
||||
}{
|
||||
{case1Function, case1Result},
|
||||
{case2Function, case2Result},
|
||||
}
|
||||
|
||||
for i, testCase := range testCases {
|
||||
result := testCase.f.toMap()
|
||||
|
||||
if !reflect.DeepEqual(result, testCase.expectedResult) {
|
||||
t.Fatalf("case %v: result: expected: %v, got: %v\n", i+1, testCase.expectedResult, result)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewBooleanFunc(t *testing.T) {
|
||||
case1Function, err := newBooleanFunc(AWSSecureTransport, NewValueSet(NewBoolValue(true)))
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error. %v\n", err)
|
||||
}
|
||||
|
||||
case2Function, err := newBooleanFunc(AWSSecureTransport, NewValueSet(NewBoolValue(false)))
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error. %v\n", err)
|
||||
}
|
||||
|
||||
testCases := []struct {
|
||||
key Key
|
||||
values ValueSet
|
||||
expectedResult Function
|
||||
expectErr bool
|
||||
}{
|
||||
{AWSSecureTransport, NewValueSet(NewBoolValue(true)), case1Function, false},
|
||||
{AWSSecureTransport, NewValueSet(NewStringValue("false")), case2Function, false},
|
||||
// Multiple values error.
|
||||
{AWSSecureTransport, NewValueSet(NewStringValue("true"), NewStringValue("false")), nil, true},
|
||||
// Invalid boolean string error.
|
||||
{AWSSecureTransport, NewValueSet(NewStringValue("foo")), nil, true},
|
||||
// Invalid value error.
|
||||
{AWSSecureTransport, NewValueSet(NewIntValue(7)), nil, true},
|
||||
}
|
||||
|
||||
for i, testCase := range testCases {
|
||||
result, err := newBooleanFunc(testCase.key, testCase.values)
|
||||
expectErr := (err != nil)
|
||||
|
||||
if expectErr != testCase.expectErr {
|
||||
t.Fatalf("case %v: error: expected: %v, got: %v\n", i+1, testCase.expectErr, expectErr)
|
||||
}
|
||||
|
||||
if !testCase.expectErr {
|
||||
if !reflect.DeepEqual(result, testCase.expectedResult) {
|
||||
t.Fatalf("case %v: result: expected: %v, got: %v\n", i+1, testCase.expectedResult, result)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
175
pkg/bucket/policy/condition/func.go
Normal file
175
pkg/bucket/policy/condition/func.go
Normal file
@@ -0,0 +1,175 @@
|
||||
/*
|
||||
* MinIO Cloud Storage, (C) 2018 MinIO, Inc.
|
||||
*
|
||||
* 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 condition
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"sort"
|
||||
)
|
||||
|
||||
// Function - condition function interface.
|
||||
type Function interface {
|
||||
// evaluate() - evaluates this condition function with given values.
|
||||
evaluate(values map[string][]string) bool
|
||||
|
||||
// key() - returns condition key used in this function.
|
||||
key() Key
|
||||
|
||||
// name() - returns condition name of this function.
|
||||
name() name
|
||||
|
||||
// String() - returns string representation of function.
|
||||
String() string
|
||||
|
||||
// toMap - returns map representation of this function.
|
||||
toMap() map[Key]ValueSet
|
||||
}
|
||||
|
||||
// Functions - list of functions.
|
||||
type Functions []Function
|
||||
|
||||
// Evaluate - evaluates all functions with given values map. Each function is evaluated
|
||||
// sequencely and next function is called only if current function succeeds.
|
||||
func (functions Functions) Evaluate(values map[string][]string) bool {
|
||||
for _, f := range functions {
|
||||
if !f.evaluate(values) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// Keys - returns list of keys used in all functions.
|
||||
func (functions Functions) Keys() KeySet {
|
||||
keySet := NewKeySet()
|
||||
|
||||
for _, f := range functions {
|
||||
keySet.Add(f.key())
|
||||
}
|
||||
|
||||
return keySet
|
||||
}
|
||||
|
||||
// MarshalJSON - encodes Functions to JSON data.
|
||||
func (functions Functions) MarshalJSON() ([]byte, error) {
|
||||
nm := make(map[name]map[Key]ValueSet)
|
||||
|
||||
for _, f := range functions {
|
||||
if _, ok := nm[f.name()]; ok {
|
||||
for k, v := range f.toMap() {
|
||||
nm[f.name()][k] = v
|
||||
}
|
||||
} else {
|
||||
nm[f.name()] = f.toMap()
|
||||
}
|
||||
}
|
||||
|
||||
return json.Marshal(nm)
|
||||
}
|
||||
|
||||
func (functions Functions) String() string {
|
||||
funcStrings := []string{}
|
||||
for _, f := range functions {
|
||||
s := fmt.Sprintf("%v", f)
|
||||
funcStrings = append(funcStrings, s)
|
||||
}
|
||||
sort.Strings(funcStrings)
|
||||
|
||||
return fmt.Sprintf("%v", funcStrings)
|
||||
}
|
||||
|
||||
var conditionFuncMap = map[name]func(Key, ValueSet) (Function, error){
|
||||
stringEquals: newStringEqualsFunc,
|
||||
stringNotEquals: newStringNotEqualsFunc,
|
||||
stringEqualsIgnoreCase: newStringEqualsIgnoreCaseFunc,
|
||||
stringNotEqualsIgnoreCase: newStringNotEqualsIgnoreCaseFunc,
|
||||
binaryEquals: newBinaryEqualsFunc,
|
||||
stringLike: newStringLikeFunc,
|
||||
stringNotLike: newStringNotLikeFunc,
|
||||
ipAddress: newIPAddressFunc,
|
||||
notIPAddress: newNotIPAddressFunc,
|
||||
null: newNullFunc,
|
||||
boolean: newBooleanFunc,
|
||||
// Add new conditions here.
|
||||
}
|
||||
|
||||
// UnmarshalJSON - decodes JSON data to Functions.
|
||||
func (functions *Functions) UnmarshalJSON(data []byte) error {
|
||||
// As string kind, int kind then json.Unmarshaler is checked at
|
||||
// https://github.com/golang/go/blob/master/src/encoding/json/decode.go#L618
|
||||
// UnmarshalJSON() is not called for types extending string
|
||||
// see https://play.golang.org/p/HrSsKksHvrS, better way to do is
|
||||
// https://play.golang.org/p/y9ElWpBgVAB
|
||||
//
|
||||
// Due to this issue, name and Key types cannot be used as map keys below.
|
||||
nm := make(map[string]map[string]ValueSet)
|
||||
if err := json.Unmarshal(data, &nm); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(nm) == 0 {
|
||||
return fmt.Errorf("condition must not be empty")
|
||||
}
|
||||
|
||||
funcs := []Function{}
|
||||
for nameString, args := range nm {
|
||||
n, err := parseName(nameString)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for keyString, values := range args {
|
||||
key, err := parseKey(keyString)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
vfn, ok := conditionFuncMap[n]
|
||||
if !ok {
|
||||
return fmt.Errorf("condition %v is not handled", n)
|
||||
}
|
||||
|
||||
f, err := vfn(key, values)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
funcs = append(funcs, f)
|
||||
}
|
||||
}
|
||||
|
||||
*functions = funcs
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GobEncode - encodes Functions to gob data.
|
||||
func (functions Functions) GobEncode() ([]byte, error) {
|
||||
return functions.MarshalJSON()
|
||||
}
|
||||
|
||||
// GobDecode - decodes gob data to Functions.
|
||||
func (functions *Functions) GobDecode(data []byte) error {
|
||||
return functions.UnmarshalJSON(data)
|
||||
}
|
||||
|
||||
// NewFunctions - returns new Functions with given function list.
|
||||
func NewFunctions(functions ...Function) Functions {
|
||||
return Functions(functions)
|
||||
}
|
||||
360
pkg/bucket/policy/condition/func_test.go
Normal file
360
pkg/bucket/policy/condition/func_test.go
Normal file
@@ -0,0 +1,360 @@
|
||||
/*
|
||||
* MinIO Cloud Storage, (C) 2018 MinIO, Inc.
|
||||
*
|
||||
* 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 condition
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestFunctionsEvaluate(t *testing.T) {
|
||||
func1, err := newNullFunc(S3XAmzCopySource, NewValueSet(NewBoolValue(true)))
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error. %v\n", err)
|
||||
}
|
||||
|
||||
func2, err := newIPAddressFunc(AWSSourceIP, NewValueSet(NewStringValue("192.168.1.0/24")))
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error. %v\n", err)
|
||||
}
|
||||
|
||||
func3, err := newStringEqualsFunc(S3XAmzCopySource, NewValueSet(NewStringValue("mybucket/myobject")))
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error. %v\n", err)
|
||||
}
|
||||
|
||||
func4, err := newStringLikeFunc(S3XAmzCopySource, NewValueSet(NewStringValue("mybucket/myobject*")))
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error. %v\n", err)
|
||||
}
|
||||
|
||||
case1Function := NewFunctions(func1, func2, func3, func4)
|
||||
|
||||
testCases := []struct {
|
||||
functions Functions
|
||||
values map[string][]string
|
||||
expectedResult bool
|
||||
}{
|
||||
{case1Function, map[string][]string{
|
||||
"x-amz-copy-source": {"mybucket/myobject"},
|
||||
"SourceIp": {"192.168.1.10"},
|
||||
}, false},
|
||||
{case1Function, map[string][]string{
|
||||
"x-amz-copy-source": {"mybucket/myobject"},
|
||||
"SourceIp": {"192.168.1.10"},
|
||||
"Refer": {"http://example.org/"},
|
||||
}, false},
|
||||
{case1Function, map[string][]string{"x-amz-copy-source": {"mybucket/myobject"}}, false},
|
||||
{case1Function, map[string][]string{"SourceIp": {"192.168.1.10"}}, false},
|
||||
{case1Function, map[string][]string{
|
||||
"x-amz-copy-source": {"mybucket/yourobject"},
|
||||
"SourceIp": {"192.168.1.10"},
|
||||
}, false},
|
||||
{case1Function, map[string][]string{
|
||||
"x-amz-copy-source": {"mybucket/myobject"},
|
||||
"SourceIp": {"192.168.2.10"},
|
||||
}, false},
|
||||
{case1Function, map[string][]string{
|
||||
"x-amz-copy-source": {"mybucket/myobject"},
|
||||
"Refer": {"http://example.org/"},
|
||||
}, false},
|
||||
}
|
||||
|
||||
for i, testCase := range testCases {
|
||||
result := testCase.functions.Evaluate(testCase.values)
|
||||
|
||||
if result != testCase.expectedResult {
|
||||
t.Errorf("case %v: expected: %v, got: %v\n", i+1, testCase.expectedResult, result)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestFunctionsKeys(t *testing.T) {
|
||||
func1, err := newNullFunc(S3XAmzCopySource, NewValueSet(NewBoolValue(true)))
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error. %v\n", err)
|
||||
}
|
||||
|
||||
func2, err := newIPAddressFunc(AWSSourceIP, NewValueSet(NewStringValue("192.168.1.0/24")))
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error. %v\n", err)
|
||||
}
|
||||
|
||||
func3, err := newStringEqualsFunc(S3XAmzCopySource, NewValueSet(NewStringValue("mybucket/myobject")))
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error. %v\n", err)
|
||||
}
|
||||
|
||||
func4, err := newStringLikeFunc(S3XAmzCopySource, NewValueSet(NewStringValue("mybucket/myobject*")))
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error. %v\n", err)
|
||||
}
|
||||
|
||||
testCases := []struct {
|
||||
functions Functions
|
||||
expectedResult KeySet
|
||||
}{
|
||||
{NewFunctions(func1, func2, func3, func4), NewKeySet(S3XAmzCopySource, AWSSourceIP)},
|
||||
}
|
||||
|
||||
for i, testCase := range testCases {
|
||||
result := testCase.functions.Keys()
|
||||
|
||||
if !reflect.DeepEqual(result, testCase.expectedResult) {
|
||||
t.Fatalf("case %v: expected: %v, got: %v\n", i+1, testCase.expectedResult, result)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestFunctionsMarshalJSON(t *testing.T) {
|
||||
func1, err := newStringLikeFunc(S3XAmzMetadataDirective, NewValueSet(NewStringValue("REPL*")))
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error. %v\n", err)
|
||||
}
|
||||
|
||||
func2, err := newStringEqualsFunc(S3XAmzCopySource, NewValueSet(NewStringValue("mybucket/myobject")))
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error. %v\n", err)
|
||||
}
|
||||
|
||||
func3, err := newStringNotEqualsFunc(S3XAmzServerSideEncryption, NewValueSet(NewStringValue("AES256")))
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error. %v\n", err)
|
||||
}
|
||||
|
||||
func4, err := newNotIPAddressFunc(AWSSourceIP,
|
||||
NewValueSet(NewStringValue("10.1.10.0/24")))
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error. %v\n", err)
|
||||
}
|
||||
|
||||
func5, err := newStringNotLikeFunc(S3XAmzStorageClass, NewValueSet(NewStringValue("STANDARD")))
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error. %v\n", err)
|
||||
}
|
||||
|
||||
func6, err := newNullFunc(S3XAmzServerSideEncryptionCustomerAlgorithm, NewValueSet(NewBoolValue(true)))
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error. %v\n", err)
|
||||
}
|
||||
|
||||
func7, err := newIPAddressFunc(AWSSourceIP,
|
||||
NewValueSet(NewStringValue("192.168.1.0/24")))
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error. %v\n", err)
|
||||
}
|
||||
|
||||
case1Result := []byte(`{"IpAddress":{"aws:SourceIp":["192.168.1.0/24"]},"NotIpAddress":{"aws:SourceIp":["10.1.10.0/24"]},"Null":{"s3:x-amz-server-side-encryption-customer-algorithm":[true]},"StringEquals":{"s3:x-amz-copy-source":["mybucket/myobject"]},"StringLike":{"s3:x-amz-metadata-directive":["REPL*"]},"StringNotEquals":{"s3:x-amz-server-side-encryption":["AES256"]},"StringNotLike":{"s3:x-amz-storage-class":["STANDARD"]}}`)
|
||||
|
||||
case2Result := []byte(`{"Null":{"s3:x-amz-server-side-encryption-customer-algorithm":[true]}}`)
|
||||
|
||||
testCases := []struct {
|
||||
functions Functions
|
||||
expectedResult []byte
|
||||
expectErr bool
|
||||
}{
|
||||
{NewFunctions(func1, func2, func3, func4, func5, func6, func7), case1Result, false},
|
||||
{NewFunctions(func6), case2Result, false},
|
||||
{NewFunctions(), []byte(`{}`), false},
|
||||
{nil, []byte(`{}`), false},
|
||||
}
|
||||
|
||||
for i, testCase := range testCases {
|
||||
result, err := json.Marshal(testCase.functions)
|
||||
expectErr := (err != nil)
|
||||
|
||||
if testCase.expectErr != expectErr {
|
||||
t.Fatalf("case %v: error: expected: %v, got: %v", i+1, testCase.expectErr, expectErr)
|
||||
}
|
||||
|
||||
if !testCase.expectErr {
|
||||
if !reflect.DeepEqual(result, testCase.expectedResult) {
|
||||
t.Fatalf("case %v: result: expected: %v, got: %v", i+1, string(testCase.expectedResult), string(result))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestFunctionsUnmarshalJSON(t *testing.T) {
|
||||
case1Data := []byte(`{
|
||||
"StringLike": {
|
||||
"s3:x-amz-metadata-directive": "REPL*"
|
||||
},
|
||||
"StringEquals": {
|
||||
"s3:x-amz-copy-source": "mybucket/myobject"
|
||||
},
|
||||
"StringNotEquals": {
|
||||
"s3:x-amz-server-side-encryption": "AES256"
|
||||
},
|
||||
"NotIpAddress": {
|
||||
"aws:SourceIp": [
|
||||
"10.1.10.0/24",
|
||||
"10.10.1.0/24"
|
||||
]
|
||||
},
|
||||
"StringNotLike": {
|
||||
"s3:x-amz-storage-class": "STANDARD"
|
||||
},
|
||||
"Null": {
|
||||
"s3:x-amz-server-side-encryption-customer-algorithm": true
|
||||
},
|
||||
"IpAddress": {
|
||||
"aws:SourceIp": [
|
||||
"192.168.1.0/24",
|
||||
"192.168.2.0/24"
|
||||
]
|
||||
}
|
||||
}`)
|
||||
func1, err := newStringLikeFunc(S3XAmzMetadataDirective, NewValueSet(NewStringValue("REPL*")))
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error. %v\n", err)
|
||||
}
|
||||
|
||||
func2, err := newStringEqualsFunc(S3XAmzCopySource, NewValueSet(NewStringValue("mybucket/myobject")))
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error. %v\n", err)
|
||||
}
|
||||
|
||||
func3, err := newStringNotEqualsFunc(S3XAmzServerSideEncryption, NewValueSet(NewStringValue("AES256")))
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error. %v\n", err)
|
||||
}
|
||||
|
||||
func4, err := newNotIPAddressFunc(AWSSourceIP,
|
||||
NewValueSet(NewStringValue("10.1.10.0/24"), NewStringValue("10.10.1.0/24")))
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error. %v\n", err)
|
||||
}
|
||||
|
||||
func5, err := newStringNotLikeFunc(S3XAmzStorageClass, NewValueSet(NewStringValue("STANDARD")))
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error. %v\n", err)
|
||||
}
|
||||
|
||||
func6, err := newNullFunc(S3XAmzServerSideEncryptionCustomerAlgorithm, NewValueSet(NewBoolValue(true)))
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error. %v\n", err)
|
||||
}
|
||||
|
||||
func7, err := newIPAddressFunc(AWSSourceIP,
|
||||
NewValueSet(NewStringValue("192.168.1.0/24"), NewStringValue("192.168.2.0/24")))
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error. %v\n", err)
|
||||
}
|
||||
|
||||
case2Data := []byte(`{
|
||||
"Null": {
|
||||
"s3:x-amz-server-side-encryption-customer-algorithm": true
|
||||
},
|
||||
"Null": {
|
||||
"s3:x-amz-server-side-encryption-customer-algorithm": "true"
|
||||
}
|
||||
}`)
|
||||
|
||||
case3Data := []byte(`{}`)
|
||||
|
||||
// Remove this test after supporting date conditions.
|
||||
case4Data := []byte(`{
|
||||
"DateEquals": { "aws:CurrentTime": "2013-06-30T00:00:00Z" }
|
||||
}`)
|
||||
|
||||
case5Data := []byte(`{
|
||||
"StringLike": {
|
||||
"s3:x-amz-metadata-directive": "REPL*"
|
||||
},
|
||||
"StringEquals": {
|
||||
"s3:x-amz-copy-source": "mybucket/myobject",
|
||||
"s3:prefix": [
|
||||
"",
|
||||
"home/"
|
||||
],
|
||||
"s3:delimiter": [
|
||||
"/"
|
||||
]
|
||||
},
|
||||
"StringNotEquals": {
|
||||
"s3:x-amz-server-side-encryption": "AES256"
|
||||
},
|
||||
"NotIpAddress": {
|
||||
"aws:SourceIp": [
|
||||
"10.1.10.0/24",
|
||||
"10.10.1.0/24"
|
||||
]
|
||||
},
|
||||
"StringNotLike": {
|
||||
"s3:x-amz-storage-class": "STANDARD"
|
||||
},
|
||||
"Null": {
|
||||
"s3:x-amz-server-side-encryption-customer-algorithm": true
|
||||
},
|
||||
"IpAddress": {
|
||||
"aws:SourceIp": [
|
||||
"192.168.1.0/24",
|
||||
"192.168.2.0/24"
|
||||
]
|
||||
}
|
||||
}`)
|
||||
|
||||
func2_1, err := newStringEqualsFunc(S3XAmzCopySource, NewValueSet(NewStringValue("mybucket/myobject")))
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error. %v\n", err)
|
||||
}
|
||||
|
||||
func2_2, err := newStringEqualsFunc(S3Prefix, NewValueSet(NewStringValue(""), NewStringValue("home/")))
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error. %v\n", err)
|
||||
}
|
||||
|
||||
func2_3, err := newStringEqualsFunc(S3Delimiter, NewValueSet(NewStringValue("/")))
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error. %v\n", err)
|
||||
}
|
||||
|
||||
testCases := []struct {
|
||||
data []byte
|
||||
expectedResult Functions
|
||||
expectErr bool
|
||||
}{
|
||||
// Success case, basic conditions.
|
||||
{case1Data, NewFunctions(func1, func2, func3, func4, func5, func6, func7), false},
|
||||
// Duplicate conditions, success case only one value is preserved.
|
||||
{case2Data, NewFunctions(func6), false},
|
||||
// empty condition error.
|
||||
{case3Data, nil, true},
|
||||
// unsupported condition error.
|
||||
{case4Data, nil, true},
|
||||
// Success case multiple keys, same condition.
|
||||
{case5Data, NewFunctions(func1, func2_1, func2_2, func2_3, func3, func4, func5, func6, func7), false},
|
||||
}
|
||||
|
||||
for i, testCase := range testCases {
|
||||
result := new(Functions)
|
||||
err := json.Unmarshal(testCase.data, result)
|
||||
expectErr := (err != nil)
|
||||
|
||||
if testCase.expectErr != expectErr {
|
||||
t.Fatalf("case %v: error: expected: %v, got: %v", i+1, testCase.expectErr, expectErr)
|
||||
}
|
||||
|
||||
if !testCase.expectErr {
|
||||
if (*result).String() != testCase.expectedResult.String() {
|
||||
t.Fatalf("case %v: result: expected: %v, got: %v", i+1, testCase.expectedResult, *result)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
186
pkg/bucket/policy/condition/ipaddressfunc.go
Normal file
186
pkg/bucket/policy/condition/ipaddressfunc.go
Normal file
@@ -0,0 +1,186 @@
|
||||
/*
|
||||
* MinIO Cloud Storage, (C) 2018 MinIO, Inc.
|
||||
*
|
||||
* 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 condition
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"sort"
|
||||
)
|
||||
|
||||
func toIPAddressFuncString(n name, key Key, values []*net.IPNet) string {
|
||||
valueStrings := []string{}
|
||||
for _, value := range values {
|
||||
valueStrings = append(valueStrings, value.String())
|
||||
}
|
||||
sort.Strings(valueStrings)
|
||||
|
||||
return fmt.Sprintf("%v:%v:%v", n, key, valueStrings)
|
||||
}
|
||||
|
||||
// ipAddressFunc - IP address function. It checks whether value by Key in given
|
||||
// values is in IP network. Here Key must be AWSSourceIP.
|
||||
// For example,
|
||||
// - if values = [192.168.1.0/24], at evaluate() it returns whether IP address
|
||||
// in value map for AWSSourceIP falls in the network 192.168.1.10/24.
|
||||
type ipAddressFunc struct {
|
||||
k Key
|
||||
values []*net.IPNet
|
||||
}
|
||||
|
||||
// evaluate() - evaluates to check whether IP address in values map for AWSSourceIP
|
||||
// falls in one of network or not.
|
||||
func (f ipAddressFunc) evaluate(values map[string][]string) bool {
|
||||
IPs := []net.IP{}
|
||||
requestValue, ok := values[http.CanonicalHeaderKey(f.k.Name())]
|
||||
if !ok {
|
||||
requestValue = values[f.k.Name()]
|
||||
}
|
||||
|
||||
for _, s := range requestValue {
|
||||
IP := net.ParseIP(s)
|
||||
if IP == nil {
|
||||
panic(fmt.Errorf("invalid IP address '%v'", s))
|
||||
}
|
||||
|
||||
IPs = append(IPs, IP)
|
||||
}
|
||||
|
||||
for _, IP := range IPs {
|
||||
for _, IPNet := range f.values {
|
||||
if IPNet.Contains(IP) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// key() - returns condition key which is used by this condition function.
|
||||
// Key is always AWSSourceIP.
|
||||
func (f ipAddressFunc) key() Key {
|
||||
return f.k
|
||||
}
|
||||
|
||||
// name() - returns "IpAddress" condition name.
|
||||
func (f ipAddressFunc) name() name {
|
||||
return ipAddress
|
||||
}
|
||||
|
||||
func (f ipAddressFunc) String() string {
|
||||
return toIPAddressFuncString(ipAddress, f.k, f.values)
|
||||
}
|
||||
|
||||
// toMap - returns map representation of this function.
|
||||
func (f ipAddressFunc) toMap() map[Key]ValueSet {
|
||||
if !f.k.IsValid() {
|
||||
return nil
|
||||
}
|
||||
|
||||
values := NewValueSet()
|
||||
for _, value := range f.values {
|
||||
values.Add(NewStringValue(value.String()))
|
||||
}
|
||||
|
||||
return map[Key]ValueSet{
|
||||
f.k: values,
|
||||
}
|
||||
}
|
||||
|
||||
// notIPAddressFunc - Not IP address function. It checks whether value by Key in given
|
||||
// values is NOT in IP network. Here Key must be AWSSourceIP.
|
||||
// For example,
|
||||
// - if values = [192.168.1.0/24], at evaluate() it returns whether IP address
|
||||
// in value map for AWSSourceIP does not fall in the network 192.168.1.10/24.
|
||||
type notIPAddressFunc struct {
|
||||
ipAddressFunc
|
||||
}
|
||||
|
||||
// evaluate() - evaluates to check whether IP address in values map for AWSSourceIP
|
||||
// does not fall in one of network.
|
||||
func (f notIPAddressFunc) evaluate(values map[string][]string) bool {
|
||||
return !f.ipAddressFunc.evaluate(values)
|
||||
}
|
||||
|
||||
// name() - returns "NotIpAddress" condition name.
|
||||
func (f notIPAddressFunc) name() name {
|
||||
return notIPAddress
|
||||
}
|
||||
|
||||
func (f notIPAddressFunc) String() string {
|
||||
return toIPAddressFuncString(notIPAddress, f.ipAddressFunc.k, f.ipAddressFunc.values)
|
||||
}
|
||||
|
||||
func valuesToIPNets(n name, values ValueSet) ([]*net.IPNet, error) {
|
||||
IPNets := []*net.IPNet{}
|
||||
for v := range values {
|
||||
s, err := v.GetString()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("value %v must be string representation of CIDR for %v condition", v, n)
|
||||
}
|
||||
|
||||
var IPNet *net.IPNet
|
||||
_, IPNet, err = net.ParseCIDR(s)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("value %v must be CIDR string for %v condition", s, n)
|
||||
}
|
||||
|
||||
IPNets = append(IPNets, IPNet)
|
||||
}
|
||||
|
||||
return IPNets, nil
|
||||
}
|
||||
|
||||
// newIPAddressFunc - returns new IP address function.
|
||||
func newIPAddressFunc(key Key, values ValueSet) (Function, error) {
|
||||
IPNets, err := valuesToIPNets(ipAddress, values)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return NewIPAddressFunc(key, IPNets...)
|
||||
}
|
||||
|
||||
// NewIPAddressFunc - returns new IP address function.
|
||||
func NewIPAddressFunc(key Key, IPNets ...*net.IPNet) (Function, error) {
|
||||
if key != AWSSourceIP {
|
||||
return nil, fmt.Errorf("only %v key is allowed for %v condition", AWSSourceIP, ipAddress)
|
||||
}
|
||||
|
||||
return &ipAddressFunc{key, IPNets}, nil
|
||||
}
|
||||
|
||||
// newNotIPAddressFunc - returns new Not IP address function.
|
||||
func newNotIPAddressFunc(key Key, values ValueSet) (Function, error) {
|
||||
IPNets, err := valuesToIPNets(notIPAddress, values)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return NewNotIPAddressFunc(key, IPNets...)
|
||||
}
|
||||
|
||||
// NewNotIPAddressFunc - returns new Not IP address function.
|
||||
func NewNotIPAddressFunc(key Key, IPNets ...*net.IPNet) (Function, error) {
|
||||
if key != AWSSourceIP {
|
||||
return nil, fmt.Errorf("only %v key is allowed for %v condition", AWSSourceIP, notIPAddress)
|
||||
}
|
||||
|
||||
return ¬IPAddressFunc{ipAddressFunc{key, IPNets}}, nil
|
||||
}
|
||||
278
pkg/bucket/policy/condition/ipaddressfunc_test.go
Normal file
278
pkg/bucket/policy/condition/ipaddressfunc_test.go
Normal file
@@ -0,0 +1,278 @@
|
||||
/*
|
||||
* MinIO Cloud Storage, (C) 2018 MinIO, Inc.
|
||||
*
|
||||
* 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 condition
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestIPAddressFuncEvaluate(t *testing.T) {
|
||||
case1Function, err := newIPAddressFunc(AWSSourceIP, NewValueSet(NewStringValue("192.168.1.0/24")))
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error. %v\n", err)
|
||||
}
|
||||
|
||||
testCases := []struct {
|
||||
function Function
|
||||
values map[string][]string
|
||||
expectedResult bool
|
||||
}{
|
||||
{case1Function, map[string][]string{"SourceIp": {"192.168.1.10"}}, true},
|
||||
{case1Function, map[string][]string{"SourceIp": {"192.168.2.10"}}, false},
|
||||
{case1Function, map[string][]string{}, false},
|
||||
{case1Function, map[string][]string{"delimiter": {"/"}}, false},
|
||||
}
|
||||
|
||||
for i, testCase := range testCases {
|
||||
result := testCase.function.evaluate(testCase.values)
|
||||
|
||||
if result != testCase.expectedResult {
|
||||
t.Fatalf("case %v: expected: %v, got: %v\n", i+1, testCase.expectedResult, result)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestIPAddressFuncKey(t *testing.T) {
|
||||
case1Function, err := newIPAddressFunc(AWSSourceIP, NewValueSet(NewStringValue("192.168.1.0/24")))
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error. %v\n", err)
|
||||
}
|
||||
|
||||
testCases := []struct {
|
||||
function Function
|
||||
expectedResult Key
|
||||
}{
|
||||
{case1Function, AWSSourceIP},
|
||||
}
|
||||
|
||||
for i, testCase := range testCases {
|
||||
result := testCase.function.key()
|
||||
|
||||
if result != testCase.expectedResult {
|
||||
t.Fatalf("case %v: expected: %v, got: %v\n", i+1, testCase.expectedResult, result)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestIPAddressFuncToMap(t *testing.T) {
|
||||
case1Function, err := newIPAddressFunc(AWSSourceIP, NewValueSet(NewStringValue("192.168.1.0/24")))
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error. %v\n", err)
|
||||
}
|
||||
|
||||
case2Function, err := newIPAddressFunc(AWSSourceIP, NewValueSet(NewStringValue("192.168.1.0/24"), NewStringValue("10.1.10.1/32")))
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error. %v\n", err)
|
||||
}
|
||||
|
||||
case1Result := map[Key]ValueSet{
|
||||
AWSSourceIP: NewValueSet(NewStringValue("192.168.1.0/24")),
|
||||
}
|
||||
|
||||
case2Result := map[Key]ValueSet{
|
||||
AWSSourceIP: NewValueSet(NewStringValue("192.168.1.0/24"), NewStringValue("10.1.10.1/32")),
|
||||
}
|
||||
|
||||
testCases := []struct {
|
||||
f Function
|
||||
expectedResult map[Key]ValueSet
|
||||
}{
|
||||
{case1Function, case1Result},
|
||||
{case2Function, case2Result},
|
||||
{&ipAddressFunc{}, nil},
|
||||
}
|
||||
|
||||
for i, testCase := range testCases {
|
||||
result := testCase.f.toMap()
|
||||
|
||||
if !reflect.DeepEqual(result, testCase.expectedResult) {
|
||||
t.Fatalf("case %v: result: expected: %v, got: %v\n", i+1, testCase.expectedResult, result)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestNotIPAddressFuncEvaluate(t *testing.T) {
|
||||
case1Function, err := newNotIPAddressFunc(AWSSourceIP, NewValueSet(NewStringValue("192.168.1.0/24")))
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error. %v\n", err)
|
||||
}
|
||||
|
||||
testCases := []struct {
|
||||
function Function
|
||||
values map[string][]string
|
||||
expectedResult bool
|
||||
}{
|
||||
{case1Function, map[string][]string{"SourceIp": {"192.168.2.10"}}, true},
|
||||
{case1Function, map[string][]string{}, true},
|
||||
{case1Function, map[string][]string{"delimiter": {"/"}}, true},
|
||||
{case1Function, map[string][]string{"SourceIp": {"192.168.1.10"}}, false},
|
||||
}
|
||||
|
||||
for i, testCase := range testCases {
|
||||
result := testCase.function.evaluate(testCase.values)
|
||||
|
||||
if result != testCase.expectedResult {
|
||||
t.Fatalf("case %v: expected: %v, got: %v\n", i+1, testCase.expectedResult, result)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestNotIPAddressFuncKey(t *testing.T) {
|
||||
case1Function, err := newNotIPAddressFunc(AWSSourceIP, NewValueSet(NewStringValue("192.168.1.0/24")))
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error. %v\n", err)
|
||||
}
|
||||
|
||||
testCases := []struct {
|
||||
function Function
|
||||
expectedResult Key
|
||||
}{
|
||||
{case1Function, AWSSourceIP},
|
||||
}
|
||||
|
||||
for i, testCase := range testCases {
|
||||
result := testCase.function.key()
|
||||
|
||||
if result != testCase.expectedResult {
|
||||
t.Fatalf("case %v: expected: %v, got: %v\n", i+1, testCase.expectedResult, result)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestNotIPAddressFuncToMap(t *testing.T) {
|
||||
case1Function, err := newNotIPAddressFunc(AWSSourceIP, NewValueSet(NewStringValue("192.168.1.0/24")))
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error. %v\n", err)
|
||||
}
|
||||
|
||||
case2Function, err := newNotIPAddressFunc(AWSSourceIP, NewValueSet(NewStringValue("192.168.1.0/24"), NewStringValue("10.1.10.1/32")))
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error. %v\n", err)
|
||||
}
|
||||
|
||||
case1Result := map[Key]ValueSet{
|
||||
AWSSourceIP: NewValueSet(NewStringValue("192.168.1.0/24")),
|
||||
}
|
||||
|
||||
case2Result := map[Key]ValueSet{
|
||||
AWSSourceIP: NewValueSet(NewStringValue("192.168.1.0/24"), NewStringValue("10.1.10.1/32")),
|
||||
}
|
||||
|
||||
testCases := []struct {
|
||||
f Function
|
||||
expectedResult map[Key]ValueSet
|
||||
}{
|
||||
{case1Function, case1Result},
|
||||
{case2Function, case2Result},
|
||||
{¬IPAddressFunc{}, nil},
|
||||
}
|
||||
|
||||
for i, testCase := range testCases {
|
||||
result := testCase.f.toMap()
|
||||
|
||||
if !reflect.DeepEqual(result, testCase.expectedResult) {
|
||||
t.Fatalf("case %v: result: expected: %v, got: %v\n", i+1, testCase.expectedResult, result)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewIPAddressFunc(t *testing.T) {
|
||||
case1Function, err := newIPAddressFunc(AWSSourceIP, NewValueSet(NewStringValue("192.168.1.0/24")))
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error. %v\n", err)
|
||||
}
|
||||
|
||||
case2Function, err := newIPAddressFunc(AWSSourceIP, NewValueSet(NewStringValue("192.168.1.0/24"), NewStringValue("10.1.10.1/32")))
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error. %v\n", err)
|
||||
}
|
||||
|
||||
testCases := []struct {
|
||||
key Key
|
||||
values ValueSet
|
||||
expectedResult Function
|
||||
expectErr bool
|
||||
}{
|
||||
{AWSSourceIP, NewValueSet(NewStringValue("192.168.1.0/24")), case1Function, false},
|
||||
{AWSSourceIP, NewValueSet(NewStringValue("192.168.1.0/24"), NewStringValue("10.1.10.1/32")), case2Function, false},
|
||||
// Unsupported key error.
|
||||
{S3Prefix, NewValueSet(NewStringValue("192.168.1.0/24")), nil, true},
|
||||
// Invalid value error.
|
||||
{AWSSourceIP, NewValueSet(NewStringValue("node1.example.org")), nil, true},
|
||||
// Invalid CIDR format error.
|
||||
{AWSSourceIP, NewValueSet(NewStringValue("192.168.1.0.0/24")), nil, true},
|
||||
}
|
||||
|
||||
for i, testCase := range testCases {
|
||||
result, err := newIPAddressFunc(testCase.key, testCase.values)
|
||||
expectErr := (err != nil)
|
||||
|
||||
if expectErr != testCase.expectErr {
|
||||
t.Fatalf("case %v: error: expected: %v, got: %v\n", i+1, testCase.expectErr, expectErr)
|
||||
}
|
||||
|
||||
if !testCase.expectErr {
|
||||
if result.String() != testCase.expectedResult.String() {
|
||||
t.Fatalf("case %v: result: expected: %v, got: %v\n", i+1, testCase.expectedResult, result)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewNotIPAddressFunc(t *testing.T) {
|
||||
case1Function, err := newNotIPAddressFunc(AWSSourceIP, NewValueSet(NewStringValue("192.168.1.0/24")))
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error. %v\n", err)
|
||||
}
|
||||
|
||||
case2Function, err := newNotIPAddressFunc(AWSSourceIP, NewValueSet(NewStringValue("192.168.1.0/24"), NewStringValue("10.1.10.1/32")))
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error. %v\n", err)
|
||||
}
|
||||
|
||||
testCases := []struct {
|
||||
key Key
|
||||
values ValueSet
|
||||
expectedResult Function
|
||||
expectErr bool
|
||||
}{
|
||||
{AWSSourceIP, NewValueSet(NewStringValue("192.168.1.0/24")), case1Function, false},
|
||||
{AWSSourceIP, NewValueSet(NewStringValue("192.168.1.0/24"), NewStringValue("10.1.10.1/32")), case2Function, false},
|
||||
// Unsupported key error.
|
||||
{S3Prefix, NewValueSet(NewStringValue("192.168.1.0/24")), nil, true},
|
||||
// Invalid value error.
|
||||
{AWSSourceIP, NewValueSet(NewStringValue("node1.example.org")), nil, true},
|
||||
// Invalid CIDR format error.
|
||||
{AWSSourceIP, NewValueSet(NewStringValue("192.168.1.0.0/24")), nil, true},
|
||||
}
|
||||
|
||||
for i, testCase := range testCases {
|
||||
result, err := newNotIPAddressFunc(testCase.key, testCase.values)
|
||||
expectErr := (err != nil)
|
||||
|
||||
if expectErr != testCase.expectErr {
|
||||
t.Fatalf("case %v: error: expected: %v, got: %v\n", i+1, testCase.expectErr, expectErr)
|
||||
}
|
||||
|
||||
if !testCase.expectErr {
|
||||
if result.String() != testCase.expectedResult.String() {
|
||||
t.Fatalf("case %v: result: expected: %v, got: %v\n", i+1, testCase.expectedResult, result)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
285
pkg/bucket/policy/condition/key.go
Normal file
285
pkg/bucket/policy/condition/key.go
Normal file
@@ -0,0 +1,285 @@
|
||||
/*
|
||||
* MinIO Cloud Storage, (C) 2018 MinIO, Inc.
|
||||
*
|
||||
* 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 condition
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Key - conditional key which is used to fetch values for any condition.
|
||||
// Refer https://docs.aws.amazon.com/IAM/latest/UserGuide/list_s3.html
|
||||
// for more information about available condition keys.
|
||||
type Key string
|
||||
|
||||
const (
|
||||
// S3XAmzCopySource - key representing x-amz-copy-source HTTP header applicable to PutObject API only.
|
||||
S3XAmzCopySource Key = "s3:x-amz-copy-source"
|
||||
|
||||
// S3XAmzServerSideEncryption - key representing x-amz-server-side-encryption HTTP header applicable
|
||||
// to PutObject API only.
|
||||
S3XAmzServerSideEncryption Key = "s3:x-amz-server-side-encryption"
|
||||
|
||||
// S3XAmzServerSideEncryptionCustomerAlgorithm - key representing
|
||||
// x-amz-server-side-encryption-customer-algorithm HTTP header applicable to PutObject API only.
|
||||
S3XAmzServerSideEncryptionCustomerAlgorithm Key = "s3:x-amz-server-side-encryption-customer-algorithm"
|
||||
|
||||
// S3XAmzMetadataDirective - key representing x-amz-metadata-directive HTTP header applicable to
|
||||
// PutObject API only.
|
||||
S3XAmzMetadataDirective Key = "s3:x-amz-metadata-directive"
|
||||
|
||||
// S3XAmzStorageClass - key representing x-amz-storage-class HTTP header applicable to PutObject API
|
||||
// only.
|
||||
S3XAmzStorageClass Key = "s3:x-amz-storage-class"
|
||||
|
||||
// S3LocationConstraint - key representing LocationConstraint XML tag of CreateBucket API only.
|
||||
S3LocationConstraint Key = "s3:LocationConstraint"
|
||||
|
||||
// S3Prefix - key representing prefix query parameter of ListBucket API only.
|
||||
S3Prefix Key = "s3:prefix"
|
||||
|
||||
// S3Delimiter - key representing delimiter query parameter of ListBucket API only.
|
||||
S3Delimiter Key = "s3:delimiter"
|
||||
|
||||
// S3MaxKeys - key representing max-keys query parameter of ListBucket API only.
|
||||
S3MaxKeys Key = "s3:max-keys"
|
||||
|
||||
// AWSReferer - key representing Referer header of any API.
|
||||
AWSReferer Key = "aws:Referer"
|
||||
|
||||
// AWSSourceIP - key representing client's IP address (not intermittent proxies) of any API.
|
||||
AWSSourceIP Key = "aws:SourceIp"
|
||||
|
||||
// AWSUserAgent - key representing UserAgent header for any API.
|
||||
AWSUserAgent Key = "aws:UserAgent"
|
||||
|
||||
// AWSSecureTransport - key representing if the clients request is authenticated or not.
|
||||
AWSSecureTransport Key = "aws:SecureTransport"
|
||||
|
||||
// AWSCurrentTime - key representing the current time.
|
||||
AWSCurrentTime Key = "aws:CurrentTime"
|
||||
|
||||
// AWSEpochTime - key representing the current epoch time.
|
||||
AWSEpochTime Key = "aws:EpochTime"
|
||||
|
||||
// AWSPrincipalType - user principal type currently supported values are "User" and "Anonymous".
|
||||
AWSPrincipalType Key = "aws:principaltype"
|
||||
|
||||
// AWSUserID - user unique ID, in MinIO this value is same as your user Access Key.
|
||||
AWSUserID Key = "aws:userid"
|
||||
|
||||
// AWSUsername - user friendly name, in MinIO this value is same as your user Access Key.
|
||||
AWSUsername Key = "aws:username"
|
||||
|
||||
// JWTSub - JWT subject claim substitution.
|
||||
JWTSub Key = "jwt:sub"
|
||||
|
||||
// JWTIss issuer claim substitution.
|
||||
JWTIss Key = "jwt:iss"
|
||||
|
||||
// JWTAud audience claim substitution.
|
||||
JWTAud Key = "jwt:aud"
|
||||
|
||||
// JWTJti JWT unique identifier claim substitution.
|
||||
JWTJti Key = "jwt:jti"
|
||||
)
|
||||
|
||||
// AllSupportedKeys - is list of all all supported keys.
|
||||
var AllSupportedKeys = []Key{
|
||||
S3XAmzCopySource,
|
||||
S3XAmzServerSideEncryption,
|
||||
S3XAmzServerSideEncryptionCustomerAlgorithm,
|
||||
S3XAmzMetadataDirective,
|
||||
S3XAmzStorageClass,
|
||||
S3LocationConstraint,
|
||||
S3Prefix,
|
||||
S3Delimiter,
|
||||
S3MaxKeys,
|
||||
AWSReferer,
|
||||
AWSSourceIP,
|
||||
AWSUserAgent,
|
||||
AWSSecureTransport,
|
||||
AWSCurrentTime,
|
||||
AWSEpochTime,
|
||||
AWSPrincipalType,
|
||||
AWSUserID,
|
||||
AWSUsername,
|
||||
JWTSub,
|
||||
JWTIss,
|
||||
JWTAud,
|
||||
JWTJti,
|
||||
// Add new supported condition keys.
|
||||
}
|
||||
|
||||
// CommonKeys - is list of all common condition keys.
|
||||
var CommonKeys = []Key{
|
||||
AWSReferer,
|
||||
AWSSourceIP,
|
||||
AWSUserAgent,
|
||||
AWSSecureTransport,
|
||||
AWSCurrentTime,
|
||||
AWSEpochTime,
|
||||
AWSPrincipalType,
|
||||
AWSUserID,
|
||||
AWSUsername,
|
||||
JWTSub,
|
||||
JWTIss,
|
||||
JWTAud,
|
||||
JWTJti,
|
||||
}
|
||||
|
||||
func substFuncFromValues(values map[string][]string) func(string) string {
|
||||
return func(v string) string {
|
||||
for _, key := range CommonKeys {
|
||||
// Empty values are not supported for policy variables.
|
||||
if rvalues, ok := values[key.Name()]; ok && rvalues[0] != "" {
|
||||
v = strings.Replace(v, key.VarName(), rvalues[0], -1)
|
||||
}
|
||||
}
|
||||
return v
|
||||
}
|
||||
}
|
||||
|
||||
// IsValid - checks if key is valid or not.
|
||||
func (key Key) IsValid() bool {
|
||||
for _, supKey := range AllSupportedKeys {
|
||||
if supKey == key {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// MarshalJSON - encodes Key to JSON data.
|
||||
func (key Key) MarshalJSON() ([]byte, error) {
|
||||
if !key.IsValid() {
|
||||
return nil, fmt.Errorf("unknown key %v", key)
|
||||
}
|
||||
|
||||
return json.Marshal(string(key))
|
||||
}
|
||||
|
||||
// VarName - returns variable key name, such as "${aws:username}"
|
||||
func (key Key) VarName() string {
|
||||
return fmt.Sprintf("${%s}", key)
|
||||
}
|
||||
|
||||
// Name - returns key name which is stripped value of prefixes "aws:" and "s3:"
|
||||
func (key Key) Name() string {
|
||||
keyString := string(key)
|
||||
|
||||
if strings.HasPrefix(keyString, "aws:") {
|
||||
return strings.TrimPrefix(keyString, "aws:")
|
||||
} else if strings.HasPrefix(keyString, "jwt:") {
|
||||
return strings.TrimPrefix(keyString, "jwt:")
|
||||
}
|
||||
return strings.TrimPrefix(keyString, "s3:")
|
||||
}
|
||||
|
||||
// UnmarshalJSON - decodes JSON data to Key.
|
||||
func (key *Key) UnmarshalJSON(data []byte) error {
|
||||
var s string
|
||||
if err := json.Unmarshal(data, &s); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
parsedKey, err := parseKey(s)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
*key = parsedKey
|
||||
return nil
|
||||
}
|
||||
|
||||
func parseKey(s string) (Key, error) {
|
||||
key := Key(s)
|
||||
|
||||
if key.IsValid() {
|
||||
return key, nil
|
||||
}
|
||||
|
||||
return key, fmt.Errorf("invalid condition key '%v'", s)
|
||||
}
|
||||
|
||||
// KeySet - set representation of slice of keys.
|
||||
type KeySet map[Key]struct{}
|
||||
|
||||
// Add - add a key to key set.
|
||||
func (set KeySet) Add(key Key) {
|
||||
set[key] = struct{}{}
|
||||
}
|
||||
|
||||
// Difference - returns a key set contains difference of two keys.
|
||||
// Example:
|
||||
// keySet1 := ["one", "two", "three"]
|
||||
// keySet2 := ["two", "four", "three"]
|
||||
// keySet1.Difference(keySet2) == ["one"]
|
||||
func (set KeySet) Difference(sset KeySet) KeySet {
|
||||
nset := make(KeySet)
|
||||
|
||||
for k := range set {
|
||||
if _, ok := sset[k]; !ok {
|
||||
nset.Add(k)
|
||||
}
|
||||
}
|
||||
|
||||
return nset
|
||||
}
|
||||
|
||||
// IsEmpty - returns whether key set is empty or not.
|
||||
func (set KeySet) IsEmpty() bool {
|
||||
return len(set) == 0
|
||||
}
|
||||
|
||||
func (set KeySet) String() string {
|
||||
return fmt.Sprintf("%v", set.ToSlice())
|
||||
}
|
||||
|
||||
// ToSlice - returns slice of keys.
|
||||
func (set KeySet) ToSlice() []Key {
|
||||
keys := []Key{}
|
||||
|
||||
for key := range set {
|
||||
keys = append(keys, key)
|
||||
}
|
||||
|
||||
return keys
|
||||
}
|
||||
|
||||
// NewKeySet - returns new KeySet contains given keys.
|
||||
func NewKeySet(keys ...Key) KeySet {
|
||||
set := make(KeySet)
|
||||
for _, key := range keys {
|
||||
set.Add(key)
|
||||
}
|
||||
|
||||
return set
|
||||
}
|
||||
|
||||
// AllSupportedAdminKeys - is list of all admin supported keys.
|
||||
var AllSupportedAdminKeys = []Key{
|
||||
AWSReferer,
|
||||
AWSSourceIP,
|
||||
AWSUserAgent,
|
||||
AWSSecureTransport,
|
||||
AWSCurrentTime,
|
||||
AWSEpochTime,
|
||||
// Add new supported condition keys.
|
||||
}
|
||||
214
pkg/bucket/policy/condition/key_test.go
Normal file
214
pkg/bucket/policy/condition/key_test.go
Normal file
@@ -0,0 +1,214 @@
|
||||
/*
|
||||
* MinIO Cloud Storage, (C) 2018 MinIO, Inc.
|
||||
*
|
||||
* 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 condition
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestKeyIsValid(t *testing.T) {
|
||||
testCases := []struct {
|
||||
key Key
|
||||
expectedResult bool
|
||||
}{
|
||||
{S3XAmzCopySource, true},
|
||||
{S3XAmzServerSideEncryption, true},
|
||||
{S3XAmzServerSideEncryptionCustomerAlgorithm, true},
|
||||
{S3XAmzMetadataDirective, true},
|
||||
{S3XAmzStorageClass, true},
|
||||
{S3LocationConstraint, true},
|
||||
{S3Prefix, true},
|
||||
{S3Delimiter, true},
|
||||
{S3MaxKeys, true},
|
||||
{AWSReferer, true},
|
||||
{AWSSourceIP, true},
|
||||
{Key("foo"), false},
|
||||
}
|
||||
|
||||
for i, testCase := range testCases {
|
||||
result := testCase.key.IsValid()
|
||||
|
||||
if testCase.expectedResult != result {
|
||||
t.Fatalf("case %v: expected: %v, got: %v\n", i+1, testCase.expectedResult, result)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestKeyMarshalJSON(t *testing.T) {
|
||||
testCases := []struct {
|
||||
key Key
|
||||
expectedResult []byte
|
||||
expectErr bool
|
||||
}{
|
||||
{S3XAmzCopySource, []byte(`"s3:x-amz-copy-source"`), false},
|
||||
{Key("foo"), nil, true},
|
||||
}
|
||||
|
||||
for i, testCase := range testCases {
|
||||
result, err := json.Marshal(testCase.key)
|
||||
expectErr := (err != nil)
|
||||
|
||||
if testCase.expectErr != expectErr {
|
||||
t.Fatalf("case %v: error: expected: %v, got: %v\n", i+1, testCase.expectErr, expectErr)
|
||||
}
|
||||
|
||||
if !testCase.expectErr {
|
||||
if !reflect.DeepEqual(result, testCase.expectedResult) {
|
||||
t.Fatalf("case %v: key: expected: %v, got: %v\n", i+1, string(testCase.expectedResult), string(result))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestKeyName(t *testing.T) {
|
||||
testCases := []struct {
|
||||
key Key
|
||||
expectedResult string
|
||||
}{
|
||||
{S3XAmzCopySource, "x-amz-copy-source"},
|
||||
{AWSReferer, "Referer"},
|
||||
}
|
||||
|
||||
for i, testCase := range testCases {
|
||||
result := testCase.key.Name()
|
||||
|
||||
if testCase.expectedResult != result {
|
||||
t.Fatalf("case %v: expected: %v, got: %v\n", i+1, testCase.expectedResult, result)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestKeyUnmarshalJSON(t *testing.T) {
|
||||
testCases := []struct {
|
||||
data []byte
|
||||
expectedKey Key
|
||||
expectErr bool
|
||||
}{
|
||||
{[]byte(`"s3:x-amz-copy-source"`), S3XAmzCopySource, false},
|
||||
{[]byte(`"foo"`), Key(""), true},
|
||||
}
|
||||
|
||||
for i, testCase := range testCases {
|
||||
var key Key
|
||||
err := json.Unmarshal(testCase.data, &key)
|
||||
expectErr := (err != nil)
|
||||
|
||||
if testCase.expectErr != expectErr {
|
||||
t.Fatalf("case %v: error: expected: %v, got: %v\n", i+1, testCase.expectErr, expectErr)
|
||||
}
|
||||
|
||||
if !testCase.expectErr {
|
||||
if testCase.expectedKey != key {
|
||||
t.Fatalf("case %v: key: expected: %v, got: %v\n", i+1, testCase.expectedKey, key)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestKeySetAdd(t *testing.T) {
|
||||
testCases := []struct {
|
||||
set KeySet
|
||||
key Key
|
||||
expectedResult KeySet
|
||||
}{
|
||||
{NewKeySet(), S3XAmzCopySource, NewKeySet(S3XAmzCopySource)},
|
||||
{NewKeySet(S3XAmzCopySource), S3XAmzCopySource, NewKeySet(S3XAmzCopySource)},
|
||||
}
|
||||
|
||||
for i, testCase := range testCases {
|
||||
testCase.set.Add(testCase.key)
|
||||
|
||||
if !reflect.DeepEqual(testCase.expectedResult, testCase.set) {
|
||||
t.Fatalf("case %v: expected: %v, got: %v\n", i+1, testCase.expectedResult, testCase.set)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestKeySetDifference(t *testing.T) {
|
||||
testCases := []struct {
|
||||
set KeySet
|
||||
setToDiff KeySet
|
||||
expectedResult KeySet
|
||||
}{
|
||||
{NewKeySet(), NewKeySet(S3XAmzCopySource), NewKeySet()},
|
||||
{NewKeySet(S3Prefix, S3Delimiter, S3MaxKeys), NewKeySet(S3Delimiter, S3MaxKeys), NewKeySet(S3Prefix)},
|
||||
}
|
||||
|
||||
for i, testCase := range testCases {
|
||||
result := testCase.set.Difference(testCase.setToDiff)
|
||||
|
||||
if !reflect.DeepEqual(testCase.expectedResult, result) {
|
||||
t.Fatalf("case %v: expected: %v, got: %v\n", i+1, testCase.expectedResult, result)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestKeySetIsEmpty(t *testing.T) {
|
||||
testCases := []struct {
|
||||
set KeySet
|
||||
expectedResult bool
|
||||
}{
|
||||
{NewKeySet(), true},
|
||||
{NewKeySet(S3Delimiter), false},
|
||||
}
|
||||
|
||||
for i, testCase := range testCases {
|
||||
result := testCase.set.IsEmpty()
|
||||
|
||||
if testCase.expectedResult != result {
|
||||
t.Fatalf("case %v: expected: %v, got: %v\n", i+1, testCase.expectedResult, result)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestKeySetString(t *testing.T) {
|
||||
testCases := []struct {
|
||||
set KeySet
|
||||
expectedResult string
|
||||
}{
|
||||
{NewKeySet(), `[]`},
|
||||
{NewKeySet(S3Delimiter), `[s3:delimiter]`},
|
||||
}
|
||||
|
||||
for i, testCase := range testCases {
|
||||
result := testCase.set.String()
|
||||
|
||||
if testCase.expectedResult != result {
|
||||
t.Fatalf("case %v: expected: %v, got: %v\n", i+1, testCase.expectedResult, result)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestKeySetToSlice(t *testing.T) {
|
||||
testCases := []struct {
|
||||
set KeySet
|
||||
expectedResult []Key
|
||||
}{
|
||||
{NewKeySet(), []Key{}},
|
||||
{NewKeySet(S3Delimiter), []Key{S3Delimiter}},
|
||||
}
|
||||
|
||||
for i, testCase := range testCases {
|
||||
result := testCase.set.ToSlice()
|
||||
|
||||
if !reflect.DeepEqual(testCase.expectedResult, result) {
|
||||
t.Fatalf("case %v: expected: %v, got: %v\n", i+1, testCase.expectedResult, result)
|
||||
}
|
||||
}
|
||||
}
|
||||
99
pkg/bucket/policy/condition/name.go
Normal file
99
pkg/bucket/policy/condition/name.go
Normal file
@@ -0,0 +1,99 @@
|
||||
/*
|
||||
* MinIO Cloud Storage, (C) 2018 MinIO, Inc.
|
||||
*
|
||||
* 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 condition
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
type name string
|
||||
|
||||
const (
|
||||
stringEquals name = "StringEquals"
|
||||
stringNotEquals = "StringNotEquals"
|
||||
stringEqualsIgnoreCase = "StringEqualsIgnoreCase"
|
||||
stringNotEqualsIgnoreCase = "StringNotEqualsIgnoreCase"
|
||||
stringLike = "StringLike"
|
||||
stringNotLike = "StringNotLike"
|
||||
binaryEquals = "BinaryEquals"
|
||||
ipAddress = "IpAddress"
|
||||
notIPAddress = "NotIpAddress"
|
||||
null = "Null"
|
||||
boolean = "Bool"
|
||||
)
|
||||
|
||||
var supportedConditions = []name{
|
||||
stringEquals,
|
||||
stringNotEquals,
|
||||
stringEqualsIgnoreCase,
|
||||
stringNotEqualsIgnoreCase,
|
||||
binaryEquals,
|
||||
stringLike,
|
||||
stringNotLike,
|
||||
ipAddress,
|
||||
notIPAddress,
|
||||
null,
|
||||
boolean,
|
||||
// Add new conditions here.
|
||||
}
|
||||
|
||||
// IsValid - checks if name is valid or not.
|
||||
func (n name) IsValid() bool {
|
||||
for _, supn := range supportedConditions {
|
||||
if n == supn {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// MarshalJSON - encodes name to JSON data.
|
||||
func (n name) MarshalJSON() ([]byte, error) {
|
||||
if !n.IsValid() {
|
||||
return nil, fmt.Errorf("invalid name %v", n)
|
||||
}
|
||||
|
||||
return json.Marshal(string(n))
|
||||
}
|
||||
|
||||
// UnmarshalJSON - decodes JSON data to condition name.
|
||||
func (n *name) UnmarshalJSON(data []byte) error {
|
||||
var s string
|
||||
if err := json.Unmarshal(data, &s); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
parsedName, err := parseName(s)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
*n = parsedName
|
||||
return nil
|
||||
}
|
||||
|
||||
func parseName(s string) (name, error) {
|
||||
n := name(s)
|
||||
|
||||
if n.IsValid() {
|
||||
return n, nil
|
||||
}
|
||||
|
||||
return n, fmt.Errorf("invalid condition name '%v'", s)
|
||||
}
|
||||
106
pkg/bucket/policy/condition/name_test.go
Normal file
106
pkg/bucket/policy/condition/name_test.go
Normal file
@@ -0,0 +1,106 @@
|
||||
/*
|
||||
* MinIO Cloud Storage, (C) 2018 MinIO, Inc.
|
||||
*
|
||||
* 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 condition
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestNameIsValid(t *testing.T) {
|
||||
testCases := []struct {
|
||||
n name
|
||||
expectedResult bool
|
||||
}{
|
||||
{stringEquals, true},
|
||||
{stringNotEquals, true},
|
||||
{stringLike, true},
|
||||
{stringNotLike, true},
|
||||
{ipAddress, true},
|
||||
{notIPAddress, true},
|
||||
{null, true},
|
||||
{name("foo"), false},
|
||||
}
|
||||
|
||||
for i, testCase := range testCases {
|
||||
result := testCase.n.IsValid()
|
||||
|
||||
if testCase.expectedResult != result {
|
||||
t.Fatalf("case %v: expected: %v, got: %v", i+1, testCase.expectedResult, result)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestNameMarshalJSON(t *testing.T) {
|
||||
testCases := []struct {
|
||||
n name
|
||||
expectedResult []byte
|
||||
expectErr bool
|
||||
}{
|
||||
{stringEquals, []byte(`"StringEquals"`), false},
|
||||
{stringNotEquals, []byte(`"StringNotEquals"`), false},
|
||||
{stringLike, []byte(`"StringLike"`), false},
|
||||
{stringNotLike, []byte(`"StringNotLike"`), false},
|
||||
{ipAddress, []byte(`"IpAddress"`), false},
|
||||
{notIPAddress, []byte(`"NotIpAddress"`), false},
|
||||
{null, []byte(`"Null"`), false},
|
||||
{name("foo"), nil, true},
|
||||
}
|
||||
|
||||
for i, testCase := range testCases {
|
||||
result, err := json.Marshal(testCase.n)
|
||||
expectErr := (err != nil)
|
||||
|
||||
if testCase.expectErr != expectErr {
|
||||
t.Fatalf("case %v: error: expected: %v, got: %v", i+1, testCase.expectErr, expectErr)
|
||||
}
|
||||
|
||||
if !testCase.expectErr {
|
||||
if !reflect.DeepEqual(result, testCase.expectedResult) {
|
||||
t.Fatalf("case %v: result: expected: %v, got: %v", i+1, string(testCase.expectedResult), string(result))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestNameUnmarshalJSON(t *testing.T) {
|
||||
testCases := []struct {
|
||||
data []byte
|
||||
expectedResult name
|
||||
expectErr bool
|
||||
}{
|
||||
{[]byte(`"StringEquals"`), stringEquals, false},
|
||||
{[]byte(`"foo"`), name(""), true},
|
||||
}
|
||||
|
||||
for i, testCase := range testCases {
|
||||
var result name
|
||||
err := json.Unmarshal(testCase.data, &result)
|
||||
expectErr := (err != nil)
|
||||
|
||||
if testCase.expectErr != expectErr {
|
||||
t.Fatalf("case %v: error: expected: %v, got: %v", i+1, testCase.expectErr, expectErr)
|
||||
}
|
||||
|
||||
if !testCase.expectErr {
|
||||
if testCase.expectedResult != result {
|
||||
t.Fatalf("case %v: result: expected: %v, got: %v", i+1, testCase.expectedResult, result)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
106
pkg/bucket/policy/condition/nullfunc.go
Normal file
106
pkg/bucket/policy/condition/nullfunc.go
Normal file
@@ -0,0 +1,106 @@
|
||||
/*
|
||||
* MinIO Cloud Storage, (C) 2018 MinIO, Inc.
|
||||
*
|
||||
* 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 condition
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"reflect"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
// nullFunc - Null condition function. It checks whether Key is not present in given
|
||||
// values or not.
|
||||
// For example,
|
||||
// 1. if Key = S3XAmzCopySource and Value = true, at evaluate() it returns whether
|
||||
// S3XAmzCopySource is NOT in given value map or not.
|
||||
// 2. if Key = S3XAmzCopySource and Value = false, at evaluate() it returns whether
|
||||
// S3XAmzCopySource is in given value map or not.
|
||||
// https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_elements_condition_operators.html#Conditions_Null
|
||||
type nullFunc struct {
|
||||
k Key
|
||||
value bool
|
||||
}
|
||||
|
||||
// evaluate() - evaluates to check whether Key is present in given values or not.
|
||||
// Depending on condition boolean value, this function returns true or false.
|
||||
func (f nullFunc) evaluate(values map[string][]string) bool {
|
||||
requestValue, ok := values[http.CanonicalHeaderKey(f.k.Name())]
|
||||
if !ok {
|
||||
requestValue = values[f.k.Name()]
|
||||
}
|
||||
|
||||
if f.value {
|
||||
return len(requestValue) == 0
|
||||
}
|
||||
|
||||
return len(requestValue) != 0
|
||||
}
|
||||
|
||||
// key() - returns condition key which is used by this condition function.
|
||||
func (f nullFunc) key() Key {
|
||||
return f.k
|
||||
}
|
||||
|
||||
// name() - returns "Null" condition name.
|
||||
func (f nullFunc) name() name {
|
||||
return null
|
||||
}
|
||||
|
||||
func (f nullFunc) String() string {
|
||||
return fmt.Sprintf("%v:%v:%v", null, f.k, f.value)
|
||||
}
|
||||
|
||||
// toMap - returns map representation of this function.
|
||||
func (f nullFunc) toMap() map[Key]ValueSet {
|
||||
if !f.k.IsValid() {
|
||||
return nil
|
||||
}
|
||||
|
||||
return map[Key]ValueSet{
|
||||
f.k: NewValueSet(NewBoolValue(f.value)),
|
||||
}
|
||||
}
|
||||
|
||||
func newNullFunc(key Key, values ValueSet) (Function, error) {
|
||||
if len(values) != 1 {
|
||||
return nil, fmt.Errorf("only one value is allowed for Null condition")
|
||||
}
|
||||
|
||||
var value bool
|
||||
for v := range values {
|
||||
switch v.GetType() {
|
||||
case reflect.Bool:
|
||||
value, _ = v.GetBool()
|
||||
case reflect.String:
|
||||
var err error
|
||||
s, _ := v.GetString()
|
||||
if value, err = strconv.ParseBool(s); err != nil {
|
||||
return nil, fmt.Errorf("value must be a boolean string for Null condition")
|
||||
}
|
||||
default:
|
||||
return nil, fmt.Errorf("value must be a boolean for Null condition")
|
||||
}
|
||||
}
|
||||
|
||||
return &nullFunc{key, value}, nil
|
||||
}
|
||||
|
||||
// NewNullFunc - returns new Null function.
|
||||
func NewNullFunc(key Key, value bool) (Function, error) {
|
||||
return &nullFunc{key, value}, nil
|
||||
}
|
||||
161
pkg/bucket/policy/condition/nullfunc_test.go
Normal file
161
pkg/bucket/policy/condition/nullfunc_test.go
Normal file
@@ -0,0 +1,161 @@
|
||||
/*
|
||||
* MinIO Cloud Storage, (C) 2018 MinIO, Inc.
|
||||
*
|
||||
* 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 condition
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestNullFuncEvaluate(t *testing.T) {
|
||||
case1Function, err := newNullFunc(S3Prefix, NewValueSet(NewBoolValue(true)))
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error. %v\n", err)
|
||||
}
|
||||
|
||||
case2Function, err := newNullFunc(S3Prefix, NewValueSet(NewBoolValue(false)))
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error. %v\n", err)
|
||||
}
|
||||
|
||||
testCases := []struct {
|
||||
function Function
|
||||
values map[string][]string
|
||||
expectedResult bool
|
||||
}{
|
||||
{case1Function, map[string][]string{"prefix": {"true"}}, false},
|
||||
{case1Function, map[string][]string{"prefix": {"false"}}, false},
|
||||
{case1Function, map[string][]string{"prefix": {"mybucket/foo"}}, false},
|
||||
{case1Function, map[string][]string{}, true},
|
||||
{case1Function, map[string][]string{"delimiter": {"/"}}, true},
|
||||
{case2Function, map[string][]string{"prefix": {"true"}}, true},
|
||||
{case2Function, map[string][]string{"prefix": {"false"}}, true},
|
||||
{case2Function, map[string][]string{"prefix": {"mybucket/foo"}}, true},
|
||||
{case2Function, map[string][]string{}, false},
|
||||
{case2Function, map[string][]string{"delimiter": {"/"}}, false},
|
||||
}
|
||||
|
||||
for i, testCase := range testCases {
|
||||
result := testCase.function.evaluate(testCase.values)
|
||||
|
||||
if result != testCase.expectedResult {
|
||||
t.Errorf("case %v: expected: %v, got: %v\n", i+1, testCase.expectedResult, result)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestNullFuncKey(t *testing.T) {
|
||||
case1Function, err := newNullFunc(S3XAmzCopySource, NewValueSet(NewBoolValue(true)))
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error. %v\n", err)
|
||||
}
|
||||
|
||||
testCases := []struct {
|
||||
function Function
|
||||
expectedResult Key
|
||||
}{
|
||||
{case1Function, S3XAmzCopySource},
|
||||
}
|
||||
|
||||
for i, testCase := range testCases {
|
||||
result := testCase.function.key()
|
||||
|
||||
if result != testCase.expectedResult {
|
||||
t.Fatalf("case %v: expected: %v, got: %v\n", i+1, testCase.expectedResult, result)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestNullFuncToMap(t *testing.T) {
|
||||
case1Function, err := newNullFunc(S3Prefix, NewValueSet(NewBoolValue(true)))
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error. %v\n", err)
|
||||
}
|
||||
|
||||
case1Result := map[Key]ValueSet{
|
||||
S3Prefix: NewValueSet(NewBoolValue(true)),
|
||||
}
|
||||
|
||||
case2Function, err := newNullFunc(S3Prefix, NewValueSet(NewBoolValue(false)))
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error. %v\n", err)
|
||||
}
|
||||
|
||||
case2Result := map[Key]ValueSet{
|
||||
S3Prefix: NewValueSet(NewBoolValue(false)),
|
||||
}
|
||||
|
||||
testCases := []struct {
|
||||
f Function
|
||||
expectedResult map[Key]ValueSet
|
||||
}{
|
||||
{case1Function, case1Result},
|
||||
{case2Function, case2Result},
|
||||
{&nullFunc{}, nil},
|
||||
}
|
||||
|
||||
for i, testCase := range testCases {
|
||||
result := testCase.f.toMap()
|
||||
|
||||
if !reflect.DeepEqual(result, testCase.expectedResult) {
|
||||
t.Fatalf("case %v: result: expected: %v, got: %v\n", i+1, testCase.expectedResult, result)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewNullFunc(t *testing.T) {
|
||||
case1Function, err := newNullFunc(S3Prefix, NewValueSet(NewBoolValue(true)))
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error. %v\n", err)
|
||||
}
|
||||
|
||||
case2Function, err := newNullFunc(S3Prefix, NewValueSet(NewBoolValue(false)))
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error. %v\n", err)
|
||||
}
|
||||
|
||||
testCases := []struct {
|
||||
key Key
|
||||
values ValueSet
|
||||
expectedResult Function
|
||||
expectErr bool
|
||||
}{
|
||||
{S3Prefix, NewValueSet(NewBoolValue(true)), case1Function, false},
|
||||
{S3Prefix, NewValueSet(NewStringValue("false")), case2Function, false},
|
||||
// Multiple values error.
|
||||
{S3Prefix, NewValueSet(NewBoolValue(true), NewBoolValue(false)), nil, true},
|
||||
// Invalid boolean string error.
|
||||
{S3Prefix, NewValueSet(NewStringValue("foo")), nil, true},
|
||||
// Invalid value error.
|
||||
{S3Prefix, NewValueSet(NewIntValue(7)), nil, true},
|
||||
}
|
||||
|
||||
for i, testCase := range testCases {
|
||||
result, err := newNullFunc(testCase.key, testCase.values)
|
||||
expectErr := (err != nil)
|
||||
|
||||
if expectErr != testCase.expectErr {
|
||||
t.Fatalf("case %v: error: expected: %v, got: %v\n", i+1, testCase.expectErr, expectErr)
|
||||
}
|
||||
|
||||
if !testCase.expectErr {
|
||||
if !reflect.DeepEqual(result, testCase.expectedResult) {
|
||||
t.Fatalf("case %v: result: expected: %v, got: %v\n", i+1, testCase.expectedResult, result)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
190
pkg/bucket/policy/condition/stringequalsfunc.go
Normal file
190
pkg/bucket/policy/condition/stringequalsfunc.go
Normal file
@@ -0,0 +1,190 @@
|
||||
/*
|
||||
* MinIO Cloud Storage, (C) 2018 MinIO, Inc.
|
||||
*
|
||||
* 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 condition
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"sort"
|
||||
|
||||
"github.com/minio/minio-go/v6/pkg/s3utils"
|
||||
"github.com/minio/minio-go/v6/pkg/set"
|
||||
)
|
||||
|
||||
func toStringEqualsFuncString(n name, key Key, values set.StringSet) string {
|
||||
valueStrings := values.ToSlice()
|
||||
sort.Strings(valueStrings)
|
||||
|
||||
return fmt.Sprintf("%v:%v:%v", n, key, valueStrings)
|
||||
}
|
||||
|
||||
// stringEqualsFunc - String equals function. It checks whether value by Key in given
|
||||
// values map is in condition values.
|
||||
// For example,
|
||||
// - if values = ["mybucket/foo"], at evaluate() it returns whether string
|
||||
// in value map for Key is in values.
|
||||
type stringEqualsFunc struct {
|
||||
k Key
|
||||
values set.StringSet
|
||||
}
|
||||
|
||||
// evaluate() - evaluates to check whether value by Key in given values is in
|
||||
// condition values.
|
||||
func (f stringEqualsFunc) evaluate(values map[string][]string) bool {
|
||||
requestValue, ok := values[http.CanonicalHeaderKey(f.k.Name())]
|
||||
if !ok {
|
||||
requestValue = values[f.k.Name()]
|
||||
}
|
||||
|
||||
fvalues := f.values.ApplyFunc(substFuncFromValues(values))
|
||||
return !fvalues.Intersection(set.CreateStringSet(requestValue...)).IsEmpty()
|
||||
}
|
||||
|
||||
// key() - returns condition key which is used by this condition function.
|
||||
func (f stringEqualsFunc) key() Key {
|
||||
return f.k
|
||||
}
|
||||
|
||||
// name() - returns "StringEquals" condition name.
|
||||
func (f stringEqualsFunc) name() name {
|
||||
return stringEquals
|
||||
}
|
||||
|
||||
func (f stringEqualsFunc) String() string {
|
||||
return toStringEqualsFuncString(stringEquals, f.k, f.values)
|
||||
}
|
||||
|
||||
// toMap - returns map representation of this function.
|
||||
func (f stringEqualsFunc) toMap() map[Key]ValueSet {
|
||||
if !f.k.IsValid() {
|
||||
return nil
|
||||
}
|
||||
|
||||
values := NewValueSet()
|
||||
for _, value := range f.values.ToSlice() {
|
||||
values.Add(NewStringValue(value))
|
||||
}
|
||||
|
||||
return map[Key]ValueSet{
|
||||
f.k: values,
|
||||
}
|
||||
}
|
||||
|
||||
// stringNotEqualsFunc - String not equals function. It checks whether value by Key in
|
||||
// given values is NOT in condition values.
|
||||
// For example,
|
||||
// - if values = ["mybucket/foo"], at evaluate() it returns whether string
|
||||
// in value map for Key is NOT in values.
|
||||
type stringNotEqualsFunc struct {
|
||||
stringEqualsFunc
|
||||
}
|
||||
|
||||
// evaluate() - evaluates to check whether value by Key in given values is NOT in
|
||||
// condition values.
|
||||
func (f stringNotEqualsFunc) evaluate(values map[string][]string) bool {
|
||||
return !f.stringEqualsFunc.evaluate(values)
|
||||
}
|
||||
|
||||
// name() - returns "StringNotEquals" condition name.
|
||||
func (f stringNotEqualsFunc) name() name {
|
||||
return stringNotEquals
|
||||
}
|
||||
|
||||
func (f stringNotEqualsFunc) String() string {
|
||||
return toStringEqualsFuncString(stringNotEquals, f.stringEqualsFunc.k, f.stringEqualsFunc.values)
|
||||
}
|
||||
|
||||
func valuesToStringSlice(n name, values ValueSet) ([]string, error) {
|
||||
valueStrings := []string{}
|
||||
|
||||
for value := range values {
|
||||
// FIXME: if AWS supports non-string values, we would need to support it.
|
||||
s, err := value.GetString()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("value must be a string for %v condition", n)
|
||||
}
|
||||
|
||||
valueStrings = append(valueStrings, s)
|
||||
}
|
||||
|
||||
return valueStrings, nil
|
||||
}
|
||||
|
||||
func validateStringEqualsValues(n name, key Key, values set.StringSet) error {
|
||||
for _, s := range values.ToSlice() {
|
||||
switch key {
|
||||
case S3XAmzCopySource:
|
||||
bucket, object := path2BucketAndObject(s)
|
||||
if object == "" {
|
||||
return fmt.Errorf("invalid value '%v' for '%v' for %v condition", s, S3XAmzCopySource, n)
|
||||
}
|
||||
if err := s3utils.CheckValidBucketName(bucket); err != nil {
|
||||
return err
|
||||
}
|
||||
case S3XAmzServerSideEncryption, S3XAmzServerSideEncryptionCustomerAlgorithm:
|
||||
if s != "AES256" {
|
||||
return fmt.Errorf("invalid value '%v' for '%v' for %v condition", s, S3XAmzServerSideEncryption, n)
|
||||
}
|
||||
case S3XAmzMetadataDirective:
|
||||
if s != "COPY" && s != "REPLACE" {
|
||||
return fmt.Errorf("invalid value '%v' for '%v' for %v condition", s, S3XAmzMetadataDirective, n)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// newStringEqualsFunc - returns new StringEquals function.
|
||||
func newStringEqualsFunc(key Key, values ValueSet) (Function, error) {
|
||||
valueStrings, err := valuesToStringSlice(stringEquals, values)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return NewStringEqualsFunc(key, valueStrings...)
|
||||
}
|
||||
|
||||
// NewStringEqualsFunc - returns new StringEquals function.
|
||||
func NewStringEqualsFunc(key Key, values ...string) (Function, error) {
|
||||
sset := set.CreateStringSet(values...)
|
||||
if err := validateStringEqualsValues(stringEquals, key, sset); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &stringEqualsFunc{key, sset}, nil
|
||||
}
|
||||
|
||||
// newStringNotEqualsFunc - returns new StringNotEquals function.
|
||||
func newStringNotEqualsFunc(key Key, values ValueSet) (Function, error) {
|
||||
valueStrings, err := valuesToStringSlice(stringNotEquals, values)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return NewStringNotEqualsFunc(key, valueStrings...)
|
||||
}
|
||||
|
||||
// NewStringNotEqualsFunc - returns new StringNotEquals function.
|
||||
func NewStringNotEqualsFunc(key Key, values ...string) (Function, error) {
|
||||
sset := set.CreateStringSet(values...)
|
||||
if err := validateStringEqualsValues(stringNotEquals, key, sset); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &stringNotEqualsFunc{stringEqualsFunc{key, sset}}, nil
|
||||
}
|
||||
708
pkg/bucket/policy/condition/stringequalsfunc_test.go
Normal file
708
pkg/bucket/policy/condition/stringequalsfunc_test.go
Normal file
@@ -0,0 +1,708 @@
|
||||
/*
|
||||
* MinIO Cloud Storage, (C) 2018 MinIO, Inc.
|
||||
*
|
||||
* 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 condition
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestStringEqualsFuncEvaluate(t *testing.T) {
|
||||
case1Function, err := newStringEqualsFunc(S3XAmzCopySource, NewValueSet(NewStringValue("mybucket/myobject")))
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error. %v\n", err)
|
||||
}
|
||||
|
||||
case2Function, err := newStringEqualsFunc(S3XAmzServerSideEncryption, NewValueSet(NewStringValue("AES256")))
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error. %v\n", err)
|
||||
}
|
||||
|
||||
case3Function, err := newStringEqualsFunc(S3XAmzMetadataDirective, NewValueSet(NewStringValue("REPLACE")))
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error. %v\n", err)
|
||||
}
|
||||
|
||||
case4Function, err := newStringEqualsFunc(S3LocationConstraint, NewValueSet(NewStringValue("eu-west-1")))
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error. %v\n", err)
|
||||
}
|
||||
|
||||
testCases := []struct {
|
||||
function Function
|
||||
values map[string][]string
|
||||
expectedResult bool
|
||||
}{
|
||||
{case1Function, map[string][]string{"x-amz-copy-source": {"mybucket/myobject"}}, true},
|
||||
{case1Function, map[string][]string{"x-amz-copy-source": {"yourbucket/myobject"}}, false},
|
||||
{case1Function, map[string][]string{}, false},
|
||||
{case1Function, map[string][]string{"delimiter": {"/"}}, false},
|
||||
|
||||
{case2Function, map[string][]string{"x-amz-server-side-encryption": {"AES256"}}, true},
|
||||
{case2Function, map[string][]string{}, false},
|
||||
{case2Function, map[string][]string{"delimiter": {"/"}}, false},
|
||||
|
||||
{case3Function, map[string][]string{"x-amz-metadata-directive": {"REPLACE"}}, true},
|
||||
{case3Function, map[string][]string{"x-amz-metadata-directive": {"COPY"}}, false},
|
||||
{case3Function, map[string][]string{}, false},
|
||||
{case3Function, map[string][]string{"delimiter": {"/"}}, false},
|
||||
|
||||
{case4Function, map[string][]string{"LocationConstraint": {"eu-west-1"}}, true},
|
||||
{case4Function, map[string][]string{"LocationConstraint": {"us-east-1"}}, false},
|
||||
{case4Function, map[string][]string{}, false},
|
||||
{case4Function, map[string][]string{"delimiter": {"/"}}, false},
|
||||
}
|
||||
|
||||
for i, testCase := range testCases {
|
||||
result := testCase.function.evaluate(testCase.values)
|
||||
|
||||
if result != testCase.expectedResult {
|
||||
t.Fatalf("case %v: expected: %v, got: %v\n", i+1, testCase.expectedResult, result)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestStringEqualsFuncKey(t *testing.T) {
|
||||
case1Function, err := newStringEqualsFunc(S3XAmzCopySource, NewValueSet(NewStringValue("mybucket/myobject")))
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error. %v\n", err)
|
||||
}
|
||||
|
||||
case2Function, err := newStringEqualsFunc(S3XAmzServerSideEncryption, NewValueSet(NewStringValue("AES256")))
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error. %v\n", err)
|
||||
}
|
||||
|
||||
case3Function, err := newStringEqualsFunc(S3XAmzMetadataDirective, NewValueSet(NewStringValue("REPLACE")))
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error. %v\n", err)
|
||||
}
|
||||
|
||||
case4Function, err := newStringEqualsFunc(S3LocationConstraint, NewValueSet(NewStringValue("eu-west-1")))
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error. %v\n", err)
|
||||
}
|
||||
|
||||
testCases := []struct {
|
||||
function Function
|
||||
expectedResult Key
|
||||
}{
|
||||
{case1Function, S3XAmzCopySource},
|
||||
{case2Function, S3XAmzServerSideEncryption},
|
||||
{case3Function, S3XAmzMetadataDirective},
|
||||
{case4Function, S3LocationConstraint},
|
||||
}
|
||||
|
||||
for i, testCase := range testCases {
|
||||
result := testCase.function.key()
|
||||
|
||||
if result != testCase.expectedResult {
|
||||
t.Fatalf("case %v: expected: %v, got: %v\n", i+1, testCase.expectedResult, result)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestStringEqualsFuncToMap(t *testing.T) {
|
||||
case1Function, err := newStringEqualsFunc(S3XAmzCopySource, NewValueSet(NewStringValue("mybucket/myobject")))
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error. %v\n", err)
|
||||
}
|
||||
|
||||
case1Result := map[Key]ValueSet{
|
||||
S3XAmzCopySource: NewValueSet(NewStringValue("mybucket/myobject")),
|
||||
}
|
||||
|
||||
case2Function, err := newStringEqualsFunc(S3XAmzCopySource,
|
||||
NewValueSet(
|
||||
NewStringValue("mybucket/myobject"),
|
||||
NewStringValue("yourbucket/myobject"),
|
||||
),
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error. %v\n", err)
|
||||
}
|
||||
|
||||
case2Result := map[Key]ValueSet{
|
||||
S3XAmzCopySource: NewValueSet(
|
||||
NewStringValue("mybucket/myobject"),
|
||||
NewStringValue("yourbucket/myobject"),
|
||||
),
|
||||
}
|
||||
|
||||
case3Function, err := newStringEqualsFunc(S3XAmzServerSideEncryption, NewValueSet(NewStringValue("AES256")))
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error. %v\n", err)
|
||||
}
|
||||
|
||||
case3Result := map[Key]ValueSet{
|
||||
S3XAmzServerSideEncryption: NewValueSet(NewStringValue("AES256")),
|
||||
}
|
||||
|
||||
case4Function, err := newStringEqualsFunc(S3XAmzServerSideEncryption,
|
||||
NewValueSet(
|
||||
NewStringValue("AES256"),
|
||||
),
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error. %v\n", err)
|
||||
}
|
||||
|
||||
case4Result := map[Key]ValueSet{
|
||||
S3XAmzServerSideEncryption: NewValueSet(
|
||||
NewStringValue("AES256"),
|
||||
),
|
||||
}
|
||||
|
||||
case5Function, err := newStringEqualsFunc(S3XAmzMetadataDirective, NewValueSet(NewStringValue("REPLACE")))
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error. %v\n", err)
|
||||
}
|
||||
|
||||
case5Result := map[Key]ValueSet{
|
||||
S3XAmzMetadataDirective: NewValueSet(NewStringValue("REPLACE")),
|
||||
}
|
||||
|
||||
case6Function, err := newStringEqualsFunc(S3XAmzMetadataDirective,
|
||||
NewValueSet(
|
||||
NewStringValue("REPLACE"),
|
||||
NewStringValue("COPY"),
|
||||
),
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error. %v\n", err)
|
||||
}
|
||||
|
||||
case6Result := map[Key]ValueSet{
|
||||
S3XAmzMetadataDirective: NewValueSet(
|
||||
NewStringValue("REPLACE"),
|
||||
NewStringValue("COPY"),
|
||||
),
|
||||
}
|
||||
|
||||
case7Function, err := newStringEqualsFunc(S3LocationConstraint, NewValueSet(NewStringValue("eu-west-1")))
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error. %v\n", err)
|
||||
}
|
||||
|
||||
case7Result := map[Key]ValueSet{
|
||||
S3LocationConstraint: NewValueSet(NewStringValue("eu-west-1")),
|
||||
}
|
||||
|
||||
case8Function, err := newStringEqualsFunc(S3LocationConstraint,
|
||||
NewValueSet(
|
||||
NewStringValue("eu-west-1"),
|
||||
NewStringValue("us-west-1"),
|
||||
),
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error. %v\n", err)
|
||||
}
|
||||
|
||||
case8Result := map[Key]ValueSet{
|
||||
S3LocationConstraint: NewValueSet(
|
||||
NewStringValue("eu-west-1"),
|
||||
NewStringValue("us-west-1"),
|
||||
),
|
||||
}
|
||||
|
||||
testCases := []struct {
|
||||
f Function
|
||||
expectedResult map[Key]ValueSet
|
||||
}{
|
||||
{case1Function, case1Result},
|
||||
{case2Function, case2Result},
|
||||
{case3Function, case3Result},
|
||||
{case4Function, case4Result},
|
||||
{case5Function, case5Result},
|
||||
{case6Function, case6Result},
|
||||
{case7Function, case7Result},
|
||||
{case8Function, case8Result},
|
||||
{&stringEqualsFunc{}, nil},
|
||||
}
|
||||
|
||||
for i, testCase := range testCases {
|
||||
result := testCase.f.toMap()
|
||||
|
||||
if !reflect.DeepEqual(result, testCase.expectedResult) {
|
||||
t.Fatalf("case %v: result: expected: %v, got: %v\n", i+1, testCase.expectedResult, result)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestStringNotEqualsFuncEvaluate(t *testing.T) {
|
||||
case1Function, err := newStringNotEqualsFunc(S3XAmzCopySource, NewValueSet(NewStringValue("mybucket/myobject")))
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error. %v\n", err)
|
||||
}
|
||||
|
||||
case2Function, err := newStringNotEqualsFunc(S3XAmzServerSideEncryption, NewValueSet(NewStringValue("AES256")))
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error. %v\n", err)
|
||||
}
|
||||
|
||||
case3Function, err := newStringNotEqualsFunc(S3XAmzMetadataDirective, NewValueSet(NewStringValue("REPLACE")))
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error. %v\n", err)
|
||||
}
|
||||
|
||||
case4Function, err := newStringNotEqualsFunc(S3LocationConstraint, NewValueSet(NewStringValue("eu-west-1")))
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error. %v\n", err)
|
||||
}
|
||||
|
||||
testCases := []struct {
|
||||
function Function
|
||||
values map[string][]string
|
||||
expectedResult bool
|
||||
}{
|
||||
{case1Function, map[string][]string{"x-amz-copy-source": {"mybucket/myobject"}}, false},
|
||||
{case1Function, map[string][]string{"x-amz-copy-source": {"yourbucket/myobject"}}, true},
|
||||
{case1Function, map[string][]string{}, true},
|
||||
{case1Function, map[string][]string{"delimiter": {"/"}}, true},
|
||||
|
||||
{case2Function, map[string][]string{"x-amz-server-side-encryption": {"AES256"}}, false},
|
||||
{case2Function, map[string][]string{}, true},
|
||||
{case2Function, map[string][]string{"delimiter": {"/"}}, true},
|
||||
|
||||
{case3Function, map[string][]string{"x-amz-metadata-directive": {"REPLACE"}}, false},
|
||||
{case3Function, map[string][]string{"x-amz-metadata-directive": {"COPY"}}, true},
|
||||
{case3Function, map[string][]string{}, true},
|
||||
{case3Function, map[string][]string{"delimiter": {"/"}}, true},
|
||||
|
||||
{case4Function, map[string][]string{"LocationConstraint": {"eu-west-1"}}, false},
|
||||
{case4Function, map[string][]string{"LocationConstraint": {"us-east-1"}}, true},
|
||||
{case4Function, map[string][]string{}, true},
|
||||
{case4Function, map[string][]string{"delimiter": {"/"}}, true},
|
||||
}
|
||||
|
||||
for i, testCase := range testCases {
|
||||
result := testCase.function.evaluate(testCase.values)
|
||||
|
||||
if result != testCase.expectedResult {
|
||||
t.Fatalf("case %v: expected: %v, got: %v\n", i+1, testCase.expectedResult, result)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestStringNotEqualsFuncKey(t *testing.T) {
|
||||
case1Function, err := newStringNotEqualsFunc(S3XAmzCopySource, NewValueSet(NewStringValue("mybucket/myobject")))
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error. %v\n", err)
|
||||
}
|
||||
|
||||
case2Function, err := newStringNotEqualsFunc(S3XAmzServerSideEncryption, NewValueSet(NewStringValue("AES256")))
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error. %v\n", err)
|
||||
}
|
||||
|
||||
case3Function, err := newStringNotEqualsFunc(S3XAmzMetadataDirective, NewValueSet(NewStringValue("REPLACE")))
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error. %v\n", err)
|
||||
}
|
||||
|
||||
case4Function, err := newStringNotEqualsFunc(S3LocationConstraint, NewValueSet(NewStringValue("eu-west-1")))
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error. %v\n", err)
|
||||
}
|
||||
|
||||
testCases := []struct {
|
||||
function Function
|
||||
expectedResult Key
|
||||
}{
|
||||
{case1Function, S3XAmzCopySource},
|
||||
{case2Function, S3XAmzServerSideEncryption},
|
||||
{case3Function, S3XAmzMetadataDirective},
|
||||
{case4Function, S3LocationConstraint},
|
||||
}
|
||||
|
||||
for i, testCase := range testCases {
|
||||
result := testCase.function.key()
|
||||
|
||||
if result != testCase.expectedResult {
|
||||
t.Fatalf("case %v: expected: %v, got: %v\n", i+1, testCase.expectedResult, result)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestStringNotEqualsFuncToMap(t *testing.T) {
|
||||
case1Function, err := newStringNotEqualsFunc(S3XAmzCopySource, NewValueSet(NewStringValue("mybucket/myobject")))
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error. %v\n", err)
|
||||
}
|
||||
|
||||
case1Result := map[Key]ValueSet{
|
||||
S3XAmzCopySource: NewValueSet(NewStringValue("mybucket/myobject")),
|
||||
}
|
||||
|
||||
case2Function, err := newStringNotEqualsFunc(S3XAmzCopySource,
|
||||
NewValueSet(
|
||||
NewStringValue("mybucket/myobject"),
|
||||
NewStringValue("yourbucket/myobject"),
|
||||
),
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error. %v\n", err)
|
||||
}
|
||||
|
||||
case2Result := map[Key]ValueSet{
|
||||
S3XAmzCopySource: NewValueSet(
|
||||
NewStringValue("mybucket/myobject"),
|
||||
NewStringValue("yourbucket/myobject"),
|
||||
),
|
||||
}
|
||||
|
||||
case3Function, err := newStringNotEqualsFunc(S3XAmzServerSideEncryption, NewValueSet(NewStringValue("AES256")))
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error. %v\n", err)
|
||||
}
|
||||
|
||||
case3Result := map[Key]ValueSet{
|
||||
S3XAmzServerSideEncryption: NewValueSet(NewStringValue("AES256")),
|
||||
}
|
||||
|
||||
case4Function, err := newStringNotEqualsFunc(S3XAmzServerSideEncryption,
|
||||
NewValueSet(
|
||||
NewStringValue("AES256"),
|
||||
),
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error. %v\n", err)
|
||||
}
|
||||
|
||||
case4Result := map[Key]ValueSet{
|
||||
S3XAmzServerSideEncryption: NewValueSet(
|
||||
NewStringValue("AES256"),
|
||||
),
|
||||
}
|
||||
|
||||
case5Function, err := newStringNotEqualsFunc(S3XAmzMetadataDirective, NewValueSet(NewStringValue("REPLACE")))
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error. %v\n", err)
|
||||
}
|
||||
|
||||
case5Result := map[Key]ValueSet{
|
||||
S3XAmzMetadataDirective: NewValueSet(NewStringValue("REPLACE")),
|
||||
}
|
||||
|
||||
case6Function, err := newStringNotEqualsFunc(S3XAmzMetadataDirective,
|
||||
NewValueSet(
|
||||
NewStringValue("REPLACE"),
|
||||
NewStringValue("COPY"),
|
||||
),
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error. %v\n", err)
|
||||
}
|
||||
|
||||
case6Result := map[Key]ValueSet{
|
||||
S3XAmzMetadataDirective: NewValueSet(
|
||||
NewStringValue("REPLACE"),
|
||||
NewStringValue("COPY"),
|
||||
),
|
||||
}
|
||||
|
||||
case7Function, err := newStringNotEqualsFunc(S3LocationConstraint, NewValueSet(NewStringValue("eu-west-1")))
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error. %v\n", err)
|
||||
}
|
||||
|
||||
case7Result := map[Key]ValueSet{
|
||||
S3LocationConstraint: NewValueSet(NewStringValue("eu-west-1")),
|
||||
}
|
||||
|
||||
case8Function, err := newStringNotEqualsFunc(S3LocationConstraint,
|
||||
NewValueSet(
|
||||
NewStringValue("eu-west-1"),
|
||||
NewStringValue("us-west-1"),
|
||||
),
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error. %v\n", err)
|
||||
}
|
||||
|
||||
case8Result := map[Key]ValueSet{
|
||||
S3LocationConstraint: NewValueSet(
|
||||
NewStringValue("eu-west-1"),
|
||||
NewStringValue("us-west-1"),
|
||||
),
|
||||
}
|
||||
|
||||
testCases := []struct {
|
||||
f Function
|
||||
expectedResult map[Key]ValueSet
|
||||
}{
|
||||
{case1Function, case1Result},
|
||||
{case2Function, case2Result},
|
||||
{case3Function, case3Result},
|
||||
{case4Function, case4Result},
|
||||
{case5Function, case5Result},
|
||||
{case6Function, case6Result},
|
||||
{case7Function, case7Result},
|
||||
{case8Function, case8Result},
|
||||
{&stringNotEqualsFunc{}, nil},
|
||||
}
|
||||
|
||||
for i, testCase := range testCases {
|
||||
result := testCase.f.toMap()
|
||||
|
||||
if !reflect.DeepEqual(result, testCase.expectedResult) {
|
||||
t.Fatalf("case %v: result: expected: %v, got: %v\n", i+1, testCase.expectedResult, result)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewStringEqualsFunc(t *testing.T) {
|
||||
case1Function, err := newStringEqualsFunc(S3XAmzCopySource, NewValueSet(NewStringValue("mybucket/myobject")))
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error. %v\n", err)
|
||||
}
|
||||
|
||||
case2Function, err := newStringEqualsFunc(S3XAmzCopySource,
|
||||
NewValueSet(
|
||||
NewStringValue("mybucket/myobject"),
|
||||
NewStringValue("yourbucket/myobject"),
|
||||
),
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error. %v\n", err)
|
||||
}
|
||||
|
||||
case3Function, err := newStringEqualsFunc(S3XAmzServerSideEncryption, NewValueSet(NewStringValue("AES256")))
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error. %v\n", err)
|
||||
}
|
||||
|
||||
case4Function, err := newStringEqualsFunc(S3XAmzServerSideEncryption,
|
||||
NewValueSet(
|
||||
NewStringValue("AES256"),
|
||||
),
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error. %v\n", err)
|
||||
}
|
||||
|
||||
case5Function, err := newStringEqualsFunc(S3XAmzMetadataDirective, NewValueSet(NewStringValue("REPLACE")))
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error. %v\n", err)
|
||||
}
|
||||
|
||||
case6Function, err := newStringEqualsFunc(S3XAmzMetadataDirective,
|
||||
NewValueSet(
|
||||
NewStringValue("REPLACE"),
|
||||
NewStringValue("COPY"),
|
||||
),
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error. %v\n", err)
|
||||
}
|
||||
|
||||
case7Function, err := newStringEqualsFunc(S3LocationConstraint, NewValueSet(NewStringValue("eu-west-1")))
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error. %v\n", err)
|
||||
}
|
||||
|
||||
case8Function, err := newStringEqualsFunc(S3LocationConstraint,
|
||||
NewValueSet(
|
||||
NewStringValue("eu-west-1"),
|
||||
NewStringValue("us-west-1"),
|
||||
),
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error. %v\n", err)
|
||||
}
|
||||
|
||||
testCases := []struct {
|
||||
key Key
|
||||
values ValueSet
|
||||
expectedResult Function
|
||||
expectErr bool
|
||||
}{
|
||||
{S3XAmzCopySource, NewValueSet(NewStringValue("mybucket/myobject")), case1Function, false},
|
||||
{S3XAmzCopySource,
|
||||
NewValueSet(
|
||||
NewStringValue("mybucket/myobject"),
|
||||
NewStringValue("yourbucket/myobject"),
|
||||
), case2Function, false},
|
||||
|
||||
{S3XAmzServerSideEncryption, NewValueSet(NewStringValue("AES256")), case3Function, false},
|
||||
{S3XAmzServerSideEncryption,
|
||||
NewValueSet(
|
||||
NewStringValue("AES256"),
|
||||
), case4Function, false},
|
||||
|
||||
{S3XAmzMetadataDirective, NewValueSet(NewStringValue("REPLACE")), case5Function, false},
|
||||
{S3XAmzMetadataDirective,
|
||||
NewValueSet(
|
||||
NewStringValue("REPLACE"),
|
||||
NewStringValue("COPY"),
|
||||
), case6Function, false},
|
||||
|
||||
{S3LocationConstraint, NewValueSet(NewStringValue("eu-west-1")), case7Function, false},
|
||||
{S3LocationConstraint,
|
||||
NewValueSet(
|
||||
NewStringValue("eu-west-1"),
|
||||
NewStringValue("us-west-1"),
|
||||
), case8Function, false},
|
||||
|
||||
// Unsupported value error.
|
||||
{S3XAmzCopySource, NewValueSet(NewStringValue("mybucket/myobject"), NewIntValue(7)), nil, true},
|
||||
{S3XAmzServerSideEncryption, NewValueSet(NewStringValue("AES256"), NewIntValue(7)), nil, true},
|
||||
{S3XAmzMetadataDirective, NewValueSet(NewStringValue("REPLACE"), NewIntValue(7)), nil, true},
|
||||
{S3LocationConstraint, NewValueSet(NewStringValue("eu-west-1"), NewIntValue(7)), nil, true},
|
||||
|
||||
// Invalid value error.
|
||||
{S3XAmzCopySource, NewValueSet(NewStringValue("mybucket")), nil, true},
|
||||
{S3XAmzServerSideEncryption, NewValueSet(NewStringValue("SSE-C")), nil, true},
|
||||
{S3XAmzMetadataDirective, NewValueSet(NewStringValue("DUPLICATE")), nil, true},
|
||||
}
|
||||
|
||||
for i, testCase := range testCases {
|
||||
result, err := newStringEqualsFunc(testCase.key, testCase.values)
|
||||
expectErr := (err != nil)
|
||||
|
||||
if expectErr != testCase.expectErr {
|
||||
t.Fatalf("case %v: error: expected: %v, got: %v\n", i+1, testCase.expectErr, expectErr)
|
||||
}
|
||||
|
||||
if !testCase.expectErr {
|
||||
if !reflect.DeepEqual(result, testCase.expectedResult) {
|
||||
t.Fatalf("case %v: result: expected: %v, got: %v\n", i+1, testCase.expectedResult, result)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewStringNotEqualsFunc(t *testing.T) {
|
||||
case1Function, err := newStringNotEqualsFunc(S3XAmzCopySource, NewValueSet(NewStringValue("mybucket/myobject")))
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error. %v\n", err)
|
||||
}
|
||||
|
||||
case2Function, err := newStringNotEqualsFunc(S3XAmzCopySource,
|
||||
NewValueSet(
|
||||
NewStringValue("mybucket/myobject"),
|
||||
NewStringValue("yourbucket/myobject"),
|
||||
),
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error. %v\n", err)
|
||||
}
|
||||
|
||||
case3Function, err := newStringNotEqualsFunc(S3XAmzServerSideEncryption, NewValueSet(NewStringValue("AES256")))
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error. %v\n", err)
|
||||
}
|
||||
|
||||
case4Function, err := newStringNotEqualsFunc(S3XAmzServerSideEncryption,
|
||||
NewValueSet(
|
||||
NewStringValue("AES256"),
|
||||
),
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error. %v\n", err)
|
||||
}
|
||||
|
||||
case5Function, err := newStringNotEqualsFunc(S3XAmzMetadataDirective, NewValueSet(NewStringValue("REPLACE")))
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error. %v\n", err)
|
||||
}
|
||||
|
||||
case6Function, err := newStringNotEqualsFunc(S3XAmzMetadataDirective,
|
||||
NewValueSet(
|
||||
NewStringValue("REPLACE"),
|
||||
NewStringValue("COPY"),
|
||||
),
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error. %v\n", err)
|
||||
}
|
||||
|
||||
case7Function, err := newStringNotEqualsFunc(S3LocationConstraint, NewValueSet(NewStringValue("eu-west-1")))
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error. %v\n", err)
|
||||
}
|
||||
|
||||
case8Function, err := newStringNotEqualsFunc(S3LocationConstraint,
|
||||
NewValueSet(
|
||||
NewStringValue("eu-west-1"),
|
||||
NewStringValue("us-west-1"),
|
||||
),
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error. %v\n", err)
|
||||
}
|
||||
|
||||
testCases := []struct {
|
||||
key Key
|
||||
values ValueSet
|
||||
expectedResult Function
|
||||
expectErr bool
|
||||
}{
|
||||
{S3XAmzCopySource, NewValueSet(NewStringValue("mybucket/myobject")), case1Function, false},
|
||||
{S3XAmzCopySource,
|
||||
NewValueSet(
|
||||
NewStringValue("mybucket/myobject"),
|
||||
NewStringValue("yourbucket/myobject"),
|
||||
), case2Function, false},
|
||||
|
||||
{S3XAmzServerSideEncryption, NewValueSet(NewStringValue("AES256")), case3Function, false},
|
||||
{S3XAmzServerSideEncryption,
|
||||
NewValueSet(
|
||||
NewStringValue("AES256"),
|
||||
), case4Function, false},
|
||||
|
||||
{S3XAmzMetadataDirective, NewValueSet(NewStringValue("REPLACE")), case5Function, false},
|
||||
{S3XAmzMetadataDirective,
|
||||
NewValueSet(
|
||||
NewStringValue("REPLACE"),
|
||||
NewStringValue("COPY"),
|
||||
), case6Function, false},
|
||||
|
||||
{S3LocationConstraint, NewValueSet(NewStringValue("eu-west-1")), case7Function, false},
|
||||
{S3LocationConstraint,
|
||||
NewValueSet(
|
||||
NewStringValue("eu-west-1"),
|
||||
NewStringValue("us-west-1"),
|
||||
), case8Function, false},
|
||||
|
||||
// Unsupported value error.
|
||||
{S3XAmzCopySource, NewValueSet(NewStringValue("mybucket/myobject"), NewIntValue(7)), nil, true},
|
||||
{S3XAmzServerSideEncryption, NewValueSet(NewStringValue("AES256"), NewIntValue(7)), nil, true},
|
||||
{S3XAmzMetadataDirective, NewValueSet(NewStringValue("REPLACE"), NewIntValue(7)), nil, true},
|
||||
{S3LocationConstraint, NewValueSet(NewStringValue("eu-west-1"), NewIntValue(7)), nil, true},
|
||||
|
||||
// Invalid value error.
|
||||
{S3XAmzCopySource, NewValueSet(NewStringValue("mybucket")), nil, true},
|
||||
{S3XAmzServerSideEncryption, NewValueSet(NewStringValue("SSE-C")), nil, true},
|
||||
{S3XAmzMetadataDirective, NewValueSet(NewStringValue("DUPLICATE")), nil, true},
|
||||
}
|
||||
|
||||
for i, testCase := range testCases {
|
||||
result, err := newStringNotEqualsFunc(testCase.key, testCase.values)
|
||||
expectErr := (err != nil)
|
||||
|
||||
if expectErr != testCase.expectErr {
|
||||
t.Fatalf("case %v: error: expected: %v, got: %v\n", i+1, testCase.expectErr, expectErr)
|
||||
}
|
||||
|
||||
if !testCase.expectErr {
|
||||
if !reflect.DeepEqual(result, testCase.expectedResult) {
|
||||
t.Fatalf("case %v: result: expected: %v, got: %v\n", i+1, testCase.expectedResult, result)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
160
pkg/bucket/policy/condition/stringequalsignorecasefunc.go
Normal file
160
pkg/bucket/policy/condition/stringequalsignorecasefunc.go
Normal file
@@ -0,0 +1,160 @@
|
||||
/*
|
||||
* MinIO Cloud Storage, (C) 2018 MinIO, Inc.
|
||||
*
|
||||
* 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 condition
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/minio/minio-go/v6/pkg/set"
|
||||
)
|
||||
|
||||
func toStringEqualsIgnoreCaseFuncString(n name, key Key, values set.StringSet) string {
|
||||
valueStrings := values.ToSlice()
|
||||
sort.Strings(valueStrings)
|
||||
|
||||
return fmt.Sprintf("%v:%v:%v", n, key, valueStrings)
|
||||
}
|
||||
|
||||
// stringEqualsIgnoreCaseFunc - String equals function. It checks whether value by Key in given
|
||||
// values map is in condition values.
|
||||
// For example,
|
||||
// - if values = ["mybucket/foo"], at evaluate() it returns whether string
|
||||
// in value map for Key is in values.
|
||||
type stringEqualsIgnoreCaseFunc struct {
|
||||
k Key
|
||||
values set.StringSet
|
||||
}
|
||||
|
||||
// evaluate() - evaluates to check whether value by Key in given values is in
|
||||
// condition values, ignores case.
|
||||
func (f stringEqualsIgnoreCaseFunc) evaluate(values map[string][]string) bool {
|
||||
requestValue, ok := values[http.CanonicalHeaderKey(f.k.Name())]
|
||||
if !ok {
|
||||
requestValue = values[f.k.Name()]
|
||||
}
|
||||
|
||||
fvalues := f.values.ApplyFunc(substFuncFromValues(values))
|
||||
|
||||
for _, v := range requestValue {
|
||||
if !fvalues.FuncMatch(strings.EqualFold, v).IsEmpty() {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// key() - returns condition key which is used by this condition function.
|
||||
func (f stringEqualsIgnoreCaseFunc) key() Key {
|
||||
return f.k
|
||||
}
|
||||
|
||||
// name() - returns "StringEqualsIgnoreCase" condition name.
|
||||
func (f stringEqualsIgnoreCaseFunc) name() name {
|
||||
return stringEqualsIgnoreCase
|
||||
}
|
||||
|
||||
func (f stringEqualsIgnoreCaseFunc) String() string {
|
||||
return toStringEqualsIgnoreCaseFuncString(stringEqualsIgnoreCase, f.k, f.values)
|
||||
}
|
||||
|
||||
// toMap - returns map representation of this function.
|
||||
func (f stringEqualsIgnoreCaseFunc) toMap() map[Key]ValueSet {
|
||||
if !f.k.IsValid() {
|
||||
return nil
|
||||
}
|
||||
|
||||
values := NewValueSet()
|
||||
for _, value := range f.values.ToSlice() {
|
||||
values.Add(NewStringValue(value))
|
||||
}
|
||||
|
||||
return map[Key]ValueSet{
|
||||
f.k: values,
|
||||
}
|
||||
}
|
||||
|
||||
// stringNotEqualsIgnoreCaseFunc - String not equals function. It checks whether value by Key in
|
||||
// given values is NOT in condition values.
|
||||
// For example,
|
||||
// - if values = ["mybucket/foo"], at evaluate() it returns whether string
|
||||
// in value map for Key is NOT in values.
|
||||
type stringNotEqualsIgnoreCaseFunc struct {
|
||||
stringEqualsIgnoreCaseFunc
|
||||
}
|
||||
|
||||
// evaluate() - evaluates to check whether value by Key in given values is NOT in
|
||||
// condition values.
|
||||
func (f stringNotEqualsIgnoreCaseFunc) evaluate(values map[string][]string) bool {
|
||||
return !f.stringEqualsIgnoreCaseFunc.evaluate(values)
|
||||
}
|
||||
|
||||
// name() - returns "StringNotEqualsIgnoreCase" condition name.
|
||||
func (f stringNotEqualsIgnoreCaseFunc) name() name {
|
||||
return stringNotEqualsIgnoreCase
|
||||
}
|
||||
|
||||
func (f stringNotEqualsIgnoreCaseFunc) String() string {
|
||||
return toStringEqualsIgnoreCaseFuncString(stringNotEqualsIgnoreCase, f.stringEqualsIgnoreCaseFunc.k, f.stringEqualsIgnoreCaseFunc.values)
|
||||
}
|
||||
|
||||
func validateStringEqualsIgnoreCaseValues(n name, key Key, values set.StringSet) error {
|
||||
return validateStringEqualsValues(n, key, values)
|
||||
}
|
||||
|
||||
// newStringEqualsIgnoreCaseFunc - returns new StringEqualsIgnoreCase function.
|
||||
func newStringEqualsIgnoreCaseFunc(key Key, values ValueSet) (Function, error) {
|
||||
valueStrings, err := valuesToStringSlice(stringEqualsIgnoreCase, values)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return NewStringEqualsIgnoreCaseFunc(key, valueStrings...)
|
||||
}
|
||||
|
||||
// NewStringEqualsIgnoreCaseFunc - returns new StringEqualsIgnoreCase function.
|
||||
func NewStringEqualsIgnoreCaseFunc(key Key, values ...string) (Function, error) {
|
||||
sset := set.CreateStringSet(values...)
|
||||
if err := validateStringEqualsIgnoreCaseValues(stringEqualsIgnoreCase, key, sset); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &stringEqualsIgnoreCaseFunc{key, sset}, nil
|
||||
}
|
||||
|
||||
// newStringNotEqualsIgnoreCaseFunc - returns new StringNotEqualsIgnoreCase function.
|
||||
func newStringNotEqualsIgnoreCaseFunc(key Key, values ValueSet) (Function, error) {
|
||||
valueStrings, err := valuesToStringSlice(stringNotEqualsIgnoreCase, values)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return NewStringNotEqualsIgnoreCaseFunc(key, valueStrings...)
|
||||
}
|
||||
|
||||
// NewStringNotEqualsIgnoreCaseFunc - returns new StringNotEqualsIgnoreCase function.
|
||||
func NewStringNotEqualsIgnoreCaseFunc(key Key, values ...string) (Function, error) {
|
||||
sset := set.CreateStringSet(values...)
|
||||
if err := validateStringEqualsIgnoreCaseValues(stringNotEqualsIgnoreCase, key, sset); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &stringNotEqualsIgnoreCaseFunc{stringEqualsIgnoreCaseFunc{key, sset}}, nil
|
||||
}
|
||||
710
pkg/bucket/policy/condition/stringequalsignorecasefunc_test.go
Normal file
710
pkg/bucket/policy/condition/stringequalsignorecasefunc_test.go
Normal file
@@ -0,0 +1,710 @@
|
||||
/*
|
||||
* MinIO Cloud Storage, (C) 2018 MinIO, Inc.
|
||||
*
|
||||
* 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 condition
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestStringEqualsIgnoreCaseFuncEvaluate(t *testing.T) {
|
||||
case1Function, err := newStringEqualsIgnoreCaseFunc(S3XAmzCopySource, NewValueSet(NewStringValue("mybucket/myobject")))
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error. %v\n", err)
|
||||
}
|
||||
|
||||
case2Function, err := newStringEqualsIgnoreCaseFunc(S3XAmzServerSideEncryption, NewValueSet(NewStringValue("AES256")))
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error. %v\n", err)
|
||||
}
|
||||
|
||||
case3Function, err := newStringEqualsIgnoreCaseFunc(S3XAmzMetadataDirective, NewValueSet(NewStringValue("REPLACE")))
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error. %v\n", err)
|
||||
}
|
||||
|
||||
case4Function, err := newStringEqualsIgnoreCaseFunc(S3LocationConstraint, NewValueSet(NewStringValue("eu-west-1")))
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error. %v\n", err)
|
||||
}
|
||||
|
||||
testCases := []struct {
|
||||
function Function
|
||||
values map[string][]string
|
||||
expectedResult bool
|
||||
}{
|
||||
{case1Function, map[string][]string{"x-amz-copy-source": {"mybucket/myobject"}}, true},
|
||||
{case1Function, map[string][]string{"x-amz-copy-source": {"yourbucket/myobject"}}, false},
|
||||
{case1Function, map[string][]string{}, false},
|
||||
{case1Function, map[string][]string{"delimiter": {"/"}}, false},
|
||||
|
||||
{case2Function, map[string][]string{"x-amz-server-side-encryption": {"AES256"}}, true},
|
||||
{case2Function, map[string][]string{"x-amz-server-side-encryption": {"aes256"}}, true},
|
||||
{case2Function, map[string][]string{}, false},
|
||||
{case2Function, map[string][]string{"delimiter": {"/"}}, false},
|
||||
|
||||
{case3Function, map[string][]string{"x-amz-metadata-directive": {"REPLACE"}}, true},
|
||||
{case3Function, map[string][]string{"x-amz-metadata-directive": {"replace"}}, true},
|
||||
{case3Function, map[string][]string{"x-amz-metadata-directive": {"COPY"}}, false},
|
||||
{case3Function, map[string][]string{}, false},
|
||||
{case3Function, map[string][]string{"delimiter": {"/"}}, false},
|
||||
|
||||
{case4Function, map[string][]string{"LocationConstraint": {"eu-west-1"}}, true},
|
||||
{case4Function, map[string][]string{"LocationConstraint": {"us-east-1"}}, false},
|
||||
{case4Function, map[string][]string{}, false},
|
||||
{case4Function, map[string][]string{"delimiter": {"/"}}, false},
|
||||
}
|
||||
|
||||
for i, testCase := range testCases {
|
||||
result := testCase.function.evaluate(testCase.values)
|
||||
|
||||
if result != testCase.expectedResult {
|
||||
t.Fatalf("case %v: expected: %v, got: %v\n", i+1, testCase.expectedResult, result)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestStringEqualsIgnoreCaseFuncKey(t *testing.T) {
|
||||
case1Function, err := newStringEqualsIgnoreCaseFunc(S3XAmzCopySource, NewValueSet(NewStringValue("mybucket/myobject")))
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error. %v\n", err)
|
||||
}
|
||||
|
||||
case2Function, err := newStringEqualsIgnoreCaseFunc(S3XAmzServerSideEncryption, NewValueSet(NewStringValue("AES256")))
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error. %v\n", err)
|
||||
}
|
||||
|
||||
case3Function, err := newStringEqualsIgnoreCaseFunc(S3XAmzMetadataDirective, NewValueSet(NewStringValue("REPLACE")))
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error. %v\n", err)
|
||||
}
|
||||
|
||||
case4Function, err := newStringEqualsIgnoreCaseFunc(S3LocationConstraint, NewValueSet(NewStringValue("eu-west-1")))
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error. %v\n", err)
|
||||
}
|
||||
|
||||
testCases := []struct {
|
||||
function Function
|
||||
expectedResult Key
|
||||
}{
|
||||
{case1Function, S3XAmzCopySource},
|
||||
{case2Function, S3XAmzServerSideEncryption},
|
||||
{case3Function, S3XAmzMetadataDirective},
|
||||
{case4Function, S3LocationConstraint},
|
||||
}
|
||||
|
||||
for i, testCase := range testCases {
|
||||
result := testCase.function.key()
|
||||
|
||||
if result != testCase.expectedResult {
|
||||
t.Fatalf("case %v: expected: %v, got: %v\n", i+1, testCase.expectedResult, result)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestStringEqualsIgnoreCaseFuncToMap(t *testing.T) {
|
||||
case1Function, err := newStringEqualsIgnoreCaseFunc(S3XAmzCopySource, NewValueSet(NewStringValue("mybucket/myobject")))
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error. %v\n", err)
|
||||
}
|
||||
|
||||
case1Result := map[Key]ValueSet{
|
||||
S3XAmzCopySource: NewValueSet(NewStringValue("mybucket/myobject")),
|
||||
}
|
||||
|
||||
case2Function, err := newStringEqualsIgnoreCaseFunc(S3XAmzCopySource,
|
||||
NewValueSet(
|
||||
NewStringValue("mybucket/myobject"),
|
||||
NewStringValue("yourbucket/myobject"),
|
||||
),
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error. %v\n", err)
|
||||
}
|
||||
|
||||
case2Result := map[Key]ValueSet{
|
||||
S3XAmzCopySource: NewValueSet(
|
||||
NewStringValue("mybucket/myobject"),
|
||||
NewStringValue("yourbucket/myobject"),
|
||||
),
|
||||
}
|
||||
|
||||
case3Function, err := newStringEqualsIgnoreCaseFunc(S3XAmzServerSideEncryption, NewValueSet(NewStringValue("AES256")))
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error. %v\n", err)
|
||||
}
|
||||
|
||||
case3Result := map[Key]ValueSet{
|
||||
S3XAmzServerSideEncryption: NewValueSet(NewStringValue("AES256")),
|
||||
}
|
||||
|
||||
case4Function, err := newStringEqualsIgnoreCaseFunc(S3XAmzServerSideEncryption,
|
||||
NewValueSet(
|
||||
NewStringValue("AES256"),
|
||||
),
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error. %v\n", err)
|
||||
}
|
||||
|
||||
case4Result := map[Key]ValueSet{
|
||||
S3XAmzServerSideEncryption: NewValueSet(
|
||||
NewStringValue("AES256"),
|
||||
),
|
||||
}
|
||||
|
||||
case5Function, err := newStringEqualsIgnoreCaseFunc(S3XAmzMetadataDirective, NewValueSet(NewStringValue("REPLACE")))
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error. %v\n", err)
|
||||
}
|
||||
|
||||
case5Result := map[Key]ValueSet{
|
||||
S3XAmzMetadataDirective: NewValueSet(NewStringValue("REPLACE")),
|
||||
}
|
||||
|
||||
case6Function, err := newStringEqualsIgnoreCaseFunc(S3XAmzMetadataDirective,
|
||||
NewValueSet(
|
||||
NewStringValue("REPLACE"),
|
||||
NewStringValue("COPY"),
|
||||
),
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error. %v\n", err)
|
||||
}
|
||||
|
||||
case6Result := map[Key]ValueSet{
|
||||
S3XAmzMetadataDirective: NewValueSet(
|
||||
NewStringValue("REPLACE"),
|
||||
NewStringValue("COPY"),
|
||||
),
|
||||
}
|
||||
|
||||
case7Function, err := newStringEqualsIgnoreCaseFunc(S3LocationConstraint, NewValueSet(NewStringValue("eu-west-1")))
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error. %v\n", err)
|
||||
}
|
||||
|
||||
case7Result := map[Key]ValueSet{
|
||||
S3LocationConstraint: NewValueSet(NewStringValue("eu-west-1")),
|
||||
}
|
||||
|
||||
case8Function, err := newStringEqualsIgnoreCaseFunc(S3LocationConstraint,
|
||||
NewValueSet(
|
||||
NewStringValue("eu-west-1"),
|
||||
NewStringValue("us-west-1"),
|
||||
),
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error. %v\n", err)
|
||||
}
|
||||
|
||||
case8Result := map[Key]ValueSet{
|
||||
S3LocationConstraint: NewValueSet(
|
||||
NewStringValue("eu-west-1"),
|
||||
NewStringValue("us-west-1"),
|
||||
),
|
||||
}
|
||||
|
||||
testCases := []struct {
|
||||
f Function
|
||||
expectedResult map[Key]ValueSet
|
||||
}{
|
||||
{case1Function, case1Result},
|
||||
{case2Function, case2Result},
|
||||
{case3Function, case3Result},
|
||||
{case4Function, case4Result},
|
||||
{case5Function, case5Result},
|
||||
{case6Function, case6Result},
|
||||
{case7Function, case7Result},
|
||||
{case8Function, case8Result},
|
||||
{&stringEqualsFunc{}, nil},
|
||||
}
|
||||
|
||||
for i, testCase := range testCases {
|
||||
result := testCase.f.toMap()
|
||||
|
||||
if !reflect.DeepEqual(result, testCase.expectedResult) {
|
||||
t.Fatalf("case %v: result: expected: %v, got: %v\n", i+1, testCase.expectedResult, result)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestStringNotEqualsIgnoreCaseFuncEvaluate(t *testing.T) {
|
||||
case1Function, err := newStringNotEqualsIgnoreCaseFunc(S3XAmzCopySource, NewValueSet(NewStringValue("mybucket/myobject")))
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error. %v\n", err)
|
||||
}
|
||||
|
||||
case2Function, err := newStringNotEqualsIgnoreCaseFunc(S3XAmzServerSideEncryption, NewValueSet(NewStringValue("AES256")))
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error. %v\n", err)
|
||||
}
|
||||
|
||||
case3Function, err := newStringNotEqualsIgnoreCaseFunc(S3XAmzMetadataDirective, NewValueSet(NewStringValue("REPLACE")))
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error. %v\n", err)
|
||||
}
|
||||
|
||||
case4Function, err := newStringNotEqualsIgnoreCaseFunc(S3LocationConstraint, NewValueSet(NewStringValue("eu-west-1")))
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error. %v\n", err)
|
||||
}
|
||||
|
||||
testCases := []struct {
|
||||
function Function
|
||||
values map[string][]string
|
||||
expectedResult bool
|
||||
}{
|
||||
{case1Function, map[string][]string{"x-amz-copy-source": {"mybucket/myobject"}}, false},
|
||||
{case1Function, map[string][]string{"x-amz-copy-source": {"yourbucket/myobject"}}, true},
|
||||
{case1Function, map[string][]string{}, true},
|
||||
{case1Function, map[string][]string{"delimiter": {"/"}}, true},
|
||||
|
||||
{case2Function, map[string][]string{"x-amz-server-side-encryption": {"AES256"}}, false},
|
||||
{case2Function, map[string][]string{}, true},
|
||||
{case2Function, map[string][]string{"delimiter": {"/"}}, true},
|
||||
|
||||
{case3Function, map[string][]string{"x-amz-metadata-directive": {"REPLACE"}}, false},
|
||||
{case3Function, map[string][]string{"x-amz-metadata-directive": {"COPY"}}, true},
|
||||
{case3Function, map[string][]string{}, true},
|
||||
{case3Function, map[string][]string{"delimiter": {"/"}}, true},
|
||||
|
||||
{case4Function, map[string][]string{"LocationConstraint": {"eu-west-1"}}, false},
|
||||
{case4Function, map[string][]string{"LocationConstraint": {"us-east-1"}}, true},
|
||||
{case4Function, map[string][]string{}, true},
|
||||
{case4Function, map[string][]string{"delimiter": {"/"}}, true},
|
||||
}
|
||||
|
||||
for i, testCase := range testCases {
|
||||
result := testCase.function.evaluate(testCase.values)
|
||||
|
||||
if result != testCase.expectedResult {
|
||||
t.Fatalf("case %v: expected: %v, got: %v\n", i+1, testCase.expectedResult, result)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestStringNotEqualsIgnoreCaseFuncKey(t *testing.T) {
|
||||
case1Function, err := newStringNotEqualsIgnoreCaseFunc(S3XAmzCopySource, NewValueSet(NewStringValue("mybucket/myobject")))
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error. %v\n", err)
|
||||
}
|
||||
|
||||
case2Function, err := newStringNotEqualsIgnoreCaseFunc(S3XAmzServerSideEncryption, NewValueSet(NewStringValue("AES256")))
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error. %v\n", err)
|
||||
}
|
||||
|
||||
case3Function, err := newStringNotEqualsIgnoreCaseFunc(S3XAmzMetadataDirective, NewValueSet(NewStringValue("REPLACE")))
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error. %v\n", err)
|
||||
}
|
||||
|
||||
case4Function, err := newStringNotEqualsIgnoreCaseFunc(S3LocationConstraint, NewValueSet(NewStringValue("eu-west-1")))
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error. %v\n", err)
|
||||
}
|
||||
|
||||
testCases := []struct {
|
||||
function Function
|
||||
expectedResult Key
|
||||
}{
|
||||
{case1Function, S3XAmzCopySource},
|
||||
{case2Function, S3XAmzServerSideEncryption},
|
||||
{case3Function, S3XAmzMetadataDirective},
|
||||
{case4Function, S3LocationConstraint},
|
||||
}
|
||||
|
||||
for i, testCase := range testCases {
|
||||
result := testCase.function.key()
|
||||
|
||||
if result != testCase.expectedResult {
|
||||
t.Fatalf("case %v: expected: %v, got: %v\n", i+1, testCase.expectedResult, result)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestStringNotEqualsIgnoreCaseFuncToMap(t *testing.T) {
|
||||
case1Function, err := newStringNotEqualsIgnoreCaseFunc(S3XAmzCopySource, NewValueSet(NewStringValue("mybucket/myobject")))
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error. %v\n", err)
|
||||
}
|
||||
|
||||
case1Result := map[Key]ValueSet{
|
||||
S3XAmzCopySource: NewValueSet(NewStringValue("mybucket/myobject")),
|
||||
}
|
||||
|
||||
case2Function, err := newStringNotEqualsIgnoreCaseFunc(S3XAmzCopySource,
|
||||
NewValueSet(
|
||||
NewStringValue("mybucket/myobject"),
|
||||
NewStringValue("yourbucket/myobject"),
|
||||
),
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error. %v\n", err)
|
||||
}
|
||||
|
||||
case2Result := map[Key]ValueSet{
|
||||
S3XAmzCopySource: NewValueSet(
|
||||
NewStringValue("mybucket/myobject"),
|
||||
NewStringValue("yourbucket/myobject"),
|
||||
),
|
||||
}
|
||||
|
||||
case3Function, err := newStringNotEqualsIgnoreCaseFunc(S3XAmzServerSideEncryption, NewValueSet(NewStringValue("AES256")))
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error. %v\n", err)
|
||||
}
|
||||
|
||||
case3Result := map[Key]ValueSet{
|
||||
S3XAmzServerSideEncryption: NewValueSet(NewStringValue("AES256")),
|
||||
}
|
||||
|
||||
case4Function, err := newStringNotEqualsIgnoreCaseFunc(S3XAmzServerSideEncryption,
|
||||
NewValueSet(
|
||||
NewStringValue("AES256"),
|
||||
),
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error. %v\n", err)
|
||||
}
|
||||
|
||||
case4Result := map[Key]ValueSet{
|
||||
S3XAmzServerSideEncryption: NewValueSet(
|
||||
NewStringValue("AES256"),
|
||||
),
|
||||
}
|
||||
|
||||
case5Function, err := newStringNotEqualsIgnoreCaseFunc(S3XAmzMetadataDirective, NewValueSet(NewStringValue("REPLACE")))
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error. %v\n", err)
|
||||
}
|
||||
|
||||
case5Result := map[Key]ValueSet{
|
||||
S3XAmzMetadataDirective: NewValueSet(NewStringValue("REPLACE")),
|
||||
}
|
||||
|
||||
case6Function, err := newStringNotEqualsIgnoreCaseFunc(S3XAmzMetadataDirective,
|
||||
NewValueSet(
|
||||
NewStringValue("REPLACE"),
|
||||
NewStringValue("COPY"),
|
||||
),
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error. %v\n", err)
|
||||
}
|
||||
|
||||
case6Result := map[Key]ValueSet{
|
||||
S3XAmzMetadataDirective: NewValueSet(
|
||||
NewStringValue("REPLACE"),
|
||||
NewStringValue("COPY"),
|
||||
),
|
||||
}
|
||||
|
||||
case7Function, err := newStringNotEqualsIgnoreCaseFunc(S3LocationConstraint, NewValueSet(NewStringValue("eu-west-1")))
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error. %v\n", err)
|
||||
}
|
||||
|
||||
case7Result := map[Key]ValueSet{
|
||||
S3LocationConstraint: NewValueSet(NewStringValue("eu-west-1")),
|
||||
}
|
||||
|
||||
case8Function, err := newStringNotEqualsIgnoreCaseFunc(S3LocationConstraint,
|
||||
NewValueSet(
|
||||
NewStringValue("eu-west-1"),
|
||||
NewStringValue("us-west-1"),
|
||||
),
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error. %v\n", err)
|
||||
}
|
||||
|
||||
case8Result := map[Key]ValueSet{
|
||||
S3LocationConstraint: NewValueSet(
|
||||
NewStringValue("eu-west-1"),
|
||||
NewStringValue("us-west-1"),
|
||||
),
|
||||
}
|
||||
|
||||
testCases := []struct {
|
||||
f Function
|
||||
expectedResult map[Key]ValueSet
|
||||
}{
|
||||
{case1Function, case1Result},
|
||||
{case2Function, case2Result},
|
||||
{case3Function, case3Result},
|
||||
{case4Function, case4Result},
|
||||
{case5Function, case5Result},
|
||||
{case6Function, case6Result},
|
||||
{case7Function, case7Result},
|
||||
{case8Function, case8Result},
|
||||
{&stringNotEqualsFunc{}, nil},
|
||||
}
|
||||
|
||||
for i, testCase := range testCases {
|
||||
result := testCase.f.toMap()
|
||||
|
||||
if !reflect.DeepEqual(result, testCase.expectedResult) {
|
||||
t.Fatalf("case %v: result: expected: %v, got: %v\n", i+1, testCase.expectedResult, result)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewStringEqualsIgnoreCaseFunc(t *testing.T) {
|
||||
case1Function, err := newStringEqualsIgnoreCaseFunc(S3XAmzCopySource, NewValueSet(NewStringValue("mybucket/myobject")))
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error. %v\n", err)
|
||||
}
|
||||
|
||||
case2Function, err := newStringEqualsIgnoreCaseFunc(S3XAmzCopySource,
|
||||
NewValueSet(
|
||||
NewStringValue("mybucket/myobject"),
|
||||
NewStringValue("yourbucket/myobject"),
|
||||
),
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error. %v\n", err)
|
||||
}
|
||||
|
||||
case3Function, err := newStringEqualsIgnoreCaseFunc(S3XAmzServerSideEncryption, NewValueSet(NewStringValue("AES256")))
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error. %v\n", err)
|
||||
}
|
||||
|
||||
case4Function, err := newStringEqualsIgnoreCaseFunc(S3XAmzServerSideEncryption,
|
||||
NewValueSet(
|
||||
NewStringValue("AES256"),
|
||||
),
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error. %v\n", err)
|
||||
}
|
||||
|
||||
case5Function, err := newStringEqualsIgnoreCaseFunc(S3XAmzMetadataDirective, NewValueSet(NewStringValue("REPLACE")))
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error. %v\n", err)
|
||||
}
|
||||
|
||||
case6Function, err := newStringEqualsIgnoreCaseFunc(S3XAmzMetadataDirective,
|
||||
NewValueSet(
|
||||
NewStringValue("REPLACE"),
|
||||
NewStringValue("COPY"),
|
||||
),
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error. %v\n", err)
|
||||
}
|
||||
|
||||
case7Function, err := newStringEqualsIgnoreCaseFunc(S3LocationConstraint, NewValueSet(NewStringValue("eu-west-1")))
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error. %v\n", err)
|
||||
}
|
||||
|
||||
case8Function, err := newStringEqualsIgnoreCaseFunc(S3LocationConstraint,
|
||||
NewValueSet(
|
||||
NewStringValue("eu-west-1"),
|
||||
NewStringValue("us-west-1"),
|
||||
),
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error. %v\n", err)
|
||||
}
|
||||
|
||||
testCases := []struct {
|
||||
key Key
|
||||
values ValueSet
|
||||
expectedResult Function
|
||||
expectErr bool
|
||||
}{
|
||||
{S3XAmzCopySource, NewValueSet(NewStringValue("mybucket/myobject")), case1Function, false},
|
||||
{S3XAmzCopySource,
|
||||
NewValueSet(
|
||||
NewStringValue("mybucket/myobject"),
|
||||
NewStringValue("yourbucket/myobject"),
|
||||
), case2Function, false},
|
||||
|
||||
{S3XAmzServerSideEncryption, NewValueSet(NewStringValue("AES256")), case3Function, false},
|
||||
{S3XAmzServerSideEncryption,
|
||||
NewValueSet(
|
||||
NewStringValue("AES256"),
|
||||
), case4Function, false},
|
||||
|
||||
{S3XAmzMetadataDirective, NewValueSet(NewStringValue("REPLACE")), case5Function, false},
|
||||
{S3XAmzMetadataDirective,
|
||||
NewValueSet(
|
||||
NewStringValue("REPLACE"),
|
||||
NewStringValue("COPY"),
|
||||
), case6Function, false},
|
||||
|
||||
{S3LocationConstraint, NewValueSet(NewStringValue("eu-west-1")), case7Function, false},
|
||||
{S3LocationConstraint,
|
||||
NewValueSet(
|
||||
NewStringValue("eu-west-1"),
|
||||
NewStringValue("us-west-1"),
|
||||
), case8Function, false},
|
||||
|
||||
// Unsupported value error.
|
||||
{S3XAmzCopySource, NewValueSet(NewStringValue("mybucket/myobject"), NewIntValue(7)), nil, true},
|
||||
{S3XAmzServerSideEncryption, NewValueSet(NewStringValue("AES256"), NewIntValue(7)), nil, true},
|
||||
{S3XAmzMetadataDirective, NewValueSet(NewStringValue("REPLACE"), NewIntValue(7)), nil, true},
|
||||
{S3LocationConstraint, NewValueSet(NewStringValue("eu-west-1"), NewIntValue(7)), nil, true},
|
||||
|
||||
// Invalid value error.
|
||||
{S3XAmzCopySource, NewValueSet(NewStringValue("mybucket")), nil, true},
|
||||
{S3XAmzServerSideEncryption, NewValueSet(NewStringValue("SSE-C")), nil, true},
|
||||
{S3XAmzMetadataDirective, NewValueSet(NewStringValue("DUPLICATE")), nil, true},
|
||||
}
|
||||
|
||||
for i, testCase := range testCases {
|
||||
result, err := newStringEqualsIgnoreCaseFunc(testCase.key, testCase.values)
|
||||
expectErr := (err != nil)
|
||||
|
||||
if expectErr != testCase.expectErr {
|
||||
t.Fatalf("case %v: error: expected: %v, got: %v\n", i+1, testCase.expectErr, expectErr)
|
||||
}
|
||||
|
||||
if !testCase.expectErr {
|
||||
if !reflect.DeepEqual(result, testCase.expectedResult) {
|
||||
t.Fatalf("case %v: result: expected: %v, got: %v\n", i+1, testCase.expectedResult, result)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewStringNotEqualsIgnoreCaseFunc(t *testing.T) {
|
||||
case1Function, err := newStringNotEqualsIgnoreCaseFunc(S3XAmzCopySource, NewValueSet(NewStringValue("mybucket/myobject")))
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error. %v\n", err)
|
||||
}
|
||||
|
||||
case2Function, err := newStringNotEqualsIgnoreCaseFunc(S3XAmzCopySource,
|
||||
NewValueSet(
|
||||
NewStringValue("mybucket/myobject"),
|
||||
NewStringValue("yourbucket/myobject"),
|
||||
),
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error. %v\n", err)
|
||||
}
|
||||
|
||||
case3Function, err := newStringNotEqualsIgnoreCaseFunc(S3XAmzServerSideEncryption, NewValueSet(NewStringValue("AES256")))
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error. %v\n", err)
|
||||
}
|
||||
|
||||
case4Function, err := newStringNotEqualsIgnoreCaseFunc(S3XAmzServerSideEncryption,
|
||||
NewValueSet(
|
||||
NewStringValue("AES256"),
|
||||
),
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error. %v\n", err)
|
||||
}
|
||||
|
||||
case5Function, err := newStringNotEqualsIgnoreCaseFunc(S3XAmzMetadataDirective, NewValueSet(NewStringValue("REPLACE")))
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error. %v\n", err)
|
||||
}
|
||||
|
||||
case6Function, err := newStringNotEqualsIgnoreCaseFunc(S3XAmzMetadataDirective,
|
||||
NewValueSet(
|
||||
NewStringValue("REPLACE"),
|
||||
NewStringValue("COPY"),
|
||||
),
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error. %v\n", err)
|
||||
}
|
||||
|
||||
case7Function, err := newStringNotEqualsIgnoreCaseFunc(S3LocationConstraint, NewValueSet(NewStringValue("eu-west-1")))
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error. %v\n", err)
|
||||
}
|
||||
|
||||
case8Function, err := newStringNotEqualsIgnoreCaseFunc(S3LocationConstraint,
|
||||
NewValueSet(
|
||||
NewStringValue("eu-west-1"),
|
||||
NewStringValue("us-west-1"),
|
||||
),
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error. %v\n", err)
|
||||
}
|
||||
|
||||
testCases := []struct {
|
||||
key Key
|
||||
values ValueSet
|
||||
expectedResult Function
|
||||
expectErr bool
|
||||
}{
|
||||
{S3XAmzCopySource, NewValueSet(NewStringValue("mybucket/myobject")), case1Function, false},
|
||||
{S3XAmzCopySource,
|
||||
NewValueSet(
|
||||
NewStringValue("mybucket/myobject"),
|
||||
NewStringValue("yourbucket/myobject"),
|
||||
), case2Function, false},
|
||||
|
||||
{S3XAmzServerSideEncryption, NewValueSet(NewStringValue("AES256")), case3Function, false},
|
||||
{S3XAmzServerSideEncryption,
|
||||
NewValueSet(
|
||||
NewStringValue("AES256"),
|
||||
), case4Function, false},
|
||||
|
||||
{S3XAmzMetadataDirective, NewValueSet(NewStringValue("REPLACE")), case5Function, false},
|
||||
{S3XAmzMetadataDirective,
|
||||
NewValueSet(
|
||||
NewStringValue("REPLACE"),
|
||||
NewStringValue("COPY"),
|
||||
), case6Function, false},
|
||||
|
||||
{S3LocationConstraint, NewValueSet(NewStringValue("eu-west-1")), case7Function, false},
|
||||
{S3LocationConstraint,
|
||||
NewValueSet(
|
||||
NewStringValue("eu-west-1"),
|
||||
NewStringValue("us-west-1"),
|
||||
), case8Function, false},
|
||||
|
||||
// Unsupported value error.
|
||||
{S3XAmzCopySource, NewValueSet(NewStringValue("mybucket/myobject"), NewIntValue(7)), nil, true},
|
||||
{S3XAmzServerSideEncryption, NewValueSet(NewStringValue("AES256"), NewIntValue(7)), nil, true},
|
||||
{S3XAmzMetadataDirective, NewValueSet(NewStringValue("REPLACE"), NewIntValue(7)), nil, true},
|
||||
{S3LocationConstraint, NewValueSet(NewStringValue("eu-west-1"), NewIntValue(7)), nil, true},
|
||||
|
||||
// Invalid value error.
|
||||
{S3XAmzCopySource, NewValueSet(NewStringValue("mybucket")), nil, true},
|
||||
{S3XAmzServerSideEncryption, NewValueSet(NewStringValue("SSE-C")), nil, true},
|
||||
{S3XAmzMetadataDirective, NewValueSet(NewStringValue("DUPLICATE")), nil, true},
|
||||
}
|
||||
|
||||
for i, testCase := range testCases {
|
||||
result, err := newStringNotEqualsIgnoreCaseFunc(testCase.key, testCase.values)
|
||||
expectErr := (err != nil)
|
||||
|
||||
if expectErr != testCase.expectErr {
|
||||
t.Fatalf("case %v: error: expected: %v, got: %v\n", i+1, testCase.expectErr, expectErr)
|
||||
}
|
||||
|
||||
if !testCase.expectErr {
|
||||
if !reflect.DeepEqual(result, testCase.expectedResult) {
|
||||
t.Fatalf("case %v: result: expected: %v, got: %v\n", i+1, testCase.expectedResult, result)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
174
pkg/bucket/policy/condition/stringlikefunc.go
Normal file
174
pkg/bucket/policy/condition/stringlikefunc.go
Normal file
@@ -0,0 +1,174 @@
|
||||
/*
|
||||
* MinIO Cloud Storage, (C) 2018 MinIO, Inc.
|
||||
*
|
||||
* 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 condition
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"sort"
|
||||
|
||||
"github.com/minio/minio-go/v6/pkg/s3utils"
|
||||
"github.com/minio/minio-go/v6/pkg/set"
|
||||
"github.com/minio/minio/pkg/wildcard"
|
||||
)
|
||||
|
||||
func toStringLikeFuncString(n name, key Key, values set.StringSet) string {
|
||||
valueStrings := values.ToSlice()
|
||||
sort.Strings(valueStrings)
|
||||
|
||||
return fmt.Sprintf("%v:%v:%v", n, key, valueStrings)
|
||||
}
|
||||
|
||||
// stringLikeFunc - String like function. It checks whether value by Key in given
|
||||
// values map is widcard matching in condition values.
|
||||
// For example,
|
||||
// - if values = ["mybucket/foo*"], at evaluate() it returns whether string
|
||||
// in value map for Key is wildcard matching in values.
|
||||
type stringLikeFunc struct {
|
||||
k Key
|
||||
values set.StringSet
|
||||
}
|
||||
|
||||
// evaluate() - evaluates to check whether value by Key in given values is wildcard
|
||||
// matching in condition values.
|
||||
func (f stringLikeFunc) evaluate(values map[string][]string) bool {
|
||||
requestValue, ok := values[http.CanonicalHeaderKey(f.k.Name())]
|
||||
if !ok {
|
||||
requestValue = values[f.k.Name()]
|
||||
}
|
||||
|
||||
fvalues := f.values.ApplyFunc(substFuncFromValues(values))
|
||||
|
||||
for _, v := range requestValue {
|
||||
if !fvalues.FuncMatch(wildcard.Match, v).IsEmpty() {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// key() - returns condition key which is used by this condition function.
|
||||
func (f stringLikeFunc) key() Key {
|
||||
return f.k
|
||||
}
|
||||
|
||||
// name() - returns "StringLike" function name.
|
||||
func (f stringLikeFunc) name() name {
|
||||
return stringLike
|
||||
}
|
||||
|
||||
func (f stringLikeFunc) String() string {
|
||||
return toStringLikeFuncString(stringLike, f.k, f.values)
|
||||
}
|
||||
|
||||
// toMap - returns map representation of this function.
|
||||
func (f stringLikeFunc) toMap() map[Key]ValueSet {
|
||||
if !f.k.IsValid() {
|
||||
return nil
|
||||
}
|
||||
|
||||
values := NewValueSet()
|
||||
for _, value := range f.values.ToSlice() {
|
||||
values.Add(NewStringValue(value))
|
||||
}
|
||||
|
||||
return map[Key]ValueSet{
|
||||
f.k: values,
|
||||
}
|
||||
}
|
||||
|
||||
// stringNotLikeFunc - String not like function. It checks whether value by Key in given
|
||||
// values map is NOT widcard matching in condition values.
|
||||
// For example,
|
||||
// - if values = ["mybucket/foo*"], at evaluate() it returns whether string
|
||||
// in value map for Key is NOT wildcard matching in values.
|
||||
type stringNotLikeFunc struct {
|
||||
stringLikeFunc
|
||||
}
|
||||
|
||||
// evaluate() - evaluates to check whether value by Key in given values is NOT wildcard
|
||||
// matching in condition values.
|
||||
func (f stringNotLikeFunc) evaluate(values map[string][]string) bool {
|
||||
return !f.stringLikeFunc.evaluate(values)
|
||||
}
|
||||
|
||||
// name() - returns "StringNotLike" function name.
|
||||
func (f stringNotLikeFunc) name() name {
|
||||
return stringNotLike
|
||||
}
|
||||
|
||||
func (f stringNotLikeFunc) String() string {
|
||||
return toStringLikeFuncString(stringNotLike, f.stringLikeFunc.k, f.stringLikeFunc.values)
|
||||
}
|
||||
|
||||
func validateStringLikeValues(n name, key Key, values set.StringSet) error {
|
||||
for _, s := range values.ToSlice() {
|
||||
switch key {
|
||||
case S3XAmzCopySource:
|
||||
bucket, object := path2BucketAndObject(s)
|
||||
if object == "" {
|
||||
return fmt.Errorf("invalid value '%v' for '%v' for %v condition", s, S3XAmzCopySource, n)
|
||||
}
|
||||
if err := s3utils.CheckValidBucketName(bucket); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// newStringLikeFunc - returns new StringLike function.
|
||||
func newStringLikeFunc(key Key, values ValueSet) (Function, error) {
|
||||
valueStrings, err := valuesToStringSlice(stringLike, values)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return NewStringLikeFunc(key, valueStrings...)
|
||||
}
|
||||
|
||||
// NewStringLikeFunc - returns new StringLike function.
|
||||
func NewStringLikeFunc(key Key, values ...string) (Function, error) {
|
||||
sset := set.CreateStringSet(values...)
|
||||
if err := validateStringLikeValues(stringLike, key, sset); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &stringLikeFunc{key, sset}, nil
|
||||
}
|
||||
|
||||
// newStringNotLikeFunc - returns new StringNotLike function.
|
||||
func newStringNotLikeFunc(key Key, values ValueSet) (Function, error) {
|
||||
valueStrings, err := valuesToStringSlice(stringNotLike, values)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return NewStringNotLikeFunc(key, valueStrings...)
|
||||
}
|
||||
|
||||
// NewStringNotLikeFunc - returns new StringNotLike function.
|
||||
func NewStringNotLikeFunc(key Key, values ...string) (Function, error) {
|
||||
sset := set.CreateStringSet(values...)
|
||||
if err := validateStringLikeValues(stringNotLike, key, sset); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &stringNotLikeFunc{stringLikeFunc{key, sset}}, nil
|
||||
}
|
||||
798
pkg/bucket/policy/condition/stringlikefunc_test.go
Normal file
798
pkg/bucket/policy/condition/stringlikefunc_test.go
Normal file
@@ -0,0 +1,798 @@
|
||||
/*
|
||||
* MinIO Cloud Storage, (C) 2018 MinIO, Inc.
|
||||
*
|
||||
* 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 condition
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestStringLikeFuncEvaluate(t *testing.T) {
|
||||
case1Function, err := newStringLikeFunc(S3XAmzCopySource, NewValueSet(NewStringValue("mybucket/myobject*")))
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error. %v\n", err)
|
||||
}
|
||||
|
||||
case2Function, err := newStringLikeFunc(S3XAmzCopySource, NewValueSet(NewStringValue("mybucket/myobject")))
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error. %v\n", err)
|
||||
}
|
||||
|
||||
case3Function, err := newStringLikeFunc(S3XAmzServerSideEncryption, NewValueSet(NewStringValue("AES*")))
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error. %v\n", err)
|
||||
}
|
||||
|
||||
case4Function, err := newStringLikeFunc(S3XAmzServerSideEncryption, NewValueSet(NewStringValue("AES256")))
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error. %v\n", err)
|
||||
}
|
||||
|
||||
case5Function, err := newStringLikeFunc(S3XAmzMetadataDirective, NewValueSet(NewStringValue("REPL*")))
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error. %v\n", err)
|
||||
}
|
||||
|
||||
case6Function, err := newStringLikeFunc(S3XAmzMetadataDirective, NewValueSet(NewStringValue("REPLACE")))
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error. %v\n", err)
|
||||
}
|
||||
|
||||
case7Function, err := newStringLikeFunc(S3LocationConstraint, NewValueSet(NewStringValue("eu-west-*")))
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error. %v\n", err)
|
||||
}
|
||||
|
||||
case8Function, err := newStringLikeFunc(S3LocationConstraint, NewValueSet(NewStringValue("eu-west-1")))
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error. %v\n", err)
|
||||
}
|
||||
|
||||
testCases := []struct {
|
||||
function Function
|
||||
values map[string][]string
|
||||
expectedResult bool
|
||||
}{
|
||||
{case1Function, map[string][]string{"x-amz-copy-source": {"mybucket/myobject"}}, true},
|
||||
{case1Function, map[string][]string{"x-amz-copy-source": {"mybucket/myobject.png"}}, true},
|
||||
{case1Function, map[string][]string{"x-amz-copy-source": {"yourbucket/myobject"}}, false},
|
||||
{case1Function, map[string][]string{}, false},
|
||||
{case1Function, map[string][]string{"delimiter": {"/"}}, false},
|
||||
|
||||
{case2Function, map[string][]string{"x-amz-copy-source": {"mybucket/myobject"}}, true},
|
||||
{case2Function, map[string][]string{"x-amz-copy-source": {"mybucket/myobject.png"}}, false},
|
||||
{case2Function, map[string][]string{"x-amz-copy-source": {"yourbucket/myobject"}}, false},
|
||||
{case2Function, map[string][]string{}, false},
|
||||
{case2Function, map[string][]string{"delimiter": {"/"}}, false},
|
||||
|
||||
{case3Function, map[string][]string{"x-amz-server-side-encryption": {"AES256"}}, true},
|
||||
{case3Function, map[string][]string{"x-amz-server-side-encryption": {"AES512"}}, true},
|
||||
{case3Function, map[string][]string{}, false},
|
||||
{case3Function, map[string][]string{"delimiter": {"/"}}, false},
|
||||
|
||||
{case4Function, map[string][]string{"x-amz-server-side-encryption": {"AES256"}}, true},
|
||||
{case4Function, map[string][]string{"x-amz-server-side-encryption": {"AES512"}}, false},
|
||||
{case4Function, map[string][]string{}, false},
|
||||
{case4Function, map[string][]string{"delimiter": {"/"}}, false},
|
||||
|
||||
{case5Function, map[string][]string{"x-amz-metadata-directive": {"REPLACE"}}, true},
|
||||
{case5Function, map[string][]string{"x-amz-metadata-directive": {"REPLACE/COPY"}}, true},
|
||||
{case5Function, map[string][]string{"x-amz-metadata-directive": {"COPY"}}, false},
|
||||
{case5Function, map[string][]string{}, false},
|
||||
{case5Function, map[string][]string{"delimiter": {"/"}}, false},
|
||||
|
||||
{case6Function, map[string][]string{"x-amz-metadata-directive": {"REPLACE"}}, true},
|
||||
{case6Function, map[string][]string{"x-amz-metadata-directive": {"REPLACE/COPY"}}, false},
|
||||
{case6Function, map[string][]string{"x-amz-metadata-directive": {"COPY"}}, false},
|
||||
{case6Function, map[string][]string{}, false},
|
||||
{case6Function, map[string][]string{"delimiter": {"/"}}, false},
|
||||
|
||||
{case7Function, map[string][]string{"LocationConstraint": {"eu-west-1"}}, true},
|
||||
{case7Function, map[string][]string{"LocationConstraint": {"eu-west-2"}}, true},
|
||||
{case7Function, map[string][]string{"LocationConstraint": {"us-east-1"}}, false},
|
||||
{case7Function, map[string][]string{}, false},
|
||||
{case7Function, map[string][]string{"delimiter": {"/"}}, false},
|
||||
|
||||
{case8Function, map[string][]string{"LocationConstraint": {"eu-west-1"}}, true},
|
||||
{case8Function, map[string][]string{"LocationConstraint": {"eu-west-2"}}, false},
|
||||
{case8Function, map[string][]string{"LocationConstraint": {"us-east-1"}}, false},
|
||||
{case8Function, map[string][]string{}, false},
|
||||
{case8Function, map[string][]string{"delimiter": {"/"}}, false},
|
||||
}
|
||||
|
||||
for i, testCase := range testCases {
|
||||
result := testCase.function.evaluate(testCase.values)
|
||||
|
||||
if result != testCase.expectedResult {
|
||||
t.Fatalf("case %v: expected: %v, got: %v\n", i+1, testCase.expectedResult, result)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestStringLikeFuncKey(t *testing.T) {
|
||||
case1Function, err := newStringLikeFunc(S3XAmzCopySource, NewValueSet(NewStringValue("mybucket/myobject")))
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error. %v\n", err)
|
||||
}
|
||||
|
||||
case2Function, err := newStringLikeFunc(S3XAmzServerSideEncryption, NewValueSet(NewStringValue("AES256")))
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error. %v\n", err)
|
||||
}
|
||||
|
||||
case3Function, err := newStringLikeFunc(S3XAmzMetadataDirective, NewValueSet(NewStringValue("REPLACE")))
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error. %v\n", err)
|
||||
}
|
||||
|
||||
case4Function, err := newStringLikeFunc(S3LocationConstraint, NewValueSet(NewStringValue("eu-west-1")))
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error. %v\n", err)
|
||||
}
|
||||
|
||||
testCases := []struct {
|
||||
function Function
|
||||
expectedResult Key
|
||||
}{
|
||||
{case1Function, S3XAmzCopySource},
|
||||
{case2Function, S3XAmzServerSideEncryption},
|
||||
{case3Function, S3XAmzMetadataDirective},
|
||||
{case4Function, S3LocationConstraint},
|
||||
}
|
||||
|
||||
for i, testCase := range testCases {
|
||||
result := testCase.function.key()
|
||||
|
||||
if result != testCase.expectedResult {
|
||||
t.Fatalf("case %v: expected: %v, got: %v\n", i+1, testCase.expectedResult, result)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestStringLikeFuncToMap(t *testing.T) {
|
||||
case1Function, err := newStringLikeFunc(S3XAmzCopySource, NewValueSet(NewStringValue("mybucket/*")))
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error. %v\n", err)
|
||||
}
|
||||
|
||||
case1Result := map[Key]ValueSet{
|
||||
S3XAmzCopySource: NewValueSet(NewStringValue("mybucket/*")),
|
||||
}
|
||||
|
||||
case2Function, err := newStringLikeFunc(S3XAmzCopySource,
|
||||
NewValueSet(
|
||||
NewStringValue("mybucket/*"),
|
||||
NewStringValue("yourbucket/myobject*"),
|
||||
),
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error. %v\n", err)
|
||||
}
|
||||
|
||||
case2Result := map[Key]ValueSet{
|
||||
S3XAmzCopySource: NewValueSet(
|
||||
NewStringValue("mybucket/*"),
|
||||
NewStringValue("yourbucket/myobject*"),
|
||||
),
|
||||
}
|
||||
|
||||
case3Function, err := newStringLikeFunc(S3XAmzServerSideEncryption, NewValueSet(NewStringValue("AES*")))
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error. %v\n", err)
|
||||
}
|
||||
|
||||
case3Result := map[Key]ValueSet{
|
||||
S3XAmzServerSideEncryption: NewValueSet(NewStringValue("AES*")),
|
||||
}
|
||||
|
||||
case4Function, err := newStringLikeFunc(S3XAmzServerSideEncryption,
|
||||
NewValueSet(
|
||||
NewStringValue("AES*"),
|
||||
),
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error. %v\n", err)
|
||||
}
|
||||
|
||||
case4Result := map[Key]ValueSet{
|
||||
S3XAmzServerSideEncryption: NewValueSet(
|
||||
NewStringValue("AES*"),
|
||||
),
|
||||
}
|
||||
|
||||
case5Function, err := newStringLikeFunc(S3XAmzMetadataDirective, NewValueSet(NewStringValue("REPL*")))
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error. %v\n", err)
|
||||
}
|
||||
|
||||
case5Result := map[Key]ValueSet{
|
||||
S3XAmzMetadataDirective: NewValueSet(NewStringValue("REPL*")),
|
||||
}
|
||||
|
||||
case6Function, err := newStringLikeFunc(S3XAmzMetadataDirective,
|
||||
NewValueSet(
|
||||
NewStringValue("REPL*"),
|
||||
NewStringValue("COPY*"),
|
||||
),
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error. %v\n", err)
|
||||
}
|
||||
|
||||
case6Result := map[Key]ValueSet{
|
||||
S3XAmzMetadataDirective: NewValueSet(
|
||||
NewStringValue("REPL*"),
|
||||
NewStringValue("COPY*"),
|
||||
),
|
||||
}
|
||||
|
||||
case7Function, err := newStringLikeFunc(S3LocationConstraint, NewValueSet(NewStringValue("eu-west-*")))
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error. %v\n", err)
|
||||
}
|
||||
|
||||
case7Result := map[Key]ValueSet{
|
||||
S3LocationConstraint: NewValueSet(NewStringValue("eu-west-*")),
|
||||
}
|
||||
|
||||
case8Function, err := newStringLikeFunc(S3LocationConstraint,
|
||||
NewValueSet(
|
||||
NewStringValue("eu-west-*"),
|
||||
NewStringValue("us-west-*"),
|
||||
),
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error. %v\n", err)
|
||||
}
|
||||
|
||||
case8Result := map[Key]ValueSet{
|
||||
S3LocationConstraint: NewValueSet(
|
||||
NewStringValue("eu-west-*"),
|
||||
NewStringValue("us-west-*"),
|
||||
),
|
||||
}
|
||||
|
||||
testCases := []struct {
|
||||
f Function
|
||||
expectedResult map[Key]ValueSet
|
||||
}{
|
||||
{case1Function, case1Result},
|
||||
{case2Function, case2Result},
|
||||
{case3Function, case3Result},
|
||||
{case4Function, case4Result},
|
||||
{case5Function, case5Result},
|
||||
{case6Function, case6Result},
|
||||
{case7Function, case7Result},
|
||||
{case8Function, case8Result},
|
||||
{&stringLikeFunc{}, nil},
|
||||
}
|
||||
|
||||
for i, testCase := range testCases {
|
||||
result := testCase.f.toMap()
|
||||
|
||||
if !reflect.DeepEqual(result, testCase.expectedResult) {
|
||||
t.Fatalf("case %v: result: expected: %v, got: %v\n", i+1, testCase.expectedResult, result)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestStringNotLikeFuncEvaluate(t *testing.T) {
|
||||
case1Function, err := newStringNotLikeFunc(S3XAmzCopySource, NewValueSet(NewStringValue("mybucket/myobject*")))
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error. %v\n", err)
|
||||
}
|
||||
|
||||
case2Function, err := newStringNotLikeFunc(S3XAmzCopySource, NewValueSet(NewStringValue("mybucket/myobject")))
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error. %v\n", err)
|
||||
}
|
||||
|
||||
case3Function, err := newStringNotLikeFunc(S3XAmzServerSideEncryption, NewValueSet(NewStringValue("AES*")))
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error. %v\n", err)
|
||||
}
|
||||
|
||||
case4Function, err := newStringNotLikeFunc(S3XAmzServerSideEncryption, NewValueSet(NewStringValue("AES256")))
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error. %v\n", err)
|
||||
}
|
||||
|
||||
case5Function, err := newStringNotLikeFunc(S3XAmzMetadataDirective, NewValueSet(NewStringValue("REPL*")))
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error. %v\n", err)
|
||||
}
|
||||
|
||||
case6Function, err := newStringNotLikeFunc(S3XAmzMetadataDirective, NewValueSet(NewStringValue("REPLACE")))
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error. %v\n", err)
|
||||
}
|
||||
|
||||
case7Function, err := newStringNotLikeFunc(S3LocationConstraint, NewValueSet(NewStringValue("eu-west-*")))
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error. %v\n", err)
|
||||
}
|
||||
|
||||
case8Function, err := newStringNotLikeFunc(S3LocationConstraint, NewValueSet(NewStringValue("eu-west-1")))
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error. %v\n", err)
|
||||
}
|
||||
|
||||
testCases := []struct {
|
||||
function Function
|
||||
values map[string][]string
|
||||
expectedResult bool
|
||||
}{
|
||||
{case1Function, map[string][]string{"x-amz-copy-source": {"mybucket/myobject"}}, false},
|
||||
{case1Function, map[string][]string{"x-amz-copy-source": {"mybucket/myobject.png"}}, false},
|
||||
{case1Function, map[string][]string{"x-amz-copy-source": {"yourbucket/myobject"}}, true},
|
||||
{case1Function, map[string][]string{}, true},
|
||||
{case1Function, map[string][]string{"delimiter": {"/"}}, true},
|
||||
|
||||
{case2Function, map[string][]string{"x-amz-copy-source": {"mybucket/myobject"}}, false},
|
||||
{case2Function, map[string][]string{"x-amz-copy-source": {"mybucket/myobject.png"}}, true},
|
||||
{case2Function, map[string][]string{"x-amz-copy-source": {"yourbucket/myobject"}}, true},
|
||||
{case2Function, map[string][]string{}, true},
|
||||
{case2Function, map[string][]string{"delimiter": {"/"}}, true},
|
||||
|
||||
{case3Function, map[string][]string{"x-amz-server-side-encryption": {"AES256"}}, false},
|
||||
{case3Function, map[string][]string{"x-amz-server-side-encryption": {"AES512"}}, false},
|
||||
{case3Function, map[string][]string{}, true},
|
||||
{case3Function, map[string][]string{"delimiter": {"/"}}, true},
|
||||
|
||||
{case4Function, map[string][]string{"x-amz-server-side-encryption": {"AES256"}}, false},
|
||||
{case4Function, map[string][]string{"x-amz-server-side-encryption": {"AES512"}}, true},
|
||||
{case4Function, map[string][]string{}, true},
|
||||
{case4Function, map[string][]string{"delimiter": {"/"}}, true},
|
||||
|
||||
{case5Function, map[string][]string{"x-amz-metadata-directive": {"REPLACE"}}, false},
|
||||
{case5Function, map[string][]string{"x-amz-metadata-directive": {"REPLACE/COPY"}}, false},
|
||||
{case5Function, map[string][]string{"x-amz-metadata-directive": {"COPY"}}, true},
|
||||
{case5Function, map[string][]string{}, true},
|
||||
{case5Function, map[string][]string{"delimiter": {"/"}}, true},
|
||||
|
||||
{case6Function, map[string][]string{"x-amz-metadata-directive": {"REPLACE"}}, false},
|
||||
{case6Function, map[string][]string{"x-amz-metadata-directive": {"REPLACE/COPY"}}, true},
|
||||
{case6Function, map[string][]string{"x-amz-metadata-directive": {"COPY"}}, true},
|
||||
{case6Function, map[string][]string{}, true},
|
||||
{case6Function, map[string][]string{"delimiter": {"/"}}, true},
|
||||
|
||||
{case7Function, map[string][]string{"LocationConstraint": {"eu-west-1"}}, false},
|
||||
{case7Function, map[string][]string{"LocationConstraint": {"eu-west-2"}}, false},
|
||||
{case7Function, map[string][]string{"LocationConstraint": {"us-east-1"}}, true},
|
||||
{case7Function, map[string][]string{}, true},
|
||||
{case7Function, map[string][]string{"delimiter": {"/"}}, true},
|
||||
|
||||
{case8Function, map[string][]string{"LocationConstraint": {"eu-west-1"}}, false},
|
||||
{case8Function, map[string][]string{"LocationConstraint": {"eu-west-2"}}, true},
|
||||
{case8Function, map[string][]string{"LocationConstraint": {"us-east-1"}}, true},
|
||||
{case8Function, map[string][]string{}, true},
|
||||
{case8Function, map[string][]string{"delimiter": {"/"}}, true},
|
||||
}
|
||||
|
||||
for i, testCase := range testCases {
|
||||
result := testCase.function.evaluate(testCase.values)
|
||||
|
||||
if result != testCase.expectedResult {
|
||||
t.Fatalf("case %v: expected: %v, got: %v\n", i+1, testCase.expectedResult, result)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestStringNotLikeFuncKey(t *testing.T) {
|
||||
case1Function, err := newStringNotLikeFunc(S3XAmzCopySource, NewValueSet(NewStringValue("mybucket/myobject")))
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error. %v\n", err)
|
||||
}
|
||||
|
||||
case2Function, err := newStringNotLikeFunc(S3XAmzServerSideEncryption, NewValueSet(NewStringValue("AES256")))
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error. %v\n", err)
|
||||
}
|
||||
|
||||
case3Function, err := newStringNotLikeFunc(S3XAmzMetadataDirective, NewValueSet(NewStringValue("REPLACE")))
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error. %v\n", err)
|
||||
}
|
||||
|
||||
case4Function, err := newStringNotLikeFunc(S3LocationConstraint, NewValueSet(NewStringValue("eu-west-1")))
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error. %v\n", err)
|
||||
}
|
||||
|
||||
testCases := []struct {
|
||||
function Function
|
||||
expectedResult Key
|
||||
}{
|
||||
{case1Function, S3XAmzCopySource},
|
||||
{case2Function, S3XAmzServerSideEncryption},
|
||||
{case3Function, S3XAmzMetadataDirective},
|
||||
{case4Function, S3LocationConstraint},
|
||||
}
|
||||
|
||||
for i, testCase := range testCases {
|
||||
result := testCase.function.key()
|
||||
|
||||
if result != testCase.expectedResult {
|
||||
t.Fatalf("case %v: expected: %v, got: %v\n", i+1, testCase.expectedResult, result)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestStringNotLikeFuncToMap(t *testing.T) {
|
||||
case1Function, err := newStringNotLikeFunc(S3XAmzCopySource, NewValueSet(NewStringValue("mybucket/*")))
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error. %v\n", err)
|
||||
}
|
||||
|
||||
case1Result := map[Key]ValueSet{
|
||||
S3XAmzCopySource: NewValueSet(NewStringValue("mybucket/*")),
|
||||
}
|
||||
|
||||
case2Function, err := newStringNotLikeFunc(S3XAmzCopySource,
|
||||
NewValueSet(
|
||||
NewStringValue("mybucket/*"),
|
||||
NewStringValue("yourbucket/myobject*"),
|
||||
),
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error. %v\n", err)
|
||||
}
|
||||
|
||||
case2Result := map[Key]ValueSet{
|
||||
S3XAmzCopySource: NewValueSet(
|
||||
NewStringValue("mybucket/*"),
|
||||
NewStringValue("yourbucket/myobject*"),
|
||||
),
|
||||
}
|
||||
|
||||
case3Function, err := newStringNotLikeFunc(S3XAmzServerSideEncryption, NewValueSet(NewStringValue("AES*")))
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error. %v\n", err)
|
||||
}
|
||||
|
||||
case3Result := map[Key]ValueSet{
|
||||
S3XAmzServerSideEncryption: NewValueSet(NewStringValue("AES*")),
|
||||
}
|
||||
|
||||
case4Function, err := newStringNotLikeFunc(S3XAmzServerSideEncryption,
|
||||
NewValueSet(
|
||||
NewStringValue("AES*"),
|
||||
),
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error. %v\n", err)
|
||||
}
|
||||
|
||||
case4Result := map[Key]ValueSet{
|
||||
S3XAmzServerSideEncryption: NewValueSet(
|
||||
NewStringValue("AES*"),
|
||||
),
|
||||
}
|
||||
|
||||
case5Function, err := newStringNotLikeFunc(S3XAmzMetadataDirective, NewValueSet(NewStringValue("REPL*")))
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error. %v\n", err)
|
||||
}
|
||||
|
||||
case5Result := map[Key]ValueSet{
|
||||
S3XAmzMetadataDirective: NewValueSet(NewStringValue("REPL*")),
|
||||
}
|
||||
|
||||
case6Function, err := newStringNotLikeFunc(S3XAmzMetadataDirective,
|
||||
NewValueSet(
|
||||
NewStringValue("REPL*"),
|
||||
NewStringValue("COPY*"),
|
||||
),
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error. %v\n", err)
|
||||
}
|
||||
|
||||
case6Result := map[Key]ValueSet{
|
||||
S3XAmzMetadataDirective: NewValueSet(
|
||||
NewStringValue("REPL*"),
|
||||
NewStringValue("COPY*"),
|
||||
),
|
||||
}
|
||||
|
||||
case7Function, err := newStringNotLikeFunc(S3LocationConstraint, NewValueSet(NewStringValue("eu-west-*")))
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error. %v\n", err)
|
||||
}
|
||||
|
||||
case7Result := map[Key]ValueSet{
|
||||
S3LocationConstraint: NewValueSet(NewStringValue("eu-west-*")),
|
||||
}
|
||||
|
||||
case8Function, err := newStringNotLikeFunc(S3LocationConstraint,
|
||||
NewValueSet(
|
||||
NewStringValue("eu-west-*"),
|
||||
NewStringValue("us-west-*"),
|
||||
),
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error. %v\n", err)
|
||||
}
|
||||
|
||||
case8Result := map[Key]ValueSet{
|
||||
S3LocationConstraint: NewValueSet(
|
||||
NewStringValue("eu-west-*"),
|
||||
NewStringValue("us-west-*"),
|
||||
),
|
||||
}
|
||||
|
||||
testCases := []struct {
|
||||
f Function
|
||||
expectedResult map[Key]ValueSet
|
||||
}{
|
||||
{case1Function, case1Result},
|
||||
{case2Function, case2Result},
|
||||
{case3Function, case3Result},
|
||||
{case4Function, case4Result},
|
||||
{case5Function, case5Result},
|
||||
{case6Function, case6Result},
|
||||
{case7Function, case7Result},
|
||||
{case8Function, case8Result},
|
||||
{&stringNotLikeFunc{}, nil},
|
||||
}
|
||||
|
||||
for i, testCase := range testCases {
|
||||
result := testCase.f.toMap()
|
||||
|
||||
if !reflect.DeepEqual(result, testCase.expectedResult) {
|
||||
t.Fatalf("case %v: result: expected: %v, got: %v\n", i+1, testCase.expectedResult, result)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewStringLikeFunc(t *testing.T) {
|
||||
case1Function, err := newStringLikeFunc(S3XAmzCopySource, NewValueSet(NewStringValue("mybucket/*")))
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error. %v\n", err)
|
||||
}
|
||||
|
||||
case2Function, err := newStringLikeFunc(S3XAmzCopySource,
|
||||
NewValueSet(
|
||||
NewStringValue("mybucket/*"),
|
||||
NewStringValue("yourbucket/myobject*"),
|
||||
),
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error. %v\n", err)
|
||||
}
|
||||
|
||||
case3Function, err := newStringLikeFunc(S3XAmzServerSideEncryption, NewValueSet(NewStringValue("AES*")))
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error. %v\n", err)
|
||||
}
|
||||
|
||||
case4Function, err := newStringLikeFunc(S3XAmzServerSideEncryption,
|
||||
NewValueSet(
|
||||
NewStringValue("AES*"),
|
||||
),
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error. %v\n", err)
|
||||
}
|
||||
|
||||
case5Function, err := newStringLikeFunc(S3XAmzMetadataDirective, NewValueSet(NewStringValue("REPL*")))
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error. %v\n", err)
|
||||
}
|
||||
|
||||
case6Function, err := newStringLikeFunc(S3XAmzMetadataDirective,
|
||||
NewValueSet(
|
||||
NewStringValue("REPL*"),
|
||||
NewStringValue("COPY*"),
|
||||
),
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error. %v\n", err)
|
||||
}
|
||||
|
||||
case7Function, err := newStringLikeFunc(S3LocationConstraint, NewValueSet(NewStringValue("eu-west-*")))
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error. %v\n", err)
|
||||
}
|
||||
|
||||
case8Function, err := newStringLikeFunc(S3LocationConstraint,
|
||||
NewValueSet(
|
||||
NewStringValue("eu-west-*"),
|
||||
NewStringValue("us-west-*"),
|
||||
),
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error. %v\n", err)
|
||||
}
|
||||
|
||||
testCases := []struct {
|
||||
key Key
|
||||
values ValueSet
|
||||
expectedResult Function
|
||||
expectErr bool
|
||||
}{
|
||||
{S3XAmzCopySource, NewValueSet(NewStringValue("mybucket/*")), case1Function, false},
|
||||
{S3XAmzCopySource,
|
||||
NewValueSet(
|
||||
NewStringValue("mybucket/*"),
|
||||
NewStringValue("yourbucket/myobject*"),
|
||||
), case2Function, false},
|
||||
|
||||
{S3XAmzServerSideEncryption, NewValueSet(NewStringValue("AES*")), case3Function, false},
|
||||
{S3XAmzServerSideEncryption,
|
||||
NewValueSet(
|
||||
NewStringValue("AES*"),
|
||||
), case4Function, false},
|
||||
|
||||
{S3XAmzMetadataDirective, NewValueSet(NewStringValue("REPL*")), case5Function, false},
|
||||
{S3XAmzMetadataDirective,
|
||||
NewValueSet(
|
||||
NewStringValue("REPL*"),
|
||||
NewStringValue("COPY*"),
|
||||
), case6Function, false},
|
||||
|
||||
{S3LocationConstraint, NewValueSet(NewStringValue("eu-west-*")), case7Function, false},
|
||||
{S3LocationConstraint,
|
||||
NewValueSet(
|
||||
NewStringValue("eu-west-*"),
|
||||
NewStringValue("us-west-*"),
|
||||
), case8Function, false},
|
||||
|
||||
// Unsupported value error.
|
||||
{S3XAmzCopySource, NewValueSet(NewStringValue("mybucket/myobject"), NewIntValue(7)), nil, true},
|
||||
{S3XAmzServerSideEncryption, NewValueSet(NewStringValue("AES256"), NewIntValue(7)), nil, true},
|
||||
{S3XAmzMetadataDirective, NewValueSet(NewStringValue("REPLACE"), NewIntValue(7)), nil, true},
|
||||
{S3LocationConstraint, NewValueSet(NewStringValue("eu-west-1"), NewIntValue(7)), nil, true},
|
||||
|
||||
// Invalid value error.
|
||||
{S3XAmzCopySource, NewValueSet(NewStringValue("mybucket")), nil, true},
|
||||
}
|
||||
|
||||
for i, testCase := range testCases {
|
||||
result, err := newStringLikeFunc(testCase.key, testCase.values)
|
||||
expectErr := (err != nil)
|
||||
|
||||
if expectErr != testCase.expectErr {
|
||||
t.Fatalf("case %v: error: expected: %v, got: %v\n", i+1, testCase.expectErr, expectErr)
|
||||
}
|
||||
|
||||
if !testCase.expectErr {
|
||||
if !reflect.DeepEqual(result, testCase.expectedResult) {
|
||||
t.Fatalf("case %v: result: expected: %v, got: %v\n", i+1, testCase.expectedResult, result)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewStringNotLikeFunc(t *testing.T) {
|
||||
case1Function, err := newStringNotLikeFunc(S3XAmzCopySource, NewValueSet(NewStringValue("mybucket/*")))
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error. %v\n", err)
|
||||
}
|
||||
|
||||
case2Function, err := newStringNotLikeFunc(S3XAmzCopySource,
|
||||
NewValueSet(
|
||||
NewStringValue("mybucket/*"),
|
||||
NewStringValue("yourbucket/myobject*"),
|
||||
),
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error. %v\n", err)
|
||||
}
|
||||
|
||||
case3Function, err := newStringNotLikeFunc(S3XAmzServerSideEncryption, NewValueSet(NewStringValue("AES*")))
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error. %v\n", err)
|
||||
}
|
||||
|
||||
case4Function, err := newStringNotLikeFunc(S3XAmzServerSideEncryption,
|
||||
NewValueSet(
|
||||
NewStringValue("AES*"),
|
||||
),
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error. %v\n", err)
|
||||
}
|
||||
|
||||
case5Function, err := newStringNotLikeFunc(S3XAmzMetadataDirective, NewValueSet(NewStringValue("REPL*")))
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error. %v\n", err)
|
||||
}
|
||||
|
||||
case6Function, err := newStringNotLikeFunc(S3XAmzMetadataDirective,
|
||||
NewValueSet(
|
||||
NewStringValue("REPL*"),
|
||||
NewStringValue("COPY*"),
|
||||
),
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error. %v\n", err)
|
||||
}
|
||||
|
||||
case7Function, err := newStringNotLikeFunc(S3LocationConstraint, NewValueSet(NewStringValue("eu-west-*")))
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error. %v\n", err)
|
||||
}
|
||||
|
||||
case8Function, err := newStringNotLikeFunc(S3LocationConstraint,
|
||||
NewValueSet(
|
||||
NewStringValue("eu-west-*"),
|
||||
NewStringValue("us-west-*"),
|
||||
),
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error. %v\n", err)
|
||||
}
|
||||
|
||||
testCases := []struct {
|
||||
key Key
|
||||
values ValueSet
|
||||
expectedResult Function
|
||||
expectErr bool
|
||||
}{
|
||||
{S3XAmzCopySource, NewValueSet(NewStringValue("mybucket/*")), case1Function, false},
|
||||
{S3XAmzCopySource,
|
||||
NewValueSet(
|
||||
NewStringValue("mybucket/*"),
|
||||
NewStringValue("yourbucket/myobject*"),
|
||||
), case2Function, false},
|
||||
|
||||
{S3XAmzServerSideEncryption, NewValueSet(NewStringValue("AES*")), case3Function, false},
|
||||
{S3XAmzServerSideEncryption,
|
||||
NewValueSet(
|
||||
NewStringValue("AES*"),
|
||||
), case4Function, false},
|
||||
|
||||
{S3XAmzMetadataDirective, NewValueSet(NewStringValue("REPL*")), case5Function, false},
|
||||
{S3XAmzMetadataDirective,
|
||||
NewValueSet(
|
||||
NewStringValue("REPL*"),
|
||||
NewStringValue("COPY*"),
|
||||
), case6Function, false},
|
||||
|
||||
{S3LocationConstraint, NewValueSet(NewStringValue("eu-west-*")), case7Function, false},
|
||||
{S3LocationConstraint,
|
||||
NewValueSet(
|
||||
NewStringValue("eu-west-*"),
|
||||
NewStringValue("us-west-*"),
|
||||
), case8Function, false},
|
||||
|
||||
// Unsupported value error.
|
||||
{S3XAmzCopySource, NewValueSet(NewStringValue("mybucket/myobject"), NewIntValue(7)), nil, true},
|
||||
{S3XAmzServerSideEncryption, NewValueSet(NewStringValue("AES256"), NewIntValue(7)), nil, true},
|
||||
{S3XAmzMetadataDirective, NewValueSet(NewStringValue("REPLACE"), NewIntValue(7)), nil, true},
|
||||
{S3LocationConstraint, NewValueSet(NewStringValue("eu-west-1"), NewIntValue(7)), nil, true},
|
||||
|
||||
// Invalid value error.
|
||||
{S3XAmzCopySource, NewValueSet(NewStringValue("mybucket")), nil, true},
|
||||
}
|
||||
|
||||
for i, testCase := range testCases {
|
||||
result, err := newStringNotLikeFunc(testCase.key, testCase.values)
|
||||
expectErr := (err != nil)
|
||||
|
||||
if expectErr != testCase.expectErr {
|
||||
t.Fatalf("case %v: error: expected: %v, got: %v\n", i+1, testCase.expectErr, expectErr)
|
||||
}
|
||||
|
||||
if !testCase.expectErr {
|
||||
if !reflect.DeepEqual(result, testCase.expectedResult) {
|
||||
t.Fatalf("case %v: result: expected: %v, got: %v\n", i+1, testCase.expectedResult, result)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
175
pkg/bucket/policy/condition/value.go
Normal file
175
pkg/bucket/policy/condition/value.go
Normal file
@@ -0,0 +1,175 @@
|
||||
/*
|
||||
* MinIO Cloud Storage, (C) 2018 MinIO, Inc.
|
||||
*
|
||||
* 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 condition
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Splits an incoming path into bucket and object components.
|
||||
func path2BucketAndObject(path string) (bucket, object string) {
|
||||
// Skip the first element if it is '/', split the rest.
|
||||
path = strings.TrimPrefix(path, "/")
|
||||
pathComponents := strings.SplitN(path, "/", 2)
|
||||
|
||||
// Save the bucket and object extracted from path.
|
||||
switch len(pathComponents) {
|
||||
case 1:
|
||||
bucket = pathComponents[0]
|
||||
case 2:
|
||||
bucket = pathComponents[0]
|
||||
object = pathComponents[1]
|
||||
}
|
||||
return bucket, object
|
||||
}
|
||||
|
||||
// Value - is enum type of string, int or bool.
|
||||
type Value struct {
|
||||
t reflect.Kind
|
||||
s string
|
||||
i int
|
||||
b bool
|
||||
}
|
||||
|
||||
// GetBool - gets stored bool value.
|
||||
func (v Value) GetBool() (bool, error) {
|
||||
var err error
|
||||
|
||||
if v.t != reflect.Bool {
|
||||
err = fmt.Errorf("not a bool Value")
|
||||
}
|
||||
|
||||
return v.b, err
|
||||
}
|
||||
|
||||
// GetInt - gets stored int value.
|
||||
func (v Value) GetInt() (int, error) {
|
||||
var err error
|
||||
|
||||
if v.t != reflect.Int {
|
||||
err = fmt.Errorf("not a int Value")
|
||||
}
|
||||
|
||||
return v.i, err
|
||||
}
|
||||
|
||||
// GetString - gets stored string value.
|
||||
func (v Value) GetString() (string, error) {
|
||||
var err error
|
||||
|
||||
if v.t != reflect.String {
|
||||
err = fmt.Errorf("not a string Value")
|
||||
}
|
||||
|
||||
return v.s, err
|
||||
}
|
||||
|
||||
// GetType - gets enum type.
|
||||
func (v Value) GetType() reflect.Kind {
|
||||
return v.t
|
||||
}
|
||||
|
||||
// MarshalJSON - encodes Value to JSON data.
|
||||
func (v Value) MarshalJSON() ([]byte, error) {
|
||||
switch v.t {
|
||||
case reflect.String:
|
||||
return json.Marshal(v.s)
|
||||
case reflect.Int:
|
||||
return json.Marshal(v.i)
|
||||
case reflect.Bool:
|
||||
return json.Marshal(v.b)
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("unknown value kind %v", v.t)
|
||||
}
|
||||
|
||||
// StoreBool - stores bool value.
|
||||
func (v *Value) StoreBool(b bool) {
|
||||
*v = Value{t: reflect.Bool, b: b}
|
||||
}
|
||||
|
||||
// StoreInt - stores int value.
|
||||
func (v *Value) StoreInt(i int) {
|
||||
*v = Value{t: reflect.Int, i: i}
|
||||
}
|
||||
|
||||
// StoreString - stores string value.
|
||||
func (v *Value) StoreString(s string) {
|
||||
*v = Value{t: reflect.String, s: s}
|
||||
}
|
||||
|
||||
// String - returns string representation of value.
|
||||
func (v Value) String() string {
|
||||
switch v.t {
|
||||
case reflect.String:
|
||||
return v.s
|
||||
case reflect.Int:
|
||||
return strconv.Itoa(v.i)
|
||||
case reflect.Bool:
|
||||
return strconv.FormatBool(v.b)
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
// UnmarshalJSON - decodes JSON data.
|
||||
func (v *Value) UnmarshalJSON(data []byte) error {
|
||||
var b bool
|
||||
if err := json.Unmarshal(data, &b); err == nil {
|
||||
v.StoreBool(b)
|
||||
return nil
|
||||
}
|
||||
|
||||
var i int
|
||||
if err := json.Unmarshal(data, &i); err == nil {
|
||||
v.StoreInt(i)
|
||||
return nil
|
||||
}
|
||||
|
||||
var s string
|
||||
if err := json.Unmarshal(data, &s); err == nil {
|
||||
v.StoreString(s)
|
||||
return nil
|
||||
}
|
||||
|
||||
return fmt.Errorf("unknown json data '%v'", data)
|
||||
}
|
||||
|
||||
// NewBoolValue - returns new bool value.
|
||||
func NewBoolValue(b bool) Value {
|
||||
value := &Value{}
|
||||
value.StoreBool(b)
|
||||
return *value
|
||||
}
|
||||
|
||||
// NewIntValue - returns new int value.
|
||||
func NewIntValue(i int) Value {
|
||||
value := &Value{}
|
||||
value.StoreInt(i)
|
||||
return *value
|
||||
}
|
||||
|
||||
// NewStringValue - returns new string value.
|
||||
func NewStringValue(s string) Value {
|
||||
value := &Value{}
|
||||
value.StoreString(s)
|
||||
return *value
|
||||
}
|
||||
260
pkg/bucket/policy/condition/value_test.go
Normal file
260
pkg/bucket/policy/condition/value_test.go
Normal file
@@ -0,0 +1,260 @@
|
||||
/*
|
||||
* MinIO Cloud Storage, (C) 2018 MinIO, Inc.
|
||||
*
|
||||
* 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 condition
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestValueGetBool(t *testing.T) {
|
||||
testCases := []struct {
|
||||
value Value
|
||||
expectedResult bool
|
||||
expectErr bool
|
||||
}{
|
||||
{NewBoolValue(true), true, false},
|
||||
{NewIntValue(7), false, true},
|
||||
{Value{}, false, true},
|
||||
}
|
||||
|
||||
for i, testCase := range testCases {
|
||||
result, err := testCase.value.GetBool()
|
||||
expectErr := (err != nil)
|
||||
|
||||
if expectErr != testCase.expectErr {
|
||||
t.Fatalf("case %v: error: expected: %v, got: %v\n", i+1, testCase.expectErr, expectErr)
|
||||
}
|
||||
|
||||
if !testCase.expectErr {
|
||||
if result != testCase.expectedResult {
|
||||
t.Fatalf("case %v: result: expected: %v, got: %v\n", i+1, testCase.expectedResult, result)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestValueGetInt(t *testing.T) {
|
||||
testCases := []struct {
|
||||
value Value
|
||||
expectedResult int
|
||||
expectErr bool
|
||||
}{
|
||||
{NewIntValue(7), 7, false},
|
||||
{NewBoolValue(true), 0, true},
|
||||
{Value{}, 0, true},
|
||||
}
|
||||
|
||||
for i, testCase := range testCases {
|
||||
result, err := testCase.value.GetInt()
|
||||
expectErr := (err != nil)
|
||||
|
||||
if expectErr != testCase.expectErr {
|
||||
t.Fatalf("case %v: error: expected: %v, got: %v\n", i+1, testCase.expectErr, expectErr)
|
||||
}
|
||||
|
||||
if !testCase.expectErr {
|
||||
if result != testCase.expectedResult {
|
||||
t.Fatalf("case %v: result: expected: %v, got: %v\n", i+1, testCase.expectedResult, result)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestValueGetString(t *testing.T) {
|
||||
testCases := []struct {
|
||||
value Value
|
||||
expectedResult string
|
||||
expectErr bool
|
||||
}{
|
||||
{NewStringValue("foo"), "foo", false},
|
||||
{NewBoolValue(true), "", true},
|
||||
{Value{}, "", true},
|
||||
}
|
||||
|
||||
for i, testCase := range testCases {
|
||||
result, err := testCase.value.GetString()
|
||||
expectErr := (err != nil)
|
||||
|
||||
if expectErr != testCase.expectErr {
|
||||
t.Fatalf("case %v: error: expected: %v, got: %v\n", i+1, testCase.expectErr, expectErr)
|
||||
}
|
||||
|
||||
if !testCase.expectErr {
|
||||
if result != testCase.expectedResult {
|
||||
t.Fatalf("case %v: result: expected: %v, got: %v\n", i+1, testCase.expectedResult, result)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestValueGetType(t *testing.T) {
|
||||
testCases := []struct {
|
||||
value Value
|
||||
expectedResult reflect.Kind
|
||||
}{
|
||||
{NewBoolValue(true), reflect.Bool},
|
||||
{NewIntValue(7), reflect.Int},
|
||||
{NewStringValue("foo"), reflect.String},
|
||||
{Value{}, reflect.Invalid},
|
||||
}
|
||||
|
||||
for i, testCase := range testCases {
|
||||
result := testCase.value.GetType()
|
||||
|
||||
if result != testCase.expectedResult {
|
||||
t.Fatalf("case %v: expected: %v, got: %v\n", i+1, testCase.expectedResult, result)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestValueMarshalJSON(t *testing.T) {
|
||||
testCases := []struct {
|
||||
value Value
|
||||
expectedResult []byte
|
||||
expectErr bool
|
||||
}{
|
||||
{NewBoolValue(true), []byte("true"), false},
|
||||
{NewIntValue(7), []byte("7"), false},
|
||||
{NewStringValue("foo"), []byte(`"foo"`), false},
|
||||
{Value{}, nil, true},
|
||||
}
|
||||
|
||||
for i, testCase := range testCases {
|
||||
result, err := json.Marshal(testCase.value)
|
||||
expectErr := (err != nil)
|
||||
|
||||
if expectErr != testCase.expectErr {
|
||||
t.Fatalf("case %v: error: expected: %v, got: %v\n", i+1, testCase.expectErr, expectErr)
|
||||
}
|
||||
|
||||
if !testCase.expectErr {
|
||||
if !reflect.DeepEqual(result, testCase.expectedResult) {
|
||||
t.Fatalf("case %v: result: expected: %v, got: %v\n", i+1, testCase.expectedResult, result)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestValueStoreBool(t *testing.T) {
|
||||
testCases := []struct {
|
||||
value bool
|
||||
expectedResult Value
|
||||
}{
|
||||
{false, NewBoolValue(false)},
|
||||
{true, NewBoolValue(true)},
|
||||
}
|
||||
|
||||
for i, testCase := range testCases {
|
||||
var result Value
|
||||
result.StoreBool(testCase.value)
|
||||
|
||||
if !reflect.DeepEqual(result, testCase.expectedResult) {
|
||||
t.Fatalf("case %v: expected: %v, got: %v\n", i+1, testCase.expectedResult, result)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestValueStoreInt(t *testing.T) {
|
||||
testCases := []struct {
|
||||
value int
|
||||
expectedResult Value
|
||||
}{
|
||||
{0, NewIntValue(0)},
|
||||
{7, NewIntValue(7)},
|
||||
}
|
||||
|
||||
for i, testCase := range testCases {
|
||||
var result Value
|
||||
result.StoreInt(testCase.value)
|
||||
|
||||
if !reflect.DeepEqual(result, testCase.expectedResult) {
|
||||
t.Fatalf("case %v: expected: %v, got: %v\n", i+1, testCase.expectedResult, result)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestValueStoreString(t *testing.T) {
|
||||
testCases := []struct {
|
||||
value string
|
||||
expectedResult Value
|
||||
}{
|
||||
{"", NewStringValue("")},
|
||||
{"foo", NewStringValue("foo")},
|
||||
}
|
||||
|
||||
for i, testCase := range testCases {
|
||||
var result Value
|
||||
result.StoreString(testCase.value)
|
||||
|
||||
if !reflect.DeepEqual(result, testCase.expectedResult) {
|
||||
t.Fatalf("case %v: expected: %v, got: %v\n", i+1, testCase.expectedResult, result)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestValueString(t *testing.T) {
|
||||
testCases := []struct {
|
||||
value Value
|
||||
expectedResult string
|
||||
}{
|
||||
{NewBoolValue(true), "true"},
|
||||
{NewIntValue(7), "7"},
|
||||
{NewStringValue("foo"), "foo"},
|
||||
{Value{}, ""},
|
||||
}
|
||||
|
||||
for i, testCase := range testCases {
|
||||
result := testCase.value.String()
|
||||
|
||||
if result != testCase.expectedResult {
|
||||
t.Fatalf("case %v: expected: %v, got: %v\n", i+1, testCase.expectedResult, result)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestValueUnmarshalJSON(t *testing.T) {
|
||||
testCases := []struct {
|
||||
data []byte
|
||||
expectedResult Value
|
||||
expectErr bool
|
||||
}{
|
||||
{[]byte("true"), NewBoolValue(true), false},
|
||||
{[]byte("7"), NewIntValue(7), false},
|
||||
{[]byte(`"foo"`), NewStringValue("foo"), false},
|
||||
{[]byte("True"), Value{}, true},
|
||||
{[]byte("7.1"), Value{}, true},
|
||||
{[]byte(`["foo"]`), Value{}, true},
|
||||
}
|
||||
|
||||
for i, testCase := range testCases {
|
||||
var result Value
|
||||
err := json.Unmarshal(testCase.data, &result)
|
||||
expectErr := (err != nil)
|
||||
|
||||
if expectErr != testCase.expectErr {
|
||||
t.Fatalf("case %v: error: expected: %v, got: %v\n", i+1, testCase.expectErr, expectErr)
|
||||
}
|
||||
|
||||
if !testCase.expectErr {
|
||||
if !reflect.DeepEqual(result, testCase.expectedResult) {
|
||||
t.Fatalf("case %v: result: expected: %v, got: %v\n", i+1, testCase.expectedResult, result)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
85
pkg/bucket/policy/condition/valueset.go
Normal file
85
pkg/bucket/policy/condition/valueset.go
Normal file
@@ -0,0 +1,85 @@
|
||||
/*
|
||||
* MinIO Cloud Storage, (C) 2018 MinIO, Inc.
|
||||
*
|
||||
* 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 condition
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// ValueSet - unique list of values.
|
||||
type ValueSet map[Value]struct{}
|
||||
|
||||
// Add - adds given value to value set.
|
||||
func (set ValueSet) Add(value Value) {
|
||||
set[value] = struct{}{}
|
||||
}
|
||||
|
||||
// MarshalJSON - encodes ValueSet to JSON data.
|
||||
func (set ValueSet) MarshalJSON() ([]byte, error) {
|
||||
var values []Value
|
||||
for k := range set {
|
||||
values = append(values, k)
|
||||
}
|
||||
|
||||
if len(values) == 0 {
|
||||
return nil, fmt.Errorf("invalid value set %v", set)
|
||||
}
|
||||
|
||||
return json.Marshal(values)
|
||||
}
|
||||
|
||||
// UnmarshalJSON - decodes JSON data.
|
||||
func (set *ValueSet) UnmarshalJSON(data []byte) error {
|
||||
var v Value
|
||||
if err := json.Unmarshal(data, &v); err == nil {
|
||||
*set = make(ValueSet)
|
||||
set.Add(v)
|
||||
return nil
|
||||
}
|
||||
|
||||
var values []Value
|
||||
if err := json.Unmarshal(data, &values); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(values) < 1 {
|
||||
return fmt.Errorf("invalid value")
|
||||
}
|
||||
|
||||
*set = make(ValueSet)
|
||||
for _, v = range values {
|
||||
if _, found := (*set)[v]; found {
|
||||
return fmt.Errorf("duplicate value found '%v'", v)
|
||||
}
|
||||
|
||||
set.Add(v)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// NewValueSet - returns new value set containing given values.
|
||||
func NewValueSet(values ...Value) ValueSet {
|
||||
set := make(ValueSet)
|
||||
|
||||
for _, value := range values {
|
||||
set.Add(value)
|
||||
}
|
||||
|
||||
return set
|
||||
}
|
||||
118
pkg/bucket/policy/condition/valueset_test.go
Normal file
118
pkg/bucket/policy/condition/valueset_test.go
Normal file
@@ -0,0 +1,118 @@
|
||||
/*
|
||||
* MinIO Cloud Storage, (C) 2018 MinIO, Inc.
|
||||
*
|
||||
* 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 condition
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestValueSetAdd(t *testing.T) {
|
||||
testCases := []struct {
|
||||
value Value
|
||||
expectedResult ValueSet
|
||||
}{
|
||||
{NewBoolValue(true), NewValueSet(NewBoolValue(true))},
|
||||
{NewIntValue(7), NewValueSet(NewIntValue(7))},
|
||||
{NewStringValue("foo"), NewValueSet(NewStringValue("foo"))},
|
||||
}
|
||||
|
||||
for i, testCase := range testCases {
|
||||
result := NewValueSet()
|
||||
result.Add(testCase.value)
|
||||
|
||||
if !reflect.DeepEqual(result, testCase.expectedResult) {
|
||||
t.Fatalf("case %v: expected: %v, got: %v\n", i+1, testCase.expectedResult, result)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestValueSetMarshalJSON(t *testing.T) {
|
||||
testCases := []struct {
|
||||
set ValueSet
|
||||
expectedResult string
|
||||
expectErr bool
|
||||
}{
|
||||
{NewValueSet(NewBoolValue(true)), `[true]`, false},
|
||||
{NewValueSet(NewIntValue(7)), `[7]`, false},
|
||||
{NewValueSet(NewStringValue("foo")), `["foo"]`, false},
|
||||
{NewValueSet(NewBoolValue(true)), `[true]`, false},
|
||||
{NewValueSet(NewStringValue("7")), `["7"]`, false},
|
||||
{NewValueSet(NewStringValue("foo")), `["foo"]`, false},
|
||||
{make(ValueSet), "", true},
|
||||
}
|
||||
|
||||
for i, testCase := range testCases {
|
||||
result, err := json.Marshal(testCase.set)
|
||||
expectErr := (err != nil)
|
||||
|
||||
if expectErr != testCase.expectErr {
|
||||
t.Fatalf("case %v: error: expected: %v, got: %v\n", i+1, testCase.expectErr, expectErr)
|
||||
}
|
||||
|
||||
if !testCase.expectErr {
|
||||
if string(result) != testCase.expectedResult {
|
||||
t.Fatalf("case %v: result: expected: %v, got: %v\n", i+1, testCase.expectedResult, string(result))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestValueSetUnmarshalJSON(t *testing.T) {
|
||||
set1 := NewValueSet(
|
||||
NewBoolValue(true),
|
||||
NewStringValue("false"),
|
||||
NewIntValue(7),
|
||||
NewStringValue("7"),
|
||||
NewStringValue("foo"),
|
||||
NewStringValue("192.168.1.100/24"),
|
||||
)
|
||||
|
||||
testCases := []struct {
|
||||
data []byte
|
||||
expectedResult ValueSet
|
||||
expectErr bool
|
||||
}{
|
||||
{[]byte(`true`), NewValueSet(NewBoolValue(true)), false},
|
||||
{[]byte(`7`), NewValueSet(NewIntValue(7)), false},
|
||||
{[]byte(`"foo"`), NewValueSet(NewStringValue("foo")), false},
|
||||
{[]byte(`[true]`), NewValueSet(NewBoolValue(true)), false},
|
||||
{[]byte(`[7]`), NewValueSet(NewIntValue(7)), false},
|
||||
{[]byte(`["foo"]`), NewValueSet(NewStringValue("foo")), false},
|
||||
{[]byte(`[true, "false", 7, "7", "foo", "192.168.1.100/24"]`), set1, false},
|
||||
{[]byte(`{}`), nil, true}, // Unsupported data.
|
||||
{[]byte(`[]`), nil, true}, // Empty array.
|
||||
{[]byte(`[7, 7, true]`), nil, true}, // Duplicate value.
|
||||
}
|
||||
|
||||
for i, testCase := range testCases {
|
||||
result := make(ValueSet)
|
||||
err := json.Unmarshal(testCase.data, &result)
|
||||
expectErr := (err != nil)
|
||||
|
||||
if expectErr != testCase.expectErr {
|
||||
t.Fatalf("case %v: error: expected: %v, got: %v\n", i+1, testCase.expectErr, expectErr)
|
||||
}
|
||||
|
||||
if !testCase.expectErr {
|
||||
if !reflect.DeepEqual(result, testCase.expectedResult) {
|
||||
t.Fatalf("case %v: result: expected: %v, got: %v\n", i+1, testCase.expectedResult, result)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user