Update targets to new listing method (#2045)
This uses the prefetch method to fetch resource IDs and validate them for authz by the user before then fetching the full list of targets.
This commit is contained in:
1 parent
21b0d584ad
commit
4cf2a87e8d
8 files changed
+150
-23
No files matched your search
@@ -366,7 +366,7 @@ func Test_MakeInactive_MakePrivate_MakePublic(t *testing.T) {
|
||||
}(),
|
||||
version: 1,
|
||||
wantErrMatch: errors.T(errors.InvalidParameter),
|
||||
wantErrContains: "certificate signed by unknown authority",
|
||||
wantErrContains: "certificate",
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ package scopeids
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/hashicorp/boundary/internal/boundary"
|
||||
"github.com/hashicorp/boundary/internal/errors"
|
||||
@@ -265,7 +266,7 @@ func filterAuthorizedResourceIds(
|
||||
}
|
||||
|
||||
res := perms.Resource{
|
||||
Type: resource.Session,
|
||||
Type: input.Type,
|
||||
}
|
||||
|
||||
// Now run authorization checks against each so we know if there is a point
|
||||
@@ -285,6 +286,9 @@ func filterAuthorizedResourceIds(
|
||||
}
|
||||
}
|
||||
|
||||
if output.ScopeResourceMap[scopeId] == nil {
|
||||
return errors.New(ctx, errors.Internal, op, fmt.Sprintf("scope id %s returned from fetching authz protected entities not found in scope resource map", scopeId))
|
||||
}
|
||||
if output.ScopeResourceMap[scopeId].Resources == nil {
|
||||
output.ScopeResourceMap[scopeId].Resources = make(map[string]ResourceInfo)
|
||||
}
|
||||
|
||||
@@ -146,6 +146,8 @@ var _ pbs.TargetServiceServer = Service{}
|
||||
|
||||
// ListTargets implements the interface pbs.TargetServiceServer.
|
||||
func (s Service) ListTargets(ctx context.Context, req *pbs.ListTargetsRequest) (*pbs.ListTargetsResponse, error) {
|
||||
const op = "targets.(Service).ListSessions"
|
||||
|
||||
if err := validateListRequest(req); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -163,17 +165,34 @@ func (s Service) ListTargets(ctx context.Context, req *pbs.ListTargetsRequest) (
|
||||
}
|
||||
}
|
||||
|
||||
scopeIds, scopeInfoMap, err := scopeids.GetListingScopeIds(
|
||||
ctx, s.iamRepoFn, authResults, req.GetScopeId(), resource.Target, req.GetRecursive())
|
||||
repo, err := s.repoFn()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(ctx, err, op)
|
||||
}
|
||||
|
||||
scopeResourceInfo, err := scopeids.GetListingResourceInformation(
|
||||
ctx,
|
||||
scopeids.GetListingResourceInformationInput{
|
||||
IamRepoFn: s.iamRepoFn,
|
||||
AuthResults: authResults,
|
||||
RootScopeId: req.GetScopeId(),
|
||||
Type: resource.Target,
|
||||
Recursive: req.GetRecursive(),
|
||||
AuthzProtectedEntityProvider: repo,
|
||||
ActionSet: IdActions,
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// If no scopes match, return an empty response
|
||||
if len(scopeIds) == 0 {
|
||||
if len(scopeResourceInfo.ScopeIds) == 0 ||
|
||||
len(scopeResourceInfo.ResourceIds) == 0 {
|
||||
return &pbs.ListTargetsResponse{}, nil
|
||||
}
|
||||
|
||||
tl, err := s.listFromRepo(ctx, scopeIds)
|
||||
tl, err := s.listFromRepo(ctx, scopeResourceInfo.ResourceIds)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -190,21 +209,14 @@ func (s Service) ListTargets(ctx context.Context, req *pbs.ListTargetsRequest) (
|
||||
Type: resource.Target,
|
||||
}
|
||||
for _, item := range tl {
|
||||
res.Id = item.GetPublicId()
|
||||
res.ScopeId = item.GetScopeId()
|
||||
authorizedActions := authResults.FetchActionSetForId(ctx, item.GetPublicId(), IdActions, auth.WithResource(&res)).Strings()
|
||||
if len(authorizedActions) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
outputFields := authResults.FetchOutputFields(res, action.List).SelfOrDefaults(authResults.UserId)
|
||||
outputOpts := make([]handlers.Option, 0, 3)
|
||||
outputOpts = append(outputOpts, handlers.WithOutputFields(&outputFields))
|
||||
if outputFields.Has(globals.ScopeField) {
|
||||
outputOpts = append(outputOpts, handlers.WithScope(scopeInfoMap[item.GetScopeId()]))
|
||||
outputOpts = append(outputOpts, handlers.WithScope(scopeResourceInfo.ScopeResourceMap[item.GetScopeId()].ScopeInfo))
|
||||
}
|
||||
if outputFields.Has(globals.AuthorizedActionsField) {
|
||||
outputOpts = append(outputOpts, handlers.WithAuthorizedActions(authorizedActions))
|
||||
outputOpts = append(outputOpts, handlers.WithAuthorizedActions(scopeResourceInfo.ScopeResourceMap[item.GetScopeId()].Resources[item.GetPublicId()].AuthorizedActions.Strings()))
|
||||
}
|
||||
|
||||
item, err := toProto(ctx, item, nil, nil, outputOpts...)
|
||||
@@ -1346,12 +1358,12 @@ func (s Service) deleteFromRepo(ctx context.Context, id string) (bool, error) {
|
||||
return rows > 0, nil
|
||||
}
|
||||
|
||||
func (s Service) listFromRepo(ctx context.Context, scopeIds []string) ([]target.Target, error) {
|
||||
func (s Service) listFromRepo(ctx context.Context, targetIds []string) ([]target.Target, error) {
|
||||
repo, err := s.repoFn()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ul, err := repo.ListTargets(ctx, target.WithScopeIds(scopeIds))
|
||||
ul, err := repo.ListTargets(ctx, target.WithTargetIds(targetIds))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -220,7 +220,7 @@ func (r *Repository) FetchAuthzProtectedEntitiesByScope(ctx context.Context, sco
|
||||
return nil, errors.New(ctx, errors.InvalidParameter, op, "no scopes given")
|
||||
case 1:
|
||||
inClauseCnt += 1
|
||||
where, args = fmt.Sprintf("where scope_id = @%d", inClauseCnt), append(args, sql.Named("1", scopeIds[0]))
|
||||
where, args = fmt.Sprintf("where scope_id = @%d", inClauseCnt), append(args, sql.Named(fmt.Sprintf("%d", inClauseCnt), scopeIds[0]))
|
||||
default:
|
||||
idsInClause := make([]string, 0, len(scopeIds))
|
||||
for _, id := range scopeIds {
|
||||
|
||||
@@ -35,6 +35,7 @@ type options struct {
|
||||
WithSessionConnectionLimit int32
|
||||
WithPublicId string
|
||||
WithWorkerFilter string
|
||||
WithTargetIds []string
|
||||
}
|
||||
|
||||
func getDefaultOptions() options {
|
||||
@@ -161,3 +162,10 @@ func WithWorkerFilter(filter string) Option {
|
||||
o.WithWorkerFilter = filter
|
||||
}
|
||||
}
|
||||
|
||||
// WithTargetIds provides an option to search by specific target IDs
|
||||
func WithTargetIds(with []string) Option {
|
||||
return func(o *options) {
|
||||
o.WithTargetIds = with
|
||||
}
|
||||
}
|
||||
@@ -43,5 +43,11 @@ final (action, library_id) as (
|
||||
)
|
||||
select * from final
|
||||
order by action, library_id;
|
||||
`
|
||||
|
||||
targetPublicIdList = `
|
||||
select public_id, scope_id from target
|
||||
%s
|
||||
;
|
||||
`
|
||||
)
|
||||
@@ -2,14 +2,17 @@ package target
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/hashicorp/boundary/internal/boundary"
|
||||
"github.com/hashicorp/boundary/internal/db"
|
||||
dbcommon "github.com/hashicorp/boundary/internal/db/common"
|
||||
"github.com/hashicorp/boundary/internal/errors"
|
||||
"github.com/hashicorp/boundary/internal/kms"
|
||||
"github.com/hashicorp/boundary/internal/oplog"
|
||||
"github.com/hashicorp/boundary/internal/types/scope"
|
||||
)
|
||||
|
||||
// Cloneable provides a cloning interface
|
||||
@@ -138,21 +141,96 @@ func (r *Repository) LookupTarget(ctx context.Context, publicIdOrName string, op
|
||||
return subtype, hostSources, credSources, nil
|
||||
}
|
||||
|
||||
// FetchAuthzProtectedEntitiesByScope implements boundary.AuthzProtectedEntityProvider
|
||||
func (r *Repository) FetchAuthzProtectedEntitiesByScope(ctx context.Context, scopeIds []string) (map[string][]boundary.AuthzProtectedEntity, error) {
|
||||
const op = "target.(Repository).FetchAuthzProtectedEntitiesByScope"
|
||||
|
||||
var where string
|
||||
var args []interface{}
|
||||
|
||||
inClauseCnt := 0
|
||||
|
||||
switch len(scopeIds) {
|
||||
case 0:
|
||||
return nil, errors.New(ctx, errors.InvalidParameter, op, "no scopes given")
|
||||
case 1:
|
||||
if scopeIds[0] != scope.Global.String() {
|
||||
inClauseCnt += 1
|
||||
where, args = fmt.Sprintf("where scope_id = @%d", inClauseCnt), append(args, sql.Named(fmt.Sprintf("%d", inClauseCnt), scopeIds[0]))
|
||||
}
|
||||
default:
|
||||
idsInClause := make([]string, 0, len(scopeIds))
|
||||
for _, id := range scopeIds {
|
||||
inClauseCnt += 1
|
||||
idsInClause, args = append(idsInClause, fmt.Sprintf("@%d", inClauseCnt)), append(args, sql.Named(fmt.Sprintf("%d", inClauseCnt), id))
|
||||
}
|
||||
where = fmt.Sprintf("where scope_id in (%s)", strings.Join(idsInClause, ","))
|
||||
}
|
||||
|
||||
q := targetPublicIdList
|
||||
query := fmt.Sprintf(q, where)
|
||||
|
||||
rows, err := r.reader.Query(ctx, query, args)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(ctx, err, op)
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
targetsMap := map[string][]boundary.AuthzProtectedEntity{}
|
||||
for rows.Next() {
|
||||
var tv targetView
|
||||
if err := r.reader.ScanRows(ctx, rows, &tv); err != nil {
|
||||
return nil, errors.Wrap(ctx, err, op, errors.WithMsg("scan row failed"))
|
||||
}
|
||||
targetsMap[tv.GetScopeId()] = append(targetsMap[tv.GetScopeId()], tv)
|
||||
}
|
||||
|
||||
return targetsMap, nil
|
||||
}
|
||||
|
||||
// ListTargets in targets in a scope. Supports the WithScopeId, WithLimit, WithType options.
|
||||
func (r *Repository) ListTargets(ctx context.Context, opt ...Option) ([]Target, error) {
|
||||
const op = "target.(Repository).ListTargets"
|
||||
opts := GetOpts(opt...)
|
||||
if len(opts.WithScopeIds) == 0 && opts.WithUserId == "" {
|
||||
return nil, errors.New(ctx, errors.InvalidParameter, op, "must specify either scope id or user id")
|
||||
if len(opts.WithScopeIds) == 0 && opts.WithUserId == "" && len(opts.WithTargetIds) == 0 {
|
||||
return nil, errors.New(ctx, errors.InvalidParameter, op, "must specify either scope ids, target ids, or user id")
|
||||
}
|
||||
// TODO (jimlambrt 8/2020) - implement WithUserId() optional filtering.
|
||||
var where []string
|
||||
var args []interface{}
|
||||
if len(opts.WithScopeIds) != 0 {
|
||||
where, args = append(where, "scope_id in (?)"), append(args, opts.WithScopeIds)
|
||||
inClauseCnt := 0
|
||||
|
||||
switch len(opts.WithScopeIds) {
|
||||
case 0:
|
||||
case 1:
|
||||
inClauseCnt += 1
|
||||
where, args = append(where, fmt.Sprintf("scope_id = @%d", inClauseCnt)), append(args, sql.Named(fmt.Sprintf("%d", inClauseCnt), opts.WithScopeIds[0]))
|
||||
default:
|
||||
idsInClause := make([]string, 0, len(opts.WithScopeIds))
|
||||
for _, id := range opts.WithScopeIds {
|
||||
inClauseCnt += 1
|
||||
idsInClause, args = append(idsInClause, fmt.Sprintf("@%d", inClauseCnt)), append(args, sql.Named(fmt.Sprintf("%d", inClauseCnt), id))
|
||||
}
|
||||
where = append(where, fmt.Sprintf("scope_id in (%s)", strings.Join(idsInClause, ",")))
|
||||
}
|
||||
|
||||
switch len(opts.WithTargetIds) {
|
||||
case 0:
|
||||
case 1:
|
||||
inClauseCnt += 1
|
||||
where, args = append(where, fmt.Sprintf("public_id = @%d", inClauseCnt)), append(args, sql.Named(fmt.Sprintf("%d", inClauseCnt), opts.WithTargetIds[0]))
|
||||
default:
|
||||
idsInClause := make([]string, 0, len(opts.WithTargetIds))
|
||||
for _, id := range opts.WithTargetIds {
|
||||
inClauseCnt += 1
|
||||
idsInClause, args = append(idsInClause, fmt.Sprintf("@%d", inClauseCnt)), append(args, sql.Named(fmt.Sprintf("%d", inClauseCnt), id))
|
||||
}
|
||||
where = append(where, fmt.Sprintf("public_id in (%s)", strings.Join(idsInClause, ",")))
|
||||
}
|
||||
|
||||
if opts.WithType != "" {
|
||||
where, args = append(where, "type = ?"), append(args, opts.WithType.String())
|
||||
inClauseCnt += 1
|
||||
where, args = append(where, fmt.Sprintf("type = @%d", inClauseCnt)), append(args, sql.Named(fmt.Sprintf("%d", inClauseCnt), opts.WithType.String()))
|
||||
}
|
||||
|
||||
var foundTargets []*targetView
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/hashicorp/boundary/internal/boundary"
|
||||
"github.com/hashicorp/boundary/internal/db/timestamp"
|
||||
"github.com/hashicorp/boundary/internal/errors"
|
||||
"github.com/hashicorp/boundary/internal/oplog"
|
||||
@@ -44,6 +45,8 @@ const (
|
||||
targetsViewDefaultTable = "target_all_subtypes"
|
||||
)
|
||||
|
||||
var _ boundary.AuthzProtectedEntity = (*targetView)(nil)
|
||||
|
||||
// targetView provides a common way to return targets regardless of their
|
||||
// underlying type.
|
||||
type targetView struct {
|
||||
@@ -77,6 +80,22 @@ func (t *targetView) SetTableName(n string) {
|
||||
}
|
||||
}
|
||||
|
||||
// GetPublicId satisfies boundary.AuthzProtectedEntity
|
||||
func (t targetView) GetPublicId() string {
|
||||
return t.PublicId
|
||||
}
|
||||
|
||||
// GetScopeId satisfies boundary.AuthzProtectedEntity
|
||||
func (t targetView) GetScopeId() string {
|
||||
return t.ScopeId
|
||||
}
|
||||
|
||||
// GetUserId satisfies boundary.AuthzProtectedEntity; targets are not associated
|
||||
// with a user ID so this always returns an empty string
|
||||
func (t targetView) GetUserId() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (t *targetView) Subtype() subtypes.Subtype {
|
||||
return subtypes.Subtype(t.Type)
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user