storj.io/minio@v0.0.0-20230509071714-0cbc90f649b1/cmd/web-handlers.go (about) 1 /* 2 * MinIO Cloud Storage, (C) 2016-2019 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 "context" 21 "crypto/subtle" 22 "encoding/json" 23 "errors" 24 "fmt" 25 "io" 26 "net/http" 27 "net/url" 28 "os" 29 "path" 30 "reflect" 31 "runtime" 32 "strconv" 33 "strings" 34 "time" 35 36 "github.com/gorilla/mux" 37 "github.com/klauspost/compress/zip" 38 miniogopolicy "github.com/minio/minio-go/v7/pkg/policy" 39 "github.com/minio/minio-go/v7/pkg/s3utils" 40 41 "storj.io/minio/cmd/config/identity/openid" 42 "storj.io/minio/cmd/crypto" 43 xhttp "storj.io/minio/cmd/http" 44 "storj.io/minio/cmd/logger" 45 "storj.io/minio/pkg/auth" 46 "storj.io/minio/pkg/bucket/lifecycle" 47 objectlock "storj.io/minio/pkg/bucket/object/lock" 48 "storj.io/minio/pkg/bucket/policy" 49 "storj.io/minio/pkg/bucket/replication" 50 "storj.io/minio/pkg/etag" 51 "storj.io/minio/pkg/event" 52 "storj.io/minio/pkg/handlers" 53 "storj.io/minio/pkg/hash" 54 iampolicy "storj.io/minio/pkg/iam/policy" 55 "storj.io/minio/pkg/ioutil" 56 "storj.io/minio/pkg/rpc/json2" 57 ) 58 59 func extractBucketObject(args reflect.Value) (bucketName, objectName string) { 60 switch args.Kind() { 61 case reflect.Ptr: 62 a := args.Elem() 63 for i := 0; i < a.NumField(); i++ { 64 switch a.Type().Field(i).Name { 65 case "BucketName": 66 bucketName = a.Field(i).String() 67 case "Prefix": 68 objectName = a.Field(i).String() 69 case "ObjectName": 70 objectName = a.Field(i).String() 71 } 72 } 73 } 74 return bucketName, objectName 75 } 76 77 // WebGenericArgs - empty struct for calls that don't accept arguments 78 // for ex. ServerInfo 79 type WebGenericArgs struct{} 80 81 // WebGenericRep - reply structure for calls for which reply is success/failure 82 // for ex. RemoveObject MakeBucket 83 type WebGenericRep struct { 84 UIVersion string `json:"uiVersion"` 85 } 86 87 // ServerInfoRep - server info reply. 88 type ServerInfoRep struct { 89 MinioVersion string 90 MinioMemory string 91 MinioPlatform string 92 MinioRuntime string 93 MinioGlobalInfo map[string]interface{} 94 MinioUserInfo map[string]interface{} 95 UIVersion string `json:"uiVersion"` 96 } 97 98 // ServerInfo - get server info. 99 func (web *webAPIHandlers) ServerInfo(r *http.Request, args *WebGenericArgs, reply *ServerInfoRep) error { 100 ctx := newWebContext(r, args, "WebServerInfo") 101 claims, owner, authErr := webRequestAuthenticate(r) 102 if authErr != nil { 103 return toJSONError(ctx, authErr) 104 } 105 host, err := os.Hostname() 106 if err != nil { 107 host = "" 108 } 109 platform := fmt.Sprintf("Host: %s | OS: %s | Arch: %s", 110 host, 111 runtime.GOOS, 112 runtime.GOARCH) 113 goruntime := fmt.Sprintf("Version: %s | CPUs: %d", runtime.Version(), runtime.NumCPU()) 114 115 reply.MinioVersion = Version 116 reply.MinioGlobalInfo = getGlobalInfo() 117 118 // Check if the user is IAM user. 119 reply.MinioUserInfo = map[string]interface{}{ 120 "isIAMUser": !owner, 121 } 122 123 if !owner { 124 creds, ok := GlobalIAMSys.GetUser(ctx, claims.AccessKey) 125 if ok && creds.SessionToken != "" { 126 reply.MinioUserInfo["isTempUser"] = true 127 } 128 } 129 130 reply.MinioPlatform = platform 131 reply.MinioRuntime = goruntime 132 reply.UIVersion = Version 133 return nil 134 } 135 136 // StorageInfoRep - contains storage usage statistics. 137 type StorageInfoRep struct { 138 Used uint64 `json:"used"` 139 UIVersion string `json:"uiVersion"` 140 } 141 142 // StorageInfo - web call to gather storage usage statistics. 143 func (web *webAPIHandlers) StorageInfo(r *http.Request, args *WebGenericArgs, reply *StorageInfoRep) error { 144 ctx := newWebContext(r, args, "WebStorageInfo") 145 objectAPI := web.ObjectAPI() 146 if objectAPI == nil { 147 return toJSONError(ctx, errServerNotInitialized) 148 } 149 _, _, authErr := webRequestAuthenticate(r) 150 if authErr != nil { 151 return toJSONError(ctx, authErr) 152 } 153 dataUsageInfo, _ := loadDataUsageFromBackend(ctx, objectAPI) 154 reply.Used = dataUsageInfo.ObjectsTotalSize 155 reply.UIVersion = Version 156 return nil 157 } 158 159 // MakeBucketArgs - make bucket args. 160 type MakeBucketArgs struct { 161 BucketName string `json:"bucketName"` 162 } 163 164 // MakeBucket - creates a new bucket. 165 func (web *webAPIHandlers) MakeBucket(r *http.Request, args *MakeBucketArgs, reply *WebGenericRep) error { 166 ctx := newWebContext(r, args, "WebMakeBucket") 167 objectAPI := web.ObjectAPI() 168 if objectAPI == nil { 169 return toJSONError(ctx, errServerNotInitialized) 170 } 171 claims, owner, authErr := webRequestAuthenticate(r) 172 if authErr != nil { 173 return toJSONError(ctx, authErr) 174 } 175 176 // For authenticated users apply IAM policy. 177 if !GlobalIAMSys.IsAllowed(iampolicy.Args{ 178 AccountName: claims.AccessKey, 179 Action: iampolicy.CreateBucketAction, 180 BucketName: args.BucketName, 181 ConditionValues: getConditionValues(r, "", claims.AccessKey, claims.Map()), 182 IsOwner: owner, 183 Claims: claims.Map(), 184 }) { 185 return toJSONError(ctx, errAccessDenied) 186 } 187 188 // Check if bucket is a reserved bucket name or invalid. 189 if isReservedOrInvalidBucket(args.BucketName, true) { 190 return toJSONError(ctx, errInvalidBucketName, args.BucketName) 191 } 192 193 opts := BucketOptions{ 194 Location: globalServerRegion, 195 LockEnabled: false, 196 } 197 198 if err := objectAPI.MakeBucketWithLocation(ctx, args.BucketName, opts); err != nil { 199 return toJSONError(ctx, err, args.BucketName) 200 } 201 202 reply.UIVersion = Version 203 204 reqParams := extractReqParams(r) 205 reqParams["accessKey"] = claims.GetAccessKey() 206 207 sendEvent(eventArgs{ 208 EventName: event.BucketCreated, 209 BucketName: args.BucketName, 210 ReqParams: reqParams, 211 UserAgent: r.UserAgent(), 212 Host: handlers.GetSourceIP(r), 213 }) 214 215 return nil 216 } 217 218 // RemoveBucketArgs - remove bucket args. 219 type RemoveBucketArgs struct { 220 BucketName string `json:"bucketName"` 221 } 222 223 // DeleteBucket - removes a bucket, must be empty. 224 func (web *webAPIHandlers) DeleteBucket(r *http.Request, args *RemoveBucketArgs, reply *WebGenericRep) error { 225 ctx := newWebContext(r, args, "WebDeleteBucket") 226 objectAPI := web.ObjectAPI() 227 if objectAPI == nil { 228 return toJSONError(ctx, errServerNotInitialized) 229 } 230 claims, owner, authErr := webRequestAuthenticate(r) 231 if authErr != nil { 232 return toJSONError(ctx, authErr) 233 } 234 235 // For authenticated users apply IAM policy. 236 if !GlobalIAMSys.IsAllowed(iampolicy.Args{ 237 AccountName: claims.AccessKey, 238 Action: iampolicy.DeleteBucketAction, 239 BucketName: args.BucketName, 240 ConditionValues: getConditionValues(r, "", claims.AccessKey, claims.Map()), 241 IsOwner: owner, 242 Claims: claims.Map(), 243 }) { 244 return toJSONError(ctx, errAccessDenied) 245 } 246 247 // Check if bucket is a reserved bucket name or invalid. 248 if isReservedOrInvalidBucket(args.BucketName, false) { 249 return toJSONError(ctx, errInvalidBucketName, args.BucketName) 250 } 251 252 reply.UIVersion = Version 253 254 deleteBucket := objectAPI.DeleteBucket 255 256 if err := deleteBucket(ctx, args.BucketName, false); err != nil { 257 return toJSONError(ctx, err, args.BucketName) 258 } 259 260 GlobalNotificationSys.DeleteBucketMetadata(ctx, args.BucketName) 261 262 reqParams := extractReqParams(r) 263 reqParams["accessKey"] = claims.AccessKey 264 265 sendEvent(eventArgs{ 266 EventName: event.BucketRemoved, 267 BucketName: args.BucketName, 268 ReqParams: reqParams, 269 UserAgent: r.UserAgent(), 270 Host: handlers.GetSourceIP(r), 271 }) 272 273 return nil 274 } 275 276 // ListBucketsRep - list buckets response 277 type ListBucketsRep struct { 278 Buckets []WebBucketInfo `json:"buckets"` 279 UIVersion string `json:"uiVersion"` 280 } 281 282 // WebBucketInfo container for list buckets metadata. 283 type WebBucketInfo struct { 284 // The name of the bucket. 285 Name string `json:"name"` 286 // Date the bucket was created. 287 CreationDate time.Time `json:"creationDate"` 288 } 289 290 // ListBuckets - list buckets api. 291 func (web *webAPIHandlers) ListBuckets(r *http.Request, args *WebGenericArgs, reply *ListBucketsRep) error { 292 ctx := newWebContext(r, args, "WebListBuckets") 293 objectAPI := web.ObjectAPI() 294 if objectAPI == nil { 295 return toJSONError(ctx, errServerNotInitialized) 296 } 297 listBuckets := objectAPI.ListBuckets 298 299 claims, owner, authErr := webRequestAuthenticate(r) 300 if authErr != nil { 301 return toJSONError(ctx, authErr) 302 } 303 304 // Set prefix value for "s3:prefix" policy conditionals. 305 r.Header.Set("prefix", "") 306 307 // Set delimiter value for "s3:delimiter" policy conditionals. 308 r.Header.Set("delimiter", SlashSeparator) 309 310 buckets, err := listBuckets(ctx) 311 if err != nil { 312 return toJSONError(ctx, err) 313 } 314 for _, bucket := range buckets { 315 if GlobalIAMSys.IsAllowed(iampolicy.Args{ 316 AccountName: claims.AccessKey, 317 Action: iampolicy.ListBucketAction, 318 BucketName: bucket.Name, 319 ConditionValues: getConditionValues(r, "", claims.AccessKey, claims.Map()), 320 IsOwner: owner, 321 ObjectName: "", 322 Claims: claims.Map(), 323 }) { 324 reply.Buckets = append(reply.Buckets, WebBucketInfo{ 325 Name: bucket.Name, 326 CreationDate: bucket.Created, 327 }) 328 } 329 } 330 331 reply.UIVersion = Version 332 return nil 333 } 334 335 // ListObjectsArgs - list object args. 336 type ListObjectsArgs struct { 337 BucketName string `json:"bucketName"` 338 Prefix string `json:"prefix"` 339 Marker string `json:"marker"` 340 } 341 342 // ListObjectsRep - list objects response. 343 type ListObjectsRep struct { 344 Objects []WebObjectInfo `json:"objects"` 345 Writable bool `json:"writable"` // Used by client to show "upload file" button. 346 UIVersion string `json:"uiVersion"` 347 } 348 349 // WebObjectInfo container for list objects metadata. 350 type WebObjectInfo struct { 351 // Name of the object 352 Key string `json:"name"` 353 // Date and time the object was last modified. 354 LastModified time.Time `json:"lastModified"` 355 // Size in bytes of the object. 356 Size int64 `json:"size"` 357 // ContentType is mime type of the object. 358 ContentType string `json:"contentType"` 359 } 360 361 // ListObjects - list objects api. 362 func (web *webAPIHandlers) ListObjects(r *http.Request, args *ListObjectsArgs, reply *ListObjectsRep) error { 363 ctx := newWebContext(r, args, "WebListObjects") 364 reply.UIVersion = Version 365 objectAPI := web.ObjectAPI() 366 if objectAPI == nil { 367 return toJSONError(ctx, errServerNotInitialized) 368 } 369 370 listObjects := objectAPI.ListObjects 371 372 claims, owner, authErr := webRequestAuthenticate(r) 373 if authErr != nil { 374 if authErr == errNoAuthToken { 375 // Set prefix value for "s3:prefix" policy conditionals. 376 r.Header.Set("prefix", args.Prefix) 377 378 // Set delimiter value for "s3:delimiter" policy conditionals. 379 r.Header.Set("delimiter", SlashSeparator) 380 381 // Check if anonymous (non-owner) has access to download objects. 382 readable := globalPolicySys.IsAllowed(policy.Args{ 383 Action: policy.ListBucketAction, 384 BucketName: args.BucketName, 385 ConditionValues: getConditionValues(r, "", "", nil), 386 IsOwner: false, 387 }) 388 389 // Check if anonymous (non-owner) has access to upload objects. 390 writable := globalPolicySys.IsAllowed(policy.Args{ 391 Action: policy.PutObjectAction, 392 BucketName: args.BucketName, 393 ConditionValues: getConditionValues(r, "", "", nil), 394 IsOwner: false, 395 ObjectName: args.Prefix + SlashSeparator, 396 }) 397 398 reply.Writable = writable 399 if !readable { 400 // Error out if anonymous user (non-owner) has no access to download or upload objects 401 if !writable { 402 return errAccessDenied 403 } 404 // return empty object list if access is write only 405 return nil 406 } 407 } else { 408 return toJSONError(ctx, authErr) 409 } 410 } 411 412 // For authenticated users apply IAM policy. 413 if authErr == nil { 414 // Set prefix value for "s3:prefix" policy conditionals. 415 r.Header.Set("prefix", args.Prefix) 416 417 // Set delimiter value for "s3:delimiter" policy conditionals. 418 r.Header.Set("delimiter", SlashSeparator) 419 420 readable := GlobalIAMSys.IsAllowed(iampolicy.Args{ 421 AccountName: claims.AccessKey, 422 Action: iampolicy.ListBucketAction, 423 BucketName: args.BucketName, 424 ConditionValues: getConditionValues(r, "", claims.AccessKey, claims.Map()), 425 IsOwner: owner, 426 Claims: claims.Map(), 427 }) 428 429 writable := GlobalIAMSys.IsAllowed(iampolicy.Args{ 430 AccountName: claims.AccessKey, 431 Action: iampolicy.PutObjectAction, 432 BucketName: args.BucketName, 433 ConditionValues: getConditionValues(r, "", claims.AccessKey, claims.Map()), 434 IsOwner: owner, 435 ObjectName: args.Prefix + SlashSeparator, 436 Claims: claims.Map(), 437 }) 438 439 reply.Writable = writable 440 if !readable { 441 // Error out if anonymous user (non-owner) has no access to download or upload objects 442 if !writable { 443 return errAccessDenied 444 } 445 // return empty object list if access is write only 446 return nil 447 } 448 } 449 450 // Check if bucket is a reserved bucket name or invalid. 451 if isReservedOrInvalidBucket(args.BucketName, false) { 452 return toJSONError(ctx, errInvalidBucketName, args.BucketName) 453 } 454 455 nextMarker := "" 456 // Fetch all the objects 457 for { 458 // Limit browser to '1000' batches to be more responsive, scrolling friendly. 459 // Also don't change the maxKeys value silly GCS SDKs do not honor maxKeys 460 // values to be '-1' 461 lo, err := listObjects(ctx, args.BucketName, args.Prefix, nextMarker, SlashSeparator, 1000) 462 if err != nil { 463 return &json2.Error{Message: err.Error()} 464 } 465 466 nextMarker = lo.NextMarker 467 for i := range lo.Objects { 468 lo.Objects[i].Size, err = lo.Objects[i].GetActualSize() 469 if err != nil { 470 return toJSONError(ctx, err) 471 } 472 } 473 474 for _, obj := range lo.Objects { 475 reply.Objects = append(reply.Objects, WebObjectInfo{ 476 Key: obj.Name, 477 LastModified: obj.ModTime, 478 Size: obj.Size, 479 ContentType: obj.ContentType, 480 }) 481 } 482 for _, prefix := range lo.Prefixes { 483 reply.Objects = append(reply.Objects, WebObjectInfo{ 484 Key: prefix, 485 }) 486 } 487 488 // Return when there are no more objects 489 if !lo.IsTruncated { 490 return nil 491 } 492 } 493 } 494 495 // RemoveObjectArgs - args to remove an object, JSON will look like. 496 // 497 // { 498 // "bucketname": "testbucket", 499 // "objects": [ 500 // "photos/hawaii/", 501 // "photos/maldives/", 502 // "photos/sanjose.jpg" 503 // ] 504 // } 505 type RemoveObjectArgs struct { 506 Objects []string `json:"objects"` // Contains objects, prefixes. 507 BucketName string `json:"bucketname"` // Contains bucket name. 508 } 509 510 // RemoveObject - removes an object, or all the objects at a given prefix. 511 func (web *webAPIHandlers) RemoveObject(r *http.Request, args *RemoveObjectArgs, reply *WebGenericRep) error { 512 ctx := newWebContext(r, args, "WebRemoveObject") 513 objectAPI := web.ObjectAPI() 514 if objectAPI == nil { 515 return toJSONError(ctx, errServerNotInitialized) 516 } 517 518 deleteObjects := objectAPI.DeleteObjects 519 if web.CacheAPI() != nil { 520 deleteObjects = web.CacheAPI().DeleteObjects 521 } 522 getObjectInfoFn := objectAPI.GetObjectInfo 523 if web.CacheAPI() != nil { 524 getObjectInfoFn = web.CacheAPI().GetObjectInfo 525 } 526 527 claims, owner, authErr := webRequestAuthenticate(r) 528 if authErr != nil { 529 if authErr == errNoAuthToken { 530 // Check if all objects are allowed to be deleted anonymously 531 for _, object := range args.Objects { 532 if !globalPolicySys.IsAllowed(policy.Args{ 533 Action: policy.DeleteObjectAction, 534 BucketName: args.BucketName, 535 ConditionValues: getConditionValues(r, "", "", nil), 536 IsOwner: false, 537 ObjectName: object, 538 }) { 539 return toJSONError(ctx, errAuthentication) 540 } 541 } 542 } else { 543 return toJSONError(ctx, authErr) 544 } 545 } 546 547 if args.BucketName == "" || len(args.Objects) == 0 { 548 return toJSONError(ctx, errInvalidArgument) 549 } 550 551 // Check if bucket is a reserved bucket name or invalid. 552 if isReservedOrInvalidBucket(args.BucketName, false) { 553 return toJSONError(ctx, errInvalidBucketName, args.BucketName) 554 } 555 556 reply.UIVersion = Version 557 558 opts := ObjectOptions{ 559 Versioned: globalBucketVersioningSys.Enabled(args.BucketName), 560 VersionSuspended: globalBucketVersioningSys.Suspended(args.BucketName), 561 } 562 var ( 563 err error 564 replicateSync bool 565 ) 566 567 reqParams := extractReqParams(r) 568 reqParams["accessKey"] = claims.GetAccessKey() 569 sourceIP := handlers.GetSourceIP(r) 570 571 next: 572 for _, objectName := range args.Objects { 573 // If not a directory, remove the object. 574 if !HasSuffix(objectName, SlashSeparator) && objectName != "" { 575 // Check permissions for non-anonymous user. 576 if authErr != errNoAuthToken { 577 if !GlobalIAMSys.IsAllowed(iampolicy.Args{ 578 AccountName: claims.AccessKey, 579 Action: iampolicy.DeleteObjectAction, 580 BucketName: args.BucketName, 581 ConditionValues: getConditionValues(r, "", claims.AccessKey, claims.Map()), 582 IsOwner: owner, 583 ObjectName: objectName, 584 Claims: claims.Map(), 585 }) { 586 return toJSONError(ctx, errAccessDenied) 587 } 588 } 589 590 if authErr == errNoAuthToken { 591 // Check if object is allowed to be deleted anonymously. 592 if !globalPolicySys.IsAllowed(policy.Args{ 593 Action: policy.DeleteObjectAction, 594 BucketName: args.BucketName, 595 ConditionValues: getConditionValues(r, "", "", nil), 596 IsOwner: false, 597 ObjectName: objectName, 598 }) { 599 return toJSONError(ctx, errAccessDenied) 600 } 601 } 602 var ( 603 replicateDel, hasLifecycleConfig bool 604 goi ObjectInfo 605 gerr error 606 ) 607 if _, err := globalBucketMetadataSys.GetLifecycleConfig(args.BucketName); err == nil { 608 hasLifecycleConfig = true 609 } 610 if hasReplicationRules(ctx, args.BucketName, []ObjectToDelete{{ObjectName: objectName}}) || hasLifecycleConfig { 611 goi, gerr = getObjectInfoFn(ctx, args.BucketName, objectName, opts) 612 if replicateDel, replicateSync = checkReplicateDelete(ctx, args.BucketName, ObjectToDelete{ 613 ObjectName: objectName, 614 VersionID: goi.VersionID, 615 }, goi, gerr); replicateDel { 616 opts.DeleteMarkerReplicationStatus = string(replication.Pending) 617 opts.DeleteMarker = true 618 } 619 } 620 621 deleteObject := objectAPI.DeleteObject 622 if web.CacheAPI() != nil { 623 deleteObject = web.CacheAPI().DeleteObject 624 } 625 626 oi, err := deleteObject(ctx, args.BucketName, objectName, opts) 627 if err != nil { 628 switch err.(type) { 629 case BucketNotFound: 630 return toJSONError(ctx, err) 631 } 632 } 633 if oi.Name == "" { 634 logger.LogIf(ctx, err) 635 continue 636 } 637 638 eventName := event.ObjectRemovedDelete 639 if oi.DeleteMarker { 640 eventName = event.ObjectRemovedDeleteMarkerCreated 641 } 642 643 // Notify object deleted event. 644 sendEvent(eventArgs{ 645 EventName: eventName, 646 BucketName: args.BucketName, 647 Object: oi, 648 ReqParams: reqParams, 649 UserAgent: r.UserAgent(), 650 Host: sourceIP, 651 }) 652 653 if replicateDel { 654 dobj := DeletedObjectVersionInfo{ 655 DeletedObject: DeletedObject{ 656 ObjectName: objectName, 657 DeleteMarkerVersionID: oi.VersionID, 658 DeleteMarkerReplicationStatus: string(oi.ReplicationStatus), 659 DeleteMarkerMTime: DeleteMarkerMTime{oi.ModTime}, 660 DeleteMarker: oi.DeleteMarker, 661 VersionPurgeStatus: oi.VersionPurgeStatus, 662 }, 663 Bucket: args.BucketName, 664 } 665 scheduleReplicationDelete(ctx, dobj, objectAPI, replicateSync) 666 } 667 if goi.TransitionStatus == lifecycle.TransitionComplete { 668 deleteTransitionedObject(ctx, objectAPI, args.BucketName, objectName, lifecycle.ObjectOpts{ 669 Name: objectName, 670 UserTags: goi.UserTags, 671 VersionID: goi.VersionID, 672 DeleteMarker: goi.DeleteMarker, 673 TransitionStatus: goi.TransitionStatus, 674 IsLatest: goi.IsLatest, 675 }, false, true) 676 } 677 678 logger.LogIf(ctx, err) 679 continue 680 } 681 682 if authErr == errNoAuthToken { 683 // Check if object is allowed to be deleted anonymously 684 if !globalPolicySys.IsAllowed(policy.Args{ 685 Action: iampolicy.DeleteObjectAction, 686 BucketName: args.BucketName, 687 ConditionValues: getConditionValues(r, "", "", nil), 688 IsOwner: false, 689 ObjectName: objectName, 690 }) { 691 return toJSONError(ctx, errAccessDenied) 692 } 693 } else { 694 if !GlobalIAMSys.IsAllowed(iampolicy.Args{ 695 AccountName: claims.AccessKey, 696 Action: iampolicy.DeleteObjectAction, 697 BucketName: args.BucketName, 698 ConditionValues: getConditionValues(r, "", claims.AccessKey, claims.Map()), 699 IsOwner: owner, 700 ObjectName: objectName, 701 Claims: claims.Map(), 702 }) { 703 return toJSONError(ctx, errAccessDenied) 704 } 705 } 706 707 // Allocate new results channel to receive ObjectInfo. 708 objInfoCh := make(chan ObjectInfo) 709 710 // Walk through all objects 711 if err = objectAPI.Walk(ctx, args.BucketName, objectName, objInfoCh, ObjectOptions{}); err != nil { 712 break next 713 } 714 715 for { 716 var objects []ObjectToDelete 717 for obj := range objInfoCh { 718 if len(objects) == maxDeleteList { 719 // Reached maximum delete requests, attempt a delete for now. 720 break 721 } 722 if obj.ReplicationStatus == replication.Replica { 723 if authErr == errNoAuthToken { 724 // Check if object is allowed to be deleted anonymously 725 if !globalPolicySys.IsAllowed(policy.Args{ 726 Action: iampolicy.ReplicateDeleteAction, 727 BucketName: args.BucketName, 728 ConditionValues: getConditionValues(r, "", "", nil), 729 IsOwner: false, 730 ObjectName: objectName, 731 }) { 732 return toJSONError(ctx, errAccessDenied) 733 } 734 } else { 735 if !GlobalIAMSys.IsAllowed(iampolicy.Args{ 736 AccountName: claims.AccessKey, 737 Action: iampolicy.ReplicateDeleteAction, 738 BucketName: args.BucketName, 739 ConditionValues: getConditionValues(r, "", claims.AccessKey, claims.Map()), 740 IsOwner: owner, 741 ObjectName: objectName, 742 Claims: claims.Map(), 743 }) { 744 return toJSONError(ctx, errAccessDenied) 745 } 746 } 747 } 748 replicateDel, _ := checkReplicateDelete(ctx, args.BucketName, ObjectToDelete{ObjectName: obj.Name, VersionID: obj.VersionID}, obj, nil) 749 // since versioned delete is not available on web browser, yet - this is a simple DeleteMarker replication 750 objToDel := ObjectToDelete{ObjectName: obj.Name} 751 if replicateDel { 752 objToDel.DeleteMarkerReplicationStatus = string(replication.Pending) 753 } 754 755 objects = append(objects, objToDel) 756 } 757 758 // Nothing to do. 759 if len(objects) == 0 { 760 break next 761 } 762 763 // Deletes a list of objects. 764 deletedObjects, errs := deleteObjects(ctx, args.BucketName, objects, opts) 765 for i, err := range errs { 766 if err != nil && !isErrObjectNotFound(err) { 767 deletedObjects[i].DeleteMarkerReplicationStatus = objects[i].DeleteMarkerReplicationStatus 768 deletedObjects[i].VersionPurgeStatus = objects[i].VersionPurgeStatus 769 } 770 if err != nil { 771 logger.LogIf(ctx, err) 772 break next 773 } 774 } 775 // Notify deleted event for objects. 776 for _, dobj := range deletedObjects { 777 objInfo := ObjectInfo{ 778 Name: dobj.ObjectName, 779 VersionID: dobj.VersionID, 780 } 781 if dobj.DeleteMarker { 782 objInfo = ObjectInfo{ 783 Name: dobj.ObjectName, 784 DeleteMarker: dobj.DeleteMarker, 785 VersionID: dobj.DeleteMarkerVersionID, 786 } 787 } 788 sendEvent(eventArgs{ 789 EventName: event.ObjectRemovedDelete, 790 BucketName: args.BucketName, 791 Object: objInfo, 792 ReqParams: reqParams, 793 UserAgent: r.UserAgent(), 794 Host: sourceIP, 795 }) 796 if dobj.DeleteMarkerReplicationStatus == string(replication.Pending) || dobj.VersionPurgeStatus == Pending { 797 dv := DeletedObjectVersionInfo{ 798 DeletedObject: dobj, 799 Bucket: args.BucketName, 800 } 801 scheduleReplicationDelete(ctx, dv, objectAPI, replicateSync) 802 } 803 } 804 } 805 } 806 807 if err != nil && !isErrObjectNotFound(err) && !isErrVersionNotFound(err) { 808 // Ignore object not found error. 809 return toJSONError(ctx, err, args.BucketName, "") 810 } 811 812 return nil 813 } 814 815 // LoginArgs - login arguments. 816 type LoginArgs struct { 817 Username string `json:"username" form:"username"` 818 Password string `json:"password" form:"password"` 819 } 820 821 // LoginRep - login reply. 822 type LoginRep struct { 823 Token string `json:"token"` 824 UIVersion string `json:"uiVersion"` 825 } 826 827 // Login - user login handler. 828 func (web *webAPIHandlers) Login(r *http.Request, args *LoginArgs, reply *LoginRep) error { 829 ctx := newWebContext(r, args, "WebLogin") 830 token, err := authenticateWeb(args.Username, args.Password) 831 if err != nil { 832 return toJSONError(ctx, err) 833 } 834 835 reply.Token = token 836 reply.UIVersion = Version 837 return nil 838 } 839 840 // SetAuthArgs - argument for SetAuth 841 type SetAuthArgs struct { 842 CurrentAccessKey string `json:"currentAccessKey"` 843 CurrentSecretKey string `json:"currentSecretKey"` 844 NewAccessKey string `json:"newAccessKey"` 845 NewSecretKey string `json:"newSecretKey"` 846 } 847 848 // SetAuthReply - reply for SetAuth 849 type SetAuthReply struct { 850 Token string `json:"token"` 851 UIVersion string `json:"uiVersion"` 852 PeerErrMsgs map[string]string `json:"peerErrMsgs"` 853 } 854 855 // SetAuth - Set accessKey and secretKey credentials. 856 func (web *webAPIHandlers) SetAuth(r *http.Request, args *SetAuthArgs, reply *SetAuthReply) error { 857 ctx := newWebContext(r, args, "WebSetAuth") 858 claims, owner, authErr := webRequestAuthenticate(r) 859 if authErr != nil { 860 return toJSONError(ctx, authErr) 861 } 862 863 if owner { 864 // Owner is not allowed to change credentials through browser. 865 return toJSONError(ctx, errChangeCredNotAllowed) 866 } 867 868 if !GlobalIAMSys.IsAllowed(iampolicy.Args{ 869 AccountName: claims.AccessKey, 870 Action: iampolicy.CreateUserAdminAction, 871 IsOwner: false, 872 ConditionValues: getConditionValues(r, "", claims.AccessKey, claims.Map()), 873 Claims: claims.Map(), 874 DenyOnly: true, 875 }) { 876 return toJSONError(ctx, errChangeCredNotAllowed) 877 } 878 879 // for IAM users, access key cannot be updated 880 // claims.AccessKey is used instead of accesskey from args 881 prevCred, ok := GlobalIAMSys.GetUser(ctx, claims.AccessKey) 882 if !ok { 883 return errInvalidAccessKeyID 884 } 885 886 // Throw error when wrong secret key is provided 887 if subtle.ConstantTimeCompare([]byte(prevCred.SecretKey), []byte(args.CurrentSecretKey)) != 1 { 888 return errIncorrectCreds 889 } 890 891 creds, err := auth.CreateCredentials(claims.AccessKey, args.NewSecretKey) 892 if err != nil { 893 return toJSONError(ctx, err) 894 } 895 896 err = GlobalIAMSys.SetUserSecretKey(creds.AccessKey, creds.SecretKey) 897 if err != nil { 898 return toJSONError(ctx, err) 899 } 900 901 reply.Token, err = authenticateWeb(creds.AccessKey, creds.SecretKey) 902 if err != nil { 903 return toJSONError(ctx, err) 904 } 905 906 reply.UIVersion = Version 907 908 return nil 909 } 910 911 // URLTokenReply contains the reply for CreateURLToken. 912 type URLTokenReply struct { 913 Token string `json:"token"` 914 UIVersion string `json:"uiVersion"` 915 } 916 917 // CreateURLToken creates a URL token (short-lived) for GET requests. 918 func (web *webAPIHandlers) CreateURLToken(r *http.Request, args *WebGenericArgs, reply *URLTokenReply) error { 919 ctx := newWebContext(r, args, "WebCreateURLToken") 920 claims, owner, authErr := webRequestAuthenticate(r) 921 if authErr != nil { 922 return toJSONError(ctx, authErr) 923 } 924 925 creds := globalActiveCred 926 if !owner { 927 var ok bool 928 creds, ok = GlobalIAMSys.GetUser(ctx, claims.AccessKey) 929 if !ok { 930 return toJSONError(ctx, errInvalidAccessKeyID) 931 } 932 } 933 934 if creds.SessionToken != "" { 935 // Use the same session token for URL token. 936 reply.Token = creds.SessionToken 937 } else { 938 token, err := authenticateURL(creds.AccessKey, creds.SecretKey) 939 if err != nil { 940 return toJSONError(ctx, err) 941 } 942 reply.Token = token 943 } 944 945 reply.UIVersion = Version 946 return nil 947 } 948 949 // Upload - file upload handler. 950 func (web *webAPIHandlers) Upload(w http.ResponseWriter, r *http.Request) { 951 ctx := NewContext(r, w, "WebUpload") 952 953 // obtain the claims here if possible, for audit logging. 954 claims, owner, authErr := webRequestAuthenticate(r) 955 956 defer logger.AuditLog(ctx, w, r, claims.Map()) 957 958 objectAPI := web.ObjectAPI() 959 if objectAPI == nil { 960 writeWebErrorResponse(w, errServerNotInitialized) 961 return 962 } 963 964 vars := mux.Vars(r) 965 bucket := vars["bucket"] 966 object, err := unescapePath(vars["object"]) 967 if err != nil { 968 writeWebErrorResponse(w, err) 969 return 970 } 971 972 retPerms := ErrAccessDenied 973 holdPerms := ErrAccessDenied 974 replPerms := ErrAccessDenied 975 if authErr != nil { 976 if authErr == errNoAuthToken { 977 // Check if anonymous (non-owner) has access to upload objects. 978 if !globalPolicySys.IsAllowed(policy.Args{ 979 Action: policy.PutObjectAction, 980 BucketName: bucket, 981 ConditionValues: getConditionValues(r, "", "", nil), 982 IsOwner: false, 983 ObjectName: object, 984 }) { 985 writeWebErrorResponse(w, errAuthentication) 986 return 987 } 988 } else { 989 writeWebErrorResponse(w, authErr) 990 return 991 } 992 } 993 994 // For authenticated users apply IAM policy. 995 if authErr == nil { 996 if !GlobalIAMSys.IsAllowed(iampolicy.Args{ 997 AccountName: claims.AccessKey, 998 Action: iampolicy.PutObjectAction, 999 BucketName: bucket, 1000 ConditionValues: getConditionValues(r, "", claims.AccessKey, claims.Map()), 1001 IsOwner: owner, 1002 ObjectName: object, 1003 Claims: claims.Map(), 1004 }) { 1005 writeWebErrorResponse(w, errAuthentication) 1006 return 1007 } 1008 if GlobalIAMSys.IsAllowed(iampolicy.Args{ 1009 AccountName: claims.AccessKey, 1010 Action: iampolicy.PutObjectRetentionAction, 1011 BucketName: bucket, 1012 ConditionValues: getConditionValues(r, "", claims.AccessKey, claims.Map()), 1013 IsOwner: owner, 1014 ObjectName: object, 1015 Claims: claims.Map(), 1016 }) { 1017 retPerms = ErrNone 1018 } 1019 if GlobalIAMSys.IsAllowed(iampolicy.Args{ 1020 AccountName: claims.AccessKey, 1021 Action: iampolicy.PutObjectLegalHoldAction, 1022 BucketName: bucket, 1023 ConditionValues: getConditionValues(r, "", claims.AccessKey, claims.Map()), 1024 IsOwner: owner, 1025 ObjectName: object, 1026 Claims: claims.Map(), 1027 }) { 1028 holdPerms = ErrNone 1029 } 1030 if GlobalIAMSys.IsAllowed(iampolicy.Args{ 1031 AccountName: claims.AccessKey, 1032 Action: iampolicy.GetReplicationConfigurationAction, 1033 BucketName: bucket, 1034 ConditionValues: getConditionValues(r, "", claims.AccessKey, claims.Map()), 1035 IsOwner: owner, 1036 ObjectName: "", 1037 Claims: claims.Map(), 1038 }) { 1039 replPerms = ErrNone 1040 } 1041 } 1042 1043 // Check if bucket is a reserved bucket name or invalid. 1044 if isReservedOrInvalidBucket(bucket, false) { 1045 writeWebErrorResponse(w, errInvalidBucketName) 1046 return 1047 } 1048 1049 // Check if bucket encryption is enabled 1050 _, err = globalBucketSSEConfigSys.Get(bucket) 1051 if (globalAutoEncryption || err == nil) && !crypto.SSEC.IsRequested(r.Header) { 1052 r.Header.Set(xhttp.AmzServerSideEncryption, xhttp.AmzEncryptionAES) 1053 } 1054 1055 // Require Content-Length to be set in the request 1056 size := r.ContentLength 1057 if size < 0 { 1058 writeWebErrorResponse(w, errSizeUnspecified) 1059 return 1060 } 1061 1062 if err := enforceBucketQuota(ctx, bucket, size); err != nil { 1063 writeWebErrorResponse(w, err) 1064 return 1065 } 1066 1067 // Extract incoming metadata if any. 1068 metadata, err := extractMetadata(ctx, r) 1069 if err != nil { 1070 WriteErrorResponse(ctx, w, ToAPIError(ctx, err), r.URL, guessIsBrowserReq(r)) 1071 return 1072 } 1073 1074 var pReader *PutObjReader 1075 var reader io.Reader = r.Body 1076 actualSize := size 1077 1078 hashReader, err := hash.NewReader(reader, size, "", "", actualSize) 1079 if err != nil { 1080 writeWebErrorResponse(w, err) 1081 return 1082 } 1083 1084 if objectAPI.IsCompressionSupported() && isCompressible(r.Header, object) && size > 0 { 1085 // Storing the compression metadata. 1086 metadata[ReservedMetadataPrefix+"compression"] = compressionAlgorithmV2 1087 metadata[ReservedMetadataPrefix+"actual-size"] = strconv.FormatInt(actualSize, 10) 1088 1089 actualReader, err := hash.NewReader(reader, actualSize, "", "", actualSize) 1090 if err != nil { 1091 writeWebErrorResponse(w, err) 1092 return 1093 } 1094 1095 // Set compression metrics. 1096 size = -1 // Since compressed size is un-predictable. 1097 s2c := newS2CompressReader(actualReader, actualSize) 1098 defer s2c.Close() 1099 reader = etag.Wrap(s2c, actualReader) 1100 hashReader, err = hash.NewReader(reader, size, "", "", actualSize) 1101 if err != nil { 1102 writeWebErrorResponse(w, err) 1103 return 1104 } 1105 } 1106 1107 mustReplicate, sync := mustReplicateWeb(ctx, r, bucket, object, metadata, "", replPerms) 1108 if mustReplicate { 1109 metadata[xhttp.AmzBucketReplicationStatus] = string(replication.Pending) 1110 } 1111 pReader = NewPutObjReader(hashReader) 1112 // get gateway encryption options 1113 opts, err := putOpts(ctx, r, bucket, object, metadata) 1114 if err != nil { 1115 writeErrorResponseHeadersOnly(w, ToAPIError(ctx, err)) 1116 return 1117 } 1118 1119 if objectAPI.IsEncryptionSupported() { 1120 if _, ok := crypto.IsRequested(r.Header); ok && !HasSuffix(object, SlashSeparator) { // handle SSE requests 1121 var ( 1122 objectEncryptionKey crypto.ObjectKey 1123 encReader io.Reader 1124 ) 1125 encReader, objectEncryptionKey, err = EncryptRequest(hashReader, r, bucket, object, metadata) 1126 if err != nil { 1127 WriteErrorResponse(ctx, w, ToAPIError(ctx, err), r.URL, guessIsBrowserReq(r)) 1128 return 1129 } 1130 info := ObjectInfo{Size: size} 1131 // do not try to verify encrypted content 1132 hashReader, err = hash.NewReader(etag.Wrap(encReader, hashReader), info.EncryptedSize(), "", "", size) 1133 if err != nil { 1134 WriteErrorResponse(ctx, w, ToAPIError(ctx, err), r.URL, guessIsBrowserReq(r)) 1135 return 1136 } 1137 pReader, err = pReader.WithEncryption(hashReader, &objectEncryptionKey) 1138 if err != nil { 1139 WriteErrorResponse(ctx, w, ToAPIError(ctx, err), r.URL, guessIsBrowserReq(r)) 1140 return 1141 } 1142 } 1143 } 1144 1145 // Ensure that metadata does not contain sensitive information 1146 crypto.RemoveSensitiveEntries(metadata) 1147 1148 putObject := objectAPI.PutObject 1149 getObjectInfo := objectAPI.GetObjectInfo 1150 if web.CacheAPI() != nil { 1151 putObject = web.CacheAPI().PutObject 1152 getObjectInfo = web.CacheAPI().GetObjectInfo 1153 } 1154 1155 // enforce object retention rules 1156 retentionMode, retentionDate, _, s3Err := checkPutObjectLockAllowed(ctx, r, bucket, object, getObjectInfo, retPerms, holdPerms) 1157 if s3Err != ErrNone { 1158 WriteErrorResponse(ctx, w, errorCodes.ToAPIErr(s3Err), r.URL, guessIsBrowserReq(r)) 1159 return 1160 } 1161 if retentionMode != "" { 1162 opts.UserDefined[strings.ToLower(xhttp.AmzObjectLockMode)] = string(retentionMode) 1163 opts.UserDefined[strings.ToLower(xhttp.AmzObjectLockRetainUntilDate)] = retentionDate.UTC().Format(iso8601TimeFormat) 1164 } 1165 1166 objInfo, err := putObject(GlobalContext, bucket, object, pReader, opts) 1167 if err != nil { 1168 writeWebErrorResponse(w, err) 1169 return 1170 } 1171 if objectAPI.IsEncryptionSupported() { 1172 switch kind, _ := crypto.IsEncrypted(objInfo.UserDefined); kind { 1173 case crypto.S3: 1174 w.Header().Set(xhttp.AmzServerSideEncryption, xhttp.AmzEncryptionAES) 1175 case crypto.SSEC: 1176 w.Header().Set(xhttp.AmzServerSideEncryptionCustomerAlgorithm, r.Header.Get(xhttp.AmzServerSideEncryptionCustomerAlgorithm)) 1177 w.Header().Set(xhttp.AmzServerSideEncryptionCustomerKeyMD5, r.Header.Get(xhttp.AmzServerSideEncryptionCustomerKeyMD5)) 1178 } 1179 } 1180 if mustReplicate { 1181 scheduleReplication(ctx, objInfo.Clone(), objectAPI, sync, replication.ObjectReplicationType) 1182 } 1183 1184 reqParams := extractReqParams(r) 1185 reqParams["accessKey"] = claims.GetAccessKey() 1186 1187 // Notify object created event. 1188 sendEvent(eventArgs{ 1189 EventName: event.ObjectCreatedPut, 1190 BucketName: bucket, 1191 Object: objInfo, 1192 ReqParams: reqParams, 1193 RespElements: extractRespElements(w), 1194 UserAgent: r.UserAgent(), 1195 Host: handlers.GetSourceIP(r), 1196 }) 1197 } 1198 1199 // Download - file download handler. 1200 func (web *webAPIHandlers) Download(w http.ResponseWriter, r *http.Request) { 1201 ctx := NewContext(r, w, "WebDownload") 1202 1203 claims, owner, authErr := webTokenAuthenticate(r.URL.Query().Get("token")) 1204 defer logger.AuditLog(ctx, w, r, claims.Map()) 1205 1206 objectAPI := web.ObjectAPI() 1207 if objectAPI == nil { 1208 writeWebErrorResponse(w, errServerNotInitialized) 1209 return 1210 } 1211 1212 vars := mux.Vars(r) 1213 1214 bucket := vars["bucket"] 1215 object, err := unescapePath(vars["object"]) 1216 if err != nil { 1217 writeWebErrorResponse(w, err) 1218 return 1219 } 1220 1221 getRetPerms := ErrAccessDenied 1222 legalHoldPerms := ErrAccessDenied 1223 1224 if authErr != nil { 1225 if authErr == errNoAuthToken { 1226 // Check if anonymous (non-owner) has access to download objects. 1227 if !globalPolicySys.IsAllowed(policy.Args{ 1228 Action: policy.GetObjectAction, 1229 BucketName: bucket, 1230 ConditionValues: getConditionValues(r, "", "", nil), 1231 IsOwner: false, 1232 ObjectName: object, 1233 }) { 1234 writeWebErrorResponse(w, errAuthentication) 1235 return 1236 } 1237 if globalPolicySys.IsAllowed(policy.Args{ 1238 Action: policy.GetObjectRetentionAction, 1239 BucketName: bucket, 1240 ConditionValues: getConditionValues(r, "", "", nil), 1241 IsOwner: false, 1242 ObjectName: object, 1243 }) { 1244 getRetPerms = ErrNone 1245 } 1246 if globalPolicySys.IsAllowed(policy.Args{ 1247 Action: policy.GetObjectLegalHoldAction, 1248 BucketName: bucket, 1249 ConditionValues: getConditionValues(r, "", "", nil), 1250 IsOwner: false, 1251 ObjectName: object, 1252 }) { 1253 legalHoldPerms = ErrNone 1254 } 1255 } else { 1256 writeWebErrorResponse(w, authErr) 1257 return 1258 } 1259 } 1260 1261 // For authenticated users apply IAM policy. 1262 if authErr == nil { 1263 if !GlobalIAMSys.IsAllowed(iampolicy.Args{ 1264 AccountName: claims.AccessKey, 1265 Action: iampolicy.GetObjectAction, 1266 BucketName: bucket, 1267 ConditionValues: getConditionValues(r, "", claims.AccessKey, claims.Map()), 1268 IsOwner: owner, 1269 ObjectName: object, 1270 Claims: claims.Map(), 1271 }) { 1272 writeWebErrorResponse(w, errAuthentication) 1273 return 1274 } 1275 if GlobalIAMSys.IsAllowed(iampolicy.Args{ 1276 AccountName: claims.AccessKey, 1277 Action: iampolicy.GetObjectRetentionAction, 1278 BucketName: bucket, 1279 ConditionValues: getConditionValues(r, "", claims.AccessKey, claims.Map()), 1280 IsOwner: owner, 1281 ObjectName: object, 1282 Claims: claims.Map(), 1283 }) { 1284 getRetPerms = ErrNone 1285 } 1286 if GlobalIAMSys.IsAllowed(iampolicy.Args{ 1287 AccountName: claims.AccessKey, 1288 Action: iampolicy.GetObjectLegalHoldAction, 1289 BucketName: bucket, 1290 ConditionValues: getConditionValues(r, "", claims.AccessKey, claims.Map()), 1291 IsOwner: owner, 1292 ObjectName: object, 1293 Claims: claims.Map(), 1294 }) { 1295 legalHoldPerms = ErrNone 1296 } 1297 } 1298 1299 // Check if bucket is a reserved bucket name or invalid. 1300 if isReservedOrInvalidBucket(bucket, false) { 1301 writeWebErrorResponse(w, errInvalidBucketName) 1302 return 1303 } 1304 1305 getObjectNInfo := objectAPI.GetObjectNInfo 1306 if web.CacheAPI() != nil { 1307 getObjectNInfo = web.CacheAPI().GetObjectNInfo 1308 } 1309 1310 var opts ObjectOptions 1311 gr, err := getObjectNInfo(ctx, bucket, object, nil, r.Header, readLock, opts) 1312 if err != nil { 1313 writeWebErrorResponse(w, err) 1314 return 1315 } 1316 defer gr.Close() 1317 1318 objInfo := gr.ObjInfo 1319 1320 // filter object lock metadata if permission does not permit 1321 objInfo.UserDefined = objectlock.FilterObjectLockMetadata(objInfo.UserDefined, getRetPerms != ErrNone, legalHoldPerms != ErrNone) 1322 1323 if objectAPI.IsEncryptionSupported() { 1324 if _, err = DecryptObjectInfo(&objInfo, r); err != nil { 1325 writeWebErrorResponse(w, err) 1326 return 1327 } 1328 } 1329 1330 // Set encryption response headers 1331 if objectAPI.IsEncryptionSupported() { 1332 switch kind, _ := crypto.IsEncrypted(objInfo.UserDefined); kind { 1333 case crypto.S3: 1334 w.Header().Set(xhttp.AmzServerSideEncryption, xhttp.AmzEncryptionAES) 1335 case crypto.SSEC: 1336 w.Header().Set(xhttp.AmzServerSideEncryptionCustomerAlgorithm, r.Header.Get(xhttp.AmzServerSideEncryptionCustomerAlgorithm)) 1337 w.Header().Set(xhttp.AmzServerSideEncryptionCustomerKeyMD5, r.Header.Get(xhttp.AmzServerSideEncryptionCustomerKeyMD5)) 1338 } 1339 } 1340 1341 // Set Parts Count Header 1342 if opts.PartNumber > 0 && len(objInfo.Parts) > 0 { 1343 setPartsCountHeaders(w, objInfo) 1344 } 1345 1346 if err = setObjectHeaders(w, objInfo, nil, opts); err != nil { 1347 writeWebErrorResponse(w, err) 1348 return 1349 } 1350 1351 // Add content disposition. 1352 w.Header().Set(xhttp.ContentDisposition, fmt.Sprintf("attachment; filename=\"%s\"", path.Base(objInfo.Name))) 1353 1354 setHeadGetRespHeaders(w, r.URL.Query()) 1355 1356 httpWriter := ioutil.WriteOnClose(w) 1357 1358 // Write object content to response body 1359 if _, err = io.Copy(httpWriter, gr); err != nil { 1360 if !httpWriter.HasWritten() { // write error response only if no data or headers has been written to client yet 1361 writeWebErrorResponse(w, err) 1362 } 1363 return 1364 } 1365 1366 if err = httpWriter.Close(); err != nil { 1367 if !httpWriter.HasWritten() { // write error response only if no data or headers has been written to client yet 1368 writeWebErrorResponse(w, err) 1369 return 1370 } 1371 } 1372 1373 reqParams := extractReqParams(r) 1374 reqParams["accessKey"] = claims.GetAccessKey() 1375 1376 // Notify object accessed via a GET request. 1377 sendEvent(eventArgs{ 1378 EventName: event.ObjectAccessedGet, 1379 BucketName: bucket, 1380 Object: objInfo, 1381 ReqParams: reqParams, 1382 RespElements: extractRespElements(w), 1383 UserAgent: r.UserAgent(), 1384 Host: handlers.GetSourceIP(r), 1385 }) 1386 } 1387 1388 // DownloadZipArgs - Argument for downloading a bunch of files as a zip file. 1389 // JSON will look like: 1390 // '{"bucketname":"testbucket","prefix":"john/pics/","objects":["hawaii/","maldives/","sanjose.jpg"]}' 1391 type DownloadZipArgs struct { 1392 Objects []string `json:"objects"` // can be files or sub-directories 1393 Prefix string `json:"prefix"` // current directory in the browser-ui 1394 BucketName string `json:"bucketname"` // bucket name. 1395 } 1396 1397 // Takes a list of objects and creates a zip file that sent as the response body. 1398 func (web *webAPIHandlers) DownloadZip(w http.ResponseWriter, r *http.Request) { 1399 host := handlers.GetSourceIP(r) 1400 1401 claims, owner, authErr := webTokenAuthenticate(r.URL.Query().Get("token")) 1402 1403 ctx := NewContext(r, w, "WebDownloadZip") 1404 defer logger.AuditLog(ctx, w, r, claims.Map()) 1405 1406 objectAPI := web.ObjectAPI() 1407 if objectAPI == nil { 1408 writeWebErrorResponse(w, errServerNotInitialized) 1409 return 1410 } 1411 1412 // Auth is done after reading the body to accommodate for anonymous requests 1413 // when bucket policy is enabled. 1414 var args DownloadZipArgs 1415 tenKB := 10 * 1024 // To limit r.Body to take care of misbehaving anonymous client. 1416 decodeErr := json.NewDecoder(io.LimitReader(r.Body, int64(tenKB))).Decode(&args) 1417 if decodeErr != nil { 1418 writeWebErrorResponse(w, decodeErr) 1419 return 1420 } 1421 1422 var getRetPerms []APIErrorCode 1423 var legalHoldPerms []APIErrorCode 1424 1425 if authErr != nil { 1426 if authErr == errNoAuthToken { 1427 for _, object := range args.Objects { 1428 // Check if anonymous (non-owner) has access to download objects. 1429 if !globalPolicySys.IsAllowed(policy.Args{ 1430 Action: policy.GetObjectAction, 1431 BucketName: args.BucketName, 1432 ConditionValues: getConditionValues(r, "", "", nil), 1433 IsOwner: false, 1434 ObjectName: pathJoin(args.Prefix, object), 1435 }) { 1436 writeWebErrorResponse(w, errAuthentication) 1437 return 1438 } 1439 retentionPerm := ErrAccessDenied 1440 if globalPolicySys.IsAllowed(policy.Args{ 1441 Action: policy.GetObjectRetentionAction, 1442 BucketName: args.BucketName, 1443 ConditionValues: getConditionValues(r, "", "", nil), 1444 IsOwner: false, 1445 ObjectName: pathJoin(args.Prefix, object), 1446 }) { 1447 retentionPerm = ErrNone 1448 } 1449 getRetPerms = append(getRetPerms, retentionPerm) 1450 1451 legalHoldPerm := ErrAccessDenied 1452 if globalPolicySys.IsAllowed(policy.Args{ 1453 Action: policy.GetObjectLegalHoldAction, 1454 BucketName: args.BucketName, 1455 ConditionValues: getConditionValues(r, "", "", nil), 1456 IsOwner: false, 1457 ObjectName: pathJoin(args.Prefix, object), 1458 }) { 1459 legalHoldPerm = ErrNone 1460 } 1461 legalHoldPerms = append(legalHoldPerms, legalHoldPerm) 1462 } 1463 } else { 1464 writeWebErrorResponse(w, authErr) 1465 return 1466 } 1467 } 1468 1469 // For authenticated users apply IAM policy. 1470 if authErr == nil { 1471 for _, object := range args.Objects { 1472 if !GlobalIAMSys.IsAllowed(iampolicy.Args{ 1473 AccountName: claims.AccessKey, 1474 Action: iampolicy.GetObjectAction, 1475 BucketName: args.BucketName, 1476 ConditionValues: getConditionValues(r, "", claims.AccessKey, claims.Map()), 1477 IsOwner: owner, 1478 ObjectName: pathJoin(args.Prefix, object), 1479 Claims: claims.Map(), 1480 }) { 1481 writeWebErrorResponse(w, errAuthentication) 1482 return 1483 } 1484 retentionPerm := ErrAccessDenied 1485 if GlobalIAMSys.IsAllowed(iampolicy.Args{ 1486 AccountName: claims.AccessKey, 1487 Action: iampolicy.GetObjectRetentionAction, 1488 BucketName: args.BucketName, 1489 ConditionValues: getConditionValues(r, "", claims.AccessKey, claims.Map()), 1490 IsOwner: owner, 1491 ObjectName: pathJoin(args.Prefix, object), 1492 Claims: claims.Map(), 1493 }) { 1494 retentionPerm = ErrNone 1495 } 1496 getRetPerms = append(getRetPerms, retentionPerm) 1497 1498 legalHoldPerm := ErrAccessDenied 1499 if GlobalIAMSys.IsAllowed(iampolicy.Args{ 1500 AccountName: claims.AccessKey, 1501 Action: iampolicy.GetObjectLegalHoldAction, 1502 BucketName: args.BucketName, 1503 ConditionValues: getConditionValues(r, "", claims.AccessKey, claims.Map()), 1504 IsOwner: owner, 1505 ObjectName: pathJoin(args.Prefix, object), 1506 Claims: claims.Map(), 1507 }) { 1508 legalHoldPerm = ErrNone 1509 } 1510 legalHoldPerms = append(legalHoldPerms, legalHoldPerm) 1511 } 1512 } 1513 1514 // Check if bucket is a reserved bucket name or invalid. 1515 if isReservedOrInvalidBucket(args.BucketName, false) { 1516 writeWebErrorResponse(w, errInvalidBucketName) 1517 return 1518 } 1519 1520 getObjectNInfo := objectAPI.GetObjectNInfo 1521 if web.CacheAPI() != nil { 1522 getObjectNInfo = web.CacheAPI().GetObjectNInfo 1523 } 1524 1525 archive := zip.NewWriter(w) 1526 defer archive.Close() 1527 1528 reqParams := extractReqParams(r) 1529 reqParams["accessKey"] = claims.GetAccessKey() 1530 respElements := extractRespElements(w) 1531 1532 for i, object := range args.Objects { 1533 if contextCanceled(ctx) { 1534 return 1535 } 1536 // Writes compressed object file to the response. 1537 zipit := func(objectName string) error { 1538 var opts ObjectOptions 1539 gr, err := getObjectNInfo(ctx, args.BucketName, objectName, nil, r.Header, readLock, opts) 1540 if err != nil { 1541 return err 1542 } 1543 defer gr.Close() 1544 1545 info := gr.ObjInfo 1546 // filter object lock metadata if permission does not permit 1547 info.UserDefined = objectlock.FilterObjectLockMetadata(info.UserDefined, getRetPerms[i] != ErrNone, legalHoldPerms[i] != ErrNone) 1548 // For reporting, set the file size to the uncompressed size. 1549 info.Size, err = info.GetActualSize() 1550 if err != nil { 1551 return err 1552 } 1553 header := &zip.FileHeader{ 1554 Name: strings.TrimPrefix(objectName, args.Prefix), 1555 Method: zip.Deflate, 1556 Flags: 1 << 11, 1557 Modified: info.ModTime, 1558 UncompressedSize64: uint64(info.Size), 1559 } 1560 if info.Size < 20 || hasStringSuffixInSlice(info.Name, standardExcludeCompressExtensions) || hasPattern(standardExcludeCompressContentTypes, info.ContentType) { 1561 // We strictly disable compression for standard extensions/content-types. 1562 header.Method = zip.Store 1563 } 1564 writer, err := archive.CreateHeader(header) 1565 if err != nil { 1566 return err 1567 } 1568 1569 // Write object content to response body 1570 if _, err = io.Copy(writer, gr); err != nil { 1571 return err 1572 } 1573 1574 // Notify object accessed via a GET request. 1575 sendEvent(eventArgs{ 1576 EventName: event.ObjectAccessedGet, 1577 BucketName: args.BucketName, 1578 Object: info, 1579 ReqParams: reqParams, 1580 RespElements: respElements, 1581 UserAgent: r.UserAgent(), 1582 Host: host, 1583 }) 1584 1585 return nil 1586 } 1587 1588 if !HasSuffix(object, SlashSeparator) { 1589 // If not a directory, compress the file and write it to response. 1590 err := zipit(pathJoin(args.Prefix, object)) 1591 if err != nil { 1592 logger.LogIf(ctx, err) 1593 return 1594 } 1595 continue 1596 } 1597 1598 objInfoCh := make(chan ObjectInfo) 1599 1600 // Walk through all objects 1601 if err := objectAPI.Walk(ctx, args.BucketName, pathJoin(args.Prefix, object), objInfoCh, ObjectOptions{}); err != nil { 1602 logger.LogIf(ctx, err) 1603 continue 1604 } 1605 1606 for obj := range objInfoCh { 1607 if err := zipit(obj.Name); err != nil { 1608 logger.LogIf(ctx, err) 1609 continue 1610 } 1611 } 1612 } 1613 } 1614 1615 // GetBucketPolicyArgs - get bucket policy args. 1616 type GetBucketPolicyArgs struct { 1617 BucketName string `json:"bucketName"` 1618 Prefix string `json:"prefix"` 1619 } 1620 1621 // GetBucketPolicyRep - get bucket policy reply. 1622 type GetBucketPolicyRep struct { 1623 UIVersion string `json:"uiVersion"` 1624 Policy miniogopolicy.BucketPolicy `json:"policy"` 1625 } 1626 1627 // GetBucketPolicy - get bucket policy for the requested prefix. 1628 func (web *webAPIHandlers) GetBucketPolicy(r *http.Request, args *GetBucketPolicyArgs, reply *GetBucketPolicyRep) error { 1629 ctx := newWebContext(r, args, "WebGetBucketPolicy") 1630 1631 objectAPI := web.ObjectAPI() 1632 if objectAPI == nil { 1633 return toJSONError(ctx, errServerNotInitialized) 1634 } 1635 1636 claims, owner, authErr := webRequestAuthenticate(r) 1637 if authErr != nil { 1638 return toJSONError(ctx, authErr) 1639 } 1640 1641 // For authenticated users apply IAM policy. 1642 if !GlobalIAMSys.IsAllowed(iampolicy.Args{ 1643 AccountName: claims.AccessKey, 1644 Action: iampolicy.GetBucketPolicyAction, 1645 BucketName: args.BucketName, 1646 ConditionValues: getConditionValues(r, "", claims.AccessKey, claims.Map()), 1647 IsOwner: owner, 1648 Claims: claims.Map(), 1649 }) { 1650 return toJSONError(ctx, errAccessDenied) 1651 } 1652 1653 // Check if bucket is a reserved bucket name or invalid. 1654 if isReservedOrInvalidBucket(args.BucketName, false) { 1655 return toJSONError(ctx, errInvalidBucketName, args.BucketName) 1656 } 1657 1658 var policyInfo = &miniogopolicy.BucketAccessPolicy{Version: "2012-10-17"} 1659 bucketPolicy, err := globalPolicySys.Get(args.BucketName) 1660 if err != nil { 1661 if _, ok := err.(BucketPolicyNotFound); !ok { 1662 return toJSONError(ctx, err, args.BucketName) 1663 } 1664 } 1665 1666 policyInfo, err = PolicyToBucketAccessPolicy(bucketPolicy) 1667 if err != nil { 1668 // This should not happen. 1669 return toJSONError(ctx, err, args.BucketName) 1670 } 1671 1672 reply.UIVersion = Version 1673 reply.Policy = miniogopolicy.GetPolicy(policyInfo.Statements, args.BucketName, args.Prefix) 1674 1675 return nil 1676 } 1677 1678 // ListAllBucketPoliciesArgs - get all bucket policies. 1679 type ListAllBucketPoliciesArgs struct { 1680 BucketName string `json:"bucketName"` 1681 } 1682 1683 // BucketAccessPolicy - Collection of canned bucket policy at a given prefix. 1684 type BucketAccessPolicy struct { 1685 Bucket string `json:"bucket"` 1686 Prefix string `json:"prefix"` 1687 Policy miniogopolicy.BucketPolicy `json:"policy"` 1688 } 1689 1690 // ListAllBucketPoliciesRep - get all bucket policy reply. 1691 type ListAllBucketPoliciesRep struct { 1692 UIVersion string `json:"uiVersion"` 1693 Policies []BucketAccessPolicy `json:"policies"` 1694 } 1695 1696 // ListAllBucketPolicies - get all bucket policy. 1697 func (web *webAPIHandlers) ListAllBucketPolicies(r *http.Request, args *ListAllBucketPoliciesArgs, reply *ListAllBucketPoliciesRep) error { 1698 ctx := newWebContext(r, args, "WebListAllBucketPolicies") 1699 objectAPI := web.ObjectAPI() 1700 if objectAPI == nil { 1701 return toJSONError(ctx, errServerNotInitialized) 1702 } 1703 1704 claims, owner, authErr := webRequestAuthenticate(r) 1705 if authErr != nil { 1706 return toJSONError(ctx, authErr) 1707 } 1708 1709 // For authenticated users apply IAM policy. 1710 if !GlobalIAMSys.IsAllowed(iampolicy.Args{ 1711 AccountName: claims.AccessKey, 1712 Action: iampolicy.GetBucketPolicyAction, 1713 BucketName: args.BucketName, 1714 ConditionValues: getConditionValues(r, "", claims.AccessKey, claims.Map()), 1715 IsOwner: owner, 1716 Claims: claims.Map(), 1717 }) { 1718 return toJSONError(ctx, errAccessDenied) 1719 } 1720 1721 // Check if bucket is a reserved bucket name or invalid. 1722 if isReservedOrInvalidBucket(args.BucketName, false) { 1723 return toJSONError(ctx, errInvalidBucketName, args.BucketName) 1724 } 1725 1726 var policyInfo = new(miniogopolicy.BucketAccessPolicy) 1727 bucketPolicy, err := globalPolicySys.Get(args.BucketName) 1728 if err != nil { 1729 if _, ok := err.(BucketPolicyNotFound); !ok { 1730 return toJSONError(ctx, err, args.BucketName) 1731 } 1732 } 1733 policyInfo, err = PolicyToBucketAccessPolicy(bucketPolicy) 1734 if err != nil { 1735 return toJSONError(ctx, err, args.BucketName) 1736 } 1737 1738 reply.UIVersion = Version 1739 for prefix, policy := range miniogopolicy.GetPolicies(policyInfo.Statements, args.BucketName, "") { 1740 bucketName, objectPrefix := path2BucketObject(prefix) 1741 objectPrefix = strings.TrimSuffix(objectPrefix, "*") 1742 reply.Policies = append(reply.Policies, BucketAccessPolicy{ 1743 Bucket: bucketName, 1744 Prefix: objectPrefix, 1745 Policy: policy, 1746 }) 1747 } 1748 1749 return nil 1750 } 1751 1752 // SetBucketPolicyWebArgs - set bucket policy args. 1753 type SetBucketPolicyWebArgs struct { 1754 BucketName string `json:"bucketName"` 1755 Prefix string `json:"prefix"` 1756 Policy string `json:"policy"` 1757 } 1758 1759 // SetBucketPolicy - set bucket policy. 1760 func (web *webAPIHandlers) SetBucketPolicy(r *http.Request, args *SetBucketPolicyWebArgs, reply *WebGenericRep) error { 1761 ctx := newWebContext(r, args, "WebSetBucketPolicy") 1762 objectAPI := web.ObjectAPI() 1763 reply.UIVersion = Version 1764 1765 if objectAPI == nil { 1766 return toJSONError(ctx, errServerNotInitialized) 1767 } 1768 1769 claims, owner, authErr := webRequestAuthenticate(r) 1770 if authErr != nil { 1771 return toJSONError(ctx, authErr) 1772 } 1773 1774 // For authenticated users apply IAM policy. 1775 if !GlobalIAMSys.IsAllowed(iampolicy.Args{ 1776 AccountName: claims.AccessKey, 1777 Action: iampolicy.PutBucketPolicyAction, 1778 BucketName: args.BucketName, 1779 ConditionValues: getConditionValues(r, "", claims.AccessKey, claims.Map()), 1780 IsOwner: owner, 1781 Claims: claims.Map(), 1782 }) { 1783 return toJSONError(ctx, errAccessDenied) 1784 } 1785 1786 // Check if bucket is a reserved bucket name or invalid. 1787 if isReservedOrInvalidBucket(args.BucketName, false) { 1788 return toJSONError(ctx, errInvalidBucketName, args.BucketName) 1789 } 1790 1791 policyType := miniogopolicy.BucketPolicy(args.Policy) 1792 if !policyType.IsValidBucketPolicy() { 1793 return &json2.Error{ 1794 Message: "Invalid policy type " + args.Policy, 1795 } 1796 } 1797 1798 bucketPolicy, err := globalPolicySys.Get(args.BucketName) 1799 if err != nil { 1800 if _, ok := err.(BucketPolicyNotFound); !ok { 1801 return toJSONError(ctx, err, args.BucketName) 1802 } 1803 } 1804 policyInfo, err := PolicyToBucketAccessPolicy(bucketPolicy) 1805 if err != nil { 1806 // This should not happen. 1807 return toJSONError(ctx, err, args.BucketName) 1808 } 1809 1810 policyInfo.Statements = miniogopolicy.SetPolicy(policyInfo.Statements, policyType, args.BucketName, args.Prefix) 1811 if len(policyInfo.Statements) == 0 { 1812 if err = globalBucketMetadataSys.Update(args.BucketName, bucketPolicyConfig, nil); err != nil { 1813 return toJSONError(ctx, err, args.BucketName) 1814 } 1815 1816 return nil 1817 } 1818 1819 bucketPolicy, err = BucketAccessPolicyToPolicy(policyInfo) 1820 if err != nil { 1821 // This should not happen. 1822 return toJSONError(ctx, err, args.BucketName) 1823 } 1824 1825 configData, err := json.Marshal(bucketPolicy) 1826 if err != nil { 1827 return toJSONError(ctx, err, args.BucketName) 1828 } 1829 1830 // Parse validate and save bucket policy. 1831 if err = globalBucketMetadataSys.Update(args.BucketName, bucketPolicyConfig, configData); err != nil { 1832 return toJSONError(ctx, err, args.BucketName) 1833 } 1834 1835 return nil 1836 } 1837 1838 // PresignedGetArgs - presigned-get API args. 1839 type PresignedGetArgs struct { 1840 // Host header required for signed headers. 1841 HostName string `json:"host"` 1842 1843 // Bucket name of the object to be presigned. 1844 BucketName string `json:"bucket"` 1845 1846 // Object name to be presigned. 1847 ObjectName string `json:"object"` 1848 1849 // Expiry in seconds. 1850 Expiry int64 `json:"expiry"` 1851 } 1852 1853 // PresignedGetRep - presigned-get URL reply. 1854 type PresignedGetRep struct { 1855 UIVersion string `json:"uiVersion"` 1856 // Presigned URL of the object. 1857 URL string `json:"url"` 1858 } 1859 1860 // PresignedGET - returns presigned-Get url. 1861 func (web *webAPIHandlers) PresignedGet(r *http.Request, args *PresignedGetArgs, reply *PresignedGetRep) error { 1862 ctx := newWebContext(r, args, "WebPresignedGet") 1863 claims, owner, authErr := webRequestAuthenticate(r) 1864 if authErr != nil { 1865 return toJSONError(ctx, authErr) 1866 } 1867 var creds auth.Credentials 1868 if !owner { 1869 var ok bool 1870 creds, ok = GlobalIAMSys.GetUser(ctx, claims.AccessKey) 1871 if !ok { 1872 return toJSONError(ctx, errInvalidAccessKeyID) 1873 } 1874 } else { 1875 creds = globalActiveCred 1876 } 1877 1878 region := globalServerRegion 1879 if args.BucketName == "" || args.ObjectName == "" { 1880 return &json2.Error{ 1881 Message: "Bucket and Object are mandatory arguments.", 1882 } 1883 } 1884 1885 // Check if bucket is a reserved bucket name or invalid. 1886 if isReservedOrInvalidBucket(args.BucketName, false) { 1887 return toJSONError(ctx, errInvalidBucketName, args.BucketName) 1888 } 1889 1890 // Check if the user indeed has GetObject access, 1891 // if not we do not need to generate presigned URLs 1892 if !GlobalIAMSys.IsAllowed(iampolicy.Args{ 1893 AccountName: claims.AccessKey, 1894 Action: iampolicy.GetObjectAction, 1895 BucketName: args.BucketName, 1896 ConditionValues: getConditionValues(r, "", claims.AccessKey, claims.Map()), 1897 IsOwner: owner, 1898 ObjectName: args.ObjectName, 1899 Claims: claims.Map(), 1900 }) { 1901 return toJSONError(ctx, errPresignedNotAllowed) 1902 } 1903 1904 reply.UIVersion = Version 1905 reply.URL = presignedGet(args.HostName, args.BucketName, args.ObjectName, args.Expiry, creds, region) 1906 return nil 1907 } 1908 1909 // Returns presigned url for GET method. 1910 func presignedGet(host, bucket, object string, expiry int64, creds auth.Credentials, region string) string { 1911 accessKey := creds.AccessKey 1912 secretKey := creds.SecretKey 1913 sessionToken := creds.SessionToken 1914 1915 date := UTCNow() 1916 dateStr := date.Format(iso8601Format) 1917 credential := fmt.Sprintf("%s/%s", accessKey, getScope(date, region)) 1918 1919 var expiryStr = "604800" // Default set to be expire in 7days. 1920 if expiry < 604800 && expiry > 0 { 1921 expiryStr = strconv.FormatInt(expiry, 10) 1922 } 1923 1924 query := url.Values{} 1925 query.Set(xhttp.AmzAlgorithm, signV4Algorithm) 1926 query.Set(xhttp.AmzCredential, credential) 1927 query.Set(xhttp.AmzDate, dateStr) 1928 query.Set(xhttp.AmzExpires, expiryStr) 1929 query.Set(xhttp.ContentDisposition, fmt.Sprintf("attachment; filename=\"%s\"", object)) 1930 // Set session token if available. 1931 if sessionToken != "" { 1932 query.Set(xhttp.AmzSecurityToken, sessionToken) 1933 } 1934 query.Set(xhttp.AmzSignedHeaders, "host") 1935 queryStr := s3utils.QueryEncode(query) 1936 1937 path := SlashSeparator + path.Join(bucket, object) 1938 1939 // "host" is the only header required to be signed for Presigned URLs. 1940 extractedSignedHeaders := make(http.Header) 1941 extractedSignedHeaders.Set("host", host) 1942 canonicalRequest := getCanonicalRequest(extractedSignedHeaders, unsignedPayload, queryStr, path, http.MethodGet) 1943 stringToSign := getStringToSign(canonicalRequest, date, getScope(date, region)) 1944 signingKey := getSigningKey(secretKey, date, region, serviceS3) 1945 signature := getSignature(signingKey, stringToSign) 1946 1947 return host + s3utils.EncodePath(path) + "?" + queryStr + "&" + xhttp.AmzSignature + "=" + signature 1948 } 1949 1950 // DiscoveryDocResp - OpenID discovery document reply. 1951 type DiscoveryDocResp struct { 1952 DiscoveryDoc openid.DiscoveryDoc 1953 UIVersion string `json:"uiVersion"` 1954 ClientID string `json:"clientId"` 1955 } 1956 1957 // GetDiscoveryDoc - returns parsed value of OpenID discovery document 1958 func (web *webAPIHandlers) GetDiscoveryDoc(r *http.Request, args *WebGenericArgs, reply *DiscoveryDocResp) error { 1959 if globalOpenIDConfig.DiscoveryDoc.AuthEndpoint != "" { 1960 reply.DiscoveryDoc = globalOpenIDConfig.DiscoveryDoc 1961 reply.ClientID = globalOpenIDConfig.ClientID 1962 } 1963 reply.UIVersion = Version 1964 return nil 1965 } 1966 1967 // LoginSTSArgs - login arguments. 1968 type LoginSTSArgs struct { 1969 Token string `json:"token" form:"token"` 1970 } 1971 1972 var errSTSNotInitialized = errors.New("STS API not initialized, please configure STS support") 1973 1974 // LoginSTS - STS user login handler. 1975 func (web *webAPIHandlers) LoginSTS(r *http.Request, args *LoginSTSArgs, reply *LoginRep) error { 1976 ctx := newWebContext(r, args, "WebLoginSTS") 1977 1978 if globalOpenIDValidators == nil { 1979 return toJSONError(ctx, errSTSNotInitialized) 1980 } 1981 1982 v, err := globalOpenIDValidators.Get("jwt") 1983 if err != nil { 1984 logger.LogIf(ctx, err) 1985 return toJSONError(ctx, errSTSNotInitialized) 1986 } 1987 1988 m, err := v.Validate(args.Token, "") 1989 if err != nil { 1990 return toJSONError(ctx, err) 1991 } 1992 1993 // JWT has requested a custom claim with policy value set. 1994 // This is a MinIO STS API specific value, this value should 1995 // be set and configured on your identity provider as part of 1996 // JWT custom claims. 1997 var policyName string 1998 policySet, ok := iampolicy.GetPoliciesFromClaims(m, iamPolicyClaimNameOpenID()) 1999 if ok { 2000 policyName = GlobalIAMSys.CurrentPolicies(strings.Join(policySet.ToSlice(), ",")) 2001 } 2002 if policyName == "" && GlobalPolicyOPA == nil { 2003 return toJSONError(ctx, fmt.Errorf("%s claim missing from the JWT token, credentials will not be generated", iamPolicyClaimNameOpenID())) 2004 } 2005 m[iamPolicyClaimNameOpenID()] = policyName 2006 2007 secret := globalActiveCred.SecretKey 2008 cred, err := auth.GetNewCredentialsWithMetadata(m, secret) 2009 if err != nil { 2010 return toJSONError(ctx, err) 2011 } 2012 2013 // Set the newly generated credentials. 2014 if err = GlobalIAMSys.SetTempUser(cred.AccessKey, cred, policyName); err != nil { 2015 return toJSONError(ctx, err) 2016 } 2017 2018 // Notify all other MinIO peers to reload temp users 2019 for _, nerr := range GlobalNotificationSys.LoadUser(cred.AccessKey, true) { 2020 if nerr.Err != nil { 2021 logger.GetReqInfo(ctx).SetTags("peerAddress", nerr.Host.String()) 2022 logger.LogIf(ctx, nerr.Err) 2023 } 2024 } 2025 2026 reply.Token = cred.SessionToken 2027 reply.UIVersion = Version 2028 return nil 2029 } 2030 2031 // toJSONError converts regular errors into more user friendly 2032 // and consumable error message for the browser UI. 2033 func toJSONError(ctx context.Context, err error, params ...string) (jerr *json2.Error) { 2034 apiErr := toWebAPIError(ctx, err) 2035 jerr = &json2.Error{ 2036 Message: apiErr.Description, 2037 } 2038 switch apiErr.Code { 2039 // Reserved bucket name provided. 2040 case "AllAccessDisabled": 2041 if len(params) > 0 { 2042 jerr = &json2.Error{ 2043 Message: fmt.Sprintf("All access to this bucket %s has been disabled.", params[0]), 2044 } 2045 } 2046 // Bucket name invalid with custom error message. 2047 case "InvalidBucketName": 2048 if len(params) > 0 { 2049 jerr = &json2.Error{ 2050 Message: fmt.Sprintf("Bucket Name %s is invalid. Lowercase letters, period, hyphen, numerals are the only allowed characters and should be minimum 3 characters in length.", params[0]), 2051 } 2052 } 2053 // Bucket not found custom error message. 2054 case "NoSuchBucket": 2055 if len(params) > 0 { 2056 jerr = &json2.Error{ 2057 Message: fmt.Sprintf("The specified bucket %s does not exist.", params[0]), 2058 } 2059 } 2060 // Object not found custom error message. 2061 case "NoSuchKey": 2062 if len(params) > 1 { 2063 jerr = &json2.Error{ 2064 Message: fmt.Sprintf("The specified key %s does not exist", params[1]), 2065 } 2066 } 2067 // Add more custom error messages here with more context. 2068 } 2069 return jerr 2070 } 2071 2072 // toWebAPIError - convert into error into APIError. 2073 func toWebAPIError(ctx context.Context, err error) APIError { 2074 switch err { 2075 case errNoAuthToken: 2076 return APIError{ 2077 Code: "WebTokenMissing", 2078 HTTPStatusCode: http.StatusBadRequest, 2079 Description: err.Error(), 2080 } 2081 case errSTSNotInitialized: 2082 return APIError(stsErrCodes.ToSTSErr(ErrSTSNotInitialized)) 2083 case errServerNotInitialized: 2084 return APIError{ 2085 Code: "XMinioServerNotInitialized", 2086 HTTPStatusCode: http.StatusServiceUnavailable, 2087 Description: err.Error(), 2088 } 2089 case errAuthentication, auth.ErrInvalidAccessKeyLength, 2090 auth.ErrInvalidSecretKeyLength, errInvalidAccessKeyID, errAccessDenied, errLockedObject: 2091 return APIError{ 2092 Code: "AccessDenied", 2093 HTTPStatusCode: http.StatusForbidden, 2094 Description: err.Error(), 2095 } 2096 case errSizeUnspecified: 2097 return APIError{ 2098 Code: "InvalidRequest", 2099 HTTPStatusCode: http.StatusBadRequest, 2100 Description: err.Error(), 2101 } 2102 case errChangeCredNotAllowed: 2103 return APIError{ 2104 Code: "MethodNotAllowed", 2105 HTTPStatusCode: http.StatusMethodNotAllowed, 2106 Description: err.Error(), 2107 } 2108 case errInvalidBucketName: 2109 return APIError{ 2110 Code: "InvalidBucketName", 2111 HTTPStatusCode: http.StatusBadRequest, 2112 Description: err.Error(), 2113 } 2114 case errInvalidArgument: 2115 return APIError{ 2116 Code: "InvalidArgument", 2117 HTTPStatusCode: http.StatusBadRequest, 2118 Description: err.Error(), 2119 } 2120 case errEncryptedObject: 2121 return GetAPIError(ErrSSEEncryptedObject) 2122 case errInvalidEncryptionParameters: 2123 return GetAPIError(ErrInvalidEncryptionParameters) 2124 case errObjectTampered: 2125 return GetAPIError(ErrObjectTampered) 2126 case errMethodNotAllowed: 2127 return GetAPIError(ErrMethodNotAllowed) 2128 } 2129 2130 // Convert error type to api error code. 2131 switch err.(type) { 2132 case StorageFull: 2133 return GetAPIError(ErrStorageFull) 2134 case BucketQuotaExceeded: 2135 return GetAPIError(ErrAdminBucketQuotaExceeded) 2136 case BucketNotFound: 2137 return GetAPIError(ErrNoSuchBucket) 2138 case BucketNotEmpty: 2139 return GetAPIError(ErrBucketNotEmpty) 2140 case BucketExists: 2141 return GetAPIError(ErrBucketAlreadyOwnedByYou) 2142 case BucketNameInvalid: 2143 return GetAPIError(ErrInvalidBucketName) 2144 case hash.BadDigest: 2145 return GetAPIError(ErrBadDigest) 2146 case IncompleteBody: 2147 return GetAPIError(ErrIncompleteBody) 2148 case ObjectExistsAsDirectory: 2149 return GetAPIError(ErrObjectExistsAsDirectory) 2150 case ObjectNotFound: 2151 return GetAPIError(ErrNoSuchKey) 2152 case ObjectNameInvalid: 2153 return GetAPIError(ErrNoSuchKey) 2154 case InsufficientWriteQuorum: 2155 return GetAPIError(ErrWriteQuorum) 2156 case InsufficientReadQuorum: 2157 return GetAPIError(ErrReadQuorum) 2158 case NotImplemented: 2159 return APIError{ 2160 Code: "NotImplemented", 2161 HTTPStatusCode: http.StatusBadRequest, 2162 Description: "Functionality not implemented", 2163 } 2164 } 2165 2166 // Log unexpected and unhandled errors. 2167 logger.LogIf(ctx, err) 2168 return ToAPIError(ctx, err) 2169 } 2170 2171 // writeWebErrorResponse - set HTTP status code and write error description to the body. 2172 func writeWebErrorResponse(w http.ResponseWriter, err error) { 2173 reqInfo := &logger.ReqInfo{ 2174 DeploymentID: globalDeploymentID, 2175 } 2176 ctx := logger.SetReqInfo(GlobalContext, reqInfo) 2177 apiErr := toWebAPIError(ctx, err) 2178 w.WriteHeader(apiErr.HTTPStatusCode) 2179 w.Write([]byte(apiErr.Description)) 2180 }