mirror of
https://github.com/zitadel/zitadel.git
synced 2024-12-12 02:54:20 +00:00
ba9b807854
* get key by id and cache them
* userinfo from events for v2 tokens
* improve keyset caching
* concurrent token and client checks
* client and project in single query
* logging and otel
* drop owner_removed column on apps and authN tables
* userinfo and project roles in go routines
* get oidc user info from projections and add actions
* add avatar URL
* some cleanup
* pull oidc work branch
* remove storage from server
* add config flag for experimental introspection
* legacy introspection flag
* drop owner_removed column on user projections
* drop owner_removed column on useer_metadata
* query userinfo unit test
* query introspection client test
* add user_grants to the userinfo query
* handle PAT scopes
* bring triggers back
* test instance keys query
* add userinfo unit tests
* unit test keys
* go mod tidy
* solve some bugs
* fix missing preferred login name
* do not run triggers in go routines, they seem to deadlock
* initialize the trigger handlers late with a sync.OnceValue
* Revert "do not run triggers in go routines, they seem to deadlock"
This reverts commit 2a03da2127
.
* add missing translations
* chore: update go version for linting
* pin oidc version
* parse a global time location for query test
* fix linter complains
* upgrade go lint
* fix more linting issues
---------
Co-authored-by: Stefan Benz <46600784+stebenz@users.noreply.github.com>
899 lines
27 KiB
Go
899 lines
27 KiB
Go
package query
|
|
|
|
import (
|
|
"context"
|
|
"database/sql"
|
|
errs "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/errors"
|
|
"github.com/zitadel/zitadel/internal/eventstore/handler/v2"
|
|
"github.com/zitadel/zitadel/internal/query/projection"
|
|
"github.com/zitadel/zitadel/internal/telemetry/tracing"
|
|
)
|
|
|
|
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, errors.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, errors.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, errors.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, errors.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 "", errors.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 "", errors.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, errors.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, errors.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, errors.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, errors.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, errors.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, errors.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, errors.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 errs.Is(err, sql.ErrNoRows) {
|
|
return nil, errors.ThrowNotFound(err, "QUERY-pCP8P", "Errors.App.NotExisting")
|
|
}
|
|
return nil, errors.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 errs.Is(err, sql.ErrNoRows) {
|
|
return "", errors.ThrowNotFound(err, "QUERY-aKcc2", "Errors.Project.NotExisting")
|
|
}
|
|
return "", errors.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 errs.Is(err, sql.ErrNoRows) {
|
|
return nil, errors.ThrowNotFound(err, "QUERY-yxTMh", "Errors.Project.NotFound")
|
|
}
|
|
return nil, errors.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, errors.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, errors.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),
|
|
}
|
|
}
|