From ad09d9889107ba9638014051271b10a8e17c11b1 Mon Sep 17 00:00:00 2001 From: Jon Austin <62040422+jonaustin09@users.noreply.github.com> Date: Thu, 15 Jun 2023 21:49:17 +0400 Subject: [PATCH] =?UTF-8?q?feat:=20Implemented=20GetBucketACL,=20PutBucket?= =?UTF-8?q?ACL=20posix=20functions,=20fixed=20a=E2=80=A6=20(#92)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: Implemented GetBucketACL, PutBucketACL posix functions, fixed authentication middleware signed headers bug * fix: Fixed GetBucketAcl return type, fixed staticcheck uppercase error, fixed unit tests for PutActions --- backend/auth/acl.go | 37 +++++++ backend/backend.go | 9 +- backend/backend_moq_test.go | 83 +++----------- backend/backend_test.go | 24 ++-- backend/posix/posix.go | 151 +++++++++++++++++++++++++- s3api/controllers/backend_moq_test.go | 83 +++----------- s3api/controllers/base.go | 23 ++-- s3api/controllers/base_test.go | 15 ++- s3api/middlewares/authentication.go | 3 +- 9 files changed, 258 insertions(+), 170 deletions(-) create mode 100644 backend/auth/acl.go diff --git a/backend/auth/acl.go b/backend/auth/acl.go new file mode 100644 index 00000000..81400025 --- /dev/null +++ b/backend/auth/acl.go @@ -0,0 +1,37 @@ +// Copyright 2023 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 "github.com/aws/aws-sdk-go-v2/service/s3/types" + +type ACL struct { + ACL types.BucketCannedACL + Owner string + Grantees []Grantee +} + +type Grantee struct { + Permission types.Permission + Access string +} + +type GetBucketAclOutput struct { + Owner *types.Owner + AccessControlList AccessControlList +} + +type AccessControlList struct { + Grants []types.Grant +} diff --git a/backend/backend.go b/backend/backend.go index 463af2c1..ee5bb480 100644 --- a/backend/backend.go +++ b/backend/backend.go @@ -20,6 +20,7 @@ import ( "github.com/aws/aws-sdk-go-v2/service/s3" "github.com/aws/aws-sdk-go-v2/service/s3/types" + "github.com/versity/versitygw/backend/auth" "github.com/versity/versitygw/s3err" "github.com/versity/versitygw/s3response" ) @@ -32,8 +33,8 @@ type Backend interface { ListBuckets() (*s3.ListBucketsOutput, error) HeadBucket(bucket string) (*s3.HeadBucketOutput, error) - GetBucketAcl(bucket string) (*s3.GetBucketAclOutput, error) - PutBucket(bucket string) error + GetBucketAcl(bucket string) (*auth.GetBucketAclOutput, error) + PutBucket(bucket, owner string) error PutBucketAcl(*s3.PutBucketAclInput) error DeleteBucket(bucket string) error @@ -90,13 +91,13 @@ func (BackendUnsupported) RestoreObject(bucket, object string, restoreRequest *s func (BackendUnsupported) UploadPartCopy(*s3.UploadPartCopyInput) (*s3.UploadPartCopyOutput, error) { return nil, s3err.GetAPIError(s3err.ErrNotImplemented) } -func (BackendUnsupported) GetBucketAcl(bucket string) (*s3.GetBucketAclOutput, error) { +func (BackendUnsupported) GetBucketAcl(bucket string) (*auth.GetBucketAclOutput, error) { return nil, s3err.GetAPIError(s3err.ErrNotImplemented) } func (BackendUnsupported) HeadBucket(bucket string) (*s3.HeadBucketOutput, error) { return nil, s3err.GetAPIError(s3err.ErrNotImplemented) } -func (BackendUnsupported) PutBucket(bucket string) error { +func (BackendUnsupported) PutBucket(bucket, owner string) error { return s3err.GetAPIError(s3err.ErrNotImplemented) } func (BackendUnsupported) DeleteBucket(bucket string) error { diff --git a/backend/backend_moq_test.go b/backend/backend_moq_test.go index 6ecfc5b2..f3a88d37 100644 --- a/backend/backend_moq_test.go +++ b/backend/backend_moq_test.go @@ -6,6 +6,7 @@ package backend import ( "github.com/aws/aws-sdk-go-v2/service/s3" "github.com/aws/aws-sdk-go-v2/service/s3/types" + "github.com/versity/versitygw/backend/auth" "github.com/versity/versitygw/s3response" "io" "sync" @@ -45,7 +46,7 @@ var _ Backend = &BackendMock{} // DeleteObjectsFunc: func(bucket string, objects *s3.DeleteObjectsInput) error { // panic("mock out the DeleteObjects method") // }, -// GetBucketAclFunc: func(bucket string) (*s3.GetBucketAclOutput, error) { +// GetBucketAclFunc: func(bucket string) (*auth.GetBucketAclOutput, error) { // panic("mock out the GetBucketAcl method") // }, // GetObjectFunc: func(bucket string, object string, acceptRange string, writer io.Writer) (*s3.GetObjectOutput, error) { @@ -81,7 +82,7 @@ var _ Backend = &BackendMock{} // ListObjectsV2Func: func(bucket string, prefix string, marker string, delim string, maxkeys int) (*s3.ListObjectsV2Output, error) { // panic("mock out the ListObjectsV2 method") // }, -// PutBucketFunc: func(bucket string) error { +// PutBucketFunc: func(bucket string, owner string) error { // panic("mock out the PutBucket method") // }, // PutBucketAclFunc: func(putBucketAclInput *s3.PutBucketAclInput) error { @@ -111,9 +112,6 @@ var _ Backend = &BackendMock{} // StringFunc: func() string { // panic("mock out the String method") // }, -// UploadPartFunc: func(bucket string, object string, uploadId string, Body io.ReadSeeker) (*s3.UploadPartOutput, error) { -// panic("mock out the UploadPart method") -// }, // UploadPartCopyFunc: func(uploadPartCopyInput *s3.UploadPartCopyInput) (*s3.UploadPartCopyOutput, error) { // panic("mock out the UploadPartCopy method") // }, @@ -149,7 +147,7 @@ type BackendMock struct { DeleteObjectsFunc func(bucket string, objects *s3.DeleteObjectsInput) error // GetBucketAclFunc mocks the GetBucketAcl method. - GetBucketAclFunc func(bucket string) (*s3.GetBucketAclOutput, error) + GetBucketAclFunc func(bucket string) (*auth.GetBucketAclOutput, error) // GetObjectFunc mocks the GetObject method. GetObjectFunc func(bucket string, object string, acceptRange string, writer io.Writer) (*s3.GetObjectOutput, error) @@ -185,7 +183,7 @@ type BackendMock struct { ListObjectsV2Func func(bucket string, prefix string, marker string, delim string, maxkeys int) (*s3.ListObjectsV2Output, error) // PutBucketFunc mocks the PutBucket method. - PutBucketFunc func(bucket string) error + PutBucketFunc func(bucket string, owner string) error // PutBucketAclFunc mocks the PutBucketAcl method. PutBucketAclFunc func(putBucketAclInput *s3.PutBucketAclInput) error @@ -214,9 +212,6 @@ type BackendMock struct { // StringFunc mocks the String method. StringFunc func() string - // UploadPartFunc mocks the UploadPart method. - UploadPartFunc func(bucket string, object string, uploadId string, Body io.ReadSeeker) (*s3.UploadPartOutput, error) - // UploadPartCopyFunc mocks the UploadPartCopy method. UploadPartCopyFunc func(uploadPartCopyInput *s3.UploadPartCopyInput) (*s3.UploadPartCopyOutput, error) @@ -390,6 +385,8 @@ type BackendMock struct { PutBucket []struct { // Bucket is the bucket argument value. Bucket string + // Owner is the owner argument value. + Owner string } // PutBucketAcl holds details about calls to the PutBucketAcl method. PutBucketAcl []struct { @@ -452,17 +449,6 @@ type BackendMock struct { // String holds details about calls to the String method. String []struct { } - // UploadPart holds details about calls to the UploadPart method. - UploadPart []struct { - // Bucket is the bucket argument value. - Bucket string - // Object is the object argument value. - Object string - // UploadId is the uploadId argument value. - UploadId string - // Body is the Body argument value. - Body io.ReadSeeker - } // UploadPartCopy holds details about calls to the UploadPartCopy method. UploadPartCopy []struct { // UploadPartCopyInput is the uploadPartCopyInput argument value. @@ -499,7 +485,6 @@ type BackendMock struct { lockSetTags sync.RWMutex lockShutdown sync.RWMutex lockString sync.RWMutex - lockUploadPart sync.RWMutex lockUploadPartCopy sync.RWMutex } @@ -812,7 +797,7 @@ func (mock *BackendMock) DeleteObjectsCalls() []struct { } // GetBucketAcl calls GetBucketAclFunc. -func (mock *BackendMock) GetBucketAcl(bucket string) (*s3.GetBucketAclOutput, error) { +func (mock *BackendMock) GetBucketAcl(bucket string) (*auth.GetBucketAclOutput, error) { if mock.GetBucketAclFunc == nil { panic("BackendMock.GetBucketAclFunc: method is nil but Backend.GetBucketAcl was just called") } @@ -1271,19 +1256,21 @@ func (mock *BackendMock) ListObjectsV2Calls() []struct { } // PutBucket calls PutBucketFunc. -func (mock *BackendMock) PutBucket(bucket string) error { +func (mock *BackendMock) PutBucket(bucket string, owner string) error { if mock.PutBucketFunc == nil { panic("BackendMock.PutBucketFunc: method is nil but Backend.PutBucket was just called") } callInfo := struct { Bucket string + Owner string }{ Bucket: bucket, + Owner: owner, } mock.lockPutBucket.Lock() mock.calls.PutBucket = append(mock.calls.PutBucket, callInfo) mock.lockPutBucket.Unlock() - return mock.PutBucketFunc(bucket) + return mock.PutBucketFunc(bucket, owner) } // PutBucketCalls gets all the calls that were made to PutBucket. @@ -1292,9 +1279,11 @@ func (mock *BackendMock) PutBucket(bucket string) error { // len(mockedBackend.PutBucketCalls()) func (mock *BackendMock) PutBucketCalls() []struct { Bucket string + Owner string } { var calls []struct { Bucket string + Owner string } mock.lockPutBucket.RLock() calls = mock.calls.PutBucket @@ -1620,50 +1609,6 @@ func (mock *BackendMock) StringCalls() []struct { return calls } -// UploadPart calls UploadPartFunc. -func (mock *BackendMock) UploadPart(bucket string, object string, uploadId string, Body io.ReadSeeker) (*s3.UploadPartOutput, error) { - if mock.UploadPartFunc == nil { - panic("BackendMock.UploadPartFunc: method is nil but Backend.UploadPart was just called") - } - callInfo := struct { - Bucket string - Object string - UploadId string - Body io.ReadSeeker - }{ - Bucket: bucket, - Object: object, - UploadId: uploadId, - Body: Body, - } - mock.lockUploadPart.Lock() - mock.calls.UploadPart = append(mock.calls.UploadPart, callInfo) - mock.lockUploadPart.Unlock() - return mock.UploadPartFunc(bucket, object, uploadId, Body) -} - -// UploadPartCalls gets all the calls that were made to UploadPart. -// Check the length with: -// -// len(mockedBackend.UploadPartCalls()) -func (mock *BackendMock) UploadPartCalls() []struct { - Bucket string - Object string - UploadId string - Body io.ReadSeeker -} { - var calls []struct { - Bucket string - Object string - UploadId string - Body io.ReadSeeker - } - mock.lockUploadPart.RLock() - calls = mock.calls.UploadPart - mock.lockUploadPart.RUnlock() - return calls -} - // UploadPartCopy calls UploadPartCopyFunc. func (mock *BackendMock) UploadPartCopy(uploadPartCopyInput *s3.UploadPartCopyInput) (*s3.UploadPartCopyOutput, error) { if mock.UploadPartCopyFunc == nil { diff --git a/backend/backend_test.go b/backend/backend_test.go index 41c6dc07..62515f54 100644 --- a/backend/backend_test.go +++ b/backend/backend_test.go @@ -21,6 +21,7 @@ import ( "github.com/aws/aws-sdk-go-v2/aws" "github.com/aws/aws-sdk-go-v2/service/s3" "github.com/aws/aws-sdk-go-v2/service/s3/types" + "github.com/versity/versitygw/backend/auth" "github.com/versity/versitygw/s3err" ) @@ -120,7 +121,7 @@ func TestBackend_GetBucketAcl(t *testing.T) { tests = append(tests, test{ name: "get bucket acl error", c: &BackendMock{ - GetBucketAclFunc: func(bucket string) (*s3.GetBucketAclOutput, error) { + GetBucketAclFunc: func(bucket string) (*auth.GetBucketAclOutput, error) { return nil, s3err.GetAPIError(s3err.ErrNotImplemented) }, }, @@ -140,8 +141,9 @@ func TestBackend_GetBucketAcl(t *testing.T) { } func TestBackend_PutBucket(t *testing.T) { type args struct { - ctx context.Context - bucketName string + ctx context.Context + bucketName string + bucketOwner string } type test struct { name string @@ -153,31 +155,33 @@ func TestBackend_PutBucket(t *testing.T) { tests = append(tests, test{ name: "put bucket ", c: &BackendMock{ - PutBucketFunc: func(bucket string) error { + PutBucketFunc: func(bucket, owner string) error { return s3err.GetAPIError(0) }, }, args: args{ - ctx: context.Background(), - bucketName: "b1", + ctx: context.Background(), + bucketName: "b1", + bucketOwner: "owner", }, wantErr: false, }, test{ name: "put bucket error", c: &BackendMock{ - PutBucketFunc: func(bucket string) error { + PutBucketFunc: func(bucket, owner string) error { return s3err.GetAPIError(s3err.ErrNotImplemented) }, }, args: args{ - ctx: context.Background(), - bucketName: "b2", + ctx: context.Background(), + bucketName: "b2", + bucketOwner: "owner", }, wantErr: true, }) for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - if err := tt.c.PutBucket(tt.args.bucketName); (err.(s3err.APIError).Code != "") != tt.wantErr { + if err := tt.c.PutBucket(tt.args.bucketName, tt.args.bucketOwner); (err.(s3err.APIError).Code != "") != tt.wantErr { t.Errorf("Backend.PutBucket() error = %v, wantErr %v", err, tt.wantErr) } }) diff --git a/backend/posix/posix.go b/backend/posix/posix.go index 68cd269f..79690f9a 100644 --- a/backend/posix/posix.go +++ b/backend/posix/posix.go @@ -35,6 +35,7 @@ import ( "github.com/google/uuid" "github.com/pkg/xattr" "github.com/versity/versitygw/backend" + "github.com/versity/versitygw/backend/auth" "github.com/versity/versitygw/s3err" "github.com/versity/versitygw/s3response" ) @@ -121,7 +122,7 @@ func (p *Posix) HeadBucket(bucket string) (*s3.HeadBucketOutput, error) { return &s3.HeadBucketOutput{}, nil } -func (p *Posix) PutBucket(bucket string) error { +func (p *Posix) PutBucket(bucket string, owner string) error { err := os.Mkdir(bucket, 0777) if err != nil && os.IsExist(err) { return s3err.GetAPIError(s3err.ErrBucketAlreadyExists) @@ -130,6 +131,16 @@ func (p *Posix) PutBucket(bucket string) error { return fmt.Errorf("mkdir bucket: %w", err) } + acl := auth.ACL{ACL: "private", Owner: owner, Grantees: []auth.Grantee{}} + jsonACL, err := json.Marshal(acl) + if err != nil { + return fmt.Errorf("marshal acl: %w", err) + } + + if err := xattr.Set(bucket, "user.acl", jsonACL); err != nil { + return fmt.Errorf("set acl: %w", err) + } + return nil } @@ -1088,6 +1099,122 @@ func (p *Posix) ListObjectsV2(bucket, prefix, marker, delim string, maxkeys int) }, nil } +func (p *Posix) PutBucketAcl(input *s3.PutBucketAclInput) error { + var ACL auth.ACL + acl, err := xattr.Get(*input.Bucket, "user.acl") + if err != nil { + return fmt.Errorf("get acl: %w", err) + } + + if err := json.Unmarshal(acl, &ACL); err != nil { + return fmt.Errorf("parse acl: %w", err) + } + + if ACL.Owner != *input.AccessControlPolicy.Owner.ID { + return s3err.GetAPIError(s3err.ErrAccessDenied) + } + + grantees := []auth.Grantee{} + + fullControlList, readList, readACPList, writeList, writeACPList := []string{}, []string{}, []string{}, []string{}, []string{} + + if *input.GrantFullControl != "" { + fullControlList = strings.Split(*input.GrantFullControl, ",") + for _, str := range fullControlList { + grantees = append(grantees, auth.Grantee{Access: str, Permission: "FULL_CONTROL"}) + } + } + if *input.GrantRead != "" { + readList = strings.Split(*input.GrantRead, ",") + for _, str := range readList { + grantees = append(grantees, auth.Grantee{Access: str, Permission: "READ"}) + } + } + if *input.GrantReadACP != "" { + readACPList = strings.Split(*input.GrantReadACP, ",") + for _, str := range readACPList { + grantees = append(grantees, auth.Grantee{Access: str, Permission: "READ_ACP"}) + } + } + if *input.GrantWrite != "" { + writeList = strings.Split(*input.GrantWrite, ",") + for _, str := range writeList { + grantees = append(grantees, auth.Grantee{Access: str, Permission: "WRITE"}) + } + } + if *input.GrantWriteACP != "" { + writeACPList = strings.Split(*input.GrantWriteACP, ",") + for _, str := range writeACPList { + grantees = append(grantees, auth.Grantee{Access: str, Permission: "WRITE_ACP"}) + } + } + + accs := append(append(append(append(fullControlList, readList...), writeACPList...), readACPList...), writeList...) + + accList, err := checkIfAccountsExist(accs) + if err != nil { + return err + } + if len(accList) > 0 { + return fmt.Errorf("accounts does not exist: %s", strings.Join(accList, ", ")) + } + + for _, elem := range grantees { + doesContain := false + for _, grantee := range ACL.Grantees { + if elem == grantee { + doesContain = true + break + } + } + if !doesContain { + ACL.Grantees = append(ACL.Grantees, elem) + } + } + + if input.ACL != "" { + ACL.ACL = input.ACL + } + + ACLJson, err := json.Marshal(ACL) + if err != nil { + return fmt.Errorf("parsing error: %w", err) + } + + if err := xattr.Set(*input.Bucket, "user.acl", ACLJson); err != nil { + return fmt.Errorf("set acl: %w", err) + } + + return nil +} + +func (p *Posix) GetBucketAcl(bucket string) (*auth.GetBucketAclOutput, error) { + var ACL auth.ACL + acl, err := xattr.Get(bucket, "user.acl") + if err != nil { + return nil, fmt.Errorf("get acl: %w", err) + } + + if err := json.Unmarshal(acl, &ACL); err != nil { + return nil, fmt.Errorf("parse acl: %w", err) + } + + grants := []types.Grant{} + + for _, elem := range ACL.Grantees { + grants = append(grants, types.Grant{Grantee: &types.Grantee{ID: &elem.Access}, Permission: elem.Permission}) + } + + return &auth.GetBucketAclOutput{ + Owner: &types.Owner{ + ID: &ACL.Owner, + }, + AccessControlList: auth.AccessControlList{ + Grants: grants, + }, + }, nil +} + func (p *Posix) GetTags(bucket, object string) (map[string]string, error) { _, err := os.Stat(bucket) if errors.Is(err, fs.ErrNotExist) { @@ -1174,3 +1301,25 @@ func isNoAttr(err error) bool { } return false } + +func checkIfAccountsExist(accs []string) ([]string, error) { + var data auth.IAMConfig + result := []string{} + + file, err := os.ReadFile("users.json") + if err != nil { + return []string{}, fmt.Errorf("unable to read config file: %w", err) + } + + if err := json.Unmarshal(file, &data); err != nil { + return []string{}, err + } + + for _, acc := range accs { + _, ok := data.AccessAccounts[acc] + if !ok { + result = append(result, acc) + } + } + return result, nil +} diff --git a/s3api/controllers/backend_moq_test.go b/s3api/controllers/backend_moq_test.go index 4e6b425c..0d958dad 100644 --- a/s3api/controllers/backend_moq_test.go +++ b/s3api/controllers/backend_moq_test.go @@ -7,6 +7,7 @@ import ( "github.com/aws/aws-sdk-go-v2/service/s3" "github.com/aws/aws-sdk-go-v2/service/s3/types" "github.com/versity/versitygw/backend" + "github.com/versity/versitygw/backend/auth" "github.com/versity/versitygw/s3response" "io" "sync" @@ -46,7 +47,7 @@ var _ backend.Backend = &BackendMock{} // DeleteObjectsFunc: func(bucket string, objects *s3.DeleteObjectsInput) error { // panic("mock out the DeleteObjects method") // }, -// GetBucketAclFunc: func(bucket string) (*s3.GetBucketAclOutput, error) { +// GetBucketAclFunc: func(bucket string) (*auth.GetBucketAclOutput, error) { // panic("mock out the GetBucketAcl method") // }, // GetObjectFunc: func(bucket string, object string, acceptRange string, writer io.Writer) (*s3.GetObjectOutput, error) { @@ -82,7 +83,7 @@ var _ backend.Backend = &BackendMock{} // ListObjectsV2Func: func(bucket string, prefix string, marker string, delim string, maxkeys int) (*s3.ListObjectsV2Output, error) { // panic("mock out the ListObjectsV2 method") // }, -// PutBucketFunc: func(bucket string) error { +// PutBucketFunc: func(bucket string, owner string) error { // panic("mock out the PutBucket method") // }, // PutBucketAclFunc: func(putBucketAclInput *s3.PutBucketAclInput) error { @@ -112,9 +113,6 @@ var _ backend.Backend = &BackendMock{} // StringFunc: func() string { // panic("mock out the String method") // }, -// UploadPartFunc: func(bucket string, object string, uploadId string, Body io.ReadSeeker) (*s3.UploadPartOutput, error) { -// panic("mock out the UploadPart method") -// }, // UploadPartCopyFunc: func(uploadPartCopyInput *s3.UploadPartCopyInput) (*s3.UploadPartCopyOutput, error) { // panic("mock out the UploadPartCopy method") // }, @@ -150,7 +148,7 @@ type BackendMock struct { DeleteObjectsFunc func(bucket string, objects *s3.DeleteObjectsInput) error // GetBucketAclFunc mocks the GetBucketAcl method. - GetBucketAclFunc func(bucket string) (*s3.GetBucketAclOutput, error) + GetBucketAclFunc func(bucket string) (*auth.GetBucketAclOutput, error) // GetObjectFunc mocks the GetObject method. GetObjectFunc func(bucket string, object string, acceptRange string, writer io.Writer) (*s3.GetObjectOutput, error) @@ -186,7 +184,7 @@ type BackendMock struct { ListObjectsV2Func func(bucket string, prefix string, marker string, delim string, maxkeys int) (*s3.ListObjectsV2Output, error) // PutBucketFunc mocks the PutBucket method. - PutBucketFunc func(bucket string) error + PutBucketFunc func(bucket string, owner string) error // PutBucketAclFunc mocks the PutBucketAcl method. PutBucketAclFunc func(putBucketAclInput *s3.PutBucketAclInput) error @@ -215,9 +213,6 @@ type BackendMock struct { // StringFunc mocks the String method. StringFunc func() string - // UploadPartFunc mocks the UploadPart method. - UploadPartFunc func(bucket string, object string, uploadId string, Body io.ReadSeeker) (*s3.UploadPartOutput, error) - // UploadPartCopyFunc mocks the UploadPartCopy method. UploadPartCopyFunc func(uploadPartCopyInput *s3.UploadPartCopyInput) (*s3.UploadPartCopyOutput, error) @@ -391,6 +386,8 @@ type BackendMock struct { PutBucket []struct { // Bucket is the bucket argument value. Bucket string + // Owner is the owner argument value. + Owner string } // PutBucketAcl holds details about calls to the PutBucketAcl method. PutBucketAcl []struct { @@ -453,17 +450,6 @@ type BackendMock struct { // String holds details about calls to the String method. String []struct { } - // UploadPart holds details about calls to the UploadPart method. - UploadPart []struct { - // Bucket is the bucket argument value. - Bucket string - // Object is the object argument value. - Object string - // UploadId is the uploadId argument value. - UploadId string - // Body is the Body argument value. - Body io.ReadSeeker - } // UploadPartCopy holds details about calls to the UploadPartCopy method. UploadPartCopy []struct { // UploadPartCopyInput is the uploadPartCopyInput argument value. @@ -500,7 +486,6 @@ type BackendMock struct { lockSetTags sync.RWMutex lockShutdown sync.RWMutex lockString sync.RWMutex - lockUploadPart sync.RWMutex lockUploadPartCopy sync.RWMutex } @@ -813,7 +798,7 @@ func (mock *BackendMock) DeleteObjectsCalls() []struct { } // GetBucketAcl calls GetBucketAclFunc. -func (mock *BackendMock) GetBucketAcl(bucket string) (*s3.GetBucketAclOutput, error) { +func (mock *BackendMock) GetBucketAcl(bucket string) (*auth.GetBucketAclOutput, error) { if mock.GetBucketAclFunc == nil { panic("BackendMock.GetBucketAclFunc: method is nil but Backend.GetBucketAcl was just called") } @@ -1272,19 +1257,21 @@ func (mock *BackendMock) ListObjectsV2Calls() []struct { } // PutBucket calls PutBucketFunc. -func (mock *BackendMock) PutBucket(bucket string) error { +func (mock *BackendMock) PutBucket(bucket string, owner string) error { if mock.PutBucketFunc == nil { panic("BackendMock.PutBucketFunc: method is nil but Backend.PutBucket was just called") } callInfo := struct { Bucket string + Owner string }{ Bucket: bucket, + Owner: owner, } mock.lockPutBucket.Lock() mock.calls.PutBucket = append(mock.calls.PutBucket, callInfo) mock.lockPutBucket.Unlock() - return mock.PutBucketFunc(bucket) + return mock.PutBucketFunc(bucket, owner) } // PutBucketCalls gets all the calls that were made to PutBucket. @@ -1293,9 +1280,11 @@ func (mock *BackendMock) PutBucket(bucket string) error { // len(mockedBackend.PutBucketCalls()) func (mock *BackendMock) PutBucketCalls() []struct { Bucket string + Owner string } { var calls []struct { Bucket string + Owner string } mock.lockPutBucket.RLock() calls = mock.calls.PutBucket @@ -1621,50 +1610,6 @@ func (mock *BackendMock) StringCalls() []struct { return calls } -// UploadPart calls UploadPartFunc. -func (mock *BackendMock) UploadPart(bucket string, object string, uploadId string, Body io.ReadSeeker) (*s3.UploadPartOutput, error) { - if mock.UploadPartFunc == nil { - panic("BackendMock.UploadPartFunc: method is nil but Backend.UploadPart was just called") - } - callInfo := struct { - Bucket string - Object string - UploadId string - Body io.ReadSeeker - }{ - Bucket: bucket, - Object: object, - UploadId: uploadId, - Body: Body, - } - mock.lockUploadPart.Lock() - mock.calls.UploadPart = append(mock.calls.UploadPart, callInfo) - mock.lockUploadPart.Unlock() - return mock.UploadPartFunc(bucket, object, uploadId, Body) -} - -// UploadPartCalls gets all the calls that were made to UploadPart. -// Check the length with: -// -// len(mockedBackend.UploadPartCalls()) -func (mock *BackendMock) UploadPartCalls() []struct { - Bucket string - Object string - UploadId string - Body io.ReadSeeker -} { - var calls []struct { - Bucket string - Object string - UploadId string - Body io.ReadSeeker - } - mock.lockUploadPart.RLock() - calls = mock.calls.UploadPart - mock.lockUploadPart.RUnlock() - return calls -} - // UploadPartCopy calls UploadPartCopyFunc. func (mock *BackendMock) UploadPartCopy(uploadPartCopyInput *s3.UploadPartCopyInput) (*s3.UploadPartCopyOutput, error) { if mock.UploadPartCopyFunc == nil { diff --git a/s3api/controllers/base.go b/s3api/controllers/base.go index f488b5b0..b38c4432 100644 --- a/s3api/controllers/base.go +++ b/s3api/controllers/base.go @@ -152,15 +152,17 @@ func (c S3ApiController) ListActions(ctx *fiber.Ctx) error { } func (c S3ApiController) PutBucketActions(ctx *fiber.Ctx) error { - bucket, acl, grantFullControl, grantRead, grantReadACP, granWrite, grantWriteACP := + bucket, acl, grantFullControl, grantRead, grantReadACP, granWrite, grantWriteACP, access := ctx.Params("bucket"), ctx.Get("X-Amz-Acl"), ctx.Get("X-Amz-Grant-Full-Control"), ctx.Get("X-Amz-Grant-Read"), ctx.Get("X-Amz-Grant-Read-Acp"), ctx.Get("X-Amz-Grant-Write"), - ctx.Get("X-Amz-Grant-Write-Acp") + ctx.Get("X-Amz-Grant-Write-Acp"), + ctx.Locals("access") + owner := access.(string) grants := grantFullControl + grantRead + grantReadACP + granWrite + grantWriteACP if grants != "" || acl != "" { @@ -168,19 +170,20 @@ func (c S3ApiController) PutBucketActions(ctx *fiber.Ctx) error { return errors.New("wrong api call") } err := c.be.PutBucketAcl(&s3.PutBucketAclInput{ - Bucket: &bucket, - ACL: types.BucketCannedACL(acl), - GrantFullControl: &grantFullControl, - GrantRead: &grantRead, - GrantReadACP: &grantReadACP, - GrantWrite: &granWrite, - GrantWriteACP: &grantWriteACP, + Bucket: &bucket, + ACL: types.BucketCannedACL(acl), + GrantFullControl: &grantFullControl, + GrantRead: &grantRead, + GrantReadACP: &grantReadACP, + GrantWrite: &granWrite, + GrantWriteACP: &grantWriteACP, + AccessControlPolicy: &types.AccessControlPolicy{Owner: &types.Owner{ID: &owner}}, }) return SendResponse(ctx, err) } - err := c.be.PutBucket(bucket) + err := c.be.PutBucket(bucket, owner) return SendResponse(ctx, err) } diff --git a/s3api/controllers/base_test.go b/s3api/controllers/base_test.go index e88f6083..c92beadf 100644 --- a/s3api/controllers/base_test.go +++ b/s3api/controllers/base_test.go @@ -28,6 +28,7 @@ import ( "github.com/gofiber/fiber/v2" "github.com/valyala/fasthttp" "github.com/versity/versitygw/backend" + "github.com/versity/versitygw/backend/auth" "github.com/versity/versitygw/s3err" "github.com/versity/versitygw/s3response" ) @@ -231,8 +232,8 @@ func TestS3ApiController_ListActions(t *testing.T) { app := fiber.New() s3ApiController := S3ApiController{be: &BackendMock{ - GetBucketAclFunc: func(bucket string) (*s3.GetBucketAclOutput, error) { - return &s3.GetBucketAclOutput{}, nil + GetBucketAclFunc: func(bucket string) (*auth.GetBucketAclOutput, error) { + return nil, nil }, ListMultipartUploadsFunc: func(output *s3.ListMultipartUploadsInput) (s3response.ListMultipartUploadsResponse, error) { return s3response.ListMultipartUploadsResponse{}, nil @@ -333,10 +334,15 @@ func TestS3ApiController_PutBucketActions(t *testing.T) { PutBucketAclFunc: func(*s3.PutBucketAclInput) error { return nil }, - PutBucketFunc: func(bucket string) error { + PutBucketFunc: func(bucket, owner string) error { return nil }, }} + // Mock ctx.Locals + app.Use(func(ctx *fiber.Ctx) error { + ctx.Locals("access", "valid access") + return ctx.Next() + }) app.Put("/:bucket", s3ApiController.PutBucketActions) // Error case @@ -406,9 +412,6 @@ func TestS3ApiController_PutActions(t *testing.T) { UploadPartCopyFunc: func(*s3.UploadPartCopyInput) (*s3.UploadPartCopyOutput, error) { return &s3.UploadPartCopyOutput{}, nil }, - UploadPartFunc: func(bucket, object, uploadId string, Body io.ReadSeeker) (*s3.UploadPartOutput, error) { - return &s3.UploadPartOutput{}, nil - }, PutObjectAclFunc: func(*s3.PutObjectAclInput) error { return nil }, diff --git a/s3api/middlewares/authentication.go b/s3api/middlewares/authentication.go index 90f11951..c3faeee2 100644 --- a/s3api/middlewares/authentication.go +++ b/s3api/middlewares/authentication.go @@ -68,7 +68,7 @@ func VerifyV4Signature(root RootUserConfig, iam auth.IAMService, debug bool) fib return controllers.SendResponse(ctx, s3err.GetAPIError(s3err.ErrCredMalformed)) } - signHdrKv := strings.Split(authParts[2], "=") + signHdrKv := strings.Split(authParts[2][:len(authParts[2])-1], "=") if len(signHdrKv) != 2 { return controllers.SendResponse(ctx, s3err.GetAPIError(s3err.ErrCredMalformed)) } @@ -135,6 +135,7 @@ func VerifyV4Signature(root RootUserConfig, iam auth.IAMService, debug bool) fib } ctx.Locals("role", account.Role) + ctx.Locals("access", creds[0]) return ctx.Next() }