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
This commit is contained in:
Livio Spring 2023-04-11 17:07:32 +02:00
parent 991a56341b
commit d25454b84b
No known key found for this signature in database
GPG Key ID: 26BB1C2FA5952CF0
32 changed files with 641 additions and 390 deletions

View File

@ -305,7 +305,7 @@
</span> </span>
<span class="right"> <span class="right">
<span> <span>
{{ 'APP.API.AUTHMETHOD.' + authMethodType?.value | translate }} {{ 'APP.API.AUTHMETHOD.' + apiAppRequest.toObject().authMethodType | translate }}
</span> </span>
</span> </span>
</div> </div>

View File

@ -331,11 +331,24 @@
</div> </div>
</cnsl-info-section> </cnsl-info-section>
<mat-checkbox
*ngIf="skipNativeAppSuccessPage && appType?.value === OIDCAppType.OIDC_APP_TYPE_NATIVE"
class="full-width"
style="margin-top: 1.5rem"
[formControl]="skipNativeAppSuccessPage"
color="primary"
>
{{ 'APP.OIDC.SKIPNATIVEAPPSUCCESSPAGE' | translate }}</mat-checkbox
>
<cnsl-info-section *ngIf="appType?.value === OIDCAppType.OIDC_APP_TYPE_NATIVE" class="full-width app-desc">
<span>{{ 'APP.OIDC.SKIPNATIVEAPPSUCCESSPAGE_DESCRIPTION' | translate }}</span>
</cnsl-info-section>
<cnsl-redirect-uris <cnsl-redirect-uris
*ngIf="appType?.value !== undefined" *ngIf="appType?.value !== undefined"
class="redirect-section" class="redirect-section"
[disabled]="!canWrite" [disabled]="!canWrite"
[devMode]="devMode?.value" [devMode]="!!devMode?.value"
[(ngModel)]="redirectUrisList" [(ngModel)]="redirectUrisList"
title="{{ 'APP.OIDC.REDIRECT' | translate }}" title="{{ 'APP.OIDC.REDIRECT' | translate }}"
[isNative]="appType?.value === OIDCAppType.OIDC_APP_TYPE_NATIVE" [isNative]="appType?.value === OIDCAppType.OIDC_APP_TYPE_NATIVE"
@ -346,7 +359,7 @@
*ngIf="appType?.value !== undefined" *ngIf="appType?.value !== undefined"
class="redirect-section" class="redirect-section"
[disabled]="!canWrite" [disabled]="!canWrite"
[devMode]="devMode?.value" [devMode]="!!devMode?.value"
[(ngModel)]="postLogoutRedirectUrisList" [(ngModel)]="postLogoutRedirectUrisList"
title="{{ 'APP.OIDC.POSTLOGOUTREDIRECT' | translate }}" title="{{ 'APP.OIDC.POSTLOGOUTREDIRECT' | translate }}"
[isNative]="appType?.value === OIDCAppType.OIDC_APP_TYPE_NATIVE" [isNative]="appType?.value === OIDCAppType.OIDC_APP_TYPE_NATIVE"

View File

