feat: ACL set up finished: added VerifyACL function, added admin checker function on list buckets, fixed all the unit tests

This commit is contained in:
jonaustin09
2023-06-16 20:55:23 +04:00
parent 3d7ce4210a
commit 49af6f0049
5 changed files with 548 additions and 193 deletions

View File

@@ -14,7 +14,15 @@
package auth
import "github.com/aws/aws-sdk-go-v2/service/s3/types"
import (
"encoding/json"
"fmt"
"os"
"github.com/aws/aws-sdk-go-v2/service/s3/types"
"github.com/pkg/xattr"
"github.com/versity/versitygw/s3err"
)
type ACL struct {
ACL types.BucketCannedACL
@@ -35,3 +43,88 @@ type GetBucketAclOutput struct {
type AccessControlList struct {
Grants []types.Grant
}
type ACLService interface {
VerifyACL(bucket, access string, permission types.Permission, isRoot bool) error
IsAdmin(access string, isRoot bool) error
}
type ACLServiceUnsupported struct{}
var _ ACLService = &ACLServiceUnsupported{}
func (ACLServiceUnsupported) VerifyACL(bucket, access string, permission types.Permission, isRoot bool) error {
var ACL ACL
if isRoot {
return nil
}
acl, err := xattr.Get(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 == access {
return nil
}
if ACL.ACL != "" {
if (permission == "READ" || permission == "READ_ACP") && (ACL.ACL != "public-read" && ACL.ACL != "public-read-write") {
return s3err.GetAPIError(s3err.ErrAccessDenied)
}
if (permission == "WRITE" || permission == "WRITE_ACP") && ACL.ACL != "public-read-write" {
return s3err.GetAPIError(s3err.ErrAccessDenied)
}
return nil
} else {
grantee := Grantee{Access: access, Permission: permission}
granteeFullCtrl := Grantee{Access: access, Permission: "FULL_CONTROL"}
isFound := false
for _, grt := range ACL.Grantees {
if grt == grantee || grt == granteeFullCtrl {
isFound = true
break
}
}
if isFound {
return nil
}
}
return s3err.GetAPIError(s3err.ErrAccessDenied)
}
func (ACLServiceUnsupported) IsAdmin(access string, isRoot bool) error {
var data IAMConfig
if isRoot {
return nil
}
file, err := os.ReadFile("users.json")
if err != nil {
return fmt.Errorf("unable to read config file: %w", err)
}
if err := json.Unmarshal(file, &data); err != nil {
return err
}
acc, ok := data.AccessAccounts[access]
if !ok {
return fmt.Errorf("user does not exist")
}
if acc.Role == "admin" {
return nil
}
return fmt.Errorf("only admin users have access to this resource")
}

View File

@@ -1114,66 +1114,60 @@ func (p *Posix) PutBucketAcl(input *s3.PutBucketAclInput) error {
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 the ACL is specified, set the ACL, else replace the grantees
if input.ACL != "" {
ACL.ACL = input.ACL
ACL.Grantees = []auth.Grantee{}
} else {
grantees := []auth.Grantee{}
fullControlList, readList, readACPList, writeList, writeACPList := []string{}, []string{}, []string{}, []string{}, []string{}
if *input.GrantFullControl != "" {
fullControlList = splitUnique(*input.GrantFullControl, ",")
fmt.Println(fullControlList)
for _, str := range fullControlList {
grantees = append(grantees, auth.Grantee{Access: str, Permission: "FULL_CONTROL"})
}
}
if *input.GrantRead != "" {
readList = splitUnique(*input.GrantRead, ",")
for _, str := range readList {
grantees = append(grantees, auth.Grantee{Access: str, Permission: "READ"})
}
}
if *input.GrantReadACP != "" {
readACPList = splitUnique(*input.GrantReadACP, ",")
for _, str := range readACPList {
grantees = append(grantees, auth.Grantee{Access: str, Permission: "READ_ACP"})
}
}
if *input.GrantWrite != "" {
writeList = splitUnique(*input.GrantWrite, ",")
for _, str := range writeList {
grantees = append(grantees, auth.Grantee{Access: str, Permission: "WRITE"})
}
}
if *input.GrantWriteACP != "" {
writeACPList = splitUnique(*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...)
// Check if the specified accounts exist
accList, err := checkIfAccountsExist(accs)
if err != nil {
return err
}
if len(accList) > 0 {
return fmt.Errorf("accounts does not exist: %s", strings.Join(accList, ", "))
}
ACL.Grantees = grantees
ACL.ACL = ""
}
ACLJson, err := json.Marshal(ACL)
@@ -1202,7 +1196,8 @@ func (p *Posix) GetBucketAcl(bucket string) (*auth.GetBucketAclOutput, error) {
grants := []types.Grant{}
for _, elem := range ACL.Grantees {
grants = append(grants, types.Grant{Grantee: &types.Grantee{ID: &elem.Access}, Permission: elem.Permission})
acs := elem.Access
grants = append(grants, types.Grant{Grantee: &types.Grantee{ID: &acs}, Permission: elem.Permission})
}
return &auth.GetBucketAclOutput{
@@ -1323,3 +1318,18 @@ func checkIfAccountsExist(accs []string) ([]string, error) {
}
return result, nil
}
func splitUnique(s, divider string) []string {
elements := strings.Split(s, divider)
uniqueElements := make(map[string]bool)
result := make([]string, 0, len(elements))
for _, element := range elements {
if _, ok := uniqueElements[element]; !ok {
result = append(result, element)
uniqueElements[element] = true
}
}
return result
}

View File

@@ -30,19 +30,25 @@ import (
"github.com/aws/aws-sdk-go-v2/service/s3/types"
"github.com/gofiber/fiber/v2"
"github.com/versity/versitygw/backend"
"github.com/versity/versitygw/backend/auth"
"github.com/versity/versitygw/s3api/utils"
"github.com/versity/versitygw/s3err"
)
type S3ApiController struct {
be backend.Backend
be backend.Backend
acl auth.ACLService
}
func New(be backend.Backend) S3ApiController {
return S3ApiController{be: be}
return S3ApiController{be: be, acl: auth.ACLServiceUnsupported{}}
}
func (c S3ApiController) ListBuckets(ctx *fiber.Ctx) error {
access, isRoot := ctx.Locals("access").(string), ctx.Locals("isRoot").(bool)
if err := c.acl.IsAdmin(access, isRoot); err != nil {
return SendXMLResponse(ctx, nil, err)
}
res, err := c.be.ListBuckets()
return SendXMLResponse(ctx, res, err)
}
@@ -55,6 +61,8 @@ func (c S3ApiController) GetActions(ctx *fiber.Ctx) error {
maxParts := ctx.QueryInt("max-parts", 0)
partNumberMarker := ctx.QueryInt("part-number-marker", 0)
acceptRange := ctx.Get("Range")
access := ctx.Locals("access").(string)
isRoot := ctx.Locals("isRoot").(bool)
if keyEnd != "" {
key = strings.Join([]string{key, keyEnd}, "/")
}
@@ -66,20 +74,35 @@ func (c S3ApiController) GetActions(ctx *fiber.Ctx) error {
if partNumberMarker < 0 || (partNumberMarker == 0 && ctx.Query("part-number-marker") != "") {
return SendResponse(ctx, s3err.GetAPIError(s3err.ErrInvalidPartNumberMarker))
}
if err := c.acl.VerifyACL(bucket, access, "READ", isRoot); err != nil {
return SendXMLResponse(ctx, nil, err)
}
res, err := c.be.ListObjectParts(bucket, key, uploadId, partNumberMarker, maxParts)
return SendXMLResponse(ctx, res, err)
}
if ctx.Request().URI().QueryArgs().Has("acl") {
if err := c.acl.VerifyACL(bucket, access, "READ_ACP", isRoot); err != nil {
return SendXMLResponse(ctx, nil, err)
}
res, err := c.be.GetObjectAcl(bucket, key)
return SendXMLResponse(ctx, res, err)
}
if attrs := ctx.Get("X-Amz-Object-Attributes"); attrs != "" {
if err := c.acl.VerifyACL(bucket, access, "READ", isRoot); err != nil {
return SendXMLResponse(ctx, nil, err)
}
res, err := c.be.GetObjectAttributes(bucket, key, strings.Split(attrs, ","))
return SendXMLResponse(ctx, res, err)
}
if err := c.acl.VerifyACL(bucket, access, "READ_ACP", isRoot); err != nil {
return SendResponse(ctx, err)
}
res, err := c.be.GetObject(bucket, key, acceptRange, ctx.Response().BodyWriter())
if err != nil {
return SendResponse(ctx, err)
@@ -131,28 +154,43 @@ func (c S3ApiController) ListActions(ctx *fiber.Ctx) error {
marker := ctx.Query("continuation-token")
delimiter := ctx.Query("delimiter")
maxkeys := ctx.QueryInt("max-keys")
access := ctx.Locals("access").(string)
isRoot := ctx.Locals("isRoot").(bool)
if ctx.Request().URI().QueryArgs().Has("acl") {
if err := c.acl.VerifyACL(bucket, access, "READ_ACP", isRoot); err != nil {
return SendXMLResponse(ctx, nil, err)
}
res, err := c.be.GetBucketAcl(ctx.Params("bucket"))
return SendXMLResponse(ctx, res, err)
}
if ctx.Request().URI().QueryArgs().Has("uploads") {
if err := c.acl.VerifyACL(bucket, access, "READ", isRoot); err != nil {
return SendXMLResponse(ctx, nil, err)
}
res, err := c.be.ListMultipartUploads(&s3.ListMultipartUploadsInput{Bucket: aws.String(ctx.Params("bucket"))})
return SendXMLResponse(ctx, res, err)
}
if ctx.QueryInt("list-type") == 2 {
if err := c.acl.VerifyACL(bucket, access, "READ", isRoot); err != nil {
return SendXMLResponse(ctx, nil, err)
}
res, err := c.be.ListObjectsV2(bucket, prefix, marker, delimiter, maxkeys)
return SendXMLResponse(ctx, res, err)
}
if err := c.acl.VerifyACL(bucket, access, "READ", isRoot); err != nil {
return SendXMLResponse(ctx, nil, err)
}
res, err := c.be.ListObjects(bucket, prefix, marker, delimiter, maxkeys)
return SendXMLResponse(ctx, res, err)
}
func (c S3ApiController) PutBucketActions(ctx *fiber.Ctx) error {
bucket, acl, grantFullControl, grantRead, grantReadACP, granWrite, grantWriteACP, access :=
bucket, acl, grantFullControl, grantRead, grantReadACP, granWrite, grantWriteACP, access, isRoot :=
ctx.Params("bucket"),
ctx.Get("X-Amz-Acl"),
ctx.Get("X-Amz-Grant-Full-Control"),
@@ -160,15 +198,24 @@ func (c S3ApiController) PutBucketActions(ctx *fiber.Ctx) error {
ctx.Get("X-Amz-Grant-Read-Acp"),
ctx.Get("X-Amz-Grant-Write"),
ctx.Get("X-Amz-Grant-Write-Acp"),
ctx.Locals("access")
ctx.Locals("access").(string),
ctx.Locals("isRoot").(bool)
owner := access.(string)
grants := grantFullControl + grantRead + grantReadACP + granWrite + grantWriteACP
if grants != "" || acl != "" {
if grants != "" && acl != "" {
return errors.New("wrong api call")
}
if acl != "" && acl != "private" && acl != "public-read" && acl != "public-read-write" {
return errors.New("wrong api call")
}
if err := c.acl.VerifyACL(bucket, access, "WRITE_ACP", isRoot); err != nil {
return SendResponse(ctx, err)
}
err := c.be.PutBucketAcl(&s3.PutBucketAclInput{
Bucket: &bucket,
ACL: types.BucketCannedACL(acl),
@@ -177,13 +224,13 @@ func (c S3ApiController) PutBucketActions(ctx *fiber.Ctx) error {
GrantReadACP: &grantReadACP,
GrantWrite: &granWrite,
GrantWriteACP: &grantWriteACP,
AccessControlPolicy: &types.AccessControlPolicy{Owner: &types.Owner{ID: &owner}},
AccessControlPolicy: &types.AccessControlPolicy{Owner: &types.Owner{ID: &access}},
})
return SendResponse(ctx, err)
}
err := c.be.PutBucket(bucket, owner)
err := c.be.PutBucket(bucket, access)
return SendResponse(ctx, err)
}
@@ -193,6 +240,8 @@ func (c S3ApiController) PutActions(ctx *fiber.Ctx) error {
keyEnd := ctx.Params("*1")
uploadId := ctx.Query("uploadId")
partNumberStr := ctx.Query("partNumber")
access := ctx.Locals("access").(string)
isRoot := ctx.Locals("isRoot").(bool)
// Copy source headers
copySource := ctx.Get("X-Amz-Copy-Source")
@@ -237,6 +286,10 @@ func (c S3ApiController) PutActions(ctx *fiber.Ctx) error {
return SendResponse(ctx, s3err.GetAPIError(s3err.ErrInvalidPart))
}
if err := c.acl.VerifyACL(bucket, access, "WRITE", isRoot); err != nil {
return SendResponse(ctx, err)
}
body := io.ReadSeeker(bytes.NewReader([]byte(ctx.Body())))
etag, err := c.be.PutObjectPart(bucket, keyStart, uploadId,
partNumber, contentLength, body)
@@ -249,6 +302,10 @@ func (c S3ApiController) PutActions(ctx *fiber.Ctx) error {
return errors.New("wrong api call")
}
if err := c.acl.VerifyACL(bucket, access, "WRITE_ACP", isRoot); err != nil {
return SendResponse(ctx, err)
}
err := c.be.PutObjectAcl(&s3.PutObjectAclInput{
Bucket: &bucket,
Key: &keyStart,
@@ -268,12 +325,20 @@ func (c S3ApiController) PutActions(ctx *fiber.Ctx) error {
copySourceSplit := strings.Split(copySource, "/")
srcBucket, srcObject := copySourceSplit[0], copySourceSplit[1:]
if err := c.acl.VerifyACL(bucket, access, "WRITE", isRoot); err != nil {
return SendXMLResponse(ctx, nil, err)
}
res, err := c.be.CopyObject(srcBucket, strings.Join(srcObject, "/"), bucket, keyStart)
return SendXMLResponse(ctx, res, err)
}
metadata := utils.GetUserMetaData(&ctx.Request().Header)
if err := c.acl.VerifyACL(bucket, access, "WRITE", isRoot); err != nil {
return SendResponse(ctx, err)
}
etag, err := c.be.PutObject(&s3.PutObjectInput{
Bucket: &bucket,
Key: &keyStart,
@@ -286,17 +351,27 @@ func (c S3ApiController) PutActions(ctx *fiber.Ctx) error {
}
func (c S3ApiController) DeleteBucket(ctx *fiber.Ctx) error {
err := c.be.DeleteBucket(ctx.Params("bucket"))
bucket, access, isRoot := ctx.Params("bucket"), ctx.Locals("access").(string), ctx.Locals("isRoot").(bool)
if err := c.acl.VerifyACL(bucket, access, "WRITE", isRoot); err != nil {
return SendResponse(ctx, err)
}
err := c.be.DeleteBucket(bucket)
return SendResponse(ctx, err)
}
func (c S3ApiController) DeleteObjects(ctx *fiber.Ctx) error {
bucket, access, isRoot := ctx.Params("bucket"), ctx.Locals("access").(string), ctx.Locals("isRoot").(bool)
var dObj types.Delete
if err := xml.Unmarshal(ctx.Body(), &dObj); err != nil {
return errors.New("wrong api call")
}
err := c.be.DeleteObjects(ctx.Params("bucket"), &s3.DeleteObjectsInput{Delete: &dObj})
if err := c.acl.VerifyACL(bucket, access, "WRITE", isRoot); err != nil {
return SendResponse(ctx, err)
}
err := c.be.DeleteObjects(bucket, &s3.DeleteObjectsInput{Delete: &dObj})
return SendResponse(ctx, err)
}
@@ -305,6 +380,8 @@ func (c S3ApiController) DeleteActions(ctx *fiber.Ctx) error {
key := ctx.Params("key")
keyEnd := ctx.Params("*1")
uploadId := ctx.Query("uploadId")
access := ctx.Locals("access").(string)
isRoot := ctx.Locals("isRoot").(bool)
if keyEnd != "" {
key = strings.Join([]string{key, keyEnd}, "/")
@@ -313,6 +390,10 @@ func (c S3ApiController) DeleteActions(ctx *fiber.Ctx) error {
if uploadId != "" {
expectedBucketOwner, requestPayer := ctx.Get("X-Amz-Expected-Bucket-Owner"), ctx.Get("X-Amz-Request-Payer")
if err := c.acl.VerifyACL(bucket, access, "WRITE", isRoot); err != nil {
return SendResponse(ctx, err)
}
err := c.be.AbortMultipartUpload(&s3.AbortMultipartUploadInput{
UploadId: &uploadId,
Bucket: &bucket,
@@ -323,12 +404,21 @@ func (c S3ApiController) DeleteActions(ctx *fiber.Ctx) error {
return SendResponse(ctx, err)
}
if err := c.acl.VerifyACL(bucket, access, "WRITE", isRoot); err != nil {
return SendResponse(ctx, err)
}
err := c.be.DeleteObject(bucket, key)
return SendResponse(ctx, err)
}
func (c S3ApiController) HeadBucket(ctx *fiber.Ctx) error {
_, err := c.be.HeadBucket(ctx.Params("bucket"))
bucket, access, isRoot := ctx.Params("bucket"), ctx.Locals("access").(string), ctx.Locals("isRoot").(bool)
if err := c.acl.VerifyACL(bucket, access, "READ", isRoot); err != nil {
return SendResponse(ctx, err)
}
_, err := c.be.HeadBucket(bucket)
// TODO: set bucket response headers
return SendResponse(ctx, err)
}
@@ -338,13 +428,17 @@ const (
)
func (c S3ApiController) HeadObject(ctx *fiber.Ctx) error {
bucket := ctx.Params("bucket")
bucket, access, isRoot := ctx.Params("bucket"), ctx.Locals("access").(string), ctx.Locals("isRoot").(bool)
key := ctx.Params("key")
keyEnd := ctx.Params("*1")
if keyEnd != "" {
key = strings.Join([]string{key, keyEnd}, "/")
}
if err := c.acl.VerifyACL(bucket, access, "READ", isRoot); err != nil {
return SendResponse(ctx, err)
}
res, err := c.be.HeadObject(bucket, key)
if err != nil {
return SendResponse(ctx, err)
@@ -389,6 +483,8 @@ func (c S3ApiController) CreateActions(ctx *fiber.Ctx) error {
key := ctx.Params("key")
keyEnd := ctx.Params("*1")
uploadId := ctx.Query("uploadId")
access := ctx.Locals("access").(string)
isRoot := ctx.Locals("isRoot").(bool)
if keyEnd != "" {
key = strings.Join([]string{key, keyEnd}, "/")
@@ -400,6 +496,11 @@ func (c S3ApiController) CreateActions(ctx *fiber.Ctx) error {
if xmlErr != nil {
return errors.New("wrong api call")
}
if err := c.acl.VerifyACL(bucket, access, "WRITE", isRoot); err != nil {
return SendResponse(ctx, err)
}
err := c.be.RestoreObject(bucket, key, &restoreRequest)
return SendResponse(ctx, err)
}
@@ -413,9 +514,18 @@ func (c S3ApiController) CreateActions(ctx *fiber.Ctx) error {
return errors.New("wrong api call")
}
if err := c.acl.VerifyACL(bucket, access, "WRITE", isRoot); err != nil {
return SendXMLResponse(ctx, nil, err)
}
res, err := c.be.CompleteMultipartUpload(bucket, key, uploadId, data.Parts)
return SendXMLResponse(ctx, res, err)
}
if err := c.acl.VerifyACL(bucket, access, "WRITE", isRoot); err != nil {
return SendXMLResponse(ctx, nil, err)
}
res, err := c.be.CreateMultipartUpload(&s3.CreateMultipartUploadInput{Bucket: &bucket, Key: &key})
return SendXMLResponse(ctx, res, err)
}

View File

@@ -39,6 +39,7 @@ func TestNew(t *testing.T) {
}
be := backend.BackendUnsupported{}
acl := auth.ACLServiceUnsupported{}
tests := []struct {
name string
@@ -51,7 +52,8 @@ func TestNew(t *testing.T) {
be: be,
},
want: S3ApiController{
be: be,
be: be,
acl: acl,
},
},
}
@@ -66,58 +68,80 @@ func TestNew(t *testing.T) {
func TestS3ApiController_ListBuckets(t *testing.T) {
type args struct {
ctx *fiber.Ctx
req *http.Request
}
app := fiber.New()
s3ApiController := S3ApiController{
be: &BackendMock{
ListBucketsFunc: func() (*s3.ListBucketsOutput, error) {
return &s3.ListBucketsOutput{}, nil
},
},
acl: auth.ACLServiceUnsupported{},
}
app.Use(func(ctx *fiber.Ctx) error {
ctx.Locals("access", "valid access")
ctx.Locals("isRoot", true)
return ctx.Next()
})
app.Get("/", s3ApiController.ListBuckets)
// Error case
appErr := fiber.New()
s3ApiControllerErr := S3ApiController{
be: &BackendMock{
ListBucketsFunc: func() (*s3.ListBucketsOutput, error) {
return nil, s3err.GetAPIError(s3err.ErrMethodNotAllowed)
},
},
acl: auth.ACLServiceUnsupported{},
}
appErr.Use(func(ctx *fiber.Ctx) error {
ctx.Locals("access", "valid access")
ctx.Locals("isRoot", true)
return ctx.Next()
})
appErr.Get("/", s3ApiControllerErr.ListBuckets)
tests := []struct {
name string
c S3ApiController
args args
app *fiber.App
wantErr bool
statusCode int
}{
{
name: "List-bucket-not-implemented",
c: S3ApiController{
be: backend.BackendUnsupported{},
},
name: "List-bucket-method-not-allowed",
args: args{
ctx: app.AcquireCtx(&fasthttp.RequestCtx{}),
req: httptest.NewRequest(http.MethodGet, "/", nil),
},
app: appErr,
wantErr: false,
statusCode: 501,
statusCode: 405,
},
{
name: "list-bucket-success",
c: S3ApiController{
be: &BackendMock{
ListBucketsFunc: func() (*s3.ListBucketsOutput, error) {
return &s3.ListBucketsOutput{}, nil
},
},
},
args: args{
ctx: app.AcquireCtx(&fasthttp.RequestCtx{}),
req: httptest.NewRequest(http.MethodGet, "/", nil),
},
app: app,
wantErr: false,
statusCode: 200,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := tt.c.ListBuckets(tt.args.ctx)
resp, err := tt.app.Test(tt.args.req)
if (err != nil) != tt.wantErr {
t.Errorf("S3ApiController.ListBuckets() error = %v, wantErr %v", err, tt.wantErr)
return
}
statusCode := tt.args.ctx.Response().StatusCode()
if statusCode != tt.statusCode {
t.Errorf("S3ApiController.ListBuckets() code = %v, wantErr %v", statusCode, tt.wantErr)
if resp.StatusCode != tt.statusCode {
t.Errorf("S3ApiController.ListBuckets() statusCode = %v, wantStatusCode = %v", resp.StatusCode, tt.statusCode)
}
})
}
@@ -129,20 +153,28 @@ func TestS3ApiController_GetActions(t *testing.T) {
}
app := fiber.New()
s3ApiController := S3ApiController{be: &BackendMock{
ListObjectPartsFunc: func(bucket, object, uploadID string, partNumberMarker int, maxParts int) (s3response.ListPartsResponse, error) {
return s3response.ListPartsResponse{}, nil
s3ApiController := S3ApiController{
be: &BackendMock{
ListObjectPartsFunc: func(bucket, object, uploadID string, partNumberMarker int, maxParts int) (s3response.ListPartsResponse, error) {
return s3response.ListPartsResponse{}, nil
},
GetObjectAclFunc: func(bucket, object string) (*s3.GetObjectAclOutput, error) {
return &s3.GetObjectAclOutput{}, nil
},
GetObjectAttributesFunc: func(bucket, object string, attributes []string) (*s3.GetObjectAttributesOutput, error) {
return &s3.GetObjectAttributesOutput{}, nil
},
GetObjectFunc: func(bucket, object, acceptRange string, writer io.Writer) (*s3.GetObjectOutput, error) {
return &s3.GetObjectOutput{Metadata: nil}, nil
},
},
GetObjectAclFunc: func(bucket, object string) (*s3.GetObjectAclOutput, error) {
return &s3.GetObjectAclOutput{}, nil
},
GetObjectAttributesFunc: func(bucket, object string, attributes []string) (*s3.GetObjectAttributesOutput, error) {
return &s3.GetObjectAttributesOutput{}, nil
},
GetObjectFunc: func(bucket, object, acceptRange string, writer io.Writer) (*s3.GetObjectOutput, error) {
return &s3.GetObjectOutput{Metadata: nil}, nil
},
}}
acl: &auth.ACLServiceUnsupported{},
}
app.Use(func(ctx *fiber.Ctx) error {
ctx.Locals("access", "valid access")
ctx.Locals("isRoot", true)
return ctx.Next()
})
app.Get("/:bucket/:key/*", s3ApiController.GetActions)
// GetObjectACL
@@ -231,29 +263,47 @@ func TestS3ApiController_ListActions(t *testing.T) {
}
app := fiber.New()
s3ApiController := S3ApiController{be: &BackendMock{
GetBucketAclFunc: func(bucket string) (*auth.GetBucketAclOutput, error) {
return nil, nil
s3ApiController := S3ApiController{
be: &BackendMock{
GetBucketAclFunc: func(bucket string) (*auth.GetBucketAclOutput, error) {
return nil, nil
},
ListMultipartUploadsFunc: func(output *s3.ListMultipartUploadsInput) (s3response.ListMultipartUploadsResponse, error) {
return s3response.ListMultipartUploadsResponse{}, nil
},
ListObjectsV2Func: func(bucket, prefix, marker, delim string, maxkeys int) (*s3.ListObjectsV2Output, error) {
return &s3.ListObjectsV2Output{}, nil
},
ListObjectsFunc: func(bucket, prefix, marker, delim string, maxkeys int) (*s3.ListObjectsOutput, error) {
return &s3.ListObjectsOutput{}, nil
},
},
ListMultipartUploadsFunc: func(output *s3.ListMultipartUploadsInput) (s3response.ListMultipartUploadsResponse, error) {
return s3response.ListMultipartUploadsResponse{}, nil
},
ListObjectsV2Func: func(bucket, prefix, marker, delim string, maxkeys int) (*s3.ListObjectsV2Output, error) {
return &s3.ListObjectsV2Output{}, nil
},
ListObjectsFunc: func(bucket, prefix, marker, delim string, maxkeys int) (*s3.ListObjectsOutput, error) {
return &s3.ListObjectsOutput{}, nil
},
}}
acl: auth.ACLServiceUnsupported{},
}
app.Use(func(ctx *fiber.Ctx) error {
ctx.Locals("access", "valid access")
ctx.Locals("isRoot", true)
return ctx.Next()
})
app.Get("/:bucket", s3ApiController.ListActions)
//Error case
s3ApiControllerError := S3ApiController{be: &BackendMock{
ListObjectsFunc: func(bucket, prefix, marker, delim string, maxkeys int) (*s3.ListObjectsOutput, error) {
return nil, s3err.GetAPIError(s3err.ErrNotImplemented)
s3ApiControllerError := S3ApiController{
be: &BackendMock{
ListObjectsFunc: func(bucket, prefix, marker, delim string, maxkeys int) (*s3.ListObjectsOutput, error) {
return nil, s3err.GetAPIError(s3err.ErrNotImplemented)
},
},
}}
acl: auth.ACLServiceUnsupported{},
}
appError := fiber.New()
appError.Use(func(ctx *fiber.Ctx) error {
ctx.Locals("access", "valid access")
ctx.Locals("isRoot", true)
return ctx.Next()
})
appError.Get("/:bucket", s3ApiControllerError.ListActions)
tests := []struct {
@@ -330,17 +380,21 @@ func TestS3ApiController_PutBucketActions(t *testing.T) {
}
app := fiber.New()
s3ApiController := S3ApiController{be: &BackendMock{
PutBucketAclFunc: func(*s3.PutBucketAclInput) error {
return nil
s3ApiController := S3ApiController{
be: &BackendMock{
PutBucketAclFunc: func(*s3.PutBucketAclInput) error {
return nil
},
PutBucketFunc: func(bucket, owner string) error {
return nil
},
},
PutBucketFunc: func(bucket, owner string) error {
return nil
},
}}
acl: auth.ACLServiceUnsupported{},
}
// Mock ctx.Locals
app.Use(func(ctx *fiber.Ctx) error {
ctx.Locals("access", "valid access")
ctx.Locals("isRoot", true)
return ctx.Next()
})
app.Put("/:bucket", s3ApiController.PutBucketActions)
@@ -408,20 +462,28 @@ func TestS3ApiController_PutActions(t *testing.T) {
}
app := fiber.New()
s3ApiController := S3ApiController{be: &BackendMock{
UploadPartCopyFunc: func(*s3.UploadPartCopyInput) (*s3.UploadPartCopyOutput, error) {
return &s3.UploadPartCopyOutput{}, nil
s3ApiController := S3ApiController{
be: &BackendMock{
UploadPartCopyFunc: func(*s3.UploadPartCopyInput) (*s3.UploadPartCopyOutput, error) {
return &s3.UploadPartCopyOutput{}, nil
},
PutObjectAclFunc: func(*s3.PutObjectAclInput) error {
return nil
},
CopyObjectFunc: func(srcBucket, srcObject, DstBucket, dstObject string) (*s3.CopyObjectOutput, error) {
return &s3.CopyObjectOutput{}, nil
},
PutObjectFunc: func(*s3.PutObjectInput) (string, error) {
return "Hey", nil
},
},
PutObjectAclFunc: func(*s3.PutObjectAclInput) error {
return nil
},
CopyObjectFunc: func(srcBucket, srcObject, DstBucket, dstObject string) (*s3.CopyObjectOutput, error) {
return &s3.CopyObjectOutput{}, nil
},
PutObjectFunc: func(*s3.PutObjectInput) (string, error) {
return "Hey", nil
},
}}
acl: auth.ACLServiceUnsupported{},
}
app.Use(func(ctx *fiber.Ctx) error {
ctx.Locals("access", "valid access")
ctx.Locals("isRoot", true)
return ctx.Next()
})
app.Put("/:bucket/:key/*", s3ApiController.PutActions)
//PutObjectAcl error
@@ -538,23 +600,40 @@ func TestS3ApiController_DeleteBucket(t *testing.T) {
}
app := fiber.New()
s3ApiController := S3ApiController{be: &BackendMock{
DeleteBucketFunc: func(bucket string) error {
return nil
s3ApiController := S3ApiController{
be: &BackendMock{
DeleteBucketFunc: func(bucket string) error {
return nil
},
},
}}
acl: auth.ACLServiceUnsupported{},
}
app.Use(func(ctx *fiber.Ctx) error {
ctx.Locals("access", "valid access")
ctx.Locals("isRoot", true)
return ctx.Next()
})
app.Delete("/:bucket", s3ApiController.DeleteBucket)
// error case
appErr := fiber.New()
s3ApiControllerErr := S3ApiController{be: &BackendMock{
DeleteBucketFunc: func(bucket string) error {
return s3err.GetAPIError(48)
s3ApiControllerErr := S3ApiController{
be: &BackendMock{
DeleteBucketFunc: func(bucket string) error {
return s3err.GetAPIError(48)
},
},
}}
acl: auth.ACLServiceUnsupported{},
}
appErr.Use(func(ctx *fiber.Ctx) error {
ctx.Locals("access", "valid access")
ctx.Locals("isRoot", true)
return ctx.Next()
})
appErr.Delete("/:bucket", s3ApiControllerErr.DeleteBucket)
tests := []struct {
@@ -602,12 +681,20 @@ func TestS3ApiController_DeleteObjects(t *testing.T) {
}
app := fiber.New()
s3ApiController := S3ApiController{be: &BackendMock{
DeleteObjectsFunc: func(bucket string, objects *s3.DeleteObjectsInput) error {
return nil
s3ApiController := S3ApiController{
be: &BackendMock{
DeleteObjectsFunc: func(bucket string, objects *s3.DeleteObjectsInput) error {
return nil
},
},
}}
acl: auth.ACLServiceUnsupported{},
}
app.Use(func(ctx *fiber.Ctx) error {
ctx.Locals("access", "valid access")
ctx.Locals("isRoot", true)
return ctx.Next()
})
app.Post("/:bucket", s3ApiController.DeleteObjects)
// Valid request body
@@ -661,15 +748,23 @@ func TestS3ApiController_DeleteActions(t *testing.T) {
}
app := fiber.New()
s3ApiController := S3ApiController{be: &BackendMock{
DeleteObjectFunc: func(bucket, object string) error {
return nil
s3ApiController := S3ApiController{
be: &BackendMock{
DeleteObjectFunc: func(bucket, object string) error {
return nil
},
AbortMultipartUploadFunc: func(*s3.AbortMultipartUploadInput) error {
return nil
},
},
AbortMultipartUploadFunc: func(*s3.AbortMultipartUploadInput) error {
return nil
},
}}
acl: auth.ACLServiceUnsupported{},
}
app.Use(func(ctx *fiber.Ctx) error {
ctx.Locals("access", "valid access")
ctx.Locals("isRoot", true)
return ctx.Next()
})
app.Delete("/:bucket/:key/*", s3ApiController.DeleteActions)
//Error case
@@ -681,6 +776,11 @@ func TestS3ApiController_DeleteActions(t *testing.T) {
},
}}
appErr.Use(func(ctx *fiber.Ctx) error {
ctx.Locals("access", "valid access")
ctx.Locals("isRoot", true)
return ctx.Next()
})
appErr.Delete("/:bucket", s3ApiControllerErr.DeleteBucket)
tests := []struct {
@@ -737,22 +837,39 @@ func TestS3ApiController_HeadBucket(t *testing.T) {
}
app := fiber.New()
s3ApiController := S3ApiController{be: &BackendMock{
HeadBucketFunc: func(bucket string) (*s3.HeadBucketOutput, error) {
return &s3.HeadBucketOutput{}, nil
s3ApiController := S3ApiController{
be: &BackendMock{
HeadBucketFunc: func(bucket string) (*s3.HeadBucketOutput, error) {
return &s3.HeadBucketOutput{}, nil
},
},
}}
acl: auth.ACLServiceUnsupported{},
}
app.Use(func(ctx *fiber.Ctx) error {
ctx.Locals("access", "valid access")
ctx.Locals("isRoot", true)
return ctx.Next()
})
app.Head("/:bucket", s3ApiController.HeadBucket)
//Error case
// Error case
appErr := fiber.New()
s3ApiControllerErr := S3ApiController{be: &BackendMock{
HeadBucketFunc: func(bucket string) (*s3.HeadBucketOutput, error) {
return nil, s3err.GetAPIError(3)
},
}}
},
acl: auth.ACLServiceUnsupported{},
}
appErr.Use(func(ctx *fiber.Ctx) error {
ctx.Locals("access", "valid access")
ctx.Locals("isRoot", true)
return ctx.Next()
})
appErr.Head("/:bucket", s3ApiControllerErr.HeadBucket)
@@ -808,29 +925,45 @@ func TestS3ApiController_HeadObject(t *testing.T) {
eTag := "Valid etag"
lastModifie := time.Now()
s3ApiController := S3ApiController{be: &BackendMock{
HeadObjectFunc: func(bucket, object string) (*s3.HeadObjectOutput, error) {
return &s3.HeadObjectOutput{
ContentEncoding: &contentEncoding,
ContentLength: 64,
ContentType: &contentType,
LastModified: &lastModifie,
ETag: &eTag,
}, nil
s3ApiController := S3ApiController{
be: &BackendMock{
HeadObjectFunc: func(bucket, object string) (*s3.HeadObjectOutput, error) {
return &s3.HeadObjectOutput{
ContentEncoding: &contentEncoding,
ContentLength: 64,
ContentType: &contentType,
LastModified: &lastModifie,
ETag: &eTag,
}, nil
},
},
}}
acl: auth.ACLServiceUnsupported{},
}
app.Use(func(ctx *fiber.Ctx) error {
ctx.Locals("access", "valid access")
ctx.Locals("isRoot", true)
return ctx.Next()
})
app.Head("/:bucket/:key/*", s3ApiController.HeadObject)
//Error case
appErr := fiber.New()
s3ApiControllerErr := S3ApiController{be: &BackendMock{
HeadObjectFunc: func(bucket, object string) (*s3.HeadObjectOutput, error) {
return nil, s3err.GetAPIError(42)
s3ApiControllerErr := S3ApiController{
be: &BackendMock{
HeadObjectFunc: func(bucket, object string) (*s3.HeadObjectOutput, error) {
return nil, s3err.GetAPIError(42)
},
},
}}
acl: auth.ACLServiceUnsupported{},
}
appErr.Use(func(ctx *fiber.Ctx) error {
ctx.Locals("access", "valid access")
ctx.Locals("isRoot", true)
return ctx.Next()
})
appErr.Head("/:bucket/:key/*", s3ApiControllerErr.HeadObject)
tests := []struct {
@@ -877,18 +1010,26 @@ func TestS3ApiController_CreateActions(t *testing.T) {
req *http.Request
}
app := fiber.New()
s3ApiController := S3ApiController{be: &BackendMock{
RestoreObjectFunc: func(bucket, object string, restoreRequest *s3.RestoreObjectInput) error {
return nil
s3ApiController := S3ApiController{
be: &BackendMock{
RestoreObjectFunc: func(bucket, object string, restoreRequest *s3.RestoreObjectInput) error {
return nil
},
CompleteMultipartUploadFunc: func(bucket, object, uploadID string, parts []types.Part) (*s3.CompleteMultipartUploadOutput, error) {
return &s3.CompleteMultipartUploadOutput{}, nil
},
CreateMultipartUploadFunc: func(*s3.CreateMultipartUploadInput) (*s3.CreateMultipartUploadOutput, error) {
return &s3.CreateMultipartUploadOutput{}, nil
},
},
CompleteMultipartUploadFunc: func(bucket, object, uploadID string, parts []types.Part) (*s3.CompleteMultipartUploadOutput, error) {
return &s3.CompleteMultipartUploadOutput{}, nil
},
CreateMultipartUploadFunc: func(*s3.CreateMultipartUploadInput) (*s3.CreateMultipartUploadOutput, error) {
return &s3.CreateMultipartUploadOutput{}, nil
},
}}
acl: auth.ACLServiceUnsupported{},
}
app.Use(func(ctx *fiber.Ctx) error {
ctx.Locals("access", "valid access")
ctx.Locals("isRoot", true)
return ctx.Next()
})
app.Post("/:bucket/:key/*", s3ApiController.CreateActions)
tests := []struct {

View File

@@ -136,6 +136,7 @@ func VerifyV4Signature(root RootUserConfig, iam auth.IAMService, debug bool) fib
ctx.Locals("role", account.Role)
ctx.Locals("access", creds[0])
ctx.Locals("isRoot", creds[0] == root.Access)
return ctx.Next()
}