From 070d3610ffd6507ecd02da6f44f313444cf0f335 Mon Sep 17 00:00:00 2001 From: Karthic Rao Date: Sat, 15 Oct 2016 09:22:46 +0530 Subject: [PATCH] tests: V2 Signature tests for object-handlers. (#2931) --- cmd/object-handlers_test.go | 1029 +++++++++++++++++++++++------------ 1 file changed, 666 insertions(+), 363 deletions(-) diff --git a/cmd/object-handlers_test.go b/cmd/object-handlers_test.go index f3a3b4137..2162f7748 100644 --- a/cmd/object-handlers_test.go +++ b/cmd/object-handlers_test.go @@ -90,6 +90,8 @@ func testAPIGetObjectHandler(obj ObjectLayer, instanceType, bucketName string, a bucketName string objectName string byteRange string // range of bytes to be fetched from GetObject. + accessKey string + secretKey string // expected output. expectedContent []byte // expected response body. expectedRespStatus int // expected response status body. @@ -97,36 +99,48 @@ func testAPIGetObjectHandler(obj ObjectLayer, instanceType, bucketName string, a // Test case - 1. // Fetching the entire object and validating its contents. { - bucketName: bucketName, - objectName: objectName, - byteRange: "", + bucketName: bucketName, + objectName: objectName, + byteRange: "", + accessKey: credentials.AccessKeyID, + secretKey: credentials.SecretAccessKey, + expectedContent: bytesData[0].byteData, expectedRespStatus: http.StatusOK, }, // Test case - 2. // Case with non-existent object name. { - bucketName: bucketName, - objectName: "abcd", - byteRange: "", + bucketName: bucketName, + objectName: "abcd", + byteRange: "", + accessKey: credentials.AccessKeyID, + secretKey: credentials.SecretAccessKey, + expectedContent: encodeResponse(getAPIErrorResponse(getAPIError(ErrNoSuchKey), getGetObjectURL("", bucketName, "abcd"))), expectedRespStatus: http.StatusNotFound, }, // Test case - 3. // Requesting from range 10-100. { - bucketName: bucketName, - objectName: objectName, - byteRange: "bytes=10-100", + bucketName: bucketName, + objectName: objectName, + byteRange: "bytes=10-100", + accessKey: credentials.AccessKeyID, + secretKey: credentials.SecretAccessKey, + expectedContent: bytesData[0].byteData[10:101], expectedRespStatus: http.StatusPartialContent, }, // Test case - 4. // Test case with invalid range. { - bucketName: bucketName, - objectName: objectName, - byteRange: "bytes=-0", + bucketName: bucketName, + objectName: objectName, + byteRange: "bytes=-0", + accessKey: credentials.AccessKeyID, + secretKey: credentials.SecretAccessKey, + expectedContent: encodeResponse(getAPIErrorResponse(getAPIError(ErrInvalidRange), getGetObjectURL("", bucketName, objectName))), expectedRespStatus: http.StatusRequestedRangeNotSatisfiable, }, @@ -134,20 +148,37 @@ func testAPIGetObjectHandler(obj ObjectLayer, instanceType, bucketName string, a // Test case with byte range exceeding the object size. // Expected to read till end of the object. { - bucketName: bucketName, - objectName: objectName, - byteRange: "bytes=10-1000000000000000", + bucketName: bucketName, + objectName: objectName, + byteRange: "bytes=10-1000000000000000", + accessKey: credentials.AccessKeyID, + secretKey: credentials.SecretAccessKey, + expectedContent: bytesData[0].byteData[10:], expectedRespStatus: http.StatusPartialContent, }, + // Test case - 6. + // Test case to induce a signature mismatch. + // Using invalid accessID. + { + bucketName: bucketName, + objectName: objectName, + byteRange: "", + accessKey: "Invalid-AccessID", + secretKey: credentials.SecretAccessKey, + + expectedContent: encodeResponse(getAPIErrorResponse(getAPIError(ErrInvalidAccessKeyID), getGetObjectURL("", bucketName, objectName))), + expectedRespStatus: http.StatusForbidden, + }, } + // Iterating over the cases, fetching the object validating the 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 Get Object end point. req, err := newTestSignedRequestV4("GET", getGetObjectURL("", testCase.bucketName, testCase.objectName), - 0, nil, credentials.AccessKeyID, credentials.SecretAccessKey) + 0, nil, testCase.accessKey, testCase.secretKey) if err != nil { t.Fatalf("Test %d: Failed to create HTTP request for Get Object: %v", i+1, err) @@ -171,6 +202,38 @@ func testAPIGetObjectHandler(obj ObjectLayer, instanceType, bucketName string, a if !bytes.Equal(testCase.expectedContent, actualContent) { t.Errorf("Test %d: %s: Object content differs from expected value.: %s", i+1, instanceType, string(actualContent)) } + + // Verify response of the V2 signed HTTP request. + // initialize HTTP NewRecorder, this records any mutations to response writer inside the handler. + recV2 := httptest.NewRecorder() + // construct HTTP request for GET Object endpoint. + reqV2, err := newTestSignedRequestV2("GET", getGetObjectURL("", testCase.bucketName, testCase.objectName), + 0, nil, testCase.accessKey, testCase.secretKey) + + if err != nil { + t.Fatalf("Test %d: %s: Failed to create HTTP request for GetObject: %v", i+1, instanceType, err) + } + + if testCase.byteRange != "" { + reqV2.Header.Add("Range", testCase.byteRange) + } + + // 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(recV2, reqV2) + if recV2.Code != testCase.expectedRespStatus { + t.Errorf("Test %d: %s: Expected the response status to be `%d`, but instead found `%d`", i+1, instanceType, testCase.expectedRespStatus, recV2.Code) + } + + // read the response body. + actualContent, err = ioutil.ReadAll(recV2.Body) + if err != nil { + t.Fatalf("Test %d: %s: Failed parsing response body: %v", i+1, instanceType, err) + } + // Verify whether the bucket obtained object is same as the one created. + if !bytes.Equal(testCase.expectedContent, actualContent) { + t.Errorf("Test %d: %s: Object content differs from expected value.", i+1, instanceType) + } } // Test for Anonymous/unsigned http request. @@ -472,29 +535,44 @@ func testAPIPutObjectHandler(obj ObjectLayer, instanceType, bucketName string, a objectName string data []byte dataLen int + accessKey string + secretKey string // expected output. - expectedContent []byte // expected response body. - expectedRespStatus int // expected response status body. + expectedRespStatus int // expected response status body. }{ // Test case - 1. // Fetching the entire object and validating its contents. { - bucketName: bucketName, - objectName: objectName, - data: bytesData, - dataLen: len(bytesData), - expectedContent: []byte{}, + bucketName: bucketName, + objectName: objectName, + data: bytesData, + dataLen: len(bytesData), + accessKey: credentials.AccessKeyID, + secretKey: credentials.SecretAccessKey, + expectedRespStatus: http.StatusOK, }, + // Test case - 2. + // Test Case with invalid accessID. + { + bucketName: bucketName, + objectName: objectName, + data: bytesData, + dataLen: len(bytesData), + accessKey: "Wrong-AcessID", + secretKey: credentials.SecretAccessKey, + + expectedRespStatus: http.StatusForbidden, + }, } // Iterating over the cases, fetching the object validating the response. for i, testCase := range testCases { - var req *http.Request + var req, reqV2 *http.Request // initialize HTTP NewRecorder, this records any mutations to response writer inside the handler. rec := httptest.NewRecorder() // construct HTTP request for Get Object end point. req, err = newTestSignedRequestV4("PUT", getPutObjectURL("", testCase.bucketName, testCase.objectName), - int64(testCase.dataLen), bytes.NewReader(testCase.data), credentials.AccessKeyID, credentials.SecretAccessKey) + int64(testCase.dataLen), bytes.NewReader(testCase.data), testCase.accessKey, testCase.secretKey) if err != nil { t.Fatalf("Test %d: Failed to create HTTP request for Put Object: %v", i+1, err) } @@ -505,26 +583,50 @@ func testAPIPutObjectHandler(obj ObjectLayer, instanceType, bucketName string, a 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. - var actualContent []byte - actualContent, 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 obtained object is same as the one created. - if !bytes.Equal(testCase.expectedContent, actualContent) { - t.Errorf("Test %d: %s: Object content differs from expected value.: %s", i+1, instanceType, string(actualContent)) + if testCase.expectedRespStatus == http.StatusOK { + buffer := new(bytes.Buffer) + + // Fetch the object to check whether the content is same as the one uploaded via PutObject. + err = obj.GetObject(testCase.bucketName, testCase.objectName, 0, int64(len(bytesData)), buffer) + if err != nil { + t.Fatalf("Test %d: %s: Failed to fetch the copied object: %s", i+1, instanceType, err) + } + if !bytes.Equal(bytesData, buffer.Bytes()) { + t.Errorf("Test %d: %s: Data Mismatch: Data fetched back from the uploaded object doesn't match the original one.", i+1, instanceType) + } + buffer.Reset() } - buffer := new(bytes.Buffer) - err = obj.GetObject(testCase.bucketName, testCase.objectName, 0, int64(len(bytesData)), buffer) + // Verify response of the V2 signed HTTP request. + // initialize HTTP NewRecorder, this records any mutations to response writer inside the handler. + recV2 := httptest.NewRecorder() + // construct HTTP request for PUT Object endpoint. + reqV2, err = newTestSignedRequestV2("PUT", getPutObjectURL("", testCase.bucketName, testCase.objectName), + int64(testCase.dataLen), bytes.NewReader(testCase.data), testCase.accessKey, testCase.secretKey) + if err != nil { - t.Fatalf("Test %d: %s: Failed to fetch the copied object: %s", i+1, instanceType, err) + t.Fatalf("Test %d: %s: Failed to create HTTP request for PutObject: %v", i+1, instanceType, err) } - if !bytes.Equal(bytesData, buffer.Bytes()) { - t.Errorf("Test %d: %s: Data Mismatch: Data fetched back from the uploaded object doesn't match the original one.", i+1, instanceType) + + // 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(recV2, reqV2) + if recV2.Code != testCase.expectedRespStatus { + t.Errorf("Test %d: %s: Expected the response status to be `%d`, but instead found `%d`", i+1, instanceType, testCase.expectedRespStatus, recV2.Code) + } + + if testCase.expectedRespStatus == http.StatusOK { + buffer := new(bytes.Buffer) + // Fetch the object to check whether the content is same as the one uploaded via PutObject. + err = obj.GetObject(testCase.bucketName, testCase.objectName, 0, int64(len(bytesData)), buffer) + if err != nil { + t.Fatalf("Test %d: %s: Failed to fetch the copied object: %s", i+1, instanceType, err) + } + if !bytes.Equal(bytesData, buffer.Bytes()) { + t.Errorf("Test %d: %s: Data Mismatch: Data fetched back from the uploaded object doesn't match the original one.", i+1, instanceType) + } + buffer.Reset() } - buffer.Reset() } // Test for Anonymous/unsigned http request. @@ -621,31 +723,42 @@ func testAPICopyObjectHandler(obj ObjectLayer, instanceType, bucketName string, bucketName string newObjectName string // name of the newly copied object. copySourceHeader string // data for "X-Amz-Copy-Source" header. Contains the object to be copied in the URL. + accessKey string + secretKey string // expected output. expectedRespStatus int }{ // Test case - 1. { - bucketName: bucketName, - newObjectName: "newObject1", - copySourceHeader: url.QueryEscape("/" + bucketName + "/" + objectName), + bucketName: bucketName, + newObjectName: "newObject1", + copySourceHeader: url.QueryEscape("/" + bucketName + "/" + objectName), + accessKey: credentials.AccessKeyID, + secretKey: credentials.SecretAccessKey, + expectedRespStatus: http.StatusOK, }, // Test case - 2. // Test case with invalid source object. { - bucketName: bucketName, - newObjectName: "newObject1", - copySourceHeader: url.QueryEscape("/"), + bucketName: bucketName, + newObjectName: "newObject1", + copySourceHeader: url.QueryEscape("/"), + accessKey: credentials.AccessKeyID, + secretKey: credentials.SecretAccessKey, + expectedRespStatus: http.StatusBadRequest, }, // Test case - 3. // Test case with new object name is same as object to be copied. { - bucketName: bucketName, - newObjectName: objectName, - copySourceHeader: url.QueryEscape("/" + bucketName + "/" + objectName), + bucketName: bucketName, + newObjectName: objectName, + copySourceHeader: url.QueryEscape("/" + bucketName + "/" + objectName), + accessKey: credentials.AccessKeyID, + secretKey: credentials.SecretAccessKey, + expectedRespStatus: http.StatusBadRequest, }, // Test case - 4. @@ -653,9 +766,12 @@ func testAPICopyObjectHandler(obj ObjectLayer, instanceType, bucketName string, // Case for the purpose of failing `api.ObjectAPI.GetObjectInfo`. // Expecting the response status code to http.StatusNotFound (404). { - bucketName: bucketName, - newObjectName: objectName, - copySourceHeader: url.QueryEscape("/" + bucketName + "/" + "non-existent-object"), + bucketName: bucketName, + newObjectName: objectName, + copySourceHeader: url.QueryEscape("/" + bucketName + "/" + "non-existent-object"), + accessKey: credentials.AccessKeyID, + secretKey: credentials.SecretAccessKey, + expectedRespStatus: http.StatusNotFound, }, // Test case - 5. @@ -663,20 +779,34 @@ func testAPICopyObjectHandler(obj ObjectLayer, instanceType, bucketName string, // Case for the purpose of failing `api.ObjectAPI.PutObject`. // Expecting the response status code to http.StatusNotFound (404). { - bucketName: "non-existent-destination-bucket", - newObjectName: objectName, - copySourceHeader: url.QueryEscape("/" + bucketName + "/" + objectName), + bucketName: "non-existent-destination-bucket", + newObjectName: objectName, + copySourceHeader: url.QueryEscape("/" + bucketName + "/" + objectName), + accessKey: credentials.AccessKeyID, + secretKey: credentials.SecretAccessKey, + expectedRespStatus: http.StatusNotFound, }, + // Test case - 6. + // Case with invalid AccessKeyID. + { + bucketName: bucketName, + newObjectName: objectName, + copySourceHeader: url.QueryEscape("/" + bucketName + "/" + objectName), + accessKey: "Invalid-AccessID", + secretKey: credentials.SecretAccessKey, + + expectedRespStatus: http.StatusForbidden, + }, } for i, testCase := range testCases { - var req *http.Request + var req, reqV2 *http.Request // initialize HTTP NewRecorder, this records any mutations to response writer inside the handler. rec := httptest.NewRecorder() // construct HTTP request for copy object. req, err = newTestSignedRequestV4("PUT", getCopyObjectURL("", testCase.bucketName, testCase.newObjectName), - 0, nil, credentials.AccessKeyID, credentials.SecretAccessKey) + 0, nil, testCase.accessKey, testCase.secretKey) if err != nil { t.Fatalf("Test %d: Failed to create HTTP request for copy Object: %v", i+1, err) @@ -704,6 +834,31 @@ func testAPICopyObjectHandler(obj ObjectLayer, instanceType, bucketName string, } buffers[0].Reset() } + + // Verify response of the V2 signed HTTP request. + // initialize HTTP NewRecorder, this records any mutations to response writer inside the handler. + recV2 := httptest.NewRecorder() + + reqV2, err = newTestRequest("PUT", getCopyObjectURL("", testCase.bucketName, testCase.newObjectName), 0, nil) + if err != nil { + t.Fatalf("Test %d: Failed to create HTTP request for copy Object: %v", i+1, err) + } + // "X-Amz-Copy-Source" header contains the information about the source bucket and the object to copied. + if testCase.copySourceHeader != "" { + reqV2.Header.Set("X-Amz-Copy-Source", testCase.copySourceHeader) + } + + err = signRequestV2(reqV2, testCase.accessKey, testCase.secretKey) + + if err != nil { + t.Fatalf("Failed to V2 Sign the HTTP request: %v.", 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(recV2, reqV2) + if recV2.Code != testCase.expectedRespStatus { + t.Errorf("Test %d: %s: Expected the response status to be `%d`, but instead found `%d`", i+1, instanceType, testCase.expectedRespStatus, recV2.Code) + } } // Test for Anonymous/unsigned http request. @@ -759,12 +914,12 @@ func testAPINewMultipartHandler(obj ObjectLayer, instanceType, bucketName string objectName := "test-object-new-multipart" rec := httptest.NewRecorder() - // construct HTTP request for copy object. + // construct HTTP request for NewMultipart upload. req, err := newTestSignedRequestV4("POST", getNewMultipartURL("", bucketName, objectName), 0, nil, credentials.AccessKeyID, credentials.SecretAccessKey) if err != nil { - t.Fatalf("Failed to create HTTP request for copy Object: %v", err) + t.Fatalf("Failed to create HTTP request for NewMultipart Request: %v", err) } // Since `apiRouter` satisfies `http.Handler` it has a ServeHTTP to execute the logic of the handler. // Call the ServeHTTP to executes the registered handler. @@ -773,6 +928,7 @@ func testAPINewMultipartHandler(obj ObjectLayer, instanceType, bucketName string if rec.Code != http.StatusOK { t.Fatalf("%s: Expected the response status to be `%d`, but instead found `%d`", instanceType, http.StatusOK, rec.Code) } + // decode the response body. decoder := xml.NewDecoder(rec.Body) multipartResponse := &InitiateMultipartUploadResponse{} @@ -787,6 +943,78 @@ func testAPINewMultipartHandler(obj ObjectLayer, instanceType, bucketName string t.Fatalf("Invalid UploadID: %s", err) } + // Testing the response for Invalid AcccessID. + // Forcing the signature check to fail. + rec = httptest.NewRecorder() + // construct HTTP request for NewMultipart upload. + // Setting an invalid accessID. + req, err = newTestSignedRequestV4("POST", getNewMultipartURL("", bucketName, objectName), + 0, nil, "Invalid-AccessID", credentials.SecretAccessKey) + + if err != nil { + t.Fatalf("Failed to create HTTP request for NewMultipart Request: %v", err) + } + + // Since `apiRouter` satisfies `http.Handler` it has a ServeHTTP method to execute the logic of the handler. + // Call the ServeHTTP to executes the registered handler. + apiRouter.ServeHTTP(rec, req) + // Assert the response code with the expected status. + if rec.Code != http.StatusForbidden { + t.Fatalf("%s: Expected the response status to be `%d`, but instead found `%d`", instanceType, http.StatusForbidden, rec.Code) + } + + // Verify response of the V2 signed HTTP request. + // initialize HTTP NewRecorder, this records any mutations to response writer inside the handler. + recV2 := httptest.NewRecorder() + // construct HTTP request for NewMultipartUpload endpoint. + reqV2, err := newTestSignedRequestV2("POST", getNewMultipartURL("", bucketName, objectName), + 0, nil, credentials.AccessKeyID, credentials.SecretAccessKey) + + if err != nil { + t.Fatalf("Failed to create HTTP request for NewMultipart Request: %v", 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(recV2, reqV2) + // Assert the response code with the expected status. + if recV2.Code != http.StatusOK { + t.Fatalf("%s: Expected the response status to be `%d`, but instead found `%d`", instanceType, http.StatusOK, recV2.Code) + } + // decode the response body. + decoder = xml.NewDecoder(recV2.Body) + multipartResponse = &InitiateMultipartUploadResponse{} + + err = decoder.Decode(multipartResponse) + if err != nil { + t.Fatalf("Error decoding the recorded response Body") + } + // verify the uploadID my making an attempt to list parts. + _, err = obj.ListObjectParts(bucketName, objectName, multipartResponse.UploadID, 0, 1) + if err != nil { + t.Fatalf("Invalid UploadID: %s", err) + } + + // Testing the response for invalid AcccessID. + // Forcing the V2 signature check to fail. + recV2 = httptest.NewRecorder() + // construct HTTP request for NewMultipartUpload endpoint. + // Setting invalid AccessID. + reqV2, err = newTestSignedRequestV2("POST", getNewMultipartURL("", bucketName, objectName), + 0, nil, "Invalid-AccessID", credentials.SecretAccessKey) + + if err != nil { + t.Fatalf("Failed to create HTTP request for NewMultipart Request: %v", 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(recV2, reqV2) + // Assert the response code with the expected status. + if recV2.Code != http.StatusForbidden { + t.Fatalf("%s: Expected the response status to be `%d`, but instead found `%d`", instanceType, http.StatusForbidden, recV2.Code) + } + // Test for Anonymous/unsigned http request. anonReq, err := newTestRequest("POST", getNewMultipartURL("", bucketName, objectName), 0, nil) @@ -842,11 +1070,11 @@ func testAPINewMultipartHandlerParallel(obj ObjectLayer, instanceType, bucketNam go func() { defer wg.Done() rec := httptest.NewRecorder() - // construct HTTP request for copy object. + // construct HTTP request NewMultipartUpload. req, err := newTestSignedRequestV4("POST", getNewMultipartURL("", bucketName, objectName), 0, nil, credentials.AccessKeyID, credentials.SecretAccessKey) if err != nil { - t.Fatalf("Failed to create HTTP request for copy Object: %v", err) + t.Fatalf("Failed to create HTTP request for NewMultipart request: %v", err) } // Since `apiRouter` satisfies `http.Handler` it has a ServeHTTP to execute the logic of the handler. // Call the ServeHTTP to executes the registered handler. @@ -888,18 +1116,26 @@ func TestAPICompleteMultipartHandler(t *testing.T) { func testAPICompleteMultipartHandler(obj ObjectLayer, instanceType, bucketName string, apiRouter http.Handler, credentials credential, t *testing.T) { + var err error + // register event notifier. + err = initEventNotifier(obj) + + if err != nil { + t.Fatal("Notifier initialization failed.") + } + // Calculates MD5 sum of the given byte array. findMD5 := func(toBeHashed []byte) string { hasher := md5.New() hasher.Write(toBeHashed) return hex.EncodeToString(hasher.Sum(nil)) } + // object used for the test. objectName := "test-object-new-multipart" // uploadID obtained from NewMultipart upload. var uploadID string - var err error // upload IDs collected. var uploadIDs []string @@ -1008,7 +1244,7 @@ func testAPICompleteMultipartHandler(obj ObjectLayer, instanceType, bucketName s }, } - // on succesfull complete multipart operation the s3MD5 for the parts uploaded will be returned. + // on successfull complete multipart operation the s3MD5 for the parts uploaded will be returned. s3MD5, err := completeMultipartMD5(inputParts[3].parts...) if err != nil { t.Fatalf("Obtaining S3MD5 failed") @@ -1019,10 +1255,12 @@ func testAPICompleteMultipartHandler(obj ObjectLayer, instanceType, bucketName s encodedSuccessResponse := encodeResponse(successResponse) testCases := []struct { - bucket string - object string - uploadID string - parts []completePart + bucket string + object string + uploadID string + parts []completePart + accessKey string + secretKey string // Expected output of CompleteMultipartUpload. expectedContent []byte // Expected HTTP Response status. @@ -1031,10 +1269,13 @@ func testAPICompleteMultipartHandler(obj ObjectLayer, instanceType, bucketName s // Test case - 1. // Upload and PartNumber exists, But a deliberate ETag mismatch is introduced. { - bucket: bucketName, - object: objectName, - uploadID: uploadIDs[0], - parts: inputParts[0].parts, + bucket: bucketName, + object: objectName, + uploadID: uploadIDs[0], + parts: inputParts[0].parts, + accessKey: credentials.AccessKeyID, + secretKey: credentials.SecretAccessKey, + expectedContent: encodeResponse(getAPIErrorResponse(getAPIError(toAPIErrorCode(BadDigest{})), getGetObjectURL("", bucketName, objectName))), expectedRespStatus: http.StatusBadRequest, @@ -1043,10 +1284,13 @@ func testAPICompleteMultipartHandler(obj ObjectLayer, instanceType, bucketName s // No parts specified in completePart{}. // Should return ErrMalformedXML in the response body. { - bucket: bucketName, - object: objectName, - uploadID: uploadIDs[0], - parts: []completePart{}, + bucket: bucketName, + object: objectName, + uploadID: uploadIDs[0], + parts: []completePart{}, + accessKey: credentials.AccessKeyID, + secretKey: credentials.SecretAccessKey, + expectedContent: encodeResponse(getAPIErrorResponse(getAPIError(ErrMalformedXML), getGetObjectURL("", bucketName, objectName))), expectedRespStatus: http.StatusBadRequest, @@ -1055,10 +1299,13 @@ func testAPICompleteMultipartHandler(obj ObjectLayer, instanceType, bucketName s // Non-Existent uploadID. // 404 Not Found response status expected. { - bucket: bucketName, - object: objectName, - uploadID: "abc", - parts: inputParts[0].parts, + bucket: bucketName, + object: objectName, + uploadID: "abc", + parts: inputParts[0].parts, + accessKey: credentials.AccessKeyID, + secretKey: credentials.SecretAccessKey, + expectedContent: encodeResponse(getAPIErrorResponse(getAPIError(toAPIErrorCode(InvalidUploadID{UploadID: "abc"})), getGetObjectURL("", bucketName, objectName))), expectedRespStatus: http.StatusNotFound, @@ -1066,10 +1313,13 @@ func testAPICompleteMultipartHandler(obj ObjectLayer, instanceType, bucketName s // Test case - 4. // Case with part size being less than minimum allowed size. { - bucket: bucketName, - object: objectName, - uploadID: uploadIDs[0], - parts: inputParts[1].parts, + bucket: bucketName, + object: objectName, + uploadID: uploadIDs[0], + parts: inputParts[1].parts, + accessKey: credentials.AccessKeyID, + secretKey: credentials.SecretAccessKey, + expectedContent: encodeResponse(completeMultipartAPIError{int64(4), int64(5242880), 1, "e2fc714c4727ee9395f324cd2e7f331f", getAPIErrorResponse(getAPIError(toAPIErrorCode(PartTooSmall{PartNumber: 1})), getGetObjectURL("", bucketName, objectName))}), @@ -1078,10 +1328,13 @@ func testAPICompleteMultipartHandler(obj ObjectLayer, instanceType, bucketName s // Test case - 5. // TestCase with invalid Part Number. { - bucket: bucketName, - object: objectName, - uploadID: uploadIDs[0], - parts: inputParts[2].parts, + bucket: bucketName, + object: objectName, + uploadID: uploadIDs[0], + parts: inputParts[2].parts, + accessKey: credentials.AccessKeyID, + secretKey: credentials.SecretAccessKey, + expectedContent: encodeResponse(getAPIErrorResponse(getAPIError(toAPIErrorCode(InvalidPart{})), getGetObjectURL("", bucketName, objectName))), expectedRespStatus: http.StatusBadRequest, @@ -1090,10 +1343,13 @@ func testAPICompleteMultipartHandler(obj ObjectLayer, instanceType, bucketName s // Parts are not sorted according to the part number. // This should return ErrInvalidPartOrder in the response body. { - bucket: bucketName, - object: objectName, - uploadID: uploadIDs[0], - parts: inputParts[3].parts, + bucket: bucketName, + object: objectName, + uploadID: uploadIDs[0], + parts: inputParts[3].parts, + accessKey: credentials.AccessKeyID, + secretKey: credentials.SecretAccessKey, + expectedContent: encodeResponse(getAPIErrorResponse(getAPIError(ErrInvalidPartOrder), getGetObjectURL("", bucketName, objectName))), expectedRespStatus: http.StatusBadRequest, @@ -1102,10 +1358,28 @@ func testAPICompleteMultipartHandler(obj ObjectLayer, instanceType, bucketName s // Test case with proper parts. // Should successed and the content in the response body is asserted. { - bucket: bucketName, - object: objectName, - uploadID: uploadIDs[0], - parts: inputParts[4].parts, + bucket: bucketName, + object: objectName, + uploadID: uploadIDs[0], + parts: inputParts[4].parts, + accessKey: "Invalid-AccessID", + secretKey: credentials.SecretAccessKey, + + expectedContent: encodeResponse(getAPIErrorResponse(getAPIError(ErrInvalidAccessKeyID), + getGetObjectURL("", bucketName, objectName))), + expectedRespStatus: http.StatusForbidden, + }, + // Test case - 8. + // Test case with proper parts. + // Should successed and the content in the response body is asserted. + { + bucket: bucketName, + object: objectName, + uploadID: uploadIDs[0], + parts: inputParts[4].parts, + accessKey: credentials.AccessKeyID, + secretKey: credentials.SecretAccessKey, + expectedContent: encodedSuccessResponse, expectedRespStatus: http.StatusOK, }, @@ -1124,12 +1398,12 @@ func testAPICompleteMultipartHandler(obj ObjectLayer, instanceType, bucketName s } // Indicating that all parts are uploaded and initiating completeMultipartUpload. req, err = newTestSignedRequestV4("POST", getCompleteMultipartUploadURL("", bucketName, objectName, testCase.uploadID), - int64(len(completeBytes)), bytes.NewReader(completeBytes), credentials.AccessKeyID, credentials.SecretAccessKey) + int64(len(completeBytes)), bytes.NewReader(completeBytes), testCase.accessKey, testCase.secretKey) if err != nil { - t.Fatalf("Failed to create HTTP request for copy Object: %v", err) + t.Fatalf("Failed to create HTTP request for CompleteMultipartUpload: %v", err) } + rec := httptest.NewRecorder() - // construct HTTP request for copy object. // Since `apiRouter` satisfies `http.Handler` it has a ServeHTTP to execute the logic of the handler. // Call the ServeHTTP to executes the registered handler. @@ -1148,6 +1422,7 @@ func testAPICompleteMultipartHandler(obj ObjectLayer, instanceType, bucketName s if !bytes.Equal(testCase.expectedContent, actualContent) { t.Errorf("Test %d : Minio %s: Object content differs from expected value.", i+1, instanceType) } + } // Testing for anonymous API request. @@ -1247,6 +1522,8 @@ func testAPIDeleteObjectHandler(obj ObjectLayer, instanceType, bucketName string testCases := []struct { bucketName string objectName string + accessKey string + secretKey string expectedRespStatus int // expected response status body. }{ @@ -1256,6 +1533,8 @@ func testAPIDeleteObjectHandler(obj ObjectLayer, instanceType, bucketName string { bucketName: bucketName, objectName: objectName, + accessKey: credentials.AccessKeyID, + secretKey: credentials.SecretAccessKey, expectedRespStatus: http.StatusNoContent, }, @@ -1265,22 +1544,35 @@ func testAPIDeleteObjectHandler(obj ObjectLayer, instanceType, bucketName string { bucketName: bucketName, objectName: objectName, + accessKey: credentials.AccessKeyID, + secretKey: credentials.SecretAccessKey, expectedRespStatus: http.StatusNoContent, }, + // Test case - 3. + // Setting Invalid AccessKey to force signature check inside the handler to fail. + // Should return HTTP response status 403 forbidden. + { + bucketName: bucketName, + objectName: objectName, + accessKey: "Invalid-AccessKey", + secretKey: credentials.SecretAccessKey, + + expectedRespStatus: http.StatusForbidden, + }, } // Iterating over the cases, call DeleteObjectHandler and validate the HTTP response. for i, testCase := range testCases { - var req *http.Request + var req, reqV2 *http.Request // initialize HTTP NewRecorder, this records any mutations to response writer inside the handler. rec := httptest.NewRecorder() - // construct HTTP request for Get Object end point. + // construct HTTP request for Delete Object end point. req, err = newTestSignedRequestV4("DELETE", getDeleteObjectURL("", testCase.bucketName, testCase.objectName), - 0, nil, credentials.AccessKeyID, credentials.SecretAccessKey) + 0, nil, testCase.accessKey, testCase.secretKey) if err != nil { - t.Fatalf("Test %d: Failed to create HTTP request for Get Object: %v", i+1, err) + t.Fatalf("Test %d: Failed to create HTTP request for Delete Object: %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,`func (api objectAPIHandlers) DeleteObjectHandler` handles the request. @@ -1289,6 +1581,27 @@ func testAPIDeleteObjectHandler(obj ObjectLayer, instanceType, bucketName string if rec.Code != testCase.expectedRespStatus { t.Fatalf("Minio %s: Case %d: Expected the response status to be `%d`, but instead found `%d`", instanceType, i+1, testCase.expectedRespStatus, rec.Code) } + + // Verify response of the V2 signed HTTP request. + // initialize HTTP NewRecorder, this records any mutations to response writer inside the handler. + recV2 := httptest.NewRecorder() + // construct HTTP request for Delete Object endpoint. + reqV2, err = newTestSignedRequestV2("DELETE", getDeleteObjectURL("", testCase.bucketName, testCase.objectName), + 0, nil, testCase.accessKey, testCase.secretKey) + + if err != nil { + t.Fatalf("Failed to create HTTP request for NewMultipart Request: %v", 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(recV2, reqV2) + // Assert the response code with the expected status. + if recV2.Code != testCase.expectedRespStatus { + t.Errorf("Case %d: Minio %s: Expected the response status to be `%d`, but instead found `%d`", i+1, + instanceType, testCase.expectedRespStatus, recV2.Code) + } + } // Test for Anonymous/unsigned http request. @@ -1307,7 +1620,6 @@ func testAPIDeleteObjectHandler(obj ObjectLayer, instanceType, bucketName string // There is no need to use an existing bucket or valid input for creating the request, // since the `objectLayer==nil` check is performed before any other checks inside the handlers. // The only aim is to generate an HTTP request in a way that the relevant/registered end point is evoked/called. - // Indicating that all parts are uploaded and initiating completeMultipartUpload. nilBucket := "dummy-bucket" nilObject := "dummy-object" @@ -1370,65 +1682,6 @@ func testAPIPutObjectPartHandlerPreSign(obj ObjectLayer, instanceType, bucketNam } } -// TestAPIPutObjectPartHandlerV2 - Tests validate the response of PutObjectPart HTTP handler -// when the request signature type is V2. -func TestAPIPutObjectPartHandlerV2(t *testing.T) { - ExecObjectLayerAPITest(t, testAPIPutObjectPartHandlerV2, []string{"NewMultipart", "PutObjectPart"}) -} - -func testAPIPutObjectPartHandlerV2(obj ObjectLayer, instanceType, bucketName string, apiRouter http.Handler, - credentials credential, t *testing.T) { - testObject := "testobject" - rec := httptest.NewRecorder() - req, err := newTestSignedRequestV4("POST", getNewMultipartURL("", bucketName, "testobject"), - 0, nil, credentials.AccessKeyID, credentials.SecretAccessKey) - if err != nil { - t.Fatalf("[%s] - Failed to create a signed request to initiate multipart upload for %s/%s: %v", - instanceType, bucketName, testObject, err) - } - apiRouter.ServeHTTP(rec, req) - - // Get uploadID of the mulitpart upload initiated. - var mpartResp InitiateMultipartUploadResponse - mpartRespBytes, err := ioutil.ReadAll(rec.Result().Body) - if err != nil { - t.Fatalf("[%s] Failed to read NewMultipartUpload response %v", instanceType, err) - - } - err = xml.Unmarshal(mpartRespBytes, &mpartResp) - if err != nil { - t.Fatalf("[%s] Failed to unmarshal NewMultipartUpload response %v", instanceType, err) - } - - rec = httptest.NewRecorder() - req, err = newTestSignedRequestV2("PUT", - getPutObjectPartURL("", bucketName, testObject, mpartResp.UploadID, "1"), - int64(len("hello")), bytes.NewReader([]byte("hello")), credentials.AccessKeyID, credentials.SecretAccessKey) - if err != nil { - t.Fatalf("[%s] - Failed to create a signed request to initiate multipart upload for %s/%s: %v", - instanceType, bucketName, testObject, err) - } - signatureMismatchErr := getAPIError(ErrSignatureDoesNotMatch) - // Reset date field in header to make signature V2 fail. - req.Header.Set("x-amz-date", "") - apiRouter.ServeHTTP(rec, req) - errBytes, err := ioutil.ReadAll(rec.Result().Body) - if err != nil { - t.Fatalf("Test %d %s Failed to read error response from upload part request %s/%s: %v", - 1, instanceType, bucketName, testObject, err) - } - var errXML APIErrorResponse - err = xml.Unmarshal(errBytes, &errXML) - if err != nil { - t.Fatalf("Test %d %s Failed to unmarshal error response from upload part request %s/%s: %v", - 1, instanceType, bucketName, testObject, err) - } - if errXML.Code != signatureMismatchErr.Code { - t.Errorf("Test %d %s expected to fail with error %s, but received %s", 1, instanceType, - signatureMismatchErr.Code, errXML.Code) - } -} - // TestAPIPutObjectPartHandlerStreaming - Tests validate the response of PutObjectPart HTTP handler // when the request signature type is `streaming signature`. func TestAPIPutObjectPartHandlerStreaming(t *testing.T) { @@ -1552,156 +1805,246 @@ func testAPIPutObjectPartHandler(obj ObjectLayer, instanceType, bucketName strin invalidMaxParts := getAPIError(ErrInvalidMaxParts) // expected error the when the uploadID is invalid. noSuchUploadID := getAPIError(ErrNoSuchUpload) + // expected error when InvalidAccessID is set. + invalidAccessID := getAPIError(ErrInvalidAccessKeyID) // SignatureMismatch for various signing types testCases := []struct { - objectName string - reader io.ReadSeeker - partNumber string - fault Fault + objectName string + reader io.ReadSeeker + partNumber string + fault Fault + accessKey string + secretKey string + expectedAPIError APIError }{ // Test case - 1. // Success case. { - objectName: testObject, - reader: bytes.NewReader([]byte("hello")), - partNumber: "1", - fault: None, + objectName: testObject, + reader: bytes.NewReader([]byte("hello")), + partNumber: "1", + fault: None, + accessKey: credentials.AccessKeyID, + secretKey: credentials.SecretAccessKey, + expectedAPIError: noAPIErr, }, // Test case - 2. // Case where part number is invalid. { - objectName: testObject, - reader: bytes.NewReader([]byte("hello")), - partNumber: "9999999999999999999", - fault: None, + objectName: testObject, + reader: bytes.NewReader([]byte("hello")), + partNumber: "9999999999999999999", + fault: None, + accessKey: credentials.AccessKeyID, + secretKey: credentials.SecretAccessKey, + expectedAPIError: invalidPart, }, // Test case - 3. // Case where the part number has exceeded the max allowed parts in an upload. { - objectName: testObject, - reader: bytes.NewReader([]byte("hello")), - partNumber: strconv.Itoa(maxPartID + 1), - fault: None, + objectName: testObject, + reader: bytes.NewReader([]byte("hello")), + partNumber: strconv.Itoa(maxPartID + 1), + fault: None, + accessKey: credentials.AccessKeyID, + secretKey: credentials.SecretAccessKey, + expectedAPIError: invalidMaxParts, }, // Test case - 4. // Case where the content length is not set in the HTTP request. { - objectName: testObject, - reader: bytes.NewReader([]byte("hello")), - partNumber: "1", - fault: MissingContentLength, + objectName: testObject, + reader: bytes.NewReader([]byte("hello")), + partNumber: "1", + fault: MissingContentLength, + accessKey: credentials.AccessKeyID, + secretKey: credentials.SecretAccessKey, + expectedAPIError: missingContent, }, // Test case - 5. // case where the object size is set to a value greater than the max allowed size. { - objectName: testObject, - reader: bytes.NewReader([]byte("hello")), - partNumber: "1", - fault: TooBigObject, + objectName: testObject, + reader: bytes.NewReader([]byte("hello")), + partNumber: "1", + fault: TooBigObject, + accessKey: credentials.AccessKeyID, + secretKey: credentials.SecretAccessKey, + expectedAPIError: entityTooLarge, }, // Test case - 6. // case where a signature mismatch is introduced and the response is validated. { - objectName: testObject, - reader: bytes.NewReader([]byte("hello")), - partNumber: "1", - fault: BadSignature, + objectName: testObject, + reader: bytes.NewReader([]byte("hello")), + partNumber: "1", + fault: BadSignature, + accessKey: credentials.AccessKeyID, + secretKey: credentials.SecretAccessKey, + expectedAPIError: badSigning, }, // Test case - 7. // Case where incorrect checksum is set and the error response // is asserted with the expected error response. { - objectName: testObject, - reader: bytes.NewReader([]byte("hello")), - partNumber: "1", - fault: BadMD5, + objectName: testObject, + reader: bytes.NewReader([]byte("hello")), + partNumber: "1", + fault: BadMD5, + accessKey: credentials.AccessKeyID, + secretKey: credentials.SecretAccessKey, + expectedAPIError: badChecksum, }, // Test case - 8. // case where the a non-existent uploadID is set. { - objectName: testObject, - reader: bytes.NewReader([]byte("hello")), - partNumber: "1", - fault: MissingUploadID, + objectName: testObject, + reader: bytes.NewReader([]byte("hello")), + partNumber: "1", + fault: MissingUploadID, + accessKey: credentials.AccessKeyID, + secretKey: credentials.SecretAccessKey, + expectedAPIError: noSuchUploadID, }, + // Test case - 9. + // case with invalid AccessID. + // Forcing the signature check inside the handler to fail. + { + objectName: testObject, + reader: bytes.NewReader([]byte("hello")), + partNumber: "1", + fault: None, + accessKey: "Invalid-AccessID", + secretKey: credentials.SecretAccessKey, + + expectedAPIError: invalidAccessID, + }, + } + + reqV2Str := "V2 Signed HTTP request" + reqV4Str := "V4 Signed HTTP request" + + // collection of input HTTP request, ResponseRecorder and request type. + // Used to make a collection of V4 and V4 HTTP request. + type inputReqRec struct { + req *http.Request + rec *httptest.ResponseRecorder + reqType string } for i, test := range testCases { // Using sub-tests introduced in Go 1.7. - t.Run(fmt.Sprintf("Minio %s : Test case %d failed.", instanceType, i+1), func(t *testing.T) { + t.Run(fmt.Sprintf("Minio %s : Test case %d.", instanceType, i+1), func(t *testing.T) { - var req *http.Request - rec := httptest.NewRecorder() + var reqV4, reqV2 *http.Request + var recV4, recV2 *httptest.ResponseRecorder + + // initialize HTTP NewRecorder, this records any mutations to response writer inside the handler. + recV4 = httptest.NewRecorder() + recV2 = httptest.NewRecorder() // setting a non-existent uploadID. // deliberately introducing the invalid value to be able to assert the response with the expected error response. if test.fault == MissingUploadID { uploadID = "upload1" } // constructing a v4 signed HTTP request. - req, err = newTestSignedRequestV4("PUT", + reqV4, err = newTestSignedRequestV4("PUT", getPutObjectPartURL("", bucketName, test.objectName, uploadID, test.partNumber), - 0, test.reader, credentials.AccessKeyID, credentials.SecretAccessKey) + 0, test.reader, test.accessKey, test.secretKey) if err != nil { - t.Fatalf("Test %d %s Failed to create a signed request to upload part for %s/%s: %v", i+1, instanceType, + t.Fatalf("Failed to create a signed V4 request to upload part for %s/%s: %v", bucketName, test.objectName, err) } + // Verify response of the V2 signed HTTP request. + // construct HTTP request for PutObject Part Object endpoint. + reqV2, err = newTestSignedRequestV2("PUT", + getPutObjectPartURL("", bucketName, test.objectName, uploadID, test.partNumber), + 0, test.reader, test.accessKey, test.secretKey) - // introduce faults in the request. - // deliberately introducing the invalid value to be able to assert the response with the expected error response. - switch test.fault { - case MissingContentLength: - req.ContentLength = -1 - // Setting the content length to a value greater than the max allowed size of a part. - // Used in test case 4. - case TooBigObject: - req.ContentLength = maxObjectSize + 1 - // Malformed signature. - // Used in test case 6. - case BadSignature: - req.Header.Set("authorization", req.Header.Get("authorization")+"a") - // Setting an invalid Content-MD5 to force a Md5 Mismatch error. - // Used in tesr case 7. - case BadMD5: - req.Header.Set("Content-MD5", "badmd5") + if err != nil { + t.Fatalf("Test %d %s Failed to create a V2 signed request to upload part for %s/%s: %v", i+1, instanceType, + bucketName, test.objectName, err) } - // invoke the PutObjectPart HTTP handler. - apiRouter.ServeHTTP(rec, req) + // collection of input HTTP request, ResponseRecorder and request type. + reqRecs := []inputReqRec{ + { + req: reqV4, + rec: recV4, + reqType: reqV4Str, + }, + { + req: reqV2, + rec: recV2, + reqType: reqV2Str, + }, + } - // validate the error response. - if test.expectedAPIError != noAPIErr { - var errBytes []byte - // read the response body. - errBytes, err = ioutil.ReadAll(rec.Result().Body) - if err != nil { - t.Fatalf("Failed to read error response from upload part request \"%s\"/\"%s\": %v.", - bucketName, test.objectName, err) - } - // parse the XML error response. - var errXML APIErrorResponse - err = xml.Unmarshal(errBytes, &errXML) - if err != nil { - t.Fatalf("Failed to unmarshal error response from upload part request \"%s\"/\"%s\": %v.", - bucketName, test.objectName, err) - } - // Validate whether the error has occured for the expected reason. - if test.expectedAPIError.Code != errXML.Code { - t.Errorf("Expected to fail with error \"%s\", but received \"%s\".", - test.expectedAPIError.Code, errXML.Code) + for _, reqRec := range reqRecs { + // Response recorder to record the response of the handler. + rec := reqRec.rec + // HTTP request used to call the handler. + req := reqRec.req + // HTTP request type string for V4/V2 requests. + reqType := reqRec.reqType + + // introduce faults in the request. + // deliberately introducing the invalid value to be able to assert the response with the expected error response. + switch test.fault { + case MissingContentLength: + req.ContentLength = -1 + // Setting the content length to a value greater than the max allowed size of a part. + // Used in test case 4. + case TooBigObject: + req.ContentLength = maxObjectSize + 1 + // Malformed signature. + // Used in test case 6. + case BadSignature: + req.Header.Set("authorization", req.Header.Get("authorization")+"a") + // Setting an invalid Content-MD5 to force a Md5 Mismatch error. + // Used in tesr case 7. + case BadMD5: + req.Header.Set("Content-MD5", "badmd5") } - // Validate the HTTP response status code with the expected one. - if test.expectedAPIError.HTTPStatusCode != rec.Code { - t.Errorf("Expected the HTTP response status code to be %d, got %d.", test.expectedAPIError.HTTPStatusCode, rec.Code) + + // invoke the PutObjectPart HTTP handler. + apiRouter.ServeHTTP(rec, req) + + // validate the error response. + if test.expectedAPIError != noAPIErr { + var errBytes []byte + // read the response body. + errBytes, err = ioutil.ReadAll(rec.Result().Body) + if err != nil { + t.Fatalf("%s, Failed to read error response from upload part request \"%s\"/\"%s\": %v.", + reqType, bucketName, test.objectName, err) + } + // parse the XML error response. + var errXML APIErrorResponse + err = xml.Unmarshal(errBytes, &errXML) + if err != nil { + t.Fatalf("%s, Failed to unmarshal error response from upload part request \"%s\"/\"%s\": %v.", + reqType, bucketName, test.objectName, err) + } + // Validate whether the error has occured for the expected reason. + if test.expectedAPIError.Code != errXML.Code { + t.Errorf("%s, Expected to fail with error \"%s\", but received \"%s\".", + reqType, test.expectedAPIError.Code, errXML.Code) + } + // Validate the HTTP response status code with the expected one. + if test.expectedAPIError.HTTPStatusCode != rec.Code { + t.Errorf("%s, Expected the HTTP response status code to be %d, got %d.", reqType, test.expectedAPIError.HTTPStatusCode, rec.Code) + } } } }) @@ -1885,11 +2228,23 @@ func testAPIListObjectPartsHandler(obj ObjectLayer, instanceType, bucketName str }, } + // string to represent V2 signed HTTP request. + reqV2Str := "V2 Signed HTTP request" + // string to represent V4 signed HTTP request. + reqV4Str := "V4 Signed HTTP request" + // Collection of HTTP request and ResponseRecorder and request type string. + type inputReqRec struct { + req *http.Request + rec *httptest.ResponseRecorder + reqType string + } + for i, test := range testCases { - var req *http.Request + var reqV4, reqV2 *http.Request // Using sub-tests introduced in Go 1.7. t.Run(fmt.Sprintf("Minio %s: Test case %d failed.", instanceType, i+1), func(t *testing.T) { - rec := httptest.NewRecorder() + recV2 := httptest.NewRecorder() + recV4 := httptest.NewRecorder() // setting a non-existent uploadID. // deliberately introducing the invalid value to be able to assert the response with the expected error response. @@ -1898,48 +2253,79 @@ func testAPIListObjectPartsHandler(obj ObjectLayer, instanceType, bucketName str } // constructing a v4 signed HTTP request for ListMultipartUploads. - req, err = newTestSignedRequestV4("GET", + reqV4, err = newTestSignedRequestV4("GET", getListMultipartURLWithParams("", bucketName, testObject, uploadID, test.maxParts, test.partNumberMarker, ""), 0, nil, credentials.AccessKeyID, credentials.SecretAccessKey) if err != nil { - t.Fatalf("Failed to create a signed request to list object parts for %s/%s: %v.", + t.Fatalf("Failed to create a V4 signed request to list object parts for %s/%s: %v.", bucketName, testObject, err) } + // Verify response of the V2 signed HTTP request. + // construct HTTP request for PutObject Part Object endpoint. + reqV2, err = newTestSignedRequestV2("GET", + getListMultipartURLWithParams("", bucketName, testObject, uploadID, test.maxParts, test.partNumberMarker, ""), + 0, nil, credentials.AccessKeyID, credentials.SecretAccessKey) - // Malformed signature. - if test.fault == BadSignature { - req.Header.Set("authorization", req.Header.Get("authorization")+"a") + if err != nil { + t.Fatalf("Failed to create a V2 signed request to list object parts for %s/%s: %v.", + bucketName, testObject, err) } - // invoke the PutObjectPart HTTP handler with the given HTTP request. - apiRouter.ServeHTTP(rec, req) - - // validate the error response. - if test.expectedErr != noAPIErr { - - var errBytes []byte - // read the response body. - errBytes, err = ioutil.ReadAll(rec.Result().Body) - if err != nil { - t.Fatalf("Failed to read error response list object parts request %s/%s: %v", bucketName, testObject, err) - } - // parse the error response. - var errXML APIErrorResponse - err = xml.Unmarshal(errBytes, &errXML) - if err != nil { - t.Fatalf("Failed to unmarshal error response from list object partsest %s/%s: %v", - bucketName, testObject, err) - } - // Validate whether the error has occured for the expected reason. - if test.expectedErr.Code != errXML.Code { - t.Errorf("Expected to fail with %s but received %s", - test.expectedErr.Code, errXML.Code) + // collection of input HTTP request, ResponseRecorder and request type. + reqRecs := []inputReqRec{ + { + req: reqV4, + rec: recV4, + reqType: reqV4Str, + }, + { + req: reqV2, + rec: recV2, + reqType: reqV2Str, + }, + } + for _, reqRec := range reqRecs { + // Response recorder to record the response of the handler. + rec := reqRec.rec + // HTTP request used to call the handler. + req := reqRec.req + // HTTP request type string for V4/V2 requests. + reqType := reqRec.reqType + // Malformed signature. + if test.fault == BadSignature { + req.Header.Set("authorization", req.Header.Get("authorization")+"a") } - // in case error is not expected response status should be 200OK. - } else { - if rec.Code != http.StatusOK { - t.Errorf("Expected to succeed with response HTTP status 200OK, but failed with HTTP status code %d.", rec.Code) + + // invoke the PutObjectPart HTTP handler with the given HTTP request. + apiRouter.ServeHTTP(rec, req) + + // validate the error response. + if test.expectedErr != noAPIErr { + + var errBytes []byte + // read the response body. + errBytes, err = ioutil.ReadAll(rec.Result().Body) + if err != nil { + t.Fatalf("%s,Failed to read error response list object parts request %s/%s: %v", reqType, bucketName, testObject, err) + } + // parse the error response. + var errXML APIErrorResponse + err = xml.Unmarshal(errBytes, &errXML) + if err != nil { + t.Fatalf("%s, Failed to unmarshal error response from list object partsest %s/%s: %v", + reqType, bucketName, testObject, err) + } + // Validate whether the error has occured for the expected reason. + if test.expectedErr.Code != errXML.Code { + t.Errorf("%s, Expected to fail with %s but received %s", + reqType, test.expectedErr.Code, errXML.Code) + } + // in case error is not expected response status should be 200OK. + } else { + if rec.Code != http.StatusOK { + t.Errorf("%s, Expected to succeed with response HTTP status 200OK, but failed with HTTP status code %d.", reqType, rec.Code) + } } } }) @@ -1975,86 +2361,3 @@ func testAPIListObjectPartsHandler(obj ObjectLayer, instanceType, bucketName str // `ExecObjectLayerAPINilTest` sets the Object Layer to `nil` and calls the handler. ExecObjectLayerAPINilTest(t, nilBucket, nilObject, instanceType, apiRouter, nilReq) } - -// TestAPIListObjectPartsHandler - Tests validate the response of ListObjectParts HTTP handler -// when signature type of the HTTP request is `V2`. -func TestListObjectPartsHandlerV2(t *testing.T) { - ExecObjectLayerAPITest(t, testAPIListObjectPartsHandlerV2, []string{"PutObjectPart", "NewMultipart", "ListObjectParts"}) -} - -func testAPIListObjectPartsHandlerV2(obj ObjectLayer, instanceType, bucketName string, apiRouter http.Handler, - credentials credential, t *testing.T) { - testObject := "testobject" - rec := httptest.NewRecorder() - req, err := newTestSignedRequestV4("POST", getNewMultipartURL("", bucketName, testObject), - 0, nil, credentials.AccessKeyID, credentials.SecretAccessKey) - if err != nil { - t.Fatalf("[%s] - Failed to create a signed request to initiate multipart upload for %s/%s: %v", - instanceType, bucketName, testObject, err) - } - apiRouter.ServeHTTP(rec, req) - - // Get uploadID of the mulitpart upload initiated. - var mpartResp InitiateMultipartUploadResponse - mpartRespBytes, err := ioutil.ReadAll(rec.Result().Body) - if err != nil { - t.Fatalf("[%s] Failed to read NewMultipartUpload response %v", instanceType, err) - - } - err = xml.Unmarshal(mpartRespBytes, &mpartResp) - if err != nil { - t.Fatalf("[%s] Failed to unmarshal NewMultipartUpload response %v", instanceType, err) - } - - // Upload a part for listing purposes. - rec = httptest.NewRecorder() - req, err = newTestSignedRequestV4("PUT", - getPutObjectPartURL("", bucketName, testObject, mpartResp.UploadID, "1"), - int64(len("hello")), bytes.NewReader([]byte("hello")), credentials.AccessKeyID, credentials.SecretAccessKey) - if err != nil { - t.Fatalf("[%s] - Failed to create a signed request to initiate multipart upload for %s/%s: %v", - instanceType, bucketName, testObject, err) - } - apiRouter.ServeHTTP(rec, req) - - rec = httptest.NewRecorder() - req, err = newTestSignedRequestV2("GET", getListMultipartURLWithParams("", bucketName, testObject, mpartResp.UploadID, "", "", ""), - 0, nil, credentials.AccessKeyID, credentials.SecretAccessKey) - if err != nil { - t.Fatalf("[%s] - Failed to create a signed request to list object parts for %s/%s: %v", - instanceType, bucketName, testObject, err) - } - - apiRouter.ServeHTTP(rec, req) - if rec.Code != http.StatusOK { - t.Errorf("Test %d %s expected to succeed but failed with HTTP status code %d", 1, instanceType, rec.Code) - } - - // Simulate signature mismatch error for V2 request. - rec = httptest.NewRecorder() - req, err = newTestSignedRequestV2("GET", - getListMultipartURLWithParams("", bucketName, testObject, mpartResp.UploadID, "", "", ""), - 0, nil, credentials.AccessKeyID, credentials.SecretAccessKey) - if err != nil { - t.Fatalf("[%s] - Failed to create a signed request to list object parts for %s/%s: %v", - instanceType, bucketName, testObject, err) - } - signatureMismatchErr := getAPIError(ErrSignatureDoesNotMatch) - req.Header.Set("x-amz-date", "") - apiRouter.ServeHTTP(rec, req) - errBytes, err := ioutil.ReadAll(rec.Result().Body) - if err != nil { - t.Fatalf("Test %d %s Failed to read error response list object parts request %s/%s: %v", - 1, instanceType, bucketName, testObject, err) - } - var errXML APIErrorResponse - err = xml.Unmarshal(errBytes, &errXML) - if err != nil { - t.Fatalf("Test %d %s Failed to unmarshal error response from list object partsest %s/%s: %v", - 1, instanceType, bucketName, testObject, err) - } - if errXML.Code != signatureMismatchErr.Code { - t.Errorf("Test %d %s expected to fail with error %s, but received %s", 1, instanceType, - signatureMismatchErr.Code, errXML.Code) - } -}