@ -2,7 +2,7 @@ import { COMMA, ENTER, SPACE } from '@angular/cdk/keycodes';
import { Location } from '@angular/common'; import { Location } from '@angular/common';
import { HttpClient } from '@angular/common/http'; import { HttpClient } from '@angular/common/http';
import { Component, OnDestroy, OnInit } from '@angular/core'; import { Component, OnDestroy, OnInit } from '@angular/core';
import { AbstractControl, UntypedFormBuilder, UntypedFormControl, UntypedFormGroup } from '@angular/forms'; import { AbstractControl, FormControl, UntypedFormBuilder, UntypedFormControl, UntypedFormGroup } from '@angular/forms';
import { MatLegacyCheckboxChange as MatCheckboxChange } from '@angular/material/legacy-checkbox'; import { MatLegacyCheckboxChange as MatCheckboxChange } from '@angular/material/legacy-checkbox';
import { MatLegacyDialog as MatDialog } from '@angular/material/legacy-dialog'; import { MatLegacyDialog as MatDialog } from '@angular/material/legacy-dialog';
import { ActivatedRoute, Router } from '@angular/router'; import { ActivatedRoute, Router } from '@angular/router';
@ -149,7 +149,8 @@ export class AppDetailComponent implements OnInit, OnDestroy {
private http: HttpClient, private http: HttpClient,
) { ) {
this.oidcForm = this.fb.group({ this.oidcForm = this.fb.group({
devMode: [{ value: false, disabled: true }, []], devMode: [{ value: false, disabled: true }],
skipNativeAppSuccessPage: [{ value: false, disabled: true }],
clientId: [{ value: '', disabled: true }], clientId: [{ value: '', disabled: true }],
responseTypesList: [{ value: [], disabled: true }], responseTypesList: [{ value: [], disabled: true }],
grantTypesList: [{ value: [], disabled: true }], grantTypesList: [{ value: [], disabled: true }],
@ -548,7 +549,8 @@ export class AppDetailComponent implements OnInit, OnDestroy {
this.app.oidcConfig.redirectUrisList = this.redirectUrisList; this.app.oidcConfig.redirectUrisList = this.redirectUrisList;
this.app.oidcConfig.postLogoutRedirectUrisList = this.postLogoutRedirectUrisList; this.app.oidcConfig.postLogoutRedirectUrisList = this.postLogoutRedirectUrisList;
this.app.oidcConfig.additionalOriginsList = this.additionalOriginsList; this.app.oidcConfig.additionalOriginsList = this.additionalOriginsList;
this.app.oidcConfig.devMode = this.devMode?.value; this.app.oidcConfig.devMode = !!this.devMode?.value;
this.app.oidcConfig.skipNativeAppSuccessPage = !!this.skipNativeAppSuccessPage?.value;
const req = new UpdateOIDCAppConfigRequest(); const req = new UpdateOIDCAppConfigRequest();
req.setProjectId(this.projectId); req.setProjectId(this.projectId);
@ -571,6 +573,7 @@ export class AppDetailComponent implements OnInit, OnDestroy {
req.setAdditionalOriginsList(this.app.oidcConfig.additionalOriginsList); req.setAdditionalOriginsList(this.app.oidcConfig.additionalOriginsList);
req.setPostLogoutRedirectUrisList(this.app.oidcConfig.postLogoutRedirectUrisList); req.setPostLogoutRedirectUrisList(this.app.oidcConfig.postLogoutRedirectUrisList);
req.setDevMode(this.app.oidcConfig.devMode); req.setDevMode(this.app.oidcConfig.devMode);
req.setSkipNativeAppSuccessPage(this.app.oidcConfig.skipNativeAppSuccessPage);
if (this.clockSkewSeconds?.value) { if (this.clockSkewSeconds?.value) {
const dur = new Duration(); const dur = new Duration();
@ -738,11 +741,15 @@ export class AppDetailComponent implements OnInit, OnDestroy {
} }
public get apiAuthMethodType(): AbstractControl | null { public get apiAuthMethodType(): AbstractControl | null {
return this.apiForm.get('authMethodType'); return this.apiForm.get('authMethodType') as UntypedFormControl;
} }
public get devMode(): UntypedFormControl | null { public get devMode(): FormControl<boolean> | null {
return this.oidcForm.get('devMode') as UntypedFormControl; return this.oidcForm.get('devMode') as FormControl<boolean>;
}
public get skipNativeAppSuccessPage(): FormControl<boolean> | null {
return this.oidcForm.get('skipNativeAppSuccessPage') as FormControl<boolean>;
} }
public get accessTokenType(): AbstractControl | null { public get accessTokenType(): AbstractControl | null {

View File

@ -1930,6 +1930,8 @@
"REGENERATESECRET": "Client Secret neu generieren", "REGENERATESECRET": "Client Secret neu generieren",
"DEVMODE": "Entwicklermodus", "DEVMODE": "Entwicklermodus",
"DEVMODEDESC": "Bei eingeschaltetem Entwicklermodus werden die Weiterleitungs-URIs im OIDC-Flow nicht validiert.", "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", "REDIRECT": "Weiterleitungs-URIs",
"REDIRECTSECTION": "Weiterleitungs-URIs", "REDIRECTSECTION": "Weiterleitungs-URIs",
"POSTLOGOUTREDIRECT": "URIs für Post-Log-out", "POSTLOGOUTREDIRECT": "URIs für Post-Log-out",

View File

@ -1923,6 +1923,8 @@
"REGENERATESECRET": "Regenerate Client Secret", "REGENERATESECRET": "Regenerate Client Secret",
"DEVMODE": "Development Mode", "DEVMODE": "Development Mode",
"DEVMODEDESC": "Beware: With development mode enabled redirect URIs will not be validated.", "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", "REDIRECT": "Redirect URIs",
"REDIRECTSECTION": "Redirect URIs", "REDIRECTSECTION": "Redirect URIs",
"POSTLOGOUTREDIRECT": "Post Logout URIs", "POSTLOGOUTREDIRECT": "Post Logout URIs",

View File

@ -1931,6 +1931,8 @@
"REGENERATESECRET": "Régénérer le secret du client", "REGENERATESECRET": "Régénérer le secret du client",
"DEVMODE": "Mode développement", "DEVMODE": "Mode développement",
"DEVMODEDESC": "Attention", "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", "REDIRECT": "Rediriger les URI",
"REDIRECTSECTION": "URI de redirection", "REDIRECTSECTION": "URI de redirection",
"POSTLOGOUTREDIRECT": "URIs de post-déconnexion", "POSTLOGOUTREDIRECT": "URIs de post-déconnexion",

View File

@ -1932,6 +1932,8 @@
"REGENERATESECRET": "Rigenera il Client Secret", "REGENERATESECRET": "Rigenera il Client Secret",
"DEVMODE": "Modalit\u00e0 di sviluppo (DEV Mode)", "DEVMODE": "Modalit\u00e0 di sviluppo (DEV Mode)",
"DEVMODEDESC": "Attenzione: Con la modalit\u00e0 di sviluppo abilitata, gli URI di reindirizzamento non saranno convalidati.", "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", "REDIRECT": "URI per il reindrizzamento",
"REDIRECTSECTION": "Reindirizzamento", "REDIRECTSECTION": "Reindirizzamento",
"POSTLOGOUTREDIRECT": "URI post logout", "POSTLOGOUTREDIRECT": "URI post logout",

View File

@ -1922,6 +1922,8 @@
"REGENERATESECRET": "クライアントシークレットを再生成する", "REGENERATESECRET": "クライアントシークレットを再生成する",
"DEVMODE": "開発モード", "DEVMODE": "開発モード",
"DEVMODEDESC": "注意開発モードを有効にすると、URIが認証されません。", "DEVMODEDESC": "注意開発モードを有効にすると、URIが認証されません。",
"SKIPNATIVEAPPSUCCESSPAGE": "ログイン後に成功ページをスキップする",
"SKIPNATIVEAPPSUCCESSPAGE_DESCRIPTION": "このネイティブアプリのログイン後に成功ページをスキップする",
"REDIRECT": "リダイレクトURI", "REDIRECT": "リダイレクトURI",
"REDIRECTSECTION": "リダイレクトURI", "REDIRECTSECTION": "リダイレクトURI",
"POSTLOGOUTREDIRECT": "ログアウトURI", "POSTLOGOUTREDIRECT": "ログアウトURI",

View File

@ -1931,6 +1931,8 @@
"REGENERATESECRET": "Odtwórz sekret klienta", "REGENERATESECRET": "Odtwórz sekret klienta",
"DEVMODE": "Tryb rozwoju", "DEVMODE": "Tryb rozwoju",
"DEVMODEDESC": "Uwaga: przy włączonym trybie rozwoju adresy URI przekierowania nie będą sprawdzane.", "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", "REDIRECT": "Adresy URI przekierowania",
"REDIRECTSECTION": "Adresy URI przekierowania", "REDIRECTSECTION": "Adresy URI przekierowania",
"POSTLOGOUTREDIRECT": "Adresy URI po wylogowaniu", "POSTLOGOUTREDIRECT": "Adresy URI po wylogowaniu",

View File

@ -1930,6 +1930,8 @@
"REGENERATESECRET": "重新生成客户端密钥", "REGENERATESECRET": "重新生成客户端密钥",
"DEVMODE": "开发模式", "DEVMODE": "开发模式",
"DEVMODEDESC": "注意:启用开发模式的重定向 URI 将不会被验证。", "DEVMODEDESC": "注意:启用开发模式的重定向 URI 将不会被验证。",
"SKIPNATIVEAPPSUCCESSPAGE": "登录后跳过成功页面",
"SKIPNATIVEAPPSUCCESSPAGE_DESCRIPTION": "登录后跳过本机应用的成功页面",
"REDIRECT": "重定向 URLs", "REDIRECT": "重定向 URLs",
"REDIRECTSECTION": "重定向 URLs", "REDIRECTSECTION": "重定向 URLs",
"POSTLOGOUTREDIRECT": "退出登录重定向 URLs", "POSTLOGOUTREDIRECT": "退出登录重定向 URLs",

View File

@ -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. 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. 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.
<img <img
alt="Redirect URIs" alt="Redirect URIs"

View File

@ -800,6 +800,7 @@ func (s *Server) getProjectsAndApps(ctx context.Context, org string) ([]*v1_pb.D
IdTokenUserinfoAssertion: app.OIDCConfig.AssertIDTokenUserinfo, IdTokenUserinfoAssertion: app.OIDCConfig.AssertIDTokenUserinfo,
ClockSkew: durationpb.New(app.OIDCConfig.ClockSkew), ClockSkew: durationpb.New(app.OIDCConfig.ClockSkew),
AdditionalOrigins: app.OIDCConfig.AdditionalOrigins, AdditionalOrigins: app.OIDCConfig.AdditionalOrigins,
SkipNativeAppSuccessPage: app.OIDCConfig.SkipNativeAppSuccessPage,
}, },
}) })
} }

View File

@ -56,6 +56,7 @@ func AddOIDCAppRequestToDomain(req *mgmt_pb.AddOIDCAppRequest) *domain.OIDCApp {
IDTokenUserinfoAssertion: req.IdTokenUserinfoAssertion, IDTokenUserinfoAssertion: req.IdTokenUserinfoAssertion,
ClockSkew: req.ClockSkew.AsDuration(), ClockSkew: req.ClockSkew.AsDuration(),
AdditionalOrigins: req.AdditionalOrigins, AdditionalOrigins: req.AdditionalOrigins,
SkipNativeAppSuccessPage: req.SkipNativeAppSuccessPage,
} }
} }
@ -106,6 +107,7 @@ func UpdateOIDCAppConfigRequestToDomain(app *mgmt_pb.UpdateOIDCAppConfigRequest)
IDTokenUserinfoAssertion: app.IdTokenUserinfoAssertion, IDTokenUserinfoAssertion: app.IdTokenUserinfoAssertion,
ClockSkew: app.ClockSkew.AsDuration(), ClockSkew: app.ClockSkew.AsDuration(),
AdditionalOrigins: app.AdditionalOrigins, AdditionalOrigins: app.AdditionalOrigins,
SkipNativeAppSuccessPage: app.SkipNativeAppSuccessPage,
} }
} }

View File

@ -60,6 +60,7 @@ func AppOIDCConfigToPb(app *query.OIDCApp) *app_pb.App_OidcConfig {
ClockSkew: durationpb.New(app.ClockSkew), ClockSkew: durationpb.New(app.ClockSkew),
AdditionalOrigins: app.AdditionalOrigins, AdditionalOrigins: app.AdditionalOrigins,
AllowedOrigins: app.AllowedOrigins, AllowedOrigins: app.AllowedOrigins,
SkipNativeAppSuccessPage: app.SkipNativeAppSuccessPage,
}, },
} }
} }

View File

@ -1,6 +1,7 @@
package login package login
import ( import (
"context"
"net/http" "net/http"
"github.com/zitadel/zitadel/internal/domain" "github.com/zitadel/zitadel/internal/domain"
@ -44,26 +45,31 @@ func (l *Login) renderSuccessAndCallback(w http.ResponseWriter, r *http.Request,
userData: l.getUserData(r, authReq, "LoginSuccess.Title", "", errID, errMessage), userData: l.getUserData(r, authReq, "LoginSuccess.Title", "", errID, errMessage),
} }
if authReq != nil { if authReq != nil {
//the id will be set via the html (maybe change this with the login refactoring) data.RedirectURI, err = l.authRequestCallback(r.Context(), authReq)
if _, ok := authReq.Request.(*domain.AuthRequestOIDC); ok { if err != nil {
data.RedirectURI = l.oidcAuthCallbackURL(r.Context(), "") l.renderInternalError(w, r, authReq, err)
} else if _, ok := authReq.Request.(*domain.AuthRequestSAML); ok { return
data.RedirectURI = l.samlAuthCallbackURL(r.Context(), "")
} }
} }
l.renderer.RenderTemplate(w, r, l.getTranslator(r.Context(), authReq), l.renderer.Templates[tmplLoginSuccess], data, nil) l.renderer.RenderTemplate(w, r, l.getTranslator(r.Context(), authReq), l.renderer.Templates[tmplLoginSuccess], data, nil)
} }
func (l *Login) redirectToCallback(w http.ResponseWriter, r *http.Request, authReq *domain.AuthRequest) { func (l *Login) redirectToCallback(w http.ResponseWriter, r *http.Request, authReq *domain.AuthRequest) {
var callback string callback, err := l.authRequestCallback(r.Context(), authReq)
switch authReq.Request.(type) { if err != nil {
case *domain.AuthRequestOIDC: l.renderInternalError(w, r, authReq, err)
callback = l.oidcAuthCallbackURL(r.Context(), authReq.ID)
case *domain.AuthRequestSAML:
callback = l.samlAuthCallbackURL(r.Context(), authReq.ID)
default:
l.renderInternalError(w, r, authReq, caos_errs.ThrowInternal(nil, "LOGIN-rhjQF", "Errors.AuthRequest.RequestTypeNotSupported"))
return return
} }
http.Redirect(w, r, callback, http.StatusFound) http.Redirect(w, r, callback, http.StatusFound)
} }
func (l *Login) authRequestCallback(ctx context.Context, authReq *domain.AuthRequest) (string, error) {
switch authReq.Request.(type) {
case *domain.AuthRequestOIDC:
return l.oidcAuthCallbackURL(ctx, authReq.ID), nil
case *domain.AuthRequestSAML:
return l.samlAuthCallbackURL(ctx, authReq.ID), nil
default:
return "", caos_errs.ThrowInternal(nil, "LOGIN-rhjQF", "Errors.AuthRequest.RequestTypeNotSupported")
}
}

