adding test case for bucket users (#773)

Co-authored-by: Minio Trusted <trusted@minio.io>
Co-authored-by: Daniel Valdivia <18384552+dvaldivia@users.noreply.github.com>
This commit is contained in:
adfost
2021-05-28 18:28:39 -07:00
committed by GitHub
parent 7db4e187ec
commit 5782b9d9a2
3 changed files with 220 additions and 19 deletions

View File

@@ -49,6 +49,6 @@ func TestArnsList(t *testing.T) {
arnsList, err = getArns(ctx, adminClient) arnsList, err = getArns(ctx, adminClient)
assert.Nil(arnsList, "arn list was not returned nil") assert.Nil(arnsList, "arn list was not returned nil")
assert.NotNil(err, "An error should have ben returned") assert.NotNil(err, "An error should have been returned")
} }

View File

@@ -17,6 +17,10 @@
package restapi package restapi
import ( import (
"sort"
iampolicy "github.com/minio/minio/pkg/iam/policy"
"github.com/go-openapi/errors" "github.com/go-openapi/errors"
"github.com/go-openapi/runtime/middleware" "github.com/go-openapi/runtime/middleware"
"github.com/minio/console/models" "github.com/minio/console/models"
@@ -30,6 +34,10 @@ import (
"strings" "strings"
) )
const Allow = 1
const Deny = -1
const Unknown = 0
func registerUsersHandlers(api *operations.ConsoleAPI) { func registerUsersHandlers(api *operations.ConsoleAPI) {
// List Users // List Users
api.AdminAPIListUsersHandler = admin_api.ListUsersHandlerFunc(func(params admin_api.ListUsersParams, session *models.Principal) middleware.Responder { api.AdminAPIListUsersHandler = admin_api.ListUsersHandlerFunc(func(params admin_api.ListUsersParams, session *models.Principal) middleware.Responder {
@@ -487,23 +495,49 @@ func getListUsersWithAccessToBucketResponse(session *models.Principal, bucket st
// defining the client to be used // defining the client to be used
adminClient := adminClient{client: mAdmin} adminClient := adminClient{client: mAdmin}
users, err := listUsers(ctx, adminClient) return listUsersWithAccessToBucket(ctx, adminClient, bucket)
}
func policyAllowsAndMatchesBucket(policy *iampolicy.Policy, bucket string) int {
policyStatements := policy.Statements
for i := 0; i < len(policyStatements); i++ {
resources := policyStatements[i].Resources
effect := policyStatements[i].Effect
if resources.Match(bucket, map[string][]string{}) {
if effect.IsValid() {
if effect.IsAllowed(true) {
return Allow
}
return Deny
}
}
}
return Unknown
}
func listUsersWithAccessToBucket(ctx context.Context, adminClient MinioAdmin, bucket string) ([]string, *models.Error) {
users, err := adminClient.listUsers(ctx)
if err != nil { if err != nil {
return nil, prepareError(err) return nil, prepareError(err)
} }
var retval []string var retval []string
seen := make(map[string]bool) akHasAccess := make(map[string]bool)
for i := 0; i < len(users); i++ { akIsDenied := make(map[string]bool)
for _, policyName := range users[i].Policy { for k, v := range users {
for _, policyName := range strings.Split(v.PolicyName, ",") {
policyName = strings.TrimSpace(policyName)
policy, err := adminClient.getPolicy(ctx, policyName) policy, err := adminClient.getPolicy(ctx, policyName)
if err == nil { if err == nil {
parsedPolicy, err2 := parsePolicy(policyName, policy) if !akIsDenied[k] {
if err2 == nil && policyMatchesBucket(parsedPolicy, bucket) && !seen[users[i].AccessKey] { switch policyAllowsAndMatchesBucket(policy, bucket) {
retval = append(retval, users[i].AccessKey) case Allow:
seen[users[i].AccessKey] = true if !akHasAccess[k] {
akHasAccess[k] = true
}
case Deny:
akIsDenied[k] = true
akHasAccess[k] = false
} }
if err2 != nil {
log.Println(err2)
} }
} else { } else {
log.Println(err) log.Println(err)
@@ -521,14 +555,17 @@ func getListUsersWithAccessToBucketResponse(session *models.Principal, bucket st
if err == nil { if err == nil {
policy, err2 := adminClient.getPolicy(ctx, info.Policy) policy, err2 := adminClient.getPolicy(ctx, info.Policy)
if err2 == nil { if err2 == nil {
parsedPolicy, err3 := parsePolicy(info.Policy, policy)
for j := 0; j < len(info.Members); j++ { for j := 0; j < len(info.Members); j++ {
if err3 == nil && !seen[info.Members[j]] && policyMatchesBucket(parsedPolicy, bucket) { if !akIsDenied[info.Members[j]] {
retval = append(retval, info.Members[j]) switch policyAllowsAndMatchesBucket(policy, bucket) {
seen[info.Members[j]] = true case Allow:
if !akHasAccess[info.Members[j]] {
akHasAccess[info.Members[j]] = true
}
case Deny:
akIsDenied[info.Members[j]] = true
akHasAccess[info.Members[j]] = false
} }
if err3 != nil {
log.Println(err3)
} }
} }
} else { } else {
@@ -538,5 +575,11 @@ func getListUsersWithAccessToBucketResponse(session *models.Principal, bucket st
log.Println(err) log.Println(err)
} }
} }
for k, v := range akHasAccess {
if v {
retval = append(retval, k)
}
}
sort.Strings(retval)
return retval, nil return retval, nil
} }

View File

@@ -17,11 +17,14 @@
package restapi package restapi
import ( import (
"bytes"
"context" "context"
"fmt" "fmt"
"strings" "strings"
"testing" "testing"
iampolicy "github.com/minio/minio/pkg/iam/policy"
"github.com/minio/minio/pkg/madmin" "github.com/minio/minio/pkg/madmin"
"errors" "errors"
@@ -400,3 +403,158 @@ func TestUserGroupsBulk(t *testing.T) {
assert.Equal("error in users-groups assignation: \"error,error,error\"", err.Error()) assert.Equal("error in users-groups assignation: \"error,error,error\"", err.Error())
} }
} }
func TestListUsersWithAccessToBucket(t *testing.T) {
assert := asrt.New(t)
ctx := context.Background()
adminClient := adminClientMock{}
user1 := madmin.UserInfo{SecretKey: "testtest",
PolicyName: "consoleAdmin,testPolicy,redundantPolicy",
Status: "enabled",
MemberOf: []string{"group1"},
}
user2 := madmin.UserInfo{SecretKey: "testtest",
PolicyName: "testPolicy, otherPolicy",
Status: "enabled",
MemberOf: []string{"group1"},
}
mockUsers := map[string]madmin.UserInfo{"testuser1": user1, "testuser2": user2}
minioListUsersMock = func() (map[string]madmin.UserInfo, error) {
return mockUsers, nil
}
policyMap := map[string]string{
"consoleAdmin": `{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"admin:*"
]
},
{
"Effect": "Allow",
"Action": [
"s3:*"
],
"Resource": [
"arn:aws:s3:::*"
]
}
]
}`,
"testPolicy": `{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Deny",
"Action": [
"s3:*"
],
"Resource": [
"arn:aws:s3:::bucket1"
]
}
]
}`,
"otherPolicy": `{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"s3:*"
],
"Resource": [
"arn:aws:s3:::bucket2"
]
}
]
}`,
"thirdPolicy": `{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"s3:*"
],
"Resource": [
"arn:aws:s3:::bucket3"
]
}
]
}`, "RedundantPolicy": `{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"s3:*"
],
"Resource": [
"arn:aws:s3:::bucket1"
]
}
]
}`,
}
minioGetPolicyMock = func(name string) (*iampolicy.Policy, error) {
iamp, err := iampolicy.ParseConfig(bytes.NewReader([]byte(policyMap[name])))
if err != nil {
return nil, err
}
return iamp, nil
}
minioListGroupsMock = func() ([]string, error) {
return []string{"group1"}, nil
}
minioGetGroupDescriptionMock = func(name string) (*madmin.GroupDesc, error) {
if name == "group1" {
mockResponse := &madmin.GroupDesc{
Name: "group1",
Policy: "thirdPolicy",
Members: []string{"testuser1", "testuser2"},
Status: "enabled",
}
return mockResponse, nil
}
return nil, errorGeneric
}
type args struct {
bucket string
}
tests := []struct {
name string
args args
want []string
}{
{
name: "Test1",
args: args{bucket: "bucket0"},
want: []string{"testuser1"},
},
{
name: "Test2",
args: args{bucket: "bucket1"},
want: []string(nil),
},
{
name: "Test3",
args: args{bucket: "bucket2"},
want: []string{"testuser1", "testuser2"},
},
{
name: "Test4",
args: args{bucket: "bucket3"},
want: []string{"testuser1", "testuser2"},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, _ := listUsersWithAccessToBucket(ctx, adminClient, tt.args.bucket)
assert.Equal(got, tt.want)
})
}
}