fix: Fixes #204, Change ListBuckets action logic to return all the buckets for admin users and the buckets owned by a user for regular users. Added integration test cases for ListBuckets action

This commit is contained in:
jonaustin09
2023-09-07 14:49:47 -04:00
parent 17651fc139
commit 8d2e2a4106
7 changed files with 261 additions and 16 deletions

View File

@@ -30,7 +30,7 @@ type Backend interface {
Shutdown()
// bucket operations
ListBuckets(_ context.Context, owner string, isRoot bool) (s3response.ListAllMyBucketsResult, error)
ListBuckets(_ context.Context, owner string, isAdmin bool) (s3response.ListAllMyBucketsResult, error)
HeadBucket(context.Context, *s3.HeadBucketInput) (*s3.HeadBucketOutput, error)
GetBucketAcl(context.Context, *s3.GetBucketAclInput) ([]byte, error)
CreateBucket(context.Context, *s3.CreateBucketInput) error

View File

@@ -98,7 +98,7 @@ func (p *Posix) String() string {
return "Posix Gateway"
}
func (p *Posix) ListBuckets(_ context.Context, owner string, isRoot bool) (s3response.ListAllMyBucketsResult, error) {
func (p *Posix) ListBuckets(_ context.Context, owner string, isAdmin bool) (s3response.ListAllMyBucketsResult, error) {
entries, err := os.ReadDir(".")
if err != nil {
return s3response.ListAllMyBucketsResult{},
@@ -118,10 +118,32 @@ func (p *Posix) ListBuckets(_ context.Context, owner string, isRoot bool) (s3res
continue
}
buckets = append(buckets, s3response.ListAllMyBucketsEntry{
Name: entry.Name(),
CreationDate: fi.ModTime(),
})
// return all the buckets for admin users
if isAdmin {
buckets = append(buckets, s3response.ListAllMyBucketsEntry{
Name: entry.Name(),
CreationDate: fi.ModTime(),
})
continue
}
aclTag, err := xattr.Get(entry.Name(), aclkey)
if err != nil {
return s3response.ListAllMyBucketsResult{}, fmt.Errorf("get acl tag: %w", err)
}
var acl auth.ACL
err = json.Unmarshal(aclTag, &acl)
if err != nil {
return s3response.ListAllMyBucketsResult{}, fmt.Errorf("parse acl tag: %w", err)
}
if acl.Owner == owner {
buckets = append(buckets, s3response.ListAllMyBucketsEntry{
Name: entry.Name(),
CreationDate: fi.ModTime(),
})
}
}
sort.Sort(backend.ByBucketName(buckets))
@@ -130,6 +152,9 @@ func (p *Posix) ListBuckets(_ context.Context, owner string, isRoot bool) (s3res
Buckets: s3response.ListAllMyBucketsList{
Bucket: buckets,
},
Owner: s3response.CanonicalUser{
ID: owner,
},
}, nil
}

View File

@@ -33,6 +33,12 @@ func TestHeadBucket(s *S3Conf) {
HeadBucket_success(s)
}
func TestListBuckets(s *S3Conf) {
ListBuckets_as_user(s)
ListBuckets_as_admin(s)
ListBuckets_success(s)
}
func TestDeleteBucket(s *S3Conf) {
DeleteBucket_non_existing_bucket(s)
DeleteBucket_non_empty_bucket(s)
@@ -176,6 +182,7 @@ func TestFullFlow(s *S3Conf) {
TestAuthentication(s)
TestCreateBucket(s)
TestHeadBucket(s)
TestListBuckets(s)
TestDeleteBucket(s)
TestPutObject(s)
TestHeadObject(s)

View File

@@ -18,6 +18,7 @@ import (
"github.com/aws/aws-sdk-go-v2/service/s3/types"
"github.com/google/uuid"
"github.com/versity/versitygw/s3err"
"github.com/versity/versitygw/s3response"
)
var (
@@ -710,6 +711,189 @@ func HeadBucket_success(s *S3Conf) {
})
}
func ListBuckets_as_user(s *S3Conf) {
testName := "ListBuckets_as_user"
actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
buckets := []s3response.ListAllMyBucketsEntry{{Name: bucket}}
for i := 0; i < 6; i++ {
bckt := getBucketName()
err := setup(s, bckt)
if err != nil {
return err
}
buckets = append(buckets, s3response.ListAllMyBucketsEntry{
Name: bckt,
})
}
usr := user{
access: "grt1",
secret: "grt1secret",
role: "user",
}
err := createUsers(s, []user{usr})
if err != nil {
return err
}
cfg := *s
cfg.awsID = usr.access
cfg.awsSecret = usr.secret
bckts := []string{}
for i := 0; i < 3; i++ {
bckts = append(bckts, buckets[i].Name)
}
err = changeBucketsOwner(s, bckts, usr.access)
if err != nil {
return err
}
userClient := s3.NewFromConfig(cfg.Config())
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
out, err := userClient.ListBuckets(ctx, &s3.ListBucketsInput{})
cancel()
if err != nil {
return err
}
if *out.Owner.ID != usr.access {
return fmt.Errorf("expected buckets owner to be %v, instead got %v", usr.access, *out.Owner.ID)
}
if ok := compareBuckets(out.Buckets, buckets[:3]); !ok {
return fmt.Errorf("expected list buckets result to be %v, instead got %v", buckets[:3], out.Buckets)
}
for _, elem := range buckets[1:] {
err = teardown(s, elem.Name)
if err != nil {
return err
}
}
return nil
})
}
func ListBuckets_as_admin(s *S3Conf) {
testName := "ListBuckets_as_admin"
actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
buckets := []s3response.ListAllMyBucketsEntry{{Name: bucket}}
for i := 0; i < 6; i++ {
bckt := getBucketName()
err := setup(s, bckt)
if err != nil {
return err
}
buckets = append(buckets, s3response.ListAllMyBucketsEntry{
Name: bckt,
})
}
usr := user{
access: "grt1",
secret: "grt1secret",
role: "user",
}
admin := user{
access: "admin1",
secret: "admin1secret",
role: "admin",
}
err := createUsers(s, []user{usr, admin})
if err != nil {
return err
}
cfg := *s
cfg.awsID = admin.access
cfg.awsSecret = admin.secret
bckts := []string{}
for i := 0; i < 3; i++ {
bckts = append(bckts, buckets[i].Name)
}
err = changeBucketsOwner(s, bckts, usr.access)
if err != nil {
return err
}
adminClient := s3.NewFromConfig(cfg.Config())
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
out, err := adminClient.ListBuckets(ctx, &s3.ListBucketsInput{})
cancel()
if err != nil {
return err
}
if *out.Owner.ID != admin.access {
return fmt.Errorf("expected buckets owner to be %v, instead got %v", admin.access, *out.Owner.ID)
}
if ok := compareBuckets(out.Buckets, buckets); !ok {
return fmt.Errorf("expected list buckets result to be %v, instead got %v", buckets, out.Buckets)
}
for _, elem := range buckets[1:] {
err = teardown(s, elem.Name)
if err != nil {
return err
}
}
return nil
})
}
func ListBuckets_success(s *S3Conf) {
testName := "ListBuckets_success"
actionHandler(s, testName, func(s3client *s3.Client, bucket string) error {
buckets := []s3response.ListAllMyBucketsEntry{{Name: bucket}}
for i := 0; i < 5; i++ {
bckt := getBucketName()
err := setup(s, bckt)
if err != nil {
return err
}
buckets = append(buckets, s3response.ListAllMyBucketsEntry{
Name: bckt,
})
}
ctx, cancel := context.WithTimeout(context.Background(), shortTimeout)
out, err := s3client.ListBuckets(ctx, &s3.ListBucketsInput{})
cancel()
if err != nil {
return err
}
if *out.Owner.ID != s.awsID {
return fmt.Errorf("expected owner to be %v, instead got %v", s.awsID, *out.Owner.ID)
}
if ok := compareBuckets(out.Buckets, buckets); !ok {
return fmt.Errorf("expected list buckets result to be %v, instead got %v", buckets, out.Buckets)
}
for _, elem := range buckets[1:] {
err = teardown(s, elem.Name)
if err != nil {
return err
}
}
return nil
})
}
func CreateDeleteBucket_success(s *S3Conf) {
testName := "CreateBucket_success"
runF(testName)

View File

@@ -22,6 +22,7 @@ import (
"github.com/aws/aws-sdk-go-v2/service/s3/types"
"github.com/aws/smithy-go"
"github.com/versity/versitygw/s3err"
"github.com/versity/versitygw/s3response"
)
var (
@@ -360,6 +361,26 @@ func areMapsSame(mp1, mp2 map[string]string) bool {
return true
}
func compareBuckets(list1 []types.Bucket, list2 []s3response.ListAllMyBucketsEntry) bool {
if len(list1) != len(list2) {
return false
}
elementMap := make(map[string]bool)
for _, elem := range list1 {
elementMap[*elem.Name] = true
}
for _, elem := range list2 {
if _, found := elementMap[elem.Name]; !found {
return false
}
}
return true
}
func compareObjects(list1 []string, list2 []types.Object) bool {
if len(list1) != len(list2) {
return false
@@ -471,3 +492,17 @@ func createUsers(s *S3Conf, users []user) error {
}
return nil
}
func changeBucketsOwner(s *S3Conf, buckets []string, owner string) error {
for _, bucket := range buckets {
out, err := execCommand("admin", "-a", s.awsID, "-s", s.awsSecret, "-er", s.endpoint, "change-bucket-owner", "-b", bucket, "-o", owner)
if err != nil {
return err
}
if !strings.Contains(string(out), "Bucket owner has been updated successfully") {
return fmt.Errorf(string(out))
}
}
return nil
}

View File

@@ -53,8 +53,8 @@ func New(be backend.Backend, iam auth.IAMService, logger s3log.AuditLogger, evs
}
func (c S3ApiController) ListBuckets(ctx *fiber.Ctx) error {
access, isRoot := ctx.Locals("access").(string), ctx.Locals("isRoot").(bool)
res, err := c.be.ListBuckets(ctx.Context(), access, isRoot)
access, role := ctx.Locals("access").(string), ctx.Locals("role").(string)
res, err := c.be.ListBuckets(ctx.Context(), access, role == "admin")
return SendXMLResponse(ctx, res, err, &MetaOpts{Logger: c.logger, Action: "ListBucket"})
}

View File

@@ -90,9 +90,6 @@ func TestS3ApiController_ListBuckets(t *testing.T) {
app := fiber.New()
s3ApiController := S3ApiController{
be: &BackendMock{
GetBucketAclFunc: func(context.Context, *s3.GetBucketAclInput) ([]byte, error) {
return acldata, nil
},
ListBucketsFunc: func(context.Context, string, bool) (s3response.ListAllMyBucketsResult, error) {
return s3response.ListAllMyBucketsResult{}, nil
},
@@ -101,7 +98,7 @@ func TestS3ApiController_ListBuckets(t *testing.T) {
app.Use(func(ctx *fiber.Ctx) error {
ctx.Locals("access", "valid access")
ctx.Locals("isRoot", true)
ctx.Locals("role", "admin")
ctx.Locals("isDebug", false)
return ctx.Next()
})
@@ -111,9 +108,6 @@ func TestS3ApiController_ListBuckets(t *testing.T) {
appErr := fiber.New()
s3ApiControllerErr := S3ApiController{
be: &BackendMock{
GetBucketAclFunc: func(context.Context, *s3.GetBucketAclInput) ([]byte, error) {
return acldata, nil
},
ListBucketsFunc: func(context.Context, string, bool) (s3response.ListAllMyBucketsResult, error) {
return s3response.ListAllMyBucketsResult{}, s3err.GetAPIError(s3err.ErrMethodNotAllowed)
},
@@ -122,7 +116,7 @@ func TestS3ApiController_ListBuckets(t *testing.T) {
appErr.Use(func(ctx *fiber.Ctx) error {
ctx.Locals("access", "valid access")
ctx.Locals("isRoot", true)
ctx.Locals("role", "admin")
ctx.Locals("isDebug", false)
return ctx.Next()
})