Add ListResolvableAliases to the user service (#4609)

* Add ListResolvableAliases to the user service

There are a few non common ways this method uses our ACL
and grant system. A user is able to list resolvable
aliases for another user if they are granted permission
to do so. That means we had to load the grants for
the user being listed for and not the requester when
determining if an alias is able to resolve to a destination
for which the requested user has permission.

Similarly, the grant hash used for the list pagination
is required to be that of the user being listed and not
the requester.
This commit is contained in:
Todd authored and GitHub committed 2024-04-17 08:26:46 -07:00
1 parent 267b8ab972
commit 0d67b86543
26 files changed
+3090 -444

No files matched your search

@@ -0,0 +1,220 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1
package target
import (
"context"
"database/sql"
"fmt"
"strings"
"time"
"github.com/hashicorp/boundary/internal/db"
"github.com/hashicorp/boundary/internal/db/timestamp"
"github.com/hashicorp/boundary/internal/errors"
"github.com/hashicorp/boundary/internal/perms"
)
// targetAndScopeIdsForDestinations returns the target ids for which there is
// at least one permission. If all targets in a specific scope are granted
// permission for an action, then the scope id is in the returned scope id slice.
func targetAndScopeIdsForDestinations(perms []perms.Permission) ([]string, []string) {
var targetIds, scopeIds []string
for _, perm := range perms {
switch {
case perm.All:
scopeIds = append(scopeIds, perm.ScopeId)
case len(perm.ResourceIds) > 0:
targetIds = append(targetIds, perm.ResourceIds...)
}
}
return targetIds, scopeIds
}
// listResolvableAliases lists aliases which have a destination id set to that
// of a target for which there is permission in the provided slice of permissions.
// Only WithLimit and WithStartPageAfterItem options are supported.
func (r *Repository) listResolvableAliases(ctx context.Context, permissions []perms.Permission, opt ...Option) ([]*Alias, time.Time, error) {
const op = "target.(Repository).listResolvableAliases"
switch {
case len(permissions) == 0:
return nil, time.Time{}, errors.New(ctx, errors.InvalidParameter, op, "missing permissions")
}
toTargetIds, toTargetsInScopeIds := targetAndScopeIdsForDestinations(permissions)
opts, err := getOpts(opt...)
if err != nil {
return nil, time.Time{}, errors.Wrap(ctx, err, op)
}
limit := r.defaultLimit
switch {
case opts.withLimit > 0:
// non-zero signals an override of the default limit for the repo.
limit = opts.withLimit
case opts.withLimit < 0:
return nil, time.Time{}, errors.New(ctx, errors.InvalidParameter, op, "limit must be non-negative")
}
var args []any
var destinationIdClauses []string
if len(toTargetIds) > 0 {
destinationIdClauses = append(destinationIdClauses, "destination_id in @target_ids")
args = append(args, sql.Named("target_ids", toTargetIds))
}
if len(toTargetsInScopeIds) > 0 {
destinationIdClauses = append(destinationIdClauses, "destination_id in (select public_id from target where project_id in @target_scope_ids)")
args = append(args, sql.Named("target_scope_ids", toTargetsInScopeIds))
}
if len(destinationIdClauses) == 0 {
return nil, time.Time{}, errors.New(ctx, errors.InvalidParameter, op, "no target ids or scope ids provided")
}
whereClause := fmt.Sprintf("destination_id is not null and (%s)", strings.Join(destinationIdClauses, " or "))
if opts.withStartPageAfterItem != nil {
whereClause = fmt.Sprintf("(create_time, public_id) < (@last_item_create_time, @last_item_id) and %s", whereClause)
args = append(args,
sql.Named("last_item_create_time", opts.withStartPageAfterItem.GetCreateTime()),
sql.Named("last_item_id", opts.withStartPageAfterItem.GetPublicId()),
)
}
dbOpts := []db.Option{db.WithLimit(limit), db.WithOrder("create_time desc, public_id desc"), db.WithDebug(true)}
return r.queryAliases(ctx, whereClause, args, dbOpts...)
}
// listResolvableAliasesRefresh lists aliases limited by the list
// permissions of the repository.
// Supported options:
// - withLimit
// - withStartPageAfterItem
func (r *Repository) listResolvableAliasesRefresh(ctx context.Context, updatedAfter time.Time, permissions []perms.Permission, opt ...Option) ([]*Alias, time.Time, error) {
const op = "target.(Repository).listResolvableAliasesRefresh"
switch {
case updatedAfter.IsZero():
return nil, time.Time{}, errors.New(ctx, errors.InvalidParameter, op, "missing updated after time")
case len(permissions) == 0:
return nil, time.Time{}, errors.New(ctx, errors.InvalidParameter, op, "missing permissions")
}
toTargetIds, toTargetsInScopeIds := targetAndScopeIdsForDestinations(permissions)
opts, err := getOpts(opt...)
if err != nil {
return nil, time.Time{}, errors.Wrap(ctx, err, op)
}
limit := r.defaultLimit
switch {
case opts.withLimit > 0:
// non-zero signals an override of the default limit for the repo.
limit = opts.withLimit
case opts.withLimit < 0:
return nil, time.Time{}, errors.New(ctx, errors.InvalidParameter, op, "limit must be non-negative")
}
var args []any
var destinationIdClauses []string
if len(toTargetIds) > 0 {
destinationIdClauses = append(destinationIdClauses, "destination_id in @target_ids")
args = append(args, sql.Named("target_ids", toTargetIds))
}
if len(toTargetsInScopeIds) > 0 {
destinationIdClauses = append(destinationIdClauses, "destination_id in (select public_id from target where project_id in @target_scope_ids)")
args = append(args, sql.Named("target_scope_ids", toTargetsInScopeIds))
}
if len(destinationIdClauses) == 0 {
return nil, time.Time{}, errors.New(ctx, errors.InvalidParameter, op, "no target ids or scope ids provided")
}
whereClause := fmt.Sprintf("update_time > @updated_after_time and destination_id is not null and (%s)",
strings.Join(destinationIdClauses, " or "))
args = append(args,
sql.Named("updated_after_time", timestamp.New(updatedAfter)),
)
if opts.withStartPageAfterItem != nil {
whereClause = fmt.Sprintf("(update_time, public_id) < (@last_item_update_time, @last_item_id) and %s", whereClause)
args = append(args,
sql.Named("last_item_update_time", opts.withStartPageAfterItem.GetUpdateTime()),
sql.Named("last_item_id", opts.withStartPageAfterItem.GetPublicId()),
)
}
dbOpts := []db.Option{db.WithLimit(limit), db.WithOrder("update_time desc, public_id desc")}
return r.queryAliases(ctx, whereClause, args, dbOpts...)
}
// listRemovedResolvableIds lists the public IDs of any aliases deleted since
// the timestamp provided or which have been updated since the timestamp provided
// and do not have a destination id set to the id of a target for which there
// are permissions in the provided slice of permissions.
func (r *Repository) listRemovedResolvableAliasIds(ctx context.Context, since time.Time, permissions []perms.Permission) ([]string, time.Time, error) {
const op = "target.(Repository).listRemovedResolvableIds"
switch {
case len(permissions) == 0:
// while a lack of permissions is one way for targets to not be included
// in the list of resolvable aliases, if permissions were always empty
// then no aliases would have been returned in the first place and so
// no ids would need to be removed. If permissions were changed to
// become empty, then the list token would be invalidated and we shouldnt
// have made it here, so it is an error for an empty slice of permissions
// to be provided.
return nil, time.Time{}, errors.New(ctx, errors.InvalidParameter, op, "missing permissions")
}
toTargetIds, toTargetsInScopeIds := targetAndScopeIdsForDestinations(permissions)
var args []any
var destinationIdClauses []string
if len(toTargetIds) > 0 {
destinationIdClauses = append(destinationIdClauses, "destination_id not in @target_ids")
args = append(args, sql.Named("target_ids", toTargetIds))
}
if len(toTargetsInScopeIds) > 0 {
destinationIdClauses = append(destinationIdClauses, "destination_id not in (select public_id from target where project_id in @target_scope_ids)")
args = append(args, sql.Named("target_scope_ids", toTargetsInScopeIds))
}
if len(destinationIdClauses) == 0 {
return nil, time.Time{}, errors.New(ctx, errors.InvalidParameter, op, "no target ids or scope ids provided")
}
whereClause := fmt.Sprintf("update_time > @updated_after_time and (destination_id is null or (%s))",
strings.Join(destinationIdClauses, " and "))
args = append(args,
sql.Named("updated_after_time", timestamp.New(since)),
)
// The calculating of the deleted aliases and the non matching alises
// must happen in the same transaction to ensure consistency.
var notMatchingAliases []*Alias
var deletedAliases []*deletedAlias
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, &deletedAliases, "delete_time >= ?", []any{since}); err != nil {
return errors.Wrap(ctx, err, op, errors.WithMsg("failed to query deleted aliases"))
}
var inRet []*Alias
if err := r.SearchWhere(ctx, &inRet, whereClause, args); err != nil {
return errors.Wrap(ctx, err, op)
}
notMatchingAliases = inRet
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 aliasIds []string
for _, da := range deletedAliases {
aliasIds = append(aliasIds, da.PublicId)
}
for _, na := range notMatchingAliases {
aliasIds = append(aliasIds, na.PublicId)
}
return aliasIds, transactionTimestamp, nil
}
+45
View File
@@ -9,6 +9,7 @@ import (
"github.com/hashicorp/boundary/internal/errors"
"github.com/hashicorp/boundary/internal/pagination"
"github.com/hashicorp/boundary/internal/perms"
)
// ListAliases lists up to page size aliases, filtering out entries that
@@ -51,3 +52,47 @@ func ListAliases(
return pagination.List(ctx, grantsHash, pageSize, filterItemFn, listItemsFn, repo.estimatedCount)
}
// ListResolvableAliases lists up to page size aliases, filtering out entries that
// do not pass the filter item function. It will automatically request
// more aliases from the database, at page size chunks, to fill the page. Only
// aliases which have the destination id set to a target for which there are
// permissions in the provided slice will be returned.
// It returns a new list token used to continue pagination or refresh items.
// Aliases are ordered by create time descending (most recently created first).
func ListResolvableAliases(
ctx context.Context,
grantsHash []byte,
pageSize int,
repo *Repository,
permissions []perms.Permission,
) (*pagination.ListResponse[*Alias], error) {
const op = "target.ListResolvableAliases"
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 repo == nil:
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing repo")
case len(permissions) == 0:
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing target permissions")
}
listItemsFn := func(ctx context.Context, lastPageItem *Alias, limit int) ([]*Alias, time.Time, error) {
opts := []Option{
WithLimit(limit),
}
if lastPageItem != nil {
opts = append(opts, WithStartPageAfterItem(lastPageItem))
}
return repo.listResolvableAliases(ctx, permissions, opts...)
}
return pagination.List(ctx, grantsHash, pageSize, alwaysTrueFilterFn, listItemsFn, repo.estimatedCount)
}
func alwaysTrueFilterFn(context.Context, *Alias) (bool, error) {
return true, nil
}
@@ -10,6 +10,7 @@ import (
"github.com/hashicorp/boundary/internal/errors"
"github.com/hashicorp/boundary/internal/listtoken"
"github.com/hashicorp/boundary/internal/pagination"
"github.com/hashicorp/boundary/internal/perms"
"github.com/hashicorp/boundary/internal/types/resource"
)
@@ -68,3 +69,58 @@ func ListAliasesPage(
return pagination.ListPage(ctx, grantsHash, pageSize, filterItemFn, listItemsFn, repo.estimatedCount, tok)
}
// ListResolvableAliasesPage lists up to page size aliases, filtering out entries that
// do not pass the filter item function. It will automatically request
// more aliases from the database, at page size chunks, to fill the page.
// Only aliases which resolve to a target for which there are permissions in the
// included slice of permissions are returned.
// 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.
// Aliases are ordered by create time descending (most recently created first).
func ListResolvableAliasesPage(
ctx context.Context,
grantsHash []byte,
pageSize int,
tok *listtoken.Token,
repo *Repository,
perms []perms.Permission,
) (*pagination.ListResponse[*Alias], error) {
const op = "target.ListResolvableAliasesPage"
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 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 tok.ResourceType != resource.Alias:
return nil, errors.New(ctx, errors.InvalidParameter, op, "token did not have a alias resource type")
case len(perms) == 0:
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing permissions")
}
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 *Alias, limit int) ([]*Alias, 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.listResolvableAliases(ctx, perms, opts...)
}
return pagination.ListPage(ctx, grantsHash, pageSize, alwaysTrueFilterFn, listItemsFn, repo.estimatedCount, tok)
}
@@ -11,6 +11,7 @@ import (
"github.com/hashicorp/boundary/internal/errors"
"github.com/hashicorp/boundary/internal/listtoken"
"github.com/hashicorp/boundary/internal/pagination"
"github.com/hashicorp/boundary/internal/perms"
"github.com/hashicorp/boundary/internal/types/resource"
)
@@ -74,3 +75,63 @@ func ListAliasesRefresh(
return pagination.ListRefresh(ctx, grantsHash, pageSize, filterItemFn, listItemsFn, repo.estimatedCount, listDeletedIdsFn, tok)
}
// ListResolvableAliasesRefresh lists up to page size aliases, filtering out entries that
// do not pass the filter item function. It will automatically request
// more aliases 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.
// Aliases are ordered by update time descending (most recently updated first).
// Aliases may contain items that were already returned during the initial
// pagination phase. It also returns a list of any aliases deleted since the
// start of the initial pagination phase or last response, or which have been
// updated since that last time and do not have a destination id that is for
// a target that is included in the list of permissions.
func ListResolvableAliasesRefresh(
ctx context.Context,
grantsHash []byte,
pageSize int,
tok *listtoken.Token,
repo *Repository,
permissions []perms.Permission,
) (*pagination.ListResponse[*Alias], error) {
const op = "target.ListResolvableAliasesRefresh"
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 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 tok.ResourceType != resource.Alias:
return nil, errors.New(ctx, errors.InvalidParameter, op, "token did not have a alias resource type")
case len(permissions) == 0:
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing target permissions")
}
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 *Alias, limit int) ([]*Alias, 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.listResolvableAliasesRefresh(ctx, rt.PreviousPhaseUpperBound.Add(-globals.RefreshReadLookbackDuration), permissions, 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.listRemovedResolvableAliasIds(ctx, since.Add(-globals.RefreshReadLookbackDuration), permissions)
}
return pagination.ListRefresh(ctx, grantsHash, pageSize, alwaysTrueFilterFn, listItemsFn, repo.estimatedCount, listDeletedIdsFn, tok)
}
@@ -11,6 +11,7 @@ import (
"github.com/hashicorp/boundary/internal/errors"
"github.com/hashicorp/boundary/internal/listtoken"
"github.com/hashicorp/boundary/internal/pagination"
"github.com/hashicorp/boundary/internal/perms"
"github.com/hashicorp/boundary/internal/types/resource"
)
@@ -81,3 +82,71 @@ func ListAliasesRefreshPage(
return pagination.ListRefreshPage(ctx, grantsHash, pageSize, filterItemFn, listItemsFn, repo.estimatedCount, listDeletedIdsFn, tok)
}
// ListResolvableAliasesRefreshPage lists up to page size aliases, filtering out entries that
// do not pass the filter item function. It will automatically request
// more aliases from the database, at page size chunks, to fill the page.
// It will start its paging based on the information in the token.
// Only aliases which resolve to a target for which there are permissions in the
// included slice of permissions are returned.
// It returns a new list token used to continue pagination or refresh items.
// Aliases are ordered by update time descending (most recently updated first).
// Aliases may contain items that were already returned during the initial
// pagination phase. It also returns a list of any aliases deleted since the
// last response or which were updated since the last response and do not resolve
// to a target for which there are permissions in the included slice of permissions.
func ListResolvableAliasesRefreshPage(
ctx context.Context,
grantsHash []byte,
pageSize int,
tok *listtoken.Token,
repo *Repository,
permissions []perms.Permission,
) (*pagination.ListResponse[*Alias], error) {
const op = "target.ListResolvableAliasesRefreshPage"
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 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 tok.ResourceType != resource.Alias:
return nil, errors.New(ctx, errors.InvalidParameter, op, "token did not have a alias resource type")
case len(permissions) == 0:
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing target permissions")
}
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 *Alias, limit int) ([]*Alias, 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.listResolvableAliasesRefresh(ctx, rt.PhaseLowerBound.Add(-globals.RefreshReadLookbackDuration), permissions, 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.listRemovedResolvableAliasIds(ctx, since.Add(-globals.RefreshReadLookbackDuration), permissions)
}
return pagination.ListRefreshPage(ctx, grantsHash, pageSize, alwaysTrueFilterFn, listItemsFn, repo.estimatedCount, listDeletedIdsFn, tok)
}
@@ -0,0 +1,535 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1
package target_test
import (
"context"
"crypto/rand"
"fmt"
"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/alias/target"
"github.com/hashicorp/boundary/internal/alias/target/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/perms"
"github.com/hashicorp/boundary/internal/target/tcp"
"github.com/hashicorp/boundary/internal/types/action"
"github.com/hashicorp/boundary/internal/types/resource"
"github.com/hashicorp/boundary/internal/types/scope"
"github.com/stretchr/testify/require"
"google.golang.org/protobuf/types/known/timestamppb"
)
func TestService_ListResolvableAliases(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)
kmsCache := kms.TestKms(t, conn, wrapper)
require.NoError(t, kmsCache.CreateKeys(context.Background(), scope.Global.String(), kms.WithRandomReader(rand.Reader)))
_, proj := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
tar := tcp.TestTarget(ctx, t, conn, proj.GetPublicId(), "target1")
var byIdResources []*target.Alias
for i := 0; i < 5; i++ {
r := target.TestAlias(t, rw, fmt.Sprintf("test%d.alias.by-id", i), target.WithDestinationId(tar.GetPublicId()))
byIdResources = append(byIdResources, r)
}
byIdPerms := []perms.Permission{
{
ScopeId: proj.GetPublicId(),
Resource: resource.Target,
Action: action.ListResolvableAliases,
ResourceIds: []string{tar.GetPublicId(), "ttcp_unknownid"},
OnlySelf: false,
All: false,
},
}
// Reverse since we read items in descending order (newest first)
slices.Reverse(byIdResources)
_, proj2 := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
tar2 := tcp.TestTarget(ctx, t, conn, proj2.GetPublicId(), "target2")
var byScopeResources []*target.Alias
for i := 0; i < 5; i++ {
r := target.TestAlias(t, rw, fmt.Sprintf("test%d.alias.by-scope", i), target.WithDestinationId(tar2.GetPublicId()))
byScopeResources = append(byScopeResources, r)
}
byScopePerms := []perms.Permission{
{
ScopeId: proj2.GetPublicId(),
Resource: resource.Target,
Action: action.ListResolvableAliases,
OnlySelf: false,
All: true,
},
}
// Reverse since we read items in descending order (newest first)
slices.Reverse(byScopeResources)
repo, repoErr := target.NewRepository(ctx, rw, rw, kmsCache)
require.NoError(t, repoErr)
// Run analyze to update postgres estimates
_, analyzeErr := sqlDB.ExecContext(ctx, "analyze")
require.NoError(t, analyzeErr)
cmpIgnoreUnexportedOpts := cmpopts.IgnoreUnexported(target.Alias{}, store.Alias{}, timestamp.Timestamp{}, timestamppb.Timestamp{})
t.Run("List validation", func(t *testing.T) {
t.Parallel()
t.Run("missing grants hash", func(t *testing.T) {
t.Parallel()
_, err := target.ListResolvableAliases(ctx, nil, 1, repo, byIdPerms)
require.ErrorContains(t, err, "missing grants hash")
})
t.Run("zero page size", func(t *testing.T) {
t.Parallel()
_, err := target.ListResolvableAliases(ctx, []byte("some hash"), 0, repo, byIdPerms)
require.ErrorContains(t, err, "page size must be at least 1")
})
t.Run("negative page size", func(t *testing.T) {
t.Parallel()
_, err := target.ListResolvableAliases(ctx, []byte("some hash"), -1, repo, byIdPerms)
require.ErrorContains(t, err, "page size must be at least 1")
})
t.Run("nil repo", func(t *testing.T) {
t.Parallel()
_, err := target.ListResolvableAliases(ctx, []byte("some hash"), 1, nil, byIdPerms)
require.ErrorContains(t, err, "missing repo")
})
t.Run("missing target permissions", func(t *testing.T) {
t.Parallel()
_, err := target.ListResolvableAliases(ctx, []byte("some hash"), 1, repo, nil)
require.ErrorContains(t, err, "missing target permissions")
})
})
t.Run("ListPage validation", func(t *testing.T) {
t.Parallel()
t.Run("missing grants hash", func(t *testing.T) {
t.Parallel()
tok, err := listtoken.NewPagination(ctx, fiveDaysAgo, resource.Alias, []byte("some hash"), "some-id", fiveDaysAgo)
require.NoError(t, err)
_, err = target.ListResolvableAliasesPage(ctx, nil, 1, tok, repo, byIdPerms)
require.ErrorContains(t, err, "missing grants hash")
})
t.Run("zero page size", func(t *testing.T) {
t.Parallel()
tok, err := listtoken.NewPagination(ctx, fiveDaysAgo, resource.Alias, []byte("some hash"), "some-id", fiveDaysAgo)
require.NoError(t, err)
_, err = target.ListResolvableAliasesPage(ctx, []byte("some hash"), 0, tok, repo, byIdPerms)
require.ErrorContains(t, err, "page size must be at least 1")
})
t.Run("negative page size", func(t *testing.T) {
t.Parallel()
tok, err := listtoken.NewPagination(ctx, fiveDaysAgo, resource.Alias, []byte("some hash"), "some-id", fiveDaysAgo)
require.NoError(t, err)
_, err = target.ListResolvableAliasesPage(ctx, []byte("some hash"), -1, tok, repo, byIdPerms)
require.ErrorContains(t, err, "page size must be at least 1")
})
t.Run("nil token", func(t *testing.T) {
t.Parallel()
_, err := target.ListResolvableAliasesPage(ctx, []byte("some hash"), 1, nil, repo, byIdPerms)
require.ErrorContains(t, err, "missing token")
})
t.Run("wrong token type", func(t *testing.T) {
t.Parallel()
tok, err := listtoken.NewStartRefresh(ctx, fiveDaysAgo, resource.Alias, []byte("some hash"), fiveDaysAgo, fiveDaysAgo)
require.NoError(t, err)
_, err = target.ListResolvableAliasesPage(ctx, []byte("some hash"), 1, tok, repo, byIdPerms)
require.ErrorContains(t, err, "token did not have a pagination token component")
})
t.Run("nil repo", func(t *testing.T) {
t.Parallel()
tok, err := listtoken.NewPagination(ctx, fiveDaysAgo, resource.Alias, []byte("some hash"), "some-id", fiveDaysAgo)
require.NoError(t, err)
_, err = target.ListResolvableAliasesPage(ctx, []byte("some hash"), 1, tok, nil, byIdPerms)
require.ErrorContains(t, err, "missing repo")
})
t.Run("missing permissions", func(t *testing.T) {
t.Parallel()
tok, err := listtoken.NewPagination(ctx, fiveDaysAgo, resource.Alias, []byte("some hash"), "some-id", fiveDaysAgo)
require.NoError(t, err)
_, err = target.ListResolvableAliasesPage(ctx, []byte("some hash"), 1, tok, repo, nil)
require.ErrorContains(t, err, "missing permissions")
})
t.Run("wrong token resource type", func(t *testing.T) {
t.Parallel()
tok, err := listtoken.NewPagination(ctx, fiveDaysAgo, resource.Target, []byte("some hash"), "some-id", fiveDaysAgo)
require.NoError(t, err)
_, err = target.ListResolvableAliasesPage(ctx, []byte("some hash"), 1, tok, repo, byIdPerms)
require.ErrorContains(t, err, "token did not have a alias resource type")
})
})
t.Run("ListRefresh validation", func(t *testing.T) {
t.Parallel()
t.Run("missing grants hash", func(t *testing.T) {
t.Parallel()
tok, err := listtoken.NewStartRefresh(ctx, fiveDaysAgo, resource.Alias, []byte("some hash"), fiveDaysAgo, fiveDaysAgo)
require.NoError(t, err)
_, err = target.ListResolvableAliasesRefresh(ctx, nil, 1, tok, repo, byIdPerms)
require.ErrorContains(t, err, "missing grants hash")
})
t.Run("zero page size", func(t *testing.T) {
t.Parallel()
tok, err := listtoken.NewStartRefresh(ctx, fiveDaysAgo, resource.Alias, []byte("some hash"), fiveDaysAgo, fiveDaysAgo)
require.NoError(t, err)
_, err = target.ListResolvableAliasesRefresh(ctx, []byte("some hash"), 0, tok, repo, byIdPerms)
require.ErrorContains(t, err, "page size must be at least 1")
})
t.Run("negative page size", func(t *testing.T) {
t.Parallel()
tok, err := listtoken.NewStartRefresh(ctx, fiveDaysAgo, resource.Alias, []byte("some hash"), fiveDaysAgo, fiveDaysAgo)
require.NoError(t, err)
_, err = target.ListResolvableAliasesRefresh(ctx, []byte("some hash"), -1, tok, repo, byIdPerms)
require.ErrorContains(t, err, "page size must be at least 1")
})
t.Run("nil token", func(t *testing.T) {
t.Parallel()
_, err := target.ListResolvableAliasesRefresh(ctx, []byte("some hash"), 1, nil, repo, byIdPerms)
require.ErrorContains(t, err, "missing token")
})
t.Run("wrong token type", func(t *testing.T) {
t.Parallel()
tok, err := listtoken.NewPagination(ctx, fiveDaysAgo, resource.Alias, []byte("some hash"), "some-id", fiveDaysAgo)
require.NoError(t, err)
_, err = target.ListResolvableAliasesRefresh(ctx, []byte("some hash"), 1, tok, repo, byIdPerms)
require.ErrorContains(t, err, "token did not have a start-refresh token component")
})
t.Run("nil repo", func(t *testing.T) {
t.Parallel()
tok, err := listtoken.NewStartRefresh(ctx, fiveDaysAgo, resource.Alias, []byte("some hash"), fiveDaysAgo, fiveDaysAgo)
require.NoError(t, err)
_, err = target.ListResolvableAliasesRefresh(ctx, []byte("some hash"), 1, tok, nil, byIdPerms)
require.ErrorContains(t, err, "missing repo")
})
t.Run("missing permissions", func(t *testing.T) {
t.Parallel()
tok, err := listtoken.NewStartRefresh(ctx, fiveDaysAgo, resource.Alias, []byte("some hash"), fiveDaysAgo, fiveDaysAgo)
require.NoError(t, err)
_, err = target.ListResolvableAliasesRefresh(ctx, []byte("some hash"), 1, tok, repo, nil)
require.ErrorContains(t, err, "missing target permissions")
})
t.Run("wrong token resource type", func(t *testing.T) {
t.Parallel()
tok, err := listtoken.NewStartRefresh(ctx, fiveDaysAgo, resource.Target, []byte("some hash"), fiveDaysAgo, fiveDaysAgo)
require.NoError(t, err)
_, err = target.ListResolvableAliasesRefresh(ctx, []byte("some hash"), 1, tok, repo, byIdPerms)
require.ErrorContains(t, err, "token did not have a alias resource type")
})
})
t.Run("ListRefreshPage validation", func(t *testing.T) {
t.Parallel()
t.Run("missing grants hash", func(t *testing.T) {
t.Parallel()
tok, err := listtoken.NewRefresh(ctx, fiveDaysAgo, resource.Alias, []byte("some hash"), fiveDaysAgo, fiveDaysAgo, fiveDaysAgo, "some other id", fiveDaysAgo)
require.NoError(t, err)
_, err = target.ListResolvableAliasesRefreshPage(ctx, nil, 1, tok, repo, byIdPerms)
require.ErrorContains(t, err, "missing grants hash")
})
t.Run("zero page size", func(t *testing.T) {
t.Parallel()
tok, err := listtoken.NewRefresh(ctx, fiveDaysAgo, resource.Alias, []byte("some hash"), fiveDaysAgo, fiveDaysAgo, fiveDaysAgo, "some other id", fiveDaysAgo)
require.NoError(t, err)
_, err = target.ListResolvableAliasesRefreshPage(ctx, []byte("some hash"), 0, tok, repo, byIdPerms)
require.ErrorContains(t, err, "page size must be at least 1")
})
t.Run("negative page size", func(t *testing.T) {
t.Parallel()
tok, err := listtoken.NewRefresh(ctx, fiveDaysAgo, resource.Alias, []byte("some hash"), fiveDaysAgo, fiveDaysAgo, fiveDaysAgo, "some other id", fiveDaysAgo)
require.NoError(t, err)
_, err = target.ListResolvableAliasesRefreshPage(ctx, []byte("some hash"), -1, tok, repo, byIdPerms)
require.ErrorContains(t, err, "page size must be at least 1")
})
t.Run("nil token", func(t *testing.T) {
t.Parallel()
_, err := target.ListResolvableAliasesRefreshPage(ctx, []byte("some hash"), 1, nil, repo, byIdPerms)
require.ErrorContains(t, err, "missing token")
})
t.Run("wrong token type", func(t *testing.T) {
t.Parallel()
tok, err := listtoken.NewPagination(ctx, fiveDaysAgo, resource.Alias, []byte("some hash"), "some-id", fiveDaysAgo)
require.NoError(t, err)
_, err = target.ListResolvableAliasesRefreshPage(ctx, []byte("some hash"), 1, tok, repo, byIdPerms)
require.ErrorContains(t, err, "token did not have a refresh token component")
})
t.Run("nil repo", func(t *testing.T) {
t.Parallel()
tok, err := listtoken.NewRefresh(ctx, fiveDaysAgo, resource.Alias, []byte("some hash"), fiveDaysAgo, fiveDaysAgo, fiveDaysAgo, "some other id", fiveDaysAgo)
require.NoError(t, err)
_, err = target.ListResolvableAliasesRefreshPage(ctx, []byte("some hash"), 1, tok, nil, byIdPerms)
require.ErrorContains(t, err, "missing repo")
})
t.Run("missing permissions", func(t *testing.T) {
t.Parallel()
tok, err := listtoken.NewRefresh(ctx, fiveDaysAgo, resource.Alias, []byte("some hash"), fiveDaysAgo, fiveDaysAgo, fiveDaysAgo, "some other id", fiveDaysAgo)
require.NoError(t, err)
_, err = target.ListResolvableAliasesRefreshPage(ctx, []byte("some hash"), 1, tok, repo, nil)
require.ErrorContains(t, err, "missing target permissions")
})
t.Run("wrong token resource type", func(t *testing.T) {
t.Parallel()
tok, err := listtoken.NewRefresh(ctx, fiveDaysAgo, resource.Target, []byte("some hash"), fiveDaysAgo, fiveDaysAgo, fiveDaysAgo, "some other id", fiveDaysAgo)
require.NoError(t, err)
_, err = target.ListResolvableAliasesRefreshPage(ctx, []byte("some hash"), 1, tok, repo, byIdPerms)
require.ErrorContains(t, err, "token did not have a alias resource type")
})
})
t.Run("simple pagination", func(t *testing.T) {
cases := []struct {
name string
perms []perms.Permission
resourceSlice []*target.Alias
}{
{
name: "by-id",
perms: byIdPerms,
resourceSlice: byIdResources,
},
{
name: "by-scope",
perms: byScopePerms,
resourceSlice: byScopeResources,
},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
resp, err := target.ListResolvableAliases(ctx, []byte("some hash"), 1, repo, tc.perms)
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, 10)
require.Empty(t, resp.DeletedIds)
require.Len(t, resp.Items, 1)
require.Empty(t, cmp.Diff(resp.Items[0], tc.resourceSlice[0], cmpIgnoreUnexportedOpts), "resources did not match", tc.resourceSlice, "resp", resp.Items)
resp2, err := target.ListResolvableAliasesPage(ctx, []byte("some hash"), 1, resp.ListToken, repo, tc.perms)
require.NoError(t, err)
require.Equal(t, resp2.ListToken.GrantsHash, []byte("some hash"))
require.False(t, resp2.CompleteListing)
require.Equal(t, resp2.EstimatedItemCount, 10)
require.Empty(t, resp2.DeletedIds)
require.Len(t, resp2.Items, 1)
require.Empty(t, cmp.Diff(resp2.Items[0], tc.resourceSlice[1], cmpIgnoreUnexportedOpts))
resp3, err := target.ListResolvableAliasesPage(ctx, []byte("some hash"), 1, resp2.ListToken, repo, tc.perms)
require.NoError(t, err)
require.Equal(t, resp3.ListToken.GrantsHash, []byte("some hash"))
require.False(t, resp3.CompleteListing)
require.Equal(t, resp3.EstimatedItemCount, 10)
require.Empty(t, resp3.DeletedIds)
require.Len(t, resp3.Items, 1)
require.Empty(t, cmp.Diff(resp3.Items[0], tc.resourceSlice[2], cmpIgnoreUnexportedOpts))
resp4, err := target.ListResolvableAliasesPage(ctx, []byte("some hash"), 1, resp3.ListToken, repo, tc.perms)
require.NoError(t, err)
require.Equal(t, resp4.ListToken.GrantsHash, []byte("some hash"))
require.False(t, resp4.CompleteListing)
require.Equal(t, resp4.EstimatedItemCount, 10)
require.Empty(t, resp4.DeletedIds)
require.Len(t, resp4.Items, 1)
require.Empty(t, cmp.Diff(resp4.Items[0], tc.resourceSlice[3], cmpIgnoreUnexportedOpts))
resp5, err := target.ListResolvableAliasesPage(ctx, []byte("some hash"), 1, resp4.ListToken, repo, tc.perms)
require.NoError(t, err)
require.Equal(t, resp5.ListToken.GrantsHash, []byte("some hash"))
require.True(t, resp5.CompleteListing)
require.Equal(t, resp5.EstimatedItemCount, 10)
require.Empty(t, resp5.DeletedIds)
require.Len(t, resp5.Items, 1)
require.Empty(t, cmp.Diff(resp5.Items[0], tc.resourceSlice[4], cmpIgnoreUnexportedOpts))
// Finished initial pagination phase, request refresh
// Expect no results.
resp6, err := target.ListResolvableAliasesRefresh(ctx, []byte("some hash"), 1, resp5.ListToken, repo, tc.perms)
require.NoError(t, err)
require.Equal(t, resp6.ListToken.GrantsHash, []byte("some hash"))
require.True(t, resp6.CompleteListing)
require.Equal(t, resp6.EstimatedItemCount, 10)
require.Empty(t, resp6.DeletedIds)
require.Empty(t, resp6.Items)
// Create some new aliases
newR1 := target.TestAlias(t, rw, "first.new.alias", target.WithDestinationId(tc.resourceSlice[0].GetDestinationId()))
newR2 := target.TestAlias(t, rw, "second.new.alias", target.WithDestinationId(tc.resourceSlice[0].GetDestinationId()))
t.Cleanup(func() {
_, err := repo.DeleteAlias(ctx, newR1.GetPublicId())
require.NoError(t, err)
_, err = repo.DeleteAlias(ctx, newR2.GetPublicId())
require.NoError(t, err)
// Run analyze to update count estimate
_, err = sqlDB.ExecContext(ctx, "analyze")
require.NoError(t, err)
})
// Run analyze to update count estimate
_, err = sqlDB.ExecContext(ctx, "analyze")
require.NoError(t, err)
// Refresh again, should get newR2
resp7, err := target.ListResolvableAliasesRefresh(ctx, []byte("some hash"), 1, resp6.ListToken, repo, tc.perms)
require.NoError(t, err)
require.Equal(t, resp7.ListToken.GrantsHash, []byte("some hash"))
require.False(t, resp7.CompleteListing)
require.Equal(t, resp7.EstimatedItemCount, 12)
require.Empty(t, resp7.DeletedIds)
require.Len(t, resp7.Items, 1)
require.Empty(t, cmp.Diff(resp7.Items[0], newR2, cmpIgnoreUnexportedOpts))
// Refresh again, should get newR1
resp8, err := target.ListResolvableAliasesRefreshPage(ctx, []byte("some hash"), 1, resp7.ListToken, repo, tc.perms)
require.NoError(t, err)
require.Equal(t, resp8.ListToken.GrantsHash, []byte("some hash"))
require.True(t, resp8.CompleteListing)
require.Equal(t, resp8.EstimatedItemCount, 12)
require.Empty(t, resp8.DeletedIds)
require.Len(t, resp8.Items, 1)
require.Empty(t, cmp.Diff(resp8.Items[0], newR1, cmpIgnoreUnexportedOpts))
// Refresh again, should get no results
resp9, err := target.ListResolvableAliasesRefresh(ctx, []byte("some hash"), 1, resp8.ListToken, repo, tc.perms)
require.NoError(t, err)
require.Equal(t, resp9.ListToken.GrantsHash, []byte("some hash"))
require.True(t, resp9.CompleteListing)
require.Equal(t, resp9.EstimatedItemCount, 12)
require.Empty(t, resp9.DeletedIds)
require.Empty(t, resp9.Items)
})
}
})
t.Run("simple pagination with destination id changes", func(t *testing.T) {
firstUpdatedA := byScopeResources[0]
// this no longer has the destination id that has permissions
firstUpdatedA.DestinationId = tar.GetPublicId()
firstUpdatedA, _, err := repo.UpdateAlias(ctx, firstUpdatedA, firstUpdatedA.GetVersion(), []string{"DestinationId"})
require.NoError(t, err)
byScopeResources = byScopeResources[1:]
t.Cleanup(func() {
firstUpdatedA.DestinationId = tar2.GetPublicId()
firstUpdatedA, _, err := repo.UpdateAlias(ctx, firstUpdatedA, firstUpdatedA.GetVersion(), []string{"DestinationId"})
require.NoError(t, err)
byScopeResources = append([]*target.Alias{firstUpdatedA}, byScopeResources...)
})
// Run analyze to update count estimate
_, err = sqlDB.ExecContext(ctx, "analyze")
require.NoError(t, err)
resp, err := target.ListResolvableAliases(ctx, []byte("some hash"), 1, repo, byScopePerms)
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, 10)
require.Empty(t, resp.DeletedIds)
require.Len(t, resp.Items, 1)
require.Empty(t, cmp.Diff(resp.Items[0], byScopeResources[0], cmpIgnoreUnexportedOpts))
// request remaining results
resp2, err := target.ListResolvableAliasesPage(ctx, []byte("some hash"), 3, resp.ListToken, repo, byScopePerms)
require.NoError(t, err)
require.Equal(t, resp2.ListToken.GrantsHash, []byte("some hash"))
require.True(t, resp2.CompleteListing)
require.Equal(t, resp2.EstimatedItemCount, 10)
require.Empty(t, resp2.DeletedIds)
require.Len(t, resp2.Items, 3)
require.Empty(t, cmp.Diff(resp2.Items, byScopeResources[1:], cmpIgnoreUnexportedOpts))
secondA := byScopeResources[0]
// this no longer has the destination id that has permissions
secondA.DestinationId = tar.GetPublicId()
secondA, _, err = repo.UpdateAlias(ctx, secondA, secondA.GetVersion(), []string{"DestinationId"})
require.NoError(t, err)
byScopeResources = byScopeResources[1:]
t.Cleanup(func() {
secondA.DestinationId = tar2.GetPublicId()
secondA, _, err := repo.UpdateAlias(ctx, secondA, secondA.GetVersion(), []string{"DestinationId"})
require.NoError(t, err)
byScopeResources = append([]*target.Alias{secondA}, byScopeResources...)
})
// 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 := target.ListResolvableAliasesRefresh(ctx, []byte("some hash"), 1, resp2.ListToken, repo, byScopePerms)
require.NoError(t, err)
require.Equal(t, resp3.ListToken.GrantsHash, []byte("some hash"))
require.True(t, resp3.CompleteListing)
require.Equal(t, resp3.EstimatedItemCount, 10)
require.Contains(t, resp3.DeletedIds, secondA.GetPublicId())
require.Empty(t, resp3.Items)
})
t.Run("simple pagination with deletion", func(t *testing.T) {
deletedAliasId := byIdResources[0].GetPublicId()
_, err := repo.DeleteAlias(ctx, deletedAliasId)
require.NoError(t, err)
byIdResources = byIdResources[1:]
// Run analyze to update count estimate
_, err = sqlDB.ExecContext(ctx, "analyze")
require.NoError(t, err)
resp, err := target.ListResolvableAliases(ctx, []byte("some hash"), 1, repo, byIdPerms)
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, 9)
require.Empty(t, resp.DeletedIds)
require.Len(t, resp.Items, 1)
require.Empty(t, cmp.Diff(resp.Items[0], byIdResources[0], cmpIgnoreUnexportedOpts))
// request remaining results
resp2, err := target.ListResolvableAliasesPage(ctx, []byte("some hash"), 8, resp.ListToken, repo, byIdPerms)
require.NoError(t, err)
require.Equal(t, resp2.ListToken.GrantsHash, []byte("some hash"))
require.True(t, resp2.CompleteListing)
require.Equal(t, resp2.EstimatedItemCount, 9)
require.Empty(t, resp2.DeletedIds)
require.Len(t, resp2.Items, 3)
require.Empty(t, cmp.Diff(resp2.Items, byIdResources[1:], cmpIgnoreUnexportedOpts))
deletedAliasId = byIdResources[0].GetPublicId()
_, err = repo.DeleteAlias(ctx, deletedAliasId)
require.NoError(t, err)
byIdResources = byIdResources[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 := target.ListResolvableAliasesRefresh(ctx, []byte("some hash"), 1, resp2.ListToken, repo, byIdPerms)
require.NoError(t, err)
require.Equal(t, resp3.ListToken.GrantsHash, []byte("some hash"))
require.True(t, resp3.CompleteListing)
require.Equal(t, resp3.EstimatedItemCount, 8)
require.Contains(t, resp3.DeletedIds, deletedAliasId)
require.Empty(t, resp3.Items)
})
}
+1
View File
@@ -114,6 +114,7 @@ func (b *Server) CreateInitialAuthenticatedUserRole(ctx context.Context, opt ...
"ids=*;type=auth-token;actions=list",
"ids={{.Account.Id}};actions=read,change-password",
"ids=*;type=session;actions=list,read:self,cancel:self",
"ids={{.User.Id}};type=user;actions=list-resolvable-aliases",
}
opts := GetOpts(opt...)
if opts.withAuthUserTargetAuthorizeSessionGrant {
+2 -50
View File
@@ -5,13 +5,9 @@ package auth
import (
"context"
"encoding/binary"
stderrors "errors"
"fmt"
"hash"
"hash/fnv"
"net/http"
"slices"
"strings"
"time"
@@ -102,7 +98,7 @@ type VerifyResults struct {
v *verifier
// Used to generate a hash of all grants
grants []perms.GrantTuple
grants perms.GrantTuples
}
type verifier struct {
@@ -947,49 +943,5 @@ func (r *VerifyResults) ScopesAuthorizedForList(ctx context.Context, rootScopeId
// GrantsHash returns a stable hash of all the grants in the verify results.
func (r *VerifyResults) GrantsHash(ctx context.Context) ([]byte, error) {
const op = "auth.GrantsHash"
var values []string
for _, grant := range r.grants {
values = append(values, grant.Grant, grant.RoleId, grant.ScopeId)
}
// Sort for deterministic output
slices.Sort(values)
hashVal, err := hashStrings(values...)
if err != nil {
return nil, errors.Wrap(ctx, err, op)
}
return binary.LittleEndian.AppendUint64(make([]byte, 0, 4), hashVal), nil
}
func hashStrings(s ...string) (uint64, error) {
hasher := fnv.New64()
var h uint64
var err error
for _, current := range s {
hasher.Reset()
if _, err = hasher.Write([]byte(current)); err != nil {
return 0, err
}
if h, err = hashUpdateOrdered(hasher, h, hasher.Sum64()); err != nil {
return 0, err
}
}
return h, nil
}
// hashUpdateOrdered is taken directly from
// https://github.com/mitchellh/hashstructure
func hashUpdateOrdered(h hash.Hash64, a, b uint64) (uint64, error) {
// For ordered updates, use a real hash function
h.Reset()
e1 := binary.Write(h, binary.LittleEndian, a)
e2 := binary.Write(h, binary.LittleEndian, b)
if e1 != nil {
return 0, e1
}
if e2 != nil {
return 0, e2
}
return h.Sum64(), nil
return r.grants.GrantHash(ctx)
}
+1 -1
View File
@@ -206,7 +206,7 @@ func (c *Controller) registerGrpcServices(s *grpc.Server) error {
services.RegisterScopeServiceServer(s, os)
}
if _, ok := currentServices[services.UserService_ServiceDesc.ServiceName]; !ok {
us, err := users.NewService(c.baseContext, c.IamRepoFn, c.conf.RawConfig.Controller.MaxPageSize)
us, err := users.NewService(c.baseContext, c.IamRepoFn, c.TargetAliasRepoFn, c.conf.RawConfig.Controller.MaxPageSize)
if err != nil {
return fmt.Errorf("failed to create user handler service: %w", err)
}
@@ -8,10 +8,12 @@ import (
"fmt"
"github.com/hashicorp/boundary/globals"
talias "github.com/hashicorp/boundary/internal/alias/target"
"github.com/hashicorp/boundary/internal/daemon/controller/auth"
"github.com/hashicorp/boundary/internal/daemon/controller/common"
"github.com/hashicorp/boundary/internal/daemon/controller/common/scopeids"
"github.com/hashicorp/boundary/internal/daemon/controller/handlers"
"github.com/hashicorp/boundary/internal/daemon/controller/handlers/targets"
"github.com/hashicorp/boundary/internal/errors"
pbs "github.com/hashicorp/boundary/internal/gen/controller/api/services"
"github.com/hashicorp/boundary/internal/iam"
@@ -23,6 +25,7 @@ import (
"github.com/hashicorp/boundary/internal/types/action"
"github.com/hashicorp/boundary/internal/types/resource"
"github.com/hashicorp/boundary/internal/types/scope"
aliaspb "github.com/hashicorp/boundary/sdk/pbs/controller/api/resources/aliases"
"github.com/hashicorp/boundary/sdk/pbs/controller/api/resources/scopes"
pb "github.com/hashicorp/boundary/sdk/pbs/controller/api/resources/users"
"github.com/hashicorp/go-secure-stdlib/strutil"
@@ -43,6 +46,7 @@ var (
action.AddAccounts,
action.SetAccounts,
action.RemoveAccounts,
action.ListResolvableAliases,
)
// CollectionActions contains the set of actions that can be performed on
@@ -71,21 +75,25 @@ type Service struct {
pbs.UnsafeUserServiceServer
repoFn common.IamRepoFactory
aliasRepoFn common.TargetAliasRepoFactory
maxPageSize uint
}
var _ pbs.UserServiceServer = (*Service)(nil)
// NewService returns a user service which handles user related requests to boundary.
func NewService(ctx context.Context, repo common.IamRepoFactory, maxPageSize uint) (Service, error) {
func NewService(ctx context.Context, repo common.IamRepoFactory, aliasRepoFn common.TargetAliasRepoFactory, maxPageSize uint) (Service, error) {
const op = "users.NewService"
if repo == nil {
switch {
case repo == nil:
return Service{}, errors.New(ctx, errors.InvalidParameter, op, "missing iam repository")
case aliasRepoFn == nil:
return Service{}, errors.New(ctx, errors.InvalidParameter, op, "missing alias repository")
}
if maxPageSize == 0 {
maxPageSize = uint(globals.DefaultMaxPageSize)
}
return Service{repoFn: repo, maxPageSize: maxPageSize}, nil
return Service{repoFn: repo, aliasRepoFn: aliasRepoFn, maxPageSize: maxPageSize}, nil
}
// ListUsers implements the interface pbs.UserServiceServer.
@@ -468,6 +476,163 @@ func (s Service) RemoveUserAccounts(ctx context.Context, req *pbs.RemoveUserAcco
return &pbs.RemoveUserAccountsResponse{Item: item}, nil
}
// ListResolvableAliases implements the interface pbs.AliasServiceServer.
func (s Service) ListResolvableAliases(ctx context.Context, req *pbs.ListResolvableAliasesRequest) (*pbs.ListResolvableAliasesResponse, error) {
const op = "users.(Service).ListResolvableAliases"
if err := validateListResolvableAliasesRequest(req); err != nil {
return nil, err
}
authResults := s.authResult(ctx, req.GetId(), action.ListResolvableAliases)
if authResults.Error != nil {
return nil, authResults.Error
}
outputFields, ok := requests.OutputFields(ctx)
if !ok {
return nil, errors.New(ctx, errors.Internal, op, "no request context found")
}
acl := authResults.ACL()
grantsHash, err := authResults.GrantsHash(ctx)
if err != nil {
return nil, errors.Wrap(ctx, err, op)
}
if req.GetId() != authResults.UserId {
var err error
acl, grantsHash, err = s.aclAndGrantHashForUser(ctx, req.GetId())
if err != nil {
return nil, errors.Wrap(ctx, err, op, errors.WithoutEvent())
}
}
permissions := acl.ListResolvablePermissions(resource.Target, targets.IdActions)
if len(permissions) == 0 {
// if there are no permitted targets then there will be no aliases that
// can resolve to them.
return &pbs.ListResolvableAliasesResponse{
ResponseType: "complete",
SortBy: "created_time",
SortDir: "desc",
}, 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())
}
repo, err := s.aliasRepoFn()
if err != nil {
return nil, errors.Wrap(ctx, err, op)
}
var listResp *pagination.ListResponse[*talias.Alias]
var sortBy string
if req.GetListToken() == "" {
sortBy = "created_time"
listResp, err = talias.ListResolvableAliases(ctx, grantsHash, pageSize, repo, permissions)
if err != nil {
return nil, err
}
} else {
listToken, err := handlers.ParseListToken(ctx, req.GetListToken(), resource.Alias, grantsHash)
if err != nil {
return nil, err
}
switch st := listToken.Subtype.(type) {
case *listtoken.PaginationToken:
sortBy = "created_time"
listResp, err = talias.ListResolvableAliasesPage(ctx, grantsHash, pageSize, listToken, repo, permissions)
if err != nil {
return nil, err
}
case *listtoken.StartRefreshToken:
sortBy = "updated_time"
listResp, err = talias.ListResolvableAliasesRefresh(ctx, grantsHash, pageSize, listToken, repo, permissions)
if err != nil {
return nil, err
}
case *listtoken.RefreshToken:
sortBy = "updated_time"
listResp, err = talias.ListResolvableAliasesRefreshPage(ctx, grantsHash, pageSize, listToken, repo, permissions)
if err != nil {
return nil, err
}
default:
return nil, handlers.ApiErrorWithCodeAndMessage(codes.InvalidArgument, "unexpected list token subtype: %T", st)
}
}
finalItems := make([]*aliaspb.Alias, 0, len(listResp.Items))
for _, item := range listResp.Items {
item, err := toResolvableAliasProto(item, handlers.WithOutputFields(outputFields))
if err != nil {
return nil, errors.Wrap(ctx, err, op)
}
finalItems = append(finalItems, item)
}
respType := "delta"
if listResp.CompleteListing {
respType = "complete"
}
resp := &pbs.ListResolvableAliasesResponse{
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_ALIAS)
if err != nil {
return nil, err
}
}
return resp, nil
}
// aclAndGrantHashForUser returns an ACL from the grants provided to the user and
// the hash of those grants.
func (s Service) aclAndGrantHashForUser(ctx context.Context, userId string) (perms.ACL, []byte, error) {
const op = "users.(Service).aclAndGrantHashForUser"
iamRepo, err := s.repoFn()
if err != nil {
return perms.ACL{}, nil, errors.Wrap(ctx, err, op, errors.WithoutEvent())
}
grantTuples, err := iamRepo.GrantsForUser(ctx, userId)
if err != nil {
return perms.ACL{}, nil, errors.Wrap(ctx, err, op, errors.WithoutEvent())
}
hash, err := grantTuples.GrantHash(ctx)
if err != nil {
return perms.ACL{}, nil, errors.Wrap(ctx, err, op, errors.WithoutEvent())
}
parsedGrants := make([]perms.Grant, 0, len(grantTuples))
// Note: Below, we always skip validation so that we don't error on formats
// that we've since restricted, e.g. "ids=foo;actions=create,read". These
// will simply not have an effect.
for _, pair := range grantTuples {
permsOpts := []perms.Option{
perms.WithUserId(userId),
perms.WithSkipFinalValidation(true),
}
parsed, err := perms.Parse(
ctx,
pair.ScopeId,
pair.Grant,
permsOpts...)
if err != nil {
return perms.ACL{}, nil, errors.Wrap(ctx, err, op)
}
parsedGrants = append(parsedGrants, parsed)
}
return perms.NewACL(parsedGrants...), hash, nil
}
func (s Service) getFromRepo(ctx context.Context, id string) (*iam.User, []string, error) {
repo, err := s.repoFn()
if err != nil {
@@ -723,6 +888,39 @@ func toProto(ctx context.Context, in *iam.User, accts []string, opt ...handlers.
return &out, nil
}
func toResolvableAliasProto(a *talias.Alias, opt ...handlers.Option) (*aliaspb.Alias, error) {
opts := handlers.GetOpts(opt...)
if opts.WithOutputFields == nil {
return nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "output fields not found when building user proto")
}
outputFields := *opts.WithOutputFields
pbItem := &aliaspb.Alias{}
if a == nil {
return pbItem, nil
}
if outputFields.Has(globals.IdField) {
pbItem.Id = a.GetPublicId()
}
if outputFields.Has(globals.CreatedTimeField) {
pbItem.CreatedTime = a.GetCreateTime().GetTimestamp()
}
if outputFields.Has(globals.UpdatedTimeField) {
pbItem.UpdatedTime = a.GetUpdateTime().GetTimestamp()
}
if outputFields.Has(globals.ValueField) {
pbItem.Value = a.GetValue()
}
if outputFields.Has(globals.DestinationIdField) && a.GetDestinationId() != "" {
pbItem.DestinationId = wrapperspb.String(a.GetDestinationId())
}
if outputFields.Has(globals.TypeField) {
pbItem.Type = "target"
}
return pbItem, nil
}
// A validateX method should exist for each method above. These methods do not make calls to any backing service but enforce
// requirements on the structure of the request. They verify that:
// - The path passed in is correctly formatted
@@ -850,6 +1048,20 @@ func validateRemoveUserAccountsRequest(req *pbs.RemoveUserAccountsRequest) error
return nil
}
func validateListResolvableAliasesRequest(req *pbs.ListResolvableAliasesRequest) error {
badFields := map[string]string{}
if !handlers.ValidId(handlers.Id(req.GetId()), globals.UserPrefix) {
badFields[globals.IdField] = "Incorrectly formatted identifier."
}
if req.GetId() == globals.RecoveryUserId {
badFields["principal_ids"] = "Cannot list resolvable aliases for the recovery user."
}
if len(badFields) > 0 {
return handlers.InvalidArgumentErrorf("Errors in provided fields.", badFields)
}
return nil
}
func newOutputOpts(ctx context.Context, item *iam.User, scopeInfoMap map[string]*scopes.ScopeInfo, authResults auth.VerifyResults) ([]handlers.Option, bool) {
res := perms.Resource{
Type: resource.User,
@@ -14,11 +14,13 @@ import (
"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"
"github.com/hashicorp/boundary/globals"
talias "github.com/hashicorp/boundary/internal/alias/target"
"github.com/hashicorp/boundary/internal/auth/ldap"
"github.com/hashicorp/boundary/internal/auth/oidc"
"github.com/hashicorp/boundary/internal/auth/password"
"github.com/hashicorp/boundary/internal/authtoken"
"github.com/hashicorp/boundary/internal/daemon/controller/auth"
"github.com/hashicorp/boundary/internal/daemon/controller/common"
"github.com/hashicorp/boundary/internal/daemon/controller/handlers"
"github.com/hashicorp/boundary/internal/daemon/controller/handlers/users"
"github.com/hashicorp/boundary/internal/db"
@@ -28,7 +30,9 @@ import (
"github.com/hashicorp/boundary/internal/kms"
"github.com/hashicorp/boundary/internal/requests"
"github.com/hashicorp/boundary/internal/server"
"github.com/hashicorp/boundary/internal/target/tcp"
"github.com/hashicorp/boundary/internal/types/scope"
pbalias "github.com/hashicorp/boundary/sdk/pbs/controller/api/resources/aliases"
"github.com/hashicorp/boundary/sdk/pbs/controller/api/resources/scopes"
pb "github.com/hashicorp/boundary/sdk/pbs/controller/api/resources/users"
"google.golang.org/genproto/protobuf/field_mask"
@@ -42,26 +46,30 @@ import (
"github.com/stretchr/testify/require"
)
var testAuthorizedActions = []string{"no-op", "read", "update", "delete", "add-accounts", "set-accounts", "remove-accounts"}
var testAuthorizedActions = []string{"no-op", "read", "update", "delete", "add-accounts", "set-accounts", "remove-accounts", "list-resolvable-aliases"}
func createDefaultUserAndRepo(t *testing.T, withAccts bool) (*iam.User, []string, func() (*iam.Repository, error)) {
func createDefaultUserAndRepos(t *testing.T, withAccts bool) (*iam.User, []string, common.IamRepoFactory, common.TargetAliasRepoFactory) {
t.Helper()
ctx := context.Background()
conn, _ := db.TestSetup(t, "postgres")
wrap := db.TestWrapper(t)
kmsCache := kms.TestKms(t, conn, wrap)
rw := db.New(conn)
repo := iam.TestRepo(t, conn, wrap)
repoFn := func() (*iam.Repository, error) {
return repo, nil
}
aliasRepoFn := func() (*talias.Repository, error) {
return talias.NewRepository(context.Background(), rw, rw, kmsCache)
}
o, _ := iam.TestScopes(t, repo)
u := iam.TestUser(t, repo, o.GetPublicId(), iam.WithDescription("default"), iam.WithName("default"))
switch withAccts {
case false:
return u, nil, repoFn
return u, nil, repoFn, aliasRepoFn
default:
require := require.New(t)
ctx := context.Background()
kmsCache := kms.TestKms(t, conn, wrap)
databaseWrap, err := kmsCache.GetWrapper(ctx, o.PublicId, kms.KeyPurposeDatabase)
require.NoError(err)
primaryAm := oidc.TestAuthMethod(t, conn, databaseWrap, o.PublicId, oidc.ActivePublicState, "alice-rp", "fido",
@@ -84,12 +92,12 @@ func createDefaultUserAndRepo(t *testing.T, withAccts bool) (*iam.User, []string
// reload the user with their accounts
u, accts, err := repo.LookupUser(ctx, u.PublicId)
require.NoError(err)
return u, accts, repoFn
return u, accts, repoFn, aliasRepoFn
}
}
func TestGet(t *testing.T) {
u, uAccts, repoFn := createDefaultUserAndRepo(t, true)
u, uAccts, repoFn, aliasRepo := createDefaultUserAndRepos(t, true)
toMerge := &pbs.GetUserRequest{
Id: u.GetPublicId(),
@@ -149,7 +157,7 @@ func TestGet(t *testing.T) {
req := proto.Clone(toMerge).(*pbs.GetUserRequest)
proto.Merge(req, tc.req)
s, err := users.NewService(context.Background(), repoFn, 1000)
s, err := users.NewService(context.Background(), repoFn, aliasRepo, 1000)
require.NoError(err, "Couldn't create new user service.")
got, gErr := s.GetUser(auth.DisabledAuthTestContext(repoFn, u.GetScopeId()), req)
@@ -172,18 +180,20 @@ func TestGet(t *testing.T) {
func TestList(t *testing.T) {
ctx := context.Background()
conn, _ := db.TestSetup(t, "postgres")
rw := db.New(conn)
wrap := db.TestWrapper(t)
kmsCache := kms.TestKms(t, conn, wrap)
iamRepo := iam.TestRepo(t, conn, wrap)
repoFn := func() (*iam.Repository, error) {
return iamRepo, nil
}
repo, err := repoFn()
require.NoError(t, err)
aliasRepoFn := func() (*talias.Repository, error) {
return talias.NewRepository(context.Background(), rw, rw, kmsCache)
}
oNoUsers, _ := iam.TestScopes(t, repo)
oWithUsers, _ := iam.TestScopes(t, repo)
oNoUsers, _ := iam.TestScopes(t, iamRepo)
oWithUsers, _ := iam.TestScopes(t, iamRepo)
kmsCache := kms.TestKms(t, conn, wrap)
databaseWrap, err := kmsCache.GetWrapper(context.Background(), oWithUsers.PublicId, kms.KeyPurposeDatabase)
require.NoError(t, err)
primaryAm := oidc.TestAuthMethod(t, conn, databaseWrap, oWithUsers.PublicId, oidc.ActivePublicState, "alice-rp", "fido",
@@ -191,12 +201,12 @@ func TestList(t *testing.T) {
oidc.WithApiUrl(oidc.TestConvertToUrls(t, "http://localhost:9200")[0]),
oidc.WithSigningAlgs(oidc.RS256),
)
iam.TestSetPrimaryAuthMethod(t, repo, oWithUsers, primaryAm.PublicId)
iam.TestSetPrimaryAuthMethod(t, iamRepo, oWithUsers, primaryAm.PublicId)
secondaryAm := password.TestAuthMethods(t, conn, oWithUsers.PublicId, 1)
require.Len(t, secondaryAm, 1)
s, err := users.NewService(context.Background(), repoFn, 1000)
s, err := users.NewService(context.Background(), repoFn, aliasRepoFn, 1000)
require.NoError(t, err)
var wantUsers []*pb.User
@@ -220,16 +230,16 @@ func TestList(t *testing.T) {
for i := 0; i < 10; i++ {
newU, err := iam.NewUser(ctx, oWithUsers.GetPublicId())
require.NoError(t, err)
u, err := repo.CreateUser(context.Background(), newU)
u, err := iamRepo.CreateUser(context.Background(), newU)
require.NoError(t, err)
oidcAcct := oidc.TestAccount(t, conn, primaryAm, fmt.Sprintf("alice+%d", i), oidc.WithFullName("Alice Eve Smith"), oidc.WithEmail("alice@smith.com"))
pwAcct := password.TestAccount(t, conn, secondaryAm[0].PublicId, fmt.Sprintf("alice+%d", i))
added, err := repo.AddUserAccounts(ctx, u.PublicId, u.Version, []string{oidcAcct.PublicId, pwAcct.PublicId})
added, err := iamRepo.AddUserAccounts(ctx, u.PublicId, u.Version, []string{oidcAcct.PublicId, pwAcct.PublicId})
require.NoError(t, err)
require.Len(t, added, 2)
u, _, err = repo.LookupUser(ctx, u.PublicId)
u, _, err = iamRepo.LookupUser(ctx, u.PublicId)
require.NoError(t, err)
wantUsers = append(wantUsers, &pb.User{
Id: u.GetPublicId(),
@@ -375,7 +385,7 @@ func userToProto(u *iam.User, si *scopes.ScopeInfo, authorizedActions []string)
CreatedTime: u.GetCreateTime().GetTimestamp(),
UpdatedTime: u.GetUpdateTime().GetTimestamp(),
Version: u.GetVersion(),
AuthorizedActions: testAuthorizedActions,
AuthorizedActions: authorizedActions,
}
if u.GetName() != "" {
pu.Name = wrapperspb.String(u.GetName())
@@ -405,6 +415,9 @@ func TestListPagination(t *testing.T) {
iamRepoFn := func() (*iam.Repository, error) {
return iamRepo, nil
}
aliasRepoFn := func() (*talias.Repository, error) {
return talias.NewRepository(ctx, rw, rw, kms)
}
tokenRepoFn := func() (*authtoken.Repository, error) {
return authtoken.NewRepository(ctx, rw, rw, kms)
}
@@ -478,7 +491,7 @@ func TestListPagination(t *testing.T) {
}
slices.Reverse(allUsers)
a, err := users.NewService(ctx, iamRepoFn, 1000)
a, err := users.NewService(ctx, iamRepoFn, aliasRepoFn, 1000)
require.NoError(t, err, "Couldn't create new user service.")
// Run analyze to update postgres estimates
@@ -751,10 +764,355 @@ func TestListPagination(t *testing.T) {
assert.ErrorIs(t, handlers.ForbiddenError(), err)
}
func TestDelete(t *testing.T) {
u, _, repoFn := createDefaultUserAndRepo(t, false)
func TestListResolvableAliasesPagination(t *testing.T) {
// Set database read timeout to avoid duplicates in response
oldReadTimeout := globals.RefreshReadLookbackDuration
globals.RefreshReadLookbackDuration = 0
t.Cleanup(func() {
globals.RefreshReadLookbackDuration = oldReadTimeout
})
ctx := context.Background()
conn, _ := db.TestSetup(t, "postgres")
sqlDB, err := conn.SqlDB(ctx)
require.NoError(t, err)
wrapper := db.TestWrapper(t)
kms := kms.TestKms(t, conn, wrapper)
rw := db.New(conn)
s, err := users.NewService(context.Background(), repoFn, 1000)
iamRepo := iam.TestRepo(t, conn, wrapper)
iamRepoFn := func() (*iam.Repository, error) {
return iamRepo, nil
}
aliasRepoFn := func() (*talias.Repository, error) {
return talias.NewRepository(ctx, rw, rw, kms)
}
tokenRepoFn := func() (*authtoken.Repository, error) {
return authtoken.NewRepository(ctx, rw, rw, kms)
}
serversRepoFn := func() (*server.Repository, error) {
return server.NewRepository(ctx, rw, rw, kms)
}
tokenRepo, err := tokenRepoFn()
require.NoError(t, err)
// Run analyze to update postgres meta tables
_, err = sqlDB.ExecContext(ctx, "analyze")
require.NoError(t, err)
_, p := iam.TestScopes(t, iamRepo)
tar := tcp.TestTarget(ctx, t, conn, p.GetPublicId(), "resolvable")
_, unresolvableP := iam.TestScopes(t, iamRepo)
unresolvedTar := tcp.TestTarget(ctx, t, conn, unresolvableP.GetPublicId(), "unresolvable")
// Create an alias that shouldn't be included in the paginated list results.
talias.TestAlias(t, rw, "unresolved.alias", talias.WithDestinationId(unresolvedTar.GetPublicId()))
authMethod := password.TestAuthMethods(t, conn, "global", 1)[0]
acct := password.TestAccount(t, conn, authMethod.GetPublicId(), "test_user")
u := iam.TestUser(t, iamRepo, "global", iam.WithAccountIds(acct.PublicId))
// add roles for requester to be able to perform all actions on everyone
allowedRole := iam.TestRole(t, conn, "global", iam.WithGrantScopeIds([]string{globals.GrantScopeThis, globals.GrantScopeDescendants}))
iam.TestRoleGrant(t, conn, allowedRole.GetPublicId(), "ids=*;type=*;actions=*")
iam.TestUserRole(t, conn, allowedRole.GetPublicId(), u.GetPublicId())
at, err := tokenRepo.CreateAuthToken(ctx, u, acct.GetPublicId())
require.NoError(t, err)
// add roles for user whose resolvable aliases are being listed they can
// only see the aliases which resolve to targets in project p.
resolvingUsersAt := authtoken.TestAuthToken(t, conn, kms, scope.Global.String())
roleForResolving := iam.TestRole(t, conn, "global", iam.WithGrantScopeIds([]string{p.GetPublicId(), globals.GrantScopeThis}))
iam.TestRoleGrant(t, conn, roleForResolving.GetPublicId(), "ids=*;type=target;actions=authorize-session")
iam.TestRoleGrant(t, conn, roleForResolving.GetPublicId(), "ids={{.User.Id}};type=user;actions=list-resolvable-aliases")
iam.TestUserRole(t, conn, roleForResolving.GetPublicId(), resolvingUsersAt.GetIamUserId())
var allAliases []*talias.Alias
var allAliasPbs []*pbalias.Alias
var safeToRemoveAlias *talias.Alias
for i := 0; i < 10; i++ {
na := talias.TestAlias(t, rw, fmt.Sprintf("aliase%d.test", i), talias.WithDestinationId(tar.GetPublicId()))
allAliases = append(allAliases, na)
allAliasPbs = append(allAliasPbs, &pbalias.Alias{
Id: na.GetPublicId(),
Value: na.GetValue(),
DestinationId: wrapperspb.String(na.GetDestinationId()),
CreatedTime: na.GetCreateTime().GetTimestamp(),
UpdatedTime: na.GetUpdateTime().GetTimestamp(),
Type: "target",
})
safeToRemoveAlias = na
}
slices.Reverse(allAliases)
slices.Reverse(allAliasPbs)
a, err := users.NewService(ctx, iamRepoFn, aliasRepoFn, 1000)
require.NoError(t, err, "Couldn't create new user service.")
// Run analyze to update postgres estimates
_, err = sqlDB.ExecContext(context.Background(), "analyze")
require.NoError(t, err)
// +1 because we have one alias that points to an unresolvable target
itemCount := uint32(len(allAliasPbs)) + 1
testPageSize := int((itemCount - 2) / 2)
// See that the resolvingUser can query themselves
requestInfo := authpb.RequestInfo{
TokenFormat: uint32(auth.AuthTokenTypeBearer),
PublicId: resolvingUsersAt.GetPublicId(),
Token: resolvingUsersAt.GetToken(),
}
requestContext := context.WithValue(context.Background(), requests.ContextRequestInformationKey, &requests.RequestContext{})
ctx = auth.NewVerifierContext(requestContext, iamRepoFn, tokenRepoFn, serversRepoFn, kms, &requestInfo)
got, err := a.ListResolvableAliases(ctx, &pbs.ListResolvableAliasesRequest{
Id: resolvingUsersAt.GetIamUserId(),
})
require.NoError(t, err)
assert.Empty(t,
cmp.Diff(
got,
&pbs.ListResolvableAliasesResponse{
Items: allAliasPbs,
ResponseType: "complete",
SortBy: "created_time",
SortDir: "desc",
RemovedIds: nil,
EstItemCount: 10,
},
protocmp.Transform(),
protocmp.SortRepeated(func(a, b string) bool {
return strings.Compare(a, b) < 0
}),
protocmp.IgnoreFields(&pbs.ListResolvableAliasesResponse{}, "list_token"),
),
)
// This user cannot list resolvable for other aliases
_, err = a.ListResolvableAliases(ctx, &pbs.ListResolvableAliasesRequest{
Id: at.GetIamUserId(),
})
require.Error(t, err)
assert.ErrorIs(t, handlers.ForbiddenError(), err)
// Now let the admin user list resolvable aliases
requestInfo = authpb.RequestInfo{
TokenFormat: uint32(auth.AuthTokenTypeBearer),
PublicId: at.GetPublicId(),
Token: at.GetToken(),
}
requestContext = context.WithValue(context.Background(), requests.ContextRequestInformationKey, &requests.RequestContext{})
ctx = auth.NewVerifierContext(requestContext, iamRepoFn, tokenRepoFn, serversRepoFn, kms, &requestInfo)
req := &pbs.ListResolvableAliasesRequest{
Id: resolvingUsersAt.GetIamUserId(),
ListToken: "",
PageSize: uint32(testPageSize),
}
got, err = a.ListResolvableAliases(ctx, req)
require.NoError(t, err)
require.Len(t, got.GetItems(), testPageSize)
// Compare without comparing the list token
assert.Empty(t,
cmp.Diff(
got,
&pbs.ListResolvableAliasesResponse{
Items: allAliasPbs[0:testPageSize],
ResponseType: "delta",
SortBy: "created_time",
SortDir: "desc",
RemovedIds: nil,
// In addition to the added users, there are the users added
// by the test setup when specifying the permissions of the
// requester
EstItemCount: itemCount,
},
protocmp.SortRepeated(func(a, b string) bool {
return strings.Compare(a, b) < 0
}),
protocmp.Transform(),
protocmp.IgnoreFields(&pbs.ListResolvableAliasesResponse{}, "list_token"),
),
)
// Request second page
req.ListToken = got.ListToken
got, err = a.ListResolvableAliases(ctx, req)
require.NoError(t, err)
require.Len(t, got.GetItems(), testPageSize)
// Compare without comparing the list token
assert.Empty(t,
cmp.Diff(
got,
&pbs.ListResolvableAliasesResponse{
Items: allAliasPbs[testPageSize : testPageSize*2],
ResponseType: "delta",
SortBy: "created_time",
SortDir: "desc",
RemovedIds: nil,
EstItemCount: itemCount,
},
protocmp.Transform(),
protocmp.SortRepeated(func(a, b string) bool {
return strings.Compare(a, b) < 0
}),
protocmp.IgnoreFields(&pbs.ListResolvableAliasesResponse{}, "list_token"),
),
)
// Request rest of results
req.ListToken = got.ListToken
req.PageSize = 10
got, err = a.ListResolvableAliases(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.ListResolvableAliasesResponse{
Items: allAliasPbs[testPageSize*2:],
ResponseType: "complete",
SortBy: "created_time",
SortDir: "desc",
RemovedIds: nil,
EstItemCount: itemCount,
},
protocmp.Transform(),
protocmp.SortRepeated(func(a, b string) bool {
return strings.Compare(a, b) < 0
}),
protocmp.IgnoreFields(&pbs.ListResolvableAliasesResponse{}, "list_token"),
),
)
// Update 2 aliases and see them in the refresh
aliasRepo, err := aliasRepoFn()
require.NoError(t, err)
for i := 0; i < 2; i++ {
r := allAliases[len(allAliasPbs)-1]
rPb := allAliasPbs[len(allAliasPbs)-1]
r.Description = fmt.Sprintf("updated%d", i)
updated, _, err := aliasRepo.UpdateAlias(ctx, r, r.GetVersion(), []string{"description"})
require.NoError(t, err)
r.Version = updated.GetVersion()
r.UpdateTime = updated.GetUpdateTime()
rPb.UpdatedTime = updated.GetUpdateTime().GetTimestamp()
// ListResolvableAliases does not return the description, or the version
// so we do not update those values here in the protobuf aliases
allAliases = append([]*talias.Alias{r}, allAliases[:len(allAliases)-1]...)
allAliasPbs = append([]*pbalias.Alias{rPb}, allAliasPbs[:len(allAliasPbs)-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.ListResolvableAliases(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.ListResolvableAliasesResponse{
Items: []*pbalias.Alias{allAliasPbs[0]},
ResponseType: "delta",
SortBy: "updated_time",
SortDir: "desc",
RemovedIds: nil,
EstItemCount: itemCount,
},
protocmp.Transform(),
protocmp.SortRepeated(func(a, b string) bool {
return strings.Compare(a, b) < 0
}),
protocmp.IgnoreFields(&pbs.ListResolvableAliasesResponse{}, "list_token"),
),
)
// Get next page
req.ListToken = got.ListToken
got, err = a.ListResolvableAliases(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.ListResolvableAliasesResponse{
Items: []*pbalias.Alias{allAliasPbs[1]},
ResponseType: "complete",
SortBy: "updated_time",
SortDir: "desc",
RemovedIds: nil,
EstItemCount: itemCount,
},
protocmp.Transform(),
protocmp.SortRepeated(func(a, b string) bool {
return strings.Compare(a, b) < 0
}),
protocmp.IgnoreFields(&pbs.ListResolvableAliasesResponse{}, "list_token"),
),
)
_, err = aliasRepo.DeleteAlias(ctx, safeToRemoveAlias.GetPublicId())
require.NoError(t, err)
req.ListToken = got.ListToken
got, err = a.ListResolvableAliases(ctx, req)
require.NoError(t, err)
assert.Empty(t,
cmp.Diff(
got,
&pbs.ListResolvableAliasesResponse{
Items: nil,
ResponseType: "complete",
SortBy: "updated_time",
SortDir: "desc",
RemovedIds: []string{safeToRemoveAlias.GetPublicId()},
EstItemCount: itemCount,
},
protocmp.Transform(),
protocmp.SortRepeated(func(a, b string) bool {
return strings.Compare(a, b) < 0
}),
protocmp.IgnoreFields(&pbs.ListResolvableAliasesResponse{}, "list_token"),
),
)
// Create unauthenticated user
unauthAt := authtoken.TestAuthToken(t, conn, kms, scope.Global.String())
unauthR := iam.TestRole(t, conn, p.GetPublicId())
_ = iam.TestUserRole(t, conn, unauthR.GetPublicId(), unauthAt.GetIamUserId())
// Make a request with the unauthenticated user,
// ensure the response contains the pagination parameters.
requestInfo = authpb.RequestInfo{
TokenFormat: uint32(auth.AuthTokenTypeBearer),
PublicId: unauthAt.GetPublicId(),
Token: unauthAt.GetToken(),
}
requestContext = context.WithValue(context.Background(), requests.ContextRequestInformationKey, &requests.RequestContext{})
ctx = auth.NewVerifierContext(requestContext, iamRepoFn, tokenRepoFn, serversRepoFn, kms, &requestInfo)
_, err = a.ListResolvableAliases(ctx, &pbs.ListResolvableAliasesRequest{
Id: resolvingUsersAt.GetIamUserId(),
})
require.Error(t, err)
assert.ErrorIs(t, handlers.ForbiddenError(), err)
}
func TestDelete(t *testing.T) {
u, _, repoFn, aliasRepoFn := createDefaultUserAndRepos(t, false)
s, err := users.NewService(context.Background(), repoFn, aliasRepoFn, 1000)
require.NoError(t, err, "Error when getting new user service.")
cases := []struct {
@@ -799,9 +1157,9 @@ func TestDelete(t *testing.T) {
func TestDelete_twice(t *testing.T) {
assert, require := assert.New(t), require.New(t)
u, _, repoFn := createDefaultUserAndRepo(t, false)
u, _, repoFn, aliasRepoFn := createDefaultUserAndRepos(t, false)
s, err := users.NewService(context.Background(), repoFn, 1000)
s, err := users.NewService(context.Background(), repoFn, aliasRepoFn, 1000)
require.NoError(err, "Error when getting new user service")
req := &pbs.DeleteUserRequest{
Id: u.GetPublicId(),
@@ -815,7 +1173,7 @@ func TestDelete_twice(t *testing.T) {
}
func TestCreate(t *testing.T) {
defaultUser, _, repoFn := createDefaultUserAndRepo(t, false)
defaultUser, _, repoFn, aliasRepoFn := createDefaultUserAndRepos(t, false)
defaultCreated := defaultUser.GetCreateTime().GetTimestamp().AsTime()
cases := []struct {
@@ -893,7 +1251,7 @@ func TestCreate(t *testing.T) {
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
s, err := users.NewService(context.Background(), repoFn, 1000)
s, err := users.NewService(context.Background(), repoFn, aliasRepoFn, 1000)
require.NoError(err, "Error when getting new user service.")
got, gErr := s.CreateUser(auth.DisabledAuthTestContext(repoFn, tc.req.GetItem().GetScopeId()), tc.req)
@@ -928,8 +1286,8 @@ func TestCreate(t *testing.T) {
}
func TestUpdate(t *testing.T) {
u, _, repoFn := createDefaultUserAndRepo(t, false)
tested, err := users.NewService(context.Background(), repoFn, 1000)
u, _, repoFn, aliasRepoFn := createDefaultUserAndRepos(t, false)
tested, err := users.NewService(context.Background(), repoFn, aliasRepoFn, 1000)
require.NoError(t, err, "Error when getting new user service.")
created := u.GetCreateTime().GetTimestamp().AsTime()
@@ -1208,12 +1566,16 @@ func TestAddAccount(t *testing.T) {
conn, _ := db.TestSetup(t, "postgres")
wrap := db.TestWrapper(t)
ctx := context.Background()
rw := db.New(conn)
kmsCache := kms.TestKms(t, conn, wrap)
iamRepo := iam.TestRepo(t, conn, wrap)
repoFn := func() (*iam.Repository, error) {
return iamRepo, nil
}
s, err := users.NewService(ctx, repoFn, 1000)
aliasRepoFn := func() (*talias.Repository, error) {
return talias.NewRepository(ctx, rw, rw, kmsCache)
}
s, err := users.NewService(ctx, repoFn, aliasRepoFn, 1000)
require.NoError(t, err, "Error when getting new user service.")
o, _ := iam.TestScopes(t, iamRepo)
@@ -1368,12 +1730,16 @@ func TestSetAccount(t *testing.T) {
conn, _ := db.TestSetup(t, "postgres")
wrap := db.TestWrapper(t)
ctx := context.Background()
rw := db.New(conn)
kmsCache := kms.TestKms(t, conn, wrap)
iamRepo := iam.TestRepo(t, conn, wrap)
repoFn := func() (*iam.Repository, error) {
return iamRepo, nil
}
s, err := users.NewService(ctx, repoFn, 1000)
aliasRepoFn := func() (*talias.Repository, error) {
return talias.NewRepository(ctx, rw, rw, kmsCache)
}
s, err := users.NewService(ctx, repoFn, aliasRepoFn, 1000)
require.NoError(t, err, "Error when getting new user service.")
o, _ := iam.TestScopes(t, iamRepo)
@@ -1530,12 +1896,16 @@ func TestRemoveAccount(t *testing.T) {
conn, _ := db.TestSetup(t, "postgres")
wrap := db.TestWrapper(t)
ctx := context.Background()
rw := db.New(conn)
kmsCache := kms.TestKms(t, conn, wrap)
iamRepo := iam.TestRepo(t, conn, wrap)
repoFn := func() (*iam.Repository, error) {
return iamRepo, nil
}
s, err := users.NewService(ctx, repoFn, 1000)
aliasRepoFn := func() (*talias.Repository, error) {
return talias.NewRepository(ctx, rw, rw, kmsCache)
}
s, err := users.NewService(ctx, repoFn, aliasRepoFn, 1000)
require.NoError(t, err, "Error when getting new user service.")
o, _ := iam.TestScopes(t, iamRepo)
+29 -29
View File
@@ -63,7 +63,7 @@ func Test_newRateLimiterConfig(t *testing.T) {
ratelimit.DefaultLimiterMaxQuotas(),
false,
&rateLimiterConfig{
maxSize: 336168,
maxSize: 338169,
configs: nil,
disabled: false,
limits: defaultLimits,
@@ -515,24 +515,24 @@ func Test_rateLimiterConfig_writeSysEvent(t *testing.T) {
disabled bool
}{
{
"defaults",
func(n string) error {
name: "defaults",
setup: func(n string) error {
return event.InitSysEventer(testLogger, testLock, n, event.WithEventerConfig(&c.EventerConfig))
},
func() { event.TestResetSystEventer(t) },
c.AllEvents.Name(),
nil,
ratelimit.DefaultLimiterMaxQuotas(),
false,
cleanup: func() { event.TestResetSystEventer(t) },
sinkFileName: c.AllEvents.Name(),
configs: nil,
maxSize: ratelimit.DefaultLimiterMaxQuotas(),
disabled: false,
},
{
"override",
func(n string) error {
name: "override",
setup: func(n string) error {
return event.InitSysEventer(testLogger, testLock, n, event.WithEventerConfig(&c.EventerConfig))
},
func() { event.TestResetSystEventer(t) },
c.AllEvents.Name(),
ratelimit.Configs{
cleanup: func() { event.TestResetSystEventer(t) },
sinkFileName: c.AllEvents.Name(),
configs: ratelimit.Configs{
{
Resources: []string{"*"},
Actions: []string{"*"},
@@ -558,30 +558,30 @@ func Test_rateLimiterConfig_writeSysEvent(t *testing.T) {
Unlimited: false,
},
},
ratelimit.DefaultLimiterMaxQuotas(),
false,
maxSize: ratelimit.DefaultLimiterMaxQuotas(),
disabled: false,
},
{
"max_size",
func(n string) error {
name: "max_size",
setup: func(n string) error {
return event.InitSysEventer(testLogger, testLock, n, event.WithEventerConfig(&c.EventerConfig))
},
func() { event.TestResetSystEventer(t) },
c.AllEvents.Name(),
nil,
3000,
false,
cleanup: func() { event.TestResetSystEventer(t) },
sinkFileName: c.AllEvents.Name(),
configs: nil,
maxSize: 3000,
disabled: false,
},
{
"disabled",
func(n string) error {
name: "disabled",
setup: func(n string) error {
return event.InitSysEventer(testLogger, testLock, n, event.WithEventerConfig(&c.EventerConfig))
},
func() { event.TestResetSystEventer(t) },
c.AllEvents.Name(),
nil,
0,
true,
cleanup: func() { event.TestResetSystEventer(t) },
sinkFileName: c.AllEvents.Name(),
configs: nil,
maxSize: 0,
disabled: true,
},
}
@@ -3787,6 +3787,32 @@
"unlimited": false
}
],
"list-resolvable-aliases": [
{
"action": "list-resolvable-aliases",
"limit": 30000,
"per": "total",
"period": "30s",
"resource": "user",
"unlimited": false
},
{
"action": "list-resolvable-aliases",
"limit": 30000,
"per": "ip-address",
"period": "30s",
"resource": "user",
"unlimited": false
},
{
"action": "list-resolvable-aliases",
"limit": 3000,
"per": "auth-token",
"period": "30s",
"resource": "user",
"unlimited": false
}
],
"no-op": [
{
"action": "no-op",
@@ -4233,7 +4259,7 @@
]
}
},
"max_size": 336168,
"max_size": 338169,
"msg": "controller api rate limiter"
},
"op": "controller.(rateLimiterConfig).writeSysEvent",
@@ -3629,6 +3629,32 @@
"unlimited": false
}
],
"list-resolvable-aliases": [
{
"action": "list-resolvable-aliases",
"limit": 30000,
"per": "total",
"period": "30s",
"resource": "user",
"unlimited": false
},
{
"action": "list-resolvable-aliases",
"limit": 30000,
"per": "ip-address",
"period": "30s",
"resource": "user",
"unlimited": false
},
{
"action": "list-resolvable-aliases",
"limit": 3000,
"per": "auth-token",
"period": "30s",
"resource": "user",
"unlimited": false
}
],
"no-op": [
{
"action": "no-op",
@@ -3629,6 +3629,32 @@
"unlimited": false
}
],
"list-resolvable-aliases": [
{
"action": "list-resolvable-aliases",
"limit": 100,
"per": "ip-address",
"period": "1m0s",
"resource": "user",
"unlimited": false
},
{
"action": "list-resolvable-aliases",
"limit": 100,
"per": "total",
"period": "1m0s",
"resource": "user",
"unlimited": false
},
{
"action": "list-resolvable-aliases",
"limit": 100,
"per": "auth-token",
"period": "1m0s",
"resource": "user",
"unlimited": false
}
],
"no-op": [
{
"action": "no-op",
@@ -4075,7 +4101,7 @@
]
}
},
"max_size": 336168,
"max_size": 338169,
"msg": "controller api rate limiter"
},
"op": "controller.(rateLimiterConfig).writeSysEvent",
+87
View File
@@ -5223,6 +5223,53 @@
]
}
},
"/v1/users/{id}:list-resolvable-aliases": {
"get": {
"summary": "Lists all Aliases which point to a resource for which the requester has some permission.",
"operationId": "UserService_ListResolvableAliases",
"responses": {
"200": {
"description": "A successful response.",
"schema": {
"$ref": "#/definitions/controller.api.services.v1.ListResolvableAliasesResponse"
}
},
"default": {
"description": "Returned when there is an error processing the request.",
"schema": {
"$ref": "#/definitions/controller.api.v1.Error"
}
}
},
"parameters": [
{
"name": "id",
"description": "",
"in": "path",
"required": true,
"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": [
"User service"
]
}
},
"/v1/users/{id}:remove-accounts": {
"post": {
"summary": "Removes the specified Accounts from being associated with the provided User.",
@@ -9896,6 +9943,46 @@
}
}
},
"controller.api.services.v1.ListResolvableAliasesResponse": {
"type": "object",
"properties": {
"items": {
"type": "array",
"items": {
"type": "object",
"$ref": "#/definitions/controller.api.resources.aliases.v1.Alias"
}
},
"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. This includes aliases which have their\ndestination_id removed or set to a resource for which the requester doesn't\nhave permissions."
},
"est_item_count": {
"type": "integer",
"format": "int64",
"description": "An estimate at the total items available. This may change during pagination."
}
}
},
"controller.api.services.v1.ListRolesResponse": {
"type": "object",
"properties": {
@@ -11,6 +11,7 @@ package services
import (
_ "github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-openapiv2/options"
aliases "github.com/hashicorp/boundary/sdk/pbs/controller/api/resources/aliases"
users "github.com/hashicorp/boundary/sdk/pbs/controller/api/resources/users"
_ "google.golang.org/genproto/googleapis/api/annotations"
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
@@ -947,6 +948,185 @@ func (x *RemoveUserAccountsResponse) GetItem() *users.User {
return nil
}
type ListResolvableAliasesRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty" class:"public" eventstream:"observation"` // @gotags: `class:"public" eventstream:"observation"`
// 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,2,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,3,opt,name=page_size,proto3" json:"page_size,omitempty" class:"public"` // @gotags: `class:"public"`
}
func (x *ListResolvableAliasesRequest) Reset() {
*x = ListResolvableAliasesRequest{}
if protoimpl.UnsafeEnabled {
mi := &file_controller_api_services_v1_user_service_proto_msgTypes[16]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *ListResolvableAliasesRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*ListResolvableAliasesRequest) ProtoMessage() {}
func (x *ListResolvableAliasesRequest) ProtoReflect() protoreflect.Message {
mi := &file_controller_api_services_v1_user_service_proto_msgTypes[16]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use ListResolvableAliasesRequest.ProtoReflect.Descriptor instead.
func (*ListResolvableAliasesRequest) Descriptor() ([]byte, []int) {
return file_controller_api_services_v1_user_service_proto_rawDescGZIP(), []int{16}
}
func (x *ListResolvableAliasesRequest) GetId() string {
if x != nil {
return x.Id
}
return ""
}
func (x *ListResolvableAliasesRequest) GetListToken() string {
if x != nil {
return x.ListToken
}
return ""
}
func (x *ListResolvableAliasesRequest) GetPageSize() uint32 {
if x != nil {
return x.PageSize
}
return 0
}
type ListResolvableAliasesResponse struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Items []*aliases.Alias `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. This includes aliases which have their
// destination_id removed or set to a resource for which the requester doesn't
// have permissions.
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 *ListResolvableAliasesResponse) Reset() {
*x = ListResolvableAliasesResponse{}
if protoimpl.UnsafeEnabled {
mi := &file_controller_api_services_v1_user_service_proto_msgTypes[17]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *ListResolvableAliasesResponse) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*ListResolvableAliasesResponse) ProtoMessage() {}
func (x *ListResolvableAliasesResponse) ProtoReflect() protoreflect.Message {
mi := &file_controller_api_services_v1_user_service_proto_msgTypes[17]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use ListResolvableAliasesResponse.ProtoReflect.Descriptor instead.
func (*ListResolvableAliasesResponse) Descriptor() ([]byte, []int) {
return file_controller_api_services_v1_user_service_proto_rawDescGZIP(), []int{17}
}
func (x *ListResolvableAliasesResponse) GetItems() []*aliases.Alias {
if x != nil {
return x.Items
}
return nil
}
func (x *ListResolvableAliasesResponse) GetResponseType() string {
if x != nil {
return x.ResponseType
}
return ""
}
func (x *ListResolvableAliasesResponse) GetListToken() string {
if x != nil {
return x.ListToken
}
return ""
}
func (x *ListResolvableAliasesResponse) GetSortBy() string {
if x != nil {
return x.SortBy
}
return ""
}
func (x *ListResolvableAliasesResponse) GetSortDir() string {
if x != nil {
return x.SortDir
}
return ""
}
func (x *ListResolvableAliasesResponse) GetRemovedIds() []string {
if x != nil {
return x.RemovedIds
}
return nil
}
func (x *ListResolvableAliasesResponse) GetEstItemCount() uint32 {
if x != nil {
return x.EstItemCount
}
return 0
}
var File_controller_api_services_v1_user_service_proto protoreflect.FileDescriptor
var file_controller_api_services_v1_user_service_proto_rawDesc = []byte{
@@ -954,242 +1134,289 @@ var file_controller_api_services_v1_user_service_proto_rawDesc = []byte{
0x2f, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2f, 0x76, 0x31, 0x2f, 0x75, 0x73, 0x65,
0x72, 0x5f, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12,
0x1a, 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, 0x1a, 0x2c, 0x63, 0x6f, 0x6e,
0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x1a, 0x2f, 0x63, 0x6f, 0x6e,
0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x72, 0x65, 0x73, 0x6f,
0x75, 0x72, 0x63, 0x65, 0x73, 0x2f, 0x75, 0x73, 0x65, 0x72, 0x73, 0x2f, 0x76, 0x31, 0x2f, 0x75,
0x73, 0x65, 0x72, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1c, 0x67, 0x6f, 0x6f, 0x67, 0x6c,
0x65, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x61, 0x6e, 0x6e, 0x6f, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e,
0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x20, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f,
0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x5f, 0x6d,
0x61, 0x73, 0x6b, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f,
0x63, 0x2d, 0x67, 0x65, 0x6e, 0x2d, 0x6f, 0x70, 0x65, 0x6e, 0x61, 0x70, 0x69, 0x76, 0x32, 0x2f,
0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2f, 0x61, 0x6e, 0x6e, 0x6f, 0x74, 0x61, 0x74, 0x69,
0x6f, 0x6e, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x20, 0x0a, 0x0e, 0x47, 0x65, 0x74,
0x55, 0x73, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69,
0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x22, 0x4e, 0x0a, 0x0f, 0x47,
0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3b,
0x0a, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x27, 0x2e, 0x63,
0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x72, 0x65,
0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x73, 0x2e, 0x76, 0x31,
0x2e, 0x55, 0x73, 0x65, 0x72, 0x52, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x22, 0xa1, 0x01, 0x0a, 0x10,
0x4c, 0x69, 0x73, 0x74, 0x55, 0x73, 0x65, 0x72, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74,
0x12, 0x19, 0x0a, 0x08, 0x73, 0x63, 0x6f, 0x70, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01,
0x28, 0x09, 0x52, 0x07, 0x73, 0x63, 0x6f, 0x70, 0x65, 0x49, 0x64, 0x12, 0x1c, 0x0a, 0x09, 0x72,
0x65, 0x63, 0x75, 0x72, 0x73, 0x69, 0x76, 0x65, 0x18, 0x14, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09,
0x72, 0x65, 0x63, 0x75, 0x72, 0x73, 0x69, 0x76, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x66, 0x69, 0x6c,
0x74, 0x65, 0x72, 0x18, 0x1e, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x66, 0x69, 0x6c, 0x74, 0x65,
0x72, 0x12, 0x1e, 0x0a, 0x0a, 0x6c, 0x69, 0x73, 0x74, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18,
0x28, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x6c, 0x69, 0x73, 0x74, 0x5f, 0x74, 0x6f, 0x6b, 0x65,
0x6e, 0x12, 0x1c, 0x0a, 0x09, 0x70, 0x61, 0x67, 0x65, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x32,
0x20, 0x01, 0x28, 0x0d, 0x52, 0x09, 0x70, 0x61, 0x67, 0x65, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x22,
0x98, 0x02, 0x0a, 0x11, 0x4c, 0x69, 0x73, 0x74, 0x55, 0x73, 0x65, 0x72, 0x73, 0x52, 0x65, 0x73,
0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3d, 0x0a, 0x05, 0x69, 0x74, 0x65, 0x6d, 0x73, 0x18, 0x01,
0x20, 0x03, 0x28, 0x0b, 0x32, 0x27, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65,
0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x2e,
0x75, 0x73, 0x65, 0x72, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x73, 0x65, 0x72, 0x52, 0x05, 0x69,
0x74, 0x65, 0x6d, 0x73, 0x12, 0x24, 0x0a, 0x0d, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65,
0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x72, 0x65, 0x73,
0x70, 0x6f, 0x6e, 0x73, 0x65, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x12, 0x1e, 0x0a, 0x0a, 0x6c, 0x69,
0x73, 0x74, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a,
0x6c, 0x69, 0x73, 0x74, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x18, 0x0a, 0x07, 0x73, 0x6f,
0x72, 0x74, 0x5f, 0x62, 0x79, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x73, 0x6f, 0x72,
0x74, 0x5f, 0x62, 0x79, 0x12, 0x1a, 0x0a, 0x08, 0x73, 0x6f, 0x72, 0x74, 0x5f, 0x64, 0x69, 0x72,
0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x73, 0x6f, 0x72, 0x74, 0x5f, 0x64, 0x69, 0x72,
0x12, 0x20, 0x0a, 0x0b, 0x72, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x64, 0x5f, 0x69, 0x64, 0x73, 0x18,
0x06, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0b, 0x72, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x64, 0x5f, 0x69,
0x64, 0x73, 0x12, 0x26, 0x0a, 0x0e, 0x65, 0x73, 0x74, 0x5f, 0x69, 0x74, 0x65, 0x6d, 0x5f, 0x63,
0x6f, 0x75, 0x6e, 0x74, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0e, 0x65, 0x73, 0x74, 0x5f,
0x69, 0x74, 0x65, 0x6d, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x22, 0x50, 0x0a, 0x11, 0x43, 0x72,
0x65, 0x61, 0x74, 0x65, 0x55, 0x73, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12,
0x75, 0x72, 0x63, 0x65, 0x73, 0x2f, 0x61, 0x6c, 0x69, 0x61, 0x73, 0x65, 0x73, 0x2f, 0x76, 0x31,
0x2f, 0x61, 0x6c, 0x69, 0x61, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x2c, 0x63, 0x6f,
0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x72, 0x65, 0x73,
0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x2f, 0x75, 0x73, 0x65, 0x72, 0x73, 0x2f, 0x76, 0x31, 0x2f,
0x75, 0x73, 0x65, 0x72, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1c, 0x67, 0x6f, 0x6f, 0x67,
0x6c, 0x65, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x61, 0x6e, 0x6e, 0x6f, 0x74, 0x61, 0x74, 0x69, 0x6f,
0x6e, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x20, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65,
0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x5f,
0x6d, 0x61, 0x73, 0x6b, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x2e, 0x70, 0x72, 0x6f, 0x74,
0x6f, 0x63, 0x2d, 0x67, 0x65, 0x6e, 0x2d, 0x6f, 0x70, 0x65, 0x6e, 0x61, 0x70, 0x69, 0x76, 0x32,
0x2f, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2f, 0x61, 0x6e, 0x6e, 0x6f, 0x74, 0x61, 0x74,
0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x20, 0x0a, 0x0e, 0x47, 0x65,
0x74, 0x55, 0x73, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x0e, 0x0a, 0x02,
0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x22, 0x4e, 0x0a, 0x0f,
0x47, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12,
0x3b, 0x0a, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x27, 0x2e,
0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x72,
0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x73, 0x2e, 0x76,
0x31, 0x2e, 0x55, 0x73, 0x65, 0x72, 0x52, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x22, 0x63, 0x0a, 0x12,
0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x55, 0x73, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e,
0x73, 0x65, 0x12, 0x10, 0x0a, 0x03, 0x75, 0x72, 0x69, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52,
0x03, 0x75, 0x72, 0x69, 0x12, 0x3b, 0x0a, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x18, 0x02, 0x20, 0x01,
0x28, 0x0b, 0x32, 0x27, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e,
0x61, 0x70, 0x69, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x2e, 0x75, 0x73,
0x65, 0x72, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x73, 0x65, 0x72, 0x52, 0x04, 0x69, 0x74, 0x65,
0x6d, 0x22, 0x9e, 0x01, 0x0a, 0x11, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x55, 0x73, 0x65, 0x72,
0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20,
0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x3b, 0x0a, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x18,
0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x27, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c,
0x31, 0x2e, 0x55, 0x73, 0x65, 0x72, 0x52, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x22, 0xa1, 0x01, 0x0a,
0x10, 0x4c, 0x69, 0x73, 0x74, 0x55, 0x73, 0x65, 0x72, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73,
0x74, 0x12, 0x19, 0x0a, 0x08, 0x73, 0x63, 0x6f, 0x70, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20,
0x01, 0x28, 0x09, 0x52, 0x07, 0x73, 0x63, 0x6f, 0x70, 0x65, 0x49, 0x64, 0x12, 0x1c, 0x0a, 0x09,
0x72, 0x65, 0x63, 0x75, 0x72, 0x73, 0x69, 0x76, 0x65, 0x18, 0x14, 0x20, 0x01, 0x28, 0x08, 0x52,
0x09, 0x72, 0x65, 0x63, 0x75, 0x72, 0x73, 0x69, 0x76, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x66, 0x69,
0x6c, 0x74, 0x65, 0x72, 0x18, 0x1e, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x66, 0x69, 0x6c, 0x74,
0x65, 0x72, 0x12, 0x1e, 0x0a, 0x0a, 0x6c, 0x69, 0x73, 0x74, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e,
0x18, 0x28, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x6c, 0x69, 0x73, 0x74, 0x5f, 0x74, 0x6f, 0x6b,
0x65, 0x6e, 0x12, 0x1c, 0x0a, 0x09, 0x70, 0x61, 0x67, 0x65, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x18,
0x32, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x09, 0x70, 0x61, 0x67, 0x65, 0x5f, 0x73, 0x69, 0x7a, 0x65,
0x22, 0x98, 0x02, 0x0a, 0x11, 0x4c, 0x69, 0x73, 0x74, 0x55, 0x73, 0x65, 0x72, 0x73, 0x52, 0x65,
0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3d, 0x0a, 0x05, 0x69, 0x74, 0x65, 0x6d, 0x73, 0x18,
0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x27, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c,
0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73,
0x2e, 0x75, 0x73, 0x65, 0x72, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x73, 0x65, 0x72, 0x52, 0x04,
0x69, 0x74, 0x65, 0x6d, 0x12, 0x3c, 0x0a, 0x0b, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x5f, 0x6d,
0x61, 0x73, 0x6b, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67,
0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x46, 0x69, 0x65, 0x6c,
0x64, 0x4d, 0x61, 0x73, 0x6b, 0x52, 0x0b, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x5f, 0x6d, 0x61,
0x73, 0x6b, 0x22, 0x51, 0x0a, 0x12, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x55, 0x73, 0x65, 0x72,
0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3b, 0x0a, 0x04, 0x69, 0x74, 0x65, 0x6d,
0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x27, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c,
0x2e, 0x75, 0x73, 0x65, 0x72, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x73, 0x65, 0x72, 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, 0x50, 0x0a, 0x11, 0x43,
0x72, 0x65, 0x61, 0x74, 0x65, 0x55, 0x73, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74,
0x12, 0x3b, 0x0a, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x27,
0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e,
0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x73, 0x2e,
0x76, 0x31, 0x2e, 0x55, 0x73, 0x65, 0x72, 0x52, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x22, 0x63, 0x0a,
0x12, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x55, 0x73, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f,
0x6e, 0x73, 0x65, 0x12, 0x10, 0x0a, 0x03, 0x75, 0x72, 0x69, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09,
0x52, 0x03, 0x75, 0x72, 0x69, 0x12, 0x3b, 0x0a, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x18, 0x02, 0x20,
0x01, 0x28, 0x0b, 0x32, 0x27, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72,
0x2e, 0x61, 0x70, 0x69, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x2e, 0x75,
0x73, 0x65, 0x72, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x73, 0x65, 0x72, 0x52, 0x04, 0x69, 0x74,
0x65, 0x6d, 0x22, 0x9e, 0x01, 0x0a, 0x11, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x55, 0x73, 0x65,
0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01,
0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x3b, 0x0a, 0x04, 0x69, 0x74, 0x65, 0x6d,
0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x27, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c,
0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65,
0x73, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x73, 0x65, 0x72, 0x52,
0x04, 0x69, 0x74, 0x65, 0x6d, 0x22, 0x23, 0x0a, 0x11, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x55,
0x73, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64,
0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x22, 0x14, 0x0a, 0x12, 0x44, 0x65,
0x6c, 0x65, 0x74, 0x65, 0x55, 0x73, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65,
0x22, 0x64, 0x0a, 0x16, 0x41, 0x64, 0x64, 0x55, 0x73, 0x65, 0x72, 0x41, 0x63, 0x63, 0x6f, 0x75,
0x6e, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64,
0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x18, 0x0a, 0x07, 0x76, 0x65,
0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x07, 0x76, 0x65, 0x72,
0x73, 0x69, 0x6f, 0x6e, 0x12, 0x20, 0x0a, 0x0b, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x5f,
0x69, 0x64, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0b, 0x61, 0x63, 0x63, 0x6f, 0x75,
0x6e, 0x74, 0x5f, 0x69, 0x64, 0x73, 0x22, 0x56, 0x0a, 0x17, 0x41, 0x64, 0x64, 0x55, 0x73, 0x65,
0x72, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73,
0x65, 0x12, 0x3b, 0x0a, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32,
0x27, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69,
0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x73,
0x2e, 0x76, 0x31, 0x2e, 0x55, 0x73, 0x65, 0x72, 0x52, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x22, 0x64,
0x0a, 0x16, 0x53, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74,
0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01,
0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x18, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73,
0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69,
0x6f, 0x6e, 0x12, 0x20, 0x0a, 0x0b, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x5f, 0x69, 0x64,
0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0b, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74,
0x5f, 0x69, 0x64, 0x73, 0x22, 0x56, 0x0a, 0x17, 0x53, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x41,
0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12,
0x3b, 0x0a, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x27, 0x2e,
0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x72,
0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x73, 0x2e, 0x76,
0x31, 0x2e, 0x55, 0x73, 0x65, 0x72, 0x52, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x22, 0x67, 0x0a, 0x19,
0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x55, 0x73, 0x65, 0x72, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e,
0x04, 0x69, 0x74, 0x65, 0x6d, 0x12, 0x3c, 0x0a, 0x0b, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x5f,
0x6d, 0x61, 0x73, 0x6b, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f,
0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x46, 0x69, 0x65,
0x6c, 0x64, 0x4d, 0x61, 0x73, 0x6b, 0x52, 0x0b, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x5f, 0x6d,
0x61, 0x73, 0x6b, 0x22, 0x51, 0x0a, 0x12, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x55, 0x73, 0x65,
0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3b, 0x0a, 0x04, 0x69, 0x74, 0x65,
0x6d, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x27, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f,
0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63,
0x65, 0x73, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x73, 0x65, 0x72,
0x52, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x22, 0x23, 0x0a, 0x11, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65,
0x55, 0x73, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69,
0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x22, 0x14, 0x0a, 0x12, 0x44,
0x65, 0x6c, 0x65, 0x74, 0x65, 0x55, 0x73, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73,
0x65, 0x22, 0x64, 0x0a, 0x16, 0x41, 0x64, 0x64, 0x55, 0x73, 0x65, 0x72, 0x41, 0x63, 0x63, 0x6f,
0x75, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69,
0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x18, 0x0a, 0x07, 0x76,
0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x07, 0x76, 0x65,
0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x20, 0x0a, 0x0b, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74,
0x5f, 0x69, 0x64, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0b, 0x61, 0x63, 0x63, 0x6f,
0x75, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x73, 0x22, 0x56, 0x0a, 0x17, 0x41, 0x64, 0x64, 0x55, 0x73,
0x65, 0x72, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e,
0x73, 0x65, 0x12, 0x3b, 0x0a, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b,
0x32, 0x27, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70,
0x69, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x2e, 0x75, 0x73, 0x65, 0x72,
0x73, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x73, 0x65, 0x72, 0x52, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x22,
0x64, 0x0a, 0x16, 0x53, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e,
0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18,
0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x18, 0x0a, 0x07, 0x76, 0x65, 0x72,
0x73, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73,
0x69, 0x6f, 0x6e, 0x12, 0x20, 0x0a, 0x0b, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x5f, 0x69,
0x64, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0b, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e,
0x74, 0x5f, 0x69, 0x64, 0x73, 0x22, 0x59, 0x0a, 0x1a, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x55,
0x73, 0x65, 0x72, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f,
0x6e, 0x73, 0x65, 0x12, 0x3b, 0x0a, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x18, 0x01, 0x20, 0x01, 0x28,
0x0b, 0x32, 0x27, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61,
0x70, 0x69, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x2e, 0x75, 0x73, 0x65,
0x72, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x73, 0x65, 0x72, 0x52, 0x04, 0x69, 0x74, 0x65, 0x6d,
0x32, 0xe1, 0x0e, 0x0a, 0x0b, 0x55, 0x73, 0x65, 0x72, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65,
0x12, 0x98, 0x01, 0x0a, 0x07, 0x47, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x12, 0x2a, 0x2e, 0x63,
0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x73, 0x65,
0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x55, 0x73, 0x65,
0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2b, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72,
0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63,
0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x52, 0x65, 0x73,
0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x34, 0x92, 0x41, 0x15, 0x12, 0x13, 0x47, 0x65, 0x74, 0x73,
0x20, 0x61, 0x20, 0x73, 0x69, 0x6e, 0x67, 0x6c, 0x65, 0x20, 0x55, 0x73, 0x65, 0x72, 0x2e, 0x82,
0xd3, 0xe4, 0x93, 0x02, 0x16, 0x62, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x12, 0x0e, 0x2f, 0x76, 0x31,
0x2f, 0x75, 0x73, 0x65, 0x72, 0x73, 0x2f, 0x7b, 0x69, 0x64, 0x7d, 0x12, 0x90, 0x01, 0x0a, 0x09,
0x4c, 0x69, 0x73, 0x74, 0x55, 0x73, 0x65, 0x72, 0x73, 0x12, 0x2c, 0x2e, 0x63, 0x6f, 0x6e, 0x74,
0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69,
0x63, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x55, 0x73, 0x65, 0x72, 0x73,
0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2d, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f,
0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65,
0x73, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x55, 0x73, 0x65, 0x72, 0x73, 0x52, 0x65,
0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x26, 0x92, 0x41, 0x12, 0x12, 0x10, 0x4c, 0x69, 0x73,
0x74, 0x73, 0x20, 0x61, 0x6c, 0x6c, 0x20, 0x55, 0x73, 0x65, 0x72, 0x73, 0x2e, 0x82, 0xd3, 0xe4,
0x93, 0x02, 0x0b, 0x12, 0x09, 0x2f, 0x76, 0x31, 0x2f, 0x75, 0x73, 0x65, 0x72, 0x73, 0x12, 0xa5,
0x01, 0x0a, 0x0a, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x55, 0x73, 0x65, 0x72, 0x12, 0x2d, 0x2e,
0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x73,
0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74,
0x65, 0x55, 0x73, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2e, 0x2e, 0x63,
0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x73, 0x65,
0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65,
0x55, 0x73, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x38, 0x92, 0x41,
0x18, 0x12, 0x16, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x73, 0x20, 0x61, 0x20, 0x73, 0x69, 0x6e,
0x67, 0x6c, 0x65, 0x20, 0x55, 0x73, 0x65, 0x72, 0x2e, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x17, 0x3a,
0x04, 0x69, 0x74, 0x65, 0x6d, 0x62, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x22, 0x09, 0x2f, 0x76, 0x31,
0x2f, 0x75, 0x73, 0x65, 0x72, 0x73, 0x12, 0xa3, 0x01, 0x0a, 0x0a, 0x55, 0x70, 0x64, 0x61, 0x74,
0x65, 0x55, 0x73, 0x65, 0x72, 0x12, 0x2d, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c,
0x74, 0x5f, 0x69, 0x64, 0x73, 0x22, 0x56, 0x0a, 0x17, 0x53, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72,
0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65,
0x12, 0x3b, 0x0a, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x27,
0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e,
0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x73, 0x2e,
0x76, 0x31, 0x2e, 0x55, 0x73, 0x65, 0x72, 0x52, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x22, 0x67, 0x0a,
0x19, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x55, 0x73, 0x65, 0x72, 0x41, 0x63, 0x63, 0x6f, 0x75,
0x6e, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64,
0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x18, 0x0a, 0x07, 0x76, 0x65,
0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x07, 0x76, 0x65, 0x72,
0x73, 0x69, 0x6f, 0x6e, 0x12, 0x20, 0x0a, 0x0b, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x5f,
0x69, 0x64, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0b, 0x61, 0x63, 0x63, 0x6f, 0x75,
0x6e, 0x74, 0x5f, 0x69, 0x64, 0x73, 0x22, 0x59, 0x0a, 0x1a, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65,
0x55, 0x73, 0x65, 0x72, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70,
0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3b, 0x0a, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x18, 0x01, 0x20, 0x01,
0x28, 0x0b, 0x32, 0x27, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e,
0x61, 0x70, 0x69, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x2e, 0x75, 0x73,
0x65, 0x72, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x73, 0x65, 0x72, 0x52, 0x04, 0x69, 0x74, 0x65,
0x6d, 0x22, 0x6c, 0x0a, 0x1c, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x73, 0x6f, 0x6c, 0x76, 0x61,
0x62, 0x6c, 0x65, 0x41, 0x6c, 0x69, 0x61, 0x73, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73,
0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69,
0x64, 0x12, 0x1e, 0x0a, 0x0a, 0x6c, 0x69, 0x73, 0x74, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18,
0x02, 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, 0x03,
0x20, 0x01, 0x28, 0x0d, 0x52, 0x09, 0x70, 0x61, 0x67, 0x65, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x22,
0xa7, 0x02, 0x0a, 0x1d, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x73, 0x6f, 0x6c, 0x76, 0x61, 0x62,
0x6c, 0x65, 0x41, 0x6c, 0x69, 0x61, 0x73, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73,
0x65, 0x12, 0x40, 0x0a, 0x05, 0x69, 0x74, 0x65, 0x6d, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b,
0x32, 0x2a, 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, 0x6c, 0x69, 0x61,
0x73, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x6c, 0x69, 0x61, 0x73, 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, 0x32, 0xfe, 0x10, 0x0a, 0x0b, 0x55, 0x73,
0x65, 0x72, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x98, 0x01, 0x0a, 0x07, 0x47, 0x65,
0x74, 0x55, 0x73, 0x65, 0x72, 0x12, 0x2a, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c,
0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e,
0x76, 0x31, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x55, 0x73, 0x65, 0x72, 0x52, 0x65, 0x71,
0x75, 0x65, 0x73, 0x74, 0x1a, 0x2e, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65,
0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73,
0x74, 0x1a, 0x2b, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61,
0x70, 0x69, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x47,
0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x34,
0x92, 0x41, 0x15, 0x12, 0x13, 0x47, 0x65, 0x74, 0x73, 0x20, 0x61, 0x20, 0x73, 0x69, 0x6e, 0x67,
0x6c, 0x65, 0x20, 0x55, 0x73, 0x65, 0x72, 0x2e, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x16, 0x62, 0x04,
0x69, 0x74, 0x65, 0x6d, 0x12, 0x0e, 0x2f, 0x76, 0x31, 0x2f, 0x75, 0x73, 0x65, 0x72, 0x73, 0x2f,
0x7b, 0x69, 0x64, 0x7d, 0x12, 0x90, 0x01, 0x0a, 0x09, 0x4c, 0x69, 0x73, 0x74, 0x55, 0x73, 0x65,
0x72, 0x73, 0x12, 0x2c, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e,
0x61, 0x70, 0x69, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e,
0x4c, 0x69, 0x73, 0x74, 0x55, 0x73, 0x65, 0x72, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74,
0x1a, 0x2d, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70,
0x69, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69,
0x73, 0x74, 0x55, 0x73, 0x65, 0x72, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22,
0x26, 0x92, 0x41, 0x12, 0x12, 0x10, 0x4c, 0x69, 0x73, 0x74, 0x73, 0x20, 0x61, 0x6c, 0x6c, 0x20,
0x55, 0x73, 0x65, 0x72, 0x73, 0x2e, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x0b, 0x12, 0x09, 0x2f, 0x76,
0x31, 0x2f, 0x75, 0x73, 0x65, 0x72, 0x73, 0x12, 0xa5, 0x01, 0x0a, 0x0a, 0x43, 0x72, 0x65, 0x61,
0x74, 0x65, 0x55, 0x73, 0x65, 0x72, 0x12, 0x2d, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c,
0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73,
0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x55, 0x73, 0x65, 0x72, 0x52, 0x65,
0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2e, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c,
0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e,
0x76, 0x31, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x55, 0x73, 0x65, 0x72, 0x52, 0x65, 0x73,
0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x38, 0x92, 0x41, 0x18, 0x12, 0x16, 0x43, 0x72, 0x65, 0x61,
0x74, 0x65, 0x73, 0x20, 0x61, 0x20, 0x73, 0x69, 0x6e, 0x67, 0x6c, 0x65, 0x20, 0x55, 0x73, 0x65,
0x72, 0x2e, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x17, 0x3a, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x62, 0x04,
0x69, 0x74, 0x65, 0x6d, 0x22, 0x09, 0x2f, 0x76, 0x31, 0x2f, 0x75, 0x73, 0x65, 0x72, 0x73, 0x12,
0xa3, 0x01, 0x0a, 0x0a, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x55, 0x73, 0x65, 0x72, 0x12, 0x2d,
0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e,
0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x70, 0x64, 0x61,
0x74, 0x65, 0x55, 0x73, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2e, 0x2e,
0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x73,
0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74,
0x65, 0x55, 0x73, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x36, 0x92,
0x41, 0x11, 0x12, 0x0f, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x73, 0x20, 0x61, 0x20, 0x55, 0x73,
0x65, 0x72, 0x2e, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x1c, 0x3a, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x62,
0x04, 0x69, 0x74, 0x65, 0x6d, 0x32, 0x0e, 0x2f, 0x76, 0x31, 0x2f, 0x75, 0x73, 0x65, 0x72, 0x73,
0x2f, 0x7b, 0x69, 0x64, 0x7d, 0x12, 0x97, 0x01, 0x0a, 0x0a, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65,
0x55, 0x73, 0x65, 0x72, 0x12, 0x2d, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65,
0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x76,
0x31, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x55, 0x73, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70,
0x6f, 0x6e, 0x73, 0x65, 0x22, 0x36, 0x92, 0x41, 0x11, 0x12, 0x0f, 0x55, 0x70, 0x64, 0x61, 0x74,
0x65, 0x73, 0x20, 0x61, 0x20, 0x55, 0x73, 0x65, 0x72, 0x2e, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x1c,
0x3a, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x62, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x32, 0x0e, 0x2f, 0x76,
0x31, 0x2f, 0x75, 0x73, 0x65, 0x72, 0x73, 0x2f, 0x7b, 0x69, 0x64, 0x7d, 0x12, 0x97, 0x01, 0x0a,
0x0a, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x55, 0x73, 0x65, 0x72, 0x12, 0x2d, 0x2e, 0x63, 0x6f,
0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x73, 0x65, 0x72,
0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x55,
0x73, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2e, 0x2e, 0x63, 0x6f, 0x6e,
0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x73, 0x65, 0x72, 0x76,
0x69, 0x63, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x55, 0x73,
0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x2a, 0x92, 0x41, 0x11, 0x12,
0x0f, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x73, 0x20, 0x61, 0x20, 0x55, 0x73, 0x65, 0x72, 0x2e,
0x82, 0xd3, 0xe4, 0x93, 0x02, 0x10, 0x2a, 0x0e, 0x2f, 0x76, 0x31, 0x2f, 0x75, 0x73, 0x65, 0x72,
0x73, 0x2f, 0x7b, 0x69, 0x64, 0x7d, 0x12, 0xcd, 0x01, 0x0a, 0x0f, 0x41, 0x64, 0x64, 0x55, 0x73,
0x65, 0x72, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x12, 0x32, 0x2e, 0x63, 0x6f, 0x6e,
0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x73, 0x65, 0x72, 0x76,
0x69, 0x63, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x64, 0x64, 0x55, 0x73, 0x65, 0x72, 0x41,
0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x33,
0x31, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x55, 0x73, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75,
0x65, 0x73, 0x74, 0x1a, 0x2e, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72,
0x2e, 0x61, 0x70, 0x69, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x76, 0x31,
0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x55, 0x73, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f,
0x6e, 0x73, 0x65, 0x22, 0x2a, 0x92, 0x41, 0x11, 0x12, 0x0f, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65,
0x73, 0x20, 0x61, 0x20, 0x55, 0x73, 0x65, 0x72, 0x2e, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x10, 0x2a,
0x0e, 0x2f, 0x76, 0x31, 0x2f, 0x75, 0x73, 0x65, 0x72, 0x73, 0x2f, 0x7b, 0x69, 0x64, 0x7d, 0x12,
0xcd, 0x01, 0x0a, 0x0f, 0x41, 0x64, 0x64, 0x55, 0x73, 0x65, 0x72, 0x41, 0x63, 0x63, 0x6f, 0x75,
0x6e, 0x74, 0x73, 0x12, 0x32, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72,
0x2e, 0x61, 0x70, 0x69, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x76, 0x31,
0x2e, 0x41, 0x64, 0x64, 0x55, 0x73, 0x65, 0x72, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73,
0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x33, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f,
0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65,
0x73, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x64, 0x64, 0x55, 0x73, 0x65, 0x72, 0x41, 0x63, 0x63, 0x6f,
0x75, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x51, 0x92, 0x41,
0x22, 0x12, 0x20, 0x41, 0x73, 0x73, 0x6f, 0x63, 0x69, 0x61, 0x74, 0x65, 0x73, 0x20, 0x61, 0x6e,
0x20, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x20, 0x74, 0x6f, 0x20, 0x61, 0x20, 0x55, 0x73,
0x65, 0x72, 0x2e, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x26, 0x3a, 0x01, 0x2a, 0x62, 0x04, 0x69, 0x74,
0x65, 0x6d, 0x22, 0x1b, 0x2f, 0x76, 0x31, 0x2f, 0x75, 0x73, 0x65, 0x72, 0x73, 0x2f, 0x7b, 0x69,
0x64, 0x7d, 0x3a, 0x61, 0x64, 0x64, 0x2d, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x12,
0xb5, 0x02, 0x0a, 0x0f, 0x53, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x41, 0x63, 0x63, 0x6f, 0x75,
0x6e, 0x74, 0x73, 0x12, 0x32, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72,
0x2e, 0x61, 0x70, 0x69, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x76, 0x31,
0x2e, 0x53, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73,
0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x33, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f,
0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65,
0x73, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x41, 0x63, 0x63, 0x6f,
0x75, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0xb8, 0x01, 0x92,
0x41, 0x88, 0x01, 0x12, 0x85, 0x01, 0x53, 0x65, 0x74, 0x20, 0x74, 0x68, 0x65, 0x20, 0x41, 0x63,
0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x20, 0x61, 0x73, 0x73, 0x6f, 0x63, 0x69, 0x61, 0x74, 0x65,
0x64, 0x20, 0x74, 0x6f, 0x20, 0x74, 0x68, 0x65, 0x20, 0x55, 0x73, 0x65, 0x72, 0x20, 0x74, 0x6f,
0x20, 0x65, 0x78, 0x61, 0x63, 0x74, 0x6c, 0x79, 0x20, 0x74, 0x68, 0x65, 0x20, 0x6c, 0x69, 0x73,
0x74, 0x20, 0x6f, 0x66, 0x20, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x64, 0x20, 0x69, 0x6e,
0x20, 0x74, 0x68, 0x65, 0x20, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2c, 0x20, 0x72, 0x65,
0x6d, 0x6f, 0x76, 0x69, 0x6e, 0x67, 0x20, 0x61, 0x6e, 0x79, 0x20, 0x41, 0x63, 0x63, 0x6f, 0x75,
0x6e, 0x74, 0x73, 0x20, 0x74, 0x68, 0x61, 0x74, 0x20, 0x61, 0x72, 0x65, 0x20, 0x6e, 0x6f, 0x74,
0x20, 0x73, 0x70, 0x65, 0x63, 0x69, 0x66, 0x69, 0x65, 0x64, 0x2e, 0x82, 0xd3, 0xe4, 0x93, 0x02,
0x26, 0x3a, 0x01, 0x2a, 0x62, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x22, 0x1b, 0x2f, 0x76, 0x31, 0x2f,
0x75, 0x73, 0x65, 0x72, 0x73, 0x2f, 0x7b, 0x69, 0x64, 0x7d, 0x3a, 0x73, 0x65, 0x74, 0x2d, 0x61,
0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x12, 0x86, 0x02, 0x0a, 0x12, 0x52, 0x65, 0x6d, 0x6f,
0x76, 0x65, 0x55, 0x73, 0x65, 0x72, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x12, 0x35,
0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e,
0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x64, 0x64, 0x55,
0x73, 0x65, 0x72, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f,
0x6e, 0x73, 0x65, 0x22, 0x51, 0x92, 0x41, 0x22, 0x12, 0x20, 0x41, 0x73, 0x73, 0x6f, 0x63, 0x69,
0x61, 0x74, 0x65, 0x73, 0x20, 0x61, 0x6e, 0x20, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x20,
0x74, 0x6f, 0x20, 0x61, 0x20, 0x55, 0x73, 0x65, 0x72, 0x2e, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x26,
0x3a, 0x01, 0x2a, 0x62, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x22, 0x1b, 0x2f, 0x76, 0x31, 0x2f, 0x75,
0x73, 0x65, 0x72, 0x73, 0x2f, 0x7b, 0x69, 0x64, 0x7d, 0x3a, 0x61, 0x64, 0x64, 0x2d, 0x61, 0x63,
0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x12, 0xb5, 0x02, 0x0a, 0x0f, 0x53, 0x65, 0x74, 0x55, 0x73,
0x65, 0x72, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x12, 0x32, 0x2e, 0x63, 0x6f, 0x6e,
0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x73, 0x65, 0x72, 0x76,
0x69, 0x63, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x41,
0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x33,
0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e,
0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65, 0x74, 0x55,
0x73, 0x65, 0x72, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f,
0x6e, 0x73, 0x65, 0x22, 0xb8, 0x01, 0x92, 0x41, 0x88, 0x01, 0x12, 0x85, 0x01, 0x53, 0x65, 0x74,
0x20, 0x74, 0x68, 0x65, 0x20, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x20, 0x61, 0x73,
0x73, 0x6f, 0x63, 0x69, 0x61, 0x74, 0x65, 0x64, 0x20, 0x74, 0x6f, 0x20, 0x74, 0x68, 0x65, 0x20,
0x55, 0x73, 0x65, 0x72, 0x20, 0x74, 0x6f, 0x20, 0x65, 0x78, 0x61, 0x63, 0x74, 0x6c, 0x79, 0x20,
0x74, 0x68, 0x65, 0x20, 0x6c, 0x69, 0x73, 0x74, 0x20, 0x6f, 0x66, 0x20, 0x70, 0x72, 0x6f, 0x76,
0x69, 0x64, 0x65, 0x64, 0x20, 0x69, 0x6e, 0x20, 0x74, 0x68, 0x65, 0x20, 0x72, 0x65, 0x71, 0x75,
0x65, 0x73, 0x74, 0x2c, 0x20, 0x72, 0x65, 0x6d, 0x6f, 0x76, 0x69, 0x6e, 0x67, 0x20, 0x61, 0x6e,
0x79, 0x20, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x20, 0x74, 0x68, 0x61, 0x74, 0x20,
0x61, 0x72, 0x65, 0x20, 0x6e, 0x6f, 0x74, 0x20, 0x73, 0x70, 0x65, 0x63, 0x69, 0x66, 0x69, 0x65,
0x64, 0x2e, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x26, 0x3a, 0x01, 0x2a, 0x62, 0x04, 0x69, 0x74, 0x65,
0x6d, 0x22, 0x1b, 0x2f, 0x76, 0x31, 0x2f, 0x75, 0x73, 0x65, 0x72, 0x73, 0x2f, 0x7b, 0x69, 0x64,
0x7d, 0x3a, 0x73, 0x65, 0x74, 0x2d, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x12, 0x86,
0x02, 0x0a, 0x12, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x55, 0x73, 0x65, 0x72, 0x41, 0x63, 0x63,
0x6f, 0x75, 0x6e, 0x74, 0x73, 0x12, 0x35, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c,
0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x6d, 0x6f,
0x76, 0x65, 0x55, 0x73, 0x65, 0x72, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x52, 0x65,
0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x36, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c,
0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e,
0x76, 0x31, 0x2e, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x55, 0x73, 0x65, 0x72, 0x41, 0x63, 0x63,
0x6f, 0x75, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x36, 0x2e, 0x63,
0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x73, 0x65,
0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65,
0x55, 0x73, 0x65, 0x72, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70,
0x6f, 0x6e, 0x73, 0x65, 0x22, 0x80, 0x01, 0x92, 0x41, 0x4e, 0x12, 0x4c, 0x52, 0x65, 0x6d, 0x6f,
0x76, 0x65, 0x73, 0x20, 0x74, 0x68, 0x65, 0x20, 0x73, 0x70, 0x65, 0x63, 0x69, 0x66, 0x69, 0x65,
0x64, 0x20, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x20, 0x66, 0x72, 0x6f, 0x6d, 0x20,
0x62, 0x65, 0x69, 0x6e, 0x67, 0x20, 0x61, 0x73, 0x73, 0x6f, 0x63, 0x69, 0x61, 0x74, 0x65, 0x64,
0x20, 0x77, 0x69, 0x74, 0x68, 0x20, 0x74, 0x68, 0x65, 0x20, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64,
0x65, 0x64, 0x20, 0x55, 0x73, 0x65, 0x72, 0x2e, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x29, 0x3a, 0x01,
0x2a, 0x62, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x22, 0x1e, 0x2f, 0x76, 0x31, 0x2f, 0x75, 0x73, 0x65,
0x72, 0x73, 0x2f, 0x7b, 0x69, 0x64, 0x7d, 0x3a, 0x72, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x2d, 0x61,
0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x1a, 0xaa, 0x02, 0x92, 0x41, 0xa6, 0x02, 0x0a, 0x0c,
0x55, 0x73, 0x65, 0x72, 0x20, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x99, 0x01, 0x41,
0x20, 0x75, 0x73, 0x65, 0x72, 0x20, 0x63, 0x61, 0x6e, 0x20, 0x62, 0x65, 0x20, 0x61, 0x20, 0x68,
0x75, 0x6d, 0x61, 0x6e, 0x20, 0x69, 0x6e, 0x64, 0x69, 0x76, 0x69, 0x64, 0x75, 0x61, 0x6c, 0x20,
0x6f, 0x72, 0x20, 0x61, 0x20, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x20, 0x61, 0x63, 0x63,
0x6f, 0x75, 0x6e, 0x74, 0x20, 0x74, 0x68, 0x61, 0x74, 0x20, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73,
0x65, 0x73, 0x20, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x2e, 0x20, 0x54, 0x68,
0x65, 0x20, 0x75, 0x73, 0x65, 0x72, 0x20, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x20, 0x70,
0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x73, 0x20, 0x65, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74,
0x73, 0x20, 0x74, 0x68, 0x61, 0x74, 0x20, 0x6c, 0x65, 0x74, 0x20, 0x79, 0x6f, 0x75, 0x20, 0x6d,
0x61, 0x6e, 0x61, 0x67, 0x65, 0x20, 0x75, 0x73, 0x65, 0x72, 0x73, 0x20, 0x69, 0x6e, 0x20, 0x42,
0x6f, 0x75, 0x6e, 0x64, 0x61, 0x72, 0x79, 0x2e, 0x1a, 0x7a, 0x0a, 0x2d, 0x52, 0x65, 0x61, 0x64,
0x20, 0x61, 0x62, 0x6f, 0x75, 0x74, 0x20, 0x75, 0x73, 0x65, 0x72, 0x73, 0x20, 0x69, 0x6e, 0x20,
0x74, 0x68, 0x65, 0x20, 0x42, 0x6f, 0x75, 0x6e, 0x64, 0x61, 0x72, 0x79, 0x20, 0x64, 0x6f, 0x6d,
0x61, 0x69, 0x6e, 0x20, 0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x12, 0x49, 0x68, 0x74, 0x74, 0x70, 0x73,
0x3a, 0x2f, 0x2f, 0x64, 0x65, 0x76, 0x65, 0x6c, 0x6f, 0x70, 0x65, 0x72, 0x2e, 0x68, 0x61, 0x73,
0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x62, 0x6f, 0x75, 0x6e, 0x64,
0x61, 0x72, 0x79, 0x2f, 0x64, 0x6f, 0x63, 0x73, 0x2f, 0x63, 0x6f, 0x6e, 0x63, 0x65, 0x70, 0x74,
0x73, 0x2f, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x2d, 0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x2f, 0x75,
0x73, 0x65, 0x72, 0x73, 0x42, 0x4d, 0x5a, 0x4b, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63,
0x6f, 0x6d, 0x2f, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2f, 0x62, 0x6f, 0x75,
0x6e, 0x64, 0x61, 0x72, 0x79, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2f, 0x67,
0x65, 0x6e, 0x2f, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2f, 0x61, 0x70,
0x69, 0x2f, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x3b, 0x73, 0x65, 0x72, 0x76, 0x69,
0x63, 0x65, 0x73, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
0x6f, 0x75, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x80, 0x01,
0x92, 0x41, 0x4e, 0x12, 0x4c, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x73, 0x20, 0x74, 0x68, 0x65,
0x20, 0x73, 0x70, 0x65, 0x63, 0x69, 0x66, 0x69, 0x65, 0x64, 0x20, 0x41, 0x63, 0x63, 0x6f, 0x75,
0x6e, 0x74, 0x73, 0x20, 0x66, 0x72, 0x6f, 0x6d, 0x20, 0x62, 0x65, 0x69, 0x6e, 0x67, 0x20, 0x61,
0x73, 0x73, 0x6f, 0x63, 0x69, 0x61, 0x74, 0x65, 0x64, 0x20, 0x77, 0x69, 0x74, 0x68, 0x20, 0x74,
0x68, 0x65, 0x20, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x64, 0x20, 0x55, 0x73, 0x65, 0x72,
0x2e, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x29, 0x3a, 0x01, 0x2a, 0x62, 0x04, 0x69, 0x74, 0x65, 0x6d,
0x22, 0x1e, 0x2f, 0x76, 0x31, 0x2f, 0x75, 0x73, 0x65, 0x72, 0x73, 0x2f, 0x7b, 0x69, 0x64, 0x7d,
0x3a, 0x72, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x2d, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73,
0x12, 0x9a, 0x02, 0x0a, 0x15, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x73, 0x6f, 0x6c, 0x76, 0x61,
0x62, 0x6c, 0x65, 0x41, 0x6c, 0x69, 0x61, 0x73, 0x65, 0x73, 0x12, 0x38, 0x2e, 0x63, 0x6f, 0x6e,
0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x73, 0x65, 0x72, 0x76,
0x69, 0x63, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x73, 0x6f,
0x6c, 0x76, 0x61, 0x62, 0x6c, 0x65, 0x41, 0x6c, 0x69, 0x61, 0x73, 0x65, 0x73, 0x52, 0x65, 0x71,
0x75, 0x65, 0x73, 0x74, 0x1a, 0x39, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65,
0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x76,
0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x73, 0x6f, 0x6c, 0x76, 0x61, 0x62, 0x6c, 0x65,
0x41, 0x6c, 0x69, 0x61, 0x73, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22,
0x8b, 0x01, 0x92, 0x41, 0x5a, 0x12, 0x58, 0x4c, 0x69, 0x73, 0x74, 0x73, 0x20, 0x61, 0x6c, 0x6c,
0x20, 0x41, 0x6c, 0x69, 0x61, 0x73, 0x65, 0x73, 0x20, 0x77, 0x68, 0x69, 0x63, 0x68, 0x20, 0x70,
0x6f, 0x69, 0x6e, 0x74, 0x20, 0x74, 0x6f, 0x20, 0x61, 0x20, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72,
0x63, 0x65, 0x20, 0x66, 0x6f, 0x72, 0x20, 0x77, 0x68, 0x69, 0x63, 0x68, 0x20, 0x74, 0x68, 0x65,
0x20, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x65, 0x72, 0x20, 0x68, 0x61, 0x73, 0x20, 0x73,
0x6f, 0x6d, 0x65, 0x20, 0x70, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x2e, 0x82,
0xd3, 0xe4, 0x93, 0x02, 0x28, 0x12, 0x26, 0x2f, 0x76, 0x31, 0x2f, 0x75, 0x73, 0x65, 0x72, 0x73,
0x2f, 0x7b, 0x69, 0x64, 0x7d, 0x3a, 0x6c, 0x69, 0x73, 0x74, 0x2d, 0x72, 0x65, 0x73, 0x6f, 0x6c,
0x76, 0x61, 0x62, 0x6c, 0x65, 0x2d, 0x61, 0x6c, 0x69, 0x61, 0x73, 0x65, 0x73, 0x1a, 0xaa, 0x02,
0x92, 0x41, 0xa6, 0x02, 0x0a, 0x0c, 0x55, 0x73, 0x65, 0x72, 0x20, 0x73, 0x65, 0x72, 0x76, 0x69,
0x63, 0x65, 0x12, 0x99, 0x01, 0x41, 0x20, 0x75, 0x73, 0x65, 0x72, 0x20, 0x63, 0x61, 0x6e, 0x20,
0x62, 0x65, 0x20, 0x61, 0x20, 0x68, 0x75, 0x6d, 0x61, 0x6e, 0x20, 0x69, 0x6e, 0x64, 0x69, 0x76,
0x69, 0x64, 0x75, 0x61, 0x6c, 0x20, 0x6f, 0x72, 0x20, 0x61, 0x20, 0x73, 0x65, 0x72, 0x76, 0x69,
0x63, 0x65, 0x20, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x20, 0x74, 0x68, 0x61, 0x74, 0x20,
0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x65, 0x73, 0x20, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63,
0x65, 0x73, 0x2e, 0x20, 0x54, 0x68, 0x65, 0x20, 0x75, 0x73, 0x65, 0x72, 0x20, 0x73, 0x65, 0x72,
0x76, 0x69, 0x63, 0x65, 0x20, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x73, 0x20, 0x65, 0x6e,
0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x73, 0x20, 0x74, 0x68, 0x61, 0x74, 0x20, 0x6c, 0x65, 0x74,
0x20, 0x79, 0x6f, 0x75, 0x20, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x20, 0x75, 0x73, 0x65, 0x72,
0x73, 0x20, 0x69, 0x6e, 0x20, 0x42, 0x6f, 0x75, 0x6e, 0x64, 0x61, 0x72, 0x79, 0x2e, 0x1a, 0x7a,
0x0a, 0x2d, 0x52, 0x65, 0x61, 0x64, 0x20, 0x61, 0x62, 0x6f, 0x75, 0x74, 0x20, 0x75, 0x73, 0x65,
0x72, 0x73, 0x20, 0x69, 0x6e, 0x20, 0x74, 0x68, 0x65, 0x20, 0x42, 0x6f, 0x75, 0x6e, 0x64, 0x61,
0x72, 0x79, 0x20, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x20, 0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x12,
0x49, 0x68, 0x74, 0x74, 0x70, 0x73, 0x3a, 0x2f, 0x2f, 0x64, 0x65, 0x76, 0x65, 0x6c, 0x6f, 0x70,
0x65, 0x72, 0x2e, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2e, 0x63, 0x6f, 0x6d,
0x2f, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x61, 0x72, 0x79, 0x2f, 0x64, 0x6f, 0x63, 0x73, 0x2f, 0x63,
0x6f, 0x6e, 0x63, 0x65, 0x70, 0x74, 0x73, 0x2f, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x2d, 0x6d,
0x6f, 0x64, 0x65, 0x6c, 0x2f, 0x75, 0x73, 0x65, 0x72, 0x73, 0x42, 0x4d, 0x5a, 0x4b, 0x67, 0x69,
0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f,
0x72, 0x70, 0x2f, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x61, 0x72, 0x79, 0x2f, 0x69, 0x6e, 0x74, 0x65,
0x72, 0x6e, 0x61, 0x6c, 0x2f, 0x67, 0x65, 0x6e, 0x2f, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c,
0x6c, 0x65, 0x72, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73,
0x3b, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f,
0x33,
}
var (
@@ -1204,59 +1431,65 @@ func file_controller_api_services_v1_user_service_proto_rawDescGZIP() []byte {
return file_controller_api_services_v1_user_service_proto_rawDescData
}
var file_controller_api_services_v1_user_service_proto_msgTypes = make([]protoimpl.MessageInfo, 16)
var file_controller_api_services_v1_user_service_proto_msgTypes = make([]protoimpl.MessageInfo, 18)
var file_controller_api_services_v1_user_service_proto_goTypes = []interface{}{
(*GetUserRequest)(nil), // 0: controller.api.services.v1.GetUserRequest
(*GetUserResponse)(nil), // 1: controller.api.services.v1.GetUserResponse
(*ListUsersRequest)(nil), // 2: controller.api.services.v1.ListUsersRequest
(*ListUsersResponse)(nil), // 3: controller.api.services.v1.ListUsersResponse
(*CreateUserRequest)(nil), // 4: controller.api.services.v1.CreateUserRequest
(*CreateUserResponse)(nil), // 5: controller.api.services.v1.CreateUserResponse
(*UpdateUserRequest)(nil), // 6: controller.api.services.v1.UpdateUserRequest
(*UpdateUserResponse)(nil), // 7: controller.api.services.v1.UpdateUserResponse
(*DeleteUserRequest)(nil), // 8: controller.api.services.v1.DeleteUserRequest
(*DeleteUserResponse)(nil), // 9: controller.api.services.v1.DeleteUserResponse
(*AddUserAccountsRequest)(nil), // 10: controller.api.services.v1.AddUserAccountsRequest
(*AddUserAccountsResponse)(nil), // 11: controller.api.services.v1.AddUserAccountsResponse
(*SetUserAccountsRequest)(nil), // 12: controller.api.services.v1.SetUserAccountsRequest
(*SetUserAccountsResponse)(nil), // 13: controller.api.services.v1.SetUserAccountsResponse
(*RemoveUserAccountsRequest)(nil), // 14: controller.api.services.v1.RemoveUserAccountsRequest
(*RemoveUserAccountsResponse)(nil), // 15: controller.api.services.v1.RemoveUserAccountsResponse
(*users.User)(nil), // 16: controller.api.resources.users.v1.User
(*fieldmaskpb.FieldMask)(nil), // 17: google.protobuf.FieldMask
(*GetUserRequest)(nil), // 0: controller.api.services.v1.GetUserRequest
(*GetUserResponse)(nil), // 1: controller.api.services.v1.GetUserResponse
(*ListUsersRequest)(nil), // 2: controller.api.services.v1.ListUsersRequest
(*ListUsersResponse)(nil), // 3: controller.api.services.v1.ListUsersResponse
(*CreateUserRequest)(nil), // 4: controller.api.services.v1.CreateUserRequest
(*CreateUserResponse)(nil), // 5: controller.api.services.v1.CreateUserResponse
(*UpdateUserRequest)(nil), // 6: controller.api.services.v1.UpdateUserRequest
(*UpdateUserResponse)(nil), // 7: controller.api.services.v1.UpdateUserResponse
(*DeleteUserRequest)(nil), // 8: controller.api.services.v1.DeleteUserRequest
(*DeleteUserResponse)(nil), // 9: controller.api.services.v1.DeleteUserResponse
(*AddUserAccountsRequest)(nil), // 10: controller.api.services.v1.AddUserAccountsRequest
(*AddUserAccountsResponse)(nil), // 11: controller.api.services.v1.AddUserAccountsResponse
(*SetUserAccountsRequest)(nil), // 12: controller.api.services.v1.SetUserAccountsRequest
(*SetUserAccountsResponse)(nil), // 13: controller.api.services.v1.SetUserAccountsResponse
(*RemoveUserAccountsRequest)(nil), // 14: controller.api.services.v1.RemoveUserAccountsRequest
(*RemoveUserAccountsResponse)(nil), // 15: controller.api.services.v1.RemoveUserAccountsResponse
(*ListResolvableAliasesRequest)(nil), // 16: controller.api.services.v1.ListResolvableAliasesRequest
(*ListResolvableAliasesResponse)(nil), // 17: controller.api.services.v1.ListResolvableAliasesResponse
(*users.User)(nil), // 18: controller.api.resources.users.v1.User
(*fieldmaskpb.FieldMask)(nil), // 19: google.protobuf.FieldMask
(*aliases.Alias)(nil), // 20: controller.api.resources.aliases.v1.Alias
}
var file_controller_api_services_v1_user_service_proto_depIdxs = []int32{
16, // 0: controller.api.services.v1.GetUserResponse.item:type_name -> controller.api.resources.users.v1.User
16, // 1: controller.api.services.v1.ListUsersResponse.items:type_name -> controller.api.resources.users.v1.User
16, // 2: controller.api.services.v1.CreateUserRequest.item:type_name -> controller.api.resources.users.v1.User
16, // 3: controller.api.services.v1.CreateUserResponse.item:type_name -> controller.api.resources.users.v1.User
16, // 4: controller.api.services.v1.UpdateUserRequest.item:type_name -> controller.api.resources.users.v1.User
17, // 5: controller.api.services.v1.UpdateUserRequest.update_mask:type_name -> google.protobuf.FieldMask
16, // 6: controller.api.services.v1.UpdateUserResponse.item:type_name -> controller.api.resources.users.v1.User
16, // 7: controller.api.services.v1.AddUserAccountsResponse.item:type_name -> controller.api.resources.users.v1.User
16, // 8: controller.api.services.v1.SetUserAccountsResponse.item:type_name -> controller.api.resources.users.v1.User
16, // 9: controller.api.services.v1.RemoveUserAccountsResponse.item:type_name -> controller.api.resources.users.v1.User
0, // 10: controller.api.services.v1.UserService.GetUser:input_type -> controller.api.services.v1.GetUserRequest
2, // 11: controller.api.services.v1.UserService.ListUsers:input_type -> controller.api.services.v1.ListUsersRequest
4, // 12: controller.api.services.v1.UserService.CreateUser:input_type -> controller.api.services.v1.CreateUserRequest
6, // 13: controller.api.services.v1.UserService.UpdateUser:input_type -> controller.api.services.v1.UpdateUserRequest
8, // 14: controller.api.services.v1.UserService.DeleteUser:input_type -> controller.api.services.v1.DeleteUserRequest
10, // 15: controller.api.services.v1.UserService.AddUserAccounts:input_type -> controller.api.services.v1.AddUserAccountsRequest
12, // 16: controller.api.services.v1.UserService.SetUserAccounts:input_type -> controller.api.services.v1.SetUserAccountsRequest
14, // 17: controller.api.services.v1.UserService.RemoveUserAccounts:input_type -> controller.api.services.v1.RemoveUserAccountsRequest
1, // 18: controller.api.services.v1.UserService.GetUser:output_type -> controller.api.services.v1.GetUserResponse
3, // 19: controller.api.services.v1.UserService.ListUsers:output_type -> controller.api.services.v1.ListUsersResponse
5, // 20: controller.api.services.v1.UserService.CreateUser:output_type -> controller.api.services.v1.CreateUserResponse
7, // 21: controller.api.services.v1.UserService.UpdateUser:output_type -> controller.api.services.v1.UpdateUserResponse
9, // 22: controller.api.services.v1.UserService.DeleteUser:output_type -> controller.api.services.v1.DeleteUserResponse
11, // 23: controller.api.services.v1.UserService.AddUserAccounts:output_type -> controller.api.services.v1.AddUserAccountsResponse
13, // 24: controller.api.services.v1.UserService.SetUserAccounts:output_type -> controller.api.services.v1.SetUserAccountsResponse
15, // 25: controller.api.services.v1.UserService.RemoveUserAccounts:output_type -> controller.api.services.v1.RemoveUserAccountsResponse
18, // [18:26] is the sub-list for method output_type
10, // [10:18] is the sub-list for method input_type
10, // [10:10] is the sub-list for extension type_name
10, // [10:10] is the sub-list for extension extendee
0, // [0:10] is the sub-list for field type_name
18, // 0: controller.api.services.v1.GetUserResponse.item:type_name -> controller.api.resources.users.v1.User
18, // 1: controller.api.services.v1.ListUsersResponse.items:type_name -> controller.api.resources.users.v1.User
18, // 2: controller.api.services.v1.CreateUserRequest.item:type_name -> controller.api.resources.users.v1.User
18, // 3: controller.api.services.v1.CreateUserResponse.item:type_name -> controller.api.resources.users.v1.User
18, // 4: controller.api.services.v1.UpdateUserRequest.item:type_name -> controller.api.resources.users.v1.User
19, // 5: controller.api.services.v1.UpdateUserRequest.update_mask:type_name -> google.protobuf.FieldMask
18, // 6: controller.api.services.v1.UpdateUserResponse.item:type_name -> controller.api.resources.users.v1.User
18, // 7: controller.api.services.v1.AddUserAccountsResponse.item:type_name -> controller.api.resources.users.v1.User
18, // 8: controller.api.services.v1.SetUserAccountsResponse.item:type_name -> controller.api.resources.users.v1.User
18, // 9: controller.api.services.v1.RemoveUserAccountsResponse.item:type_name -> controller.api.resources.users.v1.User
20, // 10: controller.api.services.v1.ListResolvableAliasesResponse.items:type_name -> controller.api.resources.aliases.v1.Alias
0, // 11: controller.api.services.v1.UserService.GetUser:input_type -> controller.api.services.v1.GetUserRequest
2, // 12: controller.api.services.v1.UserService.ListUsers:input_type -> controller.api.services.v1.ListUsersRequest
4, // 13: controller.api.services.v1.UserService.CreateUser:input_type -> controller.api.services.v1.CreateUserRequest
6, // 14: controller.api.services.v1.UserService.UpdateUser:input_type -> controller.api.services.v1.UpdateUserRequest
8, // 15: controller.api.services.v1.UserService.DeleteUser:input_type -> controller.api.services.v1.DeleteUserRequest
10, // 16: controller.api.services.v1.UserService.AddUserAccounts:input_type -> controller.api.services.v1.AddUserAccountsRequest
12, // 17: controller.api.services.v1.UserService.SetUserAccounts:input_type -> controller.api.services.v1.SetUserAccountsRequest
14, // 18: controller.api.services.v1.UserService.RemoveUserAccounts:input_type -> controller.api.services.v1.RemoveUserAccountsRequest
16, // 19: controller.api.services.v1.UserService.ListResolvableAliases:input_type -> controller.api.services.v1.ListResolvableAliasesRequest
1, // 20: controller.api.services.v1.UserService.GetUser:output_type -> controller.api.services.v1.GetUserResponse
3, // 21: controller.api.services.v1.UserService.ListUsers:output_type -> controller.api.services.v1.ListUsersResponse
5, // 22: controller.api.services.v1.UserService.CreateUser:output_type -> controller.api.services.v1.CreateUserResponse
7, // 23: controller.api.services.v1.UserService.UpdateUser:output_type -> controller.api.services.v1.UpdateUserResponse
9, // 24: controller.api.services.v1.UserService.DeleteUser:output_type -> controller.api.services.v1.DeleteUserResponse
11, // 25: controller.api.services.v1.UserService.AddUserAccounts:output_type -> controller.api.services.v1.AddUserAccountsResponse
13, // 26: controller.api.services.v1.UserService.SetUserAccounts:output_type -> controller.api.services.v1.SetUserAccountsResponse
15, // 27: controller.api.services.v1.UserService.RemoveUserAccounts:output_type -> controller.api.services.v1.RemoveUserAccountsResponse
17, // 28: controller.api.services.v1.UserService.ListResolvableAliases:output_type -> controller.api.services.v1.ListResolvableAliasesResponse
20, // [20:29] is the sub-list for method output_type
11, // [11:20] is the sub-list for method input_type
11, // [11:11] is the sub-list for extension type_name
11, // [11:11] is the sub-list for extension extendee
0, // [0:11] is the sub-list for field type_name
}
func init() { file_controller_api_services_v1_user_service_proto_init() }
@@ -1457,6 +1690,30 @@ func file_controller_api_services_v1_user_service_proto_init() {
return nil
}
}
file_controller_api_services_v1_user_service_proto_msgTypes[16].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*ListResolvableAliasesRequest); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_controller_api_services_v1_user_service_proto_msgTypes[17].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*ListResolvableAliasesResponse); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
}
type x struct{}
out := protoimpl.TypeBuilder{
@@ -1464,7 +1721,7 @@ func file_controller_api_services_v1_user_service_proto_init() {
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: file_controller_api_services_v1_user_service_proto_rawDesc,
NumEnums: 0,
NumMessages: 16,
NumMessages: 18,
NumExtensions: 0,
NumServices: 1,
},
@@ -477,6 +477,76 @@ func local_request_UserService_RemoveUserAccounts_0(ctx context.Context, marshal
}
var (
filter_UserService_ListResolvableAliases_0 = &utilities.DoubleArray{Encoding: map[string]int{"id": 0}, Base: []int{1, 1, 0}, Check: []int{0, 1, 2}}
)
func request_UserService_ListResolvableAliases_0(ctx context.Context, marshaler runtime.Marshaler, client UserServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq ListResolvableAliasesRequest
var metadata runtime.ServerMetadata
var (
val string
ok bool
err error
_ = err
)
val, ok = pathParams["id"]
if !ok {
return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "id")
}
protoReq.Id, err = runtime.String(val)
if err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "id", err)
}
if err := req.ParseForm(); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_UserService_ListResolvableAliases_0); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := client.ListResolvableAliases(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
return msg, metadata, err
}
func local_request_UserService_ListResolvableAliases_0(ctx context.Context, marshaler runtime.Marshaler, server UserServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq ListResolvableAliasesRequest
var metadata runtime.ServerMetadata
var (
val string
ok bool
err error
_ = err
)
val, ok = pathParams["id"]
if !ok {
return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "id")
}
protoReq.Id, err = runtime.String(val)
if err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "id", err)
}
if err := req.ParseForm(); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_UserService_ListResolvableAliases_0); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := server.ListResolvableAliases(ctx, &protoReq)
return msg, metadata, err
}
// RegisterUserServiceHandlerServer registers the http handlers for service UserService to "mux".
// UnaryRPC :call UserServiceServer directly.
// StreamingRPC :currently unsupported pending https://github.com/grpc/grpc-go/issues/906.
@@ -683,6 +753,31 @@ func RegisterUserServiceHandlerServer(ctx context.Context, mux *runtime.ServeMux
})
mux.Handle("GET", pattern_UserService_ListResolvableAliases_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
var stream runtime.ServerTransportStream
ctx = grpc.NewContextWithServerTransportStream(ctx, &stream)
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/controller.api.services.v1.UserService/ListResolvableAliases", runtime.WithHTTPPathPattern("/v1/users/{id}:list-resolvable-aliases"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := local_request_UserService_ListResolvableAliases_0(annotatedContext, inboundMarshaler, server, req, pathParams)
md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_UserService_ListResolvableAliases_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
return nil
}
@@ -900,6 +995,28 @@ func RegisterUserServiceHandlerClient(ctx context.Context, mux *runtime.ServeMux
})
mux.Handle("GET", pattern_UserService_ListResolvableAliases_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/controller.api.services.v1.UserService/ListResolvableAliases", runtime.WithHTTPPathPattern("/v1/users/{id}:list-resolvable-aliases"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := request_UserService_ListResolvableAliases_0(annotatedContext, inboundMarshaler, client, req, pathParams)
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_UserService_ListResolvableAliases_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
return nil
}
@@ -973,6 +1090,8 @@ var (
pattern_UserService_SetUserAccounts_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 1, 0, 4, 1, 5, 2}, []string{"v1", "users", "id"}, "set-accounts"))
pattern_UserService_RemoveUserAccounts_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 1, 0, 4, 1, 5, 2}, []string{"v1", "users", "id"}, "remove-accounts"))
pattern_UserService_ListResolvableAliases_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 1, 0, 4, 1, 5, 2}, []string{"v1", "users", "id"}, "list-resolvable-aliases"))
)
var (
@@ -991,4 +1110,6 @@ var (
forward_UserService_SetUserAccounts_0 = runtime.ForwardResponseMessage
forward_UserService_RemoveUserAccounts_0 = runtime.ForwardResponseMessage
forward_UserService_ListResolvableAliases_0 = runtime.ForwardResponseMessage
)
@@ -22,14 +22,15 @@ import (
const _ = grpc.SupportPackageIsVersion7
const (
UserService_GetUser_FullMethodName = "/controller.api.services.v1.UserService/GetUser"
UserService_ListUsers_FullMethodName = "/controller.api.services.v1.UserService/ListUsers"
UserService_CreateUser_FullMethodName = "/controller.api.services.v1.UserService/CreateUser"
UserService_UpdateUser_FullMethodName = "/controller.api.services.v1.UserService/UpdateUser"
UserService_DeleteUser_FullMethodName = "/controller.api.services.v1.UserService/DeleteUser"
UserService_AddUserAccounts_FullMethodName = "/controller.api.services.v1.UserService/AddUserAccounts"
UserService_SetUserAccounts_FullMethodName = "/controller.api.services.v1.UserService/SetUserAccounts"
UserService_RemoveUserAccounts_FullMethodName = "/controller.api.services.v1.UserService/RemoveUserAccounts"
UserService_GetUser_FullMethodName = "/controller.api.services.v1.UserService/GetUser"
UserService_ListUsers_FullMethodName = "/controller.api.services.v1.UserService/ListUsers"
UserService_CreateUser_FullMethodName = "/controller.api.services.v1.UserService/CreateUser"
UserService_UpdateUser_FullMethodName = "/controller.api.services.v1.UserService/UpdateUser"
UserService_DeleteUser_FullMethodName = "/controller.api.services.v1.UserService/DeleteUser"
UserService_AddUserAccounts_FullMethodName = "/controller.api.services.v1.UserService/AddUserAccounts"
UserService_SetUserAccounts_FullMethodName = "/controller.api.services.v1.UserService/SetUserAccounts"
UserService_RemoveUserAccounts_FullMethodName = "/controller.api.services.v1.UserService/RemoveUserAccounts"
UserService_ListResolvableAliases_FullMethodName = "/controller.api.services.v1.UserService/ListResolvableAliases"
)
// UserServiceClient is the client API for UserService service.
@@ -84,6 +85,10 @@ type UserServiceClient interface {
// will be removed from. If the provided Account ids is not associated with the
// provided User, an error is returned.
RemoveUserAccounts(ctx context.Context, in *RemoveUserAccountsRequest, opts ...grpc.CallOption) (*RemoveUserAccountsResponse, error)
// ListResolvableAliases returns a list of Aliases which point to a resource
// for which the provided user id has some permission.
// If missing or malformed an error is returned.
ListResolvableAliases(ctx context.Context, in *ListResolvableAliasesRequest, opts ...grpc.CallOption) (*ListResolvableAliasesResponse, error)
}
type userServiceClient struct {
@@ -166,6 +171,15 @@ func (c *userServiceClient) RemoveUserAccounts(ctx context.Context, in *RemoveUs
return out, nil
}
func (c *userServiceClient) ListResolvableAliases(ctx context.Context, in *ListResolvableAliasesRequest, opts ...grpc.CallOption) (*ListResolvableAliasesResponse, error) {
out := new(ListResolvableAliasesResponse)
err := c.cc.Invoke(ctx, UserService_ListResolvableAliases_FullMethodName, in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
// UserServiceServer is the server API for UserService service.
// All implementations must embed UnimplementedUserServiceServer
// for forward compatibility
@@ -218,6 +232,10 @@ type UserServiceServer interface {
// will be removed from. If the provided Account ids is not associated with the
// provided User, an error is returned.
RemoveUserAccounts(context.Context, *RemoveUserAccountsRequest) (*RemoveUserAccountsResponse, error)
// ListResolvableAliases returns a list of Aliases which point to a resource
// for which the provided user id has some permission.
// If missing or malformed an error is returned.
ListResolvableAliases(context.Context, *ListResolvableAliasesRequest) (*ListResolvableAliasesResponse, error)
mustEmbedUnimplementedUserServiceServer()
}
@@ -249,6 +267,9 @@ func (UnimplementedUserServiceServer) SetUserAccounts(context.Context, *SetUserA
func (UnimplementedUserServiceServer) RemoveUserAccounts(context.Context, *RemoveUserAccountsRequest) (*RemoveUserAccountsResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method RemoveUserAccounts not implemented")
}
func (UnimplementedUserServiceServer) ListResolvableAliases(context.Context, *ListResolvableAliasesRequest) (*ListResolvableAliasesResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ListResolvableAliases not implemented")
}
func (UnimplementedUserServiceServer) mustEmbedUnimplementedUserServiceServer() {}
// UnsafeUserServiceServer may be embedded to opt out of forward compatibility for this service.
@@ -406,6 +427,24 @@ func _UserService_RemoveUserAccounts_Handler(srv interface{}, ctx context.Contex
return interceptor(ctx, in, info, handler)
}
func _UserService_ListResolvableAliases_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(ListResolvableAliasesRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(UserServiceServer).ListResolvableAliases(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: UserService_ListResolvableAliases_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(UserServiceServer).ListResolvableAliases(ctx, req.(*ListResolvableAliasesRequest))
}
return interceptor(ctx, in, info, handler)
}
// UserService_ServiceDesc is the grpc.ServiceDesc for UserService service.
// It's only intended for direct use with grpc.RegisterService,
// and not to be introspected or modified (even as a copy)
@@ -445,6 +484,10 @@ var UserService_ServiceDesc = grpc.ServiceDesc{
MethodName: "RemoveUserAccounts",
Handler: _UserService_RemoveUserAccounts_Handler,
},
{
MethodName: "ListResolvableAliases",
Handler: _UserService_ListResolvableAliases_Handler,
},
},
Streams: []grpc.StreamDesc{},
Metadata: "controller/api/services/v1/user_service.proto",
+1 -1
View File
@@ -403,7 +403,7 @@ func (r *Repository) ListRoleGrantScopes(ctx context.Context, roleId string, opt
return roleGrantScopes, nil
}
func (r *Repository) GrantsForUser(ctx context.Context, userId string, _ ...Option) ([]perms.GrantTuple, error) {
func (r *Repository) GrantsForUser(ctx context.Context, userId string, _ ...Option) (perms.GrantTuples, error) {
const op = "iam.(Repository).GrantsForUser"
if userId == "" {
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing user id")
+81 -47
View File
@@ -276,6 +276,31 @@ func (a ACL) Allowed(r Resource, aType action.Type, userId string, opt ...Option
return
}
// ListResolvablePermissions builds a set of Permissions based on the grants in
// the ACL. The permissions will only be created if there is at least
// one grant of the provided resource type that includes at least one of the
// provided actions in the action set.
// Note that unlike the ListPermissions method, this method does not attempt to
// generate permissions for the u_recovery user. To get the resolvable aliases
// for u_recovery, the user could simply query all aliases with a destination id.
func (a ACL) ListResolvablePermissions(requestedType resource.Type, actions action.ActionSet) []Permission {
perms := make([]Permission, 0, len(a.scopeMap))
for scopeId := range a.scopeMap {
// Consider all scopes in the grants. They may not exist, but if that is
// the case the
p := Permission{
ScopeId: scopeId,
Resource: requestedType,
Action: action.ListResolvableAliases,
OnlySelf: true, // default to only self to be restrictive
}
if a.buildPermission(scopeId, requestedType, actions, &p) {
perms = append(perms, p)
}
}
return perms
}
// ListPermissions builds a set of Permissions based on the grants in the ACL.
// Permissions are determined for the given resource for each of the provided scopes.
// There must be a grant for a given resource for one of the provided "id actions"
@@ -301,55 +326,64 @@ func (a ACL) ListPermissions(requestedScopes map[string]*scopes.ScopeInfo,
perms = append(perms, p)
continue
}
// Get grants for a specific scope id from the source of truth.
grants := a.scopeMap[scopeId]
for _, grant := range grants {
// This grant doesn't match what we're looking for, ignore.
if grant.typ != requestedType && grant.typ != resource.All && globals.ResourceInfoFromPrefix(grant.id).Type != requestedType {
continue
}
// We found a grant that matches the requested resource type:
// Search to see if one or all actions in the action set have been granted.
found := false
if ok := grant.actions[action.All]; ok {
found = true
} else {
for a := range idActions {
if ok := grant.actions[a]; ok {
found = true
break
}
}
}
if !found { // In this case, none of the requested actions were granted for the given scope id.
continue
}
actions, _ := grant.Actions()
excludeList := make(action.ActionSet, len(actions))
for _, aa := range actions {
if aa != action.List {
excludeList.Add(aa)
}
}
p.OnlySelf = p.OnlySelf && excludeList.OnlySelf()
switch grant.id {
case "*":
p.All = true
case "":
continue
default:
p.ResourceIds = append(p.ResourceIds, grant.id)
}
}
if p.All || len(p.ResourceIds) > 0 {
if a.buildPermission(scopeId, requestedType, idActions, &p) {
perms = append(perms, p)
}
}
return perms
}
// buildPermission populates the provided permission with either the resource ids
// or marking All to true if there are grants that have an action that match
// one of the provided idActions for the provided type
func (a ACL) buildPermission(scopeId string,
requestedType resource.Type,
idActions action.ActionSet,
p *Permission,
) bool {
// Get grants for a specific scope id from the source of truth.
grants := a.scopeMap[scopeId]
for _, grant := range grants {
// This grant doesn't match what we're looking for, ignore.
if grant.typ != requestedType && grant.typ != resource.All && globals.ResourceInfoFromPrefix(grant.id).Type != requestedType {
continue
}
// We found a grant that matches the requested resource type:
// Search to see if one or all actions in the action set have been granted.
found := false
if ok := grant.actions[action.All]; ok {
found = true
} else {
for idA := range idActions {
if ok := grant.actions[idA]; ok {
found = true
break
}
}
}
if !found { // In this case, none of the requested actions were granted for the given scope id.
continue
}
actions, _ := grant.Actions()
excludeList := make(action.ActionSet, len(actions))
for _, aa := range actions {
if aa != action.List {
excludeList.Add(aa)
}
}
p.OnlySelf = p.OnlySelf && excludeList.OnlySelf()
switch grant.id {
case "*":
p.All = true
case "":
continue
default:
p.ResourceIds = append(p.ResourceIds, grant.id)
}
}
return p.All || len(p.ResourceIds) > 0
}
+362 -1
View File
@@ -412,6 +412,367 @@ func Test_ACLAllowed(t *testing.T) {
}
}
func TestACL_ListResolvableAliasesPermissions(t *testing.T) {
t.Parallel()
ctx := context.Background()
tests := []struct {
name string
aclGrants []scopeGrant
resourceType resource.Type
actionSet action.ActionSet
expPermissions []Permission
skipGrantValidationChecking bool
}{
{
name: "Requested resource mismatch",
aclGrants: []scopeGrant{
{
scope: "o_1",
grants: []string{"ids=*;type=target;actions=list,read"}, // List & Read for all Targets
},
},
resourceType: resource.Session, // We're requesting sessions.
actionSet: action.NewActionSet(action.Read),
expPermissions: []Permission{},
},
{
name: "Requested actions not available for the requested scope id",
aclGrants: []scopeGrant{
{
scope: "o_1",
grants: []string{"ids=*;type=session;actions=delete"},
},
},
resourceType: resource.Session,
actionSet: action.NewActionSet(action.Read),
expPermissions: []Permission{},
},
{
name: "No specific id or wildcard provided for `id` field",
aclGrants: []scopeGrant{
{
scope: "o_1",
grants: []string{"type=*;actions=list,read"},
},
},
resourceType: resource.Session,
actionSet: action.NewActionSet(action.Read),
expPermissions: []Permission{},
skipGrantValidationChecking: true,
},
{
name: "Allow all ids",
aclGrants: []scopeGrant{
{
scope: "o_1",
grants: []string{"ids=*;type=session;actions=update,read"},
},
},
resourceType: resource.Session,
actionSet: action.NewActionSet(action.Read),
expPermissions: []Permission{
{
ScopeId: "o_1",
Resource: resource.Session,
Action: action.ListResolvableAliases,
ResourceIds: nil,
OnlySelf: false,
All: true,
},
},
},
{
name: "Allow all ids, :self actions",
aclGrants: []scopeGrant{
{
scope: "o_1",
grants: []string{"ids=*;type=session;actions=list,read:self"},
},
},
resourceType: resource.Session,
actionSet: action.NewActionSet(action.ReadSelf),
expPermissions: []Permission{
{
ScopeId: "o_1",
Resource: resource.Session,
Action: action.ListResolvableAliases,
ResourceIds: nil,
OnlySelf: true,
All: true,
},
},
},
{
name: "Allow specific IDs",
aclGrants: []scopeGrant{
{
scope: "o_1",
grants: []string{
"ids=s_1;type=session;actions=list,read",
"ids=s_2,s_3;type=session;actions=list,read",
},
},
},
resourceType: resource.Session,
actionSet: action.NewActionSet(action.Read),
expPermissions: []Permission{
{
ScopeId: "o_1",
Resource: resource.Session,
Action: action.ListResolvableAliases,
ResourceIds: []string{"s_1", "s_2", "s_3"},
OnlySelf: false,
All: false,
},
},
},
{
name: "No specific type 1",
aclGrants: []scopeGrant{
{
scope: "o_1",
grants: []string{"ids=*;type=*;actions=list,read:self"},
},
},
resourceType: resource.Session,
actionSet: action.NewActionSet(action.ReadSelf),
expPermissions: []Permission{
{
ScopeId: "o_1",
Resource: resource.Session,
Action: action.ListResolvableAliases,
ResourceIds: nil,
OnlySelf: true,
All: true,
},
},
},
{
name: "List + No-op action with id wildcard",
aclGrants: []scopeGrant{
{
scope: "o_1",
grants: []string{"ids=*;type=session;actions=list,no-op"},
},
},
resourceType: resource.Session,
actionSet: action.NewActionSet(action.NoOp),
expPermissions: []Permission{
{
ScopeId: "o_1",
Resource: resource.Session,
Action: action.ListResolvableAliases,
ResourceIds: nil,
OnlySelf: false,
All: true,
},
},
},
{
name: "List + No-op action with id wildcard, read present",
aclGrants: []scopeGrant{
{
scope: "o_1",
grants: []string{"ids=*;type=session;actions=list,no-op"},
},
},
resourceType: resource.Session,
actionSet: action.NewActionSet(action.Read),
expPermissions: []Permission{},
},
{
name: "List + No-op action with specific ids",
aclGrants: []scopeGrant{
{
scope: "o_1",
grants: []string{
"ids=s_1;type=session;actions=list,no-op",
"ids=s_2,s_3;type=session;actions=list,no-op",
},
},
},
resourceType: resource.Session,
actionSet: action.NewActionSet(action.NoOp),
expPermissions: []Permission{
{
ScopeId: "o_1",
Resource: resource.Session,
Action: action.ListResolvableAliases,
ResourceIds: []string{"s_1", "s_2", "s_3"},
OnlySelf: false,
All: false,
},
},
},
{
name: "No specific type 2",
aclGrants: []scopeGrant{
{
scope: "o_1",
grants: []string{"ids=*;type=*;actions=list,read:self"},
},
},
resourceType: resource.Host,
actionSet: action.NewActionSet(action.ReadSelf),
expPermissions: []Permission{
{
ScopeId: "o_1",
Resource: resource.Host,
Action: action.ListResolvableAliases,
ResourceIds: nil,
OnlySelf: true,
All: true,
},
},
},
{
name: "Grant hierarchy is respected",
aclGrants: []scopeGrant{
{
scope: "o_1",
grants: []string{
"ids=*;type=*;actions=*",
"ids=*;type=session;actions=cancel:self,list,read:self",
},
},
},
resourceType: resource.Session,
actionSet: action.NewActionSet(action.NoOp, action.Read, action.ReadSelf, action.Cancel, action.CancelSelf),
expPermissions: []Permission{
{
ScopeId: "o_1",
Resource: resource.Session,
Action: action.ListResolvableAliases,
ResourceIds: nil,
OnlySelf: false,
All: true,
},
},
},
{
name: "Full access 1",
aclGrants: []scopeGrant{
{
scope: "o_1",
grants: []string{"ids=*;type=*;actions=*"},
},
},
resourceType: resource.Session,
actionSet: action.NewActionSet(action.Read, action.Create, action.Delete),
expPermissions: []Permission{
{
ScopeId: "o_1",
Resource: resource.Session,
Action: action.ListResolvableAliases,
ResourceIds: nil,
OnlySelf: false,
All: true,
},
},
},
{
name: "Full access 2",
aclGrants: []scopeGrant{
{
scope: "o_1",
grants: []string{"ids=*;type=*;actions=*"},
},
},
resourceType: resource.Host,
actionSet: action.NewActionSet(action.Read, action.Create, action.Delete),
expPermissions: []Permission{
{
ScopeId: "o_1",
Resource: resource.Host,
Action: action.ListResolvableAliases,
ResourceIds: nil,
OnlySelf: false,
All: true,
},
},
},
{
name: "Multiple scopes",
aclGrants: []scopeGrant{
{
scope: "o_1",
grants: []string{
"ids=s_1;type=session;actions=create,read",
"ids=s_2,s_3;type=session;actions=update,read",
},
},
{
scope: "o_2",
grants: []string{"ids=*;type=session;actions=read:self"},
},
},
resourceType: resource.Session,
actionSet: action.NewActionSet(action.Read, action.ReadSelf),
expPermissions: []Permission{
{
ScopeId: "o_1",
Resource: resource.Session,
Action: action.ListResolvableAliases,
ResourceIds: []string{"s_1", "s_2", "s_3"},
OnlySelf: false,
All: false,
},
{
ScopeId: "o_2",
Resource: resource.Session,
Action: action.ListResolvableAliases,
ResourceIds: nil,
OnlySelf: true,
All: true,
},
},
},
{
name: "separate_type_id_resource_grants",
resourceType: resource.Target,
actionSet: action.NewActionSet(action.Read, action.Cancel),
aclGrants: []scopeGrant{
{
scope: "p_1",
grants: []string{
"type=target;actions=list",
"ids=ttcp_1234567890;actions=read",
},
},
},
expPermissions: []Permission{
{
ScopeId: "p_1",
Resource: resource.Target,
Action: action.ListResolvableAliases,
ResourceIds: []string{"ttcp_1234567890"},
All: false,
OnlySelf: false,
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
var grants []Grant
for _, sg := range tt.aclGrants {
for _, g := range sg.grants {
grant, err := Parse(ctx, sg.scope, g, WithSkipFinalValidation(tt.skipGrantValidationChecking))
require.NoError(t, err)
grants = append(grants, grant)
}
}
acl := NewACL(grants...)
perms := acl.ListResolvablePermissions(tt.resourceType, tt.actionSet)
require.ElementsMatch(t, tt.expPermissions, perms)
})
}
}
func TestACL_ListPermissions(t *testing.T) {
t.Parallel()
@@ -932,7 +1293,7 @@ func Test_AnonRestrictions(t *testing.T) {
if i == resource.Controller || i == resource.Worker {
continue
}
for j := action.Type(1); j <= action.MonthlyActiveUsers; j++ {
for j := action.Type(1); j <= action.ListResolvableAliases; j++ {
id := "foobar"
prefixes := globals.ResourcePrefixesFromType(resource.Type(i))
if len(prefixes) > 0 {
+55
View File
@@ -5,8 +5,11 @@ package perms
import (
"context"
"encoding/binary"
"encoding/json"
"fmt"
"hash"
"hash/fnv"
"sort"
"strings"
"unicode"
@@ -45,6 +48,58 @@ type GrantTuple struct {
Grant string
}
type GrantTuples []GrantTuple
// GrantsHash returns a stable hash of all the grants in the GrantTuples.
func (g GrantTuples) GrantHash(ctx context.Context) ([]byte, error) {
const op = "perms.(GrantTuples).GrantHash"
// TODO: Should this return an error when the GrantTuples is empty?
var values []string
for _, grant := range g {
values = append(values, grant.Grant, grant.RoleId, grant.ScopeId)
}
// Sort for deterministic output
slices.Sort(values)
hashVal, err := hashStrings(values...)
if err != nil {
return nil, errors.Wrap(ctx, err, op)
}
return binary.LittleEndian.AppendUint64(make([]byte, 0, 4), hashVal), nil
}
func hashStrings(s ...string) (uint64, error) {
hasher := fnv.New64()
var h uint64
var err error
for _, current := range s {
hasher.Reset()
if _, err = hasher.Write([]byte(current)); err != nil {
return 0, err
}
if h, err = hashUpdateOrdered(hasher, h, hasher.Sum64()); err != nil {
return 0, err
}
}
return h, nil
}
// hashUpdateOrdered is taken directly from
// https://github.com/mitchellh/hashstructure
func hashUpdateOrdered(h hash.Hash64, a, b uint64) (uint64, error) {
// For ordered updates, use a real hash function
h.Reset()
e1 := binary.Write(h, binary.LittleEndian, a)
e2 := binary.Write(h, binary.LittleEndian, b)
if e1 != nil {
return 0, e1
}
if e2 != nil {
return 0, e2
}
return h.Sum64(), nil
}
// Scope provides an in-memory representation of iam.Scope without the
// underlying storage references or capabilities.
type Scope struct {
@@ -5,6 +5,7 @@ syntax = "proto3";
package controller.api.services.v1;
import "controller/api/resources/aliases/v1/alias.proto";
import "controller/api/resources/users/v1/user.proto";
import "google/api/annotations.proto";
import "google/protobuf/field_mask.proto";
@@ -126,6 +127,14 @@ service UserService {
};
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {summary: "Removes the specified Accounts from being associated with the provided User."};
}
// ListResolvableAliases returns a list of Aliases which point to a resource
// for which the provided user id has some permission.
// If missing or malformed an error is returned.
rpc ListResolvableAliases(ListResolvableAliasesRequest) returns (ListResolvableAliasesResponse) {
option (google.api.http) = {get: "/v1/users/{id}:list-resolvable-aliases"};
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {summary: "Lists all Aliases which point to a resource for which the requester has some permission."};
}
}
message GetUserRequest {
@@ -241,3 +250,48 @@ message RemoveUserAccountsRequest {
message RemoveUserAccountsResponse {
resources.users.v1.User item = 1;
}
message ListResolvableAliasesRequest {
string id = 1; // @gotags: `class:"public" eventstream:"observation"`
// 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 = 2 [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 = 3 [json_name = "page_size"]; // @gotags: `class:"public"`
}
message ListResolvableAliasesResponse {
repeated resources.aliases.v1.Alias 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. This includes aliases which have their
// destination_id removed or set to a resource for which the requester doesn't
// have permissions.
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"`
}
+3
View File
@@ -75,6 +75,7 @@ const (
SetGrantScopes Type = 61
RemoveGrantScopes Type = 62
MonthlyActiveUsers Type = 63
ListResolvableAliases Type = 64
// When adding new actions, be sure to update:
//
@@ -146,6 +147,7 @@ var Map = map[string]Type{
SetGrantScopes.String(): SetGrantScopes,
RemoveGrantScopes.String(): RemoveGrantScopes,
MonthlyActiveUsers.String(): MonthlyActiveUsers,
ListResolvableAliases.String(): ListResolvableAliases,
}
var DeprecatedMap = map[string]Type{
@@ -223,6 +225,7 @@ func (a Type) String() string {
"set-grant-scopes",
"remove-grant-scopes",
"monthly-active-users",
"list-resolvable-aliases",
}[a]
}
+32
View File
@@ -138,6 +138,38 @@ func TestAction(t *testing.T) {
action: Download,
want: "download",
},
{
action: AttachStoragePolicy,
want: "attach-storage-policy",
},
{
action: DetachStoragePolicy,
want: "detach-storage-policy",
},
{
action: ReApplyStoragePolicy,
want: "reapply-storage-policy",
},
{
action: AddGrantScopes,
want: "add-grant-scopes",
},
{
action: SetGrantScopes,
want: "set-grant-scopes",
},
{
action: RemoveGrantScopes,
want: "remove-grant-scopes",
},
{
action: MonthlyActiveUsers,
want: "monthly-active-users",
},
{
action: ListResolvableAliases,
want: "list-resolvable-aliases",
},
}
for _, tt := range tests {
t.Run(tt.want, func(t *testing.T) {