Add support for federation on browser (#6891)

This commit is contained in:
Harshavardhana 2018-12-19 05:13:47 -08:00 committed by Nitish Tiwari
parent 2aeb3fbe86
commit d1e41695fe
6 changed files with 365 additions and 99 deletions

View File

@ -19,7 +19,6 @@ package cmd
import ( import (
"bufio" "bufio"
"context" "context"
"fmt"
"net" "net"
"net/http" "net/http"
"strings" "strings"
@ -627,11 +626,52 @@ type bucketForwardingHandler struct {
func (f bucketForwardingHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { func (f bucketForwardingHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
if globalDNSConfig == nil || globalDomainName == "" || if globalDNSConfig == nil || globalDomainName == "" ||
guessIsBrowserReq(r) || guessIsHealthCheckReq(r) || guessIsHealthCheckReq(r) || guessIsMetricsReq(r) ||
guessIsMetricsReq(r) || guessIsRPCReq(r) || isAdminReq(r) { guessIsRPCReq(r) || isAdminReq(r) {
f.handler.ServeHTTP(w, r) f.handler.ServeHTTP(w, r)
return 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) bucket, object := urlPath2BucketObjectName(r.URL.Path)
// ListBucket requests should be handled at current endpoint as // ListBucket requests should be handled at current endpoint as
// all buckets data can be fetched from here. // all buckets data can be fetched from here.
@ -663,12 +703,11 @@ func (f bucketForwardingHandler) ServeHTTP(w http.ResponseWriter, r *http.Reques
return return
} }
if globalDomainIPs.Intersection(set.CreateStringSet(getHostsSlice(sr)...)).IsEmpty() { if globalDomainIPs.Intersection(set.CreateStringSet(getHostsSlice(sr)...)).IsEmpty() {
host, port := getRandomHostPort(sr)
r.URL.Scheme = "http" r.URL.Scheme = "http"
if globalIsSSL { if globalIsSSL {
r.URL.Scheme = "https" r.URL.Scheme = "https"
} }
r.URL.Host = fmt.Sprintf("%s:%d", host, port) r.URL.Host = getHostFromSrv(sr)
f.fwd.ServeHTTP(w, r) f.fwd.ServeHTTP(w, r)
return return
} }

View File

@ -183,6 +183,13 @@ func getReqAccessCred(r *http.Request, region string) (cred auth.Credentials) {
if cred.AccessKey == "" { if cred.AccessKey == "" {
cred, _, _ = getReqAccessKeyV2(r) cred, _, _ = getReqAccessKeyV2(r)
} }
if cred.AccessKey == "" {
claims, owner, _ := webRequestAuthenticate(r)
if owner {
return globalServerConfig.GetCredential()
}
cred, _ = globalIAMSys.GetUser(claims.Subject)
}
return cred return cred
} }
@ -194,6 +201,7 @@ func extractReqParams(r *http.Request) map[string]string {
region := globalServerConfig.GetRegion() region := globalServerConfig.GetRegion()
cred := getReqAccessCred(r, region) cred := getReqAccessCred(r, region)
// Success. // Success.
return map[string]string{ return map[string]string{
"region": region, "region": region,

View File

@ -23,6 +23,7 @@ import (
"fmt" "fmt"
"io" "io"
"math/rand" "math/rand"
"net"
"net/http" "net/http"
"path" "path"
"runtime" "runtime"
@ -299,11 +300,11 @@ func getHostsSlice(records []dns.SrvRecord) []string {
return hosts return hosts
} }
// returns a random host (and corresponding port) from a slice of DNS records // returns a host (and corresponding port) from a slice of DNS records
func getRandomHostPort(records []dns.SrvRecord) (string, int) { func getHostFromSrv(records []dns.SrvRecord) string {
rand.Seed(time.Now().Unix()) rand.Seed(time.Now().Unix())
srvRecord := records[rand.Intn(len(records))] 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. // IsCompressed returns true if the object is marked as compressed.

View File

@ -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 // Returns a minio-go Client configured to access remote host described by destDNSRecord
// Applicable only in a federated deployment // Applicable only in a federated deployment
var getRemoteInstanceClient = func(r *http.Request, host string, port int) (*miniogo.Core, error) { var getRemoteInstanceClient = func(r *http.Request, host string) (*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.
cred := getReqAccessCred(r, globalServerConfig.GetRegion()) 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 // CopyObjectHandler - Copy Object
@ -802,17 +816,6 @@ func (api objectAPIHandlers) CopyObjectHandler(w http.ResponseWriter, r *http.Re
srcInfo.metadataOnly = true 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 reader io.Reader
var length = srcInfo.Size var length = srcInfo.Size
@ -827,10 +830,27 @@ func (api objectAPIHandlers) CopyObjectHandler(w http.ResponseWriter, r *http.Re
length = actualSize 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 var compressMetadata map[string]string
// No need to compress for remote etcd calls // No need to compress for remote etcd calls
// Pass the decompressed stream to such 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 { if isCompressed {
compressMetadata = make(map[string]string, 2) compressMetadata = make(map[string]string, 2)
// Preserving the compression metadata. // Preserving the compression metadata.
@ -1009,29 +1029,28 @@ func (api objectAPIHandlers) CopyObjectHandler(w http.ResponseWriter, r *http.Re
var objInfo ObjectInfo var objInfo ObjectInfo
if isRemoteCallRequired(ctx, srcBucket, dstBucket, objectAPI) { if isRemoteCopyRequired(ctx, srcBucket, dstBucket, objectAPI) {
if globalDNSConfig == nil { var dstRecords []dns.SrvRecord
writeErrorResponse(w, ErrNoSuchBucket, r.URL, guessIsBrowserReq(r)) dstRecords, err = globalDNSConfig.Get(dstBucket)
if err != nil {
writeErrorResponse(w, toAPIErrorCode(ctx, err), r.URL, guessIsBrowserReq(r))
return return
} }
var dstRecords []dns.SrvRecord
if dstRecords, err = globalDNSConfig.Get(dstBucket); err == nil { // Send PutObject request to appropriate instance (in federated deployment)
// Send PutObject request to appropriate instance (in federated deployment) client, rerr := getRemoteInstanceClient(r, getHostFromSrv(dstRecords))
host, port := getRandomHostPort(dstRecords) if rerr != nil {
client, rerr := getRemoteInstanceClient(r, host, port) writeErrorResponse(w, toAPIErrorCode(ctx, rerr), r.URL, guessIsBrowserReq(r))
if rerr != nil { return
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
} }
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 { } else {
// Copy source object to destination, if source and destination // Copy source object to destination, if source and destination
// object is same then only metadata is updated. // object is same then only metadata is updated.

View File

@ -99,11 +99,14 @@ func (web *webAPIHandlers) ServerInfo(r *http.Request, args *WebGenericArgs, rep
reply.MinioGlobalInfo = getGlobalInfo() reply.MinioGlobalInfo = getGlobalInfo()
// If ENV creds are not set and incoming user is not owner // If ENV creds are not set and incoming user is not owner
// disable changing credentials. // disable changing credentials.
// TODO: fix this in future and allow changing user credentials.
v, ok := reply.MinioGlobalInfo["isEnvCreds"].(bool) v, ok := reply.MinioGlobalInfo["isEnvCreds"].(bool)
if ok && !v { if ok && !v {
reply.MinioGlobalInfo["isEnvCreds"] = !owner reply.MinioGlobalInfo["isEnvCreds"] = !owner
} }
// if etcd is set disallow changing credentials through UI
if globalEtcdClient != nil {
reply.MinioGlobalInfo["isEnvCreds"] = true
}
reply.MinioMemory = mem reply.MinioMemory = mem
reply.MinioPlatform = platform reply.MinioPlatform = platform
@ -148,7 +151,7 @@ func (web *webAPIHandlers) MakeBucket(r *http.Request, args *MakeBucketArgs, rep
if authErr != nil { if authErr != nil {
return toJSONError(authErr) return toJSONError(authErr)
} }
// TODO: Allow MakeBucket in future.
if !owner { if !owner {
return toJSONError(errAccessDenied) return toJSONError(errAccessDenied)
} }
@ -201,11 +204,33 @@ func (web *webAPIHandlers) DeleteBucket(r *http.Request, args *RemoveBucketArgs,
if authErr != nil { if authErr != nil {
return toJSONError(authErr) return toJSONError(authErr)
} }
// TODO: Allow DeleteBucket in future.
if !owner { if !owner {
return toJSONError(errAccessDenied) 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() ctx := context.Background()
deleteBucket := objectAPI.DeleteBucket deleteBucket := objectAPI.DeleteBucket
@ -229,7 +254,6 @@ func (web *webAPIHandlers) DeleteBucket(r *http.Request, args *RemoveBucketArgs,
} }
} }
reply.UIVersion = browser.UIVersion
return nil return nil
} }
@ -353,6 +377,42 @@ func (web *webAPIHandlers) ListObjects(r *http.Request, args *ListObjectsArgs, r
listObjects = web.CacheAPI().ListObjects 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) claims, owner, authErr := webRequestAuthenticate(r)
if authErr != nil { if authErr != nil {
if authErr == errNoAuthToken { if authErr == errNoAuthToken {
@ -493,6 +553,40 @@ func (web *webAPIHandlers) RemoveObject(r *http.Request, args *RemoveObjectArgs,
return toJSONError(errInvalidArgument) 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 var err error
next: next:
for _, objectName := range args.Objects { for _, objectName := range args.Objects {
@ -516,7 +610,7 @@ next:
return toJSONError(errAccessDenied) 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 break next
} }
continue continue
@ -543,7 +637,7 @@ next:
} }
marker = lo.NextMarker marker = lo.NextMarker
for _, obj := range lo.Objects { 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 { if err != nil {
break next break next
} }
@ -559,7 +653,6 @@ next:
return toJSONError(err, args.BucketName, "") return toJSONError(err, args.BucketName, "")
} }
reply.UIVersion = browser.UIVersion
return nil 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. // If creds are set through ENV disallow changing credentials.
// TODO: Multi-user credentials also cannot be changed from browser. if globalIsEnvCreds || globalWORMEnabled || !owner || globalEtcdClient != nil {
if globalIsEnvCreds || globalWORMEnabled || !owner {
return toJSONError(errChangeCredNotAllowed) return toJSONError(errChangeCredNotAllowed)
} }
@ -858,6 +950,7 @@ func (web *webAPIHandlers) Upload(w http.ResponseWriter, r *http.Request) {
pReader = NewPutObjReader(rawReader, hashReader, objectEncryptionKey) pReader = NewPutObjReader(rawReader, hashReader, objectEncryptionKey)
} }
} }
// Ensure that metadata does not contain sensitive information // Ensure that metadata does not contain sensitive information
crypto.RemoveSensitiveEntries(metadata) crypto.RemoveSensitiveEntries(metadata)
@ -1306,18 +1399,48 @@ func (web *webAPIHandlers) GetBucketPolicy(r *http.Request, args *GetBucketPolic
return toJSONError(errAccessDenied) return toJSONError(errAccessDenied)
} }
bucketPolicy, err := objectAPI.GetBucketPolicy(context.Background(), args.BucketName) var policyInfo = &miniogopolicy.BucketAccessPolicy{Version: "2012-10-17"}
if err != nil { if isRemoteCallRequired(context.Background(), args.BucketName, objectAPI) {
if _, ok := err.(BucketPolicyNotFound); !ok { 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 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) policyInfo, err = PolicyToBucketAccessPolicy(bucketPolicy)
if err != nil { if err != nil {
// This should not happen. // This should not happen.
return toJSONError(err, args.BucketName) return toJSONError(err, args.BucketName)
}
} }
reply.UIVersion = browser.UIVersion reply.UIVersion = browser.UIVersion
@ -1359,17 +1482,42 @@ func (web *webAPIHandlers) ListAllBucketPolicies(r *http.Request, args *ListAllB
return toJSONError(errAccessDenied) return toJSONError(errAccessDenied)
} }
bucketPolicy, err := objectAPI.GetBucketPolicy(context.Background(), args.BucketName) var policyInfo = new(miniogopolicy.BucketAccessPolicy)
if err != nil { if isRemoteCallRequired(context.Background(), args.BucketName, objectAPI) {
if _, ok := err.(BucketPolicyNotFound); !ok { 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) 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 reply.UIVersion = browser.UIVersion
@ -1419,43 +1567,94 @@ func (web *webAPIHandlers) SetBucketPolicy(r *http.Request, args *SetBucketPolic
ctx := context.Background() ctx := context.Background()
bucketPolicy, err := objectAPI.GetBucketPolicy(ctx, args.BucketName) if isRemoteCallRequired(context.Background(), args.BucketName, objectAPI) {
if err != nil { sr, err := globalDNSConfig.Get(args.BucketName)
if _, ok := err.(BucketPolicyNotFound); !ok { if err != nil {
if err == dns.ErrNoEntriesFound {
return toJSONError(BucketNotFound{
Bucket: args.BucketName,
}, args.BucketName)
}
return toJSONError(err, 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) policyInfo.Statements = miniogopolicy.SetPolicy(policyInfo.Statements, policyType, args.BucketName, args.Prefix)
if err != nil { if len(policyInfo.Statements) == 0 {
// This should not happen. if err = core.SetBucketPolicy(args.BucketName, ""); err != nil {
return toJSONError(err, args.BucketName) 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 {
if len(policyInfo.Statements) == 0 { // This should not happen.
if err = objectAPI.DeleteBucketPolicy(ctx, args.BucketName); err != nil {
return toJSONError(err, args.BucketName) return toJSONError(err, args.BucketName)
} }
globalPolicySys.Remove(args.BucketName) policyData, err := json.Marshal(bucketPolicy)
return nil if err != nil {
} return toJSONError(err, args.BucketName)
}
bucketPolicy, err = BucketAccessPolicyToPolicy(policyInfo) if err = core.SetBucketPolicy(args.BucketName, string(policyData)); err != nil {
if err != nil { return toJSONError(err, args.BucketName)
// This should not happen. }
return toJSONError(err, args.BucketName)
}
// Parse validate and save bucket policy. } else {
if err := objectAPI.SetBucketPolicy(ctx, args.BucketName, bucketPolicy); err != nil { bucketPolicy, err := objectAPI.GetBucketPolicy(ctx, args.BucketName)
return toJSONError(err, 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)
}
globalPolicySys.Set(args.BucketName, *bucketPolicy) policyInfo.Statements = miniogopolicy.SetPolicy(policyInfo.Statements, policyType, args.BucketName, args.Prefix)
globalNotificationSys.SetBucketPolicy(ctx, args.BucketName, bucketPolicy) 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)
}
// 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 return nil
} }

View File

@ -89,7 +89,7 @@ func registerWebRouter(router *mux.Router) error {
// These methods use short-expiry tokens in the URLs. These tokens may unintentionally // 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. // 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)) webBrowserRouter.Methods("POST").Path("/zip").Queries("token", "{token:.*}").HandlerFunc(httpTraceHdrs(web.DownloadZip))
// Add compression for assets. // Add compression for assets.