diff --git a/internal/apptoken/query.go b/internal/apptoken/query.go index e0069b5ec0..c922530c3b 100644 --- a/internal/apptoken/query.go +++ b/internal/apptoken/query.go @@ -402,4 +402,9 @@ left join iam_scope_project from pg_class where oid in ('app_token_global'::regclass, 'app_token_org'::regclass, 'app_token_project'::regclass) ` + + scopeIdFromAppTokenIdQuery = ` + select scope_id + from app_token + where public_id = @public_id;` ) diff --git a/internal/apptoken/repository.go b/internal/apptoken/repository.go index 7c80e8f934..ce9aea1672 100644 --- a/internal/apptoken/repository.go +++ b/internal/apptoken/repository.go @@ -16,8 +16,10 @@ import ( "github.com/hashicorp/boundary/internal/db" "github.com/hashicorp/boundary/internal/db/timestamp" "github.com/hashicorp/boundary/internal/errors" + "github.com/hashicorp/boundary/internal/iam" "github.com/hashicorp/boundary/internal/kms" "github.com/hashicorp/boundary/internal/perms" + "github.com/hashicorp/boundary/internal/types/scope" ) // Repository is the apptoken database repository @@ -623,3 +625,88 @@ func (r *Repository) estimatedCount(ctx context.Context) (int, error) { } return count, nil } + +// getAppTokenScopeType returns the [scope.Type] of an App Token by reading it from the base type app_token table. +// Use this to get scope ID to determine which of the app token subtype tables to operate on. +func getAppTokenScopeType(ctx context.Context, reader db.Reader, id string) (scope.Type, error) { + const op = "apptoken.getAppTokenScopeType" + if id == "" { + return scope.Unknown, errors.New(ctx, errors.InvalidParameter, op, "missing app token id") + } + if reader == nil { + return scope.Unknown, errors.New(ctx, errors.InvalidParameter, op, "missing db.Reader") + } + rows, err := reader.Query(ctx, scopeIdFromAppTokenIdQuery, []any{sql.Named("public_id", id)}) + if err != nil { + return scope.Unknown, errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("failed to lookup app token scope for id: %s", id))) + } + defer rows.Close() + + var scopeIds []string + for rows.Next() { + if err := reader.ScanRows(ctx, rows, &scopeIds); err != nil { + return scope.Unknown, errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("failed scan results from querying app token scope for id: %s", id))) + } + } + if err := rows.Err(); err != nil { + return scope.Unknown, errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("unexpected error scanning results from querying app token scope for id: %s", id))) + } + if len(scopeIds) == 0 { + return scope.Unknown, errors.New(ctx, errors.RecordNotFound, op, fmt.Sprintf("app token %s not found", id)) + } + if len(scopeIds) > 1 { + return scope.Unknown, errors.New(ctx, errors.MultipleRecords, op, fmt.Sprintf("expected 1 row but got: %d", len(scopeIds))) + } + scopeId := scopeIds[0] + switch { + case strings.HasPrefix(scopeId, globals.GlobalPrefix): + return scope.Global, nil + case strings.HasPrefix(scopeId, globals.OrgPrefix): + return scope.Org, nil + case strings.HasPrefix(scopeId, globals.ProjectPrefix): + return scope.Project, nil + default: + return scope.Unknown, fmt.Errorf("unknown scope type for app token %s", id) + } +} + +// getAppTokenScope returns the scope of the app token +func getAppTokenScope(ctx context.Context, r db.Reader, id string) (*iam.Scope, error) { + const op = "apptoken.getAppTokenScope" + if id == "" { + return nil, errors.New(ctx, errors.InvalidParameter, op, "missing app token id") + } + if r == nil { + return nil, errors.New(ctx, errors.InvalidParameter, op, "missing db.Reader") + } + rows, err := r.Query(ctx, scopeIdFromAppTokenIdQuery, []any{sql.Named("public_id", id)}) + if err != nil { + return nil, errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("failed to lookup app token scope for :%s", id))) + } + defer rows.Close() + + var scopeIds []string + for rows.Next() { + if err := r.ScanRows(ctx, rows, &scopeIds); err != nil { + return nil, errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("failed scan results from querying app token scope for :%s", id))) + } + } + if err := rows.Err(); err != nil { + return nil, errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("unexpected error scanning results from querying app token scope for :%s", id))) + } + + if len(scopeIds) == 0 { + return nil, errors.New(ctx, errors.RecordNotFound, op, fmt.Sprintf("app token %s not found", id)) + } + if len(scopeIds) > 1 { + return nil, errors.New(ctx, errors.MultipleRecords, op, fmt.Sprintf("expected 1 row but got: %d", len(scopeIds))) + } + + scp := iam.AllocScope() + scp.PublicId = scopeIds[0] + err = r.LookupByPublicId(ctx, &scp) + if err != nil { + return nil, errors.Wrap(ctx, err, op, errors.WithMsg("failed to lookup app token scope")) + } + return &scp, nil +} diff --git a/internal/apptoken/repository_token_grant.go b/internal/apptoken/repository_token_grant.go index e0b34d463f..f1dc10caf4 100644 --- a/internal/apptoken/repository_token_grant.go +++ b/internal/apptoken/repository_token_grant.go @@ -69,14 +69,14 @@ func (r *Repository) GrantsForToken(ctx context.Context, tokenId string, res []r opts := getOpts(opt...) - // get AppToken to get scope - appToken, err := r.getAppTokenById(ctx, tokenId) + scope, err := getAppTokenScope(ctx, r.reader, tokenId) if err != nil { return nil, errors.Wrap(ctx, err, op) } + scopeId := scope.GetPublicId() // find the correct query to use - query, err := r.resolveAppTokenQuery(ctx, appToken.ScopeId, res, reqScopeId, opts.withRecursive) + query, err := r.resolveAppTokenQuery(ctx, scopeId, res, reqScopeId, opts.withRecursive) if err != nil { return nil, errors.Wrap(ctx, err, op) } @@ -135,7 +135,7 @@ func (r *Repository) GrantsForToken(ctx context.Context, tokenId string, res []r for _, grant := range grants { resp = append(resp, tempGrantTuple{ AppTokenId: grant.AppTokenId, - AppTokenScopeId: appToken.ScopeId, + AppTokenScopeId: scopeId, AppTokenParentScopeId: grant.AppTokenParentScopeId, GrantScopeId: grant.GrantScope, Grant: strings.Join(grant.CanonicalGrants, ","),