diff --git a/internal/auth/oidc/repository_auth_method_operational_state_test.go b/internal/auth/oidc/repository_auth_method_operational_state_test.go index bb744571aa..1ccd144409 100644 --- a/internal/auth/oidc/repository_auth_method_operational_state_test.go +++ b/internal/auth/oidc/repository_auth_method_operational_state_test.go @@ -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", }, } diff --git a/internal/servers/controller/common/scopeids/scope_ids.go b/internal/servers/controller/common/scopeids/scope_ids.go index fd6be78d8d..7219d9851c 100644 --- a/internal/servers/controller/common/scopeids/scope_ids.go +++ b/internal/servers/controller/common/scopeids/scope_ids.go @@ -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) } diff --git a/internal/servers/controller/handlers/targets/target_service.go b/internal/servers/controller/handlers/targets/target_service.go index 02b0d956c5..d90528da0f 100644 --- a/internal/servers/controller/handlers/targets/target_service.go +++ b/internal/servers/controller/handlers/targets/target_service.go @@ -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 } diff --git a/internal/session/repository_session.go b/internal/session/repository_session.go index 78fee5005c..f1d5dbcd58 100644 --- a/internal/session/repository_session.go +++ b/internal/session/repository_session.go @@ -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 { diff --git a/internal/target/options.go b/internal/target/options.go index 69565cd3cd..e3d0421ac2 100644 --- a/internal/target/options.go +++ b/internal/target/options.go @@ -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 + } +} diff --git a/internal/target/query.go b/internal/target/query.go index 054861eee3..4fd31c14c1 100644 --- a/internal/target/query.go +++ b/internal/target/query.go @@ -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 +; ` ) diff --git a/internal/target/repository.go b/internal/target/repository.go index f15279d89c..9b3163ec41 100644 --- a/internal/target/repository.go +++ b/internal/target/repository.go @@ -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 diff --git a/internal/target/target.go b/internal/target/target.go index 6916c43e0f..0ac89ffd82 100644 --- a/internal/target/target.go +++ b/internal/target/target.go @@ -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) }