You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
					
					
						
							250 lines
						
					
					
						
							7.9 KiB
						
					
					
				
			
		
		
		
			
			
			
		
		
	
	
							250 lines
						
					
					
						
							7.9 KiB
						
					
					
				
								package s3api
							 | 
						|
								
							 | 
						|
								import (
							 | 
						|
									"encoding/json"
							 | 
						|
									"encoding/xml"
							 | 
						|
									"net/http/httptest"
							 | 
						|
									"testing"
							 | 
						|
									"time"
							 | 
						|
								
							 | 
						|
									"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
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								// TestListAllMyBucketsResultNamespace verifies that the ListAllMyBucketsResult
							 | 
						|
								// XML response includes the proper S3 namespace URI
							 | 
						|
								func TestListAllMyBucketsResultNamespace(t *testing.T) {
							 | 
						|
									// Create a sample ListAllMyBucketsResult response
							 | 
						|
									response := ListAllMyBucketsResult{
							 | 
						|
										Owner: CanonicalUser{
							 | 
						|
											ID:          "test-owner-id",
							 | 
						|
											DisplayName: "test-owner",
							 | 
						|
										},
							 | 
						|
										Buckets: ListAllMyBucketsList{
							 | 
						|
											Bucket: []ListAllMyBucketsEntry{
							 | 
						|
												{
							 | 
						|
													Name:         "test-bucket",
							 | 
						|
													CreationDate: time.Date(2025, 1, 1, 0, 0, 0, 0, time.UTC),
							 | 
						|
												},
							 | 
						|
											},
							 | 
						|
										},
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									// Marshal the response to XML
							 | 
						|
									xmlData, err := xml.Marshal(response)
							 | 
						|
									require.NoError(t, err, "Failed to marshal XML response")
							 | 
						|
								
							 | 
						|
									xmlString := string(xmlData)
							 | 
						|
								
							 | 
						|
									// Verify that the XML contains the proper namespace
							 | 
						|
									assert.Contains(t, xmlString, `xmlns="http://s3.amazonaws.com/doc/2006-03-01/"`,
							 | 
						|
										"XML response should contain the S3 namespace URI")
							 | 
						|
								
							 | 
						|
									// Verify the root element has the correct name
							 | 
						|
									assert.Contains(t, xmlString, "<ListAllMyBucketsResult",
							 | 
						|
										"XML response should have ListAllMyBucketsResult root element")
							 | 
						|
								
							 | 
						|
									// Verify structure contains expected elements
							 | 
						|
									assert.Contains(t, xmlString, "<Owner>", "XML should contain Owner element")
							 | 
						|
									assert.Contains(t, xmlString, "<Buckets>", "XML should contain Buckets element")
							 | 
						|
									assert.Contains(t, xmlString, "<Bucket>", "XML should contain Bucket element")
							 | 
						|
									assert.Contains(t, xmlString, "<Name>test-bucket</Name>", "XML should contain bucket name")
							 | 
						|
								
							 | 
						|
									t.Logf("Generated XML:\n%s", xmlString)
							 | 
						|
								}
							 |