mirror of
https://github.com/seaweedfs/seaweedfs.git
synced 2026-10-17 19:48:35 +02:00
* 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>
99 lines
3.7 KiB
Go
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"))
|
|
}
|