diff --git a/cmd/api-errors.go b/cmd/api-errors.go index dcfef101e..99f45505b 100644 --- a/cmd/api-errors.go +++ b/cmd/api-errors.go @@ -28,13 +28,13 @@ import ( "google.golang.org/api/googleapi" minio "github.com/minio/minio-go/v6" + "github.com/minio/minio-go/v6/pkg/tags" "github.com/minio/minio/cmd/config/etcd/dns" "github.com/minio/minio/cmd/crypto" "github.com/minio/minio/cmd/logger" "github.com/minio/minio/pkg/auth" "github.com/minio/minio/pkg/bucket/lifecycle" objectlock "github.com/minio/minio/pkg/bucket/object/lock" - "github.com/minio/minio/pkg/bucket/object/tagging" "github.com/minio/minio/pkg/bucket/policy" "github.com/minio/minio/pkg/event" "github.com/minio/minio/pkg/hash" @@ -157,6 +157,7 @@ const ( ErrInvalidRetentionDate ErrPastObjectLockRetainDate ErrUnknownWORMModeDirective + ErrBucketTaggingNotFound ErrObjectLockInvalidHeaders ErrInvalidTagDirective // Add new error codes here. @@ -777,6 +778,11 @@ var errorCodes = errorCodeMap{ Description: "Bucket is missing ObjectLockConfiguration", HTTPStatusCode: http.StatusBadRequest, }, + ErrBucketTaggingNotFound: { + Code: "NoSuchTagSet", + Description: "The TagSet does not exist", + HTTPStatusCode: http.StatusNotFound, + }, ErrObjectLockConfigurationNotFound: { Code: "ObjectLockConfigurationNotFoundError", Description: "Object Lock configuration does not exist for this bucket", @@ -1896,7 +1902,7 @@ func toAPIError(ctx context.Context, err error) APIError { Description: e.Error(), HTTPStatusCode: http.StatusBadRequest, } - case tagging.Error: + case tags.Error: apiErr = APIError{ Code: e.Code(), Description: e.Error(), diff --git a/cmd/api-router.go b/cmd/api-router.go index 395d05e92..971c2ead6 100644 --- a/cmd/api-router.go +++ b/cmd/api-router.go @@ -194,7 +194,7 @@ func registerAPIRouter(router *mux.Router, encryptionEnabled, allowSSEKMS bool) // GetBucketReplicationHandler - this is a dummy call. bucket.Methods(http.MethodGet).HandlerFunc( maxClients(collectAPIStats("getbucketreplication", httpTraceAll(api.GetBucketReplicationHandler)))).Queries("replication", "") - // GetBucketTaggingHandler - this is a dummy call. + // GetBucketTaggingHandler bucket.Methods(http.MethodGet).HandlerFunc( maxClients(collectAPIStats("getbuckettagging", httpTraceAll(api.GetBucketTaggingHandler)))).Queries("tagging", "") //DeleteBucketWebsiteHandler @@ -244,6 +244,9 @@ func registerAPIRouter(router *mux.Router, encryptionEnabled, allowSSEKMS bool) // PutBucketObjectLockConfig bucket.Methods(http.MethodPut).HandlerFunc( maxClients(collectAPIStats("putbucketobjectlockconfig", httpTraceAll(api.PutBucketObjectLockConfigHandler)))).Queries("object-lock", "") + // PutBucketTaggingHandler + bucket.Methods(http.MethodPut).HandlerFunc( + maxClients(collectAPIStats("putbuckettagging", httpTraceAll(api.PutBucketTaggingHandler)))).Queries("tagging", "") // PutBucketVersioning bucket.Methods(http.MethodPut).HandlerFunc( maxClients(collectAPIStats("putbucketversioning", httpTraceAll(api.PutBucketVersioningHandler)))).Queries("versioning", "") diff --git a/cmd/bucket-handlers.go b/cmd/bucket-handlers.go index 5284e5311..ce3659f64 100644 --- a/cmd/bucket-handlers.go +++ b/cmd/bucket-handlers.go @@ -30,6 +30,7 @@ import ( "github.com/gorilla/mux" "github.com/minio/minio-go/v6/pkg/set" + "github.com/minio/minio-go/v6/pkg/tags" "github.com/minio/minio/cmd/config/etcd/dns" "github.com/minio/minio/cmd/crypto" xhttp "github.com/minio/minio/cmd/http" @@ -46,6 +47,7 @@ import ( const ( getBucketVersioningResponse = `` objectLockConfig = "object-lock.xml" + bucketTaggingConfigFile = "tagging.xml" ) // Check if there are buckets on server without corresponding entry in etcd backend and @@ -1131,3 +1133,113 @@ func (api objectAPIHandlers) GetBucketObjectLockConfigHandler(w http.ResponseWri // Write success response. writeSuccessResponseXML(w, configData) } + +// 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 + } + 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 + } + data, err := xml.Marshal(tags) + if err != nil { + writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r)) + return + } + configFile := path.Join(bucketConfigPrefix, bucket, bucketTaggingConfigFile) + if err = saveConfig(ctx, objectAPI, configFile, data); err != nil { + 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 + } + // 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 + } + configFile := path.Join(bucketConfigPrefix, bucket, bucketTaggingConfigFile) + configData, err := readConfig(ctx, objectAPI, configFile) + if err != nil { + var aerr APIError + if err == errConfigNotFound { + aerr = errorCodes.ToAPIErr(ErrBucketTaggingNotFound) + } else { + aerr = toAPIError(ctx, err) + } + writeErrorResponse(ctx, w, aerr, r.URL, guessIsBrowserReq(r)) + 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 + } + + configFile := path.Join(bucketConfigPrefix, bucket, bucketTaggingConfigFile) + if err := deleteConfig(ctx, objectAPI, configFile); err != nil && err != errConfigNotFound { + writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r)) + return + } + + // Write success response. + writeSuccessResponseHeadersOnly(w) +} diff --git a/cmd/dummy-handlers.go b/cmd/dummy-handlers.go index e7fbd5697..c58529c99 100644 --- a/cmd/dummy-handlers.go +++ b/cmd/dummy-handlers.go @@ -21,7 +21,6 @@ import ( "net/http" "github.com/gorilla/mux" - "github.com/minio/minio/pkg/bucket/object/tagging" "github.com/minio/minio/pkg/bucket/policy" ) @@ -59,12 +58,6 @@ func (api objectAPIHandlers) GetBucketReplicationHandler(w http.ResponseWriter, w.(http.Flusher).Flush() } -// DeleteBucketTaggingHandler - DELETE bucket tagging, a dummy api -func (api objectAPIHandlers) DeleteBucketTaggingHandler(w http.ResponseWriter, r *http.Request) { - writeSuccessResponseHeadersOnly(w) - w.(http.Flusher).Flush() -} - // DeleteBucketWebsiteHandler - DELETE bucket website, a dummy api func (api objectAPIHandlers) DeleteBucketWebsiteHandler(w http.ResponseWriter, r *http.Request) { writeSuccessResponseHeadersOnly(w) @@ -130,41 +123,3 @@ func (api objectAPIHandlers) GetBucketCorsHandler(w http.ResponseWriter, r *http w.(http.Flusher).Flush() } - -// GetBucketTaggingHandler - GET bucket tagging, a dummy api -func (api objectAPIHandlers) GetBucketTaggingHandler(w http.ResponseWriter, r *http.Request) { - ctx := newContext(r, w, "GetBucketTagging") - - vars := mux.Vars(r) - bucket := vars["bucket"] - - objAPI := api.ObjectAPI() - if objAPI == nil { - writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrServerNotInitialized), r.URL, guessIsBrowserReq(r)) - return - } - - // Allow getBucketTagging if policy action is set, since this is a dummy call - // we are simply re-purposing the bucketPolicyAction. - if s3Error := checkRequestAuthType(ctx, r, policy.GetBucketPolicyAction, bucket, ""); s3Error != ErrNone { - writeErrorResponse(ctx, w, errorCodes.ToAPIErr(s3Error), r.URL, guessIsBrowserReq(r)) - return - } - - // Validate if bucket exists, before proceeding further... - _, err := objAPI.GetBucketInfo(ctx, bucket) - if err != nil { - writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r)) - return - } - - tags := &tagging.Tagging{} - tags.TagSet.Tags = append(tags.TagSet.Tags, tagging.Tag{}) - - if err := xml.NewEncoder(w).Encode(tags); err != nil { - writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r)) - return - } - - w.(http.Flusher).Flush() -} diff --git a/cmd/fs-v1.go b/cmd/fs-v1.go index fc415ffcf..9c8c124cb 100644 --- a/cmd/fs-v1.go +++ b/cmd/fs-v1.go @@ -35,12 +35,12 @@ import ( jsoniter "github.com/json-iterator/go" "github.com/minio/minio-go/v6/pkg/s3utils" + "github.com/minio/minio-go/v6/pkg/tags" "github.com/minio/minio/cmd/config" xhttp "github.com/minio/minio/cmd/http" "github.com/minio/minio/cmd/logger" bucketsse "github.com/minio/minio/pkg/bucket/encryption" "github.com/minio/minio/pkg/bucket/lifecycle" - "github.com/minio/minio/pkg/bucket/object/tagging" "github.com/minio/minio/pkg/bucket/policy" "github.com/minio/minio/pkg/color" "github.com/minio/minio/pkg/lock" @@ -1219,18 +1219,13 @@ func (fs *FSObjects) ListObjects(ctx context.Context, bucket, prefix, marker, de } // GetObjectTag - get object tags from an existing object -func (fs *FSObjects) GetObjectTag(ctx context.Context, bucket, object string) (tagging.Tagging, error) { +func (fs *FSObjects) GetObjectTag(ctx context.Context, bucket, object string) (*tags.Tags, error) { oi, err := fs.GetObjectInfo(ctx, bucket, object, ObjectOptions{}) if err != nil { - return tagging.Tagging{}, err - } - - tags, err := tagging.FromString(oi.UserTags) - if err != nil { - return tagging.Tagging{}, err + return nil, err } - return tags, nil + return tags.ParseObjectTags(oi.UserTags) } // PutObjectTag - replace or add tags to an existing object diff --git a/cmd/gateway-unsupported.go b/cmd/gateway-unsupported.go index 0f004d70f..a90f5f511 100644 --- a/cmd/gateway-unsupported.go +++ b/cmd/gateway-unsupported.go @@ -22,9 +22,9 @@ import ( "github.com/minio/minio/cmd/logger" + "github.com/minio/minio-go/v6/pkg/tags" bucketsse "github.com/minio/minio/pkg/bucket/encryption" "github.com/minio/minio/pkg/bucket/lifecycle" - "github.com/minio/minio/pkg/bucket/object/tagging" "github.com/minio/minio/pkg/bucket/policy" "github.com/minio/minio/pkg/madmin" @@ -205,9 +205,9 @@ func (a GatewayUnsupported) PutObjectTag(ctx context.Context, bucket, object str } // GetObjectTag - not implemented. -func (a GatewayUnsupported) GetObjectTag(ctx context.Context, bucket, object string) (tagging.Tagging, error) { +func (a GatewayUnsupported) GetObjectTag(ctx context.Context, bucket, object string) (*tags.Tags, error) { logger.LogIf(ctx, NotImplemented{}) - return tagging.Tagging{}, NotImplemented{} + return nil, NotImplemented{} } // DeleteObjectTag - not implemented. diff --git a/cmd/generic-handlers.go b/cmd/generic-handlers.go index d45f1d6ae..32ea7c9f0 100644 --- a/cmd/generic-handlers.go +++ b/cmd/generic-handlers.go @@ -491,7 +491,6 @@ var notImplementedBucketResourceNames = map[string]bool{ "metrics": true, "replication": true, "requestPayment": true, - "tagging": true, "versioning": true, "website": true, } diff --git a/cmd/handler-utils.go b/cmd/handler-utils.go index 099e69a48..660926ebb 100644 --- a/cmd/handler-utils.go +++ b/cmd/handler-utils.go @@ -33,7 +33,6 @@ import ( xhttp "github.com/minio/minio/cmd/http" "github.com/minio/minio/cmd/logger" "github.com/minio/minio/pkg/auth" - "github.com/minio/minio/pkg/bucket/object/tagging" "github.com/minio/minio/pkg/handlers" "github.com/minio/minio/pkg/madmin" ) @@ -161,26 +160,6 @@ func extractMetadataFromMap(ctx context.Context, v map[string][]string, m map[st return nil } -// extractTags extracts tag key and value from given http header. It then -// - Parses the input format X-Amz-Tagging:"Key1=Value1&Key2=Value2" into a map[string]string -// with entries in the format X-Amg-Tag-Key1:Value1, X-Amz-Tag-Key2:Value2 -// - Validates the tags -// - Returns the Tag in original string format "Key1=Value1&Key2=Value2" -func extractTags(ctx context.Context, tags string) (string, error) { - // Check if the metadata has tagging related header - if tags != "" { - tagging, err := tagging.FromString(tags) - if err != nil { - return "", err - } - if err := tagging.Validate(); err != nil { - return "", err - } - return tagging.String(), nil - } - return "", nil -} - // The Query string for the redirect URL the client is // redirected on successful upload. func getRedirectPostRawQuery(objInfo ObjectInfo) string { diff --git a/cmd/object-api-interface.go b/cmd/object-api-interface.go index a3a0111d6..a8bb6fbda 100644 --- a/cmd/object-api-interface.go +++ b/cmd/object-api-interface.go @@ -22,9 +22,9 @@ import ( "net/http" "github.com/minio/minio-go/v6/pkg/encrypt" + "github.com/minio/minio-go/v6/pkg/tags" bucketsse "github.com/minio/minio/pkg/bucket/encryption" "github.com/minio/minio/pkg/bucket/lifecycle" - "github.com/minio/minio/pkg/bucket/object/tagging" "github.com/minio/minio/pkg/bucket/policy" "github.com/minio/minio/pkg/madmin" @@ -138,6 +138,6 @@ type ObjectLayer interface { // ObjectTagging operations PutObjectTag(context.Context, string, string, string) error - GetObjectTag(context.Context, string, string) (tagging.Tagging, error) + GetObjectTag(context.Context, string, string) (*tags.Tags, error) DeleteObjectTag(context.Context, string, string) error } diff --git a/cmd/object-handlers.go b/cmd/object-handlers.go index cb2cb6c4f..a50f443f2 100644 --- a/cmd/object-handlers.go +++ b/cmd/object-handlers.go @@ -34,13 +34,13 @@ import ( "github.com/gorilla/mux" miniogo "github.com/minio/minio-go/v6" "github.com/minio/minio-go/v6/pkg/encrypt" + "github.com/minio/minio-go/v6/pkg/tags" "github.com/minio/minio/cmd/config/etcd/dns" "github.com/minio/minio/cmd/config/storageclass" "github.com/minio/minio/cmd/crypto" xhttp "github.com/minio/minio/cmd/http" "github.com/minio/minio/cmd/logger" objectlock "github.com/minio/minio/pkg/bucket/object/lock" - "github.com/minio/minio/pkg/bucket/object/tagging" "github.com/minio/minio/pkg/bucket/policy" "github.com/minio/minio/pkg/event" "github.com/minio/minio/pkg/handlers" @@ -647,24 +647,6 @@ func getCpObjMetadataFromHeader(ctx context.Context, r *http.Request, userMeta m return defaultMeta, nil } -// Extract tags relevant for an CopyObject operation based on conditional -// header values specified in X-Amz-Tagging-Directive. -func getCpObjTagsFromHeader(ctx context.Context, r *http.Request, tags string) (string, error) { - // if x-amz-tagging-directive says REPLACE then - // we extract tags from the input headers. - if isDirectiveReplace(r.Header.Get(xhttp.AmzTagDirective)) { - if tags := r.Header.Get(xhttp.AmzObjectTagging); tags != "" { - return extractTags(ctx, tags) - } - // If x-amz-tagging-directive is explicitly set to replace and x-amz-tagging is not set. - // The S3 behavior is to unset the tags. - return "", nil - } - - // Copy is default behavior if x-amz-tagging-directive is not set. - return tags, nil -} - // getRemoteInstanceTransport contains a singleton roundtripper. var getRemoteInstanceTransport http.RoundTripper var getRemoteInstanceTransportOnce sync.Once @@ -1056,14 +1038,18 @@ func (api objectAPIHandlers) CopyObjectHandler(w http.ResponseWriter, r *http.Re return } - tags, err := getCpObjTagsFromHeader(ctx, r, srcInfo.UserTags) - if err != nil { - writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r)) - return + objTags := srcInfo.UserTags + // If x-amz-tagging-directive header is REPLACE, get passed tags. + if isDirectiveReplace(r.Header.Get(xhttp.AmzTagDirective)) { + objTags = r.Header.Get(xhttp.AmzObjectTagging) + if _, err := tags.ParseObjectTags(objTags); err != nil { + writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r)) + return + } } - if tags != "" { - srcInfo.UserDefined[xhttp.AmzObjectTagging] = tags + if objTags != "" { + srcInfo.UserDefined[xhttp.AmzObjectTagging] = objTags } srcInfo.UserDefined = objectlock.FilterObjectLockMetadata(srcInfo.UserDefined, true, true) @@ -1265,12 +1251,13 @@ func (api objectAPIHandlers) PutObjectHandler(w http.ResponseWriter, r *http.Req return } - if tags := r.Header.Get(xhttp.AmzObjectTagging); tags != "" { - metadata[xhttp.AmzObjectTagging], err = extractTags(ctx, tags) - if err != nil { + if objTags := r.Header.Get(xhttp.AmzObjectTagging); objTags != "" { + if _, err := tags.ParseObjectTags(objTags); err != nil { writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r)) return } + + metadata[xhttp.AmzObjectTagging] = objTags } if rAuthType == authTypeStreamingSigned { @@ -2994,14 +2981,14 @@ func (api objectAPIHandlers) PutObjectTaggingHandler(w http.ResponseWriter, r *h return } - tagging, err := tagging.ParseTagging(io.LimitReader(r.Body, r.ContentLength)) + tags, err := tags.ParseObjectXML(io.LimitReader(r.Body, r.ContentLength)) if err != nil { writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r)) return } // Put object tags - err = objAPI.PutObjectTag(ctx, bucket, object, tagging.String()) + err = objAPI.PutObjectTag(ctx, bucket, object, tags.String()) if err != nil { writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r)) return @@ -3036,8 +3023,7 @@ func (api objectAPIHandlers) DeleteObjectTaggingHandler(w http.ResponseWriter, r } // Delete object tags - err = objAPI.DeleteObjectTag(ctx, bucket, object) - if err != nil { + if err = objAPI.DeleteObjectTag(ctx, bucket, object); err != nil && err != errConfigNotFound { writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r)) return } diff --git a/cmd/xl-sets.go b/cmd/xl-sets.go index 4e0a5b26d..dd57a1460 100644 --- a/cmd/xl-sets.go +++ b/cmd/xl-sets.go @@ -26,13 +26,13 @@ import ( "sync" "time" + "github.com/minio/minio-go/v6/pkg/tags" "github.com/minio/minio/cmd/config/storageclass" xhttp "github.com/minio/minio/cmd/http" "github.com/minio/minio/cmd/logger" "github.com/minio/minio/pkg/bpool" bucketsse "github.com/minio/minio/pkg/bucket/encryption" "github.com/minio/minio/pkg/bucket/lifecycle" - "github.com/minio/minio/pkg/bucket/object/tagging" "github.com/minio/minio/pkg/bucket/policy" "github.com/minio/minio/pkg/dsync" "github.com/minio/minio/pkg/madmin" @@ -1785,7 +1785,7 @@ func (s *xlSets) DeleteObjectTag(ctx context.Context, bucket, object string) err } // GetObjectTag - get object tags from an existing object -func (s *xlSets) GetObjectTag(ctx context.Context, bucket, object string) (tagging.Tagging, error) { +func (s *xlSets) GetObjectTag(ctx context.Context, bucket, object string) (*tags.Tags, error) { return s.getHashedSet(object).GetObjectTag(ctx, bucket, object) } diff --git a/cmd/xl-v1-object.go b/cmd/xl-v1-object.go index 98627e936..61cadc0fe 100644 --- a/cmd/xl-v1-object.go +++ b/cmd/xl-v1-object.go @@ -25,9 +25,9 @@ import ( "path" "sync" + "github.com/minio/minio-go/v6/pkg/tags" xhttp "github.com/minio/minio/cmd/http" "github.com/minio/minio/cmd/logger" - "github.com/minio/minio/pkg/bucket/object/tagging" "github.com/minio/minio/pkg/mimedb" "github.com/minio/minio/pkg/sync/errgroup" ) @@ -1018,7 +1018,7 @@ func (xl xlObjects) PutObjectTag(ctx context.Context, bucket, object string, tag _, writeQuorum, err := objectQuorumFromMeta(ctx, xl, metaArr, errs) if err != nil { - return err + return toObjectErr(err, bucket, object) } for i, xlMeta := range metaArr { @@ -1052,16 +1052,12 @@ func (xl xlObjects) DeleteObjectTag(ctx context.Context, bucket, object string) } // GetObjectTag - get object tags from an existing object -func (xl xlObjects) GetObjectTag(ctx context.Context, bucket, object string) (tagging.Tagging, error) { +func (xl xlObjects) GetObjectTag(ctx context.Context, bucket, object string) (*tags.Tags, error) { // GetObjectInfo will return tag value as well oi, err := xl.GetObjectInfo(ctx, bucket, object, ObjectOptions{}) if err != nil { - return tagging.Tagging{}, err + return nil, err } - tags, err := tagging.FromString(oi.UserTags) - if err != nil { - return tagging.Tagging{}, err - } - return tags, nil + return tags.ParseObjectTags(oi.UserTags) } diff --git a/cmd/xl-zones.go b/cmd/xl-zones.go index e896df128..559ac4ec2 100644 --- a/cmd/xl-zones.go +++ b/cmd/xl-zones.go @@ -26,11 +26,11 @@ import ( "sync" "time" + "github.com/minio/minio-go/v6/pkg/tags" xhttp "github.com/minio/minio/cmd/http" "github.com/minio/minio/cmd/logger" bucketsse "github.com/minio/minio/pkg/bucket/encryption" "github.com/minio/minio/pkg/bucket/lifecycle" - "github.com/minio/minio/pkg/bucket/object/tagging" "github.com/minio/minio/pkg/bucket/policy" "github.com/minio/minio/pkg/madmin" "github.com/minio/minio/pkg/sync/errgroup" @@ -1588,7 +1588,7 @@ func (z *xlZones) DeleteObjectTag(ctx context.Context, bucket, object string) er } // GetObjectTag - get object tags from an existing object -func (z *xlZones) GetObjectTag(ctx context.Context, bucket, object string) (tagging.Tagging, error) { +func (z *xlZones) GetObjectTag(ctx context.Context, bucket, object string) (*tags.Tags, error) { if z.SingleZone() { return z.zones[0].GetObjectTag(ctx, bucket, object) } @@ -1602,7 +1602,7 @@ func (z *xlZones) GetObjectTag(ctx context.Context, bucket, object string) (tagg } return tags, nil } - return tagging.Tagging{}, BucketNotFound{ + return nil, BucketNotFound{ Bucket: bucket, } } diff --git a/go.mod b/go.mod index da961ac96..65cbd8986 100644 --- a/go.mod +++ b/go.mod @@ -67,7 +67,7 @@ require ( github.com/minio/hdfs/v3 v3.0.1 github.com/minio/highwayhash v1.0.0 github.com/minio/lsync v1.0.1 - github.com/minio/minio-go/v6 v6.0.55-0.20200424204115-7506d2996b22 + github.com/minio/minio-go/v6 v6.0.55-0.20200425081427-89eebdef2af0 github.com/minio/parquet-go v0.0.0-20200414234858-838cfa8aae61 github.com/minio/sha256-simd v0.1.1 github.com/minio/simdjson-go v0.1.5-0.20200303142138-b17fe061ea37 diff --git a/pkg/bucket/lifecycle/and.go b/pkg/bucket/lifecycle/and.go index 508cc6970..977a92745 100644 --- a/pkg/bucket/lifecycle/and.go +++ b/pkg/bucket/lifecycle/and.go @@ -19,14 +19,14 @@ package lifecycle import ( "encoding/xml" - "github.com/minio/minio/pkg/bucket/object/tagging" + "github.com/minio/minio-go/v6/pkg/tags" ) // And - a tag to combine a prefix and multiple tags for lifecycle configuration rule. type And struct { - XMLName xml.Name `xml:"And"` - Prefix string `xml:"Prefix,omitempty"` - Tags []tagging.Tag `xml:"Tag,omitempty"` + XMLName xml.Name `xml:"And"` + Prefix string `xml:"Prefix,omitempty"` + Tags []tags.Tag `xml:"Tag,omitempty"` } var errDuplicateTagKey = Errorf("Duplicate Tag Keys are not allowed") diff --git a/pkg/bucket/lifecycle/filter.go b/pkg/bucket/lifecycle/filter.go index 7692e9d36..563abac92 100644 --- a/pkg/bucket/lifecycle/filter.go +++ b/pkg/bucket/lifecycle/filter.go @@ -19,7 +19,7 @@ package lifecycle import ( "encoding/xml" - "github.com/minio/minio/pkg/bucket/object/tagging" + "github.com/minio/minio-go/v6/pkg/tags" ) var ( @@ -31,7 +31,7 @@ type Filter struct { XMLName xml.Name `xml:"Filter"` Prefix string And And - Tag tagging.Tag + Tag tags.Tag } // MarshalXML - produces the xml representation of the Filter struct @@ -89,5 +89,5 @@ func (f Filter) Validate() error { // isEmpty - returns true if Filter tag is empty func (f Filter) isEmpty() bool { - return f.And.isEmpty() && f.Prefix == "" && f.Tag == tagging.Tag{} + return f.And.isEmpty() && f.Prefix == "" && f.Tag.IsEmpty() } diff --git a/pkg/bucket/object/tagging/error.go b/pkg/bucket/object/tagging/error.go deleted file mode 100644 index 89c4fab81..000000000 --- a/pkg/bucket/object/tagging/error.go +++ /dev/null @@ -1,53 +0,0 @@ -/* - * MinIO Cloud Storage, (C) 2020 MinIO, Inc. - * - * 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. - */ - -package tagging - -import ( - "fmt" -) - -// Error is the generic type for any error happening during tag -// parsing. -type Error struct { - err error - code string -} - -// Errorf - formats according to a format specifier and returns -// the string as a value that satisfies error of type tagging.Error -func Errorf(format, code string, a ...interface{}) error { - return Error{err: fmt.Errorf(format, a...), code: code} -} - -// Unwrap the internal error. -func (e Error) Unwrap() error { return e.err } - -// Error 'error' compatible method. -func (e Error) Error() string { - if e.err == nil { - return "tagging: cause " - } - return e.err.Error() -} - -// Code returns appropriate error code. -func (e Error) Code() string { - if e.code == "" { - return "BadRequest" - } - return e.code -} diff --git a/pkg/bucket/object/tagging/tag.go b/pkg/bucket/object/tagging/tag.go deleted file mode 100644 index 2f2faa726..000000000 --- a/pkg/bucket/object/tagging/tag.go +++ /dev/null @@ -1,82 +0,0 @@ -/* - * MinIO Cloud Storage, (C) 2020 MinIO, Inc. - * - * 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. - */ - -package tagging - -import ( - "encoding/xml" - "strings" - "unicode/utf8" -) - -// Tag - single tag -type Tag struct { - XMLName xml.Name `xml:"Tag"` - Key string `xml:"Key,omitempty"` - Value string `xml:"Value,omitempty"` -} - -// Validate - validates the tag element -func (t Tag) Validate() error { - if err := t.validateKey(); err != nil { - return err - } - if err := t.validateValue(); err != nil { - return err - } - return nil -} - -// validateKey - checks if key is valid or not. -func (t Tag) validateKey() error { - // cannot be longer than maxTagKeyLength characters - if utf8.RuneCountInString(t.Key) > maxTagKeyLength { - return ErrInvalidTagKey - } - // cannot be empty - if len(t.Key) == 0 { - return ErrInvalidTagKey - } - // Tag key shouldn't have "&" - if strings.Contains(t.Key, "&") { - return ErrInvalidTagKey - } - return nil -} - -// validateValue - checks if value is valid or not. -func (t Tag) validateValue() error { - // cannot be longer than maxTagValueLength characters - if utf8.RuneCountInString(t.Value) > maxTagValueLength { - return ErrInvalidTagValue - } - // Tag value shouldn't have "&" - if strings.Contains(t.Value, "&") { - return ErrInvalidTagValue - } - return nil -} - -// IsEmpty - checks if tag is empty or not -func (t Tag) IsEmpty() bool { - return t.Key == "" && t.Value == "" -} - -// String - returns a string in format "tag1=value1" for the -// current Tag -func (t Tag) String() string { - return t.Key + "=" + t.Value -} diff --git a/pkg/bucket/object/tagging/tagging.go b/pkg/bucket/object/tagging/tagging.go deleted file mode 100644 index 2b9b69560..000000000 --- a/pkg/bucket/object/tagging/tagging.go +++ /dev/null @@ -1,112 +0,0 @@ -/* - * MinIO Cloud Storage, (C) 2020 MinIO, Inc. - * - * 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. - */ - -package tagging - -import ( - "bytes" - "encoding/xml" - "io" - "net/url" -) - -// S3 API limits for tags -// Ref: https://docs.aws.amazon.com/AmazonS3/latest/dev/object-tagging.html -const ( - maxTags = 10 - maxTagKeyLength = 128 - maxTagValueLength = 256 -) - -// errors returned by tagging package -var ( - ErrTooManyTags = Errorf("Object tags cannot be greater than 10", "BadRequest") - ErrInvalidTagKey = Errorf("The TagKey you have provided is invalid", "InvalidTag") - ErrInvalidTagValue = Errorf("The TagValue you have provided is invalid", "InvalidTag") - ErrInvalidTag = Errorf("Cannot provide multiple Tags with the same key", "InvalidTag") -) - -// Tagging - object tagging interface -type Tagging struct { - XMLName xml.Name `xml:"Tagging"` - TagSet TagSet `xml:"TagSet"` -} - -// Validate - validates the tagging configuration -func (t Tagging) Validate() error { - // Tagging can't have more than 10 tags - if len(t.TagSet.Tags) > maxTags { - return ErrTooManyTags - } - // Validate all the rules in the tagging config - for _, ts := range t.TagSet.Tags { - if err := ts.Validate(); err != nil { - return err - } - } - if t.TagSet.ContainsDuplicateTag() { - return ErrInvalidTag - } - return nil -} - -// String - returns a string in format "tag1=value1&tag2=value2" with all the -// tags in this Tagging Struct -func (t Tagging) String() string { - var buf bytes.Buffer - for _, tag := range t.TagSet.Tags { - if buf.Len() > 0 { - buf.WriteString("&") - } - buf.WriteString(tag.String()) - } - return buf.String() -} - -// FromString - returns a Tagging struct when given a string in format -// "tag1=value1&tag2=value2" -func FromString(tagStr string) (Tagging, error) { - tags, err := url.ParseQuery(tagStr) - if err != nil { - return Tagging{}, err - } - var idx = 0 - parsedTags := make([]Tag, len(tags)) - for k := range tags { - parsedTags[idx].Key = k - parsedTags[idx].Value = tags.Get(k) - idx++ - } - return Tagging{ - TagSet: TagSet{ - Tags: parsedTags, - }, - }, nil -} - -// ParseTagging - parses incoming xml data in given reader -// into Tagging interface. After parsing, also validates the -// parsed fields based on S3 API constraints. -func ParseTagging(reader io.Reader) (*Tagging, error) { - var t Tagging - if err := xml.NewDecoder(reader).Decode(&t); err != nil { - return nil, err - } - if err := t.Validate(); err != nil { - return nil, err - } - return &t, nil -} diff --git a/pkg/bucket/object/tagging/tagset.go b/pkg/bucket/object/tagging/tagset.go deleted file mode 100644 index b4997db95..000000000 --- a/pkg/bucket/object/tagging/tagset.go +++ /dev/null @@ -1,41 +0,0 @@ -/* - * MinIO Cloud Storage, (C) 2020 MinIO, Inc. - * - * 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. - */ - -package tagging - -import ( - "encoding/xml" -) - -// TagSet - Set of tags under Tagging -type TagSet struct { - XMLName xml.Name `xml:"TagSet"` - Tags []Tag `xml:"Tag"` -} - -// ContainsDuplicateTag - returns true if duplicate keys are present in TagSet -func (t TagSet) ContainsDuplicateTag() bool { - x := make(map[string]struct{}, len(t.Tags)) - - for _, t := range t.Tags { - if _, has := x[t.Key]; has { - return true - } - x[t.Key] = struct{}{} - } - - return false -} diff --git a/pkg/bucket/policy/action.go b/pkg/bucket/policy/action.go index c52568b35..0e0a3a55e 100644 --- a/pkg/bucket/policy/action.go +++ b/pkg/bucket/policy/action.go @@ -109,6 +109,11 @@ const ( // PutBucketObjectLockConfigurationAction - PutObjectLockConfiguration Rest API action PutBucketObjectLockConfigurationAction = "s3:PutBucketObjectLockConfiguration" + // GetBucketTaggingAction - GetTagging Rest API action + GetBucketTaggingAction = "s3:GetBucketTagging" + // PutBucketTaggingAction - PutTagging Rest API action + PutBucketTaggingAction = "s3:PutBucketTagging" + // GetObjectTaggingAction - Get Object Tags API action GetObjectTaggingAction = "s3:GetObjectTagging" // PutObjectTaggingAction - Put Object Tags API action @@ -174,6 +179,8 @@ var supportedActions = map[Action]struct{}{ PutObjectLegalHoldAction: {}, PutBucketObjectLockConfigurationAction: {}, GetBucketObjectLockConfigurationAction: {}, + PutBucketTaggingAction: {}, + GetBucketTaggingAction: {}, BypassGovernanceRetentionAction: {}, GetObjectTaggingAction: {}, PutObjectTaggingAction: {}, @@ -296,6 +303,8 @@ var actionConditionKeyMap = map[Action]condition.KeySet{ GetBucketObjectLockConfigurationAction: condition.NewKeySet(condition.CommonKeys...), PutBucketObjectLockConfigurationAction: condition.NewKeySet(condition.CommonKeys...), + GetBucketTaggingAction: condition.NewKeySet(condition.CommonKeys...), + PutBucketTaggingAction: condition.NewKeySet(condition.CommonKeys...), PutObjectTaggingAction: condition.NewKeySet(condition.CommonKeys...), GetObjectTaggingAction: condition.NewKeySet(condition.CommonKeys...), DeleteObjectTaggingAction: condition.NewKeySet(condition.CommonKeys...), diff --git a/pkg/iam/policy/action.go b/pkg/iam/policy/action.go index 35d133472..cda186c73 100644 --- a/pkg/iam/policy/action.go +++ b/pkg/iam/policy/action.go @@ -115,6 +115,12 @@ const ( // PutBucketObjectLockConfigurationAction - PutBucketObjectLockConfiguration Rest API action PutBucketObjectLockConfigurationAction = "s3:PutBucketObjectLockConfiguration" + // GetBucketTaggingAction - GetBucketTagging Rest API action + GetBucketTaggingAction = "s3:GetBucketTagging" + + // PutBucketTaggingAction - PutBucketTagging Rest API action + PutBucketTaggingAction = "s3:PutBucketTagging" + // GetObjectTaggingAction - Get Object Tags API action GetObjectTaggingAction = "s3:GetObjectTagging" @@ -164,6 +170,8 @@ var supportedActions = map[Action]struct{}{ PutObjectLegalHoldAction: {}, PutBucketObjectLockConfigurationAction: {}, GetBucketObjectLockConfigurationAction: {}, + PutBucketTaggingAction: {}, + GetBucketTaggingAction: {}, BypassGovernanceRetentionAction: {}, GetObjectTaggingAction: {}, PutObjectTaggingAction: {}, @@ -333,6 +341,8 @@ var actionConditionKeyMap = map[Action]condition.KeySet{ GetBucketObjectLockConfigurationAction: condition.NewKeySet(condition.CommonKeys...), PutBucketObjectLockConfigurationAction: condition.NewKeySet(condition.CommonKeys...), + GetBucketTaggingAction: condition.NewKeySet(condition.CommonKeys...), + PutBucketTaggingAction: condition.NewKeySet(condition.CommonKeys...), PutObjectTaggingAction: condition.NewKeySet(condition.CommonKeys...), GetObjectTaggingAction: condition.NewKeySet(condition.CommonKeys...), DeleteObjectTaggingAction: condition.NewKeySet(condition.CommonKeys...),