diff --git a/s3api/controllers/base.go b/s3api/controllers/base.go index 6f6861c..b771fc5 100644 --- a/s3api/controllers/base.go +++ b/s3api/controllers/base.go @@ -73,45 +73,6 @@ func New(be backend.Backend, iam auth.IAMService, logger s3log.AuditLogger, evs } } -func (c S3ApiController) ListBuckets(ctx *fiber.Ctx) error { - cToken := ctx.Query("continuation-token") - prefix := ctx.Query("prefix") - maxBucketsStr := ctx.Query("max-buckets") - acct := utils.ContextKeyAccount.Get(ctx).(auth.Account) - - var maxBuckets int32 = 10000 - if maxBucketsStr != "" { - maxBucketsParsed, err := strconv.ParseInt(maxBucketsStr, 10, 32) - if err != nil || maxBucketsParsed < 0 || maxBucketsParsed > 10000 { - if c.debug { - debuglogger.Logf("error parsing max-buckets %q: %v", maxBucketsStr, err) - } - return SendXMLResponse(ctx, nil, s3err.GetAPIError(s3err.ErrInvalidMaxBuckets), - &MetaOpts{ - Logger: c.logger, - MetricsMng: c.mm, - Action: metrics.ActionListAllMyBuckets, - }) - } - maxBuckets = int32(maxBucketsParsed) - } - - res, err := c.be.ListBuckets(ctx.Context(), - s3response.ListBucketsInput{ - Owner: acct.Access, - IsAdmin: acct.Role == auth.RoleAdmin, - MaxBuckets: int32(maxBuckets), - ContinuationToken: cToken, - Prefix: prefix, - }) - return SendXMLResponse(ctx, res, err, - &MetaOpts{ - Logger: c.logger, - MetricsMng: c.mm, - Action: metrics.ActionListAllMyBuckets, - }) -} - func (c S3ApiController) GetActions(ctx *fiber.Ctx) error { bucket := ctx.Params("bucket") key := ctx.Params("key") @@ -2258,174 +2219,6 @@ func (c S3ApiController) PutActions(ctx *fiber.Ctx) error { }) } -func (c S3ApiController) DeleteBucket(ctx *fiber.Ctx) error { - bucket := ctx.Params("bucket") - acct := utils.ContextKeyAccount.Get(ctx).(auth.Account) - isRoot := utils.ContextKeyIsRoot.Get(ctx).(bool) - parsedAcl := utils.ContextKeyParsedAcl.Get(ctx).(auth.ACL) - IsBucketPublic := utils.ContextKeyPublicBucket.IsSet(ctx) - - if ctx.Request().URI().QueryArgs().Has("tagging") { - err := auth.VerifyAccess(ctx.Context(), c.be, - auth.AccessOptions{ - Readonly: c.readonly, - Acl: parsedAcl, - AclPermission: auth.PermissionWrite, - IsRoot: isRoot, - Acc: acct, - Bucket: bucket, - Action: auth.PutBucketTaggingAction, - IsBucketPublic: IsBucketPublic, - }) - if err != nil { - return SendResponse(ctx, err, - &MetaOpts{ - Logger: c.logger, - MetricsMng: c.mm, - Action: metrics.ActionDeleteBucketTagging, - BucketOwner: parsedAcl.Owner, - }) - } - - err = c.be.DeleteBucketTagging(ctx.Context(), bucket) - return SendResponse(ctx, err, - &MetaOpts{ - Logger: c.logger, - MetricsMng: c.mm, - Action: metrics.ActionDeleteBucketTagging, - BucketOwner: parsedAcl.Owner, - Status: http.StatusNoContent, - }) - } - - if ctx.Request().URI().QueryArgs().Has("ownershipControls") { - err := auth.VerifyAccess(ctx.Context(), c.be, - auth.AccessOptions{ - Readonly: c.readonly, - Acl: parsedAcl, - AclPermission: auth.PermissionWrite, - IsRoot: isRoot, - Acc: acct, - Bucket: bucket, - Action: auth.PutBucketOwnershipControlsAction, - }) - if err != nil { - return SendResponse(ctx, err, - &MetaOpts{ - Logger: c.logger, - MetricsMng: c.mm, - Action: metrics.ActionDeleteBucketOwnershipControls, - BucketOwner: parsedAcl.Owner, - }) - } - - err = c.be.DeleteBucketOwnershipControls(ctx.Context(), bucket) - return SendResponse(ctx, err, - &MetaOpts{ - Logger: c.logger, - MetricsMng: c.mm, - Action: metrics.ActionDeleteBucketOwnershipControls, - BucketOwner: parsedAcl.Owner, - Status: http.StatusNoContent, - }) - } - - if ctx.Request().URI().QueryArgs().Has("policy") { - err := auth.VerifyAccess(ctx.Context(), c.be, - auth.AccessOptions{ - Readonly: c.readonly, - Acl: parsedAcl, - AclPermission: auth.PermissionWrite, - IsRoot: isRoot, - Acc: acct, - Bucket: bucket, - Action: auth.DeleteBucketPolicyAction, - }) - if err != nil { - return SendResponse(ctx, err, - &MetaOpts{ - Logger: c.logger, - MetricsMng: c.mm, - Action: metrics.ActionDeleteBucketPolicy, - BucketOwner: parsedAcl.Owner, - }) - } - - err = c.be.DeleteBucketPolicy(ctx.Context(), bucket) - return SendResponse(ctx, err, - &MetaOpts{ - Logger: c.logger, - MetricsMng: c.mm, - Action: metrics.ActionDeleteBucketPolicy, - BucketOwner: parsedAcl.Owner, - Status: http.StatusNoContent, - }) - } - - if ctx.Request().URI().QueryArgs().Has("cors") { - err := auth.VerifyAccess(ctx.Context(), c.be, - auth.AccessOptions{ - Readonly: c.readonly, - Acl: parsedAcl, - AclPermission: auth.PermissionWrite, - IsRoot: isRoot, - Acc: acct, - Bucket: bucket, - Action: auth.PutBucketCorsAction, - IsBucketPublic: IsBucketPublic, - }) - if err != nil { - return SendResponse(ctx, err, - &MetaOpts{ - Logger: c.logger, - MetricsMng: c.mm, - Action: metrics.ActionDeleteBucketCors, - BucketOwner: parsedAcl.Owner, - }) - } - - err = c.be.DeleteBucketCors(ctx.Context(), bucket) - return SendResponse(ctx, err, - &MetaOpts{ - Logger: c.logger, - MetricsMng: c.mm, - Action: metrics.ActionDeleteBucketCors, - BucketOwner: parsedAcl.Owner, - }) - } - - err := auth.VerifyAccess(ctx.Context(), c.be, - auth.AccessOptions{ - Readonly: c.readonly, - Acl: parsedAcl, - AclPermission: auth.PermissionWrite, - IsRoot: isRoot, - Acc: acct, - Bucket: bucket, - Action: auth.DeleteBucketAction, - IsBucketPublic: IsBucketPublic, - }) - if err != nil { - return SendResponse(ctx, err, - &MetaOpts{ - Logger: c.logger, - MetricsMng: c.mm, - Action: metrics.ActionDeleteBucket, - BucketOwner: parsedAcl.Owner, - }) - } - - err = c.be.DeleteBucket(ctx.Context(), bucket) - return SendResponse(ctx, err, - &MetaOpts{ - Logger: c.logger, - MetricsMng: c.mm, - Action: metrics.ActionDeleteBucket, - BucketOwner: parsedAcl.Owner, - Status: http.StatusNoContent, - }) -} - func (c S3ApiController) DeleteObjects(ctx *fiber.Ctx) error { bucket := ctx.Params("bucket") acct := utils.ContextKeyAccount.Get(ctx).(auth.Account) @@ -2688,7 +2481,7 @@ func (c S3ApiController) DeleteActions(ctx *fiber.Ctx) error { }) } -func (c S3ApiController) HeadBucket(ctx *fiber.Ctx) error { +func (c S3ApiController) HeadBuckets(ctx *fiber.Ctx) error { bucket := ctx.Params("bucket") acct := utils.ContextKeyAccount.Get(ctx).(auth.Account) isRoot := utils.ContextKeyIsRoot.Get(ctx).(bool) diff --git a/s3api/controllers/base_test.go b/s3api/controllers/base_test.go index 25e57be..d85fbc9 100644 --- a/s3api/controllers/base_test.go +++ b/s3api/controllers/base_test.go @@ -85,83 +85,6 @@ func TestNew(t *testing.T) { } } -func TestS3ApiController_ListBuckets(t *testing.T) { - type args struct { - req *http.Request - } - - app := fiber.New() - s3ApiController := S3ApiController{ - be: &BackendMock{ - ListBucketsFunc: func(contextMoqParam context.Context, listBucketsInput s3response.ListBucketsInput) (s3response.ListAllMyBucketsResult, error) { - return s3response.ListAllMyBucketsResult{}, nil - }, - }, - } - - app.Use(func(ctx *fiber.Ctx) error { - utils.ContextKeyAccount.Set(ctx, auth.Account{Access: "valid access", Role: "admin:"}) - return ctx.Next() - }) - app.Get("/", s3ApiController.ListBuckets) - - // Error case - appErr := fiber.New() - s3ApiControllerErr := S3ApiController{ - be: &BackendMock{ - ListBucketsFunc: func(contextMoqParam context.Context, listBucketsInput s3response.ListBucketsInput) (s3response.ListAllMyBucketsResult, error) { - return s3response.ListAllMyBucketsResult{}, s3err.GetAPIError(s3err.ErrMethodNotAllowed) - }, - }, - } - - appErr.Use(func(ctx *fiber.Ctx) error { - utils.ContextKeyAccount.Set(ctx, auth.Account{Access: "valid access", Role: "admin:"}) - return ctx.Next() - }) - appErr.Get("/", s3ApiControllerErr.ListBuckets) - - tests := []struct { - name string - args args - app *fiber.App - wantErr bool - statusCode int - }{ - { - name: "List-bucket-method-not-allowed", - args: args{ - req: httptest.NewRequest(http.MethodGet, "/", nil), - }, - app: appErr, - wantErr: false, - statusCode: 405, - }, - { - name: "list-bucket-success", - args: args{ - req: httptest.NewRequest(http.MethodGet, "/", nil), - }, - app: app, - wantErr: false, - statusCode: 200, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - resp, err := tt.app.Test(tt.args.req) - - if (err != nil) != tt.wantErr { - t.Errorf("S3ApiController.ListBuckets() error = %v, wantErr %v", err, tt.wantErr) - } - - if resp.StatusCode != tt.statusCode { - t.Errorf("S3ApiController.ListBuckets() statusCode = %v, wantStatusCode = %v", resp.StatusCode, tt.statusCode) - } - }) - } -} - func getPtr(val string) *string { return &val } @@ -1044,94 +967,6 @@ func TestS3ApiController_PutActions(t *testing.T) { } } -func TestS3ApiController_DeleteBucket(t *testing.T) { - type args struct { - req *http.Request - } - - app := fiber.New() - s3ApiController := S3ApiController{ - be: &BackendMock{ - DeleteBucketFunc: func(_ context.Context, bucket string) error { - return nil - }, - DeleteBucketTaggingFunc: func(contextMoqParam context.Context, bucket string) error { - return nil - }, - DeleteBucketPolicyFunc: func(contextMoqParam context.Context, bucket string) error { - return nil - }, - DeleteBucketOwnershipControlsFunc: func(contextMoqParam context.Context, bucket string) error { - return nil - }, - }, - } - - app.Use(func(ctx *fiber.Ctx) error { - utils.ContextKeyAccount.Set(ctx, auth.Account{Access: "valid access"}) - utils.ContextKeyIsRoot.Set(ctx, true) - utils.ContextKeyParsedAcl.Set(ctx, auth.ACL{}) - return ctx.Next() - }) - - app.Delete("/:bucket", s3ApiController.DeleteBucket) - - tests := []struct { - name string - app *fiber.App - args args - wantErr bool - statusCode int - }{ - { - name: "Delete-bucket-success", - app: app, - args: args{ - req: httptest.NewRequest(http.MethodDelete, "/my-bucket", nil), - }, - wantErr: false, - statusCode: 204, - }, - { - name: "Delete-bucket-tagging-success", - app: app, - args: args{ - req: httptest.NewRequest(http.MethodDelete, "/my-bucket?tagging", nil), - }, - wantErr: false, - statusCode: 204, - }, - { - name: "Delete-bucket-ownership-controls-success", - app: app, - args: args{ - req: httptest.NewRequest(http.MethodDelete, "/my-bucket?ownershipControls", nil), - }, - wantErr: false, - statusCode: 204, - }, { - name: "Delete-bucket-policy-success", - app: app, - args: args{ - req: httptest.NewRequest(http.MethodDelete, "/my-bucket?policy", nil), - }, - wantErr: false, - statusCode: 204, - }, - } - for _, tt := range tests { - resp, err := tt.app.Test(tt.args.req) - - if (err != nil) != tt.wantErr { - t.Errorf("S3ApiController.DeleteBucket() error = %v, wantErr %v", err, tt.wantErr) - } - - if resp.StatusCode != tt.statusCode { - t.Errorf("S3ApiController.DeleteBucket() statusCode = %v, wantStatusCode = %v", resp.StatusCode, tt.statusCode) - } - } -} - func TestS3ApiController_DeleteObjects(t *testing.T) { type args struct { req *http.Request @@ -1319,95 +1154,6 @@ func TestS3ApiController_DeleteActions(t *testing.T) { } } -func TestS3ApiController_HeadBucket(t *testing.T) { - type args struct { - req *http.Request - } - - app := fiber.New() - s3ApiController := S3ApiController{ - be: &BackendMock{ - GetBucketAclFunc: func(context.Context, *s3.GetBucketAclInput) ([]byte, error) { - return acldata, nil - }, - HeadBucketFunc: func(context.Context, *s3.HeadBucketInput) (*s3.HeadBucketOutput, error) { - return &s3.HeadBucketOutput{}, nil - }, - }, - } - - app.Use(func(ctx *fiber.Ctx) error { - utils.ContextKeyAccount.Set(ctx, auth.Account{Access: "valid access"}) - utils.ContextKeyIsRoot.Set(ctx, true) - utils.ContextKeyParsedAcl.Set(ctx, auth.ACL{}) - utils.ContextKeyRegion.Set(ctx, "us-east-1") - return ctx.Next() - }) - - app.Head("/:bucket", s3ApiController.HeadBucket) - - // Error case - appErr := fiber.New() - - s3ApiControllerErr := S3ApiController{be: &BackendMock{ - GetBucketAclFunc: func(context.Context, *s3.GetBucketAclInput) ([]byte, error) { - return acldata, nil - }, - HeadBucketFunc: func(context.Context, *s3.HeadBucketInput) (*s3.HeadBucketOutput, error) { - return nil, s3err.GetAPIError(s3err.ErrBucketNotEmpty) - }, - }, - } - - appErr.Use(func(ctx *fiber.Ctx) error { - utils.ContextKeyAccount.Set(ctx, auth.Account{Access: "valid access"}) - utils.ContextKeyIsRoot.Set(ctx, true) - utils.ContextKeyParsedAcl.Set(ctx, auth.ACL{}) - utils.ContextKeyRegion.Set(ctx, "us-east-1") - return ctx.Next() - }) - - appErr.Head("/:bucket", s3ApiControllerErr.HeadBucket) - - tests := []struct { - name string - app *fiber.App - args args - wantErr bool - statusCode int - }{ - { - name: "Head-bucket-success", - app: app, - args: args{ - req: httptest.NewRequest(http.MethodHead, "/my-bucket", nil), - }, - wantErr: false, - statusCode: 200, - }, - { - name: "Head-bucket-error", - app: appErr, - args: args{ - req: httptest.NewRequest(http.MethodHead, "/my-bucket", nil), - }, - wantErr: false, - statusCode: 409, - }, - } - for _, tt := range tests { - resp, err := tt.app.Test(tt.args.req) - - if (err != nil) != tt.wantErr { - t.Errorf("S3ApiController.HeadBucket() error = %v, wantErr %v", err, tt.wantErr) - } - - if resp.StatusCode != tt.statusCode { - t.Errorf("S3ApiController.HeadBucket() statusCode = %v, wantStatusCode = %v", resp.StatusCode, tt.statusCode) - } - } -} - func TestS3ApiController_HeadObject(t *testing.T) { type args struct { req *http.Request diff --git a/s3api/controllers/bucket-delete.go b/s3api/controllers/bucket-delete.go new file mode 100644 index 0000000..59a49f6 --- /dev/null +++ b/s3api/controllers/bucket-delete.go @@ -0,0 +1,204 @@ +// 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 controllers + +import ( + "net/http" + + "github.com/gofiber/fiber/v2" + "github.com/versity/versitygw/auth" + "github.com/versity/versitygw/metrics" + "github.com/versity/versitygw/s3api/utils" +) + +func (c S3ApiController) DeleteBucketTagging(ctx *fiber.Ctx) (*Response, error) { + bucket := ctx.Params("bucket") + acct := utils.ContextKeyAccount.Get(ctx).(auth.Account) + isRoot := utils.ContextKeyIsRoot.Get(ctx).(bool) + parsedAcl := utils.ContextKeyParsedAcl.Get(ctx).(auth.ACL) + IsBucketPublic := utils.ContextKeyPublicBucket.IsSet(ctx) + + err := auth.VerifyAccess(ctx.Context(), c.be, + auth.AccessOptions{ + Readonly: c.readonly, + Acl: parsedAcl, + AclPermission: auth.PermissionWrite, + IsRoot: isRoot, + Acc: acct, + Bucket: bucket, + Action: auth.PutBucketTaggingAction, + IsBucketPublic: IsBucketPublic, + }) + if err != nil { + return &Response{ + MetaOpts: &MetaOptions{ + Action: metrics.ActionDeleteBucketTagging, + BucketOwner: parsedAcl.Owner, + }, + }, err + } + + err = c.be.DeleteBucketTagging(ctx.Context(), bucket) + return &Response{ + MetaOpts: &MetaOptions{ + Action: metrics.ActionDeleteBucketTagging, + BucketOwner: parsedAcl.Owner, + Status: http.StatusNoContent, + }, + }, err +} + +func (c S3ApiController) DeleteBucketOwnershipControls(ctx *fiber.Ctx) (*Response, error) { + bucket := ctx.Params("bucket") + acct := utils.ContextKeyAccount.Get(ctx).(auth.Account) + isRoot := utils.ContextKeyIsRoot.Get(ctx).(bool) + parsedAcl := utils.ContextKeyParsedAcl.Get(ctx).(auth.ACL) + + err := auth.VerifyAccess(ctx.Context(), c.be, + auth.AccessOptions{ + Readonly: c.readonly, + Acl: parsedAcl, + AclPermission: auth.PermissionWrite, + IsRoot: isRoot, + Acc: acct, + Bucket: bucket, + Action: auth.PutBucketOwnershipControlsAction, + }) + if err != nil { + return &Response{ + MetaOpts: &MetaOptions{ + Action: metrics.ActionDeleteBucketOwnershipControls, + BucketOwner: parsedAcl.Owner, + }, + }, err + } + + err = c.be.DeleteBucketOwnershipControls(ctx.Context(), bucket) + return &Response{ + MetaOpts: &MetaOptions{ + Action: metrics.ActionDeleteBucketOwnershipControls, + BucketOwner: parsedAcl.Owner, + Status: http.StatusNoContent, + }, + }, err +} + +func (c S3ApiController) DeleteBucketPolicy(ctx *fiber.Ctx) (*Response, error) { + bucket := ctx.Params("bucket") + acct := utils.ContextKeyAccount.Get(ctx).(auth.Account) + isRoot := utils.ContextKeyIsRoot.Get(ctx).(bool) + parsedAcl := utils.ContextKeyParsedAcl.Get(ctx).(auth.ACL) + + err := auth.VerifyAccess(ctx.Context(), c.be, + auth.AccessOptions{ + Readonly: c.readonly, + Acl: parsedAcl, + AclPermission: auth.PermissionWrite, + IsRoot: isRoot, + Acc: acct, + Bucket: bucket, + Action: auth.DeleteBucketPolicyAction, + }) + if err != nil { + return &Response{ + MetaOpts: &MetaOptions{ + Action: metrics.ActionDeleteBucketPolicy, + BucketOwner: parsedAcl.Owner, + }, + }, err + } + + err = c.be.DeleteBucketPolicy(ctx.Context(), bucket) + return &Response{ + MetaOpts: &MetaOptions{ + Action: metrics.ActionDeleteBucketPolicy, + BucketOwner: parsedAcl.Owner, + Status: http.StatusNoContent, + }, + }, err +} + +func (c S3ApiController) DeleteBucketCors(ctx *fiber.Ctx) (*Response, error) { + bucket := ctx.Params("bucket") + acct := utils.ContextKeyAccount.Get(ctx).(auth.Account) + isRoot := utils.ContextKeyIsRoot.Get(ctx).(bool) + parsedAcl := utils.ContextKeyParsedAcl.Get(ctx).(auth.ACL) + IsBucketPublic := utils.ContextKeyPublicBucket.IsSet(ctx) + + err := auth.VerifyAccess(ctx.Context(), c.be, + auth.AccessOptions{ + Readonly: c.readonly, + Acl: parsedAcl, + AclPermission: auth.PermissionWrite, + IsRoot: isRoot, + Acc: acct, + Bucket: bucket, + Action: auth.PutBucketCorsAction, + IsBucketPublic: IsBucketPublic, + }) + if err != nil { + return &Response{ + MetaOpts: &MetaOptions{ + Action: metrics.ActionDeleteBucketCors, + BucketOwner: parsedAcl.Owner, + }, + }, err + } + + err = c.be.DeleteBucketCors(ctx.Context(), bucket) + return &Response{ + MetaOpts: &MetaOptions{ + Action: metrics.ActionDeleteBucketCors, + BucketOwner: parsedAcl.Owner, + }, + }, err +} + +func (c S3ApiController) DeleteBucket(ctx *fiber.Ctx) (*Response, error) { + bucket := ctx.Params("bucket") + acct := utils.ContextKeyAccount.Get(ctx).(auth.Account) + isRoot := utils.ContextKeyIsRoot.Get(ctx).(bool) + parsedAcl := utils.ContextKeyParsedAcl.Get(ctx).(auth.ACL) + IsBucketPublic := utils.ContextKeyPublicBucket.IsSet(ctx) + + err := auth.VerifyAccess(ctx.Context(), c.be, + auth.AccessOptions{ + Readonly: c.readonly, + Acl: parsedAcl, + AclPermission: auth.PermissionWrite, + IsRoot: isRoot, + Acc: acct, + Bucket: bucket, + Action: auth.DeleteBucketAction, + IsBucketPublic: IsBucketPublic, + }) + if err != nil { + return &Response{ + MetaOpts: &MetaOptions{ + Action: metrics.ActionDeleteBucket, + BucketOwner: parsedAcl.Owner, + }, + }, err + } + + err = c.be.DeleteBucket(ctx.Context(), bucket) + return &Response{ + MetaOpts: &MetaOptions{ + Action: metrics.ActionDeleteBucket, + BucketOwner: parsedAcl.Owner, + Status: http.StatusNoContent, + }, + }, err +} diff --git a/s3api/controllers/bucket-head.go b/s3api/controllers/bucket-head.go new file mode 100644 index 0000000..322d01e --- /dev/null +++ b/s3api/controllers/bucket-head.go @@ -0,0 +1,77 @@ +// 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 controllers + +import ( + "github.com/aws/aws-sdk-go-v2/service/s3" + "github.com/gofiber/fiber/v2" + "github.com/versity/versitygw/auth" + "github.com/versity/versitygw/metrics" + "github.com/versity/versitygw/s3api/utils" +) + +func (c S3ApiController) HeadBucket(ctx *fiber.Ctx) (*Response, error) { + bucket := ctx.Params("bucket") + acct := utils.ContextKeyAccount.Get(ctx).(auth.Account) + isRoot := utils.ContextKeyIsRoot.Get(ctx).(bool) + region := utils.ContextKeyRegion.Get(ctx).(string) + parsedAcl := utils.ContextKeyParsedAcl.Get(ctx).(auth.ACL) + isPublicBucket := utils.ContextKeyPublicBucket.IsSet(ctx) + + err := auth.VerifyAccess(ctx.Context(), c.be, + auth.AccessOptions{ + Readonly: c.readonly, + Acl: parsedAcl, + AclPermission: auth.PermissionRead, + IsRoot: isRoot, + Acc: acct, + Bucket: bucket, + Action: auth.ListBucketAction, + IsBucketPublic: isPublicBucket, + }) + if err != nil { + return &Response{ + MetaOpts: &MetaOptions{ + Action: metrics.ActionHeadBucket, + BucketOwner: parsedAcl.Owner, + }, + }, err + } + + _, err = c.be.HeadBucket(ctx.Context(), + &s3.HeadBucketInput{ + Bucket: &bucket, + }) + + if err != nil { + return &Response{ + MetaOpts: &MetaOptions{ + Action: metrics.ActionHeadBucket, + BucketOwner: parsedAcl.Owner, + }, + }, err + } + + return &Response{ + Headers: map[string]string{ + "X-Amz-Access-Point-Alias": "false", + "X-Amz-Bucket-Region": region, + }, + MetaOpts: &MetaOptions{ + Action: metrics.ActionHeadBucket, + BucketOwner: parsedAcl.Owner, + }, + }, nil +} diff --git a/s3api/controllers/bucket-list.go b/s3api/controllers/bucket-list.go new file mode 100644 index 0000000..9dd649b --- /dev/null +++ b/s3api/controllers/bucket-list.go @@ -0,0 +1,50 @@ +package controllers + +import ( + "strconv" + + "github.com/gofiber/fiber/v2" + "github.com/versity/versitygw/auth" + "github.com/versity/versitygw/metrics" + "github.com/versity/versitygw/s3api/debuglogger" + "github.com/versity/versitygw/s3api/utils" + "github.com/versity/versitygw/s3err" + "github.com/versity/versitygw/s3response" +) + +func (c S3ApiController) ListBuckets(ctx *fiber.Ctx) (*Response, error) { + cToken := ctx.Query("continuation-token") + prefix := ctx.Query("prefix") + maxBucketsStr := ctx.Query("max-buckets") + + acct := utils.ContextKeyAccount.Get(ctx).(auth.Account) + + var maxBuckets int32 = 10000 + if maxBucketsStr != "" { + maxBucketsParsed, err := strconv.ParseInt(maxBucketsStr, 10, 32) + if err != nil || maxBucketsParsed < 0 || maxBucketsParsed > 10000 { + debuglogger.Logf("error parsing max-buckets %q: %v", maxBucketsStr, err) + return &Response{ + MetaOpts: &MetaOptions{ + Action: metrics.ActionListAllMyBuckets, + }, + }, s3err.GetAPIError(s3err.ErrInvalidMaxBuckets) + } + maxBuckets = int32(maxBucketsParsed) + } + + res, err := c.be.ListBuckets(ctx.Context(), + s3response.ListBucketsInput{ + Owner: acct.Access, + IsAdmin: acct.Role == auth.RoleAdmin, + MaxBuckets: int32(maxBuckets), + ContinuationToken: cToken, + Prefix: prefix, + }) + return &Response{ + Data: res, + MetaOpts: &MetaOptions{ + Action: metrics.ActionListAllMyBuckets, + }, + }, err +} diff --git a/s3api/router.go b/s3api/router.go index 0a60029..e6150dd 100644 --- a/s3api/router.go +++ b/s3api/router.go @@ -30,7 +30,7 @@ type S3ApiRouter struct { } func (sa *S3ApiRouter) Init(app *fiber.App, be backend.Backend, iam auth.IAMService, logger s3log.AuditLogger, aLogger s3log.AuditLogger, evs s3event.S3EventSender, mm *metrics.Manager, debug bool, readonly bool) { - s3ApiController := controllers.New(be, iam, logger, evs, mm, debug, readonly) + ctrl := controllers.New(be, iam, logger, evs, mm, debug, readonly) if sa.WithAdmSrv { adminController := controllers.NewAdminController(iam, be, aLogger) @@ -54,33 +54,36 @@ func (sa *S3ApiRouter) Init(app *fiber.App, be backend.Backend, iam auth.IAMServ app.Patch("/list-buckets", middlewares.IsAdmin(logger), adminController.ListBuckets) } - // ListBuckets action - app.Get("/", s3ApiController.ListBuckets) + app.Get("/", controllers.ProcessResponse(ctrl.ListBuckets, logger, evs, mm)) - // CreateBucket action - // PutBucketAcl action - app.Put("/:bucket", s3ApiController.PutBucketActions) - - // DeleteBucket action - app.Delete("/:bucket", s3ApiController.DeleteBucket) + // Put bucket operations + app.Put("/:bucket", ctrl.PutBucketActions) // HeadBucket - app.Head("/:bucket", s3ApiController.HeadBucket) + app.Head("/:bucket", controllers.ProcessResponse(ctrl.HeadBucket, logger, evs, mm)) - app.Get("/:bucket", middlewares.MatchQueryArgs("tagging"), controllers.ProcessResponse(s3ApiController.GetBucketTagging, logger, evs, mm)) - app.Get("/:bucket", middlewares.MatchQueryArgs("ownershipControls"), controllers.ProcessResponse(s3ApiController.GetBucketOwnershipControls, logger, evs, mm)) - app.Get("/:bucket", middlewares.MatchQueryArgs("versioning"), controllers.ProcessResponse(s3ApiController.GetBucketVersioning, logger, evs, mm)) - app.Get("/:bucket", middlewares.MatchQueryArgs("policy"), controllers.ProcessResponse(s3ApiController.GetBucketPolicy, logger, evs, mm)) - app.Get("/:bucket", middlewares.MatchQueryArgs("cors"), controllers.ProcessResponse(s3ApiController.GetBucketCors, logger, evs, mm)) - app.Get("/:bucket", middlewares.MatchQueryArgs("object-lock"), controllers.ProcessResponse(s3ApiController.GetObjectLockConfiguration, logger, evs, mm)) - app.Get("/:bucket", middlewares.MatchQueryArgs("acl"), controllers.ProcessResponse(s3ApiController.GetBucketAcl, logger, evs, mm)) - app.Get("/:bucket", middlewares.MatchQueryArgs("uploads"), controllers.ProcessResponse(s3ApiController.ListMultipartUploads, logger, evs, mm)) - app.Get("/:bucket", middlewares.MatchQueryArgs("versions"), controllers.ProcessResponse(s3ApiController.ListObjectVersions, logger, evs, mm)) - app.Get("/:bucket", middlewares.MatchQueryArgWithValue("list-type", "2"), controllers.ProcessResponse(s3ApiController.ListObjectsV2, logger, evs, mm)) - app.Get("/:bucket", controllers.ProcessResponse(s3ApiController.ListObjects, logger, evs, mm)) + // Delete bucket operations + app.Delete("/:bucket", middlewares.MatchQueryArgs("tagging"), controllers.ProcessResponse(ctrl.DeleteBucketTagging, logger, evs, mm)) + app.Delete("/:bucket", middlewares.MatchQueryArgs("ownershipControls"), controllers.ProcessResponse(ctrl.DeleteBucketOwnershipControls, logger, evs, mm)) + app.Delete("/:bucket", middlewares.MatchQueryArgs("policy"), controllers.ProcessResponse(ctrl.DeleteBucketPolicy, logger, evs, mm)) + app.Delete("/:bucket", middlewares.MatchQueryArgs("cors"), controllers.ProcessResponse(ctrl.DeleteBucketCors, logger, evs, mm)) + app.Delete("/:bucket", controllers.ProcessResponse(ctrl.DeleteBucket, logger, evs, mm)) + + // Get bucket operations + app.Get("/:bucket", middlewares.MatchQueryArgs("tagging"), controllers.ProcessResponse(ctrl.GetBucketTagging, logger, evs, mm)) + app.Get("/:bucket", middlewares.MatchQueryArgs("ownershipControls"), controllers.ProcessResponse(ctrl.GetBucketOwnershipControls, logger, evs, mm)) + app.Get("/:bucket", middlewares.MatchQueryArgs("versioning"), controllers.ProcessResponse(ctrl.GetBucketVersioning, logger, evs, mm)) + app.Get("/:bucket", middlewares.MatchQueryArgs("policy"), controllers.ProcessResponse(ctrl.GetBucketPolicy, logger, evs, mm)) + app.Get("/:bucket", middlewares.MatchQueryArgs("cors"), controllers.ProcessResponse(ctrl.GetBucketCors, logger, evs, mm)) + app.Get("/:bucket", middlewares.MatchQueryArgs("object-lock"), controllers.ProcessResponse(ctrl.GetObjectLockConfiguration, logger, evs, mm)) + app.Get("/:bucket", middlewares.MatchQueryArgs("acl"), controllers.ProcessResponse(ctrl.GetBucketAcl, logger, evs, mm)) + app.Get("/:bucket", middlewares.MatchQueryArgs("uploads"), controllers.ProcessResponse(ctrl.ListMultipartUploads, logger, evs, mm)) + app.Get("/:bucket", middlewares.MatchQueryArgs("versions"), controllers.ProcessResponse(ctrl.ListObjectVersions, logger, evs, mm)) + app.Get("/:bucket", middlewares.MatchQueryArgWithValue("list-type", "2"), controllers.ProcessResponse(ctrl.ListObjectsV2, logger, evs, mm)) + app.Get("/:bucket", controllers.ProcessResponse(ctrl.ListObjects, logger, evs, mm)) // HeadObject action - app.Head("/:bucket/:key/*", s3ApiController.HeadObject) + app.Head("/:bucket/:key/*", ctrl.HeadObject) // GetObjectAcl action // GetObject action @@ -88,21 +91,21 @@ func (sa *S3ApiRouter) Init(app *fiber.App, be backend.Backend, iam auth.IAMServ // GetObjectTagging action // ListParts action // GetObjectAttributes action - app.Get("/:bucket/:key/*", s3ApiController.GetActions) + app.Get("/:bucket/:key/*", ctrl.GetActions) // DeleteObject action // AbortMultipartUpload action // DeleteObjectTagging action - app.Delete("/:bucket/:key/*", s3ApiController.DeleteActions) + app.Delete("/:bucket/:key/*", ctrl.DeleteActions) // DeleteObjects action - app.Post("/:bucket", s3ApiController.DeleteObjects) + app.Post("/:bucket", ctrl.DeleteObjects) // CompleteMultipartUpload action // CreateMultipartUpload // RestoreObject action // SelectObjectContent action - app.Post("/:bucket/:key/*", s3ApiController.CreateActions) + app.Post("/:bucket/:key/*", ctrl.CreateActions) // CopyObject action // PutObject action @@ -110,5 +113,5 @@ func (sa *S3ApiRouter) Init(app *fiber.App, be backend.Backend, iam auth.IAMServ // UploadPartCopy action // PutObjectTagging action // PutObjectAcl action - app.Put("/:bucket/:key/*", s3ApiController.PutActions) + app.Put("/:bucket/:key/*", ctrl.PutActions) }