2015-06-30 17:42:29 -04:00
/ *
2020-02-03 03:24:20 -05:00
* MinIO Cloud Storage , ( C ) 2015 - 2020 MinIO , Inc .
2015-06-30 17:42:29 -04:00
*
* Licensed under the Apache License , Version 2.0 ( the "License" ) ;
* you may not use this file except in compliance with the License .
* You may obtain a copy of the License at
*
* http : //www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing , software
* distributed under the License is distributed on an "AS IS" BASIS ,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND , either express or implied .
* See the License for the specific language governing permissions and
* limitations under the License .
* /
2016-08-18 19:23:42 -04:00
package cmd
2015-06-30 17:42:29 -04:00
import (
2016-10-25 02:47:03 -04:00
"encoding/base64"
2016-03-05 19:43:48 -05:00
"encoding/xml"
2018-04-05 11:18:42 -04:00
"fmt"
2016-02-15 20:42:39 -05:00
"io"
2015-06-30 17:42:29 -04:00
"net/http"
accessPolicy: Implement Put, Get, Delete access policy.
This patch implements Get,Put,Delete bucket policies
Supporting - http://docs.aws.amazon.com/AmazonS3/latest/dev/access-policy-language-overview.html
Currently supports following actions.
"*": true,
"s3:*": true,
"s3:GetObject": true,
"s3:ListBucket": true,
"s3:PutObject": true,
"s3:CreateBucket": true,
"s3:GetBucketLocation": true,
"s3:DeleteBucket": true,
"s3:DeleteObject": true,
"s3:AbortMultipartUpload": true,
"s3:ListBucketMultipartUploads": true,
"s3:ListMultipartUploadParts": true,
following conditions for "StringEquals" and "StringNotEquals"
"s3:prefix", "s3:max-keys"
2016-02-03 19:46:56 -05:00
"net/url"
2017-01-11 16:26:42 -05:00
"path"
2017-11-15 17:10:45 -05:00
"path/filepath"
2020-07-08 20:36:56 -04:00
"strconv"
accessPolicy: Implement Put, Get, Delete access policy.
This patch implements Get,Put,Delete bucket policies
Supporting - http://docs.aws.amazon.com/AmazonS3/latest/dev/access-policy-language-overview.html
Currently supports following actions.
"*": true,
"s3:*": true,
"s3:GetObject": true,
"s3:ListBucket": true,
"s3:PutObject": true,
"s3:CreateBucket": true,
"s3:GetBucketLocation": true,
"s3:DeleteBucket": true,
"s3:DeleteObject": true,
"s3:AbortMultipartUpload": true,
"s3:ListBucketMultipartUploads": true,
"s3:ListMultipartUploadParts": true,
following conditions for "StringEquals" and "StringNotEquals"
"s3:prefix", "s3:max-keys"
2016-02-03 19:46:56 -05:00
"strings"
2015-06-30 17:42:29 -04:00
2018-04-21 22:23:54 -04:00
"github.com/gorilla/mux"
2018-05-11 15:02:30 -04:00
2020-07-14 12:38:05 -04:00
"github.com/minio/minio-go/v7/pkg/set"
"github.com/minio/minio-go/v7/pkg/tags"
2020-07-21 20:49:56 -04:00
"github.com/minio/minio/cmd/config"
2020-09-10 17:19:32 -04:00
"github.com/minio/minio/cmd/config/dns"
2018-08-17 15:52:14 -04:00
"github.com/minio/minio/cmd/crypto"
2019-07-03 01:34:32 -04:00
xhttp "github.com/minio/minio/cmd/http"
2018-04-05 18:04:40 -04:00
"github.com/minio/minio/cmd/logger"
2020-01-27 17:12:34 -05:00
objectlock "github.com/minio/minio/pkg/bucket/object/lock"
"github.com/minio/minio/pkg/bucket/policy"
2020-07-21 20:49:56 -04:00
"github.com/minio/minio/pkg/bucket/replication"
"github.com/minio/minio/pkg/env"
2018-03-15 16:03:41 -04:00
"github.com/minio/minio/pkg/event"
2018-07-02 17:40:18 -04:00
"github.com/minio/minio/pkg/handlers"
2017-10-22 01:30:34 -04:00
"github.com/minio/minio/pkg/hash"
2019-08-12 13:27:38 -04:00
iampolicy "github.com/minio/minio/pkg/iam/policy"
2018-04-05 11:18:42 -04:00
"github.com/minio/minio/pkg/sync/errgroup"
2015-06-30 17:42:29 -04:00
)
2019-11-12 17:50:18 -05:00
const (
2020-07-21 20:49:56 -04:00
objectLockConfig = "object-lock.xml"
bucketTaggingConfig = "tagging.xml"
bucketReplicationConfig = "replication.xml"
2019-11-12 17:50:18 -05:00
)
2018-04-05 11:18:42 -04:00
// Check if there are buckets on server without corresponding entry in etcd backend and
// make entries. Here is the general flow
// - Range over all the available buckets
// - Check if a bucket has an entry in etcd backend
// -- If no, make an entry
2019-12-16 23:30:57 -05:00
// -- If yes, check if the entry matches local IP check if we
// need to update the entry then proceed to update
// -- If yes, check if the IP of entry matches local IP.
// This means entry is for this instance.
// -- If IP of the entry doesn't match, this means entry is
// for another instance. Log an error to console.
2019-11-09 12:27:23 -05:00
func initFederatorBackend ( buckets [ ] BucketInfo , objLayer ObjectLayer ) {
if len ( buckets ) == 0 {
2018-04-05 11:18:42 -04:00
return
}
2019-08-13 11:49:26 -04:00
// Get buckets in the DNS
dnsBuckets , err := globalDNSConfig . List ( )
2020-09-09 15:20:49 -04:00
if err != nil && err != dns . ErrNoEntriesFound && err != dns . ErrNotImplemented {
2020-04-09 12:30:02 -04:00
logger . LogIf ( GlobalContext , err )
2019-08-13 11:49:26 -04:00
return
}
2020-02-03 03:24:20 -05:00
bucketsSet := set . NewStringSet ( )
bucketsToBeUpdated := set . NewStringSet ( )
bucketsInConflict := set . NewStringSet ( )
2020-09-09 15:20:49 -04:00
if dnsBuckets != nil {
for _ , bucket := range buckets {
bucketsSet . Add ( bucket . Name )
r , ok := dnsBuckets [ bucket . Name ]
if ! ok {
bucketsToBeUpdated . Add ( bucket . Name )
2020-02-03 03:24:20 -05:00
continue
}
2020-09-09 15:20:49 -04:00
if ! globalDomainIPs . Intersection ( set . CreateStringSet ( getHostsSlice ( r ) ... ) ) . IsEmpty ( ) {
if globalDomainIPs . Difference ( set . CreateStringSet ( getHostsSlice ( r ) ... ) ) . IsEmpty ( ) {
// No difference in terms of domainIPs and nothing
// has changed so we don't change anything on the etcd.
continue
}
// if domain IPs intersect then it won't be an empty set.
// such an intersection means that bucket exists on etcd.
// but if we do see a difference with local domain IPs with
// hostSlice from etcd then we should update with newer
// domainIPs, we proceed to do that here.
bucketsToBeUpdated . Add ( bucket . Name )
continue
}
// No IPs seem to intersect, this means that bucket exists but has
// different IP addresses perhaps from a different deployment.
// bucket names are globally unique in federation at a given
// path prefix, name collision is not allowed. We simply log
// an error and continue.
bucketsInConflict . Add ( bucket . Name )
2020-02-03 03:24:20 -05:00
}
}
2019-08-13 11:49:26 -04:00
2020-02-03 03:24:20 -05:00
// Add/update buckets that are not registered with the DNS
2019-11-09 12:27:23 -05:00
g := errgroup . WithNErrs ( len ( buckets ) )
2020-02-03 03:24:20 -05:00
bucketsToBeUpdatedSlice := bucketsToBeUpdated . ToSlice ( )
for index := range bucketsToBeUpdatedSlice {
2019-10-14 12:44:51 -04:00
index := index
2018-04-05 11:18:42 -04:00
g . Go ( func ( ) error {
2020-02-03 03:24:20 -05:00
return globalDNSConfig . Put ( bucketsToBeUpdatedSlice [ index ] )
2018-04-05 11:18:42 -04:00
} , index )
}
for _ , err := range g . Wait ( ) {
if err != nil {
2020-04-09 12:30:02 -04:00
logger . LogIf ( GlobalContext , err )
2019-08-13 11:49:26 -04:00
}
}
2020-02-03 03:24:20 -05:00
for _ , bucket := range bucketsInConflict . ToSlice ( ) {
2020-04-09 12:30:02 -04:00
logger . LogIf ( GlobalContext , fmt . Errorf ( "Unable to add bucket DNS entry for bucket %s, an entry exists for the same bucket. Use one of these IP addresses %v to access the bucket" , bucket , globalDomainIPs . ToSlice ( ) ) )
2020-02-03 03:24:20 -05:00
}
2019-08-13 11:49:26 -04:00
// Remove buckets that are in DNS for this server, but aren't local
2020-02-03 03:24:20 -05:00
for bucket , records := range dnsBuckets {
if bucketsSet . Contains ( bucket ) {
continue
}
2019-08-13 11:49:26 -04:00
2020-02-03 03:24:20 -05:00
if globalDomainIPs . Intersection ( set . CreateStringSet ( getHostsSlice ( records ) ... ) ) . IsEmpty ( ) {
2019-08-13 11:49:26 -04:00
// This is not for our server, so we can continue
2020-02-03 03:24:20 -05:00
continue
}
2019-08-13 11:49:26 -04:00
2020-02-03 03:24:20 -05:00
// We go to here, so we know the bucket no longer exists,
// but is registered in DNS to this server
if err = globalDNSConfig . Delete ( bucket ) ; err != nil {
2020-04-09 12:30:02 -04:00
logger . LogIf ( GlobalContext , fmt . Errorf ( "Failed to remove DNS entry for %s due to %w" ,
2020-02-03 03:24:20 -05:00
bucket , err ) )
2018-04-05 11:18:42 -04:00
}
}
}
2015-12-27 02:38:38 -05:00
// GetBucketLocationHandler - GET Bucket location.
// -------------------------
// This operation returns bucket location.
2016-04-12 15:45:15 -04:00
func ( api objectAPIHandlers ) GetBucketLocationHandler ( w http . ResponseWriter , r * http . Request ) {
2018-07-20 21:46:32 -04:00
ctx := newContext ( r , w , "GetBucketLocation" )
2018-03-14 15:01:47 -04:00
2018-11-21 23:03:24 -05:00
defer logger . AuditLog ( w , r , "GetBucketLocation" , mustGetClaimsFromToken ( r ) )
2018-10-12 15:25:59 -04:00
2016-02-15 20:42:39 -05:00
vars := mux . Vars ( r )
2015-12-27 02:38:38 -05:00
bucket := vars [ "bucket" ]
2016-08-10 21:47:49 -04:00
objectAPI := api . ObjectAPI ( )
if objectAPI == nil {
2019-02-13 19:07:21 -05:00
writeErrorResponse ( ctx , w , errorCodes . ToAPIErr ( ErrServerNotInitialized ) , r . URL , guessIsBrowserReq ( r ) )
2016-08-10 21:47:49 -04:00
return
}
2018-04-24 18:53:30 -04:00
if s3Error := checkRequestAuthType ( ctx , r , policy . GetBucketLocationAction , bucket , "" ) ; s3Error != ErrNone {
2019-02-13 19:07:21 -05:00
writeErrorResponse ( ctx , w , errorCodes . ToAPIErr ( s3Error ) , r . URL , guessIsBrowserReq ( r ) )
2016-11-21 16:51:05 -05:00
return
2016-02-04 15:52:25 -05:00
}
2018-03-28 17:14:06 -04:00
getBucketInfo := objectAPI . GetBucketInfo
2019-08-09 20:09:08 -04:00
2018-03-28 17:14:06 -04:00
if _ , err := getBucketInfo ( ctx , bucket ) ; err != nil {
2019-02-13 19:07:21 -05:00
writeErrorResponse ( ctx , w , toAPIError ( ctx , err ) , r . URL , guessIsBrowserReq ( r ) )
2016-01-19 20:49:48 -05:00
return
2015-12-27 02:38:38 -05:00
}
2016-02-15 20:42:39 -05:00
// Generate response.
2016-03-06 15:16:22 -05:00
encodedSuccessResponse := encodeResponse ( LocationResponse { } )
config/main: Re-write config files - add to new config v3
- New config format.
```
{
"version": "3",
"address": ":9000",
"backend": {
"type": "fs",
"disk": "/path"
},
"credential": {
"accessKey": "WLGDGYAQYIGI833EV05A",
"secretKey": "BYvgJM101sHngl2uzjXS/OBF/aMxAN06JrJ3qJlF"
},
"region": "us-east-1",
"logger": {
"file": {
"enable": false,
"fileName": "",
"level": "error"
},
"syslog": {
"enable": false,
"address": "",
"level": "debug"
},
"console": {
"enable": true,
"level": "fatal"
}
}
}
```
New command lines in lieu of supporting XL.
Minio initialize filesystem backend.
~~~
$ minio init fs <path>
~~~
Minio initialize XL backend.
~~~
$ minio init xl <url1>...<url16>
~~~
For 'fs' backend it starts the server.
~~~
$ minio server
~~~
For 'xl' backend it waits for servers to join.
~~~
$ minio server
... [PROGRESS BAR] of servers connecting
~~~
Now on other servers execute 'join' and they connect.
~~~
....
minio join <url1> -- from <url2> && minio server
minio join <url1> -- from <url3> && minio server
...
...
minio join <url1> -- from <url16> && minio server
~~~
2016-02-12 18:27:10 -05:00
// Get current region.
2019-10-23 01:59:13 -04:00
region := globalServerRegion
2017-01-18 15:24:34 -05:00
if region != globalMinioDefaultRegion {
2016-03-06 15:16:22 -05:00
encodedSuccessResponse = encodeResponse ( LocationResponse {
config/main: Re-write config files - add to new config v3
- New config format.
```
{
"version": "3",
"address": ":9000",
"backend": {
"type": "fs",
"disk": "/path"
},
"credential": {
"accessKey": "WLGDGYAQYIGI833EV05A",
"secretKey": "BYvgJM101sHngl2uzjXS/OBF/aMxAN06JrJ3qJlF"
},
"region": "us-east-1",
"logger": {
"file": {
"enable": false,
"fileName": "",
"level": "error"
},
"syslog": {
"enable": false,
"address": "",
"level": "debug"
},
"console": {
"enable": true,
"level": "fatal"
}
}
}
```
New command lines in lieu of supporting XL.
Minio initialize filesystem backend.
~~~
$ minio init fs <path>
~~~
Minio initialize XL backend.
~~~
$ minio init xl <url1>...<url16>
~~~
For 'fs' backend it starts the server.
~~~
$ minio server
~~~
For 'xl' backend it waits for servers to join.
~~~
$ minio server
... [PROGRESS BAR] of servers connecting
~~~
Now on other servers execute 'join' and they connect.
~~~
....
minio join <url1> -- from <url2> && minio server
minio join <url1> -- from <url3> && minio server
...
...
minio join <url1> -- from <url16> && minio server
~~~
2016-02-12 18:27:10 -05:00
Location : region ,
2016-02-15 20:42:39 -05:00
} )
}
2017-01-06 03:37:00 -05:00
// Write success response.
writeSuccessResponseXML ( w , encodedSuccessResponse )
2015-12-27 02:38:38 -05:00
}
2015-06-30 23:15:48 -04:00
// ListMultipartUploadsHandler - GET Bucket (List Multipart uploads)
2015-06-30 17:42:29 -04:00
// -------------------------
// This operation lists in-progress multipart uploads. An in-progress
// multipart upload is a multipart upload that has been initiated,
2015-10-16 22:09:35 -04:00
// using the Initiate Multipart Upload request, but has not yet been
// completed or aborted. This operation returns at most 1,000 multipart
// uploads in the response.
2015-06-30 17:42:29 -04:00
//
2016-04-12 15:45:15 -04:00
func ( api objectAPIHandlers ) ListMultipartUploadsHandler ( w http . ResponseWriter , r * http . Request ) {
2018-07-20 21:46:32 -04:00
ctx := newContext ( r , w , "ListMultipartUploads" )
2018-03-14 15:01:47 -04:00
2018-11-21 23:03:24 -05:00
defer logger . AuditLog ( w , r , "ListMultipartUploads" , mustGetClaimsFromToken ( r ) )
2018-10-12 15:25:59 -04:00
2016-02-15 20:42:39 -05:00
vars := mux . Vars ( r )
2015-10-16 22:09:35 -04:00
bucket := vars [ "bucket" ]
2016-08-10 21:47:49 -04:00
objectAPI := api . ObjectAPI ( )
if objectAPI == nil {
2019-02-13 19:07:21 -05:00
writeErrorResponse ( ctx , w , errorCodes . ToAPIErr ( ErrServerNotInitialized ) , r . URL , guessIsBrowserReq ( r ) )
2016-08-10 21:47:49 -04:00
return
}
2018-04-24 18:53:30 -04:00
if s3Error := checkRequestAuthType ( ctx , r , policy . ListBucketMultipartUploadsAction , bucket , "" ) ; s3Error != ErrNone {
2019-02-13 19:07:21 -05:00
writeErrorResponse ( ctx , w , errorCodes . ToAPIErr ( s3Error ) , r . URL , guessIsBrowserReq ( r ) )
2016-02-15 20:42:39 -05:00
return
2016-02-04 15:52:25 -05:00
}
2019-02-24 01:14:24 -05:00
prefix , keyMarker , uploadIDMarker , delimiter , maxUploads , encodingType , errCode := getBucketMultipartResources ( r . URL . Query ( ) )
2019-02-12 04:25:52 -05:00
if errCode != ErrNone {
2019-02-13 19:07:21 -05:00
writeErrorResponse ( ctx , w , errorCodes . ToAPIErr ( errCode ) , r . URL , guessIsBrowserReq ( r ) )
2018-10-18 10:31:46 -04:00
return
}
2019-02-12 04:25:52 -05:00
objectAPI: Fix object API interface, remove unnecessary structs.
ObjectAPI changes.
```
ListObjects(bucket, prefix, marker, delimiter string, maxKeys int) (ListObjectsInfo, *probe.Error)
ListMultipartUploads(bucket, objectPrefix, keyMarker, uploadIDMarker, delimiter string, maxUploads int) (ListMultipartsInfo, *probe.Error)
ListObjectParts(bucket, object, uploadID string, partNumberMarker, maxParts int) (ListPartsInfo, *probe.Error)
CompleteMultipartUpload(bucket string, object string, uploadID string, parts []completePart) (ObjectInfo, *probe.Error)
```
2016-04-03 04:34:20 -04:00
if maxUploads < 0 {
2019-02-13 19:07:21 -05:00
writeErrorResponse ( ctx , w , errorCodes . ToAPIErr ( ErrInvalidMaxUploads ) , r . URL , guessIsBrowserReq ( r ) )
2015-07-16 20:22:45 -04:00
return
}
2019-02-12 04:25:52 -05:00
2016-04-05 15:26:17 -04:00
if keyMarker != "" {
2016-04-29 17:24:10 -04:00
// Marker not common with prefix is not implemented.
2019-12-06 02:16:06 -05:00
if ! HasPrefix ( keyMarker , prefix ) {
2019-02-13 19:07:21 -05:00
writeErrorResponse ( ctx , w , errorCodes . ToAPIErr ( ErrNotImplemented ) , r . URL , guessIsBrowserReq ( r ) )
2016-04-29 17:24:10 -04:00
return
2016-04-05 15:26:17 -04:00
}
2015-06-30 17:42:29 -04:00
}
2018-03-14 15:01:47 -04:00
listMultipartsInfo , err := objectAPI . ListMultipartUploads ( ctx , bucket , prefix , keyMarker , uploadIDMarker , delimiter , maxUploads )
2015-09-19 06:20:07 -04:00
if err != nil {
2019-02-13 19:07:21 -05:00
writeErrorResponse ( ctx , w , toAPIError ( ctx , err ) , r . URL , guessIsBrowserReq ( r ) )
2015-08-03 19:17:21 -04:00
return
}
2015-09-19 06:20:07 -04:00
// generate response
2019-02-24 01:14:24 -05:00
response := generateListMultipartUploadsResponse ( bucket , listMultipartsInfo , encodingType )
2016-03-06 15:16:22 -05:00
encodedSuccessResponse := encodeResponse ( response )
2017-01-06 03:37:00 -05:00
2016-01-08 03:40:06 -05:00
// write success response.
2017-01-06 03:37:00 -05:00
writeSuccessResponseXML ( w , encodedSuccessResponse )
2015-06-30 17:42:29 -04:00
}
2016-10-09 12:21:37 -04:00
// ListBucketsHandler - GET Service.
2015-06-30 17:42:29 -04:00
// -----------
// This implementation of the GET operation returns a list of all buckets
// owned by the authenticated sender of the request.
2016-04-12 15:45:15 -04:00
func ( api objectAPIHandlers ) ListBucketsHandler ( w http . ResponseWriter , r * http . Request ) {
2018-07-20 21:46:32 -04:00
ctx := newContext ( r , w , "ListBuckets" )
2018-03-14 15:01:47 -04:00
2018-11-21 23:03:24 -05:00
defer logger . AuditLog ( w , r , "ListBuckets" , mustGetClaimsFromToken ( r ) )
2018-10-12 15:25:59 -04:00
2016-08-10 21:47:49 -04:00
objectAPI := api . ObjectAPI ( )
if objectAPI == nil {
2019-02-13 19:07:21 -05:00
writeErrorResponse ( ctx , w , errorCodes . ToAPIErr ( ErrServerNotInitialized ) , r . URL , guessIsBrowserReq ( r ) )
2016-08-10 21:47:49 -04:00
return
}
2019-02-12 04:25:52 -05:00
listBuckets := objectAPI . ListBuckets
2018-04-24 18:53:30 -04:00
2019-08-12 13:27:38 -04:00
accessKey , owner , s3Error := checkRequestAuthTypeToAccessKey ( ctx , r , policy . ListAllMyBucketsAction , "" , "" )
2020-04-02 15:35:22 -04:00
if s3Error != ErrNone && s3Error != ErrAccessDenied {
2019-02-13 19:07:21 -05:00
writeErrorResponse ( ctx , w , errorCodes . ToAPIErr ( s3Error ) , r . URL , guessIsBrowserReq ( r ) )
2016-02-15 20:42:39 -05:00
return
2016-02-04 15:52:25 -05:00
}
2019-02-12 04:25:52 -05:00
2018-02-02 21:18:52 -05:00
// If etcd, dns federation configured list buckets from etcd.
var bucketsInfo [ ] BucketInfo
2019-12-29 11:56:45 -05:00
if globalDNSConfig != nil && globalBucketFederation {
2018-02-02 21:18:52 -05:00
dnsBuckets , err := globalDNSConfig . List ( )
2018-07-12 17:12:40 -04:00
if err != nil && err != dns . ErrNoEntriesFound {
2019-02-13 19:07:21 -05:00
writeErrorResponse ( ctx , w , toAPIError ( ctx , err ) , r . URL , guessIsBrowserReq ( r ) )
2018-02-02 21:18:52 -05:00
return
}
2020-02-03 03:24:20 -05:00
for _ , dnsRecords := range dnsBuckets {
2018-02-02 21:18:52 -05:00
bucketsInfo = append ( bucketsInfo , BucketInfo {
2020-02-03 03:24:20 -05:00
Name : dnsRecords [ 0 ] . Key ,
Created : dnsRecords [ 0 ] . CreationDate ,
2018-02-02 21:18:52 -05:00
} )
}
} else {
// Invoke the list buckets.
var err error
bucketsInfo , err = listBuckets ( ctx )
if err != nil {
2019-02-13 19:07:21 -05:00
writeErrorResponse ( ctx , w , toAPIError ( ctx , err ) , r . URL , guessIsBrowserReq ( r ) )
2018-02-02 21:18:52 -05:00
return
}
2015-08-03 19:17:21 -04:00
}
2016-07-17 16:23:15 -04:00
2020-04-02 15:35:22 -04:00
if s3Error == ErrAccessDenied {
// Set prefix value for "s3:prefix" policy conditionals.
r . Header . Set ( "prefix" , "" )
// Set delimiter value for "s3:delimiter" policy conditionals.
r . Header . Set ( "delimiter" , SlashSeparator )
// err will be nil here as we already called this function
// earlier in this request.
2020-04-14 14:28:56 -04:00
claims , _ := getClaimsFromToken ( r , getSessionToken ( r ) )
2020-04-02 15:35:22 -04:00
n := 0
// Use the following trick to filter in place
// https://github.com/golang/go/wiki/SliceTricks#filter-in-place
for _ , bucketInfo := range bucketsInfo {
if globalIAMSys . IsAllowed ( iampolicy . Args {
AccountName : accessKey ,
Action : iampolicy . ListBucketAction ,
BucketName : bucketInfo . Name ,
ConditionValues : getConditionValues ( r , "" , accessKey , claims ) ,
IsOwner : owner ,
ObjectName : "" ,
Claims : claims ,
} ) {
bucketsInfo [ n ] = bucketInfo
n ++
}
}
bucketsInfo = bucketsInfo [ : n ]
// No buckets can be filtered return access denied error.
if len ( bucketsInfo ) == 0 {
writeErrorResponse ( ctx , w , errorCodes . ToAPIErr ( s3Error ) , r . URL , guessIsBrowserReq ( r ) )
return
2019-08-12 13:27:38 -04:00
}
}
2016-07-17 16:23:15 -04:00
// Generate response.
2020-04-02 15:35:22 -04:00
response := generateListBucketsResponse ( bucketsInfo )
2016-07-17 16:23:15 -04:00
encodedSuccessResponse := encodeResponse ( response )
2017-01-06 03:37:00 -05:00
2016-07-17 16:23:15 -04:00
// Write response.
2017-01-06 03:37:00 -05:00
writeSuccessResponseXML ( w , encodedSuccessResponse )
2015-06-30 17:42:29 -04:00
}
2016-03-05 19:43:48 -05:00
// DeleteMultipleObjectsHandler - deletes multiple objects.
2016-04-12 15:45:15 -04:00
func ( api objectAPIHandlers ) DeleteMultipleObjectsHandler ( w http . ResponseWriter , r * http . Request ) {
2018-07-20 21:46:32 -04:00
ctx := newContext ( r , w , "DeleteMultipleObjects" )
2018-03-14 15:01:47 -04:00
2018-11-21 23:03:24 -05:00
defer logger . AuditLog ( w , r , "DeleteMultipleObjects" , mustGetClaimsFromToken ( r ) )
2018-11-02 21:40:08 -04:00
2016-03-05 19:43:48 -05:00
vars := mux . Vars ( r )
bucket := vars [ "bucket" ]
2016-08-10 21:47:49 -04:00
objectAPI := api . ObjectAPI ( )
if objectAPI == nil {
2019-02-13 19:07:21 -05:00
writeErrorResponse ( ctx , w , errorCodes . ToAPIErr ( ErrServerNotInitialized ) , r . URL , guessIsBrowserReq ( r ) )
2016-08-10 21:47:49 -04:00
return
}
2020-02-22 22:36:46 -05:00
// Content-Md5 is requied should be set
2016-03-05 19:43:48 -05:00
// http://docs.aws.amazon.com/AmazonS3/latest/API/multiobjectdeleteapi.html
2020-02-22 22:36:46 -05:00
if _ , ok := r . Header [ xhttp . ContentMD5 ] ; ! ok {
writeErrorResponse ( ctx , w , errorCodes . ToAPIErr ( ErrMissingContentMD5 ) , r . URL , guessIsBrowserReq ( r ) )
2016-03-05 19:43:48 -05:00
return
}
2020-02-22 22:36:46 -05:00
// Content-Length is required and should be non-zero
2016-03-05 19:43:48 -05:00
// http://docs.aws.amazon.com/AmazonS3/latest/API/multiobjectdeleteapi.html
2020-02-22 22:36:46 -05:00
if r . ContentLength <= 0 {
writeErrorResponse ( ctx , w , errorCodes . ToAPIErr ( ErrMissingContentLength ) , r . URL , guessIsBrowserReq ( r ) )
2016-03-05 19:43:48 -05:00
return
}
2020-02-21 00:59:57 -05:00
// The max. XML contains 100000 object names (each at most 1024 bytes long) + XML overhead
const maxBodySize = 2 * 100000 * 1024
2016-03-05 19:43:48 -05:00
// Unmarshal list of keys to be deleted.
deleteObjects := & DeleteObjectsRequest { }
2020-02-21 00:59:57 -05:00
if err := xmlDecoder ( r . Body , deleteObjects , maxBodySize ) ; err != nil {
2019-10-11 21:50:54 -04:00
logger . LogIf ( ctx , err , logger . Application )
2020-02-22 22:36:46 -05:00
writeErrorResponse ( ctx , w , toAPIError ( ctx , err ) , r . URL , guessIsBrowserReq ( r ) )
2016-03-05 19:43:48 -05:00
return
}
2018-03-27 19:44:45 -04:00
2020-11-04 12:13:34 -05:00
// Call checkRequestAuthType to populate ReqInfo.AccessKey before GetBucketInfo()
// Ignore errors here to preserve the S3 error behavior of GetBucketInfo()
checkRequestAuthType ( ctx , r , policy . DeleteObjectAction , bucket , "" )
2020-07-10 11:30:23 -04:00
// Before proceeding validate if bucket exists.
_ , err := objectAPI . GetBucketInfo ( ctx , bucket )
if err != nil {
writeErrorResponse ( ctx , w , toAPIError ( ctx , err ) , r . URL , guessIsBrowserReq ( r ) )
return
}
2019-05-13 15:25:49 -04:00
deleteObjectsFn := objectAPI . DeleteObjects
2018-07-21 00:21:01 -04:00
if api . CacheAPI ( ) != nil {
2019-05-13 15:25:49 -04:00
deleteObjectsFn = api . CacheAPI ( ) . DeleteObjects
2018-07-21 00:21:01 -04:00
}
2016-09-02 04:59:08 -04:00
2020-06-12 23:04:01 -04:00
var objectsToDelete = map [ ObjectToDelete ] int { }
2019-11-20 16:18:09 -05:00
getObjectInfoFn := objectAPI . GetObjectInfo
if api . CacheAPI ( ) != nil {
getObjectInfoFn = api . CacheAPI ( ) . GetObjectInfo
}
2020-06-12 23:04:01 -04:00
dErrs := make ( [ ] DeleteError , len ( deleteObjects . Objects ) )
2016-09-02 04:59:08 -04:00
for index , object := range deleteObjects . Objects {
2020-06-12 23:04:01 -04:00
if apiErrCode := checkRequestAuthType ( ctx , r , policy . DeleteObjectAction , bucket , object . ObjectName ) ; apiErrCode != ErrNone {
if apiErrCode == ErrSignatureDoesNotMatch || apiErrCode == ErrInvalidAccessKeyID {
writeErrorResponse ( ctx , w , errorCodes . ToAPIErr ( apiErrCode ) , r . URL , guessIsBrowserReq ( r ) )
2019-04-22 10:54:43 -04:00
return
2017-08-15 15:49:31 -04:00
}
2020-06-12 23:04:01 -04:00
apiErr := errorCodes . ToAPIErr ( apiErrCode )
dErrs [ index ] = DeleteError {
Code : apiErr . Code ,
Message : apiErr . Description ,
Key : object . ObjectName ,
VersionID : object . VersionID ,
}
2018-07-21 00:21:01 -04:00
continue
}
2020-04-06 16:44:16 -04:00
2020-06-12 23:04:01 -04:00
if object . VersionID != "" {
if rcfg , _ := globalBucketObjectLockSys . Get ( bucket ) ; rcfg . LockEnabled {
if apiErrCode := enforceRetentionBypassForDelete ( ctx , r , bucket , object , getObjectInfoFn ) ; apiErrCode != ErrNone {
apiErr := errorCodes . ToAPIErr ( apiErrCode )
dErrs [ index ] = DeleteError {
Code : apiErr . Code ,
Message : apiErr . Description ,
Key : object . ObjectName ,
VersionID : object . VersionID ,
}
continue
}
2020-04-06 16:44:16 -04:00
}
2019-11-20 16:18:09 -05:00
}
2020-04-06 16:44:16 -04:00
2019-11-19 20:42:27 -05:00
// Avoid duplicate objects, we use map to filter them out.
2020-06-12 23:04:01 -04:00
if _ , ok := objectsToDelete [ object ] ; ! ok {
objectsToDelete [ object ] = index
2019-11-19 20:42:27 -05:00
}
2019-05-13 15:25:49 -04:00
}
2020-06-12 23:04:01 -04:00
toNames := func ( input map [ ObjectToDelete ] int ) ( output [ ] ObjectToDelete ) {
output = make ( [ ] ObjectToDelete , len ( input ) )
2019-11-20 20:51:10 -05:00
idx := 0
2020-06-12 23:04:01 -04:00
for obj := range input {
output [ idx ] = obj
2019-11-20 20:51:10 -05:00
idx ++
2019-04-22 10:54:43 -04:00
}
2019-05-13 15:25:49 -04:00
return
}
2019-11-20 20:51:10 -05:00
deleteList := toNames ( objectsToDelete )
2020-06-12 23:04:01 -04:00
dObjects , errs := deleteObjectsFn ( ctx , bucket , deleteList , ObjectOptions {
2020-09-02 03:19:03 -04:00
Versioned : globalBucketVersioningSys . Enabled ( bucket ) ,
VersionSuspended : globalBucketVersioningSys . Suspended ( bucket ) ,
2020-06-12 23:04:01 -04:00
} )
2019-05-13 15:25:49 -04:00
2020-06-12 23:04:01 -04:00
deletedObjects := make ( [ ] DeletedObject , len ( deleteObjects . Objects ) )
for i := range errs {
2020-11-05 15:47:46 -05:00
dindex := objectsToDelete [ ObjectToDelete {
ObjectName : dObjects [ i ] . ObjectName ,
VersionID : dObjects [ i ] . VersionID ,
} ]
2020-06-12 23:04:01 -04:00
apiErr := toAPIError ( ctx , errs [ i ] )
2020-09-02 03:19:03 -04:00
if apiErr . Code == "" || apiErr . Code == "NoSuchKey" || apiErr . Code == "InvalidArgument" {
2020-06-12 23:04:01 -04:00
deletedObjects [ dindex ] = dObjects [ i ]
continue
}
dErrs [ dindex ] = DeleteError {
Code : apiErr . Code ,
Message : apiErr . Description ,
Key : deleteList [ i ] . ObjectName ,
VersionID : deleteList [ i ] . VersionID ,
}
2016-09-02 04:59:08 -04:00
}
var deleteErrors [ ] DeleteError
2020-06-12 23:04:01 -04:00
for _ , dErr := range dErrs {
if dErr . Code != "" {
deleteErrors = append ( deleteErrors , dErr )
2016-09-07 14:49:12 -04:00
}
2016-03-05 19:43:48 -05:00
}
2016-09-02 04:59:08 -04:00
2016-03-05 19:43:48 -05:00
// Generate response
response := generateMultiDeleteResponse ( deleteObjects . Quiet , deletedObjects , deleteErrors )
encodedSuccessResponse := encodeResponse ( response )
2017-01-06 03:37:00 -05:00
2016-03-05 19:43:48 -05:00
// Write success response.
2017-01-06 03:37:00 -05:00
writeSuccessResponseXML ( w , encodedSuccessResponse )
2016-09-02 04:59:08 -04:00
2016-09-29 01:46:19 -04:00
// Notify deleted event for objects.
for _ , dobj := range deletedObjects {
2020-10-17 00:22:12 -04:00
eventName := event . ObjectRemovedDelete
2020-06-12 23:04:01 -04:00
objInfo := ObjectInfo {
Name : dobj . ObjectName ,
VersionID : dobj . VersionID ,
}
2020-10-17 00:22:12 -04:00
2020-06-12 23:04:01 -04:00
if dobj . DeleteMarker {
2020-10-17 00:22:12 -04:00
objInfo . DeleteMarker = dobj . DeleteMarker
objInfo . VersionID = dobj . DeleteMarkerVersionID
eventName = event . ObjectRemovedDeleteMarkerCreated
2020-06-12 23:04:01 -04:00
}
2020-10-17 00:22:12 -04:00
2018-03-15 16:03:41 -04:00
sendEvent ( eventArgs {
2020-10-17 00:22:12 -04:00
EventName : eventName ,
2020-06-12 23:04:01 -04:00
BucketName : bucket ,
Object : objInfo ,
2018-11-02 21:40:08 -04:00
ReqParams : extractReqParams ( r ) ,
RespElements : extractRespElements ( w ) ,
UserAgent : r . UserAgent ( ) ,
2019-03-25 14:45:42 -04:00
Host : handlers . GetSourceIP ( r ) ,
2016-09-29 01:46:19 -04:00
} )
2016-09-02 04:59:08 -04:00
}
2016-03-05 19:43:48 -05:00
}
2015-06-30 23:15:48 -04:00
// PutBucketHandler - PUT Bucket
2015-06-30 17:42:29 -04:00
// ----------
// This implementation of the PUT operation creates a new bucket for authenticated request
2016-04-12 15:45:15 -04:00
func ( api objectAPIHandlers ) PutBucketHandler ( w http . ResponseWriter , r * http . Request ) {
2018-07-20 21:46:32 -04:00
ctx := newContext ( r , w , "PutBucket" )
2018-03-14 15:01:47 -04:00
2018-11-21 23:03:24 -05:00
defer logger . AuditLog ( w , r , "PutBucket" , mustGetClaimsFromToken ( r ) )
2018-10-12 15:25:59 -04:00
2016-08-10 21:47:49 -04:00
objectAPI := api . ObjectAPI ( )
if objectAPI == nil {
2019-02-13 19:07:21 -05:00
writeErrorResponse ( ctx , w , errorCodes . ToAPIErr ( ErrServerNotInitialized ) , r . URL , guessIsBrowserReq ( r ) )
2016-08-10 21:47:49 -04:00
return
}
2018-04-24 18:53:30 -04:00
vars := mux . Vars ( r )
bucket := vars [ "bucket" ]
2019-11-12 17:50:18 -05:00
objectLockEnabled := false
2019-12-13 18:51:28 -05:00
if vs , found := r . Header [ http . CanonicalHeaderKey ( "x-amz-bucket-object-lock-enabled" ) ] ; found {
v := strings . ToLower ( strings . Join ( vs , "" ) )
if v != "true" && v != "false" {
2019-11-12 17:50:18 -05:00
writeErrorResponse ( ctx , w , errorCodes . ToAPIErr ( ErrInvalidRequest ) , r . URL , guessIsBrowserReq ( r ) )
return
}
2019-12-13 18:51:28 -05:00
objectLockEnabled = v == "true"
2019-11-12 17:50:18 -05:00
}
2018-04-24 18:53:30 -04:00
if s3Error := checkRequestAuthType ( ctx , r , policy . CreateBucketAction , bucket , "" ) ; s3Error != ErrNone {
2019-02-13 19:07:21 -05:00
writeErrorResponse ( ctx , w , errorCodes . ToAPIErr ( s3Error ) , r . URL , guessIsBrowserReq ( r ) )
2016-02-04 15:52:25 -05:00
return
2015-07-14 12:17:30 -04:00
}
2017-04-03 17:50:09 -04:00
// Parse incoming location constraint.
location , s3Error := parseLocationConstraint ( r )
if s3Error != ErrNone {
2019-02-13 19:07:21 -05:00
writeErrorResponse ( ctx , w , errorCodes . ToAPIErr ( s3Error ) , r . URL , guessIsBrowserReq ( r ) )
2016-08-25 23:00:47 -04:00
return
2016-04-20 20:35:38 -04:00
}
2016-07-24 01:51:12 -04:00
2017-04-03 17:50:09 -04:00
// Validate if location sent by the client is valid, reject
// requests which do not follow valid region requirements.
if ! isValidLocation ( location ) {
2019-02-13 19:07:21 -05:00
writeErrorResponse ( ctx , w , errorCodes . ToAPIErr ( ErrInvalidRegion ) , r . URL , guessIsBrowserReq ( r ) )
2017-04-03 17:50:09 -04:00
return
}
2020-06-12 23:04:01 -04:00
opts := BucketOptions {
Location : location ,
LockEnabled : objectLockEnabled ,
}
2018-02-02 21:18:52 -05:00
if globalDNSConfig != nil {
2020-01-22 11:25:28 -05:00
sr , err := globalDNSConfig . Get ( bucket )
if err != nil {
2020-09-09 15:20:49 -04:00
// ErrNotImplemented indicates a DNS backend that doesn't need to check if bucket already
// exists elsewhere
if err == dns . ErrNoEntriesFound || err == dns . ErrNotImplemented {
2018-02-02 21:18:52 -05:00
// Proceed to creating a bucket.
2020-06-12 23:04:01 -04:00
if err = objectAPI . MakeBucketWithLocation ( ctx , bucket , opts ) ; err != nil {
2020-05-01 12:53:14 -04:00
writeErrorResponse ( ctx , w , toAPIError ( ctx , err ) , r . URL , guessIsBrowserReq ( r ) )
return
2019-11-12 17:50:18 -05:00
}
2018-02-02 21:18:52 -05:00
if err = globalDNSConfig . Put ( bucket ) ; err != nil {
2020-03-28 00:52:59 -04:00
objectAPI . DeleteBucket ( ctx , bucket , false )
2019-02-13 19:07:21 -05:00
writeErrorResponse ( ctx , w , toAPIError ( ctx , err ) , r . URL , guessIsBrowserReq ( r ) )
2018-02-02 21:18:52 -05:00
return
}
2020-05-21 14:03:59 -04:00
// Load updated bucket metadata into memory.
globalNotificationSys . LoadBucketMetadata ( GlobalContext , bucket )
2020-05-19 16:53:54 -04:00
2018-02-02 21:18:52 -05:00
// Make sure to add Location information here only for bucket
2019-07-03 01:34:32 -04:00
w . Header ( ) . Set ( xhttp . Location ,
getObjectLocation ( r , globalDomainNames , bucket , "" ) )
2018-02-02 21:18:52 -05:00
writeSuccessResponseHeadersOnly ( w )
2020-07-20 15:52:49 -04:00
sendEvent ( eventArgs {
EventName : event . BucketCreated ,
BucketName : bucket ,
ReqParams : extractReqParams ( r ) ,
RespElements : extractRespElements ( w ) ,
UserAgent : r . UserAgent ( ) ,
Host : handlers . GetSourceIP ( r ) ,
} )
2018-02-02 21:18:52 -05:00
return
}
2019-02-13 19:07:21 -05:00
writeErrorResponse ( ctx , w , toAPIError ( ctx , err ) , r . URL , guessIsBrowserReq ( r ) )
2018-02-02 21:18:52 -05:00
return
}
2020-01-22 11:25:28 -05:00
apiErr := ErrBucketAlreadyExists
if ! globalDomainIPs . Intersection ( set . CreateStringSet ( getHostsSlice ( sr ) ... ) ) . IsEmpty ( ) {
apiErr = ErrBucketAlreadyOwnedByYou
}
// No IPs seem to intersect, this means that bucket exists but has
// different IP addresses perhaps from a different deployment.
// bucket names are globally unique in federation at a given
// path prefix, name collision is not allowed. Return appropriate error.
writeErrorResponse ( ctx , w , errorCodes . ToAPIErr ( apiErr ) , r . URL , guessIsBrowserReq ( r ) )
2018-02-15 20:45:57 -05:00
return
}
2016-07-24 01:51:12 -04:00
// Proceed to creating a bucket.
2020-06-12 23:04:01 -04:00
err := objectAPI . MakeBucketWithLocation ( ctx , bucket , opts )
2015-09-19 06:20:07 -04:00
if err != nil {
2019-02-13 19:07:21 -05:00
writeErrorResponse ( ctx , w , toAPIError ( ctx , err ) , r . URL , guessIsBrowserReq ( r ) )
2015-08-03 19:17:21 -04:00
return
}
2017-01-06 03:37:00 -05:00
2020-05-21 14:03:59 -04:00
// Load updated bucket metadata into memory.
globalNotificationSys . LoadBucketMetadata ( GlobalContext , bucket )
2019-11-12 17:50:18 -05:00
2015-09-19 06:20:07 -04:00
// Make sure to add Location information here only for bucket
2019-07-03 01:34:32 -04:00
w . Header ( ) . Set ( xhttp . Location , path . Clean ( r . URL . Path ) ) // Clean any trailing slashes.
2017-01-06 03:37:00 -05:00
writeSuccessResponseHeadersOnly ( w )
2020-07-20 15:52:49 -04:00
sendEvent ( eventArgs {
EventName : event . BucketCreated ,
BucketName : bucket ,
ReqParams : extractReqParams ( r ) ,
RespElements : extractRespElements ( w ) ,
UserAgent : r . UserAgent ( ) ,
Host : handlers . GetSourceIP ( r ) ,
} )
2015-06-30 17:42:29 -04:00
}
2015-10-02 02:51:17 -04:00
// PostPolicyBucketHandler - POST policy
// ----------
// This implementation of the POST operation handles object creation with a specified
// signature policy in multipart/form-data
2016-04-12 15:45:15 -04:00
func ( api objectAPIHandlers ) PostPolicyBucketHandler ( w http . ResponseWriter , r * http . Request ) {
2018-07-20 21:46:32 -04:00
ctx := newContext ( r , w , "PostPolicyBucket" )
2018-03-14 15:01:47 -04:00
2018-11-21 23:03:24 -05:00
defer logger . AuditLog ( w , r , "PostPolicyBucket" , mustGetClaimsFromToken ( r ) )
2018-10-12 15:25:59 -04:00
2016-08-10 21:47:49 -04:00
objectAPI := api . ObjectAPI ( )
if objectAPI == nil {
2019-02-13 19:07:21 -05:00
writeErrorResponse ( ctx , w , errorCodes . ToAPIErr ( ErrServerNotInitialized ) , r . URL , guessIsBrowserReq ( r ) )
2016-08-10 21:47:49 -04:00
return
}
2019-02-12 04:25:52 -05:00
2018-12-15 00:39:59 -05:00
if crypto . S3KMS . IsRequested ( r . Header ) { // SSE-KMS is not supported
2019-02-13 19:07:21 -05:00
writeErrorResponse ( ctx , w , errorCodes . ToAPIErr ( ErrNotImplemented ) , r . URL , guessIsBrowserReq ( r ) )
2018-12-15 00:39:59 -05:00
return
}
2020-09-15 16:57:15 -04:00
if ! objectAPI . IsEncryptionSupported ( ) && crypto . IsRequested ( r . Header ) {
2019-02-13 19:07:21 -05:00
writeErrorResponse ( ctx , w , errorCodes . ToAPIErr ( ErrNotImplemented ) , r . URL , guessIsBrowserReq ( r ) )
2018-12-15 00:39:59 -05:00
return
}
2019-02-12 04:25:52 -05:00
2017-11-13 19:30:20 -05:00
bucket := mux . Vars ( r ) [ "bucket" ]
2017-02-02 13:45:00 -05:00
// Require Content-Length to be set in the request
size := r . ContentLength
if size < 0 {
2019-02-13 19:07:21 -05:00
writeErrorResponse ( ctx , w , errorCodes . ToAPIErr ( ErrMissingContentLength ) , r . URL , guessIsBrowserReq ( r ) )
2017-02-02 13:45:00 -05:00
return
}
2019-02-22 22:18:01 -05:00
resource , err := getResource ( r . URL . Path , r . Host , globalDomainNames )
2017-11-15 17:10:45 -05:00
if err != nil {
2019-02-13 19:07:21 -05:00
writeErrorResponse ( ctx , w , errorCodes . ToAPIErr ( ErrInvalidRequest ) , r . URL , guessIsBrowserReq ( r ) )
2017-11-15 17:10:45 -05:00
return
}
// Make sure that the URL does not contain object name.
if bucket != filepath . Clean ( resource [ 1 : ] ) {
2019-02-13 19:07:21 -05:00
writeErrorResponse ( ctx , w , errorCodes . ToAPIErr ( ErrMethodNotAllowed ) , r . URL , guessIsBrowserReq ( r ) )
2017-11-15 17:10:45 -05:00
return
}
2017-02-02 13:45:00 -05:00
2015-10-02 02:51:17 -04:00
// Here the parameter is the size of the form data that should
2016-03-22 20:54:31 -04:00
// be loaded in memory, the remaining being put in temporary files.
2016-04-29 17:24:10 -04:00
reader , err := r . MultipartReader ( )
if err != nil {
2018-04-05 18:04:40 -04:00
logger . LogIf ( ctx , err )
2019-02-13 19:07:21 -05:00
writeErrorResponse ( ctx , w , errorCodes . ToAPIErr ( ErrMalformedPOSTRequest ) , r . URL , guessIsBrowserReq ( r ) )
2015-10-02 02:51:17 -04:00
return
}
2017-02-02 13:45:00 -05:00
// Read multipart data and save in memory and in the disk if needed
form , err := reader . ReadForm ( maxFormMemory )
if err != nil {
2019-10-11 21:50:54 -04:00
logger . LogIf ( ctx , err , logger . Application )
2019-02-13 19:07:21 -05:00
writeErrorResponse ( ctx , w , errorCodes . ToAPIErr ( ErrMalformedPOSTRequest ) , r . URL , guessIsBrowserReq ( r ) )
2017-02-02 13:45:00 -05:00
return
}
2019-07-10 01:18:43 -04:00
// Remove all tmp files created during multipart upload
2017-02-02 13:45:00 -05:00
defer form . RemoveAll ( )
// Extract all form fields
2018-04-05 18:04:40 -04:00
fileBody , fileName , fileSize , formValues , err := extractPostPolicyFormValues ( ctx , form )
2016-02-04 15:52:25 -05:00
if err != nil {
2019-10-11 21:50:54 -04:00
logger . LogIf ( ctx , err , logger . Application )
2019-02-13 19:07:21 -05:00
writeErrorResponse ( ctx , w , errorCodes . ToAPIErr ( ErrMalformedPOSTRequest ) , r . URL , guessIsBrowserReq ( r ) )
2015-10-02 02:51:17 -04:00
return
}
2017-02-02 13:45:00 -05:00
2017-02-09 15:37:32 -05:00
// Check if file is provided, error out otherwise.
if fileBody == nil {
2019-02-13 19:07:21 -05:00
writeErrorResponse ( ctx , w , errorCodes . ToAPIErr ( ErrPOSTFileRequired ) , r . URL , guessIsBrowserReq ( r ) )
2017-02-09 15:37:32 -05:00
return
}
2017-02-02 13:45:00 -05:00
// Close multipart file
defer fileBody . Close ( )
2017-03-13 17:41:13 -04:00
formValues . Set ( "Bucket" , bucket )
2016-02-15 20:42:39 -05:00
2017-03-13 17:41:13 -04:00
if fileName != "" && strings . Contains ( formValues . Get ( "Key" ) , "${filename}" ) {
2016-07-27 20:51:55 -04:00
// S3 feature to replace ${filename} found in Key form field
// by the filename attribute passed in multipart
2017-03-13 17:41:13 -04:00
formValues . Set ( "Key" , strings . Replace ( formValues . Get ( "Key" ) , "${filename}" , fileName , - 1 ) )
}
object := formValues . Get ( "Key" )
successRedirect := formValues . Get ( "success_action_redirect" )
successStatus := formValues . Get ( "success_action_status" )
var redirectURL * url . URL
if successRedirect != "" {
redirectURL , err = url . Parse ( successRedirect )
if err != nil {
2019-02-13 19:07:21 -05:00
writeErrorResponse ( ctx , w , errorCodes . ToAPIErr ( ErrMalformedPOSTRequest ) , r . URL , guessIsBrowserReq ( r ) )
2017-03-13 17:41:13 -04:00
return
}
2016-07-27 20:51:55 -04:00
}
2016-02-15 20:42:39 -05:00
// Verify policy signature.
2019-02-12 04:25:52 -05:00
errCode := doesPolicySignatureMatch ( formValues )
if errCode != ErrNone {
2019-02-13 19:07:21 -05:00
writeErrorResponse ( ctx , w , errorCodes . ToAPIErr ( errCode ) , r . URL , guessIsBrowserReq ( r ) )
2015-10-02 02:51:17 -04:00
return
}
2016-10-25 02:47:03 -04:00
2017-03-13 17:41:13 -04:00
policyBytes , err := base64 . StdEncoding . DecodeString ( formValues . Get ( "Policy" ) )
2016-10-25 02:47:03 -04:00
if err != nil {
2019-02-13 19:07:21 -05:00
writeErrorResponse ( ctx , w , errorCodes . ToAPIErr ( ErrMalformedPOSTRequest ) , r . URL , guessIsBrowserReq ( r ) )
2016-10-25 02:47:03 -04:00
return
}
2018-12-03 15:01:28 -05:00
// Handle policy if it is set.
if len ( policyBytes ) > 0 {
2019-09-22 17:20:49 -04:00
2018-12-03 15:01:28 -05:00
postPolicyForm , err := parsePostPolicyForm ( string ( policyBytes ) )
if err != nil {
2019-09-22 17:20:49 -04:00
writeErrorResponse ( ctx , w , errorCodes . ToAPIErr ( ErrPostPolicyConditionInvalidFormat ) , r . URL , guessIsBrowserReq ( r ) )
2017-02-02 13:45:00 -05:00
return
2016-11-21 07:15:26 -05:00
}
2017-02-02 13:45:00 -05:00
2018-12-03 15:01:28 -05:00
// Make sure formValues adhere to policy restrictions.
2019-03-05 15:10:47 -05:00
if err = checkPostPolicy ( formValues , postPolicyForm ) ; err != nil {
2020-08-24 01:06:22 -04:00
writeErrorResponse ( ctx , w , errorCodes . ToAPIErrWithErr ( ErrAccessDenied , err ) , r . URL , guessIsBrowserReq ( r ) )
2017-02-02 13:45:00 -05:00
return
2016-11-21 07:15:26 -05:00
}
2018-12-03 15:01:28 -05:00
// Ensure that the object size is within expected range, also the file size
// should not exceed the maximum single Put size (5 GiB)
lengthRange := postPolicyForm . Conditions . ContentLengthRange
if lengthRange . Valid {
if fileSize < lengthRange . Min {
2019-02-13 19:07:21 -05:00
writeErrorResponse ( ctx , w , toAPIError ( ctx , errDataTooSmall ) , r . URL , guessIsBrowserReq ( r ) )
2018-12-03 15:01:28 -05:00
return
}
if fileSize > lengthRange . Max || isMaxObjectSize ( fileSize ) {
2019-02-13 19:07:21 -05:00
writeErrorResponse ( ctx , w , toAPIError ( ctx , errDataTooLarge ) , r . URL , guessIsBrowserReq ( r ) )
2018-12-03 15:01:28 -05:00
return
}
}
2016-10-25 02:47:03 -04:00
}
2016-12-19 19:14:04 -05:00
// Extract metadata to be saved from received Form.
2018-07-10 23:27:10 -04:00
metadata := make ( map [ string ] string )
err = extractMetadataFromMap ( ctx , formValues , metadata )
2017-07-05 19:56:10 -04:00
if err != nil {
2019-02-13 19:07:21 -05:00
writeErrorResponse ( ctx , w , toAPIError ( ctx , err ) , r . URL , guessIsBrowserReq ( r ) )
2017-07-05 19:56:10 -04:00
return
}
2016-10-02 18:51:49 -04:00
2019-05-08 21:35:40 -04:00
hashReader , err := hash . NewReader ( fileBody , fileSize , "" , "" , fileSize , globalCLIContext . StrictS3Compat )
2017-10-22 01:30:34 -04:00
if err != nil {
2018-04-05 18:04:40 -04:00
logger . LogIf ( ctx , err )
2019-02-13 19:07:21 -05:00
writeErrorResponse ( ctx , w , toAPIError ( ctx , err ) , r . URL , guessIsBrowserReq ( r ) )
2017-10-22 01:30:34 -04:00
return
}
2018-11-14 20:36:41 -05:00
rawReader := hashReader
pReader := NewPutObjReader ( rawReader , nil , nil )
2020-04-09 20:01:45 -04:00
var objectEncryptionKey crypto . ObjectKey
2018-12-14 16:35:48 -05:00
2020-02-05 04:42:34 -05:00
// Check if bucket encryption is enabled
2020-05-19 16:53:54 -04:00
if _ , err = globalBucketSSEConfigSys . Get ( bucket ) ; err == nil || globalAutoEncryption {
// This request header needs to be set prior to setting ObjectOptions
if ! crypto . SSEC . IsRequested ( r . Header ) {
2020-09-01 19:58:13 -04:00
r . Header . Set ( crypto . SSEHeader , crypto . SSEAlgorithmAES256 )
2020-05-19 16:53:54 -04:00
}
2018-12-14 16:35:48 -05:00
}
2020-05-19 16:53:54 -04:00
2019-01-05 17:16:43 -05:00
// get gateway encryption options
var opts ObjectOptions
2019-02-09 00:31:06 -05:00
opts , err = putOpts ( ctx , r , bucket , object , metadata )
2019-01-05 17:16:43 -05:00
if err != nil {
2019-02-12 04:25:52 -05:00
writeErrorResponseHeadersOnly ( w , toAPIError ( ctx , err ) )
2019-01-05 17:16:43 -05:00
return
}
2018-03-05 11:02:56 -05:00
if objectAPI . IsEncryptionSupported ( ) {
2019-12-06 02:16:06 -05:00
if crypto . IsRequested ( formValues ) && ! HasSuffix ( object , SlashSeparator ) { // handle SSE requests
2019-09-20 17:56:12 -04:00
if crypto . SSECopy . IsRequested ( r . Header ) {
writeErrorResponse ( ctx , w , toAPIError ( ctx , errInvalidEncryptionParameters ) , r . URL , guessIsBrowserReq ( r ) )
return
}
2018-03-05 11:02:56 -05:00
var reader io . Reader
var key [ ] byte
2018-08-17 15:52:14 -04:00
if crypto . SSEC . IsRequested ( formValues ) {
key , err = ParseSSECustomerHeader ( formValues )
if err != nil {
2019-02-13 19:07:21 -05:00
writeErrorResponse ( ctx , w , toAPIError ( ctx , err ) , r . URL , guessIsBrowserReq ( r ) )
2018-08-17 15:52:14 -04:00
return
}
2018-03-05 11:02:56 -05:00
}
2018-11-14 20:36:41 -05:00
reader , objectEncryptionKey , err = newEncryptReader ( hashReader , key , bucket , object , metadata , crypto . S3 . IsRequested ( formValues ) )
2018-03-05 11:02:56 -05:00
if err != nil {
2019-02-13 19:07:21 -05:00
writeErrorResponse ( ctx , w , toAPIError ( ctx , err ) , r . URL , guessIsBrowserReq ( r ) )
2018-03-05 11:02:56 -05:00
return
}
info := ObjectInfo { Size : fileSize }
2019-05-08 21:35:40 -04:00
// do not try to verify encrypted content
hashReader , err = hash . NewReader ( reader , info . EncryptedSize ( ) , "" , "" , fileSize , globalCLIContext . StrictS3Compat )
2018-03-05 11:02:56 -05:00
if err != nil {
2019-02-13 19:07:21 -05:00
writeErrorResponse ( ctx , w , toAPIError ( ctx , err ) , r . URL , guessIsBrowserReq ( r ) )
2018-03-05 11:02:56 -05:00
return
}
2020-04-09 20:01:45 -04:00
pReader = NewPutObjReader ( rawReader , hashReader , & objectEncryptionKey )
2018-03-05 11:02:56 -05:00
}
}
2019-02-09 00:31:06 -05:00
objInfo , err := objectAPI . PutObject ( ctx , bucket , object , pReader , opts )
2016-02-04 15:52:25 -05:00
if err != nil {
2019-02-13 19:07:21 -05:00
writeErrorResponse ( ctx , w , toAPIError ( ctx , err ) , r . URL , guessIsBrowserReq ( r ) )
2015-10-02 02:51:17 -04:00
return
}
2017-03-13 17:41:13 -04:00
2020-06-12 23:04:01 -04:00
// We must not use the http.Header().Set method here because some (broken)
// clients expect the ETag header key to be literally "ETag" - not "Etag" (case-sensitive).
// Therefore, we have to set the ETag directly as map entry.
2019-07-03 01:34:32 -04:00
w . Header ( ) [ xhttp . ETag ] = [ ] string { ` " ` + objInfo . ETag + ` " ` }
2020-06-12 23:04:01 -04:00
// Set the relevant version ID as part of the response header.
if objInfo . VersionID != "" {
w . Header ( ) [ xhttp . AmzVersionID ] = [ ] string { objInfo . VersionID }
}
w . Header ( ) . Set ( xhttp . Location , getObjectLocation ( r , globalDomainNames , bucket , object ) )
2016-08-05 01:01:58 -04:00
2017-03-13 17:41:13 -04:00
// Notify object created event.
2018-03-15 16:03:41 -04:00
defer sendEvent ( eventArgs {
2018-11-02 21:40:08 -04:00
EventName : event . ObjectCreatedPost ,
BucketName : objInfo . Bucket ,
Object : objInfo ,
ReqParams : extractReqParams ( r ) ,
RespElements : extractRespElements ( w ) ,
UserAgent : r . UserAgent ( ) ,
2019-03-25 14:45:42 -04:00
Host : handlers . GetSourceIP ( r ) ,
2017-03-13 17:41:13 -04:00
} )
if successRedirect != "" {
// Replace raw query params..
redirectURL . RawQuery = getRedirectPostRawQuery ( objInfo )
writeRedirectSeeOther ( w , redirectURL . String ( ) )
return
}
2016-07-24 01:51:12 -04:00
2017-03-13 17:41:13 -04:00
// Decide what http response to send depending on success_action_status parameter
switch successStatus {
case "201" :
resp := encodeResponse ( PostResponse {
Bucket : objInfo . Bucket ,
Key : objInfo . Name ,
2017-05-14 15:05:51 -04:00
ETag : ` " ` + objInfo . ETag + ` " ` ,
2020-06-12 23:04:01 -04:00
Location : w . Header ( ) . Get ( xhttp . Location ) ,
2017-03-13 17:41:13 -04:00
} )
2020-06-12 23:04:01 -04:00
writeResponse ( w , http . StatusCreated , resp , mimeXML )
2017-03-13 17:41:13 -04:00
case "200" :
writeSuccessResponseHeadersOnly ( w )
default :
2016-12-18 16:39:56 -05:00
writeSuccessNoContent ( w )
}
2015-10-02 02:51:17 -04:00
}
2015-06-30 23:15:48 -04:00
// HeadBucketHandler - HEAD Bucket
2015-06-30 17:42:29 -04:00
// ----------
// This operation is useful to determine if a bucket exists.
// The operation returns a 200 OK if the bucket exists and you
// have permission to access it. Otherwise, the operation might
// return responses such as 404 Not Found and 403 Forbidden.
2016-04-12 15:45:15 -04:00
func ( api objectAPIHandlers ) HeadBucketHandler ( w http . ResponseWriter , r * http . Request ) {
2018-07-20 21:46:32 -04:00
ctx := newContext ( r , w , "HeadBucket" )
2018-03-14 15:01:47 -04:00
2018-11-21 23:03:24 -05:00
defer logger . AuditLog ( w , r , "HeadBucket" , mustGetClaimsFromToken ( r ) )
2018-10-12 15:25:59 -04:00
2016-02-15 20:42:39 -05:00
vars := mux . Vars ( r )
2015-06-30 17:42:29 -04:00
bucket := vars [ "bucket" ]
2015-07-02 23:31:22 -04:00
2016-08-10 21:47:49 -04:00
objectAPI := api . ObjectAPI ( )
if objectAPI == nil {
2019-02-12 04:25:52 -05:00
writeErrorResponseHeadersOnly ( w , errorCodes . ToAPIErr ( ErrServerNotInitialized ) )
2016-08-10 21:47:49 -04:00
return
}
2016-11-21 16:51:05 -05:00
2018-04-24 18:53:30 -04:00
if s3Error := checkRequestAuthType ( ctx , r , policy . ListBucketAction , bucket , "" ) ; s3Error != ErrNone {
2019-02-12 04:25:52 -05:00
writeErrorResponseHeadersOnly ( w , errorCodes . ToAPIErr ( s3Error ) )
accessPolicy: Implement Put, Get, Delete access policy.
This patch implements Get,Put,Delete bucket policies
Supporting - http://docs.aws.amazon.com/AmazonS3/latest/dev/access-policy-language-overview.html
Currently supports following actions.
"*": true,
"s3:*": true,
"s3:GetObject": true,
"s3:ListBucket": true,
"s3:PutObject": true,
"s3:CreateBucket": true,
"s3:GetBucketLocation": true,
"s3:DeleteBucket": true,
"s3:DeleteObject": true,
"s3:AbortMultipartUpload": true,
"s3:ListBucketMultipartUploads": true,
"s3:ListMultipartUploadParts": true,
following conditions for "StringEquals" and "StringNotEquals"
"s3:prefix", "s3:max-keys"
2016-02-03 19:46:56 -05:00
return
2016-02-04 15:52:25 -05:00
}
2018-04-24 18:53:30 -04:00
2018-03-28 17:14:06 -04:00
getBucketInfo := objectAPI . GetBucketInfo
2019-08-09 20:09:08 -04:00
2018-03-28 17:14:06 -04:00
if _ , err := getBucketInfo ( ctx , bucket ) ; err != nil {
2019-02-12 04:25:52 -05:00
writeErrorResponseHeadersOnly ( w , toAPIError ( ctx , err ) )
2015-08-03 19:17:21 -04:00
return
}
2017-01-06 03:37:00 -05:00
writeSuccessResponseHeadersOnly ( w )
2015-06-30 17:42:29 -04:00
}
2015-10-16 14:26:01 -04:00
// DeleteBucketHandler - Delete bucket
2016-04-12 15:45:15 -04:00
func ( api objectAPIHandlers ) DeleteBucketHandler ( w http . ResponseWriter , r * http . Request ) {
2018-07-20 21:46:32 -04:00
ctx := newContext ( r , w , "DeleteBucket" )
2018-03-14 15:01:47 -04:00
2018-11-21 23:03:24 -05:00
defer logger . AuditLog ( w , r , "DeleteBucket" , mustGetClaimsFromToken ( r ) )
2018-10-12 15:25:59 -04:00
2018-04-24 18:53:30 -04:00
vars := mux . Vars ( r )
bucket := vars [ "bucket" ]
2020-04-06 20:51:05 -04:00
objectAPI := api . ObjectAPI ( )
if objectAPI == nil {
writeErrorResponse ( ctx , w , errorCodes . ToAPIErr ( ErrServerNotInitialized ) , r . URL , guessIsBrowserReq ( r ) )
return
}
2020-07-08 20:36:56 -04:00
// Verify if the caller has sufficient permissions.
if s3Error := checkRequestAuthType ( ctx , r , policy . DeleteBucketAction , bucket , "" ) ; s3Error != ErrNone {
writeErrorResponse ( ctx , w , errorCodes . ToAPIErr ( s3Error ) , r . URL , guessIsBrowserReq ( r ) )
return
}
2020-03-28 00:52:59 -04:00
forceDelete := false
2020-04-06 20:51:05 -04:00
if value := r . Header . Get ( xhttp . MinIOForceDelete ) ; value != "" {
2020-07-08 20:36:56 -04:00
var err error
forceDelete , err = strconv . ParseBool ( value )
if err != nil {
apiErr := errorCodes . ToAPIErr ( ErrInvalidRequest )
apiErr . Description = err . Error ( )
writeErrorResponse ( ctx , w , apiErr , r . URL , guessIsBrowserReq ( r ) )
2020-03-28 00:52:59 -04:00
return
}
2020-07-08 20:36:56 -04:00
// if force delete header is set, we need to evaluate the policy anyways
// regardless of it being true or not.
2020-04-06 20:51:05 -04:00
if s3Error := checkRequestAuthType ( ctx , r , policy . ForceDeleteBucketAction , bucket , "" ) ; s3Error != ErrNone {
writeErrorResponse ( ctx , w , errorCodes . ToAPIErr ( s3Error ) , r . URL , guessIsBrowserReq ( r ) )
return
}
2016-08-10 21:47:49 -04:00
2020-07-08 20:36:56 -04:00
if forceDelete {
if rcfg , _ := globalBucketObjectLockSys . Get ( bucket ) ; rcfg . LockEnabled {
writeErrorResponse ( ctx , w , errorCodes . ToAPIErr ( ErrMethodNotAllowed ) , r . URL , guessIsBrowserReq ( r ) )
return
}
2020-05-19 16:53:54 -04:00
}
2016-02-04 15:52:25 -05:00
}
2018-03-28 17:14:06 -04:00
deleteBucket := objectAPI . DeleteBucket
2019-08-09 20:09:08 -04:00
2016-07-24 01:51:12 -04:00
// Attempt to delete bucket.
2020-03-28 00:52:59 -04:00
if err := deleteBucket ( ctx , bucket , forceDelete ) ; err != nil {
2020-06-12 23:04:01 -04:00
if _ , ok := err . ( BucketNotEmpty ) ; ok && ( globalBucketVersioningSys . Enabled ( bucket ) || globalBucketVersioningSys . Suspended ( bucket ) ) {
apiErr := toAPIError ( ctx , err )
apiErr . Description = "The bucket you tried to delete is not empty. You must delete all versions in the bucket."
writeErrorResponse ( ctx , w , apiErr , r . URL , guessIsBrowserReq ( r ) )
} else {
writeErrorResponse ( ctx , w , toAPIError ( ctx , err ) , r . URL , guessIsBrowserReq ( r ) )
}
2015-10-16 14:26:01 -04:00
return
}
accessPolicy: Implement Put, Get, Delete access policy.
This patch implements Get,Put,Delete bucket policies
Supporting - http://docs.aws.amazon.com/AmazonS3/latest/dev/access-policy-language-overview.html
Currently supports following actions.
"*": true,
"s3:*": true,
"s3:GetObject": true,
"s3:ListBucket": true,
"s3:PutObject": true,
"s3:CreateBucket": true,
"s3:GetBucketLocation": true,
"s3:DeleteBucket": true,
"s3:DeleteObject": true,
"s3:AbortMultipartUpload": true,
"s3:ListBucketMultipartUploads": true,
"s3:ListMultipartUploadParts": true,
following conditions for "StringEquals" and "StringNotEquals"
"s3:prefix", "s3:max-keys"
2016-02-03 19:46:56 -05:00
2020-06-12 23:04:01 -04:00
globalNotificationSys . DeleteBucketMetadata ( ctx , bucket )
2018-02-02 21:18:52 -05:00
if globalDNSConfig != nil {
if err := globalDNSConfig . Delete ( bucket ) ; err != nil {
2020-09-09 15:20:49 -04:00
logger . LogIf ( ctx , fmt . Errorf ( "Unable to delete bucket DNS entry %w, please delete it manually" , err ) )
2019-02-13 19:07:21 -05:00
writeErrorResponse ( ctx , w , toAPIError ( ctx , err ) , r . URL , guessIsBrowserReq ( r ) )
2018-02-02 21:18:52 -05:00
return
}
}
accessPolicy: Implement Put, Get, Delete access policy.
This patch implements Get,Put,Delete bucket policies
Supporting - http://docs.aws.amazon.com/AmazonS3/latest/dev/access-policy-language-overview.html
Currently supports following actions.
"*": true,
"s3:*": true,
"s3:GetObject": true,
"s3:ListBucket": true,
"s3:PutObject": true,
"s3:CreateBucket": true,
"s3:GetBucketLocation": true,
"s3:DeleteBucket": true,
"s3:DeleteObject": true,
"s3:AbortMultipartUpload": true,
"s3:ListBucketMultipartUploads": true,
"s3:ListMultipartUploadParts": true,
following conditions for "StringEquals" and "StringNotEquals"
"s3:prefix", "s3:max-keys"
2016-02-03 19:46:56 -05:00
// Write success response.
2015-10-16 23:02:37 -04:00
writeSuccessNoContent ( w )
2020-07-20 15:52:49 -04:00
sendEvent ( eventArgs {
EventName : event . BucketRemoved ,
BucketName : bucket ,
ReqParams : extractReqParams ( r ) ,
RespElements : extractRespElements ( w ) ,
UserAgent : r . UserAgent ( ) ,
Host : handlers . GetSourceIP ( r ) ,
} )
2015-10-16 14:26:01 -04:00
}
2019-11-12 17:50:18 -05:00
// PutBucketObjectLockConfigHandler - PUT Bucket object lock configuration.
// ----------
// Places an Object Lock configuration on the specified bucket. The rule
// specified in the Object Lock configuration will be applied by default
// to every new object placed in the specified bucket.
func ( api objectAPIHandlers ) PutBucketObjectLockConfigHandler ( w http . ResponseWriter , r * http . Request ) {
ctx := newContext ( r , w , "PutBucketObjectLockConfig" )
defer logger . AuditLog ( w , r , "PutBucketObjectLockConfig" , mustGetClaimsFromToken ( r ) )
vars := mux . Vars ( r )
bucket := vars [ "bucket" ]
objectAPI := api . ObjectAPI ( )
if objectAPI == nil {
writeErrorResponse ( ctx , w , errorCodes . ToAPIErr ( ErrServerNotInitialized ) , r . URL , guessIsBrowserReq ( r ) )
return
}
2020-07-22 20:39:40 -04:00
if ! globalIsErasure {
writeErrorResponseJSON ( ctx , w , errorCodes . ToAPIErr ( ErrNotImplemented ) , r . URL )
return
}
2019-11-20 16:18:09 -05:00
if s3Error := checkRequestAuthType ( ctx , r , policy . PutBucketObjectLockConfigurationAction , bucket , "" ) ; s3Error != ErrNone {
writeErrorResponse ( ctx , w , errorCodes . ToAPIErr ( s3Error ) , r . URL , guessIsBrowserReq ( r ) )
return
}
2020-05-08 16:44:44 -04:00
2020-01-16 18:41:56 -05:00
config , err := objectlock . ParseObjectLockConfig ( r . Body )
2019-11-12 17:50:18 -05:00
if err != nil {
2019-11-13 11:21:41 -05:00
apiErr := errorCodes . ToAPIErr ( ErrMalformedXML )
apiErr . Description = err . Error ( )
writeErrorResponse ( ctx , w , apiErr , r . URL , guessIsBrowserReq ( r ) )
2019-11-12 17:50:18 -05:00
return
}
2020-05-01 12:53:14 -04:00
2020-05-19 16:53:54 -04:00
configData , err := xml . Marshal ( config )
if err != nil {
2020-05-01 12:53:14 -04:00
writeErrorResponse ( ctx , w , toAPIError ( ctx , err ) , r . URL , guessIsBrowserReq ( r ) )
2019-11-12 17:50:18 -05:00
return
}
2020-05-01 12:53:14 -04:00
2020-05-23 13:01:01 -04:00
// Deny object locking configuration settings on existing buckets without object lock enabled.
if _ , err = globalBucketMetadataSys . GetObjectLockConfig ( bucket ) ; err != nil {
writeErrorResponse ( ctx , w , toAPIError ( ctx , err ) , r . URL , guessIsBrowserReq ( r ) )
return
}
2020-05-19 16:53:54 -04:00
if err = globalBucketMetadataSys . Update ( bucket , objectLockConfig , configData ) ; err != nil {
writeErrorResponse ( ctx , w , toAPIError ( ctx , err ) , r . URL , guessIsBrowserReq ( r ) )
return
2019-11-12 17:50:18 -05:00
}
// Write success response.
writeSuccessResponseHeadersOnly ( w )
}
// GetBucketObjectLockConfigHandler - GET Bucket object lock configuration.
// ----------
// Gets the Object Lock configuration for a bucket. The rule specified in
// the Object Lock configuration will be applied by default to every new
// object placed in the specified bucket.
func ( api objectAPIHandlers ) GetBucketObjectLockConfigHandler ( w http . ResponseWriter , r * http . Request ) {
ctx := newContext ( r , w , "GetBucketObjectLockConfig" )
defer logger . AuditLog ( w , r , "GetBucketObjectLockConfig" , mustGetClaimsFromToken ( r ) )
vars := mux . Vars ( r )
bucket := vars [ "bucket" ]
objectAPI := api . ObjectAPI ( )
if objectAPI == nil {
writeErrorResponse ( ctx , w , errorCodes . ToAPIErr ( ErrServerNotInitialized ) , r . URL , guessIsBrowserReq ( r ) )
return
}
2020-05-19 16:53:54 -04:00
2019-11-20 16:18:09 -05:00
// check if user has permissions to perform this operation
if s3Error := checkRequestAuthType ( ctx , r , policy . GetBucketObjectLockConfigurationAction , bucket , "" ) ; s3Error != ErrNone {
writeErrorResponse ( ctx , w , errorCodes . ToAPIErr ( s3Error ) , r . URL , guessIsBrowserReq ( r ) )
return
}
2020-05-01 12:53:14 -04:00
2020-05-20 13:18:15 -04:00
config , err := globalBucketMetadataSys . GetObjectLockConfig ( bucket )
if err != nil {
writeErrorResponse ( ctx , w , toAPIError ( ctx , err ) , r . URL , guessIsBrowserReq ( r ) )
return
}
configData , err := xml . Marshal ( config )
2019-11-12 17:50:18 -05:00
if err != nil {
2020-05-08 16:44:44 -04:00
writeErrorResponse ( ctx , w , toAPIError ( ctx , err ) , r . URL , guessIsBrowserReq ( r ) )
return
2019-11-12 17:50:18 -05:00
}
// Write success response.
writeSuccessResponseXML ( w , configData )
}
2020-05-05 17:18:13 -04:00
// PutBucketTaggingHandler - PUT Bucket tagging.
// ----------
func ( api objectAPIHandlers ) PutBucketTaggingHandler ( w http . ResponseWriter , r * http . Request ) {
ctx := newContext ( r , w , "PutBucketTagging" )
defer logger . AuditLog ( w , r , "PutBucketTagging" , mustGetClaimsFromToken ( r ) )
vars := mux . Vars ( r )
bucket := vars [ "bucket" ]
objectAPI := api . ObjectAPI ( )
if objectAPI == nil {
writeErrorResponse ( ctx , w , errorCodes . ToAPIErr ( ErrServerNotInitialized ) , r . URL , guessIsBrowserReq ( r ) )
return
}
if s3Error := checkRequestAuthType ( ctx , r , policy . PutBucketTaggingAction , bucket , "" ) ; s3Error != ErrNone {
writeErrorResponse ( ctx , w , errorCodes . ToAPIErr ( s3Error ) , r . URL , guessIsBrowserReq ( r ) )
return
}
2020-05-08 16:44:44 -04:00
2020-05-05 17:18:13 -04:00
tags , err := tags . ParseBucketXML ( io . LimitReader ( r . Body , r . ContentLength ) )
if err != nil {
apiErr := errorCodes . ToAPIErr ( ErrMalformedXML )
apiErr . Description = err . Error ( )
writeErrorResponse ( ctx , w , apiErr , r . URL , guessIsBrowserReq ( r ) )
return
}
2020-05-08 16:44:44 -04:00
2020-05-19 16:53:54 -04:00
configData , err := xml . Marshal ( tags )
if err != nil {
writeErrorResponse ( ctx , w , toAPIError ( ctx , err ) , r . URL , guessIsBrowserReq ( r ) )
return
}
2020-06-16 01:09:39 -04:00
if err = globalBucketMetadataSys . Update ( bucket , bucketTaggingConfig , configData ) ; err != nil {
2020-05-05 17:18:13 -04:00
writeErrorResponse ( ctx , w , toAPIError ( ctx , err ) , r . URL , guessIsBrowserReq ( r ) )
return
}
// Write success response.
writeSuccessResponseHeadersOnly ( w )
}
// GetBucketTaggingHandler - GET Bucket tagging.
// ----------
func ( api objectAPIHandlers ) GetBucketTaggingHandler ( w http . ResponseWriter , r * http . Request ) {
ctx := newContext ( r , w , "GetBucketTagging" )
defer logger . AuditLog ( w , r , "GetBucketTagging" , mustGetClaimsFromToken ( r ) )
vars := mux . Vars ( r )
bucket := vars [ "bucket" ]
objectAPI := api . ObjectAPI ( )
if objectAPI == nil {
writeErrorResponse ( ctx , w , errorCodes . ToAPIErr ( ErrServerNotInitialized ) , r . URL , guessIsBrowserReq ( r ) )
return
}
2020-05-08 16:44:44 -04:00
2020-05-05 17:18:13 -04:00
// check if user has permissions to perform this operation
if s3Error := checkRequestAuthType ( ctx , r , policy . GetBucketTaggingAction , bucket , "" ) ; s3Error != ErrNone {
writeErrorResponse ( ctx , w , errorCodes . ToAPIErr ( s3Error ) , r . URL , guessIsBrowserReq ( r ) )
return
}
2020-05-08 16:44:44 -04:00
2020-05-20 13:18:15 -04:00
config , err := globalBucketMetadataSys . GetTaggingConfig ( bucket )
if err != nil {
writeErrorResponse ( ctx , w , toAPIError ( ctx , err ) , r . URL , guessIsBrowserReq ( r ) )
return
}
configData , err := xml . Marshal ( config )
2020-05-08 16:44:44 -04:00
if err != nil {
writeErrorResponse ( ctx , w , toAPIError ( ctx , err ) , r . URL , guessIsBrowserReq ( r ) )
2020-05-05 17:18:13 -04:00
return
}
// Write success response.
writeSuccessResponseXML ( w , configData )
}
// DeleteBucketTaggingHandler - DELETE Bucket tagging.
// ----------
func ( api objectAPIHandlers ) DeleteBucketTaggingHandler ( w http . ResponseWriter , r * http . Request ) {
ctx := newContext ( r , w , "DeleteBucketTagging" )
defer logger . AuditLog ( w , r , "DeleteBucketTagging" , mustGetClaimsFromToken ( r ) )
vars := mux . Vars ( r )
bucket := vars [ "bucket" ]
objectAPI := api . ObjectAPI ( )
if objectAPI == nil {
writeErrorResponse ( ctx , w , errorCodes . ToAPIErr ( ErrServerNotInitialized ) , r . URL , guessIsBrowserReq ( r ) )
return
}
if s3Error := checkRequestAuthType ( ctx , r , policy . PutBucketTaggingAction , bucket , "" ) ; s3Error != ErrNone {
writeErrorResponse ( ctx , w , errorCodes . ToAPIErr ( s3Error ) , r . URL , guessIsBrowserReq ( r ) )
return
}
2020-06-16 01:09:39 -04:00
if err := globalBucketMetadataSys . Update ( bucket , bucketTaggingConfig , nil ) ; err != nil {
2020-05-05 17:18:13 -04:00
writeErrorResponse ( ctx , w , toAPIError ( ctx , err ) , r . URL , guessIsBrowserReq ( r ) )
return
}
// Write success response.
writeSuccessResponseHeadersOnly ( w )
}
2020-07-21 20:49:56 -04:00
// PutBucketReplicationConfigHandler - PUT Bucket replication configuration.
// ----------
// Add a replication configuration on the specified bucket as specified in https://docs.aws.amazon.com/AmazonS3/latest/API/API_PutBucketReplication.html
func ( api objectAPIHandlers ) PutBucketReplicationConfigHandler ( w http . ResponseWriter , r * http . Request ) {
ctx := newContext ( r , w , "PutBucketReplicationConfig" )
defer logger . AuditLog ( w , r , "PutBucketReplicationConfig" , mustGetClaimsFromToken ( r ) )
vars := mux . Vars ( r )
bucket := vars [ "bucket" ]
objectAPI := api . ObjectAPI ( )
if objectAPI == nil {
writeErrorResponse ( ctx , w , errorCodes . ToAPIErr ( ErrServerNotInitialized ) , r . URL , guessIsBrowserReq ( r ) )
return
}
2020-07-22 20:39:40 -04:00
if ! globalIsErasure {
writeErrorResponseJSON ( ctx , w , errorCodes . ToAPIErr ( ErrNotImplemented ) , r . URL )
return
}
2020-07-21 20:49:56 -04:00
// Turn off replication if disk crawl is unavailable.
if env . Get ( envDataUsageCrawlConf , config . EnableOn ) == config . EnableOff {
writeErrorResponseJSON ( ctx , w , errorCodes . ToAPIErr ( ErrBucketReplicationDisabledError ) , r . URL )
return
}
if s3Error := checkRequestAuthType ( ctx , r , policy . PutReplicationConfigurationAction , bucket , "" ) ; s3Error != ErrNone {
writeErrorResponse ( ctx , w , errorCodes . ToAPIErr ( s3Error ) , r . URL , guessIsBrowserReq ( r ) )
return
}
// Check if bucket exists.
if _ , err := objectAPI . GetBucketInfo ( ctx , bucket ) ; err != nil {
writeErrorResponse ( ctx , w , toAPIError ( ctx , err ) , r . URL , guessIsBrowserReq ( r ) )
return
}
if versioned := globalBucketVersioningSys . Enabled ( bucket ) ; ! versioned {
writeErrorResponse ( ctx , w , errorCodes . ToAPIErr ( ErrReplicationNeedsVersioningError ) , r . URL , guessIsBrowserReq ( r ) )
return
}
replicationConfig , err := replication . ParseConfig ( io . LimitReader ( r . Body , r . ContentLength ) )
if err != nil {
apiErr := errorCodes . ToAPIErr ( ErrMalformedXML )
apiErr . Description = err . Error ( )
writeErrorResponse ( ctx , w , apiErr , r . URL , guessIsBrowserReq ( r ) )
return
}
2020-07-30 22:55:22 -04:00
sameTarget , err := validateReplicationDestination ( ctx , bucket , replicationConfig )
2020-07-21 20:49:56 -04:00
if err != nil {
writeErrorResponse ( ctx , w , toAPIError ( ctx , err ) , r . URL , guessIsBrowserReq ( r ) )
return
}
// Validate the received bucket replication config
if err = replicationConfig . Validate ( bucket , sameTarget ) ; err != nil {
writeErrorResponse ( ctx , w , toAPIError ( ctx , err ) , r . URL , guessIsBrowserReq ( r ) )
return
}
configData , err := xml . Marshal ( replicationConfig )
if err != nil {
writeErrorResponse ( ctx , w , toAPIError ( ctx , err ) , r . URL , guessIsBrowserReq ( r ) )
return
}
if err = globalBucketMetadataSys . Update ( bucket , bucketReplicationConfig , configData ) ; err != nil {
writeErrorResponse ( ctx , w , toAPIError ( ctx , err ) , r . URL , guessIsBrowserReq ( r ) )
return
}
// Write success response.
writeSuccessResponseHeadersOnly ( w )
}
// GetBucketReplicationConfigHandler - GET Bucket replication configuration.
// ----------
// Gets the replication configuration for a bucket.
func ( api objectAPIHandlers ) GetBucketReplicationConfigHandler ( w http . ResponseWriter , r * http . Request ) {
ctx := newContext ( r , w , "GetBucketReplicationConfig" )
defer logger . AuditLog ( w , r , "GetBucketReplicationConfig" , mustGetClaimsFromToken ( r ) )
vars := mux . Vars ( r )
bucket := vars [ "bucket" ]
objectAPI := api . ObjectAPI ( )
if objectAPI == nil {
writeErrorResponse ( ctx , w , errorCodes . ToAPIErr ( ErrServerNotInitialized ) , r . URL , guessIsBrowserReq ( r ) )
return
}
// check if user has permissions to perform this operation
if s3Error := checkRequestAuthType ( ctx , r , policy . GetReplicationConfigurationAction , bucket , "" ) ; s3Error != ErrNone {
writeErrorResponse ( ctx , w , errorCodes . ToAPIErr ( s3Error ) , r . URL , guessIsBrowserReq ( r ) )
return
}
// Check if bucket exists.
if _ , err := objectAPI . GetBucketInfo ( ctx , bucket ) ; err != nil {
writeErrorResponse ( ctx , w , toAPIError ( ctx , err ) , r . URL , guessIsBrowserReq ( r ) )
return
}
config , err := globalBucketMetadataSys . GetReplicationConfig ( ctx , bucket )
if err != nil {
writeErrorResponse ( ctx , w , toAPIError ( ctx , err ) , r . URL , guessIsBrowserReq ( r ) )
return
}
configData , err := xml . Marshal ( config )
if err != nil {
writeErrorResponse ( ctx , w , toAPIError ( ctx , err ) , r . URL , guessIsBrowserReq ( r ) )
return
}
// Write success response.
writeSuccessResponseXML ( w , configData )
}
// DeleteBucketReplicationConfigHandler - DELETE Bucket replication config.
// ----------
func ( api objectAPIHandlers ) DeleteBucketReplicationConfigHandler ( w http . ResponseWriter , r * http . Request ) {
ctx := newContext ( r , w , "DeleteBucketReplicationConfig" )
defer logger . AuditLog ( w , r , "DeleteBucketReplicationConfig" , mustGetClaimsFromToken ( r ) )
vars := mux . Vars ( r )
bucket := vars [ "bucket" ]
objectAPI := api . ObjectAPI ( )
if objectAPI == nil {
writeErrorResponse ( ctx , w , errorCodes . ToAPIErr ( ErrServerNotInitialized ) , r . URL , guessIsBrowserReq ( r ) )
return
}
if s3Error := checkRequestAuthType ( ctx , r , policy . PutReplicationConfigurationAction , bucket , "" ) ; s3Error != ErrNone {
writeErrorResponse ( ctx , w , errorCodes . ToAPIErr ( s3Error ) , r . URL , guessIsBrowserReq ( r ) )
return
}
// Check if bucket exists.
if _ , err := objectAPI . GetBucketInfo ( ctx , bucket ) ; err != nil {
writeErrorResponse ( ctx , w , toAPIError ( ctx , err ) , r . URL , guessIsBrowserReq ( r ) )
return
}
if err := globalBucketMetadataSys . Update ( bucket , bucketReplicationConfig , nil ) ; err != nil {
writeErrorResponse ( ctx , w , toAPIError ( ctx , err ) , r . URL , guessIsBrowserReq ( r ) )
return
}
// Write success response.
writeSuccessResponseHeadersOnly ( w )
}