2022-03-01 15:07:15 +00:00
|
|
|
package handler
|
|
|
|
|
|
|
|
import (
|
|
|
|
"bytes"
|
|
|
|
"context"
|
|
|
|
"encoding/xml"
|
|
|
|
"net/http"
|
|
|
|
"net/http/httptest"
|
|
|
|
"strconv"
|
|
|
|
"testing"
|
|
|
|
"time"
|
|
|
|
|
2023-03-07 14:38:08 +00:00
|
|
|
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api"
|
|
|
|
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/data"
|
2024-09-27 09:18:41 +00:00
|
|
|
apierr "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/errors"
|
2023-10-19 14:22:26 +00:00
|
|
|
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/layer/encryption"
|
2023-07-05 14:05:45 +00:00
|
|
|
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/middleware"
|
2022-03-01 15:07:15 +00:00
|
|
|
"github.com/stretchr/testify/require"
|
|
|
|
)
|
|
|
|
|
2022-03-02 06:19:01 +00:00
|
|
|
const defaultURL = "http://localhost/"
|
2022-03-01 15:07:15 +00:00
|
|
|
|
|
|
|
func TestFormObjectLock(t *testing.T) {
|
2022-11-08 09:12:55 +00:00
|
|
|
ctx := context.Background()
|
|
|
|
|
2022-03-01 15:07:15 +00:00
|
|
|
for _, tc := range []struct {
|
|
|
|
name string
|
|
|
|
bktInfo *data.BucketInfo
|
|
|
|
config *data.ObjectLockConfiguration
|
|
|
|
header http.Header
|
|
|
|
expectedError bool
|
|
|
|
expectedLock *data.ObjectLock
|
|
|
|
}{
|
|
|
|
{
|
|
|
|
name: "default days",
|
|
|
|
bktInfo: &data.BucketInfo{ObjectLockEnabled: true},
|
|
|
|
config: &data.ObjectLockConfiguration{Rule: &data.ObjectLockRule{
|
|
|
|
DefaultRetention: &data.DefaultRetention{Mode: complianceMode, Days: 1}}},
|
2022-05-26 13:11:14 +00:00
|
|
|
expectedLock: &data.ObjectLock{Retention: &data.RetentionLock{
|
|
|
|
IsCompliance: true,
|
|
|
|
Until: time.Now().Add(24 * time.Hour)}},
|
2022-03-01 15:07:15 +00:00
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "default years",
|
|
|
|
bktInfo: &data.BucketInfo{ObjectLockEnabled: true},
|
|
|
|
config: &data.ObjectLockConfiguration{Rule: &data.ObjectLockRule{
|
|
|
|
DefaultRetention: &data.DefaultRetention{Mode: governanceMode, Years: 1}}},
|
2022-05-26 13:11:14 +00:00
|
|
|
expectedLock: &data.ObjectLock{Retention: &data.RetentionLock{
|
|
|
|
Until: time.Now().Add(365 * 24 * time.Hour)}},
|
2022-03-01 15:07:15 +00:00
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "basic override",
|
|
|
|
bktInfo: &data.BucketInfo{ObjectLockEnabled: true},
|
|
|
|
config: &data.ObjectLockConfiguration{Rule: &data.ObjectLockRule{DefaultRetention: &data.DefaultRetention{Mode: complianceMode, Days: 1}}},
|
|
|
|
header: map[string][]string{
|
2022-10-07 14:51:05 +00:00
|
|
|
api.AmzObjectLockRetainUntilDate: {time.Now().Add(time.Minute).Format(time.RFC3339)},
|
2022-03-01 15:07:15 +00:00
|
|
|
api.AmzObjectLockMode: {governanceMode},
|
|
|
|
api.AmzObjectLockLegalHold: {legalHoldOn},
|
|
|
|
},
|
2022-05-26 13:11:14 +00:00
|
|
|
expectedLock: &data.ObjectLock{
|
|
|
|
LegalHold: &data.LegalHoldLock{Enabled: true},
|
2022-10-07 14:51:05 +00:00
|
|
|
Retention: &data.RetentionLock{Until: time.Now().Add(time.Minute)}},
|
2022-03-01 15:07:15 +00:00
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "lock disabled error",
|
|
|
|
bktInfo: &data.BucketInfo{},
|
|
|
|
header: map[string][]string{api.AmzObjectLockLegalHold: {legalHoldOn}},
|
|
|
|
expectedError: true,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "invalid time format error",
|
|
|
|
bktInfo: &data.BucketInfo{ObjectLockEnabled: true},
|
|
|
|
header: map[string][]string{
|
2022-03-02 06:19:01 +00:00
|
|
|
api.AmzObjectLockRetainUntilDate: {time.Now().Format(time.RFC822)},
|
2022-03-01 15:07:15 +00:00
|
|
|
},
|
|
|
|
expectedError: true,
|
|
|
|
},
|
|
|
|
} {
|
|
|
|
t.Run(tc.name, func(t *testing.T) {
|
2022-11-08 09:12:55 +00:00
|
|
|
actualObjLock, err := formObjectLock(ctx, tc.bktInfo, tc.config, tc.header)
|
2022-03-01 15:07:15 +00:00
|
|
|
if tc.expectedError {
|
|
|
|
require.Error(t, err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
require.NoError(t, err)
|
|
|
|
assertObjectLocks(t, tc.expectedLock, actualObjLock)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestFormObjectLockFromRetention(t *testing.T) {
|
2022-11-08 09:12:55 +00:00
|
|
|
ctx := context.Background()
|
|
|
|
|
2022-03-01 15:07:15 +00:00
|
|
|
for _, tc := range []struct {
|
|
|
|
name string
|
|
|
|
retention *data.Retention
|
|
|
|
header http.Header
|
|
|
|
expectedError bool
|
|
|
|
expectedLock *data.ObjectLock
|
|
|
|
}{
|
|
|
|
{
|
|
|
|
name: "basic compliance",
|
|
|
|
retention: &data.Retention{
|
|
|
|
Mode: complianceMode,
|
2022-10-07 14:51:05 +00:00
|
|
|
RetainUntilDate: time.Now().Add(time.Minute).Format(time.RFC3339),
|
2022-03-01 15:07:15 +00:00
|
|
|
},
|
2022-05-26 13:11:14 +00:00
|
|
|
expectedLock: &data.ObjectLock{Retention: &data.RetentionLock{
|
2022-10-07 14:51:05 +00:00
|
|
|
Until: time.Now().Add(time.Minute),
|
2022-05-26 13:11:14 +00:00
|
|
|
IsCompliance: true}},
|
2022-03-01 15:07:15 +00:00
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "basic governance",
|
|
|
|
retention: &data.Retention{
|
|
|
|
Mode: governanceMode,
|
2022-10-07 14:51:05 +00:00
|
|
|
RetainUntilDate: time.Now().Add(time.Minute).Format(time.RFC3339),
|
2022-03-01 15:07:15 +00:00
|
|
|
},
|
|
|
|
header: map[string][]string{
|
|
|
|
api.AmzBypassGovernanceRetention: {strconv.FormatBool(true)},
|
|
|
|
},
|
2022-10-07 14:51:05 +00:00
|
|
|
expectedLock: &data.ObjectLock{Retention: &data.RetentionLock{Until: time.Now().Add(time.Minute)}},
|
2022-03-01 15:07:15 +00:00
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "error invalid mode",
|
|
|
|
retention: &data.Retention{
|
|
|
|
Mode: "",
|
|
|
|
RetainUntilDate: time.Now().Format(time.RFC3339),
|
|
|
|
},
|
|
|
|
expectedError: true,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "error invalid date",
|
|
|
|
retention: &data.Retention{
|
|
|
|
Mode: governanceMode,
|
|
|
|
RetainUntilDate: "",
|
|
|
|
},
|
|
|
|
expectedError: true,
|
|
|
|
},
|
|
|
|
} {
|
|
|
|
t.Run(tc.name, func(t *testing.T) {
|
2022-11-08 09:12:55 +00:00
|
|
|
actualObjLock, err := formObjectLockFromRetention(ctx, tc.retention, tc.header)
|
2022-03-01 15:07:15 +00:00
|
|
|
if tc.expectedError {
|
|
|
|
require.Error(t, err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
require.NoError(t, err)
|
|
|
|
assertObjectLocks(t, tc.expectedLock, actualObjLock)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func assertObjectLocks(t *testing.T, expected, actual *data.ObjectLock) {
|
|
|
|
require.Equal(t, expected.LegalHold, actual.LegalHold)
|
2022-05-26 13:11:14 +00:00
|
|
|
if expected.Retention != nil {
|
|
|
|
require.Equal(t, expected.Retention.IsCompliance, actual.Retention.IsCompliance)
|
|
|
|
require.InDelta(t, expected.Retention.Until.Unix(), actual.Retention.Until.Unix(), 1)
|
|
|
|
}
|
2022-03-01 15:07:15 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func TestLockConfiguration(t *testing.T) {
|
|
|
|
for _, tc := range []struct {
|
|
|
|
name string
|
|
|
|
configuration *data.ObjectLockConfiguration
|
|
|
|
expectedError bool
|
|
|
|
}{
|
|
|
|
{
|
|
|
|
name: "basic empty",
|
|
|
|
configuration: &data.ObjectLockConfiguration{},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "basic compliance",
|
|
|
|
configuration: &data.ObjectLockConfiguration{
|
|
|
|
ObjectLockEnabled: enabledValue,
|
|
|
|
Rule: &data.ObjectLockRule{
|
|
|
|
DefaultRetention: &data.DefaultRetention{
|
|
|
|
Days: 1,
|
|
|
|
Mode: complianceMode,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "basic governance",
|
|
|
|
configuration: &data.ObjectLockConfiguration{
|
|
|
|
Rule: &data.ObjectLockRule{
|
|
|
|
DefaultRetention: &data.DefaultRetention{
|
|
|
|
Mode: governanceMode,
|
|
|
|
Years: 1,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "error invalid enabled",
|
|
|
|
configuration: &data.ObjectLockConfiguration{
|
|
|
|
ObjectLockEnabled: "false",
|
|
|
|
},
|
|
|
|
expectedError: true,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "error invalid mode",
|
|
|
|
configuration: &data.ObjectLockConfiguration{
|
|
|
|
Rule: &data.ObjectLockRule{
|
|
|
|
DefaultRetention: &data.DefaultRetention{
|
|
|
|
Mode: "",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
expectedError: true,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "error no duration",
|
|
|
|
configuration: &data.ObjectLockConfiguration{
|
|
|
|
Rule: &data.ObjectLockRule{
|
|
|
|
DefaultRetention: &data.DefaultRetention{
|
|
|
|
Mode: governanceMode,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
expectedError: true,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "error both durations",
|
|
|
|
configuration: &data.ObjectLockConfiguration{
|
|
|
|
Rule: &data.ObjectLockRule{
|
|
|
|
DefaultRetention: &data.DefaultRetention{
|
|
|
|
Days: 1,
|
|
|
|
Mode: governanceMode,
|
|
|
|
Years: 1,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
expectedError: true,
|
|
|
|
},
|
|
|
|
} {
|
|
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
|
|
err := checkLockConfiguration(tc.configuration)
|
|
|
|
if tc.expectedError {
|
|
|
|
require.Error(t, err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
require.NoError(t, err)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestPutBucketLockConfigurationHandler(t *testing.T) {
|
|
|
|
ctx := context.Background()
|
|
|
|
hc := prepareHandlerContext(t)
|
|
|
|
|
|
|
|
bktLockDisabled := "bucket-lock-disabled"
|
2022-10-04 08:31:09 +00:00
|
|
|
createTestBucket(hc, bktLockDisabled)
|
2022-03-01 15:07:15 +00:00
|
|
|
|
|
|
|
bktLockEnabled := "bucket-lock-enabled"
|
2022-10-04 08:31:09 +00:00
|
|
|
createTestBucketWithLock(hc, bktLockEnabled, nil)
|
2022-03-01 15:07:15 +00:00
|
|
|
|
|
|
|
bktLockEnabledWithOldConfig := "bucket-lock-enabled-old-conf"
|
2022-10-04 08:31:09 +00:00
|
|
|
createTestBucketWithLock(hc, bktLockEnabledWithOldConfig,
|
2022-03-01 15:07:15 +00:00
|
|
|
&data.ObjectLockConfiguration{
|
|
|
|
Rule: &data.ObjectLockRule{
|
|
|
|
DefaultRetention: &data.DefaultRetention{
|
|
|
|
Days: 1,
|
|
|
|
Mode: complianceMode,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
})
|
|
|
|
|
|
|
|
for _, tc := range []struct {
|
|
|
|
name string
|
|
|
|
bucket string
|
2024-09-27 09:18:41 +00:00
|
|
|
expectedError apierr.Error
|
2022-03-01 15:07:15 +00:00
|
|
|
noError bool
|
|
|
|
configuration *data.ObjectLockConfiguration
|
|
|
|
}{
|
|
|
|
{
|
|
|
|
name: "bkt not found",
|
2024-09-27 09:18:41 +00:00
|
|
|
expectedError: apierr.GetAPIError(apierr.ErrNoSuchBucket),
|
2022-03-01 15:07:15 +00:00
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "bkt lock disabled",
|
|
|
|
bucket: bktLockDisabled,
|
2024-09-27 09:18:41 +00:00
|
|
|
expectedError: apierr.GetAPIError(apierr.ErrObjectLockConfigurationNotAllowed),
|
2022-03-01 15:07:15 +00:00
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "invalid configuration",
|
|
|
|
bucket: bktLockEnabled,
|
2024-09-27 09:18:41 +00:00
|
|
|
expectedError: apierr.GetAPIError(apierr.ErrInternalError),
|
2022-03-01 15:07:15 +00:00
|
|
|
configuration: &data.ObjectLockConfiguration{ObjectLockEnabled: "dummy"},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "basic",
|
|
|
|
bucket: bktLockEnabled,
|
|
|
|
noError: true,
|
|
|
|
configuration: &data.ObjectLockConfiguration{},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "basic override",
|
|
|
|
bucket: bktLockEnabledWithOldConfig,
|
|
|
|
noError: true,
|
|
|
|
configuration: &data.ObjectLockConfiguration{
|
|
|
|
Rule: &data.ObjectLockRule{
|
|
|
|
DefaultRetention: &data.DefaultRetention{
|
|
|
|
Mode: governanceMode,
|
|
|
|
Years: 1,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
} {
|
|
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
|
|
body, err := xml.Marshal(tc.configuration)
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
w := httptest.NewRecorder()
|
2022-03-02 06:19:01 +00:00
|
|
|
r := httptest.NewRequest(http.MethodPut, defaultURL, bytes.NewReader(body))
|
2024-04-17 14:08:55 +00:00
|
|
|
r = r.WithContext(middleware.SetReqInfo(r.Context(), middleware.NewReqInfo(w, r, middleware.ObjectRequest{Bucket: tc.bucket}, "")))
|
2022-03-01 15:07:15 +00:00
|
|
|
|
|
|
|
hc.Handler().PutBucketObjectLockConfigHandler(w, r)
|
|
|
|
|
|
|
|
if !tc.noError {
|
|
|
|
assertS3Error(t, w, tc.expectedError)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
bktInfo, err := hc.Layer().GetBucketInfo(ctx, tc.bucket)
|
|
|
|
require.NoError(t, err)
|
|
|
|
bktSettings, err := hc.Layer().GetBucketSettings(ctx, bktInfo)
|
|
|
|
require.NoError(t, err)
|
|
|
|
actualConf := bktSettings.LockConfiguration
|
2022-07-19 15:45:08 +00:00
|
|
|
require.True(t, bktSettings.VersioningEnabled())
|
2022-03-01 15:07:15 +00:00
|
|
|
require.Equal(t, tc.configuration.ObjectLockEnabled, actualConf.ObjectLockEnabled)
|
|
|
|
require.Equal(t, tc.configuration.Rule, actualConf.Rule)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestGetBucketLockConfigurationHandler(t *testing.T) {
|
|
|
|
hc := prepareHandlerContext(t)
|
|
|
|
|
|
|
|
bktLockDisabled := "bucket-lock-disabled"
|
2022-10-04 08:31:09 +00:00
|
|
|
createTestBucket(hc, bktLockDisabled)
|
2022-03-01 15:07:15 +00:00
|
|
|
|
|
|
|
bktLockEnabled := "bucket-lock-enabled"
|
2022-10-04 08:31:09 +00:00
|
|
|
createTestBucketWithLock(hc, bktLockEnabled, nil)
|
2022-03-01 15:07:15 +00:00
|
|
|
|
|
|
|
oldConfig := &data.ObjectLockConfiguration{
|
|
|
|
Rule: &data.ObjectLockRule{
|
|
|
|
DefaultRetention: &data.DefaultRetention{
|
|
|
|
Days: 1,
|
|
|
|
Mode: complianceMode,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
bktLockEnabledWithOldConfig := "bucket-lock-enabled-old-conf"
|
2022-10-04 08:31:09 +00:00
|
|
|
createTestBucketWithLock(hc, bktLockEnabledWithOldConfig, oldConfig)
|
2022-03-01 15:07:15 +00:00
|
|
|
|
|
|
|
for _, tc := range []struct {
|
|
|
|
name string
|
|
|
|
bucket string
|
2024-09-27 09:18:41 +00:00
|
|
|
expectedError apierr.Error
|
2022-03-01 15:07:15 +00:00
|
|
|
noError bool
|
|
|
|
expectedConf *data.ObjectLockConfiguration
|
|
|
|
}{
|
|
|
|
{
|
|
|
|
name: "bkt not found",
|
2024-09-27 09:18:41 +00:00
|
|
|
expectedError: apierr.GetAPIError(apierr.ErrNoSuchBucket),
|
2022-03-01 15:07:15 +00:00
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "bkt lock disabled",
|
|
|
|
bucket: bktLockDisabled,
|
2024-09-27 09:18:41 +00:00
|
|
|
expectedError: apierr.GetAPIError(apierr.ErrObjectLockConfigurationNotFound),
|
2022-03-01 15:07:15 +00:00
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "bkt lock enabled empty default",
|
|
|
|
bucket: bktLockEnabled,
|
|
|
|
noError: true,
|
|
|
|
expectedConf: &data.ObjectLockConfiguration{ObjectLockEnabled: enabledValue},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "bkt lock enabled",
|
|
|
|
bucket: bktLockEnabledWithOldConfig,
|
|
|
|
noError: true,
|
|
|
|
expectedConf: oldConfig,
|
|
|
|
},
|
|
|
|
} {
|
|
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
|
|
w := httptest.NewRecorder()
|
2022-03-02 06:19:01 +00:00
|
|
|
r := httptest.NewRequest(http.MethodPut, defaultURL, bytes.NewReader(nil))
|
2024-04-17 14:08:55 +00:00
|
|
|
r = r.WithContext(middleware.SetReqInfo(r.Context(), middleware.NewReqInfo(w, r, middleware.ObjectRequest{Bucket: tc.bucket}, "")))
|
2022-03-01 15:07:15 +00:00
|
|
|
|
|
|
|
hc.Handler().GetBucketObjectLockConfigHandler(w, r)
|
|
|
|
|
|
|
|
if !tc.noError {
|
|
|
|
assertS3Error(t, w, tc.expectedError)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
actualConf := &data.ObjectLockConfiguration{}
|
|
|
|
err := xml.NewDecoder(w.Result().Body).Decode(actualConf)
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
require.Equal(t, tc.expectedConf.ObjectLockEnabled, actualConf.ObjectLockEnabled)
|
|
|
|
require.Equal(t, tc.expectedConf.Rule, actualConf.Rule)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-09-27 09:18:41 +00:00
|
|
|
func assertS3Error(t *testing.T, w *httptest.ResponseRecorder, expectedError apierr.Error) {
|
2023-07-05 14:05:45 +00:00
|
|
|
actualErrorResponse := &middleware.ErrorResponse{}
|
2022-03-01 15:07:15 +00:00
|
|
|
err := xml.NewDecoder(w.Result().Body).Decode(actualErrorResponse)
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
require.Equal(t, expectedError.HTTPStatusCode, w.Code)
|
|
|
|
require.Equal(t, expectedError.Code, actualErrorResponse.Code)
|
|
|
|
|
2024-09-27 09:18:41 +00:00
|
|
|
if expectedError.ErrCode != apierr.ErrInternalError {
|
2022-03-01 15:07:15 +00:00
|
|
|
require.Equal(t, expectedError.Description, actualErrorResponse.Message)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestObjectLegalHold(t *testing.T) {
|
|
|
|
hc := prepareHandlerContext(t)
|
|
|
|
|
|
|
|
bktName := "bucket-lock-enabled"
|
2022-10-04 08:31:09 +00:00
|
|
|
bktInfo := createTestBucketWithLock(hc, bktName, nil)
|
2022-03-01 15:07:15 +00:00
|
|
|
|
|
|
|
objName := "obj-for-legal-hold"
|
2023-10-19 14:22:26 +00:00
|
|
|
createTestObject(hc, bktInfo, objName, encryption.Params{})
|
2022-03-01 15:07:15 +00:00
|
|
|
|
2022-10-25 10:16:08 +00:00
|
|
|
getObjectLegalHold(hc, bktName, objName, legalHoldOff)
|
2022-03-01 15:07:15 +00:00
|
|
|
|
2022-10-25 10:16:08 +00:00
|
|
|
putObjectLegalHold(hc, bktName, objName, legalHoldOn)
|
|
|
|
getObjectLegalHold(hc, bktName, objName, legalHoldOn)
|
2022-03-01 15:07:15 +00:00
|
|
|
|
2022-04-13 16:56:58 +00:00
|
|
|
// to make sure put hold is an idempotent operation
|
2022-10-25 10:16:08 +00:00
|
|
|
putObjectLegalHold(hc, bktName, objName, legalHoldOn)
|
2022-03-01 15:07:15 +00:00
|
|
|
|
2022-10-25 10:16:08 +00:00
|
|
|
putObjectLegalHold(hc, bktName, objName, legalHoldOff)
|
|
|
|
getObjectLegalHold(hc, bktName, objName, legalHoldOff)
|
|
|
|
|
|
|
|
// to make sure put hold is an idempotent operation
|
|
|
|
putObjectLegalHold(hc, bktName, objName, legalHoldOff)
|
|
|
|
}
|
2022-03-01 15:07:15 +00:00
|
|
|
|
2022-10-25 10:16:08 +00:00
|
|
|
func getObjectLegalHold(hc *handlerContext, bktName, objName, status string) {
|
|
|
|
w, r := prepareTestRequest(hc, bktName, objName, nil)
|
2022-03-01 15:07:15 +00:00
|
|
|
hc.Handler().GetObjectLegalHoldHandler(w, r)
|
2022-10-25 10:16:08 +00:00
|
|
|
assertLegalHold(hc.t, w, status)
|
|
|
|
}
|
2022-03-01 15:07:15 +00:00
|
|
|
|
2022-10-25 10:16:08 +00:00
|
|
|
func putObjectLegalHold(hc *handlerContext, bktName, objName, status string) {
|
|
|
|
w, r := prepareTestRequest(hc, bktName, objName, &data.LegalHold{Status: status})
|
2022-03-01 15:07:15 +00:00
|
|
|
hc.Handler().PutObjectLegalHoldHandler(w, r)
|
2022-10-25 10:16:08 +00:00
|
|
|
assertStatus(hc.t, w, http.StatusOK)
|
2022-03-01 15:07:15 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func assertLegalHold(t *testing.T, w *httptest.ResponseRecorder, status string) {
|
|
|
|
actualHold := &data.LegalHold{}
|
|
|
|
err := xml.NewDecoder(w.Result().Body).Decode(actualHold)
|
|
|
|
require.NoError(t, err)
|
|
|
|
require.Equal(t, status, actualHold.Status)
|
|
|
|
require.Equal(t, http.StatusOK, w.Code)
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestObjectRetention(t *testing.T) {
|
|
|
|
hc := prepareHandlerContext(t)
|
|
|
|
|
|
|
|
bktName := "bucket-lock-enabled"
|
2022-10-04 08:31:09 +00:00
|
|
|
bktInfo := createTestBucketWithLock(hc, bktName, nil)
|
2022-03-01 15:07:15 +00:00
|
|
|
|
|
|
|
objName := "obj-for-retention"
|
2023-10-19 14:22:26 +00:00
|
|
|
createTestObject(hc, bktInfo, objName, encryption.Params{})
|
2022-03-01 15:07:15 +00:00
|
|
|
|
2024-09-27 09:18:41 +00:00
|
|
|
getObjectRetention(hc, bktName, objName, nil, apierr.ErrNoSuchKey)
|
2022-03-01 15:07:15 +00:00
|
|
|
|
2022-10-07 14:51:05 +00:00
|
|
|
retention := &data.Retention{Mode: governanceMode, RetainUntilDate: time.Now().Add(time.Minute).UTC().Format(time.RFC3339)}
|
2022-10-25 10:16:08 +00:00
|
|
|
putObjectRetention(hc, bktName, objName, retention, false, 0)
|
|
|
|
getObjectRetention(hc, bktName, objName, retention, 0)
|
2022-03-01 15:07:15 +00:00
|
|
|
|
2022-10-07 14:51:05 +00:00
|
|
|
retention = &data.Retention{Mode: governanceMode, RetainUntilDate: time.Now().UTC().Add(time.Minute).Format(time.RFC3339)}
|
2024-09-27 09:18:41 +00:00
|
|
|
putObjectRetention(hc, bktName, objName, retention, false, apierr.ErrInternalError)
|
2022-03-01 15:07:15 +00:00
|
|
|
|
2022-10-07 14:51:05 +00:00
|
|
|
retention = &data.Retention{Mode: complianceMode, RetainUntilDate: time.Now().Add(time.Minute).UTC().Format(time.RFC3339)}
|
2022-10-25 10:16:08 +00:00
|
|
|
putObjectRetention(hc, bktName, objName, retention, true, 0)
|
|
|
|
getObjectRetention(hc, bktName, objName, retention, 0)
|
|
|
|
|
2024-09-27 09:18:41 +00:00
|
|
|
putObjectRetention(hc, bktName, objName, retention, true, apierr.ErrInternalError)
|
2022-10-25 10:16:08 +00:00
|
|
|
}
|
2022-03-01 15:07:15 +00:00
|
|
|
|
2024-09-27 09:18:41 +00:00
|
|
|
func getObjectRetention(hc *handlerContext, bktName, objName string, retention *data.Retention, errCode apierr.ErrorCode) {
|
2022-10-25 10:16:08 +00:00
|
|
|
w, r := prepareTestRequest(hc, bktName, objName, nil)
|
2022-03-01 15:07:15 +00:00
|
|
|
hc.Handler().GetObjectRetentionHandler(w, r)
|
2022-10-25 10:16:08 +00:00
|
|
|
if errCode == 0 {
|
|
|
|
assertRetention(hc.t, w, retention)
|
|
|
|
} else {
|
2024-09-27 09:18:41 +00:00
|
|
|
assertS3Error(hc.t, w, apierr.GetAPIError(errCode))
|
2022-10-25 10:16:08 +00:00
|
|
|
}
|
|
|
|
}
|
2022-03-01 15:07:15 +00:00
|
|
|
|
2024-09-27 09:18:41 +00:00
|
|
|
func putObjectRetention(hc *handlerContext, bktName, objName string, retention *data.Retention, byPass bool, errCode apierr.ErrorCode) {
|
2022-10-25 10:16:08 +00:00
|
|
|
w, r := prepareTestRequest(hc, bktName, objName, retention)
|
|
|
|
if byPass {
|
|
|
|
r.Header.Set(api.AmzBypassGovernanceRetention, strconv.FormatBool(true))
|
|
|
|
}
|
2022-03-01 15:07:15 +00:00
|
|
|
hc.Handler().PutObjectRetentionHandler(w, r)
|
2022-10-25 10:16:08 +00:00
|
|
|
if errCode == 0 {
|
|
|
|
assertStatus(hc.t, w, http.StatusOK)
|
|
|
|
} else {
|
2024-09-27 09:18:41 +00:00
|
|
|
assertS3Error(hc.t, w, apierr.GetAPIError(errCode))
|
2022-10-25 10:16:08 +00:00
|
|
|
}
|
2022-03-01 15:07:15 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func assertRetention(t *testing.T, w *httptest.ResponseRecorder, retention *data.Retention) {
|
|
|
|
actualRetention := &data.Retention{}
|
|
|
|
err := xml.NewDecoder(w.Result().Body).Decode(actualRetention)
|
|
|
|
require.NoError(t, err)
|
|
|
|
require.Equal(t, retention.Mode, actualRetention.Mode)
|
|
|
|
require.Equal(t, retention.RetainUntilDate, actualRetention.RetainUntilDate)
|
|
|
|
require.Equal(t, http.StatusOK, w.Code)
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestPutObjectWithLock(t *testing.T) {
|
|
|
|
hc := prepareHandlerContext(t)
|
|
|
|
|
|
|
|
bktName := "bucket-lock-enabled"
|
|
|
|
lockConfig := &data.ObjectLockConfiguration{
|
|
|
|
ObjectLockEnabled: enabledValue,
|
|
|
|
Rule: &data.ObjectLockRule{
|
|
|
|
DefaultRetention: &data.DefaultRetention{
|
|
|
|
Days: 1,
|
|
|
|
Mode: governanceMode,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
2022-10-04 08:31:09 +00:00
|
|
|
createTestBucketWithLock(hc, bktName, lockConfig)
|
2022-03-01 15:07:15 +00:00
|
|
|
|
|
|
|
objDefault := "obj-default-retention"
|
2023-08-21 13:05:16 +00:00
|
|
|
putObject(hc, bktName, objDefault)
|
2022-03-01 15:07:15 +00:00
|
|
|
|
2022-10-25 10:16:08 +00:00
|
|
|
getObjectRetentionApproximate(hc, bktName, objDefault, governanceMode, time.Now().Add(24*time.Hour))
|
|
|
|
getObjectLegalHold(hc, bktName, objDefault, legalHoldOff)
|
2022-03-01 15:07:15 +00:00
|
|
|
|
|
|
|
objOverride := "obj-override-retention"
|
2022-10-25 10:16:08 +00:00
|
|
|
w, r := prepareTestRequest(hc, bktName, objOverride, nil)
|
2022-03-01 15:07:15 +00:00
|
|
|
r.Header.Set(api.AmzObjectLockMode, complianceMode)
|
|
|
|
r.Header.Set(api.AmzObjectLockLegalHold, legalHoldOn)
|
2022-05-31 07:55:57 +00:00
|
|
|
r.Header.Set(api.AmzBypassGovernanceRetention, "true")
|
2022-03-01 15:07:15 +00:00
|
|
|
r.Header.Set(api.AmzObjectLockRetainUntilDate, time.Now().Add(2*24*time.Hour).Format(time.RFC3339))
|
|
|
|
hc.Handler().PutObjectHandler(w, r)
|
2022-05-31 07:55:57 +00:00
|
|
|
assertStatus(t, w, http.StatusOK)
|
2022-03-01 15:07:15 +00:00
|
|
|
|
2022-10-25 10:16:08 +00:00
|
|
|
getObjectRetentionApproximate(hc, bktName, objOverride, complianceMode, time.Now().Add(2*24*time.Hour))
|
|
|
|
getObjectLegalHold(hc, bktName, objOverride, legalHoldOn)
|
|
|
|
}
|
|
|
|
|
|
|
|
func getObjectRetentionApproximate(hc *handlerContext, bktName, objName, mode string, untilDate time.Time) {
|
|
|
|
w, r := prepareTestRequest(hc, bktName, objName, nil)
|
2022-03-01 15:07:15 +00:00
|
|
|
hc.Handler().GetObjectRetentionHandler(w, r)
|
2022-10-25 10:16:08 +00:00
|
|
|
expectedRetention := &data.Retention{
|
|
|
|
Mode: mode,
|
|
|
|
RetainUntilDate: untilDate.Format(time.RFC3339),
|
2022-03-01 15:07:15 +00:00
|
|
|
}
|
2022-10-25 10:16:08 +00:00
|
|
|
assertRetentionApproximate(hc.t, w, expectedRetention, 1)
|
2022-03-01 15:07:15 +00:00
|
|
|
}
|
|
|
|
|
2022-10-07 14:51:05 +00:00
|
|
|
func TestPutLockErrors(t *testing.T) {
|
|
|
|
hc := prepareHandlerContext(t)
|
|
|
|
|
|
|
|
bktName, objName := "bucket-lock-enabled", "object"
|
2022-10-04 08:31:09 +00:00
|
|
|
createTestBucketWithLock(hc, bktName, nil)
|
2022-10-07 14:51:05 +00:00
|
|
|
|
|
|
|
headers := map[string]string{api.AmzObjectLockMode: complianceMode}
|
2024-09-27 09:18:41 +00:00
|
|
|
putObjectWithLockFailed(t, hc, bktName, objName, headers, apierr.ErrObjectLockInvalidHeaders)
|
2022-10-07 14:51:05 +00:00
|
|
|
|
|
|
|
delete(headers, api.AmzObjectLockMode)
|
|
|
|
headers[api.AmzObjectLockRetainUntilDate] = time.Now().Add(time.Minute).Format(time.RFC3339)
|
2024-09-27 09:18:41 +00:00
|
|
|
putObjectWithLockFailed(t, hc, bktName, objName, headers, apierr.ErrObjectLockInvalidHeaders)
|
2022-10-07 14:51:05 +00:00
|
|
|
|
|
|
|
headers[api.AmzObjectLockMode] = "dummy"
|
2024-09-27 09:18:41 +00:00
|
|
|
putObjectWithLockFailed(t, hc, bktName, objName, headers, apierr.ErrUnknownWORMModeDirective)
|
2022-10-07 14:51:05 +00:00
|
|
|
|
|
|
|
headers[api.AmzObjectLockMode] = complianceMode
|
|
|
|
headers[api.AmzObjectLockRetainUntilDate] = time.Now().Format(time.RFC3339)
|
2024-09-27 09:18:41 +00:00
|
|
|
putObjectWithLockFailed(t, hc, bktName, objName, headers, apierr.ErrPastObjectLockRetainDate)
|
2022-10-07 14:51:05 +00:00
|
|
|
|
|
|
|
headers[api.AmzObjectLockRetainUntilDate] = "dummy"
|
2024-09-27 09:18:41 +00:00
|
|
|
putObjectWithLockFailed(t, hc, bktName, objName, headers, apierr.ErrInvalidRetentionDate)
|
2022-10-07 14:51:05 +00:00
|
|
|
|
2023-08-21 13:05:16 +00:00
|
|
|
putObject(hc, bktName, objName)
|
2022-10-07 14:51:05 +00:00
|
|
|
|
|
|
|
retention := &data.Retention{Mode: governanceMode}
|
2024-09-27 09:18:41 +00:00
|
|
|
putObjectRetentionFailed(t, hc, bktName, objName, retention, apierr.ErrMalformedXML)
|
2022-10-07 14:51:05 +00:00
|
|
|
|
|
|
|
retention.Mode = "dummy"
|
|
|
|
retention.RetainUntilDate = time.Now().Add(time.Minute).UTC().Format(time.RFC3339)
|
2024-09-27 09:18:41 +00:00
|
|
|
putObjectRetentionFailed(t, hc, bktName, objName, retention, apierr.ErrMalformedXML)
|
2022-10-07 14:51:05 +00:00
|
|
|
|
|
|
|
retention.Mode = governanceMode
|
|
|
|
retention.RetainUntilDate = time.Now().UTC().Format(time.RFC3339)
|
2024-09-27 09:18:41 +00:00
|
|
|
putObjectRetentionFailed(t, hc, bktName, objName, retention, apierr.ErrPastObjectLockRetainDate)
|
2022-10-07 14:51:05 +00:00
|
|
|
}
|
|
|
|
|
2024-09-27 09:18:41 +00:00
|
|
|
func putObjectWithLockFailed(t *testing.T, hc *handlerContext, bktName, objName string, headers map[string]string, errCode apierr.ErrorCode) {
|
2022-10-04 08:31:09 +00:00
|
|
|
w, r := prepareTestRequest(hc, bktName, objName, nil)
|
2022-10-07 14:51:05 +00:00
|
|
|
|
|
|
|
for key, val := range headers {
|
|
|
|
r.Header.Set(key, val)
|
|
|
|
}
|
|
|
|
|
|
|
|
hc.Handler().PutObjectHandler(w, r)
|
2024-09-27 09:18:41 +00:00
|
|
|
assertS3Error(t, w, apierr.GetAPIError(errCode))
|
2022-10-07 14:51:05 +00:00
|
|
|
}
|
|
|
|
|
2024-09-27 09:18:41 +00:00
|
|
|
func putObjectRetentionFailed(t *testing.T, hc *handlerContext, bktName, objName string, retention *data.Retention, errCode apierr.ErrorCode) {
|
2022-10-04 08:31:09 +00:00
|
|
|
w, r := prepareTestRequest(hc, bktName, objName, retention)
|
2022-10-07 14:51:05 +00:00
|
|
|
hc.Handler().PutObjectRetentionHandler(w, r)
|
2024-09-27 09:18:41 +00:00
|
|
|
assertS3Error(t, w, apierr.GetAPIError(errCode))
|
2022-10-07 14:51:05 +00:00
|
|
|
}
|
|
|
|
|
2022-03-01 15:07:15 +00:00
|
|
|
func assertRetentionApproximate(t *testing.T, w *httptest.ResponseRecorder, retention *data.Retention, delta float64) {
|
|
|
|
actualRetention := &data.Retention{}
|
|
|
|
err := xml.NewDecoder(w.Result().Body).Decode(actualRetention)
|
|
|
|
require.NoError(t, err)
|
|
|
|
require.Equal(t, retention.Mode, actualRetention.Mode)
|
|
|
|
require.Equal(t, http.StatusOK, w.Code)
|
|
|
|
|
|
|
|
actualUntil, err := time.Parse(time.RFC3339, actualRetention.RetainUntilDate)
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
expectedUntil, err := time.Parse(time.RFC3339, retention.RetainUntilDate)
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
require.InDelta(t, expectedUntil.Unix(), actualUntil.Unix(), delta)
|
|
|
|
}
|