diff --git a/internal/daemon/controller/common/common.go b/internal/daemon/controller/common/common.go index 168a497fb..a147a25ef 100644 --- a/internal/daemon/controller/common/common.go +++ b/internal/daemon/controller/common/common.go @@ -7,6 +7,7 @@ import ( "github.com/hashicorp/boundary/internal/auth/ldap" "github.com/hashicorp/boundary/internal/auth/oidc" "github.com/hashicorp/boundary/internal/auth/password" + "github.com/hashicorp/boundary/internal/credential" credstatic "github.com/hashicorp/boundary/internal/credential/static" "github.com/hashicorp/boundary/internal/credential/vault" pluginhost "github.com/hashicorp/boundary/internal/host/plugin" @@ -22,6 +23,7 @@ type ( AuthTokenRepoFactory = oidc.AuthTokenRepoFactory VaultCredentialRepoFactory = func() (*vault.Repository, error) StaticCredentialRepoFactory = func() (*credstatic.Repository, error) + CredentialStoreRepoFactory func() (*credential.StoreRepository, error) IamRepoFactory = iam.IamRepoFactory OidcAuthRepoFactory = oidc.OidcRepoFactory LdapAuthRepoFactory = ldap.RepoFactory diff --git a/internal/daemon/controller/controller.go b/internal/daemon/controller/controller.go index 9145f11a9..5745068a1 100644 --- a/internal/daemon/controller/controller.go +++ b/internal/daemon/controller/controller.go @@ -18,6 +18,7 @@ import ( "github.com/hashicorp/boundary/internal/census" "github.com/hashicorp/boundary/internal/cmd/base" "github.com/hashicorp/boundary/internal/cmd/config" + "github.com/hashicorp/boundary/internal/credential" credstatic "github.com/hashicorp/boundary/internal/credential/static" "github.com/hashicorp/boundary/internal/credential/vault" "github.com/hashicorp/boundary/internal/daemon/cluster" @@ -128,6 +129,7 @@ type Controller struct { AuthTokenRepoFn common.AuthTokenRepoFactory VaultCredentialRepoFn common.VaultCredentialRepoFactory StaticCredentialRepoFn common.StaticCredentialRepoFactory + CredentialStoreRepoFn common.CredentialStoreRepoFactory IamRepoFn common.IamRepoFactory OidcRepoFn common.OidcAuthRepoFactory LdapRepoFn common.LdapAuthRepoFactory @@ -405,6 +407,9 @@ func New(ctx context.Context, conf *Config) (*Controller, error) { c.StaticCredentialRepoFn = func() (*credstatic.Repository, error) { return credstatic.NewRepository(ctx, dbase, dbase, c.kms) } + c.CredentialStoreRepoFn = func() (*credential.StoreRepository, error) { + return credential.NewStoreRepository(ctx, dbase, dbase) + } c.ServersRepoFn = func() (*server.Repository, error) { return server.NewRepository(ctx, dbase, dbase, c.kms) } diff --git a/internal/daemon/controller/handler.go b/internal/daemon/controller/handler.go index c4af59cb7..0e4d31115 100644 --- a/internal/daemon/controller/handler.go +++ b/internal/daemon/controller/handler.go @@ -266,7 +266,14 @@ func (c *Controller) registerGrpcServices(s *grpc.Server) error { services.RegisterManagedGroupServiceServer(s, mgs) } if _, ok := currentServices[services.CredentialStoreService_ServiceDesc.ServiceName]; !ok { - cs, err := credentialstores.NewService(c.baseContext, c.VaultCredentialRepoFn, c.StaticCredentialRepoFn, c.IamRepoFn) + cs, err := credentialstores.NewService( + c.baseContext, + c.IamRepoFn, + c.VaultCredentialRepoFn, + c.StaticCredentialRepoFn, + c.CredentialStoreRepoFn, + c.conf.RawConfig.Controller.MaxPageSize, + ) if err != nil { return fmt.Errorf("failed to create credential store handler service: %w", err) } diff --git a/internal/daemon/controller/handlers/credentialstores/credentialstore_service.go b/internal/daemon/controller/handlers/credentialstores/credentialstore_service.go index b73a42083..ae3f881d7 100644 --- a/internal/daemon/controller/handlers/credentialstores/credentialstore_service.go +++ b/internal/daemon/controller/handlers/credentialstores/credentialstore_service.go @@ -22,6 +22,8 @@ import ( "github.com/hashicorp/boundary/internal/daemon/controller/handlers/credentials" "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" @@ -29,6 +31,7 @@ import ( "github.com/hashicorp/boundary/internal/types/scope" "github.com/hashicorp/boundary/internal/types/subtypes" pb "github.com/hashicorp/boundary/sdk/pbs/controller/api/resources/credentialstores" + "github.com/hashicorp/boundary/sdk/pbs/controller/api/resources/scopes" "google.golang.org/grpc/codes" "google.golang.org/protobuf/types/known/structpb" "google.golang.org/protobuf/types/known/wrapperspb" @@ -98,6 +101,8 @@ type Service struct { iamRepoFn common.IamRepoFactory vaultRepoFn common.VaultCredentialRepoFactory staticRepoFn common.StaticCredentialRepoFactory + storeRepoFn common.CredentialStoreRepoFactory + maxPageSize uint } var _ pbs.CredentialStoreServiceServer = (*Service)(nil) @@ -105,9 +110,11 @@ var _ pbs.CredentialStoreServiceServer = (*Service)(nil) // NewService returns a credential store service which handles credential store related requests to boundary. func NewService( ctx context.Context, + iamRepo common.IamRepoFactory, vaultRepo common.VaultCredentialRepoFactory, staticRepo common.StaticCredentialRepoFactory, - iamRepo common.IamRepoFactory, + storeRepoFn common.CredentialStoreRepoFactory, + maxPageSize uint, ) (Service, error) { const op = "credentialstores.NewService" if iamRepo == nil { @@ -119,13 +126,27 @@ func NewService( if staticRepo == nil { return Service{}, errors.New(ctx, errors.InvalidParameter, op, "missing static credential repository") } - return Service{iamRepoFn: iamRepo, vaultRepoFn: vaultRepo, staticRepoFn: staticRepo}, nil + if storeRepoFn == nil { + return Service{}, errors.New(ctx, errors.InvalidParameter, op, "missing credential store repo") + } + if maxPageSize == 0 { + maxPageSize = uint(globals.DefaultMaxPageSize) + } + return Service{ + iamRepoFn: iamRepo, + vaultRepoFn: vaultRepo, + staticRepoFn: staticRepo, + storeRepoFn: storeRepoFn, + maxPageSize: maxPageSize, + }, nil } // ListCredentialStores implements the interface pbs.CredentialStoreServiceServer func (s Service) ListCredentialStores(ctx context.Context, req *pbs.ListCredentialStoresRequest) (*pbs.ListCredentialStoresResponse, error) { + const op = "credentialstores.(Service).ListCredentialStores" + if err := validateListRequest(ctx, req); err != nil { - return nil, err + return nil, errors.Wrap(ctx, err, op) } authResults := s.authResult(ctx, req.GetScopeId(), action.List) if authResults.Error != nil { @@ -144,68 +165,132 @@ func (s Service) ListCredentialStores(ctx context.Context, req *pbs.ListCredenti scopeIds, scopeInfoMap, err := scopeids.GetListingScopeIds( ctx, s.iamRepoFn, authResults, req.GetScopeId(), resource.CredentialStore, req.GetRecursive()) if err != nil { - return nil, err + return nil, errors.Wrap(ctx, err, op) } - // If no scopes match, return an empty response - if len(scopeIds) == 0 { - return &pbs.ListCredentialStoresResponse{}, nil + pageSize := int(s.maxPageSize) + // Use the requested page size only if it is smaller than + // the configured max. + if req.GetPageSize() != 0 && uint(req.GetPageSize()) < s.maxPageSize { + pageSize = int(req.GetPageSize()) } + var filterItemFn func(ctx context.Context, item credential.Store) (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 credential.Store) (bool, error) { + outputOpts, ok, err := newOutputOpts(ctx, item, authResults, scopeInfoMap) + if err != nil { + return false, err + } + if !ok { + return false, nil + } + pbItem, err := toProto(ctx, item, outputOpts...) + if err != nil { + return false, err + } - csl, err := s.listFromRepo(ctx, scopeIds) + filterable, err := subtypes.Filterable(ctx, pbItem) + if err != nil { + return false, err + } + return filter.Match(filterable), nil + } + default: + filterItemFn = func(ctx context.Context, item credential.Store) (bool, error) { + return true, nil + } + } + repo, err := s.storeRepoFn() if err != nil { - return nil, err + return nil, errors.Wrap(ctx, err, op) } - if len(csl) == 0 { - return &pbs.ListCredentialStoresResponse{}, nil - } - - filter, err := handlers.NewFilter(ctx, req.GetFilter()) + grantsHash, err := authResults.GrantsHash(ctx) if err != nil { - return nil, err + return nil, errors.Wrap(ctx, err, op) } - finalItems := make([]*pb.CredentialStore, 0, len(csl)) - res := perms.Resource{ - Type: resource.CredentialStore, - } - for _, item := range csl { - res.Id = item.GetPublicId() - res.ScopeId = item.GetProjectId() - authorizedActions := authResults.FetchActionSetForId(ctx, item.GetPublicId(), IdActions, auth.WithResource(&res)).Strings() - if len(authorizedActions) == 0 { - 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(scopeInfoMap[item.GetProjectId()])) + var listResp *pagination.ListResponse[credential.Store] + var sortBy string + if req.GetListToken() == "" { + sortBy = "created_time" + listResp, err = credential.ListStores(ctx, grantsHash, pageSize, filterItemFn, repo, scopeIds) + if err != nil { + return nil, err } - if outputFields.Has(globals.AuthorizedActionsField) { - outputOpts = append(outputOpts, handlers.WithAuthorizedActions(authorizedActions)) + } else { + + listToken, err := handlers.ParseListToken(ctx, req.GetListToken(), resource.CredentialStore, grantsHash) + if err != nil { + return nil, err } - if outputFields.Has(globals.AuthorizedCollectionActionsField) { - collectionActions, err := calculateAuthorizedCollectionActions(ctx, authResults, item.GetPublicId()) + switch st := listToken.Subtype.(type) { + case *listtoken.PaginationToken: + sortBy = "created_time" + listResp, err = credential.ListStoresPage(ctx, grantsHash, pageSize, filterItemFn, listToken, repo, scopeIds) if err != nil { return nil, err } - outputOpts = append(outputOpts, handlers.WithAuthorizedCollectionActions(collectionActions)) - } - - item, err := toProto(ctx, item, outputOpts...) - if err != nil { - return nil, err - } - - filterable, err := subtypes.Filterable(ctx, item) - if err != nil { - return nil, err - } - if filter.Match(filterable) { - finalItems = append(finalItems, item) + case *listtoken.StartRefreshToken: + sortBy = "updated_time" + listResp, err = credential.ListStoresRefresh(ctx, grantsHash, pageSize, filterItemFn, listToken, repo, scopeIds) + if err != nil { + return nil, err + } + case *listtoken.RefreshToken: + sortBy = "updated_time" + listResp, err = credential.ListStoresRefreshPage(ctx, grantsHash, pageSize, filterItemFn, listToken, repo, scopeIds) + if err != nil { + return nil, err + } + default: + return nil, handlers.ApiErrorWithCodeAndMessage(codes.InvalidArgument, "unexpected list token subtype: %T", st) } } - return &pbs.ListCredentialStoresResponse{Items: finalItems}, nil + + finalItems := make([]*pb.CredentialStore, 0, len(listResp.Items)) + for _, item := range listResp.Items { + outputOpts, ok, err := newOutputOpts(ctx, item, authResults, scopeInfoMap) + if err != nil { + return nil, errors.Wrap(ctx, err, op) + } + if !ok { + continue + } + pbItem, err := toProto(ctx, item, outputOpts...) + if err != nil { + return nil, errors.Wrap(ctx, err, op) + } + finalItems = append(finalItems, pbItem) + } + respType := "delta" + if listResp.CompleteListing { + respType = "complete" + } + resp := &pbs.ListCredentialStoresResponse{ + Items: finalItems, + EstItemCount: uint32(listResp.EstimatedItemCount), + RemovedIds: listResp.DeletedIds, + ResponseType: respType, + SortBy: sortBy, + SortDir: "desc", + } + + if listResp.ListToken != nil { + resp.ListToken, err = handlers.MarshalListToken(ctx, listResp.ListToken, pbs.ResourceType_RESOURCE_TYPE_CREDENTIAL_STORE) + if err != nil { + return nil, err + } + } + + return resp, nil } // GetCredentialStore implements the interface pbs.CredentialStoreServiceServer. @@ -362,38 +447,6 @@ func (s Service) DeleteCredentialStore(ctx context.Context, req *pbs.DeleteCrede return nil, nil } -func (s Service) listFromRepo(ctx context.Context, scopeIds []string) ([]credential.Store, error) { - const op = "credentialstores.(Service).listFromRepo" - - vaultRepo, err := s.vaultRepoFn() - if err != nil { - return nil, errors.Wrap(ctx, err, op) - } - vaultCsl, err := vaultRepo.ListCredentialStores(ctx, scopeIds, vault.WithLimit(-1)) - if err != nil { - return nil, errors.Wrap(ctx, err, op) - } - - staticRepo, err := s.staticRepoFn() - if err != nil { - return nil, errors.Wrap(ctx, err, op) - } - staticCsl, err := staticRepo.ListCredentialStores(ctx, scopeIds, static.WithLimit(-1)) - if err != nil { - return nil, errors.Wrap(ctx, err, op) - } - - csl := make([]credential.Store, 0, len(staticCsl)+len(vaultCsl)) - for _, s := range vaultCsl { - csl = append(csl, s) - } - for _, s := range staticCsl { - csl = append(csl, s) - } - - return csl, nil -} - func (s Service) getFromRepo(ctx context.Context, id string) (credential.Store, error) { const op = "credentialstores.(Service).getFromRepo" @@ -616,6 +669,41 @@ func (s Service) authResult(ctx context.Context, id string, a action.Type) auth. return auth.Verify(ctx, opts...) } +func newOutputOpts( + ctx context.Context, + item credential.Store, + authResults auth.VerifyResults, + authzScopes map[string]*scopes.ScopeInfo, +) ([]handlers.Option, bool, error) { + res := perms.Resource{ + Type: resource.CredentialStore, + Id: item.GetPublicId(), + ScopeId: item.GetProjectId(), + } + authorizedActions := authResults.FetchActionSetForId(ctx, item.GetPublicId(), IdActions, auth.WithResource(&res)).Strings() + if len(authorizedActions) == 0 { + return nil, false, nil + } + + 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(authzScopes[item.GetProjectId()])) + } + if outputFields.Has(globals.AuthorizedActionsField) { + outputOpts = append(outputOpts, handlers.WithAuthorizedActions(authorizedActions)) + } + if outputFields.Has(globals.AuthorizedCollectionActionsField) { + collectionActions, err := calculateAuthorizedCollectionActions(ctx, authResults, item.GetPublicId()) + if err != nil { + return nil, false, err + } + outputOpts = append(outputOpts, handlers.WithAuthorizedCollectionActions(collectionActions)) + } + return outputOpts, true, nil +} + func toProto(ctx context.Context, in credential.Store, opt ...handlers.Option) (*pb.CredentialStore, error) { const op = "credentialstores.toProto" @@ -809,7 +897,7 @@ func validateCreateRequest(ctx context.Context, req *pbs.CreateCredentialStoreRe if attrs.GetTokenHmac() != "" { badFields[vaultTokenHmacField] = "This is a read only field." } - if attrs.WorkerFilter.GetValue() != "" { + if attrs.GetWorkerFilter().GetValue() != "" { err := validateVaultWorkerFilterFn(attrs.WorkerFilter.GetValue()) if err != nil { badFields[vaultWorkerFilterField] = err.Error() diff --git a/internal/daemon/controller/handlers/credentialstores/credentialstore_service_test.go b/internal/daemon/controller/handlers/credentialstores/credentialstore_service_test.go index 8fd327137..b1ece99bb 100644 --- a/internal/daemon/controller/handlers/credentialstores/credentialstore_service_test.go +++ b/internal/daemon/controller/handlers/credentialstores/credentialstore_service_test.go @@ -10,22 +10,30 @@ import ( "crypto/rand" "encoding/base64" "fmt" + "slices" "strings" "testing" "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" "github.com/hashicorp/boundary/globals" - credstatic "github.com/hashicorp/boundary/internal/credential/static" + "github.com/hashicorp/boundary/internal/auth/password" + "github.com/hashicorp/boundary/internal/authtoken" + "github.com/hashicorp/boundary/internal/credential" + "github.com/hashicorp/boundary/internal/credential/static" + "github.com/hashicorp/boundary/internal/credential/static/store" "github.com/hashicorp/boundary/internal/credential/vault" "github.com/hashicorp/boundary/internal/daemon/controller/auth" "github.com/hashicorp/boundary/internal/daemon/controller/handlers" "github.com/hashicorp/boundary/internal/db" "github.com/hashicorp/boundary/internal/errors" 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/scheduler" + "github.com/hashicorp/boundary/internal/server" "github.com/hashicorp/boundary/internal/types/scope" pb "github.com/hashicorp/boundary/sdk/pbs/controller/api/resources/credentialstores" scopepb "github.com/hashicorp/boundary/sdk/pbs/controller/api/resources/scopes" @@ -60,6 +68,43 @@ var ( } ) +func vaultCredentialStoreToProto(cs *vault.CredentialStore, prj *iam.Scope) *pb.CredentialStore { + return &pb.CredentialStore{ + Id: cs.GetPublicId(), + ScopeId: prj.GetPublicId(), + Scope: &scopepb.ScopeInfo{Id: prj.GetPublicId(), Type: scope.Project.String(), ParentScopeId: prj.GetParentId()}, + CreatedTime: cs.GetCreateTime().GetTimestamp(), + UpdatedTime: cs.GetUpdateTime().GetTimestamp(), + Version: cs.GetVersion(), + Type: vault.Subtype.String(), + AuthorizedActions: testAuthorizedActions, + AuthorizedCollectionActions: testAuthorizedVaultCollectionActions, + Attrs: &pb.CredentialStore_VaultCredentialStoreAttributes{ + VaultCredentialStoreAttributes: &pb.VaultCredentialStoreAttributes{ + Address: wrapperspb.String(cs.GetVaultAddress()), + TokenHmac: base64.RawURLEncoding.EncodeToString(cs.Token().GetTokenHmac()), + TokenStatus: cs.Token().GetStatus(), + ClientCertificate: wrapperspb.String(string(cs.ClientCertificate().GetCertificate())), + ClientCertificateKeyHmac: base64.RawURLEncoding.EncodeToString(cs.ClientCertificate().GetCertificateKeyHmac()), + }, + }, + } +} + +func staticCredentialStoreToProto(cs *static.CredentialStore, prj *iam.Scope) *pb.CredentialStore { + return &pb.CredentialStore{ + Id: cs.GetPublicId(), + ScopeId: prj.GetPublicId(), + Scope: &scopepb.ScopeInfo{Id: prj.GetPublicId(), Type: scope.Project.String(), ParentScopeId: prj.GetParentId()}, + CreatedTime: cs.GetCreateTime().GetTimestamp(), + UpdatedTime: cs.GetUpdateTime().GetTimestamp(), + Version: cs.GetVersion(), + Type: static.Subtype.String(), + AuthorizedActions: testAuthorizedActions, + AuthorizedCollectionActions: testAuthorizedStaticCollectionActions, + } +} + func TestList(t *testing.T) { ctx := context.Background() conn, _ := db.TestSetup(t, "postgres") @@ -77,8 +122,11 @@ func TestList(t *testing.T) { vaultRepoFn := func() (*vault.Repository, error) { return vault.NewRepository(ctx, rw, rw, kms, sche) } - staticRepoFn := func() (*credstatic.Repository, error) { - return credstatic.NewRepository(ctx, rw, rw, kms) + staticRepoFn := func() (*static.Repository, error) { + return static.NewRepository(ctx, rw, rw, kms) + } + credStoreRepoFn := func() (*credential.StoreRepository, error) { + return credential.NewStoreRepository(context.Background(), rw, rw) } _, prjNoStores := iam.TestScopes(t, iamRepo) @@ -86,41 +134,11 @@ func TestList(t *testing.T) { var wantStores []*pb.CredentialStore for _, s := range vault.TestCredentialStores(t, conn, wrapper, prj.GetPublicId(), 10) { - wantStores = append(wantStores, &pb.CredentialStore{ - Id: s.GetPublicId(), - ScopeId: prj.GetPublicId(), - Scope: &scopepb.ScopeInfo{Id: prj.GetPublicId(), Type: scope.Project.String(), ParentScopeId: prj.GetParentId()}, - CreatedTime: s.GetCreateTime().GetTimestamp(), - UpdatedTime: s.GetUpdateTime().GetTimestamp(), - Version: s.GetVersion(), - Type: vault.Subtype.String(), - AuthorizedActions: testAuthorizedActions, - AuthorizedCollectionActions: testAuthorizedVaultCollectionActions, - Attrs: &pb.CredentialStore_VaultCredentialStoreAttributes{ - VaultCredentialStoreAttributes: &pb.VaultCredentialStoreAttributes{ - Address: wrapperspb.String(s.GetVaultAddress()), - TokenHmac: base64.RawURLEncoding.EncodeToString(s.Token().GetTokenHmac()), - TokenStatus: s.Token().GetStatus(), - ClientCertificate: wrapperspb.String(string(s.ClientCertificate().GetCertificate())), - ClientCertificateKeyHmac: base64.RawURLEncoding.EncodeToString(s.ClientCertificate().GetCertificateKeyHmac()), - // TODO: Add all fields including tls related fields, namespace, etc... - }, - }, - }) + wantStores = append(wantStores, vaultCredentialStoreToProto(s, prj)) } - for _, s := range credstatic.TestCredentialStores(t, conn, wrapper, prj.GetPublicId(), 10) { - wantStores = append(wantStores, &pb.CredentialStore{ - Id: s.GetPublicId(), - ScopeId: prj.GetPublicId(), - Scope: &scopepb.ScopeInfo{Id: prj.GetPublicId(), Type: scope.Project.String(), ParentScopeId: prj.GetParentId()}, - CreatedTime: s.GetCreateTime().GetTimestamp(), - UpdatedTime: s.GetUpdateTime().GetTimestamp(), - Version: s.GetVersion(), - Type: credstatic.Subtype.String(), - AuthorizedActions: testAuthorizedActions, - AuthorizedCollectionActions: testAuthorizedStaticCollectionActions, - }) + for _, s := range static.TestCredentialStores(t, conn, wrapper, prj.GetPublicId(), 10) { + wantStores = append(wantStores, staticCredentialStoreToProto(s, prj)) } cases := []struct { @@ -131,40 +149,126 @@ func TestList(t *testing.T) { err error }{ { - name: "List Many Stores", - req: &pbs.ListCredentialStoresRequest{ScopeId: prj.GetPublicId()}, - res: &pbs.ListCredentialStoresResponse{Items: wantStores}, - anonRes: &pbs.ListCredentialStoresResponse{Items: wantStores}, + name: "List Many Stores", + req: &pbs.ListCredentialStoresRequest{ScopeId: prj.GetPublicId()}, + res: &pbs.ListCredentialStoresResponse{ + Items: wantStores, + ResponseType: "complete", + ListToken: "", + SortBy: "created_time", + SortDir: "desc", + RemovedIds: nil, + EstItemCount: 20, + }, + anonRes: &pbs.ListCredentialStoresResponse{ + Items: wantStores, + ResponseType: "complete", + ListToken: "", + SortBy: "created_time", + SortDir: "desc", + RemovedIds: nil, + EstItemCount: 20, + }, }, { - name: "List No Stores", - req: &pbs.ListCredentialStoresRequest{ScopeId: prjNoStores.GetPublicId()}, - res: &pbs.ListCredentialStoresResponse{}, - anonRes: &pbs.ListCredentialStoresResponse{}, + name: "List No Stores", + req: &pbs.ListCredentialStoresRequest{ScopeId: prjNoStores.GetPublicId()}, + res: &pbs.ListCredentialStoresResponse{ + ResponseType: "complete", + ListToken: "", + SortBy: "created_time", + SortDir: "desc", + RemovedIds: nil, + }, + anonRes: &pbs.ListCredentialStoresResponse{ + ResponseType: "complete", + ListToken: "", + SortBy: "created_time", + SortDir: "desc", + RemovedIds: nil, + }, }, { - name: "Filter to One Vault Store", - req: &pbs.ListCredentialStoresRequest{ScopeId: prj.GetPublicId(), Filter: fmt.Sprintf(`"/item/id"==%q`, wantStores[1].GetId())}, - res: &pbs.ListCredentialStoresResponse{Items: wantStores[1:2]}, - anonRes: &pbs.ListCredentialStoresResponse{Items: wantStores[1:2]}, + name: "Filter to One Vault Store", + req: &pbs.ListCredentialStoresRequest{ScopeId: prj.GetPublicId(), Filter: fmt.Sprintf(`"/item/id"==%q`, wantStores[1].GetId())}, + res: &pbs.ListCredentialStoresResponse{ + Items: wantStores[1:2], + ResponseType: "complete", + ListToken: "", + SortBy: "created_time", + SortDir: "desc", + RemovedIds: nil, + EstItemCount: 1, + }, + anonRes: &pbs.ListCredentialStoresResponse{ + Items: wantStores[1:2], + ResponseType: "complete", + ListToken: "", + SortBy: "created_time", + SortDir: "desc", + RemovedIds: nil, + EstItemCount: 1, + }, }, { - name: "Filter to One static Store", - req: &pbs.ListCredentialStoresRequest{ScopeId: prj.GetPublicId(), Filter: fmt.Sprintf(`"/item/id"==%q`, wantStores[11].GetId())}, - res: &pbs.ListCredentialStoresResponse{Items: wantStores[11:12]}, - anonRes: &pbs.ListCredentialStoresResponse{Items: wantStores[11:12]}, + name: "Filter to One static Store", + req: &pbs.ListCredentialStoresRequest{ScopeId: prj.GetPublicId(), Filter: fmt.Sprintf(`"/item/id"==%q`, wantStores[11].GetId())}, + res: &pbs.ListCredentialStoresResponse{ + Items: wantStores[11:12], + ResponseType: "complete", + ListToken: "", + SortBy: "created_time", + SortDir: "desc", + RemovedIds: nil, + EstItemCount: 1, + }, + anonRes: &pbs.ListCredentialStoresResponse{ + Items: wantStores[11:12], + ResponseType: "complete", + ListToken: "", + SortBy: "created_time", + SortDir: "desc", + RemovedIds: nil, + EstItemCount: 1, + }, }, { - name: "Filter on Attribute", - req: &pbs.ListCredentialStoresRequest{ScopeId: prj.GetPublicId(), Filter: fmt.Sprintf(`"/item/attributes/address"==%q`, wantStores[2].GetVaultCredentialStoreAttributes().GetAddress().Value)}, - res: &pbs.ListCredentialStoresResponse{Items: wantStores[2:3]}, - anonRes: &pbs.ListCredentialStoresResponse{}, // anonymous user does not have access to attributes + name: "Filter on Attribute", + req: &pbs.ListCredentialStoresRequest{ScopeId: prj.GetPublicId(), Filter: fmt.Sprintf(`"/item/attributes/address"==%q`, wantStores[2].GetVaultCredentialStoreAttributes().GetAddress().Value)}, + res: &pbs.ListCredentialStoresResponse{ + Items: wantStores[2:3], + ResponseType: "complete", + ListToken: "", + SortBy: "created_time", + SortDir: "desc", + RemovedIds: nil, + EstItemCount: 1, + }, + anonRes: &pbs.ListCredentialStoresResponse{ + ResponseType: "complete", + ListToken: "", + SortBy: "created_time", + SortDir: "desc", + RemovedIds: nil, + }, // anonymous user does not have access to attributes }, { - name: "Filter to No Store", - req: &pbs.ListCredentialStoresRequest{ScopeId: prj.GetPublicId(), Filter: `"/item/id"=="doesnt match"`}, - res: &pbs.ListCredentialStoresResponse{}, - anonRes: &pbs.ListCredentialStoresResponse{}, + name: "Filter to No Store", + req: &pbs.ListCredentialStoresRequest{ScopeId: prj.GetPublicId(), Filter: `"/item/id"=="doesnt match"`}, + res: &pbs.ListCredentialStoresResponse{ + ResponseType: "complete", + ListToken: "", + SortBy: "created_time", + SortDir: "desc", + RemovedIds: nil, + }, + anonRes: &pbs.ListCredentialStoresResponse{ + ResponseType: "complete", + ListToken: "", + SortBy: "created_time", + SortDir: "desc", + RemovedIds: nil, + }, }, { name: "Filter Bad Format", @@ -174,7 +278,7 @@ func TestList(t *testing.T) { } for _, tc := range cases { t.Run(tc.name, func(t *testing.T) { - s, err := NewService(ctx, vaultRepoFn, staticRepoFn, iamRepoFn) + s, err := NewService(ctx, iamRepoFn, vaultRepoFn, staticRepoFn, credStoreRepoFn, 1000) require.NoError(t, err, "Couldn't create new host set service.") // Test non-anonymous listing @@ -198,6 +302,7 @@ func TestList(t *testing.T) { cmpopts.SortSlices(func(a, b protocmp.Message) bool { return a.String() < b.String() }), + protocmp.IgnoreFields(&pbs.ListCredentialStoresResponse{}, "list_token"), )) // Test anonymous listing @@ -230,8 +335,11 @@ func TestCreateVault(t *testing.T) { vaultRepoFn := func() (*vault.Repository, error) { return vault.NewRepository(ctx, rw, rw, kms, sche) } - staticRepoFn := func() (*credstatic.Repository, error) { - return credstatic.NewRepository(ctx, rw, rw, kms) + staticRepoFn := func() (*static.Repository, error) { + return static.NewRepository(ctx, rw, rw, kms) + } + credStoreServiceFn := func() (*credential.StoreRepository, error) { + return credential.NewStoreRepository(context.Background(), rw, rw) } _, prj := iam.TestScopes(t, iamRepo) @@ -411,7 +519,7 @@ func TestCreateVault(t *testing.T) { Token: wrapperspb.String(newToken()), CaCert: wrapperspb.String(string(v.CaCert)), ClientCertificate: wrapperspb.String(string(v.ClientCert) + string(v.ClientKey)), - WorkerFilter: wrapperspb.String(fmt.Sprintf(`"worker" in "/tags/name"`)), + WorkerFilter: wrapperspb.String(`"worker" in "/tags/name"`), }, }, }}, @@ -544,7 +652,7 @@ func TestCreateVault(t *testing.T) { t.Run(tc.name, func(t *testing.T) { assert, require := assert.New(t), require.New(t) - s, err := NewService(ctx, vaultRepoFn, staticRepoFn, iamRepoFn) + s, err := NewService(ctx, iamRepoFn, vaultRepoFn, staticRepoFn, credStoreServiceFn, 1000) require.NoError(err, "Error when getting new credential store service.") defer cleanup(s) @@ -617,12 +725,15 @@ func TestCreateStatic(t *testing.T) { vaultRepoFn := func() (*vault.Repository, error) { return vault.NewRepository(ctx, rw, rw, kms, sche) } - staticRepoFn := func() (*credstatic.Repository, error) { - return credstatic.NewRepository(ctx, rw, rw, kms) + staticRepoFn := func() (*static.Repository, error) { + return static.NewRepository(ctx, rw, rw, kms) + } + credStoreServiceFn := func() (*credential.StoreRepository, error) { + return credential.NewStoreRepository(context.Background(), rw, rw) } _, prj := iam.TestScopes(t, iamRepo) - defaultCreated := credstatic.TestCredentialStore(t, conn, wrapper, prj.GetPublicId()) + defaultCreated := static.TestCredentialStore(t, conn, wrapper, prj.GetPublicId()) cleanup := func(s Service) { ctx := auth.DisabledAuthTestContext(iamRepoFn, prj.GetPublicId()) @@ -647,7 +758,7 @@ func TestCreateStatic(t *testing.T) { req: &pbs.CreateCredentialStoreRequest{Item: &pb.CredentialStore{ ScopeId: prj.GetPublicId(), Id: globals.StaticCredentialStorePrefix + "_notallowed", - Type: credstatic.Subtype.String(), + Type: static.Subtype.String(), }}, res: nil, err: handlers.ApiErrorWithCode(codes.InvalidArgument), @@ -657,7 +768,7 @@ func TestCreateStatic(t *testing.T) { req: &pbs.CreateCredentialStoreRequest{Item: &pb.CredentialStore{ ScopeId: prj.GetPublicId(), CreatedTime: timestamppb.Now(), - Type: credstatic.Subtype.String(), + Type: static.Subtype.String(), }}, res: nil, err: handlers.ApiErrorWithCode(codes.InvalidArgument), @@ -667,7 +778,7 @@ func TestCreateStatic(t *testing.T) { req: &pbs.CreateCredentialStoreRequest{Item: &pb.CredentialStore{ ScopeId: prj.GetPublicId(), UpdatedTime: timestamppb.Now(), - Type: credstatic.Subtype.String(), + Type: static.Subtype.String(), }}, res: nil, err: handlers.ApiErrorWithCode(codes.InvalidArgument), @@ -684,7 +795,7 @@ func TestCreateStatic(t *testing.T) { name: "Create a valid static CredentialStore", req: &pbs.CreateCredentialStoreRequest{Item: &pb.CredentialStore{ ScopeId: prj.GetPublicId(), - Type: credstatic.Subtype.String(), + Type: static.Subtype.String(), }}, idPrefix: globals.StaticCredentialStorePrefix + "_", res: &pbs.CreateCredentialStoreResponse{ @@ -693,7 +804,7 @@ func TestCreateStatic(t *testing.T) { ScopeId: prj.GetPublicId(), Scope: &scopepb.ScopeInfo{Id: prj.GetPublicId(), Type: prj.GetType(), ParentScopeId: prj.GetParentId()}, Version: 1, - Type: credstatic.Subtype.String(), + Type: static.Subtype.String(), AuthorizedActions: testAuthorizedActions, AuthorizedCollectionActions: testAuthorizedStaticCollectionActions, }, @@ -705,7 +816,7 @@ func TestCreateStatic(t *testing.T) { ScopeId: prj.GetPublicId(), Name: &wrapperspb.StringValue{Value: "name"}, Description: &wrapperspb.StringValue{Value: "desc"}, - Type: credstatic.Subtype.String(), + Type: static.Subtype.String(), }}, idPrefix: globals.StaticCredentialStorePrefix + "_", res: &pbs.CreateCredentialStoreResponse{ @@ -714,7 +825,7 @@ func TestCreateStatic(t *testing.T) { ScopeId: prj.GetPublicId(), Scope: &scopepb.ScopeInfo{Id: prj.GetPublicId(), Type: prj.GetType(), ParentScopeId: prj.GetParentId()}, Version: 1, - Type: credstatic.Subtype.String(), + Type: static.Subtype.String(), AuthorizedActions: testAuthorizedActions, Name: &wrapperspb.StringValue{Value: "name"}, Description: &wrapperspb.StringValue{Value: "desc"}, @@ -727,7 +838,7 @@ func TestCreateStatic(t *testing.T) { t.Run(tc.name, func(t *testing.T) { assert, require := assert.New(t), require.New(t) - s, err := NewService(ctx, vaultRepoFn, staticRepoFn, iamRepoFn) + s, err := NewService(ctx, iamRepoFn, vaultRepoFn, staticRepoFn, credStoreServiceFn, 1000) require.NoError(err, "Error when getting new credential store service.") defer cleanup(s) @@ -789,16 +900,19 @@ func TestGet(t *testing.T) { vaultRepoFn := func() (*vault.Repository, error) { return vault.NewRepository(ctx, rw, rw, kms, sche) } - staticRepoFn := func() (*credstatic.Repository, error) { - return credstatic.NewRepository(ctx, rw, rw, kms) + staticRepoFn := func() (*static.Repository, error) { + return static.NewRepository(ctx, rw, rw, kms) + } + credStoreServiceFn := func() (*credential.StoreRepository, error) { + return credential.NewStoreRepository(context.Background(), rw, rw) } _, prj := iam.TestScopes(t, iamRepo) vaultStore := vault.TestCredentialStores(t, conn, wrapper, prj.GetPublicId(), 1)[0] - staticStore := credstatic.TestCredentialStore(t, conn, wrapper, prj.GetPublicId()) - staticStorePrev := credstatic.TestCredentialStore(t, conn, wrapper, prj.GetPublicId(), credstatic.WithPublicId(fmt.Sprintf("%s_1234567890", globals.StaticCredentialStorePreviousPrefix))) - s, err := NewService(ctx, vaultRepoFn, staticRepoFn, iamRepoFn) + staticStore := static.TestCredentialStore(t, conn, wrapper, prj.GetPublicId()) + staticStorePrev := static.TestCredentialStore(t, conn, wrapper, prj.GetPublicId(), static.WithPublicId(fmt.Sprintf("%s_1234567890", globals.StaticCredentialStorePreviousPrefix))) + s, err := NewService(ctx, iamRepoFn, vaultRepoFn, staticRepoFn, credStoreServiceFn, 1000) require.NoError(t, err) cases := []struct { @@ -841,7 +955,7 @@ func TestGet(t *testing.T) { Id: staticStore.GetPublicId(), ScopeId: staticStore.GetProjectId(), Scope: &scopepb.ScopeInfo{Id: staticStore.GetProjectId(), Type: scope.Project.String(), ParentScopeId: prj.GetParentId()}, - Type: credstatic.Subtype.String(), + Type: static.Subtype.String(), AuthorizedActions: testAuthorizedActions, AuthorizedCollectionActions: testAuthorizedStaticCollectionActions, CreatedTime: staticStore.CreateTime.GetTimestamp(), @@ -858,7 +972,7 @@ func TestGet(t *testing.T) { Id: staticStorePrev.GetPublicId(), ScopeId: staticStorePrev.GetProjectId(), Scope: &scopepb.ScopeInfo{Id: staticStorePrev.GetProjectId(), Type: scope.Project.String(), ParentScopeId: prj.GetParentId()}, - Type: credstatic.Subtype.String(), + Type: static.Subtype.String(), AuthorizedActions: testAuthorizedActions, AuthorizedCollectionActions: testAuthorizedStaticCollectionActions, CreatedTime: staticStorePrev.CreateTime.GetTimestamp(), @@ -933,15 +1047,18 @@ func TestDelete(t *testing.T) { vaultRepoFn := func() (*vault.Repository, error) { return vault.NewRepository(ctx, rw, rw, kms, sche) } - staticRepoFn := func() (*credstatic.Repository, error) { - return credstatic.NewRepository(ctx, rw, rw, kms) + staticRepoFn := func() (*static.Repository, error) { + return static.NewRepository(ctx, rw, rw, kms) + } + credStoreServiceFn := func() (*credential.StoreRepository, error) { + return credential.NewStoreRepository(context.Background(), rw, rw) } _, prj := iam.TestScopes(t, iamRepo) vaultStore := vault.TestCredentialStores(t, conn, wrapper, prj.GetPublicId(), 2)[0] - staticStore := credstatic.TestCredentialStore(t, conn, wrapper, prj.GetPublicId()) - s, err := NewService(ctx, vaultRepoFn, staticRepoFn, iamRepoFn) + staticStore := static.TestCredentialStore(t, conn, wrapper, prj.GetPublicId()) + s, err := NewService(ctx, iamRepoFn, vaultRepoFn, staticRepoFn, credStoreServiceFn, 1000) require.NoError(t, err) cases := []struct { @@ -1008,14 +1125,17 @@ func TestUpdateVault(t *testing.T) { vaultRepoFn := func() (*vault.Repository, error) { return vault.NewRepository(testCtx, rw, rw, kms, sche) } - staticRepoFn := func() (*credstatic.Repository, error) { - return credstatic.NewRepository(testCtx, rw, rw, kms) + staticRepoFn := func() (*static.Repository, error) { + return static.NewRepository(testCtx, rw, rw, kms) + } + credStoreServiceFn := func() (*credential.StoreRepository, error) { + return credential.NewStoreRepository(context.Background(), rw, rw) } _, prj := iam.TestScopes(t, iamRepo) ctx := auth.DisabledAuthTestContext(iamRepoFn, prj.GetPublicId()) - s, err := NewService(ctx, vaultRepoFn, staticRepoFn, iamRepoFn) + s, err := NewService(ctx, iamRepoFn, vaultRepoFn, staticRepoFn, credStoreServiceFn, 1000) require.NoError(t, err) fieldmask := func(paths ...string) *fieldmaskpb.FieldMask { @@ -1352,23 +1472,26 @@ func TestUpdateStatic(t *testing.T) { vaultRepoFn := func() (*vault.Repository, error) { return vault.NewRepository(testCtx, rw, rw, kms, sche) } - staticRepoFn := func() (*credstatic.Repository, error) { - return credstatic.NewRepository(testCtx, rw, rw, kms) + staticRepoFn := func() (*static.Repository, error) { + return static.NewRepository(testCtx, rw, rw, kms) + } + credStoreServiceFn := func() (*credential.StoreRepository, error) { + return credential.NewStoreRepository(context.Background(), rw, rw) } _, prj := iam.TestScopes(t, iamRepo) ctx := auth.DisabledAuthTestContext(iamRepoFn, prj.GetPublicId()) - s, err := NewService(ctx, vaultRepoFn, staticRepoFn, iamRepoFn) + s, err := NewService(ctx, iamRepoFn, vaultRepoFn, staticRepoFn, credStoreServiceFn, 1000) require.NoError(t, err) fieldmask := func(paths ...string) *fieldmaskpb.FieldMask { return &fieldmaskpb.FieldMask{Paths: paths} } - freshStore := func() (*credstatic.CredentialStore, func()) { + freshStore := func() (*static.CredentialStore, func()) { t.Helper() - st := credstatic.TestCredentialStore(t, conn, wrapper, prj.GetPublicId()) + st := static.TestCredentialStore(t, conn, wrapper, prj.GetPublicId()) clean := func() { _, err := s.DeleteCredentialStore(ctx, &pbs.DeleteCredentialStoreRequest{Id: st.GetPublicId()}) require.NoError(t, err) @@ -1522,3 +1645,319 @@ func TestUpdateStatic(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 + }) + assert, require := assert.New(t), require.New(t) + ctx := context.Background() + conn, _ := db.TestSetup(t, "postgres") + sqlDB, err := conn.SqlDB(ctx) + require.NoError(err) + wrapper := db.TestWrapper(t) + kms := kms.TestKms(t, conn, wrapper) + sche := scheduler.TestScheduler(t, conn, wrapper) + rw := db.New(conn) + + iamRepo := iam.TestRepo(t, conn, wrapper) + iamRepoFn := func() (*iam.Repository, error) { + return iamRepo, nil + } + vaultRepoFn := func() (*vault.Repository, error) { + return vault.NewRepository(ctx, rw, rw, kms, sche) + } + staticRepoFn := func() (*static.Repository, error) { + return static.NewRepository(ctx, rw, rw, kms) + } + tokenRepoFn := func() (*authtoken.Repository, error) { + return authtoken.NewRepository(ctx, rw, rw, kms) + } + serversRepoFn := func() (*server.Repository, error) { + return server.NewRepository(ctx, rw, rw, kms) + } + credStoreServiceFn := func() (*credential.StoreRepository, error) { + return credential.NewStoreRepository(context.Background(), rw, rw) + } + staticRepo, err := staticRepoFn() + require.NoError(err) + tokenRepo, err := tokenRepoFn() + require.NoError(err) + + _, prjNoStores := iam.TestScopes(t, iamRepo) + o, prj := iam.TestScopes(t, iamRepo) + + var allCredentialStores []*pb.CredentialStore + for _, l := range static.TestCredentialStores(t, conn, wrapper, prj.PublicId, 5) { + allCredentialStores = append(allCredentialStores, staticCredentialStoreToProto(l, prj)) + } + for _, l := range vault.TestCredentialStores(t, conn, wrapper, prj.PublicId, 5) { + allCredentialStores = append(allCredentialStores, vaultCredentialStoreToProto(l, prj)) + } + + // Reverse slice since we're sorting by create time descending + slices.Reverse(allCredentialStores) + + // Run analyze to update postgres meta tables + _, err = sqlDB.ExecContext(ctx, "analyze") + require.NoError(err) + + authMethod := password.TestAuthMethods(t, conn, o.GetPublicId(), 1)[0] + acct := password.TestAccount(t, conn, authMethod.GetPublicId(), "test_user") + u := iam.TestUser(t, iamRepo, o.GetPublicId(), iam.WithAccountIds(acct.PublicId)) + role1 := iam.TestRole(t, conn, prj.GetPublicId()) + iam.TestRoleGrant(t, conn, role1.GetPublicId(), "id=*;type=*;actions=*") + iam.TestUserRole(t, conn, role1.GetPublicId(), u.GetPublicId()) + role2 := iam.TestRole(t, conn, prjNoStores.GetPublicId()) + iam.TestRoleGrant(t, conn, role2.GetPublicId(), "id=*;type=*;actions=*") + iam.TestUserRole(t, conn, role2.GetPublicId(), u.GetPublicId()) + at, err := tokenRepo.CreateAuthToken(ctx, u, acct.GetPublicId()) + require.NoError(err) + + // Test without anon user + requestInfo := authpb.RequestInfo{ + TokenFormat: uint32(auth.AuthTokenTypeBearer), + PublicId: at.GetPublicId(), + Token: at.GetToken(), + } + requestContext := context.WithValue(context.Background(), requests.ContextRequestInformationKey, &requests.RequestContext{}) + ctx = auth.NewVerifierContext(requestContext, iamRepoFn, tokenRepoFn, serversRepoFn, kms, &requestInfo) + + s, err := NewService(ctx, iamRepoFn, vaultRepoFn, staticRepoFn, credStoreServiceFn, 1000) + require.NoError(err) + + // Start paginating, recursively + req := &pbs.ListCredentialStoresRequest{ + ScopeId: prj.PublicId, + Filter: "", + ListToken: "", + PageSize: 2, + } + got, err := s.ListCredentialStores(ctx, req) + require.NoError(err) + require.Len(got.GetItems(), 2) + // Compare without comparing the refresh token + assert.Empty( + cmp.Diff( + got, + &pbs.ListCredentialStoresResponse{ + Items: allCredentialStores[0:2], + ResponseType: "delta", + ListToken: "", + SortBy: "created_time", + SortDir: "desc", + RemovedIds: nil, + EstItemCount: 10, + }, + protocmp.Transform(), + protocmp.IgnoreFields(&pbs.ListCredentialStoresResponse{}, "list_token"), + ), + ) + + // Request second page + req.ListToken = got.ListToken + got, err = s.ListCredentialStores(ctx, req) + require.NoError(err) + require.Len(got.GetItems(), 2) + // Compare without comparing the refresh token + assert.Empty( + cmp.Diff( + got, + &pbs.ListCredentialStoresResponse{ + Items: allCredentialStores[2:4], + ResponseType: "delta", + ListToken: "", + SortBy: "created_time", + SortDir: "desc", + RemovedIds: nil, + EstItemCount: 10, + }, + protocmp.Transform(), + protocmp.IgnoreFields(&pbs.ListCredentialStoresResponse{}, "list_token"), + ), + ) + + // Request rest of results + req.ListToken = got.ListToken + req.PageSize = 10 + got, err = s.ListCredentialStores(ctx, req) + require.NoError(err) + require.Len(got.GetItems(), 6) + // Compare without comparing the refresh token + assert.Empty( + cmp.Diff( + got, + &pbs.ListCredentialStoresResponse{ + Items: allCredentialStores[4:], + ResponseType: "complete", + ListToken: "", + SortBy: "created_time", + SortDir: "desc", + RemovedIds: nil, + EstItemCount: 10, + }, + protocmp.Transform(), + protocmp.IgnoreFields(&pbs.ListCredentialStoresResponse{}, "list_token"), + ), + ) + + // Create another credential store + newCredStore := static.TestCredentialStore(t, conn, wrapper, prj.GetPublicId()) + pbNewCredStore := staticCredentialStoreToProto(newCredStore, prj) + // Add to the front since it's most recently updated + allCredentialStores = append([]*pb.CredentialStore{pbNewCredStore}, allCredentialStores...) + + // Delete one of the other credential stores + _, err = staticRepo.DeleteCredentialStore(ctx, allCredentialStores[len(allCredentialStores)-1].Id) + require.NoError(err) + deletedCredStore := allCredentialStores[len(allCredentialStores)-1] + allCredentialStores = allCredentialStores[:len(allCredentialStores)-1] + + // Update one of the other stores + allCredentialStores[len(allCredentialStores)-1].Name = wrapperspb.String("new-name") + allCredentialStores[len(allCredentialStores)-1].Version = 2 + updatedStore := &static.CredentialStore{ + CredentialStore: &store.CredentialStore{ + PublicId: allCredentialStores[len(allCredentialStores)-1].GetId(), + Name: allCredentialStores[len(allCredentialStores)-1].GetName().GetValue(), + ProjectId: allCredentialStores[len(allCredentialStores)-1].GetScopeId(), + }, + } + stre, _, err := staticRepo.UpdateCredentialStore(ctx, updatedStore, 1, []string{"name"}) + require.NoError(err) + allCredentialStores[len(allCredentialStores)-1].UpdatedTime = stre.CredentialStore.UpdateTime.GetTimestamp() + allCredentialStores[len(allCredentialStores)-1].Version = stre.GetVersion() + // Add to the front since it's most recently updated + allCredentialStores = append( + []*pb.CredentialStore{allCredentialStores[len(allCredentialStores)-1]}, + allCredentialStores[:len(allCredentialStores)-1]..., + ) + + // Run analyze to update postgres meta tables + _, err = sqlDB.ExecContext(ctx, "analyze") + require.NoError(err) + + // Request updated results + req.ListToken = got.ListToken + req.PageSize = 1 + got, err = s.ListCredentialStores(ctx, req) + require.NoError(err) + require.Len(got.GetItems(), 1) + // Compare without comparing the refresh token + assert.Empty( + cmp.Diff( + got, + &pbs.ListCredentialStoresResponse{ + Items: []*pb.CredentialStore{allCredentialStores[0]}, + ResponseType: "delta", + ListToken: "", + SortBy: "updated_time", + SortDir: "desc", + // Should contain the deleted session + RemovedIds: []string{deletedCredStore.Id}, + EstItemCount: 10, + }, + protocmp.Transform(), + protocmp.IgnoreFields(&pbs.ListCredentialStoresResponse{}, "list_token"), + ), + ) + // Get next page + req.ListToken = got.ListToken + got, err = s.ListCredentialStores(ctx, req) + require.NoError(err) + require.Len(got.GetItems(), 1) + // Compare without comparing the list token + assert.Empty( + cmp.Diff( + got, + &pbs.ListCredentialStoresResponse{ + Items: []*pb.CredentialStore{allCredentialStores[1]}, + ResponseType: "complete", + SortBy: "updated_time", + SortDir: "desc", + RemovedIds: nil, + EstItemCount: 10, + }, + protocmp.Transform(), + protocmp.IgnoreFields(&pbs.ListCredentialStoresResponse{}, "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`, allCredentialStores[len(allCredentialStores)-2].Id, allCredentialStores[len(allCredentialStores)-1].Id) + got, err = s.ListCredentialStores(ctx, req) + require.NoError(err) + require.Len(got.GetItems(), 1) + assert.Empty( + cmp.Diff( + got, + &pbs.ListCredentialStoresResponse{ + Items: []*pb.CredentialStore{allCredentialStores[len(allCredentialStores)-2]}, + ResponseType: "delta", + ListToken: "", + SortBy: "created_time", + SortDir: "desc", + // Should be empty again + RemovedIds: nil, + EstItemCount: 10, + }, + protocmp.Transform(), + protocmp.IgnoreFields(&pbs.ListCredentialStoresResponse{}, "list_token"), + ), + ) + req.ListToken = got.ListToken + // Get the second page + got, err = s.ListCredentialStores(ctx, req) + require.NoError(err) + require.Len(got.GetItems(), 1) + assert.Empty( + cmp.Diff( + got, + &pbs.ListCredentialStoresResponse{ + Items: []*pb.CredentialStore{allCredentialStores[len(allCredentialStores)-1]}, + ResponseType: "complete", + ListToken: "", + SortBy: "created_time", + SortDir: "desc", + RemovedIds: nil, + EstItemCount: 10, + }, + protocmp.Transform(), + protocmp.IgnoreFields(&pbs.ListCredentialStoresResponse{}, "list_token"), + ), + ) + req.ListToken = got.ListToken + + // List items in the empty scope + req = &pbs.ListCredentialStoresRequest{ + ScopeId: prjNoStores.PublicId, + Filter: "", + ListToken: "", + PageSize: 2, + } + got, err = s.ListCredentialStores(ctx, req) + require.NoError(err) + require.Len(got.GetItems(), 0) + // Compare without comparing the refresh token + assert.Empty( + cmp.Diff( + got, + &pbs.ListCredentialStoresResponse{ + Items: nil, + ResponseType: "complete", + ListToken: "", + SortBy: "created_time", + SortDir: "desc", + RemovedIds: nil, + }, + protocmp.Transform(), + protocmp.IgnoreFields(&pbs.ListCredentialStoresResponse{}, "list_token"), + ), + ) +} diff --git a/internal/gen/controller.swagger.json b/internal/gen/controller.swagger.json index f7e2b9007..9db30001b 100644 --- a/internal/gen/controller.swagger.json +++ b/internal/gen/controller.swagger.json @@ -845,6 +845,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 max page size configured,\nthe page size will be truncated to this number.", + "in": "query", + "required": false, + "type": "integer", + "format": "int64" } ], "tags": [ @@ -7646,7 +7661,36 @@ "items": { "type": "object", "$ref": "#/definitions/controller.api.resources.credentialstores.v1.CredentialStore" - } + }, + "description": "The items returned in this page." + }, + "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/credential_store_service.pb.go b/internal/gen/controller/api/services/credential_store_service.pb.go index 45a5a6bd6..13db99a7f 100644 --- a/internal/gen/controller/api/services/credential_store_service.pb.go +++ b/internal/gen/controller/api/services/credential_store_service.pb.go @@ -130,6 +130,15 @@ type ListCredentialStoresRequest struct { ScopeId string `protobuf:"bytes,1,opt,name=scope_id,proto3" json:"scope_id,omitempty" class:"public"` // @gotags: `class:"public"` Recursive bool `protobuf:"varint,20,opt,name=recursive,proto3" json:"recursive,omitempty" class:"public"` // @gotags: `class:"public"` Filter string `protobuf:"bytes,30,opt,name=filter,proto3" json:"filter,omitempty" class:"public"` // @gotags: `class:"public"` + // 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 max page size configured, + // the page size will be truncated to this number. + PageSize uint32 `protobuf:"varint,50,opt,name=page_size,proto3" json:"page_size,omitempty" class:"public"` // @gotags: `class:"public"` } func (x *ListCredentialStoresRequest) Reset() { @@ -185,12 +194,47 @@ func (x *ListCredentialStoresRequest) GetFilter() string { return "" } +func (x *ListCredentialStoresRequest) GetListToken() string { + if x != nil { + return x.ListToken + } + return "" +} + +func (x *ListCredentialStoresRequest) GetPageSize() uint32 { + if x != nil { + return x.PageSize + } + return 0 +} + type ListCredentialStoresResponse struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields + // The items returned in this page. Items []*credentialstores.CredentialStore `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 *ListCredentialStoresResponse) Reset() { @@ -232,6 +276,48 @@ func (x *ListCredentialStoresResponse) GetItems() []*credentialstores.Credential return nil } +func (x *ListCredentialStoresResponse) GetResponseType() string { + if x != nil { + return x.ResponseType + } + return "" +} + +func (x *ListCredentialStoresResponse) GetListToken() string { + if x != nil { + return x.ListToken + } + return "" +} + +func (x *ListCredentialStoresResponse) GetSortBy() string { + if x != nil { + return x.SortBy + } + return "" +} + +func (x *ListCredentialStoresResponse) GetSortDir() string { + if x != nil { + return x.SortDir + } + return "" +} + +func (x *ListCredentialStoresResponse) GetRemovedIds() []string { + if x != nil { + return x.RemovedIds + } + return nil +} + +func (x *ListCredentialStoresResponse) GetEstItemCount() uint32 { + if x != nil { + return x.EstItemCount + } + return 0 +} + type CreateCredentialStoreRequest struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -561,136 +647,153 @@ var file_controller_api_services_v1_credential_store_service_proto_rawDesc = []b 0x70, 0x69, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x2e, 0x63, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x53, 0x74, 0x6f, 0x72, 0x65, - 0x52, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x22, 0x6f, 0x0a, 0x1b, 0x4c, 0x69, 0x73, 0x74, 0x43, 0x72, - 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x53, 0x74, 0x6f, 0x72, 0x65, 0x73, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x73, 0x63, 0x6f, 0x70, 0x65, 0x5f, 0x69, - 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x73, 0x63, 0x6f, 0x70, 0x65, 0x5f, 0x69, - 0x64, 0x12, 0x1c, 0x0a, 0x09, 0x72, 0x65, 0x63, 0x75, 0x72, 0x73, 0x69, 0x76, 0x65, 0x18, 0x14, - 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x72, 0x65, 0x63, 0x75, 0x72, 0x73, 0x69, 0x76, 0x65, 0x12, - 0x16, 0x0a, 0x06, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x18, 0x1e, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x06, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x22, 0x73, 0x0a, 0x1c, 0x4c, 0x69, 0x73, 0x74, 0x43, + 0x52, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x22, 0xad, 0x01, 0x0a, 0x1b, 0x4c, 0x69, 0x73, 0x74, 0x43, + 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x53, 0x74, 0x6f, 0x72, 0x65, 0x73, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x73, 0x63, 0x6f, 0x70, 0x65, 0x5f, + 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x73, 0x63, 0x6f, 0x70, 0x65, 0x5f, + 0x69, 0x64, 0x12, 0x1c, 0x0a, 0x09, 0x72, 0x65, 0x63, 0x75, 0x72, 0x73, 0x69, 0x76, 0x65, 0x18, + 0x14, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x72, 0x65, 0x63, 0x75, 0x72, 0x73, 0x69, 0x76, 0x65, + 0x12, 0x16, 0x0a, 0x06, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x18, 0x1e, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x06, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x12, 0x1e, 0x0a, 0x0a, 0x6c, 0x69, 0x73, 0x74, + 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x28, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x6c, 0x69, + 0x73, 0x74, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x1c, 0x0a, 0x09, 0x70, 0x61, 0x67, 0x65, + 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x32, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x09, 0x70, 0x61, 0x67, + 0x65, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x22, 0xb9, 0x02, 0x0a, 0x1c, 0x4c, 0x69, 0x73, 0x74, 0x43, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x53, 0x74, 0x6f, 0x72, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x53, 0x0a, 0x05, 0x69, 0x74, 0x65, 0x6d, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x3d, 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, 0x63, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, - 0x53, 0x74, 0x6f, 0x72, 0x65, 0x52, 0x05, 0x69, 0x74, 0x65, 0x6d, 0x73, 0x22, 0x71, 0x0a, 0x1c, - 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x43, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, - 0x53, 0x74, 0x6f, 0x72, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x51, 0x0a, 0x04, - 0x69, 0x74, 0x65, 0x6d, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x3d, 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, 0x63, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, - 0x73, 0x74, 0x6f, 0x72, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, 0x65, 0x64, 0x65, 0x6e, - 0x74, 0x69, 0x61, 0x6c, 0x53, 0x74, 0x6f, 0x72, 0x65, 0x52, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x22, - 0x84, 0x01, 0x0a, 0x1d, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x43, 0x72, 0x65, 0x64, 0x65, 0x6e, - 0x74, 0x69, 0x61, 0x6c, 0x53, 0x74, 0x6f, 0x72, 0x65, 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, 0x51, 0x0a, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x3d, 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, 0x63, 0x72, 0x65, - 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x73, 0x2e, 0x76, 0x31, - 0x2e, 0x43, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x53, 0x74, 0x6f, 0x72, 0x65, - 0x52, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x22, 0xbf, 0x01, 0x0a, 0x1c, 0x55, 0x70, 0x64, 0x61, 0x74, - 0x65, 0x43, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x53, 0x74, 0x6f, 0x72, 0x65, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x51, 0x0a, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x18, - 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x3d, 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, 0x63, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x73, 0x74, 0x6f, 0x72, 0x65, - 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x53, - 0x74, 0x6f, 0x72, 0x65, 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, 0x72, 0x0a, 0x1d, 0x55, 0x70, 0x64, 0x61, - 0x74, 0x65, 0x43, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x53, 0x74, 0x6f, 0x72, - 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x51, 0x0a, 0x04, 0x69, 0x74, 0x65, - 0x6d, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x3d, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, + 0x53, 0x74, 0x6f, 0x72, 0x65, 0x52, 0x05, 0x69, 0x74, 0x65, 0x6d, 0x73, 0x12, 0x24, 0x0a, 0x0d, + 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x0d, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x5f, 0x74, 0x79, + 0x70, 0x65, 0x12, 0x1e, 0x0a, 0x0a, 0x6c, 0x69, 0x73, 0x74, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, + 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x6c, 0x69, 0x73, 0x74, 0x5f, 0x74, 0x6f, 0x6b, + 0x65, 0x6e, 0x12, 0x18, 0x0a, 0x07, 0x73, 0x6f, 0x72, 0x74, 0x5f, 0x62, 0x79, 0x18, 0x04, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x07, 0x73, 0x6f, 0x72, 0x74, 0x5f, 0x62, 0x79, 0x12, 0x1a, 0x0a, 0x08, + 0x73, 0x6f, 0x72, 0x74, 0x5f, 0x64, 0x69, 0x72, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, + 0x73, 0x6f, 0x72, 0x74, 0x5f, 0x64, 0x69, 0x72, 0x12, 0x20, 0x0a, 0x0b, 0x72, 0x65, 0x6d, 0x6f, + 0x76, 0x65, 0x64, 0x5f, 0x69, 0x64, 0x73, 0x18, 0x06, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0b, 0x72, + 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x64, 0x5f, 0x69, 0x64, 0x73, 0x12, 0x26, 0x0a, 0x0e, 0x65, 0x73, + 0x74, 0x5f, 0x69, 0x74, 0x65, 0x6d, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x07, 0x20, 0x01, + 0x28, 0x0d, 0x52, 0x0e, 0x65, 0x73, 0x74, 0x5f, 0x69, 0x74, 0x65, 0x6d, 0x5f, 0x63, 0x6f, 0x75, + 0x6e, 0x74, 0x22, 0x71, 0x0a, 0x1c, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x43, 0x72, 0x65, 0x64, + 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x53, 0x74, 0x6f, 0x72, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x12, 0x51, 0x0a, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x3d, 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, 0x63, 0x72, 0x65, 0x64, + 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, + 0x43, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x53, 0x74, 0x6f, 0x72, 0x65, 0x52, + 0x04, 0x69, 0x74, 0x65, 0x6d, 0x22, 0x84, 0x01, 0x0a, 0x1d, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, + 0x43, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x53, 0x74, 0x6f, 0x72, 0x65, 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, 0x51, 0x0a, 0x04, 0x69, 0x74, 0x65, + 0x6d, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x3d, 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, 0x63, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, - 0x6c, 0x53, 0x74, 0x6f, 0x72, 0x65, 0x52, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x22, 0x2e, 0x0a, 0x1c, - 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x43, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, - 0x53, 0x74, 0x6f, 0x72, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x0e, 0x0a, 0x02, - 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x22, 0x1f, 0x0a, 0x1d, - 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x43, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, - 0x53, 0x74, 0x6f, 0x72, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x32, 0xc9, 0x08, - 0x0a, 0x16, 0x43, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x53, 0x74, 0x6f, 0x72, - 0x65, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0xd1, 0x01, 0x0a, 0x12, 0x47, 0x65, 0x74, - 0x43, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x53, 0x74, 0x6f, 0x72, 0x65, 0x12, - 0x35, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, - 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, - 0x43, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x53, 0x74, 0x6f, 0x72, 0x65, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x36, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, - 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, - 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x43, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, - 0x6c, 0x53, 0x74, 0x6f, 0x72, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x4c, - 0x92, 0x41, 0x21, 0x12, 0x1f, 0x47, 0x65, 0x74, 0x73, 0x20, 0x61, 0x20, 0x73, 0x69, 0x6e, 0x67, - 0x6c, 0x65, 0x20, 0x43, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x20, 0x53, 0x74, - 0x6f, 0x72, 0x65, 0x2e, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x22, 0x62, 0x04, 0x69, 0x74, 0x65, 0x6d, - 0x12, 0x1a, 0x2f, 0x76, 0x31, 0x2f, 0x63, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, - 0x2d, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x73, 0x2f, 0x7b, 0x69, 0x64, 0x7d, 0x12, 0xc9, 0x01, 0x0a, - 0x14, 0x4c, 0x69, 0x73, 0x74, 0x43, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x53, - 0x74, 0x6f, 0x72, 0x65, 0x73, 0x12, 0x37, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, + 0x6c, 0x53, 0x74, 0x6f, 0x72, 0x65, 0x52, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x22, 0xbf, 0x01, 0x0a, + 0x1c, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x43, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, + 0x6c, 0x53, 0x74, 0x6f, 0x72, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x0e, 0x0a, + 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x51, 0x0a, + 0x04, 0x69, 0x74, 0x65, 0x6d, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x3d, 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, 0x63, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, + 0x6c, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, 0x65, 0x64, 0x65, + 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x53, 0x74, 0x6f, 0x72, 0x65, 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, 0x72, + 0x0a, 0x1d, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x43, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, + 0x61, 0x6c, 0x53, 0x74, 0x6f, 0x72, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, + 0x51, 0x0a, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x3d, 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, 0x63, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, + 0x69, 0x61, 0x6c, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, 0x65, + 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x53, 0x74, 0x6f, 0x72, 0x65, 0x52, 0x04, 0x69, 0x74, + 0x65, 0x6d, 0x22, 0x2e, 0x0a, 0x1c, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x43, 0x72, 0x65, 0x64, + 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x53, 0x74, 0x6f, 0x72, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, + 0x69, 0x64, 0x22, 0x1f, 0x0a, 0x1d, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x43, 0x72, 0x65, 0x64, + 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x53, 0x74, 0x6f, 0x72, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x32, 0xc9, 0x08, 0x0a, 0x16, 0x43, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, + 0x61, 0x6c, 0x53, 0x74, 0x6f, 0x72, 0x65, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0xd1, + 0x01, 0x0a, 0x12, 0x47, 0x65, 0x74, 0x43, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, + 0x53, 0x74, 0x6f, 0x72, 0x65, 0x12, 0x35, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, - 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x43, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, - 0x6c, 0x53, 0x74, 0x6f, 0x72, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x38, - 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, - 0x43, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x53, 0x74, 0x6f, 0x72, 0x65, 0x73, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x3e, 0x92, 0x41, 0x1e, 0x12, 0x1c, 0x4c, - 0x69, 0x73, 0x74, 0x73, 0x20, 0x61, 0x6c, 0x6c, 0x20, 0x43, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, - 0x69, 0x61, 0x6c, 0x20, 0x53, 0x74, 0x6f, 0x72, 0x65, 0x73, 0x2e, 0x82, 0xd3, 0xe4, 0x93, 0x02, - 0x17, 0x12, 0x15, 0x2f, 0x76, 0x31, 0x2f, 0x63, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, - 0x6c, 0x2d, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x73, 0x12, 0xde, 0x01, 0x0a, 0x15, 0x43, 0x72, 0x65, - 0x61, 0x74, 0x65, 0x43, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x53, 0x74, 0x6f, - 0x72, 0x65, 0x12, 0x38, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, + 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x43, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, + 0x53, 0x74, 0x6f, 0x72, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x36, 0x2e, 0x63, + 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x73, 0x65, + 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x43, 0x72, 0x65, + 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x53, 0x74, 0x6f, 0x72, 0x65, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x4c, 0x92, 0x41, 0x21, 0x12, 0x1f, 0x47, 0x65, 0x74, 0x73, 0x20, + 0x61, 0x20, 0x73, 0x69, 0x6e, 0x67, 0x6c, 0x65, 0x20, 0x43, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, + 0x69, 0x61, 0x6c, 0x20, 0x53, 0x74, 0x6f, 0x72, 0x65, 0x2e, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x22, + 0x62, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x12, 0x1a, 0x2f, 0x76, 0x31, 0x2f, 0x63, 0x72, 0x65, 0x64, + 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x2d, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x73, 0x2f, 0x7b, 0x69, + 0x64, 0x7d, 0x12, 0xc9, 0x01, 0x0a, 0x14, 0x4c, 0x69, 0x73, 0x74, 0x43, 0x72, 0x65, 0x64, 0x65, + 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x53, 0x74, 0x6f, 0x72, 0x65, 0x73, 0x12, 0x37, 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, 0x43, 0x72, 0x65, + 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x53, 0x74, 0x6f, 0x72, 0x65, 0x73, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x1a, 0x38, 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, 0x43, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, + 0x53, 0x74, 0x6f, 0x72, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x3e, + 0x92, 0x41, 0x1e, 0x12, 0x1c, 0x4c, 0x69, 0x73, 0x74, 0x73, 0x20, 0x61, 0x6c, 0x6c, 0x20, 0x43, + 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x20, 0x53, 0x74, 0x6f, 0x72, 0x65, 0x73, + 0x2e, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x17, 0x12, 0x15, 0x2f, 0x76, 0x31, 0x2f, 0x63, 0x72, 0x65, + 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x2d, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x73, 0x12, 0xde, + 0x01, 0x0a, 0x15, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x43, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, + 0x69, 0x61, 0x6c, 0x53, 0x74, 0x6f, 0x72, 0x65, 0x12, 0x38, 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, 0x43, 0x72, 0x65, 0x64, + 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x53, 0x74, 0x6f, 0x72, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x1a, 0x39, 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, 0x43, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, - 0x53, 0x74, 0x6f, 0x72, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x39, 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, - 0x43, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x53, 0x74, 0x6f, 0x72, 0x65, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x50, 0x92, 0x41, 0x24, 0x12, 0x22, 0x43, 0x72, - 0x65, 0x61, 0x74, 0x65, 0x73, 0x20, 0x61, 0x20, 0x73, 0x69, 0x6e, 0x67, 0x6c, 0x65, 0x20, 0x43, - 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x20, 0x53, 0x74, 0x6f, 0x72, 0x65, 0x2e, - 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x23, 0x3a, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x62, 0x04, 0x69, 0x74, - 0x65, 0x6d, 0x22, 0x15, 0x2f, 0x76, 0x31, 0x2f, 0x63, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, - 0x61, 0x6c, 0x2d, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x73, 0x12, 0xdc, 0x01, 0x0a, 0x15, 0x55, 0x70, - 0x64, 0x61, 0x74, 0x65, 0x43, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x53, 0x74, - 0x6f, 0x72, 0x65, 0x12, 0x38, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, + 0x53, 0x74, 0x6f, 0x72, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x50, 0x92, + 0x41, 0x24, 0x12, 0x22, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x73, 0x20, 0x61, 0x20, 0x73, 0x69, + 0x6e, 0x67, 0x6c, 0x65, 0x20, 0x43, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x20, + 0x53, 0x74, 0x6f, 0x72, 0x65, 0x2e, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x23, 0x3a, 0x04, 0x69, 0x74, + 0x65, 0x6d, 0x62, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x22, 0x15, 0x2f, 0x76, 0x31, 0x2f, 0x63, 0x72, + 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x2d, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x73, 0x12, + 0xdc, 0x01, 0x0a, 0x15, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x43, 0x72, 0x65, 0x64, 0x65, 0x6e, + 0x74, 0x69, 0x61, 0x6c, 0x53, 0x74, 0x6f, 0x72, 0x65, 0x12, 0x38, 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, 0x43, 0x72, 0x65, + 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x53, 0x74, 0x6f, 0x72, 0x65, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x1a, 0x39, 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, 0x43, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, - 0x6c, 0x53, 0x74, 0x6f, 0x72, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x39, 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, 0x43, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x53, 0x74, 0x6f, 0x72, 0x65, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x4e, 0x92, 0x41, 0x1d, 0x12, 0x1b, 0x55, - 0x70, 0x64, 0x61, 0x74, 0x65, 0x73, 0x20, 0x61, 0x20, 0x43, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, - 0x69, 0x61, 0x6c, 0x20, 0x53, 0x74, 0x6f, 0x72, 0x65, 0x2e, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x28, - 0x3a, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x62, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x32, 0x1a, 0x2f, 0x76, - 0x31, 0x2f, 0x63, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x2d, 0x73, 0x74, 0x6f, - 0x72, 0x65, 0x73, 0x2f, 0x7b, 0x69, 0x64, 0x7d, 0x12, 0xce, 0x01, 0x0a, 0x15, 0x44, 0x65, 0x6c, - 0x65, 0x74, 0x65, 0x43, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x53, 0x74, 0x6f, - 0x72, 0x65, 0x12, 0x38, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, + 0x6c, 0x53, 0x74, 0x6f, 0x72, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x4e, + 0x92, 0x41, 0x1d, 0x12, 0x1b, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x73, 0x20, 0x61, 0x20, 0x43, + 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x20, 0x53, 0x74, 0x6f, 0x72, 0x65, 0x2e, + 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x28, 0x3a, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x62, 0x04, 0x69, 0x74, + 0x65, 0x6d, 0x32, 0x1a, 0x2f, 0x76, 0x31, 0x2f, 0x63, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, + 0x61, 0x6c, 0x2d, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x73, 0x2f, 0x7b, 0x69, 0x64, 0x7d, 0x12, 0xce, + 0x01, 0x0a, 0x15, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x43, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, + 0x69, 0x61, 0x6c, 0x53, 0x74, 0x6f, 0x72, 0x65, 0x12, 0x38, 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, 0x43, 0x72, 0x65, 0x64, + 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x53, 0x74, 0x6f, 0x72, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x1a, 0x39, 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, 0x43, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, - 0x53, 0x74, 0x6f, 0x72, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x39, 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, - 0x43, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x53, 0x74, 0x6f, 0x72, 0x65, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x40, 0x92, 0x41, 0x1b, 0x12, 0x19, 0x44, 0x65, - 0x6c, 0x65, 0x74, 0x65, 0x73, 0x20, 0x61, 0x20, 0x43, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, - 0x61, 0x6c, 0x53, 0x74, 0x6f, 0x72, 0x65, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x1c, 0x2a, 0x1a, 0x2f, - 0x76, 0x31, 0x2f, 0x63, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x2d, 0x73, 0x74, - 0x6f, 0x72, 0x65, 0x73, 0x2f, 0x7b, 0x69, 0x64, 0x7d, 0x42, 0x5b, 0xa2, 0xe3, 0x29, 0x0a, 0x63, - 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 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, + 0x53, 0x74, 0x6f, 0x72, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x40, 0x92, + 0x41, 0x1b, 0x12, 0x19, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x73, 0x20, 0x61, 0x20, 0x43, 0x72, + 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x53, 0x74, 0x6f, 0x72, 0x65, 0x82, 0xd3, 0xe4, + 0x93, 0x02, 0x1c, 0x2a, 0x1a, 0x2f, 0x76, 0x31, 0x2f, 0x63, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, + 0x69, 0x61, 0x6c, 0x2d, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x73, 0x2f, 0x7b, 0x69, 0x64, 0x7d, 0x42, + 0x5b, 0xa2, 0xe3, 0x29, 0x0a, 0x63, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 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/credential_store_service.proto b/internal/proto/controller/api/services/v1/credential_store_service.proto index f28c2b646..b104e3acf 100644 --- a/internal/proto/controller/api/services/v1/credential_store_service.proto +++ b/internal/proto/controller/api/services/v1/credential_store_service.proto @@ -85,10 +85,40 @@ message ListCredentialStoresRequest { string scope_id = 1 [json_name = "scope_id"]; // @gotags: `class:"public"` bool recursive = 20 [json_name = "recursive"]; // @gotags: `class:"public"` string filter = 30 [json_name = "filter"]; // @gotags: `class:"public"` + // 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 max page size configured, + // the page size will be truncated to this number. + uint32 page_size = 50 [json_name = "page_size"]; // @gotags: `class:"public"` } message ListCredentialStoresResponse { + // The items returned in this page. repeated resources.credentialstores.v1.CredentialStore 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 CreateCredentialStoreRequest { diff --git a/internal/proto/controller/storage/credential/store/v1/credential.proto b/internal/proto/controller/storage/credential/store/v1/credential.proto index c354a30aa..3cddf7e36 100644 --- a/internal/proto/controller/storage/credential/store/v1/credential.proto +++ b/internal/proto/controller/storage/credential/store/v1/credential.proto @@ -6,8 +6,6 @@ syntax = "proto3"; // Package store provides protobufs for storing types in the credential package. package controller.storage.credential.store.v1; -import "controller/storage/timestamp/v1/timestamp.proto"; - option go_package = "github.com/hashicorp/boundary/internal/credential/store;store"; message Store { @@ -60,64 +58,3 @@ message Dynamic { // @inject_tag: `gorm:"not_null"` string library_id = 2; } - -message StoreListQueryResult { - // public_id is a surrogate key suitable for use in a public API. - // @inject_tag: `gorm:"primary_key"` - string public_id = 1; - - // The project_id of the owning project and must be set. - // @inject_tag: `gorm:"not_null"` - string project_id = 2; - - // Optional name of the credential store. - string name = 3; - - // Optional description of the credential store. - string description = 4; - - // Create time of the credential store. - timestamp.v1.Timestamp create_time = 5; - - // Update time of the credential store. - timestamp.v1.Timestamp update_time = 6; - - // Version of the credential store. - uint32 version = 7; - - // Optional delete time of the credential store. - timestamp.v1.Timestamp delete_time = 8; - - // Optional Vault address of the credential store. - string vault_address = 9; - - // Optional namespace of the credential store. - string namespace = 10; - - // Optional CA cert of the credential store. - bytes ca_cert = 11; - - // Optional TLS server name of the credential store. - string tls_server_name = 12; - - // Optionally specifices whether to skip TLS verification of the credential store. - bool tls_skip_verify = 13; - - // Optional worker filter of the credential store. - string worker_filter = 14; - - // Optional token HMAC of the credential store. - bytes token_hmac = 15; - - // Optional token status of the credential store. - string token_status = 16; - - // Optional client certificate of the credential store. - bytes client_cert = 17; - - // Optional client cert key HMAC of the credential store. - bytes client_cert_key_hmac = 18; - - // The subtype of the credential store. - string subtype = 19; -}