1 parent
69a6f664de
commit
0fad770d8a
21 files changed
+2242
-339
No files matched your search
@@ -321,8 +321,6 @@ github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ
|
||||
github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=
|
||||
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
|
||||
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
|
||||
github.com/hashicorp/mql v0.1.2 h1:Hw1FkOltYvAH13K2VTGVUZWUU1OU/vSo+b4uzRBUePs=
|
||||
github.com/hashicorp/mql v0.1.2/go.mod h1:pSghNOmPNBD9/Bvx0M4EC2JB/mEVA6qlB9IKpNCiqOA=
|
||||
github.com/hashicorp/mql v0.1.3 h1:SZdOsocDPovwp3Q5AzoH6s000BD5zcr+hV8xAobOvuo=
|
||||
github.com/hashicorp/mql v0.1.3/go.mod h1:CrbXH2f2ndS1X35x0E8aHdNYc3POYrEWpx/1Q+pq+iw=
|
||||
github.com/hashicorp/nodeenrollment v0.2.9 h1:pFdMjd6VqDpqWtzr2+p//7DHKKPwxmbNoENCKCOYuvs=
|
||||
|
||||
@@ -185,7 +185,7 @@ func (c *Controller) registerGrpcServices(s *grpc.Server) error {
|
||||
services.RegisterScopeServiceServer(s, os)
|
||||
}
|
||||
if _, ok := currentServices[services.UserService_ServiceDesc.ServiceName]; !ok {
|
||||
us, err := users.NewService(c.baseContext, c.IamRepoFn)
|
||||
us, err := users.NewService(c.baseContext, c.IamRepoFn, c.conf.RawConfig.Controller.MaxPageSize)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create user handler service: %w", err)
|
||||
}
|
||||
|
||||
@@ -16,11 +16,14 @@ import (
|
||||
pbs "github.com/hashicorp/boundary/internal/gen/controller/api/services"
|
||||
"github.com/hashicorp/boundary/internal/iam"
|
||||
"github.com/hashicorp/boundary/internal/iam/store"
|
||||
"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"
|
||||
"github.com/hashicorp/boundary/internal/types/resource"
|
||||
"github.com/hashicorp/boundary/internal/types/scope"
|
||||
"github.com/hashicorp/boundary/sdk/pbs/controller/api/resources/scopes"
|
||||
pb "github.com/hashicorp/boundary/sdk/pbs/controller/api/resources/users"
|
||||
"github.com/hashicorp/go-secure-stdlib/strutil"
|
||||
"google.golang.org/grpc/codes"
|
||||
@@ -67,22 +70,27 @@ func init() {
|
||||
type Service struct {
|
||||
pbs.UnsafeUserServiceServer
|
||||
|
||||
repoFn common.IamRepoFactory
|
||||
repoFn common.IamRepoFactory
|
||||
maxPageSize uint
|
||||
}
|
||||
|
||||
var _ pbs.UserServiceServer = (*Service)(nil)
|
||||
|
||||
// NewService returns a user service which handles user related requests to boundary.
|
||||
func NewService(ctx context.Context, repo common.IamRepoFactory) (Service, error) {
|
||||
func NewService(ctx context.Context, repo common.IamRepoFactory, maxPageSize uint) (Service, error) {
|
||||
const op = "users.NewService"
|
||||
if repo == nil {
|
||||
return Service{}, errors.New(ctx, errors.InvalidParameter, op, "missing iam repository")
|
||||
}
|
||||
return Service{repoFn: repo}, nil
|
||||
if maxPageSize == 0 {
|
||||
maxPageSize = uint(globals.DefaultMaxPageSize)
|
||||
}
|
||||
return Service{repoFn: repo, maxPageSize: maxPageSize}, nil
|
||||
}
|
||||
|
||||
// ListUsers implements the interface pbs.UserServiceServer.
|
||||
func (s Service) ListUsers(ctx context.Context, req *pbs.ListUsersRequest) (*pbs.ListUsersResponse, error) {
|
||||
const op = "users.(Service).ListUsers"
|
||||
if err := validateListRequest(ctx, req); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -110,50 +118,115 @@ func (s Service) ListUsers(ctx context.Context, req *pbs.ListUsersRequest) (*pbs
|
||||
return &pbs.ListUsersResponse{}, nil
|
||||
}
|
||||
|
||||
ul, err := s.listFromRepo(ctx, scopeIds)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(ul) == 0 {
|
||||
return &pbs.ListUsersResponse{}, nil
|
||||
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())
|
||||
}
|
||||
|
||||
filter, err := handlers.NewFilter(ctx, req.GetFilter())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
finalItems := make([]*pb.User, 0, len(ul))
|
||||
res := perms.Resource{
|
||||
Type: resource.User,
|
||||
}
|
||||
for _, item := range ul {
|
||||
res.Id = item.GetPublicId()
|
||||
res.ScopeId = item.GetScopeId()
|
||||
authorizedActions := authResults.FetchActionSetForId(ctx, item.GetPublicId(), IdActions, auth.WithResource(&res)).Strings()
|
||||
if len(authorizedActions) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
outputFields := authResults.FetchOutputFields(res, action.List).SelfOrDefaults(*authResults.UserData.User.Id)
|
||||
outputOpts := make([]handlers.Option, 0, 3)
|
||||
outputOpts = append(outputOpts, handlers.WithOutputFields(outputFields))
|
||||
if outputFields.Has(globals.ScopeField) {
|
||||
outputOpts = append(outputOpts, handlers.WithScope(scopeInfoMap[item.GetScopeId()]))
|
||||
}
|
||||
if outputFields.Has(globals.AuthorizedActionsField) {
|
||||
outputOpts = append(outputOpts, handlers.WithAuthorizedActions(authorizedActions))
|
||||
}
|
||||
|
||||
item, err := toProto(ctx, item, nil, outputOpts...)
|
||||
var filterItemFn func(ctx context.Context, item *iam.User) (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
|
||||
}
|
||||
|
||||
if filter.Match(item) {
|
||||
finalItems = append(finalItems, item)
|
||||
filterItemFn = func(ctx context.Context, item *iam.User) (bool, error) {
|
||||
outputOpts, ok := newOutputOpts(ctx, item, scopeInfoMap, authResults)
|
||||
if !ok {
|
||||
return false, nil
|
||||
}
|
||||
pbItem, err := toProto(ctx, item, nil, outputOpts...)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return filter.Match(pbItem), nil
|
||||
}
|
||||
default:
|
||||
filterItemFn = func(ctx context.Context, item *iam.User) (bool, error) {
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
return &pbs.ListUsersResponse{Items: finalItems}, nil
|
||||
|
||||
grantsHash, err := authResults.GrantsHash(ctx)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(ctx, err, op)
|
||||
}
|
||||
|
||||
repo, err := s.repoFn()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(ctx, err, op)
|
||||
}
|
||||
var listResp *pagination.ListResponse[*iam.User]
|
||||
var sortBy string
|
||||
if req.GetListToken() == "" {
|
||||
sortBy = "created_time"
|
||||
listResp, err = iam.ListUsers(ctx, grantsHash, pageSize, filterItemFn, repo, scopeIds)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
listToken, err := handlers.ParseListToken(ctx, req.GetListToken(), resource.User, grantsHash)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
switch st := listToken.Subtype.(type) {
|
||||
case *listtoken.PaginationToken:
|
||||
sortBy = "created_time"
|
||||
listResp, err = iam.ListUsersPage(ctx, grantsHash, pageSize, filterItemFn, listToken, repo, scopeIds)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
case *listtoken.StartRefreshToken:
|
||||
sortBy = "updated_time"
|
||||
listResp, err = iam.ListUsersRefresh(ctx, grantsHash, pageSize, filterItemFn, listToken, repo, scopeIds)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
case *listtoken.RefreshToken:
|
||||
sortBy = "updated_time"
|
||||
listResp, err = iam.ListUsersRefreshPage(ctx, grantsHash, pageSize, filterItemFn, listToken, repo, scopeIds)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
default:
|
||||
return nil, handlers.ApiErrorWithCodeAndMessage(codes.InvalidArgument, "unexpected list token subtype: %T", st)
|
||||
}
|
||||
}
|
||||
|
||||
finalItems := make([]*pb.User, 0, len(listResp.Items))
|
||||
for _, item := range listResp.Items {
|
||||
outputOpts, ok := newOutputOpts(ctx, item, scopeInfoMap, authResults)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
item, err := toProto(ctx, item, nil, outputOpts...)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(ctx, err, op)
|
||||
}
|
||||
finalItems = append(finalItems, item)
|
||||
}
|
||||
respType := "delta"
|
||||
if listResp.CompleteListing {
|
||||
respType = "complete"
|
||||
}
|
||||
resp := &pbs.ListUsersResponse{
|
||||
Items: finalItems,
|
||||
EstItemCount: uint32(listResp.EstimatedItemCount),
|
||||
RemovedIds: listResp.DeletedIds,
|
||||
ResponseType: respType,
|
||||
SortBy: sortBy,
|
||||
SortDir: "desc",
|
||||
}
|
||||
if listResp.ListToken != nil {
|
||||
resp.ListToken, err = handlers.MarshalListToken(ctx, listResp.ListToken, pbs.ResourceType_RESOURCE_TYPE_USER)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// GetUsers implements the interface pbs.UserServiceServer.
|
||||
@@ -493,18 +566,6 @@ func (s Service) deleteFromRepo(ctx context.Context, id string) (bool, error) {
|
||||
return rows > 0, nil
|
||||
}
|
||||
|
||||
func (s Service) listFromRepo(ctx context.Context, scopeIds []string) ([]*iam.User, error) {
|
||||
repo, err := s.repoFn()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ul, err := repo.ListUsers(ctx, scopeIds, iam.WithLimit(-1))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return ul, nil
|
||||
}
|
||||
|
||||
func (s Service) addInRepo(ctx context.Context, userId string, accountIds []string, version uint32) (*iam.User, []string, error) {
|
||||
const op = "users.(Service).addInRepo"
|
||||
repo, err := s.repoFn()
|
||||
@@ -792,3 +853,26 @@ func validateRemoveUserAccountsRequest(req *pbs.RemoveUserAccountsRequest) error
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func newOutputOpts(ctx context.Context, item *iam.User, scopeInfoMap map[string]*scopes.ScopeInfo, authResults auth.VerifyResults) ([]handlers.Option, bool) {
|
||||
res := perms.Resource{
|
||||
Type: resource.User,
|
||||
}
|
||||
res.Id = item.GetPublicId()
|
||||
res.ScopeId = item.GetScopeId()
|
||||
authorizedActions := authResults.FetchActionSetForId(ctx, item.GetPublicId(), IdActions, auth.WithResource(&res))
|
||||
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(scopeInfoMap[item.GetScopeId()]))
|
||||
}
|
||||
if outputFields.Has(globals.AuthorizedActionsField) {
|
||||
outputOpts = append(outputOpts, handlers.WithAuthorizedActions(authorizedActions.Strings()))
|
||||
}
|
||||
return outputOpts, true
|
||||
}
|
||||
@@ -7,7 +7,7 @@ import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"sort"
|
||||
"slices"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
@@ -17,13 +17,17 @@ import (
|
||||
"github.com/hashicorp/boundary/internal/auth/ldap"
|
||||
"github.com/hashicorp/boundary/internal/auth/oidc"
|
||||
"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/handlers"
|
||||
"github.com/hashicorp/boundary/internal/daemon/controller/handlers/users"
|
||||
"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/scope"
|
||||
"github.com/hashicorp/boundary/sdk/pbs/controller/api/resources/scopes"
|
||||
pb "github.com/hashicorp/boundary/sdk/pbs/controller/api/resources/users"
|
||||
@@ -145,7 +149,7 @@ func TestGet(t *testing.T) {
|
||||
req := proto.Clone(toMerge).(*pbs.GetUserRequest)
|
||||
proto.Merge(req, tc.req)
|
||||
|
||||
s, err := users.NewService(context.Background(), repoFn)
|
||||
s, err := users.NewService(context.Background(), repoFn, 1000)
|
||||
require.NoError(err, "Couldn't create new user service.")
|
||||
|
||||
got, gErr := s.GetUser(auth.DisabledAuthTestContext(repoFn, u.GetScopeId()), req)
|
||||
@@ -166,6 +170,7 @@ func TestGet(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestList(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
conn, _ := db.TestSetup(t, "postgres")
|
||||
wrap := db.TestWrapper(t)
|
||||
iamRepo := iam.TestRepo(t, conn, wrap)
|
||||
@@ -191,23 +196,25 @@ func TestList(t *testing.T) {
|
||||
secondaryAm := password.TestAuthMethods(t, conn, oWithUsers.PublicId, 1)
|
||||
require.Len(t, secondaryAm, 1)
|
||||
|
||||
s, err := users.NewService(context.Background(), repoFn)
|
||||
s, err := users.NewService(context.Background(), repoFn, 1000)
|
||||
require.NoError(t, err)
|
||||
|
||||
var wantUsers []*pb.User
|
||||
|
||||
// Populate expected values for recursive test
|
||||
var totalUsers []*pb.User
|
||||
ctx := auth.DisabledAuthTestContext(repoFn, "global")
|
||||
anon, err := s.GetUser(ctx, &pbs.GetUserRequest{Id: globals.AnonymousUserId})
|
||||
require.NoError(t, err)
|
||||
totalUsers = append(totalUsers, anon.GetItem())
|
||||
authUser, err := s.GetUser(ctx, &pbs.GetUserRequest{Id: globals.AnyAuthenticatedUserId})
|
||||
require.NoError(t, err)
|
||||
totalUsers = append(totalUsers, authUser.GetItem())
|
||||
recovery, err := s.GetUser(ctx, &pbs.GetUserRequest{Id: globals.RecoveryUserId})
|
||||
require.NoError(t, err)
|
||||
totalUsers = append(totalUsers, recovery.GetItem())
|
||||
{
|
||||
disabledAuthCtx := auth.DisabledAuthTestContext(repoFn, "global")
|
||||
anon, err := s.GetUser(disabledAuthCtx, &pbs.GetUserRequest{Id: globals.AnonymousUserId})
|
||||
require.NoError(t, err)
|
||||
totalUsers = append(totalUsers, anon.GetItem())
|
||||
authUser, err := s.GetUser(disabledAuthCtx, &pbs.GetUserRequest{Id: globals.AnyAuthenticatedUserId})
|
||||
require.NoError(t, err)
|
||||
totalUsers = append(totalUsers, authUser.GetItem())
|
||||
recovery, err := s.GetUser(disabledAuthCtx, &pbs.GetUserRequest{Id: globals.RecoveryUserId})
|
||||
require.NoError(t, err)
|
||||
totalUsers = append(totalUsers, recovery.GetItem())
|
||||
}
|
||||
|
||||
// Add new users
|
||||
for i := 0; i < 10; i++ {
|
||||
@@ -238,12 +245,17 @@ func TestList(t *testing.T) {
|
||||
PrimaryAccountId: oidcAcct.GetPublicId(),
|
||||
})
|
||||
}
|
||||
|
||||
// Populate these users into the total
|
||||
ctx = auth.DisabledAuthTestContext(repoFn, oWithUsers.GetPublicId(), auth.WithUserId(globals.AnyAuthenticatedUserId))
|
||||
usersInOrg, err := s.ListUsers(ctx, &pbs.ListUsersRequest{ScopeId: oWithUsers.GetPublicId()})
|
||||
totalUsers = append(totalUsers, wantUsers...)
|
||||
slices.Reverse(totalUsers)
|
||||
slices.Reverse(wantUsers)
|
||||
|
||||
// Run analyze to update postgres estimates
|
||||
sqlDb, err := conn.SqlDB(ctx)
|
||||
require.NoError(t, err)
|
||||
totalUsers = append(totalUsers, usersInOrg.GetItems()...)
|
||||
_, err = sqlDb.ExecContext(ctx, "analyze")
|
||||
require.NoError(t, err)
|
||||
|
||||
cases := []struct {
|
||||
name string
|
||||
req *pbs.ListUsersRequest
|
||||
@@ -253,32 +265,64 @@ func TestList(t *testing.T) {
|
||||
{
|
||||
name: "List Many Users",
|
||||
req: &pbs.ListUsersRequest{ScopeId: oWithUsers.GetPublicId()},
|
||||
res: &pbs.ListUsersResponse{Items: wantUsers},
|
||||
res: &pbs.ListUsersResponse{
|
||||
Items: wantUsers,
|
||||
EstItemCount: uint32(len(wantUsers)),
|
||||
ResponseType: "complete",
|
||||
SortBy: "created_time",
|
||||
SortDir: "desc",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "List No Users",
|
||||
req: &pbs.ListUsersRequest{ScopeId: oNoUsers.GetPublicId()},
|
||||
res: &pbs.ListUsersResponse{},
|
||||
res: &pbs.ListUsersResponse{
|
||||
ResponseType: "complete",
|
||||
SortBy: "created_time",
|
||||
SortDir: "desc",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "List Recursively in Org",
|
||||
req: &pbs.ListUsersRequest{ScopeId: oWithUsers.GetPublicId(), Recursive: true},
|
||||
res: &pbs.ListUsersResponse{Items: wantUsers},
|
||||
res: &pbs.ListUsersResponse{
|
||||
Items: wantUsers,
|
||||
EstItemCount: uint32(len(wantUsers)),
|
||||
ResponseType: "complete",
|
||||
SortBy: "created_time",
|
||||
SortDir: "desc",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "List Recursively in Global",
|
||||
req: &pbs.ListUsersRequest{ScopeId: "global", Recursive: true},
|
||||
res: &pbs.ListUsersResponse{Items: totalUsers},
|
||||
res: &pbs.ListUsersResponse{
|
||||
Items: totalUsers,
|
||||
EstItemCount: uint32(len(totalUsers)),
|
||||
ResponseType: "complete",
|
||||
SortBy: "created_time",
|
||||
SortDir: "desc",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Filter Many Users",
|
||||
req: &pbs.ListUsersRequest{ScopeId: "global", Recursive: true, Filter: fmt.Sprintf(`"/item/scope/id"==%q`, oWithUsers.GetPublicId())},
|
||||
res: &pbs.ListUsersResponse{Items: wantUsers},
|
||||
res: &pbs.ListUsersResponse{
|
||||
Items: wantUsers,
|
||||
EstItemCount: uint32(len(wantUsers)),
|
||||
ResponseType: "complete",
|
||||
SortBy: "created_time",
|
||||
SortDir: "desc",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Filter To No Users",
|
||||
req: &pbs.ListUsersRequest{ScopeId: oWithUsers.GetPublicId(), Filter: `"/item/id"=="doesntmatch"`},
|
||||
res: &pbs.ListUsersResponse{},
|
||||
res: &pbs.ListUsersResponse{
|
||||
ResponseType: "complete",
|
||||
SortBy: "created_time",
|
||||
SortDir: "desc",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Filter Bad Format",
|
||||
@@ -298,20 +342,6 @@ func TestList(t *testing.T) {
|
||||
assert.True(errors.Is(gErr, tc.err), "ListUsers(%+v) got error %v, wanted %v", tc.req, gErr, tc.err)
|
||||
return
|
||||
}
|
||||
gotUsers := sortableUsers{
|
||||
users: got.GetItems(),
|
||||
}
|
||||
resUsers := sortableUsers{
|
||||
users: tc.res.GetItems(),
|
||||
}
|
||||
if got != nil {
|
||||
sort.Sort(gotUsers)
|
||||
got.Items = gotUsers.users
|
||||
}
|
||||
if tc.res != nil {
|
||||
sort.Sort(resUsers)
|
||||
tc.res.Items = resUsers.users
|
||||
}
|
||||
assert.Empty(cmp.Diff(
|
||||
got,
|
||||
tc.res,
|
||||
@@ -319,6 +349,7 @@ func TestList(t *testing.T) {
|
||||
cmpopts.SortSlices(func(a, b string) bool {
|
||||
return a < b
|
||||
}),
|
||||
protocmp.IgnoreFields(&pbs.ListUsersResponse{}, "list_token"),
|
||||
), "ListUsers(%q) got response %q, wanted %q", tc.req, got, tc.res)
|
||||
// Test with anon user
|
||||
|
||||
@@ -336,18 +367,372 @@ func TestList(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
type sortableUsers struct {
|
||||
users []*pb.User
|
||||
func userToProto(u *iam.User, si *scopes.ScopeInfo, authorizedActions []string) *pb.User {
|
||||
pu := &pb.User{
|
||||
Id: u.GetPublicId(),
|
||||
ScopeId: u.GetScopeId(),
|
||||
Scope: si,
|
||||
CreatedTime: u.GetCreateTime().GetTimestamp(),
|
||||
UpdatedTime: u.GetUpdateTime().GetTimestamp(),
|
||||
Version: u.GetVersion(),
|
||||
AuthorizedActions: testAuthorizedActions,
|
||||
}
|
||||
if u.GetName() != "" {
|
||||
pu.Name = wrapperspb.String(u.GetName())
|
||||
}
|
||||
if u.GetDescription() != "" {
|
||||
pu.Description = wrapperspb.String(u.GetDescription())
|
||||
}
|
||||
return pu
|
||||
}
|
||||
|
||||
func (s sortableUsers) Len() int { return len(s.users) }
|
||||
func (s sortableUsers) Less(i, j int) bool { return s.users[i].GetId() < s.users[j].GetId() }
|
||||
func (s sortableUsers) Swap(i, j int) { s.users[i], s.users[j] = s.users[j], s.users[i] }
|
||||
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)
|
||||
wrapper := db.TestWrapper(t)
|
||||
kms := kms.TestKms(t, conn, wrapper)
|
||||
rw := db.New(conn)
|
||||
|
||||
iamRepo := iam.TestRepo(t, conn, wrapper)
|
||||
iamRepoFn := func() (*iam.Repository, error) {
|
||||
return iamRepo, nil
|
||||
}
|
||||
tokenRepoFn := func() (*authtoken.Repository, error) {
|
||||
return authtoken.NewRepository(ctx, rw, rw, kms)
|
||||
}
|
||||
serversRepoFn := func() (*server.Repository, error) {
|
||||
return server.NewRepository(ctx, rw, rw, kms)
|
||||
}
|
||||
tokenRepo, err := tokenRepoFn()
|
||||
require.NoError(t, err)
|
||||
|
||||
// Run analyze to update postgres meta tables
|
||||
_, err = sqlDB.ExecContext(ctx, "analyze")
|
||||
require.NoError(t, err)
|
||||
|
||||
oNoUsers, _ := iam.TestScopes(t, iamRepo)
|
||||
oWithUsers, _ := iam.TestScopes(t, iamRepo)
|
||||
|
||||
var allUsers []*pb.User
|
||||
// Get the 3 system users (u_recovery, u_anon, u_auth)
|
||||
us, _, err := iamRepo.ListUsers(ctx, []string{"global"})
|
||||
require.NoError(t, err)
|
||||
require.Len(t, us, 3)
|
||||
// They (should) be returned in reverse order by create time, so we reverse
|
||||
slices.Reverse(us)
|
||||
for _, u := range us {
|
||||
allUsers = append(allUsers, userToProto(u, &scopes.ScopeInfo{
|
||||
Id: u.ScopeId,
|
||||
Name: scope.Global.String(),
|
||||
Description: "Global Scope",
|
||||
Type: scope.Global.String(),
|
||||
}, testAuthorizedActions))
|
||||
}
|
||||
|
||||
authMethod := password.TestAuthMethods(t, conn, "global", 1)[0]
|
||||
acct := password.TestAccount(t, conn, authMethod.GetPublicId(), "test_user")
|
||||
u := iam.TestUser(t, iamRepo, "global", iam.WithAccountIds(acct.PublicId))
|
||||
allUsers = append(allUsers, userToProto(u, &scopes.ScopeInfo{
|
||||
Id: u.ScopeId,
|
||||
Name: scope.Global.String(),
|
||||
Description: "Global Scope",
|
||||
Type: scope.Global.String(),
|
||||
}, testAuthorizedActions))
|
||||
|
||||
// add roles to be able to see all users
|
||||
allowedRole := iam.TestRole(t, conn, "global")
|
||||
iam.TestRoleGrant(t, conn, allowedRole.GetPublicId(), "id=*;type=*;actions=*")
|
||||
iam.TestUserRole(t, conn, allowedRole.GetPublicId(), u.GetPublicId())
|
||||
for _, scope := range []*iam.Scope{oWithUsers, oNoUsers} {
|
||||
allowedRole := iam.TestRole(t, conn, scope.GetPublicId())
|
||||
iam.TestRoleGrant(t, conn, allowedRole.GetPublicId(), "id=*;type=*;actions=*")
|
||||
iam.TestUserRole(t, conn, allowedRole.GetPublicId(), u.GetPublicId())
|
||||
}
|
||||
|
||||
at, err := tokenRepo.CreateAuthToken(ctx, u, acct.GetPublicId())
|
||||
require.NoError(t, err)
|
||||
|
||||
// Test without anon user
|
||||
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, kms, &requestInfo)
|
||||
|
||||
var safeToDeleteUser string
|
||||
orgScopeInfo := &scopes.ScopeInfo{Id: oWithUsers.GetPublicId(), Type: scope.Org.String(), ParentScopeId: scope.Global.String()}
|
||||
for i := 0; i < 10; i++ {
|
||||
ou := iam.TestUser(t, iamRepo, oWithUsers.GetPublicId())
|
||||
allUsers = append(allUsers, userToProto(ou, orgScopeInfo, testAuthorizedActions))
|
||||
safeToDeleteUser = ou.GetPublicId()
|
||||
}
|
||||
slices.Reverse(allUsers)
|
||||
|
||||
a, err := users.NewService(ctx, iamRepoFn, 1000)
|
||||
require.NoError(t, err, "Couldn't create new user service.")
|
||||
|
||||
// Run analyze to update postgres estimates
|
||||
_, err = sqlDB.ExecContext(context.Background(), "analyze")
|
||||
require.NoError(t, err)
|
||||
|
||||
itemCount := uint32(len(allUsers))
|
||||
testPageSize := int((itemCount - 2) / 2)
|
||||
|
||||
// Start paginating, recursively
|
||||
req := &pbs.ListUsersRequest{
|
||||
ScopeId: "global",
|
||||
Recursive: true,
|
||||
Filter: "",
|
||||
ListToken: "",
|
||||
PageSize: uint32(testPageSize),
|
||||
}
|
||||
got, err := a.ListUsers(ctx, req)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, got.GetItems(), testPageSize)
|
||||
// Compare without comparing the list token
|
||||
assert.Empty(t,
|
||||
cmp.Diff(
|
||||
got,
|
||||
&pbs.ListUsersResponse{
|
||||
Items: allUsers[0:testPageSize],
|
||||
ResponseType: "delta",
|
||||
SortBy: "created_time",
|
||||
SortDir: "desc",
|
||||
RemovedIds: nil,
|
||||
// In addition to the added users, there are the users added
|
||||
// by the test setup when specifying the permissions of the
|
||||
// requester
|
||||
EstItemCount: itemCount,
|
||||
},
|
||||
protocmp.SortRepeated(func(a, b string) bool {
|
||||
return strings.Compare(a, b) < 0
|
||||
}),
|
||||
protocmp.Transform(),
|
||||
protocmp.IgnoreFields(&pbs.ListUsersResponse{}, "list_token"),
|
||||
),
|
||||
)
|
||||
|
||||
// Request second page
|
||||
req.ListToken = got.ListToken
|
||||
got, err = a.ListUsers(ctx, req)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, got.GetItems(), testPageSize)
|
||||
// Compare without comparing the list token
|
||||
assert.Empty(t,
|
||||
cmp.Diff(
|
||||
got,
|
||||
&pbs.ListUsersResponse{
|
||||
Items: allUsers[testPageSize : testPageSize*2],
|
||||
ResponseType: "delta",
|
||||
SortBy: "created_time",
|
||||
SortDir: "desc",
|
||||
RemovedIds: nil,
|
||||
EstItemCount: itemCount,
|
||||
},
|
||||
protocmp.Transform(),
|
||||
protocmp.SortRepeated(func(a, b string) bool {
|
||||
return strings.Compare(a, b) < 0
|
||||
}),
|
||||
protocmp.IgnoreFields(&pbs.ListUsersResponse{}, "list_token"),
|
||||
),
|
||||
)
|
||||
|
||||
// Request rest of results
|
||||
req.ListToken = got.ListToken
|
||||
req.PageSize = 10
|
||||
got, err = a.ListUsers(ctx, req)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, got.GetItems(), 2)
|
||||
// Compare without comparing the list token
|
||||
assert.Empty(t,
|
||||
cmp.Diff(
|
||||
got,
|
||||
&pbs.ListUsersResponse{
|
||||
Items: allUsers[testPageSize*2:],
|
||||
ResponseType: "complete",
|
||||
SortBy: "created_time",
|
||||
SortDir: "desc",
|
||||
RemovedIds: nil,
|
||||
EstItemCount: itemCount,
|
||||
},
|
||||
protocmp.Transform(),
|
||||
protocmp.SortRepeated(func(a, b string) bool {
|
||||
return strings.Compare(a, b) < 0
|
||||
}),
|
||||
protocmp.IgnoreFields(&pbs.ListUsersResponse{}, "list_token"),
|
||||
),
|
||||
)
|
||||
|
||||
// Update 2 users and see them in the refresh
|
||||
r1 := allUsers[len(allUsers)-1]
|
||||
r1.Description = wrapperspb.String("updated1")
|
||||
resp1, err := a.UpdateUser(ctx, &pbs.UpdateUserRequest{
|
||||
Id: r1.GetId(),
|
||||
Item: &pb.User{Description: r1.GetDescription(), Version: r1.GetVersion()},
|
||||
UpdateMask: &field_mask.FieldMask{Paths: []string{"description"}},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
r1.UpdatedTime = resp1.GetItem().GetUpdatedTime()
|
||||
r1.Version = resp1.GetItem().GetVersion()
|
||||
allUsers = append([]*pb.User{r1}, allUsers[:len(allUsers)-1]...)
|
||||
|
||||
r2 := allUsers[len(allUsers)-1]
|
||||
r2.Description = wrapperspb.String("updated2")
|
||||
resp2, err := a.UpdateUser(ctx, &pbs.UpdateUserRequest{
|
||||
Id: r2.GetId(),
|
||||
Item: &pb.User{Description: r2.GetDescription(), Version: r2.GetVersion()},
|
||||
UpdateMask: &field_mask.FieldMask{Paths: []string{"description"}},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
r2.UpdatedTime = resp2.GetItem().GetUpdatedTime()
|
||||
r2.Version = resp2.GetItem().GetVersion()
|
||||
allUsers = append([]*pb.User{r2}, allUsers[:len(allUsers)-1]...)
|
||||
|
||||
// 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 = a.ListUsers(ctx, req)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, got.GetItems(), 1)
|
||||
// Compare without comparing the list token
|
||||
assert.Empty(t,
|
||||
cmp.Diff(
|
||||
got,
|
||||
&pbs.ListUsersResponse{
|
||||
Items: []*pb.User{allUsers[0]},
|
||||
ResponseType: "delta",
|
||||
SortBy: "updated_time",
|
||||
SortDir: "desc",
|
||||
RemovedIds: nil,
|
||||
EstItemCount: itemCount,
|
||||
},
|
||||
protocmp.Transform(),
|
||||
protocmp.SortRepeated(func(a, b string) bool {
|
||||
return strings.Compare(a, b) < 0
|
||||
}),
|
||||
protocmp.IgnoreFields(&pbs.ListUsersResponse{}, "list_token"),
|
||||
),
|
||||
)
|
||||
|
||||
// Get next page
|
||||
req.ListToken = got.ListToken
|
||||
got, err = a.ListUsers(ctx, req)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, got.GetItems(), 1)
|
||||
// Compare without comparing the list token
|
||||
assert.Empty(t,
|
||||
cmp.Diff(
|
||||
got,
|
||||
&pbs.ListUsersResponse{
|
||||
Items: []*pb.User{allUsers[1]},
|
||||
ResponseType: "complete",
|
||||
SortBy: "updated_time",
|
||||
SortDir: "desc",
|
||||
RemovedIds: nil,
|
||||
EstItemCount: itemCount,
|
||||
},
|
||||
protocmp.Transform(),
|
||||
protocmp.SortRepeated(func(a, b string) bool {
|
||||
return strings.Compare(a, b) < 0
|
||||
}),
|
||||
protocmp.IgnoreFields(&pbs.ListUsersResponse{}, "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`, allUsers[len(allUsers)-2].Id, allUsers[len(allUsers)-1].Id)
|
||||
got, err = a.ListUsers(ctx, req)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, got.GetItems(), 1)
|
||||
assert.Empty(t,
|
||||
cmp.Diff(
|
||||
got,
|
||||
&pbs.ListUsersResponse{
|
||||
Items: []*pb.User{allUsers[len(allUsers)-2]},
|
||||
ResponseType: "delta",
|
||||
SortBy: "created_time",
|
||||
SortDir: "desc",
|
||||
// Should be empty again
|
||||
RemovedIds: nil,
|
||||
EstItemCount: itemCount,
|
||||
},
|
||||
protocmp.Transform(),
|
||||
protocmp.SortRepeated(func(a, b string) bool {
|
||||
return strings.Compare(a, b) < 0
|
||||
}),
|
||||
protocmp.IgnoreFields(&pbs.ListUsersResponse{}, "list_token"),
|
||||
),
|
||||
)
|
||||
req.ListToken = got.ListToken
|
||||
// Get the second page
|
||||
got, err = a.ListUsers(ctx, req)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, got.GetItems(), 1)
|
||||
assert.Empty(t,
|
||||
cmp.Diff(
|
||||
got,
|
||||
&pbs.ListUsersResponse{
|
||||
Items: []*pb.User{allUsers[len(allUsers)-1]},
|
||||
ResponseType: "complete",
|
||||
SortBy: "created_time",
|
||||
SortDir: "desc",
|
||||
RemovedIds: nil,
|
||||
EstItemCount: itemCount,
|
||||
},
|
||||
protocmp.Transform(),
|
||||
protocmp.SortRepeated(func(a, b string) bool {
|
||||
return strings.Compare(a, b) < 0
|
||||
}),
|
||||
protocmp.IgnoreFields(&pbs.ListUsersResponse{}, "list_token"),
|
||||
),
|
||||
)
|
||||
|
||||
_, err = iamRepo.DeleteUser(ctx, safeToDeleteUser)
|
||||
require.NoError(t, err)
|
||||
req.ListToken = got.ListToken
|
||||
got, err = a.ListUsers(ctx, req)
|
||||
require.NoError(t, err)
|
||||
assert.Empty(t,
|
||||
cmp.Diff(
|
||||
got,
|
||||
&pbs.ListUsersResponse{
|
||||
Items: nil,
|
||||
ResponseType: "complete",
|
||||
SortBy: "updated_time",
|
||||
SortDir: "desc",
|
||||
RemovedIds: []string{safeToDeleteUser},
|
||||
EstItemCount: itemCount,
|
||||
},
|
||||
protocmp.Transform(),
|
||||
protocmp.SortRepeated(func(a, b string) bool {
|
||||
return strings.Compare(a, b) < 0
|
||||
}),
|
||||
protocmp.IgnoreFields(&pbs.ListUsersResponse{}, "list_token"),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
func TestDelete(t *testing.T) {
|
||||
u, _, repoFn := createDefaultUserAndRepo(t, false)
|
||||
|
||||
s, err := users.NewService(context.Background(), repoFn)
|
||||
s, err := users.NewService(context.Background(), repoFn, 1000)
|
||||
require.NoError(t, err, "Error when getting new user service.")
|
||||
|
||||
cases := []struct {
|
||||
@@ -394,7 +779,7 @@ func TestDelete_twice(t *testing.T) {
|
||||
assert, require := assert.New(t), require.New(t)
|
||||
u, _, repoFn := createDefaultUserAndRepo(t, false)
|
||||
|
||||
s, err := users.NewService(context.Background(), repoFn)
|
||||
s, err := users.NewService(context.Background(), repoFn, 1000)
|
||||
require.NoError(err, "Error when getting new user service")
|
||||
req := &pbs.DeleteUserRequest{
|
||||
Id: u.GetPublicId(),
|
||||
@@ -486,7 +871,7 @@ func TestCreate(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 := users.NewService(context.Background(), repoFn)
|
||||
s, err := users.NewService(context.Background(), repoFn, 1000)
|
||||
require.NoError(err, "Error when getting new user service.")
|
||||
|
||||
got, gErr := s.CreateUser(auth.DisabledAuthTestContext(repoFn, tc.req.GetItem().GetScopeId()), tc.req)
|
||||
@@ -522,7 +907,7 @@ func TestCreate(t *testing.T) {
|
||||
|
||||
func TestUpdate(t *testing.T) {
|
||||
u, _, repoFn := createDefaultUserAndRepo(t, false)
|
||||
tested, err := users.NewService(context.Background(), repoFn)
|
||||
tested, err := users.NewService(context.Background(), repoFn, 1000)
|
||||
require.NoError(t, err, "Error when getting new user service.")
|
||||
|
||||
created := u.GetCreateTime().GetTimestamp().AsTime()
|
||||
@@ -806,7 +1191,7 @@ func TestAddAccount(t *testing.T) {
|
||||
repoFn := func() (*iam.Repository, error) {
|
||||
return iamRepo, nil
|
||||
}
|
||||
s, err := users.NewService(ctx, repoFn)
|
||||
s, err := users.NewService(ctx, repoFn, 1000)
|
||||
require.NoError(t, err, "Error when getting new user service.")
|
||||
|
||||
o, _ := iam.TestScopes(t, iamRepo)
|
||||
@@ -966,7 +1351,7 @@ func TestSetAccount(t *testing.T) {
|
||||
repoFn := func() (*iam.Repository, error) {
|
||||
return iamRepo, nil
|
||||
}
|
||||
s, err := users.NewService(ctx, repoFn)
|
||||
s, err := users.NewService(ctx, repoFn, 1000)
|
||||
require.NoError(t, err, "Error when getting new user service.")
|
||||
|
||||
o, _ := iam.TestScopes(t, iamRepo)
|
||||
@@ -1128,7 +1513,7 @@ func TestRemoveAccount(t *testing.T) {
|
||||
repoFn := func() (*iam.Repository, error) {
|
||||
return iamRepo, nil
|
||||
}
|
||||
s, err := users.NewService(ctx, repoFn)
|
||||
s, err := users.NewService(ctx, repoFn, 1000)
|
||||
require.NoError(t, err, "Error when getting new user service.")
|
||||
|
||||
o, _ := iam.TestScopes(t, iamRepo)
|
||||
|
||||
@@ -8,7 +8,13 @@ begin;
|
||||
on iam_role (create_time desc, public_id desc);
|
||||
create index iam_role_update_time_public_id_idx
|
||||
on iam_role (update_time desc, public_id desc);
|
||||
|
||||
analyze iam_role;
|
||||
|
||||
-- Add new indexes for the create time and update time queries.
|
||||
create index iam_user_create_time_public_id_idx
|
||||
on iam_user (create_time desc, public_id desc);
|
||||
create index iam_user_update_time_public_id_idx
|
||||
on iam_user (update_time desc, public_id desc);
|
||||
analyze iam_user;
|
||||
|
||||
commit;
|
||||
@@ -0,0 +1,12 @@
|
||||
-- Copyright (c) HashiCorp, Inc.
|
||||
-- SPDX-License-Identifier: BUSL-1.1
|
||||
|
||||
begin;
|
||||
select plan(2);
|
||||
|
||||
select has_index('iam_user', 'iam_user_create_time_public_id_idx', array['create_time', 'public_id']);
|
||||
select has_index('iam_user', 'iam_user_update_time_public_id_idx', array['update_time', 'public_id']);
|
||||
|
||||
select * from finish();
|
||||
|
||||
rollback;
|
||||
@@ -3851,6 +3851,21 @@
|
||||
"in": "query",
|
||||
"required": false,
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"name": "list_token",
|
||||
"description": "An opaque token used to continue an existing iteration or\nrequest updated items. If not specified, pagination\nwill start from the beginning.",
|
||||
"in": "query",
|
||||
"required": false,
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"name": "page_size",
|
||||
"description": "The maximum size of a page in this iteration.\nIf unset, the default page size configured will be used.\nIf the page_size is greater than the default page configured,\nan error will be returned.",
|
||||
"in": "query",
|
||||
"required": false,
|
||||
"type": "integer",
|
||||
"format": "int64"
|
||||
}
|
||||
],
|
||||
"tags": [
|
||||
@@ -8173,6 +8188,34 @@
|
||||
"type": "object",
|
||||
"$ref": "#/definitions/controller.api.resources.users.v1.User"
|
||||
}
|
||||
},
|
||||
"response_type": {
|
||||
"type": "string",
|
||||
"description": "The type of response, either \"delta\" or \"complete\".\nDelta signifies that this is part of a paginated result\nor an update to a previously completed pagination.\nComplete signifies that it is the last page."
|
||||
},
|
||||
"list_token": {
|
||||
"type": "string",
|
||||
"description": "An opaque token used to continue an existing pagination or\nrequest updated items. Use this token in the next list request\nto request the next page."
|
||||
},
|
||||
"sort_by": {
|
||||
"type": "string",
|
||||
"description": "The name of the field which the items are sorted by."
|
||||
},
|
||||
"sort_dir": {
|
||||
"type": "string",
|
||||
"description": "The direction of the sort, either \"asc\" or \"desc\"."
|
||||
},
|
||||
"removed_ids": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"description": "A list of item IDs that have been removed since they were returned\nas part of a pagination. They should be dropped from any client cache.\nThis may contain items that are not known to the cache, if they were\ncreated and deleted between listings."
|
||||
},
|
||||
"est_item_count": {
|
||||
"type": "integer",
|
||||
"format": "int64",
|
||||
"description": "An estimate at the total items available. This may change during pagination."
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -129,6 +129,15 @@ type ListUsersRequest struct {
|
||||
ScopeId string `protobuf:"bytes,1,opt,name=scope_id,json=scopeId,proto3" json:"scope_id,omitempty" class:"public" eventstream:"observation"` // @gotags: `class:"public" eventstream:"observation"`
|
||||
Recursive bool `protobuf:"varint,20,opt,name=recursive,proto3" json:"recursive,omitempty" class:"public" eventstream:"observation"` // @gotags: `class:"public" eventstream:"observation"`
|
||||
Filter string `protobuf:"bytes,30,opt,name=filter,proto3" json:"filter,omitempty" class:"sensitive"` // @gotags: `class:"sensitive"`
|
||||
// An opaque token used to continue an existing iteration or
|
||||
// request updated items. If not specified, pagination
|
||||
// will start from the beginning.
|
||||
ListToken string `protobuf:"bytes,40,opt,name=list_token,proto3" json:"list_token,omitempty" class:"public"` // @gotags: `class:"public"`
|
||||
// The maximum size of a page in this iteration.
|
||||
// If unset, the default page size configured will be used.
|
||||
// If the page_size is greater than the default page configured,
|
||||
// an error will be returned.
|
||||
PageSize uint32 `protobuf:"varint,50,opt,name=page_size,proto3" json:"page_size,omitempty" class:"public"` // @gotags: `class:"public"`
|
||||
}
|
||||
|
||||
func (x *ListUsersRequest) Reset() {
|
||||
@@ -184,12 +193,46 @@ func (x *ListUsersRequest) GetFilter() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *ListUsersRequest) GetListToken() string {
|
||||
if x != nil {
|
||||
return x.ListToken
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *ListUsersRequest) GetPageSize() uint32 {
|
||||
if x != nil {
|
||||
return x.PageSize
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
type ListUsersResponse struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
Items []*users.User `protobuf:"bytes,1,rep,name=items,proto3" json:"items,omitempty"`
|
||||
// The type of response, either "delta" or "complete".
|
||||
// Delta signifies that this is part of a paginated result
|
||||
// or an update to a previously completed pagination.
|
||||
// Complete signifies that it is the last page.
|
||||
ResponseType string `protobuf:"bytes,2,opt,name=response_type,proto3" json:"response_type,omitempty" class:"public"` // @gotags: `class:"public"`
|
||||
// An opaque token used to continue an existing pagination or
|
||||
// request updated items. Use this token in the next list request
|
||||
// to request the next page.
|
||||
ListToken string `protobuf:"bytes,3,opt,name=list_token,proto3" json:"list_token,omitempty" class:"public"` // @gotags: `class:"public"`
|
||||
// The name of the field which the items are sorted by.
|
||||
SortBy string `protobuf:"bytes,4,opt,name=sort_by,proto3" json:"sort_by,omitempty" class:"public"` // @gotags: `class:"public"`
|
||||
// The direction of the sort, either "asc" or "desc".
|
||||
SortDir string `protobuf:"bytes,5,opt,name=sort_dir,proto3" json:"sort_dir,omitempty" class:"public"` // @gotags: `class:"public"`
|
||||
// A list of item IDs that have been removed since they were returned
|
||||
// as part of a pagination. They should be dropped from any client cache.
|
||||
// This may contain items that are not known to the cache, if they were
|
||||
// created and deleted between listings.
|
||||
RemovedIds []string `protobuf:"bytes,6,rep,name=removed_ids,proto3" json:"removed_ids,omitempty" class:"public"` // @gotags: `class:"public"`
|
||||
// An estimate at the total items available. This may change during pagination.
|
||||
EstItemCount uint32 `protobuf:"varint,7,opt,name=est_item_count,proto3" json:"est_item_count,omitempty" class:"public"` // @gotags: `class:"public"`
|
||||
}
|
||||
|
||||
func (x *ListUsersResponse) Reset() {
|
||||
@@ -231,6 +274,48 @@ func (x *ListUsersResponse) GetItems() []*users.User {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *ListUsersResponse) GetResponseType() string {
|
||||
if x != nil {
|
||||
return x.ResponseType
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *ListUsersResponse) GetListToken() string {
|
||||
if x != nil {
|
||||
return x.ListToken
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *ListUsersResponse) GetSortBy() string {
|
||||
if x != nil {
|
||||
return x.SortBy
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *ListUsersResponse) GetSortDir() string {
|
||||
if x != nil {
|
||||
return x.SortDir
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *ListUsersResponse) GetRemovedIds() []string {
|
||||
if x != nil {
|
||||
return x.RemovedIds
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *ListUsersResponse) GetEstItemCount() uint32 {
|
||||
if x != nil {
|
||||
return x.EstItemCount
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
type CreateUserRequest struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
@@ -885,190 +970,206 @@ var file_controller_api_services_v1_user_service_proto_rawDesc = []byte{
|
||||
0x0a, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x27, 0x2e, 0x63,
|
||||
0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x72, 0x65,
|
||||
0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x73, 0x2e, 0x76, 0x31,
|
||||
0x2e, 0x55, 0x73, 0x65, 0x72, 0x52, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x22, 0x63, 0x0a, 0x10, 0x4c,
|
||||
0x69, 0x73, 0x74, 0x55, 0x73, 0x65, 0x72, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12,
|
||||
0x19, 0x0a, 0x08, 0x73, 0x63, 0x6f, 0x70, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28,
|
||||
0x09, 0x52, 0x07, 0x73, 0x63, 0x6f, 0x70, 0x65, 0x49, 0x64, 0x12, 0x1c, 0x0a, 0x09, 0x72, 0x65,
|
||||
0x63, 0x75, 0x72, 0x73, 0x69, 0x76, 0x65, 0x18, 0x14, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x72,
|
||||
0x65, 0x63, 0x75, 0x72, 0x73, 0x69, 0x76, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x66, 0x69, 0x6c, 0x74,
|
||||
0x65, 0x72, 0x18, 0x1e, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72,
|
||||
0x22, 0x52, 0x0a, 0x11, 0x4c, 0x69, 0x73, 0x74, 0x55, 0x73, 0x65, 0x72, 0x73, 0x52, 0x65, 0x73,
|
||||
0x2e, 0x55, 0x73, 0x65, 0x72, 0x52, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x22, 0xa1, 0x01, 0x0a, 0x10,
|
||||
0x4c, 0x69, 0x73, 0x74, 0x55, 0x73, 0x65, 0x72, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74,
|
||||
0x12, 0x19, 0x0a, 0x08, 0x73, 0x63, 0x6f, 0x70, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01,
|
||||
0x28, 0x09, 0x52, 0x07, 0x73, 0x63, 0x6f, 0x70, 0x65, 0x49, 0x64, 0x12, 0x1c, 0x0a, 0x09, 0x72,
|
||||
0x65, 0x63, 0x75, 0x72, 0x73, 0x69, 0x76, 0x65, 0x18, 0x14, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09,
|
||||
0x72, 0x65, 0x63, 0x75, 0x72, 0x73, 0x69, 0x76, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x66, 0x69, 0x6c,
|
||||
0x74, 0x65, 0x72, 0x18, 0x1e, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x66, 0x69, 0x6c, 0x74, 0x65,
|
||||
0x72, 0x12, 0x1e, 0x0a, 0x0a, 0x6c, 0x69, 0x73, 0x74, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18,
|
||||
0x28, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x6c, 0x69, 0x73, 0x74, 0x5f, 0x74, 0x6f, 0x6b, 0x65,
|
||||
0x6e, 0x12, 0x1c, 0x0a, 0x09, 0x70, 0x61, 0x67, 0x65, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x32,
|
||||
0x20, 0x01, 0x28, 0x0d, 0x52, 0x09, 0x70, 0x61, 0x67, 0x65, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x22,
|
||||
0x98, 0x02, 0x0a, 0x11, 0x4c, 0x69, 0x73, 0x74, 0x55, 0x73, 0x65, 0x72, 0x73, 0x52, 0x65, 0x73,
|
||||
0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3d, 0x0a, 0x05, 0x69, 0x74, 0x65, 0x6d, 0x73, 0x18, 0x01,
|
||||
0x20, 0x03, 0x28, 0x0b, 0x32, 0x27, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65,
|
||||
0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x2e,
|
||||
0x75, 0x73, 0x65, 0x72, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x73, 0x65, 0x72, 0x52, 0x05, 0x69,
|
||||
0x74, 0x65, 0x6d, 0x73, 0x22, 0x50, 0x0a, 0x11, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x55, 0x73,
|
||||
0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x3b, 0x0a, 0x04, 0x69, 0x74, 0x65,
|
||||
0x6d, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x27, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f,
|
||||
0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63,
|
||||
0x65, 0x73, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x73, 0x65, 0x72,
|
||||
0x52, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x22, 0x63, 0x0a, 0x12, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65,
|
||||
0x55, 0x73, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x10, 0x0a, 0x03,
|
||||
0x75, 0x72, 0x69, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x75, 0x72, 0x69, 0x12, 0x3b,
|
||||
0x0a, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x27, 0x2e, 0x63,
|
||||
0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x72, 0x65,
|
||||
0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x73, 0x2e, 0x76, 0x31,
|
||||
0x2e, 0x55, 0x73, 0x65, 0x72, 0x52, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x22, 0x9e, 0x01, 0x0a, 0x11,
|
||||
0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x55, 0x73, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73,
|
||||
0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69,
|
||||
0x64, 0x12, 0x3b, 0x0a, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32,
|
||||
0x74, 0x65, 0x6d, 0x73, 0x12, 0x24, 0x0a, 0x0d, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65,
|
||||
0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x72, 0x65, 0x73,
|
||||
0x70, 0x6f, 0x6e, 0x73, 0x65, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x12, 0x1e, 0x0a, 0x0a, 0x6c, 0x69,
|
||||
0x73, 0x74, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a,
|
||||
0x6c, 0x69, 0x73, 0x74, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x18, 0x0a, 0x07, 0x73, 0x6f,
|
||||
0x72, 0x74, 0x5f, 0x62, 0x79, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x73, 0x6f, 0x72,
|
||||
0x74, 0x5f, 0x62, 0x79, 0x12, 0x1a, 0x0a, 0x08, 0x73, 0x6f, 0x72, 0x74, 0x5f, 0x64, 0x69, 0x72,
|
||||
0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x73, 0x6f, 0x72, 0x74, 0x5f, 0x64, 0x69, 0x72,
|
||||
0x12, 0x20, 0x0a, 0x0b, 0x72, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x64, 0x5f, 0x69, 0x64, 0x73, 0x18,
|
||||
0x06, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0b, 0x72, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x64, 0x5f, 0x69,
|
||||
0x64, 0x73, 0x12, 0x26, 0x0a, 0x0e, 0x65, 0x73, 0x74, 0x5f, 0x69, 0x74, 0x65, 0x6d, 0x5f, 0x63,
|
||||
0x6f, 0x75, 0x6e, 0x74, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0e, 0x65, 0x73, 0x74, 0x5f,
|
||||
0x69, 0x74, 0x65, 0x6d, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x22, 0x50, 0x0a, 0x11, 0x43, 0x72,
|
||||
0x65, 0x61, 0x74, 0x65, 0x55, 0x73, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12,
|
||||
0x3b, 0x0a, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x27, 0x2e,
|
||||
0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x72,
|
||||
0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x73, 0x2e, 0x76,
|
||||
0x31, 0x2e, 0x55, 0x73, 0x65, 0x72, 0x52, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x22, 0x63, 0x0a, 0x12,
|
||||
0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x55, 0x73, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e,
|
||||
0x73, 0x65, 0x12, 0x10, 0x0a, 0x03, 0x75, 0x72, 0x69, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52,
|
||||
0x03, 0x75, 0x72, 0x69, 0x12, 0x3b, 0x0a, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x18, 0x02, 0x20, 0x01,
|
||||
0x28, 0x0b, 0x32, 0x27, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e,
|
||||
0x61, 0x70, 0x69, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x2e, 0x75, 0x73,
|
||||
0x65, 0x72, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x73, 0x65, 0x72, 0x52, 0x04, 0x69, 0x74, 0x65,
|
||||
0x6d, 0x22, 0x9e, 0x01, 0x0a, 0x11, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x55, 0x73, 0x65, 0x72,
|
||||
0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20,
|
||||
0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x3b, 0x0a, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x18,
|
||||
0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x27, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c,
|
||||
0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73,
|
||||
0x2e, 0x75, 0x73, 0x65, 0x72, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x73, 0x65, 0x72, 0x52, 0x04,
|
||||
0x69, 0x74, 0x65, 0x6d, 0x12, 0x3c, 0x0a, 0x0b, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x5f, 0x6d,
|
||||
0x61, 0x73, 0x6b, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67,
|
||||
0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x46, 0x69, 0x65, 0x6c,
|
||||
0x64, 0x4d, 0x61, 0x73, 0x6b, 0x52, 0x0b, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x5f, 0x6d, 0x61,
|
||||
0x73, 0x6b, 0x22, 0x51, 0x0a, 0x12, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x55, 0x73, 0x65, 0x72,
|
||||
0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3b, 0x0a, 0x04, 0x69, 0x74, 0x65, 0x6d,
|
||||
0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x27, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c,
|
||||
0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65,
|
||||
0x73, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x73, 0x65, 0x72, 0x52,
|
||||
0x04, 0x69, 0x74, 0x65, 0x6d, 0x22, 0x23, 0x0a, 0x11, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x55,
|
||||
0x73, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64,
|
||||
0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x22, 0x14, 0x0a, 0x12, 0x44, 0x65,
|
||||
0x6c, 0x65, 0x74, 0x65, 0x55, 0x73, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65,
|
||||
0x22, 0x64, 0x0a, 0x16, 0x41, 0x64, 0x64, 0x55, 0x73, 0x65, 0x72, 0x41, 0x63, 0x63, 0x6f, 0x75,
|
||||
0x6e, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64,
|
||||
0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x18, 0x0a, 0x07, 0x76, 0x65,
|
||||
0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x07, 0x76, 0x65, 0x72,
|
||||
0x73, 0x69, 0x6f, 0x6e, 0x12, 0x20, 0x0a, 0x0b, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x5f,
|
||||
0x69, 0x64, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0b, 0x61, 0x63, 0x63, 0x6f, 0x75,
|
||||
0x6e, 0x74, 0x5f, 0x69, 0x64, 0x73, 0x22, 0x56, 0x0a, 0x17, 0x41, 0x64, 0x64, 0x55, 0x73, 0x65,
|
||||
0x72, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73,
|
||||
0x65, 0x12, 0x3b, 0x0a, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32,
|
||||
0x27, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69,
|
||||
0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x73,
|
||||
0x2e, 0x76, 0x31, 0x2e, 0x55, 0x73, 0x65, 0x72, 0x52, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x12, 0x3c,
|
||||
0x0a, 0x0b, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x5f, 0x6d, 0x61, 0x73, 0x6b, 0x18, 0x03, 0x20,
|
||||
0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f,
|
||||
0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x4d, 0x61, 0x73, 0x6b, 0x52,
|
||||
0x0b, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x5f, 0x6d, 0x61, 0x73, 0x6b, 0x22, 0x51, 0x0a, 0x12,
|
||||
0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x55, 0x73, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e,
|
||||
0x73, 0x65, 0x12, 0x3b, 0x0a, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b,
|
||||
0x32, 0x27, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70,
|
||||
0x69, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x2e, 0x75, 0x73, 0x65, 0x72,
|
||||
0x73, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x73, 0x65, 0x72, 0x52, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x22,
|
||||
0x23, 0x0a, 0x11, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x55, 0x73, 0x65, 0x72, 0x52, 0x65, 0x71,
|
||||
0x75, 0x65, 0x73, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09,
|
||||
0x52, 0x02, 0x69, 0x64, 0x22, 0x14, 0x0a, 0x12, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x55, 0x73,
|
||||
0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x64, 0x0a, 0x16, 0x41, 0x64,
|
||||
0x64, 0x55, 0x73, 0x65, 0x72, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x71,
|
||||
0x75, 0x65, 0x73, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09,
|
||||
0x52, 0x02, 0x69, 0x64, 0x12, 0x18, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18,
|
||||
0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x20,
|
||||
0x0a, 0x0b, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x73, 0x18, 0x03, 0x20,
|
||||
0x03, 0x28, 0x09, 0x52, 0x0b, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x73,
|
||||
0x22, 0x56, 0x0a, 0x17, 0x41, 0x64, 0x64, 0x55, 0x73, 0x65, 0x72, 0x41, 0x63, 0x63, 0x6f, 0x75,
|
||||
0x6e, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3b, 0x0a, 0x04, 0x69,
|
||||
0x74, 0x65, 0x6d, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x27, 0x2e, 0x63, 0x6f, 0x6e, 0x74,
|
||||
0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75,
|
||||
0x72, 0x63, 0x65, 0x73, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x73,
|
||||
0x65, 0x72, 0x52, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x22, 0x64, 0x0a, 0x16, 0x53, 0x65, 0x74, 0x55,
|
||||
0x73, 0x65, 0x72, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65,
|
||||
0x73, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02,
|
||||
0x69, 0x64, 0x12, 0x18, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20,
|
||||
0x01, 0x28, 0x0d, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x20, 0x0a, 0x0b,
|
||||
0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28,
|
||||
0x09, 0x52, 0x0b, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x73, 0x22, 0x56,
|
||||
0x0a, 0x17, 0x53, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74,
|
||||
0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3b, 0x0a, 0x04, 0x69, 0x74, 0x65,
|
||||
0x6d, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x27, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f,
|
||||
0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63,
|
||||
0x65, 0x73, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x73, 0x65, 0x72,
|
||||
0x52, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x22, 0x67, 0x0a, 0x19, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65,
|
||||
0x55, 0x73, 0x65, 0x72, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75,
|
||||
0x65, 0x73, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52,
|
||||
0x02, 0x69, 0x64, 0x12, 0x18, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x02,
|
||||
0x20, 0x01, 0x28, 0x0d, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x20, 0x0a,
|
||||
0x0b, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x73, 0x18, 0x03, 0x20, 0x03,
|
||||
0x28, 0x09, 0x52, 0x0b, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x73, 0x22,
|
||||
0x59, 0x0a, 0x1a, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x55, 0x73, 0x65, 0x72, 0x41, 0x63, 0x63,
|
||||
0x6f, 0x75, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3b, 0x0a,
|
||||
0x04, 0x69, 0x74, 0x65, 0x6d, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x27, 0x2e, 0x63, 0x6f,
|
||||
0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x72, 0x65, 0x73,
|
||||
0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x73, 0x2e, 0x76, 0x31, 0x2e,
|
||||
0x55, 0x73, 0x65, 0x72, 0x52, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x32, 0xb4, 0x0c, 0x0a, 0x0b, 0x55,
|
||||
0x73, 0x65, 0x72, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x98, 0x01, 0x0a, 0x07, 0x47,
|
||||
0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x12, 0x2a, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c,
|
||||
0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73,
|
||||
0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65,
|
||||
0x73, 0x74, 0x1a, 0x2b, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e,
|
||||
0x61, 0x70, 0x69, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e,
|
||||
0x47, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22,
|
||||
0x34, 0x92, 0x41, 0x15, 0x12, 0x13, 0x47, 0x65, 0x74, 0x73, 0x20, 0x61, 0x20, 0x73, 0x69, 0x6e,
|
||||
0x67, 0x6c, 0x65, 0x20, 0x55, 0x73, 0x65, 0x72, 0x2e, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x16, 0x62,
|
||||
0x04, 0x69, 0x74, 0x65, 0x6d, 0x12, 0x0e, 0x2f, 0x76, 0x31, 0x2f, 0x75, 0x73, 0x65, 0x72, 0x73,
|
||||
0x2f, 0x7b, 0x69, 0x64, 0x7d, 0x12, 0x90, 0x01, 0x0a, 0x09, 0x4c, 0x69, 0x73, 0x74, 0x55, 0x73,
|
||||
0x65, 0x72, 0x73, 0x12, 0x2c, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72,
|
||||
0x2e, 0x61, 0x70, 0x69, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x76, 0x31,
|
||||
0x2e, 0x4c, 0x69, 0x73, 0x74, 0x55, 0x73, 0x65, 0x72, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73,
|
||||
0x74, 0x1a, 0x2d, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61,
|
||||
0x70, 0x69, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x4c,
|
||||
0x69, 0x73, 0x74, 0x55, 0x73, 0x65, 0x72, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65,
|
||||
0x22, 0x26, 0x92, 0x41, 0x12, 0x12, 0x10, 0x4c, 0x69, 0x73, 0x74, 0x73, 0x20, 0x61, 0x6c, 0x6c,
|
||||
0x20, 0x55, 0x73, 0x65, 0x72, 0x73, 0x2e, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x0b, 0x12, 0x09, 0x2f,
|
||||
0x76, 0x31, 0x2f, 0x75, 0x73, 0x65, 0x72, 0x73, 0x12, 0xa5, 0x01, 0x0a, 0x0a, 0x43, 0x72, 0x65,
|
||||
0x61, 0x74, 0x65, 0x55, 0x73, 0x65, 0x72, 0x12, 0x2d, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f,
|
||||
0x2e, 0x76, 0x31, 0x2e, 0x55, 0x73, 0x65, 0x72, 0x52, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x22, 0x64,
|
||||
0x0a, 0x16, 0x53, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74,
|
||||
0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01,
|
||||
0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x18, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73,
|
||||
0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69,
|
||||
0x6f, 0x6e, 0x12, 0x20, 0x0a, 0x0b, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x5f, 0x69, 0x64,
|
||||
0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0b, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74,
|
||||
0x5f, 0x69, 0x64, 0x73, 0x22, 0x56, 0x0a, 0x17, 0x53, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x41,
|
||||
0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12,
|
||||
0x3b, 0x0a, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x27, 0x2e,
|
||||
0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x72,
|
||||
0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x73, 0x2e, 0x76,
|
||||
0x31, 0x2e, 0x55, 0x73, 0x65, 0x72, 0x52, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x22, 0x67, 0x0a, 0x19,
|
||||
0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x55, 0x73, 0x65, 0x72, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e,
|
||||
0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18,
|
||||
0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x18, 0x0a, 0x07, 0x76, 0x65, 0x72,
|
||||
0x73, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73,
|
||||
0x69, 0x6f, 0x6e, 0x12, 0x20, 0x0a, 0x0b, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x5f, 0x69,
|
||||
0x64, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0b, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e,
|
||||
0x74, 0x5f, 0x69, 0x64, 0x73, 0x22, 0x59, 0x0a, 0x1a, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x55,
|
||||
0x73, 0x65, 0x72, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f,
|
||||
0x6e, 0x73, 0x65, 0x12, 0x3b, 0x0a, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x18, 0x01, 0x20, 0x01, 0x28,
|
||||
0x0b, 0x32, 0x27, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61,
|
||||
0x70, 0x69, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x2e, 0x75, 0x73, 0x65,
|
||||
0x72, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x73, 0x65, 0x72, 0x52, 0x04, 0x69, 0x74, 0x65, 0x6d,
|
||||
0x32, 0xb4, 0x0c, 0x0a, 0x0b, 0x55, 0x73, 0x65, 0x72, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65,
|
||||
0x12, 0x98, 0x01, 0x0a, 0x07, 0x47, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x12, 0x2a, 0x2e, 0x63,
|
||||
0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x73, 0x65,
|
||||
0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x55, 0x73, 0x65,
|
||||
0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2b, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72,
|
||||
0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63,
|
||||
0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x52, 0x65, 0x73,
|
||||
0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x34, 0x92, 0x41, 0x15, 0x12, 0x13, 0x47, 0x65, 0x74, 0x73,
|
||||
0x20, 0x61, 0x20, 0x73, 0x69, 0x6e, 0x67, 0x6c, 0x65, 0x20, 0x55, 0x73, 0x65, 0x72, 0x2e, 0x82,
|
||||
0xd3, 0xe4, 0x93, 0x02, 0x16, 0x62, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x12, 0x0e, 0x2f, 0x76, 0x31,
|
||||
0x2f, 0x75, 0x73, 0x65, 0x72, 0x73, 0x2f, 0x7b, 0x69, 0x64, 0x7d, 0x12, 0x90, 0x01, 0x0a, 0x09,
|
||||
0x4c, 0x69, 0x73, 0x74, 0x55, 0x73, 0x65, 0x72, 0x73, 0x12, 0x2c, 0x2e, 0x63, 0x6f, 0x6e, 0x74,
|
||||
0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69,
|
||||
0x63, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x55, 0x73, 0x65, 0x72, 0x73,
|
||||
0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2d, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f,
|
||||
0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65,
|
||||
0x73, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x55, 0x73, 0x65, 0x72, 0x52,
|
||||
0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2e, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c,
|
||||
0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73,
|
||||
0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x55, 0x73, 0x65, 0x72, 0x52, 0x65,
|
||||
0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x38, 0x92, 0x41, 0x18, 0x12, 0x16, 0x43, 0x72, 0x65,
|
||||
0x61, 0x74, 0x65, 0x73, 0x20, 0x61, 0x20, 0x73, 0x69, 0x6e, 0x67, 0x6c, 0x65, 0x20, 0x55, 0x73,
|
||||
0x65, 0x72, 0x2e, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x17, 0x3a, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x62,
|
||||
0x04, 0x69, 0x74, 0x65, 0x6d, 0x22, 0x09, 0x2f, 0x76, 0x31, 0x2f, 0x75, 0x73, 0x65, 0x72, 0x73,
|
||||
0x12, 0xa3, 0x01, 0x0a, 0x0a, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x55, 0x73, 0x65, 0x72, 0x12,
|
||||
0x2d, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69,
|
||||
0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x70, 0x64,
|
||||
0x61, 0x74, 0x65, 0x55, 0x73, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2e,
|
||||
0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e,
|
||||
0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x70, 0x64, 0x61,
|
||||
0x74, 0x65, 0x55, 0x73, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x36,
|
||||
0x92, 0x41, 0x11, 0x12, 0x0f, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x73, 0x20, 0x61, 0x20, 0x55,
|
||||
0x73, 0x65, 0x72, 0x2e, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x1c, 0x3a, 0x04, 0x69, 0x74, 0x65, 0x6d,
|
||||
0x62, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x32, 0x0e, 0x2f, 0x76, 0x31, 0x2f, 0x75, 0x73, 0x65, 0x72,
|
||||
0x73, 0x2f, 0x7b, 0x69, 0x64, 0x7d, 0x12, 0x97, 0x01, 0x0a, 0x0a, 0x44, 0x65, 0x6c, 0x65, 0x74,
|
||||
0x73, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x55, 0x73, 0x65, 0x72, 0x73, 0x52, 0x65,
|
||||
0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x26, 0x92, 0x41, 0x12, 0x12, 0x10, 0x4c, 0x69, 0x73,
|
||||
0x74, 0x73, 0x20, 0x61, 0x6c, 0x6c, 0x20, 0x55, 0x73, 0x65, 0x72, 0x73, 0x2e, 0x82, 0xd3, 0xe4,
|
||||
0x93, 0x02, 0x0b, 0x12, 0x09, 0x2f, 0x76, 0x31, 0x2f, 0x75, 0x73, 0x65, 0x72, 0x73, 0x12, 0xa5,
|
||||
0x01, 0x0a, 0x0a, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x55, 0x73, 0x65, 0x72, 0x12, 0x2d, 0x2e,
|
||||
0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x73,
|
||||
0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74,
|
||||
0x65, 0x55, 0x73, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2e, 0x2e, 0x63,
|
||||
0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x73, 0x65,
|
||||
0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65,
|
||||
0x55, 0x73, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x38, 0x92, 0x41,
|
||||
0x18, 0x12, 0x16, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x73, 0x20, 0x61, 0x20, 0x73, 0x69, 0x6e,
|
||||
0x67, 0x6c, 0x65, 0x20, 0x55, 0x73, 0x65, 0x72, 0x2e, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x17, 0x3a,
|
||||
0x04, 0x69, 0x74, 0x65, 0x6d, 0x62, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x22, 0x09, 0x2f, 0x76, 0x31,
|
||||
0x2f, 0x75, 0x73, 0x65, 0x72, 0x73, 0x12, 0xa3, 0x01, 0x0a, 0x0a, 0x55, 0x70, 0x64, 0x61, 0x74,
|
||||
0x65, 0x55, 0x73, 0x65, 0x72, 0x12, 0x2d, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c,
|
||||
0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e,
|
||||
0x76, 0x31, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x55, 0x73, 0x65, 0x72, 0x52, 0x65, 0x71,
|
||||
0x76, 0x31, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x55, 0x73, 0x65, 0x72, 0x52, 0x65, 0x71,
|
||||
0x75, 0x65, 0x73, 0x74, 0x1a, 0x2e, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65,
|
||||
0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x76,
|
||||
0x31, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x55, 0x73, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70,
|
||||
0x6f, 0x6e, 0x73, 0x65, 0x22, 0x2a, 0x92, 0x41, 0x11, 0x12, 0x0f, 0x44, 0x65, 0x6c, 0x65, 0x74,
|
||||
0x65, 0x73, 0x20, 0x61, 0x20, 0x55, 0x73, 0x65, 0x72, 0x2e, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x10,
|
||||
0x2a, 0x0e, 0x2f, 0x76, 0x31, 0x2f, 0x75, 0x73, 0x65, 0x72, 0x73, 0x2f, 0x7b, 0x69, 0x64, 0x7d,
|
||||
0x12, 0xcd, 0x01, 0x0a, 0x0f, 0x41, 0x64, 0x64, 0x55, 0x73, 0x65, 0x72, 0x41, 0x63, 0x63, 0x6f,
|
||||
0x75, 0x6e, 0x74, 0x73, 0x12, 0x32, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65,
|
||||
0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x76,
|
||||
0x31, 0x2e, 0x41, 0x64, 0x64, 0x55, 0x73, 0x65, 0x72, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74,
|
||||
0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x33, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72,
|
||||
0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63,
|
||||
0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x64, 0x64, 0x55, 0x73, 0x65, 0x72, 0x41, 0x63, 0x63,
|
||||
0x6f, 0x75, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x51, 0x92,
|
||||
0x41, 0x22, 0x12, 0x20, 0x41, 0x73, 0x73, 0x6f, 0x63, 0x69, 0x61, 0x74, 0x65, 0x73, 0x20, 0x61,
|
||||
0x6e, 0x20, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x20, 0x74, 0x6f, 0x20, 0x61, 0x20, 0x55,
|
||||
0x73, 0x65, 0x72, 0x2e, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x26, 0x3a, 0x01, 0x2a, 0x62, 0x04, 0x69,
|
||||
0x74, 0x65, 0x6d, 0x22, 0x1b, 0x2f, 0x76, 0x31, 0x2f, 0x75, 0x73, 0x65, 0x72, 0x73, 0x2f, 0x7b,
|
||||
0x69, 0x64, 0x7d, 0x3a, 0x61, 0x64, 0x64, 0x2d, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73,
|
||||
0x12, 0xb5, 0x02, 0x0a, 0x0f, 0x53, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x41, 0x63, 0x63, 0x6f,
|
||||
0x75, 0x6e, 0x74, 0x73, 0x12, 0x32, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65,
|
||||
0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x76,
|
||||
0x31, 0x2e, 0x53, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74,
|
||||
0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x33, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72,
|
||||
0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63,
|
||||
0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x41, 0x63, 0x63,
|
||||
0x6f, 0x75, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0xb8, 0x01,
|
||||
0x92, 0x41, 0x88, 0x01, 0x12, 0x85, 0x01, 0x53, 0x65, 0x74, 0x20, 0x74, 0x68, 0x65, 0x20, 0x41,
|
||||
0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x20, 0x61, 0x73, 0x73, 0x6f, 0x63, 0x69, 0x61, 0x74,
|
||||
0x65, 0x64, 0x20, 0x74, 0x6f, 0x20, 0x74, 0x68, 0x65, 0x20, 0x55, 0x73, 0x65, 0x72, 0x20, 0x74,
|
||||
0x6f, 0x20, 0x65, 0x78, 0x61, 0x63, 0x74, 0x6c, 0x79, 0x20, 0x74, 0x68, 0x65, 0x20, 0x6c, 0x69,
|
||||
0x73, 0x74, 0x20, 0x6f, 0x66, 0x20, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x64, 0x20, 0x69,
|
||||
0x6e, 0x20, 0x74, 0x68, 0x65, 0x20, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2c, 0x20, 0x72,
|
||||
0x65, 0x6d, 0x6f, 0x76, 0x69, 0x6e, 0x67, 0x20, 0x61, 0x6e, 0x79, 0x20, 0x41, 0x63, 0x63, 0x6f,
|
||||
0x75, 0x6e, 0x74, 0x73, 0x20, 0x74, 0x68, 0x61, 0x74, 0x20, 0x61, 0x72, 0x65, 0x20, 0x6e, 0x6f,
|
||||
0x74, 0x20, 0x73, 0x70, 0x65, 0x63, 0x69, 0x66, 0x69, 0x65, 0x64, 0x2e, 0x82, 0xd3, 0xe4, 0x93,
|
||||
0x02, 0x26, 0x3a, 0x01, 0x2a, 0x62, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x22, 0x1b, 0x2f, 0x76, 0x31,
|
||||
0x2f, 0x75, 0x73, 0x65, 0x72, 0x73, 0x2f, 0x7b, 0x69, 0x64, 0x7d, 0x3a, 0x73, 0x65, 0x74, 0x2d,
|
||||
0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x12, 0x86, 0x02, 0x0a, 0x12, 0x52, 0x65, 0x6d,
|
||||
0x6f, 0x76, 0x65, 0x55, 0x73, 0x65, 0x72, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x12,
|
||||
0x35, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69,
|
||||
0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x6d,
|
||||
0x6f, 0x76, 0x65, 0x55, 0x73, 0x65, 0x72, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x52,
|
||||
0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x36, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c,
|
||||
0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73,
|
||||
0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x55, 0x73, 0x65, 0x72, 0x41, 0x63,
|
||||
0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x80,
|
||||
0x01, 0x92, 0x41, 0x4e, 0x12, 0x4c, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x73, 0x20, 0x74, 0x68,
|
||||
0x65, 0x20, 0x73, 0x70, 0x65, 0x63, 0x69, 0x66, 0x69, 0x65, 0x64, 0x20, 0x41, 0x63, 0x63, 0x6f,
|
||||
0x75, 0x6e, 0x74, 0x73, 0x20, 0x66, 0x72, 0x6f, 0x6d, 0x20, 0x62, 0x65, 0x69, 0x6e, 0x67, 0x20,
|
||||
0x61, 0x73, 0x73, 0x6f, 0x63, 0x69, 0x61, 0x74, 0x65, 0x64, 0x20, 0x77, 0x69, 0x74, 0x68, 0x20,
|
||||
0x74, 0x68, 0x65, 0x20, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x64, 0x20, 0x55, 0x73, 0x65,
|
||||
0x72, 0x2e, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x29, 0x3a, 0x01, 0x2a, 0x62, 0x04, 0x69, 0x74, 0x65,
|
||||
0x6d, 0x22, 0x1e, 0x2f, 0x76, 0x31, 0x2f, 0x75, 0x73, 0x65, 0x72, 0x73, 0x2f, 0x7b, 0x69, 0x64,
|
||||
0x7d, 0x3a, 0x72, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x2d, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74,
|
||||
0x73, 0x42, 0x4d, 0x5a, 0x4b, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f,
|
||||
0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2f, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x61,
|
||||
0x72, 0x79, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2f, 0x67, 0x65, 0x6e, 0x2f,
|
||||
0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x73,
|
||||
0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x3b, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73,
|
||||
0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
|
||||
0x31, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x55, 0x73, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70,
|
||||
0x6f, 0x6e, 0x73, 0x65, 0x22, 0x36, 0x92, 0x41, 0x11, 0x12, 0x0f, 0x55, 0x70, 0x64, 0x61, 0x74,
|
||||
0x65, 0x73, 0x20, 0x61, 0x20, 0x55, 0x73, 0x65, 0x72, 0x2e, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x1c,
|
||||
0x3a, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x62, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x32, 0x0e, 0x2f, 0x76,
|
||||
0x31, 0x2f, 0x75, 0x73, 0x65, 0x72, 0x73, 0x2f, 0x7b, 0x69, 0x64, 0x7d, 0x12, 0x97, 0x01, 0x0a,
|
||||
0x0a, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x55, 0x73, 0x65, 0x72, 0x12, 0x2d, 0x2e, 0x63, 0x6f,
|
||||
0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x73, 0x65, 0x72,
|
||||
0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x55,
|
||||
0x73, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2e, 0x2e, 0x63, 0x6f, 0x6e,
|
||||
0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x73, 0x65, 0x72, 0x76,
|
||||
0x69, 0x63, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x55, 0x73,
|
||||
0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x2a, 0x92, 0x41, 0x11, 0x12,
|
||||
0x0f, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x73, 0x20, 0x61, 0x20, 0x55, 0x73, 0x65, 0x72, 0x2e,
|
||||
0x82, 0xd3, 0xe4, 0x93, 0x02, 0x10, 0x2a, 0x0e, 0x2f, 0x76, 0x31, 0x2f, 0x75, 0x73, 0x65, 0x72,
|
||||
0x73, 0x2f, 0x7b, 0x69, 0x64, 0x7d, 0x12, 0xcd, 0x01, 0x0a, 0x0f, 0x41, 0x64, 0x64, 0x55, 0x73,
|
||||
0x65, 0x72, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x12, 0x32, 0x2e, 0x63, 0x6f, 0x6e,
|
||||
0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x73, 0x65, 0x72, 0x76,
|
||||
0x69, 0x63, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x64, 0x64, 0x55, 0x73, 0x65, 0x72, 0x41,
|
||||
0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x33,
|
||||
0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e,
|
||||
0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x64, 0x64, 0x55,
|
||||
0x73, 0x65, 0x72, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f,
|
||||
0x6e, 0x73, 0x65, 0x22, 0x51, 0x92, 0x41, 0x22, 0x12, 0x20, 0x41, 0x73, 0x73, 0x6f, 0x63, 0x69,
|
||||
0x61, 0x74, 0x65, 0x73, 0x20, 0x61, 0x6e, 0x20, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x20,
|
||||
0x74, 0x6f, 0x20, 0x61, 0x20, 0x55, 0x73, 0x65, 0x72, 0x2e, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x26,
|
||||
0x3a, 0x01, 0x2a, 0x62, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x22, 0x1b, 0x2f, 0x76, 0x31, 0x2f, 0x75,
|
||||
0x73, 0x65, 0x72, 0x73, 0x2f, 0x7b, 0x69, 0x64, 0x7d, 0x3a, 0x61, 0x64, 0x64, 0x2d, 0x61, 0x63,
|
||||
0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x12, 0xb5, 0x02, 0x0a, 0x0f, 0x53, 0x65, 0x74, 0x55, 0x73,
|
||||
0x65, 0x72, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x12, 0x32, 0x2e, 0x63, 0x6f, 0x6e,
|
||||
0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x73, 0x65, 0x72, 0x76,
|
||||
0x69, 0x63, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x41,
|
||||
0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x33,
|
||||
0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e,
|
||||
0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65, 0x74, 0x55,
|
||||
0x73, 0x65, 0x72, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f,
|
||||
0x6e, 0x73, 0x65, 0x22, 0xb8, 0x01, 0x92, 0x41, 0x88, 0x01, 0x12, 0x85, 0x01, 0x53, 0x65, 0x74,
|
||||
0x20, 0x74, 0x68, 0x65, 0x20, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x20, 0x61, 0x73,
|
||||
0x73, 0x6f, 0x63, 0x69, 0x61, 0x74, 0x65, 0x64, 0x20, 0x74, 0x6f, 0x20, 0x74, 0x68, 0x65, 0x20,
|
||||
0x55, 0x73, 0x65, 0x72, 0x20, 0x74, 0x6f, 0x20, 0x65, 0x78, 0x61, 0x63, 0x74, 0x6c, 0x79, 0x20,
|
||||
0x74, 0x68, 0x65, 0x20, 0x6c, 0x69, 0x73, 0x74, 0x20, 0x6f, 0x66, 0x20, 0x70, 0x72, 0x6f, 0x76,
|
||||
0x69, 0x64, 0x65, 0x64, 0x20, 0x69, 0x6e, 0x20, 0x74, 0x68, 0x65, 0x20, 0x72, 0x65, 0x71, 0x75,
|
||||
0x65, 0x73, 0x74, 0x2c, 0x20, 0x72, 0x65, 0x6d, 0x6f, 0x76, 0x69, 0x6e, 0x67, 0x20, 0x61, 0x6e,
|
||||
0x79, 0x20, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x20, 0x74, 0x68, 0x61, 0x74, 0x20,
|
||||
0x61, 0x72, 0x65, 0x20, 0x6e, 0x6f, 0x74, 0x20, 0x73, 0x70, 0x65, 0x63, 0x69, 0x66, 0x69, 0x65,
|
||||
0x64, 0x2e, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x26, 0x3a, 0x01, 0x2a, 0x62, 0x04, 0x69, 0x74, 0x65,
|
||||
0x6d, 0x22, 0x1b, 0x2f, 0x76, 0x31, 0x2f, 0x75, 0x73, 0x65, 0x72, 0x73, 0x2f, 0x7b, 0x69, 0x64,
|
||||
0x7d, 0x3a, 0x73, 0x65, 0x74, 0x2d, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x12, 0x86,
|
||||
0x02, 0x0a, 0x12, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x55, 0x73, 0x65, 0x72, 0x41, 0x63, 0x63,
|
||||
0x6f, 0x75, 0x6e, 0x74, 0x73, 0x12, 0x35, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c,
|
||||
0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e,
|
||||
0x76, 0x31, 0x2e, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x55, 0x73, 0x65, 0x72, 0x41, 0x63, 0x63,
|
||||
0x6f, 0x75, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x36, 0x2e, 0x63,
|
||||
0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x73, 0x65,
|
||||
0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65,
|
||||
0x55, 0x73, 0x65, 0x72, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70,
|
||||
0x6f, 0x6e, 0x73, 0x65, 0x22, 0x80, 0x01, 0x92, 0x41, 0x4e, 0x12, 0x4c, 0x52, 0x65, 0x6d, 0x6f,
|
||||
0x76, 0x65, 0x73, 0x20, 0x74, 0x68, 0x65, 0x20, 0x73, 0x70, 0x65, 0x63, 0x69, 0x66, 0x69, 0x65,
|
||||
0x64, 0x20, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x20, 0x66, 0x72, 0x6f, 0x6d, 0x20,
|
||||
0x62, 0x65, 0x69, 0x6e, 0x67, 0x20, 0x61, 0x73, 0x73, 0x6f, 0x63, 0x69, 0x61, 0x74, 0x65, 0x64,
|
||||
0x20, 0x77, 0x69, 0x74, 0x68, 0x20, 0x74, 0x68, 0x65, 0x20, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64,
|
||||
0x65, 0x64, 0x20, 0x55, 0x73, 0x65, 0x72, 0x2e, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x29, 0x3a, 0x01,
|
||||
0x2a, 0x62, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x22, 0x1e, 0x2f, 0x76, 0x31, 0x2f, 0x75, 0x73, 0x65,
|
||||
0x72, 0x73, 0x2f, 0x7b, 0x69, 0x64, 0x7d, 0x3a, 0x72, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x2d, 0x61,
|
||||
0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x42, 0x4d, 0x5a, 0x4b, 0x67, 0x69, 0x74, 0x68, 0x75,
|
||||
0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2f,
|
||||
0x62, 0x6f, 0x75, 0x6e, 0x64, 0x61, 0x72, 0x79, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61,
|
||||
0x6c, 0x2f, 0x67, 0x65, 0x6e, 0x2f, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72,
|
||||
0x2f, 0x61, 0x70, 0x69, 0x2f, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x3b, 0x73, 0x65,
|
||||
0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
|
||||
}
|
||||
|
||||
var (
|
||||
|
||||
@@ -180,4 +180,8 @@ const (
|
||||
estimateCountRoles = `
|
||||
select reltuples::bigint as estimate from pg_class where oid in ('iam_role'::regclass)
|
||||
`
|
||||
|
||||
estimateCountUsers = `
|
||||
select reltuples::bigint as estimate from pg_class where oid in ('iam_user'::regclass)
|
||||
`
|
||||
)
|
||||
@@ -765,7 +765,8 @@ func TestRepository_ListRoles_Multiple_Scopes(t *testing.T) {
|
||||
|
||||
got, ttime, err := repo.listRoles(context.Background(), []string{"global", org.GetPublicId(), proj.GetPublicId()})
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, total, len(got)) // Transaction timestamp should be within ~10 seconds of now
|
||||
assert.Equal(t, total, len(got))
|
||||
// Transaction timestamp should be within ~10 seconds of now
|
||||
assert.True(t, time.Now().Before(ttime.Add(10*time.Second)))
|
||||
assert.True(t, time.Now().After(ttime.Add(-10*time.Second)))
|
||||
}
|
||||
|
||||
+139
-54
@@ -5,11 +5,14 @@ package iam
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/hashicorp/boundary/globals"
|
||||
"github.com/hashicorp/boundary/internal/db"
|
||||
"github.com/hashicorp/boundary/internal/db/timestamp"
|
||||
"github.com/hashicorp/boundary/internal/errors"
|
||||
"github.com/hashicorp/boundary/internal/kms"
|
||||
"github.com/hashicorp/boundary/internal/oplog"
|
||||
@@ -209,19 +212,6 @@ func (r *Repository) DeleteUser(ctx context.Context, withPublicId string, _ ...O
|
||||
return rowsDeleted, nil
|
||||
}
|
||||
|
||||
// ListUsers lists users in the given scopes and supports the WithLimit option.
|
||||
func (r *Repository) ListUsers(ctx context.Context, withScopeIds []string, opt ...Option) ([]*User, error) {
|
||||
const op = "iam.(Repository).ListUsers"
|
||||
if len(withScopeIds) == 0 {
|
||||
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing scope id")
|
||||
}
|
||||
users, err := r.getUsers(ctx, "", withScopeIds, opt...)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(ctx, err, op)
|
||||
}
|
||||
return users, nil
|
||||
}
|
||||
|
||||
// LookupUserWithLogin will attempt to lookup the user with a matching
|
||||
// account id and return the user if found. If a user is not found and the
|
||||
// account's scope is not the PrimaryAuthMethod, then an error is returned.
|
||||
@@ -841,65 +831,160 @@ func associationChanges(ctx context.Context, reader db.Reader, userId string, ac
|
||||
}
|
||||
|
||||
// lookupUser will lookup a single user and returns nil, nil when no user is found.
|
||||
// no options are currently supported
|
||||
func (r *Repository) lookupUser(ctx context.Context, userId string, opt ...Option) (*User, error) {
|
||||
const op = "iam.(Repository).lookupUser"
|
||||
users, err := r.getUsers(ctx, userId, nil, opt...)
|
||||
if err != nil {
|
||||
if userId == "" {
|
||||
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing user id")
|
||||
}
|
||||
ret := allocUserAccountInfo()
|
||||
ret.PublicId = userId
|
||||
if err := r.reader.LookupById(ctx, ret); err != nil {
|
||||
if errors.IsNotFoundError(err) {
|
||||
return nil, nil
|
||||
}
|
||||
return nil, errors.Wrap(ctx, err, op)
|
||||
}
|
||||
switch {
|
||||
case len(users) == 0:
|
||||
return nil, nil // not an error to return no rows for a lookup
|
||||
case len(users) > 1:
|
||||
return nil, errors.New(ctx, errors.NotSpecificIntegrity, op, fmt.Sprintf("%s matched more than 1 ", userId))
|
||||
default:
|
||||
return users[0], nil
|
||||
}
|
||||
return ret.shallowConversion(), nil
|
||||
}
|
||||
|
||||
// getUsers allows the caller to specify to either lookup a specific User via
|
||||
// its ID or search for a set of Users within a set of scopes. Passing both
|
||||
// scopeIds and a userId is an error. The WithLimit option is supported and all
|
||||
// other options are ignored.
|
||||
//
|
||||
// When no record is found then it returns nil, nil
|
||||
func (r *Repository) getUsers(ctx context.Context, userId string, scopeIds []string, opt ...Option) ([]*User, error) {
|
||||
const op = "iam.(Repository).getUsers"
|
||||
if userId == "" && len(scopeIds) == 0 {
|
||||
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing search criteria: both user id and scope ids are empty")
|
||||
// listUsers lists users in the given scopes and supports WithLimit option.
|
||||
func (r *Repository) ListUsers(ctx context.Context, withScopeIds []string, opt ...Option) ([]*User, time.Time, error) {
|
||||
const op = "iam.(Repository).listUsers"
|
||||
if len(withScopeIds) == 0 {
|
||||
return nil, time.Time{}, errors.New(ctx, errors.InvalidParameter, op, "missing scope id")
|
||||
}
|
||||
if userId != "" && len(scopeIds) > 0 {
|
||||
return nil, errors.New(ctx, errors.InvalidParameter, op, "searching for both a specific user id and scope ids is not supported")
|
||||
opts := getOpts(opt...)
|
||||
|
||||
limit := r.defaultLimit
|
||||
switch {
|
||||
case opts.withLimit > 0:
|
||||
// non-zero signals an override of the default limit for the repo.
|
||||
limit = opts.withLimit
|
||||
case opts.withLimit < 0:
|
||||
return nil, time.Time{}, errors.New(ctx, errors.InvalidParameter, op, "limit must be non-negative")
|
||||
}
|
||||
|
||||
var args []any
|
||||
whereClause := "scope_id in @scope_ids"
|
||||
args = append(args, sql.Named("scope_ids", withScopeIds))
|
||||
|
||||
if opts.withStartPageAfterItem != nil {
|
||||
whereClause = fmt.Sprintf("(create_time, public_id) < (@last_item_create_time, @last_item_id) and %s", whereClause)
|
||||
args = append(args,
|
||||
sql.Named("last_item_create_time", opts.withStartPageAfterItem.GetCreateTime()),
|
||||
sql.Named("last_item_id", opts.withStartPageAfterItem.GetPublicId()),
|
||||
)
|
||||
}
|
||||
dbOpts := []db.Option{db.WithLimit(limit), db.WithOrder("create_time desc, public_id desc")}
|
||||
return r.queryUsers(ctx, whereClause, args, dbOpts...)
|
||||
}
|
||||
|
||||
// listUsersRefresh lists users in the given scopes and supports the
|
||||
// WithLimit and WithStartPageAfterItem options.
|
||||
func (r *Repository) listUsersRefresh(ctx context.Context, updatedAfter time.Time, withScopeIds []string, opt ...Option) ([]*User, time.Time, error) {
|
||||
const op = "iam.(Repository).listUsersRefresh"
|
||||
|
||||
switch {
|
||||
case updatedAfter.IsZero():
|
||||
return nil, time.Time{}, errors.New(ctx, errors.InvalidParameter, op, "missing updated after time")
|
||||
|
||||
case len(withScopeIds) == 0:
|
||||
return nil, time.Time{}, errors.New(ctx, errors.InvalidParameter, op, "missing scope id")
|
||||
}
|
||||
|
||||
opts := getOpts(opt...)
|
||||
dbArgs := []db.Option{}
|
||||
|
||||
limit := r.defaultLimit
|
||||
if opts.withLimit != 0 {
|
||||
switch {
|
||||
case opts.withLimit > 0:
|
||||
// non-zero signals an override of the default limit for the repo.
|
||||
limit = opts.withLimit
|
||||
case opts.withLimit < 0:
|
||||
return nil, time.Time{}, errors.New(ctx, errors.InvalidParameter, op, "limit must be non-negative")
|
||||
}
|
||||
dbArgs = append(dbArgs, db.WithLimit(limit))
|
||||
|
||||
var args []any
|
||||
var where []string
|
||||
switch {
|
||||
case userId != "":
|
||||
where, args = append(where, "public_id = ?"), append(args, userId)
|
||||
default:
|
||||
where, args = append(where, "scope_id in(?)"), append(args, scopeIds)
|
||||
whereClause := "update_time > @updated_after_time and scope_id in @scope_ids"
|
||||
args = append(args,
|
||||
sql.Named("updated_after_time", timestamp.New(updatedAfter)),
|
||||
sql.Named("scope_ids", withScopeIds),
|
||||
)
|
||||
if opts.withStartPageAfterItem != nil {
|
||||
whereClause = fmt.Sprintf("(update_time, public_id) < (@last_item_update_time, @last_item_id) and %s", whereClause)
|
||||
args = append(args,
|
||||
sql.Named("last_item_update_time", opts.withStartPageAfterItem.GetUpdateTime()),
|
||||
sql.Named("last_item_id", opts.withStartPageAfterItem.GetPublicId()),
|
||||
)
|
||||
}
|
||||
var usersAcctInfo []*userAccountInfo
|
||||
err := r.reader.SearchWhere(ctx, &usersAcctInfo, strings.Join(where, " and "), args, dbArgs...)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(ctx, err, op)
|
||||
|
||||
dbOpts := []db.Option{db.WithLimit(limit), db.WithOrder("update_time desc, public_id desc")}
|
||||
return r.queryUsers(ctx, whereClause, args, dbOpts...)
|
||||
}
|
||||
|
||||
func (r *Repository) queryUsers(ctx context.Context, whereClause string, args []any, opt ...db.Option) ([]*User, time.Time, error) {
|
||||
const op = "iam.(Repository).queryUsers"
|
||||
|
||||
var transactionTimestamp time.Time
|
||||
var ret []*userAccountInfo
|
||||
if _, err := r.writer.DoTx(ctx, db.StdRetryCnt, db.ExpBackoff{}, func(rd db.Reader, w db.Writer) error {
|
||||
var inRet []*userAccountInfo
|
||||
if err := rd.SearchWhere(ctx, &inRet, whereClause, args, opt...); err != nil {
|
||||
return errors.Wrap(ctx, err, op)
|
||||
}
|
||||
ret = inRet
|
||||
var err error
|
||||
transactionTimestamp, err = rd.Now(ctx)
|
||||
return err
|
||||
}); err != nil {
|
||||
return nil, time.Time{}, err
|
||||
}
|
||||
if len(usersAcctInfo) == 0 { // we're done if nothing is found.
|
||||
return nil, nil
|
||||
}
|
||||
users := make([]*User, 0, len(usersAcctInfo))
|
||||
for _, u := range usersAcctInfo {
|
||||
|
||||
users := make([]*User, 0, len(ret))
|
||||
for _, u := range ret {
|
||||
users = append(users, u.shallowConversion())
|
||||
}
|
||||
return users, nil
|
||||
|
||||
return users, transactionTimestamp, nil
|
||||
}
|
||||
|
||||
// listUserDeletedIds lists the public IDs of any users deleted since the timestamp provided.
|
||||
func (r *Repository) listUserDeletedIds(ctx context.Context, since time.Time) ([]string, time.Time, error) {
|
||||
const op = "iam.(Repository).listUserDeletedIds"
|
||||
var deletedResources []*deletedUser
|
||||
var transactionTimestamp time.Time
|
||||
if _, err := r.writer.DoTx(ctx, db.StdRetryCnt, db.ExpBackoff{}, func(r db.Reader, _ db.Writer) error {
|
||||
if err := r.SearchWhere(ctx, &deletedResources, "delete_time >= ?", []any{since}); err != nil {
|
||||
return errors.Wrap(ctx, err, op, errors.WithMsg("failed to query deleted users"))
|
||||
}
|
||||
var err error
|
||||
transactionTimestamp, err = r.Now(ctx)
|
||||
if err != nil {
|
||||
return errors.Wrap(ctx, err, op, errors.WithMsg("failed to get transaction timestamp"))
|
||||
}
|
||||
return nil
|
||||
}); err != nil {
|
||||
return nil, time.Time{}, err
|
||||
}
|
||||
var dIds []string
|
||||
for _, res := range deletedResources {
|
||||
dIds = append(dIds, res.PublicId)
|
||||
}
|
||||
return dIds, transactionTimestamp, nil
|
||||
}
|
||||
|
||||
// estimatedUserCount returns an estimate of the total number of items in the iam_user table.
|
||||
func (r *Repository) estimatedUserCount(ctx context.Context) (int, error) {
|
||||
const op = "iam.(Repository).estimatedUserCount"
|
||||
rows, err := r.reader.Query(ctx, estimateCountUsers, nil)
|
||||
if err != nil {
|
||||
return 0, errors.Wrap(ctx, err, op, errors.WithMsg("failed to query total users"))
|
||||
}
|
||||
var count int
|
||||
for rows.Next() {
|
||||
if err := r.reader.ScanRows(ctx, rows, &count); err != nil {
|
||||
return 0, errors.Wrap(ctx, err, op, errors.WithMsg("failed to query total users"))
|
||||
}
|
||||
}
|
||||
return count, nil
|
||||
}
|
||||
@@ -6,6 +6,7 @@ package iam
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/hashicorp/boundary/internal/db"
|
||||
dbassert "github.com/hashicorp/boundary/internal/db/assert"
|
||||
@@ -332,3 +333,270 @@ func TestRepository_dissociateUserWithAccount(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestRepository_ListUsers_internal(t *testing.T) {
|
||||
t.Parallel()
|
||||
conn, _ := db.TestSetup(t, "postgres")
|
||||
const testLimit = 10
|
||||
wrapper := db.TestWrapper(t)
|
||||
repo := TestRepo(t, conn, wrapper, WithLimit(testLimit))
|
||||
org, _ := TestScopes(t, repo)
|
||||
|
||||
type args struct {
|
||||
withOrgId string
|
||||
opt []Option
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
createCnt int
|
||||
args args
|
||||
wantCnt int
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "negative limit",
|
||||
createCnt: testLimit + 1,
|
||||
args: args{
|
||||
withOrgId: org.PublicId,
|
||||
opt: []Option{WithLimit(-1)},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "default-limit",
|
||||
createCnt: testLimit + 1,
|
||||
args: args{
|
||||
withOrgId: org.PublicId,
|
||||
},
|
||||
wantCnt: testLimit,
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "custom-limit",
|
||||
createCnt: testLimit + 1,
|
||||
args: args{
|
||||
withOrgId: org.PublicId,
|
||||
opt: []Option{WithLimit(3)},
|
||||
},
|
||||
wantCnt: 3,
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "bad-org",
|
||||
createCnt: 1,
|
||||
args: args{
|
||||
withOrgId: "bad-id",
|
||||
},
|
||||
wantCnt: 0,
|
||||
wantErr: false,
|
||||
},
|
||||
}
|
||||
type userInfo struct {
|
||||
email string
|
||||
fullName string
|
||||
primaryAcctId string
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
assert, require := assert.New(t), require.New(t)
|
||||
t.Cleanup(func() {
|
||||
db.TestDeleteWhere(t, conn, func() any { u := AllocUser(); return &u }(), "public_id != 'u_anon' and public_id != 'u_auth' and public_id != 'u_recovery'")
|
||||
})
|
||||
testUsers := []*User{}
|
||||
wantUserInfo := map[string]userInfo{}
|
||||
for i := 0; i < tt.createCnt; i++ {
|
||||
u := TestUser(t, repo, org.PublicId)
|
||||
testUsers = append(testUsers, u)
|
||||
}
|
||||
assert.Equal(tt.createCnt, len(testUsers))
|
||||
got, ttime, err := repo.ListUsers(context.Background(), []string{tt.args.withOrgId}, tt.args.opt...)
|
||||
if tt.wantErr {
|
||||
require.Error(err)
|
||||
return
|
||||
}
|
||||
require.NoError(err)
|
||||
assert.Equal(tt.wantCnt, len(got))
|
||||
for _, u := range got {
|
||||
assert.Equal(wantUserInfo[u.PublicId], userInfo{
|
||||
email: u.Email,
|
||||
fullName: u.FullName,
|
||||
primaryAcctId: u.PrimaryAccountId,
|
||||
})
|
||||
}
|
||||
// Transaction timestamp should be within ~10 seconds of now
|
||||
assert.True(time.Now().Before(ttime.Add(10 * time.Second)))
|
||||
assert.True(time.Now().After(ttime.Add(-10 * time.Second)))
|
||||
})
|
||||
}
|
||||
t.Run("withStartPageAfter", func(t *testing.T) {
|
||||
assert, require := assert.New(t), require.New(t)
|
||||
ctx := context.Background()
|
||||
|
||||
for i := 0; i < 10; i++ {
|
||||
_ = TestUser(t, repo, org.GetPublicId())
|
||||
}
|
||||
|
||||
page1, ttime, err := repo.ListUsers(ctx, []string{org.GetPublicId()}, WithLimit(2))
|
||||
require.NoError(err)
|
||||
require.Len(page1, 2)
|
||||
// Transaction timestamp should be within ~10 seconds of now
|
||||
assert.True(time.Now().Before(ttime.Add(10 * time.Second)))
|
||||
assert.True(time.Now().After(ttime.Add(-10 * time.Second)))
|
||||
page2, ttime, err := repo.ListUsers(ctx, []string{org.GetPublicId()}, WithLimit(2), WithStartPageAfterItem(page1[1]))
|
||||
require.NoError(err)
|
||||
require.Len(page2, 2)
|
||||
assert.True(time.Now().Before(ttime.Add(10 * time.Second)))
|
||||
assert.True(time.Now().After(ttime.Add(-10 * time.Second)))
|
||||
for _, item := range page1 {
|
||||
assert.NotEqual(item.GetPublicId(), page2[0].GetPublicId())
|
||||
assert.NotEqual(item.GetPublicId(), page2[1].GetPublicId())
|
||||
}
|
||||
page3, ttime, err := repo.ListUsers(ctx, []string{org.GetPublicId()}, WithLimit(2), WithStartPageAfterItem(page2[1]))
|
||||
require.NoError(err)
|
||||
require.Len(page3, 2)
|
||||
assert.True(time.Now().Before(ttime.Add(10 * time.Second)))
|
||||
assert.True(time.Now().After(ttime.Add(-10 * time.Second)))
|
||||
for _, item := range page2 {
|
||||
assert.NotEqual(item.GetPublicId(), page3[0].GetPublicId())
|
||||
assert.NotEqual(item.GetPublicId(), page3[1].GetPublicId())
|
||||
}
|
||||
page4, ttime, err := repo.ListUsers(ctx, []string{org.GetPublicId()}, WithLimit(2), WithStartPageAfterItem(page3[1]))
|
||||
require.NoError(err)
|
||||
assert.Len(page4, 2)
|
||||
assert.True(time.Now().Before(ttime.Add(10 * time.Second)))
|
||||
assert.True(time.Now().After(ttime.Add(-10 * time.Second)))
|
||||
for _, item := range page3 {
|
||||
assert.NotEqual(item.GetPublicId(), page4[0].GetPublicId())
|
||||
assert.NotEqual(item.GetPublicId(), page4[1].GetPublicId())
|
||||
}
|
||||
page5, ttime, err := repo.ListUsers(ctx, []string{org.GetPublicId()}, WithLimit(2), WithStartPageAfterItem(page4[1]))
|
||||
require.NoError(err)
|
||||
assert.Len(page5, 2)
|
||||
assert.True(time.Now().Before(ttime.Add(10 * time.Second)))
|
||||
assert.True(time.Now().After(ttime.Add(-10 * time.Second)))
|
||||
for _, item := range page4 {
|
||||
assert.NotEqual(item.GetPublicId(), page5[0].GetPublicId())
|
||||
assert.NotEqual(item.GetPublicId(), page5[1].GetPublicId())
|
||||
}
|
||||
page6, ttime, err := repo.ListUsers(ctx, []string{org.GetPublicId()}, WithLimit(2), WithStartPageAfterItem(page5[1]))
|
||||
require.NoError(err)
|
||||
assert.Empty(page6)
|
||||
assert.True(time.Now().Before(ttime.Add(10 * time.Second)))
|
||||
assert.True(time.Now().After(ttime.Add(-10 * time.Second)))
|
||||
|
||||
// Create 2 new users
|
||||
newR1 := TestUser(t, repo, org.GetPublicId())
|
||||
newR2 := TestUser(t, repo, org.GetPublicId())
|
||||
|
||||
// since it will return newest to oldest, we get page1[1] first
|
||||
page7, ttime, err := repo.listUsersRefresh(
|
||||
ctx,
|
||||
time.Now().Add(-1*time.Second),
|
||||
[]string{org.GetPublicId()},
|
||||
WithLimit(1),
|
||||
)
|
||||
require.NoError(err)
|
||||
require.Len(page7, 1)
|
||||
require.Equal(page7[0].GetPublicId(), newR2.GetPublicId())
|
||||
assert.True(time.Now().Before(ttime.Add(10 * time.Second)))
|
||||
assert.True(time.Now().After(ttime.Add(-10 * time.Second)))
|
||||
|
||||
page8, ttime, err := repo.listUsersRefresh(
|
||||
context.Background(),
|
||||
time.Now().Add(-1*time.Second),
|
||||
[]string{org.GetPublicId()},
|
||||
WithLimit(1),
|
||||
WithStartPageAfterItem(page7[0]),
|
||||
)
|
||||
require.NoError(err)
|
||||
require.Len(page8, 1)
|
||||
require.Equal(page8[0].GetPublicId(), newR1.GetPublicId())
|
||||
assert.True(time.Now().Before(ttime.Add(10 * time.Second)))
|
||||
assert.True(time.Now().After(ttime.Add(-10 * time.Second)))
|
||||
})
|
||||
}
|
||||
|
||||
func Test_listUserDeletedIds(t *testing.T) {
|
||||
t.Parallel()
|
||||
ctx := context.Background()
|
||||
conn, _ := db.TestSetup(t, "postgres")
|
||||
wrapper := db.TestWrapper(t)
|
||||
repo := TestRepo(t, conn, wrapper)
|
||||
org, _ := TestScopes(t, repo)
|
||||
r := TestUser(t, repo, org.GetPublicId())
|
||||
|
||||
// Expect no entries at the start
|
||||
deletedIds, ttime, err := repo.listUserDeletedIds(ctx, time.Now().AddDate(-1, 0, 0))
|
||||
require.NoError(t, err)
|
||||
require.Empty(t, deletedIds)
|
||||
// Transaction timestamp should be within ~10 seconds of now
|
||||
assert.True(t, time.Now().Before(ttime.Add(10*time.Second)))
|
||||
assert.True(t, time.Now().After(ttime.Add(-10*time.Second)))
|
||||
|
||||
// Delete a user
|
||||
_, err = repo.DeleteUser(ctx, r.GetPublicId())
|
||||
require.NoError(t, err)
|
||||
|
||||
// Expect a single entry
|
||||
deletedIds, ttime, err = repo.listUserDeletedIds(ctx, time.Now().AddDate(-1, 0, 0))
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, []string{r.GetPublicId()}, deletedIds)
|
||||
// Transaction timestamp should be within ~10 seconds of now
|
||||
assert.True(t, time.Now().Before(ttime.Add(10*time.Second)))
|
||||
assert.True(t, time.Now().After(ttime.Add(-10*time.Second)))
|
||||
|
||||
// Try again with the time set to now, expect no entries
|
||||
deletedIds, ttime, err = repo.listUserDeletedIds(ctx, time.Now())
|
||||
require.NoError(t, err)
|
||||
require.Empty(t, deletedIds)
|
||||
// Transaction timestamp should be within ~10 seconds of now
|
||||
assert.True(t, time.Now().Before(ttime.Add(10*time.Second)))
|
||||
assert.True(t, time.Now().After(ttime.Add(-10*time.Second)))
|
||||
}
|
||||
|
||||
func Test_estimatedUserCount(t *testing.T) {
|
||||
t.Parallel()
|
||||
ctx := context.Background()
|
||||
conn, _ := db.TestSetup(t, "postgres")
|
||||
sqlDb, err := conn.SqlDB(ctx)
|
||||
require.NoError(t, err)
|
||||
rw := db.New(conn)
|
||||
wrapper := db.TestWrapper(t)
|
||||
kms := kms.TestKms(t, conn, wrapper)
|
||||
repo, err := NewRepository(ctx, rw, rw, kms)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Run analyze to update estimate
|
||||
_, err = sqlDb.ExecContext(ctx, "analyze")
|
||||
require.NoError(t, err)
|
||||
|
||||
// Check total entries at start, expect 3
|
||||
// u_anon, u_auth, and u_recovery
|
||||
numItems, err := repo.estimatedUserCount(ctx)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, 3, numItems)
|
||||
|
||||
iamRepo := TestRepo(t, conn, wrapper)
|
||||
org, _ := TestScopes(t, iamRepo)
|
||||
// Create a user, expect 1 entry
|
||||
u := TestUser(t, repo, org.GetPublicId())
|
||||
|
||||
// Run analyze to update estimate
|
||||
_, err = sqlDb.ExecContext(ctx, "analyze")
|
||||
require.NoError(t, err)
|
||||
numItems, err = repo.estimatedUserCount(ctx)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, 4, numItems)
|
||||
|
||||
// Delete the user, expect 3 again
|
||||
_, err = repo.DeleteUser(ctx, u.GetPublicId())
|
||||
require.NoError(t, err)
|
||||
|
||||
// Run analyze to update estimate
|
||||
_, err = sqlDb.ExecContext(ctx, "analyze")
|
||||
require.NoError(t, err)
|
||||
|
||||
numItems, err = repo.estimatedUserCount(ctx)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, 3, numItems)
|
||||
}
|
||||
@@ -608,14 +608,13 @@ func TestRepository_ListUsers(t *testing.T) {
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "no-limit",
|
||||
name: "negative-limit",
|
||||
createCnt: testLimit + 1,
|
||||
args: args{
|
||||
withOrgId: org.PublicId,
|
||||
opt: []iam.Option{iam.WithLimit(-1)},
|
||||
},
|
||||
wantCnt: testLimit + 1,
|
||||
wantErr: false,
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "default-limit",
|
||||
@@ -674,7 +673,7 @@ func TestRepository_ListUsers(t *testing.T) {
|
||||
|
||||
}
|
||||
assert.Equal(tt.createCnt, len(testUsers))
|
||||
got, err := repo.ListUsers(context.Background(), []string{tt.args.withOrgId}, tt.args.opt...)
|
||||
got, _, err := repo.ListUsers(context.Background(), []string{tt.args.withOrgId}, tt.args.opt...)
|
||||
if tt.wantErr {
|
||||
require.Error(err)
|
||||
return
|
||||
@@ -699,7 +698,9 @@ func TestRepository_ListUsers_Multiple_Scopes(t *testing.T) {
|
||||
repo := iam.TestRepo(t, conn, wrapper)
|
||||
org, _ := iam.TestScopes(t, repo)
|
||||
|
||||
db.TestDeleteWhere(t, conn, func() any { i := iam.AllocUser(); return &i }(), "public_id != 'u_anon' and public_id != 'u_auth' and public_id != 'u_recovery'")
|
||||
t.Cleanup(func() {
|
||||
db.TestDeleteWhere(t, conn, func() any { i := iam.AllocUser(); return &i }(), "public_id != 'u_anon' and public_id != 'u_auth' and public_id != 'u_recovery'")
|
||||
})
|
||||
|
||||
const numPerScope = 10
|
||||
var total int = 3 // anon, auth, recovery
|
||||
@@ -710,9 +711,12 @@ func TestRepository_ListUsers_Multiple_Scopes(t *testing.T) {
|
||||
total++
|
||||
}
|
||||
|
||||
got, err := repo.ListUsers(context.Background(), []string{"global", org.GetPublicId()})
|
||||
got, ttime, err := repo.ListUsers(context.Background(), []string{"global", org.GetPublicId()})
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, total, len(got))
|
||||
// Transaction timestamp should be within ~10 seconds of now
|
||||
assert.True(t, time.Now().Before(ttime.Add(10*time.Second)))
|
||||
assert.True(t, time.Now().After(ttime.Add(-10*time.Second)))
|
||||
}
|
||||
|
||||
func TestRepository_LookupUserWithLogin(t *testing.T) {
|
||||
|
||||
@@ -51,3 +51,44 @@ func ListRoles(
|
||||
|
||||
return pagination.List(ctx, grantsHash, pageSize, filterItemFn, listItemsFn, repo.estimatedRoleCount)
|
||||
}
|
||||
|
||||
// ListUsers 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 returns a new list token used to continue pagination or refresh items.
|
||||
// Users are ordered by create time descending (most recently created first).
|
||||
func ListUsers(
|
||||
ctx context.Context,
|
||||
grantsHash []byte,
|
||||
pageSize int,
|
||||
filterItemFn pagination.ListFilterFunc[*User],
|
||||
repo *Repository,
|
||||
withScopeIds []string,
|
||||
) (*pagination.ListResponse[*User], error) {
|
||||
const op = "iam.ListUsers"
|
||||
|
||||
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 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")
|
||||
}
|
||||
|
||||
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))
|
||||
}
|
||||
return repo.ListUsers(ctx, withScopeIds, opts...)
|
||||
}
|
||||
|
||||
return pagination.List(ctx, grantsHash, pageSize, filterItemFn, listItemsFn, repo.estimatedUserCount)
|
||||
}
|
||||
@@ -611,3 +611,630 @@ func TestService_ListRoles(t *testing.T) {
|
||||
require.Empty(t, resp3.Items)
|
||||
})
|
||||
}
|
||||
|
||||
func TestService_ListUsers(t *testing.T) {
|
||||
fiveDaysAgo := time.Now()
|
||||
// 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(context.Background())
|
||||
require.NoError(t, err)
|
||||
rw := db.New(conn)
|
||||
wrapper := db.TestWrapper(t)
|
||||
kms := kms.TestKms(t, conn, wrapper)
|
||||
iamRepo := iam.TestRepo(t, conn, wrapper)
|
||||
org, _ := iam.TestScopes(t, iamRepo)
|
||||
|
||||
us, _, err := iamRepo.ListUsers(ctx, []string{"global"})
|
||||
require.NoError(t, err)
|
||||
require.Len(t, us, 3)
|
||||
|
||||
// Since ListUsers is supposed to return the newest users first, we need to
|
||||
// reverse the slice to match the order of the allResources slice.
|
||||
slices.Reverse(us)
|
||||
var allResources []*iam.User
|
||||
allResources = append(allResources, us...)
|
||||
|
||||
// Create 5 users, which, with u_anon, u_recovery, and u_auth, will add
|
||||
// up to 8 users.
|
||||
for i := 0; i < 5; i++ {
|
||||
r := iam.TestUser(t, iamRepo, org.GetPublicId())
|
||||
allResources = append(allResources, r)
|
||||
}
|
||||
|
||||
repo, err := iam.NewRepository(ctx, rw, rw, kms)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Reverse since we read items in descending order (newest first)
|
||||
slices.Reverse(allResources)
|
||||
|
||||
// Run analyze to update postgres estimates
|
||||
_, err = sqlDB.ExecContext(ctx, "analyze")
|
||||
require.NoError(t, err)
|
||||
|
||||
cmpIgnoreUnexportedOpts := cmpopts.IgnoreUnexported(iam.User{}, store.User{}, timestamp.Timestamp{}, timestamppb.Timestamp{})
|
||||
|
||||
t.Run("List validation", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
t.Run("missing grants hash", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
filterFunc := func(_ context.Context, r *iam.User) (bool, error) {
|
||||
return true, nil
|
||||
}
|
||||
_, err := iam.ListUsers(ctx, nil, 1, filterFunc, repo, []string{org.GetPublicId()})
|
||||
require.ErrorContains(t, err, "missing grants hash")
|
||||
})
|
||||
t.Run("zero page size", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
filterFunc := func(_ context.Context, r *iam.User) (bool, error) {
|
||||
return true, nil
|
||||
}
|
||||
_, err := iam.ListUsers(ctx, []byte("some hash"), 0, filterFunc, repo, []string{org.GetPublicId()})
|
||||
require.ErrorContains(t, err, "page size must be at least 1")
|
||||
})
|
||||
t.Run("negative page size", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
filterFunc := func(_ context.Context, r *iam.User) (bool, error) {
|
||||
return true, nil
|
||||
}
|
||||
_, err := iam.ListUsers(ctx, []byte("some hash"), -1, filterFunc, repo, []string{org.GetPublicId()})
|
||||
require.ErrorContains(t, err, "page size must be at least 1")
|
||||
})
|
||||
t.Run("nil filter func", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
_, err := iam.ListUsers(ctx, []byte("some hash"), 1, nil, repo, []string{org.GetPublicId()})
|
||||
require.ErrorContains(t, err, "missing filter item callback")
|
||||
})
|
||||
t.Run("nil repo", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
filterFunc := func(_ context.Context, r *iam.User) (bool, error) {
|
||||
return true, nil
|
||||
}
|
||||
_, err := iam.ListUsers(ctx, []byte("some hash"), 1, filterFunc, nil, []string{org.GetPublicId()})
|
||||
require.ErrorContains(t, err, "missing repo")
|
||||
})
|
||||
t.Run("missing scope ids", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
filterFunc := func(_ context.Context, r *iam.User) (bool, error) {
|
||||
return true, nil
|
||||
}
|
||||
_, err := iam.ListUsers(ctx, []byte("some hash"), 1, filterFunc, repo, nil)
|
||||
require.ErrorContains(t, err, "missing scope ids")
|
||||
})
|
||||
})
|
||||
t.Run("ListPage validation", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
t.Run("missing grants hash", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
filterFunc := func(_ context.Context, r *iam.User) (bool, error) {
|
||||
return true, nil
|
||||
}
|
||||
tok, err := listtoken.NewPagination(ctx, fiveDaysAgo, resource.User, []byte("some hash"), "some-id", fiveDaysAgo)
|
||||
require.NoError(t, err)
|
||||
_, err = iam.ListUsersPage(ctx, nil, 1, filterFunc, tok, repo, []string{org.GetPublicId()})
|
||||
require.ErrorContains(t, err, "missing grants hash")
|
||||
})
|
||||
t.Run("zero page size", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
filterFunc := func(_ context.Context, r *iam.User) (bool, error) {
|
||||
return true, nil
|
||||
}
|
||||
tok, err := listtoken.NewPagination(ctx, fiveDaysAgo, resource.User, []byte("some hash"), "some-id", fiveDaysAgo)
|
||||
require.NoError(t, err)
|
||||
_, err = iam.ListUsersPage(ctx, []byte("some hash"), 0, filterFunc, tok, repo, []string{org.GetPublicId()})
|
||||
require.ErrorContains(t, err, "page size must be at least 1")
|
||||
})
|
||||
t.Run("negative page size", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
filterFunc := func(_ context.Context, r *iam.User) (bool, error) {
|
||||
return true, nil
|
||||
}
|
||||
tok, err := listtoken.NewPagination(ctx, fiveDaysAgo, resource.User, []byte("some hash"), "some-id", fiveDaysAgo)
|
||||
require.NoError(t, err)
|
||||
_, err = iam.ListUsersPage(ctx, []byte("some hash"), -1, filterFunc, tok, repo, []string{org.GetPublicId()})
|
||||
require.ErrorContains(t, err, "page size must be at least 1")
|
||||
})
|
||||
t.Run("nil filter func", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
tok, err := listtoken.NewPagination(ctx, fiveDaysAgo, resource.User, []byte("some hash"), "some-id", fiveDaysAgo)
|
||||
require.NoError(t, err)
|
||||
_, err = iam.ListUsersPage(ctx, []byte("some hash"), 1, nil, tok, repo, []string{org.GetPublicId()})
|
||||
require.ErrorContains(t, err, "missing filter item callback")
|
||||
})
|
||||
t.Run("nil token", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
filterFunc := func(_ context.Context, r *iam.User) (bool, error) {
|
||||
return true, nil
|
||||
}
|
||||
_, err = iam.ListUsersPage(ctx, []byte("some hash"), 1, filterFunc, nil, repo, []string{org.GetPublicId()})
|
||||
require.ErrorContains(t, err, "missing token")
|
||||
})
|
||||
t.Run("wrong token type", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
filterFunc := func(_ context.Context, r *iam.User) (bool, error) {
|
||||
return true, nil
|
||||
}
|
||||
tok, err := listtoken.NewStartRefresh(ctx, fiveDaysAgo, resource.User, []byte("some hash"), fiveDaysAgo, fiveDaysAgo)
|
||||
require.NoError(t, err)
|
||||
_, err = iam.ListUsersPage(ctx, []byte("some hash"), 1, filterFunc, tok, repo, []string{org.GetPublicId()})
|
||||
require.ErrorContains(t, err, "token did not have a pagination token component")
|
||||
})
|
||||
t.Run("nil repo", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
filterFunc := func(_ context.Context, r *iam.User) (bool, error) {
|
||||
return true, nil
|
||||
}
|
||||
tok, err := listtoken.NewPagination(ctx, fiveDaysAgo, resource.User, []byte("some hash"), "some-id", fiveDaysAgo)
|
||||
require.NoError(t, err)
|
||||
_, err = iam.ListUsersPage(ctx, []byte("some hash"), 1, filterFunc, tok, nil, []string{org.GetPublicId()})
|
||||
require.ErrorContains(t, err, "missing repo")
|
||||
})
|
||||
t.Run("missing scope ids", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
filterFunc := func(_ context.Context, r *iam.User) (bool, error) {
|
||||
return true, nil
|
||||
}
|
||||
tok, err := listtoken.NewPagination(ctx, fiveDaysAgo, resource.User, []byte("some hash"), "some-id", fiveDaysAgo)
|
||||
require.NoError(t, err)
|
||||
_, err = iam.ListUsersPage(ctx, []byte("some hash"), 1, filterFunc, tok, repo, nil)
|
||||
require.ErrorContains(t, err, "missing scope ids")
|
||||
})
|
||||
t.Run("wrong token resource type", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
filterFunc := func(_ context.Context, r *iam.User) (bool, error) {
|
||||
return true, nil
|
||||
}
|
||||
tok, err := listtoken.NewPagination(ctx, fiveDaysAgo, resource.Target, []byte("some hash"), "some-id", fiveDaysAgo)
|
||||
require.NoError(t, err)
|
||||
_, err = iam.ListUsersPage(ctx, []byte("some hash"), 1, filterFunc, tok, repo, []string{"global"})
|
||||
require.ErrorContains(t, err, "token did not have a user resource type")
|
||||
})
|
||||
})
|
||||
t.Run("ListRefresh validation", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
t.Run("missing grants hash", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
filterFunc := func(_ context.Context, r *iam.User) (bool, error) {
|
||||
return true, nil
|
||||
}
|
||||
tok, err := listtoken.NewStartRefresh(ctx, fiveDaysAgo, resource.User, []byte("some hash"), fiveDaysAgo, fiveDaysAgo)
|
||||
require.NoError(t, err)
|
||||
_, err = iam.ListUsersRefresh(ctx, nil, 1, filterFunc, tok, repo, []string{org.GetPublicId()})
|
||||
require.ErrorContains(t, err, "missing grants hash")
|
||||
})
|
||||
t.Run("zero page size", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
filterFunc := func(_ context.Context, r *iam.User) (bool, error) {
|
||||
return true, nil
|
||||
}
|
||||
tok, err := listtoken.NewStartRefresh(ctx, fiveDaysAgo, resource.User, []byte("some hash"), fiveDaysAgo, fiveDaysAgo)
|
||||
require.NoError(t, err)
|
||||
_, err = iam.ListUsersRefresh(ctx, []byte("some hash"), 0, filterFunc, tok, repo, []string{org.GetPublicId()})
|
||||
require.ErrorContains(t, err, "page size must be at least 1")
|
||||
})
|
||||
t.Run("negative page size", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
filterFunc := func(_ context.Context, r *iam.User) (bool, error) {
|
||||
return true, nil
|
||||
}
|
||||
tok, err := listtoken.NewStartRefresh(ctx, fiveDaysAgo, resource.User, []byte("some hash"), fiveDaysAgo, fiveDaysAgo)
|
||||
require.NoError(t, err)
|
||||
_, err = iam.ListUsersRefresh(ctx, []byte("some hash"), -1, filterFunc, tok, repo, []string{org.GetPublicId()})
|
||||
require.ErrorContains(t, err, "page size must be at least 1")
|
||||
})
|
||||
t.Run("nil filter func", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
tok, err := listtoken.NewStartRefresh(ctx, fiveDaysAgo, resource.User, []byte("some hash"), fiveDaysAgo, fiveDaysAgo)
|
||||
require.NoError(t, err)
|
||||
_, err = iam.ListUsersRefresh(ctx, []byte("some hash"), 1, nil, tok, repo, []string{org.GetPublicId()})
|
||||
require.ErrorContains(t, err, "missing filter item callback")
|
||||
})
|
||||
t.Run("nil token", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
filterFunc := func(_ context.Context, r *iam.User) (bool, error) {
|
||||
return true, nil
|
||||
}
|
||||
_, err = iam.ListUsersRefresh(ctx, []byte("some hash"), 1, filterFunc, nil, repo, []string{org.GetPublicId()})
|
||||
require.ErrorContains(t, err, "missing token")
|
||||
})
|
||||
t.Run("wrong token type", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
filterFunc := func(_ context.Context, r *iam.User) (bool, error) {
|
||||
return true, nil
|
||||
}
|
||||
tok, err := listtoken.NewPagination(ctx, fiveDaysAgo, resource.User, []byte("some hash"), "some-id", fiveDaysAgo)
|
||||
require.NoError(t, err)
|
||||
_, err = iam.ListUsersRefresh(ctx, []byte("some hash"), 1, filterFunc, tok, repo, []string{org.GetPublicId()})
|
||||
require.ErrorContains(t, err, "token did not have a start-refresh token component")
|
||||
})
|
||||
t.Run("nil repo", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
filterFunc := func(_ context.Context, r *iam.User) (bool, error) {
|
||||
return true, nil
|
||||
}
|
||||
tok, err := listtoken.NewStartRefresh(ctx, fiveDaysAgo, resource.User, []byte("some hash"), fiveDaysAgo, fiveDaysAgo)
|
||||
require.NoError(t, err)
|
||||
_, err = iam.ListUsersRefresh(ctx, []byte("some hash"), 1, filterFunc, tok, nil, []string{org.GetPublicId()})
|
||||
require.ErrorContains(t, err, "missing repo")
|
||||
})
|
||||
t.Run("missing scope ids", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
filterFunc := func(_ context.Context, r *iam.User) (bool, error) {
|
||||
return true, nil
|
||||
}
|
||||
tok, err := listtoken.NewStartRefresh(ctx, fiveDaysAgo, resource.User, []byte("some hash"), fiveDaysAgo, fiveDaysAgo)
|
||||
require.NoError(t, err)
|
||||
_, err = iam.ListUsersRefresh(ctx, []byte("some hash"), 1, filterFunc, tok, repo, nil)
|
||||
require.ErrorContains(t, err, "missing scope ids")
|
||||
})
|
||||
t.Run("wrong token resource type", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
filterFunc := func(_ context.Context, r *iam.User) (bool, error) {
|
||||
return true, nil
|
||||
}
|
||||
tok, err := listtoken.NewStartRefresh(ctx, fiveDaysAgo, resource.Target, []byte("some hash"), fiveDaysAgo, fiveDaysAgo)
|
||||
require.NoError(t, err)
|
||||
_, err = iam.ListUsersRefresh(ctx, []byte("some hash"), 1, filterFunc, tok, repo, []string{"global"})
|
||||
require.ErrorContains(t, err, "token did not have a user resource type")
|
||||
})
|
||||
})
|
||||
t.Run("ListRefreshPage validation", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
t.Run("missing grants hash", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
filterFunc := func(_ context.Context, r *iam.User) (bool, error) {
|
||||
return true, nil
|
||||
}
|
||||
tok, err := listtoken.NewRefresh(ctx, fiveDaysAgo, resource.User, []byte("some hash"), fiveDaysAgo, fiveDaysAgo, fiveDaysAgo, "some other id", fiveDaysAgo)
|
||||
require.NoError(t, err)
|
||||
_, err = iam.ListUsersRefreshPage(ctx, nil, 1, filterFunc, tok, repo, []string{org.GetPublicId()})
|
||||
require.ErrorContains(t, err, "missing grants hash")
|
||||
})
|
||||
t.Run("zero page size", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
filterFunc := func(_ context.Context, r *iam.User) (bool, error) {
|
||||
return true, nil
|
||||
}
|
||||
tok, err := listtoken.NewRefresh(ctx, fiveDaysAgo, resource.User, []byte("some hash"), fiveDaysAgo, fiveDaysAgo, fiveDaysAgo, "some other id", fiveDaysAgo)
|
||||
require.NoError(t, err)
|
||||
_, err = iam.ListUsersRefreshPage(ctx, []byte("some hash"), 0, filterFunc, tok, repo, []string{org.GetPublicId()})
|
||||
require.ErrorContains(t, err, "page size must be at least 1")
|
||||
})
|
||||
t.Run("negative page size", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
filterFunc := func(_ context.Context, r *iam.User) (bool, error) {
|
||||
return true, nil
|
||||
}
|
||||
tok, err := listtoken.NewRefresh(ctx, fiveDaysAgo, resource.User, []byte("some hash"), fiveDaysAgo, fiveDaysAgo, fiveDaysAgo, "some other id", fiveDaysAgo)
|
||||
require.NoError(t, err)
|
||||
_, err = iam.ListUsersRefreshPage(ctx, []byte("some hash"), -1, filterFunc, tok, repo, []string{org.GetPublicId()})
|
||||
require.ErrorContains(t, err, "page size must be at least 1")
|
||||
})
|
||||
t.Run("nil filter func", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
tok, err := listtoken.NewRefresh(ctx, fiveDaysAgo, resource.User, []byte("some hash"), fiveDaysAgo, fiveDaysAgo, fiveDaysAgo, "some other id", fiveDaysAgo)
|
||||
require.NoError(t, err)
|
||||
_, err = iam.ListUsersRefreshPage(ctx, []byte("some hash"), 1, nil, tok, repo, []string{org.GetPublicId()})
|
||||
require.ErrorContains(t, err, "missing filter item callback")
|
||||
})
|
||||
t.Run("nil token", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
filterFunc := func(_ context.Context, r *iam.User) (bool, error) {
|
||||
return true, nil
|
||||
}
|
||||
_, err = iam.ListUsersRefreshPage(ctx, []byte("some hash"), 1, filterFunc, nil, repo, []string{org.GetPublicId()})
|
||||
require.ErrorContains(t, err, "missing token")
|
||||
})
|
||||
t.Run("wrong token type", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
filterFunc := func(_ context.Context, r *iam.User) (bool, error) {
|
||||
return true, nil
|
||||
}
|
||||
tok, err := listtoken.NewPagination(ctx, fiveDaysAgo, resource.User, []byte("some hash"), "some-id", fiveDaysAgo)
|
||||
require.NoError(t, err)
|
||||
_, err = iam.ListUsersRefreshPage(ctx, []byte("some hash"), 1, filterFunc, tok, repo, []string{org.GetPublicId()})
|
||||
require.ErrorContains(t, err, "token did not have a refresh token component")
|
||||
})
|
||||
t.Run("nil repo", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
filterFunc := func(_ context.Context, r *iam.User) (bool, error) {
|
||||
return true, nil
|
||||
}
|
||||
tok, err := listtoken.NewRefresh(ctx, fiveDaysAgo, resource.User, []byte("some hash"), fiveDaysAgo, fiveDaysAgo, fiveDaysAgo, "some other id", fiveDaysAgo)
|
||||
require.NoError(t, err)
|
||||
_, err = iam.ListUsersRefreshPage(ctx, []byte("some hash"), 1, filterFunc, tok, nil, []string{org.GetPublicId()})
|
||||
require.ErrorContains(t, err, "missing repo")
|
||||
})
|
||||
t.Run("missing scope ids", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
filterFunc := func(_ context.Context, r *iam.User) (bool, error) {
|
||||
return true, nil
|
||||
}
|
||||
tok, err := listtoken.NewRefresh(ctx, fiveDaysAgo, resource.User, []byte("some hash"), fiveDaysAgo, fiveDaysAgo, fiveDaysAgo, "some other id", fiveDaysAgo)
|
||||
require.NoError(t, err)
|
||||
_, err = iam.ListUsersRefreshPage(ctx, []byte("some hash"), 1, filterFunc, tok, repo, nil)
|
||||
require.ErrorContains(t, err, "missing scope ids")
|
||||
})
|
||||
t.Run("wrong token resource type", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
filterFunc := func(_ context.Context, r *iam.User) (bool, error) {
|
||||
return true, nil
|
||||
}
|
||||
tok, err := listtoken.NewRefresh(ctx, fiveDaysAgo, resource.Target, []byte("some hash"), fiveDaysAgo, fiveDaysAgo, fiveDaysAgo, "some other id", fiveDaysAgo)
|
||||
require.NoError(t, err)
|
||||
_, err = iam.ListUsersRefreshPage(ctx, []byte("some hash"), 1, filterFunc, tok, repo, []string{"global"})
|
||||
require.ErrorContains(t, err, "token did not have a user resource type")
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("simple pagination", func(t *testing.T) {
|
||||
filterFunc := func(_ context.Context, r *iam.User) (bool, error) {
|
||||
return true, nil
|
||||
}
|
||||
resp, err := iam.ListUsers(ctx, []byte("some hash"), 1, filterFunc, repo, []string{org.GetPublicId(), "global"})
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, resp.ListToken)
|
||||
require.Equal(t, resp.ListToken.GrantsHash, []byte("some hash"))
|
||||
require.False(t, resp.CompleteListing)
|
||||
require.Equal(t, resp.EstimatedItemCount, len(allResources))
|
||||
require.Empty(t, resp.DeletedIds)
|
||||
require.Len(t, resp.Items, 1)
|
||||
require.Empty(t, cmp.Diff(resp.Items[0], allResources[0], cmpIgnoreUnexportedOpts))
|
||||
|
||||
resp2, err := iam.ListUsersPage(ctx, []byte("some hash"), 1, filterFunc, resp.ListToken, repo, []string{org.GetPublicId(), "global"})
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, resp2.ListToken.GrantsHash, []byte("some hash"))
|
||||
require.False(t, resp2.CompleteListing)
|
||||
require.Equal(t, resp2.EstimatedItemCount, 8)
|
||||
require.Empty(t, resp2.DeletedIds)
|
||||
require.Len(t, resp2.Items, 1)
|
||||
require.Empty(t, cmp.Diff(resp2.Items[0], allResources[1], cmpIgnoreUnexportedOpts))
|
||||
|
||||
resp3, err := iam.ListUsersPage(ctx, []byte("some hash"), 1, filterFunc, resp2.ListToken, repo, []string{org.GetPublicId(), "global"})
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, resp3.ListToken.GrantsHash, []byte("some hash"))
|
||||
require.False(t, resp3.CompleteListing)
|
||||
require.Equal(t, resp3.EstimatedItemCount, 8)
|
||||
require.Empty(t, resp3.DeletedIds)
|
||||
require.Len(t, resp3.Items, 1)
|
||||
require.Empty(t, cmp.Diff(resp3.Items[0], allResources[2], cmpIgnoreUnexportedOpts))
|
||||
|
||||
resp4, err := iam.ListUsersPage(ctx, []byte("some hash"), 1, filterFunc, resp3.ListToken, repo, []string{org.GetPublicId(), "global"})
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, resp4.ListToken.GrantsHash, []byte("some hash"))
|
||||
require.False(t, resp4.CompleteListing)
|
||||
require.Equal(t, resp4.EstimatedItemCount, 8)
|
||||
require.Empty(t, resp4.DeletedIds)
|
||||
require.Len(t, resp4.Items, 1)
|
||||
require.Empty(t, cmp.Diff(resp4.Items[0], allResources[3], cmpIgnoreUnexportedOpts))
|
||||
|
||||
resp5, err := iam.ListUsersPage(ctx, []byte("some hash"), 1, filterFunc, resp4.ListToken, repo, []string{org.GetPublicId(), "global"})
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, resp5.ListToken.GrantsHash, []byte("some hash"))
|
||||
require.False(t, resp5.CompleteListing)
|
||||
require.Equal(t, resp5.EstimatedItemCount, 8)
|
||||
require.Empty(t, resp5.DeletedIds)
|
||||
require.Len(t, resp5.Items, 1)
|
||||
require.Empty(t, cmp.Diff(resp5.Items[0], allResources[4], cmpIgnoreUnexportedOpts))
|
||||
|
||||
resp6, err := iam.ListUsersPage(ctx, []byte("some hash"), 1, filterFunc, resp5.ListToken, repo, []string{org.GetPublicId(), "global"})
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, resp6.ListToken.GrantsHash, []byte("some hash"))
|
||||
require.False(t, resp6.CompleteListing)
|
||||
require.Equal(t, resp6.EstimatedItemCount, 8)
|
||||
require.Empty(t, resp6.DeletedIds)
|
||||
require.Len(t, resp6.Items, 1)
|
||||
require.Empty(t, cmp.Diff(resp6.Items[0], allResources[5], cmpIgnoreUnexportedOpts))
|
||||
|
||||
resp7, err := iam.ListUsersPage(ctx, []byte("some hash"), 1, filterFunc, resp6.ListToken, repo, []string{org.GetPublicId(), "global"})
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, resp7.ListToken.GrantsHash, []byte("some hash"))
|
||||
require.False(t, resp7.CompleteListing)
|
||||
require.Equal(t, resp7.EstimatedItemCount, 8)
|
||||
require.Empty(t, resp7.DeletedIds)
|
||||
require.Len(t, resp7.Items, 1)
|
||||
require.Empty(t, cmp.Diff(resp7.Items[0], allResources[6], cmpIgnoreUnexportedOpts))
|
||||
|
||||
resp8, err := iam.ListUsersPage(ctx, []byte("some hash"), 1, filterFunc, resp7.ListToken, repo, []string{org.GetPublicId(), "global"})
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, resp8.ListToken.GrantsHash, []byte("some hash"))
|
||||
require.True(t, resp8.CompleteListing)
|
||||
require.Equal(t, resp8.EstimatedItemCount, 8)
|
||||
require.Empty(t, resp8.DeletedIds)
|
||||
require.Len(t, resp8.Items, 1)
|
||||
require.Empty(t, cmp.Diff(resp8.Items[0], allResources[7], cmpIgnoreUnexportedOpts))
|
||||
|
||||
// Finished initial pagination phase, request refresh
|
||||
// Expect no results.
|
||||
resp9, err := iam.ListUsersRefresh(ctx, []byte("some hash"), 1, filterFunc, resp8.ListToken, repo, []string{org.GetPublicId(), "global"})
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, resp9.ListToken.GrantsHash, []byte("some hash"))
|
||||
require.True(t, resp9.CompleteListing)
|
||||
require.Equal(t, resp9.EstimatedItemCount, 8)
|
||||
require.Empty(t, resp9.DeletedIds)
|
||||
require.Empty(t, resp9.Items)
|
||||
|
||||
// Create some new users
|
||||
newR1 := iam.TestUser(t, iamRepo, org.GetPublicId())
|
||||
newR2 := iam.TestUser(t, iamRepo, org.GetPublicId())
|
||||
t.Cleanup(func() {
|
||||
_, err = repo.DeleteUser(ctx, newR1.GetPublicId())
|
||||
require.NoError(t, err)
|
||||
_, err = repo.DeleteUser(ctx, newR2.GetPublicId())
|
||||
require.NoError(t, err)
|
||||
// Run analyze to update count estimate
|
||||
_, err = sqlDB.ExecContext(ctx, "analyze")
|
||||
require.NoError(t, err)
|
||||
})
|
||||
// Run analyze to update count estimate
|
||||
_, err = sqlDB.ExecContext(ctx, "analyze")
|
||||
require.NoError(t, err)
|
||||
|
||||
// Refresh again, should get newR2
|
||||
resp10, err := iam.ListUsersRefresh(ctx, []byte("some hash"), 1, filterFunc, resp9.ListToken, repo, []string{org.GetPublicId(), "global"})
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, resp10.ListToken.GrantsHash, []byte("some hash"))
|
||||
require.False(t, resp10.CompleteListing)
|
||||
require.Equal(t, resp10.EstimatedItemCount, 10)
|
||||
require.Empty(t, resp10.DeletedIds)
|
||||
require.Len(t, resp10.Items, 1)
|
||||
require.Empty(t, cmp.Diff(resp10.Items[0], newR2, cmpIgnoreUnexportedOpts))
|
||||
|
||||
// Refresh again, should get newR1
|
||||
resp11, err := iam.ListUsersRefreshPage(ctx, []byte("some hash"), 1, filterFunc, resp10.ListToken, repo, []string{org.GetPublicId(), "global"})
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, resp11.ListToken.GrantsHash, []byte("some hash"))
|
||||
require.True(t, resp11.CompleteListing)
|
||||
require.Equal(t, resp11.EstimatedItemCount, 10)
|
||||
require.Empty(t, resp11.DeletedIds)
|
||||
require.Len(t, resp11.Items, 1)
|
||||
require.Empty(t, cmp.Diff(resp11.Items[0], newR1, cmpIgnoreUnexportedOpts))
|
||||
|
||||
// Refresh again, should get no results
|
||||
resp12, err := iam.ListUsersRefresh(ctx, []byte("some hash"), 1, filterFunc, resp11.ListToken, repo, []string{org.GetPublicId(), "global"})
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, resp12.ListToken.GrantsHash, []byte("some hash"))
|
||||
require.True(t, resp12.CompleteListing)
|
||||
require.Equal(t, resp12.EstimatedItemCount, 10)
|
||||
require.Empty(t, resp12.DeletedIds)
|
||||
require.Empty(t, resp12.Items)
|
||||
})
|
||||
|
||||
t.Run("simple pagination with aggressive filtering", func(t *testing.T) {
|
||||
filterFunc := func(_ context.Context, r *iam.User) (bool, error) {
|
||||
return r.GetPublicId() == allResources[1].GetPublicId() ||
|
||||
r.GetPublicId() == allResources[len(allResources)-1].GetPublicId(), nil
|
||||
}
|
||||
resp, err := iam.ListUsers(ctx, []byte("some hash"), 1, filterFunc, repo, []string{org.GetPublicId(), "global"})
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, resp.ListToken)
|
||||
require.Equal(t, resp.ListToken.GrantsHash, []byte("some hash"))
|
||||
require.False(t, resp.CompleteListing)
|
||||
require.Equal(t, resp.EstimatedItemCount, 8)
|
||||
require.Empty(t, resp.DeletedIds)
|
||||
require.Len(t, resp.Items, 1)
|
||||
require.Empty(t, cmp.Diff(resp.Items[0], allResources[1], cmpIgnoreUnexportedOpts))
|
||||
|
||||
resp2, err := iam.ListUsersPage(ctx, []byte("some hash"), 1, filterFunc, resp.ListToken, repo, []string{org.GetPublicId(), "global"})
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, resp2.ListToken)
|
||||
require.Equal(t, resp2.ListToken.GrantsHash, []byte("some hash"))
|
||||
require.True(t, resp2.CompleteListing)
|
||||
require.Equal(t, resp2.EstimatedItemCount, 8)
|
||||
require.Empty(t, resp2.DeletedIds)
|
||||
require.Len(t, resp2.Items, 1)
|
||||
require.Empty(t, cmp.Diff(resp2.Items[0], allResources[len(allResources)-1], cmpIgnoreUnexportedOpts))
|
||||
|
||||
// request a refresh, nothing should be returned
|
||||
resp3, err := iam.ListUsersRefresh(ctx, []byte("some hash"), 1, filterFunc, resp.ListToken, repo, []string{org.GetPublicId(), "global"})
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, resp3.ListToken.GrantsHash, []byte("some hash"))
|
||||
require.True(t, resp3.CompleteListing)
|
||||
require.Equal(t, resp3.EstimatedItemCount, 8)
|
||||
require.Empty(t, resp3.DeletedIds)
|
||||
require.Empty(t, resp3.Items)
|
||||
|
||||
// Create some new tokens
|
||||
newR1 := iam.TestUser(t, iamRepo, org.GetPublicId())
|
||||
newR2 := iam.TestUser(t, iamRepo, org.GetPublicId())
|
||||
newR3 := iam.TestUser(t, iamRepo, org.GetPublicId())
|
||||
newR4 := iam.TestUser(t, iamRepo, org.GetPublicId())
|
||||
// Run analyze to update count estimate
|
||||
_, err = sqlDB.ExecContext(ctx, "analyze")
|
||||
require.NoError(t, err)
|
||||
t.Cleanup(func() {
|
||||
_, err = repo.DeleteUser(ctx, newR1.GetPublicId())
|
||||
require.NoError(t, err)
|
||||
_, err = repo.DeleteUser(ctx, newR2.GetPublicId())
|
||||
require.NoError(t, err)
|
||||
_, err = repo.DeleteUser(ctx, newR3.GetPublicId())
|
||||
require.NoError(t, err)
|
||||
_, err = repo.DeleteUser(ctx, newR4.GetPublicId())
|
||||
require.NoError(t, err)
|
||||
// Run analyze to update count estimate
|
||||
_, err = sqlDB.ExecContext(ctx, "analyze")
|
||||
require.NoError(t, err)
|
||||
})
|
||||
|
||||
filterFunc = func(_ context.Context, r *iam.User) (bool, error) {
|
||||
return r.GetPublicId() == newR3.GetPublicId() ||
|
||||
r.GetPublicId() == newR1.GetPublicId(), nil
|
||||
}
|
||||
// Refresh again, should get newR3
|
||||
resp4, err := iam.ListUsersRefresh(ctx, []byte("some hash"), 1, filterFunc, resp3.ListToken, repo, []string{org.GetPublicId(), "global"})
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, resp4.ListToken.GrantsHash, []byte("some hash"))
|
||||
require.False(t, resp4.CompleteListing)
|
||||
require.Equal(t, resp4.EstimatedItemCount, 12)
|
||||
require.Empty(t, resp4.DeletedIds)
|
||||
require.Len(t, resp4.Items, 1)
|
||||
require.Empty(t, cmp.Diff(resp4.Items[0], newR3, cmpIgnoreUnexportedOpts))
|
||||
|
||||
// Refresh again, should get newR1
|
||||
resp5, err := iam.ListUsersRefreshPage(ctx, []byte("some hash"), 1, filterFunc, resp4.ListToken, repo, []string{org.GetPublicId(), "global"})
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, resp5.ListToken.GrantsHash, []byte("some hash"))
|
||||
require.True(t, resp5.CompleteListing)
|
||||
require.Equal(t, resp5.EstimatedItemCount, 12)
|
||||
require.Empty(t, resp5.DeletedIds)
|
||||
require.Len(t, resp5.Items, 1)
|
||||
require.Empty(t, cmp.Diff(resp5.Items[0], newR1, cmpIgnoreUnexportedOpts))
|
||||
})
|
||||
|
||||
t.Run("simple pagination with deletion", func(t *testing.T) {
|
||||
filterFunc := func(_ context.Context, r *iam.User) (bool, error) {
|
||||
return true, nil
|
||||
}
|
||||
deletedUserId := allResources[0].GetPublicId()
|
||||
_, err := repo.DeleteUser(ctx, deletedUserId)
|
||||
require.NoError(t, err)
|
||||
allResources = allResources[1:]
|
||||
|
||||
// Run analyze to update count estimate
|
||||
_, err = sqlDB.ExecContext(ctx, "analyze")
|
||||
require.NoError(t, err)
|
||||
|
||||
resp, err := iam.ListUsers(ctx, []byte("some hash"), 1, filterFunc, repo, []string{org.GetPublicId(), "global"})
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, resp.ListToken)
|
||||
require.Equal(t, resp.ListToken.GrantsHash, []byte("some hash"))
|
||||
require.False(t, resp.CompleteListing)
|
||||
require.Equal(t, resp.EstimatedItemCount, 7)
|
||||
require.Empty(t, resp.DeletedIds)
|
||||
require.Len(t, resp.Items, 1)
|
||||
require.Empty(t, cmp.Diff(resp.Items[0], allResources[0], cmpIgnoreUnexportedOpts))
|
||||
|
||||
// request remaining results
|
||||
resp2, err := iam.ListUsersPage(ctx, []byte("some hash"), 6, filterFunc, resp.ListToken, repo, []string{org.GetPublicId(), "global"})
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, resp2.ListToken.GrantsHash, []byte("some hash"))
|
||||
require.True(t, resp2.CompleteListing)
|
||||
require.Equal(t, resp2.EstimatedItemCount, 7)
|
||||
require.Empty(t, resp2.DeletedIds)
|
||||
require.Len(t, resp2.Items, 6)
|
||||
require.Empty(t, cmp.Diff(resp2.Items, allResources[1:], cmpIgnoreUnexportedOpts))
|
||||
|
||||
deletedUserId = allResources[0].GetPublicId()
|
||||
_, err = repo.DeleteUser(ctx, deletedUserId)
|
||||
require.NoError(t, err)
|
||||
allResources = allResources[1:]
|
||||
|
||||
// Run analyze to update count estimate
|
||||
_, err = sqlDB.ExecContext(ctx, "analyze")
|
||||
require.NoError(t, err)
|
||||
|
||||
// request a refresh, nothing should be returned except the deleted id
|
||||
resp3, err := iam.ListUsersRefresh(ctx, []byte("some hash"), 1, filterFunc, resp2.ListToken, repo, []string{org.GetPublicId(), "global"})
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, resp3.ListToken.GrantsHash, []byte("some hash"))
|
||||
require.True(t, resp3.CompleteListing)
|
||||
require.Equal(t, resp3.EstimatedItemCount, 6)
|
||||
require.Contains(t, resp3.DeletedIds, deletedUserId)
|
||||
require.Empty(t, resp3.Items)
|
||||
})
|
||||
}
|
||||
@@ -68,3 +68,59 @@ func ListRolesPage(
|
||||
|
||||
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)
|
||||
}
|
||||
@@ -74,3 +74,64 @@ func ListRolesRefresh(
|
||||
|
||||
return pagination.ListRefresh(ctx, grantsHash, pageSize, filterItemFn, listItemsFn, repo.estimatedRoleCount, listDeletedIdsFn, tok)
|
||||
}
|
||||
|
||||
// ListUsersRefresh 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 update time descending (most recently updated first).
|
||||
// Users may contain items that were already returned during the initial
|
||||
// pagination phase. It also returns a list of any users deleted since the
|
||||
// start of the initial pagination phase or last response.
|
||||
func ListUsersRefresh(
|
||||
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.ListUsersRefresh"
|
||||
|
||||
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")
|
||||
}
|
||||
rt, ok := tok.Subtype.(*listtoken.StartRefreshToken)
|
||||
if !ok {
|
||||
return nil, errors.New(ctx, errors.InvalidParameter, op, "token did not have a start-refresh 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))
|
||||
}
|
||||
// Add the database read timeout to account for any creations missed due to concurrent
|
||||
// transactions in the initial pagination phase.
|
||||
return repo.listUsersRefresh(ctx, rt.PreviousPhaseUpperBound.Add(-globals.RefreshReadLookbackDuration), withScopeIds, opts...)
|
||||
}
|
||||
listDeletedIdsFn := func(ctx context.Context, since time.Time) ([]string, time.Time, error) {
|
||||
// Add the database read timeout to account for any deletions missed due to concurrent
|
||||
// transactions in previous requests.
|
||||
return repo.listUserDeletedIds(ctx, since.Add(-globals.RefreshReadLookbackDuration))
|
||||
}
|
||||
|
||||
return pagination.ListRefresh(ctx, grantsHash, pageSize, filterItemFn, listItemsFn, repo.estimatedUserCount, listDeletedIdsFn, tok)
|
||||
}
|
||||
@@ -81,3 +81,71 @@ func ListRolesRefreshPage(
|
||||
|
||||
return pagination.ListRefreshPage(ctx, grantsHash, pageSize, filterItemFn, listItemsFn, repo.estimatedRoleCount, listDeletedIdsFn, tok)
|
||||
}
|
||||
|
||||
// ListUsersRefreshPage 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 update time descending (most recently updated first).
|
||||
// Users may contain items that were already returned during the initial
|
||||
// pagination phase. It also returns a list of any users deleted since the
|
||||
// last response.
|
||||
func ListUsersRefreshPage(
|
||||
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.ListUsersRefreshPage"
|
||||
|
||||
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")
|
||||
}
|
||||
rt, ok := tok.Subtype.(*listtoken.RefreshToken)
|
||||
if !ok {
|
||||
return nil, errors.New(ctx, errors.InvalidParameter, op, "token did not have a refresh 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))
|
||||
}
|
||||
// Add the database read timeout to account for any creations missed due to concurrent
|
||||
// transactions in the original list pagination phase.
|
||||
return repo.listUsersRefresh(ctx, rt.PhaseLowerBound.Add(-globals.RefreshReadLookbackDuration), withScopeIds, opts...)
|
||||
}
|
||||
|
||||
listDeletedIdsFn := func(ctx context.Context, since time.Time) ([]string, time.Time, error) {
|
||||
// Add the database read timeout to account for any deletes missed due to concurrent
|
||||
// transactions in the original list pagination phase.
|
||||
return repo.listUserDeletedIds(ctx, since.Add(-globals.RefreshReadLookbackDuration))
|
||||
}
|
||||
|
||||
return pagination.ListRefreshPage(ctx, grantsHash, pageSize, filterItemFn, listItemsFn, repo.estimatedUserCount, listDeletedIdsFn, tok)
|
||||
}
|
||||
@@ -177,6 +177,10 @@ func TestUser(t testing.TB, repo *Repository, scopeId string, opt ...Option) *Us
|
||||
if len(opts.withAccountIds) > 0 {
|
||||
_, err := repo.AddUserAccounts(ctx, user.PublicId, user.Version, opts.withAccountIds)
|
||||
require.NoError(err)
|
||||
// now that we have updated user accounts, we need to re-fetch the user
|
||||
// to get the updated version and update time
|
||||
user, err = repo.lookupUser(ctx, user.GetPublicId())
|
||||
require.NoError(err)
|
||||
}
|
||||
return user
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"context"
|
||||
|
||||
"github.com/hashicorp/boundary/internal/db"
|
||||
"github.com/hashicorp/boundary/internal/db/timestamp"
|
||||
"github.com/hashicorp/boundary/internal/errors"
|
||||
"github.com/hashicorp/boundary/internal/iam/store"
|
||||
"github.com/hashicorp/boundary/internal/types/action"
|
||||
@@ -119,6 +120,13 @@ type userAccountInfo struct {
|
||||
tableName string `gorm:"-"`
|
||||
}
|
||||
|
||||
// allocUserAccountInfo will allocate an empty userAccountInfo
|
||||
func allocUserAccountInfo() *userAccountInfo {
|
||||
return &userAccountInfo{
|
||||
User: &store.User{},
|
||||
}
|
||||
}
|
||||
|
||||
func (u *userAccountInfo) shallowConversion() *User {
|
||||
return &User{
|
||||
User: u.User,
|
||||
@@ -143,3 +151,13 @@ func (u *userAccountInfo) SetTableName(n string) {
|
||||
u.tableName = n
|
||||
}
|
||||
}
|
||||
|
||||
type deletedUser struct {
|
||||
PublicId string `gorm:"primary_key"`
|
||||
DeleteTime *timestamp.Timestamp
|
||||
}
|
||||
|
||||
// TableName returns the tablename to override the default gorm table name
|
||||
func (u *deletedUser) TableName() string {
|
||||
return "iam_user_deleted"
|
||||
}
|
||||
@@ -129,10 +129,47 @@ message ListUsersRequest {
|
||||
string scope_id = 1; // @gotags: `class:"public" eventstream:"observation"`
|
||||
bool recursive = 20 [json_name = "recursive"]; // @gotags: `class:"public" eventstream:"observation"`
|
||||
string filter = 30 [json_name = "filter"]; // @gotags: `class:"sensitive"`
|
||||
|
||||
// An opaque token used to continue an existing iteration or
|
||||
// request updated items. If not specified, pagination
|
||||
// will start from the beginning.
|
||||
string list_token = 40 [json_name = "list_token"]; // @gotags: `class:"public"`
|
||||
|
||||
// The maximum size of a page in this iteration.
|
||||
// If unset, the default page size configured will be used.
|
||||
// If the page_size is greater than the default page configured,
|
||||
// an error will be returned.
|
||||
uint32 page_size = 50 [json_name = "page_size"]; // @gotags: `class:"public"`
|
||||
}
|
||||
|
||||
message ListUsersResponse {
|
||||
repeated resources.users.v1.User items = 1;
|
||||
|
||||
// The type of response, either "delta" or "complete".
|
||||
// Delta signifies that this is part of a paginated result
|
||||
// or an update to a previously completed pagination.
|
||||
// Complete signifies that it is the last page.
|
||||
string response_type = 2 [json_name = "response_type"]; // @gotags: `class:"public"`
|
||||
|
||||
// An opaque token used to continue an existing pagination or
|
||||
// request updated items. Use this token in the next list request
|
||||
// to request the next page.
|
||||
string list_token = 3 [json_name = "list_token"]; // @gotags: `class:"public"`
|
||||
|
||||
// The name of the field which the items are sorted by.
|
||||
string sort_by = 4 [json_name = "sort_by"]; // @gotags: `class:"public"`
|
||||
|
||||
// The direction of the sort, either "asc" or "desc".
|
||||
string sort_dir = 5 [json_name = "sort_dir"]; // @gotags: `class:"public"`
|
||||
|
||||
// A list of item IDs that have been removed since they were returned
|
||||
// as part of a pagination. They should be dropped from any client cache.
|
||||
// This may contain items that are not known to the cache, if they were
|
||||
// created and deleted between listings.
|
||||
repeated string removed_ids = 6 [json_name = "removed_ids"]; // @gotags: `class:"public"`
|
||||
|
||||
// An estimate at the total items available. This may change during pagination.
|
||||
uint32 est_item_count = 7 [json_name = "est_item_count"]; // @gotags: `class:"public"`
|
||||
}
|
||||
|
||||
message CreateUserRequest {
|
||||
|
||||
Reference in new issue
Block a user