2022-02-08 09:37:28 +01:00
package query
import (
"context"
"database/sql"
2023-12-08 16:30:55 +02:00
"errors"
2025-06-04 09:17:23 +02:00
"slices"
2022-02-08 09:37:28 +01:00
"time"
sq "github.com/Masterminds/squirrel"
2023-10-19 12:19:10 +02:00
"github.com/zitadel/logging"
2022-04-27 01:01:45 +02:00
"github.com/zitadel/zitadel/internal/api/authz"
2022-08-31 09:52:43 +02:00
"github.com/zitadel/zitadel/internal/database"
2025-06-04 09:17:23 +02:00
"github.com/zitadel/zitadel/internal/domain"
2023-10-19 12:19:10 +02:00
"github.com/zitadel/zitadel/internal/eventstore/handler/v2"
2022-12-01 09:18:53 +01:00
"github.com/zitadel/zitadel/internal/query/projection"
"github.com/zitadel/zitadel/internal/telemetry/tracing"
2023-12-08 16:30:55 +02:00
"github.com/zitadel/zitadel/internal/zerrors"
2022-02-08 09:37:28 +01:00
)
2025-06-04 09:17:23 +02:00
func patsCheckPermission ( ctx context . Context , tokens * PersonalAccessTokens , permissionCheck domain . PermissionCheck ) {
tokens . PersonalAccessTokens = slices . DeleteFunc ( tokens . PersonalAccessTokens ,
func ( token * PersonalAccessToken ) bool {
return userCheckPermission ( ctx , token . ResourceOwner , token . UserID , permissionCheck ) != nil
} ,
)
}
2022-02-08 09:37:28 +01:00
var (
personalAccessTokensTable = table {
2022-10-27 08:08:36 +02:00
name : projection . PersonalAccessTokenProjectionTable ,
instanceIDCol : projection . PersonalAccessTokenColumnInstanceID ,
2022-02-08 09:37:28 +01:00
}
PersonalAccessTokenColumnID = Column {
name : projection . PersonalAccessTokenColumnID ,
table : personalAccessTokensTable ,
}
PersonalAccessTokenColumnUserID = Column {
name : projection . PersonalAccessTokenColumnUserID ,
table : personalAccessTokensTable ,
}
PersonalAccessTokenColumnExpiration = Column {
name : projection . PersonalAccessTokenColumnExpiration ,
table : personalAccessTokensTable ,
}
PersonalAccessTokenColumnScopes = Column {
name : projection . PersonalAccessTokenColumnScopes ,
table : personalAccessTokensTable ,
}
PersonalAccessTokenColumnCreationDate = Column {
name : projection . PersonalAccessTokenColumnCreationDate ,
table : personalAccessTokensTable ,
}
PersonalAccessTokenColumnChangeDate = Column {
name : projection . PersonalAccessTokenColumnChangeDate ,
table : personalAccessTokensTable ,
}
PersonalAccessTokenColumnResourceOwner = Column {
name : projection . PersonalAccessTokenColumnResourceOwner ,
table : personalAccessTokensTable ,
}
2022-03-23 09:02:39 +01:00
PersonalAccessTokenColumnInstanceID = Column {
name : projection . PersonalAccessTokenColumnInstanceID ,
table : personalAccessTokensTable ,
}
2022-02-08 09:37:28 +01:00
PersonalAccessTokenColumnSequence = Column {
name : projection . PersonalAccessTokenColumnSequence ,
table : personalAccessTokensTable ,
}
2022-11-30 17:01:17 +01:00
PersonalAccessTokenColumnOwnerRemoved = Column {
name : projection . PersonalAccessTokenColumnOwnerRemoved ,
table : personalAccessTokensTable ,
}
2022-02-08 09:37:28 +01:00
)
type PersonalAccessTokens struct {
SearchResponse
PersonalAccessTokens [ ] * PersonalAccessToken
}
type PersonalAccessToken struct {
ID string
CreationDate time . Time
ChangeDate time . Time
ResourceOwner string
Sequence uint64
UserID string
Expiration time . Time
2023-10-19 12:19:10 +02:00
Scopes database . TextArray [ string ]
2022-02-08 09:37:28 +01:00
}
type PersonalAccessTokenSearchQueries struct {
SearchRequest
Queries [ ] SearchQuery
}
2025-06-04 09:17:23 +02:00
func ( q * Queries ) PersonalAccessTokenByID ( ctx context . Context , shouldTriggerBulk bool , id string , queries ... SearchQuery ) ( pat * PersonalAccessToken , err error ) {
2022-12-01 09:18:53 +01:00
ctx , span := tracing . NewSpan ( ctx )
defer func ( ) { span . EndWithError ( err ) } ( )
2022-06-14 07:51:00 +02:00
if shouldTriggerBulk {
2023-10-26 17:07:56 +02:00
_ , traceSpan := tracing . NewNamedSpan ( ctx , "TriggerPersonalAccessTokenProjection" )
2023-10-19 12:19:10 +02:00
ctx , err = projection . PersonalAccessTokenProjection . Trigger ( ctx , handler . WithAwaitRunning ( ) )
logging . OnError ( err ) . Debug ( "trigger failed" )
2023-10-26 17:07:56 +02:00
traceSpan . EndWithError ( err )
2022-06-14 07:51:00 +02:00
}
2025-04-02 16:53:06 +02:00
query , scan := preparePersonalAccessTokenQuery ( )
2022-02-08 09:37:28 +01:00
for _ , q := range queries {
query = q . toQuery ( query )
}
2022-11-30 17:01:17 +01:00
eq := sq . Eq {
2025-06-04 09:17:23 +02:00
PersonalAccessTokenColumnID . identifier ( ) : id ,
PersonalAccessTokenColumnInstanceID . identifier ( ) : authz . GetInstance ( ctx ) . InstanceID ( ) ,
PersonalAccessTokenColumnOwnerRemoved . identifier ( ) : false ,
2022-11-30 17:01:17 +01:00
}
stmt , args , err := query . Where ( eq ) . ToSql ( )
2022-02-08 09:37:28 +01:00
if err != nil {
2025-06-06 10:48:29 +02:00
return nil , zerrors . ThrowInternal ( err , "QUERY-Dgfb4" , "Errors.Query.SQLStatement" )
2022-02-08 09:37:28 +01:00
}
2023-08-22 14:49:02 +02:00
err = q . client . QueryRowContext ( ctx , func ( row * sql . Row ) error {
pat , err = scan ( row )
return err
} , stmt , args ... )
if err != nil {
return nil , err
}
return pat , nil
2022-02-08 09:37:28 +01:00
}
2025-06-04 09:17:23 +02:00
// SearchPersonalAccessTokens returns personal access token resources.
// If permissionCheck is nil, the PATs are not filtered.
// If permissionCheck is not nil and the PermissionCheckV2 feature flag is false, the returned PATs are filtered in-memory by the given permission check.
// If permissionCheck is not nil and the PermissionCheckV2 feature flag is true, the returned PATs are filtered in the database.
func ( q * Queries ) SearchPersonalAccessTokens ( ctx context . Context , queries * PersonalAccessTokenSearchQueries , permissionCheck domain . PermissionCheck ) ( authNKeys * PersonalAccessTokens , err error ) {
permissionCheckV2 := PermissionV2 ( ctx , permissionCheck )
keys , err := q . searchPersonalAccessTokens ( ctx , queries , permissionCheckV2 )
if err != nil {
return nil , err
}
if permissionCheck != nil && ! authz . GetFeatures ( ctx ) . PermissionCheckV2 {
patsCheckPermission ( ctx , keys , permissionCheck )
}
return keys , nil
}
func ( q * Queries ) searchPersonalAccessTokens ( ctx context . Context , queries * PersonalAccessTokenSearchQueries , permissionCheckV2 bool ) ( personalAccessTokens * PersonalAccessTokens , err error ) {
2022-12-01 09:18:53 +01:00
ctx , span := tracing . NewSpan ( ctx )
defer func ( ) { span . EndWithError ( err ) } ( )
2025-04-02 16:53:06 +02:00
query , scan := preparePersonalAccessTokensQuery ( )
2025-06-04 09:17:23 +02:00
query = queries . toQuery ( query )
query = userPermissionCheckV2WithCustomColumns ( ctx , query , permissionCheckV2 , queries . Queries , PersonalAccessTokenColumnResourceOwner , PersonalAccessTokenColumnUserID )
2022-11-30 17:01:17 +01:00
eq := sq . Eq {
2025-06-04 09:17:23 +02:00
PersonalAccessTokenColumnInstanceID . identifier ( ) : authz . GetInstance ( ctx ) . InstanceID ( ) ,
PersonalAccessTokenColumnOwnerRemoved . identifier ( ) : false ,
2022-11-30 17:01:17 +01:00
}
2025-06-04 09:17:23 +02:00
stmt , args , err := query . Where ( eq ) . ToSql ( )
2022-02-08 09:37:28 +01:00
if err != nil {
2023-12-08 16:30:55 +02:00
return nil , zerrors . ThrowInvalidArgument ( err , "QUERY-Hjw2w" , "Errors.Query.InvalidRequest" )
2022-02-08 09:37:28 +01:00
}
2023-08-22 14:49:02 +02:00
err = q . client . QueryContext ( ctx , func ( rows * sql . Rows ) error {
personalAccessTokens , err = scan ( rows )
return err
} , stmt , args ... )
2022-02-08 09:37:28 +01:00
if err != nil {
2023-12-08 16:30:55 +02:00
return nil , zerrors . ThrowInternal ( err , "QUERY-Bmz63" , "Errors.Internal" )
2022-02-08 09:37:28 +01:00
}
2023-08-22 14:49:02 +02:00
2023-10-19 12:19:10 +02:00
personalAccessTokens . State , err = q . latestState ( ctx , personalAccessTokensTable )
2022-02-08 09:37:28 +01:00
return personalAccessTokens , err
}
func NewPersonalAccessTokenResourceOwnerSearchQuery ( value string ) ( SearchQuery , error ) {
return NewTextQuery ( PersonalAccessTokenColumnResourceOwner , value , TextEquals )
}
func NewPersonalAccessTokenUserIDSearchQuery ( value string ) ( SearchQuery , error ) {
return NewTextQuery ( PersonalAccessTokenColumnUserID , value , TextEquals )
}
2025-06-04 09:17:23 +02:00
func NewPersonalAccessTokenIDQuery ( id string ) ( SearchQuery , error ) {
return NewTextQuery ( PersonalAccessTokenColumnID , id , TextEquals )
}
func NewPersonalAccessTokenCreationDateQuery ( ts time . Time , compare TimestampComparison ) ( SearchQuery , error ) {
return NewTimestampQuery ( PersonalAccessTokenColumnCreationDate , ts , compare )
}
func NewPersonalAccessTokenExpirationDateDateQuery ( ts time . Time , compare TimestampComparison ) ( SearchQuery , error ) {
return NewTimestampQuery ( PersonalAccessTokenColumnExpiration , ts , compare )
}
2022-02-08 09:37:28 +01:00
func ( r * PersonalAccessTokenSearchQueries ) AppendMyResourceOwnerQuery ( orgID string ) error {
query , err := NewPersonalAccessTokenResourceOwnerSearchQuery ( orgID )
if err != nil {
return err
}
r . Queries = append ( r . Queries , query )
return nil
}
func ( q * PersonalAccessTokenSearchQueries ) toQuery ( query sq . SelectBuilder ) sq . SelectBuilder {
query = q . SearchRequest . toQuery ( query )
for _ , q := range q . Queries {
query = q . toQuery ( query )
}
return query
}
2025-04-02 16:53:06 +02:00
func preparePersonalAccessTokenQuery ( ) ( sq . SelectBuilder , func ( * sql . Row ) ( * PersonalAccessToken , error ) ) {
2022-02-08 09:37:28 +01:00
return sq . Select (
PersonalAccessTokenColumnID . identifier ( ) ,
PersonalAccessTokenColumnCreationDate . identifier ( ) ,
PersonalAccessTokenColumnChangeDate . identifier ( ) ,
PersonalAccessTokenColumnResourceOwner . identifier ( ) ,
PersonalAccessTokenColumnSequence . identifier ( ) ,
PersonalAccessTokenColumnUserID . identifier ( ) ,
PersonalAccessTokenColumnExpiration . identifier ( ) ,
PersonalAccessTokenColumnScopes . identifier ( ) ) .
2025-04-02 16:53:06 +02:00
From ( personalAccessTokensTable . identifier ( ) ) .
2023-02-27 22:36:43 +01:00
PlaceholderFormat ( sq . Dollar ) ,
2022-02-08 09:37:28 +01:00
func ( row * sql . Row ) ( * PersonalAccessToken , error ) {
p := new ( PersonalAccessToken )
err := row . Scan (
& p . ID ,
& p . CreationDate ,
& p . ChangeDate ,
& p . ResourceOwner ,
& p . Sequence ,
& p . UserID ,
& p . Expiration ,
2022-08-31 09:52:43 +02:00
& p . Scopes ,
2022-02-08 09:37:28 +01:00
)
if err != nil {
2023-12-08 16:30:55 +02:00
if errors . Is ( err , sql . ErrNoRows ) {
return nil , zerrors . ThrowNotFound ( err , "QUERY-fRunu" , "Errors.PersonalAccessToken.NotFound" )
2022-02-08 09:37:28 +01:00
}
2023-12-08 16:30:55 +02:00
return nil , zerrors . ThrowInternal ( err , "QUERY-dj2FF" , "Errors.Internal" )
2022-02-08 09:37:28 +01:00
}
return p , nil
}
}
2025-04-02 16:53:06 +02:00
func preparePersonalAccessTokensQuery ( ) ( sq . SelectBuilder , func ( * sql . Rows ) ( * PersonalAccessTokens , error ) ) {
2022-02-08 09:37:28 +01:00
return sq . Select (
PersonalAccessTokenColumnID . identifier ( ) ,
PersonalAccessTokenColumnCreationDate . identifier ( ) ,
PersonalAccessTokenColumnChangeDate . identifier ( ) ,
PersonalAccessTokenColumnResourceOwner . identifier ( ) ,
PersonalAccessTokenColumnSequence . identifier ( ) ,
PersonalAccessTokenColumnUserID . identifier ( ) ,
PersonalAccessTokenColumnExpiration . identifier ( ) ,
PersonalAccessTokenColumnScopes . identifier ( ) ,
countColumn . identifier ( ) ) .
2025-04-02 16:53:06 +02:00
From ( personalAccessTokensTable . identifier ( ) ) .
2023-02-27 22:36:43 +01:00
PlaceholderFormat ( sq . Dollar ) ,
2022-02-08 09:37:28 +01:00
func ( rows * sql . Rows ) ( * PersonalAccessTokens , error ) {
personalAccessTokens := make ( [ ] * PersonalAccessToken , 0 )
var count uint64
for rows . Next ( ) {
token := new ( PersonalAccessToken )
err := rows . Scan (
& token . ID ,
& token . CreationDate ,
& token . ChangeDate ,
& token . ResourceOwner ,
& token . Sequence ,
& token . UserID ,
& token . Expiration ,
2022-08-31 09:52:43 +02:00
& token . Scopes ,
2022-02-08 09:37:28 +01:00
& count ,
)
if err != nil {
return nil , err
}
personalAccessTokens = append ( personalAccessTokens , token )
}
if err := rows . Close ( ) ; err != nil {
2023-12-08 16:30:55 +02:00
return nil , zerrors . ThrowInternal ( err , "QUERY-QMXJv" , "Errors.Query.CloseRows" )
2022-02-08 09:37:28 +01:00
}
return & PersonalAccessTokens {
PersonalAccessTokens : personalAccessTokens ,
SearchResponse : SearchResponse {
Count : count ,
} ,
} , nil
}
}