diff --git a/backend/azure/azure.go b/backend/azure/azure.go index c567d67..4975984 100644 --- a/backend/azure/azure.go +++ b/backend/azure/azure.go @@ -364,6 +364,9 @@ func (az *Azure) PutObject(ctx context.Context, po s3response.PutObjectInput) (s if po.ObjectLockLegalHoldStatus == types.ObjectLockLegalHoldStatusOn { err := az.PutObjectLegalHold(ctx, *po.Bucket, *po.Key, "", true) if err != nil { + if errors.Is(err, s3err.GetAPIError(s3err.ErrMissingObjectLockConfiguration)) { + err = s3err.GetAPIError(s3err.ErrMissingObjectLockConfigurationNoSpaces) + } return s3response.PutObjectOutput{}, err } } @@ -380,6 +383,9 @@ func (az *Azure) PutObject(ctx context.Context, po s3response.PutObjectInput) (s } err = az.PutObjectRetention(ctx, *po.Bucket, *po.Key, "", retParsed) if err != nil { + if errors.Is(err, s3err.GetAPIError(s3err.ErrMissingObjectLockConfiguration)) { + err = s3err.GetAPIError(s3err.ErrMissingObjectLockConfigurationNoSpaces) + } return s3response.PutObjectOutput{}, err } } @@ -980,6 +986,9 @@ func (az *Azure) CopyObject(ctx context.Context, input s3response.CopyObjectInpu if input.ObjectLockLegalHoldStatus != "" { err = az.PutObjectLegalHold(ctx, *input.Bucket, *input.Key, "", input.ObjectLockLegalHoldStatus == types.ObjectLockLegalHoldStatusOn) if err != nil { + if errors.Is(err, s3err.GetAPIError(s3err.ErrMissingObjectLockConfiguration)) { + err = s3err.GetAPIError(s3err.ErrMissingObjectLockConfigurationNoSpaces) + } return s3response.CopyObjectOutput{}, azureErrToS3Err(err) } } @@ -998,6 +1007,9 @@ func (az *Azure) CopyObject(ctx context.Context, input s3response.CopyObjectInpu } err = az.PutObjectRetention(ctx, *input.Bucket, *input.Key, "", retParsed) if err != nil { + if errors.Is(err, s3err.GetAPIError(s3err.ErrMissingObjectLockConfiguration)) { + err = s3err.GetAPIError(s3err.ErrMissingObjectLockConfigurationNoSpaces) + } return s3response.CopyObjectOutput{}, azureErrToS3Err(err) } } @@ -1140,7 +1152,7 @@ func (az *Azure) CreateMultipartUpload(ctx context.Context, input s3response.Cre } if len(bucketLock) == 0 { - return s3response.InitiateMultipartUploadResult{}, s3err.GetAPIError(s3err.ErrInvalidBucketObjectLockConfiguration) + return s3response.InitiateMultipartUploadResult{}, s3err.GetAPIError(s3err.ErrMissingObjectLockConfigurationNoSpaces) } var bucketLockConfig auth.BucketLockConfig @@ -1149,7 +1161,7 @@ func (az *Azure) CreateMultipartUpload(ctx context.Context, input s3response.Cre } if !bucketLockConfig.Enabled { - return s3response.InitiateMultipartUploadResult{}, s3err.GetAPIError(s3err.ErrInvalidBucketObjectLockConfiguration) + return s3response.InitiateMultipartUploadResult{}, s3err.GetAPIError(s3err.ErrMissingObjectLockConfigurationNoSpaces) } } @@ -1839,7 +1851,7 @@ func (az *Azure) isBucketObjectLockEnabled(ctx context.Context, bucket string) e } if len(cfg) == 0 { - return s3err.GetAPIError(s3err.ErrInvalidBucketObjectLockConfiguration) + return s3err.GetAPIError(s3err.ErrMissingObjectLockConfiguration) } var bucketLockConfig auth.BucketLockConfig @@ -1848,7 +1860,7 @@ func (az *Azure) isBucketObjectLockEnabled(ctx context.Context, bucket string) e } if !bucketLockConfig.Enabled { - return s3err.GetAPIError(s3err.ErrInvalidBucketObjectLockConfiguration) + return s3err.GetAPIError(s3err.ErrMissingObjectLockConfiguration) } return nil diff --git a/backend/posix/posix.go b/backend/posix/posix.go index ed5933c..c5c80d0 100644 --- a/backend/posix/posix.go +++ b/backend/posix/posix.go @@ -1359,6 +1359,9 @@ func (p *Posix) CreateMultipartUpload(ctx context.Context, mpu s3response.Create if mpu.ObjectLockLegalHoldStatus == types.ObjectLockLegalHoldStatusOn { err := p.PutObjectLegalHold(ctx, bucket, filepath.Join(objdir, uploadID), "", true) if err != nil { + if errors.Is(err, s3err.GetAPIError(s3err.ErrMissingObjectLockConfiguration)) { + err = s3err.GetAPIError(s3err.ErrMissingObjectLockConfigurationNoSpaces) + } // cleanup object if returning error os.RemoveAll(filepath.Join(tmppath, uploadID)) os.Remove(tmppath) @@ -1381,6 +1384,9 @@ func (p *Posix) CreateMultipartUpload(ctx context.Context, mpu s3response.Create } err = p.PutObjectRetention(ctx, bucket, filepath.Join(objdir, uploadID), "", retParsed) if err != nil { + if errors.Is(err, s3err.GetAPIError(s3err.ErrMissingObjectLockConfiguration)) { + err = s3err.GetAPIError(s3err.ErrMissingObjectLockConfigurationNoSpaces) + } // cleanup object if returning error os.RemoveAll(filepath.Join(tmppath, uploadID)) os.Remove(tmppath) @@ -3238,6 +3244,9 @@ func (p *Posix) PutObjectWithPostFunc(ctx context.Context, po s3response.PutObje if po.ObjectLockLegalHoldStatus == types.ObjectLockLegalHoldStatusOn { err := p.PutObjectLegalHold(ctx, *po.Bucket, *po.Key, "", true) if err != nil { + if errors.Is(err, s3err.GetAPIError(s3err.ErrMissingObjectLockConfiguration)) { + err = s3err.GetAPIError(s3err.ErrMissingObjectLockConfigurationNoSpaces) + } return s3response.PutObjectOutput{}, err } } @@ -3254,6 +3263,9 @@ func (p *Posix) PutObjectWithPostFunc(ctx context.Context, po s3response.PutObje } err = p.PutObjectRetention(ctx, *po.Bucket, *po.Key, "", retParsed) if err != nil { + if errors.Is(err, s3err.GetAPIError(s3err.ErrMissingObjectLockConfiguration)) { + err = s3err.GetAPIError(s3err.ErrMissingObjectLockConfigurationNoSpaces) + } return s3response.PutObjectOutput{}, err } } @@ -5108,7 +5120,7 @@ func (p *Posix) isBucketObjectLockEnabled(bucket string) error { return s3err.GetAPIError(s3err.ErrNoSuchBucket) } if errors.Is(err, meta.ErrNoSuchKey) { - return s3err.GetAPIError(s3err.ErrInvalidBucketObjectLockConfiguration) + return s3err.GetAPIError(s3err.ErrMissingObjectLockConfiguration) } if err != nil { return fmt.Errorf("get object lock config: %w", err) @@ -5120,7 +5132,7 @@ func (p *Posix) isBucketObjectLockEnabled(bucket string) error { } if !bucketLockConfig.Enabled { - return s3err.GetAPIError(s3err.ErrInvalidBucketObjectLockConfiguration) + return s3err.GetAPIError(s3err.ErrMissingObjectLockConfiguration) } return nil diff --git a/s3err/s3err.go b/s3err/s3err.go index 6721d42..9516d92 100644 --- a/s3err/s3err.go +++ b/s3err/s3err.go @@ -138,7 +138,8 @@ const ( ErrInvalidURI ErrObjectLockConfigurationNotFound ErrNoSuchObjectLockConfiguration - ErrInvalidBucketObjectLockConfiguration + ErrMissingObjectLockConfiguration + ErrMissingObjectLockConfigurationNoSpaces ErrObjectLockConfigurationNotAllowed ErrObjectLocked ErrInvalidRetainUntilDate @@ -600,9 +601,14 @@ var errorCodeResponse = map[ErrorCode]APIError{ Description: "The specified object does not have a ObjectLock configuration.", HTTPStatusCode: http.StatusBadRequest, }, - ErrInvalidBucketObjectLockConfiguration: { + ErrMissingObjectLockConfiguration: { Code: "InvalidRequest", - Description: "Bucket is missing Object Lock Configuration.", + Description: "Bucket is missing Object Lock Configuration", + HTTPStatusCode: http.StatusBadRequest, + }, + ErrMissingObjectLockConfigurationNoSpaces: { + Code: "InvalidRequest", + Description: "Bucket is missing ObjectLockConfiguration", HTTPStatusCode: http.StatusBadRequest, }, ErrObjectLockConfigurationNotAllowed: { diff --git a/tests/integration/CopyObject.go b/tests/integration/CopyObject.go index 858f25c..07e70a7 100644 --- a/tests/integration/CopyObject.go +++ b/tests/integration/CopyObject.go @@ -746,6 +746,45 @@ func CopyObject_should_replace_meta_props(s *S3Conf) error { }) } +func CopyObject_missing_bucket_lock(s *S3Conf) error { + testName := "CopyObject_missing_bucket_lock" + return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { + srcObj, dstObj := "source-object", "dst-object" + _, err := putObjectWithData(10, &s3.PutObjectInput{ + Bucket: &bucket, + Key: &srcObj, + }, s3client) + if err != nil { + return err + } + + // with retention + ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) + _, err = s3client.CopyObject(ctx, &s3.CopyObjectInput{ + Bucket: &bucket, + Key: &dstObj, + CopySource: getPtr(fmt.Sprintf("%s/%s", bucket, srcObj)), + ObjectLockMode: types.ObjectLockModeGovernance, + ObjectLockRetainUntilDate: getPtr(time.Now().AddDate(0, 1, 0)), + }) + cancel() + if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrMissingObjectLockConfigurationNoSpaces)); err != nil { + return err + } + + // with legal hold + ctx, cancel = context.WithTimeout(context.Background(), shortTimeout) + _, err = s3client.CopyObject(ctx, &s3.CopyObjectInput{ + Bucket: &bucket, + Key: &dstObj, + CopySource: getPtr(fmt.Sprintf("%s/%s", bucket, srcObj)), + ObjectLockLegalHoldStatus: types.ObjectLockLegalHoldStatusOn, + }) + cancel() + return checkApiErr(err, s3err.GetAPIError(s3err.ErrMissingObjectLockConfigurationNoSpaces)) + }) +} + func CopyObject_invalid_legal_hold(s *S3Conf) error { testName := "CopyObject_invalid_legal_hold" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { diff --git a/tests/integration/CreateMultipartUpload.go b/tests/integration/CreateMultipartUpload.go index 4e183f9..a30dd1b 100644 --- a/tests/integration/CreateMultipartUpload.go +++ b/tests/integration/CreateMultipartUpload.go @@ -209,18 +209,29 @@ func CreateMultipartUpload_with_object_lock_not_enabled(s *S3Conf) error { testName := "CreateMultipartUpload_with_object_lock_not_enabled" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { obj := "my-obj" + + // with retention ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) _, err := s3client.CreateMultipartUpload(ctx, &s3.CreateMultipartUploadInput{ + Bucket: &bucket, + Key: &obj, + ObjectLockMode: types.ObjectLockModeGovernance, + ObjectLockRetainUntilDate: getPtr(time.Now().AddDate(1, 0, 0)), + }) + cancel() + if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrMissingObjectLockConfigurationNoSpaces)); err != nil { + return err + } + + // with legal hold + ctx, cancel = context.WithTimeout(context.Background(), shortTimeout) + _, err = s3client.CreateMultipartUpload(ctx, &s3.CreateMultipartUploadInput{ Bucket: &bucket, Key: &obj, ObjectLockLegalHoldStatus: types.ObjectLockLegalHoldStatusOn, }) cancel() - if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrInvalidBucketObjectLockConfiguration)); err != nil { - return err - } - - return nil + return checkApiErr(err, s3err.GetAPIError(s3err.ErrMissingObjectLockConfigurationNoSpaces)) }) } diff --git a/tests/integration/GetObjectLegalHold.go b/tests/integration/GetObjectLegalHold.go index 05db166..52da802 100644 --- a/tests/integration/GetObjectLegalHold.go +++ b/tests/integration/GetObjectLegalHold.go @@ -72,7 +72,7 @@ func GetObjectLegalHold_disabled_lock(s *S3Conf) error { Key: &key, }) cancel() - if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrInvalidBucketObjectLockConfiguration)); err != nil { + if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrMissingObjectLockConfiguration)); err != nil { return err } diff --git a/tests/integration/GetObjectRetention.go b/tests/integration/GetObjectRetention.go index bc0629f..d0ff789 100644 --- a/tests/integration/GetObjectRetention.go +++ b/tests/integration/GetObjectRetention.go @@ -73,7 +73,7 @@ func GetObjectRetention_disabled_lock(s *S3Conf) error { Key: &key, }) cancel() - if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrInvalidBucketObjectLockConfiguration)); err != nil { + if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrMissingObjectLockConfiguration)); err != nil { return err } diff --git a/tests/integration/PutObject.go b/tests/integration/PutObject.go index 7fa44b1..02b1671 100644 --- a/tests/integration/PutObject.go +++ b/tests/integration/PutObject.go @@ -274,6 +274,30 @@ func PutObject_with_object_lock(s *S3Conf) error { }, withLock()) } +func PutObject_missing_bucket_lock(s *S3Conf) error { + testName := "PutObject_missing_bucket_lock" + return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { + // with retention + _, err := putObjectWithData(3, &s3.PutObjectInput{ + Bucket: &bucket, + Key: getPtr("my-object"), + ObjectLockMode: types.ObjectLockModeGovernance, + ObjectLockRetainUntilDate: getPtr(time.Now().AddDate(0, 0, 2)), + }, s3client) + if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrMissingObjectLockConfigurationNoSpaces)); err != nil { + return err + } + + // with legal hold + _, err = putObjectWithData(2, &s3.PutObjectInput{ + Bucket: &bucket, + Key: getPtr("my-object"), + ObjectLockLegalHoldStatus: types.ObjectLockLegalHoldStatusOn, + }, s3client) + return checkApiErr(err, s3err.GetAPIError(s3err.ErrMissingObjectLockConfigurationNoSpaces)) + }) +} + func PutObject_invalid_legal_hold(s *S3Conf) error { testName := "PutObject_invalid_legal_hold" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { diff --git a/tests/integration/PutObjectLegalHold.go b/tests/integration/PutObjectLegalHold.go index 052404e..3ac028f 100644 --- a/tests/integration/PutObjectLegalHold.go +++ b/tests/integration/PutObjectLegalHold.go @@ -115,7 +115,7 @@ func PutObjectLegalHold_unset_bucket_object_lock_config(s *S3Conf) error { }, }) cancel() - if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrInvalidBucketObjectLockConfiguration)); err != nil { + if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrMissingObjectLockConfiguration)); err != nil { return err } diff --git a/tests/integration/PutObjectRetention.go b/tests/integration/PutObjectRetention.go index bae154c..8e13c62 100644 --- a/tests/integration/PutObjectRetention.go +++ b/tests/integration/PutObjectRetention.go @@ -84,7 +84,7 @@ func PutObjectRetention_unset_bucket_object_lock_config(s *S3Conf) error { }, }) cancel() - if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrInvalidBucketObjectLockConfiguration)); err != nil { + if err := checkApiErr(err, s3err.GetAPIError(s3err.ErrMissingObjectLockConfiguration)); err != nil { return err } diff --git a/tests/integration/group-tests.go b/tests/integration/group-tests.go index f5c6c54..361de81 100644 --- a/tests/integration/group-tests.go +++ b/tests/integration/group-tests.go @@ -163,6 +163,7 @@ func TestPutObject(ts *TestState) { ts.Run(PutObject_tagging) ts.Run(PutObject_missing_object_lock_retention_config) ts.Run(PutObject_with_object_lock) + ts.Run(PutObject_missing_bucket_lock) ts.Run(PutObject_invalid_legal_hold) ts.Run(PutObject_invalid_object_lock_mode) ts.Run(PutObject_past_retain_until_date) @@ -328,6 +329,7 @@ func TestCopyObject(ts *TestState) { ts.Run(CopyObject_non_existing_dir_object) ts.Run(CopyObject_should_copy_meta_props) ts.Run(CopyObject_should_replace_meta_props) + ts.Run(CopyObject_missing_bucket_lock) ts.Run(CopyObject_invalid_legal_hold) ts.Run(CopyObject_invalid_object_lock_mode) ts.Run(CopyObject_with_legal_hold) @@ -1201,6 +1203,7 @@ func GetIntTests() IntTests { "PutObject_missing_object_lock_retention_config": PutObject_missing_object_lock_retention_config, "PutObject_name_too_long": PutObject_name_too_long, "PutObject_with_object_lock": PutObject_with_object_lock, + "PutObject_missing_bucket_lock": PutObject_missing_bucket_lock, "PutObject_invalid_legal_hold": PutObject_invalid_legal_hold, "PutObject_invalid_object_lock_mode": PutObject_invalid_object_lock_mode, "PutObject_past_retain_until_date": PutObject_past_retain_until_date, @@ -1373,6 +1376,7 @@ func GetIntTests() IntTests { "CopyObject_non_existing_dir_object": CopyObject_non_existing_dir_object, "CopyObject_should_copy_meta_props": CopyObject_should_copy_meta_props, "CopyObject_should_replace_meta_props": CopyObject_should_replace_meta_props, + "CopyObject_missing_bucket_lock": CopyObject_missing_bucket_lock, "CopyObject_invalid_legal_hold": CopyObject_invalid_legal_hold, "CopyObject_invalid_object_lock_mode": CopyObject_invalid_object_lock_mode, "CopyObject_with_legal_hold": CopyObject_with_legal_hold,