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:
Harshavardhana
2020-01-28 03:42:34 +05:30
committed by kannappanr
parent 4cb6ebcfa2
commit 0cbebf0f57
109 changed files with 102 additions and 101 deletions

View 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
}

View 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)
}
}
}
}

View 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
}

View 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)
}
}
}
}

View 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)
}

View 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)
}
}
}
}

View 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 &notIPAddressFunc{ipAddressFunc{key, IPNets}}, nil
}

View 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},
{&notIPAddressFunc{}, 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)
}
}
}
}

View 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.
}

View 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)
}
}
}

View 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)
}

View 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)
}
}
}
}

View 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
}

View 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)
}
}
}
}

View 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
}

View 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)
}
}
}
}

View 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
}

View 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)
}
}
}
}

View 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
}

View 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)
}
}
}
}

View 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
}

View 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)
}
}
}
}

View 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
}

View 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)
}
}
}
}