/* * Minio Cloud Storage, (C) 2015, 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 main import ( "bytes" "fmt" "io/ioutil" "net/http" "net/http/httptest" "testing" ) // Tests validate Bucket policy resource matcher. func TestBucketPolicyResourceMatch(t *testing.T) { // generates statement with given resource.. generateStatement := func(resource string) policyStatement { statement := policyStatement{} statement.Resources = []string{resource} return statement } // generates resource prefix. generateResource := func(bucketName, objectName string) string { return AWSResourcePrefix + bucketName + "/" + objectName } testCases := []struct { resourceToMatch string statement policyStatement expectedResourceMatch bool }{ // Test case 1-4. // Policy with resource ending with bucket/* allows access to all objects inside the given bucket. {generateResource("minio-bucket", ""), generateStatement(fmt.Sprintf("%s%s", AWSResourcePrefix, "minio-bucket"+"/*")), true}, {generateResource("minio-bucket", ""), generateStatement(fmt.Sprintf("%s%s", AWSResourcePrefix, "minio-bucket"+"/*")), true}, {generateResource("minio-bucket", ""), generateStatement(fmt.Sprintf("%s%s", AWSResourcePrefix, "minio-bucket"+"/*")), true}, {generateResource("minio-bucket", ""), generateStatement(fmt.Sprintf("%s%s", AWSResourcePrefix, "minio-bucket"+"/*")), true}, // Test case - 5. // Policy with resource ending with bucket/oo* should not allow access to bucket/output.txt. {generateResource("minio-bucket", "output.txt"), generateStatement(fmt.Sprintf("%s%s", AWSResourcePrefix, "minio-bucket"+"/oo*")), false}, // Test case - 6. // Policy with resource ending with bucket/oo* should allow access to bucket/ootput.txt. {generateResource("minio-bucket", "ootput.txt"), generateStatement(fmt.Sprintf("%s%s", AWSResourcePrefix, "minio-bucket"+"/oo*")), true}, // Test case - 7. // Policy with resource ending with bucket/oo* allows access to all subfolders starting with "oo" inside given bucket. {generateResource("minio-bucket", "oop-bucket/my-file"), generateStatement(fmt.Sprintf("%s%s", AWSResourcePrefix, "minio-bucket"+"/oo*")), true}, // Test case - 8. {generateResource("minio-bucket", "Asia/India/1.pjg"), generateStatement(fmt.Sprintf("%s%s", AWSResourcePrefix, "minio-bucket"+"/Asia/Japan/*")), false}, // Test case - 9. {generateResource("minio-bucket", "Asia/India/1.pjg"), generateStatement(fmt.Sprintf("%s%s", AWSResourcePrefix, "minio-bucket"+"/Asia/Japan/*")), false}, // Test case - 10. // Proves that the name space is flat. {generateResource("minio-bucket", "Africa/Bihar/India/design_info.doc/Bihar"), generateStatement(fmt.Sprintf("%s%s", AWSResourcePrefix, "minio-bucket"+"/*/India/*/Bihar")), true}, // Test case - 11. // Proves that the name space is flat. {generateResource("minio-bucket", "Asia/China/India/States/Bihar/output.txt"), generateStatement(fmt.Sprintf("%s%s", AWSResourcePrefix, "minio-bucket"+"/*/India/*/Bihar/*")), true}, } for i, testCase := range testCases { actualResourceMatch := bucketPolicyResourceMatch(testCase.resourceToMatch, testCase.statement) if testCase.expectedResourceMatch != actualResourceMatch { t.Errorf("Test %d: Expected Resource match to be `%v`, but instead found it to be `%v`", i+1, testCase.expectedResourceMatch, actualResourceMatch) } } } // TestBucketPolicyActionMatch - Test validates whether given action on the // bucket/object matches the allowed actions in policyStatement. // This test preserves the allowed actions for all 3 sets of policies, that is read-write,read-only, write-only. // The intention of the test is to catch any changes made to allowed action for on eof the above 3 major policy groups mentioned. func TestBucketPolicyActionMatch(t *testing.T) { bucketName := getRandomBucketName() objectPrefix := "test-object" testCases := []struct { action string statement policyStatement expectedResult bool }{ // s3:GetBucketLocation is the action necessary to be present in the bucket policy to allow // fetching of bucket location on an Anonymous/unsigned request. //r ead-write bucket policy is expected to allow GetBucketLocation operation on an anonymous request (Test case - 1). {"s3:GetBucketLocation", getReadWriteBucketStatement(bucketName, objectPrefix), true}, // write-only bucket policy is expected to allow GetBucketLocation operation on an anonymous request (Test case - 2). {"s3:GetBucketLocation", getWriteOnlyBucketStatement(bucketName, objectPrefix), true}, // read-only bucket policy is expected to allow GetBucketLocation operation on an anonymous request (Test case - 3). {"s3:GetBucketLocation", getReadOnlyBucketStatement(bucketName, objectPrefix), true}, // Any of the Object level access permissions shouldn't allow for GetBucketLocation operation on an Anonymous/unsigned request (Test cases 4-6). {"s3:GetBucketLocation", getReadWriteObjectStatement(bucketName, objectPrefix), false}, {"s3:GetBucketLocation", getWriteOnlyObjectStatement(bucketName, objectPrefix), false}, {"s3:GetBucketLocation", getReadOnlyObjectStatement(bucketName, objectPrefix), false}, // s3:ListBucketMultipartUploads is the action necessary to be present in the bucket policy to allow // Listing of multipart uploads in a given bucket for an Anonymous/unsigned request. //read-write bucket policy is expected to allow ListBucketMultipartUploads operation on an anonymous request (Test case 7). {"s3:ListBucketMultipartUploads", getReadWriteBucketStatement(bucketName, objectPrefix), true}, // write-only bucket policy is expected to allow ListBucketMultipartUploads operation on an anonymous request (Test case 8). {"s3:ListBucketMultipartUploads", getWriteOnlyBucketStatement(bucketName, objectPrefix), true}, // read-only bucket policy is expected to not allow ListBucketMultipartUploads operation on an anonymous request (Test case 9). // the allowed actions in read-only bucket statement are "s3:GetBucketLocation","s3:ListBucket", // this shouldnot allow for ListBucketMultipartUploads operations. {"s3:ListBucketMultipartUploads", getReadOnlyBucketStatement(bucketName, objectPrefix), false}, // Any of the object level policy will not allow for s3:ListBucketMultipartUploads (Test cases 10-12). {"s3:ListBucketMultipartUploads", getReadWriteObjectStatement(bucketName, objectPrefix), false}, {"s3:ListBucketMultipartUploads", getWriteOnlyObjectStatement(bucketName, objectPrefix), false}, {"s3:ListBucketMultipartUploads", getReadOnlyObjectStatement(bucketName, objectPrefix), false}, // s3:ListBucket is the action necessary to be present in the bucket policy to allow // listing of all objects inside a given bucket on an Anonymous/unsigned request. // Cases for testing ListBucket access for different Bucket level access permissions. // read-only bucket policy is expected to allow ListBucket operation on an anonymous request (Test case 13). {"s3:ListBucket", getReadOnlyBucketStatement(bucketName, objectPrefix), true}, // read-write bucket policy is expected to allow ListBucket operation on an anonymous request (Test case 14). {"s3:ListBucket", getReadWriteBucketStatement(bucketName, objectPrefix), true}, // write-only bucket policy is expected to not allow ListBucket operation on an anonymous request (Test case 15). // the allowed actions in write-only bucket statement are "s3:GetBucketLocation", "s3:ListBucketMultipartUploads", // this shouldnot allow for ListBucket operations. {"s3:ListBucket", getWriteOnlyBucketStatement(bucketName, objectPrefix), false}, // Cases for testing ListBucket access for different Object level access permissions (Test cases 16-18). // Any of the Object level access permissions shouldn't allow for ListBucket operation on an Anonymous/unsigned request. {"s3:ListBucket", getReadOnlyObjectStatement(bucketName, objectPrefix), false}, {"s3:ListBucket", getReadWriteObjectStatement(bucketName, objectPrefix), false}, {"s3:ListBucket", getWriteOnlyObjectStatement(bucketName, objectPrefix), false}, // s3:DeleteObject is the action necessary to be present in the bucket policy to allow // deleting/removal of objects inside a given bucket for an Anonymous/unsigned request. // Cases for testing DeleteObject access for different Bucket level access permissions (Test cases 19-21). // Any of the Bucket level access permissions shouldn't allow for DeleteObject operation on an Anonymous/unsigned request. {"s3:DeleteObject", getReadOnlyBucketStatement(bucketName, objectPrefix), false}, {"s3:DeleteObject", getReadWriteBucketStatement(bucketName, objectPrefix), false}, {"s3:DeleteObject", getWriteOnlyBucketStatement(bucketName, objectPrefix), false}, // Cases for testing DeleteObject access for different Object level access permissions (Test cases 22). // read-only bucket policy is expected to not allow Delete Object operation on an anonymous request. {"s3:DeleteObject", getReadOnlyObjectStatement(bucketName, objectPrefix), false}, // read-write bucket policy is expected to allow Delete Bucket operation on an anonymous request (Test cases 23). {"s3:DeleteObject", getReadWriteObjectStatement(bucketName, objectPrefix), true}, // write-only bucket policy is expected to allow Delete Object operation on an anonymous request (Test cases 24). {"s3:DeleteObject", getWriteOnlyObjectStatement(bucketName, objectPrefix), true}, // s3:AbortMultipartUpload is the action necessary to be present in the bucket policy to allow // cancelling or abortion of an already initiated multipart upload operation for an Anonymous/unsigned request. // Cases for testing AbortMultipartUpload access for different Bucket level access permissions (Test cases 25-27). // Any of the Bucket level access permissions shouldn't allow for AbortMultipartUpload operation on an Anonymous/unsigned request. {"s3:AbortMultipartUpload", getReadOnlyBucketStatement(bucketName, objectPrefix), false}, {"s3:AbortMultipartUpload", getReadWriteBucketStatement(bucketName, objectPrefix), false}, {"s3:AbortMultipartUpload", getWriteOnlyBucketStatement(bucketName, objectPrefix), false}, // Cases for testing AbortMultipartUpload access for different Object level access permissions. // read-only object policy is expected to not allow AbortMultipartUpload operation on an anonymous request (Test case 28). {"s3:AbortMultipartUpload", getReadOnlyObjectStatement(bucketName, objectPrefix), false}, // read-write object policy is expected to allow AbortMultipartUpload operation on an anonymous request (Test case 29). {"s3:AbortMultipartUpload", getReadWriteObjectStatement(bucketName, objectPrefix), true}, // write-only object policy is expected to allow AbortMultipartUpload operation on an anonymous request (Test case 30). {"s3:AbortMultipartUpload", getWriteOnlyObjectStatement(bucketName, objectPrefix), true}, // s3:PutObject is the action necessary to be present in the bucket policy to allow // uploading of an object for an Anonymous/unsigned request. // Cases for testing PutObject access for different Bucket level access permissions (Test cases 31-33). // Any of the Bucket level access permissions shouldn't allow for PutObject operation on an Anonymous/unsigned request. {"s3:PutObject", getReadOnlyBucketStatement(bucketName, objectPrefix), false}, {"s3:PutObject", getReadWriteBucketStatement(bucketName, objectPrefix), false}, {"s3:PutObject", getWriteOnlyBucketStatement(bucketName, objectPrefix), false}, // Cases for testing PutObject access for different Object level access permissions. // read-only object policy is expected to not allow PutObject operation on an anonymous request (Test case 34). {"s3:PutObject", getReadOnlyObjectStatement(bucketName, objectPrefix), false}, // read-write object policy is expected to allow PutObject operation on an anonymous request (Test case 35). {"s3:PutObject", getReadWriteObjectStatement(bucketName, objectPrefix), true}, // write-only object policy is expected to allow PutObject operation on an anonymous request (Test case 36). {"s3:PutObject", getWriteOnlyObjectStatement(bucketName, objectPrefix), true}, // s3:GetObject is the action necessary to be present in the bucket policy to allow // downloading of an object for an Anonymous/unsigned request. // Cases for testing GetObject access for different Bucket level access permissions (Test cases 37-39). // Any of the Bucket level access permissions shouldn't allow for GetObject operation on an Anonymous/unsigned request. {"s3:GetObject", getReadOnlyBucketStatement(bucketName, objectPrefix), false}, {"s3:GetObject", getReadWriteBucketStatement(bucketName, objectPrefix), false}, {"s3:GetObject", getWriteOnlyBucketStatement(bucketName, objectPrefix), false}, // Cases for testing GetObject access for different Object level access permissions. // read-only bucket policy is expected to allow downloading of an Object on an anonymous request (Test case 40). {"s3:GetObject", getReadOnlyObjectStatement(bucketName, objectPrefix), true}, // read-write bucket policy is expected to allow downloading of an Object on an anonymous request (Test case 41). {"s3:GetObject", getReadWriteObjectStatement(bucketName, objectPrefix), true}, // write-only bucket policy is expected to not allow downloading of an Object on an anonymous request (Test case 42). {"s3:GetObject", getWriteOnlyObjectStatement(bucketName, objectPrefix), false}, // s3:ListMultipartUploadParts is the action necessary to be present in the bucket policy to allow // Listing of uploaded parts for an Anonymous/unsigned request. // Any of the Bucket level access permissions shouldn't allow for ListMultipartUploadParts operation on an Anonymous/unsigned request. // read-only bucket policy is expected to not allow ListMultipartUploadParts operation on an anonymous request (Test cases 43-45). {"s3:ListMultipartUploadParts", getReadOnlyBucketStatement(bucketName, objectPrefix), false}, {"s3:ListMultipartUploadParts", getReadWriteBucketStatement(bucketName, objectPrefix), false}, {"s3:ListMultipartUploadParts", getWriteOnlyBucketStatement(bucketName, objectPrefix), false}, // read-only object policy is expected to not allow ListMultipartUploadParts operation on an anonymous request (Test case 46). {"s3:ListMultipartUploadParts", getReadOnlyObjectStatement(bucketName, objectPrefix), false}, // read-write object policy is expected to allow ListMultipartUploadParts operation on an anonymous request (Test case 47). {"s3:ListMultipartUploadParts", getReadWriteObjectStatement(bucketName, objectPrefix), true}, // write-only object policy is expected to allow ListMultipartUploadParts operation on an anonymous request (Test case 48). {"s3:ListMultipartUploadParts", getWriteOnlyObjectStatement(bucketName, objectPrefix), true}, } for i, testCase := range testCases { actualResult := bucketPolicyActionMatch(testCase.action, testCase.statement) if testCase.expectedResult != actualResult { t.Errorf("Test %d: Expected the result to be `%v`, but instead found it to be `%v`", i+1, testCase.expectedResult, actualResult) } } } // TestWildCardMatch - Tests validate the logic of wild card matching. // Its used to match the action and resources of the policy statement and the request. func TestWildCardMatch(t *testing.T) { testCases := []struct { pattern string text string expectedResult bool }{ // Test case - 1. // Test case with pattern "*". Expected to match any text. {"*", "s3:GetObject", true}, // Test case - 2. // Test case with empty pattern. This only matches empty string. {"", "s3:GetObject", false}, // Test case - 3. // Test case with empty pattern. This only matches empty string. {"", "", true}, // Test case - 4. // Test case with single "*" at the end. {"s3:*", "s3:ListMultipartUploadParts", true}, // Test case - 5. // Test case with a no "*". In this case the pattern and text should be the same. {"s3:ListBucketMultipartUploads", "s3:ListBucket", false}, // Test case - 6. // Test case with a no "*". In this case the pattern and text should be the same. {"s3:ListBucket", "s3:ListBucket", true}, // Test case - 7. // Test case with a no "*". In this case the pattern and text should be the same. {"s3:ListBucketMultipartUploads", "s3:ListBucketMultipartUploads", true}, // Test case - 8. // Test case with pattern containing key name with a prefix. Should accept the same text without a "*". {"my-bucket/oo*", "my-bucket/oo", true}, // Test case - 9. // Test case with "*" at the end of the pattern. {"my-bucket/In*", "my-bucket/India/Karnataka/", true}, // Test case - 10. // Test case with prefixes shuffled. // This should fail. {"my-bucket/In*", "my-bucket/Karnataka/India/", false}, // Test case - 11. // Test case with text expanded to the wildcards in the pattern. {"my-bucket/In*/Ka*/Ban", "my-bucket/India/Karnataka/Ban", true}, // Test case - 12. // Test case with the keyname part is repeated as prefix several times. // This is valid. {"my-bucket/In*/Ka*/Ban", "my-bucket/India/Karnataka/Ban/Ban/Ban/Ban/Ban", true}, // Test case - 13. // Test case to validate that `*` can be expanded into multiple prefixes. {"my-bucket/In*/Ka*/Ban", "my-bucket/India/Karnataka/Area1/Area2/Area3/Ban", true}, // Test case to validate that `*` can be expanded into multiple prefixes. {"my-bucket/In*/Ka*/Ban", "my-bucket/India/State1/State2/Karnataka/Area1/Area2/Area3/Ban", true}, // Test case - 14. // Test case where the keyname part of the pattern is expanded in the text. {"my-bucket/In*/Ka*/Ban", "my-bucket/India/Karnataka/Bangalore", false}, // Test case - 15. // Test case with prefixes and wildcard expanded for all "*". {"my-bucket/In*/Ka*/Ban*", "my-bucket/India/Karnataka/Bangalore", true}, // Test case - 16. // Test case with keyname part being a wildcard in the pattern. {"my-bucket/*", "my-bucket/India", true}, // Test case - 17. {"my-bucket/oo*", "my-bucket/odo", false}, } // Iterating over the test cases, call the function under test and asert the output. for i, testCase := range testCases { actualResult := wildCardMatch(testCase.pattern, testCase.text) if testCase.expectedResult != actualResult { t.Errorf("Test %d: Expected the result to be `%v`, but instead found it to be `%v`", i+1, testCase.expectedResult, actualResult) } } } // Wrapper for calling Put Bucket Policy HTTP handler tests for both XL multiple disks and single node setup. func TestPutBucketPolicyHandler(t *testing.T) { ExecObjectLayerTest(t, testPutBucketPolicyHandler) } // testPutBucketPolicyHandler - Test for Bucket policy end point. // TODO: Add exhaustive cases with various combination of statement fields. func testPutBucketPolicyHandler(obj ObjectLayer, instanceType string, t *testing.T) { // get random bucket name. bucketName := getRandomBucketName() // Create bucket. err := obj.MakeBucket(bucketName) if err != nil { // failed to create newbucket, abort. t.Fatalf("%s : %s", instanceType, err) } // Register the API end points with XL/FS object layer. apiRouter := initTestAPIEndPoints(obj, []string{"PutBucketPolicy"}) // initialize the server and obtain the credentials and root. // credentials are necessary to sign the HTTP request. credentials, rootPath, err := initTestConfig("us-east-1") if err != nil { t.Fatalf("Init Test config failed") } // remove the root folder after the test ends. defer removeAll(rootPath) // template for constructing HTTP request body for PUT bucket policy. bucketPolicyTemplate := `{ "Version": "2012-10-17", "Statement": [ { "Action": [ "s3:GetBucketLocation", "s3:ListBucket" ], "Effect": "Allow", "Principal": { "AWS": [ "*" ] }, "Resource": [ "arn:aws:s3:::%s" ] }, { "Action": [ "s3:GetObject" ], "Effect": "Allow", "Principal": { "AWS": [ "*" ] }, "Resource": [ "arn:aws:s3:::%s/this*" ] } ] }` // test cases with sample input and expected output. testCases := []struct { bucketName string accessKey string secretKey string // expected Response. expectedRespStatus int }{ {bucketName, credentials.AccessKeyID, credentials.SecretAccessKey, http.StatusNoContent}, } // Iterating over the test cases, calling the function under test and asserting the response. for i, testCase := range testCases { // obtain the put bucket policy request body. bucketPolicyStr := fmt.Sprintf(bucketPolicyTemplate, testCase.bucketName, testCase.bucketName) // initialize HTTP NewRecorder, this records any mutations to response writer inside the handler. rec := httptest.NewRecorder() // construct HTTP request for PUT bucket policy endpoint. req, err := newTestRequest("PUT", getPutPolicyURL("", testCase.bucketName), int64(len(bucketPolicyStr)), bytes.NewReader([]byte(bucketPolicyStr)), testCase.accessKey, testCase.secretKey) if err != nil { t.Fatalf("Test %d: Failed to create HTTP request for PutBucketPolicyHandler: %v", i+1, err) } // Since `apiRouter` satisfies `http.Handler` it has a ServeHTTP to execute the logic ofthe handler. // Call the ServeHTTP to execute the handler. apiRouter.ServeHTTP(rec, req) if rec.Code != testCase.expectedRespStatus { t.Errorf("Test %d: Expected the response status to be `%d`, but instead found `%d`", i+1, testCase.expectedRespStatus, rec.Code) } } } // Wrapper for calling Get Bucket Policy HTTP handler tests for both XL multiple disks and single node setup. func TestGetBucketPolicyHandler(t *testing.T) { ExecObjectLayerTest(t, testGetBucketPolicyHandler) } // testGetBucketPolicyHandler - Test for end point which fetches the access policy json of the given bucket. // TODO: Add exhaustive cases with various combination of statement fields. func testGetBucketPolicyHandler(obj ObjectLayer, instanceType string, t *testing.T) { // get random bucket name. bucketName := getRandomBucketName() // Create bucket. err := obj.MakeBucket(bucketName) if err != nil { // failed to create newbucket, abort. t.Fatalf("%s : %s", instanceType, err) } // Register the API end points with XL/FS object layer. // Registering only the PutBucketPolicy and GetBucketPolicy handlers. apiRouter := initTestAPIEndPoints(obj, []string{"PutBucketPolicy", "GetBucketPolicy"}) // initialize the server and obtain the credentials and root. // credentials are necessary to sign the HTTP request. credentials, rootPath, err := initTestConfig("us-east-1") if err != nil { t.Fatalf("Init Test config failed") } // remove the root folder after the test ends. defer removeAll(rootPath) // template for constructing HTTP request body for PUT bucket policy. bucketPolicyTemplate := `{ "Version": "2012-10-17", "Statement": [ { "Action": [ "s3:GetBucketLocation", "s3:ListBucket" ], "Effect": "Allow", "Principal": { "AWS": [ "*" ] }, "Resource": [ "arn:aws:s3:::%s" ] }, { "Action": [ "s3:GetObject" ], "Effect": "Allow", "Principal": { "AWS": [ "*" ] }, "Resource": [ "arn:aws:s3:::%s/this*" ] } ] }` // Writing bucket policy before running test on GetBucketPolicy. putTestPolicies := []struct { bucketName string accessKey string secretKey string // expected Response. expectedRespStatus int }{ {bucketName, credentials.AccessKeyID, credentials.SecretAccessKey, http.StatusNoContent}, } // Iterating over the cases and writing the bucket policy. // its required to write the policies first before running tests on GetBucketPolicy. for i, testPolicy := range putTestPolicies { // obtain the put bucket policy request body. bucketPolicyStr := fmt.Sprintf(bucketPolicyTemplate, testPolicy.bucketName, testPolicy.bucketName) // initialize HTTP NewRecorder, this records any mutations to response writer inside the handler. rec := httptest.NewRecorder() // construct HTTP request for PUT bucket policy endpoint. req, err := newTestRequest("PUT", getPutPolicyURL("", testPolicy.bucketName), int64(len(bucketPolicyStr)), bytes.NewReader([]byte(bucketPolicyStr)), testPolicy.accessKey, testPolicy.secretKey) if err != nil { t.Fatalf("Test %d: Failed to create HTTP request for PutBucketPolicyHandler: %v", i+1, err) } // Since `apiRouter` satisfies `http.Handler` it has a ServeHTTP to execute the logic ofthe handler. // Call the ServeHTTP to execute the handler. apiRouter.ServeHTTP(rec, req) if rec.Code != testPolicy.expectedRespStatus { t.Fatalf("Case %d: Expected the response status to be `%d`, but instead found `%d`", i+1, testPolicy.expectedRespStatus, rec.Code) } } // test cases with inputs and expected result for GetBucketPolicyHandler. testCases := []struct { bucketName string accessKey string secretKey string // expected output. expectedBucketPolicy string expectedRespStatus int }{ {bucketName, credentials.AccessKeyID, credentials.SecretAccessKey, bucketPolicyTemplate, http.StatusOK}, } // Iterating over the cases, fetching the policy and validating the response. for i, testCase := range testCases { // expected bucket policy json string. expectedBucketPolicyStr := fmt.Sprintf(testCase.expectedBucketPolicy, testCase.bucketName, testCase.bucketName) // initialize HTTP NewRecorder, this records any mutations to response writer inside the handler. rec := httptest.NewRecorder() // construct HTTP request for PUT bucket policy endpoint. req, err := newTestRequest("GET", getGetPolicyURL("", testCase.bucketName), 0, nil, testCase.accessKey, testCase.secretKey) if err != nil { t.Fatalf("Test %d: Failed to create HTTP request for GetBucketPolicyHandler: %v", i+1, err) } // Since `apiRouter` satisfies `http.Handler` it has a ServeHTTP to execute the logic of the handler. // Call the ServeHTTP to execute the handler, GetBucketPolicyHandler handles the request. apiRouter.ServeHTTP(rec, req) // Assert the response code with the expected status. if rec.Code != testCase.expectedRespStatus { t.Fatalf("Case %d: Expected the response status to be `%d`, but instead found `%d`", i+1, testCase.expectedRespStatus, rec.Code) } // read the response body. bucketPolicyReadBuf, err := ioutil.ReadAll(rec.Body) if err != nil { t.Fatalf("Test %d: %s: Failed parsing response body: %v", i+1, instanceType, err) } // Verify whether the bucket policy fetched is same as the one inserted. if expectedBucketPolicyStr != string(bucketPolicyReadBuf) { t.Errorf("Test %d: %s: Bucket policy differs from expected value.", i+1, instanceType) } } } // Wrapper for calling Delete Bucket Policy HTTP handler tests for both XL multiple disks and single node setup. func TestDeleteBucketPolicyHandler(t *testing.T) { ExecObjectLayerTest(t, testDeleteBucketPolicyHandler) } // testDeleteBucketPolicyHandler - Test for Delete bucket policy end point. // TODO: Add exhaustive cases with various combination of statement fields. func testDeleteBucketPolicyHandler(obj ObjectLayer, instanceType string, t *testing.T) { // get random bucket name. bucketName := getRandomBucketName() // Create bucket. err := obj.MakeBucket(bucketName) if err != nil { // failed to create newbucket, abort. t.Fatalf("%s : %s", instanceType, err) } // Register the API end points with XL/FS object layer. // Registering PutBucketPolicy and DeleteBucketPolicy handlers. apiRouter := initTestAPIEndPoints(obj, []string{"PutBucketPolicy", "DeleteBucketPolicy"}) // initialize the server and obtain the credentials and root. // credentials are necessary to sign the HTTP request. credentials, rootPath, err := initTestConfig("us-east-1") if err != nil { t.Fatalf("Init Test config failed") } // remove the root folder after the test ends. defer removeAll(rootPath) // template for constructing HTTP request body for PUT bucket policy. bucketPolicyTemplate := `{ "Version": "2012-10-17", "Statement": [ { "Action": [ "s3:GetBucketLocation", "s3:ListBucket" ], "Effect": "Allow", "Principal": { "AWS": [ "*" ] }, "Resource": [ "arn:aws:s3:::%s" ] }, { "Action": [ "s3:GetObject" ], "Effect": "Allow", "Principal": { "AWS": [ "*" ] }, "Resource": [ "arn:aws:s3:::%s/this*" ] } ] }` // Writing bucket policy before running test on DeleteBucketPolicy. putTestPolicies := []struct { bucketName string accessKey string secretKey string // expected Response. expectedRespStatus int }{ {bucketName, credentials.AccessKeyID, credentials.SecretAccessKey, http.StatusNoContent}, } // Iterating over the cases and writing the bucket policy. // its required to write the policies first before running tests on GetBucketPolicy. for i, testPolicy := range putTestPolicies { // obtain the put bucket policy request body. bucketPolicyStr := fmt.Sprintf(bucketPolicyTemplate, testPolicy.bucketName, testPolicy.bucketName) // initialize HTTP NewRecorder, this records any mutations to response writer inside the handler. rec := httptest.NewRecorder() // construct HTTP request for PUT bucket policy endpoint. req, err := newTestRequest("PUT", getPutPolicyURL("", testPolicy.bucketName), int64(len(bucketPolicyStr)), bytes.NewReader([]byte(bucketPolicyStr)), testPolicy.accessKey, testPolicy.secretKey) if err != nil { t.Fatalf("Test %d: Failed to create HTTP request for PutBucketPolicyHandler: %v", i+1, err) } // Since `apiRouter` satisfies `http.Handler` it has a ServeHTTP to execute the logic of the handler. // Call the ServeHTTP to execute the handler. apiRouter.ServeHTTP(rec, req) if rec.Code != testPolicy.expectedRespStatus { t.Fatalf("Case %d: Expected the response status to be `%d`, but instead found `%d`", i+1, testPolicy.expectedRespStatus, rec.Code) } } // testcases with input and expected output for DeleteBucketPolicyHandler. testCases := []struct { bucketName string accessKey string secretKey string // expected Response. expectedRespStatus int }{ {bucketName, credentials.AccessKeyID, credentials.SecretAccessKey, http.StatusNoContent}, } // Iterating over the cases and deleting the bucket policy and then asserting response. for i, testCase := range testCases { // initialize HTTP NewRecorder, this records any mutations to response writer inside the handler. rec := httptest.NewRecorder() // construct HTTP request for Delete bucket policy endpoint. req, err := newTestRequest("DELETE", getDeletePolicyURL("", testCase.bucketName), 0, nil, testCase.accessKey, testCase.secretKey) if err != nil { t.Fatalf("Test %d: Failed to create HTTP request for GetBucketPolicyHandler: %v", i+1, err) } // Since `apiRouter` satisfies `http.Handler` it has a ServeHTTP to execute the logic of the handler. // Call the ServeHTTP to execute the handler, DeleteBucketPolicyHandler handles the request. apiRouter.ServeHTTP(rec, req) // Assert the response code with the expected status. if rec.Code != testCase.expectedRespStatus { t.Fatalf("Case %d: Expected the response status to be `%d`, but instead found `%d`", i+1, testCase.expectedRespStatus, rec.Code) } } }