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 committed by GitHub
parent b3d8787921
commit 8bf36301ed
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
32 changed files with 641 additions and 390 deletions

View File

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

View File

@ -331,11 +331,24 @@
</div>
</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
*ngIf="appType?.value !== undefined"
class="redirect-section"
[disabled]="!canWrite"
[devMode]="devMode?.value"
[devMode]="!!devMode?.value"
[(ngModel)]="redirectUrisList"
title="{{ 'APP.OIDC.REDIRECT' | translate }}"
[isNative]="appType?.value === OIDCAppType.OIDC_APP_TYPE_NATIVE"
@ -346,7 +359,7 @@
*ngIf="appType?.value !== undefined"
class="redirect-section"
[disabled]="!canWrite"
[devMode]="devMode?.value"
[devMode]="!!devMode?.value"
[(ngModel)]="postLogoutRedirectUrisList"
title="{{ 'APP.OIDC.POSTLOGOUTREDIRECT' | translate }}"
[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 { HttpClient } from '@angular/common/http';
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 { MatLegacyDialog as MatDialog } from '@angular/material/legacy-dialog';
import { ActivatedRoute, Router } from '@angular/router';
@ -149,7 +149,8 @@ export class AppDetailComponent implements OnInit, OnDestroy {
private http: HttpClient,
) {
this.oidcForm = this.fb.group({
devMode: [{ value: false, disabled: true }, []],
devMode: [{ value: false, disabled: true }],
skipNativeAppSuccessPage: [{ value: false, disabled: true }],
clientId: [{ value: '', disabled: true }],
responseTypesList: [{ 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.postLogoutRedirectUrisList = this.postLogoutRedirectUrisList;
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();
req.setProjectId(this.projectId);
@ -571,6 +573,7 @@ export class AppDetailComponent implements OnInit, OnDestroy {
req.setAdditionalOriginsList(this.app.oidcConfig.additionalOriginsList);
req.setPostLogoutRedirectUrisList(this.app.oidcConfig.postLogoutRedirectUrisList);
req.setDevMode(this.app.oidcConfig.devMode);
req.setSkipNativeAppSuccessPage(this.app.oidcConfig.skipNativeAppSuccessPage);
if (this.clockSkewSeconds?.value) {
const dur = new Duration();
@ -738,11 +741,15 @@ export class AppDetailComponent implements OnInit, OnDestroy {
}
public get apiAuthMethodType(): AbstractControl | null {
return this.apiForm.get('authMethodType');
return this.apiForm.get('authMethodType') as UntypedFormControl;
}
public get devMode(): UntypedFormControl | null {
return this.oidcForm.get('devMode') as UntypedFormControl;
public get devMode(): FormControl<boolean> | null {
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 {

View File

@ -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",

View File

@ -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",

View File

@ -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",

View File

@ -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",

View File

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

View File

@ -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",

View File

@ -1937,6 +1937,8 @@
"REGENERATESECRET": "重新生成客户端密钥",
"DEVMODE": "开发模式",
"DEVMODEDESC": "注意:启用开发模式的重定向 URI 将不会被验证。",
"SKIPNATIVEAPPSUCCESSPAGE": "登录后跳过成功页面",
"SKIPNATIVEAPPSUCCESSPAGE_DESCRIPTION": "登录后跳过本机应用的成功页面",
"REDIRECT": "重定向 URLs",
"REDIRECTSECTION": "重定向 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.
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
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,
ClockSkew: durationpb.New(app.OIDCConfig.ClockSkew),
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,
ClockSkew: req.ClockSkew.AsDuration(),
AdditionalOrigins: req.AdditionalOrigins,
SkipNativeAppSuccessPage: req.SkipNativeAppSuccessPage,
}
}
@ -106,6 +107,7 @@ func UpdateOIDCAppConfigRequestToDomain(app *mgmt_pb.UpdateOIDCAppConfigRequest)
IDTokenUserinfoAssertion: app.IdTokenUserinfoAssertion,
ClockSkew: app.ClockSkew.AsDuration(),
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),
AdditionalOrigins: app.AdditionalOrigins,
AllowedOrigins: app.AllowedOrigins,
SkipNativeAppSuccessPage: app.SkipNativeAppSuccessPage,
},
}
}

View File

@ -1,6 +1,7 @@
package login
import (
"context"
"net/http"
"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),
}
if authReq != nil {
//the id will be set via the html (maybe change this with the login refactoring)
if _, ok := authReq.Request.(*domain.AuthRequestOIDC); ok {
data.RedirectURI = l.oidcAuthCallbackURL(r.Context(), "")
} else if _, ok := authReq.Request.(*domain.AuthRequestSAML); ok {
data.RedirectURI = l.samlAuthCallbackURL(r.Context(), "")
data.RedirectURI, err = l.authRequestCallback(r.Context(), authReq)
if err != nil {
l.renderInternalError(w, r, authReq, err)
return
}
}
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) {
var callback string
switch authReq.Request.(type) {
case *domain.AuthRequestOIDC:
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"))
callback, err := l.authRequestCallback(r.Context(), authReq)
if err != nil {
l.renderInternalError(w, r, authReq, err)
return
}
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() {
let form = document.getElementsByTagName('form')[0];
if (form) {
let button = document.getElementById("redirect-button");
if (button) {
button.addEventListener("click", function (event) {
location.reload();
event.preventDefault();
});
}
form.submit();
}
}

View File

@ -29,4 +29,4 @@
</div>
{{end}}
{{template "main-bottom" .}}
{{template "main-bottom" .}}

View File

@ -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) {

View File

@ -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(

View File

@ -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
}

View File

@ -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
}

View File

@ -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,
},
},

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 {
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,
}
}

View File

@ -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"

View File

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

View File

@ -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

View File

@ -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,
},
},
},

View File

@ -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

View File

@ -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),

View File

@ -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),

View File

@ -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 {

View File

@ -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 {