diff --git a/cmd/object-handlers_test.go b/cmd/object-handlers_test.go index ee8b84596..bc71d07f7 100644 --- a/cmd/object-handlers_test.go +++ b/cmd/object-handlers_test.go @@ -1276,92 +1276,6 @@ func testAPIPutObjectPartHandlerStreaming(obj ObjectLayer, instanceType, bucketN } } -// TestAPIPutObjectPartHandlerAnon - Tests validate the response of PutObjectPart HTTP handler -// when the request type is anonymous/unsigned. -func TestAPIPutObjectPartHandlerAnon(t *testing.T) { - ExecObjectLayerAPITest(t, testAPIPutObjectPartHandlerAnon, []string{"PutObjectPart", "NewMultipart"}) -} - -func testAPIPutObjectPartHandlerAnon(obj ObjectLayer, instanceType, bucketName string, apiRouter http.Handler, - credentials credential, t *testing.T) { - // Initialize bucket policies for anonymous request test - err := initBucketPolicies(obj) - if err != nil { - t.Fatalf("Failed to initialize bucket policies: %v", err) - } - - 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) - - } - - accessDeniedErr := getAPIError(ErrAccessDenied) - anonRec := httptest.NewRecorder() - anonReq, aErr := newTestRequest("PUT", - getPutObjectPartURL("", bucketName, testObject, mpartResp.UploadID, "1"), - int64(len("hello")), bytes.NewReader([]byte("hello"))) - if aErr != nil { - t.Fatalf("Test %d %s Failed to create an anonymous request to upload part for %s/%s: %v", - 1, instanceType, bucketName, testObject, aErr) - } - apiRouter.ServeHTTP(anonRec, anonReq) - - anonErrBytes, err := ioutil.ReadAll(anonRec.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 anonErrXML APIErrorResponse - err = xml.Unmarshal(anonErrBytes, &anonErrXML) - 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 accessDeniedErr.Code != anonErrXML.Code { - t.Errorf("Test %d %s expected to fail with error %s, but received %s", 1, instanceType, - accessDeniedErr.Code, anonErrXML.Code) - } - - // Set write only policy on bucket to allow anonymous PutObjectPart API - // request to go through. - writeOnlyPolicy := bucketPolicy{ - Version: "1.0", - Statements: []policyStatement{getWriteOnlyObjectStatement(bucketName, "")}, - } - globalBucketPolicies.SetBucketPolicy(bucketName, &writeOnlyPolicy) - - anonRec = httptest.NewRecorder() - anonReq, aErr = newTestRequest("PUT", - getPutObjectPartURL("", bucketName, testObject, mpartResp.UploadID, "1"), - int64(len("hello")), bytes.NewReader([]byte("hello"))) - if aErr != nil { - t.Fatalf("Test %d %s Failed to create an anonymous request to upload part for %s/%s: %v", - 1, instanceType, bucketName, testObject, aErr) - } - apiRouter.ServeHTTP(anonRec, anonReq) - if anonRec.Code != http.StatusOK { - t.Errorf("Test %d %s expected PutObject Part with authAnonymous type to succeed but failed with "+ - "HTTP status code %d", 1, instanceType, anonRec.Code) - } -} - // TestAPIPutObjectPartHandler - Tests validate the response of PutObjectPart HTTP handler // for variety of inputs. func TestAPIPutObjectPartHandler(t *testing.T) { @@ -1383,6 +1297,8 @@ func testAPIPutObjectPartHandler(obj ObjectLayer, instanceType, bucketName strin t.Fatalf("Minio %s : %s", instanceType, err) } + uploadIDCopy := uploadID + // expected error types for invalid inputs to PutObjectPartHandler. noAPIErr := APIError{} // expected error when content length is missing in the HTTP request. @@ -1554,6 +1470,19 @@ func testAPIPutObjectPartHandler(obj ObjectLayer, instanceType, bucketName strin }) } + // Test for Anonymous/unsigned http request. + anonReq, err := newTestRequest("PUT", getPutObjectPartURL("", bucketName, testObject, uploadIDCopy, "1"), + int64(len("hello")), bytes.NewReader([]byte("hello"))) + if err != nil { + t.Fatalf("Minio %s: Failed to create an anonymous request to upload part for %s/%s: %v", + instanceType, bucketName, testObject, err) + } + + // ExecObjectLayerAPIAnonTest - Calls the HTTP API handler using the anonymous request, validates the ErrAccessDeniedResponse, + // sets the bucket policy using the policy statement generated from `getWriteOnlyObjectStatement` so that the + // unsigned request goes through and its validated again. + ExecObjectLayerAPIAnonTest(t, "TestAPIPutObjectPartHandler", bucketName, testObject, instanceType, apiRouter, anonReq, getWriteOnlyObjectStatement) + // HTTP request for testing when `ObjectLayer` is set to `nil`. // There is no need to use an existing bucket and valid input for creating the request // since the `objectLayer==nil` check is performed before any other checks inside the handlers. @@ -1561,8 +1490,7 @@ func testAPIPutObjectPartHandler(obj ObjectLayer, instanceType, bucketName strin nilBucket := "dummy-bucket" nilObject := "dummy-object" - nilReq, err := newTestSignedRequestV4("PUT", - getPutObjectPartURL("", nilBucket, nilObject, "0", "0"), + nilReq, err := newTestSignedRequestV4("PUT", getPutObjectPartURL("", nilBucket, nilObject, "0", "0"), 0, bytes.NewReader([]byte("testNilObjLayer")), "", "") if err != nil { @@ -1571,6 +1499,7 @@ func testAPIPutObjectPartHandler(obj ObjectLayer, instanceType, bucketName strin // execute the object layer set to `nil` test. // `ExecObjectLayerAPINilTest` manages the operation. ExecObjectLayerAPINilTest(t, nilBucket, nilObject, instanceType, apiRouter, nilReq) + } // TestAPIListObjectPartsHandlerPreSign - Tests validate the response of ListObjectParts HTTP handler @@ -1949,6 +1878,7 @@ func testAPIListObjectPartsHandlerAnon(obj ObjectLayer, instanceType, bucketName t.Fatalf("Test %d %s Failed to create a signed request to upload part for %s/%s: %v", 1, instanceType, bucketName, testObject, err) } + // Attempt an anonymous ListObjectParts API request to trigger AccessDenied error. accessDeniedErr := getAPIError(ErrAccessDenied) anonRec := httptest.NewRecorder() @@ -1959,6 +1889,7 @@ func testAPIListObjectPartsHandlerAnon(obj ObjectLayer, instanceType, bucketName t.Fatalf("Test %d %s Failed to create an anonymous request to list multipart of an upload for %s/%s: %v", 1, instanceType, bucketName, testObject, aErr) } + apiRouter.ServeHTTP(anonRec, anonReq) anonErrBytes, err := ioutil.ReadAll(anonRec.Result().Body) if err != nil { diff --git a/cmd/test-utils_test.go b/cmd/test-utils_test.go index 02e0fa9fb..9a8e9e56d 100644 --- a/cmd/test-utils_test.go +++ b/cmd/test-utils_test.go @@ -1387,6 +1387,76 @@ func initAPIHandlerTest(obj ObjectLayer, endPoints []string) (bucketName, rootPa return bucketName, rootPath, apiRouter, nil } +// ExecObjectLayerAPIAnonTest - Helper function to validate object Layer API handler response for anonymous/unsigned HTTP request. +// Here is the brief description of some of the arguments to the function below. +// apiRouter - http.Handler with the relevant API endPoint (API endPoint under test) registered. +// anonReq - unsigned *http.Request to invoke the handler's response for anonymous requests. +// policyFunc - function to return bucketPolicy statement which would permit the anonymous request to be served. +// The test works in 2 steps, here is the description of the steps. +// STEP 1: Call the handler with the unsigned HTTP request (anonReq), assert for the `ErrAccessDenied` error response. +// STEP 2: Set the policy to allow the unsigned request, use the policyFunc to obtain the relevant statement and call the handler again to verify its success. +func ExecObjectLayerAPIAnonTest(t *testing.T, testName, bucketName, objectName, instanceType string, apiRouter http.Handler, + anonReq *http.Request, policyFunc func(string, string) policyStatement) { + // simple function which ends the test by printing the common message which gives the context of the test + // and then followed by the the actual error message. + failTest := func(failMsg string) { + t.Fatalf("Minio %s: Anonymous HTTP request test Fail for \"%s\": \n %s.", instanceType, testName, failMsg) + } + // httptest Recorder to capture all the response by the http handler. + rec := httptest.NewRecorder() + // reading the body to preserve it so that it can be used again for second attempt of sending unsigned HTTP request. + // If the body is read in the handler the same request cannot be made use of. + buf, err := ioutil.ReadAll(anonReq.Body) + if err != nil { + failTest(err.Error()) + } + // creating 2 read closer (to set as request body) from the body content. + readerOne := ioutil.NopCloser(bytes.NewBuffer(buf)) + readerTwo := ioutil.NopCloser(bytes.NewBuffer(buf)) + + anonReq.Body = readerOne + + // call the HTTP handler. + apiRouter.ServeHTTP(rec, anonReq) + + // expected error response when the unsigned HTTP request is not permitted. + accesDeniedHTTPStatus := getAPIError(ErrAccessDenied).HTTPStatusCode + if rec.Code != accesDeniedHTTPStatus { + failTest(fmt.Sprintf("Object API Nil Test expected to fail with %d, but failed with %d.", accesDeniedHTTPStatus, rec.Code)) + } + + // expected error response in bytes when objectLayer is not initialized, or set to `nil`. + expectedErrResponse := encodeResponse(getAPIErrorResponse(getAPIError(ErrAccessDenied), getGetObjectURL("", bucketName, objectName))) + + // read the response body. + actualContent, err := ioutil.ReadAll(rec.Body) + if err != nil { + failTest(fmt.Sprintf("Failed parsing response body: %v.", err)) + } + // verify whether actual error response (from the response body), matches the expected error response. + if !bytes.Equal(expectedErrResponse, actualContent) { + failTest("Object content differs from expected value.") + } + + // Set write only policy on bucket to allow anonymous HTTP request for the operation under test. + // request to go through. + policy := bucketPolicy{ + Version: "1.0", + Statements: []policyStatement{policyFunc(bucketName, "")}, + } + globalBucketPolicies.SetBucketPolicy(bucketName, &policy) + // now call the handler again with the unsigned/anonymous request, it should be accepted. + rec = httptest.NewRecorder() + + anonReq.Body = readerTwo + + apiRouter.ServeHTTP(rec, anonReq) + if rec.Code != http.StatusOK { + failTest(fmt.Sprintf("Expected the anonymous HTTP request to be served after the policy changes\n,Expected response HTTP status code to be %d, got %d.", + http.StatusOK, rec.Code)) + } +} + // ExecObjectLayerAPINilTest - Sets the object layer to `nil`, and calls rhe registered object layer API endpoint, and assert the error response. // The purpose is to validate the API handlers response when the object layer is uninitialized. // Usage hint: Should be used at the end of the API end points tests (ex: check the last few lines of `testAPIListObjectPartsHandler`), need a sample HTTP request @@ -1606,19 +1676,6 @@ func registerAPIFunctions(muxRouter *router.Router, objLayer ObjectLayer, apiFun registerBucketLevelFunc(bucketRouter, api, apiFunctions...) } -// Returns a http.Handler capable of routing API requests to handlers corresponding to apiFunctions, -// with ObjectAPI set to nil. -func initTestNilObjAPIEndPoints(apiFunctions []string) http.Handler { - muxRouter := router.NewRouter() - if len(apiFunctions) > 0 { - // Iterate the list of API functions requested for and register them in mux HTTP handler. - registerAPIFunctions(muxRouter, nil, apiFunctions...) - return muxRouter - } - registerAPIRouter(muxRouter) - return muxRouter -} - // Takes in XL/FS object layer, and the list of API end points to be tested/required, registers the API end points and returns the HTTP handler. // Need isolated registration of API end points while writing unit tests for end points. // All the API end points are registered only for the default case.