package query import ( "context" "database/sql" "errors" "time" sq "github.com/Masterminds/squirrel" "github.com/zitadel/logging" "github.com/zitadel/zitadel/internal/api/authz" "github.com/zitadel/zitadel/internal/api/call" "github.com/zitadel/zitadel/internal/database" "github.com/zitadel/zitadel/internal/domain" "github.com/zitadel/zitadel/internal/eventstore/handler/v2" "github.com/zitadel/zitadel/internal/query/projection" "github.com/zitadel/zitadel/internal/telemetry/tracing" "github.com/zitadel/zitadel/internal/zerrors" ) type Apps struct { SearchResponse Apps []*App } type App struct { ID string CreationDate time.Time ChangeDate time.Time ResourceOwner string State domain.AppState Sequence uint64 ProjectID string Name string OIDCConfig *OIDCApp SAMLConfig *SAMLApp APIConfig *APIApp } type OIDCApp struct { RedirectURIs database.TextArray[string] ResponseTypes database.Array[domain.OIDCResponseType] GrantTypes database.Array[domain.OIDCGrantType] AppType domain.OIDCApplicationType ClientID string AuthMethodType domain.OIDCAuthMethodType PostLogoutRedirectURIs database.TextArray[string] Version domain.OIDCVersion ComplianceProblems database.TextArray[string] IsDevMode bool AccessTokenType domain.OIDCTokenType AssertAccessTokenRole bool AssertIDTokenRole bool AssertIDTokenUserinfo bool ClockSkew time.Duration AdditionalOrigins database.TextArray[string] AllowedOrigins database.TextArray[string] SkipNativeAppSuccessPage bool } type SAMLApp struct { Metadata []byte MetadataURL string EntityID string } type APIApp struct { ClientID string AuthMethodType domain.APIAuthMethodType } type AppSearchQueries struct { SearchRequest Queries []SearchQuery } func (q *AppSearchQueries) toQuery(query sq.SelectBuilder) sq.SelectBuilder { query = q.SearchRequest.toQuery(query) for _, q := range q.Queries { query = q.toQuery(query) } return query } var ( appsTable = table{ name: projection.AppProjectionTable, instanceIDCol: projection.AppColumnInstanceID, } AppColumnID = Column{ name: projection.AppColumnID, table: appsTable, } AppColumnName = Column{ name: projection.AppColumnName, table: appsTable, } AppColumnProjectID = Column{ name: projection.AppColumnProjectID, table: appsTable, } AppColumnCreationDate = Column{ name: projection.AppColumnCreationDate, table: appsTable, } AppColumnChangeDate = Column{ name: projection.AppColumnChangeDate, table: appsTable, } AppColumnResourceOwner = Column{ name: projection.AppColumnResourceOwner, table: appsTable, } AppColumnInstanceID = Column{ name: projection.AppColumnInstanceID, table: appsTable, } AppColumnState = Column{ name: projection.AppColumnState, table: appsTable, } AppColumnSequence = Column{ name: projection.AppColumnSequence, table: appsTable, } ) var ( appSAMLConfigsTable = table{ name: projection.AppSAMLTable, instanceIDCol: projection.AppSAMLConfigColumnInstanceID, } AppSAMLConfigColumnAppID = Column{ name: projection.AppSAMLConfigColumnAppID, table: appSAMLConfigsTable, } AppSAMLConfigColumnEntityID = Column{ name: projection.AppSAMLConfigColumnEntityID, table: appSAMLConfigsTable, } AppSAMLConfigColumnMetadata = Column{ name: projection.AppSAMLConfigColumnMetadata, table: appSAMLConfigsTable, } AppSAMLConfigColumnMetadataURL = Column{ name: projection.AppSAMLConfigColumnMetadataURL, table: appSAMLConfigsTable, } ) var ( appAPIConfigsTable = table{ name: projection.AppAPITable, instanceIDCol: projection.AppAPIConfigColumnInstanceID, } AppAPIConfigColumnAppID = Column{ name: projection.AppAPIConfigColumnAppID, table: appAPIConfigsTable, } AppAPIConfigColumnClientID = Column{ name: projection.AppAPIConfigColumnClientID, table: appAPIConfigsTable, } AppAPIConfigColumnAuthMethod = Column{ name: projection.AppAPIConfigColumnAuthMethod, table: appAPIConfigsTable, } ) var ( appOIDCConfigsTable = table{ name: projection.AppOIDCTable, instanceIDCol: projection.AppOIDCConfigColumnInstanceID, } AppOIDCConfigColumnAppID = Column{ name: projection.AppOIDCConfigColumnAppID, table: appOIDCConfigsTable, } AppOIDCConfigColumnVersion = Column{ name: projection.AppOIDCConfigColumnVersion, table: appOIDCConfigsTable, } AppOIDCConfigColumnClientID = Column{ name: projection.AppOIDCConfigColumnClientID, table: appOIDCConfigsTable, } AppOIDCConfigColumnRedirectUris = Column{ name: projection.AppOIDCConfigColumnRedirectUris, table: appOIDCConfigsTable, } AppOIDCConfigColumnResponseTypes = Column{ name: projection.AppOIDCConfigColumnResponseTypes, table: appOIDCConfigsTable, } AppOIDCConfigColumnGrantTypes = Column{ name: projection.AppOIDCConfigColumnGrantTypes, table: appOIDCConfigsTable, } AppOIDCConfigColumnApplicationType = Column{ name: projection.AppOIDCConfigColumnApplicationType, table: appOIDCConfigsTable, } AppOIDCConfigColumnAuthMethodType = Column{ name: projection.AppOIDCConfigColumnAuthMethodType, table: appOIDCConfigsTable, } AppOIDCConfigColumnPostLogoutRedirectUris = Column{ name: projection.AppOIDCConfigColumnPostLogoutRedirectUris, table: appOIDCConfigsTable, } AppOIDCConfigColumnDevMode = Column{ name: projection.AppOIDCConfigColumnDevMode, table: appOIDCConfigsTable, } AppOIDCConfigColumnAccessTokenType = Column{ name: projection.AppOIDCConfigColumnAccessTokenType, table: appOIDCConfigsTable, } AppOIDCConfigColumnAccessTokenRoleAssertion = Column{ name: projection.AppOIDCConfigColumnAccessTokenRoleAssertion, table: appOIDCConfigsTable, } AppOIDCConfigColumnIDTokenRoleAssertion = Column{ name: projection.AppOIDCConfigColumnIDTokenRoleAssertion, table: appOIDCConfigsTable, } AppOIDCConfigColumnIDTokenUserinfoAssertion = Column{ name: projection.AppOIDCConfigColumnIDTokenUserinfoAssertion, table: appOIDCConfigsTable, } AppOIDCConfigColumnClockSkew = Column{ name: projection.AppOIDCConfigColumnClockSkew, table: appOIDCConfigsTable, } AppOIDCConfigColumnAdditionalOrigins = Column{ name: projection.AppOIDCConfigColumnAdditionalOrigins, table: appOIDCConfigsTable, } AppOIDCConfigColumnSkipNativeAppSuccessPage = Column{ name: projection.AppOIDCConfigColumnSkipNativeAppSuccessPage, table: appOIDCConfigsTable, } ) func (q *Queries) AppByProjectAndAppID(ctx context.Context, shouldTriggerBulk bool, projectID, appID string) (app *App, err error) { ctx, span := tracing.NewSpan(ctx) defer func() { span.EndWithError(err) }() if shouldTriggerBulk { _, traceSpan := tracing.NewNamedSpan(ctx, "TriggerAppProjection") ctx, err = projection.AppProjection.Trigger(ctx, handler.WithAwaitRunning()) logging.OnError(err).Debug("trigger failed") traceSpan.EndWithError(err) } stmt, scan := prepareAppQuery(ctx, q.client) eq := sq.Eq{ AppColumnID.identifier(): appID, AppColumnProjectID.identifier(): projectID, AppColumnInstanceID.identifier(): authz.GetInstance(ctx).InstanceID(), } query, args, err := stmt.Where(eq).ToSql() if err != nil { return nil, zerrors.ThrowInternal(err, "QUERY-AFDgg", "Errors.Query.SQLStatement") } err = q.client.QueryRowContext(ctx, func(row *sql.Row) error { app, err = scan(row) return err }, query, args...) return app, err } func (q *Queries) AppByID(ctx context.Context, appID string) (app *App, err error) { ctx, span := tracing.NewSpan(ctx) defer func() { span.EndWithError(err) }() stmt, scan := prepareAppQuery(ctx, q.client) eq := sq.Eq{ AppColumnID.identifier(): appID, AppColumnInstanceID.identifier(): authz.GetInstance(ctx).InstanceID(), } query, args, err := stmt.Where(eq).ToSql() if err != nil { return nil, zerrors.ThrowInternal(err, "QUERY-immt9", "Errors.Query.SQLStatement") } err = q.client.QueryRowContext(ctx, func(row *sql.Row) error { app, err = scan(row) return err }, query, args...) return app, err } func (q *Queries) AppBySAMLEntityID(ctx context.Context, entityID string) (app *App, err error) { ctx, span := tracing.NewSpan(ctx) defer func() { span.EndWithError(err) }() stmt, scan := prepareAppQuery(ctx, q.client) eq := sq.Eq{ AppSAMLConfigColumnEntityID.identifier(): entityID, AppColumnInstanceID.identifier(): authz.GetInstance(ctx).InstanceID(), } query, args, err := stmt.Where(eq).ToSql() if err != nil { return nil, zerrors.ThrowInternal(err, "QUERY-JgUop", "Errors.Query.SQLStatement") } err = q.client.QueryRowContext(ctx, func(row *sql.Row) error { app, err = scan(row) return err }, query, args...) return app, err } func (q *Queries) ProjectByClientID(ctx context.Context, appID string) (project *Project, err error) { ctx, span := tracing.NewSpan(ctx) defer func() { span.EndWithError(err) }() stmt, scan := prepareProjectByAppQuery(ctx, q.client) eq := sq.Eq{AppColumnInstanceID.identifier(): authz.GetInstance(ctx).InstanceID()} query, args, err := stmt.Where(sq.And{ eq, sq.Or{ sq.Eq{AppOIDCConfigColumnClientID.identifier(): appID}, sq.Eq{AppAPIConfigColumnClientID.identifier(): appID}, sq.Eq{AppSAMLConfigColumnAppID.identifier(): appID}, }, }).ToSql() if err != nil { return nil, zerrors.ThrowInternal(err, "QUERY-XhJi3", "Errors.Query.SQLStatement") } err = q.client.QueryRowContext(ctx, func(row *sql.Row) error { project, err = scan(row) return err }, query, args...) return project, err } func (q *Queries) ProjectIDFromOIDCClientID(ctx context.Context, appID string) (id string, err error) { ctx, span := tracing.NewSpan(ctx) defer func() { span.EndWithError(err) }() stmt, scan := prepareProjectIDByAppQuery(ctx, q.client) eq := sq.Eq{ AppOIDCConfigColumnClientID.identifier(): appID, AppColumnInstanceID.identifier(): authz.GetInstance(ctx).InstanceID(), } query, args, err := stmt.Where(eq).ToSql() if err != nil { return "", zerrors.ThrowInternal(err, "QUERY-7d92U", "Errors.Query.SQLStatement") } err = q.client.QueryRowContext(ctx, func(row *sql.Row) error { id, err = scan(row) return err }, query, args...) return id, err } func (q *Queries) ProjectIDFromClientID(ctx context.Context, appID string) (id string, err error) { ctx, span := tracing.NewSpan(ctx) defer func() { span.EndWithError(err) }() stmt, scan := prepareProjectIDByAppQuery(ctx, q.client) eq := sq.Eq{AppColumnInstanceID.identifier(): authz.GetInstance(ctx).InstanceID()} where := sq.And{ eq, sq.Or{ sq.Eq{AppOIDCConfigColumnClientID.identifier(): appID}, sq.Eq{AppAPIConfigColumnClientID.identifier(): appID}, sq.Eq{AppSAMLConfigColumnAppID.identifier(): appID}, }, } query, args, err := stmt.Where(where).ToSql() if err != nil { return "", zerrors.ThrowInternal(err, "QUERY-SDfg3", "Errors.Query.SQLStatement") } err = q.client.QueryRowContext(ctx, func(row *sql.Row) error { id, err = scan(row) return err }, query, args...) return id, err } func (q *Queries) ProjectByOIDCClientID(ctx context.Context, id string) (project *Project, err error) { ctx, span := tracing.NewSpan(ctx) defer func() { span.EndWithError(err) }() stmt, scan := prepareProjectByAppQuery(ctx, q.client) eq := sq.Eq{ AppOIDCConfigColumnClientID.identifier(): id, AppColumnInstanceID.identifier(): authz.GetInstance(ctx).InstanceID(), } query, args, err := stmt.Where(eq).ToSql() if err != nil { return nil, zerrors.ThrowInternal(err, "QUERY-XhJi4", "Errors.Query.SQLStatement") } err = q.client.QueryRowContext(ctx, func(row *sql.Row) error { project, err = scan(row) return err }, query, args...) return project, err } func (q *Queries) AppByOIDCClientID(ctx context.Context, clientID string) (app *App, err error) { ctx, span := tracing.NewSpan(ctx) defer func() { span.EndWithError(err) }() stmt, scan := prepareAppQuery(ctx, q.client) eq := sq.Eq{ AppOIDCConfigColumnClientID.identifier(): clientID, AppColumnInstanceID.identifier(): authz.GetInstance(ctx).InstanceID(), } query, args, err := stmt.Where(eq).ToSql() if err != nil { return nil, zerrors.ThrowInternal(err, "QUERY-JgVop", "Errors.Query.SQLStatement") } err = q.client.QueryRowContext(ctx, func(row *sql.Row) error { app, err = scan(row) return err }, query, args...) return app, err } func (q *Queries) AppByClientID(ctx context.Context, clientID string) (app *App, err error) { ctx, span := tracing.NewSpan(ctx) defer func() { span.EndWithError(err) }() stmt, scan := prepareAppQuery(ctx, q.client) eq := sq.Eq{AppColumnInstanceID.identifier(): authz.GetInstance(ctx).InstanceID()} query, args, err := stmt.Where(sq.And{ eq, sq.Or{ sq.Eq{AppOIDCConfigColumnClientID.identifier(): clientID}, sq.Eq{AppAPIConfigColumnClientID.identifier(): clientID}, }, }).ToSql() if err != nil { return nil, zerrors.ThrowInternal(err, "QUERY-Dfge2", "Errors.Query.SQLStatement") } err = q.client.QueryRowContext(ctx, func(row *sql.Row) error { app, err = scan(row) return err }, query, args...) return app, err } func (q *Queries) SearchApps(ctx context.Context, queries *AppSearchQueries, withOwnerRemoved bool) (apps *Apps, err error) { ctx, span := tracing.NewSpan(ctx) defer func() { span.EndWithError(err) }() query, scan := prepareAppsQuery(ctx, q.client) eq := sq.Eq{AppColumnInstanceID.identifier(): authz.GetInstance(ctx).InstanceID()} stmt, args, err := queries.toQuery(query).Where(eq).ToSql() if err != nil { return nil, zerrors.ThrowInvalidArgument(err, "QUERY-fajp8", "Errors.Query.InvalidRequest") } err = q.client.QueryContext(ctx, func(rows *sql.Rows) error { apps, err = scan(rows) return err }, stmt, args...) if err != nil { return nil, zerrors.ThrowInternal(err, "QUERY-aJnZL", "Errors.Internal") } apps.State, err = q.latestState(ctx, appsTable) return apps, err } func (q *Queries) SearchClientIDs(ctx context.Context, queries *AppSearchQueries) (ids []string, err error) { ctx, span := tracing.NewSpan(ctx) defer func() { span.EndWithError(err) }() query, scan := prepareClientIDsQuery(ctx, q.client) eq := sq.Eq{AppColumnInstanceID.identifier(): authz.GetInstance(ctx).InstanceID()} stmt, args, err := queries.toQuery(query).Where(eq).ToSql() if err != nil { return nil, zerrors.ThrowInvalidArgument(err, "QUERY-fajp8", "Errors.Query.InvalidRequest") } err = q.client.QueryContext(ctx, func(rows *sql.Rows) error { ids, err = scan(rows) return err }, stmt, args...) if err != nil { return nil, zerrors.ThrowInternal(err, "QUERY-aJnZL", "Errors.Internal") } return ids, nil } func NewAppNameSearchQuery(method TextComparison, value string) (SearchQuery, error) { return NewTextQuery(AppColumnName, value, method) } func NewAppProjectIDSearchQuery(id string) (SearchQuery, error) { return NewTextQuery(AppColumnProjectID, id, TextEquals) } func prepareAppQuery(ctx context.Context, db prepareDatabase) (sq.SelectBuilder, func(*sql.Row) (*App, error)) { return sq.Select( AppColumnID.identifier(), AppColumnName.identifier(), AppColumnProjectID.identifier(), AppColumnCreationDate.identifier(), AppColumnChangeDate.identifier(), AppColumnResourceOwner.identifier(), AppColumnState.identifier(), AppColumnSequence.identifier(), AppAPIConfigColumnAppID.identifier(), AppAPIConfigColumnClientID.identifier(), AppAPIConfigColumnAuthMethod.identifier(), AppOIDCConfigColumnAppID.identifier(), AppOIDCConfigColumnVersion.identifier(), AppOIDCConfigColumnClientID.identifier(), AppOIDCConfigColumnRedirectUris.identifier(), AppOIDCConfigColumnResponseTypes.identifier(), AppOIDCConfigColumnGrantTypes.identifier(), AppOIDCConfigColumnApplicationType.identifier(), AppOIDCConfigColumnAuthMethodType.identifier(), AppOIDCConfigColumnPostLogoutRedirectUris.identifier(), AppOIDCConfigColumnDevMode.identifier(), AppOIDCConfigColumnAccessTokenType.identifier(), AppOIDCConfigColumnAccessTokenRoleAssertion.identifier(), AppOIDCConfigColumnIDTokenRoleAssertion.identifier(), AppOIDCConfigColumnIDTokenUserinfoAssertion.identifier(), AppOIDCConfigColumnClockSkew.identifier(), AppOIDCConfigColumnAdditionalOrigins.identifier(), AppOIDCConfigColumnSkipNativeAppSuccessPage.identifier(), AppSAMLConfigColumnAppID.identifier(), AppSAMLConfigColumnEntityID.identifier(), AppSAMLConfigColumnMetadata.identifier(), AppSAMLConfigColumnMetadataURL.identifier(), ).From(appsTable.identifier()). LeftJoin(join(AppAPIConfigColumnAppID, AppColumnID)). LeftJoin(join(AppOIDCConfigColumnAppID, AppColumnID)). LeftJoin(join(AppSAMLConfigColumnAppID, AppColumnID) + db.Timetravel(call.Took(ctx))). PlaceholderFormat(sq.Dollar), func(row *sql.Row) (*App, error) { app := new(App) var ( apiConfig = sqlAPIConfig{} oidcConfig = sqlOIDCConfig{} samlConfig = sqlSAMLConfig{} ) err := row.Scan( &app.ID, &app.Name, &app.ProjectID, &app.CreationDate, &app.ChangeDate, &app.ResourceOwner, &app.State, &app.Sequence, &apiConfig.appID, &apiConfig.clientID, &apiConfig.authMethod, &oidcConfig.appID, &oidcConfig.version, &oidcConfig.clientID, &oidcConfig.redirectUris, &oidcConfig.responseTypes, &oidcConfig.grantTypes, &oidcConfig.applicationType, &oidcConfig.authMethodType, &oidcConfig.postLogoutRedirectUris, &oidcConfig.devMode, &oidcConfig.accessTokenType, &oidcConfig.accessTokenRoleAssertion, &oidcConfig.iDTokenRoleAssertion, &oidcConfig.iDTokenUserinfoAssertion, &oidcConfig.clockSkew, &oidcConfig.additionalOrigins, &oidcConfig.skipNativeAppSuccessPage, &samlConfig.appID, &samlConfig.entityID, &samlConfig.metadata, &samlConfig.metadataURL, ) if err != nil { if errors.Is(err, sql.ErrNoRows) { return nil, zerrors.ThrowNotFound(err, "QUERY-pCP8P", "Errors.App.NotExisting") } return nil, zerrors.ThrowInternal(err, "QUERY-4SJlx", "Errors.Internal") } apiConfig.set(app) oidcConfig.set(app) samlConfig.set(app) return app, nil } } func prepareProjectIDByAppQuery(ctx context.Context, db prepareDatabase) (sq.SelectBuilder, func(*sql.Row) (projectID string, err error)) { return sq.Select( AppColumnProjectID.identifier(), ).From(appsTable.identifier()). LeftJoin(join(AppAPIConfigColumnAppID, AppColumnID)). LeftJoin(join(AppOIDCConfigColumnAppID, AppColumnID)). LeftJoin(join(AppSAMLConfigColumnAppID, AppColumnID) + db.Timetravel(call.Took(ctx))). PlaceholderFormat(sq.Dollar), func(row *sql.Row) (projectID string, err error) { err = row.Scan( &projectID, ) if err != nil { if errors.Is(err, sql.ErrNoRows) { return "", zerrors.ThrowNotFound(err, "QUERY-aKcc2", "Errors.Project.NotExisting") } return "", zerrors.ThrowInternal(err, "QUERY-3A5TG", "Errors.Internal") } return projectID, nil } } func prepareProjectByAppQuery(ctx context.Context, db prepareDatabase) (sq.SelectBuilder, func(*sql.Row) (*Project, error)) { return sq.Select( ProjectColumnID.identifier(), ProjectColumnCreationDate.identifier(), ProjectColumnChangeDate.identifier(), ProjectColumnResourceOwner.identifier(), ProjectColumnState.identifier(), ProjectColumnSequence.identifier(), ProjectColumnName.identifier(), ProjectColumnProjectRoleAssertion.identifier(), ProjectColumnProjectRoleCheck.identifier(), ProjectColumnHasProjectCheck.identifier(), ProjectColumnPrivateLabelingSetting.identifier(), ).From(projectsTable.identifier()). Join(join(AppColumnProjectID, ProjectColumnID)). LeftJoin(join(AppAPIConfigColumnAppID, AppColumnID)). LeftJoin(join(AppOIDCConfigColumnAppID, AppColumnID)). LeftJoin(join(AppSAMLConfigColumnAppID, AppColumnID) + db.Timetravel(call.Took(ctx))). PlaceholderFormat(sq.Dollar), func(row *sql.Row) (*Project, error) { p := new(Project) err := row.Scan( &p.ID, &p.CreationDate, &p.ChangeDate, &p.ResourceOwner, &p.State, &p.Sequence, &p.Name, &p.ProjectRoleAssertion, &p.ProjectRoleCheck, &p.HasProjectCheck, &p.PrivateLabelingSetting, ) if err != nil { if errors.Is(err, sql.ErrNoRows) { return nil, zerrors.ThrowNotFound(err, "QUERY-yxTMh", "Errors.Project.NotFound") } return nil, zerrors.ThrowInternal(err, "QUERY-dj2FF", "Errors.Internal") } return p, nil } } func prepareAppsQuery(ctx context.Context, db prepareDatabase) (sq.SelectBuilder, func(*sql.Rows) (*Apps, error)) { return sq.Select( AppColumnID.identifier(), AppColumnName.identifier(), AppColumnProjectID.identifier(), AppColumnCreationDate.identifier(), AppColumnChangeDate.identifier(), AppColumnResourceOwner.identifier(), AppColumnState.identifier(), AppColumnSequence.identifier(), AppAPIConfigColumnAppID.identifier(), AppAPIConfigColumnClientID.identifier(), AppAPIConfigColumnAuthMethod.identifier(), AppOIDCConfigColumnAppID.identifier(), AppOIDCConfigColumnVersion.identifier(), AppOIDCConfigColumnClientID.identifier(), AppOIDCConfigColumnRedirectUris.identifier(), AppOIDCConfigColumnResponseTypes.identifier(), AppOIDCConfigColumnGrantTypes.identifier(), AppOIDCConfigColumnApplicationType.identifier(), AppOIDCConfigColumnAuthMethodType.identifier(), AppOIDCConfigColumnPostLogoutRedirectUris.identifier(), AppOIDCConfigColumnDevMode.identifier(), AppOIDCConfigColumnAccessTokenType.identifier(), AppOIDCConfigColumnAccessTokenRoleAssertion.identifier(), AppOIDCConfigColumnIDTokenRoleAssertion.identifier(), AppOIDCConfigColumnIDTokenUserinfoAssertion.identifier(), AppOIDCConfigColumnClockSkew.identifier(), AppOIDCConfigColumnAdditionalOrigins.identifier(), AppOIDCConfigColumnSkipNativeAppSuccessPage.identifier(), AppSAMLConfigColumnAppID.identifier(), AppSAMLConfigColumnEntityID.identifier(), AppSAMLConfigColumnMetadata.identifier(), AppSAMLConfigColumnMetadataURL.identifier(), countColumn.identifier(), ).From(appsTable.identifier()). LeftJoin(join(AppAPIConfigColumnAppID, AppColumnID)). LeftJoin(join(AppOIDCConfigColumnAppID, AppColumnID)). LeftJoin(join(AppSAMLConfigColumnAppID, AppColumnID) + db.Timetravel(call.Took(ctx))). PlaceholderFormat(sq.Dollar), func(row *sql.Rows) (*Apps, error) { apps := &Apps{Apps: []*App{}} for row.Next() { app := new(App) var ( apiConfig = sqlAPIConfig{} oidcConfig = sqlOIDCConfig{} samlConfig = sqlSAMLConfig{} ) err := row.Scan( &app.ID, &app.Name, &app.ProjectID, &app.CreationDate, &app.ChangeDate, &app.ResourceOwner, &app.State, &app.Sequence, &apiConfig.appID, &apiConfig.clientID, &apiConfig.authMethod, &oidcConfig.appID, &oidcConfig.version, &oidcConfig.clientID, &oidcConfig.redirectUris, &oidcConfig.responseTypes, &oidcConfig.grantTypes, &oidcConfig.applicationType, &oidcConfig.authMethodType, &oidcConfig.postLogoutRedirectUris, &oidcConfig.devMode, &oidcConfig.accessTokenType, &oidcConfig.accessTokenRoleAssertion, &oidcConfig.iDTokenRoleAssertion, &oidcConfig.iDTokenUserinfoAssertion, &oidcConfig.clockSkew, &oidcConfig.additionalOrigins, &oidcConfig.skipNativeAppSuccessPage, &samlConfig.appID, &samlConfig.entityID, &samlConfig.metadata, &samlConfig.metadataURL, &apps.Count, ) if err != nil { return nil, zerrors.ThrowInternal(err, "QUERY-XGWAX", "Errors.Internal") } apiConfig.set(app) oidcConfig.set(app) samlConfig.set(app) apps.Apps = append(apps.Apps, app) } return apps, nil } } func prepareClientIDsQuery(ctx context.Context, db prepareDatabase) (sq.SelectBuilder, func(*sql.Rows) ([]string, error)) { return sq.Select( AppAPIConfigColumnClientID.identifier(), AppOIDCConfigColumnClientID.identifier(), ).From(appsTable.identifier()). LeftJoin(join(AppAPIConfigColumnAppID, AppColumnID)). LeftJoin(join(AppOIDCConfigColumnAppID, AppColumnID) + db.Timetravel(call.Took(ctx))). PlaceholderFormat(sq.Dollar), func(rows *sql.Rows) ([]string, error) { ids := database.TextArray[string]{} for rows.Next() { var apiID sql.NullString var oidcID sql.NullString if err := rows.Scan( &apiID, &oidcID, ); err != nil { return nil, zerrors.ThrowInternal(err, "QUERY-0R2Nw", "Errors.Internal") } if apiID.Valid { ids = append(ids, apiID.String) } else if oidcID.Valid { ids = append(ids, oidcID.String) } } return ids, nil } } type sqlOIDCConfig struct { appID sql.NullString version sql.NullInt32 clientID sql.NullString redirectUris database.TextArray[string] applicationType sql.NullInt16 authMethodType sql.NullInt16 postLogoutRedirectUris database.TextArray[string] devMode sql.NullBool accessTokenType sql.NullInt16 accessTokenRoleAssertion sql.NullBool iDTokenRoleAssertion sql.NullBool iDTokenUserinfoAssertion sql.NullBool clockSkew sql.NullInt64 additionalOrigins database.TextArray[string] responseTypes database.Array[domain.OIDCResponseType] grantTypes database.Array[domain.OIDCGrantType] skipNativeAppSuccessPage sql.NullBool } func (c sqlOIDCConfig) set(app *App) { if !c.appID.Valid { return } app.OIDCConfig = &OIDCApp{ Version: domain.OIDCVersion(c.version.Int32), ClientID: c.clientID.String, RedirectURIs: c.redirectUris, AppType: domain.OIDCApplicationType(c.applicationType.Int16), AuthMethodType: domain.OIDCAuthMethodType(c.authMethodType.Int16), PostLogoutRedirectURIs: c.postLogoutRedirectUris, IsDevMode: c.devMode.Bool, AccessTokenType: domain.OIDCTokenType(c.accessTokenType.Int16), AssertAccessTokenRole: c.accessTokenRoleAssertion.Bool, AssertIDTokenRole: c.iDTokenRoleAssertion.Bool, AssertIDTokenUserinfo: c.iDTokenUserinfoAssertion.Bool, ClockSkew: time.Duration(c.clockSkew.Int64), AdditionalOrigins: c.additionalOrigins, ResponseTypes: c.responseTypes, GrantTypes: c.grantTypes, SkipNativeAppSuccessPage: c.skipNativeAppSuccessPage.Bool, } compliance := domain.GetOIDCCompliance(app.OIDCConfig.Version, app.OIDCConfig.AppType, app.OIDCConfig.GrantTypes, app.OIDCConfig.ResponseTypes, app.OIDCConfig.AuthMethodType, app.OIDCConfig.RedirectURIs) app.OIDCConfig.ComplianceProblems = compliance.Problems var err error app.OIDCConfig.AllowedOrigins, err = domain.OIDCOriginAllowList(app.OIDCConfig.RedirectURIs, app.OIDCConfig.AdditionalOrigins) logging.LogWithFields("app", app.ID).OnError(err).Warn("unable to set allowed origins") } type sqlSAMLConfig struct { appID sql.NullString entityID sql.NullString metadataURL sql.NullString metadata []byte } func (c sqlSAMLConfig) set(app *App) { if !c.appID.Valid { return } app.SAMLConfig = &SAMLApp{ MetadataURL: c.metadataURL.String, Metadata: c.metadata, EntityID: c.entityID.String, } } type sqlAPIConfig struct { appID sql.NullString clientID sql.NullString authMethod sql.NullInt16 } func (c sqlAPIConfig) set(app *App) { if !c.appID.Valid { return } app.APIConfig = &APIApp{ ClientID: c.clientID.String, AuthMethodType: domain.APIAuthMethodType(c.authMethod.Int16), } }