Managed group list pagination (#4208)
* wip * managed groups pagination * address PR comments * missed one change
This commit is contained in:
1 parent
f49ba4662c
commit
52b1c1b4c7
31 files changed
+4059
-278
No files matched your search
@@ -279,7 +279,7 @@ func (c *Controller) registerGrpcServices(s *grpc.Server) error {
|
||||
services.RegisterSessionServiceServer(s, ss)
|
||||
}
|
||||
if _, ok := currentServices[services.ManagedGroupService_ServiceDesc.ServiceName]; !ok {
|
||||
mgs, err := managed_groups.NewService(c.baseContext, c.OidcRepoFn, c.LdapRepoFn)
|
||||
mgs, err := managed_groups.NewService(c.baseContext, c.OidcRepoFn, c.LdapRepoFn, c.conf.RawConfig.Controller.MaxPageSize)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create managed groups handler service: %w", err)
|
||||
}
|
||||
|
||||
@@ -19,6 +19,8 @@ import (
|
||||
"github.com/hashicorp/boundary/internal/daemon/controller/handlers"
|
||||
"github.com/hashicorp/boundary/internal/errors"
|
||||
pbs "github.com/hashicorp/boundary/internal/gen/controller/api/services"
|
||||
"github.com/hashicorp/boundary/internal/listtoken"
|
||||
"github.com/hashicorp/boundary/internal/pagination"
|
||||
"github.com/hashicorp/boundary/internal/perms"
|
||||
"github.com/hashicorp/boundary/internal/requests"
|
||||
"github.com/hashicorp/boundary/internal/types/action"
|
||||
@@ -93,14 +95,15 @@ func init() {
|
||||
type Service struct {
|
||||
pbs.UnsafeManagedGroupServiceServer
|
||||
|
||||
oidcRepoFn common.OidcAuthRepoFactory
|
||||
ldapRepoFn common.LdapAuthRepoFactory
|
||||
oidcRepoFn common.OidcAuthRepoFactory
|
||||
ldapRepoFn common.LdapAuthRepoFactory
|
||||
maxPageSize uint
|
||||
}
|
||||
|
||||
var _ pbs.ManagedGroupServiceServer = (*Service)(nil)
|
||||
|
||||
// NewService returns a managed group service which handles managed group related requests to boundary.
|
||||
func NewService(ctx context.Context, oidcRepo common.OidcAuthRepoFactory, ldapRepo common.LdapAuthRepoFactory) (Service, error) {
|
||||
func NewService(ctx context.Context, oidcRepo common.OidcAuthRepoFactory, ldapRepo common.LdapAuthRepoFactory, maxPageSize uint) (Service, error) {
|
||||
const op = "managed_groups.NewService"
|
||||
switch {
|
||||
case oidcRepo == nil:
|
||||
@@ -108,70 +111,187 @@ func NewService(ctx context.Context, oidcRepo common.OidcAuthRepoFactory, ldapRe
|
||||
case ldapRepo == nil:
|
||||
return Service{}, errors.New(ctx, errors.InvalidParameter, op, "missing ldap repository provided")
|
||||
}
|
||||
return Service{oidcRepoFn: oidcRepo, ldapRepoFn: ldapRepo}, nil
|
||||
if maxPageSize == 0 {
|
||||
maxPageSize = uint(globals.DefaultMaxPageSize)
|
||||
}
|
||||
return Service{oidcRepoFn: oidcRepo, ldapRepoFn: ldapRepo, maxPageSize: maxPageSize}, nil
|
||||
}
|
||||
|
||||
// ListManagedGroups implements the interface pbs.ManagedGroupsServiceServer.
|
||||
func (s Service) ListManagedGroups(ctx context.Context, req *pbs.ListManagedGroupsRequest) (*pbs.ListManagedGroupsResponse, error) {
|
||||
const op = "managed_groups.(Service).ListManagedGroups"
|
||||
if err := validateListRequest(ctx, req); err != nil {
|
||||
return nil, err
|
||||
return nil, errors.Wrap(ctx, err, op)
|
||||
}
|
||||
_, authResults := s.parentAndAuthResult(ctx, req.GetAuthMethodId(), action.List)
|
||||
if authResults.Error != nil {
|
||||
return nil, authResults.Error
|
||||
}
|
||||
ul, err := s.listFromRepo(ctx, req.GetAuthMethodId())
|
||||
|
||||
authMethodId := req.GetAuthMethodId()
|
||||
pageSize := int(s.maxPageSize)
|
||||
// Use the requested page size only if it is smaller than
|
||||
// the configured max.
|
||||
if req.GetPageSize() != 0 && uint(req.GetPageSize()) < s.maxPageSize {
|
||||
pageSize = int(req.GetPageSize())
|
||||
}
|
||||
|
||||
var filterItemFn func(ctx context.Context, item auth.ManagedGroup) (bool, error)
|
||||
switch {
|
||||
case req.GetFilter() != "":
|
||||
// Only use a filter if we need to
|
||||
filter, err := handlers.NewFilter(ctx, req.GetFilter())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// TODO: replace the need for this function with some way to convert the `filter`
|
||||
// to a domain type. This would allow filtering to happen in the domain, and we could
|
||||
// remove this callback altogether.
|
||||
filterItemFn = func(ctx context.Context, item auth.ManagedGroup) (bool, error) {
|
||||
outputOpts, ok := newOutputOpts(ctx, item, authMethodId, authResults)
|
||||
if !ok {
|
||||
return false, nil
|
||||
}
|
||||
pbItem, err := toProto(ctx, item, outputOpts...)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
filterable, err := subtypes.Filterable(ctx, pbItem)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return filter.Match(filterable), nil
|
||||
}
|
||||
default:
|
||||
filterItemFn = func(ctx context.Context, item auth.ManagedGroup) (bool, error) {
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
|
||||
grantsHash, err := authResults.GrantsHash(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(ul) == 0 {
|
||||
return &pbs.ListManagedGroupsResponse{}, nil
|
||||
|
||||
var listResp *pagination.ListResponse[auth.ManagedGroup]
|
||||
var sortBy string
|
||||
switch globals.ResourceInfoFromPrefix(authMethodId).Subtype {
|
||||
case ldap.Subtype:
|
||||
repo, err := s.ldapRepoFn()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(ctx, err, op)
|
||||
}
|
||||
if req.GetListToken() == "" {
|
||||
sortBy = "created_time"
|
||||
listResp, err = ldap.ListManagedGroups(ctx, grantsHash, pageSize, filterItemFn, repo, authMethodId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
listToken, err := handlers.ParseListToken(ctx, req.GetListToken(), resource.ManagedGroup, grantsHash)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
switch st := listToken.Subtype.(type) {
|
||||
case *listtoken.PaginationToken:
|
||||
sortBy = "created_time"
|
||||
listResp, err = ldap.ListManagedGroupsPage(ctx, grantsHash, pageSize, filterItemFn, listToken, repo, authMethodId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
case *listtoken.StartRefreshToken:
|
||||
sortBy = "updated_time"
|
||||
listResp, err = ldap.ListManagedGroupsRefresh(ctx, grantsHash, pageSize, filterItemFn, listToken, repo, authMethodId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
case *listtoken.RefreshToken:
|
||||
sortBy = "updated_time"
|
||||
listResp, err = ldap.ListManagedGroupsRefreshPage(ctx, grantsHash, pageSize, filterItemFn, listToken, repo, authMethodId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
default:
|
||||
return nil, handlers.ApiErrorWithCodeAndMessage(codes.InvalidArgument, "unexpected list token subtype: %T", st)
|
||||
}
|
||||
}
|
||||
case oidc.Subtype:
|
||||
repo, err := s.oidcRepoFn()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(ctx, err, op)
|
||||
}
|
||||
if req.GetListToken() == "" {
|
||||
sortBy = "created_time"
|
||||
listResp, err = oidc.ListManagedGroups(ctx, grantsHash, pageSize, filterItemFn, repo, authMethodId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
listToken, err := handlers.ParseListToken(ctx, req.GetListToken(), resource.ManagedGroup, grantsHash)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
switch st := listToken.Subtype.(type) {
|
||||
case *listtoken.PaginationToken:
|
||||
sortBy = "created_time"
|
||||
listResp, err = oidc.ListManagedGroupsPage(ctx, grantsHash, pageSize, filterItemFn, listToken, repo, authMethodId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
case *listtoken.StartRefreshToken:
|
||||
sortBy = "updated_time"
|
||||
listResp, err = oidc.ListManagedGroupsRefresh(ctx, grantsHash, pageSize, filterItemFn, listToken, repo, authMethodId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
case *listtoken.RefreshToken:
|
||||
sortBy = "updated_time"
|
||||
listResp, err = oidc.ListManagedGroupsRefreshPage(ctx, grantsHash, pageSize, filterItemFn, listToken, repo, authMethodId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
default:
|
||||
return nil, handlers.ApiErrorWithCodeAndMessage(codes.InvalidArgument, "unexpected list token subtype: %T", st)
|
||||
}
|
||||
}
|
||||
default:
|
||||
return nil, handlers.ApiErrorWithCodeAndMessage(codes.InvalidArgument, "unknown auth method type for id: %s", authMethodId)
|
||||
}
|
||||
|
||||
filter, err := handlers.NewFilter(ctx, req.GetFilter())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
finalItems := make([]*pb.ManagedGroup, 0, len(ul))
|
||||
|
||||
res := perms.Resource{
|
||||
ScopeId: authResults.Scope.Id,
|
||||
Type: resource.ManagedGroup,
|
||||
Pin: req.GetAuthMethodId(),
|
||||
}
|
||||
for _, mg := range ul {
|
||||
res.Id = mg.GetPublicId()
|
||||
authorizedActions := authResults.FetchActionSetForId(ctx, mg.GetPublicId(), IdActions[globals.ResourceInfoFromPrefix(mg.GetPublicId()).Subtype], requestauth.WithResource(&res)).Strings()
|
||||
if len(authorizedActions) == 0 {
|
||||
finalItems := make([]*pb.ManagedGroup, 0, len(listResp.Items))
|
||||
for _, item := range listResp.Items {
|
||||
outputOpts, ok := newOutputOpts(ctx, item, authMethodId, authResults)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
outputFields := authResults.FetchOutputFields(res, action.List).SelfOrDefaults(authResults.UserId)
|
||||
outputOpts := make([]handlers.Option, 0, 3)
|
||||
outputOpts = append(outputOpts, handlers.WithOutputFields(outputFields))
|
||||
if outputFields.Has(globals.ScopeField) {
|
||||
outputOpts = append(outputOpts, handlers.WithScope(authResults.Scope))
|
||||
}
|
||||
if outputFields.Has(globals.AuthorizedActionsField) {
|
||||
outputOpts = append(outputOpts, handlers.WithAuthorizedActions(authorizedActions))
|
||||
pbItem, err := toProto(ctx, item, outputOpts...)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
finalItems = append(finalItems, pbItem)
|
||||
}
|
||||
respType := "delta"
|
||||
if listResp.CompleteListing {
|
||||
respType = "complete"
|
||||
}
|
||||
resp := &pbs.ListManagedGroupsResponse{
|
||||
Items: finalItems,
|
||||
EstItemCount: uint32(listResp.EstimatedItemCount),
|
||||
RemovedIds: listResp.DeletedIds,
|
||||
ResponseType: respType,
|
||||
SortBy: sortBy,
|
||||
SortDir: "desc",
|
||||
}
|
||||
|
||||
item, err := toProto(ctx, mg, outputOpts...)
|
||||
if listResp.ListToken != nil {
|
||||
resp.ListToken, err = handlers.MarshalListToken(ctx, listResp.ListToken, pbs.ResourceType_RESOURCE_TYPE_MANAGED_GROUP)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// This comes last so that we can use item fields in the filter after
|
||||
// the allowed fields are populated above
|
||||
filterable, err := subtypes.Filterable(ctx, item)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if filter.Match(filterable) {
|
||||
finalItems = append(finalItems, item)
|
||||
}
|
||||
}
|
||||
return &pbs.ListManagedGroupsResponse{Items: finalItems}, nil
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// GetManagedGroup implements the interface pbs.ManagedGroupServiceServer.
|
||||
@@ -588,39 +708,6 @@ func (s Service) deleteFromRepo(ctx context.Context, scopeId, id string) (bool,
|
||||
return rows > 0, nil
|
||||
}
|
||||
|
||||
func (s Service) listFromRepo(ctx context.Context, authMethodId string) ([]auth.ManagedGroup, error) {
|
||||
const op = "managed_groups.(Service).listFromRepo"
|
||||
|
||||
var outUl []auth.ManagedGroup
|
||||
switch globals.ResourceInfoFromPrefix(authMethodId).Subtype {
|
||||
case oidc.Subtype:
|
||||
oidcRepo, err := s.oidcRepoFn()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(ctx, err, op)
|
||||
}
|
||||
oidcl, err := oidcRepo.ListManagedGroups(ctx, authMethodId, oidc.WithLimit(-1))
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(ctx, err, op)
|
||||
}
|
||||
for _, a := range oidcl {
|
||||
outUl = append(outUl, a)
|
||||
}
|
||||
case ldap.Subtype:
|
||||
ldapRepo, err := s.ldapRepoFn()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(ctx, err, op)
|
||||
}
|
||||
ldapUl, err := ldapRepo.ListManagedGroups(ctx, authMethodId, ldap.WithLimit(ctx, -1))
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(ctx, err, op)
|
||||
}
|
||||
for _, a := range ldapUl {
|
||||
outUl = append(outUl, a)
|
||||
}
|
||||
}
|
||||
return outUl, nil
|
||||
}
|
||||
|
||||
func (s Service) parentAndAuthResult(ctx context.Context, id string, a action.Type) (auth.AuthMethod, requestauth.VerifyResults) {
|
||||
const op = "managed_groups.(Service)."
|
||||
res := requestauth.VerifyResults{}
|
||||
@@ -908,3 +995,27 @@ func validateListRequest(ctx context.Context, req *pbs.ListManagedGroupsRequest)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func newOutputOpts(ctx context.Context, item auth.ManagedGroup, authMethodId string, authResults requestauth.VerifyResults) ([]handlers.Option, bool) {
|
||||
res := perms.Resource{
|
||||
ScopeId: authResults.Scope.Id,
|
||||
Type: resource.ManagedGroup,
|
||||
Pin: authMethodId,
|
||||
}
|
||||
res.Id = item.GetPublicId()
|
||||
authorizedActions := authResults.FetchActionSetForId(ctx, item.GetPublicId(), IdActions[globals.ResourceInfoFromPrefix(item.GetPublicId()).Subtype], requestauth.WithResource(&res)).Strings()
|
||||
if len(authorizedActions) == 0 {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
outputFields := authResults.FetchOutputFields(res, action.List).SelfOrDefaults(authResults.UserId)
|
||||
outputOpts := make([]handlers.Option, 0, 3)
|
||||
outputOpts = append(outputOpts, handlers.WithOutputFields(outputFields))
|
||||
if outputFields.Has(globals.ScopeField) {
|
||||
outputOpts = append(outputOpts, handlers.WithScope(authResults.Scope))
|
||||
}
|
||||
if outputFields.Has(globals.AuthorizedActionsField) {
|
||||
outputOpts = append(outputOpts, handlers.WithAuthorizedActions(authorizedActions))
|
||||
}
|
||||
return outputOpts, true
|
||||
}
|
||||
@@ -7,7 +7,7 @@ import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"sort"
|
||||
"slices"
|
||||
"strconv"
|
||||
"strings"
|
||||
"testing"
|
||||
@@ -16,16 +16,22 @@ import (
|
||||
"github.com/google/go-cmp/cmp/cmpopts"
|
||||
"github.com/hashicorp/boundary/globals"
|
||||
"github.com/hashicorp/boundary/internal/auth/ldap"
|
||||
ldapstore "github.com/hashicorp/boundary/internal/auth/ldap/store"
|
||||
"github.com/hashicorp/boundary/internal/auth/oidc"
|
||||
oidcstore "github.com/hashicorp/boundary/internal/auth/oidc/store"
|
||||
"github.com/hashicorp/boundary/internal/auth/password"
|
||||
"github.com/hashicorp/boundary/internal/authtoken"
|
||||
"github.com/hashicorp/boundary/internal/daemon/controller/auth"
|
||||
"github.com/hashicorp/boundary/internal/daemon/controller/common"
|
||||
"github.com/hashicorp/boundary/internal/daemon/controller/handlers"
|
||||
"github.com/hashicorp/boundary/internal/daemon/controller/handlers/managed_groups"
|
||||
"github.com/hashicorp/boundary/internal/db"
|
||||
pbs "github.com/hashicorp/boundary/internal/gen/controller/api/services"
|
||||
authpb "github.com/hashicorp/boundary/internal/gen/controller/auth"
|
||||
"github.com/hashicorp/boundary/internal/iam"
|
||||
"github.com/hashicorp/boundary/internal/kms"
|
||||
"github.com/hashicorp/boundary/internal/requests"
|
||||
"github.com/hashicorp/boundary/internal/server"
|
||||
"github.com/hashicorp/boundary/internal/types/action"
|
||||
"github.com/hashicorp/boundary/internal/types/scope"
|
||||
pb "github.com/hashicorp/boundary/sdk/pbs/controller/api/resources/managedgroups"
|
||||
@@ -94,7 +100,7 @@ func TestNewService(t *testing.T) {
|
||||
}
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
_, err := managed_groups.NewService(ctx, tc.oidcRepo, tc.ldapRepo)
|
||||
_, err := managed_groups.NewService(ctx, tc.oidcRepo, tc.ldapRepo, 1000)
|
||||
if tc.wantErr {
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.Error(), tc.wantErrContains)
|
||||
@@ -121,7 +127,7 @@ func TestGet(t *testing.T) {
|
||||
return ldap.NewRepository(ctx, rw, rw, kmsCache)
|
||||
}
|
||||
|
||||
s, err := managed_groups.NewService(ctx, oidcRepoFn, ldapRepoFn)
|
||||
s, err := managed_groups.NewService(ctx, oidcRepoFn, ldapRepoFn, 1000)
|
||||
require.NoError(t, err, "Couldn't create new managed groups service.")
|
||||
|
||||
org, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrap))
|
||||
@@ -268,6 +274,8 @@ func TestGet(t *testing.T) {
|
||||
func TestListOidc(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
conn, _ := db.TestSetup(t, "postgres")
|
||||
sqlDb, err := conn.SqlDB(ctx)
|
||||
require.NoError(t, err)
|
||||
rw := db.New(conn)
|
||||
wrap := db.TestWrapper(t)
|
||||
kmsCache := kms.TestKms(t, conn, wrap)
|
||||
@@ -333,6 +341,12 @@ func TestListOidc(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
slices.Reverse(wantSomeManagedGroups)
|
||||
slices.Reverse(wantOtherManagedGroups)
|
||||
|
||||
_, err = sqlDb.ExecContext(ctx, "analyze")
|
||||
require.NoError(t, err)
|
||||
|
||||
cases := []struct {
|
||||
name string
|
||||
req *pbs.ListManagedGroupsRequest
|
||||
@@ -343,17 +357,33 @@ func TestListOidc(t *testing.T) {
|
||||
{
|
||||
name: "List Some ManagedGroups",
|
||||
req: &pbs.ListManagedGroupsRequest{AuthMethodId: amSomeManagedGroups.GetPublicId()},
|
||||
res: &pbs.ListManagedGroupsResponse{Items: wantSomeManagedGroups},
|
||||
res: &pbs.ListManagedGroupsResponse{
|
||||
Items: wantSomeManagedGroups,
|
||||
EstItemCount: 3,
|
||||
ResponseType: "complete",
|
||||
SortBy: "created_time",
|
||||
SortDir: "desc",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "List Other ManagedGroups",
|
||||
req: &pbs.ListManagedGroupsRequest{AuthMethodId: amOtherManagedGroups.GetPublicId()},
|
||||
res: &pbs.ListManagedGroupsResponse{Items: wantOtherManagedGroups},
|
||||
res: &pbs.ListManagedGroupsResponse{
|
||||
Items: wantOtherManagedGroups,
|
||||
EstItemCount: 3,
|
||||
ResponseType: "complete",
|
||||
SortBy: "created_time",
|
||||
SortDir: "desc",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "List No ManagedGroups",
|
||||
req: &pbs.ListManagedGroupsRequest{AuthMethodId: amNoManagedGroups.GetPublicId()},
|
||||
res: &pbs.ListManagedGroupsResponse{},
|
||||
res: &pbs.ListManagedGroupsResponse{
|
||||
ResponseType: "complete",
|
||||
SortBy: "created_time",
|
||||
SortDir: "desc",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Unfound Auth Method",
|
||||
@@ -366,7 +396,13 @@ func TestListOidc(t *testing.T) {
|
||||
AuthMethodId: amSomeManagedGroups.GetPublicId(),
|
||||
Filter: fmt.Sprintf(`"/item/name"==%q`, wantSomeManagedGroups[1].Name.GetValue()),
|
||||
},
|
||||
res: &pbs.ListManagedGroupsResponse{Items: wantSomeManagedGroups[1:2]},
|
||||
res: &pbs.ListManagedGroupsResponse{
|
||||
Items: wantSomeManagedGroups[1:2],
|
||||
EstItemCount: 1,
|
||||
ResponseType: "complete",
|
||||
SortBy: "created_time",
|
||||
SortDir: "desc",
|
||||
},
|
||||
skipAnon: true,
|
||||
},
|
||||
{
|
||||
@@ -375,7 +411,11 @@ func TestListOidc(t *testing.T) {
|
||||
AuthMethodId: amSomeManagedGroups.GetPublicId(),
|
||||
Filter: `"/item/id"=="noManagedGroupmatchesthis"`,
|
||||
},
|
||||
res: &pbs.ListManagedGroupsResponse{},
|
||||
res: &pbs.ListManagedGroupsResponse{
|
||||
ResponseType: "complete",
|
||||
SortBy: "created_time",
|
||||
SortDir: "desc",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Filter Bad Format",
|
||||
@@ -386,7 +426,7 @@ func TestListOidc(t *testing.T) {
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
assert, require := assert.New(t), require.New(t)
|
||||
s, err := managed_groups.NewService(ctx, oidcRepoFn, ldapRepoFn)
|
||||
s, err := managed_groups.NewService(ctx, oidcRepoFn, ldapRepoFn, 1000)
|
||||
require.NoError(err, "Couldn't create new managed group service.")
|
||||
|
||||
got, gErr := s.ListManagedGroups(auth.DisabledAuthTestContext(iamRepoFn, o.GetPublicId()), tc.req)
|
||||
@@ -397,17 +437,14 @@ func TestListOidc(t *testing.T) {
|
||||
} else {
|
||||
require.NoError(gErr)
|
||||
}
|
||||
sort.Slice(got.Items, func(i, j int) bool {
|
||||
return strings.Compare(got.Items[i].GetName().GetValue(),
|
||||
got.Items[j].GetName().GetValue()) < 0
|
||||
})
|
||||
assert.Empty(cmp.Diff(
|
||||
got,
|
||||
tc.res,
|
||||
protocmp.Transform(),
|
||||
cmpopts.SortSlices(func(a, b string) bool {
|
||||
return a < b
|
||||
}),
|
||||
protocmp.Transform(),
|
||||
protocmp.IgnoreFields(&pbs.ListManagedGroupsResponse{}, "list_token"),
|
||||
), "ListManagedGroups() with scope %q got response %q, wanted %q", tc.req, got, tc.res)
|
||||
|
||||
// Now test with anon
|
||||
@@ -433,6 +470,8 @@ func TestListLdap(t *testing.T) {
|
||||
rw := db.New(conn)
|
||||
wrap := db.TestWrapper(t)
|
||||
kmsCache := kms.TestKms(t, conn, wrap)
|
||||
sqlDb, err := conn.SqlDB(ctx)
|
||||
require.NoError(t, err)
|
||||
oidcRepoFn := func() (*oidc.Repository, error) {
|
||||
return oidc.NewRepository(ctx, rw, rw, kmsCache)
|
||||
}
|
||||
@@ -493,6 +532,12 @@ func TestListLdap(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
slices.Reverse(wantSomeManagedGroups)
|
||||
slices.Reverse(wantOtherManagedGroups)
|
||||
|
||||
_, err = sqlDb.ExecContext(ctx, "analyze")
|
||||
require.NoError(t, err)
|
||||
|
||||
cases := []struct {
|
||||
name string
|
||||
req *pbs.ListManagedGroupsRequest
|
||||
@@ -504,17 +549,33 @@ func TestListLdap(t *testing.T) {
|
||||
{
|
||||
name: "List Some ManagedGroups",
|
||||
req: &pbs.ListManagedGroupsRequest{AuthMethodId: amSomeManagedGroups.GetPublicId()},
|
||||
res: &pbs.ListManagedGroupsResponse{Items: wantSomeManagedGroups},
|
||||
res: &pbs.ListManagedGroupsResponse{
|
||||
Items: wantSomeManagedGroups,
|
||||
EstItemCount: 3,
|
||||
ResponseType: "complete",
|
||||
SortBy: "created_time",
|
||||
SortDir: "desc",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "List Other ManagedGroups",
|
||||
req: &pbs.ListManagedGroupsRequest{AuthMethodId: amOtherManagedGroups.GetPublicId()},
|
||||
res: &pbs.ListManagedGroupsResponse{Items: wantOtherManagedGroups},
|
||||
res: &pbs.ListManagedGroupsResponse{
|
||||
Items: wantOtherManagedGroups,
|
||||
EstItemCount: 3,
|
||||
ResponseType: "complete",
|
||||
SortBy: "created_time",
|
||||
SortDir: "desc",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "List No ManagedGroups",
|
||||
req: &pbs.ListManagedGroupsRequest{AuthMethodId: amNoManagedGroups.GetPublicId()},
|
||||
res: &pbs.ListManagedGroupsResponse{},
|
||||
res: &pbs.ListManagedGroupsResponse{
|
||||
ResponseType: "complete",
|
||||
SortBy: "created_time",
|
||||
SortDir: "desc",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Unfound Auth Method",
|
||||
@@ -528,7 +589,13 @@ func TestListLdap(t *testing.T) {
|
||||
AuthMethodId: amSomeManagedGroups.GetPublicId(),
|
||||
Filter: fmt.Sprintf(`"/item/name"==%q`, wantSomeManagedGroups[1].Name.GetValue()),
|
||||
},
|
||||
res: &pbs.ListManagedGroupsResponse{Items: wantSomeManagedGroups[1:2]},
|
||||
res: &pbs.ListManagedGroupsResponse{
|
||||
Items: wantSomeManagedGroups[1:2],
|
||||
EstItemCount: 1,
|
||||
ResponseType: "complete",
|
||||
SortBy: "created_time",
|
||||
SortDir: "desc",
|
||||
},
|
||||
skipAnon: true,
|
||||
},
|
||||
{
|
||||
@@ -537,7 +604,11 @@ func TestListLdap(t *testing.T) {
|
||||
AuthMethodId: amSomeManagedGroups.GetPublicId(),
|
||||
Filter: `"/item/id"=="noManagedGroupmatchesthis"`,
|
||||
},
|
||||
res: &pbs.ListManagedGroupsResponse{},
|
||||
res: &pbs.ListManagedGroupsResponse{
|
||||
ResponseType: "complete",
|
||||
SortBy: "created_time",
|
||||
SortDir: "desc",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Filter Bad Format",
|
||||
@@ -549,7 +620,7 @@ func TestListLdap(t *testing.T) {
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
assert, require := assert.New(t), require.New(t)
|
||||
s, err := managed_groups.NewService(ctx, oidcRepoFn, ldapRepoFn)
|
||||
s, err := managed_groups.NewService(ctx, oidcRepoFn, ldapRepoFn, 1000)
|
||||
require.NoError(err, "Couldn't create new managed group service.")
|
||||
|
||||
got, gErr := s.ListManagedGroups(auth.DisabledAuthTestContext(iamRepoFn, o.GetPublicId()), tc.req)
|
||||
@@ -562,17 +633,14 @@ func TestListLdap(t *testing.T) {
|
||||
} else {
|
||||
require.NoError(gErr)
|
||||
}
|
||||
sort.Slice(got.Items, func(i, j int) bool {
|
||||
return strings.Compare(got.Items[i].GetName().GetValue(),
|
||||
got.Items[j].GetName().GetValue()) < 0
|
||||
})
|
||||
assert.Empty(cmp.Diff(
|
||||
got,
|
||||
tc.res,
|
||||
protocmp.Transform(),
|
||||
cmpopts.SortSlices(func(a, b string) bool {
|
||||
return a < b
|
||||
}),
|
||||
protocmp.Transform(),
|
||||
protocmp.IgnoreFields(&pbs.ListManagedGroupsResponse{}, "list_token"),
|
||||
), "ListManagedGroups() with scope %q got response %q, wanted %q", tc.req, got, tc.res)
|
||||
|
||||
// Now test with anon
|
||||
@@ -592,6 +660,645 @@ func TestListLdap(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestListPagination(t *testing.T) {
|
||||
// Set database read timeout to avoid duplicates in response
|
||||
oldReadTimeout := globals.RefreshReadLookbackDuration
|
||||
globals.RefreshReadLookbackDuration = 0
|
||||
t.Cleanup(func() {
|
||||
globals.RefreshReadLookbackDuration = oldReadTimeout
|
||||
})
|
||||
|
||||
ctx := context.Background()
|
||||
conn, _ := db.TestSetup(t, "postgres")
|
||||
sqlDB, err := conn.SqlDB(ctx)
|
||||
require.NoError(t, err)
|
||||
rw := db.New(conn)
|
||||
wrap := db.TestWrapper(t)
|
||||
kmsCache := kms.TestKms(t, conn, wrap)
|
||||
oidcRepoFn := func() (*oidc.Repository, error) {
|
||||
return oidc.NewRepository(ctx, rw, rw, kmsCache)
|
||||
}
|
||||
iamRepoFn := func() (*iam.Repository, error) {
|
||||
return iam.NewRepository(ctx, rw, rw, kmsCache)
|
||||
}
|
||||
ldapRepoFn := func() (*ldap.Repository, error) {
|
||||
return ldap.NewRepository(ctx, rw, rw, kmsCache)
|
||||
}
|
||||
|
||||
tokenRepoFn := func() (*authtoken.Repository, error) {
|
||||
return authtoken.NewRepository(ctx, rw, rw, kmsCache)
|
||||
}
|
||||
serversRepoFn := func() (*server.Repository, error) {
|
||||
return server.NewRepository(ctx, rw, rw, kmsCache)
|
||||
}
|
||||
|
||||
s, err := managed_groups.NewService(ctx, oidcRepoFn, ldapRepoFn, 1000)
|
||||
require.NoError(t, err)
|
||||
|
||||
iamRepo := iam.TestRepo(t, conn, wrap)
|
||||
tokenRepo, _ := tokenRepoFn()
|
||||
oidcRepo, _ := oidcRepoFn()
|
||||
ldapRepo, _ := ldapRepoFn()
|
||||
o, pwt := iam.TestScopes(t, iamRepo)
|
||||
|
||||
t.Run("oidc", func(t *testing.T) {
|
||||
databaseWrapper, err := kmsCache.GetWrapper(ctx, o.PublicId, kms.KeyPurposeDatabase)
|
||||
require.NoError(t, err)
|
||||
authMethod := oidc.TestAuthMethod(t, conn, databaseWrapper, o.PublicId, oidc.ActivePrivateState, "somemgs", "fido",
|
||||
oidc.WithIssuer(oidc.TestConvertToUrls(t, "https://www.somemgs.com")[0]), oidc.WithSigningAlgs(oidc.RS256), oidc.WithApiUrl(oidc.TestConvertToUrls(t, "https://www.alice.com/callback")[0]))
|
||||
|
||||
var mgs []*pb.ManagedGroup
|
||||
for i := 0; i < 10; i++ {
|
||||
mg := oidc.TestManagedGroup(t, conn, authMethod, oidc.TestFakeManagedGroupFilter, oidc.WithName(strconv.Itoa(i)))
|
||||
mgs = append(mgs, &pb.ManagedGroup{
|
||||
Id: mg.GetPublicId(),
|
||||
AuthMethodId: mg.GetAuthMethodId(),
|
||||
Name: wrapperspb.String(strconv.Itoa(i)),
|
||||
CreatedTime: mg.GetCreateTime().GetTimestamp(),
|
||||
UpdatedTime: mg.GetUpdateTime().GetTimestamp(),
|
||||
Scope: &scopepb.ScopeInfo{Id: o.GetPublicId(), Type: scope.Org.String(), ParentScopeId: scope.Global.String()},
|
||||
Version: 1,
|
||||
Type: oidc.Subtype.String(),
|
||||
Attrs: &pb.ManagedGroup_OidcManagedGroupAttributes{
|
||||
OidcManagedGroupAttributes: &pb.OidcManagedGroupAttributes{
|
||||
Filter: oidc.TestFakeManagedGroupFilter,
|
||||
},
|
||||
},
|
||||
AuthorizedActions: oidcAuthorizedActions,
|
||||
})
|
||||
}
|
||||
|
||||
acct := oidc.TestAccount(t, conn, authMethod, "test-login-last")
|
||||
u := iam.TestUser(t, iamRepo, o.GetPublicId(), iam.WithAccountIds(acct.PublicId))
|
||||
|
||||
privProjRole := iam.TestRole(t, conn, pwt.GetPublicId())
|
||||
iam.TestRoleGrant(t, conn, privProjRole.GetPublicId(), "id=*;type=*;actions=*")
|
||||
iam.TestUserRole(t, conn, privProjRole.GetPublicId(), u.GetPublicId())
|
||||
privOrgRole := iam.TestRole(t, conn, o.GetPublicId())
|
||||
iam.TestRoleGrant(t, conn, privOrgRole.GetPublicId(), "id=*;type=*;actions=*")
|
||||
iam.TestUserRole(t, conn, privOrgRole.GetPublicId(), u.GetPublicId())
|
||||
|
||||
// Since we sort by created_time descending, we reverse the slice
|
||||
slices.Reverse(mgs)
|
||||
|
||||
at, _ := tokenRepo.CreateAuthToken(ctx, u, acct.GetPublicId())
|
||||
|
||||
requestInfo := authpb.RequestInfo{
|
||||
TokenFormat: uint32(auth.AuthTokenTypeBearer),
|
||||
PublicId: at.GetPublicId(),
|
||||
Token: at.GetToken(),
|
||||
}
|
||||
requestContext := context.WithValue(context.Background(), requests.ContextRequestInformationKey, &requests.RequestContext{})
|
||||
ctx = auth.NewVerifierContext(requestContext, iamRepoFn, tokenRepoFn, serversRepoFn, kmsCache, &requestInfo)
|
||||
|
||||
req := &pbs.ListManagedGroupsRequest{
|
||||
AuthMethodId: authMethod.GetPublicId(),
|
||||
Filter: "",
|
||||
ListToken: "",
|
||||
PageSize: 2,
|
||||
}
|
||||
|
||||
// Run analyze in the DB to update the estimate tables
|
||||
_, err = sqlDB.ExecContext(ctx, "analyze")
|
||||
require.NoError(t, err)
|
||||
|
||||
got, err := s.ListManagedGroups(ctx, req)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, got.GetItems(), 2)
|
||||
|
||||
// all comparisons will be done without list token
|
||||
assert.Empty(t,
|
||||
cmp.Diff(
|
||||
got,
|
||||
&pbs.ListManagedGroupsResponse{
|
||||
Items: mgs[0:2],
|
||||
ResponseType: "delta",
|
||||
ListToken: "",
|
||||
SortBy: "created_time",
|
||||
SortDir: "desc",
|
||||
RemovedIds: nil,
|
||||
EstItemCount: 10,
|
||||
},
|
||||
cmpopts.SortSlices(func(a, b string) bool {
|
||||
return a < b
|
||||
}),
|
||||
protocmp.Transform(),
|
||||
protocmp.IgnoreFields(&pbs.ListManagedGroupsResponse{}, "list_token"),
|
||||
),
|
||||
)
|
||||
|
||||
// second page
|
||||
req.ListToken = got.ListToken
|
||||
got, err = s.ListManagedGroups(ctx, req)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, got.GetItems(), 2)
|
||||
|
||||
assert.Empty(t,
|
||||
cmp.Diff(
|
||||
got,
|
||||
&pbs.ListManagedGroupsResponse{
|
||||
Items: mgs[2:4],
|
||||
ResponseType: "delta",
|
||||
ListToken: "",
|
||||
SortBy: "created_time",
|
||||
SortDir: "desc",
|
||||
RemovedIds: nil,
|
||||
EstItemCount: 10,
|
||||
},
|
||||
cmpopts.SortSlices(func(a, b string) bool {
|
||||
return a < b
|
||||
}),
|
||||
protocmp.Transform(),
|
||||
protocmp.IgnoreFields(&pbs.ListManagedGroupsResponse{}, "list_token"),
|
||||
),
|
||||
)
|
||||
|
||||
// remainder of results
|
||||
req.ListToken = got.ListToken
|
||||
req.PageSize = 6
|
||||
got, err = s.ListManagedGroups(ctx, req)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, got.GetItems(), 6)
|
||||
|
||||
assert.Empty(t,
|
||||
cmp.Diff(
|
||||
got,
|
||||
&pbs.ListManagedGroupsResponse{
|
||||
Items: mgs[4:],
|
||||
ResponseType: "complete",
|
||||
ListToken: "",
|
||||
SortBy: "created_time",
|
||||
SortDir: "desc",
|
||||
RemovedIds: nil,
|
||||
EstItemCount: 10,
|
||||
},
|
||||
cmpopts.SortSlices(func(a, b string) bool {
|
||||
return a < b
|
||||
}),
|
||||
protocmp.Transform(),
|
||||
protocmp.IgnoreFields(&pbs.ListManagedGroupsResponse{}, "list_token"),
|
||||
),
|
||||
)
|
||||
|
||||
// create another managed group
|
||||
mg := oidc.TestManagedGroup(t, conn, authMethod, oidc.TestFakeManagedGroupFilter, oidc.WithName("new-oidc-mg"))
|
||||
newMg := &pb.ManagedGroup{
|
||||
Id: mg.GetPublicId(),
|
||||
AuthMethodId: mg.GetAuthMethodId(),
|
||||
Name: wrapperspb.String("new-oidc-mg"),
|
||||
CreatedTime: mg.GetCreateTime().GetTimestamp(),
|
||||
UpdatedTime: mg.GetUpdateTime().GetTimestamp(),
|
||||
Scope: &scopepb.ScopeInfo{Id: o.GetPublicId(), Type: scope.Org.String(), ParentScopeId: scope.Global.String()},
|
||||
Version: 1,
|
||||
Type: oidc.Subtype.String(),
|
||||
Attrs: &pb.ManagedGroup_OidcManagedGroupAttributes{
|
||||
OidcManagedGroupAttributes: &pb.OidcManagedGroupAttributes{
|
||||
Filter: oidc.TestFakeManagedGroupFilter,
|
||||
},
|
||||
},
|
||||
AuthorizedActions: oidcAuthorizedActions,
|
||||
}
|
||||
// Add to front since it's the latest updated
|
||||
mgs = append([]*pb.ManagedGroup{newMg}, mgs...)
|
||||
|
||||
// delete different mg
|
||||
_, err = oidcRepo.DeleteManagedGroup(ctx, o.GetPublicId(), mgs[len(mgs)-1].Id)
|
||||
require.NoError(t, err)
|
||||
deletedMg := mgs[len(mgs)-1]
|
||||
mgs = mgs[:len(mgs)-1]
|
||||
|
||||
// update another mg
|
||||
mgs[1].Name = wrapperspb.String("new-name")
|
||||
mgs[1].Version = 2
|
||||
m := &oidc.ManagedGroup{
|
||||
ManagedGroup: &oidcstore.ManagedGroup{
|
||||
PublicId: mgs[1].Id,
|
||||
AuthMethodId: mgs[1].AuthMethodId,
|
||||
Name: mgs[1].Name.GetValue(),
|
||||
},
|
||||
}
|
||||
um, _, err := oidcRepo.UpdateManagedGroup(ctx, o.GetPublicId(), m, 1, []string{"name"})
|
||||
require.NoError(t, err)
|
||||
mgs[1].UpdatedTime = um.GetUpdateTime().GetTimestamp()
|
||||
mgs[1].Version = um.GetVersion()
|
||||
// Add to the front since it's most recently updated
|
||||
mgs = append(
|
||||
[]*pb.ManagedGroup{mgs[1]},
|
||||
append(
|
||||
[]*pb.ManagedGroup{mgs[0]},
|
||||
mgs[2:]...,
|
||||
)...,
|
||||
)
|
||||
|
||||
// Run analyze to update postgres estimates
|
||||
_, err = sqlDB.ExecContext(ctx, "analyze")
|
||||
require.NoError(t, err)
|
||||
|
||||
// request updated results
|
||||
req.ListToken = got.ListToken
|
||||
req.PageSize = 1
|
||||
got, err = s.ListManagedGroups(ctx, req)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, got.GetItems(), 1)
|
||||
assert.Empty(t,
|
||||
cmp.Diff(
|
||||
got,
|
||||
&pbs.ListManagedGroupsResponse{
|
||||
Items: []*pb.ManagedGroup{mgs[0]},
|
||||
ResponseType: "delta",
|
||||
ListToken: "",
|
||||
SortBy: "updated_time",
|
||||
SortDir: "desc",
|
||||
RemovedIds: []string{deletedMg.Id},
|
||||
EstItemCount: 10,
|
||||
},
|
||||
cmpopts.SortSlices(func(a, b string) bool {
|
||||
return a < b
|
||||
}),
|
||||
protocmp.Transform(),
|
||||
protocmp.IgnoreFields(&pbs.ListManagedGroupsResponse{}, "list_token"),
|
||||
),
|
||||
)
|
||||
|
||||
// Get next page
|
||||
req.ListToken = got.ListToken
|
||||
got, err = s.ListManagedGroups(ctx, req)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, got.GetItems(), 1)
|
||||
assert.Empty(t,
|
||||
cmp.Diff(
|
||||
got,
|
||||
&pbs.ListManagedGroupsResponse{
|
||||
Items: []*pb.ManagedGroup{mgs[1]},
|
||||
ResponseType: "complete",
|
||||
ListToken: "",
|
||||
SortBy: "updated_time",
|
||||
SortDir: "desc",
|
||||
RemovedIds: nil,
|
||||
EstItemCount: 10,
|
||||
},
|
||||
cmpopts.SortSlices(func(a, b string) bool {
|
||||
return a < b
|
||||
}),
|
||||
protocmp.Transform(),
|
||||
protocmp.IgnoreFields(&pbs.ListManagedGroupsResponse{}, "list_token"),
|
||||
),
|
||||
)
|
||||
|
||||
// Request new page with filter requiring looping
|
||||
// to fill the page.
|
||||
req.ListToken = ""
|
||||
req.PageSize = 1
|
||||
req.Filter = fmt.Sprintf(`"/item/id"==%q or "/item/id"==%q`, mgs[len(mgs)-2].Id, mgs[len(mgs)-1].Id)
|
||||
got, err = s.ListManagedGroups(ctx, req)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, got.GetItems(), 1)
|
||||
assert.Empty(t,
|
||||
cmp.Diff(
|
||||
got,
|
||||
&pbs.ListManagedGroupsResponse{
|
||||
Items: []*pb.ManagedGroup{mgs[len(mgs)-2]},
|
||||
ResponseType: "delta",
|
||||
ListToken: "",
|
||||
SortBy: "created_time",
|
||||
SortDir: "desc",
|
||||
// Should be empty again
|
||||
RemovedIds: nil,
|
||||
EstItemCount: 10,
|
||||
},
|
||||
cmpopts.SortSlices(func(a, b string) bool {
|
||||
return a < b
|
||||
}),
|
||||
protocmp.Transform(),
|
||||
protocmp.IgnoreFields(&pbs.ListManagedGroupsResponse{}, "list_token"),
|
||||
),
|
||||
)
|
||||
req.ListToken = got.ListToken
|
||||
// Get the second page
|
||||
got, err = s.ListManagedGroups(ctx, req)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, got.GetItems(), 1)
|
||||
assert.Empty(t,
|
||||
cmp.Diff(
|
||||
got,
|
||||
&pbs.ListManagedGroupsResponse{
|
||||
Items: []*pb.ManagedGroup{mgs[len(mgs)-1]},
|
||||
ResponseType: "complete",
|
||||
ListToken: "",
|
||||
SortBy: "created_time",
|
||||
SortDir: "desc",
|
||||
RemovedIds: nil,
|
||||
EstItemCount: 10,
|
||||
},
|
||||
cmpopts.SortSlices(func(a, b string) bool {
|
||||
return a < b
|
||||
}),
|
||||
protocmp.Transform(),
|
||||
protocmp.IgnoreFields(&pbs.ListManagedGroupsResponse{}, "list_token"),
|
||||
),
|
||||
)
|
||||
})
|
||||
|
||||
t.Run("ldap", func(t *testing.T) {
|
||||
o, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrap))
|
||||
databaseWrapper, err := kmsCache.GetWrapper(ctx, o.PublicId, kms.KeyPurposeDatabase)
|
||||
require.NoError(t, err)
|
||||
authMethod := ldap.TestAuthMethod(t, conn, databaseWrapper, o.PublicId, []string{"ldaps://some-mgs"})
|
||||
|
||||
testGroups := []string{"admin", "users"}
|
||||
var mgs []*pb.ManagedGroup
|
||||
for i := 0; i < 10; i++ {
|
||||
mg := ldap.TestManagedGroup(t, conn, authMethod, testGroups, ldap.WithName(ctx, strconv.Itoa(i)))
|
||||
mgs = append(mgs, &pb.ManagedGroup{
|
||||
Id: mg.GetPublicId(),
|
||||
AuthMethodId: mg.GetAuthMethodId(),
|
||||
Name: wrapperspb.String(strconv.Itoa(i)),
|
||||
CreatedTime: mg.GetCreateTime().GetTimestamp(),
|
||||
UpdatedTime: mg.GetUpdateTime().GetTimestamp(),
|
||||
Scope: &scopepb.ScopeInfo{Id: o.GetPublicId(), Type: scope.Org.String(), ParentScopeId: scope.Global.String()},
|
||||
Version: 1,
|
||||
Type: ldap.Subtype.String(),
|
||||
Attrs: &pb.ManagedGroup_LdapManagedGroupAttributes{
|
||||
LdapManagedGroupAttributes: &pb.LdapManagedGroupAttributes{
|
||||
GroupNames: testGroups,
|
||||
},
|
||||
},
|
||||
AuthorizedActions: ldapAuthorizedActions,
|
||||
})
|
||||
}
|
||||
|
||||
acct := ldap.TestAccount(t, conn, authMethod, "test-login-last")
|
||||
u := iam.TestUser(t, iamRepo, o.GetPublicId(), iam.WithAccountIds(acct.PublicId))
|
||||
|
||||
privProjRole := iam.TestRole(t, conn, pwt.GetPublicId())
|
||||
iam.TestRoleGrant(t, conn, privProjRole.GetPublicId(), "id=*;type=*;actions=*")
|
||||
iam.TestUserRole(t, conn, privProjRole.GetPublicId(), u.GetPublicId())
|
||||
privOrgRole := iam.TestRole(t, conn, o.GetPublicId())
|
||||
iam.TestRoleGrant(t, conn, privOrgRole.GetPublicId(), "id=*;type=*;actions=*")
|
||||
iam.TestUserRole(t, conn, privOrgRole.GetPublicId(), u.GetPublicId())
|
||||
|
||||
// Since we sort by created_time descending, we reverse the slice
|
||||
slices.Reverse(mgs)
|
||||
|
||||
at, _ := tokenRepo.CreateAuthToken(ctx, u, acct.GetPublicId())
|
||||
|
||||
requestInfo := authpb.RequestInfo{
|
||||
TokenFormat: uint32(auth.AuthTokenTypeBearer),
|
||||
PublicId: at.GetPublicId(),
|
||||
Token: at.GetToken(),
|
||||
}
|
||||
requestContext := context.WithValue(context.Background(), requests.ContextRequestInformationKey, &requests.RequestContext{})
|
||||
ctx = auth.NewVerifierContext(requestContext, iamRepoFn, tokenRepoFn, serversRepoFn, kmsCache, &requestInfo)
|
||||
|
||||
req := &pbs.ListManagedGroupsRequest{
|
||||
AuthMethodId: authMethod.GetPublicId(),
|
||||
Filter: "",
|
||||
ListToken: "",
|
||||
PageSize: 2,
|
||||
}
|
||||
|
||||
// Run analyze in the DB to update the estimate tables
|
||||
_, err = sqlDB.ExecContext(ctx, "analyze")
|
||||
require.NoError(t, err)
|
||||
|
||||
got, err := s.ListManagedGroups(ctx, req)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, got.GetItems(), 2)
|
||||
|
||||
// all comparisons will be done without list token
|
||||
assert.Empty(t,
|
||||
cmp.Diff(
|
||||
got,
|
||||
&pbs.ListManagedGroupsResponse{
|
||||
Items: mgs[0:2],
|
||||
ResponseType: "delta",
|
||||
ListToken: "",
|
||||
SortBy: "created_time",
|
||||
SortDir: "desc",
|
||||
RemovedIds: nil,
|
||||
EstItemCount: 10,
|
||||
},
|
||||
cmpopts.SortSlices(func(a, b string) bool {
|
||||
return a < b
|
||||
}),
|
||||
protocmp.Transform(),
|
||||
protocmp.IgnoreFields(&pbs.ListManagedGroupsResponse{}, "list_token"),
|
||||
),
|
||||
)
|
||||
|
||||
// second page
|
||||
req.ListToken = got.ListToken
|
||||
got, err = s.ListManagedGroups(ctx, req)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, got.GetItems(), 2)
|
||||
|
||||
assert.Empty(t,
|
||||
cmp.Diff(
|
||||
got,
|
||||
&pbs.ListManagedGroupsResponse{
|
||||
Items: mgs[2:4],
|
||||
ResponseType: "delta",
|
||||
ListToken: "",
|
||||
SortBy: "created_time",
|
||||
SortDir: "desc",
|
||||
RemovedIds: nil,
|
||||
EstItemCount: 10,
|
||||
},
|
||||
cmpopts.SortSlices(func(a, b string) bool {
|
||||
return a < b
|
||||
}),
|
||||
protocmp.Transform(),
|
||||
protocmp.IgnoreFields(&pbs.ListManagedGroupsResponse{}, "list_token"),
|
||||
),
|
||||
)
|
||||
|
||||
// remainder of results
|
||||
req.ListToken = got.ListToken
|
||||
req.PageSize = 6
|
||||
got, err = s.ListManagedGroups(ctx, req)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, got.GetItems(), 6)
|
||||
|
||||
assert.Empty(t,
|
||||
cmp.Diff(
|
||||
got,
|
||||
&pbs.ListManagedGroupsResponse{
|
||||
Items: mgs[4:],
|
||||
ResponseType: "complete",
|
||||
ListToken: "",
|
||||
SortBy: "created_time",
|
||||
SortDir: "desc",
|
||||
RemovedIds: nil,
|
||||
EstItemCount: 10,
|
||||
},
|
||||
cmpopts.SortSlices(func(a, b string) bool {
|
||||
return a < b
|
||||
}),
|
||||
protocmp.Transform(),
|
||||
protocmp.IgnoreFields(&pbs.ListManagedGroupsResponse{}, "list_token"),
|
||||
),
|
||||
)
|
||||
|
||||
// create another mg
|
||||
mg := ldap.TestManagedGroup(t, conn, authMethod, testGroups, ldap.WithName(ctx, "new-ldap-name"))
|
||||
newMg := &pb.ManagedGroup{
|
||||
Id: mg.GetPublicId(),
|
||||
AuthMethodId: mg.GetAuthMethodId(),
|
||||
Name: wrapperspb.String("new-ldap-name"),
|
||||
CreatedTime: mg.GetCreateTime().GetTimestamp(),
|
||||
UpdatedTime: mg.GetUpdateTime().GetTimestamp(),
|
||||
Scope: &scopepb.ScopeInfo{Id: o.GetPublicId(), Type: scope.Org.String(), ParentScopeId: scope.Global.String()},
|
||||
Version: 1,
|
||||
Type: ldap.Subtype.String(),
|
||||
Attrs: &pb.ManagedGroup_LdapManagedGroupAttributes{
|
||||
LdapManagedGroupAttributes: &pb.LdapManagedGroupAttributes{
|
||||
GroupNames: testGroups,
|
||||
},
|
||||
},
|
||||
AuthorizedActions: ldapAuthorizedActions,
|
||||
}
|
||||
// Add to front since it's the latest updated
|
||||
mgs = append([]*pb.ManagedGroup{newMg}, mgs...)
|
||||
|
||||
// delete different mg
|
||||
_, err = ldapRepo.DeleteManagedGroup(ctx, o.GetPublicId(), mgs[len(mgs)-1].Id)
|
||||
require.NoError(t, err)
|
||||
deletedMg := mgs[len(mgs)-1]
|
||||
mgs = mgs[:len(mgs)-1]
|
||||
|
||||
// update another mg
|
||||
mgs[1].Name = wrapperspb.String("new-name")
|
||||
mgs[1].Version = 2
|
||||
m := &ldap.ManagedGroup{
|
||||
ManagedGroup: &ldapstore.ManagedGroup{
|
||||
PublicId: mgs[1].Id,
|
||||
AuthMethodId: mgs[1].AuthMethodId,
|
||||
Name: mgs[1].Name.GetValue(),
|
||||
},
|
||||
}
|
||||
um, _, err := ldapRepo.UpdateManagedGroup(ctx, o.GetPublicId(), m, 1, []string{"name"})
|
||||
require.NoError(t, err)
|
||||
mgs[1].UpdatedTime = um.GetUpdateTime().GetTimestamp()
|
||||
mgs[1].Version = um.GetVersion()
|
||||
// Add to the front since it's most recently updated
|
||||
mgs = append(
|
||||
[]*pb.ManagedGroup{mgs[1]},
|
||||
append(
|
||||
[]*pb.ManagedGroup{mgs[0]},
|
||||
mgs[2:]...,
|
||||
)...,
|
||||
)
|
||||
|
||||
// Run analyze to update postgres estimates
|
||||
_, err = sqlDB.ExecContext(ctx, "analyze")
|
||||
require.NoError(t, err)
|
||||
|
||||
// request updated results
|
||||
req.ListToken = got.ListToken
|
||||
req.PageSize = 1
|
||||
got, err = s.ListManagedGroups(ctx, req)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, got.GetItems(), 1)
|
||||
assert.Empty(t,
|
||||
cmp.Diff(
|
||||
got,
|
||||
&pbs.ListManagedGroupsResponse{
|
||||
Items: []*pb.ManagedGroup{mgs[0]},
|
||||
ResponseType: "delta",
|
||||
ListToken: "",
|
||||
SortBy: "updated_time",
|
||||
SortDir: "desc",
|
||||
RemovedIds: []string{deletedMg.Id},
|
||||
EstItemCount: 10,
|
||||
},
|
||||
cmpopts.SortSlices(func(a, b string) bool {
|
||||
return a < b
|
||||
}),
|
||||
protocmp.Transform(),
|
||||
protocmp.IgnoreFields(&pbs.ListManagedGroupsResponse{}, "list_token"),
|
||||
),
|
||||
)
|
||||
|
||||
// Get next page
|
||||
req.ListToken = got.ListToken
|
||||
got, err = s.ListManagedGroups(ctx, req)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, got.GetItems(), 1)
|
||||
assert.Empty(t,
|
||||
cmp.Diff(
|
||||
got,
|
||||
&pbs.ListManagedGroupsResponse{
|
||||
Items: []*pb.ManagedGroup{mgs[1]},
|
||||
ResponseType: "complete",
|
||||
ListToken: "",
|
||||
SortBy: "updated_time",
|
||||
SortDir: "desc",
|
||||
RemovedIds: nil,
|
||||
EstItemCount: 10,
|
||||
},
|
||||
cmpopts.SortSlices(func(a, b string) bool {
|
||||
return a < b
|
||||
}),
|
||||
protocmp.Transform(),
|
||||
protocmp.IgnoreFields(&pbs.ListManagedGroupsResponse{}, "list_token"),
|
||||
),
|
||||
)
|
||||
|
||||
// Request new page with filter requiring looping
|
||||
// to fill the page.
|
||||
req.ListToken = ""
|
||||
req.PageSize = 1
|
||||
req.Filter = fmt.Sprintf(`"/item/id"==%q or "/item/id"==%q`, mgs[len(mgs)-2].Id, mgs[len(mgs)-1].Id)
|
||||
got, err = s.ListManagedGroups(ctx, req)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, got.GetItems(), 1)
|
||||
assert.Empty(t,
|
||||
cmp.Diff(
|
||||
got,
|
||||
&pbs.ListManagedGroupsResponse{
|
||||
Items: []*pb.ManagedGroup{mgs[len(mgs)-2]},
|
||||
ResponseType: "delta",
|
||||
ListToken: "",
|
||||
SortBy: "created_time",
|
||||
SortDir: "desc",
|
||||
// Should be empty again
|
||||
RemovedIds: nil,
|
||||
EstItemCount: 10,
|
||||
},
|
||||
cmpopts.SortSlices(func(a, b string) bool {
|
||||
return a < b
|
||||
}),
|
||||
protocmp.Transform(),
|
||||
protocmp.IgnoreFields(&pbs.ListManagedGroupsResponse{}, "list_token"),
|
||||
),
|
||||
)
|
||||
req.ListToken = got.ListToken
|
||||
// Get the second page
|
||||
got, err = s.ListManagedGroups(ctx, req)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, got.GetItems(), 1)
|
||||
assert.Empty(t,
|
||||
cmp.Diff(
|
||||
got,
|
||||
&pbs.ListManagedGroupsResponse{
|
||||
Items: []*pb.ManagedGroup{mgs[len(mgs)-1]},
|
||||
ResponseType: "complete",
|
||||
ListToken: "",
|
||||
SortBy: "created_time",
|
||||
SortDir: "desc",
|
||||
RemovedIds: nil,
|
||||
EstItemCount: 10,
|
||||
},
|
||||
cmpopts.SortSlices(func(a, b string) bool {
|
||||
return a < b
|
||||
}),
|
||||
protocmp.Transform(),
|
||||
protocmp.IgnoreFields(&pbs.ListManagedGroupsResponse{}, "list_token"),
|
||||
),
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
func TestDelete(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
conn, _ := db.TestSetup(t, "postgres")
|
||||
@@ -624,7 +1331,7 @@ func TestDelete(t *testing.T) {
|
||||
ldapAm := ldap.TestAuthMethod(t, conn, databaseWrapper, o.PublicId, []string{"ldaps://ldap1"})
|
||||
ldapMg := ldap.TestManagedGroup(t, conn, ldapAm, []string{"admin", "users"})
|
||||
|
||||
s, err := managed_groups.NewService(ctx, oidcRepoFn, ldapRepoFn)
|
||||
s, err := managed_groups.NewService(ctx, oidcRepoFn, ldapRepoFn, 1000)
|
||||
require.NoError(t, err, "Error when getting new user service.")
|
||||
|
||||
cases := []struct {
|
||||
@@ -719,7 +1426,7 @@ func TestDelete_twice(t *testing.T) {
|
||||
)
|
||||
oidcMg := oidc.TestManagedGroup(t, conn, oidcAm, oidc.TestFakeManagedGroupFilter)
|
||||
|
||||
s, err := managed_groups.NewService(ctx, oidcRepoFn, ldapRepoFn)
|
||||
s, err := managed_groups.NewService(ctx, oidcRepoFn, ldapRepoFn, 1000)
|
||||
require.NoError(err, "Error when getting new user service")
|
||||
req := &pbs.DeleteManagedGroupRequest{
|
||||
Id: oidcMg.GetPublicId(),
|
||||
@@ -747,7 +1454,7 @@ func TestCreateOidc(t *testing.T) {
|
||||
return ldap.NewRepository(ctx, rw, rw, kmsCache)
|
||||
}
|
||||
|
||||
s, err := managed_groups.NewService(ctx, oidcRepoFn, ldapRepoFn)
|
||||
s, err := managed_groups.NewService(ctx, oidcRepoFn, ldapRepoFn, 1000)
|
||||
require.NoError(t, err, "Error when getting new managed group service.")
|
||||
|
||||
o, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrap))
|
||||
@@ -959,7 +1666,7 @@ func TestCreateLdap(t *testing.T) {
|
||||
return ldap.NewRepository(ctx, rw, rw, kmsCache)
|
||||
}
|
||||
|
||||
s, err := managed_groups.NewService(ctx, oidcRepoFn, ldapRepoFn)
|
||||
s, err := managed_groups.NewService(ctx, oidcRepoFn, ldapRepoFn, 1000)
|
||||
require.NoError(t, err, "Error when getting new managed group service.")
|
||||
|
||||
o, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrap))
|
||||
@@ -1183,7 +1890,7 @@ func TestUpdateOidc(t *testing.T) {
|
||||
oidc.WithSigningAlgs(oidc.RS256),
|
||||
oidc.WithApiUrl(oidc.TestConvertToUrls(t, "https://www.alice.com/callback")[0]))
|
||||
|
||||
tested, err := managed_groups.NewService(ctx, oidcRepoFn, ldapRepoFn)
|
||||
tested, err := managed_groups.NewService(ctx, oidcRepoFn, ldapRepoFn, 1000)
|
||||
require.NoError(t, err, "Error when getting new managed_groups service.")
|
||||
|
||||
defaultScopeInfo := &scopepb.ScopeInfo{Id: o.GetPublicId(), Type: o.GetType(), ParentScopeId: scope.Global.String()}
|
||||
@@ -1573,7 +2280,7 @@ func TestUpdateLdap(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
am := ldap.TestAuthMethod(t, conn, databaseWrapper, o.PublicId, []string{"ldaps://ldap1"})
|
||||
|
||||
tested, err := managed_groups.NewService(ctx, oidcRepoFn, ldapRepoFn)
|
||||
tested, err := managed_groups.NewService(ctx, oidcRepoFn, ldapRepoFn, 1000)
|
||||
require.NoError(t, err, "Error when getting new managed_groups service.")
|
||||
|
||||
testGroups := []string{"test", "admin"}
|
||||
|
||||
Reference in new issue
Block a user