Auth token pagination (#4113)
* Add pagination support for auth tokens * change analyze count estimate comment * adjust queries for efficiency and security * even more efficient queries
This commit is contained in:
1 parent
48e8d454a4
commit
6ba3676c37
18 files changed
+2222
-176
No files matched your search
@@ -12,9 +12,11 @@ import (
|
||||
"github.com/hashicorp/boundary/globals"
|
||||
"github.com/hashicorp/boundary/internal/authtoken/store"
|
||||
"github.com/hashicorp/boundary/internal/db"
|
||||
"github.com/hashicorp/boundary/internal/db/timestamp"
|
||||
"github.com/hashicorp/boundary/internal/errors"
|
||||
"github.com/hashicorp/boundary/internal/gen/controller/tokens"
|
||||
"github.com/hashicorp/boundary/internal/kms"
|
||||
"github.com/hashicorp/boundary/internal/types/resource"
|
||||
wrapping "github.com/hashicorp/go-kms-wrapping/v2"
|
||||
"github.com/hashicorp/go-kms-wrapping/v2/extras/structwrapping"
|
||||
"github.com/hashicorp/go-secure-stdlib/base62"
|
||||
@@ -56,8 +58,8 @@ type AuthToken struct {
|
||||
tableName string `gorm:"-"`
|
||||
}
|
||||
|
||||
func (s *AuthToken) clone() *AuthToken {
|
||||
cp := proto.Clone(s.AuthToken)
|
||||
func (at *AuthToken) clone() *AuthToken {
|
||||
cp := proto.Clone(at.AuthToken)
|
||||
return &AuthToken{
|
||||
AuthToken: cp.(*store.AuthToken),
|
||||
}
|
||||
@@ -167,3 +169,44 @@ func EncryptToken(ctx context.Context, kmsCache *kms.Kms, scopeId, publicId, tok
|
||||
|
||||
return globals.ServiceTokenV1 + encoded, nil
|
||||
}
|
||||
|
||||
// GetResourceType returns the resource type of the AuthToken
|
||||
func (at AuthToken) GetResourceType() resource.Type {
|
||||
return resource.AuthToken
|
||||
}
|
||||
|
||||
func (at AuthToken) GetUpdateTime() *timestamp.Timestamp {
|
||||
return at.UpdateTime
|
||||
}
|
||||
|
||||
func (at AuthToken) GetCreateTime() *timestamp.Timestamp {
|
||||
return at.CreateTime
|
||||
}
|
||||
|
||||
// GetDescription returns an empty string so that
|
||||
// AuthToken will satisfy resource requirements
|
||||
func (at AuthToken) GetDescription() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
// GetName returns an empty string so that
|
||||
// AuthToken will satisfy resource requirements
|
||||
func (at AuthToken) GetName() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
// GetVersion returns 0 so that
|
||||
// AuthToken will satisfy resource requirements
|
||||
func (at AuthToken) GetVersion() uint32 {
|
||||
return 0
|
||||
}
|
||||
|
||||
type deletedAuthToken struct {
|
||||
PublicId string `gorm:"primary_key"`
|
||||
DeleteTime *timestamp.Timestamp
|
||||
}
|
||||
|
||||
// TableName returns the tablename to override the default gorm table name
|
||||
func (s *deletedAuthToken) TableName() string {
|
||||
return "auth_token_deleted"
|
||||
}
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
"github.com/hashicorp/boundary/internal/auth/password"
|
||||
"github.com/hashicorp/boundary/internal/db"
|
||||
"github.com/hashicorp/boundary/internal/iam"
|
||||
"github.com/hashicorp/boundary/internal/pagination"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -38,6 +39,7 @@ type options struct {
|
||||
withPublicId string
|
||||
withPasswordOptions []password.Option
|
||||
withIamOptions []iam.Option
|
||||
withStartPageAfterItem pagination.Item
|
||||
}
|
||||
|
||||
func getDefaultOptions() options {
|
||||
@@ -116,3 +118,11 @@ func WithIamOptions(with ...iam.Option) Option {
|
||||
o.withIamOptions = with
|
||||
}
|
||||
}
|
||||
|
||||
// WithStartPageAfterItem is used to paginate over the results.
|
||||
// The next page will start after the provided item.
|
||||
func WithStartPageAfterItem(item pagination.Item) Option {
|
||||
return func(o *options) {
|
||||
o.withStartPageAfterItem = item
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,182 @@
|
||||
// Copyright (c) HashiCorp, Inc.
|
||||
// SPDX-License-Identifier: BUSL-1.1
|
||||
|
||||
package authtoken
|
||||
|
||||
const (
|
||||
estimateCountAuthTokens = `
|
||||
select reltuples::bigint as estimate from pg_class where oid in ('auth_token'::regclass)
|
||||
`
|
||||
listAuthTokensTemplate = `
|
||||
with auth_tokens as (
|
||||
select public_id,
|
||||
auth_account_id,
|
||||
create_time,
|
||||
update_time,
|
||||
approximate_last_access_time,
|
||||
expiration_time,
|
||||
status
|
||||
from auth_token
|
||||
order by create_time desc, public_id asc
|
||||
limit %d
|
||||
),
|
||||
auth_accounts as (
|
||||
select public_id,
|
||||
auth_method_id,
|
||||
scope_id,
|
||||
iam_user_id,
|
||||
iam_user_scope_id
|
||||
from auth_account
|
||||
where %s
|
||||
and public_id in (select auth_account_id from auth_tokens)
|
||||
),
|
||||
final as (
|
||||
select at.public_id,
|
||||
at.auth_account_id,
|
||||
aa.auth_method_id,
|
||||
aa.scope_id,
|
||||
aa.iam_user_id,
|
||||
aa.iam_user_scope_id,
|
||||
at.create_time,
|
||||
at.update_time,
|
||||
at.approximate_last_access_time,
|
||||
at.expiration_time,
|
||||
at.status
|
||||
from auth_tokens at
|
||||
join auth_accounts aa on aa.public_id = at.auth_account_id
|
||||
)
|
||||
select *
|
||||
from final
|
||||
order by create_time desc, public_id asc;
|
||||
`
|
||||
listAuthTokensPageTemplate = `
|
||||
with auth_tokens as (
|
||||
select public_id,
|
||||
auth_account_id,
|
||||
create_time,
|
||||
update_time,
|
||||
approximate_last_access_time,
|
||||
expiration_time,
|
||||
status
|
||||
from auth_token
|
||||
where (create_time, public_id) < (@last_item_create_time, @last_item_id)
|
||||
order by create_time desc, public_id asc
|
||||
limit %d
|
||||
),
|
||||
auth_accounts as (
|
||||
select public_id,
|
||||
auth_method_id,
|
||||
scope_id,
|
||||
iam_user_id,
|
||||
iam_user_scope_id
|
||||
from auth_account
|
||||
where %s
|
||||
and public_id in (select auth_account_id from auth_tokens)
|
||||
),
|
||||
final as (
|
||||
select at.public_id,
|
||||
at.auth_account_id,
|
||||
aa.auth_method_id,
|
||||
aa.scope_id,
|
||||
aa.iam_user_id,
|
||||
aa.iam_user_scope_id,
|
||||
at.create_time,
|
||||
at.update_time,
|
||||
at.approximate_last_access_time,
|
||||
at.expiration_time,
|
||||
at.status
|
||||
from auth_tokens at
|
||||
join auth_accounts aa on aa.public_id = at.auth_account_id
|
||||
)
|
||||
select *
|
||||
from final
|
||||
order by create_time desc, public_id asc;
|
||||
`
|
||||
refreshAuthTokensTemplate = `
|
||||
with auth_tokens as (
|
||||
select public_id,
|
||||
auth_account_id,
|
||||
create_time,
|
||||
update_time,
|
||||
approximate_last_access_time,
|
||||
expiration_time,
|
||||
status
|
||||
from auth_token
|
||||
where update_time > @updated_after_time
|
||||
order by update_time desc, public_id asc
|
||||
limit %d
|
||||
),
|
||||
auth_accounts as (
|
||||
select public_id,
|
||||
auth_method_id,
|
||||
scope_id,
|
||||
iam_user_id,
|
||||
iam_user_scope_id
|
||||
from auth_account
|
||||
where %s
|
||||
and public_id in (select auth_account_id from auth_tokens)
|
||||
),
|
||||
final as (
|
||||
select at.public_id,
|
||||
at.auth_account_id,
|
||||
aa.auth_method_id,
|
||||
aa.scope_id,
|
||||
aa.iam_user_id,
|
||||
aa.iam_user_scope_id,
|
||||
at.create_time,
|
||||
at.update_time,
|
||||
at.approximate_last_access_time,
|
||||
at.expiration_time,
|
||||
at.status
|
||||
from auth_tokens at
|
||||
join auth_accounts aa on aa.public_id = at.auth_account_id
|
||||
)
|
||||
select *
|
||||
from final
|
||||
order by update_time desc, public_id asc;
|
||||
`
|
||||
refreshAuthTokensPageTemplate = `
|
||||
with auth_tokens as (
|
||||
select public_id,
|
||||
auth_account_id,
|
||||
create_time,
|
||||
update_time,
|
||||
approximate_last_access_time,
|
||||
expiration_time,
|
||||
status
|
||||
from auth_token
|
||||
where update_time > @updated_after_time
|
||||
and (update_time, public_id) < (@last_item_update_time, @last_item_id)
|
||||
order by update_time desc, public_id asc
|
||||
limit %d
|
||||
),
|
||||
auth_accounts as (
|
||||
select public_id,
|
||||
auth_method_id,
|
||||
scope_id,
|
||||
iam_user_id,
|
||||
iam_user_scope_id
|
||||
from auth_account
|
||||
where %s
|
||||
and public_id in (select auth_account_id from auth_tokens)
|
||||
),
|
||||
final as (
|
||||
select at.public_id,
|
||||
at.auth_account_id,
|
||||
aa.auth_method_id,
|
||||
aa.scope_id,
|
||||
aa.iam_user_id,
|
||||
aa.iam_user_scope_id,
|
||||
at.create_time,
|
||||
at.update_time,
|
||||
at.approximate_last_access_time,
|
||||
at.expiration_time,
|
||||
at.status
|
||||
from auth_tokens at
|
||||
join auth_accounts aa on aa.public_id = at.auth_account_id
|
||||
)
|
||||
select *
|
||||
from final
|
||||
order by update_time desc, public_id asc;
|
||||
`
|
||||
)
|
||||
@@ -5,7 +5,10 @@ package authtoken
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/golang/protobuf/ptypes"
|
||||
@@ -290,29 +293,163 @@ func (r *Repository) ValidateToken(ctx context.Context, id, token string, opt ..
|
||||
return retAT, nil
|
||||
}
|
||||
|
||||
// ListAuthTokens lists auth tokens in the given scopes and supports the
|
||||
// WithLimit option.
|
||||
func (r *Repository) ListAuthTokens(ctx context.Context, withScopeIds []string, opt ...Option) ([]*AuthToken, error) {
|
||||
const op = "authtoken.(Repository).ListAuthTokens"
|
||||
// listAuthTokens lists auth tokens in the given scopes and supports the
|
||||
// WithLimit and WithStartPageAfterItem options.
|
||||
func (r *Repository) listAuthTokens(ctx context.Context, withScopeIds []string, opt ...Option) ([]*AuthToken, time.Time, error) {
|
||||
const op = "authtoken.(Repository).listAuthTokens"
|
||||
if len(withScopeIds) == 0 {
|
||||
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing scope id")
|
||||
return nil, time.Time{}, errors.New(ctx, errors.InvalidParameter, op, "missing scope id")
|
||||
}
|
||||
opts := getOpts(opt...)
|
||||
|
||||
// use the view, to bring in the required account columns. Just don't forget
|
||||
// to convert them before returning them
|
||||
var atvs []*authTokenView
|
||||
if err := r.reader.SearchWhere(ctx, &atvs, "auth_account_id in (select public_id from auth_account where scope_id in (?))", []any{withScopeIds}, db.WithLimit(opts.withLimit)); err != nil {
|
||||
return nil, errors.Wrap(ctx, err, op)
|
||||
limit := r.limit
|
||||
if opts.withLimit != 0 {
|
||||
// non-zero signals an override of the default limit for the repo.
|
||||
limit = opts.withLimit
|
||||
}
|
||||
authTokens := make([]*AuthToken, 0, len(atvs))
|
||||
for _, atv := range atvs {
|
||||
atv.Token = ""
|
||||
atv.CtToken = nil
|
||||
atv.KeyId = ""
|
||||
authTokens = append(authTokens, atv.toAuthToken())
|
||||
|
||||
var args []any
|
||||
var inClauses []string
|
||||
for i, scopeId := range withScopeIds {
|
||||
arg := "scope_id_" + strconv.Itoa(i)
|
||||
inClauses = append(inClauses, "@"+arg)
|
||||
args = append(args, sql.Named(arg, scopeId))
|
||||
}
|
||||
return authTokens, nil
|
||||
inClause := strings.Join(inClauses, ", ")
|
||||
whereClause := "scope_id in (" + inClause + ")"
|
||||
|
||||
query := fmt.Sprintf(listAuthTokensTemplate, limit, whereClause)
|
||||
if opts.withStartPageAfterItem != nil {
|
||||
query = fmt.Sprintf(listAuthTokensPageTemplate, limit, whereClause)
|
||||
args = append(args,
|
||||
sql.Named("last_item_create_time", opts.withStartPageAfterItem.GetCreateTime()),
|
||||
sql.Named("last_item_id", opts.withStartPageAfterItem.GetPublicId()),
|
||||
)
|
||||
}
|
||||
|
||||
return r.queryAuthTokens(ctx, query, args, limit)
|
||||
}
|
||||
|
||||
// listAuthTokensRefresh lists auth tokens in the given scopes and supports the
|
||||
// WithLimit and WithStartPageAfterItem options.
|
||||
func (r *Repository) listAuthTokensRefresh(ctx context.Context, updatedAfter time.Time, withScopeIds []string, opt ...Option) ([]*AuthToken, time.Time, error) {
|
||||
const op = "authtoken.(Repository).listAuthTokensRefresh"
|
||||
|
||||
switch {
|
||||
case updatedAfter.IsZero():
|
||||
return nil, time.Time{}, errors.New(ctx, errors.InvalidParameter, op, "missing updated after time")
|
||||
|
||||
case len(withScopeIds) == 0:
|
||||
return nil, time.Time{}, errors.New(ctx, errors.InvalidParameter, op, "missing scope id")
|
||||
}
|
||||
|
||||
opts := getOpts(opt...)
|
||||
|
||||
limit := r.limit
|
||||
if opts.withLimit != 0 {
|
||||
// non-zero signals an override of the default limit for the repo.
|
||||
limit = opts.withLimit
|
||||
}
|
||||
|
||||
var args []any
|
||||
var inClauses []string
|
||||
for i, scopeId := range withScopeIds {
|
||||
arg := "scope_id_" + strconv.Itoa(i)
|
||||
inClauses = append(inClauses, "@"+arg)
|
||||
args = append(args, sql.Named(arg, scopeId))
|
||||
}
|
||||
inClause := strings.Join(inClauses, ", ")
|
||||
whereClause := "scope_id in (" + inClause + ")"
|
||||
|
||||
query := fmt.Sprintf(refreshAuthTokensTemplate, limit, whereClause)
|
||||
args = append(args,
|
||||
sql.Named("updated_after_time", timestamp.New(updatedAfter)),
|
||||
)
|
||||
if opts.withStartPageAfterItem != nil {
|
||||
query = fmt.Sprintf(refreshAuthTokensPageTemplate, limit, whereClause)
|
||||
args = append(args,
|
||||
sql.Named("last_item_update_time", opts.withStartPageAfterItem.GetUpdateTime()),
|
||||
sql.Named("last_item_id", opts.withStartPageAfterItem.GetPublicId()),
|
||||
)
|
||||
}
|
||||
|
||||
return r.queryAuthTokens(ctx, query, args, limit)
|
||||
}
|
||||
|
||||
func (r *Repository) queryAuthTokens(ctx context.Context, query string, args []any, limit int) ([]*AuthToken, time.Time, error) {
|
||||
const op = "authtoken.(Repository).queryAuthTokens"
|
||||
|
||||
var transactionTimestamp time.Time
|
||||
var authTokens []*AuthToken
|
||||
if _, err := r.writer.DoTx(ctx, db.StdRetryCnt, db.ExpBackoff{}, func(rd db.Reader, w db.Writer) error {
|
||||
rows, err := rd.Query(ctx, query, args)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
// use the view, to bring in the required account columns. Just don't forget
|
||||
// to convert them before returning them
|
||||
var atvs []*authTokenView
|
||||
for rows.Next() {
|
||||
var atv authTokenView
|
||||
if err := rd.ScanRows(ctx, rows, &atv); err != nil {
|
||||
return errors.Wrap(ctx, err, op, errors.WithMsg("scan row failed"))
|
||||
}
|
||||
atvs = append(atvs, &atv)
|
||||
}
|
||||
|
||||
authTokens = make([]*AuthToken, 0, len(atvs))
|
||||
for _, atv := range atvs {
|
||||
authTokens = append(authTokens, atv.toAuthToken())
|
||||
}
|
||||
transactionTimestamp, err = rd.Now(ctx)
|
||||
return err
|
||||
}); err != nil {
|
||||
return nil, time.Time{}, err
|
||||
}
|
||||
return authTokens, transactionTimestamp, nil
|
||||
}
|
||||
|
||||
// listDeletedIds lists the public IDs of any auth tokens deleted since the timestamp provided.
|
||||
func (r *Repository) listDeletedIds(ctx context.Context, since time.Time) ([]string, time.Time, error) {
|
||||
const op = "authtoken.(Repository).listDeletedIds"
|
||||
var deletedAuthTokens []*deletedAuthToken
|
||||
var transactionTimestamp time.Time
|
||||
if _, err := r.writer.DoTx(ctx, db.StdRetryCnt, db.ExpBackoff{}, func(r db.Reader, _ db.Writer) error {
|
||||
if err := r.SearchWhere(ctx, &deletedAuthTokens, "delete_time >= ?", []any{since}); err != nil {
|
||||
return errors.Wrap(ctx, err, op, errors.WithMsg("failed to query deleted auth tokens"))
|
||||
}
|
||||
var err error
|
||||
transactionTimestamp, err = r.Now(ctx)
|
||||
if err != nil {
|
||||
return errors.Wrap(ctx, err, op, errors.WithMsg("failed to get transaction timestamp"))
|
||||
}
|
||||
return nil
|
||||
}); err != nil {
|
||||
return nil, time.Time{}, err
|
||||
}
|
||||
var atIds []string
|
||||
for _, at := range deletedAuthTokens {
|
||||
atIds = append(atIds, at.PublicId)
|
||||
}
|
||||
return atIds, transactionTimestamp, nil
|
||||
}
|
||||
|
||||
// estimatedCount returns an estimate of the total number of items in the auth tokens table.
|
||||
func (r *Repository) estimatedCount(ctx context.Context) (int, error) {
|
||||
const op = "authtoken.(Repository).estimatedCount"
|
||||
rows, err := r.reader.Query(ctx, estimateCountAuthTokens, nil)
|
||||
if err != nil {
|
||||
return 0, errors.Wrap(ctx, err, op, errors.WithMsg("failed to query total auth tokens"))
|
||||
}
|
||||
var count int
|
||||
for rows.Next() {
|
||||
if err := r.reader.ScanRows(ctx, rows, &count); err != nil {
|
||||
return 0, errors.Wrap(ctx, err, op, errors.WithMsg("failed to query total auth tokens"))
|
||||
}
|
||||
}
|
||||
return count, nil
|
||||
}
|
||||
|
||||
// DeleteAuthToken deletes the token with the provided id from the repository returning a count of the
|
||||
|
||||
@@ -657,24 +657,28 @@ func TestRepository_ListAuthTokens(t *testing.T) {
|
||||
emptyOrg, _ := iam.TestScopes(t, repo)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
orgId string
|
||||
want []*AuthToken
|
||||
name string
|
||||
orgId string
|
||||
want []*AuthToken
|
||||
wantTTime time.Time
|
||||
}{
|
||||
{
|
||||
name: "populated",
|
||||
orgId: org.GetPublicId(),
|
||||
want: []*AuthToken{at1, at2, at3},
|
||||
name: "populated",
|
||||
orgId: org.GetPublicId(),
|
||||
want: []*AuthToken{at1, at2, at3},
|
||||
wantTTime: time.Now(),
|
||||
},
|
||||
{
|
||||
name: "empty",
|
||||
orgId: emptyOrg.GetPublicId(),
|
||||
want: []*AuthToken{},
|
||||
name: "empty",
|
||||
orgId: emptyOrg.GetPublicId(),
|
||||
want: []*AuthToken{},
|
||||
wantTTime: time.Now(),
|
||||
},
|
||||
{
|
||||
name: "empty-org-id",
|
||||
orgId: "",
|
||||
want: []*AuthToken{},
|
||||
name: "empty-org-id",
|
||||
orgId: "",
|
||||
want: []*AuthToken{},
|
||||
wantTTime: time.Now(),
|
||||
},
|
||||
}
|
||||
|
||||
@@ -685,13 +689,109 @@ func TestRepository_ListAuthTokens(t *testing.T) {
|
||||
repo, err := NewRepository(ctx, rw, rw, kms)
|
||||
require.NoError(err)
|
||||
require.NotNil(repo)
|
||||
got, err := repo.ListAuthTokens(ctx, []string{tt.orgId})
|
||||
got, ttime, err := repo.listAuthTokens(ctx, []string{tt.orgId})
|
||||
assert.NoError(err)
|
||||
sort.Slice(tt.want, func(i, j int) bool { return tt.want[i].PublicId < tt.want[j].PublicId })
|
||||
sort.Slice(got, func(i, j int) bool { return got[i].PublicId < got[j].PublicId })
|
||||
assert.Empty(cmp.Diff(tt.want, got, protocmp.Transform()), "row count")
|
||||
// Transaction timestamp should be within ~10 seconds of now
|
||||
assert.True(tt.wantTTime.Before(ttime.Add(10 * time.Second)))
|
||||
assert.True(tt.wantTTime.After(ttime.Add(-10 * time.Second)))
|
||||
})
|
||||
}
|
||||
t.Run("withStartPageAfter", func(t *testing.T) {
|
||||
assert, require := assert.New(t), require.New(t)
|
||||
|
||||
for i := 0; i < 7; i++ {
|
||||
at := TestAuthToken(t, conn, kms, org.GetPublicId())
|
||||
at.Token = ""
|
||||
at.KeyId = ""
|
||||
}
|
||||
|
||||
repo, err := NewRepository(ctx, rw, rw, kms)
|
||||
require.NoError(err)
|
||||
page1, ttime, err := repo.listAuthTokens(ctx, []string{org.GetPublicId()}, WithLimit(2))
|
||||
require.NoError(err)
|
||||
require.Len(page1, 2)
|
||||
// Transaction timestamp should be within ~10 seconds of now
|
||||
assert.True(time.Now().Before(ttime.Add(10 * time.Second)))
|
||||
assert.True(time.Now().After(ttime.Add(-10 * time.Second)))
|
||||
page2, ttime, err := repo.listAuthTokens(ctx, []string{org.GetPublicId()}, WithLimit(2), WithStartPageAfterItem(page1[1]))
|
||||
require.NoError(err)
|
||||
require.Len(page2, 2)
|
||||
assert.True(time.Now().Before(ttime.Add(10 * time.Second)))
|
||||
assert.True(time.Now().After(ttime.Add(-10 * time.Second)))
|
||||
for _, item := range page1 {
|
||||
assert.NotEqual(item.GetPublicId(), page2[0].GetPublicId())
|
||||
assert.NotEqual(item.GetPublicId(), page2[1].GetPublicId())
|
||||
}
|
||||
page3, ttime, err := repo.listAuthTokens(ctx, []string{org.GetPublicId()}, WithLimit(2), WithStartPageAfterItem(page2[1]))
|
||||
require.NoError(err)
|
||||
require.Len(page3, 2)
|
||||
assert.True(time.Now().Before(ttime.Add(10 * time.Second)))
|
||||
assert.True(time.Now().After(ttime.Add(-10 * time.Second)))
|
||||
for _, item := range page2 {
|
||||
assert.NotEqual(item.GetPublicId(), page3[0].GetPublicId())
|
||||
assert.NotEqual(item.GetPublicId(), page3[1].GetPublicId())
|
||||
}
|
||||
page4, ttime, err := repo.listAuthTokens(ctx, []string{org.GetPublicId()}, WithLimit(2), WithStartPageAfterItem(page3[1]))
|
||||
require.NoError(err)
|
||||
require.Len(page4, 2)
|
||||
assert.True(time.Now().Before(ttime.Add(10 * time.Second)))
|
||||
assert.True(time.Now().After(ttime.Add(-10 * time.Second)))
|
||||
for _, item := range page3 {
|
||||
assert.NotEqual(item.GetPublicId(), page4[0].GetPublicId())
|
||||
assert.NotEqual(item.GetPublicId(), page4[1].GetPublicId())
|
||||
}
|
||||
page5, ttime, err := repo.listAuthTokens(ctx, []string{org.GetPublicId()}, WithLimit(2), WithStartPageAfterItem(page4[1]))
|
||||
require.NoError(err)
|
||||
require.Len(page5, 2)
|
||||
assert.True(time.Now().Before(ttime.Add(10 * time.Second)))
|
||||
assert.True(time.Now().After(ttime.Add(-10 * time.Second)))
|
||||
for _, item := range page4 {
|
||||
assert.NotEqual(item.GetPublicId(), page5[0].GetPublicId())
|
||||
assert.NotEqual(item.GetPublicId(), page5[1].GetPublicId())
|
||||
}
|
||||
page6, ttime, err := repo.listAuthTokens(ctx, []string{org.GetPublicId()}, WithLimit(2), WithStartPageAfterItem(page5[1]))
|
||||
require.NoError(err)
|
||||
require.Empty(page6)
|
||||
assert.True(time.Now().Before(ttime.Add(10 * time.Second)))
|
||||
assert.True(time.Now().After(ttime.Add(-10 * time.Second)))
|
||||
|
||||
// Create 2 new auth tokens
|
||||
newAt1 := TestAuthToken(t, conn, kms, org.GetPublicId())
|
||||
newAt1.Token = ""
|
||||
newAt1.KeyId = ""
|
||||
newAt2 := TestAuthToken(t, conn, kms, org.GetPublicId())
|
||||
newAt2.Token = ""
|
||||
newAt2.KeyId = ""
|
||||
|
||||
// since it will return newest to oldest, we get page1[1] first
|
||||
page7, ttime, err := repo.listAuthTokensRefresh(
|
||||
ctx,
|
||||
time.Now().Add(-1*time.Second),
|
||||
[]string{org.GetPublicId()},
|
||||
WithLimit(1),
|
||||
)
|
||||
require.NoError(err)
|
||||
require.Len(page7, 1)
|
||||
require.Equal(page7[0].GetPublicId(), newAt2.GetPublicId())
|
||||
assert.True(time.Now().Before(ttime.Add(10 * time.Second)))
|
||||
assert.True(time.Now().After(ttime.Add(-10 * time.Second)))
|
||||
|
||||
page8, ttime, err := repo.listAuthTokensRefresh(
|
||||
context.Background(),
|
||||
time.Now().Add(-1*time.Second),
|
||||
[]string{org.GetPublicId()},
|
||||
WithLimit(1),
|
||||
WithStartPageAfterItem(page7[0]),
|
||||
)
|
||||
require.NoError(err)
|
||||
require.Len(page8, 1)
|
||||
require.Equal(page8[0].GetPublicId(), newAt1.GetPublicId())
|
||||
assert.True(time.Now().Before(ttime.Add(10 * time.Second)))
|
||||
assert.True(time.Now().After(ttime.Add(-10 * time.Second)))
|
||||
})
|
||||
}
|
||||
|
||||
func TestRepository_ListAuthTokens_Multiple_Scopes(t *testing.T) {
|
||||
@@ -715,9 +815,11 @@ func TestRepository_ListAuthTokens_Multiple_Scopes(t *testing.T) {
|
||||
total++
|
||||
}
|
||||
|
||||
got, err := repo.ListAuthTokens(ctx, []string{"global", org.GetPublicId(), proj.GetPublicId()})
|
||||
got, ttime, err := repo.listAuthTokens(ctx, []string{"global", org.GetPublicId(), proj.GetPublicId()})
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, total, len(got))
|
||||
assert.Equal(t, total, len(got)) // Transaction timestamp should be within ~10 seconds of now
|
||||
assert.True(t, time.Now().Before(ttime.Add(10*time.Second)))
|
||||
assert.True(t, time.Now().After(ttime.Add(-10*time.Second)))
|
||||
}
|
||||
|
||||
func Test_IssuePendingToken(t *testing.T) {
|
||||
@@ -880,3 +982,94 @@ func Test_CloseExpiredPendingTokens(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_listDeletedIds(t *testing.T) {
|
||||
t.Parallel()
|
||||
ctx := context.Background()
|
||||
conn, _ := db.TestSetup(t, "postgres")
|
||||
rw := db.New(conn)
|
||||
wrapper := db.TestWrapper(t)
|
||||
kms := kms.TestKms(t, conn, wrapper)
|
||||
iamRepo := iam.TestRepo(t, conn, wrapper)
|
||||
org, _ := iam.TestScopes(t, iamRepo)
|
||||
at := TestAuthToken(t, conn, kms, org.GetPublicId())
|
||||
at.Token = ""
|
||||
at.KeyId = ""
|
||||
repo, err := NewRepository(ctx, rw, rw, kms)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Expect no entries at the start
|
||||
deletedIds, ttime, err := repo.listDeletedIds(ctx, time.Now().AddDate(-1, 0, 0))
|
||||
require.NoError(t, err)
|
||||
require.Empty(t, deletedIds)
|
||||
// Transaction timestamp should be within ~10 seconds of now
|
||||
assert.True(t, time.Now().Before(ttime.Add(10*time.Second)))
|
||||
assert.True(t, time.Now().After(ttime.Add(-10*time.Second)))
|
||||
|
||||
// Delete an auth token
|
||||
_, err = repo.DeleteAuthToken(ctx, at.GetPublicId())
|
||||
require.NoError(t, err)
|
||||
|
||||
// Expect a single entry
|
||||
deletedIds, ttime, err = repo.listDeletedIds(ctx, time.Now().AddDate(-1, 0, 0))
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, []string{at.GetPublicId()}, deletedIds)
|
||||
// Transaction timestamp should be within ~10 seconds of now
|
||||
assert.True(t, time.Now().Before(ttime.Add(10*time.Second)))
|
||||
assert.True(t, time.Now().After(ttime.Add(-10*time.Second)))
|
||||
|
||||
// Try again with the time set to now, expect no entries
|
||||
deletedIds, ttime, err = repo.listDeletedIds(ctx, time.Now())
|
||||
require.NoError(t, err)
|
||||
require.Empty(t, deletedIds)
|
||||
// Transaction timestamp should be within ~10 seconds of now
|
||||
assert.True(t, time.Now().Before(ttime.Add(10*time.Second)))
|
||||
assert.True(t, time.Now().After(ttime.Add(-10*time.Second)))
|
||||
}
|
||||
|
||||
func Test_estimatedCount(t *testing.T) {
|
||||
t.Parallel()
|
||||
ctx := context.Background()
|
||||
conn, _ := db.TestSetup(t, "postgres")
|
||||
sqlDb, err := conn.SqlDB(ctx)
|
||||
require.NoError(t, err)
|
||||
rw := db.New(conn)
|
||||
wrapper := db.TestWrapper(t)
|
||||
kms := kms.TestKms(t, conn, wrapper)
|
||||
repo, err := NewRepository(ctx, rw, rw, kms)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Run analyze to update estimate
|
||||
_, err = sqlDb.ExecContext(ctx, "analyze")
|
||||
require.NoError(t, err)
|
||||
|
||||
// Check total entries at start, expect 0
|
||||
numItems, err := repo.estimatedCount(ctx)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, 0, numItems)
|
||||
|
||||
iamRepo := iam.TestRepo(t, conn, wrapper)
|
||||
org, _ := iam.TestScopes(t, iamRepo)
|
||||
// Create an auth token, expect 1 entry
|
||||
at := TestAuthToken(t, conn, kms, org.GetPublicId())
|
||||
at.Token = ""
|
||||
at.KeyId = ""
|
||||
|
||||
// Run analyze to update estimate
|
||||
_, err = sqlDb.ExecContext(ctx, "analyze")
|
||||
require.NoError(t, err)
|
||||
numItems, err = repo.estimatedCount(ctx)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, 1, numItems)
|
||||
|
||||
// Delete the auth token, expect 0 again
|
||||
_, err = repo.DeleteAuthToken(ctx, at.GetPublicId())
|
||||
require.NoError(t, err)
|
||||
|
||||
// Run analyze to update estimate
|
||||
_, err = sqlDb.ExecContext(ctx, "analyze")
|
||||
require.NoError(t, err)
|
||||
numItems, err = repo.estimatedCount(ctx)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, 0, numItems)
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
// Copyright (c) HashiCorp, Inc.
|
||||
// SPDX-License-Identifier: BUSL-1.1
|
||||
|
||||
package authtoken
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/hashicorp/boundary/internal/errors"
|
||||
"github.com/hashicorp/boundary/internal/pagination"
|
||||
)
|
||||
|
||||
// List lists up to page size auth tokens, filtering out entries that
|
||||
// do not pass the filter item function. It will automatically request
|
||||
// more auth tokens from the database, at page size chunks, to fill the page.
|
||||
// It returns a new list token used to continue pagination or refresh items.
|
||||
// Auth tokens are ordered by create time descending (most recently created first).
|
||||
func List(
|
||||
ctx context.Context,
|
||||
grantsHash []byte,
|
||||
pageSize int,
|
||||
filterItemFn pagination.ListFilterFunc[*AuthToken],
|
||||
repo *Repository,
|
||||
withScopeIds []string,
|
||||
) (*pagination.ListResponse[*AuthToken], error) {
|
||||
const op = "authtoken.List"
|
||||
|
||||
switch {
|
||||
case len(grantsHash) == 0:
|
||||
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing grants hash")
|
||||
case pageSize < 1:
|
||||
return nil, errors.New(ctx, errors.InvalidParameter, op, "page size must be at least 1")
|
||||
case filterItemFn == nil:
|
||||
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing filter item callback")
|
||||
case repo == nil:
|
||||
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing repo")
|
||||
case withScopeIds == nil:
|
||||
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing scope ids")
|
||||
}
|
||||
|
||||
listItemsFn := func(ctx context.Context, lastPageItem *AuthToken, limit int) ([]*AuthToken, time.Time, error) {
|
||||
opts := []Option{
|
||||
WithLimit(limit),
|
||||
}
|
||||
if lastPageItem != nil {
|
||||
opts = append(opts, WithStartPageAfterItem(lastPageItem))
|
||||
}
|
||||
return repo.listAuthTokens(ctx, withScopeIds, opts...)
|
||||
}
|
||||
|
||||
return pagination.List(ctx, grantsHash, pageSize, filterItemFn, listItemsFn, repo.estimatedCount)
|
||||
}
|
||||
@@ -0,0 +1,587 @@
|
||||
// Copyright (c) HashiCorp, Inc.
|
||||
// SPDX-License-Identifier: BUSL-1.1
|
||||
|
||||
package authtoken_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"slices"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/google/go-cmp/cmp/cmpopts"
|
||||
"github.com/hashicorp/boundary/globals"
|
||||
"github.com/hashicorp/boundary/internal/authtoken"
|
||||
"github.com/hashicorp/boundary/internal/authtoken/store"
|
||||
"github.com/hashicorp/boundary/internal/db"
|
||||
"github.com/hashicorp/boundary/internal/db/timestamp"
|
||||
"github.com/hashicorp/boundary/internal/iam"
|
||||
"github.com/hashicorp/boundary/internal/kms"
|
||||
"github.com/hashicorp/boundary/internal/listtoken"
|
||||
"github.com/hashicorp/boundary/internal/types/resource"
|
||||
"github.com/stretchr/testify/require"
|
||||
"google.golang.org/protobuf/types/known/timestamppb"
|
||||
)
|
||||
|
||||
func TestService_List(t *testing.T) {
|
||||
fiveDaysAgo := time.Now()
|
||||
// Set database read timeout to avoid duplicates in response
|
||||
oldReadTimeout := globals.RefreshReadLookbackDuration
|
||||
globals.RefreshReadLookbackDuration = 0
|
||||
t.Cleanup(func() {
|
||||
globals.RefreshReadLookbackDuration = oldReadTimeout
|
||||
})
|
||||
ctx := context.Background()
|
||||
conn, _ := db.TestSetup(t, "postgres")
|
||||
sqlDB, err := conn.SqlDB(context.Background())
|
||||
require.NoError(t, err)
|
||||
rw := db.New(conn)
|
||||
wrapper := db.TestWrapper(t)
|
||||
kms := kms.TestKms(t, conn, wrapper)
|
||||
iamRepo := iam.TestRepo(t, conn, wrapper)
|
||||
org, _ := iam.TestScopes(t, iamRepo)
|
||||
|
||||
var allTokens []*authtoken.AuthToken
|
||||
for i := 0; i < 5; i++ {
|
||||
at := authtoken.TestAuthToken(t, conn, kms, org.GetPublicId())
|
||||
at.Token = ""
|
||||
at.KeyId = ""
|
||||
allTokens = append(allTokens, at)
|
||||
}
|
||||
|
||||
repo, err := authtoken.NewRepository(ctx, rw, rw, kms)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Reverse since we read items in descending order (newest first)
|
||||
slices.Reverse(allTokens)
|
||||
|
||||
// Run analyze to update postgres estimates
|
||||
_, err = sqlDB.ExecContext(ctx, "analyze")
|
||||
require.NoError(t, err)
|
||||
|
||||
cmpIgnoreUnexportedOpts := cmpopts.IgnoreUnexported(authtoken.AuthToken{}, store.AuthToken{}, timestamp.Timestamp{}, timestamppb.Timestamp{})
|
||||
cmpIgnoreFieldsOpts := cmpopts.IgnoreFields(authtoken.AuthToken{}, "Token", "KeyId")
|
||||
|
||||
t.Run("List validation", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
t.Run("missing grants hash", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
filterFunc := func(_ context.Context, at *authtoken.AuthToken) (bool, error) {
|
||||
return true, nil
|
||||
}
|
||||
_, err := authtoken.List(ctx, nil, 1, filterFunc, repo, []string{org.GetPublicId()})
|
||||
require.ErrorContains(t, err, "missing grants hash")
|
||||
})
|
||||
t.Run("zero page size", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
filterFunc := func(_ context.Context, at *authtoken.AuthToken) (bool, error) {
|
||||
return true, nil
|
||||
}
|
||||
_, err := authtoken.List(ctx, []byte("some hash"), 0, filterFunc, repo, []string{org.GetPublicId()})
|
||||
require.ErrorContains(t, err, "page size must be at least 1")
|
||||
})
|
||||
t.Run("negative page size", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
filterFunc := func(_ context.Context, at *authtoken.AuthToken) (bool, error) {
|
||||
return true, nil
|
||||
}
|
||||
_, err := authtoken.List(ctx, []byte("some hash"), -1, filterFunc, repo, []string{org.GetPublicId()})
|
||||
require.ErrorContains(t, err, "page size must be at least 1")
|
||||
})
|
||||
t.Run("nil filter func", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
_, err := authtoken.List(ctx, []byte("some hash"), 1, nil, repo, []string{org.GetPublicId()})
|
||||
require.ErrorContains(t, err, "missing filter item callback")
|
||||
})
|
||||
t.Run("nil repo", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
filterFunc := func(_ context.Context, at *authtoken.AuthToken) (bool, error) {
|
||||
return true, nil
|
||||
}
|
||||
_, err := authtoken.List(ctx, []byte("some hash"), 1, filterFunc, nil, []string{org.GetPublicId()})
|
||||
require.ErrorContains(t, err, "missing repo")
|
||||
})
|
||||
t.Run("missing scope ids", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
filterFunc := func(_ context.Context, at *authtoken.AuthToken) (bool, error) {
|
||||
return true, nil
|
||||
}
|
||||
_, err := authtoken.List(ctx, []byte("some hash"), 1, filterFunc, repo, nil)
|
||||
require.ErrorContains(t, err, "missing scope ids")
|
||||
})
|
||||
})
|
||||
t.Run("ListPage validation", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
t.Run("missing grants hash", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
filterFunc := func(_ context.Context, at *authtoken.AuthToken) (bool, error) {
|
||||
return true, nil
|
||||
}
|
||||
tok, err := listtoken.NewPagination(ctx, fiveDaysAgo, resource.AuthToken, []byte("some hash"), "some-id", fiveDaysAgo)
|
||||
require.NoError(t, err)
|
||||
_, err = authtoken.ListPage(ctx, nil, 1, filterFunc, tok, repo, []string{org.GetPublicId()})
|
||||
require.ErrorContains(t, err, "missing grants hash")
|
||||
})
|
||||
t.Run("zero page size", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
filterFunc := func(_ context.Context, at *authtoken.AuthToken) (bool, error) {
|
||||
return true, nil
|
||||
}
|
||||
tok, err := listtoken.NewPagination(ctx, fiveDaysAgo, resource.AuthToken, []byte("some hash"), "some-id", fiveDaysAgo)
|
||||
require.NoError(t, err)
|
||||
_, err = authtoken.ListPage(ctx, []byte("some hash"), 0, filterFunc, tok, repo, []string{org.GetPublicId()})
|
||||
require.ErrorContains(t, err, "page size must be at least 1")
|
||||
})
|
||||
t.Run("negative page size", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
filterFunc := func(_ context.Context, at *authtoken.AuthToken) (bool, error) {
|
||||
return true, nil
|
||||
}
|
||||
tok, err := listtoken.NewPagination(ctx, fiveDaysAgo, resource.AuthToken, []byte("some hash"), "some-id", fiveDaysAgo)
|
||||
require.NoError(t, err)
|
||||
_, err = authtoken.ListPage(ctx, []byte("some hash"), -1, filterFunc, tok, repo, []string{org.GetPublicId()})
|
||||
require.ErrorContains(t, err, "page size must be at least 1")
|
||||
})
|
||||
t.Run("nil filter func", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
tok, err := listtoken.NewPagination(ctx, fiveDaysAgo, resource.AuthToken, []byte("some hash"), "some-id", fiveDaysAgo)
|
||||
require.NoError(t, err)
|
||||
_, err = authtoken.ListPage(ctx, []byte("some hash"), 1, nil, tok, repo, []string{org.GetPublicId()})
|
||||
require.ErrorContains(t, err, "missing filter item callback")
|
||||
})
|
||||
t.Run("nil token", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
filterFunc := func(_ context.Context, at *authtoken.AuthToken) (bool, error) {
|
||||
return true, nil
|
||||
}
|
||||
_, err = authtoken.ListPage(ctx, []byte("some hash"), 1, filterFunc, nil, repo, []string{org.GetPublicId()})
|
||||
require.ErrorContains(t, err, "missing token")
|
||||
})
|
||||
t.Run("wrong token type", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
filterFunc := func(_ context.Context, at *authtoken.AuthToken) (bool, error) {
|
||||
return true, nil
|
||||
}
|
||||
tok, err := listtoken.NewStartRefresh(ctx, fiveDaysAgo, resource.AuthToken, []byte("some hash"), fiveDaysAgo, fiveDaysAgo)
|
||||
require.NoError(t, err)
|
||||
_, err = authtoken.ListPage(ctx, []byte("some hash"), 1, filterFunc, tok, repo, []string{org.GetPublicId()})
|
||||
require.ErrorContains(t, err, "token did not have a pagination token component")
|
||||
})
|
||||
t.Run("nil repo", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
filterFunc := func(_ context.Context, at *authtoken.AuthToken) (bool, error) {
|
||||
return true, nil
|
||||
}
|
||||
tok, err := listtoken.NewPagination(ctx, fiveDaysAgo, resource.AuthToken, []byte("some hash"), "some-id", fiveDaysAgo)
|
||||
require.NoError(t, err)
|
||||
_, err = authtoken.ListPage(ctx, []byte("some hash"), 1, filterFunc, tok, nil, []string{org.GetPublicId()})
|
||||
require.ErrorContains(t, err, "missing repo")
|
||||
})
|
||||
t.Run("missing scope ids", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
filterFunc := func(_ context.Context, at *authtoken.AuthToken) (bool, error) {
|
||||
return true, nil
|
||||
}
|
||||
tok, err := listtoken.NewPagination(ctx, fiveDaysAgo, resource.AuthToken, []byte("some hash"), "some-id", fiveDaysAgo)
|
||||
require.NoError(t, err)
|
||||
_, err = authtoken.ListPage(ctx, []byte("some hash"), 1, filterFunc, tok, repo, nil)
|
||||
require.ErrorContains(t, err, "missing scope ids")
|
||||
})
|
||||
})
|
||||
t.Run("ListRefresh validation", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
t.Run("missing grants hash", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
filterFunc := func(_ context.Context, at *authtoken.AuthToken) (bool, error) {
|
||||
return true, nil
|
||||
}
|
||||
tok, err := listtoken.NewStartRefresh(ctx, fiveDaysAgo, resource.AuthToken, []byte("some hash"), fiveDaysAgo, fiveDaysAgo)
|
||||
require.NoError(t, err)
|
||||
_, err = authtoken.ListRefresh(ctx, nil, 1, filterFunc, tok, repo, []string{org.GetPublicId()})
|
||||
require.ErrorContains(t, err, "missing grants hash")
|
||||
})
|
||||
t.Run("zero page size", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
filterFunc := func(_ context.Context, at *authtoken.AuthToken) (bool, error) {
|
||||
return true, nil
|
||||
}
|
||||
tok, err := listtoken.NewStartRefresh(ctx, fiveDaysAgo, resource.AuthToken, []byte("some hash"), fiveDaysAgo, fiveDaysAgo)
|
||||
require.NoError(t, err)
|
||||
_, err = authtoken.ListRefresh(ctx, []byte("some hash"), 0, filterFunc, tok, repo, []string{org.GetPublicId()})
|
||||
require.ErrorContains(t, err, "page size must be at least 1")
|
||||
})
|
||||
t.Run("negative page size", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
filterFunc := func(_ context.Context, at *authtoken.AuthToken) (bool, error) {
|
||||
return true, nil
|
||||
}
|
||||
tok, err := listtoken.NewStartRefresh(ctx, fiveDaysAgo, resource.AuthToken, []byte("some hash"), fiveDaysAgo, fiveDaysAgo)
|
||||
require.NoError(t, err)
|
||||
_, err = authtoken.ListRefresh(ctx, []byte("some hash"), -1, filterFunc, tok, repo, []string{org.GetPublicId()})
|
||||
require.ErrorContains(t, err, "page size must be at least 1")
|
||||
})
|
||||
t.Run("nil filter func", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
tok, err := listtoken.NewStartRefresh(ctx, fiveDaysAgo, resource.AuthToken, []byte("some hash"), fiveDaysAgo, fiveDaysAgo)
|
||||
require.NoError(t, err)
|
||||
_, err = authtoken.ListRefresh(ctx, []byte("some hash"), 1, nil, tok, repo, []string{org.GetPublicId()})
|
||||
require.ErrorContains(t, err, "missing filter item callback")
|
||||
})
|
||||
t.Run("nil token", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
filterFunc := func(_ context.Context, at *authtoken.AuthToken) (bool, error) {
|
||||
return true, nil
|
||||
}
|
||||
_, err = authtoken.ListRefresh(ctx, []byte("some hash"), 1, filterFunc, nil, repo, []string{org.GetPublicId()})
|
||||
require.ErrorContains(t, err, "missing token")
|
||||
})
|
||||
t.Run("wrong token type", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
filterFunc := func(_ context.Context, at *authtoken.AuthToken) (bool, error) {
|
||||
return true, nil
|
||||
}
|
||||
tok, err := listtoken.NewPagination(ctx, fiveDaysAgo, resource.AuthToken, []byte("some hash"), "some-id", fiveDaysAgo)
|
||||
require.NoError(t, err)
|
||||
_, err = authtoken.ListRefresh(ctx, []byte("some hash"), 1, filterFunc, tok, repo, []string{org.GetPublicId()})
|
||||
require.ErrorContains(t, err, "token did not have a start-refresh token component")
|
||||
})
|
||||
t.Run("nil repo", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
filterFunc := func(_ context.Context, at *authtoken.AuthToken) (bool, error) {
|
||||
return true, nil
|
||||
}
|
||||
tok, err := listtoken.NewStartRefresh(ctx, fiveDaysAgo, resource.AuthToken, []byte("some hash"), fiveDaysAgo, fiveDaysAgo)
|
||||
require.NoError(t, err)
|
||||
_, err = authtoken.ListRefresh(ctx, []byte("some hash"), 1, filterFunc, tok, nil, []string{org.GetPublicId()})
|
||||
require.ErrorContains(t, err, "missing repo")
|
||||
})
|
||||
t.Run("missing scope ids", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
filterFunc := func(_ context.Context, at *authtoken.AuthToken) (bool, error) {
|
||||
return true, nil
|
||||
}
|
||||
tok, err := listtoken.NewStartRefresh(ctx, fiveDaysAgo, resource.AuthToken, []byte("some hash"), fiveDaysAgo, fiveDaysAgo)
|
||||
require.NoError(t, err)
|
||||
_, err = authtoken.ListRefresh(ctx, []byte("some hash"), 1, filterFunc, tok, repo, nil)
|
||||
require.ErrorContains(t, err, "missing scope ids")
|
||||
})
|
||||
})
|
||||
t.Run("ListRefreshPage validation", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
t.Run("missing grants hash", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
filterFunc := func(_ context.Context, at *authtoken.AuthToken) (bool, error) {
|
||||
return true, nil
|
||||
}
|
||||
tok, err := listtoken.NewRefresh(ctx, fiveDaysAgo, resource.AuthToken, []byte("some hash"), fiveDaysAgo, fiveDaysAgo, fiveDaysAgo, "some other id", fiveDaysAgo)
|
||||
require.NoError(t, err)
|
||||
_, err = authtoken.ListRefreshPage(ctx, nil, 1, filterFunc, tok, repo, []string{org.GetPublicId()})
|
||||
require.ErrorContains(t, err, "missing grants hash")
|
||||
})
|
||||
t.Run("zero page size", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
filterFunc := func(_ context.Context, at *authtoken.AuthToken) (bool, error) {
|
||||
return true, nil
|
||||
}
|
||||
tok, err := listtoken.NewRefresh(ctx, fiveDaysAgo, resource.AuthToken, []byte("some hash"), fiveDaysAgo, fiveDaysAgo, fiveDaysAgo, "some other id", fiveDaysAgo)
|
||||
require.NoError(t, err)
|
||||
_, err = authtoken.ListRefreshPage(ctx, []byte("some hash"), 0, filterFunc, tok, repo, []string{org.GetPublicId()})
|
||||
require.ErrorContains(t, err, "page size must be at least 1")
|
||||
})
|
||||
t.Run("negative page size", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
filterFunc := func(_ context.Context, at *authtoken.AuthToken) (bool, error) {
|
||||
return true, nil
|
||||
}
|
||||
tok, err := listtoken.NewRefresh(ctx, fiveDaysAgo, resource.AuthToken, []byte("some hash"), fiveDaysAgo, fiveDaysAgo, fiveDaysAgo, "some other id", fiveDaysAgo)
|
||||
require.NoError(t, err)
|
||||
_, err = authtoken.ListRefreshPage(ctx, []byte("some hash"), -1, filterFunc, tok, repo, []string{org.GetPublicId()})
|
||||
require.ErrorContains(t, err, "page size must be at least 1")
|
||||
})
|
||||
t.Run("nil filter func", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
tok, err := listtoken.NewRefresh(ctx, fiveDaysAgo, resource.AuthToken, []byte("some hash"), fiveDaysAgo, fiveDaysAgo, fiveDaysAgo, "some other id", fiveDaysAgo)
|
||||
require.NoError(t, err)
|
||||
_, err = authtoken.ListRefreshPage(ctx, []byte("some hash"), 1, nil, tok, repo, []string{org.GetPublicId()})
|
||||
require.ErrorContains(t, err, "missing filter item callback")
|
||||
})
|
||||
t.Run("nil token", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
filterFunc := func(_ context.Context, at *authtoken.AuthToken) (bool, error) {
|
||||
return true, nil
|
||||
}
|
||||
_, err = authtoken.ListRefreshPage(ctx, []byte("some hash"), 1, filterFunc, nil, repo, []string{org.GetPublicId()})
|
||||
require.ErrorContains(t, err, "missing token")
|
||||
})
|
||||
t.Run("wrong token type", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
filterFunc := func(_ context.Context, at *authtoken.AuthToken) (bool, error) {
|
||||
return true, nil
|
||||
}
|
||||
tok, err := listtoken.NewPagination(ctx, fiveDaysAgo, resource.AuthToken, []byte("some hash"), "some-id", fiveDaysAgo)
|
||||
require.NoError(t, err)
|
||||
_, err = authtoken.ListRefreshPage(ctx, []byte("some hash"), 1, filterFunc, tok, repo, []string{org.GetPublicId()})
|
||||
require.ErrorContains(t, err, "token did not have a refresh token component")
|
||||
})
|
||||
t.Run("nil repo", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
filterFunc := func(_ context.Context, at *authtoken.AuthToken) (bool, error) {
|
||||
return true, nil
|
||||
}
|
||||
tok, err := listtoken.NewRefresh(ctx, fiveDaysAgo, resource.AuthToken, []byte("some hash"), fiveDaysAgo, fiveDaysAgo, fiveDaysAgo, "some other id", fiveDaysAgo)
|
||||
require.NoError(t, err)
|
||||
_, err = authtoken.ListRefreshPage(ctx, []byte("some hash"), 1, filterFunc, tok, nil, []string{org.GetPublicId()})
|
||||
require.ErrorContains(t, err, "missing repo")
|
||||
})
|
||||
t.Run("missing scope ids", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
filterFunc := func(_ context.Context, at *authtoken.AuthToken) (bool, error) {
|
||||
return true, nil
|
||||
}
|
||||
tok, err := listtoken.NewRefresh(ctx, fiveDaysAgo, resource.AuthToken, []byte("some hash"), fiveDaysAgo, fiveDaysAgo, fiveDaysAgo, "some other id", fiveDaysAgo)
|
||||
require.NoError(t, err)
|
||||
_, err = authtoken.ListRefreshPage(ctx, []byte("some hash"), 1, filterFunc, tok, repo, nil)
|
||||
require.ErrorContains(t, err, "missing scope ids")
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("simple pagination", func(t *testing.T) {
|
||||
filterFunc := func(_ context.Context, at *authtoken.AuthToken) (bool, error) {
|
||||
return true, nil
|
||||
}
|
||||
resp, err := authtoken.List(ctx, []byte("some hash"), 1, filterFunc, repo, []string{org.GetPublicId()})
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, resp.ListToken)
|
||||
require.Equal(t, resp.ListToken.GrantsHash, []byte("some hash"))
|
||||
require.False(t, resp.CompleteListing)
|
||||
require.Equal(t, resp.EstimatedItemCount, 5)
|
||||
require.Empty(t, resp.DeletedIds)
|
||||
require.Len(t, resp.Items, 1)
|
||||
require.Empty(t, cmp.Diff(resp.Items[0], allTokens[0], cmpIgnoreUnexportedOpts, cmpIgnoreFieldsOpts))
|
||||
|
||||
resp2, err := authtoken.ListPage(ctx, []byte("some hash"), 1, filterFunc, resp.ListToken, repo, []string{org.GetPublicId()})
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, resp2.ListToken.GrantsHash, []byte("some hash"))
|
||||
require.False(t, resp2.CompleteListing)
|
||||
require.Equal(t, resp2.EstimatedItemCount, 5)
|
||||
require.Empty(t, resp2.DeletedIds)
|
||||
require.Len(t, resp2.Items, 1)
|
||||
require.Empty(t, cmp.Diff(resp2.Items[0], allTokens[1], cmpIgnoreUnexportedOpts, cmpIgnoreFieldsOpts))
|
||||
|
||||
resp3, err := authtoken.ListPage(ctx, []byte("some hash"), 1, filterFunc, resp2.ListToken, repo, []string{org.GetPublicId()})
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, resp3.ListToken.GrantsHash, []byte("some hash"))
|
||||
require.False(t, resp3.CompleteListing)
|
||||
require.Equal(t, resp3.EstimatedItemCount, 5)
|
||||
require.Empty(t, resp3.DeletedIds)
|
||||
require.Len(t, resp3.Items, 1)
|
||||
require.Empty(t, cmp.Diff(resp3.Items[0], allTokens[2], cmpIgnoreUnexportedOpts, cmpIgnoreFieldsOpts))
|
||||
|
||||
resp4, err := authtoken.ListPage(ctx, []byte("some hash"), 1, filterFunc, resp3.ListToken, repo, []string{org.GetPublicId()})
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, resp4.ListToken.GrantsHash, []byte("some hash"))
|
||||
require.False(t, resp4.CompleteListing)
|
||||
require.Equal(t, resp4.EstimatedItemCount, 5)
|
||||
require.Empty(t, resp4.DeletedIds)
|
||||
require.Len(t, resp4.Items, 1)
|
||||
require.Empty(t, cmp.Diff(resp4.Items[0], allTokens[3], cmpIgnoreUnexportedOpts, cmpIgnoreFieldsOpts))
|
||||
|
||||
resp5, err := authtoken.ListPage(ctx, []byte("some hash"), 1, filterFunc, resp4.ListToken, repo, []string{org.GetPublicId()})
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, resp5.ListToken.GrantsHash, []byte("some hash"))
|
||||
require.True(t, resp5.CompleteListing)
|
||||
require.Equal(t, resp5.EstimatedItemCount, 5)
|
||||
require.Empty(t, resp5.DeletedIds)
|
||||
require.Len(t, resp5.Items, 1)
|
||||
require.Empty(t, cmp.Diff(resp5.Items[0], allTokens[4], cmpIgnoreUnexportedOpts, cmpIgnoreFieldsOpts))
|
||||
|
||||
// Finished initial pagination phase, request refresh
|
||||
// Expect no results.
|
||||
resp6, err := authtoken.ListRefresh(ctx, []byte("some hash"), 1, filterFunc, resp5.ListToken, repo, []string{org.GetPublicId()})
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, resp6.ListToken.GrantsHash, []byte("some hash"))
|
||||
require.True(t, resp6.CompleteListing)
|
||||
require.Equal(t, resp6.EstimatedItemCount, 5)
|
||||
require.Empty(t, resp6.DeletedIds)
|
||||
require.Empty(t, resp6.Items)
|
||||
|
||||
// Create some new auth tokens
|
||||
newAt1 := authtoken.TestAuthToken(t, conn, kms, org.GetPublicId())
|
||||
newAt2 := authtoken.TestAuthToken(t, conn, kms, org.GetPublicId())
|
||||
t.Cleanup(func() {
|
||||
_, err = repo.DeleteAuthToken(ctx, newAt1.GetPublicId())
|
||||
require.NoError(t, err)
|
||||
_, err = repo.DeleteAuthToken(ctx, newAt2.GetPublicId())
|
||||
require.NoError(t, err)
|
||||
// Run analyze to update count estimate
|
||||
_, err = sqlDB.ExecContext(ctx, "analyze")
|
||||
require.NoError(t, err)
|
||||
})
|
||||
// Run analyze to update count estimate
|
||||
_, err = sqlDB.ExecContext(ctx, "analyze")
|
||||
require.NoError(t, err)
|
||||
|
||||
// Refresh again, should get newAt2
|
||||
resp7, err := authtoken.ListRefresh(ctx, []byte("some hash"), 1, filterFunc, resp6.ListToken, repo, []string{org.GetPublicId()})
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, resp7.ListToken.GrantsHash, []byte("some hash"))
|
||||
require.False(t, resp7.CompleteListing)
|
||||
require.Equal(t, resp7.EstimatedItemCount, 7)
|
||||
require.Empty(t, resp7.DeletedIds)
|
||||
require.Len(t, resp7.Items, 1)
|
||||
require.Empty(t, cmp.Diff(resp7.Items[0], newAt2, cmpIgnoreUnexportedOpts, cmpIgnoreFieldsOpts))
|
||||
|
||||
// Refresh again, should get newAt1
|
||||
resp8, err := authtoken.ListRefreshPage(ctx, []byte("some hash"), 1, filterFunc, resp7.ListToken, repo, []string{org.GetPublicId()})
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, resp8.ListToken.GrantsHash, []byte("some hash"))
|
||||
require.True(t, resp8.CompleteListing)
|
||||
require.Equal(t, resp8.EstimatedItemCount, 7)
|
||||
require.Empty(t, resp8.DeletedIds)
|
||||
require.Len(t, resp8.Items, 1)
|
||||
require.Empty(t, cmp.Diff(resp8.Items[0], newAt1, cmpIgnoreUnexportedOpts, cmpIgnoreFieldsOpts))
|
||||
|
||||
// Refresh again, should get no results
|
||||
resp9, err := authtoken.ListRefresh(ctx, []byte("some hash"), 1, filterFunc, resp8.ListToken, repo, []string{org.GetPublicId()})
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, resp9.ListToken.GrantsHash, []byte("some hash"))
|
||||
require.True(t, resp9.CompleteListing)
|
||||
require.Equal(t, resp9.EstimatedItemCount, 7)
|
||||
require.Empty(t, resp9.DeletedIds)
|
||||
require.Empty(t, resp9.Items)
|
||||
})
|
||||
|
||||
t.Run("simple pagination with aggressive filtering", func(t *testing.T) {
|
||||
filterFunc := func(_ context.Context, at *authtoken.AuthToken) (bool, error) {
|
||||
return at.GetPublicId() == allTokens[1].GetPublicId() ||
|
||||
at.GetPublicId() == allTokens[len(allTokens)-1].GetPublicId(), nil
|
||||
}
|
||||
resp, err := authtoken.List(ctx, []byte("some hash"), 1, filterFunc, repo, []string{org.GetPublicId()})
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, resp.ListToken)
|
||||
require.Equal(t, resp.ListToken.GrantsHash, []byte("some hash"))
|
||||
require.False(t, resp.CompleteListing)
|
||||
require.Equal(t, resp.EstimatedItemCount, 5)
|
||||
require.Empty(t, resp.DeletedIds)
|
||||
require.Len(t, resp.Items, 1)
|
||||
require.Empty(t, cmp.Diff(resp.Items[0], allTokens[1], cmpIgnoreUnexportedOpts, cmpIgnoreFieldsOpts))
|
||||
|
||||
resp2, err := authtoken.ListPage(ctx, []byte("some hash"), 1, filterFunc, resp.ListToken, repo, []string{org.GetPublicId()})
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, resp2.ListToken)
|
||||
require.Equal(t, resp2.ListToken.GrantsHash, []byte("some hash"))
|
||||
require.True(t, resp2.CompleteListing)
|
||||
require.Equal(t, resp2.EstimatedItemCount, 5)
|
||||
require.Empty(t, resp2.DeletedIds)
|
||||
require.Len(t, resp2.Items, 1)
|
||||
require.Empty(t, cmp.Diff(resp2.Items[0], allTokens[len(allTokens)-1], cmpIgnoreUnexportedOpts, cmpIgnoreFieldsOpts))
|
||||
|
||||
// request a refresh, nothing should be returned
|
||||
resp3, err := authtoken.ListRefresh(ctx, []byte("some hash"), 1, filterFunc, resp.ListToken, repo, []string{org.GetPublicId()})
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, resp3.ListToken.GrantsHash, []byte("some hash"))
|
||||
require.True(t, resp3.CompleteListing)
|
||||
require.Equal(t, resp3.EstimatedItemCount, 5)
|
||||
require.Empty(t, resp3.DeletedIds)
|
||||
require.Empty(t, resp3.Items)
|
||||
|
||||
// Create some new tokens
|
||||
newAt1 := authtoken.TestAuthToken(t, conn, kms, org.GetPublicId())
|
||||
newAt2 := authtoken.TestAuthToken(t, conn, kms, org.GetPublicId())
|
||||
newAt3 := authtoken.TestAuthToken(t, conn, kms, org.GetPublicId())
|
||||
newAt4 := authtoken.TestAuthToken(t, conn, kms, org.GetPublicId())
|
||||
// Run analyze to update count estimate
|
||||
_, err = sqlDB.ExecContext(ctx, "analyze")
|
||||
require.NoError(t, err)
|
||||
t.Cleanup(func() {
|
||||
_, err = repo.DeleteAuthToken(ctx, newAt1.GetPublicId())
|
||||
require.NoError(t, err)
|
||||
_, err = repo.DeleteAuthToken(ctx, newAt2.GetPublicId())
|
||||
require.NoError(t, err)
|
||||
_, err = repo.DeleteAuthToken(ctx, newAt3.GetPublicId())
|
||||
require.NoError(t, err)
|
||||
_, err = repo.DeleteAuthToken(ctx, newAt4.GetPublicId())
|
||||
require.NoError(t, err)
|
||||
// Run analyze to update count estimate
|
||||
_, err = sqlDB.ExecContext(ctx, "analyze")
|
||||
require.NoError(t, err)
|
||||
})
|
||||
|
||||
filterFunc = func(_ context.Context, at *authtoken.AuthToken) (bool, error) {
|
||||
return at.GetPublicId() == newAt3.GetPublicId() ||
|
||||
at.GetPublicId() == newAt1.GetPublicId(), nil
|
||||
}
|
||||
// Refresh again, should get newAt3
|
||||
resp4, err := authtoken.ListRefresh(ctx, []byte("some hash"), 1, filterFunc, resp3.ListToken, repo, []string{org.GetPublicId()})
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, resp4.ListToken.GrantsHash, []byte("some hash"))
|
||||
require.False(t, resp4.CompleteListing)
|
||||
require.Equal(t, resp4.EstimatedItemCount, 9)
|
||||
require.Empty(t, resp4.DeletedIds)
|
||||
require.Len(t, resp4.Items, 1)
|
||||
require.Empty(t, cmp.Diff(resp4.Items[0], newAt3, cmpIgnoreUnexportedOpts, cmpIgnoreFieldsOpts))
|
||||
|
||||
// Refresh again, should get newAt1
|
||||
resp5, err := authtoken.ListRefreshPage(ctx, []byte("some hash"), 1, filterFunc, resp4.ListToken, repo, []string{org.GetPublicId()})
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, resp5.ListToken.GrantsHash, []byte("some hash"))
|
||||
require.True(t, resp5.CompleteListing)
|
||||
require.Equal(t, resp5.EstimatedItemCount, 9)
|
||||
require.Empty(t, resp5.DeletedIds)
|
||||
require.Len(t, resp5.Items, 1)
|
||||
require.Empty(t, cmp.Diff(resp5.Items[0], newAt1, cmpIgnoreUnexportedOpts, cmpIgnoreFieldsOpts))
|
||||
})
|
||||
|
||||
t.Run("simple pagination with deletion", func(t *testing.T) {
|
||||
filterFunc := func(_ context.Context, at *authtoken.AuthToken) (bool, error) {
|
||||
return true, nil
|
||||
}
|
||||
deletedAuthTokenId := allTokens[0].GetPublicId()
|
||||
_, err := repo.DeleteAuthToken(ctx, deletedAuthTokenId)
|
||||
require.NoError(t, err)
|
||||
allTokens = allTokens[1:]
|
||||
|
||||
// Run analyze to update count estimate
|
||||
_, err = sqlDB.ExecContext(ctx, "analyze")
|
||||
require.NoError(t, err)
|
||||
|
||||
resp, err := authtoken.List(ctx, []byte("some hash"), 1, filterFunc, repo, []string{org.GetPublicId()})
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, resp.ListToken)
|
||||
require.Equal(t, resp.ListToken.GrantsHash, []byte("some hash"))
|
||||
require.False(t, resp.CompleteListing)
|
||||
require.Equal(t, resp.EstimatedItemCount, 4)
|
||||
require.Empty(t, resp.DeletedIds)
|
||||
require.Len(t, resp.Items, 1)
|
||||
require.Empty(t, cmp.Diff(resp.Items[0], allTokens[0], cmpIgnoreUnexportedOpts, cmpIgnoreFieldsOpts))
|
||||
|
||||
// request remaining results
|
||||
resp2, err := authtoken.ListPage(ctx, []byte("some hash"), 3, filterFunc, resp.ListToken, repo, []string{org.GetPublicId()})
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, resp2.ListToken.GrantsHash, []byte("some hash"))
|
||||
require.True(t, resp2.CompleteListing)
|
||||
require.Equal(t, resp2.EstimatedItemCount, 4)
|
||||
require.Empty(t, resp2.DeletedIds)
|
||||
require.Len(t, resp2.Items, 3)
|
||||
require.Empty(t, cmp.Diff(resp2.Items, allTokens[1:], cmpIgnoreUnexportedOpts, cmpIgnoreFieldsOpts))
|
||||
|
||||
deletedAuthTokenId = allTokens[0].GetPublicId()
|
||||
_, err = repo.DeleteAuthToken(ctx, deletedAuthTokenId)
|
||||
require.NoError(t, err)
|
||||
allTokens = allTokens[1:]
|
||||
|
||||
// Run analyze to update count estimate
|
||||
_, err = sqlDB.ExecContext(ctx, "analyze")
|
||||
require.NoError(t, err)
|
||||
|
||||
// request a refresh, nothing should be returned except the deleted id
|
||||
resp3, err := authtoken.ListRefresh(ctx, []byte("some hash"), 1, filterFunc, resp2.ListToken, repo, []string{org.GetPublicId()})
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, resp3.ListToken.GrantsHash, []byte("some hash"))
|
||||
require.True(t, resp3.CompleteListing)
|
||||
require.Equal(t, resp3.EstimatedItemCount, 3)
|
||||
require.Contains(t, resp3.DeletedIds, deletedAuthTokenId)
|
||||
require.Empty(t, resp3.Items)
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
// Copyright (c) HashiCorp, Inc.
|
||||
// SPDX-License-Identifier: BUSL-1.1
|
||||
|
||||
package authtoken
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/hashicorp/boundary/internal/errors"
|
||||
"github.com/hashicorp/boundary/internal/listtoken"
|
||||
"github.com/hashicorp/boundary/internal/pagination"
|
||||
)
|
||||
|
||||
// ListPage lists up to page size auth tokens, filtering out entries that
|
||||
// do not pass the filter item function. It will automatically request
|
||||
// more auth tokens from the database, at page size chunks, to fill the page.
|
||||
// It will start its paging based on the information in the token.
|
||||
// It returns a new list token used to continue pagination or refresh items.
|
||||
// Auth tokens are ordered by create time descending (most recently created first).
|
||||
func ListPage(
|
||||
ctx context.Context,
|
||||
grantsHash []byte,
|
||||
pageSize int,
|
||||
filterItemFn pagination.ListFilterFunc[*AuthToken],
|
||||
tok *listtoken.Token,
|
||||
repo *Repository,
|
||||
withScopeIds []string,
|
||||
) (*pagination.ListResponse[*AuthToken], error) {
|
||||
const op = "authtoken.ListPage"
|
||||
|
||||
switch {
|
||||
case len(grantsHash) == 0:
|
||||
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing grants hash")
|
||||
case pageSize < 1:
|
||||
return nil, errors.New(ctx, errors.InvalidParameter, op, "page size must be at least 1")
|
||||
case filterItemFn == nil:
|
||||
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing filter item callback")
|
||||
case tok == nil:
|
||||
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing token")
|
||||
case repo == nil:
|
||||
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing repo")
|
||||
case withScopeIds == nil:
|
||||
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing scope ids")
|
||||
}
|
||||
if _, ok := tok.Subtype.(*listtoken.PaginationToken); !ok {
|
||||
return nil, errors.New(ctx, errors.InvalidParameter, op, "token did not have a pagination token component")
|
||||
}
|
||||
|
||||
listItemsFn := func(ctx context.Context, lastPageItem *AuthToken, limit int) ([]*AuthToken, time.Time, error) {
|
||||
opts := []Option{
|
||||
WithLimit(limit),
|
||||
}
|
||||
if lastPageItem != nil {
|
||||
opts = append(opts, WithStartPageAfterItem(lastPageItem))
|
||||
} else {
|
||||
lastItem, err := tok.LastItem(ctx)
|
||||
if err != nil {
|
||||
return nil, time.Time{}, err
|
||||
}
|
||||
opts = append(opts, WithStartPageAfterItem(lastItem))
|
||||
}
|
||||
return repo.listAuthTokens(ctx, withScopeIds, opts...)
|
||||
}
|
||||
|
||||
return pagination.ListPage(ctx, grantsHash, pageSize, filterItemFn, listItemsFn, repo.estimatedCount, tok)
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
// Copyright (c) HashiCorp, Inc.
|
||||
// SPDX-License-Identifier: BUSL-1.1
|
||||
|
||||
package authtoken
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/hashicorp/boundary/globals"
|
||||
"github.com/hashicorp/boundary/internal/errors"
|
||||
"github.com/hashicorp/boundary/internal/listtoken"
|
||||
"github.com/hashicorp/boundary/internal/pagination"
|
||||
)
|
||||
|
||||
// ListRefresh lists up to page size auth tokens, filtering out entries that
|
||||
// do not pass the filter item function. It will automatically request
|
||||
// more auth tokens from the database, at page size chunks, to fill the page.
|
||||
// It will start its paging based on the information in the token.
|
||||
// It returns a new list token used to continue pagination or refresh items.
|
||||
// Auth tokens are ordered by update time descending (most recently updated first).
|
||||
// Auth tokens may contain items that were already returned during the initial
|
||||
// pagination phase. It also returns a list of any auth tokens deleted since the
|
||||
// start of the initial pagination phase or last response.
|
||||
func ListRefresh(
|
||||
ctx context.Context,
|
||||
grantsHash []byte,
|
||||
pageSize int,
|
||||
filterItemFn pagination.ListFilterFunc[*AuthToken],
|
||||
tok *listtoken.Token,
|
||||
repo *Repository,
|
||||
withScopeIds []string,
|
||||
) (*pagination.ListResponse[*AuthToken], error) {
|
||||
const op = "authtoken.ListRefresh"
|
||||
|
||||
switch {
|
||||
case len(grantsHash) == 0:
|
||||
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing grants hash")
|
||||
case pageSize < 1:
|
||||
return nil, errors.New(ctx, errors.InvalidParameter, op, "page size must be at least 1")
|
||||
case filterItemFn == nil:
|
||||
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing filter item callback")
|
||||
case tok == nil:
|
||||
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing token")
|
||||
case repo == nil:
|
||||
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing repo")
|
||||
case withScopeIds == nil:
|
||||
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing scope ids")
|
||||
}
|
||||
rt, ok := tok.Subtype.(*listtoken.StartRefreshToken)
|
||||
if !ok {
|
||||
return nil, errors.New(ctx, errors.InvalidParameter, op, "token did not have a start-refresh token component")
|
||||
}
|
||||
|
||||
listItemsFn := func(ctx context.Context, lastPageItem *AuthToken, limit int) ([]*AuthToken, time.Time, error) {
|
||||
opts := []Option{
|
||||
WithLimit(limit),
|
||||
}
|
||||
if lastPageItem != nil {
|
||||
opts = append(opts, WithStartPageAfterItem(lastPageItem))
|
||||
}
|
||||
// Add the database read timeout to account for any creations missed due to concurrent
|
||||
// transactions in the initial pagination phase.
|
||||
return repo.listAuthTokensRefresh(ctx, rt.PreviousPhaseUpperBound.Add(-globals.RefreshReadLookbackDuration), withScopeIds, opts...)
|
||||
}
|
||||
listDeletedIdsFn := func(ctx context.Context, since time.Time) ([]string, time.Time, error) {
|
||||
// Add the database read timeout to account for any deletions missed due to concurrent
|
||||
// transactions in previous requests.
|
||||
return repo.listDeletedIds(ctx, since.Add(-globals.RefreshReadLookbackDuration))
|
||||
}
|
||||
|
||||
return pagination.ListRefresh(ctx, grantsHash, pageSize, filterItemFn, listItemsFn, repo.estimatedCount, listDeletedIdsFn, tok)
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
// Copyright (c) HashiCorp, Inc.
|
||||
// SPDX-License-Identifier: BUSL-1.1
|
||||
|
||||
package authtoken
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/hashicorp/boundary/globals"
|
||||
"github.com/hashicorp/boundary/internal/errors"
|
||||
"github.com/hashicorp/boundary/internal/listtoken"
|
||||
"github.com/hashicorp/boundary/internal/pagination"
|
||||
)
|
||||
|
||||
// ListRefreshPage lists up to page size auth tokens, filtering out entries that
|
||||
// do not pass the filter item function. It will automatically request
|
||||
// more auth tokens from the database, at page size chunks, to fill the page.
|
||||
// It will start its paging based on the information in the token.
|
||||
// It returns a new list token used to continue pagination or refresh items.
|
||||
// Auth tokens are ordered by update time descending (most recently updated first).
|
||||
// Auth tokens may contain items that were already returned during the initial
|
||||
// pagination phase. It also returns a list of any auth tokens deleted since the
|
||||
// last response.
|
||||
func ListRefreshPage(
|
||||
ctx context.Context,
|
||||
grantsHash []byte,
|
||||
pageSize int,
|
||||
filterItemFn pagination.ListFilterFunc[*AuthToken],
|
||||
tok *listtoken.Token,
|
||||
repo *Repository,
|
||||
withScopeIds []string,
|
||||
) (*pagination.ListResponse[*AuthToken], error) {
|
||||
const op = "authtoken.ListRefreshPage"
|
||||
|
||||
switch {
|
||||
case len(grantsHash) == 0:
|
||||
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing grants hash")
|
||||
case pageSize < 1:
|
||||
return nil, errors.New(ctx, errors.InvalidParameter, op, "page size must be at least 1")
|
||||
case filterItemFn == nil:
|
||||
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing filter item callback")
|
||||
case tok == nil:
|
||||
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing token")
|
||||
case repo == nil:
|
||||
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing repo")
|
||||
case withScopeIds == nil:
|
||||
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing scope ids")
|
||||
}
|
||||
rt, ok := tok.Subtype.(*listtoken.RefreshToken)
|
||||
if !ok {
|
||||
return nil, errors.New(ctx, errors.InvalidParameter, op, "token did not have a refresh token component")
|
||||
}
|
||||
|
||||
listItemsFn := func(ctx context.Context, lastPageItem *AuthToken, limit int) ([]*AuthToken, time.Time, error) {
|
||||
opts := []Option{
|
||||
WithLimit(limit),
|
||||
}
|
||||
if lastPageItem != nil {
|
||||
opts = append(opts, WithStartPageAfterItem(lastPageItem))
|
||||
} else {
|
||||
lastItem, err := tok.LastItem(ctx)
|
||||
if err != nil {
|
||||
return nil, time.Time{}, err
|
||||
}
|
||||
opts = append(opts, WithStartPageAfterItem(lastItem))
|
||||
}
|
||||
// Add the database read timeout to account for any creations missed due to concurrent
|
||||
// transactions in the original list pagination phase.
|
||||
return repo.listAuthTokensRefresh(ctx, rt.PhaseLowerBound.Add(-globals.RefreshReadLookbackDuration), withScopeIds, opts...)
|
||||
}
|
||||
|
||||
listDeletedIdsFn := func(ctx context.Context, since time.Time) ([]string, time.Time, error) {
|
||||
// Add the database read timeout to account for any deletes missed due to concurrent
|
||||
// transactions in the original list pagination phase.
|
||||
return repo.listDeletedIds(ctx, since.Add(-globals.RefreshReadLookbackDuration))
|
||||
}
|
||||
|
||||
return pagination.ListRefreshPage(ctx, grantsHash, pageSize, filterItemFn, listItemsFn, repo.estimatedCount, listDeletedIdsFn, tok)
|
||||
}
|
||||
@@ -171,7 +171,7 @@ func (c *Controller) registerGrpcServices(s *grpc.Server) error {
|
||||
services.RegisterAuthMethodServiceServer(s, authMethods)
|
||||
}
|
||||
if _, ok := currentServices[services.AuthTokenService_ServiceDesc.ServiceName]; !ok {
|
||||
authtoks, err := authtokens.NewService(c.baseContext, c.AuthTokenRepoFn, c.IamRepoFn)
|
||||
authtoks, err := authtokens.NewService(c.baseContext, c.AuthTokenRepoFn, c.IamRepoFn, c.conf.RawConfig.Controller.MaxPageSize)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create auth token handler service: %w", err)
|
||||
}
|
||||
|
||||
@@ -15,12 +15,15 @@ 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"
|
||||
"github.com/hashicorp/boundary/internal/types/resource"
|
||||
"github.com/hashicorp/boundary/internal/types/scope"
|
||||
pb "github.com/hashicorp/boundary/sdk/pbs/controller/api/resources/authtokens"
|
||||
"github.com/hashicorp/boundary/sdk/pbs/controller/api/resources/scopes"
|
||||
"google.golang.org/grpc/codes"
|
||||
)
|
||||
|
||||
@@ -51,14 +54,15 @@ func init() {
|
||||
type Service struct {
|
||||
pbs.UnsafeAuthTokenServiceServer
|
||||
|
||||
repoFn common.AuthTokenRepoFactory
|
||||
iamRepoFn common.IamRepoFactory
|
||||
repoFn common.AuthTokenRepoFactory
|
||||
iamRepoFn common.IamRepoFactory
|
||||
maxPageSize uint
|
||||
}
|
||||
|
||||
var _ pbs.AuthTokenServiceServer = (*Service)(nil)
|
||||
|
||||
// NewService returns a user service which handles user related requests to boundary.
|
||||
func NewService(ctx context.Context, repo common.AuthTokenRepoFactory, iamRepoFn common.IamRepoFactory) (Service, error) {
|
||||
func NewService(ctx context.Context, repo common.AuthTokenRepoFactory, iamRepoFn common.IamRepoFactory, maxPageSize uint) (Service, error) {
|
||||
const op = "authtoken.NewService"
|
||||
if repo == nil {
|
||||
return Service{}, errors.New(ctx, errors.InvalidParameter, op, "missing auth token repository")
|
||||
@@ -66,14 +70,20 @@ func NewService(ctx context.Context, repo common.AuthTokenRepoFactory, iamRepoFn
|
||||
if iamRepoFn == nil {
|
||||
return Service{}, errors.New(ctx, errors.InvalidParameter, op, "missing iam repository")
|
||||
}
|
||||
return Service{repoFn: repo, iamRepoFn: iamRepoFn}, nil
|
||||
if maxPageSize == 0 {
|
||||
maxPageSize = uint(globals.DefaultMaxPageSize)
|
||||
}
|
||||
return Service{repoFn: repo, iamRepoFn: iamRepoFn, maxPageSize: maxPageSize}, nil
|
||||
}
|
||||
|
||||
// ListAuthTokens implements the interface pbs.AuthTokenServiceServer.
|
||||
func (s Service) ListAuthTokens(ctx context.Context, req *pbs.ListAuthTokensRequest) (*pbs.ListAuthTokensResponse, error) {
|
||||
const op = "authtokens.(Service).ListAuthTokens"
|
||||
|
||||
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 {
|
||||
// If it's forbidden, and it's a recursive request, and they're
|
||||
@@ -84,69 +94,131 @@ func (s Service) ListAuthTokens(ctx context.Context, req *pbs.ListAuthTokensRequ
|
||||
req.GetRecursive() &&
|
||||
authResults.AuthenticationFinished {
|
||||
} else {
|
||||
return nil, authResults.Error
|
||||
return nil, errors.Wrap(ctx, authResults.Error, op)
|
||||
}
|
||||
}
|
||||
|
||||
scopeIds, scopeInfoMap, err := scopeids.GetListingScopeIds(
|
||||
ctx, s.iamRepoFn, authResults, req.GetScopeId(), resource.AuthToken, 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.ListAuthTokensResponse{}, nil
|
||||
}
|
||||
|
||||
ul, err := s.listFromRepo(ctx, scopeIds)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(ul) == 0 {
|
||||
return &pbs.ListAuthTokensResponse{}, nil
|
||||
pageSize := int(s.maxPageSize)
|
||||
// Use the requested page size only if it is smaller than
|
||||
// the configured max.
|
||||
if req.GetPageSize() != 0 && uint(req.GetPageSize()) < s.maxPageSize {
|
||||
pageSize = int(req.GetPageSize())
|
||||
}
|
||||
|
||||
filter, err := handlers.NewFilter(ctx, req.GetFilter())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
finalItems := make([]*pb.AuthToken, 0, len(ul))
|
||||
res := perms.Resource{
|
||||
Type: resource.AuthToken,
|
||||
}
|
||||
for _, at := range ul {
|
||||
res.Id = at.GetPublicId()
|
||||
res.ScopeId = at.GetScopeId()
|
||||
authorizedActions := authResults.FetchActionSetForId(ctx, at.GetPublicId(), IdActions, auth.WithResource(&res))
|
||||
if len(authorizedActions) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
if authorizedActions.OnlySelf() && at.GetIamUserId() != authResults.UserId {
|
||||
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[at.GetScopeId()]))
|
||||
}
|
||||
if outputFields.Has(globals.AuthorizedActionsField) {
|
||||
outputOpts = append(outputOpts, handlers.WithAuthorizedActions(authorizedActions.Strings()))
|
||||
}
|
||||
|
||||
item, err := toProto(ctx, at, outputOpts...)
|
||||
var filterItemFn func(ctx context.Context, item *authtoken.AuthToken) (bool, error)
|
||||
switch {
|
||||
case req.GetFilter() != "":
|
||||
// Only use a filter if we need to
|
||||
filter, err := handlers.NewFilter(ctx, req.GetFilter())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if filter.Match(item) {
|
||||
finalItems = append(finalItems, item)
|
||||
filterItemFn = func(ctx context.Context, item *authtoken.AuthToken) (bool, error) {
|
||||
outputOpts, ok := newOutputOpts(ctx, item, scopeInfoMap, authResults)
|
||||
if !ok {
|
||||
return false, nil
|
||||
}
|
||||
pbItem, err := toProto(ctx, item, outputOpts...)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return filter.Match(pbItem), nil
|
||||
}
|
||||
default:
|
||||
filterItemFn = func(ctx context.Context, item *authtoken.AuthToken) (bool, error) {
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
|
||||
return &pbs.ListAuthTokensResponse{Items: finalItems}, nil
|
||||
grantsHash, err := authResults.GrantsHash(ctx)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(ctx, err, op)
|
||||
}
|
||||
|
||||
repo, err := s.repoFn()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(ctx, err, op)
|
||||
}
|
||||
|
||||
var listResp *pagination.ListResponse[*authtoken.AuthToken]
|
||||
var sortBy string
|
||||
if req.GetListToken() == "" {
|
||||
sortBy = "created_time"
|
||||
listResp, err = authtoken.List(ctx, grantsHash, pageSize, filterItemFn, repo, scopeIds)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
listToken, err := handlers.ParseListToken(ctx, req.GetListToken(), resource.AuthToken, grantsHash)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
switch st := listToken.Subtype.(type) {
|
||||
case *listtoken.PaginationToken:
|
||||
sortBy = "created_time"
|
||||
listResp, err = authtoken.ListPage(ctx, grantsHash, pageSize, filterItemFn, listToken, repo, scopeIds)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
case *listtoken.StartRefreshToken:
|
||||
sortBy = "updated_time"
|
||||
listResp, err = authtoken.ListRefresh(ctx, grantsHash, pageSize, filterItemFn, listToken, repo, scopeIds)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
case *listtoken.RefreshToken:
|
||||
sortBy = "updated_time"
|
||||
listResp, err = authtoken.ListRefreshPage(ctx, grantsHash, pageSize, filterItemFn, listToken, repo, scopeIds)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
default:
|
||||
return nil, handlers.ApiErrorWithCodeAndMessage(codes.InvalidArgument, "unexpected list token subtype: %T", st)
|
||||
}
|
||||
}
|
||||
|
||||
finalItems := make([]*pb.AuthToken, 0, len(listResp.Items))
|
||||
for _, item := range listResp.Items {
|
||||
outputOpts, ok := newOutputOpts(ctx, item, scopeInfoMap, authResults)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
item, err := toProto(ctx, item, outputOpts...)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(ctx, err, op)
|
||||
}
|
||||
finalItems = append(finalItems, item)
|
||||
}
|
||||
respType := "delta"
|
||||
if listResp.CompleteListing {
|
||||
respType = "complete"
|
||||
}
|
||||
resp := &pbs.ListAuthTokensResponse{
|
||||
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_AUTH_TOKEN)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// GetAuthToken implements the interface pbs.AuthTokenServiceServer.
|
||||
@@ -266,19 +338,6 @@ func (s Service) deleteFromRepo(ctx context.Context, id string) (bool, error) {
|
||||
return rows > 0, nil
|
||||
}
|
||||
|
||||
func (s Service) listFromRepo(ctx context.Context, scopeIds []string) ([]*authtoken.AuthToken, error) {
|
||||
repo, err := s.repoFn()
|
||||
_ = repo
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ul, err := repo.ListAuthTokens(ctx, scopeIds, authtoken.WithLimit(-1))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return ul, nil
|
||||
}
|
||||
|
||||
func (s Service) authResult(ctx context.Context, id string, a action.Type) auth.VerifyResults {
|
||||
res := auth.VerifyResults{}
|
||||
|
||||
@@ -395,3 +454,30 @@ func validateListRequest(ctx context.Context, req *pbs.ListAuthTokensRequest) er
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func newOutputOpts(ctx context.Context, item *authtoken.AuthToken, scopeInfoMap map[string]*scopes.ScopeInfo, authResults auth.VerifyResults) ([]handlers.Option, bool) {
|
||||
res := perms.Resource{
|
||||
Type: resource.AuthToken,
|
||||
}
|
||||
res.Id = item.GetPublicId()
|
||||
res.ScopeId = item.GetScopeId()
|
||||
authorizedActions := authResults.FetchActionSetForId(ctx, item.GetPublicId(), IdActions, auth.WithResource(&res))
|
||||
if len(authorizedActions) == 0 {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
if authorizedActions.OnlySelf() && item.GetIamUserId() != authResults.UserId {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
outputFields := authResults.FetchOutputFields(res, action.List).SelfOrDefaults(authResults.UserId)
|
||||
outputOpts := make([]handlers.Option, 0, 3)
|
||||
outputOpts = append(outputOpts, handlers.WithOutputFields(outputFields))
|
||||
if outputFields.Has(globals.ScopeField) {
|
||||
outputOpts = append(outputOpts, handlers.WithScope(scopeInfoMap[item.GetScopeId()]))
|
||||
}
|
||||
if outputFields.Has(globals.AuthorizedActionsField) {
|
||||
outputOpts = append(outputOpts, handlers.WithAuthorizedActions(authorizedActions.Strings()))
|
||||
}
|
||||
return outputOpts, true
|
||||
}
|
||||
@@ -8,11 +8,13 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http/httptest"
|
||||
"slices"
|
||||
"testing"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/google/go-cmp/cmp/cmpopts"
|
||||
"github.com/hashicorp/boundary/globals"
|
||||
"github.com/hashicorp/boundary/internal/auth/password"
|
||||
"github.com/hashicorp/boundary/internal/authtoken"
|
||||
"github.com/hashicorp/boundary/internal/daemon/controller/auth"
|
||||
"github.com/hashicorp/boundary/internal/daemon/controller/handlers"
|
||||
@@ -34,7 +36,10 @@ import (
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
var testAuthorizedActions = []string{"no-op", "read", "read:self", "delete", "delete:self"}
|
||||
var (
|
||||
fullAuthorizedActions = []string{"no-op", "read", "read:self", "delete", "delete:self"}
|
||||
selfAuthorizedActions = []string{"read:self", "delete:self"}
|
||||
)
|
||||
|
||||
func TestGetSelf(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
@@ -53,7 +58,7 @@ func TestGetSelf(t *testing.T) {
|
||||
return server.NewRepository(ctx, rw, rw, kms)
|
||||
}
|
||||
|
||||
a, err := authtokens.NewService(ctx, tokenRepoFn, iamRepoFn)
|
||||
a, err := authtokens.NewService(ctx, tokenRepoFn, iamRepoFn, 1000)
|
||||
require.NoError(t, err, "Couldn't create new auth token service.")
|
||||
|
||||
o, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrap))
|
||||
@@ -132,7 +137,7 @@ func TestGet(t *testing.T) {
|
||||
return authtoken.NewRepository(ctx, rw, rw, kms)
|
||||
}
|
||||
|
||||
s, err := authtokens.NewService(ctx, repoFn, iamRepoFn)
|
||||
s, err := authtokens.NewService(ctx, repoFn, iamRepoFn, 1000)
|
||||
require.NoError(t, err, "Couldn't create new auth token service.")
|
||||
|
||||
org, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrap))
|
||||
@@ -149,7 +154,7 @@ func TestGet(t *testing.T) {
|
||||
ApproximateLastUsedTime: at.GetApproximateLastAccessTime().GetTimestamp(),
|
||||
ExpirationTime: at.GetExpirationTime().GetTimestamp(),
|
||||
Scope: &scopes.ScopeInfo{Id: org.GetPublicId(), Type: scope.Org.String(), ParentScopeId: scope.Global.String()},
|
||||
AuthorizedActions: testAuthorizedActions,
|
||||
AuthorizedActions: fullAuthorizedActions,
|
||||
}
|
||||
|
||||
cases := []struct {
|
||||
@@ -232,19 +237,22 @@ func TestList_Self(t *testing.T) {
|
||||
cases := []struct {
|
||||
name string
|
||||
requester *authtoken.AuthToken
|
||||
req *pbs.ListAuthTokensRequest
|
||||
count int
|
||||
}{
|
||||
{
|
||||
name: "First token sees only self",
|
||||
req: &pbs.ListAuthTokensRequest{ScopeId: o.GetPublicId()},
|
||||
requester: at,
|
||||
},
|
||||
{
|
||||
name: "Second token sees only self",
|
||||
req: &pbs.ListAuthTokensRequest{ScopeId: o.GetPublicId()},
|
||||
requester: otherAt,
|
||||
},
|
||||
}
|
||||
|
||||
a, err := authtokens.NewService(testCtx, tokenRepoFn, iamRepoFn)
|
||||
a, err := authtokens.NewService(testCtx, tokenRepoFn, iamRepoFn, 1000)
|
||||
require.NoError(t, err)
|
||||
|
||||
for _, tc := range cases {
|
||||
@@ -261,7 +269,7 @@ func TestList_Self(t *testing.T) {
|
||||
}
|
||||
|
||||
ctx := auth.NewVerifierContext(testCtx, iamRepoFn, tokenRepoFn, serversRepoFn, kms, &requestInfo)
|
||||
got, err := a.ListAuthTokens(ctx, &pbs.ListAuthTokensRequest{ScopeId: o.GetPublicId()})
|
||||
got, err := a.ListAuthTokens(ctx, tc.req)
|
||||
require.NoError(err)
|
||||
require.Len(got.Items, 1)
|
||||
assert.Equal(got.Items[0].GetId(), tc.requester.GetPublicId())
|
||||
@@ -273,6 +281,8 @@ func TestList_Self(t *testing.T) {
|
||||
|
||||
func TestList(t *testing.T) {
|
||||
conn, _ := db.TestSetup(t, "postgres")
|
||||
sqlDB, err := conn.SqlDB(context.Background())
|
||||
require.NoError(t, err)
|
||||
rw := db.New(conn)
|
||||
wrap := db.TestWrapper(t)
|
||||
kms := kms.TestKms(t, conn, wrap)
|
||||
@@ -287,9 +297,10 @@ func TestList(t *testing.T) {
|
||||
orgNoTokens, _ := iam.TestScopes(t, iamRepo)
|
||||
|
||||
var globalTokens []*pb.AuthToken
|
||||
var allTokens []*pb.AuthToken
|
||||
for i := 0; i < 3; i++ {
|
||||
at := authtoken.TestAuthToken(t, conn, kms, scope.Global.String())
|
||||
globalTokens = append(globalTokens, &pb.AuthToken{
|
||||
pbat := &pb.AuthToken{
|
||||
Id: at.GetPublicId(),
|
||||
ScopeId: at.GetScopeId(),
|
||||
UserId: at.GetIamUserId(),
|
||||
@@ -300,15 +311,17 @@ func TestList(t *testing.T) {
|
||||
ApproximateLastUsedTime: at.GetApproximateLastAccessTime().GetTimestamp(),
|
||||
ExpirationTime: at.GetExpirationTime().GetTimestamp(),
|
||||
Scope: &scopes.ScopeInfo{Id: scope.Global.String(), Type: scope.Global.String(), Name: scope.Global.String(), Description: "Global Scope"},
|
||||
AuthorizedActions: testAuthorizedActions,
|
||||
})
|
||||
AuthorizedActions: fullAuthorizedActions,
|
||||
}
|
||||
globalTokens = append(globalTokens, pbat)
|
||||
allTokens = append(allTokens, pbat)
|
||||
}
|
||||
|
||||
orgWithSomeTokens, _ := iam.TestScopes(t, iamRepo)
|
||||
var wantSomeTokens []*pb.AuthToken
|
||||
for i := 0; i < 3; i++ {
|
||||
at := authtoken.TestAuthToken(t, conn, kms, orgWithSomeTokens.GetPublicId())
|
||||
wantSomeTokens = append(wantSomeTokens, &pb.AuthToken{
|
||||
pbat := &pb.AuthToken{
|
||||
Id: at.GetPublicId(),
|
||||
ScopeId: at.GetScopeId(),
|
||||
UserId: at.GetIamUserId(),
|
||||
@@ -319,15 +332,17 @@ func TestList(t *testing.T) {
|
||||
ApproximateLastUsedTime: at.GetApproximateLastAccessTime().GetTimestamp(),
|
||||
ExpirationTime: at.GetExpirationTime().GetTimestamp(),
|
||||
Scope: &scopes.ScopeInfo{Id: orgWithSomeTokens.GetPublicId(), Type: scope.Org.String(), ParentScopeId: scope.Global.String()},
|
||||
AuthorizedActions: testAuthorizedActions,
|
||||
})
|
||||
AuthorizedActions: fullAuthorizedActions,
|
||||
}
|
||||
wantSomeTokens = append(wantSomeTokens, pbat)
|
||||
allTokens = append(allTokens, pbat)
|
||||
}
|
||||
|
||||
orgWithOtherTokens, _ := iam.TestScopes(t, iamRepo)
|
||||
var wantOtherTokens []*pb.AuthToken
|
||||
for i := 0; i < 3; i++ {
|
||||
at := authtoken.TestAuthToken(t, conn, kms, orgWithOtherTokens.GetPublicId())
|
||||
wantOtherTokens = append(wantOtherTokens, &pb.AuthToken{
|
||||
pbat := &pb.AuthToken{
|
||||
Id: at.GetPublicId(),
|
||||
ScopeId: at.GetScopeId(),
|
||||
UserId: at.GetIamUserId(),
|
||||
@@ -338,12 +353,21 @@ func TestList(t *testing.T) {
|
||||
ApproximateLastUsedTime: at.GetApproximateLastAccessTime().GetTimestamp(),
|
||||
ExpirationTime: at.GetExpirationTime().GetTimestamp(),
|
||||
Scope: &scopes.ScopeInfo{Id: orgWithOtherTokens.GetPublicId(), Type: scope.Org.String(), ParentScopeId: scope.Global.String()},
|
||||
AuthorizedActions: testAuthorizedActions,
|
||||
})
|
||||
AuthorizedActions: fullAuthorizedActions,
|
||||
}
|
||||
wantOtherTokens = append(wantOtherTokens, pbat)
|
||||
allTokens = append(allTokens, pbat)
|
||||
}
|
||||
|
||||
allTokens := append(globalTokens, wantSomeTokens...)
|
||||
allTokens = append(allTokens, wantOtherTokens...)
|
||||
// Reverse slices since response is ordered by created_time descending (newest first)
|
||||
slices.Reverse(globalTokens)
|
||||
slices.Reverse(wantSomeTokens)
|
||||
slices.Reverse(wantOtherTokens)
|
||||
slices.Reverse(allTokens)
|
||||
|
||||
// Run analyze to update postgres estimates
|
||||
_, err = sqlDB.ExecContext(context.Background(), "analyze")
|
||||
require.NoError(t, err)
|
||||
|
||||
cases := []struct {
|
||||
name string
|
||||
@@ -354,17 +378,33 @@ func TestList(t *testing.T) {
|
||||
{
|
||||
name: "List Some Tokens",
|
||||
req: &pbs.ListAuthTokensRequest{ScopeId: orgWithSomeTokens.GetPublicId()},
|
||||
res: &pbs.ListAuthTokensResponse{Items: wantSomeTokens},
|
||||
res: &pbs.ListAuthTokensResponse{
|
||||
Items: wantSomeTokens,
|
||||
EstItemCount: 3,
|
||||
ResponseType: "complete",
|
||||
SortBy: "created_time",
|
||||
SortDir: "desc",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "List Other Tokens",
|
||||
req: &pbs.ListAuthTokensRequest{ScopeId: orgWithOtherTokens.GetPublicId()},
|
||||
res: &pbs.ListAuthTokensResponse{Items: wantOtherTokens},
|
||||
res: &pbs.ListAuthTokensResponse{
|
||||
Items: wantOtherTokens,
|
||||
EstItemCount: 3,
|
||||
ResponseType: "complete",
|
||||
SortBy: "created_time",
|
||||
SortDir: "desc",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "List No Token",
|
||||
req: &pbs.ListAuthTokensRequest{ScopeId: orgNoTokens.GetPublicId()},
|
||||
res: &pbs.ListAuthTokensResponse{},
|
||||
res: &pbs.ListAuthTokensResponse{
|
||||
ResponseType: "complete",
|
||||
SortBy: "created_time",
|
||||
SortDir: "desc",
|
||||
},
|
||||
},
|
||||
// TODO: When an org doesn't exist, we should return a 404 instead of an empty list.
|
||||
{
|
||||
@@ -375,7 +415,24 @@ func TestList(t *testing.T) {
|
||||
{
|
||||
name: "List Recursively",
|
||||
req: &pbs.ListAuthTokensRequest{ScopeId: "global", Recursive: true},
|
||||
res: &pbs.ListAuthTokensResponse{Items: allTokens},
|
||||
res: &pbs.ListAuthTokensResponse{
|
||||
Items: allTokens,
|
||||
EstItemCount: 9,
|
||||
ResponseType: "complete",
|
||||
SortBy: "created_time",
|
||||
SortDir: "desc",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Paginate listing",
|
||||
req: &pbs.ListAuthTokensRequest{ScopeId: "global", Recursive: true, PageSize: 2},
|
||||
res: &pbs.ListAuthTokensResponse{
|
||||
Items: allTokens[:2],
|
||||
EstItemCount: 9,
|
||||
ResponseType: "delta",
|
||||
SortBy: "created_time",
|
||||
SortDir: "desc",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Filter to Some Tokens",
|
||||
@@ -383,12 +440,22 @@ func TestList(t *testing.T) {
|
||||
ScopeId: "global", Recursive: true,
|
||||
Filter: fmt.Sprintf(`"/item/scope/id"==%q`, orgWithSomeTokens.GetPublicId()),
|
||||
},
|
||||
res: &pbs.ListAuthTokensResponse{Items: wantSomeTokens},
|
||||
res: &pbs.ListAuthTokensResponse{
|
||||
Items: wantSomeTokens,
|
||||
EstItemCount: 3,
|
||||
ResponseType: "complete",
|
||||
SortBy: "created_time",
|
||||
SortDir: "desc",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Filter All Tokens",
|
||||
req: &pbs.ListAuthTokensRequest{ScopeId: orgWithOtherTokens.GetPublicId(), Filter: `"/item/scope/id"=="thisdoesntmatch"`},
|
||||
res: &pbs.ListAuthTokensResponse{},
|
||||
res: &pbs.ListAuthTokensResponse{
|
||||
ResponseType: "complete",
|
||||
SortBy: "created_time",
|
||||
SortDir: "desc",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Filter Bad Format",
|
||||
@@ -398,7 +465,7 @@ func TestList(t *testing.T) {
|
||||
}
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
s, err := authtokens.NewService(context.Background(), repoFn, iamRepoFn)
|
||||
s, err := authtokens.NewService(context.Background(), repoFn, iamRepoFn, 1000)
|
||||
assert, require := assert.New(t), require.New(t)
|
||||
require.NoError(err, "Couldn't create new user service.")
|
||||
|
||||
@@ -419,7 +486,8 @@ func TestList(t *testing.T) {
|
||||
cmpopts.SortSlices(func(a, b string) bool {
|
||||
return a < b
|
||||
}),
|
||||
), "ListAuthTokens() with scope %q got response %q, wanted %q", tc.req.GetScopeId(), got, tc.res)
|
||||
protocmp.IgnoreFields(&pbs.ListAuthTokensResponse{}, "list_token"),
|
||||
))
|
||||
|
||||
// Now check anon listing
|
||||
got, gErr = s.ListAuthTokens(auth.DisabledAuthTestContext(iamRepoFn, tc.req.GetScopeId(), auth.WithUserId(globals.AnonymousUserId)), tc.req)
|
||||
@@ -435,6 +503,274 @@ func TestList(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func authTokenToProto(at *authtoken.AuthToken, scope *scopes.ScopeInfo, authorizedActions []string) *pb.AuthToken {
|
||||
return &pb.AuthToken{
|
||||
Id: at.GetPublicId(),
|
||||
ScopeId: at.GetScopeId(),
|
||||
UserId: at.GetIamUserId(),
|
||||
AuthMethodId: at.GetAuthMethodId(),
|
||||
AccountId: at.GetAuthAccountId(),
|
||||
CreatedTime: at.GetCreateTime().GetTimestamp(),
|
||||
UpdatedTime: at.GetUpdateTime().GetTimestamp(),
|
||||
ApproximateLastUsedTime: at.GetApproximateLastAccessTime().GetTimestamp(),
|
||||
ExpirationTime: at.GetExpirationTime().GetTimestamp(),
|
||||
Scope: scope,
|
||||
AuthorizedActions: authorizedActions,
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
kms := kms.TestKms(t, conn, wrap)
|
||||
iamRepoFn := func() (*iam.Repository, error) {
|
||||
return iam.TestRepo(t, conn, wrap), nil
|
||||
}
|
||||
tokenRepoFn := func() (*authtoken.Repository, error) {
|
||||
return authtoken.NewRepository(ctx, rw, rw, kms)
|
||||
}
|
||||
serversRepoFn := func() (*server.Repository, error) {
|
||||
return server.NewRepository(ctx, rw, rw, kms)
|
||||
}
|
||||
iamRepo := iam.TestRepo(t, conn, wrap)
|
||||
tokenRepo, _ := tokenRepoFn()
|
||||
orgWithTokens, pwt := iam.TestScopes(t, iamRepo)
|
||||
|
||||
authMethod := password.TestAuthMethods(t, conn, orgWithTokens.GetPublicId(), 1)[0]
|
||||
// auth account is only used to join auth method to user.
|
||||
// We don't do anything else with the auth account in the test setup.
|
||||
acct := password.TestAccount(t, conn, authMethod.GetPublicId(), "test_user")
|
||||
|
||||
u := iam.TestUser(t, iamRepo, orgWithTokens.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())
|
||||
|
||||
var allTokens []*pb.AuthToken
|
||||
for i := 0; i < 9; i++ {
|
||||
at, _ := tokenRepo.CreateAuthToken(ctx, u, acct.GetPublicId())
|
||||
atp := authTokenToProto(at, &scopes.ScopeInfo{Id: orgWithTokens.GetPublicId(), Type: scope.Org.String(), ParentScopeId: scope.Global.String()}, selfAuthorizedActions)
|
||||
allTokens = append(allTokens, atp)
|
||||
}
|
||||
|
||||
a, err := authtokens.NewService(ctx, tokenRepoFn, iamRepoFn, 1000)
|
||||
require.NoError(t, err, "Couldn't create new user service.")
|
||||
|
||||
masterToken, _ := tokenRepo.CreateAuthToken(ctx, u, acct.GetPublicId())
|
||||
mtp := authTokenToProto(masterToken, &scopes.ScopeInfo{Id: orgWithTokens.GetPublicId(), Type: scope.Org.String(), ParentScopeId: scope.Global.String()}, selfAuthorizedActions)
|
||||
allTokens = append(allTokens, mtp)
|
||||
|
||||
slices.Reverse(allTokens)
|
||||
|
||||
// Run analyze to update postgres estimates
|
||||
_, err = sqlDB.ExecContext(context.Background(), "analyze")
|
||||
require.NoError(t, err)
|
||||
|
||||
requestInfo := authpb.RequestInfo{
|
||||
TokenFormat: uint32(auth.AuthTokenTypeBearer),
|
||||
Token: masterToken.GetToken(),
|
||||
PublicId: masterToken.GetPublicId(),
|
||||
}
|
||||
requestContext := context.WithValue(ctx, requests.ContextRequestInformationKey, &requests.RequestContext{})
|
||||
ctx = auth.NewVerifierContext(requestContext, iamRepoFn, tokenRepoFn, serversRepoFn, kms, &requestInfo)
|
||||
|
||||
// Start paginating, recursively
|
||||
req := &pbs.ListAuthTokensRequest{
|
||||
ScopeId: "global",
|
||||
Recursive: true,
|
||||
Filter: "",
|
||||
ListToken: "",
|
||||
PageSize: 2,
|
||||
}
|
||||
got, err := a.ListAuthTokens(ctx, req)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, got.GetItems(), 2)
|
||||
// Compare without comparing the list token
|
||||
assert.Empty(t,
|
||||
cmp.Diff(
|
||||
got,
|
||||
&pbs.ListAuthTokensResponse{
|
||||
Items: allTokens[0:2],
|
||||
ResponseType: "delta",
|
||||
SortBy: "created_time",
|
||||
SortDir: "desc",
|
||||
RemovedIds: nil,
|
||||
EstItemCount: 10,
|
||||
},
|
||||
protocmp.Transform(),
|
||||
protocmp.IgnoreFields(&pbs.ListAuthTokensResponse{}, "list_token"),
|
||||
),
|
||||
)
|
||||
|
||||
// Request second page
|
||||
req.ListToken = got.ListToken
|
||||
got, err = a.ListAuthTokens(ctx, req)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, got.GetItems(), 2)
|
||||
// Compare without comparing the list token
|
||||
assert.Empty(t,
|
||||
cmp.Diff(
|
||||
got,
|
||||
&pbs.ListAuthTokensResponse{
|
||||
Items: allTokens[2:4],
|
||||
ResponseType: "delta",
|
||||
SortBy: "created_time",
|
||||
SortDir: "desc",
|
||||
RemovedIds: nil,
|
||||
EstItemCount: 10,
|
||||
},
|
||||
protocmp.Transform(),
|
||||
protocmp.IgnoreFields(&pbs.ListAuthTokensResponse{}, "list_token"),
|
||||
),
|
||||
)
|
||||
|
||||
// Request rest of results
|
||||
req.ListToken = got.ListToken
|
||||
req.PageSize = 10
|
||||
got, err = a.ListAuthTokens(ctx, req)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, got.GetItems(), 6)
|
||||
// Compare without comparing the list token
|
||||
assert.Empty(t,
|
||||
cmp.Diff(
|
||||
got,
|
||||
&pbs.ListAuthTokensResponse{
|
||||
Items: allTokens[4:],
|
||||
ResponseType: "complete",
|
||||
SortBy: "created_time",
|
||||
SortDir: "desc",
|
||||
RemovedIds: nil,
|
||||
EstItemCount: 10,
|
||||
},
|
||||
protocmp.Transform(),
|
||||
protocmp.IgnoreFields(&pbs.ListAuthTokensResponse{}, "list_token"),
|
||||
),
|
||||
)
|
||||
|
||||
// Create two auth tokens in lieu of updating
|
||||
at1, _ := tokenRepo.CreateAuthToken(ctx, u, acct.GetPublicId())
|
||||
atp1 := authTokenToProto(at1, &scopes.ScopeInfo{Id: orgWithTokens.GetPublicId(), Type: scope.Org.String(), ParentScopeId: scope.Global.String()}, selfAuthorizedActions)
|
||||
// Add to the front since it's most recently updated
|
||||
allTokens = append([]*pb.AuthToken{atp1}, allTokens...)
|
||||
|
||||
at2, _ := tokenRepo.CreateAuthToken(ctx, u, acct.GetPublicId())
|
||||
atp2 := authTokenToProto(at2, &scopes.ScopeInfo{Id: orgWithTokens.GetPublicId(), Type: scope.Org.String(), ParentScopeId: scope.Global.String()}, selfAuthorizedActions)
|
||||
// Add to the front since it's most recently updated
|
||||
allTokens = append([]*pb.AuthToken{atp2}, allTokens...)
|
||||
|
||||
// Delete one of the other auth tokens
|
||||
_, err = tokenRepo.DeleteAuthToken(ctx, allTokens[len(allTokens)-1].Id)
|
||||
require.NoError(t, err)
|
||||
deletedAuthToken := allTokens[len(allTokens)-1]
|
||||
allTokens = allTokens[:len(allTokens)-1]
|
||||
|
||||
// Run analyze to update postgres estimates
|
||||
_, err = sqlDB.ExecContext(ctx, "analyze")
|
||||
require.NoError(t, err)
|
||||
|
||||
// Request updated results
|
||||
req.ListToken = got.ListToken
|
||||
req.PageSize = 1
|
||||
got, err = a.ListAuthTokens(ctx, req)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, got.GetItems(), 1)
|
||||
// Compare without comparing the list token
|
||||
assert.Empty(t,
|
||||
cmp.Diff(
|
||||
got,
|
||||
&pbs.ListAuthTokensResponse{
|
||||
Items: []*pb.AuthToken{allTokens[0]},
|
||||
ResponseType: "delta",
|
||||
SortBy: "updated_time",
|
||||
SortDir: "desc",
|
||||
// Should contain the deleted auth token
|
||||
RemovedIds: []string{deletedAuthToken.Id},
|
||||
EstItemCount: 11,
|
||||
},
|
||||
protocmp.Transform(),
|
||||
protocmp.IgnoreFields(&pbs.ListAuthTokensResponse{}, "list_token"),
|
||||
),
|
||||
)
|
||||
|
||||
// Get next page
|
||||
req.ListToken = got.ListToken
|
||||
got, err = a.ListAuthTokens(ctx, req)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, got.GetItems(), 1)
|
||||
// Compare without comparing the list token
|
||||
assert.Empty(t,
|
||||
cmp.Diff(
|
||||
got,
|
||||
&pbs.ListAuthTokensResponse{
|
||||
Items: []*pb.AuthToken{allTokens[1]},
|
||||
ResponseType: "complete",
|
||||
SortBy: "updated_time",
|
||||
SortDir: "desc",
|
||||
RemovedIds: nil,
|
||||
EstItemCount: 11,
|
||||
},
|
||||
protocmp.Transform(),
|
||||
protocmp.IgnoreFields(&pbs.ListAuthTokensResponse{}, "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`, allTokens[len(allTokens)-2].Id, allTokens[len(allTokens)-1].Id)
|
||||
got, err = a.ListAuthTokens(ctx, req)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, got.GetItems(), 1)
|
||||
assert.Empty(t,
|
||||
cmp.Diff(
|
||||
got,
|
||||
&pbs.ListAuthTokensResponse{
|
||||
Items: []*pb.AuthToken{allTokens[len(allTokens)-2]},
|
||||
ResponseType: "delta",
|
||||
SortBy: "created_time",
|
||||
SortDir: "desc",
|
||||
// Should be empty again
|
||||
RemovedIds: nil,
|
||||
EstItemCount: 11,
|
||||
},
|
||||
protocmp.Transform(),
|
||||
protocmp.IgnoreFields(&pbs.ListAuthTokensResponse{}, "list_token"),
|
||||
),
|
||||
)
|
||||
req.ListToken = got.ListToken
|
||||
// Get the second page
|
||||
got, err = a.ListAuthTokens(ctx, req)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, got.GetItems(), 1)
|
||||
assert.Empty(t,
|
||||
cmp.Diff(
|
||||
got,
|
||||
&pbs.ListAuthTokensResponse{
|
||||
Items: []*pb.AuthToken{allTokens[len(allTokens)-1]},
|
||||
ResponseType: "complete",
|
||||
SortBy: "created_time",
|
||||
SortDir: "desc",
|
||||
RemovedIds: nil,
|
||||
EstItemCount: 11,
|
||||
},
|
||||
protocmp.Transform(),
|
||||
protocmp.IgnoreFields(&pbs.ListAuthTokensResponse{}, "list_token"),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
func TestDeleteSelf(t *testing.T) {
|
||||
testCtx := context.Background()
|
||||
conn, _ := db.TestSetup(t, "postgres")
|
||||
@@ -454,7 +790,7 @@ func TestDeleteSelf(t *testing.T) {
|
||||
return server.NewRepository(testCtx, rw, rw, kms)
|
||||
}
|
||||
|
||||
a, err := authtokens.NewService(testCtx, tokenRepoFn, iamRepoFn)
|
||||
a, err := authtokens.NewService(testCtx, tokenRepoFn, iamRepoFn, 1000)
|
||||
require.NoError(t, err, "Couldn't create new auth token service.")
|
||||
|
||||
o, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrap))
|
||||
@@ -544,7 +880,7 @@ func TestDelete(t *testing.T) {
|
||||
org, _ := iam.TestScopes(t, iamRepo)
|
||||
at := authtoken.TestAuthToken(t, conn, kms, org.GetPublicId())
|
||||
|
||||
s, err := authtokens.NewService(ctx, repoFn, iamRepoFn)
|
||||
s, err := authtokens.NewService(ctx, repoFn, iamRepoFn, 1000)
|
||||
require.NoError(t, err, "Error when getting new user service.")
|
||||
|
||||
cases := []struct {
|
||||
@@ -609,7 +945,7 @@ func TestDelete_twice(t *testing.T) {
|
||||
org, _ := iam.TestScopes(t, iamRepo)
|
||||
at := authtoken.TestAuthToken(t, conn, kms, org.GetPublicId())
|
||||
|
||||
s, err := authtokens.NewService(ctx, repoFn, iamRepoFn)
|
||||
s, err := authtokens.NewService(ctx, repoFn, iamRepoFn, 1000)
|
||||
require.NoError(err, "Error when getting new user service")
|
||||
req := &pbs.DeleteAuthTokenRequest{
|
||||
Id: at.GetPublicId(),
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
-- Copyright (c) HashiCorp, Inc.
|
||||
-- SPDX-License-Identifier: BUSL-1.1
|
||||
|
||||
begin;
|
||||
|
||||
-- Add new indexes for the create time and update time queries.
|
||||
create index auth_token_create_time_public_id_idx
|
||||
on auth_token (create_time desc, public_id asc);
|
||||
create index auth_token_update_time_public_id_idx
|
||||
on auth_token (update_time desc, public_id asc);
|
||||
|
||||
analyze auth_token;
|
||||
|
||||
commit;
|
||||
@@ -0,0 +1,12 @@
|
||||
-- Copyright (c) HashiCorp, Inc.
|
||||
-- SPDX-License-Identifier: BUSL-1.1
|
||||
|
||||
begin;
|
||||
select plan(2);
|
||||
|
||||
select has_index('auth_token', 'auth_token_create_time_public_id_idx', array['create_time', 'public_id']);
|
||||
select has_index('auth_token', 'auth_token_update_time_public_id_idx', array['update_time', 'public_id']);
|
||||
|
||||
select * from finish();
|
||||
|
||||
rollback;
|
||||
@@ -585,6 +585,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": [
|
||||
@@ -7551,6 +7566,34 @@
|
||||
"type": "object",
|
||||
"$ref": "#/definitions/controller.api.resources.authtokens.v1.AuthToken"
|
||||
}
|
||||
},
|
||||
"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."
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -128,6 +128,15 @@ type ListAuthTokensRequest 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 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 *ListAuthTokensRequest) Reset() {
|
||||
@@ -183,12 +192,46 @@ func (x *ListAuthTokensRequest) GetFilter() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *ListAuthTokensRequest) GetListToken() string {
|
||||
if x != nil {
|
||||
return x.ListToken
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *ListAuthTokensRequest) GetPageSize() uint32 {
|
||||
if x != nil {
|
||||
return x.PageSize
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
type ListAuthTokensResponse struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
Items []*authtokens.AuthToken `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 *ListAuthTokensResponse) Reset() {
|
||||
@@ -230,6 +273,48 @@ func (x *ListAuthTokensResponse) GetItems() []*authtokens.AuthToken {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *ListAuthTokensResponse) GetResponseType() string {
|
||||
if x != nil {
|
||||
return x.ResponseType
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *ListAuthTokensResponse) GetListToken() string {
|
||||
if x != nil {
|
||||
return x.ListToken
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *ListAuthTokensResponse) GetSortBy() string {
|
||||
if x != nil {
|
||||
return x.SortBy
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *ListAuthTokensResponse) GetSortDir() string {
|
||||
if x != nil {
|
||||
return x.SortDir
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *ListAuthTokensResponse) GetRemovedIds() []string {
|
||||
if x != nil {
|
||||
return x.RemovedIds
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *ListAuthTokensResponse) GetEstItemCount() uint32 {
|
||||
if x != nil {
|
||||
return x.EstItemCount
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
type DeleteAuthTokenRequest struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
@@ -339,65 +424,81 @@ var file_controller_api_services_v1_authtokens_service_proto_rawDesc = []byte{
|
||||
0x20, 0x01, 0x28, 0x0b, 0x32, 0x31, 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, 0x75, 0x74, 0x68, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x75,
|
||||
0x74, 0x68, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x52, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x22, 0x69, 0x0a,
|
||||
0x15, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x75, 0x74, 0x68, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 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, 0x61, 0x0a, 0x16, 0x4c, 0x69, 0x73, 0x74,
|
||||
0x74, 0x68, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x52, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x22, 0xa7, 0x01,
|
||||
0x0a, 0x15, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x75, 0x74, 0x68, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 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, 0xa7, 0x02, 0x0a, 0x16, 0x4c, 0x69, 0x73, 0x74,
|
||||
0x41, 0x75, 0x74, 0x68, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e,
|
||||
0x73, 0x65, 0x12, 0x47, 0x0a, 0x05, 0x69, 0x74, 0x65, 0x6d, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28,
|
||||
0x0b, 0x32, 0x31, 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, 0x75, 0x74,
|
||||
0x68, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x75, 0x74, 0x68, 0x54,
|
||||
0x6f, 0x6b, 0x65, 0x6e, 0x52, 0x05, 0x69, 0x74, 0x65, 0x6d, 0x73, 0x22, 0x28, 0x0a, 0x16, 0x44,
|
||||
0x6f, 0x6b, 0x65, 0x6e, 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, 0x28, 0x0a, 0x16, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x41, 0x75, 0x74, 0x68, 0x54,
|
||||
0x6f, 0x6b, 0x65, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69,
|
||||
0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x22, 0x19, 0x0a, 0x17, 0x44,
|
||||
0x65, 0x6c, 0x65, 0x74, 0x65, 0x41, 0x75, 0x74, 0x68, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x52, 0x65,
|
||||
0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28,
|
||||
0x09, 0x52, 0x02, 0x69, 0x64, 0x22, 0x19, 0x0a, 0x17, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x41,
|
||||
0x75, 0x74, 0x68, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65,
|
||||
0x32, 0xac, 0x04, 0x0a, 0x10, 0x41, 0x75, 0x74, 0x68, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x53, 0x65,
|
||||
0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0xb3, 0x01, 0x0a, 0x0c, 0x47, 0x65, 0x74, 0x41, 0x75, 0x74,
|
||||
0x68, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 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, 0x47, 0x65, 0x74, 0x41, 0x75, 0x74, 0x68, 0x54, 0x6f, 0x6b, 0x65, 0x6e,
|
||||
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, 0x47, 0x65, 0x74, 0x41, 0x75, 0x74, 0x68, 0x54, 0x6f, 0x6b, 0x65,
|
||||
0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x40, 0x92, 0x41, 0x1b, 0x12, 0x19,
|
||||
0x47, 0x65, 0x74, 0x73, 0x20, 0x61, 0x20, 0x73, 0x69, 0x6e, 0x67, 0x6c, 0x65, 0x20, 0x41, 0x75,
|
||||
0x74, 0x68, 0x20, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x2e, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x1c, 0x62,
|
||||
0x04, 0x69, 0x74, 0x65, 0x6d, 0x12, 0x14, 0x2f, 0x76, 0x31, 0x2f, 0x61, 0x75, 0x74, 0x68, 0x2d,
|
||||
0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x2f, 0x7b, 0x69, 0x64, 0x7d, 0x12, 0xab, 0x01, 0x0a, 0x0e,
|
||||
0x4c, 0x69, 0x73, 0x74, 0x41, 0x75, 0x74, 0x68, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x73, 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, 0x4c, 0x69, 0x73, 0x74,
|
||||
0x41, 0x75, 0x74, 0x68, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x73, 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, 0x4c,
|
||||
0x69, 0x73, 0x74, 0x41, 0x75, 0x74, 0x68, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x52, 0x65, 0x73,
|
||||
0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x32, 0x92, 0x41, 0x18, 0x12, 0x16, 0x4c, 0x69, 0x73, 0x74,
|
||||
0x73, 0x20, 0x61, 0x6c, 0x6c, 0x20, 0x41, 0x75, 0x74, 0x68, 0x20, 0x54, 0x6f, 0x6b, 0x65, 0x6e,
|
||||
0x73, 0x2e, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x11, 0x12, 0x0f, 0x2f, 0x76, 0x31, 0x2f, 0x61, 0x75,
|
||||
0x74, 0x68, 0x2d, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x12, 0xb3, 0x01, 0x0a, 0x0f, 0x44, 0x65,
|
||||
0x6c, 0x65, 0x74, 0x65, 0x41, 0x75, 0x74, 0x68, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x32, 0x2e,
|
||||
0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x32, 0xac, 0x04, 0x0a, 0x10, 0x41, 0x75, 0x74, 0x68, 0x54,
|
||||
0x6f, 0x6b, 0x65, 0x6e, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0xb3, 0x01, 0x0a, 0x0c,
|
||||
0x47, 0x65, 0x74, 0x41, 0x75, 0x74, 0x68, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 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, 0x47, 0x65, 0x74, 0x41, 0x75, 0x74,
|
||||
0x68, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 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, 0x44, 0x65, 0x6c, 0x65, 0x74,
|
||||
0x65, 0x41, 0x75, 0x74, 0x68, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73,
|
||||
0x74, 0x1a, 0x33, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61,
|
||||
0x70, 0x69, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x44,
|
||||
0x65, 0x6c, 0x65, 0x74, 0x65, 0x41, 0x75, 0x74, 0x68, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x52, 0x65,
|
||||
0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x37, 0x92, 0x41, 0x18, 0x12, 0x16, 0x44, 0x65, 0x6c,
|
||||
0x65, 0x74, 0x65, 0x73, 0x20, 0x61, 0x6e, 0x20, 0x41, 0x75, 0x74, 0x68, 0x20, 0x54, 0x6f, 0x6b,
|
||||
0x65, 0x6e, 0x2e, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x16, 0x2a, 0x14, 0x2f, 0x76, 0x31, 0x2f, 0x61,
|
||||
0x75, 0x74, 0x68, 0x2d, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x2f, 0x7b, 0x69, 0x64, 0x7d, 0x42,
|
||||
0x4d, 0x5a, 0x4b, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x68, 0x61,
|
||||
0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2f, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x61, 0x72, 0x79,
|
||||
0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2f, 0x67, 0x65, 0x6e, 0x2f, 0x63, 0x6f,
|
||||
0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x73, 0x65, 0x72,
|
||||
0x76, 0x69, 0x63, 0x65, 0x73, 0x3b, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x62, 0x06,
|
||||
0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
|
||||
0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x41, 0x75,
|
||||
0x74, 0x68, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22,
|
||||
0x40, 0x92, 0x41, 0x1b, 0x12, 0x19, 0x47, 0x65, 0x74, 0x73, 0x20, 0x61, 0x20, 0x73, 0x69, 0x6e,
|
||||
0x67, 0x6c, 0x65, 0x20, 0x41, 0x75, 0x74, 0x68, 0x20, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x2e, 0x82,
|
||||
0xd3, 0xe4, 0x93, 0x02, 0x1c, 0x62, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x12, 0x14, 0x2f, 0x76, 0x31,
|
||||
0x2f, 0x61, 0x75, 0x74, 0x68, 0x2d, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x2f, 0x7b, 0x69, 0x64,
|
||||
0x7d, 0x12, 0xab, 0x01, 0x0a, 0x0e, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x75, 0x74, 0x68, 0x54, 0x6f,
|
||||
0x6b, 0x65, 0x6e, 0x73, 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, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x75, 0x74, 0x68, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x73,
|
||||
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, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x75, 0x74, 0x68, 0x54, 0x6f, 0x6b,
|
||||
0x65, 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x32, 0x92, 0x41, 0x18,
|
||||
0x12, 0x16, 0x4c, 0x69, 0x73, 0x74, 0x73, 0x20, 0x61, 0x6c, 0x6c, 0x20, 0x41, 0x75, 0x74, 0x68,
|
||||
0x20, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x2e, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x11, 0x12, 0x0f,
|
||||
0x2f, 0x76, 0x31, 0x2f, 0x61, 0x75, 0x74, 0x68, 0x2d, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x12,
|
||||
0xb3, 0x01, 0x0a, 0x0f, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x41, 0x75, 0x74, 0x68, 0x54, 0x6f,
|
||||
0x6b, 0x65, 0x6e, 0x12, 0x32, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72,
|
||||
0x2e, 0x61, 0x70, 0x69, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x76, 0x31,
|
||||
0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x41, 0x75, 0x74, 0x68, 0x54, 0x6f, 0x6b, 0x65, 0x6e,
|
||||
0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x33, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f,
|
||||
0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65,
|
||||
0x73, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x41, 0x75, 0x74, 0x68, 0x54,
|
||||
0x6f, 0x6b, 0x65, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x37, 0x92, 0x41,
|
||||
0x18, 0x12, 0x16, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x73, 0x20, 0x61, 0x6e, 0x20, 0x41, 0x75,
|
||||
0x74, 0x68, 0x20, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x2e, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x16, 0x2a,
|
||||
0x14, 0x2f, 0x76, 0x31, 0x2f, 0x61, 0x75, 0x74, 0x68, 0x2d, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x73,
|
||||
0x2f, 0x7b, 0x69, 0x64, 0x7d, 0x42, 0x4d, 0x5a, 0x4b, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e,
|
||||
0x63, 0x6f, 0x6d, 0x2f, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2f, 0x62, 0x6f,
|
||||
0x75, 0x6e, 0x64, 0x61, 0x72, 0x79, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2f,
|
||||
0x67, 0x65, 0x6e, 0x2f, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2f, 0x61,
|
||||
0x70, 0x69, 0x2f, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x3b, 0x73, 0x65, 0x72, 0x76,
|
||||
0x69, 0x63, 0x65, 0x73, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
|
||||
}
|
||||
|
||||
var (
|
||||
|
||||
@@ -52,10 +52,39 @@ message ListAuthTokensRequest {
|
||||
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 default page configured,
|
||||
// an error will be returned.
|
||||
uint32 page_size = 50 [json_name = "page_size"]; // @gotags: `class:"public"`
|
||||
}
|
||||
|
||||
message ListAuthTokensResponse {
|
||||
repeated resources.authtokens.v1.AuthToken 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 DeleteAuthTokenRequest {
|
||||
|
||||
Reference in new issue
Block a user