2021-12-14 10:57:20 +01:00
package query
import (
"context"
"database/sql"
2024-09-11 12:04:09 +03:00
_ "embed"
2023-12-08 16:30:55 +02:00
"errors"
2025-06-04 09:17:23 +02:00
"slices"
2021-12-14 10:57:20 +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"
"github.com/zitadel/zitadel/internal/domain"
2023-10-19 12:19:10 +02:00
"github.com/zitadel/zitadel/internal/eventstore/handler/v2"
2022-04-27 01:01:45 +02:00
"github.com/zitadel/zitadel/internal/query/projection"
2022-12-01 09:18:53 +01:00
"github.com/zitadel/zitadel/internal/telemetry/tracing"
2023-12-08 16:30:55 +02:00
"github.com/zitadel/zitadel/internal/zerrors"
2021-12-14 10:57:20 +01:00
)
2025-06-04 09:17:23 +02:00
func keysCheckPermission ( ctx context . Context , keys * AuthNKeys , permissionCheck domain . PermissionCheck ) {
keys . AuthNKeys = slices . DeleteFunc ( keys . AuthNKeys ,
func ( key * AuthNKey ) bool {
return userCheckPermission ( ctx , key . ResourceOwner , key . AggregateID , permissionCheck ) != nil
} ,
)
}
2021-12-14 10:57:20 +01:00
var (
authNKeyTable = table {
2022-10-27 08:08:36 +02:00
name : projection . AuthNKeyTable ,
instanceIDCol : projection . AuthNKeyInstanceIDCol ,
2021-12-14 10:57:20 +01:00
}
AuthNKeyColumnID = Column {
name : projection . AuthNKeyIDCol ,
table : authNKeyTable ,
}
AuthNKeyColumnCreationDate = Column {
name : projection . AuthNKeyCreationDateCol ,
table : authNKeyTable ,
}
2022-11-30 17:01:17 +01:00
AuthNKeyColumnChangeDate = Column {
name : projection . AuthNKeyChangeDateCol ,
table : authNKeyTable ,
}
2021-12-14 10:57:20 +01:00
AuthNKeyColumnResourceOwner = Column {
name : projection . AuthNKeyResourceOwnerCol ,
table : authNKeyTable ,
}
2022-03-23 09:02:39 +01:00
AuthNKeyColumnInstanceID = Column {
name : projection . AuthNKeyInstanceIDCol ,
table : authNKeyTable ,
}
2021-12-14 10:57:20 +01:00
AuthNKeyColumnAggregateID = Column {
name : projection . AuthNKeyAggregateIDCol ,
table : authNKeyTable ,
}
AuthNKeyColumnSequence = Column {
name : projection . AuthNKeySequenceCol ,
table : authNKeyTable ,
}
AuthNKeyColumnObjectID = Column {
name : projection . AuthNKeyObjectIDCol ,
table : authNKeyTable ,
}
AuthNKeyColumnExpiration = Column {
name : projection . AuthNKeyExpirationCol ,
table : authNKeyTable ,
}
AuthNKeyColumnIdentifier = Column {
name : projection . AuthNKeyIdentifierCol ,
table : authNKeyTable ,
}
AuthNKeyColumnPublicKey = Column {
name : projection . AuthNKeyPublicKeyCol ,
table : authNKeyTable ,
}
AuthNKeyColumnType = Column {
name : projection . AuthNKeyTypeCol ,
table : authNKeyTable ,
}
AuthNKeyColumnEnabled = Column {
name : projection . AuthNKeyEnabledCol ,
table : authNKeyTable ,
}
)
type AuthNKeys struct {
SearchResponse
AuthNKeys [ ] * AuthNKey
}
type AuthNKey struct {
ID string
2025-06-04 09:17:23 +02:00
AggregateID string
2021-12-14 10:57:20 +01:00
CreationDate time . Time
2022-11-30 17:01:17 +01:00
ChangeDate time . Time
2021-12-14 10:57:20 +01:00
ResourceOwner string
Sequence uint64
Expiration time . Time
Type domain . AuthNKeyType
}
2022-10-18 16:07:30 +01:00
type AuthNKeysData struct {
SearchResponse
AuthNKeysData [ ] * AuthNKeyData
}
type AuthNKeyData struct {
ID string
CreationDate time . Time
2022-11-30 17:01:17 +01:00
ChangeDate time . Time
2022-10-18 16:07:30 +01:00
ResourceOwner string
Sequence uint64
Expiration time . Time
Type domain . AuthNKeyType
Identifier string
PublicKey [ ] byte
}
2021-12-14 10:57:20 +01:00
type AuthNKeySearchQueries struct {
SearchRequest
Queries [ ] SearchQuery
}
func ( q * AuthNKeySearchQueries ) toQuery ( query sq . SelectBuilder ) sq . SelectBuilder {
query = q . SearchRequest . toQuery ( query )
for _ , q := range q . Queries {
query = q . toQuery ( query )
}
return query
}
2025-06-04 09:17:23 +02:00
type JoinFilter int
const (
JoinFilterUnspecified JoinFilter = iota
JoinFilterApp
JoinFilterUserMachine
)
// SearchAuthNKeys returns machine or app keys, depending on the join filter.
// If permissionCheck is nil, the keys are not filtered.
// If permissionCheck is not nil and the PermissionCheckV2 feature flag is false, the returned keys are filtered in-memory by the given permission check.
// If permissionCheck is not nil and the PermissionCheckV2 feature flag is true, the returned keys are filtered in the database.
func ( q * Queries ) SearchAuthNKeys ( ctx context . Context , queries * AuthNKeySearchQueries , filter JoinFilter , permissionCheck domain . PermissionCheck ) ( authNKeys * AuthNKeys , err error ) {
permissionCheckV2 := PermissionV2 ( ctx , permissionCheck )
keys , err := q . searchAuthNKeys ( ctx , queries , filter , permissionCheckV2 )
if err != nil {
return nil , err
}
if permissionCheck != nil && ! authz . GetFeatures ( ctx ) . PermissionCheckV2 {
keysCheckPermission ( ctx , keys , permissionCheck )
}
return keys , nil
}
func ( q * Queries ) searchAuthNKeys ( ctx context . Context , queries * AuthNKeySearchQueries , joinFilter JoinFilter , permissionCheckV2 bool ) ( authNKeys * AuthNKeys , 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 := prepareAuthNKeysQuery ( )
2021-12-14 10:57:20 +01:00
query = queries . toQuery ( query )
2025-06-04 09:17:23 +02:00
switch joinFilter {
case JoinFilterUnspecified :
// Select all authN keys
case JoinFilterApp :
joinCol := ProjectColumnID
query = query . Join ( joinCol . table . identifier ( ) + " ON " + AuthNKeyColumnIdentifier . identifier ( ) + " = " + joinCol . identifier ( ) )
case JoinFilterUserMachine :
joinCol := MachineUserIDCol
query = query . Join ( joinCol . table . identifier ( ) + " ON " + AuthNKeyColumnIdentifier . identifier ( ) + " = " + joinCol . identifier ( ) )
query = userPermissionCheckV2WithCustomColumns ( ctx , query , permissionCheckV2 , queries . Queries , AuthNKeyColumnResourceOwner , AuthNKeyColumnIdentifier )
}
2022-11-30 17:01:17 +01:00
eq := sq . Eq {
AuthNKeyColumnEnabled . identifier ( ) : true ,
AuthNKeyColumnInstanceID . identifier ( ) : authz . GetInstance ( ctx ) . InstanceID ( ) ,
}
stmt , args , err := query . Where ( eq ) . ToSql ( )
2021-12-14 10:57:20 +01:00
if err != nil {
2023-12-08 16:30:55 +02:00
return nil , zerrors . ThrowInvalidArgument ( err , "QUERY-SAf3f" , "Errors.Query.InvalidRequest" )
2021-12-14 10:57:20 +01:00
}
2023-08-22 14:49:02 +02:00
err = q . client . QueryContext ( ctx , func ( rows * sql . Rows ) error {
authNKeys , err = scan ( rows )
return err
} , stmt , args ... )
2021-12-14 10:57:20 +01:00
if err != nil {
2023-12-08 16:30:55 +02:00
return nil , zerrors . ThrowInternal ( err , "QUERY-Dbg53" , "Errors.Internal" )
2021-12-14 10:57:20 +01:00
}
2023-08-22 14:49:02 +02:00
2023-10-19 12:19:10 +02:00
authNKeys . State , err = q . latestState ( ctx , authNKeyTable )
2021-12-14 10:57:20 +01:00
return authNKeys , err
}
2023-11-21 14:11:38 +02:00
func ( q * Queries ) SearchAuthNKeysData ( ctx context . Context , queries * AuthNKeySearchQueries ) ( authNKeys * AuthNKeysData , 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 := prepareAuthNKeysDataQuery ( )
2022-10-18 16:07:30 +01:00
query = queries . toQuery ( query )
2022-11-30 17:01:17 +01:00
eq := sq . Eq {
AuthNKeyColumnEnabled . identifier ( ) : true ,
AuthNKeyColumnInstanceID . identifier ( ) : authz . GetInstance ( ctx ) . InstanceID ( ) ,
}
stmt , args , err := query . Where ( eq ) . ToSql ( )
2022-10-18 16:07:30 +01:00
if err != nil {
2023-12-08 16:30:55 +02:00
return nil , zerrors . ThrowInvalidArgument ( err , "QUERY-SAg3f" , "Errors.Query.InvalidRequest" )
2022-10-18 16:07:30 +01:00
}
2023-08-22 14:49:02 +02:00
err = q . client . QueryContext ( ctx , func ( rows * sql . Rows ) error {
authNKeys , err = scan ( rows )
return err
} , stmt , args ... )
2022-10-18 16:07:30 +01:00
if err != nil {
2023-12-08 16:30:55 +02:00
return nil , zerrors . ThrowInternal ( err , "QUERY-Dbi53" , "Errors.Internal" )
2022-10-18 16:07:30 +01:00
}
2023-10-19 12:19:10 +02:00
authNKeys . State , err = q . latestState ( ctx , authNKeyTable )
2022-10-18 16:07:30 +01:00
return authNKeys , err
}
2023-11-21 14:11:38 +02:00
func ( q * Queries ) GetAuthNKeyByID ( ctx context . Context , shouldTriggerBulk bool , id string , queries ... SearchQuery ) ( key * AuthNKey , 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 , "TriggerAuthNKeyProjection" )
2023-10-19 12:19:10 +02:00
ctx , err = projection . AuthNKeyProjection . 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 := prepareAuthNKeyQuery ( )
2021-12-14 10:57:20 +01:00
for _ , q := range queries {
query = q . toQuery ( query )
}
2022-11-30 17:01:17 +01:00
eq := sq . Eq {
AuthNKeyColumnID . identifier ( ) : id ,
AuthNKeyColumnEnabled . identifier ( ) : true ,
AuthNKeyColumnInstanceID . identifier ( ) : authz . GetInstance ( ctx ) . InstanceID ( ) ,
}
stmt , args , err := query . Where ( eq ) . ToSql ( )
2021-12-14 10:57:20 +01:00
if err != nil {
2023-12-08 16:30:55 +02:00
return nil , zerrors . ThrowInternal ( err , "QUERY-AGhg4" , "Errors.Query.SQLStatement" )
2021-12-14 10:57:20 +01:00
}
2023-08-22 14:49:02 +02:00
err = q . client . QueryRowContext ( ctx , func ( row * sql . Row ) error {
key , err = scan ( row )
return err
} , stmt , args ... )
return key , err
2021-12-14 10:57:20 +01:00
}
func NewAuthNKeyResourceOwnerQuery ( id string ) ( SearchQuery , error ) {
return NewTextQuery ( AuthNKeyColumnResourceOwner , id , TextEquals )
}
func NewAuthNKeyAggregateIDQuery ( id string ) ( SearchQuery , error ) {
return NewTextQuery ( AuthNKeyColumnAggregateID , id , TextEquals )
}
func NewAuthNKeyObjectIDQuery ( id string ) ( SearchQuery , error ) {
return NewTextQuery ( AuthNKeyColumnObjectID , id , TextEquals )
}
2025-06-04 09:17:23 +02:00
func NewAuthNKeyIDQuery ( id string ) ( SearchQuery , error ) {
return NewTextQuery ( AuthNKeyColumnID , id , TextEquals )
}
func NewAuthNKeyIdentifyerQuery ( id string ) ( SearchQuery , error ) {
return NewTextQuery ( AuthNKeyColumnIdentifier , id , TextEquals )
}
func NewAuthNKeyCreationDateQuery ( ts time . Time , compare TimestampComparison ) ( SearchQuery , error ) {
return NewTimestampQuery ( AuthNKeyColumnCreationDate , ts , compare )
}
func NewAuthNKeyExpirationDateDateQuery ( ts time . Time , compare TimestampComparison ) ( SearchQuery , error ) {
return NewTimestampQuery ( AuthNKeyColumnExpiration , ts , compare )
}
2024-09-11 12:04:09 +03:00
//go:embed authn_key_user.sql
var authNKeyUserQuery string
type AuthNKeyUser struct {
UserID string
ResourceOwner string
Username string
TokenType domain . OIDCTokenType
PublicKey [ ] byte
}
func ( q * Queries ) GetAuthNKeyUser ( ctx context . Context , keyID , userID string ) ( _ * AuthNKeyUser , err error ) {
ctx , span := tracing . NewSpan ( ctx )
defer func ( ) { span . EndWithError ( err ) } ( )
dst := new ( AuthNKeyUser )
err = q . client . QueryRowContext ( ctx , func ( row * sql . Row ) error {
return row . Scan (
& dst . UserID ,
& dst . ResourceOwner ,
& dst . Username ,
& dst . TokenType ,
& dst . PublicKey ,
)
} ,
authNKeyUserQuery ,
authz . GetInstance ( ctx ) . InstanceID ( ) ,
keyID , userID ,
)
if err != nil {
if errors . Is ( err , sql . ErrNoRows ) {
return nil , zerrors . ThrowNotFound ( err , "QUERY-Tha6f" , "Errors.AuthNKey.NotFound" )
}
return nil , zerrors . ThrowInternal ( err , "QUERY-aen2A" , "Errors.Internal" )
}
return dst , nil
}
2025-04-02 16:53:06 +02:00
func prepareAuthNKeysQuery ( ) ( sq . SelectBuilder , func ( rows * sql . Rows ) ( * AuthNKeys , error ) ) {
2025-06-04 09:17:23 +02:00
query := sq . Select (
AuthNKeyColumnID . identifier ( ) ,
AuthNKeyColumnAggregateID . identifier ( ) ,
AuthNKeyColumnCreationDate . identifier ( ) ,
AuthNKeyColumnChangeDate . identifier ( ) ,
AuthNKeyColumnResourceOwner . identifier ( ) ,
AuthNKeyColumnSequence . identifier ( ) ,
AuthNKeyColumnExpiration . identifier ( ) ,
AuthNKeyColumnType . identifier ( ) ,
countColumn . identifier ( ) ,
) . From ( authNKeyTable . identifier ( ) ) .
PlaceholderFormat ( sq . Dollar )
return query , func ( rows * sql . Rows ) ( * AuthNKeys , error ) {
authNKeys := make ( [ ] * AuthNKey , 0 )
var count uint64
for rows . Next ( ) {
authNKey := new ( AuthNKey )
err := rows . Scan (
& authNKey . ID ,
& authNKey . AggregateID ,
& authNKey . CreationDate ,
& authNKey . ChangeDate ,
& authNKey . ResourceOwner ,
& authNKey . Sequence ,
& authNKey . Expiration ,
& authNKey . Type ,
& count ,
)
if err != nil {
return nil , err
2021-12-14 10:57:20 +01:00
}
2025-06-04 09:17:23 +02:00
authNKeys = append ( authNKeys , authNKey )
}
2021-12-14 10:57:20 +01:00
2025-06-04 09:17:23 +02:00
if err := rows . Close ( ) ; err != nil {
return nil , zerrors . ThrowInternal ( err , "QUERY-Dgfn3" , "Errors.Query.CloseRows" )
2021-12-14 10:57:20 +01:00
}
2025-06-04 09:17:23 +02:00
return & AuthNKeys {
AuthNKeys : authNKeys ,
SearchResponse : SearchResponse {
Count : count ,
} ,
} , nil
}
2021-12-14 10:57:20 +01:00
}
2025-04-02 16:53:06 +02:00
func prepareAuthNKeyQuery ( ) ( sq . SelectBuilder , func ( row * sql . Row ) ( * AuthNKey , error ) ) {
2021-12-14 10:57:20 +01:00
return sq . Select (
AuthNKeyColumnID . identifier ( ) ,
AuthNKeyColumnCreationDate . identifier ( ) ,
2022-11-30 17:01:17 +01:00
AuthNKeyColumnChangeDate . identifier ( ) ,
2021-12-14 10:57:20 +01:00
AuthNKeyColumnResourceOwner . identifier ( ) ,
AuthNKeyColumnSequence . identifier ( ) ,
AuthNKeyColumnExpiration . identifier ( ) ,
AuthNKeyColumnType . identifier ( ) ,
2025-04-02 16:53:06 +02:00
) . From ( authNKeyTable . identifier ( ) ) .
2023-02-27 22:36:43 +01:00
PlaceholderFormat ( sq . Dollar ) ,
2021-12-14 10:57:20 +01:00
func ( row * sql . Row ) ( * AuthNKey , error ) {
authNKey := new ( AuthNKey )
err := row . Scan (
& authNKey . ID ,
& authNKey . CreationDate ,
2022-11-30 17:01:17 +01:00
& authNKey . ChangeDate ,
2021-12-14 10:57:20 +01:00
& authNKey . ResourceOwner ,
& authNKey . Sequence ,
& authNKey . Expiration ,
& authNKey . Type ,
)
if err != nil {
2023-12-08 16:30:55 +02:00
if errors . Is ( err , sql . ErrNoRows ) {
return nil , zerrors . ThrowNotFound ( err , "QUERY-Dgr3g" , "Errors.AuthNKey.NotFound" )
2021-12-14 10:57:20 +01:00
}
2023-12-08 16:30:55 +02:00
return nil , zerrors . ThrowInternal ( err , "QUERY-BGnbr" , "Errors.Internal" )
2021-12-14 10:57:20 +01:00
}
return authNKey , nil
}
}
2025-04-02 16:53:06 +02:00
func prepareAuthNKeysDataQuery ( ) ( sq . SelectBuilder , func ( rows * sql . Rows ) ( * AuthNKeysData , error ) ) {
2022-10-18 16:07:30 +01:00
return sq . Select (
AuthNKeyColumnID . identifier ( ) ,
AuthNKeyColumnCreationDate . identifier ( ) ,
2022-11-30 17:01:17 +01:00
AuthNKeyColumnChangeDate . identifier ( ) ,
2022-10-18 16:07:30 +01:00
AuthNKeyColumnResourceOwner . identifier ( ) ,
AuthNKeyColumnSequence . identifier ( ) ,
AuthNKeyColumnExpiration . identifier ( ) ,
AuthNKeyColumnType . identifier ( ) ,
AuthNKeyColumnIdentifier . identifier ( ) ,
AuthNKeyColumnPublicKey . identifier ( ) ,
countColumn . identifier ( ) ,
2025-04-02 16:53:06 +02:00
) . From ( authNKeyTable . identifier ( ) ) .
2023-02-27 22:36:43 +01:00
PlaceholderFormat ( sq . Dollar ) ,
2022-10-18 16:07:30 +01:00
func ( rows * sql . Rows ) ( * AuthNKeysData , error ) {
authNKeys := make ( [ ] * AuthNKeyData , 0 )
var count uint64
for rows . Next ( ) {
authNKey := new ( AuthNKeyData )
err := rows . Scan (
& authNKey . ID ,
& authNKey . CreationDate ,
2022-11-30 17:01:17 +01:00
& authNKey . ChangeDate ,
2022-10-18 16:07:30 +01:00
& authNKey . ResourceOwner ,
& authNKey . Sequence ,
& authNKey . Expiration ,
& authNKey . Type ,
& authNKey . Identifier ,
& authNKey . PublicKey ,
& count ,
)
if err != nil {
return nil , err
}
authNKeys = append ( authNKeys , authNKey )
}
if err := rows . Close ( ) ; err != nil {
2023-12-08 16:30:55 +02:00
return nil , zerrors . ThrowInternal ( err , "QUERY-Dgfn3" , "Errors.Query.CloseRows" )
2022-10-18 16:07:30 +01:00
}
return & AuthNKeysData {
AuthNKeysData : authNKeys ,
SearchResponse : SearchResponse {
Count : count ,
} ,
} , nil
}
}