View File

@ -5,13 +5,6 @@ document.addEventListener('DOMContentLoaded', function () {
function autoSubmit() { function autoSubmit() {
let form = document.getElementsByTagName('form')[0]; let form = document.getElementsByTagName('form')[0];
if (form) { if (form) {
let button = document.getElementById("redirect-button");
if (button) {
button.addEventListener("click", function (event) {
location.reload();
event.preventDefault();
});
}
form.submit(); form.submit();
} }
} }

View File

@ -1224,7 +1224,7 @@ func (repo *AuthRequestRepo) hasSucceededPage(ctx context.Context, request *doma
if err != nil { if err != nil {
return false, err 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) { func (repo *AuthRequestRepo) getDomainPolicy(ctx context.Context, orgID string) (*query.DomainPolicy, error) {

View File

@ -161,7 +161,9 @@ func TestCommandSide_AddInstanceDomain(t *testing.T) {
true, true,
true, true,
time.Second*1, time.Second*1,
[]string{"https://sub.test.ch"}), []string{"https://sub.test.ch"},
false,
),
), ),
), ),
expectPush( expectPush(

View File

@ -33,6 +33,7 @@ type addOIDCApp struct {
IDTokenUserinfoAssertion bool IDTokenUserinfoAssertion bool
ClockSkew time.Duration ClockSkew time.Duration
AdditionalOrigins []string AdditionalOrigins []string
SkipSuccessPageForNativeApp bool
ClientID string ClientID string
ClientSecret *crypto.CryptoValue ClientSecret *crypto.CryptoValue
@ -109,6 +110,7 @@ func (c *Commands) AddOIDCAppCommand(app *addOIDCApp, clientSecretAlg crypto.Has
app.IDTokenUserinfoAssertion, app.IDTokenUserinfoAssertion,
app.ClockSkew, app.ClockSkew,
app.AdditionalOrigins, app.AdditionalOrigins,
app.SkipSuccessPageForNativeApp,
), ),
}, nil }, nil
}, nil }, nil
@ -191,7 +193,9 @@ func (c *Commands) addOIDCApplicationWithID(ctx context.Context, oidcApp *domain
oidcApp.IDTokenRoleAssertion, oidcApp.IDTokenRoleAssertion,
oidcApp.IDTokenUserinfoAssertion, oidcApp.IDTokenUserinfoAssertion,
oidcApp.ClockSkew, oidcApp.ClockSkew,
oidcApp.AdditionalOrigins)) oidcApp.AdditionalOrigins,
oidcApp.SkipNativeAppSuccessPage,
))
addedApplication.AppID = oidcApp.AppID addedApplication.AppID = oidcApp.AppID
pushedEvents, err := c.eventstore.Push(ctx, events...) pushedEvents, err := c.eventstore.Push(ctx, events...)
@ -241,7 +245,9 @@ func (c *Commands) ChangeOIDCApplication(ctx context.Context, oidc *domain.OIDCA
oidc.IDTokenRoleAssertion, oidc.IDTokenRoleAssertion,
oidc.IDTokenUserinfoAssertion, oidc.IDTokenUserinfoAssertion,
oidc.ClockSkew, oidc.ClockSkew,
oidc.AdditionalOrigins) oidc.AdditionalOrigins,
oidc.SkipNativeAppSuccessPage,
)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@ -35,6 +35,7 @@ type OIDCApplicationWriteModel struct {
ClockSkew time.Duration ClockSkew time.Duration
State domain.AppState State domain.AppState
AdditionalOrigins []string AdditionalOrigins []string
SkipNativeAppSuccessPage bool
oidc bool oidc bool
} }
@ -156,6 +157,7 @@ func (wm *OIDCApplicationWriteModel) appendAddOIDCEvent(e *project.OIDCConfigAdd
wm.IDTokenUserinfoAssertion = e.IDTokenUserinfoAssertion wm.IDTokenUserinfoAssertion = e.IDTokenUserinfoAssertion
wm.ClockSkew = e.ClockSkew wm.ClockSkew = e.ClockSkew
wm.AdditionalOrigins = e.AdditionalOrigins wm.AdditionalOrigins = e.AdditionalOrigins
wm.SkipNativeAppSuccessPage = e.SkipNativeAppSuccessPage
} }
func (wm *OIDCApplicationWriteModel) appendChangeOIDCEvent(e *project.OIDCConfigChangedEvent) { func (wm *OIDCApplicationWriteModel) appendChangeOIDCEvent(e *project.OIDCConfigChangedEvent) {
@ -201,6 +203,9 @@ func (wm *OIDCApplicationWriteModel) appendChangeOIDCEvent(e *project.OIDCConfig
if e.AdditionalOrigins != nil { if e.AdditionalOrigins != nil {
wm.AdditionalOrigins = *e.AdditionalOrigins wm.AdditionalOrigins = *e.AdditionalOrigins
} }
if e.SkipNativeAppSuccessPage != nil {
wm.SkipNativeAppSuccessPage = *e.SkipNativeAppSuccessPage
}
} }
func (wm *OIDCApplicationWriteModel) Query() *eventstore.SearchQueryBuilder { func (wm *OIDCApplicationWriteModel) Query() *eventstore.SearchQueryBuilder {
@ -240,6 +245,7 @@ func (wm *OIDCApplicationWriteModel) NewChangedEvent(
idTokenUserinfoAssertion bool, idTokenUserinfoAssertion bool,
clockSkew time.Duration, clockSkew time.Duration,
additionalOrigins []string, additionalOrigins []string,
skipNativeAppSuccessPage bool,
) (*project.OIDCConfigChangedEvent, bool, error) { ) (*project.OIDCConfigChangedEvent, bool, error) {
changes := make([]project.OIDCConfigChanges, 0) changes := make([]project.OIDCConfigChanges, 0)
var err error var err error
@ -286,6 +292,10 @@ func (wm *OIDCApplicationWriteModel) NewChangedEvent(
if !reflect.DeepEqual(wm.AdditionalOrigins, additionalOrigins) { if !reflect.DeepEqual(wm.AdditionalOrigins, additionalOrigins) {
changes = append(changes, project.ChangeAdditionalOrigins(additionalOrigins)) changes = append(changes, project.ChangeAdditionalOrigins(additionalOrigins))
} }
if wm.SkipNativeAppSuccessPage != skipNativeAppSuccessPage {
changes = append(changes, project.ChangeSkipNativeAppSuccessPage(skipNativeAppSuccessPage))
}
if len(changes) == 0 { if len(changes) == 0 {
return nil, false, nil return nil, false, nil
} }

View File

@ -169,6 +169,7 @@ func TestAddOIDCApp(t *testing.T) {
false, false,
0, 0,
nil, nil,
false,
), ),
}, },
}, },
@ -325,7 +326,9 @@ func TestCommandSide_AddOIDCApplication(t *testing.T) {
true, true,
true, true,
time.Second*1, time.Second*1,
[]string{"https://sub.test.ch"}), []string{"https://sub.test.ch"},
true,
),
), ),
}, },
uniqueConstraintsFromEventConstraint(project.NewAddApplicationUniqueConstraint("app", "project1")), uniqueConstraintsFromEventConstraint(project.NewAddApplicationUniqueConstraint("app", "project1")),
@ -354,6 +357,7 @@ func TestCommandSide_AddOIDCApplication(t *testing.T) {
IDTokenUserinfoAssertion: true, IDTokenUserinfoAssertion: true,
ClockSkew: time.Second * 1, ClockSkew: time.Second * 1,
AdditionalOrigins: []string{"https://sub.test.ch"}, AdditionalOrigins: []string{"https://sub.test.ch"},
SkipNativeAppSuccessPage: true,
}, },
resourceOwner: "org1", resourceOwner: "org1",
secretGenerator: GetMockSecretGenerator(t), secretGenerator: GetMockSecretGenerator(t),
@ -382,6 +386,7 @@ func TestCommandSide_AddOIDCApplication(t *testing.T) {
IDTokenUserinfoAssertion: true, IDTokenUserinfoAssertion: true,
ClockSkew: time.Second * 1, ClockSkew: time.Second * 1,
AdditionalOrigins: []string{"https://sub.test.ch"}, AdditionalOrigins: []string{"https://sub.test.ch"},
SkipNativeAppSuccessPage: true,
State: domain.AppStateActive, State: domain.AppStateActive,
Compliance: &domain.Compliance{}, Compliance: &domain.Compliance{},
}, },
@ -558,7 +563,9 @@ func TestCommandSide_ChangeOIDCApplication(t *testing.T) {
true, true,
true, true,
time.Second*1, 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, IDTokenUserinfoAssertion: true,
ClockSkew: time.Second * 1, ClockSkew: time.Second * 1,
AdditionalOrigins: []string{"https://sub.test.ch"}, AdditionalOrigins: []string{"https://sub.test.ch"},
SkipNativeAppSuccessPage: true,
}, },
resourceOwner: "org1", resourceOwner: "org1",
}, },
@ -629,7 +637,9 @@ func TestCommandSide_ChangeOIDCApplication(t *testing.T) {
true, true,
true, true,
time.Second*1, time.Second*1,
[]string{"https://sub.test.ch"}), []string{"https://sub.test.ch"},
true,
),
), ),
), ),
expectPush( expectPush(
@ -666,6 +676,7 @@ func TestCommandSide_ChangeOIDCApplication(t *testing.T) {
IDTokenUserinfoAssertion: false, IDTokenUserinfoAssertion: false,
ClockSkew: time.Second * 2, ClockSkew: time.Second * 2,
AdditionalOrigins: []string{"https://sub.test.ch"}, AdditionalOrigins: []string{"https://sub.test.ch"},
SkipNativeAppSuccessPage: true,
}, },
resourceOwner: "org1", resourceOwner: "org1",
}, },
@ -692,6 +703,7 @@ func TestCommandSide_ChangeOIDCApplication(t *testing.T) {
IDTokenUserinfoAssertion: false, IDTokenUserinfoAssertion: false,
ClockSkew: time.Second * 2, ClockSkew: time.Second * 2,
AdditionalOrigins: []string{"https://sub.test.ch"}, AdditionalOrigins: []string{"https://sub.test.ch"},
SkipNativeAppSuccessPage: true,
Compliance: &domain.Compliance{}, Compliance: &domain.Compliance{},
State: domain.AppStateActive, State: domain.AppStateActive,
}, },
@ -826,7 +838,9 @@ func TestCommandSide_ChangeOIDCApplicationSecret(t *testing.T) {
true, true,
true, true,
time.Second*1, time.Second*1,
[]string{"https://sub.test.ch"}), []string{"https://sub.test.ch"},
false,
),
), ),
), ),
expectPush( expectPush(
@ -877,6 +891,7 @@ func TestCommandSide_ChangeOIDCApplicationSecret(t *testing.T) {
IDTokenUserinfoAssertion: true, IDTokenUserinfoAssertion: true,
ClockSkew: time.Second * 1, ClockSkew: time.Second * 1,
AdditionalOrigins: []string{"https://sub.test.ch"}, AdditionalOrigins: []string{"https://sub.test.ch"},
SkipNativeAppSuccessPage: false,
State: domain.AppStateActive, State: domain.AppStateActive,
}, },
}, },

