Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Issue 204, ListBuckets for admin and regular users #214

Merged
merged 1 commit into from
Sep 7, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion backend/backend.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
35 changes: 30 additions & 5 deletions backend/posix/posix.go
Original file line number Diff line number Diff line change
Expand Up @@ -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{},
Expand All @@ -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))
Expand All @@ -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
}

Expand Down
7 changes: 7 additions & 0 deletions integration/action-tests.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -176,6 +182,7 @@ func TestFullFlow(s *S3Conf) {
TestAuthentication(s)
TestCreateBucket(s)
TestHeadBucket(s)
TestListBuckets(s)
TestDeleteBucket(s)
TestPutObject(s)
TestHeadObject(s)
Expand Down
184 changes: 184 additions & 0 deletions integration/tests.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
Expand Down Expand Up @@ -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)
Expand Down
35 changes: 35 additions & 0 deletions integration/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
}
4 changes: 2 additions & 2 deletions s3api/controllers/base.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"})
}

Expand Down
10 changes: 2 additions & 8 deletions s3api/controllers/base_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
},
Expand All @@ -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()
})
Expand All @@ -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)
},
Expand All @@ -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()
})
Expand Down