Files
seaweedfs/weed/s3api/s3api_object_lock_fix_test.go
T
Chris LuandDevin 44657988ea s3: apply destination retention on copies instead of inheriting it (#11773)
* s3: apply destination retention on copies instead of inheriting it

CopyObject carried the source object's retention mode, retain-until date
and legal hold into the destination entry, so a copy onto a bucket with
default COMPLIANCE retention produced an unlocked object that could be
deleted early. Copied objects now take their retention from the copy
request's lock headers or the destination bucket's default, matching
PUT semantics; a versioned self-copy gets the same treatment while an
unversioned metadata-only self-copy keeps the object's existing lock.

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* s3: validate lock headers on metadata-only self-copies

---------

Co-authored-by: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-10-16 19:52:10 -07:00

99 lines
3.7 KiB
Go

package s3api
import (
"testing"
"github.com/seaweedfs/seaweedfs/weed/pb/filer_pb"
"github.com/seaweedfs/seaweedfs/weed/s3api/s3_constants"
"github.com/stretchr/testify/assert"
)
// TestVeeamObjectLockBugFix tests the fix for the bug where GetObjectLockConfigurationHandler
// would return NoSuchObjectLockConfiguration for buckets with no extended attributes,
// even when Object Lock was enabled. This caused Veeam to think Object Lock wasn't supported.
func TestVeeamObjectLockBugFix(t *testing.T) {
t.Run("Bug case: bucket with no extended attributes", func(t *testing.T) {
// This simulates the bug case where a bucket has no extended attributes at all
// The old code would immediately return NoSuchObjectLockConfiguration
// The new code correctly checks if Object Lock is enabled before returning an error
entry := &filer_pb.Entry{
Name: "test-bucket",
Extended: nil, // This is the key - no extended attributes
}
// Simulate the isObjectLockEnabledForBucket logic
enabled := false
if entry.Extended != nil {
if enabledBytes, exists := entry.Extended[s3_constants.ExtObjectLockEnabledKey]; exists {
enabled = string(enabledBytes) == s3_constants.ObjectLockEnabled || string(enabledBytes) == "true"
}
}
// Should correctly return false (not enabled) - this would trigger 404 correctly
assert.False(t, enabled, "Object Lock should not be enabled when no extended attributes exist")
})
t.Run("Fix verification: bucket with Object Lock enabled via boolean flag", func(t *testing.T) {
// This verifies the fix works when Object Lock is enabled via boolean flag
entry := &filer_pb.Entry{
Name: "test-bucket",
Extended: map[string][]byte{
s3_constants.ExtObjectLockEnabledKey: []byte("true"),
},
}
// Simulate the isObjectLockEnabledForBucket logic
enabled := false
if entry.Extended != nil {
if enabledBytes, exists := entry.Extended[s3_constants.ExtObjectLockEnabledKey]; exists {
enabled = string(enabledBytes) == s3_constants.ObjectLockEnabled || string(enabledBytes) == "true"
}
}
// Should correctly return true (enabled) - this would generate minimal XML response
assert.True(t, enabled, "Object Lock should be enabled when boolean flag is set")
})
t.Run("Fix verification: bucket with Object Lock enabled via Enabled constant", func(t *testing.T) {
// Test using the s3_constants.ObjectLockEnabled constant
entry := &filer_pb.Entry{
Name: "test-bucket",
Extended: map[string][]byte{
s3_constants.ExtObjectLockEnabledKey: []byte(s3_constants.ObjectLockEnabled),
},
}
// Simulate the isObjectLockEnabledForBucket logic
enabled := false
if entry.Extended != nil {
if enabledBytes, exists := entry.Extended[s3_constants.ExtObjectLockEnabledKey]; exists {
enabled = string(enabledBytes) == s3_constants.ObjectLockEnabled || string(enabledBytes) == "true"
}
}
// Should correctly return true (enabled)
assert.True(t, enabled, "Object Lock should be enabled when constant is used")
})
}
// Copied objects get their own retention from the request or the destination
// bucket's default; the source's lock keys never carry into the destination
// entry.
func TestCopyDoesNotInheritLockKeys(t *testing.T) {
// The destination-Extended copy loop filters on isCopyLockKey, so
// retention and legal hold never carry over from the source entry.
for _, key := range []string{
s3_constants.ExtObjectLockModeKey,
s3_constants.ExtRetentionUntilDateKey,
s3_constants.ExtLegalHoldKey,
} {
assert.True(t, isCopyLockKey(key), "%s must be filtered from a copy destination", key)
}
assert.False(t, isCopyLockKey(s3_constants.ExtAmzOwnerKey))
assert.False(t, isCopyLockKey("custom-key"))
}