diff --git a/internal/daemon/controller/handler.go b/internal/daemon/controller/handler.go index c71e082e0..0d97f326e 100644 --- a/internal/daemon/controller/handler.go +++ b/internal/daemon/controller/handler.go @@ -165,7 +165,7 @@ func (c *Controller) registerGrpcServices(s *grpc.Server) error { services.RegisterHostServiceServer(s, hs) } if _, ok := currentServices[services.AccountService_ServiceDesc.ServiceName]; !ok { - accts, err := accounts.NewService(c.baseContext, c.PasswordAuthRepoFn, c.OidcRepoFn, c.LdapRepoFn) + accts, err := accounts.NewService(c.baseContext, c.PasswordAuthRepoFn, c.OidcRepoFn, c.LdapRepoFn, c.conf.RawConfig.Controller.MaxPageSize) if err != nil { return fmt.Errorf("failed to create account handler service: %w", err) } diff --git a/internal/daemon/controller/handlers/accounts/account_service.go b/internal/daemon/controller/handlers/accounts/account_service.go index 07f5a533a..18e2c5932 100644 --- a/internal/daemon/controller/handlers/accounts/account_service.go +++ b/internal/daemon/controller/handlers/accounts/account_service.go @@ -22,6 +22,8 @@ import ( "github.com/hashicorp/boundary/internal/daemon/controller/handlers" "github.com/hashicorp/boundary/internal/errors" pbs "github.com/hashicorp/boundary/internal/gen/controller/api/services" + "github.com/hashicorp/boundary/internal/listtoken" + "github.com/hashicorp/boundary/internal/pagination" "github.com/hashicorp/boundary/internal/perms" "github.com/hashicorp/boundary/internal/requests" "github.com/hashicorp/boundary/internal/types/action" @@ -118,15 +120,16 @@ func init() { type Service struct { pbs.UnsafeAccountServiceServer - pwRepoFn common.PasswordAuthRepoFactory - oidcRepoFn common.OidcAuthRepoFactory - ldapRepoFn common.LdapAuthRepoFactory + pwRepoFn common.PasswordAuthRepoFactory + oidcRepoFn common.OidcAuthRepoFactory + ldapRepoFn common.LdapAuthRepoFactory + maxPageSize uint } var _ pbs.AccountServiceServer = (*Service)(nil) // NewService returns a account service which handles account related requests to boundary. -func NewService(ctx context.Context, pwRepo common.PasswordAuthRepoFactory, oidcRepo common.OidcAuthRepoFactory, ldapRepo common.LdapAuthRepoFactory) (Service, error) { +func NewService(ctx context.Context, pwRepo common.PasswordAuthRepoFactory, oidcRepo common.OidcAuthRepoFactory, ldapRepo common.LdapAuthRepoFactory, maxPageSize uint) (Service, error) { const op = "accounts.NewService" switch { case pwRepo == nil: @@ -136,70 +139,226 @@ func NewService(ctx context.Context, pwRepo common.PasswordAuthRepoFactory, oidc case ldapRepo == nil: return Service{}, errors.New(ctx, errors.InvalidParameter, op, "missing ldap repository") } - return Service{pwRepoFn: pwRepo, oidcRepoFn: oidcRepo, ldapRepoFn: ldapRepo}, nil + if maxPageSize == 0 { + maxPageSize = uint(globals.DefaultMaxPageSize) + } + return Service{pwRepoFn: pwRepo, oidcRepoFn: oidcRepo, ldapRepoFn: ldapRepo, maxPageSize: maxPageSize}, nil } // ListAccounts implements the interface pbs.AccountServiceServer. func (s Service) ListAccounts(ctx context.Context, req *pbs.ListAccountsRequest) (*pbs.ListAccountsResponse, error) { + const op = "accounts.(Service).ListAccounts" if err := validateListRequest(ctx, req); err != nil { - return nil, err + return nil, errors.Wrap(ctx, err, op) } _, authResults := s.parentAndAuthResult(ctx, req.GetAuthMethodId(), action.List) if authResults.Error != nil { return nil, authResults.Error } - ul, err := s.listFromRepo(ctx, req.GetAuthMethodId()) + + authMethodId := req.GetAuthMethodId() + pageSize := int(s.maxPageSize) + // Use the requested page size only if it is smaller than + // the configured max. + if req.GetPageSize() != 0 && uint(req.GetPageSize()) < s.maxPageSize { + pageSize = int(req.GetPageSize()) + } + + var filterItemFn func(ctx context.Context, item auth.Account) (bool, error) + switch { + case req.GetFilter() != "": + // Only use a filter if we need to + filter, err := handlers.NewFilter(ctx, req.GetFilter()) + if err != nil { + return nil, err + } + // TODO: replace the need for this function with some way to convert the `filter` + // to a domain type. This would allow filtering to happen in the domain, and we could + // remove this callback altogether. + filterItemFn = func(ctx context.Context, item auth.Account) (bool, error) { + outputOpts, ok := newOutputOpts(ctx, item, authMethodId, authResults) + if !ok { + return false, nil + } + pbItem, err := toProto(ctx, item, outputOpts...) + if err != nil { + return false, err + } + + filterable, err := subtypes.Filterable(ctx, pbItem) + if err != nil { + return false, err + } + return filter.Match(filterable), nil + } + default: + filterItemFn = func(ctx context.Context, item auth.Account) (bool, error) { + return true, nil + } + } + + grantsHash, err := authResults.GrantsHash(ctx) if err != nil { return nil, err } - if len(ul) == 0 { - return &pbs.ListAccountsResponse{}, nil + + var listResp *pagination.ListResponse[auth.Account] + var sortBy string + switch globals.ResourceInfoFromPrefix(authMethodId).Subtype { + case ldap.Subtype: + repo, err := s.ldapRepoFn() + if err != nil { + return nil, errors.Wrap(ctx, err, op) + } + if req.GetListToken() == "" { + sortBy = "created_time" + listResp, err = ldap.ListAccounts(ctx, grantsHash, pageSize, filterItemFn, repo, authMethodId) + if err != nil { + return nil, err + } + } else { + listToken, err := handlers.ParseListToken(ctx, req.GetListToken(), resource.Account, grantsHash) + if err != nil { + return nil, err + } + switch st := listToken.Subtype.(type) { + case *listtoken.PaginationToken: + sortBy = "created_time" + listResp, err = ldap.ListAccountsPage(ctx, grantsHash, pageSize, filterItemFn, listToken, repo, authMethodId) + if err != nil { + return nil, err + } + case *listtoken.StartRefreshToken: + sortBy = "updated_time" + listResp, err = ldap.ListAccountsRefresh(ctx, grantsHash, pageSize, filterItemFn, listToken, repo, authMethodId) + if err != nil { + return nil, err + } + case *listtoken.RefreshToken: + sortBy = "updated_time" + listResp, err = ldap.ListAccountsRefreshPage(ctx, grantsHash, pageSize, filterItemFn, listToken, repo, authMethodId) + if err != nil { + return nil, err + } + default: + return nil, handlers.ApiErrorWithCodeAndMessage(codes.InvalidArgument, "unexpected list token subtype: %T", st) + } + } + case oidc.Subtype: + repo, err := s.oidcRepoFn() + if err != nil { + return nil, errors.Wrap(ctx, err, op) + } + if req.GetListToken() == "" { + sortBy = "created_time" + listResp, err = oidc.ListAccounts(ctx, grantsHash, pageSize, filterItemFn, repo, authMethodId) + if err != nil { + return nil, err + } + } else { + listToken, err := handlers.ParseListToken(ctx, req.GetListToken(), resource.Account, grantsHash) + if err != nil { + return nil, err + } + switch st := listToken.Subtype.(type) { + case *listtoken.PaginationToken: + sortBy = "created_time" + listResp, err = oidc.ListAccountsPage(ctx, grantsHash, pageSize, filterItemFn, listToken, repo, authMethodId) + if err != nil { + return nil, err + } + case *listtoken.StartRefreshToken: + sortBy = "updated_time" + listResp, err = oidc.ListAccountsRefresh(ctx, grantsHash, pageSize, filterItemFn, listToken, repo, authMethodId) + if err != nil { + return nil, err + } + case *listtoken.RefreshToken: + sortBy = "updated_time" + listResp, err = oidc.ListAccountsRefreshPage(ctx, grantsHash, pageSize, filterItemFn, listToken, repo, authMethodId) + if err != nil { + return nil, err + } + default: + return nil, handlers.ApiErrorWithCodeAndMessage(codes.InvalidArgument, "unexpected list token subtype: %T", st) + } + } + case password.Subtype: + repo, err := s.pwRepoFn() + if err != nil { + return nil, errors.Wrap(ctx, err, op) + } + if req.GetListToken() == "" { + sortBy = "created_time" + listResp, err = password.ListAccounts(ctx, grantsHash, pageSize, filterItemFn, repo, authMethodId) + if err != nil { + return nil, err + } + } else { + listToken, err := handlers.ParseListToken(ctx, req.GetListToken(), resource.Account, grantsHash) + if err != nil { + return nil, err + } + switch st := listToken.Subtype.(type) { + case *listtoken.PaginationToken: + sortBy = "created_time" + listResp, err = password.ListAccountsPage(ctx, grantsHash, pageSize, filterItemFn, listToken, repo, authMethodId) + if err != nil { + return nil, err + } + case *listtoken.StartRefreshToken: + sortBy = "updated_time" + listResp, err = password.ListAccountsRefresh(ctx, grantsHash, pageSize, filterItemFn, listToken, repo, authMethodId) + if err != nil { + return nil, err + } + case *listtoken.RefreshToken: + sortBy = "updated_time" + listResp, err = password.ListAccountsRefreshPage(ctx, grantsHash, pageSize, filterItemFn, listToken, repo, authMethodId) + if err != nil { + return nil, err + } + default: + return nil, handlers.ApiErrorWithCodeAndMessage(codes.InvalidArgument, "unexpected list token subtype: %T", st) + } + } + default: + return nil, handlers.ApiErrorWithCodeAndMessage(codes.InvalidArgument, "unknown auth method type for id: %s", authMethodId) } - filter, err := handlers.NewFilter(ctx, req.GetFilter()) - if err != nil { - return nil, err - } - finalItems := make([]*pb.Account, 0, len(ul)) - - res := perms.Resource{ - ScopeId: authResults.Scope.Id, - Type: resource.Account, - Pin: req.GetAuthMethodId(), - } - for _, acct := range ul { - res.Id = acct.GetPublicId() - authorizedActions := authResults.FetchActionSetForId(ctx, acct.GetPublicId(), IdActions[globals.ResourceInfoFromPrefix(acct.GetPublicId()).Subtype], requestauth.WithResource(&res)).Strings() - if len(authorizedActions) == 0 { + finalItems := make([]*pb.Account, 0, len(listResp.Items)) + for _, item := range listResp.Items { + outputOpts, ok := newOutputOpts(ctx, item, authMethodId, authResults) + if !ok { continue } - - outputFields := authResults.FetchOutputFields(res, action.List).SelfOrDefaults(authResults.UserId) - outputOpts := make([]handlers.Option, 0, 3) - outputOpts = append(outputOpts, handlers.WithOutputFields(outputFields)) - if outputFields.Has(globals.ScopeField) { - outputOpts = append(outputOpts, handlers.WithScope(authResults.Scope)) - } - if outputFields.Has(globals.AuthorizedActionsField) { - outputOpts = append(outputOpts, handlers.WithAuthorizedActions(authorizedActions)) + pbItem, err := toProto(ctx, item, outputOpts...) + if err != nil { + continue } + finalItems = append(finalItems, pbItem) + } + respType := "delta" + if listResp.CompleteListing { + respType = "complete" + } + resp := &pbs.ListAccountsResponse{ + Items: finalItems, + EstItemCount: uint32(listResp.EstimatedItemCount), + RemovedIds: listResp.DeletedIds, + ResponseType: respType, + SortBy: sortBy, + SortDir: "desc", + } - item, err := toProto(ctx, acct, outputOpts...) + if listResp.ListToken != nil { + resp.ListToken, err = handlers.MarshalListToken(ctx, listResp.ListToken, pbs.ResourceType_RESOURCE_TYPE_ACCOUNT) if err != nil { return nil, err } - - // This comes last so that we can use item fields in the filter after - // the allowed fields are populated above - filterable, err := subtypes.Filterable(ctx, item) - if err != nil { - return nil, err - } - if filter.Match(filterable) { - finalItems = append(finalItems, item) - } } - return &pbs.ListAccountsResponse{Items: finalItems}, nil + + return resp, nil } // GetAccount implements the interface pbs.AccountServiceServer. @@ -821,51 +980,6 @@ func (s Service) deleteFromRepo(ctx context.Context, scopeId, id string) (bool, return rows > 0, nil } -func (s Service) listFromRepo(ctx context.Context, authMethodId string) ([]auth.Account, error) { - const op = "accounts.(Service).listFromRepo" - - var outUl []auth.Account - switch globals.ResourceInfoFromPrefix(authMethodId).Subtype { - case password.Subtype: - pwRepo, err := s.pwRepoFn() - if err != nil { - return nil, errors.Wrap(ctx, err, op) - } - pwl, err := pwRepo.ListAccounts(ctx, authMethodId, password.WithLimit(-1)) - if err != nil { - return nil, errors.Wrap(ctx, err, op) - } - for _, a := range pwl { - outUl = append(outUl, a) - } - case oidc.Subtype: - oidcRepo, err := s.oidcRepoFn() - if err != nil { - return nil, errors.Wrap(ctx, err, op) - } - oidcl, err := oidcRepo.ListAccounts(ctx, authMethodId, oidc.WithLimit(-1)) - if err != nil { - return nil, errors.Wrap(ctx, err, op) - } - for _, a := range oidcl { - outUl = append(outUl, a) - } - case ldap.Subtype: - ldapRepo, err := s.ldapRepoFn() - if err != nil { - return nil, errors.Wrap(ctx, err, op) - } - ldapList, err := ldapRepo.ListAccounts(ctx, authMethodId, ldap.WithLimit(ctx, -1)) - if err != nil { - return nil, errors.Wrap(ctx, err, op) - } - for _, a := range ldapList { - outUl = append(outUl, a) - } - } - return outUl, nil -} - func (s Service) changePasswordInRepo(ctx context.Context, scopeId, id string, version uint32, currentPassword, newPassword string) (auth.Account, error) { const op = "account.(Service).changePasswordInRepo" repo, err := s.pwRepoFn() @@ -1371,3 +1485,27 @@ func validateSetPasswordRequest(ctx context.Context, req *pbs.SetPasswordRequest } return nil } + +func newOutputOpts(ctx context.Context, item auth.Account, authMethodId string, authResults requestauth.VerifyResults) ([]handlers.Option, bool) { + res := perms.Resource{ + ScopeId: authResults.Scope.Id, + Type: resource.Account, + Pin: authMethodId, + } + res.Id = item.GetPublicId() + authorizedActions := authResults.FetchActionSetForId(ctx, item.GetPublicId(), IdActions[globals.ResourceInfoFromPrefix(item.GetPublicId()).Subtype], requestauth.WithResource(&res)).Strings() + if len(authorizedActions) == 0 { + return nil, false + } + + outputFields := authResults.FetchOutputFields(res, action.List).SelfOrDefaults(authResults.UserId) + outputOpts := make([]handlers.Option, 0, 3) + outputOpts = append(outputOpts, handlers.WithOutputFields(outputFields)) + if outputFields.Has(globals.ScopeField) { + outputOpts = append(outputOpts, handlers.WithScope(authResults.Scope)) + } + if outputFields.Has(globals.AuthorizedActionsField) { + outputOpts = append(outputOpts, handlers.WithAuthorizedActions(authorizedActions)) + } + return outputOpts, true +} diff --git a/internal/daemon/controller/handlers/accounts/account_service_test.go b/internal/daemon/controller/handlers/accounts/account_service_test.go index 0cfd2ab5c..d52d81ec6 100644 --- a/internal/daemon/controller/handlers/accounts/account_service_test.go +++ b/internal/daemon/controller/handlers/accounts/account_service_test.go @@ -7,7 +7,7 @@ import ( "context" "errors" "fmt" - "sort" + "slices" "strings" "testing" @@ -15,16 +15,23 @@ import ( "github.com/google/go-cmp/cmp/cmpopts" "github.com/hashicorp/boundary/globals" "github.com/hashicorp/boundary/internal/auth/ldap" + ldapstore "github.com/hashicorp/boundary/internal/auth/ldap/store" "github.com/hashicorp/boundary/internal/auth/oidc" + oidcstore "github.com/hashicorp/boundary/internal/auth/oidc/store" "github.com/hashicorp/boundary/internal/auth/password" + pwstore "github.com/hashicorp/boundary/internal/auth/password/store" + "github.com/hashicorp/boundary/internal/authtoken" requestauth "github.com/hashicorp/boundary/internal/daemon/controller/auth" "github.com/hashicorp/boundary/internal/daemon/controller/common" "github.com/hashicorp/boundary/internal/daemon/controller/handlers" "github.com/hashicorp/boundary/internal/daemon/controller/handlers/accounts" "github.com/hashicorp/boundary/internal/db" pbs "github.com/hashicorp/boundary/internal/gen/controller/api/services" + authpb "github.com/hashicorp/boundary/internal/gen/controller/auth" "github.com/hashicorp/boundary/internal/iam" "github.com/hashicorp/boundary/internal/kms" + "github.com/hashicorp/boundary/internal/requests" + "github.com/hashicorp/boundary/internal/server" "github.com/hashicorp/boundary/internal/types/action" "github.com/hashicorp/boundary/internal/types/scope" pb "github.com/hashicorp/boundary/sdk/pbs/controller/api/resources/accounts" @@ -107,7 +114,7 @@ func TestNewService(t *testing.T) { } for _, tc := range cases { t.Run(tc.name, func(t *testing.T) { - _, err := accounts.NewService(ctx, tc.pwRepo, tc.oidcRepo, ldapRepoFn) + _, err := accounts.NewService(ctx, tc.pwRepo, tc.oidcRepo, ldapRepoFn, 1000) if tc.wantErr { assert.Error(t, err) } else { @@ -136,7 +143,7 @@ func TestGet(t *testing.T) { return ldap.NewRepository(ctx, rw, rw, kmsCache) } - s, err := accounts.NewService(ctx, pwRepoFn, oidcRepoFn, ldapRepoFn) + s, err := accounts.NewService(ctx, pwRepoFn, oidcRepoFn, ldapRepoFn, 1000) require.NoError(t, err, "Couldn't create new auth token service.") org, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrap)) @@ -153,7 +160,7 @@ func TestGet(t *testing.T) { Version: 1, Type: password.Subtype.String(), Attrs: &pb.Account_PasswordAccountAttributes{ - &pb.PasswordAccountAttributes{LoginName: pwA.GetLoginName()}, + PasswordAccountAttributes: &pb.PasswordAccountAttributes{LoginName: pwA.GetLoginName()}, }, AuthorizedActions: pwAuthorizedActions, } @@ -180,7 +187,7 @@ func TestGet(t *testing.T) { Version: 1, Type: oidc.Subtype.String(), Attrs: &pb.Account_OidcAccountAttributes{ - &pb.OidcAccountAttributes{ + OidcAccountAttributes: &pb.OidcAccountAttributes{ Issuer: oidcAm.GetIssuer(), Subject: "test-subject", }, @@ -314,6 +321,8 @@ func TestListPassword(t *testing.T) { ctx := context.TODO() conn, _ := db.TestSetup(t, "postgres") rw := db.New(conn) + sqlDb, err := conn.SqlDB(ctx) + require.NoError(t, err) wrap := db.TestWrapper(t) kms := kms.TestKms(t, conn, wrap) pwRepoFn := func() (*password.Repository, error) { @@ -350,6 +359,8 @@ func TestListPassword(t *testing.T) { }) } + slices.Reverse(wantSomeAccounts) + var wantOtherAccounts []*pb.Account for _, aa := range password.TestMultipleAccounts(t, conn, amOtherAccounts.GetPublicId(), 3) { wantOtherAccounts = append(wantOtherAccounts, &pb.Account{ @@ -361,12 +372,17 @@ func TestListPassword(t *testing.T) { Version: 1, Type: "password", Attrs: &pb.Account_PasswordAccountAttributes{ - &pb.PasswordAccountAttributes{LoginName: aa.GetLoginName()}, + PasswordAccountAttributes: &pb.PasswordAccountAttributes{LoginName: aa.GetLoginName()}, }, AuthorizedActions: pwAuthorizedActions, }) } + slices.Reverse(wantOtherAccounts) + + _, err = sqlDb.ExecContext(ctx, "analyze") + require.NoError(t, err) + cases := []struct { name string req *pbs.ListAccountsRequest @@ -377,17 +393,34 @@ func TestListPassword(t *testing.T) { { name: "List Some Accounts", req: &pbs.ListAccountsRequest{AuthMethodId: amSomeAccounts.GetPublicId()}, - res: &pbs.ListAccountsResponse{Items: wantSomeAccounts}, + res: &pbs.ListAccountsResponse{ + Items: wantSomeAccounts, + ResponseType: "complete", + SortBy: "created_time", + SortDir: "desc", + EstItemCount: uint32(len(wantSomeAccounts)), + }, }, { name: "List Other Accounts", req: &pbs.ListAccountsRequest{AuthMethodId: amOtherAccounts.GetPublicId()}, - res: &pbs.ListAccountsResponse{Items: wantOtherAccounts}, + res: &pbs.ListAccountsResponse{ + Items: wantOtherAccounts, + ResponseType: "complete", + SortBy: "created_time", + SortDir: "desc", + EstItemCount: uint32(len(wantOtherAccounts)), + }, }, { name: "List No Accounts", req: &pbs.ListAccountsRequest{AuthMethodId: amNoAccounts.GetPublicId()}, - res: &pbs.ListAccountsResponse{}, + res: &pbs.ListAccountsResponse{ + ResponseType: "complete", + SortBy: "created_time", + SortDir: "desc", + EstItemCount: 0, + }, }, { name: "Unfound Auth Method", @@ -400,7 +433,13 @@ func TestListPassword(t *testing.T) { AuthMethodId: amSomeAccounts.GetPublicId(), Filter: fmt.Sprintf(`"/item/attributes/login_name"==%q`, wantSomeAccounts[1].GetPasswordAccountAttributes().LoginName), }, - res: &pbs.ListAccountsResponse{Items: wantSomeAccounts[1:2]}, + res: &pbs.ListAccountsResponse{ + Items: wantSomeAccounts[1:2], + ResponseType: "complete", + SortBy: "created_time", + SortDir: "desc", + EstItemCount: 1, + }, skipAnon: true, }, { @@ -409,7 +448,11 @@ func TestListPassword(t *testing.T) { AuthMethodId: amSomeAccounts.GetPublicId(), Filter: `"/item/id"=="noaccountmatchesthis"`, }, - res: &pbs.ListAccountsResponse{}, + res: &pbs.ListAccountsResponse{ + ResponseType: "complete", + SortBy: "created_time", + SortDir: "desc", + }, }, { name: "Filter Bad Format", @@ -420,7 +463,7 @@ func TestListPassword(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 := accounts.NewService(ctx, pwRepoFn, oidcRepoFn, ldapRepoFn) + s, err := accounts.NewService(ctx, pwRepoFn, oidcRepoFn, ldapRepoFn, 1000) require.NoError(err, "Couldn't create new user service.") // Test non-anon first @@ -435,12 +478,12 @@ func TestListPassword(t *testing.T) { assert.Empty(cmp.Diff( got, tc.res, - protocmp.Transform(), - protocmp.SortRepeatedFields(got), cmpopts.SortSlices(func(a, b string) bool { return a < b }), - ), "ListAccounts() with scope %q got response %q, wanted %q", tc.req, got, tc.res) + protocmp.Transform(), + protocmp.IgnoreFields(&pbs.ListAccountsResponse{}, "list_token"), + )) // Now test with anon if tc.skipAnon { @@ -463,6 +506,8 @@ func TestListOidc(t *testing.T) { ctx := context.Background() conn, _ := db.TestSetup(t, "postgres") rw := db.New(conn) + sqlDb, err := conn.SqlDB(ctx) + require.NoError(t, err) wrap := db.TestWrapper(t) kmsCache := kms.TestKms(t, conn, wrap) pwRepoFn := func() (*password.Repository, error) { @@ -501,7 +546,7 @@ func TestListOidc(t *testing.T) { Version: 1, Type: oidc.Subtype.String(), Attrs: &pb.Account_OidcAccountAttributes{ - &pb.OidcAccountAttributes{ + OidcAccountAttributes: &pb.OidcAccountAttributes{ Issuer: amSomeAccounts.GetIssuer(), Subject: subId, }, @@ -510,6 +555,8 @@ func TestListOidc(t *testing.T) { }) } + slices.Reverse(wantSomeAccounts) + var wantOtherAccounts []*pb.Account for i := 0; i < 3; i++ { subId := fmt.Sprintf("test-subject%d", i) @@ -523,7 +570,7 @@ func TestListOidc(t *testing.T) { Version: 1, Type: oidc.Subtype.String(), Attrs: &pb.Account_OidcAccountAttributes{ - &pb.OidcAccountAttributes{ + OidcAccountAttributes: &pb.OidcAccountAttributes{ Issuer: amOtherAccounts.GetIssuer(), Subject: subId, }, @@ -532,6 +579,11 @@ func TestListOidc(t *testing.T) { }) } + slices.Reverse(wantOtherAccounts) + + _, err = sqlDb.ExecContext(ctx, "analyze") + require.NoError(t, err) + cases := []struct { name string req *pbs.ListAccountsRequest @@ -542,17 +594,33 @@ func TestListOidc(t *testing.T) { { name: "List Some Accounts", req: &pbs.ListAccountsRequest{AuthMethodId: amSomeAccounts.GetPublicId()}, - res: &pbs.ListAccountsResponse{Items: wantSomeAccounts}, + res: &pbs.ListAccountsResponse{ + Items: wantSomeAccounts, + ResponseType: "complete", + SortBy: "created_time", + SortDir: "desc", + EstItemCount: uint32(len(wantSomeAccounts)), + }, }, { name: "List Other Accounts", req: &pbs.ListAccountsRequest{AuthMethodId: amOtherAccounts.GetPublicId()}, - res: &pbs.ListAccountsResponse{Items: wantOtherAccounts}, + res: &pbs.ListAccountsResponse{ + Items: wantOtherAccounts, + ResponseType: "complete", + SortBy: "created_time", + SortDir: "desc", + EstItemCount: uint32(len(wantOtherAccounts)), + }, }, { name: "List No Accounts", req: &pbs.ListAccountsRequest{AuthMethodId: amNoAccounts.GetPublicId()}, - res: &pbs.ListAccountsResponse{}, + res: &pbs.ListAccountsResponse{ + ResponseType: "complete", + SortBy: "created_time", + SortDir: "desc", + }, }, { name: "Unfound Auth Method", @@ -565,7 +633,13 @@ func TestListOidc(t *testing.T) { AuthMethodId: amSomeAccounts.GetPublicId(), Filter: fmt.Sprintf(`"/item/attributes/subject"==%q`, wantSomeAccounts[1].GetOidcAccountAttributes().Subject), }, - res: &pbs.ListAccountsResponse{Items: wantSomeAccounts[1:2]}, + res: &pbs.ListAccountsResponse{ + Items: wantSomeAccounts[1:2], + ResponseType: "complete", + SortBy: "created_time", + SortDir: "desc", + EstItemCount: 1, + }, skipAnon: true, }, { @@ -574,7 +648,11 @@ func TestListOidc(t *testing.T) { AuthMethodId: amSomeAccounts.GetPublicId(), Filter: `"/item/id"=="noaccountmatchesthis"`, }, - res: &pbs.ListAccountsResponse{}, + res: &pbs.ListAccountsResponse{ + ResponseType: "complete", + SortBy: "created_time", + SortDir: "desc", + }, }, { name: "Filter Bad Format", @@ -585,7 +663,7 @@ func TestListOidc(t *testing.T) { for _, tc := range cases { t.Run(tc.name, func(t *testing.T) { assert, require := assert.New(t), require.New(t) - s, err := accounts.NewService(ctx, pwRepoFn, oidcRepoFn, ldapRepoFn) + s, err := accounts.NewService(ctx, pwRepoFn, oidcRepoFn, ldapRepoFn, 1000) require.NoError(err, "Couldn't create new user service.") got, gErr := s.ListAccounts(requestauth.DisabledAuthTestContext(iamRepoFn, o.GetPublicId()), tc.req) @@ -596,18 +674,15 @@ func TestListOidc(t *testing.T) { } else { require.NoError(gErr) } - sort.Slice(got.Items, func(i, j int) bool { - return strings.Compare(got.Items[i].GetOidcAccountAttributes().Subject, - got.Items[j].GetOidcAccountAttributes().Subject) < 0 - }) assert.Empty(cmp.Diff( got, tc.res, - protocmp.Transform(), cmpopts.SortSlices(func(a, b string) bool { return a < b }), - ), "ListAccounts() with scope %q got response %q, wanted %q", tc.req, got, tc.res) + protocmp.Transform(), + protocmp.IgnoreFields(&pbs.ListAccountsResponse{}, "list_token"), + )) // Now test with anon if tc.skipAnon { @@ -630,6 +705,8 @@ func TestListLdap(t *testing.T) { ctx := context.Background() conn, _ := db.TestSetup(t, "postgres") rw := db.New(conn) + sqlDb, err := conn.SqlDB(ctx) + require.NoError(t, err) wrap := db.TestWrapper(t) kmsCache := kms.TestKms(t, conn, wrap) pwRepoFn := func() (*password.Repository, error) { @@ -673,6 +750,8 @@ func TestListLdap(t *testing.T) { }) } + slices.Reverse(wantSomeAccounts) + var wantOtherAccounts []*pb.Account for i := 0; i < 3; i++ { loginName := fmt.Sprintf("test-login-name%d", i) @@ -694,6 +773,11 @@ func TestListLdap(t *testing.T) { }) } + slices.Reverse(wantOtherAccounts) + + _, err = sqlDb.ExecContext(ctx, "analyze") + require.NoError(t, err) + cases := []struct { name string req *pbs.ListAccountsRequest @@ -704,17 +788,33 @@ func TestListLdap(t *testing.T) { { name: "List Some Accounts", req: &pbs.ListAccountsRequest{AuthMethodId: amSomeAccounts.GetPublicId()}, - res: &pbs.ListAccountsResponse{Items: wantSomeAccounts}, + res: &pbs.ListAccountsResponse{ + Items: wantSomeAccounts, + ResponseType: "complete", + SortBy: "created_time", + SortDir: "desc", + EstItemCount: uint32(len(wantSomeAccounts)), + }, }, { name: "List Other Accounts", req: &pbs.ListAccountsRequest{AuthMethodId: amOtherAccounts.GetPublicId()}, - res: &pbs.ListAccountsResponse{Items: wantOtherAccounts}, + res: &pbs.ListAccountsResponse{ + Items: wantOtherAccounts, + ResponseType: "complete", + SortBy: "created_time", + SortDir: "desc", + EstItemCount: uint32(len(wantOtherAccounts)), + }, }, { name: "List No Accounts", req: &pbs.ListAccountsRequest{AuthMethodId: amNoAccounts.GetPublicId()}, - res: &pbs.ListAccountsResponse{}, + res: &pbs.ListAccountsResponse{ + ResponseType: "complete", + SortBy: "created_time", + SortDir: "desc", + }, }, { name: "Unfound Auth Method", @@ -727,7 +827,13 @@ func TestListLdap(t *testing.T) { AuthMethodId: amSomeAccounts.GetPublicId(), Filter: fmt.Sprintf(`"/item/attributes/login_name"==%q`, wantSomeAccounts[1].GetLdapAccountAttributes().LoginName), }, - res: &pbs.ListAccountsResponse{Items: wantSomeAccounts[1:2]}, + res: &pbs.ListAccountsResponse{ + Items: wantSomeAccounts[1:2], + ResponseType: "complete", + SortBy: "created_time", + SortDir: "desc", + EstItemCount: 1, + }, skipAnon: true, }, { @@ -736,7 +842,11 @@ func TestListLdap(t *testing.T) { AuthMethodId: amSomeAccounts.GetPublicId(), Filter: `"/item/id"=="noaccountmatchesthis"`, }, - res: &pbs.ListAccountsResponse{}, + res: &pbs.ListAccountsResponse{ + ResponseType: "complete", + SortBy: "created_time", + SortDir: "desc", + }, }, { name: "Filter Bad Format", @@ -747,7 +857,7 @@ func TestListLdap(t *testing.T) { for _, tc := range cases { t.Run(tc.name, func(t *testing.T) { assert, require := assert.New(t), require.New(t) - s, err := accounts.NewService(ctx, pwRepoFn, oidcRepoFn, ldapRepoFn) + s, err := accounts.NewService(ctx, pwRepoFn, oidcRepoFn, ldapRepoFn, 1000) require.NoError(err, "Couldn't create new user service.") got, gErr := s.ListAccounts(requestauth.DisabledAuthTestContext(iamRepoFn, o.GetPublicId()), tc.req) @@ -758,18 +868,15 @@ func TestListLdap(t *testing.T) { } else { require.NoError(gErr) } - sort.Slice(got.Items, func(i, j int) bool { - return strings.Compare(got.Items[i].GetLdapAccountAttributes().LoginName, - got.Items[j].GetLdapAccountAttributes().LoginName) < 0 - }) assert.Empty(cmp.Diff( got, tc.res, - protocmp.Transform(), cmpopts.SortSlices(func(a, b string) bool { return a < b }), - ), "ListAccounts() with scope %q got response %q, wanted %q", tc.req, got, tc.res) + protocmp.Transform(), + protocmp.IgnoreFields(&pbs.ListAccountsResponse{}, "list_token"), + )) // Now test with anon if tc.skipAnon { @@ -788,6 +895,983 @@ func TestListLdap(t *testing.T) { } } +func TestListPagination(t *testing.T) { + // Set database read timeout to avoid duplicates in response + oldReadTimeout := globals.RefreshReadLookbackDuration + globals.RefreshReadLookbackDuration = 0 + t.Cleanup(func() { + globals.RefreshReadLookbackDuration = oldReadTimeout + }) + + ctx := context.Background() + conn, _ := db.TestSetup(t, "postgres") + sqlDB, err := conn.SqlDB(ctx) + require.NoError(t, err) + rw := db.New(conn) + wrap := db.TestWrapper(t) + kmsCache := kms.TestKms(t, conn, wrap) + pwRepoFn := func() (*password.Repository, error) { + return password.NewRepository(ctx, rw, rw, kmsCache) + } + oidcRepoFn := func() (*oidc.Repository, error) { + return oidc.NewRepository(ctx, rw, rw, kmsCache) + } + iamRepoFn := func() (*iam.Repository, error) { + return iam.NewRepository(ctx, rw, rw, kmsCache) + } + ldapRepoFn := func() (*ldap.Repository, error) { + return ldap.NewRepository(ctx, rw, rw, kmsCache) + } + tokenRepoFn := func() (*authtoken.Repository, error) { + return authtoken.NewRepository(ctx, rw, rw, kmsCache) + } + serversRepoFn := func() (*server.Repository, error) { + return server.NewRepository(ctx, rw, rw, kmsCache) + } + + s, err := accounts.NewService(ctx, pwRepoFn, oidcRepoFn, ldapRepoFn, 1000) + require.NoError(t, err) + + iamRepo := iam.TestRepo(t, conn, wrap) + tokenRepo, _ := tokenRepoFn() + pwRepo, _ := pwRepoFn() + oidcRepo, _ := oidcRepoFn() + ldapRepo, _ := ldapRepoFn() + o, pwt := iam.TestScopes(t, iamRepo) + + t.Run("password", func(t *testing.T) { + authMethod := password.TestAuthMethods(t, conn, o.GetPublicId(), 1)[0] + + var accounts []*pb.Account + for _, aa := range password.TestMultipleAccounts(t, conn, authMethod.GetPublicId(), 9) { + accounts = append(accounts, &pb.Account{ + Id: aa.GetPublicId(), + AuthMethodId: aa.GetAuthMethodId(), + CreatedTime: aa.GetCreateTime().GetTimestamp(), + UpdatedTime: aa.GetUpdateTime().GetTimestamp(), + Scope: &scopepb.ScopeInfo{Id: o.GetPublicId(), Type: scope.Org.String(), ParentScopeId: scope.Global.String()}, + Version: 1, + Type: "password", + Attrs: &pb.Account_PasswordAccountAttributes{ + PasswordAccountAttributes: &pb.PasswordAccountAttributes{LoginName: aa.GetLoginName()}, + }, + AuthorizedActions: pwAuthorizedActions, + }) + } + + acct := password.TestAccount(t, conn, authMethod.GetPublicId(), "test-login-last") + u := iam.TestUser(t, iamRepo, o.GetPublicId(), iam.WithAccountIds(acct.PublicId)) + + privProjRole := iam.TestRole(t, conn, pwt.GetPublicId()) + iam.TestRoleGrant(t, conn, privProjRole.GetPublicId(), "id=*;type=*;actions=*") + iam.TestUserRole(t, conn, privProjRole.GetPublicId(), u.GetPublicId()) + privOrgRole := iam.TestRole(t, conn, o.GetPublicId()) + iam.TestRoleGrant(t, conn, privOrgRole.GetPublicId(), "id=*;type=*;actions=*") + iam.TestUserRole(t, conn, privOrgRole.GetPublicId(), u.GetPublicId()) + + accounts = append(accounts, &pb.Account{ + Id: acct.GetPublicId(), + AuthMethodId: acct.GetAuthMethodId(), + CreatedTime: acct.GetCreateTime().GetTimestamp(), + UpdatedTime: acct.GetUpdateTime().GetTimestamp(), + Scope: &scopepb.ScopeInfo{Id: o.GetPublicId(), Type: scope.Org.String(), ParentScopeId: scope.Global.String()}, + Version: 1, + Type: "password", + Attrs: &pb.Account_PasswordAccountAttributes{ + PasswordAccountAttributes: &pb.PasswordAccountAttributes{LoginName: acct.GetLoginName()}, + }, + AuthorizedActions: pwAuthorizedActions, + }) + + // Since we sort by created_time descending, we reverse the slice + slices.Reverse(accounts) + + at, _ := tokenRepo.CreateAuthToken(ctx, u, acct.GetPublicId()) + + requestInfo := authpb.RequestInfo{ + TokenFormat: uint32(requestauth.AuthTokenTypeBearer), + PublicId: at.GetPublicId(), + Token: at.GetToken(), + } + requestContext := context.WithValue(context.Background(), requests.ContextRequestInformationKey, &requests.RequestContext{}) + ctx = requestauth.NewVerifierContext(requestContext, iamRepoFn, tokenRepoFn, serversRepoFn, kmsCache, &requestInfo) + + req := &pbs.ListAccountsRequest{ + AuthMethodId: authMethod.GetPublicId(), + Filter: "", + ListToken: "", + PageSize: 2, + } + + // Run analyze in the DB to update the estimate tables + _, err = sqlDB.ExecContext(ctx, "analyze") + require.NoError(t, err) + + got, err := s.ListAccounts(ctx, req) + require.NoError(t, err) + require.Len(t, got.GetItems(), 2) + + // all comparisons will be done without list token + assert.Empty(t, + cmp.Diff( + got, + &pbs.ListAccountsResponse{ + Items: accounts[0:2], + ResponseType: "delta", + ListToken: "", + SortBy: "created_time", + SortDir: "desc", + RemovedIds: nil, + EstItemCount: 10, + }, + cmpopts.SortSlices(func(a, b string) bool { + return a < b + }), + protocmp.Transform(), + protocmp.IgnoreFields(&pbs.ListAccountsResponse{}, "list_token"), + ), + ) + + // second page + req.ListToken = got.ListToken + got, err = s.ListAccounts(ctx, req) + require.NoError(t, err) + require.Len(t, got.GetItems(), 2) + + assert.Empty(t, + cmp.Diff( + got, + &pbs.ListAccountsResponse{ + Items: accounts[2:4], + ResponseType: "delta", + ListToken: "", + SortBy: "created_time", + SortDir: "desc", + RemovedIds: nil, + EstItemCount: 10, + }, + cmpopts.SortSlices(func(a, b string) bool { + return a < b + }), + protocmp.Transform(), + protocmp.IgnoreFields(&pbs.ListAccountsResponse{}, "list_token"), + ), + ) + + // remainder of results + req.ListToken = got.ListToken + req.PageSize = 6 + got, err = s.ListAccounts(ctx, req) + require.NoError(t, err) + require.Len(t, got.GetItems(), 6) + + assert.Empty(t, + cmp.Diff( + got, + &pbs.ListAccountsResponse{ + Items: accounts[4:], + ResponseType: "complete", + ListToken: "", + SortBy: "created_time", + SortDir: "desc", + RemovedIds: nil, + EstItemCount: 10, + }, + cmpopts.SortSlices(func(a, b string) bool { + return a < b + }), + protocmp.Transform(), + protocmp.IgnoreFields(&pbs.ListAccountsResponse{}, "list_token"), + ), + ) + + // create another acct + aa := password.TestAccount(t, conn, authMethod.GetPublicId(), "test-login-new-last") + newAccount := &pb.Account{ + Id: aa.GetPublicId(), + AuthMethodId: aa.GetAuthMethodId(), + CreatedTime: aa.GetCreateTime().GetTimestamp(), + UpdatedTime: aa.GetUpdateTime().GetTimestamp(), + Scope: &scopepb.ScopeInfo{Id: o.GetPublicId(), Type: scope.Org.String(), ParentScopeId: scope.Global.String()}, + Version: 1, + Type: "password", + Attrs: &pb.Account_PasswordAccountAttributes{ + PasswordAccountAttributes: &pb.PasswordAccountAttributes{LoginName: aa.GetLoginName()}, + }, + AuthorizedActions: pwAuthorizedActions, + } + // Append new account to the front of the slice, since it's the most recently updated + accounts = append([]*pb.Account{newAccount}, accounts...) + + // delete different acct + _, err = pwRepo.DeleteAccount(ctx, o.GetPublicId(), accounts[len(accounts)-1].Id) + require.NoError(t, err) + deletedAccount := accounts[len(accounts)-1] + accounts = accounts[:len(accounts)-1] + + // update another acct + accounts[1].Name = wrapperspb.String("new-name") + accounts[1].Version = 2 + a := &password.Account{ + Account: &pwstore.Account{ + PublicId: accounts[1].Id, + AuthMethodId: accounts[1].AuthMethodId, + Name: accounts[1].Name.GetValue(), + }, + } + ua, _, err := pwRepo.UpdateAccount(ctx, o.GetPublicId(), a, 1, []string{"name"}) + require.NoError(t, err) + accounts[1].UpdatedTime = ua.GetUpdateTime().GetTimestamp() + accounts[1].Version = ua.GetVersion() + // Add to the front since it's most recently updated + accounts = append( + []*pb.Account{accounts[1]}, + append( + []*pb.Account{accounts[0]}, + accounts[2:]..., + )..., + ) + + // Run analyze to update postgres estimates + _, err = sqlDB.ExecContext(ctx, "analyze") + require.NoError(t, err) + + // request updated results + req.ListToken = got.ListToken + req.PageSize = 1 + got, err = s.ListAccounts(ctx, req) + require.NoError(t, err) + require.Len(t, got.GetItems(), 1) + assert.Empty(t, + cmp.Diff( + got, + &pbs.ListAccountsResponse{ + Items: []*pb.Account{accounts[0]}, + ResponseType: "delta", + ListToken: "", + SortBy: "updated_time", + SortDir: "desc", + RemovedIds: []string{deletedAccount.Id}, + EstItemCount: 10, + }, + cmpopts.SortSlices(func(a, b string) bool { + return a < b + }), + protocmp.Transform(), + protocmp.IgnoreFields(&pbs.ListAccountsResponse{}, "list_token"), + ), + ) + + // Get the next page + req.ListToken = got.ListToken + got, err = s.ListAccounts(ctx, req) + require.NoError(t, err) + require.Len(t, got.GetItems(), 1) + assert.Empty(t, + cmp.Diff( + got, + &pbs.ListAccountsResponse{ + Items: []*pb.Account{accounts[1]}, + ResponseType: "complete", + ListToken: "", + SortBy: "updated_time", + SortDir: "desc", + RemovedIds: nil, + EstItemCount: 10, + }, + cmpopts.SortSlices(func(a, b string) bool { + return a < b + }), + protocmp.Transform(), + protocmp.IgnoreFields(&pbs.ListAccountsResponse{}, "list_token"), + ), + ) + + // Request new page with filter requiring looping + // to fill the page. + req.ListToken = "" + req.Filter = fmt.Sprintf(`"/item/id"==%q or "/item/id"==%q`, accounts[len(accounts)-2].Id, accounts[len(accounts)-1].Id) + got, err = s.ListAccounts(ctx, req) + require.NoError(t, err) + require.Len(t, got.GetItems(), 1) + assert.Empty(t, + cmp.Diff( + got, + &pbs.ListAccountsResponse{ + Items: []*pb.Account{accounts[len(accounts)-2]}, + ResponseType: "delta", + ListToken: "", + SortBy: "created_time", + SortDir: "desc", + // Should be empty again + RemovedIds: nil, + EstItemCount: 10, + }, + cmpopts.SortSlices(func(a, b string) bool { + return a < b + }), + protocmp.Transform(), + protocmp.IgnoreFields(&pbs.ListAccountsResponse{}, "list_token"), + ), + ) + req.ListToken = got.ListToken + // Get the second page + got, err = s.ListAccounts(ctx, req) + require.NoError(t, err) + require.Len(t, got.GetItems(), 1) + assert.Empty(t, + cmp.Diff( + got, + &pbs.ListAccountsResponse{ + Items: []*pb.Account{accounts[len(accounts)-1]}, + ResponseType: "complete", + ListToken: "", + SortBy: "created_time", + SortDir: "desc", + RemovedIds: nil, + EstItemCount: 10, + }, + cmpopts.SortSlices(func(a, b string) bool { + return a < b + }), + protocmp.Transform(), + protocmp.IgnoreFields(&pbs.ListAccountsResponse{}, "list_token"), + ), + ) + }) + + t.Run("oidc", func(t *testing.T) { + o, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrap)) + databaseWrapper, err := kmsCache.GetWrapper(ctx, o.PublicId, kms.KeyPurposeDatabase) + require.NoError(t, err) + authMethod := oidc.TestAuthMethod(t, conn, databaseWrapper, o.PublicId, oidc.ActivePrivateState, "someAccounts", "fido", + oidc.WithIssuer(oidc.TestConvertToUrls(t, "https://www.someaccounts.com")[0]), oidc.WithSigningAlgs(oidc.RS256), oidc.WithApiUrl(oidc.TestConvertToUrls(t, "https://www.alice.com/callback")[0])) + + var accounts []*pb.Account + for i := 0; i < 9; i++ { + subId := fmt.Sprintf("test-subject%d", i) + aa := oidc.TestAccount(t, conn, authMethod, subId) + accounts = append(accounts, &pb.Account{ + Id: aa.GetPublicId(), + AuthMethodId: aa.GetAuthMethodId(), + CreatedTime: aa.GetCreateTime().GetTimestamp(), + UpdatedTime: aa.GetUpdateTime().GetTimestamp(), + Scope: &scopepb.ScopeInfo{Id: o.GetPublicId(), Type: scope.Org.String(), ParentScopeId: scope.Global.String()}, + Version: 1, + Type: oidc.Subtype.String(), + Attrs: &pb.Account_OidcAccountAttributes{ + OidcAccountAttributes: &pb.OidcAccountAttributes{ + Issuer: authMethod.GetIssuer(), + Subject: subId, + }, + }, + AuthorizedActions: oidcAuthorizedActions, + }) + } + + acct := oidc.TestAccount(t, conn, authMethod, "test-login-last") + u := iam.TestUser(t, iamRepo, o.GetPublicId(), iam.WithAccountIds(acct.PublicId)) + + privProjRole := iam.TestRole(t, conn, pwt.GetPublicId()) + iam.TestRoleGrant(t, conn, privProjRole.GetPublicId(), "id=*;type=*;actions=*") + iam.TestUserRole(t, conn, privProjRole.GetPublicId(), u.GetPublicId()) + privOrgRole := iam.TestRole(t, conn, o.GetPublicId()) + iam.TestRoleGrant(t, conn, privOrgRole.GetPublicId(), "id=*;type=*;actions=*") + iam.TestUserRole(t, conn, privOrgRole.GetPublicId(), u.GetPublicId()) + + accounts = append(accounts, &pb.Account{ + Id: acct.GetPublicId(), + AuthMethodId: acct.GetAuthMethodId(), + CreatedTime: acct.GetCreateTime().GetTimestamp(), + UpdatedTime: acct.GetUpdateTime().GetTimestamp(), + Scope: &scopepb.ScopeInfo{Id: o.GetPublicId(), Type: scope.Org.String(), ParentScopeId: scope.Global.String()}, + Version: 1, + Type: oidc.Subtype.String(), + Attrs: &pb.Account_OidcAccountAttributes{ + OidcAccountAttributes: &pb.OidcAccountAttributes{ + Issuer: authMethod.GetIssuer(), + Subject: "test-login-last", + }, + }, + AuthorizedActions: oidcAuthorizedActions, + }) + + // Since we sort by created_time descending, we reverse the slice + slices.Reverse(accounts) + + at, _ := tokenRepo.CreateAuthToken(ctx, u, acct.GetPublicId()) + + requestInfo := authpb.RequestInfo{ + TokenFormat: uint32(requestauth.AuthTokenTypeBearer), + PublicId: at.GetPublicId(), + Token: at.GetToken(), + } + requestContext := context.WithValue(context.Background(), requests.ContextRequestInformationKey, &requests.RequestContext{}) + ctx = requestauth.NewVerifierContext(requestContext, iamRepoFn, tokenRepoFn, serversRepoFn, kmsCache, &requestInfo) + + req := &pbs.ListAccountsRequest{ + AuthMethodId: authMethod.GetPublicId(), + Filter: "", + ListToken: "", + PageSize: 2, + } + + // Run analyze in the DB to update the estimate tables + _, err = sqlDB.ExecContext(ctx, "analyze") + require.NoError(t, err) + + got, err := s.ListAccounts(ctx, req) + require.NoError(t, err) + require.Len(t, got.GetItems(), 2) + + // all comparisons will be done without list token + assert.Empty(t, + cmp.Diff( + got, + &pbs.ListAccountsResponse{ + Items: accounts[0:2], + ResponseType: "delta", + ListToken: "", + SortBy: "created_time", + SortDir: "desc", + RemovedIds: nil, + EstItemCount: 10, + }, + cmpopts.SortSlices(func(a, b string) bool { + return a < b + }), + protocmp.Transform(), + protocmp.IgnoreFields(&pbs.ListAccountsResponse{}, "list_token"), + ), + ) + + // second page + req.ListToken = got.ListToken + got, err = s.ListAccounts(ctx, req) + require.NoError(t, err) + require.Len(t, got.GetItems(), 2) + + assert.Empty(t, + cmp.Diff( + got, + &pbs.ListAccountsResponse{ + Items: accounts[2:4], + ResponseType: "delta", + ListToken: "", + SortBy: "created_time", + SortDir: "desc", + RemovedIds: nil, + EstItemCount: 10, + }, + cmpopts.SortSlices(func(a, b string) bool { + return a < b + }), + protocmp.Transform(), + protocmp.IgnoreFields(&pbs.ListAccountsResponse{}, "list_token"), + ), + ) + + // remainder of results + req.ListToken = got.ListToken + req.PageSize = 6 + got, err = s.ListAccounts(ctx, req) + require.NoError(t, err) + require.Len(t, got.GetItems(), 6) + + assert.Empty(t, + cmp.Diff( + got, + &pbs.ListAccountsResponse{ + Items: accounts[4:], + ResponseType: "complete", + ListToken: "", + SortBy: "created_time", + SortDir: "desc", + RemovedIds: nil, + EstItemCount: 10, + }, + cmpopts.SortSlices(func(a, b string) bool { + return a < b + }), + protocmp.Transform(), + protocmp.IgnoreFields(&pbs.ListAccountsResponse{}, "list_token"), + ), + ) + + // create another acct + aa := oidc.TestAccount(t, conn, authMethod, "test-login-new-last") + newAccount := &pb.Account{ + Id: aa.GetPublicId(), + AuthMethodId: aa.GetAuthMethodId(), + CreatedTime: aa.GetCreateTime().GetTimestamp(), + UpdatedTime: aa.GetUpdateTime().GetTimestamp(), + Scope: &scopepb.ScopeInfo{Id: o.GetPublicId(), Type: scope.Org.String(), ParentScopeId: scope.Global.String()}, + Version: 1, + Type: oidc.Subtype.String(), + Attrs: &pb.Account_OidcAccountAttributes{ + OidcAccountAttributes: &pb.OidcAccountAttributes{ + Issuer: authMethod.GetIssuer(), + Subject: "test-login-new-last", + }, + }, + AuthorizedActions: oidcAuthorizedActions, + } + // Add to front since it's the latest updated + accounts = append([]*pb.Account{newAccount}, accounts...) + + // delete different acct + _, err = oidcRepo.DeleteAccount(ctx, o.GetPublicId(), accounts[len(accounts)-1].Id) + require.NoError(t, err) + deletedAccount := accounts[len(accounts)-1] + accounts = accounts[:len(accounts)-1] + + // update another acct + accounts[1].Name = wrapperspb.String("new-name") + accounts[1].Version = 2 + a := &oidc.Account{ + Account: &oidcstore.Account{ + PublicId: accounts[1].Id, + AuthMethodId: accounts[1].AuthMethodId, + Name: accounts[1].Name.GetValue(), + }, + } + ua, _, err := oidcRepo.UpdateAccount(ctx, o.GetPublicId(), a, 1, []string{"name"}) + require.NoError(t, err) + accounts[1].UpdatedTime = ua.GetUpdateTime().GetTimestamp() + accounts[1].Version = ua.GetVersion() + // Add to the front since it's most recently updated + accounts = append( + []*pb.Account{accounts[1]}, + append( + []*pb.Account{accounts[0]}, + accounts[2:]..., + )..., + ) + + // Run analyze to update postgres estimates + _, err = sqlDB.ExecContext(ctx, "analyze") + require.NoError(t, err) + + // request updated results + req.ListToken = got.ListToken + req.PageSize = 1 + got, err = s.ListAccounts(ctx, req) + require.NoError(t, err) + require.Len(t, got.GetItems(), 1) + assert.Empty(t, + cmp.Diff( + got, + &pbs.ListAccountsResponse{ + Items: []*pb.Account{accounts[0]}, + ResponseType: "delta", + ListToken: "", + SortBy: "updated_time", + SortDir: "desc", + RemovedIds: []string{deletedAccount.Id}, + EstItemCount: 10, + }, + cmpopts.SortSlices(func(a, b string) bool { + return a < b + }), + protocmp.Transform(), + protocmp.IgnoreFields(&pbs.ListAccountsResponse{}, "list_token"), + ), + ) + + // Get next page + req.ListToken = got.ListToken + got, err = s.ListAccounts(ctx, req) + require.NoError(t, err) + require.Len(t, got.GetItems(), 1) + assert.Empty(t, + cmp.Diff( + got, + &pbs.ListAccountsResponse{ + Items: []*pb.Account{accounts[1]}, + ResponseType: "complete", + ListToken: "", + SortBy: "updated_time", + SortDir: "desc", + RemovedIds: nil, + EstItemCount: 10, + }, + cmpopts.SortSlices(func(a, b string) bool { + return a < b + }), + protocmp.Transform(), + protocmp.IgnoreFields(&pbs.ListAccountsResponse{}, "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`, accounts[len(accounts)-2].Id, accounts[len(accounts)-1].Id) + got, err = s.ListAccounts(ctx, req) + require.NoError(t, err) + require.Len(t, got.GetItems(), 1) + assert.Empty(t, + cmp.Diff( + got, + &pbs.ListAccountsResponse{ + Items: []*pb.Account{accounts[len(accounts)-2]}, + ResponseType: "delta", + ListToken: "", + SortBy: "created_time", + SortDir: "desc", + // Should be empty again + RemovedIds: nil, + EstItemCount: 10, + }, + cmpopts.SortSlices(func(a, b string) bool { + return a < b + }), + protocmp.Transform(), + protocmp.IgnoreFields(&pbs.ListAccountsResponse{}, "list_token"), + ), + ) + req.ListToken = got.ListToken + // Get the second page + got, err = s.ListAccounts(ctx, req) + require.NoError(t, err) + require.Len(t, got.GetItems(), 1) + assert.Empty(t, + cmp.Diff( + got, + &pbs.ListAccountsResponse{ + Items: []*pb.Account{accounts[len(accounts)-1]}, + ResponseType: "complete", + ListToken: "", + SortBy: "created_time", + SortDir: "desc", + RemovedIds: nil, + EstItemCount: 10, + }, + cmpopts.SortSlices(func(a, b string) bool { + return a < b + }), + protocmp.Transform(), + protocmp.IgnoreFields(&pbs.ListAccountsResponse{}, "list_token"), + ), + ) + }) + + t.Run("ldap", func(t *testing.T) { + o, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrap)) + databaseWrapper, err := kmsCache.GetWrapper(ctx, o.PublicId, kms.KeyPurposeDatabase) + require.NoError(t, err) + authMethod := ldap.TestAuthMethod(t, conn, databaseWrapper, o.PublicId, []string{"ldaps://some-accounts"}) + + var accounts []*pb.Account + for i := 0; i < 9; i++ { + loginName := fmt.Sprintf("test-login-name%d", i) + aa := ldap.TestAccount(t, conn, authMethod, loginName) + accounts = append(accounts, &pb.Account{ + Id: aa.GetPublicId(), + AuthMethodId: aa.GetAuthMethodId(), + CreatedTime: aa.GetCreateTime().GetTimestamp(), + UpdatedTime: aa.GetUpdateTime().GetTimestamp(), + Scope: &scopepb.ScopeInfo{Id: o.GetPublicId(), Type: scope.Org.String(), ParentScopeId: scope.Global.String()}, + Version: 1, + Type: ldap.Subtype.String(), + Attrs: &pb.Account_LdapAccountAttributes{ + LdapAccountAttributes: &pb.LdapAccountAttributes{ + LoginName: loginName, + }, + }, + AuthorizedActions: ldapAuthorizedActions, + }) + } + + acct := ldap.TestAccount(t, conn, authMethod, "test-login-last") + u := iam.TestUser(t, iamRepo, o.GetPublicId(), iam.WithAccountIds(acct.PublicId)) + + privProjRole := iam.TestRole(t, conn, pwt.GetPublicId()) + iam.TestRoleGrant(t, conn, privProjRole.GetPublicId(), "id=*;type=*;actions=*") + iam.TestUserRole(t, conn, privProjRole.GetPublicId(), u.GetPublicId()) + privOrgRole := iam.TestRole(t, conn, o.GetPublicId()) + iam.TestRoleGrant(t, conn, privOrgRole.GetPublicId(), "id=*;type=*;actions=*") + iam.TestUserRole(t, conn, privOrgRole.GetPublicId(), u.GetPublicId()) + + accounts = append(accounts, &pb.Account{ + Id: acct.GetPublicId(), + AuthMethodId: acct.GetAuthMethodId(), + CreatedTime: acct.GetCreateTime().GetTimestamp(), + UpdatedTime: acct.GetUpdateTime().GetTimestamp(), + Scope: &scopepb.ScopeInfo{Id: o.GetPublicId(), Type: scope.Org.String(), ParentScopeId: scope.Global.String()}, + Version: 1, + Type: ldap.Subtype.String(), + Attrs: &pb.Account_LdapAccountAttributes{ + LdapAccountAttributes: &pb.LdapAccountAttributes{ + LoginName: "test-login-last", + }, + }, + AuthorizedActions: ldapAuthorizedActions, + }) + + // Since we sort by created_time descending, we reverse the slice + slices.Reverse(accounts) + + at, _ := tokenRepo.CreateAuthToken(ctx, u, acct.GetPublicId()) + + requestInfo := authpb.RequestInfo{ + TokenFormat: uint32(requestauth.AuthTokenTypeBearer), + PublicId: at.GetPublicId(), + Token: at.GetToken(), + } + requestContext := context.WithValue(context.Background(), requests.ContextRequestInformationKey, &requests.RequestContext{}) + ctx = requestauth.NewVerifierContext(requestContext, iamRepoFn, tokenRepoFn, serversRepoFn, kmsCache, &requestInfo) + + req := &pbs.ListAccountsRequest{ + AuthMethodId: authMethod.GetPublicId(), + Filter: "", + ListToken: "", + PageSize: 2, + } + + // Run analyze in the DB to update the estimate tables + _, err = sqlDB.ExecContext(ctx, "analyze") + require.NoError(t, err) + + got, err := s.ListAccounts(ctx, req) + require.NoError(t, err) + require.Len(t, got.GetItems(), 2) + + // all comparisons will be done without list token + assert.Empty(t, + cmp.Diff( + got, + &pbs.ListAccountsResponse{ + Items: accounts[0:2], + ResponseType: "delta", + ListToken: "", + SortBy: "created_time", + SortDir: "desc", + RemovedIds: nil, + EstItemCount: 10, + }, + cmpopts.SortSlices(func(a, b string) bool { + return a < b + }), + protocmp.Transform(), + protocmp.IgnoreFields(&pbs.ListAccountsResponse{}, "list_token"), + ), + ) + + // second page + req.ListToken = got.ListToken + got, err = s.ListAccounts(ctx, req) + require.NoError(t, err) + require.Len(t, got.GetItems(), 2) + + assert.Empty(t, + cmp.Diff( + got, + &pbs.ListAccountsResponse{ + Items: accounts[2:4], + ResponseType: "delta", + ListToken: "", + SortBy: "created_time", + SortDir: "desc", + RemovedIds: nil, + EstItemCount: 10, + }, + cmpopts.SortSlices(func(a, b string) bool { + return a < b + }), + protocmp.Transform(), + protocmp.IgnoreFields(&pbs.ListAccountsResponse{}, "list_token"), + ), + ) + + // remainder of results + req.ListToken = got.ListToken + req.PageSize = 6 + got, err = s.ListAccounts(ctx, req) + require.NoError(t, err) + require.Len(t, got.GetItems(), 6) + + assert.Empty(t, + cmp.Diff( + got, + &pbs.ListAccountsResponse{ + Items: accounts[4:], + ResponseType: "complete", + ListToken: "", + SortBy: "created_time", + SortDir: "desc", + RemovedIds: nil, + EstItemCount: 10, + }, + cmpopts.SortSlices(func(a, b string) bool { + return a < b + }), + protocmp.Transform(), + protocmp.IgnoreFields(&pbs.ListAccountsResponse{}, "list_token"), + ), + ) + + // create another acct + aa := ldap.TestAccount(t, conn, authMethod, "test-login-new-last") + newAccount := &pb.Account{ + Id: aa.GetPublicId(), + AuthMethodId: aa.GetAuthMethodId(), + CreatedTime: aa.GetCreateTime().GetTimestamp(), + UpdatedTime: aa.GetUpdateTime().GetTimestamp(), + Scope: &scopepb.ScopeInfo{Id: o.GetPublicId(), Type: scope.Org.String(), ParentScopeId: scope.Global.String()}, + Version: 1, + Type: ldap.Subtype.String(), + Attrs: &pb.Account_LdapAccountAttributes{ + LdapAccountAttributes: &pb.LdapAccountAttributes{ + LoginName: "test-login-new-last", + }, + }, + AuthorizedActions: ldapAuthorizedActions, + } + // Add to front since it's the latest updated + accounts = append([]*pb.Account{newAccount}, accounts...) + + // delete different acct + _, err = ldapRepo.DeleteAccount(ctx, accounts[len(accounts)-1].Id) + require.NoError(t, err) + deletedAccount := accounts[len(accounts)-1] + accounts = accounts[:len(accounts)-1] + + // update another acct + accounts[1].Name = wrapperspb.String("new-name") + accounts[1].Version = 2 + a := &ldap.Account{ + Account: &ldapstore.Account{ + PublicId: accounts[1].Id, + AuthMethodId: accounts[1].AuthMethodId, + Name: accounts[1].Name.GetValue(), + ScopeId: accounts[1].Scope.Id, + }, + } + ua, _, err := ldapRepo.UpdateAccount(ctx, o.GetPublicId(), a, 1, []string{"name"}) + require.NoError(t, err) + accounts[1].UpdatedTime = ua.GetUpdateTime().GetTimestamp() + accounts[1].Version = ua.GetVersion() + // Add to the front since it's most recently updated + accounts = append( + []*pb.Account{accounts[1]}, + append( + []*pb.Account{accounts[0]}, + accounts[2:]..., + )..., + ) + + // Run analyze to update postgres estimates + _, err = sqlDB.ExecContext(ctx, "analyze") + require.NoError(t, err) + + // request updated results + req.ListToken = got.ListToken + req.PageSize = 1 + got, err = s.ListAccounts(ctx, req) + require.NoError(t, err) + require.Len(t, got.GetItems(), 1) + assert.Empty(t, + cmp.Diff( + got, + &pbs.ListAccountsResponse{ + Items: []*pb.Account{accounts[0]}, + ResponseType: "delta", + ListToken: "", + SortBy: "updated_time", + SortDir: "desc", + RemovedIds: []string{deletedAccount.Id}, + EstItemCount: 10, + }, + cmpopts.SortSlices(func(a, b string) bool { + return a < b + }), + protocmp.Transform(), + protocmp.IgnoreFields(&pbs.ListAccountsResponse{}, "list_token"), + ), + ) + + // Get next page + req.ListToken = got.ListToken + got, err = s.ListAccounts(ctx, req) + require.NoError(t, err) + require.Len(t, got.GetItems(), 1) + assert.Empty(t, + cmp.Diff( + got, + &pbs.ListAccountsResponse{ + Items: []*pb.Account{accounts[1]}, + ResponseType: "complete", + ListToken: "", + SortBy: "updated_time", + SortDir: "desc", + RemovedIds: nil, + EstItemCount: 10, + }, + cmpopts.SortSlices(func(a, b string) bool { + return a < b + }), + protocmp.Transform(), + protocmp.IgnoreFields(&pbs.ListAccountsResponse{}, "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`, accounts[len(accounts)-2].Id, accounts[len(accounts)-1].Id) + got, err = s.ListAccounts(ctx, req) + require.NoError(t, err) + require.Len(t, got.GetItems(), 1) + assert.Empty(t, + cmp.Diff( + got, + &pbs.ListAccountsResponse{ + Items: []*pb.Account{accounts[len(accounts)-2]}, + ResponseType: "delta", + ListToken: "", + SortBy: "created_time", + SortDir: "desc", + // Should be empty again + RemovedIds: nil, + EstItemCount: 10, + }, + cmpopts.SortSlices(func(a, b string) bool { + return a < b + }), + protocmp.Transform(), + protocmp.IgnoreFields(&pbs.ListAccountsResponse{}, "list_token"), + ), + ) + req.ListToken = got.ListToken + // Get the second page + got, err = s.ListAccounts(ctx, req) + require.NoError(t, err) + require.Len(t, got.GetItems(), 1) + assert.Empty(t, + cmp.Diff( + got, + &pbs.ListAccountsResponse{ + Items: []*pb.Account{accounts[len(accounts)-1]}, + ResponseType: "complete", + ListToken: "", + SortBy: "created_time", + SortDir: "desc", + RemovedIds: nil, + EstItemCount: 10, + }, + cmpopts.SortSlices(func(a, b string) bool { + return a < b + }), + protocmp.Transform(), + protocmp.IgnoreFields(&pbs.ListAccountsResponse{}, "list_token"), + ), + ) + }) +} + func TestDelete(t *testing.T) { ctx := context.Background() conn, _ := db.TestSetup(t, "postgres") @@ -825,7 +1909,7 @@ func TestDelete(t *testing.T) { ldapAm := ldap.TestAuthMethod(t, conn, databaseWrapper, o.PublicId, []string{"ldaps://ldap1"}) ldapAcct := ldap.TestAccount(t, conn, ldapAm, "test-account") - s, err := accounts.NewService(ctx, pwRepoFn, oidcRepoFn, ldapRepoFn) + s, err := accounts.NewService(ctx, pwRepoFn, oidcRepoFn, ldapRepoFn, 1000) require.NoError(t, err, "Error when getting new user service.") cases := []struct { @@ -934,7 +2018,7 @@ func TestDelete_twice(t *testing.T) { am := password.TestAuthMethods(t, conn, o.GetPublicId(), 1)[0] ac := password.TestAccount(t, conn, am.GetPublicId(), "name1") - s, err := accounts.NewService(ctx, pwRepoFn, oidcRepoFn, ldapRepoFn) + s, err := accounts.NewService(ctx, pwRepoFn, oidcRepoFn, ldapRepoFn, 1000) require.NoError(err, "Error when getting new user service") req := &pbs.DeleteAccountRequest{ Id: ac.GetPublicId(), @@ -965,7 +2049,7 @@ func TestCreatePassword(t *testing.T) { return ldap.NewRepository(ctx, rw, rw, kms) } - s, err := accounts.NewService(ctx, pwRepoFn, oidcRepoFn, ldapRepoFn) + s, err := accounts.NewService(ctx, pwRepoFn, oidcRepoFn, ldapRepoFn, 1000) require.NoError(t, err, "Error when getting new account service.") o, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrap)) @@ -989,7 +2073,7 @@ func TestCreatePassword(t *testing.T) { Description: &wrapperspb.StringValue{Value: "desc"}, Type: "password", Attrs: &pb.Account_PasswordAccountAttributes{ - &pb.PasswordAccountAttributes{ + PasswordAccountAttributes: &pb.PasswordAccountAttributes{ LoginName: "validaccount", Password: nil, }, @@ -1006,7 +2090,7 @@ func TestCreatePassword(t *testing.T) { Version: 1, Type: "password", Attrs: &pb.Account_PasswordAccountAttributes{ - &pb.PasswordAccountAttributes{ + PasswordAccountAttributes: &pb.PasswordAccountAttributes{ LoginName: "validaccount", Password: nil, }, @@ -1021,7 +2105,7 @@ func TestCreatePassword(t *testing.T) { Item: &pb.Account{ AuthMethodId: defaultAccount.GetAuthMethodId(), Attrs: &pb.Account_PasswordAccountAttributes{ - &pb.PasswordAccountAttributes{ + PasswordAccountAttributes: &pb.PasswordAccountAttributes{ LoginName: "notypedefined", Password: nil, }, @@ -1036,7 +2120,7 @@ func TestCreatePassword(t *testing.T) { Version: 1, Type: "password", Attrs: &pb.Account_PasswordAccountAttributes{ - &pb.PasswordAccountAttributes{ + PasswordAccountAttributes: &pb.PasswordAccountAttributes{ LoginName: "notypedefined", Password: nil, }, @@ -1053,7 +2137,7 @@ func TestCreatePassword(t *testing.T) { Name: &wrapperspb.StringValue{Value: "name_with_password"}, Description: &wrapperspb.StringValue{Value: "desc"}, Attrs: &pb.Account_PasswordAccountAttributes{ - &pb.PasswordAccountAttributes{ + PasswordAccountAttributes: &pb.PasswordAccountAttributes{ LoginName: "haspassword", Password: &wrapperspb.StringValue{Value: "somepassword"}, }, @@ -1070,7 +2154,7 @@ func TestCreatePassword(t *testing.T) { Version: 1, Type: "password", Attrs: &pb.Account_PasswordAccountAttributes{ - &pb.PasswordAccountAttributes{ + PasswordAccountAttributes: &pb.PasswordAccountAttributes{ LoginName: "haspassword", Password: nil, }, @@ -1086,7 +2170,7 @@ func TestCreatePassword(t *testing.T) { AuthMethodId: defaultAccount.GetAuthMethodId(), Type: "wrong", Attrs: &pb.Account_PasswordAccountAttributes{ - &pb.PasswordAccountAttributes{ + PasswordAccountAttributes: &pb.PasswordAccountAttributes{ LoginName: "nopwprovided", Password: nil, }, @@ -1104,7 +2188,7 @@ func TestCreatePassword(t *testing.T) { Id: globals.PasswordAccountPrefix + "_notallowed", Type: "password", Attrs: &pb.Account_PasswordAccountAttributes{ - &pb.PasswordAccountAttributes{ + PasswordAccountAttributes: &pb.PasswordAccountAttributes{ LoginName: "cantprovideid", Password: nil, }, @@ -1122,7 +2206,7 @@ func TestCreatePassword(t *testing.T) { CreatedTime: timestamppb.Now(), Type: "password", Attrs: &pb.Account_PasswordAccountAttributes{ - &pb.PasswordAccountAttributes{ + PasswordAccountAttributes: &pb.PasswordAccountAttributes{ LoginName: "nocreatedtime", Password: nil, }, @@ -1140,7 +2224,7 @@ func TestCreatePassword(t *testing.T) { UpdatedTime: timestamppb.Now(), Type: "password", Attrs: &pb.Account_PasswordAccountAttributes{ - &pb.PasswordAccountAttributes{ + PasswordAccountAttributes: &pb.PasswordAccountAttributes{ LoginName: "noupdatetime", Password: nil, }, @@ -1219,7 +2303,7 @@ func TestCreateOidc(t *testing.T) { return ldap.NewRepository(ctx, rw, rw, kmsCache) } - s, err := accounts.NewService(ctx, pwRepoFn, oidcRepoFn, ldapRepoFn) + s, err := accounts.NewService(ctx, pwRepoFn, oidcRepoFn, ldapRepoFn, 1000) require.NoError(t, err, "Error when getting new account service.") o, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrap)) @@ -1248,7 +2332,7 @@ func TestCreateOidc(t *testing.T) { Description: &wrapperspb.StringValue{Value: "desc"}, Type: oidc.Subtype.String(), Attrs: &pb.Account_OidcAccountAttributes{ - &pb.OidcAccountAttributes{ + OidcAccountAttributes: &pb.OidcAccountAttributes{ Subject: "valid-account", }, }, @@ -1264,7 +2348,7 @@ func TestCreateOidc(t *testing.T) { Version: 1, Type: oidc.Subtype.String(), Attrs: &pb.Account_OidcAccountAttributes{ - &pb.OidcAccountAttributes{ + OidcAccountAttributes: &pb.OidcAccountAttributes{ Subject: "valid-account", Issuer: am.GetIssuer(), }, @@ -1279,7 +2363,7 @@ func TestCreateOidc(t *testing.T) { Item: &pb.Account{ AuthMethodId: am.GetPublicId(), Attrs: &pb.Account_OidcAccountAttributes{ - &pb.OidcAccountAttributes{ + OidcAccountAttributes: &pb.OidcAccountAttributes{ Subject: "no type defined", }, }, @@ -1293,7 +2377,7 @@ func TestCreateOidc(t *testing.T) { Version: 1, Type: oidc.Subtype.String(), Attrs: &pb.Account_OidcAccountAttributes{ - &pb.OidcAccountAttributes{ + OidcAccountAttributes: &pb.OidcAccountAttributes{ Subject: "no type defined", Issuer: am.GetIssuer(), }, @@ -1310,7 +2394,7 @@ func TestCreateOidc(t *testing.T) { Name: &wrapperspb.StringValue{Value: "overwritten issuer"}, Type: oidc.Subtype.String(), Attrs: &pb.Account_OidcAccountAttributes{ - &pb.OidcAccountAttributes{ + OidcAccountAttributes: &pb.OidcAccountAttributes{ Subject: "overwritten-issuer", Issuer: "https://overwrite.com", }, @@ -1326,7 +2410,7 @@ func TestCreateOidc(t *testing.T) { Version: 1, Type: oidc.Subtype.String(), Attrs: &pb.Account_OidcAccountAttributes{ - &pb.OidcAccountAttributes{ + OidcAccountAttributes: &pb.OidcAccountAttributes{ Subject: "overwritten-issuer", Issuer: "https://overwrite.com", }, @@ -1342,7 +2426,7 @@ func TestCreateOidc(t *testing.T) { AuthMethodId: am.GetPublicId(), Type: password.Subtype.String(), Attrs: &pb.Account_OidcAccountAttributes{ - &pb.OidcAccountAttributes{ + OidcAccountAttributes: &pb.OidcAccountAttributes{ Subject: "cant-specify-mismatching-type", }, }, @@ -1359,7 +2443,7 @@ func TestCreateOidc(t *testing.T) { Id: globals.OidcAccountPrefix + "_notallowed", Type: oidc.Subtype.String(), Attrs: &pb.Account_OidcAccountAttributes{ - &pb.OidcAccountAttributes{ + OidcAccountAttributes: &pb.OidcAccountAttributes{ Subject: "cant-specify-id", }, }, @@ -1376,7 +2460,7 @@ func TestCreateOidc(t *testing.T) { CreatedTime: timestamppb.Now(), Type: oidc.Subtype.String(), Attrs: &pb.Account_OidcAccountAttributes{ - &pb.OidcAccountAttributes{ + OidcAccountAttributes: &pb.OidcAccountAttributes{ Subject: "cant-specify-created-time", }, }, @@ -1393,7 +2477,7 @@ func TestCreateOidc(t *testing.T) { UpdatedTime: timestamppb.Now(), Type: oidc.Subtype.String(), Attrs: &pb.Account_OidcAccountAttributes{ - &pb.OidcAccountAttributes{ + OidcAccountAttributes: &pb.OidcAccountAttributes{ Subject: "cant-specify-update-time", }, }, @@ -1463,7 +2547,7 @@ func TestCreateLdap(t *testing.T) { return ldap.NewRepository(ctx, rw, rw, kmsCache) } - s, err := accounts.NewService(ctx, pwRepoFn, oidcRepoFn, ldapRepoFn) + s, err := accounts.NewService(ctx, pwRepoFn, oidcRepoFn, ldapRepoFn, 1000) require.NoError(t, err, "Error when getting new account service.") o, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrap)) @@ -1751,17 +2835,17 @@ func TestUpdatePassword(t *testing.T) { o, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrap)) am := password.TestAuthMethods(t, conn, o.GetPublicId(), 1)[0] - tested, err := accounts.NewService(ctx, pwRepoFn, oidcRepoFn, ldapRepoFn) + tested, err := accounts.NewService(ctx, pwRepoFn, oidcRepoFn, ldapRepoFn, 1000) require.NoError(t, err, "Error when getting new accounts service.") defaultScopeInfo := &scopepb.ScopeInfo{Id: o.GetPublicId(), Type: o.GetType(), ParentScopeId: scope.Global.String()} defaultAttributes := &pb.Account_PasswordAccountAttributes{ - &pb.PasswordAccountAttributes{ + PasswordAccountAttributes: &pb.PasswordAccountAttributes{ LoginName: "default", }, } modifiedAttributes := &pb.Account_PasswordAccountAttributes{ - &pb.PasswordAccountAttributes{ + PasswordAccountAttributes: &pb.PasswordAccountAttributes{ LoginName: "modified", }, } @@ -2157,18 +3241,18 @@ func TestUpdateOidc(t *testing.T) { oidc.WithSigningAlgs(oidc.RS256), oidc.WithApiUrl(oidc.TestConvertToUrls(t, "https://www.alice.com/callback")[0])) - tested, err := accounts.NewService(ctx, pwRepoFn, oidcRepoFn, ldapRepoFn) + tested, err := accounts.NewService(ctx, pwRepoFn, oidcRepoFn, ldapRepoFn, 1000) require.NoError(t, err, "Error when getting new auth_method service.") defaultScopeInfo := &scopepb.ScopeInfo{Id: o.GetPublicId(), Type: o.GetType(), ParentScopeId: scope.Global.String()} defaultAttributes := &pb.Account_OidcAccountAttributes{ - &pb.OidcAccountAttributes{ + OidcAccountAttributes: &pb.OidcAccountAttributes{ Issuer: "https://www.alice.com", Subject: "test-subject", }, } modifiedAttributes := &pb.Account_OidcAccountAttributes{ - &pb.OidcAccountAttributes{ + OidcAccountAttributes: &pb.OidcAccountAttributes{ Issuer: "https://www.changed.com", Subject: "changed", }, @@ -2549,7 +3633,7 @@ func TestUpdateLdap(t *testing.T) { require.NoError(t, err) am := ldap.TestAuthMethod(t, conn, databaseWrapper, o.PublicId, []string{"ldaps://ldap"}) - tested, err := accounts.NewService(ctx, pwRepoFn, oidcRepoFn, ldapRepoFn) + tested, err := accounts.NewService(ctx, pwRepoFn, oidcRepoFn, ldapRepoFn, 1000) require.NoError(t, err, "Error when getting new auth_method service.") defaultScopeInfo := &scopepb.ScopeInfo{Id: o.GetPublicId(), Type: o.GetType(), ParentScopeId: scope.Global.String()} @@ -2925,13 +4009,13 @@ func TestSetPassword(t *testing.T) { } o, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrap)) - tested, err := accounts.NewService(ctx, pwRepoFn, oidcRepoFn, ldapRepoFn) + tested, err := accounts.NewService(ctx, pwRepoFn, oidcRepoFn, ldapRepoFn, 1000) require.NoError(t, err, "Error when getting new auth_method service.") createAccount := func(t *testing.T, pw string) *pb.Account { am := password.TestAuthMethods(t, conn, o.GetPublicId(), 1)[0] pwAttrs := &pb.Account_PasswordAccountAttributes{ - &pb.PasswordAccountAttributes{ + PasswordAccountAttributes: &pb.PasswordAccountAttributes{ LoginName: "testusername", }, } @@ -3074,14 +4158,16 @@ func TestChangePassword(t *testing.T) { } o, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrap)) - tested, err := accounts.NewService(ctx, pwRepoFn, oidcRepoFn, ldapRepoFn) + tested, err := accounts.NewService(ctx, pwRepoFn, oidcRepoFn, ldapRepoFn, 1000) require.NoError(t, err, "Error when getting new auth_method service.") createAccount := func(t *testing.T, pw string) *pb.Account { am := password.TestAuthMethods(t, conn, o.GetPublicId(), 1)[0] - pwAttrs := &pb.Account_PasswordAccountAttributes{&pb.PasswordAccountAttributes{ - LoginName: "testusername", - }} + pwAttrs := &pb.Account_PasswordAccountAttributes{ + PasswordAccountAttributes: &pb.PasswordAccountAttributes{ + LoginName: "testusername", + }, + } if pw != "" { pwAttrs.PasswordAccountAttributes.Password = wrapperspb.String(pw) } diff --git a/internal/gen/controller.swagger.json b/internal/gen/controller.swagger.json index f1ffd953e..bd1f41446 100644 --- a/internal/gen/controller.swagger.json +++ b/internal/gen/controller.swagger.json @@ -100,6 +100,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": [ @@ -7692,6 +7707,34 @@ "type": "object", "$ref": "#/definitions/controller.api.resources.accounts.v1.Account" } + }, + "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." } } }, diff --git a/internal/gen/controller/api/services/account_service.pb.go b/internal/gen/controller/api/services/account_service.pb.go index bfd15d46b..9236d222e 100644 --- a/internal/gen/controller/api/services/account_service.pb.go +++ b/internal/gen/controller/api/services/account_service.pb.go @@ -129,6 +129,15 @@ type ListAccountsRequest struct { AuthMethodId string `protobuf:"bytes,1,opt,name=auth_method_id,proto3" json:"auth_method_id,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 *ListAccountsRequest) Reset() { @@ -177,12 +186,46 @@ func (x *ListAccountsRequest) GetFilter() string { return "" } +func (x *ListAccountsRequest) GetListToken() string { + if x != nil { + return x.ListToken + } + return "" +} + +func (x *ListAccountsRequest) GetPageSize() uint32 { + if x != nil { + return x.PageSize + } + return 0 +} + type ListAccountsResponse struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields Items []*accounts.Account `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 *ListAccountsResponse) Reset() { @@ -224,6 +267,48 @@ func (x *ListAccountsResponse) GetItems() []*accounts.Account { return nil } +func (x *ListAccountsResponse) GetResponseType() string { + if x != nil { + return x.ResponseType + } + return "" +} + +func (x *ListAccountsResponse) GetListToken() string { + if x != nil { + return x.ListToken + } + return "" +} + +func (x *ListAccountsResponse) GetSortBy() string { + if x != nil { + return x.SortBy + } + return "" +} + +func (x *ListAccountsResponse) GetSortDir() string { + if x != nil { + return x.SortDir + } + return "" +} + +func (x *ListAccountsResponse) GetRemovedIds() []string { + if x != nil { + return x.RemovedIds + } + return nil +} + +func (x *ListAccountsResponse) GetEstItemCount() uint32 { + if x != nil { + return x.EstItemCount + } + return 0 +} + type CreateAccountRequest struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -781,170 +866,186 @@ var file_controller_api_services_v1_account_service_proto_rawDesc = []byte{ 0x6d, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2d, 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, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x41, - 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x52, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x22, 0x55, 0x0a, 0x13, - 0x4c, 0x69, 0x73, 0x74, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x12, 0x26, 0x0a, 0x0e, 0x61, 0x75, 0x74, 0x68, 0x5f, 0x6d, 0x65, 0x74, 0x68, - 0x6f, 0x64, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x61, 0x75, 0x74, - 0x68, 0x5f, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x5f, 0x69, 0x64, 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, 0x5b, 0x0a, 0x14, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x63, 0x63, 0x6f, 0x75, + 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x52, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x22, 0x93, 0x01, 0x0a, + 0x13, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x12, 0x26, 0x0a, 0x0e, 0x61, 0x75, 0x74, 0x68, 0x5f, 0x6d, 0x65, 0x74, + 0x68, 0x6f, 0x64, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x61, 0x75, + 0x74, 0x68, 0x5f, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x5f, 0x69, 0x64, 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, 0xa1, 0x02, 0x0a, 0x14, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x43, 0x0a, 0x05, 0x69, 0x74, 0x65, 0x6d, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2d, 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, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x52, 0x05, 0x69, 0x74, 0x65, 0x6d, 0x73, - 0x22, 0x59, 0x0a, 0x14, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, - 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x41, 0x0a, 0x04, 0x69, 0x74, 0x65, 0x6d, - 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2d, 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, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x63, - 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x52, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x22, 0x6c, 0x0a, 0x15, 0x43, - 0x72, 0x65, 0x61, 0x74, 0x65, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 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, 0x41, 0x0a, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x18, 0x02, - 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2d, 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, - 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x63, 0x63, 0x6f, - 0x75, 0x6e, 0x74, 0x52, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x22, 0xa7, 0x01, 0x0a, 0x14, 0x55, 0x70, - 0x64, 0x61, 0x74, 0x65, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, - 0x69, 0x64, 0x12, 0x41, 0x0a, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, - 0x32, 0x2d, 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, 0x61, 0x63, 0x63, 0x6f, - 0x75, 0x6e, 0x74, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 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, 0x5a, 0x0a, 0x15, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x41, 0x63, 0x63, - 0x6f, 0x75, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x41, 0x0a, 0x04, - 0x69, 0x74, 0x65, 0x6d, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2d, 0x2e, 0x63, 0x6f, 0x6e, + 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, 0x59, 0x0a, 0x14, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, + 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x41, + 0x0a, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2d, 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, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, + 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x52, 0x04, 0x69, 0x74, 0x65, + 0x6d, 0x22, 0x6c, 0x0a, 0x15, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x41, 0x63, 0x63, 0x6f, 0x75, + 0x6e, 0x74, 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, 0x41, 0x0a, 0x04, + 0x69, 0x74, 0x65, 0x6d, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2d, 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, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x52, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x22, - 0x26, 0x0a, 0x14, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x22, 0x17, 0x0a, 0x15, 0x44, 0x65, 0x6c, 0x65, 0x74, - 0x65, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x22, 0x5a, 0x0a, 0x12, 0x53, 0x65, 0x74, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x52, + 0xa7, 0x01, 0x0a, 0x14, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, + 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x41, 0x0a, 0x04, 0x69, 0x74, 0x65, 0x6d, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2d, 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, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x63, + 0x63, 0x6f, 0x75, 0x6e, 0x74, 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, 0x5a, 0x0a, 0x15, 0x55, 0x70, 0x64, + 0x61, 0x74, 0x65, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x12, 0x41, 0x0a, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x2d, 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, 0x61, 0x63, 0x63, 0x6f, + 0x75, 0x6e, 0x74, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x52, + 0x04, 0x69, 0x74, 0x65, 0x6d, 0x22, 0x26, 0x0a, 0x14, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x41, + 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x0e, 0x0a, + 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x22, 0x17, 0x0a, + 0x15, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x5a, 0x0a, 0x12, 0x53, 0x65, 0x74, 0x50, 0x61, 0x73, + 0x73, 0x77, 0x6f, 0x72, 0x64, 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, 0x1a, 0x0a, 0x08, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, + 0x72, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, + 0x72, 0x64, 0x22, 0x58, 0x0a, 0x13, 0x53, 0x65, 0x74, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, + 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x41, 0x0a, 0x04, 0x69, 0x74, 0x65, + 0x6d, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2d, 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, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x41, + 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x52, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x22, 0x91, 0x01, 0x0a, + 0x15, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 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, 0x1a, 0x0a, 0x08, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x18, 0x03, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x08, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x22, 0x58, 0x0a, 0x13, - 0x53, 0x65, 0x74, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, - 0x6e, 0x73, 0x65, 0x12, 0x41, 0x0a, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x2d, 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, 0x61, 0x63, 0x63, - 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, - 0x52, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x22, 0x91, 0x01, 0x0a, 0x15, 0x43, 0x68, 0x61, 0x6e, 0x67, - 0x65, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 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, 0x2a, 0x0a, 0x10, 0x63, 0x75, - 0x72, 0x72, 0x65, 0x6e, 0x74, 0x5f, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x18, 0x03, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x10, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x5f, 0x70, 0x61, - 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x12, 0x22, 0x0a, 0x0c, 0x6e, 0x65, 0x77, 0x5f, 0x70, 0x61, - 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x6e, 0x65, - 0x77, 0x5f, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x22, 0x5b, 0x0a, 0x16, 0x43, 0x68, - 0x61, 0x6e, 0x67, 0x65, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x41, 0x0a, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x0b, 0x32, 0x2d, 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, 0x61, 0x63, - 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, - 0x74, 0x52, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x32, 0xd9, 0x0a, 0x0a, 0x0e, 0x41, 0x63, 0x63, 0x6f, - 0x75, 0x6e, 0x74, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0xa7, 0x01, 0x0a, 0x0a, 0x47, - 0x65, 0x74, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 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, 0x47, 0x65, 0x74, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, - 0x74, 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, 0x47, 0x65, 0x74, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x3a, 0x92, 0x41, 0x18, 0x12, 0x16, 0x47, - 0x65, 0x74, 0x73, 0x20, 0x61, 0x20, 0x73, 0x69, 0x6e, 0x67, 0x6c, 0x65, 0x20, 0x41, 0x63, 0x63, - 0x6f, 0x75, 0x6e, 0x74, 0x2e, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x19, 0x62, 0x04, 0x69, 0x74, 0x65, - 0x6d, 0x12, 0x11, 0x2f, 0x76, 0x31, 0x2f, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x2f, - 0x7b, 0x69, 0x64, 0x7d, 0x12, 0xb9, 0x01, 0x0a, 0x0c, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x63, 0x63, - 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x12, 0x2f, 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, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x30, 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, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x46, 0x92, 0x41, 0x2f, 0x12, 0x2d, 0x4c, - 0x69, 0x73, 0x74, 0x73, 0x20, 0x61, 0x6c, 0x6c, 0x20, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, - 0x73, 0x20, 0x69, 0x6e, 0x20, 0x61, 0x20, 0x73, 0x70, 0x65, 0x63, 0x69, 0x66, 0x69, 0x63, 0x20, - 0x41, 0x75, 0x74, 0x68, 0x20, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x2e, 0x82, 0xd3, 0xe4, 0x93, - 0x02, 0x0e, 0x12, 0x0c, 0x2f, 0x76, 0x31, 0x2f, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, - 0x12, 0xd0, 0x01, 0x0a, 0x0d, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x41, 0x63, 0x63, 0x6f, 0x75, - 0x6e, 0x74, 0x12, 0x30, 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, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x1a, 0x31, 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, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x5a, 0x92, 0x41, 0x37, 0x12, 0x35, 0x43, 0x72, - 0x65, 0x61, 0x74, 0x65, 0x73, 0x20, 0x61, 0x20, 0x73, 0x69, 0x6e, 0x67, 0x6c, 0x65, 0x20, 0x41, - 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x20, 0x69, 0x6e, 0x20, 0x74, 0x68, 0x65, 0x20, 0x70, 0x72, - 0x6f, 0x76, 0x69, 0x64, 0x65, 0x64, 0x20, 0x41, 0x75, 0x74, 0x68, 0x20, 0x4d, 0x65, 0x74, 0x68, - 0x6f, 0x64, 0x2e, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x1a, 0x3a, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x62, - 0x04, 0x69, 0x74, 0x65, 0x6d, 0x22, 0x0c, 0x2f, 0x76, 0x31, 0x2f, 0x61, 0x63, 0x63, 0x6f, 0x75, - 0x6e, 0x74, 0x73, 0x12, 0xb3, 0x01, 0x0a, 0x0d, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x41, 0x63, - 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x30, 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, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x31, 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, 0x41, 0x63, 0x63, 0x6f, 0x75, - 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x3d, 0x92, 0x41, 0x15, 0x12, - 0x13, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x73, 0x20, 0x61, 0x6e, 0x20, 0x41, 0x63, 0x63, 0x6f, - 0x75, 0x6e, 0x74, 0x2e, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x1f, 0x3a, 0x04, 0x69, 0x74, 0x65, 0x6d, - 0x62, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x32, 0x11, 0x2f, 0x76, 0x31, 0x2f, 0x61, 0x63, 0x63, 0x6f, - 0x75, 0x6e, 0x74, 0x73, 0x2f, 0x7b, 0x69, 0x64, 0x7d, 0x12, 0xa7, 0x01, 0x0a, 0x0d, 0x44, 0x65, - 0x6c, 0x65, 0x74, 0x65, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x30, 0x2e, 0x63, 0x6f, + 0x12, 0x2a, 0x0a, 0x10, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x5f, 0x70, 0x61, 0x73, 0x73, + 0x77, 0x6f, 0x72, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x10, 0x63, 0x75, 0x72, 0x72, + 0x65, 0x6e, 0x74, 0x5f, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x12, 0x22, 0x0a, 0x0c, + 0x6e, 0x65, 0x77, 0x5f, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x18, 0x04, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x0c, 0x6e, 0x65, 0x77, 0x5f, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, + 0x22, 0x5b, 0x0a, 0x16, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, + 0x72, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x41, 0x0a, 0x04, 0x69, 0x74, + 0x65, 0x6d, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2d, 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, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x2e, 0x76, 0x31, 0x2e, + 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x52, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x32, 0xd9, 0x0a, + 0x0a, 0x0e, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, + 0x12, 0xa7, 0x01, 0x0a, 0x0a, 0x47, 0x65, 0x74, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 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, 0x47, 0x65, 0x74, + 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 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, 0x47, 0x65, 0x74, 0x41, + 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x3a, + 0x92, 0x41, 0x18, 0x12, 0x16, 0x47, 0x65, 0x74, 0x73, 0x20, 0x61, 0x20, 0x73, 0x69, 0x6e, 0x67, + 0x6c, 0x65, 0x20, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x2e, 0x82, 0xd3, 0xe4, 0x93, 0x02, + 0x19, 0x62, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x12, 0x11, 0x2f, 0x76, 0x31, 0x2f, 0x61, 0x63, 0x63, + 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x2f, 0x7b, 0x69, 0x64, 0x7d, 0x12, 0xb9, 0x01, 0x0a, 0x0c, 0x4c, + 0x69, 0x73, 0x74, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x12, 0x2f, 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, 0x41, + 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x63, 0x63, + 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x30, 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, 0x41, 0x63, + 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x46, + 0x92, 0x41, 0x2f, 0x12, 0x2d, 0x4c, 0x69, 0x73, 0x74, 0x73, 0x20, 0x61, 0x6c, 0x6c, 0x20, 0x41, + 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x20, 0x69, 0x6e, 0x20, 0x61, 0x20, 0x73, 0x70, 0x65, + 0x63, 0x69, 0x66, 0x69, 0x63, 0x20, 0x41, 0x75, 0x74, 0x68, 0x20, 0x4d, 0x65, 0x74, 0x68, 0x6f, + 0x64, 0x2e, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x0e, 0x12, 0x0c, 0x2f, 0x76, 0x31, 0x2f, 0x61, 0x63, + 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x12, 0xd0, 0x01, 0x0a, 0x0d, 0x43, 0x72, 0x65, 0x61, 0x74, + 0x65, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x30, 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, 0x41, 0x63, 0x63, 0x6f, + 0x75, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x31, 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, 0x41, 0x63, + 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x5a, 0x92, + 0x41, 0x37, 0x12, 0x35, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x73, 0x20, 0x61, 0x20, 0x73, 0x69, + 0x6e, 0x67, 0x6c, 0x65, 0x20, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x20, 0x69, 0x6e, 0x20, + 0x74, 0x68, 0x65, 0x20, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x64, 0x20, 0x41, 0x75, 0x74, + 0x68, 0x20, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x2e, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x1a, 0x3a, + 0x04, 0x69, 0x74, 0x65, 0x6d, 0x62, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x22, 0x0c, 0x2f, 0x76, 0x31, + 0x2f, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x12, 0xb3, 0x01, 0x0a, 0x0d, 0x55, 0x70, + 0x64, 0x61, 0x74, 0x65, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x30, 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, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x31, 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, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x22, 0x31, 0x92, 0x41, 0x15, 0x12, 0x13, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x73, 0x20, 0x61, - 0x6e, 0x20, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x2e, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x13, - 0x2a, 0x11, 0x2f, 0x76, 0x31, 0x2f, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x2f, 0x7b, - 0x69, 0x64, 0x7d, 0x12, 0xcf, 0x01, 0x0a, 0x0b, 0x53, 0x65, 0x74, 0x50, 0x61, 0x73, 0x73, 0x77, - 0x6f, 0x72, 0x64, 0x12, 0x2e, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, + 0x22, 0x3d, 0x92, 0x41, 0x15, 0x12, 0x13, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x73, 0x20, 0x61, + 0x6e, 0x20, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x2e, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x1f, + 0x3a, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x62, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x32, 0x11, 0x2f, 0x76, + 0x31, 0x2f, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x2f, 0x7b, 0x69, 0x64, 0x7d, 0x12, + 0xa7, 0x01, 0x0a, 0x0d, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, + 0x74, 0x12, 0x30, 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, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x1a, 0x31, 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, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x1a, 0x2f, 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, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x5f, 0x92, 0x41, 0x2d, 0x12, 0x2b, 0x53, 0x65, 0x74, 0x73, 0x20, - 0x74, 0x68, 0x65, 0x20, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x20, 0x66, 0x6f, 0x72, - 0x20, 0x74, 0x68, 0x65, 0x20, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x64, 0x20, 0x41, 0x63, - 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x2e, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x29, 0x3a, 0x01, 0x2a, 0x62, - 0x04, 0x69, 0x74, 0x65, 0x6d, 0x22, 0x1e, 0x2f, 0x76, 0x31, 0x2f, 0x61, 0x63, 0x63, 0x6f, 0x75, - 0x6e, 0x74, 0x73, 0x2f, 0x7b, 0x69, 0x64, 0x7d, 0x3a, 0x73, 0x65, 0x74, 0x2d, 0x70, 0x61, 0x73, - 0x73, 0x77, 0x6f, 0x72, 0x64, 0x12, 0xdb, 0x01, 0x0a, 0x0e, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, - 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x12, 0x31, 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, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x50, 0x61, 0x73, 0x73, - 0x77, 0x6f, 0x72, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 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, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x50, - 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, - 0x62, 0x92, 0x41, 0x2d, 0x12, 0x2b, 0x53, 0x65, 0x74, 0x73, 0x20, 0x74, 0x68, 0x65, 0x20, 0x70, - 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x20, 0x66, 0x6f, 0x72, 0x20, 0x74, 0x68, 0x65, 0x20, - 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x64, 0x20, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, - 0x2e, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x2c, 0x3a, 0x01, 0x2a, 0x62, 0x04, 0x69, 0x74, 0x65, 0x6d, - 0x22, 0x21, 0x2f, 0x76, 0x31, 0x2f, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x2f, 0x7b, - 0x69, 0x64, 0x7d, 0x3a, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x2d, 0x70, 0x61, 0x73, 0x73, 0x77, - 0x6f, 0x72, 0x64, 0x42, 0x55, 0xa2, 0xe3, 0x29, 0x04, 0x61, 0x75, 0x74, 0x68, 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, + 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x31, 0x92, 0x41, 0x15, 0x12, 0x13, 0x44, 0x65, 0x6c, + 0x65, 0x74, 0x65, 0x73, 0x20, 0x61, 0x6e, 0x20, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x2e, + 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x13, 0x2a, 0x11, 0x2f, 0x76, 0x31, 0x2f, 0x61, 0x63, 0x63, 0x6f, + 0x75, 0x6e, 0x74, 0x73, 0x2f, 0x7b, 0x69, 0x64, 0x7d, 0x12, 0xcf, 0x01, 0x0a, 0x0b, 0x53, 0x65, + 0x74, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x12, 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, 0x53, 0x65, 0x74, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, + 0x72, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2f, 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, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, + 0x72, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x5f, 0x92, 0x41, 0x2d, 0x12, + 0x2b, 0x53, 0x65, 0x74, 0x73, 0x20, 0x74, 0x68, 0x65, 0x20, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, + 0x72, 0x64, 0x20, 0x66, 0x6f, 0x72, 0x20, 0x74, 0x68, 0x65, 0x20, 0x70, 0x72, 0x6f, 0x76, 0x69, + 0x64, 0x65, 0x64, 0x20, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x2e, 0x82, 0xd3, 0xe4, 0x93, + 0x02, 0x29, 0x3a, 0x01, 0x2a, 0x62, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x22, 0x1e, 0x2f, 0x76, 0x31, + 0x2f, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x2f, 0x7b, 0x69, 0x64, 0x7d, 0x3a, 0x73, + 0x65, 0x74, 0x2d, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x12, 0xdb, 0x01, 0x0a, 0x0e, + 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x12, 0x31, + 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, 0x68, 0x61, 0x6e, + 0x67, 0x65, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x1a, 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, 0x43, + 0x68, 0x61, 0x6e, 0x67, 0x65, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x62, 0x92, 0x41, 0x2d, 0x12, 0x2b, 0x53, 0x65, 0x74, 0x73, + 0x20, 0x74, 0x68, 0x65, 0x20, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x20, 0x66, 0x6f, + 0x72, 0x20, 0x74, 0x68, 0x65, 0x20, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x64, 0x20, 0x41, + 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x2e, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x2c, 0x3a, 0x01, 0x2a, + 0x62, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x22, 0x21, 0x2f, 0x76, 0x31, 0x2f, 0x61, 0x63, 0x63, 0x6f, + 0x75, 0x6e, 0x74, 0x73, 0x2f, 0x7b, 0x69, 0x64, 0x7d, 0x3a, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, + 0x2d, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x42, 0x55, 0xa2, 0xe3, 0x29, 0x04, 0x61, + 0x75, 0x74, 0x68, 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 ( diff --git a/internal/proto/controller/api/services/v1/account_service.proto b/internal/proto/controller/api/services/v1/account_service.proto index f3198bfab..558f996e8 100644 --- a/internal/proto/controller/api/services/v1/account_service.proto +++ b/internal/proto/controller/api/services/v1/account_service.proto @@ -110,10 +110,39 @@ message GetAccountResponse { message ListAccountsRequest { string auth_method_id = 1 [json_name = "auth_method_id"]; // @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 ListAccountsResponse { repeated resources.accounts.v1.Account 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 CreateAccountRequest { diff --git a/internal/tests/api/accounts/account_test.go b/internal/tests/api/accounts/account_test.go index 3f0677c0d..7db60a611 100644 --- a/internal/tests/api/accounts/account_test.go +++ b/internal/tests/api/accounts/account_test.go @@ -357,10 +357,7 @@ func TestCustomMethods(t *testing.T) { require.NoError(err) require.Len(al.Items, 2) - userAcct := al.Items[0] - if userAcct.Name == "admin" { - userAcct = al.Items[1] - } + userAcct := al.Items[1] _, err = userAccountClient.SetPassword(tc.Context(), userAcct.Id, "setpassword", userAcct.Version) require.Error(err)