From 8bf36301ed6134c4a1c3f90522e7e44c0ff3e660 Mon Sep 17 00:00:00 2001 From: Livio Spring Date: Tue, 11 Apr 2023 17:07:32 +0200 Subject: [PATCH] feat: allow skip of success page for native apps (#5627) add possibility to return to callback directly after login without rendering the successful login page --- .../apps/app-create/app-create.component.html | 2 +- .../apps/app-detail/app-detail.component.html | 17 +- .../apps/app-detail/app-detail.component.ts | 19 +- console/src/assets/i18n/de.json | 2 + console/src/assets/i18n/en.json | 2 + console/src/assets/i18n/fr.json | 2 + console/src/assets/i18n/it.json | 2 + console/src/assets/i18n/ja.json | 2 + console/src/assets/i18n/pl.json | 2 + console/src/assets/i18n/zh.json | 2 + .../guides/manage/console/applications.mdx | 1 + internal/api/grpc/admin/export.go | 1 + .../project_application_converter.go | 2 + internal/api/grpc/project/application.go | 1 + .../api/ui/login/login_success_handler.go | 32 +- .../static/resources/scripts/login_success.js | 7 - .../login/static/templates/login_success.html | 2 +- .../eventsourcing/eventstore/auth_request.go | 2 +- internal/command/instance_domain_test.go | 4 +- internal/command/project_application_oidc.go | 38 +- .../command/project_application_oidc_model.go | 10 + .../command/project_application_oidc_test.go | 23 +- internal/command/project_converter.go | 9 +- internal/command/user_human_otp.go | 4 +- internal/domain/application_oidc.go | 1 + internal/query/app.go | 75 +- internal/query/app_test.go | 661 +++++++++++------- internal/query/projection/app.go | 8 +- internal/query/projection/app_test.go | 70 +- internal/repository/project/oidc_config.go | 13 +- proto/zitadel/app.proto | 5 + proto/zitadel/management.proto | 10 + 32 files changed, 641 insertions(+), 390 deletions(-) diff --git a/console/src/app/pages/projects/apps/app-create/app-create.component.html b/console/src/app/pages/projects/apps/app-create/app-create.component.html index d11e908a16..7bd4729e67 100644 --- a/console/src/app/pages/projects/apps/app-create/app-create.component.html +++ b/console/src/app/pages/projects/apps/app-create/app-create.component.html @@ -306,7 +306,7 @@ - {{ 'APP.API.AUTHMETHOD.' + authMethodType?.value | translate }} + {{ 'APP.API.AUTHMETHOD.' + apiAppRequest.toObject().authMethodType | translate }} diff --git a/console/src/app/pages/projects/apps/app-detail/app-detail.component.html b/console/src/app/pages/projects/apps/app-detail/app-detail.component.html index bc18c9bcfc..f910e129d3 100644 --- a/console/src/app/pages/projects/apps/app-detail/app-detail.component.html +++ b/console/src/app/pages/projects/apps/app-detail/app-detail.component.html @@ -331,11 +331,24 @@ + + {{ 'APP.OIDC.SKIPNATIVEAPPSUCCESSPAGE' | translate }} + + {{ 'APP.OIDC.SKIPNATIVEAPPSUCCESSPAGE_DESCRIPTION' | translate }} + + | null { + return this.oidcForm.get('devMode') as FormControl; + } + + public get skipNativeAppSuccessPage(): FormControl | null { + return this.oidcForm.get('skipNativeAppSuccessPage') as FormControl; } public get accessTokenType(): AbstractControl | null { diff --git a/console/src/assets/i18n/de.json b/console/src/assets/i18n/de.json index b550168418..5eb01ed24b 100644 --- a/console/src/assets/i18n/de.json +++ b/console/src/assets/i18n/de.json @@ -1937,6 +1937,8 @@ "REGENERATESECRET": "Client Secret neu generieren", "DEVMODE": "Entwicklermodus", "DEVMODEDESC": "Bei eingeschaltetem Entwicklermodus werden die Weiterleitungs-URIs im OIDC-Flow nicht validiert.", + "SKIPNATIVEAPPSUCCESSPAGE": "Login Erfolgseite überspringen", + "SKIPNATIVEAPPSUCCESSPAGE_DESCRIPTION": "Erfolgseite nach dem Login für diese Native Applikation überspringen.", "REDIRECT": "Weiterleitungs-URIs", "REDIRECTSECTION": "Weiterleitungs-URIs", "POSTLOGOUTREDIRECT": "URIs für Post-Log-out", diff --git a/console/src/assets/i18n/en.json b/console/src/assets/i18n/en.json index df86a19aaa..c9864c9222 100644 --- a/console/src/assets/i18n/en.json +++ b/console/src/assets/i18n/en.json @@ -1930,6 +1930,8 @@ "REGENERATESECRET": "Regenerate Client Secret", "DEVMODE": "Development Mode", "DEVMODEDESC": "Beware: With development mode enabled redirect URIs will not be validated.", + "SKIPNATIVEAPPSUCCESSPAGE": "Skip Login Success Page", + "SKIPNATIVEAPPSUCCESSPAGE_DESCRIPTION": "Skip the success page after a login for this native app.", "REDIRECT": "Redirect URIs", "REDIRECTSECTION": "Redirect URIs", "POSTLOGOUTREDIRECT": "Post Logout URIs", diff --git a/console/src/assets/i18n/fr.json b/console/src/assets/i18n/fr.json index 9443fb891a..2cd44a1b67 100644 --- a/console/src/assets/i18n/fr.json +++ b/console/src/assets/i18n/fr.json @@ -1938,6 +1938,8 @@ "REGENERATESECRET": "Régénérer le secret du client", "DEVMODE": "Mode développement", "DEVMODEDESC": "Attention", + "SKIPNATIVEAPPSUCCESSPAGE": "Sauter la page de succès de connexion", + "SKIPNATIVEAPPSUCCESSPAGE_DESCRIPTION": "Sauter la page de succès après la connexion pour cette application native", "REDIRECT": "Rediriger les URI", "REDIRECTSECTION": "URI de redirection", "POSTLOGOUTREDIRECT": "URIs de post-déconnexion", diff --git a/console/src/assets/i18n/it.json b/console/src/assets/i18n/it.json index 0e9e5b41cb..0e42e01a64 100644 --- a/console/src/assets/i18n/it.json +++ b/console/src/assets/i18n/it.json @@ -1939,6 +1939,8 @@ "REGENERATESECRET": "Rigenera il Client Secret", "DEVMODE": "Modalit\u00e0 di sviluppo (DEV Mode)", "DEVMODEDESC": "Attenzione: Con la modalit\u00e0 di sviluppo abilitata, gli URI di reindirizzamento non saranno convalidati.", + "SKIPNATIVEAPPSUCCESSPAGE": "Salta la pagina di successo dopo il login", + "SKIPNATIVEAPPSUCCESSPAGE_DESCRIPTION": "Salta la pagina di successo dopo il login per questa applicazione nativa", "REDIRECT": "URI per il reindrizzamento", "REDIRECTSECTION": "Reindirizzamento", "POSTLOGOUTREDIRECT": "URI post logout", diff --git a/console/src/assets/i18n/ja.json b/console/src/assets/i18n/ja.json index 3c8d8fdfa9..f6a262a3ac 100644 --- a/console/src/assets/i18n/ja.json +++ b/console/src/assets/i18n/ja.json @@ -1929,6 +1929,8 @@ "REGENERATESECRET": "クライアントシークレットを再生成する", "DEVMODE": "開発モード", "DEVMODEDESC": "注意:開発モードを有効にすると、URIが認証されません。", + "SKIPNATIVEAPPSUCCESSPAGE": "ログイン後に成功ページをスキップする", + "SKIPNATIVEAPPSUCCESSPAGE_DESCRIPTION": "このネイティブアプリのログイン後に成功ページをスキップする", "REDIRECT": "リダイレクトURI", "REDIRECTSECTION": "リダイレクトURI", "POSTLOGOUTREDIRECT": "ログアウトURI", diff --git a/console/src/assets/i18n/pl.json b/console/src/assets/i18n/pl.json index 0c9761c766..4b5bedde76 100644 --- a/console/src/assets/i18n/pl.json +++ b/console/src/assets/i18n/pl.json @@ -1938,6 +1938,8 @@ "REGENERATESECRET": "Odtwórz sekret klienta", "DEVMODE": "Tryb rozwoju", "DEVMODEDESC": "Uwaga: przy włączonym trybie rozwoju adresy URI przekierowania nie będą sprawdzane.", + "SKIPNATIVEAPPSUCCESSPAGE": "Pomiń stronę sukcesu po zalogowaniu", + "SKIPNATIVEAPPSUCCESSPAGE_DESCRIPTION": "Pomiń stronę sukcesu po zalogowaniu dla tej Natywny aplikację", "REDIRECT": "Adresy URI przekierowania", "REDIRECTSECTION": "Adresy URI przekierowania", "POSTLOGOUTREDIRECT": "Adresy URI po wylogowaniu", diff --git a/console/src/assets/i18n/zh.json b/console/src/assets/i18n/zh.json index c7bd3eb226..9fed206931 100644 --- a/console/src/assets/i18n/zh.json +++ b/console/src/assets/i18n/zh.json @@ -1937,6 +1937,8 @@ "REGENERATESECRET": "重新生成客户端密钥", "DEVMODE": "开发模式", "DEVMODEDESC": "注意:启用开发模式的重定向 URI 将不会被验证。", + "SKIPNATIVEAPPSUCCESSPAGE": "登录后跳过成功页面", + "SKIPNATIVEAPPSUCCESSPAGE_DESCRIPTION": "登录后跳过本机应用的成功页面", "REDIRECT": "重定向 URLs", "REDIRECTSECTION": "重定向 URLs", "POSTLOGOUTREDIRECT": "退出登录重定向 URLs", diff --git a/docs/docs/guides/manage/console/applications.mdx b/docs/docs/guides/manage/console/applications.mdx index 0266c5ae4d..8cad1c9eb7 100644 --- a/docs/docs/guides/manage/console/applications.mdx +++ b/docs/docs/guides/manage/console/applications.mdx @@ -142,6 +142,7 @@ On the bottom you can optionally set a **ClockSkew** time which is added to the Like on creation, you can modify you redirect settings here. Note that for local development you most likely have to enable development mode, as redirects to http:// are otherwise blocked. +On Native Apps you can also skip the Login Success Page. Redirect URIs {{end}} -{{template "main-bottom" .}} \ No newline at end of file +{{template "main-bottom" .}} diff --git a/internal/auth/repository/eventsourcing/eventstore/auth_request.go b/internal/auth/repository/eventsourcing/eventstore/auth_request.go index 6a879a3e8e..34a30a6abf 100644 --- a/internal/auth/repository/eventsourcing/eventstore/auth_request.go +++ b/internal/auth/repository/eventsourcing/eventstore/auth_request.go @@ -1223,7 +1223,7 @@ func (repo *AuthRequestRepo) hasSucceededPage(ctx context.Context, request *doma if err != nil { return false, err } - return app.OIDCConfig.AppType == domain.OIDCApplicationTypeNative, nil + return app.OIDCConfig.AppType == domain.OIDCApplicationTypeNative && !app.OIDCConfig.SkipNativeAppSuccessPage, nil } func (repo *AuthRequestRepo) getDomainPolicy(ctx context.Context, orgID string) (*query.DomainPolicy, error) { diff --git a/internal/command/instance_domain_test.go b/internal/command/instance_domain_test.go index 6cd03a28ef..90a692519f 100644 --- a/internal/command/instance_domain_test.go +++ b/internal/command/instance_domain_test.go @@ -161,7 +161,9 @@ func TestCommandSide_AddInstanceDomain(t *testing.T) { true, true, time.Second*1, - []string{"https://sub.test.ch"}), + []string{"https://sub.test.ch"}, + false, + ), ), ), expectPush( diff --git a/internal/command/project_application_oidc.go b/internal/command/project_application_oidc.go index 99eec03d41..44efd279a1 100644 --- a/internal/command/project_application_oidc.go +++ b/internal/command/project_application_oidc.go @@ -19,20 +19,21 @@ import ( type addOIDCApp struct { AddApp - Version domain.OIDCVersion - RedirectUris []string - ResponseTypes []domain.OIDCResponseType - GrantTypes []domain.OIDCGrantType - ApplicationType domain.OIDCApplicationType - AuthMethodType domain.OIDCAuthMethodType - PostLogoutRedirectUris []string - DevMode bool - AccessTokenType domain.OIDCTokenType - AccessTokenRoleAssertion bool - IDTokenRoleAssertion bool - IDTokenUserinfoAssertion bool - ClockSkew time.Duration - AdditionalOrigins []string + Version domain.OIDCVersion + RedirectUris []string + ResponseTypes []domain.OIDCResponseType + GrantTypes []domain.OIDCGrantType + ApplicationType domain.OIDCApplicationType + AuthMethodType domain.OIDCAuthMethodType + PostLogoutRedirectUris []string + DevMode bool + AccessTokenType domain.OIDCTokenType + AccessTokenRoleAssertion bool + IDTokenRoleAssertion bool + IDTokenUserinfoAssertion bool + ClockSkew time.Duration + AdditionalOrigins []string + SkipSuccessPageForNativeApp bool ClientID string ClientSecret *crypto.CryptoValue @@ -109,6 +110,7 @@ func (c *Commands) AddOIDCAppCommand(app *addOIDCApp, clientSecretAlg crypto.Has app.IDTokenUserinfoAssertion, app.ClockSkew, app.AdditionalOrigins, + app.SkipSuccessPageForNativeApp, ), }, nil }, nil @@ -191,7 +193,9 @@ func (c *Commands) addOIDCApplicationWithID(ctx context.Context, oidcApp *domain oidcApp.IDTokenRoleAssertion, oidcApp.IDTokenUserinfoAssertion, oidcApp.ClockSkew, - oidcApp.AdditionalOrigins)) + oidcApp.AdditionalOrigins, + oidcApp.SkipNativeAppSuccessPage, + )) addedApplication.AppID = oidcApp.AppID pushedEvents, err := c.eventstore.Push(ctx, events...) @@ -241,7 +245,9 @@ func (c *Commands) ChangeOIDCApplication(ctx context.Context, oidc *domain.OIDCA oidc.IDTokenRoleAssertion, oidc.IDTokenUserinfoAssertion, oidc.ClockSkew, - oidc.AdditionalOrigins) + oidc.AdditionalOrigins, + oidc.SkipNativeAppSuccessPage, + ) if err != nil { return nil, err } diff --git a/internal/command/project_application_oidc_model.go b/internal/command/project_application_oidc_model.go index d88c79a4bd..7b70d89554 100644 --- a/internal/command/project_application_oidc_model.go +++ b/internal/command/project_application_oidc_model.go @@ -35,6 +35,7 @@ type OIDCApplicationWriteModel struct { ClockSkew time.Duration State domain.AppState AdditionalOrigins []string + SkipNativeAppSuccessPage bool oidc bool } @@ -156,6 +157,7 @@ func (wm *OIDCApplicationWriteModel) appendAddOIDCEvent(e *project.OIDCConfigAdd wm.IDTokenUserinfoAssertion = e.IDTokenUserinfoAssertion wm.ClockSkew = e.ClockSkew wm.AdditionalOrigins = e.AdditionalOrigins + wm.SkipNativeAppSuccessPage = e.SkipNativeAppSuccessPage } func (wm *OIDCApplicationWriteModel) appendChangeOIDCEvent(e *project.OIDCConfigChangedEvent) { @@ -201,6 +203,9 @@ func (wm *OIDCApplicationWriteModel) appendChangeOIDCEvent(e *project.OIDCConfig if e.AdditionalOrigins != nil { wm.AdditionalOrigins = *e.AdditionalOrigins } + if e.SkipNativeAppSuccessPage != nil { + wm.SkipNativeAppSuccessPage = *e.SkipNativeAppSuccessPage + } } func (wm *OIDCApplicationWriteModel) Query() *eventstore.SearchQueryBuilder { @@ -240,6 +245,7 @@ func (wm *OIDCApplicationWriteModel) NewChangedEvent( idTokenUserinfoAssertion bool, clockSkew time.Duration, additionalOrigins []string, + skipNativeAppSuccessPage bool, ) (*project.OIDCConfigChangedEvent, bool, error) { changes := make([]project.OIDCConfigChanges, 0) var err error @@ -286,6 +292,10 @@ func (wm *OIDCApplicationWriteModel) NewChangedEvent( if !reflect.DeepEqual(wm.AdditionalOrigins, additionalOrigins) { changes = append(changes, project.ChangeAdditionalOrigins(additionalOrigins)) } + if wm.SkipNativeAppSuccessPage != skipNativeAppSuccessPage { + changes = append(changes, project.ChangeSkipNativeAppSuccessPage(skipNativeAppSuccessPage)) + } + if len(changes) == 0 { return nil, false, nil } diff --git a/internal/command/project_application_oidc_test.go b/internal/command/project_application_oidc_test.go index d8ed4bb80a..86b3c6e04e 100644 --- a/internal/command/project_application_oidc_test.go +++ b/internal/command/project_application_oidc_test.go @@ -169,6 +169,7 @@ func TestAddOIDCApp(t *testing.T) { false, 0, nil, + false, ), }, }, @@ -325,7 +326,9 @@ func TestCommandSide_AddOIDCApplication(t *testing.T) { true, true, time.Second*1, - []string{"https://sub.test.ch"}), + []string{"https://sub.test.ch"}, + true, + ), ), }, uniqueConstraintsFromEventConstraint(project.NewAddApplicationUniqueConstraint("app", "project1")), @@ -354,6 +357,7 @@ func TestCommandSide_AddOIDCApplication(t *testing.T) { IDTokenUserinfoAssertion: true, ClockSkew: time.Second * 1, AdditionalOrigins: []string{"https://sub.test.ch"}, + SkipNativeAppSuccessPage: true, }, resourceOwner: "org1", secretGenerator: GetMockSecretGenerator(t), @@ -382,6 +386,7 @@ func TestCommandSide_AddOIDCApplication(t *testing.T) { IDTokenUserinfoAssertion: true, ClockSkew: time.Second * 1, AdditionalOrigins: []string{"https://sub.test.ch"}, + SkipNativeAppSuccessPage: true, State: domain.AppStateActive, Compliance: &domain.Compliance{}, }, @@ -558,7 +563,9 @@ func TestCommandSide_ChangeOIDCApplication(t *testing.T) { true, true, time.Second*1, - []string{"https://sub.test.ch"}), + []string{"https://sub.test.ch"}, + true, + ), ), ), ), @@ -585,6 +592,7 @@ func TestCommandSide_ChangeOIDCApplication(t *testing.T) { IDTokenUserinfoAssertion: true, ClockSkew: time.Second * 1, AdditionalOrigins: []string{"https://sub.test.ch"}, + SkipNativeAppSuccessPage: true, }, resourceOwner: "org1", }, @@ -629,7 +637,9 @@ func TestCommandSide_ChangeOIDCApplication(t *testing.T) { true, true, time.Second*1, - []string{"https://sub.test.ch"}), + []string{"https://sub.test.ch"}, + true, + ), ), ), expectPush( @@ -666,6 +676,7 @@ func TestCommandSide_ChangeOIDCApplication(t *testing.T) { IDTokenUserinfoAssertion: false, ClockSkew: time.Second * 2, AdditionalOrigins: []string{"https://sub.test.ch"}, + SkipNativeAppSuccessPage: true, }, resourceOwner: "org1", }, @@ -692,6 +703,7 @@ func TestCommandSide_ChangeOIDCApplication(t *testing.T) { IDTokenUserinfoAssertion: false, ClockSkew: time.Second * 2, AdditionalOrigins: []string{"https://sub.test.ch"}, + SkipNativeAppSuccessPage: true, Compliance: &domain.Compliance{}, State: domain.AppStateActive, }, @@ -826,7 +838,9 @@ func TestCommandSide_ChangeOIDCApplicationSecret(t *testing.T) { true, true, time.Second*1, - []string{"https://sub.test.ch"}), + []string{"https://sub.test.ch"}, + false, + ), ), ), expectPush( @@ -877,6 +891,7 @@ func TestCommandSide_ChangeOIDCApplicationSecret(t *testing.T) { IDTokenUserinfoAssertion: true, ClockSkew: time.Second * 1, AdditionalOrigins: []string{"https://sub.test.ch"}, + SkipNativeAppSuccessPage: false, State: domain.AppStateActive, }, }, diff --git a/internal/command/project_converter.go b/internal/command/project_converter.go index 02a1b9eac7..35679d8a14 100644 --- a/internal/command/project_converter.go +++ b/internal/command/project_converter.go @@ -25,14 +25,6 @@ func projectGrantWriteModelToProjectGrant(writeModel *ProjectGrantWriteModel) *d } } -func applicationWriteModelToApplication(writeModel *ApplicationWriteModel) domain.Application { - return &domain.ChangeApp{ - AppID: writeModel.AppID, - AppName: writeModel.Name, - State: writeModel.State, - } -} - func oidcWriteModelToOIDCConfig(writeModel *OIDCApplicationWriteModel) *domain.OIDCApp { return &domain.OIDCApp{ ObjectRoot: writeModelToObjectRoot(writeModel.WriteModel), @@ -54,6 +46,7 @@ func oidcWriteModelToOIDCConfig(writeModel *OIDCApplicationWriteModel) *domain.O IDTokenUserinfoAssertion: writeModel.IDTokenUserinfoAssertion, ClockSkew: writeModel.ClockSkew, AdditionalOrigins: writeModel.AdditionalOrigins, + SkipNativeAppSuccessPage: writeModel.SkipNativeAppSuccessPage, } } diff --git a/internal/command/user_human_otp.go b/internal/command/user_human_otp.go index 3ee507f735..02dbdae552 100644 --- a/internal/command/user_human_otp.go +++ b/internal/command/user_human_otp.go @@ -3,9 +3,9 @@ package command import ( "context" - "github.com/zitadel/zitadel/internal/crypto" - "github.com/zitadel/logging" + + "github.com/zitadel/zitadel/internal/crypto" "github.com/zitadel/zitadel/internal/domain" caos_errs "github.com/zitadel/zitadel/internal/errors" "github.com/zitadel/zitadel/internal/eventstore/v1/models" diff --git a/internal/domain/application_oidc.go b/internal/domain/application_oidc.go index 808052c050..ad08b18d7a 100644 --- a/internal/domain/application_oidc.go +++ b/internal/domain/application_oidc.go @@ -45,6 +45,7 @@ type OIDCApp struct { IDTokenUserinfoAssertion bool ClockSkew time.Duration AdditionalOrigins []string + SkipNativeAppSuccessPage bool State AppState } diff --git a/internal/query/app.go b/internal/query/app.go index e08d25e096..6b40f712e3 100644 --- a/internal/query/app.go +++ b/internal/query/app.go @@ -40,23 +40,24 @@ type App struct { } type OIDCApp struct { - RedirectURIs database.StringArray - ResponseTypes database.EnumArray[domain.OIDCResponseType] - GrantTypes database.EnumArray[domain.OIDCGrantType] - AppType domain.OIDCApplicationType - ClientID string - AuthMethodType domain.OIDCAuthMethodType - PostLogoutRedirectURIs database.StringArray - Version domain.OIDCVersion - ComplianceProblems database.StringArray - IsDevMode bool - AccessTokenType domain.OIDCTokenType - AssertAccessTokenRole bool - AssertIDTokenRole bool - AssertIDTokenUserinfo bool - ClockSkew time.Duration - AdditionalOrigins database.StringArray - AllowedOrigins database.StringArray + RedirectURIs database.StringArray + ResponseTypes database.EnumArray[domain.OIDCResponseType] + GrantTypes database.EnumArray[domain.OIDCGrantType] + AppType domain.OIDCApplicationType + ClientID string + AuthMethodType domain.OIDCAuthMethodType + PostLogoutRedirectURIs database.StringArray + Version domain.OIDCVersion + ComplianceProblems database.StringArray + IsDevMode bool + AccessTokenType domain.OIDCTokenType + AssertAccessTokenRole bool + AssertIDTokenRole bool + AssertIDTokenUserinfo bool + ClockSkew time.Duration + AdditionalOrigins database.StringArray + AllowedOrigins database.StringArray + SkipNativeAppSuccessPage bool } type SAMLApp struct { @@ -241,6 +242,10 @@ var ( name: projection.AppOIDCConfigColumnAdditionalOrigins, table: appOIDCConfigsTable, } + AppOIDCConfigColumnSkipNativeAppSuccessPage = Column{ + name: projection.AppOIDCConfigColumnSkipNativeAppSuccessPage, + table: appOIDCConfigsTable, + } ) func (q *Queries) AppByProjectAndAppID(ctx context.Context, shouldTriggerBulk bool, projectID, appID string, withOwnerRemoved bool) (_ *App, err error) { @@ -535,6 +540,7 @@ func prepareAppQuery(ctx context.Context, db prepareDatabase) (sq.SelectBuilder, AppOIDCConfigColumnIDTokenUserinfoAssertion.identifier(), AppOIDCConfigColumnClockSkew.identifier(), AppOIDCConfigColumnAdditionalOrigins.identifier(), + AppOIDCConfigColumnSkipNativeAppSuccessPage.identifier(), AppSAMLConfigColumnAppID.identifier(), AppSAMLConfigColumnEntityID.identifier(), @@ -583,6 +589,7 @@ func prepareAppQuery(ctx context.Context, db prepareDatabase) (sq.SelectBuilder, &oidcConfig.iDTokenUserinfoAssertion, &oidcConfig.clockSkew, &oidcConfig.additionalOrigins, + &oidcConfig.skipNativeAppSuccessPage, &samlConfig.appID, &samlConfig.entityID, @@ -703,6 +710,7 @@ func prepareAppsQuery(ctx context.Context, db prepareDatabase) (sq.SelectBuilder AppOIDCConfigColumnIDTokenUserinfoAssertion.identifier(), AppOIDCConfigColumnClockSkew.identifier(), AppOIDCConfigColumnAdditionalOrigins.identifier(), + AppOIDCConfigColumnSkipNativeAppSuccessPage.identifier(), AppSAMLConfigColumnAppID.identifier(), AppSAMLConfigColumnEntityID.identifier(), @@ -754,6 +762,7 @@ func prepareAppsQuery(ctx context.Context, db prepareDatabase) (sq.SelectBuilder &oidcConfig.iDTokenUserinfoAssertion, &oidcConfig.clockSkew, &oidcConfig.additionalOrigins, + &oidcConfig.skipNativeAppSuccessPage, &samlConfig.appID, &samlConfig.entityID, @@ -825,6 +834,7 @@ type sqlOIDCConfig struct { additionalOrigins database.StringArray responseTypes database.EnumArray[domain.OIDCResponseType] grantTypes database.EnumArray[domain.OIDCGrantType] + skipNativeAppSuccessPage sql.NullBool } func (c sqlOIDCConfig) set(app *App) { @@ -832,21 +842,22 @@ func (c sqlOIDCConfig) set(app *App) { 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, + 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 diff --git a/internal/query/app_test.go b/internal/query/app_test.go index 3076a6ed0c..e75e4ce06f 100644 --- a/internal/query/app_test.go +++ b/internal/query/app_test.go @@ -15,96 +15,98 @@ import ( ) var ( - expectedAppQuery = regexp.QuoteMeta(`SELECT projections.apps4.id,` + - ` projections.apps4.name,` + - ` projections.apps4.project_id,` + - ` projections.apps4.creation_date,` + - ` projections.apps4.change_date,` + - ` projections.apps4.resource_owner,` + - ` projections.apps4.state,` + - ` projections.apps4.sequence,` + + expectedAppQuery = regexp.QuoteMeta(`SELECT projections.apps5.id,` + + ` projections.apps5.name,` + + ` projections.apps5.project_id,` + + ` projections.apps5.creation_date,` + + ` projections.apps5.change_date,` + + ` projections.apps5.resource_owner,` + + ` projections.apps5.state,` + + ` projections.apps5.sequence,` + // api config - ` projections.apps4_api_configs.app_id,` + - ` projections.apps4_api_configs.client_id,` + - ` projections.apps4_api_configs.auth_method,` + + ` projections.apps5_api_configs.app_id,` + + ` projections.apps5_api_configs.client_id,` + + ` projections.apps5_api_configs.auth_method,` + // oidc config - ` projections.apps4_oidc_configs.app_id,` + - ` projections.apps4_oidc_configs.version,` + - ` projections.apps4_oidc_configs.client_id,` + - ` projections.apps4_oidc_configs.redirect_uris,` + - ` projections.apps4_oidc_configs.response_types,` + - ` projections.apps4_oidc_configs.grant_types,` + - ` projections.apps4_oidc_configs.application_type,` + - ` projections.apps4_oidc_configs.auth_method_type,` + - ` projections.apps4_oidc_configs.post_logout_redirect_uris,` + - ` projections.apps4_oidc_configs.is_dev_mode,` + - ` projections.apps4_oidc_configs.access_token_type,` + - ` projections.apps4_oidc_configs.access_token_role_assertion,` + - ` projections.apps4_oidc_configs.id_token_role_assertion,` + - ` projections.apps4_oidc_configs.id_token_userinfo_assertion,` + - ` projections.apps4_oidc_configs.clock_skew,` + - ` projections.apps4_oidc_configs.additional_origins,` + + ` projections.apps5_oidc_configs.app_id,` + + ` projections.apps5_oidc_configs.version,` + + ` projections.apps5_oidc_configs.client_id,` + + ` projections.apps5_oidc_configs.redirect_uris,` + + ` projections.apps5_oidc_configs.response_types,` + + ` projections.apps5_oidc_configs.grant_types,` + + ` projections.apps5_oidc_configs.application_type,` + + ` projections.apps5_oidc_configs.auth_method_type,` + + ` projections.apps5_oidc_configs.post_logout_redirect_uris,` + + ` projections.apps5_oidc_configs.is_dev_mode,` + + ` projections.apps5_oidc_configs.access_token_type,` + + ` projections.apps5_oidc_configs.access_token_role_assertion,` + + ` projections.apps5_oidc_configs.id_token_role_assertion,` + + ` projections.apps5_oidc_configs.id_token_userinfo_assertion,` + + ` projections.apps5_oidc_configs.clock_skew,` + + ` projections.apps5_oidc_configs.additional_origins,` + + ` projections.apps5_oidc_configs.skip_native_app_success_page,` + //saml config - ` projections.apps4_saml_configs.app_id,` + - ` projections.apps4_saml_configs.entity_id,` + - ` projections.apps4_saml_configs.metadata,` + - ` projections.apps4_saml_configs.metadata_url` + - ` FROM projections.apps4` + - ` LEFT JOIN projections.apps4_api_configs ON projections.apps4.id = projections.apps4_api_configs.app_id AND projections.apps4.instance_id = projections.apps4_api_configs.instance_id` + - ` LEFT JOIN projections.apps4_oidc_configs ON projections.apps4.id = projections.apps4_oidc_configs.app_id AND projections.apps4.instance_id = projections.apps4_oidc_configs.instance_id` + - ` LEFT JOIN projections.apps4_saml_configs ON projections.apps4.id = projections.apps4_saml_configs.app_id AND projections.apps4.instance_id = projections.apps4_saml_configs.instance_id` + + ` projections.apps5_saml_configs.app_id,` + + ` projections.apps5_saml_configs.entity_id,` + + ` projections.apps5_saml_configs.metadata,` + + ` projections.apps5_saml_configs.metadata_url` + + ` FROM projections.apps5` + + ` LEFT JOIN projections.apps5_api_configs ON projections.apps5.id = projections.apps5_api_configs.app_id AND projections.apps5.instance_id = projections.apps5_api_configs.instance_id` + + ` LEFT JOIN projections.apps5_oidc_configs ON projections.apps5.id = projections.apps5_oidc_configs.app_id AND projections.apps5.instance_id = projections.apps5_oidc_configs.instance_id` + + ` LEFT JOIN projections.apps5_saml_configs ON projections.apps5.id = projections.apps5_saml_configs.app_id AND projections.apps5.instance_id = projections.apps5_saml_configs.instance_id` + ` AS OF SYSTEM TIME '-1 ms'`) - expectedAppsQuery = regexp.QuoteMeta(`SELECT projections.apps4.id,` + - ` projections.apps4.name,` + - ` projections.apps4.project_id,` + - ` projections.apps4.creation_date,` + - ` projections.apps4.change_date,` + - ` projections.apps4.resource_owner,` + - ` projections.apps4.state,` + - ` projections.apps4.sequence,` + + expectedAppsQuery = regexp.QuoteMeta(`SELECT projections.apps5.id,` + + ` projections.apps5.name,` + + ` projections.apps5.project_id,` + + ` projections.apps5.creation_date,` + + ` projections.apps5.change_date,` + + ` projections.apps5.resource_owner,` + + ` projections.apps5.state,` + + ` projections.apps5.sequence,` + // api config - ` projections.apps4_api_configs.app_id,` + - ` projections.apps4_api_configs.client_id,` + - ` projections.apps4_api_configs.auth_method,` + + ` projections.apps5_api_configs.app_id,` + + ` projections.apps5_api_configs.client_id,` + + ` projections.apps5_api_configs.auth_method,` + // oidc config - ` projections.apps4_oidc_configs.app_id,` + - ` projections.apps4_oidc_configs.version,` + - ` projections.apps4_oidc_configs.client_id,` + - ` projections.apps4_oidc_configs.redirect_uris,` + - ` projections.apps4_oidc_configs.response_types,` + - ` projections.apps4_oidc_configs.grant_types,` + - ` projections.apps4_oidc_configs.application_type,` + - ` projections.apps4_oidc_configs.auth_method_type,` + - ` projections.apps4_oidc_configs.post_logout_redirect_uris,` + - ` projections.apps4_oidc_configs.is_dev_mode,` + - ` projections.apps4_oidc_configs.access_token_type,` + - ` projections.apps4_oidc_configs.access_token_role_assertion,` + - ` projections.apps4_oidc_configs.id_token_role_assertion,` + - ` projections.apps4_oidc_configs.id_token_userinfo_assertion,` + - ` projections.apps4_oidc_configs.clock_skew,` + - ` projections.apps4_oidc_configs.additional_origins,` + + ` projections.apps5_oidc_configs.app_id,` + + ` projections.apps5_oidc_configs.version,` + + ` projections.apps5_oidc_configs.client_id,` + + ` projections.apps5_oidc_configs.redirect_uris,` + + ` projections.apps5_oidc_configs.response_types,` + + ` projections.apps5_oidc_configs.grant_types,` + + ` projections.apps5_oidc_configs.application_type,` + + ` projections.apps5_oidc_configs.auth_method_type,` + + ` projections.apps5_oidc_configs.post_logout_redirect_uris,` + + ` projections.apps5_oidc_configs.is_dev_mode,` + + ` projections.apps5_oidc_configs.access_token_type,` + + ` projections.apps5_oidc_configs.access_token_role_assertion,` + + ` projections.apps5_oidc_configs.id_token_role_assertion,` + + ` projections.apps5_oidc_configs.id_token_userinfo_assertion,` + + ` projections.apps5_oidc_configs.clock_skew,` + + ` projections.apps5_oidc_configs.additional_origins,` + + ` projections.apps5_oidc_configs.skip_native_app_success_page,` + //saml config - ` projections.apps4_saml_configs.app_id,` + - ` projections.apps4_saml_configs.entity_id,` + - ` projections.apps4_saml_configs.metadata,` + - ` projections.apps4_saml_configs.metadata_url,` + + ` projections.apps5_saml_configs.app_id,` + + ` projections.apps5_saml_configs.entity_id,` + + ` projections.apps5_saml_configs.metadata,` + + ` projections.apps5_saml_configs.metadata_url,` + ` COUNT(*) OVER ()` + - ` FROM projections.apps4` + - ` LEFT JOIN projections.apps4_api_configs ON projections.apps4.id = projections.apps4_api_configs.app_id AND projections.apps4.instance_id = projections.apps4_api_configs.instance_id` + - ` LEFT JOIN projections.apps4_oidc_configs ON projections.apps4.id = projections.apps4_oidc_configs.app_id AND projections.apps4.instance_id = projections.apps4_oidc_configs.instance_id` + - ` LEFT JOIN projections.apps4_saml_configs ON projections.apps4.id = projections.apps4_saml_configs.app_id AND projections.apps4.instance_id = projections.apps4_saml_configs.instance_id` + + ` FROM projections.apps5` + + ` LEFT JOIN projections.apps5_api_configs ON projections.apps5.id = projections.apps5_api_configs.app_id AND projections.apps5.instance_id = projections.apps5_api_configs.instance_id` + + ` LEFT JOIN projections.apps5_oidc_configs ON projections.apps5.id = projections.apps5_oidc_configs.app_id AND projections.apps5.instance_id = projections.apps5_oidc_configs.instance_id` + + ` LEFT JOIN projections.apps5_saml_configs ON projections.apps5.id = projections.apps5_saml_configs.app_id AND projections.apps5.instance_id = projections.apps5_saml_configs.instance_id` + ` AS OF SYSTEM TIME '-1 ms'`) - expectedAppIDsQuery = regexp.QuoteMeta(`SELECT projections.apps4_api_configs.client_id,` + - ` projections.apps4_oidc_configs.client_id` + - ` FROM projections.apps4` + - ` LEFT JOIN projections.apps4_api_configs ON projections.apps4.id = projections.apps4_api_configs.app_id AND projections.apps4.instance_id = projections.apps4_api_configs.instance_id` + - ` LEFT JOIN projections.apps4_oidc_configs ON projections.apps4.id = projections.apps4_oidc_configs.app_id AND projections.apps4.instance_id = projections.apps4_oidc_configs.instance_id` + + expectedAppIDsQuery = regexp.QuoteMeta(`SELECT projections.apps5_api_configs.client_id,` + + ` projections.apps5_oidc_configs.client_id` + + ` FROM projections.apps5` + + ` LEFT JOIN projections.apps5_api_configs ON projections.apps5.id = projections.apps5_api_configs.app_id AND projections.apps5.instance_id = projections.apps5_api_configs.instance_id` + + ` LEFT JOIN projections.apps5_oidc_configs ON projections.apps5.id = projections.apps5_oidc_configs.app_id AND projections.apps5.instance_id = projections.apps5_oidc_configs.instance_id` + ` AS OF SYSTEM TIME '-1 ms'`) - expectedProjectIDByAppQuery = regexp.QuoteMeta(`SELECT projections.apps4.project_id` + - ` FROM projections.apps4` + - ` LEFT JOIN projections.apps4_api_configs ON projections.apps4.id = projections.apps4_api_configs.app_id AND projections.apps4.instance_id = projections.apps4_api_configs.instance_id` + - ` LEFT JOIN projections.apps4_oidc_configs ON projections.apps4.id = projections.apps4_oidc_configs.app_id AND projections.apps4.instance_id = projections.apps4_oidc_configs.instance_id` + - ` LEFT JOIN projections.apps4_saml_configs ON projections.apps4.id = projections.apps4_saml_configs.app_id AND projections.apps4.instance_id = projections.apps4_saml_configs.instance_id` + + expectedProjectIDByAppQuery = regexp.QuoteMeta(`SELECT projections.apps5.project_id` + + ` FROM projections.apps5` + + ` LEFT JOIN projections.apps5_api_configs ON projections.apps5.id = projections.apps5_api_configs.app_id AND projections.apps5.instance_id = projections.apps5_api_configs.instance_id` + + ` LEFT JOIN projections.apps5_oidc_configs ON projections.apps5.id = projections.apps5_oidc_configs.app_id AND projections.apps5.instance_id = projections.apps5_oidc_configs.instance_id` + + ` LEFT JOIN projections.apps5_saml_configs ON projections.apps5.id = projections.apps5_saml_configs.app_id AND projections.apps5.instance_id = projections.apps5_saml_configs.instance_id` + ` AS OF SYSTEM TIME '-1 ms'`) expectedProjectByAppQuery = regexp.QuoteMeta(`SELECT projections.projects3.id,` + ` projections.projects3.creation_date,` + @@ -118,10 +120,10 @@ var ( ` projections.projects3.has_project_check,` + ` projections.projects3.private_labeling_setting` + ` FROM projections.projects3` + - ` JOIN projections.apps4 ON projections.projects3.id = projections.apps4.project_id AND projections.projects3.instance_id = projections.apps4.instance_id` + - ` LEFT JOIN projections.apps4_api_configs ON projections.apps4.id = projections.apps4_api_configs.app_id AND projections.apps4.instance_id = projections.apps4_api_configs.instance_id` + - ` LEFT JOIN projections.apps4_oidc_configs ON projections.apps4.id = projections.apps4_oidc_configs.app_id AND projections.apps4.instance_id = projections.apps4_oidc_configs.instance_id` + - ` LEFT JOIN projections.apps4_saml_configs ON projections.apps4.id = projections.apps4_saml_configs.app_id AND projections.apps4.instance_id = projections.apps4_saml_configs.instance_id` + + ` JOIN projections.apps5 ON projections.projects3.id = projections.apps5.project_id AND projections.projects3.instance_id = projections.apps5.instance_id` + + ` LEFT JOIN projections.apps5_api_configs ON projections.apps5.id = projections.apps5_api_configs.app_id AND projections.apps5.instance_id = projections.apps5_api_configs.instance_id` + + ` LEFT JOIN projections.apps5_oidc_configs ON projections.apps5.id = projections.apps5_oidc_configs.app_id AND projections.apps5.instance_id = projections.apps5_oidc_configs.instance_id` + + ` LEFT JOIN projections.apps5_saml_configs ON projections.apps5.id = projections.apps5_saml_configs.app_id AND projections.apps5.instance_id = projections.apps5_saml_configs.instance_id` + ` AS OF SYSTEM TIME '-1 ms'`) appCols = database.StringArray{ @@ -154,6 +156,7 @@ var ( "id_token_userinfo_assertion", "clock_skew", "additional_origins", + "skip_native_app_success_page", //saml config "app_id", "entity_id", @@ -224,6 +227,7 @@ func Test_AppsPrepare(t *testing.T) { nil, nil, nil, + nil, // saml config nil, nil, @@ -289,6 +293,7 @@ func Test_AppsPrepare(t *testing.T) { nil, nil, nil, + nil, // saml config nil, nil, @@ -357,6 +362,7 @@ func Test_AppsPrepare(t *testing.T) { nil, nil, nil, + nil, // saml config "app-id", "https://test.com/saml/metadata", @@ -427,6 +433,7 @@ func Test_AppsPrepare(t *testing.T) { true, 1 * time.Second, database.StringArray{"additional.origin"}, + false, // saml config nil, nil, @@ -451,23 +458,24 @@ func Test_AppsPrepare(t *testing.T) { Name: "app-name", ProjectID: "project-id", OIDCConfig: &OIDCApp{ - Version: domain.OIDCVersionV1, - ClientID: "oidc-client-id", - RedirectURIs: database.StringArray{"https://redirect.to/me"}, - ResponseTypes: database.EnumArray[domain.OIDCResponseType]{domain.OIDCResponseTypeIDTokenToken}, - GrantTypes: database.EnumArray[domain.OIDCGrantType]{domain.OIDCGrantTypeImplicit}, - AppType: domain.OIDCApplicationTypeUserAgent, - AuthMethodType: domain.OIDCAuthMethodTypeNone, - PostLogoutRedirectURIs: database.StringArray{"post.logout.ch"}, - IsDevMode: true, - AccessTokenType: domain.OIDCTokenTypeJWT, - AssertAccessTokenRole: true, - AssertIDTokenRole: true, - AssertIDTokenUserinfo: true, - ClockSkew: 1 * time.Second, - AdditionalOrigins: database.StringArray{"additional.origin"}, - ComplianceProblems: nil, - AllowedOrigins: database.StringArray{"https://redirect.to", "additional.origin"}, + Version: domain.OIDCVersionV1, + ClientID: "oidc-client-id", + RedirectURIs: database.StringArray{"https://redirect.to/me"}, + ResponseTypes: database.EnumArray[domain.OIDCResponseType]{domain.OIDCResponseTypeIDTokenToken}, + GrantTypes: database.EnumArray[domain.OIDCGrantType]{domain.OIDCGrantTypeImplicit}, + AppType: domain.OIDCApplicationTypeUserAgent, + AuthMethodType: domain.OIDCAuthMethodTypeNone, + PostLogoutRedirectURIs: database.StringArray{"post.logout.ch"}, + IsDevMode: true, + AccessTokenType: domain.OIDCTokenTypeJWT, + AssertAccessTokenRole: true, + AssertIDTokenRole: true, + AssertIDTokenUserinfo: true, + ClockSkew: 1 * time.Second, + AdditionalOrigins: database.StringArray{"additional.origin"}, + ComplianceProblems: nil, + AllowedOrigins: database.StringArray{"https://redirect.to", "additional.origin"}, + SkipNativeAppSuccessPage: false, }, }, }, @@ -511,6 +519,7 @@ func Test_AppsPrepare(t *testing.T) { true, 1 * time.Second, database.StringArray{"additional.origin"}, + false, // saml config nil, nil, @@ -535,23 +544,24 @@ func Test_AppsPrepare(t *testing.T) { Name: "app-name", ProjectID: "project-id", OIDCConfig: &OIDCApp{ - Version: domain.OIDCVersionV1, - ClientID: "oidc-client-id", - RedirectURIs: database.StringArray{"https://redirect.to/me"}, - ResponseTypes: database.EnumArray[domain.OIDCResponseType]{domain.OIDCResponseTypeIDTokenToken}, - GrantTypes: database.EnumArray[domain.OIDCGrantType]{domain.OIDCGrantTypeImplicit}, - AppType: domain.OIDCApplicationTypeUserAgent, - AuthMethodType: domain.OIDCAuthMethodTypeNone, - PostLogoutRedirectURIs: database.StringArray{"post.logout.ch"}, - IsDevMode: false, - AccessTokenType: domain.OIDCTokenTypeJWT, - AssertAccessTokenRole: false, - AssertIDTokenRole: false, - AssertIDTokenUserinfo: true, - ClockSkew: 1 * time.Second, - AdditionalOrigins: database.StringArray{"additional.origin"}, - ComplianceProblems: nil, - AllowedOrigins: database.StringArray{"https://redirect.to", "additional.origin"}, + Version: domain.OIDCVersionV1, + ClientID: "oidc-client-id", + RedirectURIs: database.StringArray{"https://redirect.to/me"}, + ResponseTypes: database.EnumArray[domain.OIDCResponseType]{domain.OIDCResponseTypeIDTokenToken}, + GrantTypes: database.EnumArray[domain.OIDCGrantType]{domain.OIDCGrantTypeImplicit}, + AppType: domain.OIDCApplicationTypeUserAgent, + AuthMethodType: domain.OIDCAuthMethodTypeNone, + PostLogoutRedirectURIs: database.StringArray{"post.logout.ch"}, + IsDevMode: false, + AccessTokenType: domain.OIDCTokenTypeJWT, + AssertAccessTokenRole: false, + AssertIDTokenRole: false, + AssertIDTokenUserinfo: true, + ClockSkew: 1 * time.Second, + AdditionalOrigins: database.StringArray{"additional.origin"}, + ComplianceProblems: nil, + AllowedOrigins: database.StringArray{"https://redirect.to", "additional.origin"}, + SkipNativeAppSuccessPage: false, }, }, }, @@ -595,6 +605,7 @@ func Test_AppsPrepare(t *testing.T) { true, 1 * time.Second, database.StringArray{"additional.origin"}, + false, // saml config nil, nil, @@ -619,23 +630,24 @@ func Test_AppsPrepare(t *testing.T) { Name: "app-name", ProjectID: "project-id", OIDCConfig: &OIDCApp{ - Version: domain.OIDCVersionV1, - ClientID: "oidc-client-id", - RedirectURIs: database.StringArray{"https://redirect.to/me"}, - ResponseTypes: database.EnumArray[domain.OIDCResponseType]{domain.OIDCResponseTypeIDTokenToken}, - GrantTypes: database.EnumArray[domain.OIDCGrantType]{domain.OIDCGrantTypeImplicit}, - AppType: domain.OIDCApplicationTypeUserAgent, - AuthMethodType: domain.OIDCAuthMethodTypeNone, - PostLogoutRedirectURIs: database.StringArray{"post.logout.ch"}, - IsDevMode: true, - AccessTokenType: domain.OIDCTokenTypeJWT, - AssertAccessTokenRole: true, - AssertIDTokenRole: false, - AssertIDTokenUserinfo: true, - ClockSkew: 1 * time.Second, - AdditionalOrigins: database.StringArray{"additional.origin"}, - ComplianceProblems: nil, - AllowedOrigins: database.StringArray{"https://redirect.to", "additional.origin"}, + Version: domain.OIDCVersionV1, + ClientID: "oidc-client-id", + RedirectURIs: database.StringArray{"https://redirect.to/me"}, + ResponseTypes: database.EnumArray[domain.OIDCResponseType]{domain.OIDCResponseTypeIDTokenToken}, + GrantTypes: database.EnumArray[domain.OIDCGrantType]{domain.OIDCGrantTypeImplicit}, + AppType: domain.OIDCApplicationTypeUserAgent, + AuthMethodType: domain.OIDCAuthMethodTypeNone, + PostLogoutRedirectURIs: database.StringArray{"post.logout.ch"}, + IsDevMode: true, + AccessTokenType: domain.OIDCTokenTypeJWT, + AssertAccessTokenRole: true, + AssertIDTokenRole: false, + AssertIDTokenUserinfo: true, + ClockSkew: 1 * time.Second, + AdditionalOrigins: database.StringArray{"additional.origin"}, + ComplianceProblems: nil, + AllowedOrigins: database.StringArray{"https://redirect.to", "additional.origin"}, + SkipNativeAppSuccessPage: false, }, }, }, @@ -679,6 +691,7 @@ func Test_AppsPrepare(t *testing.T) { true, 1 * time.Second, database.StringArray{"additional.origin"}, + false, // saml config nil, nil, @@ -703,23 +716,24 @@ func Test_AppsPrepare(t *testing.T) { Name: "app-name", ProjectID: "project-id", OIDCConfig: &OIDCApp{ - Version: domain.OIDCVersionV1, - ClientID: "oidc-client-id", - RedirectURIs: database.StringArray{"https://redirect.to/me"}, - ResponseTypes: database.EnumArray[domain.OIDCResponseType]{domain.OIDCResponseTypeIDTokenToken}, - GrantTypes: database.EnumArray[domain.OIDCGrantType]{domain.OIDCGrantTypeImplicit}, - AppType: domain.OIDCApplicationTypeUserAgent, - AuthMethodType: domain.OIDCAuthMethodTypeNone, - PostLogoutRedirectURIs: database.StringArray{"post.logout.ch"}, - IsDevMode: false, - AccessTokenType: domain.OIDCTokenTypeJWT, - AssertAccessTokenRole: false, - AssertIDTokenRole: true, - AssertIDTokenUserinfo: true, - ClockSkew: 1 * time.Second, - AdditionalOrigins: database.StringArray{"additional.origin"}, - ComplianceProblems: nil, - AllowedOrigins: database.StringArray{"https://redirect.to", "additional.origin"}, + Version: domain.OIDCVersionV1, + ClientID: "oidc-client-id", + RedirectURIs: database.StringArray{"https://redirect.to/me"}, + ResponseTypes: database.EnumArray[domain.OIDCResponseType]{domain.OIDCResponseTypeIDTokenToken}, + GrantTypes: database.EnumArray[domain.OIDCGrantType]{domain.OIDCGrantTypeImplicit}, + AppType: domain.OIDCApplicationTypeUserAgent, + AuthMethodType: domain.OIDCAuthMethodTypeNone, + PostLogoutRedirectURIs: database.StringArray{"post.logout.ch"}, + IsDevMode: false, + AccessTokenType: domain.OIDCTokenTypeJWT, + AssertAccessTokenRole: false, + AssertIDTokenRole: true, + AssertIDTokenUserinfo: true, + ClockSkew: 1 * time.Second, + AdditionalOrigins: database.StringArray{"additional.origin"}, + ComplianceProblems: nil, + AllowedOrigins: database.StringArray{"https://redirect.to", "additional.origin"}, + SkipNativeAppSuccessPage: false, }, }, }, @@ -763,6 +777,7 @@ func Test_AppsPrepare(t *testing.T) { true, 1 * time.Second, database.StringArray{"additional.origin"}, + false, // saml config nil, nil, @@ -787,23 +802,110 @@ func Test_AppsPrepare(t *testing.T) { Name: "app-name", ProjectID: "project-id", OIDCConfig: &OIDCApp{ - Version: domain.OIDCVersionV1, - ClientID: "oidc-client-id", - RedirectURIs: database.StringArray{"https://redirect.to/me"}, - ResponseTypes: database.EnumArray[domain.OIDCResponseType]{domain.OIDCResponseTypeIDTokenToken}, - GrantTypes: database.EnumArray[domain.OIDCGrantType]{domain.OIDCGrantTypeImplicit}, - AppType: domain.OIDCApplicationTypeUserAgent, - AuthMethodType: domain.OIDCAuthMethodTypeNone, - PostLogoutRedirectURIs: database.StringArray{"post.logout.ch"}, - IsDevMode: false, - AccessTokenType: domain.OIDCTokenTypeJWT, - AssertAccessTokenRole: true, - AssertIDTokenRole: true, - AssertIDTokenUserinfo: true, - ClockSkew: 1 * time.Second, - AdditionalOrigins: database.StringArray{"additional.origin"}, - ComplianceProblems: nil, - AllowedOrigins: database.StringArray{"https://redirect.to", "additional.origin"}, + Version: domain.OIDCVersionV1, + ClientID: "oidc-client-id", + RedirectURIs: database.StringArray{"https://redirect.to/me"}, + ResponseTypes: database.EnumArray[domain.OIDCResponseType]{domain.OIDCResponseTypeIDTokenToken}, + GrantTypes: database.EnumArray[domain.OIDCGrantType]{domain.OIDCGrantTypeImplicit}, + AppType: domain.OIDCApplicationTypeUserAgent, + AuthMethodType: domain.OIDCAuthMethodTypeNone, + PostLogoutRedirectURIs: database.StringArray{"post.logout.ch"}, + IsDevMode: false, + AccessTokenType: domain.OIDCTokenTypeJWT, + AssertAccessTokenRole: true, + AssertIDTokenRole: true, + AssertIDTokenUserinfo: true, + ClockSkew: 1 * time.Second, + AdditionalOrigins: database.StringArray{"additional.origin"}, + ComplianceProblems: nil, + AllowedOrigins: database.StringArray{"https://redirect.to", "additional.origin"}, + SkipNativeAppSuccessPage: false, + }, + }, + }, + }, + }, + { + name: "prepareAppsQuery oidc app native success page skip", + prepare: prepareAppsQuery, + want: want{ + sqlExpectations: mockQueries( + expectedAppsQuery, + appsCols, + [][]driver.Value{ + { + "app-id", + "app-name", + "project-id", + testNow, + testNow, + "ro", + domain.AppStateActive, + uint64(20211109), + // api config + nil, + nil, + nil, + // oidc config + "app-id", + domain.OIDCVersionV1, + "oidc-client-id", + database.StringArray{"https://redirect.to/me"}, + database.EnumArray[domain.OIDCResponseType]{domain.OIDCResponseTypeIDTokenToken}, + database.EnumArray[domain.OIDCGrantType]{domain.OIDCGrantTypeImplicit}, + domain.OIDCApplicationTypeNative, + domain.OIDCAuthMethodTypeNone, + database.StringArray{"post.logout.ch"}, + false, + domain.OIDCTokenTypeJWT, + false, + false, + true, + 1 * time.Second, + database.StringArray{"additional.origin"}, + true, + // saml config + nil, + nil, + nil, + nil, + }, + }, + ), + }, + object: &Apps{ + SearchResponse: SearchResponse{ + Count: 1, + }, + Apps: []*App{ + { + ID: "app-id", + CreationDate: testNow, + ChangeDate: testNow, + ResourceOwner: "ro", + State: domain.AppStateActive, + Sequence: 20211109, + Name: "app-name", + ProjectID: "project-id", + OIDCConfig: &OIDCApp{ + Version: domain.OIDCVersionV1, + ClientID: "oidc-client-id", + RedirectURIs: database.StringArray{"https://redirect.to/me"}, + ResponseTypes: database.EnumArray[domain.OIDCResponseType]{domain.OIDCResponseTypeIDTokenToken}, + GrantTypes: database.EnumArray[domain.OIDCGrantType]{domain.OIDCGrantTypeImplicit}, + AppType: domain.OIDCApplicationTypeNative, + AuthMethodType: domain.OIDCAuthMethodTypeNone, + PostLogoutRedirectURIs: database.StringArray{"post.logout.ch"}, + IsDevMode: false, + AccessTokenType: domain.OIDCTokenTypeJWT, + AssertAccessTokenRole: false, + AssertIDTokenRole: false, + AssertIDTokenUserinfo: true, + ClockSkew: 1 * time.Second, + AdditionalOrigins: database.StringArray{"additional.origin"}, + ComplianceProblems: nil, + AllowedOrigins: database.StringArray{"https://redirect.to", "additional.origin"}, + SkipNativeAppSuccessPage: true, }, }, }, @@ -847,6 +949,7 @@ func Test_AppsPrepare(t *testing.T) { true, 1 * time.Second, database.StringArray{"additional.origin"}, + false, // saml config nil, nil, @@ -883,6 +986,7 @@ func Test_AppsPrepare(t *testing.T) { nil, nil, nil, + nil, // saml config nil, nil, @@ -919,6 +1023,7 @@ func Test_AppsPrepare(t *testing.T) { nil, nil, nil, + nil, // saml config "saml-app-id", "https://test.com/saml/metadata", @@ -943,23 +1048,24 @@ func Test_AppsPrepare(t *testing.T) { Name: "app-name", ProjectID: "project-id", OIDCConfig: &OIDCApp{ - Version: domain.OIDCVersionV1, - ClientID: "oidc-client-id", - RedirectURIs: database.StringArray{"https://redirect.to/me"}, - ResponseTypes: database.EnumArray[domain.OIDCResponseType]{domain.OIDCResponseTypeIDTokenToken}, - GrantTypes: database.EnumArray[domain.OIDCGrantType]{domain.OIDCGrantTypeImplicit}, - AppType: domain.OIDCApplicationTypeUserAgent, - AuthMethodType: domain.OIDCAuthMethodTypeNone, - PostLogoutRedirectURIs: database.StringArray{"post.logout.ch"}, - IsDevMode: true, - AccessTokenType: domain.OIDCTokenTypeJWT, - AssertAccessTokenRole: true, - AssertIDTokenRole: true, - AssertIDTokenUserinfo: true, - ClockSkew: 1 * time.Second, - AdditionalOrigins: database.StringArray{"additional.origin"}, - ComplianceProblems: nil, - AllowedOrigins: database.StringArray{"https://redirect.to", "additional.origin"}, + Version: domain.OIDCVersionV1, + ClientID: "oidc-client-id", + RedirectURIs: database.StringArray{"https://redirect.to/me"}, + ResponseTypes: database.EnumArray[domain.OIDCResponseType]{domain.OIDCResponseTypeIDTokenToken}, + GrantTypes: database.EnumArray[domain.OIDCGrantType]{domain.OIDCGrantTypeImplicit}, + AppType: domain.OIDCApplicationTypeUserAgent, + AuthMethodType: domain.OIDCAuthMethodTypeNone, + PostLogoutRedirectURIs: database.StringArray{"post.logout.ch"}, + IsDevMode: true, + AccessTokenType: domain.OIDCTokenTypeJWT, + AssertAccessTokenRole: true, + AssertIDTokenRole: true, + AssertIDTokenUserinfo: true, + ClockSkew: 1 * time.Second, + AdditionalOrigins: database.StringArray{"additional.origin"}, + ComplianceProblems: nil, + AllowedOrigins: database.StringArray{"https://redirect.to", "additional.origin"}, + SkipNativeAppSuccessPage: false, }, }, { @@ -1085,6 +1191,7 @@ func Test_AppPrepare(t *testing.T) { nil, nil, nil, + nil, // saml config nil, nil, @@ -1142,6 +1249,7 @@ func Test_AppPrepare(t *testing.T) { nil, nil, nil, + nil, // saml config nil, nil, @@ -1204,6 +1312,7 @@ func Test_AppPrepare(t *testing.T) { true, 1 * time.Second, database.StringArray{"additional.origin"}, + false, // saml config nil, nil, @@ -1223,23 +1332,24 @@ func Test_AppPrepare(t *testing.T) { Name: "app-name", ProjectID: "project-id", OIDCConfig: &OIDCApp{ - Version: domain.OIDCVersionV1, - ClientID: "oidc-client-id", - RedirectURIs: database.StringArray{"https://redirect.to/me"}, - ResponseTypes: database.EnumArray[domain.OIDCResponseType]{domain.OIDCResponseTypeIDTokenToken}, - GrantTypes: database.EnumArray[domain.OIDCGrantType]{domain.OIDCGrantTypeImplicit}, - AppType: domain.OIDCApplicationTypeUserAgent, - AuthMethodType: domain.OIDCAuthMethodTypeNone, - PostLogoutRedirectURIs: database.StringArray{"post.logout.ch"}, - IsDevMode: true, - AccessTokenType: domain.OIDCTokenTypeJWT, - AssertAccessTokenRole: true, - AssertIDTokenRole: true, - AssertIDTokenUserinfo: true, - ClockSkew: 1 * time.Second, - AdditionalOrigins: database.StringArray{"additional.origin"}, - ComplianceProblems: nil, - AllowedOrigins: database.StringArray{"https://redirect.to", "additional.origin"}, + Version: domain.OIDCVersionV1, + ClientID: "oidc-client-id", + RedirectURIs: database.StringArray{"https://redirect.to/me"}, + ResponseTypes: database.EnumArray[domain.OIDCResponseType]{domain.OIDCResponseTypeIDTokenToken}, + GrantTypes: database.EnumArray[domain.OIDCGrantType]{domain.OIDCGrantTypeImplicit}, + AppType: domain.OIDCApplicationTypeUserAgent, + AuthMethodType: domain.OIDCAuthMethodTypeNone, + PostLogoutRedirectURIs: database.StringArray{"post.logout.ch"}, + IsDevMode: true, + AccessTokenType: domain.OIDCTokenTypeJWT, + AssertAccessTokenRole: true, + AssertIDTokenRole: true, + AssertIDTokenUserinfo: true, + ClockSkew: 1 * time.Second, + AdditionalOrigins: database.StringArray{"additional.origin"}, + ComplianceProblems: nil, + AllowedOrigins: database.StringArray{"https://redirect.to", "additional.origin"}, + SkipNativeAppSuccessPage: false, }, }, }, { @@ -1280,6 +1390,7 @@ func Test_AppPrepare(t *testing.T) { nil, nil, nil, + nil, // saml config "app-id", "https://test.com/saml/metadata", @@ -1343,6 +1454,7 @@ func Test_AppPrepare(t *testing.T) { true, 1 * time.Second, database.StringArray{"additional.origin"}, + false, // saml config nil, nil, @@ -1362,23 +1474,24 @@ func Test_AppPrepare(t *testing.T) { Name: "app-name", ProjectID: "project-id", OIDCConfig: &OIDCApp{ - Version: domain.OIDCVersionV1, - ClientID: "oidc-client-id", - RedirectURIs: database.StringArray{"https://redirect.to/me"}, - ResponseTypes: database.EnumArray[domain.OIDCResponseType]{domain.OIDCResponseTypeIDTokenToken}, - GrantTypes: database.EnumArray[domain.OIDCGrantType]{domain.OIDCGrantTypeImplicit}, - AppType: domain.OIDCApplicationTypeUserAgent, - AuthMethodType: domain.OIDCAuthMethodTypeNone, - PostLogoutRedirectURIs: database.StringArray{"post.logout.ch"}, - IsDevMode: false, - AccessTokenType: domain.OIDCTokenTypeJWT, - AssertAccessTokenRole: true, - AssertIDTokenRole: true, - AssertIDTokenUserinfo: true, - ClockSkew: 1 * time.Second, - AdditionalOrigins: database.StringArray{"additional.origin"}, - ComplianceProblems: nil, - AllowedOrigins: database.StringArray{"https://redirect.to", "additional.origin"}, + Version: domain.OIDCVersionV1, + ClientID: "oidc-client-id", + RedirectURIs: database.StringArray{"https://redirect.to/me"}, + ResponseTypes: database.EnumArray[domain.OIDCResponseType]{domain.OIDCResponseTypeIDTokenToken}, + GrantTypes: database.EnumArray[domain.OIDCGrantType]{domain.OIDCGrantTypeImplicit}, + AppType: domain.OIDCApplicationTypeUserAgent, + AuthMethodType: domain.OIDCAuthMethodTypeNone, + PostLogoutRedirectURIs: database.StringArray{"post.logout.ch"}, + IsDevMode: false, + AccessTokenType: domain.OIDCTokenTypeJWT, + AssertAccessTokenRole: true, + AssertIDTokenRole: true, + AssertIDTokenUserinfo: true, + ClockSkew: 1 * time.Second, + AdditionalOrigins: database.StringArray{"additional.origin"}, + ComplianceProblems: nil, + AllowedOrigins: database.StringArray{"https://redirect.to", "additional.origin"}, + SkipNativeAppSuccessPage: false, }, }, }, @@ -1420,6 +1533,7 @@ func Test_AppPrepare(t *testing.T) { true, 1 * time.Second, database.StringArray{"additional.origin"}, + false, // saml config nil, nil, @@ -1439,23 +1553,24 @@ func Test_AppPrepare(t *testing.T) { Name: "app-name", ProjectID: "project-id", OIDCConfig: &OIDCApp{ - Version: domain.OIDCVersionV1, - ClientID: "oidc-client-id", - RedirectURIs: database.StringArray{"https://redirect.to/me"}, - ResponseTypes: database.EnumArray[domain.OIDCResponseType]{domain.OIDCResponseTypeIDTokenToken}, - GrantTypes: database.EnumArray[domain.OIDCGrantType]{domain.OIDCGrantTypeImplicit}, - AppType: domain.OIDCApplicationTypeUserAgent, - AuthMethodType: domain.OIDCAuthMethodTypeNone, - PostLogoutRedirectURIs: database.StringArray{"post.logout.ch"}, - IsDevMode: true, - AccessTokenType: domain.OIDCTokenTypeJWT, - AssertAccessTokenRole: false, - AssertIDTokenRole: true, - AssertIDTokenUserinfo: true, - ClockSkew: 1 * time.Second, - AdditionalOrigins: database.StringArray{"additional.origin"}, - ComplianceProblems: nil, - AllowedOrigins: database.StringArray{"https://redirect.to", "additional.origin"}, + Version: domain.OIDCVersionV1, + ClientID: "oidc-client-id", + RedirectURIs: database.StringArray{"https://redirect.to/me"}, + ResponseTypes: database.EnumArray[domain.OIDCResponseType]{domain.OIDCResponseTypeIDTokenToken}, + GrantTypes: database.EnumArray[domain.OIDCGrantType]{domain.OIDCGrantTypeImplicit}, + AppType: domain.OIDCApplicationTypeUserAgent, + AuthMethodType: domain.OIDCAuthMethodTypeNone, + PostLogoutRedirectURIs: database.StringArray{"post.logout.ch"}, + IsDevMode: true, + AccessTokenType: domain.OIDCTokenTypeJWT, + AssertAccessTokenRole: false, + AssertIDTokenRole: true, + AssertIDTokenUserinfo: true, + ClockSkew: 1 * time.Second, + AdditionalOrigins: database.StringArray{"additional.origin"}, + ComplianceProblems: nil, + AllowedOrigins: database.StringArray{"https://redirect.to", "additional.origin"}, + SkipNativeAppSuccessPage: false, }, }, }, @@ -1497,6 +1612,7 @@ func Test_AppPrepare(t *testing.T) { true, 1 * time.Second, database.StringArray{"additional.origin"}, + false, // saml config nil, nil, @@ -1516,23 +1632,24 @@ func Test_AppPrepare(t *testing.T) { Name: "app-name", ProjectID: "project-id", OIDCConfig: &OIDCApp{ - Version: domain.OIDCVersionV1, - ClientID: "oidc-client-id", - RedirectURIs: database.StringArray{"https://redirect.to/me"}, - ResponseTypes: database.EnumArray[domain.OIDCResponseType]{domain.OIDCResponseTypeIDTokenToken}, - GrantTypes: database.EnumArray[domain.OIDCGrantType]{domain.OIDCGrantTypeImplicit}, - AppType: domain.OIDCApplicationTypeUserAgent, - AuthMethodType: domain.OIDCAuthMethodTypeNone, - PostLogoutRedirectURIs: database.StringArray{"post.logout.ch"}, - IsDevMode: true, - AccessTokenType: domain.OIDCTokenTypeJWT, - AssertAccessTokenRole: true, - AssertIDTokenRole: false, - AssertIDTokenUserinfo: true, - ClockSkew: 1 * time.Second, - AdditionalOrigins: database.StringArray{"additional.origin"}, - ComplianceProblems: nil, - AllowedOrigins: database.StringArray{"https://redirect.to", "additional.origin"}, + Version: domain.OIDCVersionV1, + ClientID: "oidc-client-id", + RedirectURIs: database.StringArray{"https://redirect.to/me"}, + ResponseTypes: database.EnumArray[domain.OIDCResponseType]{domain.OIDCResponseTypeIDTokenToken}, + GrantTypes: database.EnumArray[domain.OIDCGrantType]{domain.OIDCGrantTypeImplicit}, + AppType: domain.OIDCApplicationTypeUserAgent, + AuthMethodType: domain.OIDCAuthMethodTypeNone, + PostLogoutRedirectURIs: database.StringArray{"post.logout.ch"}, + IsDevMode: true, + AccessTokenType: domain.OIDCTokenTypeJWT, + AssertAccessTokenRole: true, + AssertIDTokenRole: false, + AssertIDTokenUserinfo: true, + ClockSkew: 1 * time.Second, + AdditionalOrigins: database.StringArray{"additional.origin"}, + ComplianceProblems: nil, + AllowedOrigins: database.StringArray{"https://redirect.to", "additional.origin"}, + SkipNativeAppSuccessPage: false, }, }, }, @@ -1574,6 +1691,7 @@ func Test_AppPrepare(t *testing.T) { false, 1 * time.Second, database.StringArray{"additional.origin"}, + false, // saml config nil, nil, @@ -1593,23 +1711,24 @@ func Test_AppPrepare(t *testing.T) { Name: "app-name", ProjectID: "project-id", OIDCConfig: &OIDCApp{ - Version: domain.OIDCVersionV1, - ClientID: "oidc-client-id", - RedirectURIs: database.StringArray{"https://redirect.to/me"}, - ResponseTypes: database.EnumArray[domain.OIDCResponseType]{domain.OIDCResponseTypeIDTokenToken}, - GrantTypes: database.EnumArray[domain.OIDCGrantType]{domain.OIDCGrantTypeImplicit}, - AppType: domain.OIDCApplicationTypeUserAgent, - AuthMethodType: domain.OIDCAuthMethodTypeNone, - PostLogoutRedirectURIs: database.StringArray{"post.logout.ch"}, - IsDevMode: true, - AccessTokenType: domain.OIDCTokenTypeJWT, - AssertAccessTokenRole: true, - AssertIDTokenRole: true, - AssertIDTokenUserinfo: false, - ClockSkew: 1 * time.Second, - AdditionalOrigins: database.StringArray{"additional.origin"}, - ComplianceProblems: nil, - AllowedOrigins: database.StringArray{"https://redirect.to", "additional.origin"}, + Version: domain.OIDCVersionV1, + ClientID: "oidc-client-id", + RedirectURIs: database.StringArray{"https://redirect.to/me"}, + ResponseTypes: database.EnumArray[domain.OIDCResponseType]{domain.OIDCResponseTypeIDTokenToken}, + GrantTypes: database.EnumArray[domain.OIDCGrantType]{domain.OIDCGrantTypeImplicit}, + AppType: domain.OIDCApplicationTypeUserAgent, + AuthMethodType: domain.OIDCAuthMethodTypeNone, + PostLogoutRedirectURIs: database.StringArray{"post.logout.ch"}, + IsDevMode: true, + AccessTokenType: domain.OIDCTokenTypeJWT, + AssertAccessTokenRole: true, + AssertIDTokenRole: true, + AssertIDTokenUserinfo: false, + ClockSkew: 1 * time.Second, + AdditionalOrigins: database.StringArray{"additional.origin"}, + ComplianceProblems: nil, + AllowedOrigins: database.StringArray{"https://redirect.to", "additional.origin"}, + SkipNativeAppSuccessPage: false, }, }, }, diff --git a/internal/query/projection/app.go b/internal/query/projection/app.go index ee5b34fbca..483764d5d5 100644 --- a/internal/query/projection/app.go +++ b/internal/query/projection/app.go @@ -15,7 +15,7 @@ import ( ) const ( - AppProjectionTable = "projections.apps4" + AppProjectionTable = "projections.apps5" AppAPITable = AppProjectionTable + "_" + appAPITableSuffix AppOIDCTable = AppProjectionTable + "_" + appOIDCTableSuffix AppSAMLTable = AppProjectionTable + "_" + appSAMLTableSuffix @@ -57,6 +57,7 @@ const ( AppOIDCConfigColumnIDTokenUserinfoAssertion = "id_token_userinfo_assertion" AppOIDCConfigColumnClockSkew = "clock_skew" AppOIDCConfigColumnAdditionalOrigins = "additional_origins" + AppOIDCConfigColumnSkipNativeAppSuccessPage = "skip_native_app_success_page" appSAMLTableSuffix = "saml_configs" AppSAMLConfigColumnAppID = "app_id" @@ -122,6 +123,7 @@ func newAppProjection(ctx context.Context, config crdb.StatementHandlerConfig) * crdb.NewColumn(AppOIDCConfigColumnIDTokenUserinfoAssertion, crdb.ColumnTypeBool, crdb.Default(false)), crdb.NewColumn(AppOIDCConfigColumnClockSkew, crdb.ColumnTypeInt64, crdb.Default(0)), crdb.NewColumn(AppOIDCConfigColumnAdditionalOrigins, crdb.ColumnTypeTextArray, crdb.Nullable()), + crdb.NewColumn(AppOIDCConfigColumnSkipNativeAppSuccessPage, crdb.ColumnTypeBool, crdb.Default(false)), }, crdb.NewPrimaryKey(AppOIDCConfigColumnInstanceID, AppOIDCConfigColumnAppID), appOIDCTableSuffix, @@ -463,6 +465,7 @@ func (p *appProjection) reduceOIDCConfigAdded(event eventstore.Event) (*handler. handler.NewCol(AppOIDCConfigColumnIDTokenUserinfoAssertion, e.IDTokenUserinfoAssertion), handler.NewCol(AppOIDCConfigColumnClockSkew, e.ClockSkew), handler.NewCol(AppOIDCConfigColumnAdditionalOrigins, database.StringArray(e.AdditionalOrigins)), + handler.NewCol(AppOIDCConfigColumnSkipNativeAppSuccessPage, e.SkipNativeAppSuccessPage), }, crdb.WithTableSuffix(appOIDCTableSuffix), ), @@ -528,6 +531,9 @@ func (p *appProjection) reduceOIDCConfigChanged(event eventstore.Event) (*handle if e.AdditionalOrigins != nil { cols = append(cols, handler.NewCol(AppOIDCConfigColumnAdditionalOrigins, database.StringArray(*e.AdditionalOrigins))) } + if e.SkipNativeAppSuccessPage != nil { + cols = append(cols, handler.NewCol(AppOIDCConfigColumnSkipNativeAppSuccessPage, *e.SkipNativeAppSuccessPage)) + } if len(cols) == 0 { return crdb.NewNoOpStatement(e), nil diff --git a/internal/query/projection/app_test.go b/internal/query/projection/app_test.go index 9320b62ad3..c605ed6dc4 100644 --- a/internal/query/projection/app_test.go +++ b/internal/query/projection/app_test.go @@ -45,7 +45,7 @@ func TestAppProjection_reduces(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "INSERT INTO projections.apps4 (id, name, project_id, creation_date, change_date, resource_owner, instance_id, state, sequence) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)", + expectedStmt: "INSERT INTO projections.apps5 (id, name, project_id, creation_date, change_date, resource_owner, instance_id, state, sequence) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)", expectedArgs: []interface{}{ "app-id", "my-app", @@ -82,7 +82,7 @@ func TestAppProjection_reduces(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "UPDATE projections.apps4 SET (name, change_date, sequence) = ($1, $2, $3) WHERE (id = $4) AND (instance_id = $5)", + expectedStmt: "UPDATE projections.apps5 SET (name, change_date, sequence) = ($1, $2, $3) WHERE (id = $4) AND (instance_id = $5)", expectedArgs: []interface{}{ "my-app", anyArg{}, @@ -95,6 +95,27 @@ func TestAppProjection_reduces(t *testing.T) { }, }, }, + { + name: "project reduceAppChanged no change", + args: args{ + event: getEvent(testEvent( + repository.EventType(project.ApplicationChangedType), + project.AggregateType, + []byte(`{ + "appId": "app-id" + }`), + ), project.ApplicationChangedEventMapper), + }, + reduce: (&appProjection{}).reduceAppChanged, + want: wantReduce{ + aggregateType: eventstore.AggregateType("project"), + sequence: 15, + previousSequence: 10, + executer: &testExecuter{ + executions: []execution{}, + }, + }, + }, { name: "project reduceAppDeactivated", args: args{ @@ -114,7 +135,7 @@ func TestAppProjection_reduces(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "UPDATE projections.apps4 SET (state, change_date, sequence) = ($1, $2, $3) WHERE (id = $4) AND (instance_id = $5)", + expectedStmt: "UPDATE projections.apps5 SET (state, change_date, sequence) = ($1, $2, $3) WHERE (id = $4) AND (instance_id = $5)", expectedArgs: []interface{}{ domain.AppStateInactive, anyArg{}, @@ -146,7 +167,7 @@ func TestAppProjection_reduces(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "UPDATE projections.apps4 SET (state, change_date, sequence) = ($1, $2, $3) WHERE (id = $4) AND (instance_id = $5)", + expectedStmt: "UPDATE projections.apps5 SET (state, change_date, sequence) = ($1, $2, $3) WHERE (id = $4) AND (instance_id = $5)", expectedArgs: []interface{}{ domain.AppStateActive, anyArg{}, @@ -178,7 +199,7 @@ func TestAppProjection_reduces(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "DELETE FROM projections.apps4 WHERE (id = $1) AND (instance_id = $2)", + expectedStmt: "DELETE FROM projections.apps5 WHERE (id = $1) AND (instance_id = $2)", expectedArgs: []interface{}{ "app-id", "instance-id", @@ -205,7 +226,7 @@ func TestAppProjection_reduces(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "DELETE FROM projections.apps4 WHERE (project_id = $1) AND (instance_id = $2)", + expectedStmt: "DELETE FROM projections.apps5 WHERE (project_id = $1) AND (instance_id = $2)", expectedArgs: []interface{}{ "agg-id", "instance-id", @@ -232,7 +253,7 @@ func TestAppProjection_reduces(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "DELETE FROM projections.apps4 WHERE (instance_id = $1)", + expectedStmt: "DELETE FROM projections.apps5 WHERE (instance_id = $1)", expectedArgs: []interface{}{ "agg-id", }, @@ -263,7 +284,7 @@ func TestAppProjection_reduces(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "INSERT INTO projections.apps4_api_configs (app_id, instance_id, client_id, client_secret, auth_method) VALUES ($1, $2, $3, $4, $5)", + expectedStmt: "INSERT INTO projections.apps5_api_configs (app_id, instance_id, client_id, client_secret, auth_method) VALUES ($1, $2, $3, $4, $5)", expectedArgs: []interface{}{ "app-id", "instance-id", @@ -273,7 +294,7 @@ func TestAppProjection_reduces(t *testing.T) { }, }, { - expectedStmt: "UPDATE projections.apps4 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)", + expectedStmt: "UPDATE projections.apps5 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)", expectedArgs: []interface{}{ anyArg{}, uint64(15), @@ -307,7 +328,7 @@ func TestAppProjection_reduces(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "UPDATE projections.apps4_api_configs SET (client_secret, auth_method) = ($1, $2) WHERE (app_id = $3) AND (instance_id = $4)", + expectedStmt: "UPDATE projections.apps5_api_configs SET (client_secret, auth_method) = ($1, $2) WHERE (app_id = $3) AND (instance_id = $4)", expectedArgs: []interface{}{ anyArg{}, domain.APIAuthMethodTypePrivateKeyJWT, @@ -316,7 +337,7 @@ func TestAppProjection_reduces(t *testing.T) { }, }, { - expectedStmt: "UPDATE projections.apps4 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)", + expectedStmt: "UPDATE projections.apps5 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)", expectedArgs: []interface{}{ anyArg{}, uint64(15), @@ -369,7 +390,7 @@ func TestAppProjection_reduces(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "UPDATE projections.apps4_api_configs SET client_secret = $1 WHERE (app_id = $2) AND (instance_id = $3)", + expectedStmt: "UPDATE projections.apps5_api_configs SET client_secret = $1 WHERE (app_id = $2) AND (instance_id = $3)", expectedArgs: []interface{}{ anyArg{}, "app-id", @@ -377,7 +398,7 @@ func TestAppProjection_reduces(t *testing.T) { }, }, { - expectedStmt: "UPDATE projections.apps4 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)", + expectedStmt: "UPDATE projections.apps5 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)", expectedArgs: []interface{}{ anyArg{}, uint64(15), @@ -412,7 +433,8 @@ func TestAppProjection_reduces(t *testing.T) { "idTokenRoleAssertion": true, "idTokenUserinfoAssertion": true, "clockSkew": 1000, - "additionalOrigins": ["origin.one.ch", "origin.two.ch"] + "additionalOrigins": ["origin.one.ch", "origin.two.ch"], + "skipNativeAppSuccessPage": true }`), ), project.OIDCConfigAddedEventMapper), }, @@ -424,7 +446,7 @@ func TestAppProjection_reduces(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "INSERT INTO projections.apps4_oidc_configs (app_id, instance_id, version, client_id, client_secret, redirect_uris, response_types, grant_types, application_type, auth_method_type, post_logout_redirect_uris, is_dev_mode, access_token_type, access_token_role_assertion, id_token_role_assertion, id_token_userinfo_assertion, clock_skew, additional_origins) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18)", + expectedStmt: "INSERT INTO projections.apps5_oidc_configs (app_id, instance_id, version, client_id, client_secret, redirect_uris, response_types, grant_types, application_type, auth_method_type, post_logout_redirect_uris, is_dev_mode, access_token_type, access_token_role_assertion, id_token_role_assertion, id_token_userinfo_assertion, clock_skew, additional_origins, skip_native_app_success_page) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19)", expectedArgs: []interface{}{ "app-id", "instance-id", @@ -444,10 +466,11 @@ func TestAppProjection_reduces(t *testing.T) { true, 1 * time.Microsecond, database.StringArray{"origin.one.ch", "origin.two.ch"}, + true, }, }, { - expectedStmt: "UPDATE projections.apps4 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)", + expectedStmt: "UPDATE projections.apps5 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)", expectedArgs: []interface{}{ anyArg{}, uint64(15), @@ -480,7 +503,9 @@ func TestAppProjection_reduces(t *testing.T) { "idTokenRoleAssertion": true, "idTokenUserinfoAssertion": true, "clockSkew": 1000, - "additionalOrigins": ["origin.one.ch", "origin.two.ch"] + "additionalOrigins": ["origin.one.ch", "origin.two.ch"], + "skipNativeAppSuccessPage": true + }`), ), project.OIDCConfigChangedEventMapper), }, @@ -492,7 +517,7 @@ func TestAppProjection_reduces(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "UPDATE projections.apps4_oidc_configs SET (version, redirect_uris, response_types, grant_types, application_type, auth_method_type, post_logout_redirect_uris, is_dev_mode, access_token_type, access_token_role_assertion, id_token_role_assertion, id_token_userinfo_assertion, clock_skew, additional_origins) = ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14) WHERE (app_id = $15) AND (instance_id = $16)", + expectedStmt: "UPDATE projections.apps5_oidc_configs SET (version, redirect_uris, response_types, grant_types, application_type, auth_method_type, post_logout_redirect_uris, is_dev_mode, access_token_type, access_token_role_assertion, id_token_role_assertion, id_token_userinfo_assertion, clock_skew, additional_origins, skip_native_app_success_page) = ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15) WHERE (app_id = $16) AND (instance_id = $17)", expectedArgs: []interface{}{ domain.OIDCVersionV1, database.StringArray{"redirect.one.ch", "redirect.two.ch"}, @@ -508,12 +533,13 @@ func TestAppProjection_reduces(t *testing.T) { true, 1 * time.Microsecond, database.StringArray{"origin.one.ch", "origin.two.ch"}, + true, "app-id", "instance-id", }, }, { - expectedStmt: "UPDATE projections.apps4 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)", + expectedStmt: "UPDATE projections.apps5 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)", expectedArgs: []interface{}{ anyArg{}, uint64(15), @@ -566,7 +592,7 @@ func TestAppProjection_reduces(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "UPDATE projections.apps4_oidc_configs SET client_secret = $1 WHERE (app_id = $2) AND (instance_id = $3)", + expectedStmt: "UPDATE projections.apps5_oidc_configs SET client_secret = $1 WHERE (app_id = $2) AND (instance_id = $3)", expectedArgs: []interface{}{ anyArg{}, "app-id", @@ -574,7 +600,7 @@ func TestAppProjection_reduces(t *testing.T) { }, }, { - expectedStmt: "UPDATE projections.apps4 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)", + expectedStmt: "UPDATE projections.apps5 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)", expectedArgs: []interface{}{ anyArg{}, uint64(15), @@ -603,7 +629,7 @@ func TestAppProjection_reduces(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "UPDATE projections.apps4 SET (change_date, sequence, owner_removed) = ($1, $2, $3) WHERE (instance_id = $4) AND (resource_owner = $5)", + expectedStmt: "UPDATE projections.apps5 SET (change_date, sequence, owner_removed) = ($1, $2, $3) WHERE (instance_id = $4) AND (resource_owner = $5)", expectedArgs: []interface{}{ anyArg{}, uint64(15), diff --git a/internal/repository/project/oidc_config.go b/internal/repository/project/oidc_config.go index df809daa7c..d39e92c2aa 100644 --- a/internal/repository/project/oidc_config.go +++ b/internal/repository/project/oidc_config.go @@ -40,6 +40,7 @@ type OIDCConfigAddedEvent struct { IDTokenUserinfoAssertion bool `json:"idTokenUserinfoAssertion,omitempty"` ClockSkew time.Duration `json:"clockSkew,omitempty"` AdditionalOrigins []string `json:"additionalOrigins,omitempty"` + SkipNativeAppSuccessPage bool `json:"skipNativeAppSuccessPage,omitempty"` } func (e *OIDCConfigAddedEvent) Data() interface{} { @@ -70,6 +71,7 @@ func NewOIDCConfigAddedEvent( idTokenUserinfoAssertion bool, clockSkew time.Duration, additionalOrigins []string, + skipNativeAppSuccessPage bool, ) *OIDCConfigAddedEvent { return &OIDCConfigAddedEvent{ BaseEvent: *eventstore.NewBaseEventForPush( @@ -94,6 +96,7 @@ func NewOIDCConfigAddedEvent( IDTokenUserinfoAssertion: idTokenUserinfoAssertion, ClockSkew: clockSkew, AdditionalOrigins: additionalOrigins, + SkipNativeAppSuccessPage: skipNativeAppSuccessPage, } } @@ -179,8 +182,7 @@ func (e *OIDCConfigAddedEvent) Validate(cmd eventstore.Command) bool { return false } } - - return true + return e.SkipNativeAppSuccessPage == c.SkipNativeAppSuccessPage } func OIDCConfigAddedEventMapper(event *repository.Event) (eventstore.Event, error) { @@ -214,6 +216,7 @@ type OIDCConfigChangedEvent struct { IDTokenUserinfoAssertion *bool `json:"idTokenUserinfoAssertion,omitempty"` ClockSkew *time.Duration `json:"clockSkew,omitempty"` AdditionalOrigins *[]string `json:"additionalOrigins,omitempty"` + SkipNativeAppSuccessPage *bool `json:"skipNativeAppSuccessPage,omitempty"` } func (e *OIDCConfigChangedEvent) Data() interface{} { @@ -334,6 +337,12 @@ func ChangeAdditionalOrigins(additionalOrigins []string) func(event *OIDCConfigC } } +func ChangeSkipNativeAppSuccessPage(skipNativeAppSuccessPage bool) func(event *OIDCConfigChangedEvent) { + return func(e *OIDCConfigChangedEvent) { + e.SkipNativeAppSuccessPage = &skipNativeAppSuccessPage + } +} + func OIDCConfigChangedEventMapper(event *repository.Event) (eventstore.Event, error) { e := &OIDCConfigChangedEvent{ BaseEvent: *eventstore.BaseEventFromRepo(event), diff --git a/proto/zitadel/app.proto b/proto/zitadel/app.proto index 0a855f9552..d889135bc4 100644 --- a/proto/zitadel/app.proto +++ b/proto/zitadel/app.proto @@ -163,6 +163,11 @@ message OIDCConfig { description: "all allowed origins from where the API can be used"; } ]; + bool skip_native_app_success_page = 20 [ + (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = { + description: "Skip the successful login page on native apps and directly redirect the user to the callback."; + } + ]; } enum OIDCResponseType { diff --git a/proto/zitadel/management.proto b/proto/zitadel/management.proto index 71dd9b99ff..e410d839ae 100644 --- a/proto/zitadel/management.proto +++ b/proto/zitadel/management.proto @@ -8773,6 +8773,11 @@ message AddOIDCAppRequest { description: "Additional origins (other than the redirect_uris) from where the API can be used"; } ]; + bool skip_native_app_success_page = 17 [ + (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = { + description: "Skip the successful login page on native apps and directly redirect the user to the callback."; + } + ]; } message AddOIDCAppResponse { @@ -8943,6 +8948,11 @@ message UpdateOIDCAppConfigRequest { description: "Additional origins (other than the redirect_uris) from where the API can be used"; } ]; + bool skip_native_app_success_page = 16 [ + (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = { + description: "Skip the successful login page on native apps and directly redirect the user to the callback."; + } + ]; } message UpdateOIDCAppConfigResponse {