mirror of
https://github.com/versity/versitygw.git
synced 2026-08-16 04:06:16 +00:00
feat: Added object WORM protection by object-lock feature from AWS with the following actions support: PutObjectLockConfiguration, GetObjectLockConfiguration, PutObjectRetention, GetObjectRetention, PutObjectLegalHold, GetObjectLegalHold
This commit is contained in:
committed by
Ben McClelland
parent
c0489f981c
commit
fbaba0b944
@@ -131,7 +131,6 @@ func verifyBucketPolicy(policy []byte, access, bucket, object string, action Act
|
||||
resource += "/" + object
|
||||
}
|
||||
|
||||
fmt.Println(access, action, resource)
|
||||
if !bucketPolicy.isAllowed(access, action, resource) {
|
||||
return s3err.GetAPIError(s3err.ErrAccessDenied)
|
||||
}
|
||||
|
||||
@@ -23,79 +23,97 @@ import (
|
||||
type Action string
|
||||
|
||||
const (
|
||||
GetBucketAclAction Action = "s3:GetBucketAcl"
|
||||
CreateBucketAction Action = "s3:CreateBucket"
|
||||
PutBucketAclAction Action = "s3:PutBucketAcl"
|
||||
DeleteBucketAction Action = "s3:DeleteBucket"
|
||||
PutBucketVersioningAction Action = "s3:PutBucketVersioning"
|
||||
GetBucketVersioningAction Action = "s3:GetBucketVersioning"
|
||||
PutBucketPolicyAction Action = "s3:PutBucketPolicy"
|
||||
GetBucketPolicyAction Action = "s3:GetBucketPolicy"
|
||||
DeleteBucketPolicyAction Action = "s3:DeleteBucketPolicy"
|
||||
AbortMultipartUploadAction Action = "s3:AbortMultipartUpload"
|
||||
ListMultipartUploadPartsAction Action = "s3:ListMultipartUploadParts"
|
||||
ListBucketMultipartUploadsAction Action = "s3:ListBucketMultipartUploads"
|
||||
PutObjectAction Action = "s3:PutObject"
|
||||
GetObjectAction Action = "s3:GetObject"
|
||||
DeleteObjectAction Action = "s3:DeleteObject"
|
||||
GetObjectAclAction Action = "s3:GetObjectAcl"
|
||||
GetObjectAttributesAction Action = "s3:GetObjectAttributes"
|
||||
PutObjectAclAction Action = "s3:PutObjectAcl"
|
||||
RestoreObjectAction Action = "s3:RestoreObject"
|
||||
GetBucketTaggingAction Action = "s3:GetBucketTagging"
|
||||
PutBucketTaggingAction Action = "s3:PutBucketTagging"
|
||||
GetObjectTaggingAction Action = "s3:GetObjectTagging"
|
||||
PutObjectTaggingAction Action = "s3:PutObjectTagging"
|
||||
DeleteObjectTaggingAction Action = "s3:DeleteObjectTagging"
|
||||
ListBucketVersionsAction Action = "s3:ListBucketVersions"
|
||||
ListBucketAction Action = "s3:ListBucket"
|
||||
AllActions Action = "s3:*"
|
||||
GetBucketAclAction Action = "s3:GetBucketAcl"
|
||||
CreateBucketAction Action = "s3:CreateBucket"
|
||||
PutBucketAclAction Action = "s3:PutBucketAcl"
|
||||
DeleteBucketAction Action = "s3:DeleteBucket"
|
||||
PutBucketVersioningAction Action = "s3:PutBucketVersioning"
|
||||
GetBucketVersioningAction Action = "s3:GetBucketVersioning"
|
||||
PutBucketPolicyAction Action = "s3:PutBucketPolicy"
|
||||
GetBucketPolicyAction Action = "s3:GetBucketPolicy"
|
||||
DeleteBucketPolicyAction Action = "s3:DeleteBucketPolicy"
|
||||
AbortMultipartUploadAction Action = "s3:AbortMultipartUpload"
|
||||
ListMultipartUploadPartsAction Action = "s3:ListMultipartUploadParts"
|
||||
ListBucketMultipartUploadsAction Action = "s3:ListBucketMultipartUploads"
|
||||
PutObjectAction Action = "s3:PutObject"
|
||||
GetObjectAction Action = "s3:GetObject"
|
||||
DeleteObjectAction Action = "s3:DeleteObject"
|
||||
GetObjectAclAction Action = "s3:GetObjectAcl"
|
||||
GetObjectAttributesAction Action = "s3:GetObjectAttributes"
|
||||
PutObjectAclAction Action = "s3:PutObjectAcl"
|
||||
RestoreObjectAction Action = "s3:RestoreObject"
|
||||
GetBucketTaggingAction Action = "s3:GetBucketTagging"
|
||||
PutBucketTaggingAction Action = "s3:PutBucketTagging"
|
||||
GetObjectTaggingAction Action = "s3:GetObjectTagging"
|
||||
PutObjectTaggingAction Action = "s3:PutObjectTagging"
|
||||
DeleteObjectTaggingAction Action = "s3:DeleteObjectTagging"
|
||||
ListBucketVersionsAction Action = "s3:ListBucketVersions"
|
||||
ListBucketAction Action = "s3:ListBucket"
|
||||
GetBucketObjectLockConfigurationAction Action = "s3:GetBucketObjectLockConfiguration"
|
||||
PutBucketObjectLockConfigurationAction Action = "s3:PutBucketObjectLockConfiguration"
|
||||
GetObjectLegalHoldAction Action = "s3:GetObjectLegalHold"
|
||||
PutObjectLegalHoldAction Action = "s3:PutObjectLegalHold"
|
||||
GetObjectRetentionAction Action = "s3:GetObjectRetention"
|
||||
PutObjectRetentionAction Action = "s3:PutObjectRetention"
|
||||
BypassGovernanceRetentionAction Action = "s3:BypassGovernanceRetention"
|
||||
AllActions Action = "s3:*"
|
||||
)
|
||||
|
||||
var supportedActionList = map[Action]struct{}{
|
||||
GetBucketAclAction: {},
|
||||
CreateBucketAction: {},
|
||||
PutBucketAclAction: {},
|
||||
DeleteBucketAction: {},
|
||||
PutBucketVersioningAction: {},
|
||||
GetBucketVersioningAction: {},
|
||||
PutBucketPolicyAction: {},
|
||||
GetBucketPolicyAction: {},
|
||||
DeleteBucketPolicyAction: {},
|
||||
AbortMultipartUploadAction: {},
|
||||
ListMultipartUploadPartsAction: {},
|
||||
ListBucketMultipartUploadsAction: {},
|
||||
PutObjectAction: {},
|
||||
GetObjectAction: {},
|
||||
DeleteObjectAction: {},
|
||||
GetObjectAclAction: {},
|
||||
GetObjectAttributesAction: {},
|
||||
PutObjectAclAction: {},
|
||||
RestoreObjectAction: {},
|
||||
GetBucketTaggingAction: {},
|
||||
PutBucketTaggingAction: {},
|
||||
GetObjectTaggingAction: {},
|
||||
PutObjectTaggingAction: {},
|
||||
DeleteObjectTaggingAction: {},
|
||||
ListBucketVersionsAction: {},
|
||||
ListBucketAction: {},
|
||||
AllActions: {},
|
||||
GetBucketAclAction: {},
|
||||
CreateBucketAction: {},
|
||||
PutBucketAclAction: {},
|
||||
DeleteBucketAction: {},
|
||||
PutBucketVersioningAction: {},
|
||||
GetBucketVersioningAction: {},
|
||||
PutBucketPolicyAction: {},
|
||||
GetBucketPolicyAction: {},
|
||||
DeleteBucketPolicyAction: {},
|
||||
AbortMultipartUploadAction: {},
|
||||
ListMultipartUploadPartsAction: {},
|
||||
ListBucketMultipartUploadsAction: {},
|
||||
PutObjectAction: {},
|
||||
GetObjectAction: {},
|
||||
DeleteObjectAction: {},
|
||||
GetObjectAclAction: {},
|
||||
GetObjectAttributesAction: {},
|
||||
PutObjectAclAction: {},
|
||||
RestoreObjectAction: {},
|
||||
GetBucketTaggingAction: {},
|
||||
PutBucketTaggingAction: {},
|
||||
GetObjectTaggingAction: {},
|
||||
PutObjectTaggingAction: {},
|
||||
DeleteObjectTaggingAction: {},
|
||||
ListBucketVersionsAction: {},
|
||||
ListBucketAction: {},
|
||||
PutBucketObjectLockConfigurationAction: {},
|
||||
GetObjectLegalHoldAction: {},
|
||||
PutObjectLegalHoldAction: {},
|
||||
GetObjectRetentionAction: {},
|
||||
PutObjectRetentionAction: {},
|
||||
BypassGovernanceRetentionAction: {},
|
||||
AllActions: {},
|
||||
}
|
||||
|
||||
var supportedObjectActionList = map[Action]struct{}{
|
||||
AbortMultipartUploadAction: {},
|
||||
ListMultipartUploadPartsAction: {},
|
||||
PutObjectAction: {},
|
||||
GetObjectAction: {},
|
||||
DeleteObjectAction: {},
|
||||
GetObjectAclAction: {},
|
||||
GetObjectAttributesAction: {},
|
||||
PutObjectAclAction: {},
|
||||
RestoreObjectAction: {},
|
||||
GetObjectTaggingAction: {},
|
||||
PutObjectTaggingAction: {},
|
||||
DeleteObjectTaggingAction: {},
|
||||
AllActions: {},
|
||||
AbortMultipartUploadAction: {},
|
||||
ListMultipartUploadPartsAction: {},
|
||||
PutObjectAction: {},
|
||||
GetObjectAction: {},
|
||||
DeleteObjectAction: {},
|
||||
GetObjectAclAction: {},
|
||||
GetObjectAttributesAction: {},
|
||||
PutObjectAclAction: {},
|
||||
RestoreObjectAction: {},
|
||||
GetObjectTaggingAction: {},
|
||||
PutObjectTaggingAction: {},
|
||||
DeleteObjectTaggingAction: {},
|
||||
GetObjectLegalHoldAction: {},
|
||||
PutObjectLegalHoldAction: {},
|
||||
GetObjectRetentionAction: {},
|
||||
PutObjectRetentionAction: {},
|
||||
BypassGovernanceRetentionAction: {},
|
||||
AllActions: {},
|
||||
}
|
||||
|
||||
// Validates Action: it should either wildcard match with supported actions list or be in it
|
||||
|
||||
@@ -0,0 +1,150 @@
|
||||
// Copyright 2023 Versity Software
|
||||
// This file is 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 auth
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/aws/aws-sdk-go-v2/service/s3/types"
|
||||
"github.com/versity/versitygw/backend"
|
||||
"github.com/versity/versitygw/s3err"
|
||||
)
|
||||
|
||||
type BucketLockConfig struct {
|
||||
Enabled bool
|
||||
DefaultRetention *types.DefaultRetention
|
||||
CreatedAt *time.Time
|
||||
}
|
||||
|
||||
type ObjectLockConfig struct {
|
||||
LegalHoldEnabled bool
|
||||
Retention *types.ObjectLockRetention
|
||||
}
|
||||
|
||||
func ParseBucketLockConfigurationOutput(input []byte) (*types.ObjectLockConfiguration, error) {
|
||||
var config BucketLockConfig
|
||||
if err := json.Unmarshal(input, &config); err != nil {
|
||||
return nil, fmt.Errorf("parse object lock config: %w", err)
|
||||
}
|
||||
|
||||
result := &types.ObjectLockConfiguration{
|
||||
Rule: &types.ObjectLockRule{
|
||||
DefaultRetention: config.DefaultRetention,
|
||||
},
|
||||
}
|
||||
|
||||
if config.Enabled {
|
||||
result.ObjectLockEnabled = types.ObjectLockEnabledEnabled
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func CheckObjectAccess(ctx context.Context, bucket, userAccess string, objects []string, isAdminOrRoot bool, be backend.Backend) error {
|
||||
data, err := be.GetObjectLockConfiguration(ctx, bucket)
|
||||
if err != nil {
|
||||
if errors.Is(err, s3err.GetAPIError(s3err.ErrObjectLockConfigurationNotFound)) {
|
||||
return nil
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
var bucketLockConfig BucketLockConfig
|
||||
if err := json.Unmarshal(data, &bucketLockConfig); err != nil {
|
||||
return fmt.Errorf("parse object lock config: %w", err)
|
||||
}
|
||||
|
||||
if !bucketLockConfig.Enabled {
|
||||
return nil
|
||||
}
|
||||
|
||||
for _, obj := range objects {
|
||||
retention, err := be.GetObjectRetention(ctx, bucket, obj, "")
|
||||
if err != nil {
|
||||
if errors.Is(err, s3err.GetAPIError(s3err.ErrNoSuchKey)) {
|
||||
continue
|
||||
}
|
||||
if errors.Is(err, s3err.GetAPIError(s3err.ErrNoSuchObjectLockConfiguration)) {
|
||||
continue
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
if retention.Mode != "" && retention.RetainUntilDate != nil {
|
||||
if retention.RetainUntilDate.After(time.Now()) {
|
||||
switch retention.Mode {
|
||||
case types.ObjectLockRetentionModeGovernance:
|
||||
if !isAdminOrRoot {
|
||||
policy, err := be.GetBucketPolicy(ctx, bucket)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = verifyBucketPolicy(policy, userAccess, bucket, obj, BypassGovernanceRetentionAction)
|
||||
if err != nil {
|
||||
return s3err.GetAPIError(s3err.ErrObjectLocked)
|
||||
}
|
||||
}
|
||||
case types.ObjectLockRetentionModeCompliance:
|
||||
return s3err.GetAPIError(s3err.ErrObjectLocked)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
legalHold, err := be.GetObjectLegalHold(ctx, bucket, obj, "")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if legalHold.Status == types.ObjectLockLegalHoldStatusOn && !isAdminOrRoot {
|
||||
return s3err.GetAPIError(s3err.ErrObjectLocked)
|
||||
}
|
||||
}
|
||||
|
||||
if bucketLockConfig.DefaultRetention != nil && bucketLockConfig.CreatedAt != nil {
|
||||
expirationDate := *bucketLockConfig.CreatedAt
|
||||
if bucketLockConfig.DefaultRetention.Days != nil {
|
||||
expirationDate = expirationDate.AddDate(0, 0, int(*bucketLockConfig.DefaultRetention.Days))
|
||||
}
|
||||
if bucketLockConfig.DefaultRetention.Years != nil {
|
||||
expirationDate = expirationDate.AddDate(int(*bucketLockConfig.DefaultRetention.Years), 0, 0)
|
||||
}
|
||||
|
||||
if expirationDate.After(time.Now()) {
|
||||
switch bucketLockConfig.DefaultRetention.Mode {
|
||||
case types.ObjectLockRetentionModeGovernance:
|
||||
if !isAdminOrRoot {
|
||||
policy, err := be.GetBucketPolicy(ctx, bucket)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = verifyBucketPolicy(policy, userAccess, bucket, "", BypassGovernanceRetentionAction)
|
||||
if err != nil {
|
||||
return s3err.GetAPIError(s3err.ErrObjectLocked)
|
||||
}
|
||||
}
|
||||
case types.ObjectLockRetentionModeCompliance:
|
||||
return s3err.GetAPIError(s3err.ErrObjectLocked)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -21,6 +21,7 @@ import (
|
||||
"io"
|
||||
|
||||
"github.com/aws/aws-sdk-go-v2/service/s3"
|
||||
"github.com/aws/aws-sdk-go-v2/service/s3/types"
|
||||
"github.com/versity/versitygw/s3err"
|
||||
"github.com/versity/versitygw/s3response"
|
||||
"github.com/versity/versitygw/s3select"
|
||||
@@ -81,6 +82,14 @@ type Backend interface {
|
||||
PutObjectTagging(_ context.Context, bucket, object string, tags map[string]string) error
|
||||
DeleteObjectTagging(_ context.Context, bucket, object string) error
|
||||
|
||||
// object lock operations
|
||||
PutObjectLockConfiguration(context.Context, *s3.PutObjectLockConfigurationInput) error
|
||||
GetObjectLockConfiguration(_ context.Context, bucket string) ([]byte, error)
|
||||
PutObjectRetention(context.Context, *s3.PutObjectRetentionInput) error
|
||||
GetObjectRetention(_ context.Context, bucket, object, versionId string) (*types.ObjectLockRetention, error)
|
||||
PutObjectLegalHold(context.Context, *s3.PutObjectLegalHoldInput) error
|
||||
GetObjectLegalHold(_ context.Context, bucket, object, versionId string) (*types.ObjectLockLegalHold, error)
|
||||
|
||||
// non AWS actions
|
||||
ChangeBucketOwner(_ context.Context, bucket, newOwner string) error
|
||||
ListBucketsAndOwners(context.Context) ([]s3response.Bucket, error)
|
||||
@@ -229,6 +238,25 @@ func (BackendUnsupported) DeleteObjectTagging(_ context.Context, bucket, object
|
||||
return s3err.GetAPIError(s3err.ErrNotImplemented)
|
||||
}
|
||||
|
||||
func (BackendUnsupported) PutObjectLockConfiguration(context.Context, *s3.PutObjectLockConfigurationInput) error {
|
||||
return s3err.GetAPIError(s3err.ErrNotImplemented)
|
||||
}
|
||||
func (BackendUnsupported) GetObjectLockConfiguration(_ context.Context, bucket string) ([]byte, error) {
|
||||
return nil, s3err.GetAPIError(s3err.ErrNotImplemented)
|
||||
}
|
||||
func (BackendUnsupported) PutObjectRetention(context.Context, *s3.PutObjectRetentionInput) error {
|
||||
return s3err.GetAPIError(s3err.ErrNotImplemented)
|
||||
}
|
||||
func (BackendUnsupported) GetObjectRetention(_ context.Context, bucket, object, versionId string) (*types.ObjectLockRetention, error) {
|
||||
return nil, s3err.GetAPIError(s3err.ErrNotImplemented)
|
||||
}
|
||||
func (BackendUnsupported) PutObjectLegalHold(context.Context, *s3.PutObjectLegalHoldInput) error {
|
||||
return s3err.GetAPIError(s3err.ErrNotImplemented)
|
||||
}
|
||||
func (BackendUnsupported) GetObjectLegalHold(_ context.Context, bucket, object, versionId string) (*types.ObjectLockLegalHold, error) {
|
||||
return nil, s3err.GetAPIError(s3err.ErrNotImplemented)
|
||||
}
|
||||
|
||||
func (BackendUnsupported) ChangeBucketOwner(_ context.Context, bucket, newOwner string) error {
|
||||
return s3err.GetAPIError(s3err.ErrNotImplemented)
|
||||
}
|
||||
|
||||
@@ -30,10 +30,12 @@ import (
|
||||
"strconv"
|
||||
"strings"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/aws/aws-sdk-go-v2/service/s3"
|
||||
"github.com/aws/aws-sdk-go-v2/service/s3/types"
|
||||
"github.com/google/uuid"
|
||||
"github.com/pkg/xattr"
|
||||
"github.com/versity/versitygw/auth"
|
||||
"github.com/versity/versitygw/backend"
|
||||
"github.com/versity/versitygw/backend/meta"
|
||||
@@ -75,6 +77,8 @@ const (
|
||||
aclkey = "acl"
|
||||
etagkey = "etag"
|
||||
policykey = "policy"
|
||||
bucketLockKey = "bucket-lock"
|
||||
objectLockKey = "object-lock"
|
||||
)
|
||||
|
||||
type PosixOpts struct {
|
||||
@@ -2011,6 +2015,259 @@ func (p *Posix) DeleteBucketPolicy(ctx context.Context, bucket string) error {
|
||||
return p.PutBucketPolicy(ctx, bucket, nil)
|
||||
}
|
||||
|
||||
func (p *Posix) PutObjectLockConfiguration(_ context.Context, input *s3.PutObjectLockConfigurationInput) error {
|
||||
_, err := os.Stat(*input.Bucket)
|
||||
if errors.Is(err, fs.ErrNotExist) {
|
||||
return s3err.GetAPIError(s3err.ErrNoSuchBucket)
|
||||
}
|
||||
if err != nil {
|
||||
return fmt.Errorf("stat bucket: %w", err)
|
||||
}
|
||||
|
||||
lockConfig := input.ObjectLockConfiguration
|
||||
|
||||
config := auth.BucketLockConfig{
|
||||
Enabled: lockConfig.ObjectLockEnabled == types.ObjectLockEnabledEnabled,
|
||||
}
|
||||
|
||||
if lockConfig.Rule != nil && lockConfig.Rule.DefaultRetention != nil {
|
||||
retentation := lockConfig.Rule.DefaultRetention
|
||||
if retentation.Years != nil && retentation.Days != nil {
|
||||
return s3err.GetAPIError(s3err.ErrInvalidRequest)
|
||||
}
|
||||
|
||||
config.DefaultRetention = retentation
|
||||
now := time.Now()
|
||||
config.CreatedAt = &now
|
||||
}
|
||||
|
||||
configParsed, err := json.Marshal(config)
|
||||
if err != nil {
|
||||
return fmt.Errorf("parse object lock config: %w", err)
|
||||
}
|
||||
|
||||
if err := xattr.Set(*input.Bucket, bucketLockKey, configParsed); err != nil {
|
||||
return fmt.Errorf("set tags: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *Posix) GetObjectLockConfiguration(_ context.Context, bucket string) ([]byte, error) {
|
||||
_, err := os.Stat(bucket)
|
||||
if errors.Is(err, fs.ErrNotExist) {
|
||||
return nil, s3err.GetAPIError(s3err.ErrNoSuchBucket)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("stat bucket: %w", err)
|
||||
}
|
||||
|
||||
cfg, err := xattr.Get(bucket, bucketLockKey)
|
||||
if errors.Is(err, meta.ErrNoSuchKey) {
|
||||
return nil, s3err.GetAPIError(s3err.ErrObjectLockConfigurationNotFound)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("get object lock config: %w", err)
|
||||
}
|
||||
|
||||
return cfg, nil
|
||||
}
|
||||
|
||||
func (p *Posix) PutObjectLegalHold(_ context.Context, input *s3.PutObjectLegalHoldInput) error {
|
||||
_, err := os.Stat(*input.Bucket)
|
||||
if errors.Is(err, fs.ErrNotExist) {
|
||||
return s3err.GetAPIError(s3err.ErrNoSuchBucket)
|
||||
}
|
||||
if err != nil {
|
||||
return fmt.Errorf("stat bucket: %w", err)
|
||||
}
|
||||
|
||||
cfg, err := xattr.Get(*input.Bucket, bucketLockKey)
|
||||
if errors.Is(err, meta.ErrNoSuchKey) {
|
||||
return s3err.GetAPIError(s3err.ErrInvalidBucketObjectLockConfiguration)
|
||||
}
|
||||
if err != nil {
|
||||
return fmt.Errorf("get object lock config: %w", err)
|
||||
}
|
||||
|
||||
var bucketLockConfig auth.BucketLockConfig
|
||||
if err := json.Unmarshal(cfg, &bucketLockConfig); err != nil {
|
||||
return fmt.Errorf("parse bucket lock config: %w", err)
|
||||
}
|
||||
|
||||
if !bucketLockConfig.Enabled {
|
||||
return s3err.GetAPIError(s3err.ErrInvalidBucketObjectLockConfiguration)
|
||||
}
|
||||
|
||||
path := filepath.Join(*input.Bucket, *input.Key)
|
||||
var config auth.ObjectLockConfig
|
||||
|
||||
data, err := xattr.Get(path, objectLockKey)
|
||||
if err != nil {
|
||||
if errors.Is(err, fs.ErrNotExist) {
|
||||
return s3err.GetAPIError(s3err.ErrNoSuchKey)
|
||||
}
|
||||
if errors.Is(err, meta.ErrNoSuchKey) {
|
||||
return fmt.Errorf("get object lock config: %w", err)
|
||||
}
|
||||
|
||||
config = auth.ObjectLockConfig{}
|
||||
} else {
|
||||
if err := json.Unmarshal(data, &config); err != nil {
|
||||
return fmt.Errorf("parse object lock data %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
switch input.LegalHold.Status {
|
||||
case types.ObjectLockLegalHoldStatusOff:
|
||||
config.LegalHoldEnabled = false
|
||||
case types.ObjectLockLegalHoldStatusOn:
|
||||
config.LegalHoldEnabled = true
|
||||
default:
|
||||
return s3err.GetAPIError(s3err.ErrInvalidRequest)
|
||||
}
|
||||
|
||||
b, err := json.Marshal(config)
|
||||
if err != nil {
|
||||
return fmt.Errorf("marshal object lock config: %w", err)
|
||||
}
|
||||
|
||||
err = xattr.Set(path, objectLockKey, b)
|
||||
if errors.Is(err, fs.ErrNotExist) {
|
||||
return s3err.GetAPIError(s3err.ErrNoSuchKey)
|
||||
}
|
||||
if err != nil {
|
||||
return fmt.Errorf("set object lock config: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *Posix) GetObjectLegalHold(_ context.Context, bucket, object, versionId string) (*types.ObjectLockLegalHold, error) {
|
||||
_, err := os.Stat(bucket)
|
||||
if errors.Is(err, fs.ErrNotExist) {
|
||||
return nil, s3err.GetAPIError(s3err.ErrNoSuchBucket)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("stat bucket: %w", err)
|
||||
}
|
||||
|
||||
data, err := xattr.Get(filepath.Join(bucket, object), objectLockKey)
|
||||
if errors.Is(err, meta.ErrNoSuchKey) {
|
||||
return nil, s3err.GetAPIError(s3err.ErrNoSuchObjectLockConfiguration)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("get object lock config: %w", err)
|
||||
}
|
||||
|
||||
var config auth.ObjectLockConfig
|
||||
if err := json.Unmarshal(data, &config); err != nil {
|
||||
return nil, fmt.Errorf("pare object lock config: %w", err)
|
||||
}
|
||||
|
||||
result := &types.ObjectLockLegalHold{}
|
||||
|
||||
if config.LegalHoldEnabled {
|
||||
result.Status = types.ObjectLockLegalHoldStatusOn
|
||||
} else {
|
||||
result.Status = types.ObjectLockLegalHoldStatusOff
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (p *Posix) PutObjectRetention(_ context.Context, input *s3.PutObjectRetentionInput) error {
|
||||
_, err := os.Stat(*input.Bucket)
|
||||
if errors.Is(err, fs.ErrNotExist) {
|
||||
return s3err.GetAPIError(s3err.ErrNoSuchBucket)
|
||||
}
|
||||
if err != nil {
|
||||
return fmt.Errorf("stat bucket: %w", err)
|
||||
}
|
||||
|
||||
cfg, err := xattr.Get(*input.Bucket, bucketLockKey)
|
||||
if errors.Is(err, meta.ErrNoSuchKey) {
|
||||
return s3err.GetAPIError(s3err.ErrInvalidBucketObjectLockConfiguration)
|
||||
}
|
||||
if err != nil {
|
||||
return fmt.Errorf("get object lock config: %w", err)
|
||||
}
|
||||
|
||||
var bucketLockConfig auth.BucketLockConfig
|
||||
if err := json.Unmarshal(cfg, &bucketLockConfig); err != nil {
|
||||
return fmt.Errorf("parse bucket lock config: %w", err)
|
||||
}
|
||||
|
||||
if !bucketLockConfig.Enabled {
|
||||
return s3err.GetAPIError(s3err.ErrInvalidBucketObjectLockConfiguration)
|
||||
}
|
||||
|
||||
path := filepath.Join(*input.Bucket, *input.Key)
|
||||
var config auth.ObjectLockConfig
|
||||
|
||||
data, err := xattr.Get(path, objectLockKey)
|
||||
if err != nil {
|
||||
if errors.Is(err, fs.ErrNotExist) {
|
||||
return s3err.GetAPIError(s3err.ErrNoSuchKey)
|
||||
}
|
||||
if errors.Is(err, meta.ErrNoSuchKey) {
|
||||
return fmt.Errorf("get object lock config: %w", err)
|
||||
}
|
||||
|
||||
config = auth.ObjectLockConfig{}
|
||||
} else {
|
||||
if err := json.Unmarshal(data, &config); err != nil {
|
||||
return fmt.Errorf("parse object lock data %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
config.Retention = input.Retention
|
||||
|
||||
b, err := json.Marshal(config)
|
||||
if err != nil {
|
||||
return fmt.Errorf("marshal object lock config: %w", err)
|
||||
}
|
||||
|
||||
err = xattr.Set(path, objectLockKey, b)
|
||||
if errors.Is(err, fs.ErrNotExist) {
|
||||
return s3err.GetAPIError(s3err.ErrNoSuchKey)
|
||||
}
|
||||
if err != nil {
|
||||
return fmt.Errorf("set object lock config: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *Posix) GetObjectRetention(_ context.Context, bucket, object, versionId string) (*types.ObjectLockRetention, error) {
|
||||
_, err := os.Stat(bucket)
|
||||
if errors.Is(err, fs.ErrNotExist) {
|
||||
return nil, s3err.GetAPIError(s3err.ErrNoSuchBucket)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("stat bucket: %w", err)
|
||||
}
|
||||
|
||||
data, err := xattr.Get(filepath.Join(bucket, object), objectLockKey)
|
||||
if errors.Is(err, meta.ErrNoSuchKey) {
|
||||
return nil, s3err.GetAPIError(s3err.ErrNoSuchObjectLockConfiguration)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("get object lock config: %w", err)
|
||||
}
|
||||
|
||||
var config auth.ObjectLockConfig
|
||||
if err := json.Unmarshal(data, &config); err != nil {
|
||||
return nil, fmt.Errorf("pare object lock config: %w", err)
|
||||
}
|
||||
|
||||
if config.Retention == nil {
|
||||
return &types.ObjectLockRetention{}, nil
|
||||
}
|
||||
|
||||
return config.Retention, nil
|
||||
}
|
||||
|
||||
func (p *Posix) ChangeBucketOwner(ctx context.Context, bucket, newOwner string) error {
|
||||
_, err := os.Stat(bucket)
|
||||
if errors.Is(err, fs.ErrNotExist) {
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"bufio"
|
||||
"context"
|
||||
"github.com/aws/aws-sdk-go-v2/service/s3"
|
||||
"github.com/aws/aws-sdk-go-v2/service/s3/types"
|
||||
"github.com/versity/versitygw/backend"
|
||||
"github.com/versity/versitygw/s3response"
|
||||
"io"
|
||||
@@ -80,6 +81,15 @@ var _ backend.Backend = &BackendMock{}
|
||||
// GetObjectAttributesFunc: func(contextMoqParam context.Context, getObjectAttributesInput *s3.GetObjectAttributesInput) (*s3.GetObjectAttributesOutput, error) {
|
||||
// panic("mock out the GetObjectAttributes method")
|
||||
// },
|
||||
// GetObjectLegalHoldFunc: func(contextMoqParam context.Context, bucket string, object string, versionId string) (*types.ObjectLockLegalHold, error) {
|
||||
// panic("mock out the GetObjectLegalHold method")
|
||||
// },
|
||||
// GetObjectLockConfigurationFunc: func(contextMoqParam context.Context, bucket string) ([]byte, error) {
|
||||
// panic("mock out the GetObjectLockConfiguration method")
|
||||
// },
|
||||
// GetObjectRetentionFunc: func(contextMoqParam context.Context, bucket string, object string, versionId string) (*types.ObjectLockRetention, error) {
|
||||
// panic("mock out the GetObjectRetention method")
|
||||
// },
|
||||
// GetObjectTaggingFunc: func(contextMoqParam context.Context, bucket string, object string) (map[string]string, error) {
|
||||
// panic("mock out the GetObjectTagging method")
|
||||
// },
|
||||
@@ -128,6 +138,15 @@ var _ backend.Backend = &BackendMock{}
|
||||
// PutObjectAclFunc: func(contextMoqParam context.Context, putObjectAclInput *s3.PutObjectAclInput) error {
|
||||
// panic("mock out the PutObjectAcl method")
|
||||
// },
|
||||
// PutObjectLegalHoldFunc: func(contextMoqParam context.Context, putObjectLegalHoldInput *s3.PutObjectLegalHoldInput) error {
|
||||
// panic("mock out the PutObjectLegalHold method")
|
||||
// },
|
||||
// PutObjectLockConfigurationFunc: func(contextMoqParam context.Context, putObjectLockConfigurationInput *s3.PutObjectLockConfigurationInput) error {
|
||||
// panic("mock out the PutObjectLockConfiguration method")
|
||||
// },
|
||||
// PutObjectRetentionFunc: func(contextMoqParam context.Context, putObjectRetentionInput *s3.PutObjectRetentionInput) error {
|
||||
// panic("mock out the PutObjectRetention method")
|
||||
// },
|
||||
// PutObjectTaggingFunc: func(contextMoqParam context.Context, bucket string, object string, tags map[string]string) error {
|
||||
// panic("mock out the PutObjectTagging method")
|
||||
// },
|
||||
@@ -213,6 +232,15 @@ type BackendMock struct {
|
||||
// GetObjectAttributesFunc mocks the GetObjectAttributes method.
|
||||
GetObjectAttributesFunc func(contextMoqParam context.Context, getObjectAttributesInput *s3.GetObjectAttributesInput) (*s3.GetObjectAttributesOutput, error)
|
||||
|
||||
// GetObjectLegalHoldFunc mocks the GetObjectLegalHold method.
|
||||
GetObjectLegalHoldFunc func(contextMoqParam context.Context, bucket string, object string, versionId string) (*types.ObjectLockLegalHold, error)
|
||||
|
||||
// GetObjectLockConfigurationFunc mocks the GetObjectLockConfiguration method.
|
||||
GetObjectLockConfigurationFunc func(contextMoqParam context.Context, bucket string) ([]byte, error)
|
||||
|
||||
// GetObjectRetentionFunc mocks the GetObjectRetention method.
|
||||
GetObjectRetentionFunc func(contextMoqParam context.Context, bucket string, object string, versionId string) (*types.ObjectLockRetention, error)
|
||||
|
||||
// GetObjectTaggingFunc mocks the GetObjectTagging method.
|
||||
GetObjectTaggingFunc func(contextMoqParam context.Context, bucket string, object string) (map[string]string, error)
|
||||
|
||||
@@ -261,6 +289,15 @@ type BackendMock struct {
|
||||
// PutObjectAclFunc mocks the PutObjectAcl method.
|
||||
PutObjectAclFunc func(contextMoqParam context.Context, putObjectAclInput *s3.PutObjectAclInput) error
|
||||
|
||||
// PutObjectLegalHoldFunc mocks the PutObjectLegalHold method.
|
||||
PutObjectLegalHoldFunc func(contextMoqParam context.Context, putObjectLegalHoldInput *s3.PutObjectLegalHoldInput) error
|
||||
|
||||
// PutObjectLockConfigurationFunc mocks the PutObjectLockConfiguration method.
|
||||
PutObjectLockConfigurationFunc func(contextMoqParam context.Context, putObjectLockConfigurationInput *s3.PutObjectLockConfigurationInput) error
|
||||
|
||||
// PutObjectRetentionFunc mocks the PutObjectRetention method.
|
||||
PutObjectRetentionFunc func(contextMoqParam context.Context, putObjectRetentionInput *s3.PutObjectRetentionInput) error
|
||||
|
||||
// PutObjectTaggingFunc mocks the PutObjectTagging method.
|
||||
PutObjectTaggingFunc func(contextMoqParam context.Context, bucket string, object string, tags map[string]string) error
|
||||
|
||||
@@ -425,6 +462,35 @@ type BackendMock struct {
|
||||
// GetObjectAttributesInput is the getObjectAttributesInput argument value.
|
||||
GetObjectAttributesInput *s3.GetObjectAttributesInput
|
||||
}
|
||||
// GetObjectLegalHold holds details about calls to the GetObjectLegalHold method.
|
||||
GetObjectLegalHold []struct {
|
||||
// ContextMoqParam is the contextMoqParam argument value.
|
||||
ContextMoqParam context.Context
|
||||
// Bucket is the bucket argument value.
|
||||
Bucket string
|
||||
// Object is the object argument value.
|
||||
Object string
|
||||
// VersionId is the versionId argument value.
|
||||
VersionId string
|
||||
}
|
||||
// GetObjectLockConfiguration holds details about calls to the GetObjectLockConfiguration method.
|
||||
GetObjectLockConfiguration []struct {
|
||||
// ContextMoqParam is the contextMoqParam argument value.
|
||||
ContextMoqParam context.Context
|
||||
// Bucket is the bucket argument value.
|
||||
Bucket string
|
||||
}
|
||||
// GetObjectRetention holds details about calls to the GetObjectRetention method.
|
||||
GetObjectRetention []struct {
|
||||
// ContextMoqParam is the contextMoqParam argument value.
|
||||
ContextMoqParam context.Context
|
||||
// Bucket is the bucket argument value.
|
||||
Bucket string
|
||||
// Object is the object argument value.
|
||||
Object string
|
||||
// VersionId is the versionId argument value.
|
||||
VersionId string
|
||||
}
|
||||
// GetObjectTagging holds details about calls to the GetObjectTagging method.
|
||||
GetObjectTagging []struct {
|
||||
// ContextMoqParam is the contextMoqParam argument value.
|
||||
@@ -545,6 +611,27 @@ type BackendMock struct {
|
||||
// PutObjectAclInput is the putObjectAclInput argument value.
|
||||
PutObjectAclInput *s3.PutObjectAclInput
|
||||
}
|
||||
// PutObjectLegalHold holds details about calls to the PutObjectLegalHold method.
|
||||
PutObjectLegalHold []struct {
|
||||
// ContextMoqParam is the contextMoqParam argument value.
|
||||
ContextMoqParam context.Context
|
||||
// PutObjectLegalHoldInput is the putObjectLegalHoldInput argument value.
|
||||
PutObjectLegalHoldInput *s3.PutObjectLegalHoldInput
|
||||
}
|
||||
// PutObjectLockConfiguration holds details about calls to the PutObjectLockConfiguration method.
|
||||
PutObjectLockConfiguration []struct {
|
||||
// ContextMoqParam is the contextMoqParam argument value.
|
||||
ContextMoqParam context.Context
|
||||
// PutObjectLockConfigurationInput is the putObjectLockConfigurationInput argument value.
|
||||
PutObjectLockConfigurationInput *s3.PutObjectLockConfigurationInput
|
||||
}
|
||||
// PutObjectRetention holds details about calls to the PutObjectRetention method.
|
||||
PutObjectRetention []struct {
|
||||
// ContextMoqParam is the contextMoqParam argument value.
|
||||
ContextMoqParam context.Context
|
||||
// PutObjectRetentionInput is the putObjectRetentionInput argument value.
|
||||
PutObjectRetentionInput *s3.PutObjectRetentionInput
|
||||
}
|
||||
// PutObjectTagging holds details about calls to the PutObjectTagging method.
|
||||
PutObjectTagging []struct {
|
||||
// ContextMoqParam is the contextMoqParam argument value.
|
||||
@@ -591,48 +678,54 @@ type BackendMock struct {
|
||||
UploadPartCopyInput *s3.UploadPartCopyInput
|
||||
}
|
||||
}
|
||||
lockAbortMultipartUpload sync.RWMutex
|
||||
lockChangeBucketOwner sync.RWMutex
|
||||
lockCompleteMultipartUpload sync.RWMutex
|
||||
lockCopyObject sync.RWMutex
|
||||
lockCreateBucket sync.RWMutex
|
||||
lockCreateMultipartUpload sync.RWMutex
|
||||
lockDeleteBucket sync.RWMutex
|
||||
lockDeleteBucketPolicy sync.RWMutex
|
||||
lockDeleteBucketTagging sync.RWMutex
|
||||
lockDeleteObject sync.RWMutex
|
||||
lockDeleteObjectTagging sync.RWMutex
|
||||
lockDeleteObjects sync.RWMutex
|
||||
lockGetBucketAcl sync.RWMutex
|
||||
lockGetBucketPolicy sync.RWMutex
|
||||
lockGetBucketTagging sync.RWMutex
|
||||
lockGetBucketVersioning sync.RWMutex
|
||||
lockGetObject sync.RWMutex
|
||||
lockGetObjectAcl sync.RWMutex
|
||||
lockGetObjectAttributes sync.RWMutex
|
||||
lockGetObjectTagging sync.RWMutex
|
||||
lockHeadBucket sync.RWMutex
|
||||
lockHeadObject sync.RWMutex
|
||||
lockListBuckets sync.RWMutex
|
||||
lockListBucketsAndOwners sync.RWMutex
|
||||
lockListMultipartUploads sync.RWMutex
|
||||
lockListObjectVersions sync.RWMutex
|
||||
lockListObjects sync.RWMutex
|
||||
lockListObjectsV2 sync.RWMutex
|
||||
lockListParts sync.RWMutex
|
||||
lockPutBucketAcl sync.RWMutex
|
||||
lockPutBucketPolicy sync.RWMutex
|
||||
lockPutBucketTagging sync.RWMutex
|
||||
lockPutBucketVersioning sync.RWMutex
|
||||
lockPutObject sync.RWMutex
|
||||
lockPutObjectAcl sync.RWMutex
|
||||
lockPutObjectTagging sync.RWMutex
|
||||
lockRestoreObject sync.RWMutex
|
||||
lockSelectObjectContent sync.RWMutex
|
||||
lockShutdown sync.RWMutex
|
||||
lockString sync.RWMutex
|
||||
lockUploadPart sync.RWMutex
|
||||
lockUploadPartCopy sync.RWMutex
|
||||
lockAbortMultipartUpload sync.RWMutex
|
||||
lockChangeBucketOwner sync.RWMutex
|
||||
lockCompleteMultipartUpload sync.RWMutex
|
||||
lockCopyObject sync.RWMutex
|
||||
lockCreateBucket sync.RWMutex
|
||||
lockCreateMultipartUpload sync.RWMutex
|
||||
lockDeleteBucket sync.RWMutex
|
||||
lockDeleteBucketPolicy sync.RWMutex
|
||||
lockDeleteBucketTagging sync.RWMutex
|
||||
lockDeleteObject sync.RWMutex
|
||||
lockDeleteObjectTagging sync.RWMutex
|
||||
lockDeleteObjects sync.RWMutex
|
||||
lockGetBucketAcl sync.RWMutex
|
||||
lockGetBucketPolicy sync.RWMutex
|
||||
lockGetBucketTagging sync.RWMutex
|
||||
lockGetBucketVersioning sync.RWMutex
|
||||
lockGetObject sync.RWMutex
|
||||
lockGetObjectAcl sync.RWMutex
|
||||
lockGetObjectAttributes sync.RWMutex
|
||||
lockGetObjectLegalHold sync.RWMutex
|
||||
lockGetObjectLockConfiguration sync.RWMutex
|
||||
lockGetObjectRetention sync.RWMutex
|
||||
lockGetObjectTagging sync.RWMutex
|
||||
lockHeadBucket sync.RWMutex
|
||||
lockHeadObject sync.RWMutex
|
||||
lockListBuckets sync.RWMutex
|
||||
lockListBucketsAndOwners sync.RWMutex
|
||||
lockListMultipartUploads sync.RWMutex
|
||||
lockListObjectVersions sync.RWMutex
|
||||
lockListObjects sync.RWMutex
|
||||
lockListObjectsV2 sync.RWMutex
|
||||
lockListParts sync.RWMutex
|
||||
lockPutBucketAcl sync.RWMutex
|
||||
lockPutBucketPolicy sync.RWMutex
|
||||
lockPutBucketTagging sync.RWMutex
|
||||
lockPutBucketVersioning sync.RWMutex
|
||||
lockPutObject sync.RWMutex
|
||||
lockPutObjectAcl sync.RWMutex
|
||||
lockPutObjectLegalHold sync.RWMutex
|
||||
lockPutObjectLockConfiguration sync.RWMutex
|
||||
lockPutObjectRetention sync.RWMutex
|
||||
lockPutObjectTagging sync.RWMutex
|
||||
lockRestoreObject sync.RWMutex
|
||||
lockSelectObjectContent sync.RWMutex
|
||||
lockShutdown sync.RWMutex
|
||||
lockString sync.RWMutex
|
||||
lockUploadPart sync.RWMutex
|
||||
lockUploadPartCopy sync.RWMutex
|
||||
}
|
||||
|
||||
// AbortMultipartUpload calls AbortMultipartUploadFunc.
|
||||
@@ -1335,6 +1428,130 @@ func (mock *BackendMock) GetObjectAttributesCalls() []struct {
|
||||
return calls
|
||||
}
|
||||
|
||||
// GetObjectLegalHold calls GetObjectLegalHoldFunc.
|
||||
func (mock *BackendMock) GetObjectLegalHold(contextMoqParam context.Context, bucket string, object string, versionId string) (*types.ObjectLockLegalHold, error) {
|
||||
if mock.GetObjectLegalHoldFunc == nil {
|
||||
panic("BackendMock.GetObjectLegalHoldFunc: method is nil but Backend.GetObjectLegalHold was just called")
|
||||
}
|
||||
callInfo := struct {
|
||||
ContextMoqParam context.Context
|
||||
Bucket string
|
||||
Object string
|
||||
VersionId string
|
||||
}{
|
||||
ContextMoqParam: contextMoqParam,
|
||||
Bucket: bucket,
|
||||
Object: object,
|
||||
VersionId: versionId,
|
||||
}
|
||||
mock.lockGetObjectLegalHold.Lock()
|
||||
mock.calls.GetObjectLegalHold = append(mock.calls.GetObjectLegalHold, callInfo)
|
||||
mock.lockGetObjectLegalHold.Unlock()
|
||||
return mock.GetObjectLegalHoldFunc(contextMoqParam, bucket, object, versionId)
|
||||
}
|
||||
|
||||
// GetObjectLegalHoldCalls gets all the calls that were made to GetObjectLegalHold.
|
||||
// Check the length with:
|
||||
//
|
||||
// len(mockedBackend.GetObjectLegalHoldCalls())
|
||||
func (mock *BackendMock) GetObjectLegalHoldCalls() []struct {
|
||||
ContextMoqParam context.Context
|
||||
Bucket string
|
||||
Object string
|
||||
VersionId string
|
||||
} {
|
||||
var calls []struct {
|
||||
ContextMoqParam context.Context
|
||||
Bucket string
|
||||
Object string
|
||||
VersionId string
|
||||
}
|
||||
mock.lockGetObjectLegalHold.RLock()
|
||||
calls = mock.calls.GetObjectLegalHold
|
||||
mock.lockGetObjectLegalHold.RUnlock()
|
||||
return calls
|
||||
}
|
||||
|
||||
// GetObjectLockConfiguration calls GetObjectLockConfigurationFunc.
|
||||
func (mock *BackendMock) GetObjectLockConfiguration(contextMoqParam context.Context, bucket string) ([]byte, error) {
|
||||
if mock.GetObjectLockConfigurationFunc == nil {
|
||||
panic("BackendMock.GetObjectLockConfigurationFunc: method is nil but Backend.GetObjectLockConfiguration was just called")
|
||||
}
|
||||
callInfo := struct {
|
||||
ContextMoqParam context.Context
|
||||
Bucket string
|
||||
}{
|
||||
ContextMoqParam: contextMoqParam,
|
||||
Bucket: bucket,
|
||||
}
|
||||
mock.lockGetObjectLockConfiguration.Lock()
|
||||
mock.calls.GetObjectLockConfiguration = append(mock.calls.GetObjectLockConfiguration, callInfo)
|
||||
mock.lockGetObjectLockConfiguration.Unlock()
|
||||
return mock.GetObjectLockConfigurationFunc(contextMoqParam, bucket)
|
||||
}
|
||||
|
||||
// GetObjectLockConfigurationCalls gets all the calls that were made to GetObjectLockConfiguration.
|
||||
// Check the length with:
|
||||
//
|
||||
// len(mockedBackend.GetObjectLockConfigurationCalls())
|
||||
func (mock *BackendMock) GetObjectLockConfigurationCalls() []struct {
|
||||
ContextMoqParam context.Context
|
||||
Bucket string
|
||||
} {
|
||||
var calls []struct {
|
||||
ContextMoqParam context.Context
|
||||
Bucket string
|
||||
}
|
||||
mock.lockGetObjectLockConfiguration.RLock()
|
||||
calls = mock.calls.GetObjectLockConfiguration
|
||||
mock.lockGetObjectLockConfiguration.RUnlock()
|
||||
return calls
|
||||
}
|
||||
|
||||
// GetObjectRetention calls GetObjectRetentionFunc.
|
||||
func (mock *BackendMock) GetObjectRetention(contextMoqParam context.Context, bucket string, object string, versionId string) (*types.ObjectLockRetention, error) {
|
||||
if mock.GetObjectRetentionFunc == nil {
|
||||
panic("BackendMock.GetObjectRetentionFunc: method is nil but Backend.GetObjectRetention was just called")
|
||||
}
|
||||
callInfo := struct {
|
||||
ContextMoqParam context.Context
|
||||
Bucket string
|
||||
Object string
|
||||
VersionId string
|
||||
}{
|
||||
ContextMoqParam: contextMoqParam,
|
||||
Bucket: bucket,
|
||||
Object: object,
|
||||
VersionId: versionId,
|
||||
}
|
||||
mock.lockGetObjectRetention.Lock()
|
||||
mock.calls.GetObjectRetention = append(mock.calls.GetObjectRetention, callInfo)
|
||||
mock.lockGetObjectRetention.Unlock()
|
||||
return mock.GetObjectRetentionFunc(contextMoqParam, bucket, object, versionId)
|
||||
}
|
||||
|
||||
// GetObjectRetentionCalls gets all the calls that were made to GetObjectRetention.
|
||||
// Check the length with:
|
||||
//
|
||||
// len(mockedBackend.GetObjectRetentionCalls())
|
||||
func (mock *BackendMock) GetObjectRetentionCalls() []struct {
|
||||
ContextMoqParam context.Context
|
||||
Bucket string
|
||||
Object string
|
||||
VersionId string
|
||||
} {
|
||||
var calls []struct {
|
||||
ContextMoqParam context.Context
|
||||
Bucket string
|
||||
Object string
|
||||
VersionId string
|
||||
}
|
||||
mock.lockGetObjectRetention.RLock()
|
||||
calls = mock.calls.GetObjectRetention
|
||||
mock.lockGetObjectRetention.RUnlock()
|
||||
return calls
|
||||
}
|
||||
|
||||
// GetObjectTagging calls GetObjectTaggingFunc.
|
||||
func (mock *BackendMock) GetObjectTagging(contextMoqParam context.Context, bucket string, object string) (map[string]string, error) {
|
||||
if mock.GetObjectTaggingFunc == nil {
|
||||
@@ -1927,6 +2144,114 @@ func (mock *BackendMock) PutObjectAclCalls() []struct {
|
||||
return calls
|
||||
}
|
||||
|
||||
// PutObjectLegalHold calls PutObjectLegalHoldFunc.
|
||||
func (mock *BackendMock) PutObjectLegalHold(contextMoqParam context.Context, putObjectLegalHoldInput *s3.PutObjectLegalHoldInput) error {
|
||||
if mock.PutObjectLegalHoldFunc == nil {
|
||||
panic("BackendMock.PutObjectLegalHoldFunc: method is nil but Backend.PutObjectLegalHold was just called")
|
||||
}
|
||||
callInfo := struct {
|
||||
ContextMoqParam context.Context
|
||||
PutObjectLegalHoldInput *s3.PutObjectLegalHoldInput
|
||||
}{
|
||||
ContextMoqParam: contextMoqParam,
|
||||
PutObjectLegalHoldInput: putObjectLegalHoldInput,
|
||||
}
|
||||
mock.lockPutObjectLegalHold.Lock()
|
||||
mock.calls.PutObjectLegalHold = append(mock.calls.PutObjectLegalHold, callInfo)
|
||||
mock.lockPutObjectLegalHold.Unlock()
|
||||
return mock.PutObjectLegalHoldFunc(contextMoqParam, putObjectLegalHoldInput)
|
||||
}
|
||||
|
||||
// PutObjectLegalHoldCalls gets all the calls that were made to PutObjectLegalHold.
|
||||
// Check the length with:
|
||||
//
|
||||
// len(mockedBackend.PutObjectLegalHoldCalls())
|
||||
func (mock *BackendMock) PutObjectLegalHoldCalls() []struct {
|
||||
ContextMoqParam context.Context
|
||||
PutObjectLegalHoldInput *s3.PutObjectLegalHoldInput
|
||||
} {
|
||||
var calls []struct {
|
||||
ContextMoqParam context.Context
|
||||
PutObjectLegalHoldInput *s3.PutObjectLegalHoldInput
|
||||
}
|
||||
mock.lockPutObjectLegalHold.RLock()
|
||||
calls = mock.calls.PutObjectLegalHold
|
||||
mock.lockPutObjectLegalHold.RUnlock()
|
||||
return calls
|
||||
}
|
||||
|
||||
// PutObjectLockConfiguration calls PutObjectLockConfigurationFunc.
|
||||
func (mock *BackendMock) PutObjectLockConfiguration(contextMoqParam context.Context, putObjectLockConfigurationInput *s3.PutObjectLockConfigurationInput) error {
|
||||
if mock.PutObjectLockConfigurationFunc == nil {
|
||||
panic("BackendMock.PutObjectLockConfigurationFunc: method is nil but Backend.PutObjectLockConfiguration was just called")
|
||||
}
|
||||
callInfo := struct {
|
||||
ContextMoqParam context.Context
|
||||
PutObjectLockConfigurationInput *s3.PutObjectLockConfigurationInput
|
||||
}{
|
||||
ContextMoqParam: contextMoqParam,
|
||||
PutObjectLockConfigurationInput: putObjectLockConfigurationInput,
|
||||
}
|
||||
mock.lockPutObjectLockConfiguration.Lock()
|
||||
mock.calls.PutObjectLockConfiguration = append(mock.calls.PutObjectLockConfiguration, callInfo)
|
||||
mock.lockPutObjectLockConfiguration.Unlock()
|
||||
return mock.PutObjectLockConfigurationFunc(contextMoqParam, putObjectLockConfigurationInput)
|
||||
}
|
||||
|
||||
// PutObjectLockConfigurationCalls gets all the calls that were made to PutObjectLockConfiguration.
|
||||
// Check the length with:
|
||||
//
|
||||
// len(mockedBackend.PutObjectLockConfigurationCalls())
|
||||
func (mock *BackendMock) PutObjectLockConfigurationCalls() []struct {
|
||||
ContextMoqParam context.Context
|
||||
PutObjectLockConfigurationInput *s3.PutObjectLockConfigurationInput
|
||||
} {
|
||||
var calls []struct {
|
||||
ContextMoqParam context.Context
|
||||
PutObjectLockConfigurationInput *s3.PutObjectLockConfigurationInput
|
||||
}
|
||||
mock.lockPutObjectLockConfiguration.RLock()
|
||||
calls = mock.calls.PutObjectLockConfiguration
|
||||
mock.lockPutObjectLockConfiguration.RUnlock()
|
||||
return calls
|
||||
}
|
||||
|
||||
// PutObjectRetention calls PutObjectRetentionFunc.
|
||||
func (mock *BackendMock) PutObjectRetention(contextMoqParam context.Context, putObjectRetentionInput *s3.PutObjectRetentionInput) error {
|
||||
if mock.PutObjectRetentionFunc == nil {
|
||||
panic("BackendMock.PutObjectRetentionFunc: method is nil but Backend.PutObjectRetention was just called")
|
||||
}
|
||||
callInfo := struct {
|
||||
ContextMoqParam context.Context
|
||||
PutObjectRetentionInput *s3.PutObjectRetentionInput
|
||||
}{
|
||||
ContextMoqParam: contextMoqParam,
|
||||
PutObjectRetentionInput: putObjectRetentionInput,
|
||||
}
|
||||
mock.lockPutObjectRetention.Lock()
|
||||
mock.calls.PutObjectRetention = append(mock.calls.PutObjectRetention, callInfo)
|
||||
mock.lockPutObjectRetention.Unlock()
|
||||
return mock.PutObjectRetentionFunc(contextMoqParam, putObjectRetentionInput)
|
||||
}
|
||||
|
||||
// PutObjectRetentionCalls gets all the calls that were made to PutObjectRetention.
|
||||
// Check the length with:
|
||||
//
|
||||
// len(mockedBackend.PutObjectRetentionCalls())
|
||||
func (mock *BackendMock) PutObjectRetentionCalls() []struct {
|
||||
ContextMoqParam context.Context
|
||||
PutObjectRetentionInput *s3.PutObjectRetentionInput
|
||||
} {
|
||||
var calls []struct {
|
||||
ContextMoqParam context.Context
|
||||
PutObjectRetentionInput *s3.PutObjectRetentionInput
|
||||
}
|
||||
mock.lockPutObjectRetention.RLock()
|
||||
calls = mock.calls.PutObjectRetention
|
||||
mock.lockPutObjectRetention.RUnlock()
|
||||
return calls
|
||||
}
|
||||
|
||||
// PutObjectTagging calls PutObjectTaggingFunc.
|
||||
func (mock *BackendMock) PutObjectTagging(contextMoqParam context.Context, bucket string, object string, tags map[string]string) error {
|
||||
if mock.PutObjectTaggingFunc == nil {
|
||||
|
||||
@@ -131,6 +131,62 @@ func (c S3ApiController) GetActions(ctx *fiber.Ctx) error {
|
||||
})
|
||||
}
|
||||
|
||||
if ctx.Request().URI().QueryArgs().Has("retention") {
|
||||
err := auth.VerifyAccess(ctx.Context(), c.be, auth.AccessOptions{
|
||||
Acl: parsedAcl,
|
||||
AclPermission: types.PermissionRead,
|
||||
IsRoot: isRoot,
|
||||
Acc: acct,
|
||||
Bucket: bucket,
|
||||
Object: key,
|
||||
Action: auth.GetObjectRetentionAction,
|
||||
})
|
||||
if err != nil {
|
||||
return SendXMLResponse(ctx, nil, err,
|
||||
&MetaOpts{
|
||||
Logger: c.logger,
|
||||
Action: "GetObjectRetention",
|
||||
BucketOwner: parsedAcl.Owner,
|
||||
})
|
||||
}
|
||||
|
||||
data, err := c.be.GetObjectRetention(ctx.Context(), bucket, key, versionId)
|
||||
return SendXMLResponse(ctx, data, err,
|
||||
&MetaOpts{
|
||||
Logger: c.logger,
|
||||
Action: "GetObjectRetention",
|
||||
BucketOwner: parsedAcl.Owner,
|
||||
})
|
||||
}
|
||||
|
||||
if ctx.Request().URI().QueryArgs().Has("legal-hold") {
|
||||
err := auth.VerifyAccess(ctx.Context(), c.be, auth.AccessOptions{
|
||||
Acl: parsedAcl,
|
||||
AclPermission: types.PermissionRead,
|
||||
IsRoot: isRoot,
|
||||
Acc: acct,
|
||||
Bucket: bucket,
|
||||
Object: key,
|
||||
Action: auth.GetObjectLegalHoldAction,
|
||||
})
|
||||
if err != nil {
|
||||
return SendXMLResponse(ctx, nil, err,
|
||||
&MetaOpts{
|
||||
Logger: c.logger,
|
||||
Action: "GetObjectLegalHold",
|
||||
BucketOwner: parsedAcl.Owner,
|
||||
})
|
||||
}
|
||||
|
||||
data, err := c.be.GetObjectLegalHold(ctx.Context(), bucket, key, versionId)
|
||||
return SendXMLResponse(ctx, data, err,
|
||||
&MetaOpts{
|
||||
Logger: c.logger,
|
||||
Action: "GetObjectLegalHold",
|
||||
BucketOwner: parsedAcl.Owner,
|
||||
})
|
||||
}
|
||||
|
||||
if uploadId != "" {
|
||||
if maxParts < 0 && ctx.Request().URI().QueryArgs().Has("max-parts") {
|
||||
return SendResponse(ctx,
|
||||
@@ -547,6 +603,43 @@ func (c S3ApiController) ListActions(ctx *fiber.Ctx) error {
|
||||
})
|
||||
}
|
||||
|
||||
if ctx.Request().URI().QueryArgs().Has("object-lock") {
|
||||
err := auth.VerifyAccess(ctx.Context(), c.be, auth.AccessOptions{
|
||||
Acl: parsedAcl,
|
||||
AclPermission: types.PermissionRead,
|
||||
IsRoot: isRoot,
|
||||
Acc: acct,
|
||||
Bucket: bucket,
|
||||
Action: auth.GetBucketObjectLockConfigurationAction,
|
||||
})
|
||||
if err != nil {
|
||||
return SendXMLResponse(ctx, nil, err,
|
||||
&MetaOpts{
|
||||
Logger: c.logger,
|
||||
Action: "GetObjectLockConfiguration",
|
||||
BucketOwner: parsedAcl.Owner,
|
||||
})
|
||||
}
|
||||
|
||||
data, err := c.be.GetObjectLockConfiguration(ctx.Context(), bucket)
|
||||
if err != nil {
|
||||
return SendXMLResponse(ctx, nil, err,
|
||||
&MetaOpts{
|
||||
Logger: c.logger,
|
||||
Action: "GetObjectLockConfiguration",
|
||||
BucketOwner: parsedAcl.Owner,
|
||||
})
|
||||
}
|
||||
|
||||
resp, err := auth.ParseBucketLockConfigurationOutput(data)
|
||||
return SendXMLResponse(ctx, resp, err,
|
||||
&MetaOpts{
|
||||
Logger: c.logger,
|
||||
Action: "GetObjectLockConfiguration",
|
||||
BucketOwner: parsedAcl.Owner,
|
||||
})
|
||||
}
|
||||
|
||||
if ctx.Request().URI().QueryArgs().Has("acl") {
|
||||
err := auth.VerifyAccess(ctx.Context(), c.be, auth.AccessOptions{
|
||||
Acl: parsedAcl,
|
||||
@@ -845,6 +938,47 @@ func (c S3ApiController) PutBucketActions(ctx *fiber.Ctx) error {
|
||||
})
|
||||
}
|
||||
|
||||
if ctx.Request().URI().QueryArgs().Has("object-lock") {
|
||||
parsedAcl := ctx.Locals("parsedAcl").(auth.ACL)
|
||||
|
||||
var input types.ObjectLockConfiguration
|
||||
if err := xml.Unmarshal(ctx.Body(), &input); err != nil {
|
||||
return SendResponse(ctx, s3err.GetAPIError(s3err.ErrInvalidRequest),
|
||||
&MetaOpts{
|
||||
Logger: c.logger,
|
||||
Action: "PutObjectLockConfiguration",
|
||||
BucketOwner: parsedAcl.Owner,
|
||||
})
|
||||
}
|
||||
|
||||
if err := auth.VerifyAccess(ctx.Context(), c.be, auth.AccessOptions{
|
||||
Acl: parsedAcl,
|
||||
AclPermission: types.PermissionWrite,
|
||||
IsRoot: isRoot,
|
||||
Acc: acct,
|
||||
Bucket: bucket,
|
||||
Action: auth.PutBucketObjectLockConfigurationAction,
|
||||
}); err != nil {
|
||||
return SendResponse(ctx, err,
|
||||
&MetaOpts{
|
||||
Logger: c.logger,
|
||||
Action: "PutObjectLockConfiguration",
|
||||
BucketOwner: parsedAcl.Owner,
|
||||
})
|
||||
}
|
||||
|
||||
err := c.be.PutObjectLockConfiguration(ctx.Context(), &s3.PutObjectLockConfigurationInput{
|
||||
Bucket: &bucket,
|
||||
ObjectLockConfiguration: &input,
|
||||
})
|
||||
return SendResponse(ctx, err,
|
||||
&MetaOpts{
|
||||
Logger: c.logger,
|
||||
Action: "PutObjectLockConfiguration",
|
||||
BucketOwner: parsedAcl.Owner,
|
||||
})
|
||||
}
|
||||
|
||||
if ctx.Request().URI().QueryArgs().Has("policy") {
|
||||
parsedAcl := ctx.Locals("parsedAcl").(auth.ACL)
|
||||
err := auth.VerifyAccess(ctx.Context(), c.be, auth.AccessOptions{
|
||||
@@ -1076,6 +1210,8 @@ func (c S3ApiController) PutActions(ctx *fiber.Ctx) error {
|
||||
keyStart := ctx.Params("key")
|
||||
keyEnd := ctx.Params("*1")
|
||||
uploadId := ctx.Query("uploadId")
|
||||
versionId := ctx.Query("versionId")
|
||||
bypassGovernanceRetention := ctx.Get("X-Amz-Bypass-Governance-Retention")
|
||||
acct := ctx.Locals("account").(auth.Account)
|
||||
isRoot := ctx.Locals("isRoot").(bool)
|
||||
parsedAcl := ctx.Locals("parsedAcl").(auth.ACL)
|
||||
@@ -1176,6 +1312,98 @@ func (c S3ApiController) PutActions(ctx *fiber.Ctx) error {
|
||||
})
|
||||
}
|
||||
|
||||
if ctx.Request().URI().QueryArgs().Has("retention") {
|
||||
var retention types.ObjectLockRetention
|
||||
if err := xml.Unmarshal(ctx.Body(), &retention); err != nil {
|
||||
return SendResponse(ctx, s3err.GetAPIError(s3err.ErrInvalidRequest), &MetaOpts{
|
||||
Logger: c.logger,
|
||||
Action: "PutObjectRetention",
|
||||
BucketOwner: parsedAcl.Owner,
|
||||
})
|
||||
}
|
||||
|
||||
if retention.RetainUntilDate == nil || retention.RetainUntilDate.Before(time.Now()) {
|
||||
return SendResponse(ctx, s3err.GetAPIError(s3err.ErrPastObjectLockRetainDate),
|
||||
&MetaOpts{
|
||||
Logger: c.logger,
|
||||
Action: "PutObjectRetention",
|
||||
BucketOwner: parsedAcl.Owner,
|
||||
})
|
||||
}
|
||||
|
||||
if err := auth.VerifyAccess(ctx.Context(), c.be, auth.AccessOptions{
|
||||
Acl: parsedAcl,
|
||||
AclPermission: types.PermissionWrite,
|
||||
IsRoot: isRoot,
|
||||
Acc: acct,
|
||||
Bucket: bucket,
|
||||
Object: keyStart,
|
||||
Action: auth.PutObjectRetentionAction,
|
||||
}); err != nil {
|
||||
return SendResponse(ctx, err,
|
||||
&MetaOpts{
|
||||
Logger: c.logger,
|
||||
Action: "PutObjectRetention",
|
||||
BucketOwner: parsedAcl.Owner,
|
||||
})
|
||||
}
|
||||
|
||||
pass := bypassGovernanceRetention == "True"
|
||||
|
||||
err := c.be.PutObjectRetention(ctx.Context(), &s3.PutObjectRetentionInput{
|
||||
Bucket: &bucket,
|
||||
Key: &keyStart,
|
||||
VersionId: &versionId,
|
||||
Retention: &retention,
|
||||
BypassGovernanceRetention: &pass,
|
||||
})
|
||||
return SendResponse(ctx, err, &MetaOpts{
|
||||
Logger: c.logger,
|
||||
Action: "PutObjectRetention",
|
||||
BucketOwner: parsedAcl.Owner,
|
||||
})
|
||||
}
|
||||
|
||||
if ctx.Request().URI().QueryArgs().Has("legal-hold") {
|
||||
var legalHold types.ObjectLockLegalHold
|
||||
if err := xml.Unmarshal(ctx.Body(), &legalHold); err != nil {
|
||||
return SendResponse(ctx, s3err.GetAPIError(s3err.ErrInvalidRequest), &MetaOpts{
|
||||
Logger: c.logger,
|
||||
Action: "PutObjectLegalHold",
|
||||
BucketOwner: parsedAcl.Owner,
|
||||
})
|
||||
}
|
||||
|
||||
if err := auth.VerifyAccess(ctx.Context(), c.be, auth.AccessOptions{
|
||||
Acl: parsedAcl,
|
||||
AclPermission: types.PermissionWrite,
|
||||
IsRoot: isRoot,
|
||||
Acc: acct,
|
||||
Bucket: bucket,
|
||||
Object: keyStart,
|
||||
Action: auth.PutObjectLegalHoldAction,
|
||||
}); err != nil {
|
||||
return SendResponse(ctx, err,
|
||||
&MetaOpts{
|
||||
Logger: c.logger,
|
||||
Action: "PutObjectLegalHold",
|
||||
BucketOwner: parsedAcl.Owner,
|
||||
})
|
||||
}
|
||||
|
||||
err := c.be.PutObjectLegalHold(ctx.Context(), &s3.PutObjectLegalHoldInput{
|
||||
Bucket: &bucket,
|
||||
Key: &keyStart,
|
||||
VersionId: &versionId,
|
||||
LegalHold: &legalHold,
|
||||
})
|
||||
return SendResponse(ctx, err, &MetaOpts{
|
||||
Logger: c.logger,
|
||||
Action: "PutObjectLegalHold",
|
||||
BucketOwner: parsedAcl.Owner,
|
||||
})
|
||||
}
|
||||
|
||||
if ctx.Request().URI().QueryArgs().Has("uploadId") &&
|
||||
ctx.Request().URI().QueryArgs().Has("partNumber") &&
|
||||
copySource != "" {
|
||||
@@ -1525,6 +1753,16 @@ func (c S3ApiController) PutActions(ctx *fiber.Ctx) error {
|
||||
})
|
||||
}
|
||||
|
||||
err = auth.CheckObjectAccess(ctx.Context(), bucket, acct.Access, []string{keyStart}, isRoot || acct.Role == auth.RoleAdmin, c.be)
|
||||
if err != nil {
|
||||
return SendResponse(ctx, err,
|
||||
&MetaOpts{
|
||||
Logger: c.logger,
|
||||
Action: "PutObject",
|
||||
BucketOwner: parsedAcl.Owner,
|
||||
})
|
||||
}
|
||||
|
||||
contentLength, err := strconv.ParseInt(contentLengthStr, 10, 64)
|
||||
if err != nil {
|
||||
if c.debug {
|
||||
@@ -1703,6 +1941,16 @@ func (c S3ApiController) DeleteObjects(ctx *fiber.Ctx) error {
|
||||
})
|
||||
}
|
||||
|
||||
err = auth.CheckObjectAccess(ctx.Context(), bucket, acct.Access, utils.ParseDeleteObjects(dObj.Objects), isRoot || acct.Role == auth.RoleAdmin, c.be)
|
||||
if err != nil {
|
||||
return SendResponse(ctx, err,
|
||||
&MetaOpts{
|
||||
Logger: c.logger,
|
||||
Action: "DeleteObjects",
|
||||
BucketOwner: parsedAcl.Owner,
|
||||
})
|
||||
}
|
||||
|
||||
res, err := c.be.DeleteObjects(ctx.Context(),
|
||||
&s3.DeleteObjectsInput{
|
||||
Bucket: &bucket,
|
||||
@@ -1823,6 +2071,16 @@ func (c S3ApiController) DeleteActions(ctx *fiber.Ctx) error {
|
||||
})
|
||||
}
|
||||
|
||||
err = auth.CheckObjectAccess(ctx.Context(), bucket, acct.Access, []string{key}, isRoot || acct.Role == auth.RoleAdmin, c.be)
|
||||
if err != nil {
|
||||
return SendResponse(ctx, err,
|
||||
&MetaOpts{
|
||||
Logger: c.logger,
|
||||
Action: "DeleteObject",
|
||||
BucketOwner: parsedAcl.Owner,
|
||||
})
|
||||
}
|
||||
|
||||
err = c.be.DeleteObject(ctx.Context(),
|
||||
&s3.DeleteObjectInput{
|
||||
Bucket: &bucket,
|
||||
|
||||
@@ -205,6 +205,12 @@ func TestS3ApiController_GetActions(t *testing.T) {
|
||||
GetObjectTaggingFunc: func(_ context.Context, bucket, object string) (map[string]string, error) {
|
||||
return map[string]string{"hello": "world"}, nil
|
||||
},
|
||||
GetObjectRetentionFunc: func(contextMoqParam context.Context, bucket, object, versionId string) (*types.ObjectLockRetention, error) {
|
||||
return &types.ObjectLockRetention{}, nil
|
||||
},
|
||||
GetObjectLegalHoldFunc: func(contextMoqParam context.Context, bucket, object, versionId string) (*types.ObjectLockLegalHold, error) {
|
||||
return &types.ObjectLockLegalHold{}, nil
|
||||
},
|
||||
},
|
||||
}
|
||||
app.Use(func(ctx *fiber.Ctx) error {
|
||||
@@ -236,6 +242,24 @@ func TestS3ApiController_GetActions(t *testing.T) {
|
||||
wantErr: false,
|
||||
statusCode: 200,
|
||||
},
|
||||
{
|
||||
name: "Get-actions-get-object-retention-success",
|
||||
app: app,
|
||||
args: args{
|
||||
req: httptest.NewRequest(http.MethodGet, "/my-bucket/my-obj?retention", nil),
|
||||
},
|
||||
wantErr: false,
|
||||
statusCode: 200,
|
||||
},
|
||||
{
|
||||
name: "Get-actions-get-object-legal-hold-success",
|
||||
app: app,
|
||||
args: args{
|
||||
req: httptest.NewRequest(http.MethodGet, "/my-bucket/my-obj?legal-hold", nil),
|
||||
},
|
||||
wantErr: false,
|
||||
statusCode: 200,
|
||||
},
|
||||
{
|
||||
name: "Get-actions-invalid-max-parts-string",
|
||||
app: app,
|
||||
@@ -329,6 +353,11 @@ func TestS3ApiController_ListActions(t *testing.T) {
|
||||
req *http.Request
|
||||
}
|
||||
|
||||
objectLockResult, err := json.Marshal(auth.BucketLockConfig{})
|
||||
if err != nil {
|
||||
t.Errorf("failed to parse object lock result %v", err)
|
||||
}
|
||||
|
||||
app := fiber.New()
|
||||
s3ApiController := S3ApiController{
|
||||
be: &BackendMock{
|
||||
@@ -356,6 +385,9 @@ func TestS3ApiController_ListActions(t *testing.T) {
|
||||
GetBucketPolicyFunc: func(contextMoqParam context.Context, bucket string) ([]byte, error) {
|
||||
return []byte{}, nil
|
||||
},
|
||||
GetObjectLockConfigurationFunc: func(contextMoqParam context.Context, bucket string) ([]byte, error) {
|
||||
return objectLockResult, nil
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -369,7 +401,7 @@ func TestS3ApiController_ListActions(t *testing.T) {
|
||||
|
||||
app.Get("/:bucket", s3ApiController.ListActions)
|
||||
|
||||
//Error case
|
||||
// Error case
|
||||
s3ApiControllerError := S3ApiController{
|
||||
be: &BackendMock{
|
||||
GetBucketAclFunc: func(context.Context, *s3.GetBucketAclInput) ([]byte, error) {
|
||||
@@ -418,6 +450,15 @@ func TestS3ApiController_ListActions(t *testing.T) {
|
||||
wantErr: false,
|
||||
statusCode: 200,
|
||||
},
|
||||
{
|
||||
name: "Get-object-lock-configuration-success",
|
||||
app: app,
|
||||
args: args{
|
||||
req: httptest.NewRequest(http.MethodGet, "/my-bucket?object-lock", nil),
|
||||
},
|
||||
wantErr: false,
|
||||
statusCode: 200,
|
||||
},
|
||||
{
|
||||
name: "Get-bucket-acl-success",
|
||||
app: app,
|
||||
@@ -584,6 +625,18 @@ func TestS3ApiController_PutBucketActions(t *testing.T) {
|
||||
}
|
||||
`
|
||||
|
||||
objectLockBody := `
|
||||
<ObjectLockConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
|
||||
<ObjectLockEnabled>Enabled</ObjectLockEnabled>
|
||||
<Rule>
|
||||
<DefaultRetention>
|
||||
<Mode>GOVERNANCE</Mode>
|
||||
<Years>2</Years>
|
||||
</DefaultRetention>
|
||||
</Rule>
|
||||
</ObjectLockConfiguration>
|
||||
`
|
||||
|
||||
s3ApiController := S3ApiController{
|
||||
be: &BackendMock{
|
||||
GetBucketAclFunc: func(context.Context, *s3.GetBucketAclInput) ([]byte, error) {
|
||||
@@ -604,6 +657,9 @@ func TestS3ApiController_PutBucketActions(t *testing.T) {
|
||||
PutBucketPolicyFunc: func(contextMoqParam context.Context, bucket string, policy []byte) error {
|
||||
return nil
|
||||
},
|
||||
PutObjectLockConfigurationFunc: func(contextMoqParam context.Context, putObjectLockConfigurationInput *s3.PutObjectLockConfigurationInput) error {
|
||||
return nil
|
||||
},
|
||||
},
|
||||
}
|
||||
// Mock ctx.Locals
|
||||
@@ -662,6 +718,24 @@ func TestS3ApiController_PutBucketActions(t *testing.T) {
|
||||
wantErr: false,
|
||||
statusCode: 200,
|
||||
},
|
||||
{
|
||||
name: "Put-object-lock-configuration-invalid-body",
|
||||
app: app,
|
||||
args: args{
|
||||
req: httptest.NewRequest(http.MethodPut, "/my-bucket?object-lock", nil),
|
||||
},
|
||||
wantErr: false,
|
||||
statusCode: 400,
|
||||
},
|
||||
{
|
||||
name: "Put-object-lock-configuration-success",
|
||||
app: app,
|
||||
args: args{
|
||||
req: httptest.NewRequest(http.MethodPut, "/my-bucket?object-lock", strings.NewReader(objectLockBody)),
|
||||
},
|
||||
wantErr: false,
|
||||
statusCode: 200,
|
||||
},
|
||||
{
|
||||
name: "Put-bucket-versioning-invalid-body",
|
||||
app: app,
|
||||
@@ -806,6 +880,19 @@ func TestS3ApiController_PutActions(t *testing.T) {
|
||||
</Tagging>
|
||||
`
|
||||
|
||||
retentionBody := `
|
||||
<Retention xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
|
||||
<Mode>GOVERNANCE</Mode>
|
||||
<RetainUntilDate>2025-01-01T00:00:00Z</RetainUntilDate>
|
||||
</Retention>
|
||||
`
|
||||
|
||||
legalHoldBody := `
|
||||
<LegalHold xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
|
||||
<Status>string</Status>
|
||||
</LegalHold>
|
||||
`
|
||||
|
||||
app := fiber.New()
|
||||
s3ApiController := S3ApiController{
|
||||
be: &BackendMock{
|
||||
@@ -832,6 +919,15 @@ func TestS3ApiController_PutActions(t *testing.T) {
|
||||
UploadPartCopyFunc: func(context.Context, *s3.UploadPartCopyInput) (s3response.CopyObjectResult, error) {
|
||||
return s3response.CopyObjectResult{}, nil
|
||||
},
|
||||
PutObjectLegalHoldFunc: func(contextMoqParam context.Context, putObjectLegalHoldInput *s3.PutObjectLegalHoldInput) error {
|
||||
return nil
|
||||
},
|
||||
PutObjectRetentionFunc: func(contextMoqParam context.Context, putObjectRetentionInput *s3.PutObjectRetentionInput) error {
|
||||
return nil
|
||||
},
|
||||
GetObjectLockConfigurationFunc: func(contextMoqParam context.Context, bucket string) ([]byte, error) {
|
||||
return nil, s3err.GetAPIError(s3err.ErrObjectLockConfigurationNotFound)
|
||||
},
|
||||
},
|
||||
}
|
||||
app.Use(func(ctx *fiber.Ctx) error {
|
||||
@@ -910,6 +1006,42 @@ func TestS3ApiController_PutActions(t *testing.T) {
|
||||
wantErr: false,
|
||||
statusCode: 200,
|
||||
},
|
||||
{
|
||||
name: "put-object-retention-invalid-request",
|
||||
app: app,
|
||||
args: args{
|
||||
req: httptest.NewRequest(http.MethodPut, "/my-bucket/my-key?retention", nil),
|
||||
},
|
||||
wantErr: false,
|
||||
statusCode: 400,
|
||||
},
|
||||
{
|
||||
name: "put-object-retention-success",
|
||||
app: app,
|
||||
args: args{
|
||||
req: httptest.NewRequest(http.MethodPut, "/my-bucket/my-key?retention", strings.NewReader(retentionBody)),
|
||||
},
|
||||
wantErr: false,
|
||||
statusCode: 200,
|
||||
},
|
||||
{
|
||||
name: "put-legal-hold-invalid-request",
|
||||
app: app,
|
||||
args: args{
|
||||
req: httptest.NewRequest(http.MethodPut, "/my-bucket/my-key?legal-hold", nil),
|
||||
},
|
||||
wantErr: false,
|
||||
statusCode: 400,
|
||||
},
|
||||
{
|
||||
name: "put-legal-hold-success",
|
||||
app: app,
|
||||
args: args{
|
||||
req: httptest.NewRequest(http.MethodPut, "/my-bucket/my-key?legal-hold", strings.NewReader(legalHoldBody)),
|
||||
},
|
||||
wantErr: false,
|
||||
statusCode: 200,
|
||||
},
|
||||
{
|
||||
name: "Put-object-acl-invalid-acl",
|
||||
app: app,
|
||||
@@ -1096,6 +1228,9 @@ func TestS3ApiController_DeleteObjects(t *testing.T) {
|
||||
DeleteObjectsFunc: func(context.Context, *s3.DeleteObjectsInput) (s3response.DeleteResult, error) {
|
||||
return s3response.DeleteResult{}, nil
|
||||
},
|
||||
GetObjectLockConfigurationFunc: func(contextMoqParam context.Context, bucket string) ([]byte, error) {
|
||||
return nil, s3err.GetAPIError(s3err.ErrObjectLockConfigurationNotFound)
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -1173,6 +1308,9 @@ func TestS3ApiController_DeleteActions(t *testing.T) {
|
||||
DeleteObjectTaggingFunc: func(_ context.Context, bucket, object string) error {
|
||||
return nil
|
||||
},
|
||||
GetObjectLockConfigurationFunc: func(contextMoqParam context.Context, bucket string) ([]byte, error) {
|
||||
return nil, s3err.GetAPIError(s3err.ErrObjectLockConfigurationNotFound)
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -1195,6 +1333,9 @@ func TestS3ApiController_DeleteActions(t *testing.T) {
|
||||
DeleteObjectFunc: func(context.Context, *s3.DeleteObjectInput) error {
|
||||
return s3err.GetAPIError(7)
|
||||
},
|
||||
GetObjectLockConfigurationFunc: func(contextMoqParam context.Context, bucket string) ([]byte, error) {
|
||||
return nil, s3err.GetAPIError(s3err.ErrObjectLockConfigurationNotFound)
|
||||
},
|
||||
}}
|
||||
|
||||
appErr.Use(func(ctx *fiber.Ctx) error {
|
||||
|
||||
@@ -48,7 +48,8 @@ func AclParser(be backend.Backend, logger s3log.AuditLogger) fiber.Handler {
|
||||
!ctx.Request().URI().QueryArgs().Has("acl") &&
|
||||
!ctx.Request().URI().QueryArgs().Has("tagging") &&
|
||||
!ctx.Request().URI().QueryArgs().Has("versioning") &&
|
||||
!ctx.Request().URI().QueryArgs().Has("policy") {
|
||||
!ctx.Request().URI().QueryArgs().Has("policy") &&
|
||||
!ctx.Request().URI().QueryArgs().Has("object-lock") {
|
||||
if err := auth.MayCreateBucket(acct, isRoot); err != nil {
|
||||
return controllers.SendXMLResponse(ctx, nil, err, &controllers.MetaOpts{Logger: logger, Action: "CreateBucket"})
|
||||
}
|
||||
|
||||
@@ -26,6 +26,7 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/aws/aws-sdk-go-v2/service/s3/types"
|
||||
"github.com/aws/smithy-go/encoding/httpbinding"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"github.com/valyala/fasthttp"
|
||||
@@ -244,3 +245,11 @@ func ValidateDate(date time.Time) error {
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func ParseDeleteObjects(objs []types.ObjectIdentifier) (result []string) {
|
||||
for _, obj := range objs {
|
||||
result = append(result, *obj.Key)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
@@ -111,6 +111,11 @@ const (
|
||||
ErrInvalidObjectState
|
||||
ErrInvalidRange
|
||||
ErrInvalidURI
|
||||
ErrObjectLockConfigurationNotFound
|
||||
ErrNoSuchObjectLockConfiguration
|
||||
ErrInvalidBucketObjectLockConfiguration
|
||||
ErrObjectLocked
|
||||
ErrPastObjectLockRetainDate
|
||||
|
||||
// Non-AWS errors
|
||||
ErrExistingObjectIsDirectory
|
||||
@@ -400,6 +405,31 @@ var errorCodeResponse = map[ErrorCode]APIError{
|
||||
Description: "The specified URI couldn't be parsed.",
|
||||
HTTPStatusCode: http.StatusBadRequest,
|
||||
},
|
||||
ErrObjectLockConfigurationNotFound: {
|
||||
Code: "ObjectLockConfigurationNotFoundError",
|
||||
Description: "Object Lock configuration does not exist for this bucket",
|
||||
HTTPStatusCode: http.StatusNotFound,
|
||||
},
|
||||
ErrNoSuchObjectLockConfiguration: {
|
||||
Code: "NoSuchObjectLockConfiguration",
|
||||
Description: "The specified object does not have an ObjectLock configuration",
|
||||
HTTPStatusCode: http.StatusBadRequest,
|
||||
},
|
||||
ErrInvalidBucketObjectLockConfiguration: {
|
||||
Code: "InvalidRequest",
|
||||
Description: "Bucket is missing ObjectLockConfiguration",
|
||||
HTTPStatusCode: http.StatusBadRequest,
|
||||
},
|
||||
ErrObjectLocked: {
|
||||
Code: "InvalidRequest",
|
||||
Description: "Object is WORM protected and cannot be overwritten",
|
||||
HTTPStatusCode: http.StatusBadRequest,
|
||||
},
|
||||
ErrPastObjectLockRetainDate: {
|
||||
Code: "InvalidRequest",
|
||||
Description: "the retain until date must be in the future",
|
||||
HTTPStatusCode: http.StatusBadRequest,
|
||||
},
|
||||
ErrExistingObjectIsDirectory: {
|
||||
Code: "ExistingObjectIsDirectory",
|
||||
Description: "Existing Object is a directory.",
|
||||
|
||||
Reference in New Issue
Block a user