package s3api import ( "encoding/json" "net/http/httptest" "testing" "github.com/aws/aws-sdk-go/service/s3" "github.com/seaweedfs/seaweedfs/weed/pb/filer_pb" "github.com/seaweedfs/seaweedfs/weed/s3api/s3_constants" "github.com/seaweedfs/seaweedfs/weed/s3api/s3err" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) func TestPutBucketAclCannedAclSupport(t *testing.T) { // Test that the ExtractAcl function can handle various canned ACLs // This tests the core functionality without requiring a fully initialized S3ApiServer testCases := []struct { name string cannedAcl string shouldWork bool description string }{ { name: "private", cannedAcl: s3_constants.CannedAclPrivate, shouldWork: true, description: "private ACL should be accepted", }, { name: "public-read", cannedAcl: s3_constants.CannedAclPublicRead, shouldWork: true, description: "public-read ACL should be accepted", }, { name: "public-read-write", cannedAcl: s3_constants.CannedAclPublicReadWrite, shouldWork: true, description: "public-read-write ACL should be accepted", }, { name: "authenticated-read", cannedAcl: s3_constants.CannedAclAuthenticatedRead, shouldWork: true, description: "authenticated-read ACL should be accepted", }, { name: "bucket-owner-read", cannedAcl: s3_constants.CannedAclBucketOwnerRead, shouldWork: true, description: "bucket-owner-read ACL should be accepted", }, { name: "bucket-owner-full-control", cannedAcl: s3_constants.CannedAclBucketOwnerFullControl, shouldWork: true, description: "bucket-owner-full-control ACL should be accepted", }, { name: "invalid-acl", cannedAcl: "invalid-acl-value", shouldWork: false, description: "invalid ACL should be rejected", }, } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { // Create a request with the specified canned ACL req := httptest.NewRequest("PUT", "/bucket?acl", nil) req.Header.Set(s3_constants.AmzCannedAcl, tc.cannedAcl) req.Header.Set(s3_constants.AmzAccountId, "test-account-123") // Create a mock IAM for testing mockIam := &mockIamInterface{} // Test the ACL extraction directly grants, errCode := ExtractAcl(req, mockIam, "", "test-account-123", "test-account-123", "test-account-123") if tc.shouldWork { assert.Equal(t, s3err.ErrNone, errCode, "Expected ACL parsing to succeed for %s", tc.cannedAcl) assert.NotEmpty(t, grants, "Expected grants to be generated for valid ACL %s", tc.cannedAcl) t.Logf("✓ PASS: %s - %s", tc.name, tc.description) } else { assert.NotEqual(t, s3err.ErrNone, errCode, "Expected ACL parsing to fail for invalid ACL %s", tc.cannedAcl) t.Logf("✓ PASS: %s - %s", tc.name, tc.description) } }) } } // TestBucketWithoutACLIsNotPublicRead tests that buckets without ACLs are not public-read func TestBucketWithoutACLIsNotPublicRead(t *testing.T) { // Create a bucket config without ACL (like a freshly created bucket) config := &BucketConfig{ Name: "test-bucket", IsPublicRead: false, // Should be explicitly false } // Verify that buckets without ACL are not public-read assert.False(t, config.IsPublicRead, "Bucket without ACL should not be public-read") } func TestBucketConfigInitialization(t *testing.T) { // Test that BucketConfig properly initializes IsPublicRead field config := &BucketConfig{ Name: "test-bucket", IsPublicRead: false, // Explicitly set to false for private buckets } // Verify proper initialization assert.False(t, config.IsPublicRead, "Newly created bucket should not be public-read by default") } // TestUpdateBucketConfigCacheConsistency tests that updateBucketConfigCacheFromEntry // properly handles the IsPublicRead flag consistently with getBucketConfig func TestUpdateBucketConfigCacheConsistency(t *testing.T) { t.Run("bucket without ACL should have IsPublicRead=false", func(t *testing.T) { // Simulate an entry without ACL (like a freshly created bucket) entry := &filer_pb.Entry{ Name: "test-bucket", Attributes: &filer_pb.FuseAttributes{ FileMode: 0755, }, // Extended is nil or doesn't contain ACL } // Test what updateBucketConfigCacheFromEntry would create config := &BucketConfig{ Name: entry.Name, Entry: entry, IsPublicRead: false, // Should be explicitly false } // When Extended is nil, IsPublicRead should be false assert.False(t, config.IsPublicRead, "Bucket without Extended metadata should not be public-read") // When Extended exists but has no ACL key, IsPublicRead should also be false entry.Extended = make(map[string][]byte) entry.Extended["some-other-key"] = []byte("some-value") config = &BucketConfig{ Name: entry.Name, Entry: entry, IsPublicRead: false, // Should be explicitly false } // Simulate the else branch: no ACL means private bucket if _, exists := entry.Extended[s3_constants.ExtAmzAclKey]; !exists { config.IsPublicRead = false } assert.False(t, config.IsPublicRead, "Bucket with Extended but no ACL should not be public-read") }) t.Run("bucket with public-read ACL should have IsPublicRead=true", func(t *testing.T) { // Create a mock public-read ACL using AWS S3 SDK types publicReadGrants := []*s3.Grant{ { Grantee: &s3.Grantee{ Type: &s3_constants.GrantTypeGroup, URI: &s3_constants.GranteeGroupAllUsers, }, Permission: &s3_constants.PermissionRead, }, } aclBytes, err := json.Marshal(publicReadGrants) require.NoError(t, err) entry := &filer_pb.Entry{ Name: "public-bucket", Extended: map[string][]byte{ s3_constants.ExtAmzAclKey: aclBytes, }, } config := &BucketConfig{ Name: entry.Name, Entry: entry, IsPublicRead: false, // Start with false } // Simulate what updateBucketConfigCacheFromEntry would do if acl, exists := entry.Extended[s3_constants.ExtAmzAclKey]; exists { config.ACL = acl config.IsPublicRead = parseAndCachePublicReadStatus(acl) } assert.True(t, config.IsPublicRead, "Bucket with public-read ACL should be public-read") }) } // mockIamInterface is a simple mock for testing type mockIamInterface struct{} func (m *mockIamInterface) GetAccountNameById(canonicalId string) string { return "test-user-" + canonicalId } func (m *mockIamInterface) GetAccountIdByEmail(email string) string { return "account-for-" + email }