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
118 lines
3.1 KiB
Go
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)
|
|
}
|
|
})
|
|
}
|
|
}
|