View File

@ -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 { func oidcWriteModelToOIDCConfig(writeModel *OIDCApplicationWriteModel) *domain.OIDCApp {
return &domain.OIDCApp{ return &domain.OIDCApp{
ObjectRoot: writeModelToObjectRoot(writeModel.WriteModel), ObjectRoot: writeModelToObjectRoot(writeModel.WriteModel),
@ -54,6 +46,7 @@ func oidcWriteModelToOIDCConfig(writeModel *OIDCApplicationWriteModel) *domain.O
IDTokenUserinfoAssertion: writeModel.IDTokenUserinfoAssertion, IDTokenUserinfoAssertion: writeModel.IDTokenUserinfoAssertion,
ClockSkew: writeModel.ClockSkew, ClockSkew: writeModel.ClockSkew,
AdditionalOrigins: writeModel.AdditionalOrigins, AdditionalOrigins: writeModel.AdditionalOrigins,
SkipNativeAppSuccessPage: writeModel.SkipNativeAppSuccessPage,
} }
} }

View File

@ -3,9 +3,9 @@ package command
import ( import (
"context" "context"
"github.com/zitadel/zitadel/internal/crypto"
"github.com/zitadel/logging" "github.com/zitadel/logging"
"github.com/zitadel/zitadel/internal/crypto"
"github.com/zitadel/zitadel/internal/domain" "github.com/zitadel/zitadel/internal/domain"
caos_errs "github.com/zitadel/zitadel/internal/errors" caos_errs "github.com/zitadel/zitadel/internal/errors"
"github.com/zitadel/zitadel/internal/eventstore/v1/models" "github.com/zitadel/zitadel/internal/eventstore/v1/models"

View File

@ -45,6 +45,7 @@ type OIDCApp struct {
IDTokenUserinfoAssertion bool IDTokenUserinfoAssertion bool
ClockSkew time.Duration ClockSkew time.Duration
AdditionalOrigins []string AdditionalOrigins []string
SkipNativeAppSuccessPage bool
State AppState State AppState
} }

View File

@ -57,6 +57,7 @@ type OIDCApp struct {
ClockSkew time.Duration ClockSkew time.Duration
AdditionalOrigins database.StringArray AdditionalOrigins database.StringArray
AllowedOrigins database.StringArray AllowedOrigins database.StringArray
SkipNativeAppSuccessPage bool
} }
type SAMLApp struct { type SAMLApp struct {
@ -241,6 +242,10 @@ var (
name: projection.AppOIDCConfigColumnAdditionalOrigins, name: projection.AppOIDCConfigColumnAdditionalOrigins,
table: appOIDCConfigsTable, 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) { 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(), AppOIDCConfigColumnIDTokenUserinfoAssertion.identifier(),
AppOIDCConfigColumnClockSkew.identifier(), AppOIDCConfigColumnClockSkew.identifier(),
AppOIDCConfigColumnAdditionalOrigins.identifier(), AppOIDCConfigColumnAdditionalOrigins.identifier(),
AppOIDCConfigColumnSkipNativeAppSuccessPage.identifier(),
AppSAMLConfigColumnAppID.identifier(), AppSAMLConfigColumnAppID.identifier(),
AppSAMLConfigColumnEntityID.identifier(), AppSAMLConfigColumnEntityID.identifier(),
@ -583,6 +589,7 @@ func prepareAppQuery(ctx context.Context, db prepareDatabase) (sq.SelectBuilder,
&oidcConfig.iDTokenUserinfoAssertion, &oidcConfig.iDTokenUserinfoAssertion,
&oidcConfig.clockSkew, &oidcConfig.clockSkew,
&oidcConfig.additionalOrigins, &oidcConfig.additionalOrigins,
&oidcConfig.skipNativeAppSuccessPage,
&samlConfig.appID, &samlConfig.appID,
&samlConfig.entityID, &samlConfig.entityID,
@ -703,6 +710,7 @@ func prepareAppsQuery(ctx context.Context, db prepareDatabase) (sq.SelectBuilder
AppOIDCConfigColumnIDTokenUserinfoAssertion.identifier(), AppOIDCConfigColumnIDTokenUserinfoAssertion.identifier(),
AppOIDCConfigColumnClockSkew.identifier(), AppOIDCConfigColumnClockSkew.identifier(),
AppOIDCConfigColumnAdditionalOrigins.identifier(), AppOIDCConfigColumnAdditionalOrigins.identifier(),
AppOIDCConfigColumnSkipNativeAppSuccessPage.identifier(),
AppSAMLConfigColumnAppID.identifier(), AppSAMLConfigColumnAppID.identifier(),
AppSAMLConfigColumnEntityID.identifier(), AppSAMLConfigColumnEntityID.identifier(),
@ -754,6 +762,7 @@ func prepareAppsQuery(ctx context.Context, db prepareDatabase) (sq.SelectBuilder
&oidcConfig.iDTokenUserinfoAssertion, &oidcConfig.iDTokenUserinfoAssertion,
&oidcConfig.clockSkew, &oidcConfig.clockSkew,
&oidcConfig.additionalOrigins, &oidcConfig.additionalOrigins,
&oidcConfig.skipNativeAppSuccessPage,
&samlConfig.appID, &samlConfig.appID,
&samlConfig.entityID, &samlConfig.entityID,
@ -825,6 +834,7 @@ type sqlOIDCConfig struct {
additionalOrigins database.StringArray additionalOrigins database.StringArray
responseTypes database.EnumArray[domain.OIDCResponseType] responseTypes database.EnumArray[domain.OIDCResponseType]
grantTypes database.EnumArray[domain.OIDCGrantType] grantTypes database.EnumArray[domain.OIDCGrantType]
skipNativeAppSuccessPage sql.NullBool
} }
func (c sqlOIDCConfig) set(app *App) { func (c sqlOIDCConfig) set(app *App) {
@ -847,6 +857,7 @@ func (c sqlOIDCConfig) set(app *App) {
AdditionalOrigins: c.additionalOrigins, AdditionalOrigins: c.additionalOrigins,
ResponseTypes: c.responseTypes, ResponseTypes: c.responseTypes,
GrantTypes: c.grantTypes, 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) 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 app.OIDCConfig.ComplianceProblems = compliance.Problems

View File

@ -15,96 +15,98 @@ import (
) )
var ( var (
expectedAppQuery = regexp.QuoteMeta(`SELECT projections.apps4.id,` + expectedAppQuery = regexp.QuoteMeta(`SELECT projections.apps5.id,` +
` projections.apps4.name,` + ` projections.apps5.name,` +
` projections.apps4.project_id,` + ` projections.apps5.project_id,` +
` projections.apps4.creation_date,` + ` projections.apps5.creation_date,` +
` projections.apps4.change_date,` + ` projections.apps5.change_date,` +
` projections.apps4.resource_owner,` + ` projections.apps5.resource_owner,` +
` projections.apps4.state,` + ` projections.apps5.state,` +
` projections.apps4.sequence,` + ` projections.apps5.sequence,` +
// api config // api config
` projections.apps4_api_configs.app_id,` + ` projections.apps5_api_configs.app_id,` +
` projections.apps4_api_configs.client_id,` + ` projections.apps5_api_configs.client_id,` +
` projections.apps4_api_configs.auth_method,` + ` projections.apps5_api_configs.auth_method,` +
// oidc config // oidc config
` projections.apps4_oidc_configs.app_id,` + ` projections.apps5_oidc_configs.app_id,` +
` projections.apps4_oidc_configs.version,` + ` projections.apps5_oidc_configs.version,` +
` projections.apps4_oidc_configs.client_id,` + ` projections.apps5_oidc_configs.client_id,` +
` projections.apps4_oidc_configs.redirect_uris,` + ` projections.apps5_oidc_configs.redirect_uris,` +
` projections.apps4_oidc_configs.response_types,` + ` projections.apps5_oidc_configs.response_types,` +
` projections.apps4_oidc_configs.grant_types,` + ` projections.apps5_oidc_configs.grant_types,` +
` projections.apps4_oidc_configs.application_type,` + ` projections.apps5_oidc_configs.application_type,` +
` projections.apps4_oidc_configs.auth_method_type,` + ` projections.apps5_oidc_configs.auth_method_type,` +
` projections.apps4_oidc_configs.post_logout_redirect_uris,` + ` projections.apps5_oidc_configs.post_logout_redirect_uris,` +
` projections.apps4_oidc_configs.is_dev_mode,` + ` projections.apps5_oidc_configs.is_dev_mode,` +
` projections.apps4_oidc_configs.access_token_type,` + ` projections.apps5_oidc_configs.access_token_type,` +
` projections.apps4_oidc_configs.access_token_role_assertion,` + ` projections.apps5_oidc_configs.access_token_role_assertion,` +
` projections.apps4_oidc_configs.id_token_role_assertion,` + ` projections.apps5_oidc_configs.id_token_role_assertion,` +
` projections.apps4_oidc_configs.id_token_userinfo_assertion,` + ` projections.apps5_oidc_configs.id_token_userinfo_assertion,` +
` projections.apps4_oidc_configs.clock_skew,` + ` projections.apps5_oidc_configs.clock_skew,` +
` projections.apps4_oidc_configs.additional_origins,` + ` projections.apps5_oidc_configs.additional_origins,` +
` projections.apps5_oidc_configs.skip_native_app_success_page,` +
//saml config //saml config
` projections.apps4_saml_configs.app_id,` + ` projections.apps5_saml_configs.app_id,` +
` projections.apps4_saml_configs.entity_id,` + ` projections.apps5_saml_configs.entity_id,` +
` projections.apps4_saml_configs.metadata,` + ` projections.apps5_saml_configs.metadata,` +
` projections.apps4_saml_configs.metadata_url` + ` projections.apps5_saml_configs.metadata_url` +
` FROM projections.apps4` + ` FROM projections.apps5` +
` 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.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.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.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.apps4_saml_configs ON projections.apps4.id = projections.apps4_saml_configs.app_id AND projections.apps4.instance_id = projections.apps4_saml_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'`) ` AS OF SYSTEM TIME '-1 ms'`)
expectedAppsQuery = regexp.QuoteMeta(`SELECT projections.apps4.id,` + expectedAppsQuery = regexp.QuoteMeta(`SELECT projections.apps5.id,` +
` projections.apps4.name,` + ` projections.apps5.name,` +
` projections.apps4.project_id,` + ` projections.apps5.project_id,` +
` projections.apps4.creation_date,` + ` projections.apps5.creation_date,` +
` projections.apps4.change_date,` + ` projections.apps5.change_date,` +
` projections.apps4.resource_owner,` + ` projections.apps5.resource_owner,` +
` projections.apps4.state,` + ` projections.apps5.state,` +
` projections.apps4.sequence,` + ` projections.apps5.sequence,` +
// api config // api config
` projections.apps4_api_configs.app_id,` + ` projections.apps5_api_configs.app_id,` +
` projections.apps4_api_configs.client_id,` + ` projections.apps5_api_configs.client_id,` +
` projections.apps4_api_configs.auth_method,` + ` projections.apps5_api_configs.auth_method,` +
// oidc config // oidc config
` projections.apps4_oidc_configs.app_id,` + ` projections.apps5_oidc_configs.app_id,` +
` projections.apps4_oidc_configs.version,` + ` projections.apps5_oidc_configs.version,` +
` projections.apps4_oidc_configs.client_id,` + ` projections.apps5_oidc_configs.client_id,` +
` projections.apps4_oidc_configs.redirect_uris,` + ` projections.apps5_oidc_configs.redirect_uris,` +
` projections.apps4_oidc_configs.response_types,` + ` projections.apps5_oidc_configs.response_types,` +
` projections.apps4_oidc_configs.grant_types,` + ` projections.apps5_oidc_configs.grant_types,` +
` projections.apps4_oidc_configs.application_type,` + ` projections.apps5_oidc_configs.application_type,` +
` projections.apps4_oidc_configs.auth_method_type,` + ` projections.apps5_oidc_configs.auth_method_type,` +
` projections.apps4_oidc_configs.post_logout_redirect_uris,` + ` projections.apps5_oidc_configs.post_logout_redirect_uris,` +
` projections.apps4_oidc_configs.is_dev_mode,` + ` projections.apps5_oidc_configs.is_dev_mode,` +
` projections.apps4_oidc_configs.access_token_type,` + ` projections.apps5_oidc_configs.access_token_type,` +
` projections.apps4_oidc_configs.access_token_role_assertion,` + ` projections.apps5_oidc_configs.access_token_role_assertion,` +
` projections.apps4_oidc_configs.id_token_role_assertion,` + ` projections.apps5_oidc_configs.id_token_role_assertion,` +
` projections.apps4_oidc_configs.id_token_userinfo_assertion,` + ` projections.apps5_oidc_configs.id_token_userinfo_assertion,` +
` projections.apps4_oidc_configs.clock_skew,` + ` projections.apps5_oidc_configs.clock_skew,` +
` projections.apps4_oidc_configs.additional_origins,` + ` projections.apps5_oidc_configs.additional_origins,` +
` projections.apps5_oidc_configs.skip_native_app_success_page,` +
//saml config //saml config
` projections.apps4_saml_configs.app_id,` + ` projections.apps5_saml_configs.app_id,` +
` projections.apps4_saml_configs.entity_id,` + ` projections.apps5_saml_configs.entity_id,` +
` projections.apps4_saml_configs.metadata,` + ` projections.apps5_saml_configs.metadata,` +
` projections.apps4_saml_configs.metadata_url,` + ` projections.apps5_saml_configs.metadata_url,` +
` COUNT(*) OVER ()` + ` COUNT(*) OVER ()` +
` FROM projections.apps4` + ` FROM projections.apps5` +
` 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.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.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.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.apps4_saml_configs ON projections.apps4.id = projections.apps4_saml_configs.app_id AND projections.apps4.instance_id = projections.apps4_saml_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'`) ` AS OF SYSTEM TIME '-1 ms'`)
expectedAppIDsQuery = regexp.QuoteMeta(`SELECT projections.apps4_api_configs.client_id,` + expectedAppIDsQuery = regexp.QuoteMeta(`SELECT projections.apps5_api_configs.client_id,` +
` projections.apps4_oidc_configs.client_id` + ` projections.apps5_oidc_configs.client_id` +
` FROM projections.apps4` + ` FROM projections.apps5` +
` 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.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.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.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'`) ` AS OF SYSTEM TIME '-1 ms'`)
expectedProjectIDByAppQuery = regexp.QuoteMeta(`SELECT projections.apps4.project_id` + expectedProjectIDByAppQuery = regexp.QuoteMeta(`SELECT projections.apps5.project_id` +
` FROM projections.apps4` + ` FROM projections.apps5` +
` 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.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.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.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.apps4_saml_configs ON projections.apps4.id = projections.apps4_saml_configs.app_id AND projections.apps4.instance_id = projections.apps4_saml_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'`) ` AS OF SYSTEM TIME '-1 ms'`)
expectedProjectByAppQuery = regexp.QuoteMeta(`SELECT projections.projects3.id,` + expectedProjectByAppQuery = regexp.QuoteMeta(`SELECT projections.projects3.id,` +
` projections.projects3.creation_date,` + ` projections.projects3.creation_date,` +
@ -118,10 +120,10 @@ var (
` projections.projects3.has_project_check,` + ` projections.projects3.has_project_check,` +
` projections.projects3.private_labeling_setting` + ` projections.projects3.private_labeling_setting` +
` FROM projections.projects3` + ` FROM projections.projects3` +
` JOIN projections.apps4 ON projections.projects3.id = projections.apps4.project_id AND projections.projects3.instance_id = projections.apps4.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.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.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.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.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.apps4_saml_configs ON projections.apps4.id = projections.apps4_saml_configs.app_id AND projections.apps4.instance_id = projections.apps4_saml_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'`) ` AS OF SYSTEM TIME '-1 ms'`)
appCols = database.StringArray{ appCols = database.StringArray{
@ -154,6 +156,7 @@ var (
"id_token_userinfo_assertion", "id_token_userinfo_assertion",
"clock_skew", "clock_skew",
"additional_origins", "additional_origins",
"skip_native_app_success_page",
//saml config //saml config
"app_id", "app_id",
"entity_id", "entity_id",
@ -224,6 +227,7 @@ func Test_AppsPrepare(t *testing.T) {
nil, nil,
nil, nil,
nil, nil,
nil,
// saml config // saml config
nil, nil,
nil, nil,
@ -289,6 +293,7 @@ func Test_AppsPrepare(t *testing.T) {
nil, nil,
nil, nil,
nil, nil,
nil,
// saml config // saml config
nil, nil,
nil, nil,
@ -357,6 +362,7 @@ func Test_AppsPrepare(t *testing.T) {
nil, nil,
nil, nil,
nil, nil,
nil,
// saml config // saml config
"app-id", "app-id",
"https://test.com/saml/metadata", "https://test.com/saml/metadata",
@ -427,6 +433,7 @@ func Test_AppsPrepare(t *testing.T) {
true, true,
1 * time.Second, 1 * time.Second,
database.StringArray{"additional.origin"}, database.StringArray{"additional.origin"},
false,
// saml config // saml config
nil, nil,
nil, nil,
@ -468,6 +475,7 @@ func Test_AppsPrepare(t *testing.T) {
AdditionalOrigins: database.StringArray{"additional.origin"}, AdditionalOrigins: database.StringArray{"additional.origin"},
ComplianceProblems: nil, ComplianceProblems: nil,
AllowedOrigins: database.StringArray{"https://redirect.to", "additional.origin"}, AllowedOrigins: database.StringArray{"https://redirect.to", "additional.origin"},
SkipNativeAppSuccessPage: false,
}, },
}, },
}, },
@ -511,6 +519,7 @@ func Test_AppsPrepare(t *testing.T) {
true, true,
1 * time.Second, 1 * time.Second,
database.StringArray{"additional.origin"}, database.StringArray{"additional.origin"},
false,
// saml config // saml config
nil, nil,
nil, nil,
@ -552,6 +561,7 @@ func Test_AppsPrepare(t *testing.T) {
AdditionalOrigins: database.StringArray{"additional.origin"}, AdditionalOrigins: database.StringArray{"additional.origin"},
ComplianceProblems: nil, ComplianceProblems: nil,
AllowedOrigins: database.StringArray{"https://redirect.to", "additional.origin"}, AllowedOrigins: database.StringArray{"https://redirect.to", "additional.origin"},
SkipNativeAppSuccessPage: false,
}, },
}, },
}, },
@ -595,6 +605,7 @@ func Test_AppsPrepare(t *testing.T) {
true, true,
1 * time.Second, 1 * time.Second,
database.StringArray{"additional.origin"}, database.StringArray{"additional.origin"},
false,
// saml config // saml config
nil, nil,
nil, nil,
@ -636,6 +647,7 @@ func Test_AppsPrepare(t *testing.T) {
AdditionalOrigins: database.StringArray{"additional.origin"}, AdditionalOrigins: database.StringArray{"additional.origin"},
ComplianceProblems: nil, ComplianceProblems: nil,
AllowedOrigins: database.StringArray{"https://redirect.to", "additional.origin"}, AllowedOrigins: database.StringArray{"https://redirect.to", "additional.origin"},
SkipNativeAppSuccessPage: false,
}, },
}, },
}, },
@ -679,6 +691,7 @@ func Test_AppsPrepare(t *testing.T) {
true, true,
1 * time.Second, 1 * time.Second,
database.StringArray{"additional.origin"}, database.StringArray{"additional.origin"},
false,
// saml config // saml config
nil, nil,
nil, nil,
@ -720,6 +733,7 @@ func Test_AppsPrepare(t *testing.T) {
AdditionalOrigins: database.StringArray{"additional.origin"}, AdditionalOrigins: database.StringArray{"additional.origin"},
ComplianceProblems: nil, ComplianceProblems: nil,
AllowedOrigins: database.StringArray{"https://redirect.to", "additional.origin"}, AllowedOrigins: database.StringArray{"https://redirect.to", "additional.origin"},
SkipNativeAppSuccessPage: false,
}, },
}, },
}, },
@ -763,6 +777,7 @@ func Test_AppsPrepare(t *testing.T) {
true, true,
1 * time.Second, 1 * time.Second,
database.StringArray{"additional.origin"}, database.StringArray{"additional.origin"},
false,
// saml config // saml config
nil, nil,
nil, nil,
@ -804,6 +819,93 @@ func Test_AppsPrepare(t *testing.T) {
AdditionalOrigins: database.StringArray{"additional.origin"}, AdditionalOrigins: database.StringArray{"additional.origin"},
ComplianceProblems: nil, ComplianceProblems: nil,
AllowedOrigins: database.StringArray{"https://redirect.to", "additional.origin"}, 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, true,
1 * time.Second, 1 * time.Second,
database.StringArray{"additional.origin"}, database.StringArray{"additional.origin"},
false,
// saml config // saml config
nil, nil,
nil, nil,
@ -883,6 +986,7 @@ func Test_AppsPrepare(t *testing.T) {
nil, nil,
nil, nil,
nil, nil,
nil,
// saml config // saml config
nil, nil,
nil, nil,
@ -919,6 +1023,7 @@ func Test_AppsPrepare(t *testing.T) {
nil, nil,
nil, nil,
nil, nil,
nil,
// saml config // saml config
"saml-app-id", "saml-app-id",
"https://test.com/saml/metadata", "https://test.com/saml/metadata",
@ -960,6 +1065,7 @@ func Test_AppsPrepare(t *testing.T) {
AdditionalOrigins: database.StringArray{"additional.origin"}, AdditionalOrigins: database.StringArray{"additional.origin"},
ComplianceProblems: nil, ComplianceProblems: nil,
AllowedOrigins: database.StringArray{"https://redirect.to", "additional.origin"}, AllowedOrigins: database.StringArray{"https://redirect.to", "additional.origin"},
SkipNativeAppSuccessPage: false,
}, },
}, },
{ {
@ -1085,6 +1191,7 @@ func Test_AppPrepare(t *testing.T) {
nil, nil,
nil, nil,
nil, nil,
nil,
// saml config // saml config
nil, nil,
nil, nil,
@ -1142,6 +1249,7 @@ func Test_AppPrepare(t *testing.T) {
nil, nil,
nil, nil,
nil, nil,
nil,
// saml config // saml config
nil, nil,
nil, nil,
@ -1204,6 +1312,7 @@ func Test_AppPrepare(t *testing.T) {
true, true,
1 * time.Second, 1 * time.Second,
database.StringArray{"additional.origin"}, database.StringArray{"additional.origin"},
false,
// saml config // saml config
nil, nil,
nil, nil,
@ -1240,6 +1349,7 @@ func Test_AppPrepare(t *testing.T) {
AdditionalOrigins: database.StringArray{"additional.origin"}, AdditionalOrigins: database.StringArray{"additional.origin"},
ComplianceProblems: nil, ComplianceProblems: nil,
AllowedOrigins: database.StringArray{"https://redirect.to", "additional.origin"}, AllowedOrigins: database.StringArray{"https://redirect.to", "additional.origin"},
SkipNativeAppSuccessPage: false,
}, },
}, },
}, { }, {
@ -1280,6 +1390,7 @@ func Test_AppPrepare(t *testing.T) {
nil, nil,
nil, nil,
nil, nil,
nil,
// saml config // saml config
"app-id", "app-id",
"https://test.com/saml/metadata", "https://test.com/saml/metadata",
@ -1343,6 +1454,7 @@ func Test_AppPrepare(t *testing.T) {
true, true,
1 * time.Second, 1 * time.Second,
database.StringArray{"additional.origin"}, database.StringArray{"additional.origin"},
false,
// saml config // saml config
nil, nil,
nil, nil,
@ -1379,6 +1491,7 @@ func Test_AppPrepare(t *testing.T) {
AdditionalOrigins: database.StringArray{"additional.origin"}, AdditionalOrigins: database.StringArray{"additional.origin"},
ComplianceProblems: nil, ComplianceProblems: nil,
AllowedOrigins: database.StringArray{"https://redirect.to", "additional.origin"}, AllowedOrigins: database.StringArray{"https://redirect.to", "additional.origin"},
SkipNativeAppSuccessPage: false,
}, },
}, },
}, },
@ -1420,6 +1533,7 @@ func Test_AppPrepare(t *testing.T) {
true, true,
1 * time.Second, 1 * time.Second,
database.StringArray{"additional.origin"}, database.StringArray{"additional.origin"},
false,
// saml config // saml config
nil, nil,
nil, nil,
@ -1456,6 +1570,7 @@ func Test_AppPrepare(t *testing.T) {
AdditionalOrigins: database.StringArray{"additional.origin"}, AdditionalOrigins: database.StringArray{"additional.origin"},
ComplianceProblems: nil, ComplianceProblems: nil,
AllowedOrigins: database.StringArray{"https://redirect.to", "additional.origin"}, AllowedOrigins: database.StringArray{"https://redirect.to", "additional.origin"},
SkipNativeAppSuccessPage: false,
}, },
}, },
}, },
@ -1497,6 +1612,7 @@ func Test_AppPrepare(t *testing.T) {
true, true,
1 * time.Second, 1 * time.Second,
database.StringArray{"additional.origin"}, database.StringArray{"additional.origin"},
false,
// saml config // saml config
nil, nil,
nil, nil,
@ -1533,6 +1649,7 @@ func Test_AppPrepare(t *testing.T) {
AdditionalOrigins: database.StringArray{"additional.origin"}, AdditionalOrigins: database.StringArray{"additional.origin"},
ComplianceProblems: nil, ComplianceProblems: nil,
AllowedOrigins: database.StringArray{"https://redirect.to", "additional.origin"}, AllowedOrigins: database.StringArray{"https://redirect.to", "additional.origin"},
SkipNativeAppSuccessPage: false,
}, },
}, },
}, },
@ -1574,6 +1691,7 @@ func Test_AppPrepare(t *testing.T) {
false, false,
1 * time.Second, 1 * time.Second,
database.StringArray{"additional.origin"}, database.StringArray{"additional.origin"},
false,
// saml config // saml config
nil, nil,
nil, nil,
@ -1610,6 +1728,7 @@ func Test_AppPrepare(t *testing.T) {
AdditionalOrigins: database.StringArray{"additional.origin"}, AdditionalOrigins: database.StringArray{"additional.origin"},
ComplianceProblems: nil, ComplianceProblems: nil,
AllowedOrigins: database.StringArray{"https://redirect.to", "additional.origin"}, AllowedOrigins: database.StringArray{"https://redirect.to", "additional.origin"},
SkipNativeAppSuccessPage: false,
}, },
}, },
}, },

