feat: specify login UI version on instance and apps (#9071)

# Which Problems Are Solved

To be able to migrate or test the new login UI, admins might want to
(temporarily) switch individual apps.
At a later point admin might want to make sure all applications use the
new login UI.

# How the Problems Are Solved

- Added a feature flag `` on instance level to require all apps to use
the new login and provide an optional base url.
- if the flag is enabled, all (OIDC) applications will automatically use
the v2 login.
  - if disabled, applications can decide based on their configuration
- Added an option on OIDC apps to use the new login UI and an optional
base url.
- Removed the requirement to use `x-zitadel-login-client` to be
redirected to the login V2 and retrieve created authrequest and link
them to SSO sessions.
- Added a new "IAM_LOGIN_CLIENT" role to allow management of users,
sessions, grants and more without `x-zitadel-login-client`.

# Additional Changes

None

# Additional Context

closes https://github.com/zitadel/zitadel/issues/8702
This commit is contained in:
Livio Spring
2024-12-19 10:37:46 +01:00
committed by GitHub
parent b5e92a6144
commit 50d2b26a28
89 changed files with 1670 additions and 321 deletions

View File

@@ -60,6 +60,8 @@ type OIDCApp struct {
AllowedOrigins database.TextArray[string]
SkipNativeAppSuccessPage bool
BackChannelLogoutURI string
LoginVersion domain.LoginVersion
LoginBaseURI *string
}
type SAMLApp struct {
@@ -180,6 +182,10 @@ var (
name: projection.AppOIDCConfigColumnAppID,
table: appOIDCConfigsTable,
}
AppOIDCConfigColumnInstanceID = Column{
name: projection.AppOIDCConfigColumnInstanceID,
table: appOIDCConfigsTable,
}
AppOIDCConfigColumnVersion = Column{
name: projection.AppOIDCConfigColumnVersion,
table: appOIDCConfigsTable,
@@ -248,6 +254,14 @@ var (
name: projection.AppOIDCConfigColumnBackChannelLogoutURI,
table: appOIDCConfigsTable,
}
AppOIDCConfigColumnLoginVersion = Column{
name: projection.AppOIDCConfigColumnLoginVersion,
table: appOIDCConfigsTable,
}
AppOIDCConfigColumnLoginBaseURI = Column{
name: projection.AppOIDCConfigColumnLoginBaseURI,
table: appOIDCConfigsTable,
}
)
func (q *Queries) AppByProjectAndAppID(ctx context.Context, shouldTriggerBulk bool, projectID, appID string) (app *App, err error) {
@@ -501,6 +515,30 @@ func (q *Queries) SearchClientIDs(ctx context.Context, queries *AppSearchQueries
return ids, nil
}
func (q *Queries) OIDCClientLoginVersion(ctx context.Context, clientID string) (loginVersion domain.LoginVersion, err error) {
ctx, span := tracing.NewSpan(ctx)
defer func() { span.EndWithError(err) }()
query, scan := prepareLoginVersionByClientID(ctx, q.client)
eq := sq.Eq{
AppOIDCConfigColumnInstanceID.identifier(): authz.GetInstance(ctx).InstanceID(),
AppOIDCConfigColumnClientID.identifier(): clientID,
}
stmt, args, err := query.Where(eq).ToSql()
if err != nil {
return domain.LoginVersionUnspecified, zerrors.ThrowInvalidArgument(err, "QUERY-WEh31", "Errors.Query.InvalidRequest")
}
err = q.client.QueryRowContext(ctx, func(row *sql.Row) error {
loginVersion, err = scan(row)
return err
}, stmt, args...)
if err != nil {
return domain.LoginVersionUnspecified, zerrors.ThrowInternal(err, "QUERY-W2gsa", "Errors.Internal")
}
return loginVersion, nil
}
func NewAppNameSearchQuery(method TextComparison, value string) (SearchQuery, error) {
return NewTextQuery(AppColumnName, value, method)
}
@@ -542,6 +580,8 @@ func prepareAppQuery(ctx context.Context, db prepareDatabase, activeOnly bool) (
AppOIDCConfigColumnAdditionalOrigins.identifier(),
AppOIDCConfigColumnSkipNativeAppSuccessPage.identifier(),
AppOIDCConfigColumnBackChannelLogoutURI.identifier(),
AppOIDCConfigColumnLoginVersion.identifier(),
AppOIDCConfigColumnLoginBaseURI.identifier(),
AppSAMLConfigColumnAppID.identifier(),
AppSAMLConfigColumnEntityID.identifier(),
@@ -607,6 +647,8 @@ func scanApp(row *sql.Row) (*App, error) {
&oidcConfig.additionalOrigins,
&oidcConfig.skipNativeAppSuccessPage,
&oidcConfig.backChannelLogoutURI,
&oidcConfig.loginVersion,
&oidcConfig.loginBaseURI,
&samlConfig.appID,
&samlConfig.entityID,
@@ -657,6 +699,8 @@ func prepareOIDCAppQuery() (sq.SelectBuilder, func(*sql.Row) (*App, error)) {
AppOIDCConfigColumnAdditionalOrigins.identifier(),
AppOIDCConfigColumnSkipNativeAppSuccessPage.identifier(),
AppOIDCConfigColumnBackChannelLogoutURI.identifier(),
AppOIDCConfigColumnLoginVersion.identifier(),
AppOIDCConfigColumnLoginBaseURI.identifier(),
).From(appsTable.identifier()).
Join(join(AppOIDCConfigColumnAppID, AppColumnID)).
PlaceholderFormat(sq.Dollar), func(row *sql.Row) (*App, error) {
@@ -694,6 +738,8 @@ func prepareOIDCAppQuery() (sq.SelectBuilder, func(*sql.Row) (*App, error)) {
&oidcConfig.additionalOrigins,
&oidcConfig.skipNativeAppSuccessPage,
&oidcConfig.backChannelLogoutURI,
&oidcConfig.loginVersion,
&oidcConfig.loginBaseURI,
)
if err != nil {
@@ -906,6 +952,8 @@ func prepareAppsQuery(ctx context.Context, db prepareDatabase) (sq.SelectBuilder
AppOIDCConfigColumnAdditionalOrigins.identifier(),
AppOIDCConfigColumnSkipNativeAppSuccessPage.identifier(),
AppOIDCConfigColumnBackChannelLogoutURI.identifier(),
AppOIDCConfigColumnLoginVersion.identifier(),
AppOIDCConfigColumnLoginBaseURI.identifier(),
AppSAMLConfigColumnAppID.identifier(),
AppSAMLConfigColumnEntityID.identifier(),
@@ -959,6 +1007,8 @@ func prepareAppsQuery(ctx context.Context, db prepareDatabase) (sq.SelectBuilder
&oidcConfig.additionalOrigins,
&oidcConfig.skipNativeAppSuccessPage,
&oidcConfig.backChannelLogoutURI,
&oidcConfig.loginVersion,
&oidcConfig.loginBaseURI,
&samlConfig.appID,
&samlConfig.entityID,
@@ -1013,6 +1063,21 @@ func prepareClientIDsQuery(ctx context.Context, db prepareDatabase) (sq.SelectBu
}
}
func prepareLoginVersionByClientID(ctx context.Context, db prepareDatabase) (sq.SelectBuilder, func(*sql.Row) (domain.LoginVersion, error)) {
return sq.Select(
AppOIDCConfigColumnLoginVersion.identifier(),
).From(appOIDCConfigsTable.identifier()).
PlaceholderFormat(sq.Dollar), func(row *sql.Row) (domain.LoginVersion, error) {
var loginVersion sql.NullInt16
if err := row.Scan(
&loginVersion,
); err != nil {
return domain.LoginVersionUnspecified, zerrors.ThrowInternal(err, "QUERY-KL2io", "Errors.Internal")
}
return domain.LoginVersion(loginVersion.Int16), nil
}
}
type sqlOIDCConfig struct {
appID sql.NullString
version sql.NullInt32
@@ -1032,6 +1097,8 @@ type sqlOIDCConfig struct {
grantTypes database.NumberArray[domain.OIDCGrantType]
skipNativeAppSuccessPage sql.NullBool
backChannelLogoutURI sql.NullString
loginVersion sql.NullInt16
loginBaseURI sql.NullString
}
func (c sqlOIDCConfig) set(app *App) {
@@ -1056,6 +1123,10 @@ func (c sqlOIDCConfig) set(app *App) {
GrantTypes: c.grantTypes,
SkipNativeAppSuccessPage: c.skipNativeAppSuccessPage.Bool,
BackChannelLogoutURI: c.backChannelLogoutURI.String,
LoginVersion: domain.LoginVersion(c.loginVersion.Int16),
}
if c.loginBaseURI.Valid {
app.OIDCConfig.LoginBaseURI = &c.loginBaseURI.String
}
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