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