storj.io/minio@v0.0.0-20230509071714-0cbc90f649b1/cmd/bucket-handlers.go (about) 1 /* 2 * MinIO Cloud Storage, (C) 2015-2020 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 "crypto/subtle" 22 "encoding/base64" 23 "encoding/json" 24 "encoding/xml" 25 "fmt" 26 "io" 27 "net/http" 28 "net/textproto" 29 "net/url" 30 "path" 31 "strconv" 32 "strings" 33 34 "github.com/google/uuid" 35 "github.com/gorilla/mux" 36 "github.com/minio/minio-go/v7/pkg/tags" 37 38 "storj.io/minio/cmd/crypto" 39 xhttp "storj.io/minio/cmd/http" 40 "storj.io/minio/cmd/logger" 41 "storj.io/minio/pkg/bucket/lifecycle" 42 objectlock "storj.io/minio/pkg/bucket/object/lock" 43 "storj.io/minio/pkg/bucket/policy" 44 "storj.io/minio/pkg/bucket/replication" 45 "storj.io/minio/pkg/event" 46 "storj.io/minio/pkg/handlers" 47 "storj.io/minio/pkg/hash" 48 iampolicy "storj.io/minio/pkg/iam/policy" 49 ) 50 51 const ( 52 objectLockConfig = "object-lock.xml" 53 bucketTaggingConfig = "tagging.xml" 54 bucketReplicationConfig = "replication.xml" 55 ) 56 57 // GetBucketLocationHandler - GET Bucket location. 58 // ------------------------- 59 // This operation returns bucket location. 60 func (api ObjectAPIHandlers) GetBucketLocationHandler(w http.ResponseWriter, r *http.Request) { 61 ctx := NewContext(r, w, "GetBucketLocation") 62 63 defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r)) 64 65 vars := mux.Vars(r) 66 bucket := vars["bucket"] 67 68 objectAPI := api.ObjectAPI() 69 if objectAPI == nil { 70 WriteErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrServerNotInitialized), r.URL, guessIsBrowserReq(r)) 71 return 72 } 73 74 if s3Error := checkRequestAuthType(ctx, r, policy.GetBucketLocationAction, bucket, ""); s3Error != ErrNone { 75 WriteErrorResponse(ctx, w, errorCodes.ToAPIErr(s3Error), r.URL, guessIsBrowserReq(r)) 76 return 77 } 78 79 getBucketInfo := objectAPI.GetBucketInfo 80 81 if _, err := getBucketInfo(ctx, bucket); err != nil { 82 WriteErrorResponse(ctx, w, ToAPIError(ctx, err), r.URL, guessIsBrowserReq(r)) 83 return 84 } 85 86 // Generate response. 87 encodedSuccessResponse := EncodeResponse(LocationResponse{}) 88 // Get current region. 89 region := globalServerRegion 90 if region != globalMinioDefaultRegion { 91 encodedSuccessResponse = EncodeResponse(LocationResponse{ 92 Location: region, 93 }) 94 } 95 96 // Write success response. 97 WriteSuccessResponseXML(w, encodedSuccessResponse) 98 } 99 100 // ListMultipartUploadsHandler - GET Bucket (List Multipart uploads) 101 // ------------------------- 102 // This operation lists in-progress multipart uploads. An in-progress 103 // multipart upload is a multipart upload that has been initiated, 104 // using the Initiate Multipart Upload request, but has not yet been 105 // completed or aborted. This operation returns at most 1,000 multipart 106 // uploads in the response. 107 // 108 func (api ObjectAPIHandlers) ListMultipartUploadsHandler(w http.ResponseWriter, r *http.Request) { 109 ctx := NewContext(r, w, "ListMultipartUploads") 110 111 defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r)) 112 113 vars := mux.Vars(r) 114 bucket := vars["bucket"] 115 116 objectAPI := api.ObjectAPI() 117 if objectAPI == nil { 118 WriteErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrServerNotInitialized), r.URL, guessIsBrowserReq(r)) 119 return 120 } 121 122 if s3Error := checkRequestAuthType(ctx, r, policy.ListBucketMultipartUploadsAction, bucket, ""); s3Error != ErrNone { 123 WriteErrorResponse(ctx, w, errorCodes.ToAPIErr(s3Error), r.URL, guessIsBrowserReq(r)) 124 return 125 } 126 127 prefix, keyMarker, uploadIDMarker, delimiter, maxUploads, encodingType, errCode := getBucketMultipartResources(r.URL.Query()) 128 if errCode != ErrNone { 129 WriteErrorResponse(ctx, w, errorCodes.ToAPIErr(errCode), r.URL, guessIsBrowserReq(r)) 130 return 131 } 132 133 if maxUploads < 0 { 134 WriteErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrInvalidMaxUploads), r.URL, guessIsBrowserReq(r)) 135 return 136 } 137 138 if keyMarker != "" { 139 // Marker not common with prefix is not implemented. 140 if !HasPrefix(keyMarker, prefix) { 141 WriteErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrNotImplemented), r.URL, guessIsBrowserReq(r)) 142 return 143 } 144 } 145 146 listMultipartsInfo, err := objectAPI.ListMultipartUploads(ctx, bucket, prefix, keyMarker, uploadIDMarker, delimiter, maxUploads) 147 if err != nil { 148 WriteErrorResponse(ctx, w, ToAPIError(ctx, err), r.URL, guessIsBrowserReq(r)) 149 return 150 } 151 // generate response 152 response := generateListMultipartUploadsResponse(bucket, listMultipartsInfo, encodingType) 153 encodedSuccessResponse := EncodeResponse(response) 154 155 // write success response. 156 WriteSuccessResponseXML(w, encodedSuccessResponse) 157 } 158 159 // ListBucketsHandler - GET Service. 160 // ----------- 161 // This implementation of the GET operation returns a list of all buckets 162 // owned by the authenticated sender of the request. 163 func (api ObjectAPIHandlers) ListBucketsHandler(w http.ResponseWriter, r *http.Request) { 164 ctx := NewContext(r, w, "ListBuckets") 165 166 defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r)) 167 168 objectAPI := api.ObjectAPI() 169 if objectAPI == nil { 170 WriteErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrServerNotInitialized), r.URL, guessIsBrowserReq(r)) 171 return 172 } 173 174 listBuckets := objectAPI.ListBuckets 175 176 cred, owner, s3Error := CheckRequestAuthTypeCredential(ctx, r, policy.ListAllMyBucketsAction, "", "") 177 if s3Error != ErrNone && s3Error != ErrAccessDenied { 178 WriteErrorResponse(ctx, w, errorCodes.ToAPIErr(s3Error), r.URL, guessIsBrowserReq(r)) 179 return 180 } 181 182 // Invoke the list buckets. 183 bucketsInfo, err := listBuckets(ctx) 184 if err != nil { 185 WriteErrorResponse(ctx, w, ToAPIError(ctx, err), r.URL, guessIsBrowserReq(r)) 186 return 187 } 188 189 if s3Error == ErrAccessDenied { 190 // Set prefix value for "s3:prefix" policy conditionals. 191 r.Header.Set("prefix", "") 192 193 // Set delimiter value for "s3:delimiter" policy conditionals. 194 r.Header.Set("delimiter", SlashSeparator) 195 196 // err will be nil here as we already called this function 197 // earlier in this request. 198 claims, _ := getClaimsFromToken(getSessionToken(r)) 199 n := 0 200 // Use the following trick to filter in place 201 // https://github.com/golang/go/wiki/SliceTricks#filter-in-place 202 for _, bucketInfo := range bucketsInfo { 203 if GlobalIAMSys.IsAllowed(iampolicy.Args{ 204 AccountName: cred.AccessKey, 205 Groups: cred.Groups, 206 Action: iampolicy.ListBucketAction, 207 BucketName: bucketInfo.Name, 208 ConditionValues: getConditionValues(r, "", cred.AccessKey, claims), 209 IsOwner: owner, 210 ObjectName: "", 211 Claims: claims, 212 }) { 213 bucketsInfo[n] = bucketInfo 214 n++ 215 } 216 } 217 bucketsInfo = bucketsInfo[:n] 218 // No buckets can be filtered return access denied error. 219 if len(bucketsInfo) == 0 { 220 WriteErrorResponse(ctx, w, errorCodes.ToAPIErr(s3Error), r.URL, guessIsBrowserReq(r)) 221 return 222 } 223 } 224 225 // Generate response. 226 response := generateListBucketsResponse(bucketsInfo) 227 encodedSuccessResponse := EncodeResponse(response) 228 229 // Write response. 230 WriteSuccessResponseXML(w, encodedSuccessResponse) 231 } 232 233 // DeleteMultipleObjectsHandler - deletes multiple objects. 234 func (api ObjectAPIHandlers) DeleteMultipleObjectsHandler(w http.ResponseWriter, r *http.Request) { 235 ctx := NewContext(r, w, "DeleteMultipleObjects") 236 237 defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r)) 238 239 vars := mux.Vars(r) 240 bucket := vars["bucket"] 241 242 objectAPI := api.ObjectAPI() 243 if objectAPI == nil { 244 WriteErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrServerNotInitialized), r.URL, guessIsBrowserReq(r)) 245 return 246 } 247 248 // Content-Md5 is requied should be set 249 // http://docs.aws.amazon.com/AmazonS3/latest/API/multiobjectdeleteapi.html 250 if _, ok := r.Header[xhttp.ContentMD5]; !ok { 251 WriteErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrMissingContentMD5), r.URL, guessIsBrowserReq(r)) 252 return 253 } 254 255 // Content-Length is required and should be non-zero 256 // http://docs.aws.amazon.com/AmazonS3/latest/API/multiobjectdeleteapi.html 257 if r.ContentLength <= 0 { 258 WriteErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrMissingContentLength), r.URL, guessIsBrowserReq(r)) 259 return 260 } 261 262 // The max. XML contains 100000 object names (each at most 1024 bytes long) + XML overhead 263 const maxBodySize = 2 * 100000 * 1024 264 265 // Unmarshal list of keys to be deleted. 266 deleteObjects := &DeleteObjectsRequest{} 267 if err := xmlDecoder(r.Body, deleteObjects, maxBodySize); err != nil { 268 logger.LogIf(ctx, err, logger.Application) 269 WriteErrorResponse(ctx, w, ToAPIError(ctx, err), r.URL, guessIsBrowserReq(r)) 270 return 271 } 272 273 // Convert object name delete objects if it has `/` in the beginning. 274 for i := range deleteObjects.Objects { 275 deleteObjects.Objects[i].ObjectName = trimLeadingSlash(deleteObjects.Objects[i].ObjectName) 276 } 277 278 // Call checkRequestAuthType to populate ReqInfo.AccessKey before GetBucketInfo() 279 // Ignore errors here to preserve the S3 error behavior of GetBucketInfo() 280 checkRequestAuthType(ctx, r, policy.DeleteObjectAction, bucket, "") 281 282 // Before proceeding validate if bucket exists. 283 _, err := objectAPI.GetBucketInfo(ctx, bucket) 284 if err != nil { 285 WriteErrorResponse(ctx, w, ToAPIError(ctx, err), r.URL, guessIsBrowserReq(r)) 286 return 287 } 288 289 deleteObjectsFn := objectAPI.DeleteObjects 290 if api.CacheAPI() != nil { 291 deleteObjectsFn = api.CacheAPI().DeleteObjects 292 } 293 294 // Return Malformed XML as S3 spec if the list of objects is empty 295 if len(deleteObjects.Objects) == 0 { 296 WriteErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrMalformedXML), r.URL, guessIsBrowserReq(r)) 297 return 298 } 299 300 var objectsToDelete = map[ObjectToDelete]int{} 301 getObjectInfoFn := objectAPI.GetObjectInfo 302 if api.CacheAPI() != nil { 303 getObjectInfoFn = api.CacheAPI().GetObjectInfo 304 } 305 var ( 306 hasLockEnabled, hasLifecycleConfig, replicateSync bool 307 goi ObjectInfo 308 gerr error 309 ) 310 replicateDeletes := hasReplicationRules(ctx, bucket, deleteObjects.Objects) 311 if rcfg, _ := globalBucketObjectLockSys.Get(bucket); rcfg.LockEnabled { 312 hasLockEnabled = true 313 } 314 if _, err := globalBucketMetadataSys.GetLifecycleConfig(bucket); err == nil { 315 hasLifecycleConfig = true 316 } 317 dErrs := make([]DeleteError, len(deleteObjects.Objects)) 318 for index, object := range deleteObjects.Objects { 319 if apiErrCode := checkRequestAuthType(ctx, r, policy.DeleteObjectAction, bucket, object.ObjectName); apiErrCode != ErrNone { 320 if apiErrCode == ErrSignatureDoesNotMatch || apiErrCode == ErrInvalidAccessKeyID { 321 WriteErrorResponse(ctx, w, errorCodes.ToAPIErr(apiErrCode), r.URL, guessIsBrowserReq(r)) 322 return 323 } 324 apiErr := errorCodes.ToAPIErr(apiErrCode) 325 dErrs[index] = DeleteError{ 326 Code: apiErr.Code, 327 Message: apiErr.Description, 328 Key: object.ObjectName, 329 VersionID: object.VersionID, 330 } 331 continue 332 } 333 if object.VersionID != "" && object.VersionID != nullVersionID { 334 if _, err := uuid.Parse(object.VersionID); err != nil { 335 logger.LogIf(ctx, fmt.Errorf("invalid version-id specified %w", err)) 336 apiErr := errorCodes.ToAPIErr(ErrNoSuchVersion) 337 dErrs[index] = DeleteError{ 338 Code: apiErr.Code, 339 Message: apiErr.Description, 340 Key: object.ObjectName, 341 VersionID: object.VersionID, 342 } 343 continue 344 } 345 } 346 347 if replicateDeletes || hasLockEnabled || hasLifecycleConfig { 348 goi, gerr = getObjectInfoFn(ctx, bucket, object.ObjectName, ObjectOptions{ 349 VersionID: object.VersionID, 350 }) 351 } 352 if hasLifecycleConfig && gerr == nil { 353 object.PurgeTransitioned = goi.TransitionStatus 354 } 355 if replicateDeletes { 356 replicate, repsync := checkReplicateDelete(ctx, bucket, ObjectToDelete{ 357 ObjectName: object.ObjectName, 358 VersionID: object.VersionID, 359 }, goi, gerr) 360 replicateSync = repsync 361 if replicate { 362 if apiErrCode := checkRequestAuthType(ctx, r, policy.ReplicateDeleteAction, bucket, object.ObjectName); apiErrCode != ErrNone { 363 if apiErrCode == ErrSignatureDoesNotMatch || apiErrCode == ErrInvalidAccessKeyID { 364 WriteErrorResponse(ctx, w, errorCodes.ToAPIErr(apiErrCode), r.URL, guessIsBrowserReq(r)) 365 return 366 } 367 continue 368 } 369 if object.VersionID != "" { 370 object.VersionPurgeStatus = Pending 371 } else { 372 object.DeleteMarkerReplicationStatus = string(replication.Pending) 373 } 374 } 375 } 376 if object.VersionID != "" { 377 if hasLockEnabled { 378 if apiErrCode := enforceRetentionBypassForDelete(ctx, r, bucket, object, goi, gerr); apiErrCode != ErrNone { 379 apiErr := errorCodes.ToAPIErr(apiErrCode) 380 dErrs[index] = DeleteError{ 381 Code: apiErr.Code, 382 Message: apiErr.Description, 383 Key: object.ObjectName, 384 VersionID: object.VersionID, 385 } 386 continue 387 } 388 } 389 } 390 391 // Avoid duplicate objects, we use map to filter them out. 392 if _, ok := objectsToDelete[object]; !ok { 393 objectsToDelete[object] = index 394 } 395 } 396 397 toNames := func(input map[ObjectToDelete]int) (output []ObjectToDelete) { 398 output = make([]ObjectToDelete, len(input)) 399 idx := 0 400 for obj := range input { 401 output[idx] = obj 402 idx++ 403 } 404 return 405 } 406 407 deleteList := toNames(objectsToDelete) 408 dObjects, errs := deleteObjectsFn(ctx, bucket, deleteList, ObjectOptions{ 409 Versioned: globalBucketVersioningSys.Enabled(bucket), 410 VersionSuspended: globalBucketVersioningSys.Suspended(bucket), 411 }) 412 deletedObjects := make([]DeletedObject, len(deleteObjects.Objects)) 413 for i := range errs { 414 // DeleteMarkerVersionID is not used specifically to avoid 415 // lookup errors, since DeleteMarkerVersionID is only 416 // created during DeleteMarker creation when client didn't 417 // specify a versionID. 418 objToDel := ObjectToDelete{ 419 ObjectName: dObjects[i].ObjectName, 420 VersionID: dObjects[i].VersionID, 421 VersionPurgeStatus: dObjects[i].VersionPurgeStatus, 422 DeleteMarkerReplicationStatus: dObjects[i].DeleteMarkerReplicationStatus, 423 PurgeTransitioned: dObjects[i].PurgeTransitioned, 424 } 425 dindex := objectsToDelete[objToDel] 426 if errs[i] == nil || isErrObjectNotFound(errs[i]) || isErrVersionNotFound(errs[i]) { 427 if replicateDeletes { 428 dObjects[i].DeleteMarkerReplicationStatus = deleteList[i].DeleteMarkerReplicationStatus 429 dObjects[i].VersionPurgeStatus = deleteList[i].VersionPurgeStatus 430 } 431 deletedObjects[dindex] = dObjects[i] 432 continue 433 } 434 apiErr := ToAPIError(ctx, errs[i]) 435 dErrs[dindex] = DeleteError{ 436 Code: apiErr.Code, 437 Message: apiErr.Description, 438 Key: deleteList[i].ObjectName, 439 VersionID: deleteList[i].VersionID, 440 } 441 } 442 443 var deleteErrors []DeleteError 444 for _, dErr := range dErrs { 445 if dErr.Code != "" { 446 deleteErrors = append(deleteErrors, dErr) 447 } 448 } 449 450 // Generate response 451 response := generateMultiDeleteResponse(deleteObjects.Quiet, deletedObjects, deleteErrors) 452 encodedSuccessResponse := EncodeResponse(response) 453 454 // Write success response. 455 WriteSuccessResponseXML(w, encodedSuccessResponse) 456 for _, dobj := range deletedObjects { 457 if dobj.ObjectName == "" { 458 continue 459 } 460 461 if replicateDeletes { 462 if dobj.DeleteMarkerReplicationStatus == string(replication.Pending) || dobj.VersionPurgeStatus == Pending { 463 dv := DeletedObjectVersionInfo{ 464 DeletedObject: dobj, 465 Bucket: bucket, 466 } 467 scheduleReplicationDelete(ctx, dv, objectAPI, replicateSync) 468 } 469 } 470 471 if hasLifecycleConfig && dobj.PurgeTransitioned == lifecycle.TransitionComplete { // clean up transitioned tier 472 deleteTransitionedObject(ctx, objectAPI, bucket, dobj.ObjectName, lifecycle.ObjectOpts{ 473 Name: dobj.ObjectName, 474 VersionID: dobj.VersionID, 475 DeleteMarker: dobj.DeleteMarker, 476 }, false, true) 477 } 478 479 eventName := event.ObjectRemovedDelete 480 objInfo := ObjectInfo{ 481 Name: dobj.ObjectName, 482 VersionID: dobj.VersionID, 483 DeleteMarker: dobj.DeleteMarker, 484 } 485 486 if objInfo.DeleteMarker { 487 objInfo.VersionID = dobj.DeleteMarkerVersionID 488 eventName = event.ObjectRemovedDeleteMarkerCreated 489 } 490 491 sendEvent(eventArgs{ 492 EventName: eventName, 493 BucketName: bucket, 494 Object: objInfo, 495 ReqParams: extractReqParams(r), 496 RespElements: extractRespElements(w), 497 UserAgent: r.UserAgent(), 498 Host: handlers.GetSourceIP(r), 499 }) 500 } 501 } 502 503 // PutBucketHandler - PUT Bucket 504 // ---------- 505 // This implementation of the PUT operation creates a new bucket for authenticated request 506 func (api ObjectAPIHandlers) PutBucketHandler(w http.ResponseWriter, r *http.Request) { 507 ctx := NewContext(r, w, "PutBucket") 508 509 defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r)) 510 511 objectAPI := api.ObjectAPI() 512 if objectAPI == nil { 513 WriteErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrServerNotInitialized), r.URL, guessIsBrowserReq(r)) 514 return 515 } 516 517 vars := mux.Vars(r) 518 bucket := vars["bucket"] 519 520 objectLockEnabled := false 521 if vs, found := r.Header[http.CanonicalHeaderKey("x-amz-bucket-object-lock-enabled")]; found { 522 v := strings.ToLower(strings.Join(vs, "")) 523 if v != "true" && v != "false" { 524 WriteErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrInvalidRequest), r.URL, guessIsBrowserReq(r)) 525 return 526 } 527 objectLockEnabled = v == "true" 528 } 529 530 if s3Error := checkRequestAuthType(ctx, r, policy.CreateBucketAction, bucket, ""); s3Error != ErrNone { 531 WriteErrorResponse(ctx, w, errorCodes.ToAPIErr(s3Error), r.URL, guessIsBrowserReq(r)) 532 return 533 } 534 535 // Parse incoming location constraint. 536 location, s3Error := parseLocationConstraint(r) 537 if s3Error != ErrNone { 538 WriteErrorResponse(ctx, w, errorCodes.ToAPIErr(s3Error), r.URL, guessIsBrowserReq(r)) 539 return 540 } 541 542 // Validate if location sent by the client is valid, reject 543 // requests which do not follow valid region requirements. 544 if !isValidLocation(location) { 545 WriteErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrInvalidRegion), r.URL, guessIsBrowserReq(r)) 546 return 547 } 548 549 opts := BucketOptions{ 550 Location: location, 551 LockEnabled: objectLockEnabled, 552 } 553 554 // Proceed to creating a bucket. 555 err := objectAPI.MakeBucketWithLocation(ctx, bucket, opts) 556 if err != nil { 557 WriteErrorResponse(ctx, w, ToAPIError(ctx, err), r.URL, guessIsBrowserReq(r)) 558 return 559 } 560 561 // Load updated bucket metadata into memory. 562 GlobalNotificationSys.LoadBucketMetadata(GlobalContext, bucket) 563 564 // Make sure to add Location information here only for bucket 565 if cp := pathClean(r.URL.Path); cp != "" { 566 w.Header().Set(xhttp.Location, cp) // Clean any trailing slashes. 567 } 568 569 writeSuccessResponseHeadersOnly(w) 570 571 sendEvent(eventArgs{ 572 EventName: event.BucketCreated, 573 BucketName: bucket, 574 ReqParams: extractReqParams(r), 575 RespElements: extractRespElements(w), 576 UserAgent: r.UserAgent(), 577 Host: handlers.GetSourceIP(r), 578 }) 579 } 580 581 // PostPolicyBucketHandler - POST policy 582 // ---------- 583 // This implementation of the POST operation handles object creation with a specified 584 // signature policy in multipart/form-data 585 func (api ObjectAPIHandlers) PostPolicyBucketHandler(w http.ResponseWriter, r *http.Request) { 586 ctx := NewContext(r, w, "PostPolicyBucket") 587 588 defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r)) 589 590 objectAPI := api.ObjectAPI() 591 if objectAPI == nil { 592 WriteErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrServerNotInitialized), r.URL, guessIsBrowserReq(r)) 593 return 594 } 595 596 if crypto.S3KMS.IsRequested(r.Header) { // SSE-KMS is not supported 597 WriteErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrNotImplemented), r.URL, guessIsBrowserReq(r)) 598 return 599 } 600 601 if _, ok := crypto.IsRequested(r.Header); !objectAPI.IsEncryptionSupported() && ok { 602 WriteErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrNotImplemented), r.URL, guessIsBrowserReq(r)) 603 return 604 } 605 606 bucket := mux.Vars(r)["bucket"] 607 608 // Require Content-Length to be set in the request 609 size := r.ContentLength 610 if size < 0 { 611 WriteErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrMissingContentLength), r.URL, guessIsBrowserReq(r)) 612 return 613 } 614 615 resource, err := getResource(r.URL.Path, r.Host, globalDomainNames) 616 if err != nil { 617 WriteErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrInvalidRequest), r.URL, guessIsBrowserReq(r)) 618 return 619 } 620 621 // Make sure that the URL does not contain object name. 622 if bucket != path.Clean(resource[1:]) { 623 WriteErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrMethodNotAllowed), r.URL, guessIsBrowserReq(r)) 624 return 625 } 626 627 // Here the parameter is the size of the form data that should 628 // be loaded in memory, the remaining being put in temporary files. 629 reader, err := r.MultipartReader() 630 if err != nil { 631 logger.LogIf(ctx, err) 632 WriteErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrMalformedPOSTRequest), r.URL, guessIsBrowserReq(r)) 633 return 634 } 635 636 // Read multipart data and save in memory and in the disk if needed 637 form, err := reader.ReadForm(maxFormMemory) 638 if err != nil { 639 logger.LogIf(ctx, err, logger.Application) 640 WriteErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrMalformedPOSTRequest), r.URL, guessIsBrowserReq(r)) 641 return 642 } 643 644 // Remove all tmp files created during multipart upload 645 defer form.RemoveAll() 646 647 // Extract all form fields 648 fileBody, fileName, fileSize, formValues, err := extractPostPolicyFormValues(ctx, form) 649 if err != nil { 650 logger.LogIf(ctx, err, logger.Application) 651 WriteErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrMalformedPOSTRequest), r.URL, guessIsBrowserReq(r)) 652 return 653 } 654 655 // Check if file is provided, error out otherwise. 656 if fileBody == nil { 657 WriteErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrPOSTFileRequired), r.URL, guessIsBrowserReq(r)) 658 return 659 } 660 661 // Close multipart file 662 defer fileBody.Close() 663 664 formValues.Set("Bucket", bucket) 665 if fileName != "" && strings.Contains(formValues.Get("Key"), "${filename}") { 666 // S3 feature to replace ${filename} found in Key form field 667 // by the filename attribute passed in multipart 668 formValues.Set("Key", strings.Replace(formValues.Get("Key"), "${filename}", fileName, -1)) 669 } 670 object := trimLeadingSlash(formValues.Get("Key")) 671 672 successRedirect := formValues.Get("success_action_redirect") 673 successStatus := formValues.Get("success_action_status") 674 var redirectURL *url.URL 675 if successRedirect != "" { 676 redirectURL, err = url.Parse(successRedirect) 677 if err != nil { 678 WriteErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrMalformedPOSTRequest), r.URL, guessIsBrowserReq(r)) 679 return 680 } 681 } 682 683 // Verify policy signature. 684 cred, errCode := doesPolicySignatureMatch(ctx, formValues) 685 if errCode != ErrNone { 686 WriteErrorResponse(ctx, w, errorCodes.ToAPIErr(errCode), r.URL, guessIsBrowserReq(r)) 687 return 688 } 689 690 // Once signature is validated, check if the user has 691 // explicit permissions for the user. 692 { 693 token := formValues.Get(xhttp.AmzSecurityToken) 694 if token != "" && cred.AccessKey == "" { 695 WriteErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrNoAccessKey), r.URL, guessIsBrowserReq(r)) 696 return 697 } 698 699 if cred.IsServiceAccount() && token == "" { 700 token = cred.SessionToken 701 } 702 703 if subtle.ConstantTimeCompare([]byte(token), []byte(cred.SessionToken)) != 1 { 704 WriteErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrInvalidToken), r.URL, guessIsBrowserReq(r)) 705 return 706 } 707 708 // Extract claims if any. 709 claims, err := getClaimsFromToken(token) 710 if err != nil { 711 WriteErrorResponse(ctx, w, ToAPIError(ctx, err), r.URL, guessIsBrowserReq(r)) 712 return 713 } 714 715 if !GlobalIAMSys.IsAllowed(iampolicy.Args{ 716 AccountName: cred.AccessKey, 717 Action: iampolicy.PutObjectAction, 718 ConditionValues: getConditionValues(r, "", cred.AccessKey, claims), 719 BucketName: bucket, 720 ObjectName: object, 721 IsOwner: globalActiveCred.AccessKey == cred.AccessKey, 722 Claims: claims, 723 }) { 724 WriteErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrAccessDenied), r.URL, guessIsBrowserReq(r)) 725 return 726 } 727 } 728 729 policyBytes, err := base64.StdEncoding.DecodeString(formValues.Get("Policy")) 730 if err != nil { 731 WriteErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrMalformedPOSTRequest), r.URL, guessIsBrowserReq(r)) 732 return 733 } 734 735 // Handle policy if it is set. 736 if len(policyBytes) > 0 { 737 postPolicyForm, err := parsePostPolicyForm(bytes.NewReader(policyBytes)) 738 if err != nil { 739 errAPI := errorCodes.ToAPIErr(ErrPostPolicyConditionInvalidFormat) 740 errAPI.Description = fmt.Sprintf("%s '(%s)'", errAPI.Description, err) 741 WriteErrorResponse(ctx, w, errAPI, r.URL, guessIsBrowserReq(r)) 742 return 743 } 744 745 // Make sure formValues adhere to policy restrictions. 746 if err = checkPostPolicy(formValues, postPolicyForm); err != nil { 747 WriteErrorResponse(ctx, w, errorCodes.ToAPIErrWithErr(ErrAccessDenied, err), r.URL, guessIsBrowserReq(r)) 748 return 749 } 750 751 // Ensure that the object size is within expected range, also the file size 752 // should not exceed the maximum single Put size (5 GiB) 753 lengthRange := postPolicyForm.Conditions.ContentLengthRange 754 if lengthRange.Valid { 755 if fileSize < lengthRange.Min { 756 WriteErrorResponse(ctx, w, ToAPIError(ctx, errDataTooSmall), r.URL, guessIsBrowserReq(r)) 757 return 758 } 759 760 if fileSize > lengthRange.Max || isMaxObjectSize(fileSize) { 761 WriteErrorResponse(ctx, w, ToAPIError(ctx, errDataTooLarge), r.URL, guessIsBrowserReq(r)) 762 return 763 } 764 } 765 } 766 767 // Extract metadata to be saved from received Form. 768 metadata := make(map[string]string) 769 err = extractMetadataFromMime(ctx, textproto.MIMEHeader(formValues), metadata) 770 if err != nil { 771 WriteErrorResponse(ctx, w, ToAPIError(ctx, err), r.URL, guessIsBrowserReq(r)) 772 return 773 } 774 775 hashReader, err := hash.NewReader(fileBody, fileSize, "", "", fileSize) 776 if err != nil { 777 logger.LogIf(ctx, err) 778 WriteErrorResponse(ctx, w, ToAPIError(ctx, err), r.URL, guessIsBrowserReq(r)) 779 return 780 } 781 rawReader := hashReader 782 pReader := NewPutObjReader(rawReader) 783 var objectEncryptionKey crypto.ObjectKey 784 785 // Check if bucket encryption is enabled 786 if _, err = globalBucketSSEConfigSys.Get(bucket); err == nil || globalAutoEncryption { 787 // This request header needs to be set prior to setting ObjectOptions 788 if !crypto.SSEC.IsRequested(r.Header) { 789 r.Header.Set(xhttp.AmzServerSideEncryption, xhttp.AmzEncryptionAES) 790 } 791 } 792 793 // get gateway encryption options 794 var opts ObjectOptions 795 opts, err = putOpts(ctx, r, bucket, object, metadata) 796 if err != nil { 797 writeErrorResponseHeadersOnly(w, ToAPIError(ctx, err)) 798 return 799 } 800 if objectAPI.IsEncryptionSupported() { 801 if _, ok := crypto.IsRequested(formValues); ok && !HasSuffix(object, SlashSeparator) { // handle SSE requests 802 if crypto.SSECopy.IsRequested(r.Header) { 803 WriteErrorResponse(ctx, w, ToAPIError(ctx, errInvalidEncryptionParameters), r.URL, guessIsBrowserReq(r)) 804 return 805 } 806 var reader io.Reader 807 var key []byte 808 if crypto.SSEC.IsRequested(formValues) { 809 key, err = ParseSSECustomerHeader(formValues) 810 if err != nil { 811 WriteErrorResponse(ctx, w, ToAPIError(ctx, err), r.URL, guessIsBrowserReq(r)) 812 return 813 } 814 } 815 reader, objectEncryptionKey, err = newEncryptReader(hashReader, key, bucket, object, metadata, crypto.S3.IsRequested(formValues)) 816 if err != nil { 817 WriteErrorResponse(ctx, w, ToAPIError(ctx, err), r.URL, guessIsBrowserReq(r)) 818 return 819 } 820 info := ObjectInfo{Size: fileSize} 821 // do not try to verify encrypted content 822 hashReader, err = hash.NewReader(reader, info.EncryptedSize(), "", "", fileSize) 823 if err != nil { 824 WriteErrorResponse(ctx, w, ToAPIError(ctx, err), r.URL, guessIsBrowserReq(r)) 825 return 826 } 827 pReader, err = pReader.WithEncryption(hashReader, &objectEncryptionKey) 828 if err != nil { 829 WriteErrorResponse(ctx, w, ToAPIError(ctx, err), r.URL, guessIsBrowserReq(r)) 830 return 831 } 832 } 833 } 834 835 objInfo, err := objectAPI.PutObject(ctx, bucket, object, pReader, opts) 836 if err != nil { 837 WriteErrorResponse(ctx, w, ToAPIError(ctx, err), r.URL, guessIsBrowserReq(r)) 838 return 839 } 840 841 // We must not use the http.Header().Set method here because some (broken) 842 // clients expect the ETag header key to be literally "ETag" - not "Etag" (case-sensitive). 843 // Therefore, we have to set the ETag directly as map entry. 844 w.Header()[xhttp.ETag] = []string{`"` + objInfo.ETag + `"`} 845 846 // Set the relevant version ID as part of the response header. 847 if objInfo.VersionID != "" { 848 w.Header()[xhttp.AmzVersionID] = []string{objInfo.VersionID} 849 } 850 851 w.Header().Set(xhttp.Location, getObjectLocation(r, globalDomainNames, bucket, object)) 852 853 // Notify object created event. 854 defer sendEvent(eventArgs{ 855 EventName: event.ObjectCreatedPost, 856 BucketName: objInfo.Bucket, 857 Object: objInfo, 858 ReqParams: extractReqParams(r), 859 RespElements: extractRespElements(w), 860 UserAgent: r.UserAgent(), 861 Host: handlers.GetSourceIP(r), 862 }) 863 864 if successRedirect != "" { 865 // Replace raw query params.. 866 redirectURL.RawQuery = getRedirectPostRawQuery(objInfo) 867 writeRedirectSeeOther(w, redirectURL.String()) 868 return 869 } 870 871 // Decide what http response to send depending on success_action_status parameter 872 switch successStatus { 873 case "201": 874 resp := EncodeResponse(PostResponse{ 875 Bucket: objInfo.Bucket, 876 Key: objInfo.Name, 877 ETag: `"` + objInfo.ETag + `"`, 878 Location: w.Header().Get(xhttp.Location), 879 }) 880 writeResponse(w, http.StatusCreated, resp, mimeXML) 881 case "200": 882 writeSuccessResponseHeadersOnly(w) 883 default: 884 writeSuccessNoContent(w) 885 } 886 } 887 888 // GetBucketPolicyStatusHandler - Retrieves the policy status 889 // for an MinIO bucket, indicating whether the bucket is public. 890 func (api ObjectAPIHandlers) GetBucketPolicyStatusHandler(w http.ResponseWriter, r *http.Request) { 891 ctx := NewContext(r, w, "GetBucketPolicyStatus") 892 893 defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r)) 894 895 vars := mux.Vars(r) 896 bucket := vars["bucket"] 897 898 objectAPI := api.ObjectAPI() 899 if objectAPI == nil { 900 writeErrorResponseHeadersOnly(w, errorCodes.ToAPIErr(ErrServerNotInitialized)) 901 return 902 } 903 904 if s3Error := checkRequestAuthType(ctx, r, policy.GetBucketPolicyStatusAction, bucket, ""); s3Error != ErrNone { 905 writeErrorResponseHeadersOnly(w, errorCodes.ToAPIErr(s3Error)) 906 return 907 } 908 909 // Check if bucket exists. 910 if _, err := objectAPI.GetBucketInfo(ctx, bucket); err != nil { 911 WriteErrorResponse(ctx, w, ToAPIError(ctx, err), r.URL, guessIsBrowserReq(r)) 912 return 913 } 914 915 // Check if anonymous (non-owner) has access to list objects. 916 readable := globalPolicySys.IsAllowed(policy.Args{ 917 Action: policy.ListBucketAction, 918 BucketName: bucket, 919 ConditionValues: getConditionValues(r, "", "", nil), 920 IsOwner: false, 921 }) 922 923 // Check if anonymous (non-owner) has access to upload objects. 924 writable := globalPolicySys.IsAllowed(policy.Args{ 925 Action: policy.PutObjectAction, 926 BucketName: bucket, 927 ConditionValues: getConditionValues(r, "", "", nil), 928 IsOwner: false, 929 }) 930 931 encodedSuccessResponse := EncodeResponse(PolicyStatus{ 932 IsPublic: func() string { 933 // Silly to have special 'boolean' values yes 934 // but complying with silly implementation 935 // https://docs.aws.amazon.com/AmazonS3/latest/API/API_GetBucketPolicyStatus.html 936 if readable && writable { 937 return "TRUE" 938 } 939 return "FALSE" 940 }(), 941 }) 942 943 WriteSuccessResponseXML(w, encodedSuccessResponse) 944 } 945 946 // HeadBucketHandler - HEAD Bucket 947 // ---------- 948 // This operation is useful to determine if a bucket exists. 949 // The operation returns a 200 OK if the bucket exists and you 950 // have permission to access it. Otherwise, the operation might 951 // return responses such as 404 Not Found and 403 Forbidden. 952 func (api ObjectAPIHandlers) HeadBucketHandler(w http.ResponseWriter, r *http.Request) { 953 ctx := NewContext(r, w, "HeadBucket") 954 955 defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r)) 956 957 vars := mux.Vars(r) 958 bucket := vars["bucket"] 959 960 objectAPI := api.ObjectAPI() 961 if objectAPI == nil { 962 writeErrorResponseHeadersOnly(w, errorCodes.ToAPIErr(ErrServerNotInitialized)) 963 return 964 } 965 966 if s3Error := checkRequestAuthType(ctx, r, policy.ListBucketAction, bucket, ""); s3Error != ErrNone { 967 writeErrorResponseHeadersOnly(w, errorCodes.ToAPIErr(s3Error)) 968 return 969 } 970 971 getBucketInfo := objectAPI.GetBucketInfo 972 973 if _, err := getBucketInfo(ctx, bucket); err != nil { 974 writeErrorResponseHeadersOnly(w, ToAPIError(ctx, err)) 975 return 976 } 977 978 writeSuccessResponseHeadersOnly(w) 979 } 980 981 // DeleteBucketHandler - Delete bucket 982 func (api ObjectAPIHandlers) DeleteBucketHandler(w http.ResponseWriter, r *http.Request) { 983 ctx := NewContext(r, w, "DeleteBucket") 984 985 defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r)) 986 987 vars := mux.Vars(r) 988 bucket := vars["bucket"] 989 990 objectAPI := api.ObjectAPI() 991 if objectAPI == nil { 992 WriteErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrServerNotInitialized), r.URL, guessIsBrowserReq(r)) 993 return 994 } 995 996 // Verify if the caller has sufficient permissions. 997 if s3Error := checkRequestAuthType(ctx, r, policy.DeleteBucketAction, bucket, ""); s3Error != ErrNone { 998 WriteErrorResponse(ctx, w, errorCodes.ToAPIErr(s3Error), r.URL, guessIsBrowserReq(r)) 999 return 1000 } 1001 1002 forceDelete := false 1003 if value := r.Header.Get(xhttp.MinIOForceDelete); value != "" { 1004 var err error 1005 forceDelete, err = strconv.ParseBool(value) 1006 if err != nil { 1007 apiErr := errorCodes.ToAPIErr(ErrInvalidRequest) 1008 apiErr.Description = err.Error() 1009 WriteErrorResponse(ctx, w, apiErr, r.URL, guessIsBrowserReq(r)) 1010 return 1011 } 1012 1013 // if force delete header is set, we need to evaluate the policy anyways 1014 // regardless of it being true or not. 1015 if s3Error := checkRequestAuthType(ctx, r, policy.ForceDeleteBucketAction, bucket, ""); s3Error != ErrNone { 1016 WriteErrorResponse(ctx, w, errorCodes.ToAPIErr(s3Error), r.URL, guessIsBrowserReq(r)) 1017 return 1018 } 1019 1020 if forceDelete { 1021 if rcfg, _ := globalBucketObjectLockSys.Get(bucket); rcfg.LockEnabled { 1022 WriteErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrMethodNotAllowed), r.URL, guessIsBrowserReq(r)) 1023 return 1024 } 1025 } 1026 } 1027 1028 deleteBucket := objectAPI.DeleteBucket 1029 1030 // Attempt to delete bucket. 1031 if err := deleteBucket(ctx, bucket, forceDelete); err != nil { 1032 if _, ok := err.(BucketNotEmpty); ok && (globalBucketVersioningSys.Enabled(bucket) || globalBucketVersioningSys.Suspended(bucket)) { 1033 apiErr := ToAPIError(ctx, err) 1034 apiErr.Description = "The bucket you tried to delete is not empty. You must delete all versions in the bucket." 1035 WriteErrorResponse(ctx, w, apiErr, r.URL, guessIsBrowserReq(r)) 1036 } else { 1037 WriteErrorResponse(ctx, w, ToAPIError(ctx, err), r.URL, guessIsBrowserReq(r)) 1038 } 1039 return 1040 } 1041 1042 GlobalNotificationSys.DeleteBucketMetadata(ctx, bucket) 1043 1044 // Write success response. 1045 writeSuccessNoContent(w) 1046 1047 sendEvent(eventArgs{ 1048 EventName: event.BucketRemoved, 1049 BucketName: bucket, 1050 ReqParams: extractReqParams(r), 1051 RespElements: extractRespElements(w), 1052 UserAgent: r.UserAgent(), 1053 Host: handlers.GetSourceIP(r), 1054 }) 1055 } 1056 1057 // PutBucketObjectLockConfigHandler - PUT Bucket object lock configuration. 1058 // ---------- 1059 // Places an Object Lock configuration on the specified bucket. The rule 1060 // specified in the Object Lock configuration will be applied by default 1061 // to every new object placed in the specified bucket. 1062 func (api ObjectAPIHandlers) PutBucketObjectLockConfigHandler(w http.ResponseWriter, r *http.Request) { 1063 ctx := NewContext(r, w, "PutBucketObjectLockConfig") 1064 1065 defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r)) 1066 1067 vars := mux.Vars(r) 1068 bucket := vars["bucket"] 1069 1070 objectAPI := api.ObjectAPI() 1071 if objectAPI == nil { 1072 WriteErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrServerNotInitialized), r.URL, guessIsBrowserReq(r)) 1073 return 1074 } 1075 if !globalIsErasure { 1076 writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrNotImplemented), r.URL) 1077 return 1078 } 1079 if s3Error := checkRequestAuthType(ctx, r, policy.PutBucketObjectLockConfigurationAction, bucket, ""); s3Error != ErrNone { 1080 WriteErrorResponse(ctx, w, errorCodes.ToAPIErr(s3Error), r.URL, guessIsBrowserReq(r)) 1081 return 1082 } 1083 1084 config, err := objectlock.ParseObjectLockConfig(r.Body) 1085 if err != nil { 1086 apiErr := errorCodes.ToAPIErr(ErrMalformedXML) 1087 apiErr.Description = err.Error() 1088 WriteErrorResponse(ctx, w, apiErr, r.URL, guessIsBrowserReq(r)) 1089 return 1090 } 1091 1092 configData, err := xml.Marshal(config) 1093 if err != nil { 1094 WriteErrorResponse(ctx, w, ToAPIError(ctx, err), r.URL, guessIsBrowserReq(r)) 1095 return 1096 } 1097 1098 // Deny object locking configuration settings on existing buckets without object lock enabled. 1099 if _, err = globalBucketMetadataSys.GetObjectLockConfig(bucket); err != nil { 1100 WriteErrorResponse(ctx, w, ToAPIError(ctx, err), r.URL, guessIsBrowserReq(r)) 1101 return 1102 } 1103 1104 if err = globalBucketMetadataSys.Update(bucket, objectLockConfig, configData); err != nil { 1105 WriteErrorResponse(ctx, w, ToAPIError(ctx, err), r.URL, guessIsBrowserReq(r)) 1106 return 1107 } 1108 1109 // Write success response. 1110 writeSuccessResponseHeadersOnly(w) 1111 } 1112 1113 // GetBucketObjectLockConfigHandler - GET Bucket object lock configuration. 1114 // ---------- 1115 // Gets the Object Lock configuration for a bucket. The rule specified in 1116 // the Object Lock configuration will be applied by default to every new 1117 // object placed in the specified bucket. 1118 func (api ObjectAPIHandlers) GetBucketObjectLockConfigHandler(w http.ResponseWriter, r *http.Request) { 1119 ctx := NewContext(r, w, "GetBucketObjectLockConfig") 1120 1121 defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r)) 1122 1123 vars := mux.Vars(r) 1124 bucket := vars["bucket"] 1125 1126 objectAPI := api.ObjectAPI() 1127 if objectAPI == nil { 1128 WriteErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrServerNotInitialized), r.URL, guessIsBrowserReq(r)) 1129 return 1130 } 1131 1132 // check if user has permissions to perform this operation 1133 if s3Error := checkRequestAuthType(ctx, r, policy.GetBucketObjectLockConfigurationAction, bucket, ""); s3Error != ErrNone { 1134 WriteErrorResponse(ctx, w, errorCodes.ToAPIErr(s3Error), r.URL, guessIsBrowserReq(r)) 1135 return 1136 } 1137 1138 config, err := globalBucketMetadataSys.GetObjectLockConfig(bucket) 1139 if err != nil { 1140 WriteErrorResponse(ctx, w, ToAPIError(ctx, err), r.URL, guessIsBrowserReq(r)) 1141 return 1142 } 1143 1144 configData, err := xml.Marshal(config) 1145 if err != nil { 1146 WriteErrorResponse(ctx, w, ToAPIError(ctx, err), r.URL, guessIsBrowserReq(r)) 1147 return 1148 } 1149 1150 // Write success response. 1151 WriteSuccessResponseXML(w, configData) 1152 } 1153 1154 // PutBucketTaggingHandler - PUT Bucket tagging. 1155 // ---------- 1156 func (api ObjectAPIHandlers) PutBucketTaggingHandler(w http.ResponseWriter, r *http.Request) { 1157 ctx := NewContext(r, w, "PutBucketTagging") 1158 1159 defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r)) 1160 1161 vars := mux.Vars(r) 1162 bucket := vars["bucket"] 1163 1164 objectAPI := api.ObjectAPI() 1165 if objectAPI == nil { 1166 WriteErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrServerNotInitialized), r.URL, guessIsBrowserReq(r)) 1167 return 1168 } 1169 1170 if s3Error := checkRequestAuthType(ctx, r, policy.PutBucketTaggingAction, bucket, ""); s3Error != ErrNone { 1171 WriteErrorResponse(ctx, w, errorCodes.ToAPIErr(s3Error), r.URL, guessIsBrowserReq(r)) 1172 return 1173 } 1174 1175 tags, err := tags.ParseBucketXML(io.LimitReader(r.Body, r.ContentLength)) 1176 if err != nil { 1177 apiErr := errorCodes.ToAPIErr(ErrMalformedXML) 1178 apiErr.Description = err.Error() 1179 WriteErrorResponse(ctx, w, apiErr, r.URL, guessIsBrowserReq(r)) 1180 return 1181 } 1182 1183 configData, err := xml.Marshal(tags) 1184 if err != nil { 1185 WriteErrorResponse(ctx, w, ToAPIError(ctx, err), r.URL, guessIsBrowserReq(r)) 1186 return 1187 } 1188 1189 if err = globalBucketMetadataSys.Update(bucket, bucketTaggingConfig, configData); err != nil { 1190 WriteErrorResponse(ctx, w, ToAPIError(ctx, err), r.URL, guessIsBrowserReq(r)) 1191 return 1192 } 1193 1194 // Write success response. 1195 writeSuccessResponseHeadersOnly(w) 1196 } 1197 1198 // GetBucketTaggingHandler - GET Bucket tagging. 1199 // ---------- 1200 func (api ObjectAPIHandlers) GetBucketTaggingHandler(w http.ResponseWriter, r *http.Request) { 1201 ctx := NewContext(r, w, "GetBucketTagging") 1202 1203 defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r)) 1204 1205 vars := mux.Vars(r) 1206 bucket := vars["bucket"] 1207 1208 objectAPI := api.ObjectAPI() 1209 if objectAPI == nil { 1210 WriteErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrServerNotInitialized), r.URL, guessIsBrowserReq(r)) 1211 return 1212 } 1213 1214 // check if user has permissions to perform this operation 1215 if s3Error := checkRequestAuthType(ctx, r, policy.GetBucketTaggingAction, bucket, ""); s3Error != ErrNone { 1216 WriteErrorResponse(ctx, w, errorCodes.ToAPIErr(s3Error), r.URL, guessIsBrowserReq(r)) 1217 return 1218 } 1219 1220 config, err := globalBucketMetadataSys.GetTaggingConfig(bucket) 1221 if err != nil { 1222 WriteErrorResponse(ctx, w, ToAPIError(ctx, err), r.URL, guessIsBrowserReq(r)) 1223 return 1224 } 1225 1226 configData, err := xml.Marshal(config) 1227 if err != nil { 1228 WriteErrorResponse(ctx, w, ToAPIError(ctx, err), r.URL, guessIsBrowserReq(r)) 1229 return 1230 } 1231 1232 // Write success response. 1233 WriteSuccessResponseXML(w, configData) 1234 } 1235 1236 // DeleteBucketTaggingHandler - DELETE Bucket tagging. 1237 // ---------- 1238 func (api ObjectAPIHandlers) DeleteBucketTaggingHandler(w http.ResponseWriter, r *http.Request) { 1239 ctx := NewContext(r, w, "DeleteBucketTagging") 1240 1241 defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r)) 1242 1243 vars := mux.Vars(r) 1244 bucket := vars["bucket"] 1245 1246 objectAPI := api.ObjectAPI() 1247 if objectAPI == nil { 1248 WriteErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrServerNotInitialized), r.URL, guessIsBrowserReq(r)) 1249 return 1250 } 1251 1252 if s3Error := checkRequestAuthType(ctx, r, policy.PutBucketTaggingAction, bucket, ""); s3Error != ErrNone { 1253 WriteErrorResponse(ctx, w, errorCodes.ToAPIErr(s3Error), r.URL, guessIsBrowserReq(r)) 1254 return 1255 } 1256 1257 if err := globalBucketMetadataSys.Update(bucket, bucketTaggingConfig, nil); err != nil { 1258 WriteErrorResponse(ctx, w, ToAPIError(ctx, err), r.URL, guessIsBrowserReq(r)) 1259 return 1260 } 1261 1262 // Write success response. 1263 writeSuccessResponseHeadersOnly(w) 1264 } 1265 1266 // PutBucketReplicationConfigHandler - PUT Bucket replication configuration. 1267 // ---------- 1268 // Add a replication configuration on the specified bucket as specified in https://docs.aws.amazon.com/AmazonS3/latest/API/API_PutBucketReplication.html 1269 func (api ObjectAPIHandlers) PutBucketReplicationConfigHandler(w http.ResponseWriter, r *http.Request) { 1270 ctx := NewContext(r, w, "PutBucketReplicationConfig") 1271 defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r)) 1272 1273 vars := mux.Vars(r) 1274 bucket := vars["bucket"] 1275 objectAPI := api.ObjectAPI() 1276 if objectAPI == nil { 1277 WriteErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrServerNotInitialized), r.URL, guessIsBrowserReq(r)) 1278 return 1279 } 1280 if !globalIsErasure { 1281 writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrNotImplemented), r.URL) 1282 return 1283 } 1284 if s3Error := checkRequestAuthType(ctx, r, policy.PutReplicationConfigurationAction, bucket, ""); s3Error != ErrNone { 1285 WriteErrorResponse(ctx, w, errorCodes.ToAPIErr(s3Error), r.URL, guessIsBrowserReq(r)) 1286 return 1287 } 1288 // Check if bucket exists. 1289 if _, err := objectAPI.GetBucketInfo(ctx, bucket); err != nil { 1290 WriteErrorResponse(ctx, w, ToAPIError(ctx, err), r.URL, guessIsBrowserReq(r)) 1291 return 1292 } 1293 1294 if versioned := globalBucketVersioningSys.Enabled(bucket); !versioned { 1295 WriteErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrReplicationNeedsVersioningError), r.URL, guessIsBrowserReq(r)) 1296 return 1297 } 1298 replicationConfig, err := replication.ParseConfig(io.LimitReader(r.Body, r.ContentLength)) 1299 if err != nil { 1300 apiErr := errorCodes.ToAPIErr(ErrMalformedXML) 1301 apiErr.Description = err.Error() 1302 WriteErrorResponse(ctx, w, apiErr, r.URL, guessIsBrowserReq(r)) 1303 return 1304 } 1305 sameTarget, err := validateReplicationDestination(ctx, bucket, replicationConfig) 1306 if err != nil { 1307 WriteErrorResponse(ctx, w, ToAPIError(ctx, err), r.URL, guessIsBrowserReq(r)) 1308 return 1309 } 1310 // Validate the received bucket replication config 1311 if err = replicationConfig.Validate(bucket, sameTarget); err != nil { 1312 WriteErrorResponse(ctx, w, ToAPIError(ctx, err), r.URL, guessIsBrowserReq(r)) 1313 return 1314 } 1315 configData, err := xml.Marshal(replicationConfig) 1316 if err != nil { 1317 WriteErrorResponse(ctx, w, ToAPIError(ctx, err), r.URL, guessIsBrowserReq(r)) 1318 return 1319 } 1320 if err = globalBucketMetadataSys.Update(bucket, bucketReplicationConfig, configData); err != nil { 1321 WriteErrorResponse(ctx, w, ToAPIError(ctx, err), r.URL, guessIsBrowserReq(r)) 1322 return 1323 } 1324 1325 // Write success response. 1326 writeSuccessResponseHeadersOnly(w) 1327 } 1328 1329 // GetBucketReplicationConfigHandler - GET Bucket replication configuration. 1330 // ---------- 1331 // Gets the replication configuration for a bucket. 1332 func (api ObjectAPIHandlers) GetBucketReplicationConfigHandler(w http.ResponseWriter, r *http.Request) { 1333 ctx := NewContext(r, w, "GetBucketReplicationConfig") 1334 1335 defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r)) 1336 1337 vars := mux.Vars(r) 1338 bucket := vars["bucket"] 1339 1340 objectAPI := api.ObjectAPI() 1341 if objectAPI == nil { 1342 WriteErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrServerNotInitialized), r.URL, guessIsBrowserReq(r)) 1343 return 1344 } 1345 1346 // check if user has permissions to perform this operation 1347 if s3Error := checkRequestAuthType(ctx, r, policy.GetReplicationConfigurationAction, bucket, ""); s3Error != ErrNone { 1348 WriteErrorResponse(ctx, w, errorCodes.ToAPIErr(s3Error), r.URL, guessIsBrowserReq(r)) 1349 return 1350 } 1351 // Check if bucket exists. 1352 if _, err := objectAPI.GetBucketInfo(ctx, bucket); err != nil { 1353 WriteErrorResponse(ctx, w, ToAPIError(ctx, err), r.URL, guessIsBrowserReq(r)) 1354 return 1355 } 1356 1357 config, err := globalBucketMetadataSys.GetReplicationConfig(ctx, bucket) 1358 if err != nil { 1359 WriteErrorResponse(ctx, w, ToAPIError(ctx, err), r.URL, guessIsBrowserReq(r)) 1360 return 1361 } 1362 configData, err := xml.Marshal(config) 1363 if err != nil { 1364 WriteErrorResponse(ctx, w, ToAPIError(ctx, err), r.URL, guessIsBrowserReq(r)) 1365 return 1366 } 1367 1368 // Write success response. 1369 WriteSuccessResponseXML(w, configData) 1370 } 1371 1372 // DeleteBucketReplicationConfigHandler - DELETE Bucket replication config. 1373 // ---------- 1374 func (api ObjectAPIHandlers) DeleteBucketReplicationConfigHandler(w http.ResponseWriter, r *http.Request) { 1375 ctx := NewContext(r, w, "DeleteBucketReplicationConfig") 1376 defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r)) 1377 vars := mux.Vars(r) 1378 bucket := vars["bucket"] 1379 1380 objectAPI := api.ObjectAPI() 1381 if objectAPI == nil { 1382 WriteErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrServerNotInitialized), r.URL, guessIsBrowserReq(r)) 1383 return 1384 } 1385 1386 if s3Error := checkRequestAuthType(ctx, r, policy.PutReplicationConfigurationAction, bucket, ""); s3Error != ErrNone { 1387 WriteErrorResponse(ctx, w, errorCodes.ToAPIErr(s3Error), r.URL, guessIsBrowserReq(r)) 1388 return 1389 } 1390 // Check if bucket exists. 1391 if _, err := objectAPI.GetBucketInfo(ctx, bucket); err != nil { 1392 WriteErrorResponse(ctx, w, ToAPIError(ctx, err), r.URL, guessIsBrowserReq(r)) 1393 return 1394 } 1395 if err := globalBucketMetadataSys.Update(bucket, bucketReplicationConfig, nil); err != nil { 1396 WriteErrorResponse(ctx, w, ToAPIError(ctx, err), r.URL, guessIsBrowserReq(r)) 1397 return 1398 } 1399 1400 // Write success response. 1401 writeSuccessResponseHeadersOnly(w) 1402 } 1403 1404 // GetBucketReplicationMetricsHandler - GET Bucket replication metrics. 1405 // ---------- 1406 // Gets the replication metrics for a bucket. 1407 func (api ObjectAPIHandlers) GetBucketReplicationMetricsHandler(w http.ResponseWriter, r *http.Request) { 1408 ctx := NewContext(r, w, "GetBucketReplicationMetrics") 1409 1410 defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r)) 1411 1412 vars := mux.Vars(r) 1413 bucket := vars["bucket"] 1414 1415 objectAPI := api.ObjectAPI() 1416 if objectAPI == nil { 1417 WriteErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrServerNotInitialized), r.URL, guessIsBrowserReq(r)) 1418 return 1419 } 1420 1421 // check if user has permissions to perform this operation 1422 if s3Error := checkRequestAuthType(ctx, r, policy.GetReplicationConfigurationAction, bucket, ""); s3Error != ErrNone { 1423 WriteErrorResponse(ctx, w, errorCodes.ToAPIErr(s3Error), r.URL, guessIsBrowserReq(r)) 1424 return 1425 } 1426 1427 // Check if bucket exists. 1428 if _, err := objectAPI.GetBucketInfo(ctx, bucket); err != nil { 1429 WriteErrorResponse(ctx, w, ToAPIError(ctx, err), r.URL, guessIsBrowserReq(r)) 1430 return 1431 } 1432 1433 bucketStats := GlobalNotificationSys.GetClusterBucketStats(r.Context(), bucket) 1434 bucketReplStats := BucketReplicationStats{} 1435 // sum up metrics from each node in the cluster 1436 for _, bucketStat := range bucketStats { 1437 bucketReplStats.FailedCount += bucketStat.ReplicationStats.FailedCount 1438 bucketReplStats.FailedSize += bucketStat.ReplicationStats.FailedSize 1439 bucketReplStats.PendingCount += bucketStat.ReplicationStats.PendingCount 1440 bucketReplStats.PendingSize += bucketStat.ReplicationStats.PendingSize 1441 bucketReplStats.ReplicaSize += bucketStat.ReplicationStats.ReplicaSize 1442 bucketReplStats.ReplicatedSize += bucketStat.ReplicationStats.ReplicatedSize 1443 } 1444 // add initial usage from the time of cluster up 1445 usageStat := globalReplicationStats.GetInitialUsage(bucket) 1446 bucketReplStats.FailedCount += usageStat.FailedCount 1447 bucketReplStats.FailedSize += usageStat.FailedSize 1448 bucketReplStats.PendingCount += usageStat.PendingCount 1449 bucketReplStats.PendingSize += usageStat.PendingSize 1450 bucketReplStats.ReplicaSize += usageStat.ReplicaSize 1451 bucketReplStats.ReplicatedSize += usageStat.ReplicatedSize 1452 1453 if err := json.NewEncoder(w).Encode(&bucketReplStats); err != nil { 1454 WriteErrorResponse(ctx, w, ToAPIError(ctx, err), r.URL, guessIsBrowserReq(r)) 1455 return 1456 } 1457 w.(http.Flusher).Flush() 1458 }