// Copyright (c) HashiCorp, Inc. // SPDX-License-Identifier: BUSL-1.1 package iam import ( "context" "time" "github.com/hashicorp/boundary/internal/errors" "github.com/hashicorp/boundary/internal/listtoken" "github.com/hashicorp/boundary/internal/pagination" "github.com/hashicorp/boundary/internal/types/resource" ) // ListRolesPage lists up to page size roles, filtering out entries that // do not pass the filter item function. It will automatically request // more roles from the database, at page size chunks, to fill the page. // It will start its paging based on the information in the token. // It returns a new list token used to continue pagination or refresh items. // Roles are ordered by create time descending (most recently created first). func ListRolesPage( ctx context.Context, grantsHash []byte, pageSize int, filterItemFn pagination.ListFilterFunc[*Role], tok *listtoken.Token, repo *Repository, withScopeIds []string, ) (*pagination.ListResponse[*Role], error) { const op = "iam.ListRolesPage" switch { case len(grantsHash) == 0: return nil, errors.New(ctx, errors.InvalidParameter, op, "missing grants hash") case pageSize < 1: return nil, errors.New(ctx, errors.InvalidParameter, op, "page size must be at least 1") case filterItemFn == nil: return nil, errors.New(ctx, errors.InvalidParameter, op, "missing filter item callback") case tok == nil: return nil, errors.New(ctx, errors.InvalidParameter, op, "missing token") case repo == nil: return nil, errors.New(ctx, errors.InvalidParameter, op, "missing repo") case len(withScopeIds) == 0: return nil, errors.New(ctx, errors.InvalidParameter, op, "missing scope ids") case tok.ResourceType != resource.Role: return nil, errors.New(ctx, errors.InvalidParameter, op, "token did not have a role resource type") } if _, ok := tok.Subtype.(*listtoken.PaginationToken); !ok { return nil, errors.New(ctx, errors.InvalidParameter, op, "token did not have a pagination token component") } listItemsFn := func(ctx context.Context, lastPageItem *Role, limit int) ([]*Role, time.Time, error) { opts := []Option{ WithLimit(limit), } if lastPageItem != nil { opts = append(opts, WithStartPageAfterItem(lastPageItem)) } else { lastItem, err := tok.LastItem(ctx) if err != nil { return nil, time.Time{}, err } opts = append(opts, WithStartPageAfterItem(lastItem)) } return repo.listRoles(ctx, withScopeIds, opts...) } return pagination.ListPage(ctx, grantsHash, pageSize, filterItemFn, listItemsFn, repo.estimatedRoleCount, tok) } // ListUsersPage lists up to page size users, filtering out entries that // do not pass the filter item function. It will automatically request // more users from the database, at page size chunks, to fill the page. // It will start its paging based on the information in the token. // It returns a new list token used to continue pagination or refresh items. // Users are ordered by create time descending (most recently created first). func ListUsersPage( ctx context.Context, grantsHash []byte, pageSize int, filterItemFn pagination.ListFilterFunc[*User], tok *listtoken.Token, repo *Repository, withScopeIds []string, ) (*pagination.ListResponse[*User], error) { const op = "iam.ListUsersPage" switch { case len(grantsHash) == 0: return nil, errors.New(ctx, errors.InvalidParameter, op, "missing grants hash") case pageSize < 1: return nil, errors.New(ctx, errors.InvalidParameter, op, "page size must be at least 1") case filterItemFn == nil: return nil, errors.New(ctx, errors.InvalidParameter, op, "missing filter item callback") case tok == nil: return nil, errors.New(ctx, errors.InvalidParameter, op, "missing token") case repo == nil: return nil, errors.New(ctx, errors.InvalidParameter, op, "missing repo") case withScopeIds == nil: return nil, errors.New(ctx, errors.InvalidParameter, op, "missing scope ids") case tok.ResourceType != resource.User: return nil, errors.New(ctx, errors.InvalidParameter, op, "token did not have a user resource type") } if _, ok := tok.Subtype.(*listtoken.PaginationToken); !ok { return nil, errors.New(ctx, errors.InvalidParameter, op, "token did not have a pagination token component") } listItemsFn := func(ctx context.Context, lastPageItem *User, limit int) ([]*User, time.Time, error) { opts := []Option{ WithLimit(limit), } if lastPageItem != nil { opts = append(opts, WithStartPageAfterItem(lastPageItem)) } else { lastItem, err := tok.LastItem(ctx) if err != nil { return nil, time.Time{}, err } opts = append(opts, WithStartPageAfterItem(lastItem)) } return repo.ListUsers(ctx, withScopeIds, opts...) } return pagination.ListPage(ctx, grantsHash, pageSize, filterItemFn, listItemsFn, repo.estimatedUserCount, tok) } // ListGroupsPage lists up to page size groups, filtering out entries that // do not pass the filter item function. It will automatically request // more groups from the database, at page size chunks, to fill the page. // It will start its paging based on the information in the token. // It returns a new list token used to continue pagination or refresh items. // Groups are ordered by create time descending (most recently created first). func ListGroupsPage( ctx context.Context, grantsHash []byte, pageSize int, filterItemFn pagination.ListFilterFunc[*Group], tok *listtoken.Token, repo *Repository, withScopeIds []string, ) (*pagination.ListResponse[*Group], error) { const op = "iam.ListGroupsPage" switch { case len(grantsHash) == 0: return nil, errors.New(ctx, errors.InvalidParameter, op, "missing grants hash") case pageSize < 1: return nil, errors.New(ctx, errors.InvalidParameter, op, "page size must be at least 1") case filterItemFn == nil: return nil, errors.New(ctx, errors.InvalidParameter, op, "missing filter item callback") case tok == nil: return nil, errors.New(ctx, errors.InvalidParameter, op, "missing token") case repo == nil: return nil, errors.New(ctx, errors.InvalidParameter, op, "missing repo") case withScopeIds == nil: return nil, errors.New(ctx, errors.InvalidParameter, op, "missing scope ids") case tok.ResourceType != resource.Group: return nil, errors.New(ctx, errors.InvalidParameter, op, "token did not have a group resource type") } if _, ok := tok.Subtype.(*listtoken.PaginationToken); !ok { return nil, errors.New(ctx, errors.InvalidParameter, op, "token did not have a pagination token component") } listItemsFn := func(ctx context.Context, lastPageItem *Group, limit int) ([]*Group, time.Time, error) { opts := []Option{ WithLimit(limit), } if lastPageItem != nil { opts = append(opts, WithStartPageAfterItem(lastPageItem)) } else { lastItem, err := tok.LastItem(ctx) if err != nil { return nil, time.Time{}, err } opts = append(opts, WithStartPageAfterItem(lastItem)) } return repo.listGroups(ctx, withScopeIds, opts...) } return pagination.ListPage(ctx, grantsHash, pageSize, filterItemFn, listItemsFn, repo.estimatedGroupCount, tok) } // ListScopesPage lists up to page size scopes, filtering out entries that // do not pass the filter item function. It will automatically request // more scopes from the database, at page size chunks, to fill the page. // It will start its paging based on the information in the token. // It returns a new list token used to continue pagination or refresh items. // Scopes are ordered by create time descending (most recently created first). func ListScopesPage( ctx context.Context, grantsHash []byte, pageSize int, filterItemFn pagination.ListFilterFunc[*Scope], tok *listtoken.Token, repo *Repository, parentIds []string, ) (*pagination.ListResponse[*Scope], error) { const op = "iam.ListScopesPage" switch { case len(grantsHash) == 0: return nil, errors.New(ctx, errors.InvalidParameter, op, "missing grants hash") case pageSize < 1: return nil, errors.New(ctx, errors.InvalidParameter, op, "page size must be at least 1") case filterItemFn == nil: return nil, errors.New(ctx, errors.InvalidParameter, op, "missing filter item callback") case tok == nil: return nil, errors.New(ctx, errors.InvalidParameter, op, "missing token") case repo == nil: return nil, errors.New(ctx, errors.InvalidParameter, op, "missing repo") case len(parentIds) == 0: return nil, errors.New(ctx, errors.InvalidParameter, op, "missing parent ids") case tok.ResourceType != resource.Scope: return nil, errors.New(ctx, errors.InvalidParameter, op, "token did not have a scope resource type") } if _, ok := tok.Subtype.(*listtoken.PaginationToken); !ok { return nil, errors.New(ctx, errors.InvalidParameter, op, "token did not have a pagination token component") } listItemsFn := func(ctx context.Context, lastPageItem *Scope, limit int) ([]*Scope, time.Time, error) { opts := []Option{ WithLimit(limit), } if lastPageItem != nil { opts = append(opts, WithStartPageAfterItem(lastPageItem)) } else { lastItem, err := tok.LastItem(ctx) if err != nil { return nil, time.Time{}, err } opts = append(opts, WithStartPageAfterItem(lastItem)) } return repo.listScopes(ctx, parentIds, opts...) } return pagination.ListPage(ctx, grantsHash, pageSize, filterItemFn, listItemsFn, repo.estimatedScopeCount, tok) }