Files
versity-versitygw/auth/acl_test.go
T
dualfroz 0e3045b87f fix: drop the owner's FULL_CONTROL grant when grant headers set the bucket ACL
CreateBucket and PutBucketAcl with x-amz-grant-* headers stored the
requested grants plus a FULL_CONTROL grant for the bucket owner, so
GetBucketAcl returned a grant AWS doesn't. On AWS the headers replace the
whole ACL and the owner gets FULL_CONTROL only when a header names it.

UpdateACL now keeps just the header grants when there are any. A canned
ACL and a bucket created without grants still get the owner's FULL_CONTROL
grant. The owner keeps full access either way, since aclAllows lets the
bucket owner perform any action regardless of grants.

Fixes #1407
2026-10-14 19:40:59 +02:00

118 lines
3.1 KiB
Go

// Copyright 2026 Versity Software
// This file is licensed under the Apache License, Version 2.0
// (the "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
package auth
import (
"testing"
"github.com/aws/aws-sdk-go-v2/service/s3/types"
"github.com/stretchr/testify/assert"
)
// existingAccountsIAM resolves every access key id it is asked about, so
// UpdateACL accepts any grantee.
type existingAccountsIAM struct {
IAMService
}
func (existingAccountsIAM) ResolveAccounts([]string) ([]string, error) {
return nil, nil
}
// TestUpdateACL checks which grants end up in the bucket ACL. Grant headers
// replace the ACL, as on AWS, so the owner gets a FULL_CONTROL grant only
// from a canned ACL, from the default ACL of a bucket created without
// grants, or when a header names it.
func TestUpdateACL(t *testing.T) {
empty := ""
readAcpGrantee := "user1"
ownerGrantee := "owner"
ownerFullControl := Grantee{
Permission: PermissionFullControl,
Access: "owner",
Type: types.TypeCanonicalUser,
}
tests := []struct {
name string
input *PutBucketAclInput
want []Grantee
}{
{
name: "grant header",
input: &PutBucketAclInput{
GrantReadACP: &readAcpGrantee,
},
want: []Grantee{
{
Permission: PermissionReadAcp,
Access: "user1",
Type: types.TypeCanonicalUser,
},
},
},
{
name: "grant header naming the owner",
input: &PutBucketAclInput{
GrantFullControl: &ownerGrantee,
},
want: []Grantee{ownerFullControl},
},
{
name: "create bucket without grants",
input: &PutBucketAclInput{
GrantFullControl: &empty,
GrantRead: &empty,
GrantReadACP: &empty,
GrantWrite: &empty,
GrantWriteACP: &empty,
AccessControlPolicy: &AccessControlPolicy{},
},
want: []Grantee{ownerFullControl},
},
{
name: "canned acl",
input: &PutBucketAclInput{
ACL: types.BucketCannedACLPublicRead,
},
want: []Grantee{
ownerFullControl,
{
Permission: PermissionRead,
Access: "all-users",
Type: types.TypeGroup,
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
aclJSON, err := UpdateACL(tt.input, ACL{Owner: "owner"}, existingAccountsIAM{})
assert.NoError(t, err)
acl, err := ParseACL(aclJSON)
assert.NoError(t, err)
assert.Equal(t, "owner", acl.Owner)
assert.Equal(t, tt.want, acl.Grantees)
for _, action := range []Action{GetBucketAclAction, PutBucketAclAction, ListBucketAction, PutObjectAction, PutBucketPolicyAction} {
assert.True(t, aclAllows(acl, "owner", action, false), "owner denied %s", action)
}
})
}
}