diff --git a/cmd/generic-handlers.go b/cmd/generic-handlers.go index f58d28ba2..2f6012efc 100644 --- a/cmd/generic-handlers.go +++ b/cmd/generic-handlers.go @@ -19,7 +19,6 @@ package cmd import ( "bufio" "context" - "fmt" "net" "net/http" "strings" @@ -627,11 +626,52 @@ type bucketForwardingHandler struct { func (f bucketForwardingHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { if globalDNSConfig == nil || globalDomainName == "" || - guessIsBrowserReq(r) || guessIsHealthCheckReq(r) || - guessIsMetricsReq(r) || guessIsRPCReq(r) || isAdminReq(r) { + guessIsHealthCheckReq(r) || guessIsMetricsReq(r) || + guessIsRPCReq(r) || isAdminReq(r) { f.handler.ServeHTTP(w, r) return } + + // For browser requests, when federation is setup we need to + // specifically handle download and upload for browser requests. + if guessIsBrowserReq(r) && globalDNSConfig != nil && globalDomainName != "" { + var bucket, _ string + switch r.Method { + case http.MethodPut: + if getRequestAuthType(r) == authTypeJWT { + bucket, _ = urlPath2BucketObjectName(strings.TrimPrefix(r.URL.Path, minioReservedBucketPath+"/upload")) + } + case http.MethodGet: + if t := r.URL.Query().Get("token"); t != "" { + bucket, _ = urlPath2BucketObjectName(strings.TrimPrefix(r.URL.Path, minioReservedBucketPath+"/download")) + } else if getRequestAuthType(r) != authTypeJWT && !strings.HasPrefix(r.URL.Path, minioReservedBucketPath) { + bucket, _ = urlPath2BucketObjectName(r.URL.Path) + } + } + if bucket != "" { + sr, err := globalDNSConfig.Get(bucket) + if err != nil { + if err == dns.ErrNoEntriesFound { + writeErrorResponse(w, ErrNoSuchBucket, r.URL, guessIsBrowserReq(r)) + } else { + writeErrorResponse(w, toAPIErrorCode(context.Background(), err), r.URL, guessIsBrowserReq(r)) + } + return + } + if globalDomainIPs.Intersection(set.CreateStringSet(getHostsSlice(sr)...)).IsEmpty() { + r.URL.Scheme = "http" + if globalIsSSL { + r.URL.Scheme = "https" + } + r.URL.Host = getHostFromSrv(sr) + f.fwd.ServeHTTP(w, r) + return + } + } + f.handler.ServeHTTP(w, r) + return + } + bucket, object := urlPath2BucketObjectName(r.URL.Path) // ListBucket requests should be handled at current endpoint as // all buckets data can be fetched from here. @@ -663,12 +703,11 @@ func (f bucketForwardingHandler) ServeHTTP(w http.ResponseWriter, r *http.Reques return } if globalDomainIPs.Intersection(set.CreateStringSet(getHostsSlice(sr)...)).IsEmpty() { - host, port := getRandomHostPort(sr) r.URL.Scheme = "http" if globalIsSSL { r.URL.Scheme = "https" } - r.URL.Host = fmt.Sprintf("%s:%d", host, port) + r.URL.Host = getHostFromSrv(sr) f.fwd.ServeHTTP(w, r) return } diff --git a/cmd/handler-utils.go b/cmd/handler-utils.go index dc38c9425..2074b3ac8 100644 --- a/cmd/handler-utils.go +++ b/cmd/handler-utils.go @@ -183,6 +183,13 @@ func getReqAccessCred(r *http.Request, region string) (cred auth.Credentials) { if cred.AccessKey == "" { cred, _, _ = getReqAccessKeyV2(r) } + if cred.AccessKey == "" { + claims, owner, _ := webRequestAuthenticate(r) + if owner { + return globalServerConfig.GetCredential() + } + cred, _ = globalIAMSys.GetUser(claims.Subject) + } return cred } @@ -194,6 +201,7 @@ func extractReqParams(r *http.Request) map[string]string { region := globalServerConfig.GetRegion() cred := getReqAccessCred(r, region) + // Success. return map[string]string{ "region": region, diff --git a/cmd/object-api-utils.go b/cmd/object-api-utils.go index c36877a7e..75e8efa37 100644 --- a/cmd/object-api-utils.go +++ b/cmd/object-api-utils.go @@ -23,6 +23,7 @@ import ( "fmt" "io" "math/rand" + "net" "net/http" "path" "runtime" @@ -299,11 +300,11 @@ func getHostsSlice(records []dns.SrvRecord) []string { return hosts } -// returns a random host (and corresponding port) from a slice of DNS records -func getRandomHostPort(records []dns.SrvRecord) (string, int) { +// returns a host (and corresponding port) from a slice of DNS records +func getHostFromSrv(records []dns.SrvRecord) string { rand.Seed(time.Now().Unix()) srvRecord := records[rand.Intn(len(records))] - return srvRecord.Host, srvRecord.Port + return net.JoinHostPort(srvRecord.Host, fmt.Sprintf("%d", srvRecord.Port)) } // IsCompressed returns true if the object is marked as compressed. diff --git a/cmd/object-handlers.go b/cmd/object-handlers.go index c3497951f..69a117b23 100644 --- a/cmd/object-handlers.go +++ b/cmd/object-handlers.go @@ -668,11 +668,25 @@ func getCpObjMetadataFromHeader(ctx context.Context, r *http.Request, userMeta m // Returns a minio-go Client configured to access remote host described by destDNSRecord // Applicable only in a federated deployment -var getRemoteInstanceClient = func(r *http.Request, host string, port int) (*miniogo.Core, error) { - // In a federated deployment, all the instances share config files and hence expected to have same - // credentials, make sure to send the same credentials for which the request came in. +var getRemoteInstanceClient = func(r *http.Request, host string) (*miniogo.Core, error) { cred := getReqAccessCred(r, globalServerConfig.GetRegion()) - return miniogo.NewCore(net.JoinHostPort(host, strconv.Itoa(port)), cred.AccessKey, cred.SecretKey, globalIsSSL) + // In a federated deployment, all the instances share config files + // and hence expected to have same credentials. + core, err := miniogo.NewCore(host, cred.AccessKey, cred.SecretKey, globalIsSSL) + if err != nil { + return nil, err + } + core.SetCustomTransport(NewCustomHTTPTransport()) + return core, nil +} + +// Check if the bucket is on a remote site, this code only gets executed when federation is enabled. +var isRemoteCallRequired = func(ctx context.Context, bucket string, objAPI ObjectLayer) bool { + if globalDNSConfig == nil { + return false + } + _, err := objAPI.GetBucketInfo(ctx, bucket) + return err == toObjectErr(errVolumeNotFound, bucket) } // CopyObjectHandler - Copy Object @@ -802,17 +816,6 @@ func (api objectAPIHandlers) CopyObjectHandler(w http.ResponseWriter, r *http.Re srcInfo.metadataOnly = true } - // Checks if a remote putobject call is needed for CopyObject operation - // 1. If source and destination bucket names are same, it means no call needed to etcd to get destination info - // 2. If destination bucket doesn't exist locally, only then a etcd call is needed - var isRemoteCallRequired = func(ctx context.Context, src, dst string, objAPI ObjectLayer) bool { - if src == dst { - return false - } - _, berr := objAPI.GetBucketInfo(ctx, dst) - return berr == toObjectErr(errVolumeNotFound, dst) - } - var reader io.Reader var length = srcInfo.Size @@ -827,10 +830,27 @@ func (api objectAPIHandlers) CopyObjectHandler(w http.ResponseWriter, r *http.Re length = actualSize } + // Check if the destination bucket is on a remote site, this code only gets executed + // when federation is enabled, ie when globalDNSConfig is non 'nil'. + // + // This function is similar to isRemoteCallRequired but specifically for COPY object API + // if destination and source are same we do not need to check for destnation bucket + // to exist locally. + var isRemoteCopyRequired = func(ctx context.Context, srcBucket, dstBucket string, objAPI ObjectLayer) bool { + if globalDNSConfig == nil { + return false + } + if srcBucket == dstBucket { + return false + } + _, err := objAPI.GetBucketInfo(ctx, dstBucket) + return err == toObjectErr(errVolumeNotFound, dstBucket) + } + var compressMetadata map[string]string // No need to compress for remote etcd calls // Pass the decompressed stream to such calls. - isCompressed := objectAPI.IsCompressionSupported() && isCompressible(r.Header, srcObject) && !isRemoteCallRequired(ctx, srcBucket, dstBucket, objectAPI) + isCompressed := objectAPI.IsCompressionSupported() && isCompressible(r.Header, srcObject) && !isRemoteCopyRequired(ctx, srcBucket, dstBucket, objectAPI) if isCompressed { compressMetadata = make(map[string]string, 2) // Preserving the compression metadata. @@ -1009,29 +1029,28 @@ func (api objectAPIHandlers) CopyObjectHandler(w http.ResponseWriter, r *http.Re var objInfo ObjectInfo - if isRemoteCallRequired(ctx, srcBucket, dstBucket, objectAPI) { - if globalDNSConfig == nil { - writeErrorResponse(w, ErrNoSuchBucket, r.URL, guessIsBrowserReq(r)) + if isRemoteCopyRequired(ctx, srcBucket, dstBucket, objectAPI) { + var dstRecords []dns.SrvRecord + dstRecords, err = globalDNSConfig.Get(dstBucket) + if err != nil { + writeErrorResponse(w, toAPIErrorCode(ctx, err), r.URL, guessIsBrowserReq(r)) return } - var dstRecords []dns.SrvRecord - if dstRecords, err = globalDNSConfig.Get(dstBucket); err == nil { - // Send PutObject request to appropriate instance (in federated deployment) - host, port := getRandomHostPort(dstRecords) - client, rerr := getRemoteInstanceClient(r, host, port) - if rerr != nil { - writeErrorResponse(w, toAPIErrorCode(ctx, rerr), r.URL, guessIsBrowserReq(r)) - return - } - remoteObjInfo, rerr := client.PutObject(dstBucket, dstObject, srcInfo.Reader, - srcInfo.Size, "", "", srcInfo.UserDefined, dstOpts.ServerSideEncryption) - if rerr != nil { - writeErrorResponse(w, toAPIErrorCode(ctx, rerr), r.URL, guessIsBrowserReq(r)) - return - } - objInfo.ETag = remoteObjInfo.ETag - objInfo.ModTime = remoteObjInfo.LastModified + + // Send PutObject request to appropriate instance (in federated deployment) + client, rerr := getRemoteInstanceClient(r, getHostFromSrv(dstRecords)) + if rerr != nil { + writeErrorResponse(w, toAPIErrorCode(ctx, rerr), r.URL, guessIsBrowserReq(r)) + return + } + remoteObjInfo, rerr := client.PutObject(dstBucket, dstObject, srcInfo.Reader, + srcInfo.Size, "", "", srcInfo.UserDefined, dstOpts.ServerSideEncryption) + if rerr != nil { + writeErrorResponse(w, toAPIErrorCode(ctx, rerr), r.URL, guessIsBrowserReq(r)) + return } + objInfo.ETag = remoteObjInfo.ETag + objInfo.ModTime = remoteObjInfo.LastModified } else { // Copy source object to destination, if source and destination // object is same then only metadata is updated. diff --git a/cmd/web-handlers.go b/cmd/web-handlers.go index b616a7459..8ce3ed3e9 100644 --- a/cmd/web-handlers.go +++ b/cmd/web-handlers.go @@ -99,11 +99,14 @@ func (web *webAPIHandlers) ServerInfo(r *http.Request, args *WebGenericArgs, rep reply.MinioGlobalInfo = getGlobalInfo() // If ENV creds are not set and incoming user is not owner // disable changing credentials. - // TODO: fix this in future and allow changing user credentials. v, ok := reply.MinioGlobalInfo["isEnvCreds"].(bool) if ok && !v { reply.MinioGlobalInfo["isEnvCreds"] = !owner } + // if etcd is set disallow changing credentials through UI + if globalEtcdClient != nil { + reply.MinioGlobalInfo["isEnvCreds"] = true + } reply.MinioMemory = mem reply.MinioPlatform = platform @@ -148,7 +151,7 @@ func (web *webAPIHandlers) MakeBucket(r *http.Request, args *MakeBucketArgs, rep if authErr != nil { return toJSONError(authErr) } - // TODO: Allow MakeBucket in future. + if !owner { return toJSONError(errAccessDenied) } @@ -201,11 +204,33 @@ func (web *webAPIHandlers) DeleteBucket(r *http.Request, args *RemoveBucketArgs, if authErr != nil { return toJSONError(authErr) } - // TODO: Allow DeleteBucket in future. + if !owner { return toJSONError(errAccessDenied) } + reply.UIVersion = browser.UIVersion + + if isRemoteCallRequired(context.Background(), args.BucketName, objectAPI) { + sr, err := globalDNSConfig.Get(args.BucketName) + if err != nil { + if err == dns.ErrNoEntriesFound { + return toJSONError(BucketNotFound{ + Bucket: args.BucketName, + }, args.BucketName) + } + return toJSONError(err, args.BucketName) + } + core, err := getRemoteInstanceClient(r, getHostFromSrv(sr)) + if err != nil { + return toJSONError(err, args.BucketName) + } + if err = core.RemoveBucket(args.BucketName); err != nil { + return toJSONError(err, args.BucketName) + } + return nil + } + ctx := context.Background() deleteBucket := objectAPI.DeleteBucket @@ -229,7 +254,6 @@ func (web *webAPIHandlers) DeleteBucket(r *http.Request, args *RemoveBucketArgs, } } - reply.UIVersion = browser.UIVersion return nil } @@ -353,6 +377,42 @@ func (web *webAPIHandlers) ListObjects(r *http.Request, args *ListObjectsArgs, r listObjects = web.CacheAPI().ListObjects } + if isRemoteCallRequired(context.Background(), args.BucketName, objectAPI) { + sr, err := globalDNSConfig.Get(args.BucketName) + if err != nil { + if err == dns.ErrNoEntriesFound { + return toJSONError(BucketNotFound{ + Bucket: args.BucketName, + }, args.BucketName) + } + return toJSONError(err, args.BucketName) + } + core, err := getRemoteInstanceClient(r, getHostFromSrv(sr)) + if err != nil { + return toJSONError(err, args.BucketName) + } + result, err := core.ListObjects(args.BucketName, args.Prefix, args.Marker, slashSeparator, 1000) + if err != nil { + return toJSONError(err, args.BucketName) + } + reply.NextMarker = result.NextMarker + reply.IsTruncated = result.IsTruncated + for _, obj := range result.Contents { + reply.Objects = append(reply.Objects, WebObjectInfo{ + Key: obj.Key, + LastModified: obj.LastModified, + Size: obj.Size, + ContentType: obj.ContentType, + }) + } + for _, p := range result.CommonPrefixes { + reply.Objects = append(reply.Objects, WebObjectInfo{ + Key: p.Prefix, + }) + } + return nil + } + claims, owner, authErr := webRequestAuthenticate(r) if authErr != nil { if authErr == errNoAuthToken { @@ -493,6 +553,40 @@ func (web *webAPIHandlers) RemoveObject(r *http.Request, args *RemoveObjectArgs, return toJSONError(errInvalidArgument) } + reply.UIVersion = browser.UIVersion + if isRemoteCallRequired(context.Background(), args.BucketName, objectAPI) { + sr, err := globalDNSConfig.Get(args.BucketName) + if err != nil { + if err == dns.ErrNoEntriesFound { + return toJSONError(BucketNotFound{ + Bucket: args.BucketName, + }, args.BucketName) + } + return toJSONError(err, args.BucketName) + } + core, err := getRemoteInstanceClient(r, getHostFromSrv(sr)) + if err != nil { + return toJSONError(err, args.BucketName) + } + objectsCh := make(chan string) + + // Send object names that are needed to be removed to objectsCh + go func() { + defer close(objectsCh) + + for _, objectName := range args.Objects { + objectsCh <- objectName + } + }() + + for resp := range core.RemoveObjects(args.BucketName, objectsCh) { + if resp.Err != nil { + return toJSONError(resp.Err, args.BucketName, resp.ObjectName) + } + } + return nil + } + var err error next: for _, objectName := range args.Objects { @@ -516,7 +610,7 @@ next: return toJSONError(errAccessDenied) } - if err = deleteObject(nil, objectAPI, web.CacheAPI(), args.BucketName, objectName, r); err != nil { + if err = deleteObject(context.Background(), objectAPI, web.CacheAPI(), args.BucketName, objectName, r); err != nil { break next } continue @@ -543,7 +637,7 @@ next: } marker = lo.NextMarker for _, obj := range lo.Objects { - err = deleteObject(nil, objectAPI, web.CacheAPI(), args.BucketName, obj.Name, r) + err = deleteObject(context.Background(), objectAPI, web.CacheAPI(), args.BucketName, obj.Name, r) if err != nil { break next } @@ -559,7 +653,6 @@ next: return toJSONError(err, args.BucketName, "") } - reply.UIVersion = browser.UIVersion return nil } @@ -633,8 +726,7 @@ func (web *webAPIHandlers) SetAuth(r *http.Request, args *SetAuthArgs, reply *Se } // If creds are set through ENV disallow changing credentials. - // TODO: Multi-user credentials also cannot be changed from browser. - if globalIsEnvCreds || globalWORMEnabled || !owner { + if globalIsEnvCreds || globalWORMEnabled || !owner || globalEtcdClient != nil { return toJSONError(errChangeCredNotAllowed) } @@ -858,6 +950,7 @@ func (web *webAPIHandlers) Upload(w http.ResponseWriter, r *http.Request) { pReader = NewPutObjReader(rawReader, hashReader, objectEncryptionKey) } } + // Ensure that metadata does not contain sensitive information crypto.RemoveSensitiveEntries(metadata) @@ -1306,18 +1399,48 @@ func (web *webAPIHandlers) GetBucketPolicy(r *http.Request, args *GetBucketPolic return toJSONError(errAccessDenied) } - bucketPolicy, err := objectAPI.GetBucketPolicy(context.Background(), args.BucketName) - if err != nil { - if _, ok := err.(BucketPolicyNotFound); !ok { + var policyInfo = &miniogopolicy.BucketAccessPolicy{Version: "2012-10-17"} + if isRemoteCallRequired(context.Background(), args.BucketName, objectAPI) { + sr, err := globalDNSConfig.Get(args.BucketName) + if err != nil { + if err == dns.ErrNoEntriesFound { + return toJSONError(BucketNotFound{ + Bucket: args.BucketName, + }, args.BucketName) + } return toJSONError(err, args.BucketName) } - return err - } + client, rerr := getRemoteInstanceClient(r, getHostFromSrv(sr)) + if rerr != nil { + return toJSONError(rerr, args.BucketName) + } + policyStr, err := client.GetBucketPolicy(args.BucketName) + if err != nil { + return toJSONError(rerr, args.BucketName) + } + bucketPolicy, err := policy.ParseConfig(strings.NewReader(policyStr), args.BucketName) + if err != nil { + return toJSONError(rerr, args.BucketName) + } + policyInfo, err = PolicyToBucketAccessPolicy(bucketPolicy) + if err != nil { + // This should not happen. + return toJSONError(err, args.BucketName) + } + } else { + bucketPolicy, err := objectAPI.GetBucketPolicy(context.Background(), args.BucketName) + if err != nil { + if _, ok := err.(BucketPolicyNotFound); !ok { + return toJSONError(err, args.BucketName) + } + return err + } - policyInfo, err := PolicyToBucketAccessPolicy(bucketPolicy) - if err != nil { - // This should not happen. - return toJSONError(err, args.BucketName) + policyInfo, err = PolicyToBucketAccessPolicy(bucketPolicy) + if err != nil { + // This should not happen. + return toJSONError(err, args.BucketName) + } } reply.UIVersion = browser.UIVersion @@ -1359,17 +1482,42 @@ func (web *webAPIHandlers) ListAllBucketPolicies(r *http.Request, args *ListAllB return toJSONError(errAccessDenied) } - bucketPolicy, err := objectAPI.GetBucketPolicy(context.Background(), args.BucketName) - if err != nil { - if _, ok := err.(BucketPolicyNotFound); !ok { + var policyInfo = new(miniogopolicy.BucketAccessPolicy) + if isRemoteCallRequired(context.Background(), args.BucketName, objectAPI) { + sr, err := globalDNSConfig.Get(args.BucketName) + if err != nil { + if err == dns.ErrNoEntriesFound { + return toJSONError(BucketNotFound{ + Bucket: args.BucketName, + }, args.BucketName) + } + return toJSONError(err, args.BucketName) + } + core, rerr := getRemoteInstanceClient(r, getHostFromSrv(sr)) + if rerr != nil { + return toJSONError(rerr, args.BucketName) + } + var policyStr string + policyStr, err = core.Client.GetBucketPolicy(args.BucketName) + if err != nil { + return toJSONError(err, args.BucketName) + } + if policyStr != "" { + if err = json.Unmarshal([]byte(policyStr), policyInfo); err != nil { + return toJSONError(err, args.BucketName) + } + } + } else { + bucketPolicy, err := objectAPI.GetBucketPolicy(context.Background(), args.BucketName) + if err != nil { + if _, ok := err.(BucketPolicyNotFound); !ok { + return toJSONError(err, args.BucketName) + } + } + policyInfo, err = PolicyToBucketAccessPolicy(bucketPolicy) + if err != nil { return toJSONError(err, args.BucketName) } - } - - policyInfo, err := PolicyToBucketAccessPolicy(bucketPolicy) - if err != nil { - // This should not happen. - return toJSONError(err, args.BucketName) } reply.UIVersion = browser.UIVersion @@ -1419,43 +1567,94 @@ func (web *webAPIHandlers) SetBucketPolicy(r *http.Request, args *SetBucketPolic ctx := context.Background() - bucketPolicy, err := objectAPI.GetBucketPolicy(ctx, args.BucketName) - if err != nil { - if _, ok := err.(BucketPolicyNotFound); !ok { + if isRemoteCallRequired(context.Background(), args.BucketName, objectAPI) { + sr, err := globalDNSConfig.Get(args.BucketName) + if err != nil { + if err == dns.ErrNoEntriesFound { + return toJSONError(BucketNotFound{ + Bucket: args.BucketName, + }, args.BucketName) + } return toJSONError(err, args.BucketName) } - } + core, rerr := getRemoteInstanceClient(r, getHostFromSrv(sr)) + if rerr != nil { + return toJSONError(rerr, args.BucketName) + } + var policyStr string + // Use the abstracted API instead of core, such that + // NoSuchBucketPolicy errors are automatically handled. + policyStr, err = core.Client.GetBucketPolicy(args.BucketName) + if err != nil { + return toJSONError(err, args.BucketName) + } + var policyInfo = &miniogopolicy.BucketAccessPolicy{Version: "2012-10-17"} + if policyStr != "" { + if err = json.Unmarshal([]byte(policyStr), policyInfo); err != nil { + return toJSONError(err, args.BucketName) + } + } - policyInfo, err := PolicyToBucketAccessPolicy(bucketPolicy) - if err != nil { - // This should not happen. - return toJSONError(err, args.BucketName) - } + policyInfo.Statements = miniogopolicy.SetPolicy(policyInfo.Statements, policyType, args.BucketName, args.Prefix) + if len(policyInfo.Statements) == 0 { + if err = core.SetBucketPolicy(args.BucketName, ""); err != nil { + return toJSONError(err, args.BucketName) + } + return nil + } - policyInfo.Statements = miniogopolicy.SetPolicy(policyInfo.Statements, policyType, args.BucketName, args.Prefix) + bucketPolicy, err := BucketAccessPolicyToPolicy(policyInfo) + if err != nil { + // This should not happen. + return toJSONError(err, args.BucketName) + } - if len(policyInfo.Statements) == 0 { - if err = objectAPI.DeleteBucketPolicy(ctx, args.BucketName); err != nil { + policyData, err := json.Marshal(bucketPolicy) + if err != nil { return toJSONError(err, args.BucketName) } - globalPolicySys.Remove(args.BucketName) - return nil - } + if err = core.SetBucketPolicy(args.BucketName, string(policyData)); err != nil { + return toJSONError(err, args.BucketName) + } - bucketPolicy, err = BucketAccessPolicyToPolicy(policyInfo) - if err != nil { - // This should not happen. - return toJSONError(err, args.BucketName) - } + } else { + bucketPolicy, err := objectAPI.GetBucketPolicy(ctx, args.BucketName) + if err != nil { + if _, ok := err.(BucketPolicyNotFound); !ok { + return toJSONError(err, args.BucketName) + } + } + policyInfo, err := PolicyToBucketAccessPolicy(bucketPolicy) + if err != nil { + // This should not happen. + return toJSONError(err, args.BucketName) + } - // Parse validate and save bucket policy. - if err := objectAPI.SetBucketPolicy(ctx, args.BucketName, bucketPolicy); err != nil { - return toJSONError(err, args.BucketName) - } + policyInfo.Statements = miniogopolicy.SetPolicy(policyInfo.Statements, policyType, args.BucketName, args.Prefix) + if len(policyInfo.Statements) == 0 { + if err = objectAPI.DeleteBucketPolicy(ctx, args.BucketName); err != nil { + return toJSONError(err, args.BucketName) + } + + globalPolicySys.Remove(args.BucketName) + return nil + } + + bucketPolicy, err = BucketAccessPolicyToPolicy(policyInfo) + if err != nil { + // This should not happen. + return toJSONError(err, args.BucketName) + } - globalPolicySys.Set(args.BucketName, *bucketPolicy) - globalNotificationSys.SetBucketPolicy(ctx, args.BucketName, bucketPolicy) + // Parse validate and save bucket policy. + if err := objectAPI.SetBucketPolicy(ctx, args.BucketName, bucketPolicy); err != nil { + return toJSONError(err, args.BucketName) + } + + globalPolicySys.Set(args.BucketName, *bucketPolicy) + globalNotificationSys.SetBucketPolicy(ctx, args.BucketName, bucketPolicy) + } return nil } diff --git a/cmd/web-router.go b/cmd/web-router.go index 379a2c1b6..20067e0b1 100644 --- a/cmd/web-router.go +++ b/cmd/web-router.go @@ -89,7 +89,7 @@ func registerWebRouter(router *mux.Router) error { // These methods use short-expiry tokens in the URLs. These tokens may unintentionally // be logged, so a new one must be generated for each request. - webBrowserRouter.Methods("GET").Path("/download/{bucket}/{object:.+}").Queries("token", "{token:.*}").HandlerFunc(web.Download) + webBrowserRouter.Methods("GET").Path("/download/{bucket}/{object:.+}").Queries("token", "{token:.*}").HandlerFunc(httpTraceHdrs(web.Download)) webBrowserRouter.Methods("POST").Path("/zip").Queries("token", "{token:.*}").HandlerFunc(httpTraceHdrs(web.DownloadZip)) // Add compression for assets.