View File

@ -15,7 +15,7 @@ import (
) )
const ( const (
AppProjectionTable = "projections.apps4" AppProjectionTable = "projections.apps5"
AppAPITable = AppProjectionTable + "_" + appAPITableSuffix AppAPITable = AppProjectionTable + "_" + appAPITableSuffix
AppOIDCTable = AppProjectionTable + "_" + appOIDCTableSuffix AppOIDCTable = AppProjectionTable + "_" + appOIDCTableSuffix
AppSAMLTable = AppProjectionTable + "_" + appSAMLTableSuffix AppSAMLTable = AppProjectionTable + "_" + appSAMLTableSuffix
@ -57,6 +57,7 @@ const (
AppOIDCConfigColumnIDTokenUserinfoAssertion = "id_token_userinfo_assertion" AppOIDCConfigColumnIDTokenUserinfoAssertion = "id_token_userinfo_assertion"
AppOIDCConfigColumnClockSkew = "clock_skew" AppOIDCConfigColumnClockSkew = "clock_skew"
AppOIDCConfigColumnAdditionalOrigins = "additional_origins" AppOIDCConfigColumnAdditionalOrigins = "additional_origins"
AppOIDCConfigColumnSkipNativeAppSuccessPage = "skip_native_app_success_page"
appSAMLTableSuffix = "saml_configs" appSAMLTableSuffix = "saml_configs"
AppSAMLConfigColumnAppID = "app_id" 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(AppOIDCConfigColumnIDTokenUserinfoAssertion, crdb.ColumnTypeBool, crdb.Default(false)),
crdb.NewColumn(AppOIDCConfigColumnClockSkew, crdb.ColumnTypeInt64, crdb.Default(0)), crdb.NewColumn(AppOIDCConfigColumnClockSkew, crdb.ColumnTypeInt64, crdb.Default(0)),
crdb.NewColumn(AppOIDCConfigColumnAdditionalOrigins, crdb.ColumnTypeTextArray, crdb.Nullable()), crdb.NewColumn(AppOIDCConfigColumnAdditionalOrigins, crdb.ColumnTypeTextArray, crdb.Nullable()),
crdb.NewColumn(AppOIDCConfigColumnSkipNativeAppSuccessPage, crdb.ColumnTypeBool, crdb.Default(false)),
}, },
crdb.NewPrimaryKey(AppOIDCConfigColumnInstanceID, AppOIDCConfigColumnAppID), crdb.NewPrimaryKey(AppOIDCConfigColumnInstanceID, AppOIDCConfigColumnAppID),
appOIDCTableSuffix, appOIDCTableSuffix,
@ -463,6 +465,7 @@ func (p *appProjection) reduceOIDCConfigAdded(event eventstore.Event) (*handler.
handler.NewCol(AppOIDCConfigColumnIDTokenUserinfoAssertion, e.IDTokenUserinfoAssertion), handler.NewCol(AppOIDCConfigColumnIDTokenUserinfoAssertion, e.IDTokenUserinfoAssertion),
handler.NewCol(AppOIDCConfigColumnClockSkew, e.ClockSkew), handler.NewCol(AppOIDCConfigColumnClockSkew, e.ClockSkew),
handler.NewCol(AppOIDCConfigColumnAdditionalOrigins, database.StringArray(e.AdditionalOrigins)), handler.NewCol(AppOIDCConfigColumnAdditionalOrigins, database.StringArray(e.AdditionalOrigins)),
handler.NewCol(AppOIDCConfigColumnSkipNativeAppSuccessPage, e.SkipNativeAppSuccessPage),
}, },
crdb.WithTableSuffix(appOIDCTableSuffix), crdb.WithTableSuffix(appOIDCTableSuffix),
), ),
@ -528,6 +531,9 @@ func (p *appProjection) reduceOIDCConfigChanged(event eventstore.Event) (*handle
if e.AdditionalOrigins != nil { if e.AdditionalOrigins != nil {
cols = append(cols, handler.NewCol(AppOIDCConfigColumnAdditionalOrigins, database.StringArray(*e.AdditionalOrigins))) 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 { if len(cols) == 0 {
return crdb.NewNoOpStatement(e), nil return crdb.NewNoOpStatement(e), nil

View File

@ -45,7 +45,7 @@ func TestAppProjection_reduces(t *testing.T) {
executer: &testExecuter{ executer: &testExecuter{
executions: []execution{ 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{}{ expectedArgs: []interface{}{
"app-id", "app-id",
"my-app", "my-app",
@ -82,7 +82,7 @@ func TestAppProjection_reduces(t *testing.T) {
executer: &testExecuter{ executer: &testExecuter{
executions: []execution{ 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{}{ expectedArgs: []interface{}{
"my-app", "my-app",
anyArg{}, 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", name: "project reduceAppDeactivated",
args: args{ args: args{
@ -114,7 +135,7 @@ func TestAppProjection_reduces(t *testing.T) {
executer: &testExecuter{ executer: &testExecuter{
executions: []execution{ 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{}{ expectedArgs: []interface{}{
domain.AppStateInactive, domain.AppStateInactive,
anyArg{}, anyArg{},
@ -146,7 +167,7 @@ func TestAppProjection_reduces(t *testing.T) {
executer: &testExecuter{ executer: &testExecuter{
executions: []execution{ 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{}{ expectedArgs: []interface{}{
domain.AppStateActive, domain.AppStateActive,
anyArg{}, anyArg{},
@ -178,7 +199,7 @@ func TestAppProjection_reduces(t *testing.T) {
executer: &testExecuter{ executer: &testExecuter{
executions: []execution{ 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{}{ expectedArgs: []interface{}{
"app-id", "app-id",
"instance-id", "instance-id",
@ -205,7 +226,7 @@ func TestAppProjection_reduces(t *testing.T) {
executer: &testExecuter{ executer: &testExecuter{
executions: []execution{ 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{}{ expectedArgs: []interface{}{
"agg-id", "agg-id",
"instance-id", "instance-id",
@ -232,7 +253,7 @@ func TestAppProjection_reduces(t *testing.T) {
executer: &testExecuter{ executer: &testExecuter{
executions: []execution{ executions: []execution{
{ {
expectedStmt: "DELETE FROM projections.apps4 WHERE (instance_id = $1)", expectedStmt: "DELETE FROM projections.apps5 WHERE (instance_id = $1)",
expectedArgs: []interface{}{ expectedArgs: []interface{}{
"agg-id", "agg-id",
}, },
@ -263,7 +284,7 @@ func TestAppProjection_reduces(t *testing.T) {
executer: &testExecuter{ executer: &testExecuter{
executions: []execution{ 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{}{ expectedArgs: []interface{}{
"app-id", "app-id",
"instance-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{}{ expectedArgs: []interface{}{
anyArg{}, anyArg{},
uint64(15), uint64(15),
@ -307,7 +328,7 @@ func TestAppProjection_reduces(t *testing.T) {
executer: &testExecuter{ executer: &testExecuter{
executions: []execution{ 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{}{ expectedArgs: []interface{}{
anyArg{}, anyArg{},
domain.APIAuthMethodTypePrivateKeyJWT, 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{}{ expectedArgs: []interface{}{
anyArg{}, anyArg{},
uint64(15), uint64(15),
@ -369,7 +390,7 @@ func TestAppProjection_reduces(t *testing.T) {
executer: &testExecuter{ executer: &testExecuter{
executions: []execution{ 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{}{ expectedArgs: []interface{}{
anyArg{}, anyArg{},
"app-id", "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{}{ expectedArgs: []interface{}{
anyArg{}, anyArg{},
uint64(15), uint64(15),
@ -412,7 +433,8 @@ func TestAppProjection_reduces(t *testing.T) {
"idTokenRoleAssertion": true, "idTokenRoleAssertion": true,
"idTokenUserinfoAssertion": true, "idTokenUserinfoAssertion": true,
"clockSkew": 1000, "clockSkew": 1000,
"additionalOrigins": ["origin.one.ch", "origin.two.ch"] "additionalOrigins": ["origin.one.ch", "origin.two.ch"],
"skipNativeAppSuccessPage": true
}`), }`),
), project.OIDCConfigAddedEventMapper), ), project.OIDCConfigAddedEventMapper),
}, },
@ -424,7 +446,7 @@ func TestAppProjection_reduces(t *testing.T) {
executer: &testExecuter{ executer: &testExecuter{
executions: []execution{ 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{}{ expectedArgs: []interface{}{
"app-id", "app-id",
"instance-id", "instance-id",
@ -444,10 +466,11 @@ func TestAppProjection_reduces(t *testing.T) {
true, true,
1 * time.Microsecond, 1 * time.Microsecond,
database.StringArray{"origin.one.ch", "origin.two.ch"}, 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{}{ expectedArgs: []interface{}{
anyArg{}, anyArg{},
uint64(15), uint64(15),
@ -480,7 +503,9 @@ func TestAppProjection_reduces(t *testing.T) {
"idTokenRoleAssertion": true, "idTokenRoleAssertion": true,
"idTokenUserinfoAssertion": true, "idTokenUserinfoAssertion": true,
"clockSkew": 1000, "clockSkew": 1000,
"additionalOrigins": ["origin.one.ch", "origin.two.ch"] "additionalOrigins": ["origin.one.ch", "origin.two.ch"],
"skipNativeAppSuccessPage": true
}`), }`),
), project.OIDCConfigChangedEventMapper), ), project.OIDCConfigChangedEventMapper),
}, },
@ -492,7 +517,7 @@ func TestAppProjection_reduces(t *testing.T) {
executer: &testExecuter{ executer: &testExecuter{
executions: []execution{ 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{}{ expectedArgs: []interface{}{
domain.OIDCVersionV1, domain.OIDCVersionV1,
database.StringArray{"redirect.one.ch", "redirect.two.ch"}, database.StringArray{"redirect.one.ch", "redirect.two.ch"},
@ -508,12 +533,13 @@ func TestAppProjection_reduces(t *testing.T) {
true, true,
1 * time.Microsecond, 1 * time.Microsecond,
database.StringArray{"origin.one.ch", "origin.two.ch"}, database.StringArray{"origin.one.ch", "origin.two.ch"},
true,
"app-id", "app-id",
"instance-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{}{ expectedArgs: []interface{}{
anyArg{}, anyArg{},
uint64(15), uint64(15),
@ -566,7 +592,7 @@ func TestAppProjection_reduces(t *testing.T) {
executer: &testExecuter{ executer: &testExecuter{
executions: []execution{ 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{}{ expectedArgs: []interface{}{
anyArg{}, anyArg{},
"app-id", "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{}{ expectedArgs: []interface{}{
anyArg{}, anyArg{},
uint64(15), uint64(15),
@ -603,7 +629,7 @@ func TestAppProjection_reduces(t *testing.T) {
executer: &testExecuter{ executer: &testExecuter{
executions: []execution{ 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{}{ expectedArgs: []interface{}{
anyArg{}, anyArg{},
uint64(15), uint64(15),

View File

@ -40,6 +40,7 @@ type OIDCConfigAddedEvent struct {
IDTokenUserinfoAssertion bool `json:"idTokenUserinfoAssertion,omitempty"` IDTokenUserinfoAssertion bool `json:"idTokenUserinfoAssertion,omitempty"`
ClockSkew time.Duration `json:"clockSkew,omitempty"` ClockSkew time.Duration `json:"clockSkew,omitempty"`
AdditionalOrigins []string `json:"additionalOrigins,omitempty"` AdditionalOrigins []string `json:"additionalOrigins,omitempty"`
SkipNativeAppSuccessPage bool `json:"skipNativeAppSuccessPage,omitempty"`
} }
func (e *OIDCConfigAddedEvent) Data() interface{} { func (e *OIDCConfigAddedEvent) Data() interface{} {
@ -70,6 +71,7 @@ func NewOIDCConfigAddedEvent(
idTokenUserinfoAssertion bool, idTokenUserinfoAssertion bool,
clockSkew time.Duration, clockSkew time.Duration,
additionalOrigins []string, additionalOrigins []string,
skipNativeAppSuccessPage bool,
) *OIDCConfigAddedEvent { ) *OIDCConfigAddedEvent {
return &OIDCConfigAddedEvent{ return &OIDCConfigAddedEvent{
BaseEvent: *eventstore.NewBaseEventForPush( BaseEvent: *eventstore.NewBaseEventForPush(
@ -94,6 +96,7 @@ func NewOIDCConfigAddedEvent(
IDTokenUserinfoAssertion: idTokenUserinfoAssertion, IDTokenUserinfoAssertion: idTokenUserinfoAssertion,
ClockSkew: clockSkew, ClockSkew: clockSkew,
AdditionalOrigins: additionalOrigins, AdditionalOrigins: additionalOrigins,
SkipNativeAppSuccessPage: skipNativeAppSuccessPage,
} }
} }
@ -179,8 +182,7 @@ func (e *OIDCConfigAddedEvent) Validate(cmd eventstore.Command) bool {
return false return false
} }
} }
return e.SkipNativeAppSuccessPage == c.SkipNativeAppSuccessPage
return true
} }
func OIDCConfigAddedEventMapper(event *repository.Event) (eventstore.Event, error) { func OIDCConfigAddedEventMapper(event *repository.Event) (eventstore.Event, error) {
@ -214,6 +216,7 @@ type OIDCConfigChangedEvent struct {
IDTokenUserinfoAssertion *bool `json:"idTokenUserinfoAssertion,omitempty"` IDTokenUserinfoAssertion *bool `json:"idTokenUserinfoAssertion,omitempty"`
ClockSkew *time.Duration `json:"clockSkew,omitempty"` ClockSkew *time.Duration `json:"clockSkew,omitempty"`
AdditionalOrigins *[]string `json:"additionalOrigins,omitempty"` AdditionalOrigins *[]string `json:"additionalOrigins,omitempty"`
SkipNativeAppSuccessPage *bool `json:"skipNativeAppSuccessPage,omitempty"`
} }
func (e *OIDCConfigChangedEvent) Data() interface{} { 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) { func OIDCConfigChangedEventMapper(event *repository.Event) (eventstore.Event, error) {
e := &OIDCConfigChangedEvent{ e := &OIDCConfigChangedEvent{
BaseEvent: *eventstore.BaseEventFromRepo(event), BaseEvent: *eventstore.BaseEventFromRepo(event),

View File

@ -163,6 +163,11 @@ message OIDCConfig {
description: "all allowed origins from where the API can be used"; 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 { enum OIDCResponseType {

View File

@ -8773,6 +8773,11 @@ message AddOIDCAppRequest {
description: "Additional origins (other than the redirect_uris) from where the API can be used"; 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 { message AddOIDCAppResponse {
@ -8943,6 +8948,11 @@ message UpdateOIDCAppConfigRequest {
description: "Additional origins (other than the redirect_uris) from where the API can be used"; 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 { message UpdateOIDCAppConfigResponse {