storj.io/minio@v0.0.0-20230509071714-0cbc90f649b1/cmd/bucket-handlers_test.go (about) 1 /* 2 * MinIO Cloud Storage, (C) 2016, 2017, 2018 MinIO, Inc. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package cmd 18 19 import ( 20 "bytes" 21 "encoding/xml" 22 "io/ioutil" 23 "net/http" 24 "net/http/httptest" 25 "strconv" 26 "testing" 27 28 "storj.io/minio/pkg/auth" 29 ) 30 31 // Wrapper for calling RemoveBucket HTTP handler tests for both Erasure multiple disks and single node setup. 32 func TestRemoveBucketHandler(t *testing.T) { 33 ExecObjectLayerAPITest(t, testRemoveBucketHandler, []string{"RemoveBucket"}) 34 } 35 36 func testRemoveBucketHandler(obj ObjectLayer, instanceType, bucketName string, apiRouter http.Handler, 37 credentials auth.Credentials, t *testing.T) { 38 _, err := obj.PutObject(GlobalContext, bucketName, "test-object", mustGetPutObjReader(t, bytes.NewReader([]byte{}), int64(0), "", "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"), ObjectOptions{}) 39 // if object upload fails stop the test. 40 if err != nil { 41 t.Fatalf("Error uploading object: <ERROR> %v", err) 42 } 43 44 // initialize httptest Recorder, this records any mutations to response writer inside the handler. 45 rec := httptest.NewRecorder() 46 // construct HTTP request for DELETE bucket. 47 req, err := newTestSignedRequestV4(http.MethodDelete, getBucketLocationURL("", bucketName), 0, nil, credentials.AccessKey, credentials.SecretKey, nil) 48 if err != nil { 49 t.Fatalf("Test %s: Failed to create HTTP request for RemoveBucketHandler: <ERROR> %v", instanceType, err) 50 } 51 // Since `apiRouter` satisfies `http.Handler` it has a ServeHTTP to execute the logic of the handler. 52 // Call the ServeHTTP to execute the handler. 53 apiRouter.ServeHTTP(rec, req) 54 switch rec.Code { 55 case http.StatusOK, http.StatusCreated, http.StatusAccepted, http.StatusNoContent: 56 t.Fatalf("Test %v: expected failure, but succeeded with %v", instanceType, rec.Code) 57 } 58 59 // Verify response of the V2 signed HTTP request. 60 // initialize HTTP NewRecorder, this records any mutations to response writer inside the handler. 61 recV2 := httptest.NewRecorder() 62 // construct HTTP request for DELETE bucket. 63 reqV2, err := newTestSignedRequestV2(http.MethodDelete, getBucketLocationURL("", bucketName), 0, nil, credentials.AccessKey, credentials.SecretKey, nil) 64 if err != nil { 65 t.Fatalf("Test %s: Failed to create HTTP request for RemoveBucketHandler: <ERROR> %v", instanceType, err) 66 } 67 // Since `apiRouter` satisfies `http.Handler` it has a ServeHTTP to execute the logic of the handler. 68 // Call the ServeHTTP to execute the handler. 69 apiRouter.ServeHTTP(recV2, reqV2) 70 switch recV2.Code { 71 case http.StatusOK, http.StatusCreated, http.StatusAccepted, http.StatusNoContent: 72 t.Fatalf("Test %v: expected failure, but succeeded with %v", instanceType, recV2.Code) 73 } 74 } 75 76 // Wrapper for calling GetBucketPolicy HTTP handler tests for both Erasure multiple disks and single node setup. 77 func TestGetBucketLocationHandler(t *testing.T) { 78 ExecObjectLayerAPITest(t, testGetBucketLocationHandler, []string{"GetBucketLocation"}) 79 } 80 81 func testGetBucketLocationHandler(obj ObjectLayer, instanceType, bucketName string, apiRouter http.Handler, 82 credentials auth.Credentials, t *testing.T) { 83 84 // test cases with sample input and expected output. 85 testCases := []struct { 86 bucketName string 87 accessKey string 88 secretKey string 89 // expected Response. 90 expectedRespStatus int 91 locationResponse []byte 92 errorResponse APIErrorResponse 93 shouldPass bool 94 }{ 95 // Test case - 1. 96 // Tests for authenticated request and proper response. 97 { 98 bucketName: bucketName, 99 accessKey: credentials.AccessKey, 100 secretKey: credentials.SecretKey, 101 expectedRespStatus: http.StatusOK, 102 locationResponse: []byte(`<?xml version="1.0" encoding="UTF-8"?> 103 <LocationConstraint xmlns="http://s3.amazonaws.com/doc/2006-03-01/"></LocationConstraint>`), 104 errorResponse: APIErrorResponse{}, 105 shouldPass: true, 106 }, 107 // Test case - 2. 108 // Tests for signature mismatch error. 109 { 110 bucketName: bucketName, 111 accessKey: "abcd", 112 secretKey: "abcd", 113 expectedRespStatus: http.StatusForbidden, 114 locationResponse: []byte(""), 115 errorResponse: APIErrorResponse{ 116 Resource: SlashSeparator + bucketName + SlashSeparator, 117 Code: "InvalidAccessKeyId", 118 Message: "The Access Key Id you provided does not exist in our records.", 119 }, 120 shouldPass: false, 121 }, 122 } 123 124 for i, testCase := range testCases { 125 if i != 1 { 126 continue 127 } 128 // initialize httptest Recorder, this records any mutations to response writer inside the handler. 129 rec := httptest.NewRecorder() 130 // construct HTTP request for Get bucket location. 131 req, err := newTestSignedRequestV4(http.MethodGet, getBucketLocationURL("", testCase.bucketName), 0, nil, testCase.accessKey, testCase.secretKey, nil) 132 if err != nil { 133 t.Fatalf("Test %d: %s: Failed to create HTTP request for GetBucketLocationHandler: <ERROR> %v", i+1, instanceType, err) 134 } 135 // Since `apiRouter` satisfies `http.Handler` it has a ServeHTTP to execute the logic of the handler. 136 // Call the ServeHTTP to execute the handler. 137 apiRouter.ServeHTTP(rec, req) 138 if rec.Code != testCase.expectedRespStatus { 139 t.Errorf("Test %d: %s: Expected the response status to be `%d`, but instead found `%d`", i+1, instanceType, testCase.expectedRespStatus, rec.Code) 140 } 141 if !bytes.Equal(testCase.locationResponse, rec.Body.Bytes()) && testCase.shouldPass { 142 t.Errorf("Test %d: %s: Expected the response to be `%s`, but instead found `%s`", i+1, instanceType, string(testCase.locationResponse), rec.Body.String()) 143 } 144 errorResponse := APIErrorResponse{} 145 err = xml.Unmarshal(rec.Body.Bytes(), &errorResponse) 146 if err != nil && !testCase.shouldPass { 147 t.Fatalf("Test %d: %s: Unable to marshal response body %s", i+1, instanceType, rec.Body.String()) 148 } 149 if errorResponse.Resource != testCase.errorResponse.Resource { 150 t.Errorf("Test %d: %s: Expected the error resource to be `%s`, but instead found `%s`", i+1, instanceType, testCase.errorResponse.Resource, errorResponse.Resource) 151 } 152 if errorResponse.Message != testCase.errorResponse.Message { 153 t.Errorf("Test %d: %s: Expected the error message to be `%s`, but instead found `%s`", i+1, instanceType, testCase.errorResponse.Message, errorResponse.Message) 154 } 155 if errorResponse.Code != testCase.errorResponse.Code { 156 t.Errorf("Test %d: %s: Expected the error code to be `%s`, but instead found `%s`", i+1, instanceType, testCase.errorResponse.Code, errorResponse.Code) 157 } 158 159 // Verify response of the V2 signed HTTP request. 160 // initialize HTTP NewRecorder, this records any mutations to response writer inside the handler. 161 recV2 := httptest.NewRecorder() 162 // construct HTTP request for PUT bucket policy endpoint. 163 reqV2, err := newTestSignedRequestV2(http.MethodGet, getBucketLocationURL("", testCase.bucketName), 0, nil, testCase.accessKey, testCase.secretKey, nil) 164 165 if err != nil { 166 t.Fatalf("Test %d: %s: Failed to create HTTP request for PutBucketPolicyHandler: <ERROR> %v", i+1, instanceType, err) 167 } 168 // Since `apiRouter` satisfies `http.Handler` it has a ServeHTTP to execute the logic of the handler. 169 // Call the ServeHTTP to execute the handler. 170 apiRouter.ServeHTTP(recV2, reqV2) 171 if recV2.Code != testCase.expectedRespStatus { 172 t.Errorf("Test %d: %s: Expected the response status to be `%d`, but instead found `%d`", i+1, instanceType, testCase.expectedRespStatus, recV2.Code) 173 } 174 175 errorResponse = APIErrorResponse{} 176 err = xml.Unmarshal(recV2.Body.Bytes(), &errorResponse) 177 if err != nil && !testCase.shouldPass { 178 t.Fatalf("Test %d: %s: Unable to marshal response body %s", i+1, instanceType, recV2.Body.String()) 179 } 180 if errorResponse.Resource != testCase.errorResponse.Resource { 181 t.Errorf("Test %d: %s: Expected the error resource to be `%s`, but instead found `%s`", i+1, instanceType, testCase.errorResponse.Resource, errorResponse.Resource) 182 } 183 if errorResponse.Message != testCase.errorResponse.Message { 184 t.Errorf("Test %d: %s: Expected the error message to be `%s`, but instead found `%s`", i+1, instanceType, testCase.errorResponse.Message, errorResponse.Message) 185 } 186 if errorResponse.Code != testCase.errorResponse.Code { 187 t.Errorf("Test %d: %s: Expected the error code to be `%s`, but instead found `%s`", i+1, instanceType, testCase.errorResponse.Code, errorResponse.Code) 188 } 189 190 } 191 192 // Test for Anonymous/unsigned http request. 193 // ListBucketsHandler doesn't support bucket policies, setting the policies shouldn't make any difference. 194 anonReq, err := newTestRequest(http.MethodGet, getBucketLocationURL("", bucketName), 0, nil) 195 if err != nil { 196 t.Fatalf("MinIO %s: Failed to create an anonymous request.", instanceType) 197 } 198 199 // ExecObjectLayerAPIAnonTest - Calls the HTTP API handler using the anonymous request, validates the ErrAccessDeniedResponse, 200 // sets the bucket policy using the policy statement generated from `getReadOnlyBucketStatement` so that the 201 // unsigned request goes through and its validated again. 202 ExecObjectLayerAPIAnonTest(t, obj, "TestGetBucketLocationHandler", bucketName, "", instanceType, apiRouter, anonReq, getAnonReadOnlyBucketPolicy(bucketName)) 203 204 // HTTP request for testing when `objectLayer` is set to `nil`. 205 // There is no need to use an existing bucket and valid input for creating the request 206 // since the `objectLayer==nil` check is performed before any other checks inside the handlers. 207 // The only aim is to generate an HTTP request in a way that the relevant/registered end point is evoked/called. 208 209 nilBucket := "dummy-bucket" 210 nilReq, err := newTestRequest(http.MethodGet, getBucketLocationURL("", nilBucket), 0, nil) 211 212 if err != nil { 213 t.Errorf("MinIO %s: Failed to create HTTP request for testing the response when object Layer is set to `nil`.", instanceType) 214 } 215 // Executes the object layer set to `nil` test. 216 // `ExecObjectLayerAPINilTest` manages the operation. 217 ExecObjectLayerAPINilTest(t, nilBucket, "", instanceType, apiRouter, nilReq) 218 } 219 220 // Wrapper for calling HeadBucket HTTP handler tests for both Erasure multiple disks and single node setup. 221 func TestHeadBucketHandler(t *testing.T) { 222 ExecObjectLayerAPITest(t, testHeadBucketHandler, []string{"HeadBucket"}) 223 } 224 225 func testHeadBucketHandler(obj ObjectLayer, instanceType, bucketName string, apiRouter http.Handler, 226 credentials auth.Credentials, t *testing.T) { 227 228 // test cases with sample input and expected output. 229 testCases := []struct { 230 bucketName string 231 accessKey string 232 secretKey string 233 // expected Response. 234 expectedRespStatus int 235 }{ 236 // Test case - 1. 237 // Bucket exists. 238 { 239 bucketName: bucketName, 240 accessKey: credentials.AccessKey, 241 secretKey: credentials.SecretKey, 242 expectedRespStatus: http.StatusOK, 243 }, 244 // Test case - 2. 245 // Non-existent bucket name. 246 { 247 bucketName: "2333", 248 accessKey: credentials.AccessKey, 249 secretKey: credentials.SecretKey, 250 expectedRespStatus: http.StatusNotFound, 251 }, 252 // Test case - 3. 253 // Testing for signature mismatch error. 254 // setting invalid acess and secret key. 255 { 256 bucketName: bucketName, 257 accessKey: "abcd", 258 secretKey: "abcd", 259 expectedRespStatus: http.StatusForbidden, 260 }, 261 } 262 263 for i, testCase := range testCases { 264 // initialize HTTP NewRecorder, this records any mutations to response writer inside the handler. 265 rec := httptest.NewRecorder() 266 // construct HTTP request for HEAD bucket. 267 req, err := newTestSignedRequestV4(http.MethodHead, getHEADBucketURL("", testCase.bucketName), 0, nil, testCase.accessKey, testCase.secretKey, nil) 268 if err != nil { 269 t.Fatalf("Test %d: %s: Failed to create HTTP request for HeadBucketHandler: <ERROR> %v", i+1, instanceType, err) 270 } 271 // Since `apiRouter` satisfies `http.Handler` it has a ServeHTTP to execute the logic of the handler. 272 // Call the ServeHTTP to execute the handler. 273 apiRouter.ServeHTTP(rec, req) 274 if rec.Code != testCase.expectedRespStatus { 275 t.Errorf("Test %d: %s: Expected the response status to be `%d`, but instead found `%d`", i+1, instanceType, testCase.expectedRespStatus, rec.Code) 276 } 277 278 // Verify response the V2 signed HTTP request. 279 // initialize HTTP NewRecorder, this records any mutations to response writer inside the handler. 280 recV2 := httptest.NewRecorder() 281 // construct HTTP request for PUT bucket policy endpoint. 282 reqV2, err := newTestSignedRequestV2(http.MethodHead, getHEADBucketURL("", testCase.bucketName), 0, nil, testCase.accessKey, testCase.secretKey, nil) 283 284 if err != nil { 285 t.Fatalf("Test %d: %s: Failed to create HTTP request for PutBucketPolicyHandler: <ERROR> %v", i+1, instanceType, err) 286 } 287 // Since `apiRouter` satisfies `http.Handler` it has a ServeHTTP to execute the logic of the handler. 288 // Call the ServeHTTP to execute the handler. 289 apiRouter.ServeHTTP(recV2, reqV2) 290 if recV2.Code != testCase.expectedRespStatus { 291 t.Errorf("Test %d: %s: Expected the response status to be `%d`, but instead found `%d`", i+1, instanceType, testCase.expectedRespStatus, recV2.Code) 292 } 293 294 } 295 296 // Test for Anonymous/unsigned http request. 297 anonReq, err := newTestRequest(http.MethodHead, getHEADBucketURL("", bucketName), 0, nil) 298 299 if err != nil { 300 t.Fatalf("MinIO %s: Failed to create an anonymous request for bucket \"%s\": <ERROR> %v", 301 instanceType, bucketName, err) 302 } 303 304 // ExecObjectLayerAPIAnonTest - Calls the HTTP API handler using the anonymous request, validates the ErrAccessDeniedResponse, 305 // sets the bucket policy using the policy statement generated from `getReadOnlyBucketStatement` so that the 306 // unsigned request goes through and its validated again. 307 ExecObjectLayerAPIAnonTest(t, obj, "TestHeadBucketHandler", bucketName, "", instanceType, apiRouter, anonReq, getAnonReadOnlyBucketPolicy(bucketName)) 308 309 // HTTP request for testing when `objectLayer` is set to `nil`. 310 // There is no need to use an existing bucket and valid input for creating the request 311 // since the `objectLayer==nil` check is performed before any other checks inside the handlers. 312 // The only aim is to generate an HTTP request in a way that the relevant/registered end point is evoked/called. 313 314 nilBucket := "dummy-bucket" 315 nilReq, err := newTestRequest(http.MethodHead, getHEADBucketURL("", nilBucket), 0, nil) 316 317 if err != nil { 318 t.Errorf("MinIO %s: Failed to create HTTP request for testing the response when object Layer is set to `nil`.", instanceType) 319 } 320 // execute the object layer set to `nil` test. 321 // `ExecObjectLayerAPINilTest` manages the operation. 322 ExecObjectLayerAPINilTest(t, nilBucket, "", instanceType, apiRouter, nilReq) 323 } 324 325 // Wrapper for calling TestListMultipartUploadsHandler tests for both Erasure multiple disks and single node setup. 326 func TestListMultipartUploadsHandler(t *testing.T) { 327 ExecObjectLayerAPITest(t, testListMultipartUploadsHandler, []string{"ListMultipartUploads"}) 328 } 329 330 // testListMultipartUploadsHandler - Tests validate listing of multipart uploads. 331 func testListMultipartUploadsHandler(obj ObjectLayer, instanceType, bucketName string, apiRouter http.Handler, 332 credentials auth.Credentials, t *testing.T) { 333 334 // Collection of non-exhaustive ListMultipartUploads test cases, valid errors 335 // and success responses. 336 testCases := []struct { 337 // Inputs to ListMultipartUploads. 338 bucket string 339 prefix string 340 keyMarker string 341 uploadIDMarker string 342 delimiter string 343 maxUploads string 344 accessKey string 345 secretKey string 346 expectedRespStatus int 347 shouldPass bool 348 }{ 349 // Test case - 1. 350 // Setting invalid bucket name. 351 { 352 bucket: ".test", 353 prefix: "", 354 keyMarker: "", 355 uploadIDMarker: "", 356 delimiter: "", 357 maxUploads: "0", 358 accessKey: credentials.AccessKey, 359 secretKey: credentials.SecretKey, 360 expectedRespStatus: http.StatusNotFound, 361 shouldPass: false, 362 }, 363 // Test case - 2. 364 // Setting a non-existent bucket. 365 { 366 bucket: "volatile-bucket-1", 367 prefix: "", 368 keyMarker: "", 369 uploadIDMarker: "", 370 delimiter: "", 371 maxUploads: "0", 372 accessKey: credentials.AccessKey, 373 secretKey: credentials.SecretKey, 374 expectedRespStatus: http.StatusNotFound, 375 shouldPass: false, 376 }, 377 // Test case -3. 378 // Delimiter unsupported, but response is empty. 379 { 380 bucket: bucketName, 381 prefix: "", 382 keyMarker: "", 383 uploadIDMarker: "", 384 delimiter: "-", 385 maxUploads: "0", 386 accessKey: credentials.AccessKey, 387 secretKey: credentials.SecretKey, 388 expectedRespStatus: http.StatusOK, 389 shouldPass: true, 390 }, 391 // Test case - 4. 392 // Setting Invalid prefix and marker combination. 393 { 394 bucket: bucketName, 395 prefix: "asia", 396 keyMarker: "europe-object", 397 uploadIDMarker: "", 398 delimiter: "", 399 maxUploads: "0", 400 accessKey: credentials.AccessKey, 401 secretKey: credentials.SecretKey, 402 expectedRespStatus: http.StatusNotImplemented, 403 shouldPass: false, 404 }, 405 // Test case - 5. 406 // Invalid upload id and marker combination. 407 { 408 bucket: bucketName, 409 prefix: "asia", 410 keyMarker: "asia/europe/", 411 uploadIDMarker: "abc", 412 delimiter: "", 413 maxUploads: "0", 414 accessKey: credentials.AccessKey, 415 secretKey: credentials.SecretKey, 416 expectedRespStatus: http.StatusNotImplemented, 417 shouldPass: false, 418 }, 419 // Test case - 6. 420 // Setting a negative value to max-uploads paramater, should result in http.StatusBadRequest. 421 { 422 bucket: bucketName, 423 prefix: "", 424 keyMarker: "", 425 uploadIDMarker: "", 426 delimiter: "", 427 maxUploads: "-1", 428 accessKey: credentials.AccessKey, 429 secretKey: credentials.SecretKey, 430 expectedRespStatus: http.StatusBadRequest, 431 shouldPass: false, 432 }, 433 // Test case - 7. 434 // Case with right set of parameters, 435 // should result in success 200OK. 436 { 437 bucket: bucketName, 438 prefix: "", 439 keyMarker: "", 440 uploadIDMarker: "", 441 delimiter: SlashSeparator, 442 maxUploads: "100", 443 accessKey: credentials.AccessKey, 444 secretKey: credentials.SecretKey, 445 expectedRespStatus: http.StatusOK, 446 shouldPass: true, 447 }, 448 // Test case - 8. 449 // Good case without delimiter. 450 { 451 bucket: bucketName, 452 prefix: "", 453 keyMarker: "", 454 uploadIDMarker: "", 455 delimiter: "", 456 maxUploads: "100", 457 accessKey: credentials.AccessKey, 458 secretKey: credentials.SecretKey, 459 expectedRespStatus: http.StatusOK, 460 shouldPass: true, 461 }, 462 // Test case - 9. 463 // Setting Invalid AccessKey and SecretKey to induce and verify Signature Mismatch error. 464 { 465 bucket: bucketName, 466 prefix: "", 467 keyMarker: "", 468 uploadIDMarker: "", 469 delimiter: "", 470 maxUploads: "100", 471 accessKey: "abcd", 472 secretKey: "abcd", 473 expectedRespStatus: http.StatusForbidden, 474 shouldPass: true, 475 }, 476 } 477 478 for i, testCase := range testCases { 479 // initialize HTTP NewRecorder, this records any mutations to response writer inside the handler. 480 rec := httptest.NewRecorder() 481 482 // construct HTTP request for List multipart uploads endpoint. 483 u := getListMultipartUploadsURLWithParams("", testCase.bucket, testCase.prefix, testCase.keyMarker, testCase.uploadIDMarker, testCase.delimiter, testCase.maxUploads) 484 req, gerr := newTestSignedRequestV4(http.MethodGet, u, 0, nil, testCase.accessKey, testCase.secretKey, nil) 485 if gerr != nil { 486 t.Fatalf("Test %d: %s: Failed to create HTTP request for ListMultipartUploadsHandler: <ERROR> %v", i+1, instanceType, gerr) 487 } 488 // Since `apiRouter` satisfies `http.Handler` it has a ServeHTTP to execute the logic of the handler. 489 // Call the ServeHTTP to execute the handler. 490 apiRouter.ServeHTTP(rec, req) 491 if rec.Code != testCase.expectedRespStatus { 492 t.Errorf("Test %d: %s: Expected the response status to be `%d`, but instead found `%d`", i+1, instanceType, testCase.expectedRespStatus, rec.Code) 493 } 494 495 // Verify response the V2 signed HTTP request. 496 // initialize HTTP NewRecorder, this records any mutations to response writer inside the handler. 497 recV2 := httptest.NewRecorder() 498 // construct HTTP request for PUT bucket policy endpoint. 499 500 // verify response for V2 signed HTTP request. 501 reqV2, err := newTestSignedRequestV2(http.MethodGet, u, 0, nil, testCase.accessKey, testCase.secretKey, nil) 502 if err != nil { 503 t.Fatalf("Test %d: %s: Failed to create HTTP request for PutBucketPolicyHandler: <ERROR> %v", i+1, instanceType, err) 504 } 505 // Since `apiRouter` satisfies `http.Handler` it has a ServeHTTP to execute the logic of the handler. 506 // Call the ServeHTTP to execute the handler. 507 apiRouter.ServeHTTP(recV2, reqV2) 508 if recV2.Code != testCase.expectedRespStatus { 509 t.Errorf("Test %d: %s: Expected the response status to be `%d`, but instead found `%d`", i+1, instanceType, testCase.expectedRespStatus, recV2.Code) 510 } 511 } 512 513 // initialize HTTP NewRecorder, this records any mutations to response writer inside the handler. 514 rec := httptest.NewRecorder() 515 516 // construct HTTP request for List multipart uploads endpoint. 517 u := getListMultipartUploadsURLWithParams("", bucketName, "", "", "", "", "") 518 req, err := newTestSignedRequestV4(http.MethodGet, u, 0, nil, "", "", nil) // Generate an anonymous request. 519 if err != nil { 520 t.Fatalf("Test %s: Failed to create HTTP request for ListMultipartUploadsHandler: <ERROR> %v", instanceType, err) 521 } 522 // Since `apiRouter` satisfies `http.Handler` it has a ServeHTTP to execute the logic of the handler. 523 // Call the ServeHTTP to execute the handler. 524 apiRouter.ServeHTTP(rec, req) 525 if rec.Code != http.StatusForbidden { 526 t.Errorf("Test %s: Expected the response status to be `http.StatusForbidden`, but instead found `%d`", instanceType, rec.Code) 527 } 528 529 url := getListMultipartUploadsURLWithParams("", testCases[6].bucket, testCases[6].prefix, testCases[6].keyMarker, 530 testCases[6].uploadIDMarker, testCases[6].delimiter, testCases[6].maxUploads) 531 // Test for Anonymous/unsigned http request. 532 anonReq, err := newTestRequest(http.MethodGet, url, 0, nil) 533 if err != nil { 534 t.Fatalf("MinIO %s: Failed to create an anonymous request for bucket \"%s\": <ERROR> %v", 535 instanceType, bucketName, err) 536 } 537 538 // ExecObjectLayerAPIAnonTest - Calls the HTTP API handler using the anonymous request, validates the ErrAccessDeniedResponse, 539 // sets the bucket policy using the policy statement generated from `getWriteOnlyBucketStatement` so that the 540 // unsigned request goes through and its validated again. 541 ExecObjectLayerAPIAnonTest(t, obj, "TestListMultipartUploadsHandler", bucketName, "", instanceType, apiRouter, anonReq, getAnonWriteOnlyBucketPolicy(bucketName)) 542 543 // HTTP request for testing when `objectLayer` is set to `nil`. 544 // There is no need to use an existing bucket and valid input for creating the request 545 // since the `objectLayer==nil` check is performed before any other checks inside the handlers. 546 // The only aim is to generate an HTTP request in a way that the relevant/registered end point is evoked/called. 547 548 nilBucket := "dummy-bucket" 549 url = getListMultipartUploadsURLWithParams("", nilBucket, "dummy-prefix", testCases[6].keyMarker, 550 testCases[6].uploadIDMarker, testCases[6].delimiter, testCases[6].maxUploads) 551 552 nilReq, err := newTestRequest(http.MethodGet, url, 0, nil) 553 554 if err != nil { 555 t.Errorf("MinIO %s: Failed to create HTTP request for testing the response when object Layer is set to `nil`.", instanceType) 556 } 557 // execute the object layer set to `nil` test. 558 // `ExecObjectLayerAPINilTest` manages the operation. 559 ExecObjectLayerAPINilTest(t, nilBucket, "", instanceType, apiRouter, nilReq) 560 } 561 562 // Wrapper for calling TestListBucketsHandler tests for both Erasure multiple disks and single node setup. 563 func TestListBucketsHandler(t *testing.T) { 564 ExecObjectLayerAPITest(t, testListBucketsHandler, []string{"ListBuckets"}) 565 } 566 567 // testListBucketsHandler - Tests validate listing of buckets. 568 func testListBucketsHandler(obj ObjectLayer, instanceType, bucketName string, apiRouter http.Handler, 569 credentials auth.Credentials, t *testing.T) { 570 571 testCases := []struct { 572 bucketName string 573 accessKey string 574 secretKey string 575 expectedRespStatus int 576 }{ 577 // Test case - 1. 578 // Validate a good case request succeeds. 579 { 580 bucketName: bucketName, 581 accessKey: credentials.AccessKey, 582 secretKey: credentials.SecretKey, 583 expectedRespStatus: http.StatusOK, 584 }, 585 // Test case - 2. 586 // Test case with invalid accessKey to produce and validate Signature MisMatch error. 587 { 588 bucketName: bucketName, 589 accessKey: "abcd", 590 secretKey: "abcd", 591 expectedRespStatus: http.StatusForbidden, 592 }, 593 } 594 595 for i, testCase := range testCases { 596 // initialize HTTP NewRecorder, this records any mutations to response writer inside the handler. 597 rec := httptest.NewRecorder() 598 req, lerr := newTestSignedRequestV4(http.MethodGet, getListBucketURL(""), 0, nil, testCase.accessKey, testCase.secretKey, nil) 599 if lerr != nil { 600 t.Fatalf("Test %d: %s: Failed to create HTTP request for ListBucketsHandler: <ERROR> %v", i+1, instanceType, lerr) 601 } 602 // Since `apiRouter` satisfies `http.Handler` it has a ServeHTTP to execute the logic of the handler. 603 // Call the ServeHTTP to execute the handler. 604 apiRouter.ServeHTTP(rec, req) 605 if rec.Code != testCase.expectedRespStatus { 606 t.Errorf("Test %d: %s: Expected the response status to be `%d`, but instead found `%d`", i+1, instanceType, testCase.expectedRespStatus, rec.Code) 607 } 608 609 // Verify response of the V2 signed HTTP request. 610 // initialize HTTP NewRecorder, this records any mutations to response writer inside the handler. 611 recV2 := httptest.NewRecorder() 612 // construct HTTP request for PUT bucket policy endpoint. 613 614 // verify response for V2 signed HTTP request. 615 reqV2, err := newTestSignedRequestV2(http.MethodGet, getListBucketURL(""), 0, nil, testCase.accessKey, testCase.secretKey, nil) 616 617 if err != nil { 618 t.Fatalf("Test %d: %s: Failed to create HTTP request for PutBucketPolicyHandler: <ERROR> %v", i+1, instanceType, err) 619 } 620 // Since `apiRouter` satisfies `http.Handler` it has a ServeHTTP to execute the logic of the handler. 621 // Call the ServeHTTP to execute the handler. 622 apiRouter.ServeHTTP(recV2, reqV2) 623 if recV2.Code != testCase.expectedRespStatus { 624 t.Errorf("Test %d: %s: Expected the response status to be `%d`, but instead found `%d`", i+1, instanceType, testCase.expectedRespStatus, recV2.Code) 625 } 626 } 627 628 // Test for Anonymous/unsigned http request. 629 // ListBucketsHandler doesn't support bucket policies, setting the policies shouldn't make a difference. 630 anonReq, err := newTestRequest(http.MethodGet, getListBucketURL(""), 0, nil) 631 632 if err != nil { 633 t.Fatalf("MinIO %s: Failed to create an anonymous request.", instanceType) 634 } 635 636 // ExecObjectLayerAPIAnonTest - Calls the HTTP API handler using the anonymous request, validates the ErrAccessDeniedResponse, 637 // sets the bucket policy using the policy statement generated from `getWriteOnlyObjectStatement` so that the 638 // unsigned request goes through and its validated again. 639 ExecObjectLayerAPIAnonTest(t, obj, "ListBucketsHandler", "", "", instanceType, apiRouter, anonReq, getAnonWriteOnlyBucketPolicy("*")) 640 641 // HTTP request for testing when `objectLayer` is set to `nil`. 642 // There is no need to use an existing bucket and valid input for creating the request 643 // since the `objectLayer==nil` check is performed before any other checks inside the handlers. 644 // The only aim is to generate an HTTP request in a way that the relevant/registered end point is evoked/called. 645 646 nilReq, err := newTestRequest(http.MethodGet, getListBucketURL(""), 0, nil) 647 648 if err != nil { 649 t.Errorf("MinIO %s: Failed to create HTTP request for testing the response when object Layer is set to `nil`.", instanceType) 650 } 651 // execute the object layer set to `nil` test. 652 // `ExecObjectLayerAPINilTest` manages the operation. 653 ExecObjectLayerAPINilTest(t, "", "", instanceType, apiRouter, nilReq) 654 } 655 656 // Wrapper for calling DeleteMultipleObjects HTTP handler tests for both Erasure multiple disks and single node setup. 657 func TestAPIDeleteMultipleObjectsHandler(t *testing.T) { 658 ExecObjectLayerAPITest(t, testAPIDeleteMultipleObjectsHandler, []string{"DeleteMultipleObjects"}) 659 } 660 661 func testAPIDeleteMultipleObjectsHandler(obj ObjectLayer, instanceType, bucketName string, apiRouter http.Handler, 662 credentials auth.Credentials, t *testing.T) { 663 664 var err error 665 666 contentBytes := []byte("hello") 667 sha256sum := "" 668 var objectNames []string 669 for i := 0; i < 10; i++ { 670 objectName := "test-object-" + strconv.Itoa(i) 671 // uploading the object. 672 _, err = obj.PutObject(GlobalContext, bucketName, objectName, mustGetPutObjReader(t, bytes.NewReader(contentBytes), int64(len(contentBytes)), "", sha256sum), ObjectOptions{}) 673 // if object upload fails stop the test. 674 if err != nil { 675 t.Fatalf("Put Object %d: Error uploading object: <ERROR> %v", i, err) 676 } 677 678 // object used for the test. 679 objectNames = append(objectNames, objectName) 680 } 681 682 getObjectToDeleteList := func(objectNames []string) (objectList []ObjectToDelete) { 683 for _, objectName := range objectNames { 684 objectList = append(objectList, ObjectToDelete{ 685 ObjectName: objectName, 686 }) 687 } 688 689 return objectList 690 } 691 692 getDeleteErrorList := func(objects []ObjectToDelete) (deleteErrorList []DeleteError) { 693 for _, obj := range objects { 694 deleteErrorList = append(deleteErrorList, DeleteError{ 695 Code: errorCodes[ErrAccessDenied].Code, 696 Message: errorCodes[ErrAccessDenied].Description, 697 Key: obj.ObjectName, 698 }) 699 } 700 701 return deleteErrorList 702 } 703 704 requestList := []DeleteObjectsRequest{ 705 {Quiet: false, Objects: getObjectToDeleteList(objectNames[:5])}, 706 {Quiet: true, Objects: getObjectToDeleteList(objectNames[5:])}, 707 } 708 709 // generate multi objects delete response. 710 successRequest0 := EncodeResponse(requestList[0]) 711 712 deletedObjects := make([]DeletedObject, len(requestList[0].Objects)) 713 for i := range requestList[0].Objects { 714 deletedObjects[i] = DeletedObject{ 715 ObjectName: requestList[0].Objects[i].ObjectName, 716 } 717 } 718 719 successResponse0 := generateMultiDeleteResponse(requestList[0].Quiet, deletedObjects, nil) 720 encodedSuccessResponse0 := EncodeResponse(successResponse0) 721 722 successRequest1 := EncodeResponse(requestList[1]) 723 724 deletedObjects = make([]DeletedObject, len(requestList[1].Objects)) 725 for i := range requestList[0].Objects { 726 deletedObjects[i] = DeletedObject{ 727 ObjectName: requestList[1].Objects[i].ObjectName, 728 } 729 } 730 731 successResponse1 := generateMultiDeleteResponse(requestList[1].Quiet, deletedObjects, nil) 732 encodedSuccessResponse1 := EncodeResponse(successResponse1) 733 734 // generate multi objects delete response for errors. 735 // errorRequest := EncodeResponse(requestList[1]) 736 errorResponse := generateMultiDeleteResponse(requestList[1].Quiet, deletedObjects, nil) 737 encodedErrorResponse := EncodeResponse(errorResponse) 738 739 anonRequest := EncodeResponse(requestList[0]) 740 anonResponse := generateMultiDeleteResponse(requestList[0].Quiet, nil, getDeleteErrorList(requestList[0].Objects)) 741 encodedAnonResponse := EncodeResponse(anonResponse) 742 743 testCases := []struct { 744 bucket string 745 objects []byte 746 accessKey string 747 secretKey string 748 expectedContent []byte 749 expectedRespStatus int 750 }{ 751 // Test case - 1. 752 // Delete objects with invalid access key. 753 { 754 bucket: bucketName, 755 objects: successRequest0, 756 accessKey: "Invalid-AccessID", 757 secretKey: credentials.SecretKey, 758 expectedContent: nil, 759 expectedRespStatus: http.StatusForbidden, 760 }, 761 // Test case - 2. 762 // Delete valid objects with quiet flag off. 763 { 764 bucket: bucketName, 765 objects: successRequest0, 766 accessKey: credentials.AccessKey, 767 secretKey: credentials.SecretKey, 768 expectedContent: encodedSuccessResponse0, 769 expectedRespStatus: http.StatusOK, 770 }, 771 // Test case - 3. 772 // Delete valid objects with quiet flag on. 773 { 774 bucket: bucketName, 775 objects: successRequest1, 776 accessKey: credentials.AccessKey, 777 secretKey: credentials.SecretKey, 778 expectedContent: encodedSuccessResponse1, 779 expectedRespStatus: http.StatusOK, 780 }, 781 // Test case - 4. 782 // Delete previously deleted objects. 783 { 784 bucket: bucketName, 785 objects: successRequest1, 786 accessKey: credentials.AccessKey, 787 secretKey: credentials.SecretKey, 788 expectedContent: encodedErrorResponse, 789 expectedRespStatus: http.StatusOK, 790 }, 791 // Test case - 5. 792 // Anonymous user access denied response 793 // Currently anonymous users cannot delete multiple objects in MinIO server 794 { 795 bucket: bucketName, 796 objects: anonRequest, 797 accessKey: "", 798 secretKey: "", 799 expectedContent: encodedAnonResponse, 800 expectedRespStatus: http.StatusOK, 801 }, 802 } 803 804 for i, testCase := range testCases { 805 var req *http.Request 806 var actualContent []byte 807 808 // Generate a signed or anonymous request based on the testCase 809 if testCase.accessKey != "" { 810 req, err = newTestSignedRequestV4(http.MethodPost, getDeleteMultipleObjectsURL("", bucketName), 811 int64(len(testCase.objects)), bytes.NewReader(testCase.objects), testCase.accessKey, testCase.secretKey, nil) 812 } else { 813 req, err = newTestRequest(http.MethodPost, getDeleteMultipleObjectsURL("", bucketName), 814 int64(len(testCase.objects)), bytes.NewReader(testCase.objects)) 815 } 816 817 if err != nil { 818 t.Fatalf("Failed to create HTTP request for DeleteMultipleObjects: <ERROR> %v", err) 819 } 820 821 rec := httptest.NewRecorder() 822 823 // Since `apiRouter` satisfies `http.Handler` it has a ServeHTTP to execute the logic of the handler. 824 // Call the ServeHTTP to executes the registered handler. 825 apiRouter.ServeHTTP(rec, req) 826 // Assert the response code with the expected status. 827 if rec.Code != testCase.expectedRespStatus { 828 t.Errorf("Test %d: MinIO %s: Expected the response status to be `%d`, but instead found `%d`", i+1, instanceType, testCase.expectedRespStatus, rec.Code) 829 } 830 831 // read the response body. 832 actualContent, err = ioutil.ReadAll(rec.Body) 833 if err != nil { 834 t.Fatalf("Test %d : MinIO %s: Failed parsing response body: <ERROR> %v", i+1, instanceType, err) 835 } 836 837 // Verify whether the bucket obtained object is same as the one created. 838 if testCase.expectedContent != nil && !bytes.Equal(testCase.expectedContent, actualContent) { 839 t.Log(string(testCase.expectedContent), string(actualContent)) 840 t.Errorf("Test %d : MinIO %s: Object content differs from expected value.", i+1, instanceType) 841 } 842 } 843 844 // HTTP request to test the case of `objectLayer` being set to `nil`. 845 // There is no need to use an existing bucket or valid input for creating the request, 846 // since the `objectLayer==nil` check is performed before any other checks inside the handlers. 847 // The only aim is to generate an HTTP request in a way that the relevant/registered end point is evoked/called. 848 // Indicating that all parts are uploaded and initiating completeMultipartUpload. 849 nilBucket := "dummy-bucket" 850 nilObject := "" 851 852 nilReq, err := newTestSignedRequestV4(http.MethodPost, getDeleteMultipleObjectsURL("", nilBucket), 0, nil, "", "", nil) 853 if err != nil { 854 t.Errorf("MinIO %s: Failed to create HTTP request for testing the response when object Layer is set to `nil`.", instanceType) 855 } 856 // execute the object layer set to `nil` test. 857 // `ExecObjectLayerAPINilTest` manages the operation. 858 ExecObjectLayerAPINilTest(t, nilBucket, nilObject, instanceType, apiRouter, nilReq) 859 }