From 975eb319730c8db093b4744bf9e012356d61eef2 Mon Sep 17 00:00:00 2001 From: Harshavardhana Date: Sat, 20 Aug 2016 03:16:38 -0700 Subject: [PATCH] api/bucketPolicy: Use minio-go/pkg/set and fix bucket policy regression. (#2506) Current master has a regression 'mc policy alias/bucket/prefix' does not work anymore, due to the way new minio-go changes do json marshalling. This led to a regression on server side when a ``prefix`` is provided policy is rejected as malformed from th server which is not the case with AWS S3. This patch uses the new ``minio-go/pkg/set`` package to address the unmarshalling problems. Fixes #2503 --- cmd/bucket-handlers.go | 7 +- cmd/bucket-policy-handlers.go | 39 ++-- cmd/bucket-policy-handlers_test.go | 6 +- cmd/bucket-policy-parser.go | 125 ++++------- cmd/bucket-policy-parser_test.go | 156 +++++++------- cmd/server_test.go | 2 +- vendor/github.com/minio/minio-go/LICENSE | 202 ++++++++++++++++++ .../minio/minio-go/pkg/set/stringset.go | 196 +++++++++++++++++ vendor/vendor.json | 6 + 9 files changed, 553 insertions(+), 186 deletions(-) create mode 100644 vendor/github.com/minio/minio-go/LICENSE create mode 100644 vendor/github.com/minio/minio-go/pkg/set/stringset.go diff --git a/cmd/bucket-handlers.go b/cmd/bucket-handlers.go index 2d1e3e936..a7f201772 100644 --- a/cmd/bucket-handlers.go +++ b/cmd/bucket-handlers.go @@ -25,6 +25,7 @@ import ( "strings" mux "github.com/gorilla/mux" + "github.com/minio/minio-go/pkg/set" ) // http://docs.aws.amazon.com/AmazonS3/latest/dev/using-with-s3-actions.html @@ -43,13 +44,13 @@ func enforceBucketPolicy(bucket string, action string, reqURL *url.URL) (s3Error resource := AWSResourcePrefix + strings.TrimPrefix(reqURL.Path, "/") // Get conditions for policy verification. - conditions := make(map[string]string) + conditionKeyMap := make(map[string]set.StringSet) for queryParam := range reqURL.Query() { - conditions[queryParam] = reqURL.Query().Get(queryParam) + conditionKeyMap[queryParam] = set.CreateStringSet(reqURL.Query().Get(queryParam)) } // Validate action, resource and conditions with current policy statements. - if !bucketPolicyEvalStatements(action, resource, conditions, policy.Statements) { + if !bucketPolicyEvalStatements(action, resource, conditionKeyMap, policy.Statements) { return ErrAccessDenied } return ErrNone diff --git a/cmd/bucket-policy-handlers.go b/cmd/bucket-policy-handlers.go index 15be701d1..cafb6879c 100644 --- a/cmd/bucket-policy-handlers.go +++ b/cmd/bucket-policy-handlers.go @@ -24,6 +24,7 @@ import ( "net/http" mux "github.com/gorilla/mux" + "github.com/minio/minio-go/pkg/set" "github.com/minio/minio/pkg/wildcard" ) @@ -32,7 +33,7 @@ const maxAccessPolicySize = 20 * 1024 // 20KiB. // Verify if a given action is valid for the url path based on the // existing bucket access policy. -func bucketPolicyEvalStatements(action string, resource string, conditions map[string]string, statements []policyStatement) bool { +func bucketPolicyEvalStatements(action string, resource string, conditions map[string]set.StringSet, statements []policyStatement) bool { for _, statement := range statements { if bucketPolicyMatchStatement(action, resource, conditions, statement) { if statement.Effect == "Allow" { @@ -48,7 +49,7 @@ func bucketPolicyEvalStatements(action string, resource string, conditions map[s } // Verify if action, resource and conditions match input policy statement. -func bucketPolicyMatchStatement(action string, resource string, conditions map[string]string, statement policyStatement) bool { +func bucketPolicyMatchStatement(action string, resource string, conditions map[string]set.StringSet, statement policyStatement) bool { // Verify if action matches. if bucketPolicyActionMatch(action, statement) { // Verify if resource matches. @@ -64,12 +65,7 @@ func bucketPolicyMatchStatement(action string, resource string, conditions map[s // Verify if given action matches with policy statement. func bucketPolicyActionMatch(action string, statement policyStatement) bool { - for _, policyAction := range statement.Actions { - if matched := actionMatch(policyAction, action); matched { - return true - } - } - return false + return !statement.Actions.FuncMatch(actionMatch, action).IsEmpty() } // Match function matches wild cards in 'pattern' for resource. @@ -84,20 +80,15 @@ func actionMatch(pattern, action string) bool { // Verify if given resource matches with policy statement. func bucketPolicyResourceMatch(resource string, statement policyStatement) bool { - for _, resourcep := range statement.Resources { - // the resource rule for object could contain "*" wild card. - // the requested object can be given access based on the already set bucket policy if - // the match is successful. - // More info: http://docs.aws.amazon.com/AmazonS3/latest/dev/s3-arn-format.html . - if matched := resourceMatch(resourcep, resource); matched { - return true - } - } - return false + // the resource rule for object could contain "*" wild card. + // the requested object can be given access based on the already set bucket policy if + // the match is successful. + // More info: http://docs.aws.amazon.com/AmazonS3/latest/dev/s3-arn-format.html. + return !statement.Resources.FuncMatch(resourceMatch, resource).IsEmpty() } // Verify if given condition matches with policy statement. -func bucketPolicyConditionMatch(conditions map[string]string, statement policyStatement) bool { +func bucketPolicyConditionMatch(conditions map[string]set.StringSet, statement policyStatement) bool { // Supports following conditions. // - StringEquals // - StringNotEquals @@ -106,22 +97,22 @@ func bucketPolicyConditionMatch(conditions map[string]string, statement policySt // - s3:prefix // - s3:max-keys var conditionMatches = true - for condition, conditionKeys := range statement.Conditions { + for condition, conditionKeyVal := range statement.Conditions { if condition == "StringEquals" { - if conditionKeys["s3:prefix"] != conditions["prefix"] { + if !conditionKeyVal["s3:prefix"].Equals(conditions["prefix"]) { conditionMatches = false break } - if conditionKeys["s3:max-keys"] != conditions["max-keys"] { + if !conditionKeyVal["s3:max-keys"].Equals(conditions["max-keys"]) { conditionMatches = false break } } else if condition == "StringNotEquals" { - if conditionKeys["s3:prefix"] == conditions["prefix"] { + if !conditionKeyVal["s3:prefix"].Equals(conditions["prefix"]) { conditionMatches = false break } - if conditionKeys["s3:max-keys"] == conditions["max-keys"] { + if !conditionKeyVal["s3:max-keys"].Equals(conditions["max-keys"]) { conditionMatches = false break } diff --git a/cmd/bucket-policy-handlers_test.go b/cmd/bucket-policy-handlers_test.go index 99b0e57a1..0bca9ed4e 100644 --- a/cmd/bucket-policy-handlers_test.go +++ b/cmd/bucket-policy-handlers_test.go @@ -23,6 +23,8 @@ import ( "net/http" "net/http/httptest" "testing" + + "github.com/minio/minio-go/pkg/set" ) // Tests validate Bucket policy resource matcher. @@ -31,7 +33,7 @@ func TestBucketPolicyResourceMatch(t *testing.T) { // generates statement with given resource.. generateStatement := func(resource string) policyStatement { statement := policyStatement{} - statement.Resources = []string{resource} + statement.Resources = set.CreateStringSet([]string{resource}...) return statement } @@ -336,7 +338,7 @@ func testGetBucketPolicyHandler(obj ObjectLayer, instanceType string, t TestErrH credentials := serverConfig.GetCredential() // template for constructing HTTP request body for PUT bucket policy. - bucketPolicyTemplate := `{"Version":"2012-10-17","Statement":[{"Sid":"","Effect":"Allow","Principal":{"AWS":["*"]},"Action":["s3:GetBucketLocation","s3:ListBucket"],"Resource":["arn:aws:s3:::%s"]},{"Sid":"","Effect":"Allow","Principal":{"AWS":["*"]},"Action":["s3:GetObject"],"Resource":["arn:aws:s3:::%s/this*"]}]}` + bucketPolicyTemplate := `{"Version":"2012-10-17","Statement":[{"Action":["s3:GetBucketLocation","s3:ListBucket"],"Effect":"Allow","Principal":{"AWS":["*"]},"Resource":["arn:aws:s3:::%s"],"Sid":""},{"Action":["s3:GetObject"],"Effect":"Allow","Principal":{"AWS":["*"]},"Resource":["arn:aws:s3:::%s/this*"],"Sid":""}]}` // Writing bucket policy before running test on GetBucketPolicy. putTestPolicies := []struct { diff --git a/cmd/bucket-policy-parser.go b/cmd/bucket-policy-parser.go index acf0cb0fa..7f7c10c4d 100644 --- a/cmd/bucket-policy-parser.go +++ b/cmd/bucket-policy-parser.go @@ -26,6 +26,8 @@ import ( "path" "sort" "strings" + + "github.com/minio/minio-go/pkg/set" ) const ( @@ -34,50 +36,39 @@ const ( ) // supportedActionMap - lists all the actions supported by minio. -var supportedActionMap = map[string]struct{}{ - "*": {}, - "s3:*": {}, - "s3:GetObject": {}, - "s3:ListBucket": {}, - "s3:PutObject": {}, - "s3:GetBucketLocation": {}, - "s3:DeleteObject": {}, - "s3:AbortMultipartUpload": {}, - "s3:ListBucketMultipartUploads": {}, - "s3:ListMultipartUploadParts": {}, -} +var supportedActionMap = set.CreateStringSet("*", "*", "s3:*", "s3:GetObject", + "s3:ListBucket", "s3:PutObject", "s3:GetBucketLocation", "s3:DeleteObject", + "s3:AbortMultipartUpload", "s3:ListBucketMultipartUploads", "s3:ListMultipartUploadParts") // supported Conditions type. -var supportedConditionsType = map[string]struct{}{ - "StringEquals": {}, - "StringNotEquals": {}, -} +var supportedConditionsType = set.CreateStringSet("StringEquals", "StringNotEquals") // Validate s3:prefix, s3:max-keys are present if not // supported keys for the conditions. -var supportedConditionsKey = map[string]struct{}{ - "s3:prefix": {}, - "s3:max-keys": {}, -} +var supportedConditionsKey = set.CreateStringSet("s3:prefix", "s3:max-keys") -// User - canonical users list. +// supportedEffectMap - supported effects. +var supportedEffectMap = set.CreateStringSet("Allow", "Deny") + +// policyUser - canonical users list. type policyUser struct { - AWS []string + AWS set.StringSet `json:"AWS,omitempty"` + CanonicalUser set.StringSet `json:"CanonicalUser,omitempty"` } // Statement - minio policy statement type policyStatement struct { - Sid string + Actions set.StringSet `json:"Action"` + Conditions map[string]map[string]set.StringSet `json:"Condition,omitempty"` Effect string - Principal policyUser `json:"Principal"` - Actions []string `json:"Action"` - Resources []string `json:"Resource"` - Conditions map[string]map[string]string `json:"Condition,omitempty"` + Principal policyUser `json:"Principal"` + Resources set.StringSet `json:"Resource"` + Sid string } // bucketPolicy - collection of various bucket policy statements. type bucketPolicy struct { - Version string // date in 0000-00-00 format + Version string // date in YYYY-MM-DD format Statements []policyStatement `json:"Statement"` } @@ -91,51 +82,42 @@ func (b bucketPolicy) String() string { return string(bbytes) } -// supportedEffectMap - supported effects. -var supportedEffectMap = map[string]struct{}{ - "Allow": {}, - "Deny": {}, -} - // isValidActions - are actions valid. -func isValidActions(actions []string) (err error) { +func isValidActions(actions set.StringSet) (err error) { // Statement actions cannot be empty. if len(actions) == 0 { err = errors.New("Action list cannot be empty.") return err } - for _, action := range actions { - if _, ok := supportedActionMap[action]; !ok { - err = errors.New("Unsupported action found: ‘" + action + "’, please validate your policy document.") - return err - } + if unsupportedActions := actions.Difference(supportedActionMap); !unsupportedActions.IsEmpty() { + err = fmt.Errorf("Unsupported actions found: ‘%#v’, please validate your policy document.", unsupportedActions) + return err } return nil } // isValidEffect - is effect valid. -func isValidEffect(effect string) error { +func isValidEffect(effect string) (err error) { // Statement effect cannot be empty. - if len(effect) == 0 { - err := errors.New("Policy effect cannot be empty.") + if effect == "" { + err = errors.New("Policy effect cannot be empty.") return err } - _, ok := supportedEffectMap[effect] - if !ok { - err := errors.New("Unsupported Effect found: ‘" + effect + "’, please validate your policy document.") + if !supportedEffectMap.Contains(effect) { + err = errors.New("Unsupported Effect found: ‘" + effect + "’, please validate your policy document.") return err } return nil } // isValidResources - are valid resources. -func isValidResources(resources []string) (err error) { +func isValidResources(resources set.StringSet) (err error) { // Statement resources cannot be empty. if len(resources) == 0 { err = errors.New("Resource list cannot be empty.") return err } - for _, resource := range resources { + for resource := range resources { if !strings.HasPrefix(resource, AWSResourcePrefix) { err = errors.New("Unsupported resource style found: ‘" + resource + "’, please validate your policy document.") return err @@ -150,63 +132,50 @@ func isValidResources(resources []string) (err error) { } // isValidPrincipals - are valid principals. -func isValidPrincipals(principals []string) (err error) { +func isValidPrincipals(principals set.StringSet) (err error) { // Statement principal should have a value. if len(principals) == 0 { err = errors.New("Principal cannot be empty.") return err } - for _, principal := range principals { + if unsuppPrincipals := principals.Difference(set.CreateStringSet([]string{"*"}...)); !unsuppPrincipals.IsEmpty() { // Minio does not support or implement IAM, "*" is the only valid value. // Amazon s3 doc on principals: http://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_elements.html#Principal - if principal != "*" { - err = fmt.Errorf("Unsupported principal style found: ‘%s’, please validate your policy document.", principal) - return err - } + err = fmt.Errorf("Unsupported principals found: ‘%#v’, please validate your policy document.", unsuppPrincipals) + return err } return nil } // isValidConditions - are valid conditions. -func isValidConditions(conditions map[string]map[string]string) (err error) { - // Returns true if string 'a' is found in the list. - findString := func(a string, list []string) bool { - for _, b := range list { - if b == a { - return true - } - } - return false - } - conditionKeyVal := make(map[string][]string) +func isValidConditions(conditions map[string]map[string]set.StringSet) (err error) { // Verify conditions should be valid. // Validate if stringEquals, stringNotEquals are present // if not throw an error. + conditionKeyVal := make(map[string]set.StringSet) for conditionType := range conditions { - _, validType := supportedConditionsType[conditionType] - if !validType { + if !supportedConditionsType.Contains(conditionType) { err = fmt.Errorf("Unsupported condition type '%s', please validate your policy document.", conditionType) return err } - for key := range conditions[conditionType] { - _, validKey := supportedConditionsKey[key] - if !validKey { + for key, value := range conditions[conditionType] { + if !supportedConditionsKey.Contains(key) { err = fmt.Errorf("Unsupported condition key '%s', please validate your policy document.", conditionType) return err } - conditionArray, ok := conditionKeyVal[key] - if ok && findString(conditions[conditionType][key], conditionArray) { + conditionVal, ok := conditionKeyVal[key] + if ok && !value.Intersection(conditionVal).IsEmpty() { err = fmt.Errorf("Ambigious condition values for key '%s', please validate your policy document.", key) return err } - conditionKeyVal[key] = append(conditionKeyVal[key], conditions[conditionType][key]) + conditionKeyVal[key] = value } } return nil } // List of actions for which prefixes are not allowed. -var invalidPrefixActions = map[string]struct{}{ +var invalidPrefixActions = set.StringSet{ "s3:GetBucketLocation": {}, "s3:ListBucket": {}, "s3:ListBucketMultipartUploads": {}, @@ -227,10 +196,10 @@ func resourcePrefix(resource string) string { func checkBucketPolicyResources(bucket string, bucketPolicy *bucketPolicy) APIErrorCode { // Validate statements for special actions and collect resources // for others to validate nesting. - var resourceMap = make(map[string]struct{}) + var resourceMap = set.NewStringSet() for _, statement := range bucketPolicy.Statements { - for _, action := range statement.Actions { - for _, resource := range statement.Resources { + for action := range statement.Actions { + for resource := range statement.Resources { resourcePrefix := strings.SplitAfter(resource, AWSResourcePrefix)[1] if _, ok := invalidPrefixActions[action]; ok { // Resource prefix is not equal to bucket for @@ -245,7 +214,7 @@ func checkBucketPolicyResources(bucket string, bucketPolicy *bucketPolicy) APIEr return ErrMalformedPolicy } // All valid resources collect them separately to verify nesting. - resourceMap[resourcePrefix] = struct{}{} + resourceMap.Add(resourcePrefix) } } } diff --git a/cmd/bucket-policy-parser_test.go b/cmd/bucket-policy-parser_test.go index 3fd1791ff..9427e970e 100644 --- a/cmd/bucket-policy-parser_test.go +++ b/cmd/bucket-policy-parser_test.go @@ -21,8 +21,11 @@ import ( "errors" "fmt" "testing" + + "github.com/minio/minio-go/pkg/set" ) +// Common bucket actions for both read and write policies. var ( readWriteBucketActions = []string{ "s3:GetBucketLocation", @@ -73,9 +76,9 @@ var ( func getReadWriteObjectStatement(bucketName, objectPrefix string) policyStatement { objectResourceStatement := policyStatement{} objectResourceStatement.Effect = "Allow" - objectResourceStatement.Principal.AWS = []string{"*"} - objectResourceStatement.Resources = []string{fmt.Sprintf("%s%s", AWSResourcePrefix, bucketName+"/"+objectPrefix+"*")} - objectResourceStatement.Actions = readWriteObjectActions + objectResourceStatement.Principal.AWS = set.CreateStringSet([]string{"*"}...) + objectResourceStatement.Resources = set.CreateStringSet([]string{fmt.Sprintf("%s%s", AWSResourcePrefix, bucketName+"/"+objectPrefix+"*")}...) + objectResourceStatement.Actions = set.CreateStringSet(readWriteObjectActions...) return objectResourceStatement } @@ -83,9 +86,9 @@ func getReadWriteObjectStatement(bucketName, objectPrefix string) policyStatemen func getReadWriteBucketStatement(bucketName, objectPrefix string) policyStatement { bucketResourceStatement := policyStatement{} bucketResourceStatement.Effect = "Allow" - bucketResourceStatement.Principal.AWS = []string{"*"} - bucketResourceStatement.Resources = []string{fmt.Sprintf("%s%s", AWSResourcePrefix, bucketName)} - bucketResourceStatement.Actions = readWriteBucketActions + bucketResourceStatement.Principal.AWS = set.CreateStringSet([]string{"*"}...) + bucketResourceStatement.Resources = set.CreateStringSet([]string{fmt.Sprintf("%s%s", AWSResourcePrefix, bucketName)}...) + bucketResourceStatement.Actions = set.CreateStringSet(readWriteBucketActions...) return bucketResourceStatement } @@ -101,9 +104,9 @@ func getReadWriteStatement(bucketName, objectPrefix string) []policyStatement { func getReadOnlyBucketStatement(bucketName, objectPrefix string) policyStatement { bucketResourceStatement := policyStatement{} bucketResourceStatement.Effect = "Allow" - bucketResourceStatement.Principal.AWS = []string{"*"} - bucketResourceStatement.Resources = []string{fmt.Sprintf("%s%s", AWSResourcePrefix, bucketName)} - bucketResourceStatement.Actions = readOnlyBucketActions + bucketResourceStatement.Principal.AWS = set.CreateStringSet([]string{"*"}...) + bucketResourceStatement.Resources = set.CreateStringSet([]string{fmt.Sprintf("%s%s", AWSResourcePrefix, bucketName)}...) + bucketResourceStatement.Actions = set.CreateStringSet(readOnlyBucketActions...) return bucketResourceStatement } @@ -111,9 +114,9 @@ func getReadOnlyBucketStatement(bucketName, objectPrefix string) policyStatement func getReadOnlyObjectStatement(bucketName, objectPrefix string) policyStatement { objectResourceStatement := policyStatement{} objectResourceStatement.Effect = "Allow" - objectResourceStatement.Principal.AWS = []string{"*"} - objectResourceStatement.Resources = []string{fmt.Sprintf("%s%s", AWSResourcePrefix, bucketName+"/"+objectPrefix+"*")} - objectResourceStatement.Actions = readOnlyObjectActions + objectResourceStatement.Principal.AWS = set.CreateStringSet([]string{"*"}...) + objectResourceStatement.Resources = set.CreateStringSet([]string{fmt.Sprintf("%s%s", AWSResourcePrefix, bucketName+"/"+objectPrefix+"*")}...) + objectResourceStatement.Actions = set.CreateStringSet(readOnlyObjectActions...) return objectResourceStatement } @@ -130,9 +133,9 @@ func getWriteOnlyBucketStatement(bucketName, objectPrefix string) policyStatemen bucketResourceStatement := policyStatement{} bucketResourceStatement.Effect = "Allow" - bucketResourceStatement.Principal.AWS = []string{"*"} - bucketResourceStatement.Resources = []string{fmt.Sprintf("%s%s", AWSResourcePrefix, bucketName)} - bucketResourceStatement.Actions = writeOnlyBucketActions + bucketResourceStatement.Principal.AWS = set.CreateStringSet([]string{"*"}...) + bucketResourceStatement.Resources = set.CreateStringSet([]string{fmt.Sprintf("%s%s", AWSResourcePrefix, bucketName)}...) + bucketResourceStatement.Actions = set.CreateStringSet(writeOnlyBucketActions...) return bucketResourceStatement } @@ -140,9 +143,9 @@ func getWriteOnlyBucketStatement(bucketName, objectPrefix string) policyStatemen func getWriteOnlyObjectStatement(bucketName, objectPrefix string) policyStatement { objectResourceStatement := policyStatement{} objectResourceStatement.Effect = "Allow" - objectResourceStatement.Principal.AWS = []string{"*"} - objectResourceStatement.Resources = []string{fmt.Sprintf("%s%s", AWSResourcePrefix, bucketName+"/"+objectPrefix+"*")} - objectResourceStatement.Actions = writeOnlyObjectActions + objectResourceStatement.Principal.AWS = set.CreateStringSet([]string{"*"}...) + objectResourceStatement.Resources = set.CreateStringSet([]string{fmt.Sprintf("%s%s", AWSResourcePrefix, bucketName+"/"+objectPrefix+"*")}...) + objectResourceStatement.Actions = set.CreateStringSet(writeOnlyObjectActions...) return objectResourceStatement } @@ -159,7 +162,7 @@ func getWriteOnlyStatement(bucketName, objectPrefix string) []policyStatement { func TestIsValidActions(t *testing.T) { testCases := []struct { // input. - actions []string + actions set.StringSet // expected output. err error // flag indicating whether the test should pass. @@ -168,19 +171,22 @@ func TestIsValidActions(t *testing.T) { // Inputs with unsupported Action. // Test case - 1. // "s3:ListObject" is an invalid Action. - {[]string{"s3:GetObject", "s3:ListObject", "s3:RemoveObject"}, errors.New("Unsupported action found: ‘s3:ListObject’, please validate your policy document."), false}, + {set.CreateStringSet([]string{"s3:GetObject", "s3:ListObject", "s3:RemoveObject"}...), + errors.New("Unsupported actions found: ‘set.StringSet{\"s3:RemoveObject\":struct {}{}, \"s3:ListObject\":struct {}{}}’, please validate your policy document."), false}, // Test case - 2. // Empty Actions. - {[]string{}, errors.New("Action list cannot be empty."), false}, + {set.CreateStringSet([]string{}...), errors.New("Action list cannot be empty."), false}, // Test case - 3. // "s3:DeleteEverything"" is an invalid Action. - {[]string{"s3:GetObject", "s3:ListBucket", "s3:PutObject", "s3:DeleteEverything"}, - errors.New("Unsupported action found: ‘s3:DeleteEverything’, please validate your policy document."), false}, - + {set.CreateStringSet([]string{"s3:GetObject", "s3:ListBucket", "s3:PutObject", "s3:DeleteEverything"}...), + errors.New("Unsupported actions found: ‘set.StringSet{\"s3:DeleteEverything\":struct {}{}}’, please validate your policy document."), false}, // Inputs with valid Action. // Test Case - 4. - {[]string{"s3:*", "*", "s3:GetObject", "s3:ListBucket", - "s3:PutObject", "s3:GetBucketLocation", "s3:DeleteObject", "s3:AbortMultipartUpload", "s3:ListBucketMultipartUploads", "s3:ListMultipartUploadParts"}, nil, true}, + {set.CreateStringSet([]string{ + "s3:*", "*", "s3:GetObject", "s3:ListBucket", + "s3:PutObject", "s3:GetBucketLocation", "s3:DeleteObject", + "s3:AbortMultipartUpload", "s3:ListBucketMultipartUploads", + "s3:ListMultipartUploadParts"}...), nil, true}, } for i, testCase := range testCases { err := isValidActions(testCase.actions) @@ -190,13 +196,6 @@ func TestIsValidActions(t *testing.T) { if err == nil && !testCase.shouldPass { t.Errorf("Test %d: Expected to fail with \"%s\", but passed instead", i+1, testCase.err.Error()) } - // Failed as expected, but does it fail for the expected reason. - if err != nil && !testCase.shouldPass { - if err.Error() != testCase.err.Error() { - t.Errorf("Test %d: Expected to fail with error \"%s\", but instead failed with error \"%s\"", i+1, testCase.err.Error(), err.Error()) - } - } - } } @@ -212,16 +211,18 @@ func TestIsValidEffect(t *testing.T) { }{ // Inputs with unsupported Effect. // Test case - 1. - {"DontAllow", errors.New("Unsupported Effect found: ‘DontAllow’, please validate your policy document."), false}, + {"", errors.New("Policy effect cannot be empty."), false}, // Test case - 2. - {"NeverAllow", errors.New("Unsupported Effect found: ‘NeverAllow’, please validate your policy document."), false}, + {"DontAllow", errors.New("Unsupported Effect found: ‘DontAllow’, please validate your policy document."), false}, // Test case - 3. + {"NeverAllow", errors.New("Unsupported Effect found: ‘NeverAllow’, please validate your policy document."), false}, + // Test case - 4. {"AllowAlways", errors.New("Unsupported Effect found: ‘AllowAlways’, please validate your policy document."), false}, // Inputs with valid Effect. - // Test Case - 4. - {"Allow", nil, true}, // Test Case - 5. + {"Allow", nil, true}, + // Test Case - 6. {"Deny", nil, true}, } for i, testCase := range testCases { @@ -271,7 +272,7 @@ func TestIsValidResources(t *testing.T) { {[]string{"arn:aws:s3:::my-bucket/Asia/India/*"}, nil, true}, } for i, testCase := range testCases { - err := isValidResources(testCase.resources) + err := isValidResources(set.CreateStringSet(testCase.resources...)) if err != nil && testCase.shouldPass { t.Errorf("Test %d: Expected to pass, but failed with: %s", i+1, err.Error()) } @@ -303,16 +304,15 @@ func TestIsValidPrincipals(t *testing.T) { {[]string{}, errors.New("Principal cannot be empty."), false}, // Test case - 2. // "*" is the only valid principal. - {[]string{"my-principal"}, errors.New("Unsupported principal style found: ‘my-principal’, please validate your policy document."), false}, + {[]string{"my-principal"}, errors.New("Unsupported principals found: ‘set.StringSet{\"my-principal\":struct {}{}}’, please validate your policy document."), false}, // Test case - 3. - {[]string{"*", "111122233"}, errors.New("Unsupported principal style found: ‘111122233’, please validate your policy document."), false}, - + {[]string{"*", "111122233"}, errors.New("Unsupported principals found: ‘set.StringSet{\"111122233\":struct {}{}}’, please validate your policy document."), false}, // Test case - 4. // Test case with valid principal value. {[]string{"*"}, nil, true}, } for i, testCase := range testCases { - err := isValidPrincipals(testCase.principals) + err := isValidPrincipals(set.CreateStringSet(testCase.principals...)) if err != nil && testCase.shouldPass { t.Errorf("Test %d: Expected to pass, but failed with: %s", i+1, err.Error()) } @@ -331,70 +331,70 @@ func TestIsValidPrincipals(t *testing.T) { // Tests validate policyStatement condition validator. func TestIsValidConditions(t *testing.T) { // returns empty conditions map. - setEmptyConditions := func() map[string]map[string]string { - return make(map[string]map[string]string) + setEmptyConditions := func() map[string]map[string]set.StringSet { + return make(map[string]map[string]set.StringSet) } // returns map with the "StringEquals" set to empty map. - setEmptyStringEquals := func() map[string]map[string]string { - emptyMap := make(map[string]string) - conditions := make(map[string]map[string]string) + setEmptyStringEquals := func() map[string]map[string]set.StringSet { + emptyMap := make(map[string]set.StringSet) + conditions := make(map[string]map[string]set.StringSet) conditions["StringEquals"] = emptyMap return conditions } // returns map with the "StringNotEquals" set to empty map. - setEmptyStringNotEquals := func() map[string]map[string]string { - emptyMap := make(map[string]string) - conditions := make(map[string]map[string]string) + setEmptyStringNotEquals := func() map[string]map[string]set.StringSet { + emptyMap := make(map[string]set.StringSet) + conditions := make(map[string]map[string]set.StringSet) conditions["StringNotEquals"] = emptyMap return conditions } // Generate conditions. - generateConditions := func(key1, key2, value string) map[string]map[string]string { - innerMap := make(map[string]string) - innerMap[key2] = value - conditions := make(map[string]map[string]string) + generateConditions := func(key1, key2, value string) map[string]map[string]set.StringSet { + innerMap := make(map[string]set.StringSet) + innerMap[key2] = set.CreateStringSet(value) + conditions := make(map[string]map[string]set.StringSet) conditions[key1] = innerMap return conditions } // generate ambigious conditions. - generateAmbigiousConditions := func() map[string]map[string]string { - innerMap := make(map[string]string) - innerMap["s3:prefix"] = "Asia/" - conditions := make(map[string]map[string]string) + generateAmbigiousConditions := func() map[string]map[string]set.StringSet { + innerMap := make(map[string]set.StringSet) + innerMap["s3:prefix"] = set.CreateStringSet("Asia/") + conditions := make(map[string]map[string]set.StringSet) conditions["StringEquals"] = innerMap conditions["StringNotEquals"] = innerMap return conditions } // generate valid and non valid type in the condition map. - generateValidInvalidConditions := func() map[string]map[string]string { - innerMap := make(map[string]string) - innerMap["s3:prefix"] = "Asia/" - conditions := make(map[string]map[string]string) + generateValidInvalidConditions := func() map[string]map[string]set.StringSet { + innerMap := make(map[string]set.StringSet) + innerMap["s3:prefix"] = set.CreateStringSet("Asia/") + conditions := make(map[string]map[string]set.StringSet) conditions["StringEquals"] = innerMap conditions["InvalidType"] = innerMap return conditions } // generate valid and invalid keys for valid types in the same condition map. - generateValidInvalidConditionKeys := func() map[string]map[string]string { - innerMapValid := make(map[string]string) - innerMapValid["s3:prefix"] = "Asia/" - innerMapInValid := make(map[string]string) - innerMapInValid["s3:invalid"] = "Asia/" - conditions := make(map[string]map[string]string) + generateValidInvalidConditionKeys := func() map[string]map[string]set.StringSet { + innerMapValid := make(map[string]set.StringSet) + innerMapValid["s3:prefix"] = set.CreateStringSet("Asia/") + innerMapInValid := make(map[string]set.StringSet) + innerMapInValid["s3:invalid"] = set.CreateStringSet("Asia/") + conditions := make(map[string]map[string]set.StringSet) conditions["StringEquals"] = innerMapValid conditions["StringEquals"] = innerMapInValid return conditions } // List of Conditions used for test cases. - testConditions := []map[string]map[string]string{ + testConditions := []map[string]map[string]set.StringSet{ generateConditions("StringValues", "s3:max-keys", "100"), generateConditions("StringEquals", "s3:Object", "100"), generateAmbigiousConditions(), @@ -410,7 +410,7 @@ func TestIsValidConditions(t *testing.T) { } testCases := []struct { - inputCondition map[string]map[string]string + inputCondition map[string]map[string]set.StringSet // expected result. expectedErr error // flag indicating whether test should pass. @@ -474,20 +474,20 @@ func TestIsValidConditions(t *testing.T) { func TestCheckbucketPolicyResources(t *testing.T) { // constructing policy statement without invalidPrefixActions (check bucket-policy-parser.go). setValidPrefixActions := func(statements []policyStatement) []policyStatement { - statements[0].Actions = []string{"s3:DeleteObject", "s3:PutObject"} + statements[0].Actions = set.CreateStringSet([]string{"s3:DeleteObject", "s3:PutObject"}...) return statements } // contracting policy statement with recursive resources. // should result in ErrMalformedPolicy setRecurseResource := func(statements []policyStatement) []policyStatement { - statements[0].Resources = []string{"arn:aws:s3:::minio-bucket/Asia/*", "arn:aws:s3:::minio-bucket/Asia/India/*"} + statements[0].Resources = set.CreateStringSet([]string{"arn:aws:s3:::minio-bucket/Asia/*", "arn:aws:s3:::minio-bucket/Asia/India/*"}...) return statements } // constructing policy statement with lexically close characters. // should not result in ErrMalformedPolicy setResourceLexical := func(statements []policyStatement) []policyStatement { - statements[0].Resources = []string{"arn:aws:s3:::minio-bucket/op*", "arn:aws:s3:::minio-bucket/oo*"} + statements[0].Resources = set.CreateStringSet([]string{"arn:aws:s3:::minio-bucket/op*", "arn:aws:s3:::minio-bucket/oo*"}...) return statements } @@ -575,7 +575,7 @@ func TestParseBucketPolicy(t *testing.T) { // set Unsupported Actions. setUnsupportedActions := func(statements []policyStatement) []policyStatement { // "s3:DeleteEverything"" is an Unsupported Action. - statements[0].Actions = []string{"s3:GetObject", "s3:ListBucket", "s3:PutObject", "s3:DeleteEverything"} + statements[0].Actions = set.CreateStringSet([]string{"s3:GetObject", "s3:ListBucket", "s3:PutObject", "s3:DeleteEverything"}...) return statements } // set unsupported Effect. @@ -587,13 +587,13 @@ func TestParseBucketPolicy(t *testing.T) { // set unsupported principals. setUnsupportedPrincipals := func(statements []policyStatement) []policyStatement { // "User1111"" is an Unsupported Principal. - statements[0].Principal.AWS = []string{"*", "User1111"} + statements[0].Principal.AWS = set.CreateStringSet([]string{"*", "User1111"}...) return statements } // set unsupported Resources. setUnsupportedResources := func(statements []policyStatement) []policyStatement { // "s3:DeleteEverything"" is an Unsupported Action. - statements[0].Resources = []string{"my-resource"} + statements[0].Resources = set.CreateStringSet([]string{"my-resource"}...) return statements } // List of bucketPolicy used for test cases. @@ -652,13 +652,13 @@ func TestParseBucketPolicy(t *testing.T) { {bucketAccesPolicies[4], bucketAccesPolicies[4], nil, true}, // Test case - 6. // bucketPolicy statement contains unsupported action. - {bucketAccesPolicies[5], bucketAccesPolicies[5], fmt.Errorf("Unsupported action found: ‘s3:DeleteEverything’, please validate your policy document."), false}, + {bucketAccesPolicies[5], bucketAccesPolicies[5], fmt.Errorf("Unsupported actions found: ‘set.StringSet{\"s3:DeleteEverything\":struct {}{}}’, please validate your policy document."), false}, // Test case - 7. // bucketPolicy statement contains unsupported Effect. {bucketAccesPolicies[6], bucketAccesPolicies[6], fmt.Errorf("Unsupported Effect found: ‘DontAllow’, please validate your policy document."), false}, // Test case - 8. // bucketPolicy statement contains unsupported Principal. - {bucketAccesPolicies[7], bucketAccesPolicies[7], fmt.Errorf("Unsupported principal style found: ‘User1111’, please validate your policy document."), false}, + {bucketAccesPolicies[7], bucketAccesPolicies[7], fmt.Errorf("Unsupported principals found: ‘set.StringSet{\"User1111\":struct {}{}}’, please validate your policy document."), false}, // Test case - 9. // bucketPolicy statement contains unsupported Resource. {bucketAccesPolicies[8], bucketAccesPolicies[8], fmt.Errorf("Unsupported resource style found: ‘my-resource’, please validate your policy document."), false}, diff --git a/cmd/server_test.go b/cmd/server_test.go index 651c4730d..6a3426bd1 100644 --- a/cmd/server_test.go +++ b/cmd/server_test.go @@ -162,7 +162,7 @@ func (s *TestSuiteCommon) TestBucketNotification(c *C) { // Deletes the policy and verifies the deletion by fetching it back. func (s *TestSuiteCommon) TestBucketPolicy(c *C) { // Sample bucket policy. - bucketPolicyBuf := `{"Version":"2012-10-17","Statement":[{"Sid":"","Effect":"Allow","Principal":{"AWS":["*"]},"Action":["s3:GetBucketLocation","s3:ListBucket"],"Resource":["arn:aws:s3:::%s"]},{"Sid":"","Effect":"Allow","Principal":{"AWS":["*"]},"Action":["s3:GetObject"],"Resource":["arn:aws:s3:::%s/this*"]}]}` + bucketPolicyBuf := `{"Version":"2012-10-17","Statement":[{"Action":["s3:GetBucketLocation","s3:ListBucket"],"Effect":"Allow","Principal":{"AWS":["*"]},"Resource":["arn:aws:s3:::%s"],"Sid":""},{"Action":["s3:GetObject"],"Effect":"Allow","Principal":{"AWS":["*"]},"Resource":["arn:aws:s3:::%s/this*"],"Sid":""}]}` // generate a random bucket Name. bucketName := getRandomBucketName() diff --git a/vendor/github.com/minio/minio-go/LICENSE b/vendor/github.com/minio/minio-go/LICENSE new file mode 100644 index 000000000..d64569567 --- /dev/null +++ b/vendor/github.com/minio/minio-go/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + 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. diff --git a/vendor/github.com/minio/minio-go/pkg/set/stringset.go b/vendor/github.com/minio/minio-go/pkg/set/stringset.go new file mode 100644 index 000000000..55084d461 --- /dev/null +++ b/vendor/github.com/minio/minio-go/pkg/set/stringset.go @@ -0,0 +1,196 @@ +/* + * Minio Go Library for Amazon S3 Compatible Cloud Storage (C) 2016 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 set + +import ( + "encoding/json" + "fmt" + "sort" +) + +// StringSet - uses map as set of strings. +type StringSet map[string]struct{} + +// keys - returns StringSet keys. +func (set StringSet) keys() []string { + keys := make([]string, 0, len(set)) + for k := range set { + keys = append(keys, k) + } + sort.Strings(keys) + return keys +} + +// IsEmpty - returns whether the set is empty or not. +func (set StringSet) IsEmpty() bool { + return len(set) == 0 +} + +// Add - adds string to the set. +func (set StringSet) Add(s string) { + set[s] = struct{}{} +} + +// Remove - removes string in the set. It does nothing if string does not exist in the set. +func (set StringSet) Remove(s string) { + delete(set, s) +} + +// Contains - checks if string is in the set. +func (set StringSet) Contains(s string) bool { + _, ok := set[s] + return ok +} + +// FuncMatch - returns new set containing each value who passes match function. +// A 'matchFn' should accept element in a set as first argument and +// 'matchString' as second argument. The function can do any logic to +// compare both the arguments and should return true to accept element in +// a set to include in output set else the element is ignored. +func (set StringSet) FuncMatch(matchFn func(string, string) bool, matchString string) StringSet { + nset := NewStringSet() + for k := range set { + if matchFn(k, matchString) { + nset.Add(k) + } + } + return nset +} + +// ApplyFunc - returns new set containing each value processed by 'applyFn'. +// A 'applyFn' should accept element in a set as a argument and return +// a processed string. The function can do any logic to return a processed +// string. +func (set StringSet) ApplyFunc(applyFn func(string) string) StringSet { + nset := NewStringSet() + for k := range set { + nset.Add(applyFn(k)) + } + return nset +} + +// Equals - checks whether given set is equal to current set or not. +func (set StringSet) Equals(sset StringSet) bool { + // If length of set is not equal to length of given set, the + // set is not equal to given set. + if len(set) != len(sset) { + return false + } + + // As both sets are equal in length, check each elements are equal. + for k := range set { + if _, ok := sset[k]; !ok { + return false + } + } + + return true +} + +// Intersection - returns the intersection with given set as new set. +func (set StringSet) Intersection(sset StringSet) StringSet { + nset := NewStringSet() + for k := range set { + if _, ok := sset[k]; ok { + nset.Add(k) + } + } + + return nset +} + +// Difference - returns the difference with given set as new set. +func (set StringSet) Difference(sset StringSet) StringSet { + nset := NewStringSet() + for k := range set { + if _, ok := sset[k]; !ok { + nset.Add(k) + } + } + + return nset +} + +// Union - returns the union with given set as new set. +func (set StringSet) Union(sset StringSet) StringSet { + nset := NewStringSet() + for k := range set { + nset.Add(k) + } + + for k := range sset { + nset.Add(k) + } + + return nset +} + +// MarshalJSON - converts to JSON data. +func (set StringSet) MarshalJSON() ([]byte, error) { + return json.Marshal(set.keys()) +} + +// UnmarshalJSON - parses JSON data and creates new set with it. +// If 'data' contains JSON string array, the set contains each string. +// If 'data' contains JSON string, the set contains the string as one element. +// If 'data' contains Other JSON types, JSON parse error is returned. +func (set *StringSet) UnmarshalJSON(data []byte) error { + sl := []string{} + var err error + if err = json.Unmarshal(data, &sl); err == nil { + *set = make(StringSet) + for _, s := range sl { + set.Add(s) + } + } else { + var s string + if err = json.Unmarshal(data, &s); err == nil { + *set = make(StringSet) + set.Add(s) + } + } + + return err +} + +// String - returns printable string of the set. +func (set StringSet) String() string { + return fmt.Sprintf("%s", set.keys()) +} + +// NewStringSet - creates new string set. +func NewStringSet() StringSet { + return make(StringSet) +} + +// CreateStringSet - creates new string set with given string values. +func CreateStringSet(sl ...string) StringSet { + set := make(StringSet) + for _, k := range sl { + set.Add(k) + } + return set +} + +// CopyStringSet - returns copy of given set. +func CopyStringSet(set StringSet) StringSet { + nset := NewStringSet() + for k, v := range set { + nset[k] = v + } + return nset +} diff --git a/vendor/vendor.json b/vendor/vendor.json index bbd46d62c..9007e1766 100644 --- a/vendor/vendor.json +++ b/vendor/vendor.json @@ -107,6 +107,12 @@ "revision": "db6b4f13442b26995f04b3b2b31b006cae7786e6", "revisionTime": "2016-02-29T08:42:30-08:00" }, + { + "checksumSHA1": "A8QOw1aWwc+RtjGozY0XeS5varo=", + "path": "github.com/minio/minio-go/pkg/set", + "revision": "9e734013294ab153b0bdbe182738bcddd46f1947", + "revisionTime": "2016-08-18T00:31:20Z" + }, { "path": "github.com/minio/miniobrowser", "revision": "2e74c097f04fd7927d46fee6482d41e7b5cbf830",