mirror of
https://github.com/zitadel/zitadel.git
synced 2025-02-28 21:47:23 +00:00
feat: specify login UI version on instance and apps (#9071)
# Which Problems Are Solved To be able to migrate or test the new login UI, admins might want to (temporarily) switch individual apps. At a later point admin might want to make sure all applications use the new login UI. # How the Problems Are Solved - Added a feature flag `` on instance level to require all apps to use the new login and provide an optional base url. - if the flag is enabled, all (OIDC) applications will automatically use the v2 login. - if disabled, applications can decide based on their configuration - Added an option on OIDC apps to use the new login UI and an optional base url. - Removed the requirement to use `x-zitadel-login-client` to be redirected to the login V2 and retrieve created authrequest and link them to SSO sessions. - Added a new "IAM_LOGIN_CLIENT" role to allow management of users, sessions, grants and more without `x-zitadel-login-client`. # Additional Changes None # Additional Context closes https://github.com/zitadel/zitadel/issues/8702
This commit is contained in:
parent
b5e92a6144
commit
50d2b26a28
@ -1289,6 +1289,7 @@ InternalAuthZ:
|
|||||||
- "project.grant.member.delete"
|
- "project.grant.member.delete"
|
||||||
- "events.read"
|
- "events.read"
|
||||||
- "milestones.read"
|
- "milestones.read"
|
||||||
|
- "session.read"
|
||||||
- "session.delete"
|
- "session.delete"
|
||||||
- "action.target.read"
|
- "action.target.read"
|
||||||
- "action.target.write"
|
- "action.target.write"
|
||||||
@ -1481,6 +1482,43 @@ InternalAuthZ:
|
|||||||
- "project.grant.member.write"
|
- "project.grant.member.write"
|
||||||
- "project.grant.member.delete"
|
- "project.grant.member.delete"
|
||||||
- "session.delete"
|
- "session.delete"
|
||||||
|
- Role: "IAM_LOGIN_CLIENT"
|
||||||
|
Permissions:
|
||||||
|
- "iam.read"
|
||||||
|
- "iam.policy.read"
|
||||||
|
- "iam.member.read"
|
||||||
|
- "iam.member.write"
|
||||||
|
- "iam.idp.read"
|
||||||
|
- "iam.feature.read"
|
||||||
|
- "iam.restrictions.read"
|
||||||
|
- "org.read"
|
||||||
|
- "org.member.read"
|
||||||
|
- "org.member.write"
|
||||||
|
- "org.idp.read"
|
||||||
|
- "org.feature.read"
|
||||||
|
- "user.read"
|
||||||
|
- "user.write"
|
||||||
|
- "user.grant.read"
|
||||||
|
- "user.grant.write"
|
||||||
|
- "user.membership.read"
|
||||||
|
- "user.credential.write"
|
||||||
|
- "user.passkey.write"
|
||||||
|
- "user.feature.read"
|
||||||
|
- "policy.read"
|
||||||
|
- "project.read"
|
||||||
|
- "project.member.read"
|
||||||
|
- "project.member.write"
|
||||||
|
- "project.role.read"
|
||||||
|
- "project.app.read"
|
||||||
|
- "project.member.read"
|
||||||
|
- "project.member.write"
|
||||||
|
- "project.grant.read"
|
||||||
|
- "project.grant.member.read"
|
||||||
|
- "project.grant.member.write"
|
||||||
|
- "session.read"
|
||||||
|
- "session.link"
|
||||||
|
- "session.delete"
|
||||||
|
- "userschema.read"
|
||||||
- Role: "ORG_USER_MANAGER"
|
- Role: "ORG_USER_MANAGER"
|
||||||
Permissions:
|
Permissions:
|
||||||
- "org.read"
|
- "org.read"
|
||||||
|
27
cmd/setup/42.go
Normal file
27
cmd/setup/42.go
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
package setup
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
_ "embed"
|
||||||
|
|
||||||
|
"github.com/zitadel/zitadel/internal/database"
|
||||||
|
"github.com/zitadel/zitadel/internal/eventstore"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
//go:embed 42.sql
|
||||||
|
addOIDCAppLoginVersion string
|
||||||
|
)
|
||||||
|
|
||||||
|
type Apps7OIDCConfigsLoginVersion struct {
|
||||||
|
dbClient *database.DB
|
||||||
|
}
|
||||||
|
|
||||||
|
func (mig *Apps7OIDCConfigsLoginVersion) Execute(ctx context.Context, _ eventstore.Event) error {
|
||||||
|
_, err := mig.dbClient.ExecContext(ctx, addOIDCAppLoginVersion)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (mig *Apps7OIDCConfigsLoginVersion) String() string {
|
||||||
|
return "40_apps7_oidc_configs_login_version"
|
||||||
|
}
|
2
cmd/setup/42.sql
Normal file
2
cmd/setup/42.sql
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
ALTER TABLE IF EXISTS projections.apps7_oidc_configs ADD COLUMN IF NOT EXISTS login_version SMALLINT;
|
||||||
|
ALTER TABLE IF EXISTS projections.apps7_oidc_configs ADD COLUMN IF NOT EXISTS login_base_uri TEXT;
|
@ -127,6 +127,7 @@ type Steps struct {
|
|||||||
s37Apps7OIDConfigsBackChannelLogoutURI *Apps7OIDConfigsBackChannelLogoutURI
|
s37Apps7OIDConfigsBackChannelLogoutURI *Apps7OIDConfigsBackChannelLogoutURI
|
||||||
s38BackChannelLogoutNotificationStart *BackChannelLogoutNotificationStart
|
s38BackChannelLogoutNotificationStart *BackChannelLogoutNotificationStart
|
||||||
s40InitPushFunc *InitPushFunc
|
s40InitPushFunc *InitPushFunc
|
||||||
|
s42Apps7OIDCConfigsLoginVersion *Apps7OIDCConfigsLoginVersion
|
||||||
}
|
}
|
||||||
|
|
||||||
func MustNewSteps(v *viper.Viper) *Steps {
|
func MustNewSteps(v *viper.Viper) *Steps {
|
||||||
|
@ -170,6 +170,7 @@ func Setup(ctx context.Context, config *Config, steps *Steps, masterKey string)
|
|||||||
steps.s37Apps7OIDConfigsBackChannelLogoutURI = &Apps7OIDConfigsBackChannelLogoutURI{dbClient: esPusherDBClient}
|
steps.s37Apps7OIDConfigsBackChannelLogoutURI = &Apps7OIDConfigsBackChannelLogoutURI{dbClient: esPusherDBClient}
|
||||||
steps.s38BackChannelLogoutNotificationStart = &BackChannelLogoutNotificationStart{dbClient: esPusherDBClient, esClient: eventstoreClient}
|
steps.s38BackChannelLogoutNotificationStart = &BackChannelLogoutNotificationStart{dbClient: esPusherDBClient, esClient: eventstoreClient}
|
||||||
steps.s40InitPushFunc = &InitPushFunc{dbClient: esPusherDBClient}
|
steps.s40InitPushFunc = &InitPushFunc{dbClient: esPusherDBClient}
|
||||||
|
steps.s42Apps7OIDCConfigsLoginVersion = &Apps7OIDCConfigsLoginVersion{dbClient: esPusherDBClient}
|
||||||
|
|
||||||
err = projection.Create(ctx, projectionDBClient, eventstoreClient, config.Projections, nil, nil, nil)
|
err = projection.Create(ctx, projectionDBClient, eventstoreClient, config.Projections, nil, nil, nil)
|
||||||
logging.OnError(err).Fatal("unable to start projections")
|
logging.OnError(err).Fatal("unable to start projections")
|
||||||
@ -240,6 +241,7 @@ func Setup(ctx context.Context, config *Config, steps *Steps, masterKey string)
|
|||||||
steps.s32AddAuthSessionID,
|
steps.s32AddAuthSessionID,
|
||||||
steps.s33SMSConfigs3TwilioAddVerifyServiceSid,
|
steps.s33SMSConfigs3TwilioAddVerifyServiceSid,
|
||||||
steps.s37Apps7OIDConfigsBackChannelLogoutURI,
|
steps.s37Apps7OIDConfigsBackChannelLogoutURI,
|
||||||
|
steps.s42Apps7OIDCConfigsLoginVersion,
|
||||||
} {
|
} {
|
||||||
mustExecuteMigration(ctx, eventstoreClient, step, "migration failed")
|
mustExecuteMigration(ctx, eventstoreClient, step, "migration failed")
|
||||||
}
|
}
|
||||||
|
@ -147,6 +147,22 @@
|
|||||||
>
|
>
|
||||||
{{ 'APP.OIDC.REFRESHTOKEN' | translate }}
|
{{ 'APP.OIDC.REFRESHTOKEN' | translate }}
|
||||||
</mat-checkbox>
|
</mat-checkbox>
|
||||||
|
|
||||||
|
<mat-checkbox
|
||||||
|
*ngIf="loginV2"
|
||||||
|
color="primary"
|
||||||
|
class="rt"
|
||||||
|
[formControl]="loginV2"
|
||||||
|
name="loginV2"
|
||||||
|
matTooltip="{{ 'APP.LOGINV2DESC' | translate }}"
|
||||||
|
>
|
||||||
|
{{ 'APP.LOGINV2.USEV2' | translate }}
|
||||||
|
</mat-checkbox>
|
||||||
|
|
||||||
|
<cnsl-form-field class="app-formfield">
|
||||||
|
<cnsl-label>{{ 'APP.LOGINV2.BASEURL' | translate }}</cnsl-label>
|
||||||
|
<input cnslInput formControlName="loginV2BaseURL" />
|
||||||
|
</cnsl-form-field>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { COMMA, ENTER, SPACE } from '@angular/cdk/keycodes';
|
import { COMMA, ENTER, SPACE } from '@angular/cdk/keycodes';
|
||||||
import { Location } from '@angular/common';
|
import { Location } from '@angular/common';
|
||||||
import { Component, OnDestroy, OnInit, ViewEncapsulation, signal } from '@angular/core';
|
import { Component, OnDestroy, OnInit, signal } from '@angular/core';
|
||||||
import { AbstractControl, FormControl, UntypedFormBuilder, UntypedFormControl, UntypedFormGroup } from '@angular/forms';
|
import { AbstractControl, FormControl, UntypedFormBuilder, UntypedFormControl, UntypedFormGroup } from '@angular/forms';
|
||||||
import { MatCheckboxChange } from '@angular/material/checkbox';
|
import { MatCheckboxChange } from '@angular/material/checkbox';
|
||||||
import { MatDialog } from '@angular/material/dialog';
|
import { MatDialog } from '@angular/material/dialog';
|
||||||
@ -21,6 +21,9 @@ import {
|
|||||||
APIConfig,
|
APIConfig,
|
||||||
App,
|
App,
|
||||||
AppState,
|
AppState,
|
||||||
|
LoginV1,
|
||||||
|
LoginV2,
|
||||||
|
LoginVersion,
|
||||||
OIDCAppType,
|
OIDCAppType,
|
||||||
OIDCAuthMethodType,
|
OIDCAuthMethodType,
|
||||||
OIDCConfig,
|
OIDCConfig,
|
||||||
@ -50,8 +53,8 @@ import {
|
|||||||
getAuthMethodFromPartialConfig,
|
getAuthMethodFromPartialConfig,
|
||||||
getPartialConfigFromAuthMethod,
|
getPartialConfigFromAuthMethod,
|
||||||
IMPLICIT_METHOD,
|
IMPLICIT_METHOD,
|
||||||
PKCE_METHOD,
|
|
||||||
PK_JWT_METHOD,
|
PK_JWT_METHOD,
|
||||||
|
PKCE_METHOD,
|
||||||
POST_METHOD,
|
POST_METHOD,
|
||||||
} from '../authmethods';
|
} from '../authmethods';
|
||||||
import { AuthMethodDialogComponent } from './auth-method-dialog/auth-method-dialog.component';
|
import { AuthMethodDialogComponent } from './auth-method-dialog/auth-method-dialog.component';
|
||||||
@ -182,6 +185,7 @@ export class AppDetailComponent implements OnInit, OnDestroy {
|
|||||||
public currentSetting: string | undefined = this.settingsList[0].id;
|
public currentSetting: string | undefined = this.settingsList[0].id;
|
||||||
|
|
||||||
public isNew = signal<boolean>(false);
|
public isNew = signal<boolean>(false);
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private envSvc: EnvironmentService,
|
private envSvc: EnvironmentService,
|
||||||
public translate: TranslateService,
|
public translate: TranslateService,
|
||||||
@ -203,6 +207,8 @@ export class AppDetailComponent implements OnInit, OnDestroy {
|
|||||||
grantTypesList: [{ value: [], disabled: true }],
|
grantTypesList: [{ value: [], disabled: true }],
|
||||||
appType: [{ value: '', disabled: true }],
|
appType: [{ value: '', disabled: true }],
|
||||||
authMethodType: [{ value: '', disabled: true }],
|
authMethodType: [{ value: '', disabled: true }],
|
||||||
|
loginV2: [{ value: false, disabled: true }],
|
||||||
|
loginV2BaseURL: [{ value: '', disabled: true }],
|
||||||
});
|
});
|
||||||
|
|
||||||
this.oidcTokenForm = this.fb.group({
|
this.oidcTokenForm = this.fb.group({
|
||||||
@ -430,6 +436,12 @@ export class AppDetailComponent implements OnInit, OnDestroy {
|
|||||||
const inSecs = this.app.oidcConfig?.clockSkew.seconds + this.app.oidcConfig?.clockSkew.nanos / 100000;
|
const inSecs = this.app.oidcConfig?.clockSkew.seconds + this.app.oidcConfig?.clockSkew.nanos / 100000;
|
||||||
this.oidcTokenForm.controls['clockSkewSeconds'].setValue(inSecs);
|
this.oidcTokenForm.controls['clockSkewSeconds'].setValue(inSecs);
|
||||||
}
|
}
|
||||||
|
if (this.app.oidcConfig?.loginVersion?.loginV1) {
|
||||||
|
this.oidcForm.controls['loginV2'].setValue(false);
|
||||||
|
} else if (this.app.oidcConfig?.loginVersion?.loginV2) {
|
||||||
|
this.oidcForm.controls['loginV2'].setValue(true);
|
||||||
|
this.oidcForm.controls['loginV2BaseURL'].setValue(this.app.oidcConfig.loginVersion.loginV2.baseUri);
|
||||||
|
}
|
||||||
if (this.app.oidcConfig) {
|
if (this.app.oidcConfig) {
|
||||||
this.oidcForm.patchValue(this.app.oidcConfig);
|
this.oidcForm.patchValue(this.app.oidcConfig);
|
||||||
this.oidcTokenForm.patchValue(this.app.oidcConfig);
|
this.oidcTokenForm.patchValue(this.app.oidcConfig);
|
||||||
@ -655,6 +667,15 @@ export class AppDetailComponent implements OnInit, OnDestroy {
|
|||||||
req.setAuthMethodType(this.app.oidcConfig.authMethodType);
|
req.setAuthMethodType(this.app.oidcConfig.authMethodType);
|
||||||
req.setGrantTypesList(this.app.oidcConfig.grantTypesList);
|
req.setGrantTypesList(this.app.oidcConfig.grantTypesList);
|
||||||
req.setAppType(this.app.oidcConfig.appType);
|
req.setAppType(this.app.oidcConfig.appType);
|
||||||
|
const login = new LoginVersion();
|
||||||
|
if (this.loginV2?.value) {
|
||||||
|
const loginV2 = new LoginV2();
|
||||||
|
loginV2.setBaseUri(this.loginV2BaseURL?.value);
|
||||||
|
login.setLoginV2(loginV2);
|
||||||
|
} else {
|
||||||
|
login.setLoginV1(new LoginV1());
|
||||||
|
}
|
||||||
|
req.setLoginVersion(login);
|
||||||
|
|
||||||
// token
|
// token
|
||||||
req.setAccessTokenType(this.app.oidcConfig.accessTokenType);
|
req.setAccessTokenType(this.app.oidcConfig.accessTokenType);
|
||||||
@ -839,6 +860,14 @@ export class AppDetailComponent implements OnInit, OnDestroy {
|
|||||||
return this.oidcForm.get('authMethodType');
|
return this.oidcForm.get('authMethodType');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public get loginV2(): FormControl<boolean> | null {
|
||||||
|
return this.oidcForm.get('loginV2') as FormControl<boolean>;
|
||||||
|
}
|
||||||
|
|
||||||
|
public get loginV2BaseURL(): AbstractControl | null {
|
||||||
|
return this.oidcForm.get('loginV2BaseURL');
|
||||||
|
}
|
||||||
|
|
||||||
public get apiAuthMethodType(): AbstractControl | null {
|
public get apiAuthMethodType(): AbstractControl | null {
|
||||||
return this.apiForm.get('authMethodType') as UntypedFormControl;
|
return this.apiForm.get('authMethodType') as UntypedFormControl;
|
||||||
}
|
}
|
||||||
|
@ -2537,6 +2537,10 @@
|
|||||||
"CLIENTSECRETREGENERATED": "генерирана клиентска тайна.",
|
"CLIENTSECRETREGENERATED": "генерирана клиентска тайна.",
|
||||||
"DELETED": "Приложението е изтрито.",
|
"DELETED": "Приложението е изтрито.",
|
||||||
"CONFIGCHANGED": "Открити са промени!"
|
"CONFIGCHANGED": "Открити са промени!"
|
||||||
|
},
|
||||||
|
"LOGINV2": {
|
||||||
|
"USEV2": "Използвайте новия интерфейс за вход",
|
||||||
|
"BASEURL": "Персонализиран основен URL адрес за новия интерфейс за вход"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"GENDERS": {
|
"GENDERS": {
|
||||||
|
@ -2550,6 +2550,10 @@
|
|||||||
"CLIENTSECRETREGENERATED": "klient tajemství regenerováno.",
|
"CLIENTSECRETREGENERATED": "klient tajemství regenerováno.",
|
||||||
"DELETED": "Aplikace smazána.",
|
"DELETED": "Aplikace smazána.",
|
||||||
"CONFIGCHANGED": "Zjištěny změny!"
|
"CONFIGCHANGED": "Zjištěny změny!"
|
||||||
|
},
|
||||||
|
"LOGINV2": {
|
||||||
|
"USEV2": "Použít nové uživatelské rozhraní pro přihlášení",
|
||||||
|
"BASEURL": "Vlastní základní adresa URL pro nové uživatelské rozhraní pro přihlášení"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"GENDERS": {
|
"GENDERS": {
|
||||||
|
@ -2541,6 +2541,10 @@
|
|||||||
"CLIENTSECRETREGENERATED": "Client Secret generiert.",
|
"CLIENTSECRETREGENERATED": "Client Secret generiert.",
|
||||||
"DELETED": "App gelöscht.",
|
"DELETED": "App gelöscht.",
|
||||||
"CONFIGCHANGED": "Konfigurationsänderung entdeckt."
|
"CONFIGCHANGED": "Konfigurationsänderung entdeckt."
|
||||||
|
},
|
||||||
|
"LOGINV2": {
|
||||||
|
"USEV2": "Neue Login-Benutzeroberfläche verwenden",
|
||||||
|
"BASEURL": "Benutzerdefinierte Basis-URL für die neue Login-Benutzeroberfläche"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"GENDERS": {
|
"GENDERS": {
|
||||||
|
@ -2566,6 +2566,10 @@
|
|||||||
"CLIENTSECRETREGENERATED": "client secret generated.",
|
"CLIENTSECRETREGENERATED": "client secret generated.",
|
||||||
"DELETED": "App deleted.",
|
"DELETED": "App deleted.",
|
||||||
"CONFIGCHANGED": "Changes detected!"
|
"CONFIGCHANGED": "Changes detected!"
|
||||||
|
},
|
||||||
|
"LOGINV2": {
|
||||||
|
"USEV2": "Use new Login UI",
|
||||||
|
"BASEURL": "Custom base URL for the new Login UI"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"GENDERS": {
|
"GENDERS": {
|
||||||
|
@ -2538,6 +2538,10 @@
|
|||||||
"CLIENTSECRETREGENERATED": "secreto del cliente generado.",
|
"CLIENTSECRETREGENERATED": "secreto del cliente generado.",
|
||||||
"DELETED": "App borrada.",
|
"DELETED": "App borrada.",
|
||||||
"CONFIGCHANGED": "¡Cambios detectados!"
|
"CONFIGCHANGED": "¡Cambios detectados!"
|
||||||
|
},
|
||||||
|
"LOGINV2": {
|
||||||
|
"USEV2": "Usar la nueva interfaz de usuario de inicio de sesión",
|
||||||
|
"BASEURL": "URL base personalizada para la nueva interfaz de usuario de inicio de sesión"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"GENDERS": {
|
"GENDERS": {
|
||||||
|
@ -2542,6 +2542,10 @@
|
|||||||
"CLIENTSECRETREGENERATED": "secret client généré.",
|
"CLIENTSECRETREGENERATED": "secret client généré.",
|
||||||
"DELETED": "Application supprimée.",
|
"DELETED": "Application supprimée.",
|
||||||
"CONFIGCHANGED": "Changements détectés !"
|
"CONFIGCHANGED": "Changements détectés !"
|
||||||
|
},
|
||||||
|
"LOGINV2": {
|
||||||
|
"USEV2": "Utiliser la nouvelle interface utilisateur de connexion",
|
||||||
|
"BASEURL": "URL de base personnalisée pour la nouvelle interface utilisateur de connexion"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"GENDERS": {
|
"GENDERS": {
|
||||||
|
@ -2564,6 +2564,10 @@
|
|||||||
"CLIENTSECRETREGENERATED": "Az ügyfél titok generálva.",
|
"CLIENTSECRETREGENERATED": "Az ügyfél titok generálva.",
|
||||||
"DELETED": "Az app törölve.",
|
"DELETED": "Az app törölve.",
|
||||||
"CONFIGCHANGED": "Változások észlelve!"
|
"CONFIGCHANGED": "Változások észlelve!"
|
||||||
|
},
|
||||||
|
"LOGINV2": {
|
||||||
|
"USEV2": "Új bejelentkezési felhasználói felület használata",
|
||||||
|
"BASEURL": "Egyéni alapértelmezett URL az új bejelentkezési felhasználói felülethez"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"GENDERS": {
|
"GENDERS": {
|
||||||
|
@ -2255,6 +2255,10 @@
|
|||||||
"CLIENTSECRETREGENERATED": "rahasia klien dihasilkan.",
|
"CLIENTSECRETREGENERATED": "rahasia klien dihasilkan.",
|
||||||
"DELETED": "Aplikasi dihapus.",
|
"DELETED": "Aplikasi dihapus.",
|
||||||
"CONFIGCHANGED": "Perubahan terdeteksi!"
|
"CONFIGCHANGED": "Perubahan terdeteksi!"
|
||||||
|
},
|
||||||
|
"LOGINV2": {
|
||||||
|
"USEV2": "Gunakan UI Login baru",
|
||||||
|
"BASEURL": "URL dasar kustom untuk UI Login baru"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"GENDERS": { "0": "Tidak dikenal", "1": "Perempuan", "2": "Pria", "3": "Lainnya" },
|
"GENDERS": { "0": "Tidak dikenal", "1": "Perempuan", "2": "Pria", "3": "Lainnya" },
|
||||||
|
@ -2542,6 +2542,10 @@
|
|||||||
"CLIENTSECRETREGENERATED": "Client secret generato.",
|
"CLIENTSECRETREGENERATED": "Client secret generato.",
|
||||||
"DELETED": "App rimossa con successo.",
|
"DELETED": "App rimossa con successo.",
|
||||||
"CONFIGCHANGED": "Modifiche alla configurazione rilevate"
|
"CONFIGCHANGED": "Modifiche alla configurazione rilevate"
|
||||||
|
},
|
||||||
|
"LOGINV2": {
|
||||||
|
"USEV2": "Utilizza la nuova interfaccia utente di accesso",
|
||||||
|
"BASEURL": "URL base personalizzato per la nuova interfaccia utente di accesso"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"GENDERS": {
|
"GENDERS": {
|
||||||
|
@ -2532,6 +2532,10 @@
|
|||||||
"CLIENTSECRETREGENERATED": "クライアントシークレットが生成されました。",
|
"CLIENTSECRETREGENERATED": "クライアントシークレットが生成されました。",
|
||||||
"DELETED": "アプリが削除されました。",
|
"DELETED": "アプリが削除されました。",
|
||||||
"CONFIGCHANGED": "変更を検出しました!"
|
"CONFIGCHANGED": "変更を検出しました!"
|
||||||
|
},
|
||||||
|
"LOGINV2": {
|
||||||
|
"USEV2": "新しいログインUIを使用する",
|
||||||
|
"BASEURL": "新しいログインUIのカスタムベースURL"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"GENDERS": {
|
"GENDERS": {
|
||||||
|
@ -2538,6 +2538,10 @@
|
|||||||
"CLIENTSECRETREGENERATED": "Клиентската тајна е генерирана.",
|
"CLIENTSECRETREGENERATED": "Клиентската тајна е генерирана.",
|
||||||
"DELETED": "Апликацијата е избришана.",
|
"DELETED": "Апликацијата е избришана.",
|
||||||
"CONFIGCHANGED": "Детектирани промени!"
|
"CONFIGCHANGED": "Детектирани промени!"
|
||||||
|
},
|
||||||
|
"LOGINV2": {
|
||||||
|
"USEV2": "Користете нов интерфејс за најава",
|
||||||
|
"BASEURL": "Прилагоден основен URL за новиот интерфејс за најава"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"GENDERS": {
|
"GENDERS": {
|
||||||
|
@ -2557,6 +2557,10 @@
|
|||||||
"CLIENTSECRETREGENERATED": "client geheim gegenereerd.",
|
"CLIENTSECRETREGENERATED": "client geheim gegenereerd.",
|
||||||
"DELETED": "App verwijderd.",
|
"DELETED": "App verwijderd.",
|
||||||
"CONFIGCHANGED": "Wijzigingen gedetecteerd!"
|
"CONFIGCHANGED": "Wijzigingen gedetecteerd!"
|
||||||
|
},
|
||||||
|
"LOGINV2": {
|
||||||
|
"USEV2": "Nieuwe login-gebruikersinterface gebruiken",
|
||||||
|
"BASEURL": "Aangepaste basis-URL voor de nieuwe login-gebruikersinterface"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"GENDERS": {
|
"GENDERS": {
|
||||||
|
@ -2541,6 +2541,10 @@
|
|||||||
"CLIENTSECRETREGENERATED": "Sekret klienta został wygenerowany.",
|
"CLIENTSECRETREGENERATED": "Sekret klienta został wygenerowany.",
|
||||||
"DELETED": "Aplikacja została usunięta.",
|
"DELETED": "Aplikacja została usunięta.",
|
||||||
"CONFIGCHANGED": "Wykryto zmiany!"
|
"CONFIGCHANGED": "Wykryto zmiany!"
|
||||||
|
},
|
||||||
|
"LOGINV2": {
|
||||||
|
"USEV2": "Użyj nowego interfejsu użytkownika logowania",
|
||||||
|
"BASEURL": "Niestandardowy podstawowy adres URL dla nowego interfejsu użytkownika logowania"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"GENDERS": {
|
"GENDERS": {
|
||||||
|
@ -2537,6 +2537,10 @@
|
|||||||
"CLIENTSECRETREGENERATED": "segredo do cliente gerado.",
|
"CLIENTSECRETREGENERATED": "segredo do cliente gerado.",
|
||||||
"DELETED": "Aplicativo excluído.",
|
"DELETED": "Aplicativo excluído.",
|
||||||
"CONFIGCHANGED": "Alterações detectadas!"
|
"CONFIGCHANGED": "Alterações detectadas!"
|
||||||
|
},
|
||||||
|
"LOGINV2": {
|
||||||
|
"USEV2": "Usar a nova interface de usuário de login",
|
||||||
|
"BASEURL": "URL base personalizado para a nova interface de usuário de login"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"GENDERS": {
|
"GENDERS": {
|
||||||
|
@ -2649,6 +2649,10 @@
|
|||||||
"CLIENTSECRETREGENERATED": "Клиентский ключ сгенерирован.",
|
"CLIENTSECRETREGENERATED": "Клиентский ключ сгенерирован.",
|
||||||
"DELETED": "Приложение удалено.",
|
"DELETED": "Приложение удалено.",
|
||||||
"CONFIGCHANGED": "Обнаружены изменения!"
|
"CONFIGCHANGED": "Обнаружены изменения!"
|
||||||
|
},
|
||||||
|
"LOGINV2": {
|
||||||
|
"USEV2": "Использовать новый интерфейс входа",
|
||||||
|
"BASEURL": "Настраиваемый базовый URL для нового интерфейса входа"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"GENDERS": {
|
"GENDERS": {
|
||||||
|
@ -2570,6 +2570,10 @@
|
|||||||
"CLIENTSECRETREGENERATED": "Klienthemlighet genererad.",
|
"CLIENTSECRETREGENERATED": "Klienthemlighet genererad.",
|
||||||
"DELETED": "App raderad.",
|
"DELETED": "App raderad.",
|
||||||
"CONFIGCHANGED": "Ändringar upptäckta!"
|
"CONFIGCHANGED": "Ändringar upptäckta!"
|
||||||
|
},
|
||||||
|
"LOGINV2": {
|
||||||
|
"USEV2": "Använd nya inloggningsgränssnittet",
|
||||||
|
"BASEURL": "Anpassad bas-URL för det nya inloggningsgränssnittet"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"GENDERS": {
|
"GENDERS": {
|
||||||
|
@ -2541,6 +2541,10 @@
|
|||||||
"CLIENTSECRETREGENERATED": "客户端秘钥已生成。",
|
"CLIENTSECRETREGENERATED": "客户端秘钥已生成。",
|
||||||
"DELETED": "应用已删除。",
|
"DELETED": "应用已删除。",
|
||||||
"CONFIGCHANGED": "检测到变化!"
|
"CONFIGCHANGED": "检测到变化!"
|
||||||
|
},
|
||||||
|
"LOGINV2": {
|
||||||
|
"USEV2": "使用新的登录UI",
|
||||||
|
"BASEURL": "新的登录UI的自定义基本URL"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"GENDERS": {
|
"GENDERS": {
|
||||||
|
@ -644,7 +644,15 @@ func importOIDCApps(ctx context.Context, s *Server, errors *[]*admin_pb.ImportDa
|
|||||||
}
|
}
|
||||||
for _, app := range org.GetOidcApps() {
|
for _, app := range org.GetOidcApps() {
|
||||||
logging.Debugf("import oidcapplication: %s", app.GetAppId())
|
logging.Debugf("import oidcapplication: %s", app.GetAppId())
|
||||||
_, err := s.command.AddOIDCApplicationWithID(ctx, management.AddOIDCAppRequestToDomain(app.App), org.GetOrgId(), app.GetAppId())
|
oidcApp, err := management.AddOIDCAppRequestToDomain(app.App)
|
||||||
|
if err != nil {
|
||||||
|
*errors = append(*errors, &admin_pb.ImportDataError{Type: "oidc_app", Id: app.GetAppId(), Message: err.Error()})
|
||||||
|
if isCtxTimeout(ctx) {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
_, err = s.command.AddOIDCApplicationWithID(ctx, oidcApp, org.GetOrgId(), app.GetAppId())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
*errors = append(*errors, &admin_pb.ImportDataError{Type: "oidc_app", Id: app.GetAppId(), Message: err.Error()})
|
*errors = append(*errors, &admin_pb.ImportDataError{Type: "oidc_app", Id: app.GetAppId(), Message: err.Error()})
|
||||||
if isCtxTimeout(ctx) {
|
if isCtxTimeout(ctx) {
|
||||||
|
@ -25,6 +25,7 @@ var iamRoles = []string{
|
|||||||
"IAM_USER_MANAGER",
|
"IAM_USER_MANAGER",
|
||||||
"IAM_ADMIN_IMPERSONATOR",
|
"IAM_ADMIN_IMPERSONATOR",
|
||||||
"IAM_END_USER_IMPERSONATOR",
|
"IAM_END_USER_IMPERSONATOR",
|
||||||
|
"IAM_LOGIN_CLIENT",
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestServer_ListIAMMemberRoles(t *testing.T) {
|
func TestServer_ListIAMMemberRoles(t *testing.T) {
|
||||||
|
@ -1,6 +1,10 @@
|
|||||||
package feature
|
package feature
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"net/url"
|
||||||
|
|
||||||
|
"github.com/muhlemmer/gu"
|
||||||
|
|
||||||
"github.com/zitadel/zitadel/internal/api/grpc/object/v2"
|
"github.com/zitadel/zitadel/internal/api/grpc/object/v2"
|
||||||
"github.com/zitadel/zitadel/internal/command"
|
"github.com/zitadel/zitadel/internal/command"
|
||||||
"github.com/zitadel/zitadel/internal/feature"
|
"github.com/zitadel/zitadel/internal/feature"
|
||||||
@ -8,7 +12,11 @@ import (
|
|||||||
feature_pb "github.com/zitadel/zitadel/pkg/grpc/feature/v2"
|
feature_pb "github.com/zitadel/zitadel/pkg/grpc/feature/v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
func systemFeaturesToCommand(req *feature_pb.SetSystemFeaturesRequest) *command.SystemFeatures {
|
func systemFeaturesToCommand(req *feature_pb.SetSystemFeaturesRequest) (*command.SystemFeatures, error) {
|
||||||
|
loginV2, err := loginV2ToDomain(req.GetLoginV2())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
return &command.SystemFeatures{
|
return &command.SystemFeatures{
|
||||||
LoginDefaultOrg: req.LoginDefaultOrg,
|
LoginDefaultOrg: req.LoginDefaultOrg,
|
||||||
TriggerIntrospectionProjections: req.OidcTriggerIntrospectionProjections,
|
TriggerIntrospectionProjections: req.OidcTriggerIntrospectionProjections,
|
||||||
@ -20,7 +28,8 @@ func systemFeaturesToCommand(req *feature_pb.SetSystemFeaturesRequest) *command.
|
|||||||
OIDCSingleV1SessionTermination: req.OidcSingleV1SessionTermination,
|
OIDCSingleV1SessionTermination: req.OidcSingleV1SessionTermination,
|
||||||
DisableUserTokenEvent: req.DisableUserTokenEvent,
|
DisableUserTokenEvent: req.DisableUserTokenEvent,
|
||||||
EnableBackChannelLogout: req.EnableBackChannelLogout,
|
EnableBackChannelLogout: req.EnableBackChannelLogout,
|
||||||
}
|
LoginV2: loginV2,
|
||||||
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func systemFeaturesToPb(f *query.SystemFeatures) *feature_pb.GetSystemFeaturesResponse {
|
func systemFeaturesToPb(f *query.SystemFeatures) *feature_pb.GetSystemFeaturesResponse {
|
||||||
@ -36,10 +45,15 @@ func systemFeaturesToPb(f *query.SystemFeatures) *feature_pb.GetSystemFeaturesRe
|
|||||||
OidcSingleV1SessionTermination: featureSourceToFlagPb(&f.OIDCSingleV1SessionTermination),
|
OidcSingleV1SessionTermination: featureSourceToFlagPb(&f.OIDCSingleV1SessionTermination),
|
||||||
DisableUserTokenEvent: featureSourceToFlagPb(&f.DisableUserTokenEvent),
|
DisableUserTokenEvent: featureSourceToFlagPb(&f.DisableUserTokenEvent),
|
||||||
EnableBackChannelLogout: featureSourceToFlagPb(&f.EnableBackChannelLogout),
|
EnableBackChannelLogout: featureSourceToFlagPb(&f.EnableBackChannelLogout),
|
||||||
|
LoginV2: loginV2ToLoginV2FlagPb(f.LoginV2),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func instanceFeaturesToCommand(req *feature_pb.SetInstanceFeaturesRequest) *command.InstanceFeatures {
|
func instanceFeaturesToCommand(req *feature_pb.SetInstanceFeaturesRequest) (*command.InstanceFeatures, error) {
|
||||||
|
loginV2, err := loginV2ToDomain(req.GetLoginV2())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
return &command.InstanceFeatures{
|
return &command.InstanceFeatures{
|
||||||
LoginDefaultOrg: req.LoginDefaultOrg,
|
LoginDefaultOrg: req.LoginDefaultOrg,
|
||||||
TriggerIntrospectionProjections: req.OidcTriggerIntrospectionProjections,
|
TriggerIntrospectionProjections: req.OidcTriggerIntrospectionProjections,
|
||||||
@ -53,7 +67,8 @@ func instanceFeaturesToCommand(req *feature_pb.SetInstanceFeaturesRequest) *comm
|
|||||||
OIDCSingleV1SessionTermination: req.OidcSingleV1SessionTermination,
|
OIDCSingleV1SessionTermination: req.OidcSingleV1SessionTermination,
|
||||||
DisableUserTokenEvent: req.DisableUserTokenEvent,
|
DisableUserTokenEvent: req.DisableUserTokenEvent,
|
||||||
EnableBackChannelLogout: req.EnableBackChannelLogout,
|
EnableBackChannelLogout: req.EnableBackChannelLogout,
|
||||||
}
|
LoginV2: loginV2,
|
||||||
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func instanceFeaturesToPb(f *query.InstanceFeatures) *feature_pb.GetInstanceFeaturesResponse {
|
func instanceFeaturesToPb(f *query.InstanceFeatures) *feature_pb.GetInstanceFeaturesResponse {
|
||||||
@ -71,6 +86,7 @@ func instanceFeaturesToPb(f *query.InstanceFeatures) *feature_pb.GetInstanceFeat
|
|||||||
OidcSingleV1SessionTermination: featureSourceToFlagPb(&f.OIDCSingleV1SessionTermination),
|
OidcSingleV1SessionTermination: featureSourceToFlagPb(&f.OIDCSingleV1SessionTermination),
|
||||||
DisableUserTokenEvent: featureSourceToFlagPb(&f.DisableUserTokenEvent),
|
DisableUserTokenEvent: featureSourceToFlagPb(&f.DisableUserTokenEvent),
|
||||||
EnableBackChannelLogout: featureSourceToFlagPb(&f.EnableBackChannelLogout),
|
EnableBackChannelLogout: featureSourceToFlagPb(&f.EnableBackChannelLogout),
|
||||||
|
LoginV2: loginV2ToLoginV2FlagPb(f.LoginV2),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -81,6 +97,39 @@ func featureSourceToImprovedPerformanceFlagPb(fs *query.FeatureSource[[]feature.
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func loginV2ToDomain(loginV2 *feature_pb.LoginV2) (_ *feature.LoginV2, err error) {
|
||||||
|
if loginV2 == nil {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
var baseURI *url.URL
|
||||||
|
if loginV2.GetBaseUri() != "" {
|
||||||
|
baseURI, err = url.Parse(loginV2.GetBaseUri())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return &feature.LoginV2{
|
||||||
|
Required: loginV2.GetRequired(),
|
||||||
|
BaseURI: baseURI,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func loginV2ToLoginV2FlagPb(f query.FeatureSource[*feature.LoginV2]) *feature_pb.LoginV2FeatureFlag {
|
||||||
|
var required bool
|
||||||
|
var baseURI *string
|
||||||
|
if f.Value != nil {
|
||||||
|
required = f.Value.Required
|
||||||
|
if f.Value.BaseURI != nil && f.Value.BaseURI.String() != "" {
|
||||||
|
baseURI = gu.Ptr(f.Value.BaseURI.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return &feature_pb.LoginV2FeatureFlag{
|
||||||
|
Required: required,
|
||||||
|
BaseUri: baseURI,
|
||||||
|
Source: featureLevelToSourcePb(f.Level),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func featureSourceToFlagPb(fs *query.FeatureSource[bool]) *feature_pb.FeatureFlag {
|
func featureSourceToFlagPb(fs *query.FeatureSource[bool]) *feature_pb.FeatureFlag {
|
||||||
return &feature_pb.FeatureFlag{
|
return &feature_pb.FeatureFlag{
|
||||||
Enabled: fs.Value,
|
Enabled: fs.Value,
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package feature
|
package feature
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"net/url"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@ -26,6 +27,10 @@ func Test_systemFeaturesToCommand(t *testing.T) {
|
|||||||
OidcTokenExchange: gu.Ptr(true),
|
OidcTokenExchange: gu.Ptr(true),
|
||||||
ImprovedPerformance: nil,
|
ImprovedPerformance: nil,
|
||||||
OidcSingleV1SessionTermination: gu.Ptr(true),
|
OidcSingleV1SessionTermination: gu.Ptr(true),
|
||||||
|
LoginV2: &feature_pb.LoginV2{
|
||||||
|
Required: true,
|
||||||
|
BaseUri: gu.Ptr("https://login.com"),
|
||||||
|
},
|
||||||
}
|
}
|
||||||
want := &command.SystemFeatures{
|
want := &command.SystemFeatures{
|
||||||
LoginDefaultOrg: gu.Ptr(true),
|
LoginDefaultOrg: gu.Ptr(true),
|
||||||
@ -36,9 +41,14 @@ func Test_systemFeaturesToCommand(t *testing.T) {
|
|||||||
TokenExchange: gu.Ptr(true),
|
TokenExchange: gu.Ptr(true),
|
||||||
ImprovedPerformance: nil,
|
ImprovedPerformance: nil,
|
||||||
OIDCSingleV1SessionTermination: gu.Ptr(true),
|
OIDCSingleV1SessionTermination: gu.Ptr(true),
|
||||||
|
LoginV2: &feature.LoginV2{
|
||||||
|
Required: true,
|
||||||
|
BaseURI: &url.URL{Scheme: "https", Host: "login.com"},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
got := systemFeaturesToCommand(arg)
|
got, err := systemFeaturesToCommand(arg)
|
||||||
assert.Equal(t, want, got)
|
assert.Equal(t, want, got)
|
||||||
|
assert.NoError(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
func Test_systemFeaturesToPb(t *testing.T) {
|
func Test_systemFeaturesToPb(t *testing.T) {
|
||||||
@ -84,6 +94,13 @@ func Test_systemFeaturesToPb(t *testing.T) {
|
|||||||
Level: feature.LevelSystem,
|
Level: feature.LevelSystem,
|
||||||
Value: true,
|
Value: true,
|
||||||
},
|
},
|
||||||
|
LoginV2: query.FeatureSource[*feature.LoginV2]{
|
||||||
|
Level: feature.LevelSystem,
|
||||||
|
Value: &feature.LoginV2{
|
||||||
|
Required: true,
|
||||||
|
BaseURI: &url.URL{Scheme: "https", Host: "login.com"},
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
want := &feature_pb.GetSystemFeaturesResponse{
|
want := &feature_pb.GetSystemFeaturesResponse{
|
||||||
Details: &object.Details{
|
Details: &object.Details{
|
||||||
@ -131,6 +148,11 @@ func Test_systemFeaturesToPb(t *testing.T) {
|
|||||||
Enabled: true,
|
Enabled: true,
|
||||||
Source: feature_pb.Source_SOURCE_SYSTEM,
|
Source: feature_pb.Source_SOURCE_SYSTEM,
|
||||||
},
|
},
|
||||||
|
LoginV2: &feature_pb.LoginV2FeatureFlag{
|
||||||
|
Required: true,
|
||||||
|
BaseUri: gu.Ptr("https://login.com"),
|
||||||
|
Source: feature_pb.Source_SOURCE_SYSTEM,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
got := systemFeaturesToPb(arg)
|
got := systemFeaturesToPb(arg)
|
||||||
assert.Equal(t, want, got)
|
assert.Equal(t, want, got)
|
||||||
@ -149,6 +171,10 @@ func Test_instanceFeaturesToCommand(t *testing.T) {
|
|||||||
DebugOidcParentError: gu.Ptr(true),
|
DebugOidcParentError: gu.Ptr(true),
|
||||||
OidcSingleV1SessionTermination: gu.Ptr(true),
|
OidcSingleV1SessionTermination: gu.Ptr(true),
|
||||||
EnableBackChannelLogout: gu.Ptr(true),
|
EnableBackChannelLogout: gu.Ptr(true),
|
||||||
|
LoginV2: &feature_pb.LoginV2{
|
||||||
|
Required: true,
|
||||||
|
BaseUri: gu.Ptr("https://login.com"),
|
||||||
|
},
|
||||||
}
|
}
|
||||||
want := &command.InstanceFeatures{
|
want := &command.InstanceFeatures{
|
||||||
LoginDefaultOrg: gu.Ptr(true),
|
LoginDefaultOrg: gu.Ptr(true),
|
||||||
@ -162,9 +188,14 @@ func Test_instanceFeaturesToCommand(t *testing.T) {
|
|||||||
DebugOIDCParentError: gu.Ptr(true),
|
DebugOIDCParentError: gu.Ptr(true),
|
||||||
OIDCSingleV1SessionTermination: gu.Ptr(true),
|
OIDCSingleV1SessionTermination: gu.Ptr(true),
|
||||||
EnableBackChannelLogout: gu.Ptr(true),
|
EnableBackChannelLogout: gu.Ptr(true),
|
||||||
|
LoginV2: &feature.LoginV2{
|
||||||
|
Required: true,
|
||||||
|
BaseURI: &url.URL{Scheme: "https", Host: "login.com"},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
got := instanceFeaturesToCommand(arg)
|
got, err := instanceFeaturesToCommand(arg)
|
||||||
assert.Equal(t, want, got)
|
assert.Equal(t, want, got)
|
||||||
|
assert.NoError(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
func Test_instanceFeaturesToPb(t *testing.T) {
|
func Test_instanceFeaturesToPb(t *testing.T) {
|
||||||
@ -214,6 +245,13 @@ func Test_instanceFeaturesToPb(t *testing.T) {
|
|||||||
Level: feature.LevelInstance,
|
Level: feature.LevelInstance,
|
||||||
Value: true,
|
Value: true,
|
||||||
},
|
},
|
||||||
|
LoginV2: query.FeatureSource[*feature.LoginV2]{
|
||||||
|
Level: feature.LevelInstance,
|
||||||
|
Value: &feature.LoginV2{
|
||||||
|
Required: true,
|
||||||
|
BaseURI: &url.URL{Scheme: "https", Host: "login.com"},
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
want := &feature_pb.GetInstanceFeaturesResponse{
|
want := &feature_pb.GetInstanceFeaturesResponse{
|
||||||
Details: &object.Details{
|
Details: &object.Details{
|
||||||
@ -269,6 +307,11 @@ func Test_instanceFeaturesToPb(t *testing.T) {
|
|||||||
Enabled: true,
|
Enabled: true,
|
||||||
Source: feature_pb.Source_SOURCE_INSTANCE,
|
Source: feature_pb.Source_SOURCE_INSTANCE,
|
||||||
},
|
},
|
||||||
|
LoginV2: &feature_pb.LoginV2FeatureFlag{
|
||||||
|
Required: true,
|
||||||
|
BaseUri: gu.Ptr("https://login.com"),
|
||||||
|
Source: feature_pb.Source_SOURCE_INSTANCE,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
got := instanceFeaturesToPb(arg)
|
got := instanceFeaturesToPb(arg)
|
||||||
assert.Equal(t, want, got)
|
assert.Equal(t, want, got)
|
||||||
|
@ -11,7 +11,11 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func (s *Server) SetSystemFeatures(ctx context.Context, req *feature.SetSystemFeaturesRequest) (_ *feature.SetSystemFeaturesResponse, err error) {
|
func (s *Server) SetSystemFeatures(ctx context.Context, req *feature.SetSystemFeaturesRequest) (_ *feature.SetSystemFeaturesResponse, err error) {
|
||||||
details, err := s.command.SetSystemFeatures(ctx, systemFeaturesToCommand(req))
|
features, err := systemFeaturesToCommand(req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
details, err := s.command.SetSystemFeatures(ctx, features)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -39,7 +43,11 @@ func (s *Server) GetSystemFeatures(ctx context.Context, req *feature.GetSystemFe
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) SetInstanceFeatures(ctx context.Context, req *feature.SetInstanceFeaturesRequest) (_ *feature.SetInstanceFeaturesResponse, err error) {
|
func (s *Server) SetInstanceFeatures(ctx context.Context, req *feature.SetInstanceFeaturesRequest) (_ *feature.SetInstanceFeaturesResponse, err error) {
|
||||||
details, err := s.command.SetInstanceFeatures(ctx, instanceFeaturesToCommand(req))
|
features, err := instanceFeaturesToCommand(req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
details, err := s.command.SetInstanceFeatures(ctx, features)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -27,7 +27,7 @@ func TestMain(m *testing.M) {
|
|||||||
|
|
||||||
Instance = integration.NewInstance(ctx)
|
Instance = integration.NewInstance(ctx)
|
||||||
|
|
||||||
UserCTX = Instance.WithAuthorization(ctx, integration.UserTypeLogin)
|
UserCTX = Instance.WithAuthorization(ctx, integration.UserTypeNoPermission)
|
||||||
IamCTX = Instance.WithAuthorization(ctx, integration.UserTypeIAMOwner)
|
IamCTX = Instance.WithAuthorization(ctx, integration.UserTypeIAMOwner)
|
||||||
CTX = Instance.WithAuthorization(ctx, integration.UserTypeOrgOwner)
|
CTX = Instance.WithAuthorization(ctx, integration.UserTypeOrgOwner)
|
||||||
Client = Instance.Client.IDPv2
|
Client = Instance.Client.IDPv2
|
||||||
|
@ -80,7 +80,11 @@ func (s *Server) ListAppChanges(ctx context.Context, req *mgmt_pb.ListAppChanges
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) AddOIDCApp(ctx context.Context, req *mgmt_pb.AddOIDCAppRequest) (*mgmt_pb.AddOIDCAppResponse, error) {
|
func (s *Server) AddOIDCApp(ctx context.Context, req *mgmt_pb.AddOIDCAppRequest) (*mgmt_pb.AddOIDCAppResponse, error) {
|
||||||
app, err := s.command.AddOIDCApplication(ctx, AddOIDCAppRequestToDomain(req), authz.GetCtxData(ctx).OrgID)
|
oidcApp, err := AddOIDCAppRequestToDomain(req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
app, err := s.command.AddOIDCApplication(ctx, oidcApp, authz.GetCtxData(ctx).OrgID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -128,7 +132,11 @@ func (s *Server) UpdateApp(ctx context.Context, req *mgmt_pb.UpdateAppRequest) (
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) UpdateOIDCAppConfig(ctx context.Context, req *mgmt_pb.UpdateOIDCAppConfigRequest) (*mgmt_pb.UpdateOIDCAppConfigResponse, error) {
|
func (s *Server) UpdateOIDCAppConfig(ctx context.Context, req *mgmt_pb.UpdateOIDCAppConfigRequest) (*mgmt_pb.UpdateOIDCAppConfigResponse, error) {
|
||||||
config, err := s.command.ChangeOIDCApplication(ctx, UpdateOIDCAppConfigRequestToDomain(req), authz.GetCtxData(ctx).OrgID)
|
oidcApp, err := UpdateOIDCAppConfigRequestToDomain(req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
config, err := s.command.ChangeOIDCApplication(ctx, oidcApp, authz.GetCtxData(ctx).OrgID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -36,7 +36,11 @@ func ListAppsRequestToModel(req *mgmt_pb.ListAppsRequest) (*query.AppSearchQueri
|
|||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func AddOIDCAppRequestToDomain(req *mgmt_pb.AddOIDCAppRequest) *domain.OIDCApp {
|
func AddOIDCAppRequestToDomain(req *mgmt_pb.AddOIDCAppRequest) (*domain.OIDCApp, error) {
|
||||||
|
loginVersion, loginBaseURI, err := app_grpc.LoginVersionToDomain(req.GetLoginVersion())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
return &domain.OIDCApp{
|
return &domain.OIDCApp{
|
||||||
ObjectRoot: models.ObjectRoot{
|
ObjectRoot: models.ObjectRoot{
|
||||||
AggregateID: req.ProjectId,
|
AggregateID: req.ProjectId,
|
||||||
@ -58,7 +62,9 @@ func AddOIDCAppRequestToDomain(req *mgmt_pb.AddOIDCAppRequest) *domain.OIDCApp {
|
|||||||
AdditionalOrigins: req.AdditionalOrigins,
|
AdditionalOrigins: req.AdditionalOrigins,
|
||||||
SkipNativeAppSuccessPage: req.SkipNativeAppSuccessPage,
|
SkipNativeAppSuccessPage: req.SkipNativeAppSuccessPage,
|
||||||
BackChannelLogoutURI: req.GetBackChannelLogoutUri(),
|
BackChannelLogoutURI: req.GetBackChannelLogoutUri(),
|
||||||
}
|
LoginVersion: loginVersion,
|
||||||
|
LoginBaseURI: loginBaseURI,
|
||||||
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func AddSAMLAppRequestToDomain(req *mgmt_pb.AddSAMLAppRequest) *domain.SAMLApp {
|
func AddSAMLAppRequestToDomain(req *mgmt_pb.AddSAMLAppRequest) *domain.SAMLApp {
|
||||||
@ -89,7 +95,11 @@ func UpdateAppRequestToDomain(app *mgmt_pb.UpdateAppRequest) domain.Application
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func UpdateOIDCAppConfigRequestToDomain(app *mgmt_pb.UpdateOIDCAppConfigRequest) *domain.OIDCApp {
|
func UpdateOIDCAppConfigRequestToDomain(app *mgmt_pb.UpdateOIDCAppConfigRequest) (*domain.OIDCApp, error) {
|
||||||
|
loginVersion, loginBaseURI, err := app_grpc.LoginVersionToDomain(app.GetLoginVersion())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
return &domain.OIDCApp{
|
return &domain.OIDCApp{
|
||||||
ObjectRoot: models.ObjectRoot{
|
ObjectRoot: models.ObjectRoot{
|
||||||
AggregateID: app.ProjectId,
|
AggregateID: app.ProjectId,
|
||||||
@ -110,7 +120,9 @@ func UpdateOIDCAppConfigRequestToDomain(app *mgmt_pb.UpdateOIDCAppConfigRequest)
|
|||||||
AdditionalOrigins: app.AdditionalOrigins,
|
AdditionalOrigins: app.AdditionalOrigins,
|
||||||
SkipNativeAppSuccessPage: app.SkipNativeAppSuccessPage,
|
SkipNativeAppSuccessPage: app.SkipNativeAppSuccessPage,
|
||||||
BackChannelLogoutURI: app.BackChannelLogoutUri,
|
BackChannelLogoutURI: app.BackChannelLogoutUri,
|
||||||
}
|
LoginVersion: loginVersion,
|
||||||
|
LoginBaseURI: loginBaseURI,
|
||||||
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func UpdateSAMLAppConfigRequestToDomain(app *mgmt_pb.UpdateSAMLAppConfigRequest) *domain.SAMLApp {
|
func UpdateSAMLAppConfigRequestToDomain(app *mgmt_pb.UpdateSAMLAppConfigRequest) *domain.SAMLApp {
|
||||||
|
@ -16,15 +16,18 @@ import (
|
|||||||
"google.golang.org/protobuf/types/known/timestamppb"
|
"google.golang.org/protobuf/types/known/timestamppb"
|
||||||
|
|
||||||
"github.com/zitadel/zitadel/internal/integration"
|
"github.com/zitadel/zitadel/internal/integration"
|
||||||
|
"github.com/zitadel/zitadel/pkg/grpc/app"
|
||||||
"github.com/zitadel/zitadel/pkg/grpc/object/v2"
|
"github.com/zitadel/zitadel/pkg/grpc/object/v2"
|
||||||
oidc_pb "github.com/zitadel/zitadel/pkg/grpc/oidc/v2"
|
oidc_pb "github.com/zitadel/zitadel/pkg/grpc/oidc/v2"
|
||||||
"github.com/zitadel/zitadel/pkg/grpc/session/v2"
|
"github.com/zitadel/zitadel/pkg/grpc/session/v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
CTX context.Context
|
CTX context.Context
|
||||||
Instance *integration.Instance
|
CTXLoginClient context.Context
|
||||||
Client oidc_pb.OIDCServiceClient
|
Instance *integration.Instance
|
||||||
|
Client oidc_pb.OIDCServiceClient
|
||||||
|
loginV2 = &app.LoginVersion{Version: &app.LoginVersion_LoginV2{LoginV2: &app.LoginV2{BaseUri: nil}}}
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@ -42,6 +45,7 @@ func TestMain(m *testing.M) {
|
|||||||
Client = Instance.Client.OIDCv2
|
Client = Instance.Client.OIDCv2
|
||||||
|
|
||||||
CTX = Instance.WithAuthorization(ctx, integration.UserTypeOrgOwner)
|
CTX = Instance.WithAuthorization(ctx, integration.UserTypeOrgOwner)
|
||||||
|
CTXLoginClient = Instance.WithAuthorization(ctx, integration.UserTypeLogin)
|
||||||
return m.Run()
|
return m.Run()
|
||||||
}())
|
}())
|
||||||
}
|
}
|
||||||
@ -51,29 +55,58 @@ func TestServer_GetAuthRequest(t *testing.T) {
|
|||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
client, err := Instance.CreateOIDCNativeClient(CTX, redirectURI, logoutRedirectURI, project.GetId(), false)
|
client, err := Instance.CreateOIDCNativeClient(CTX, redirectURI, logoutRedirectURI, project.GetId(), false)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
authRequestID, err := Instance.CreateOIDCAuthRequest(CTX, client.GetClientId(), Instance.Users[integration.UserTypeOrgOwner].ID, redirectURI)
|
|
||||||
require.NoError(t, err)
|
|
||||||
now := time.Now()
|
now := time.Now()
|
||||||
|
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
AuthRequestID string
|
AuthRequestID string
|
||||||
|
ctx context.Context
|
||||||
want *oidc_pb.GetAuthRequestResponse
|
want *oidc_pb.GetAuthRequestResponse
|
||||||
wantErr bool
|
wantErr bool
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "Not found",
|
name: "Not found",
|
||||||
AuthRequestID: "123",
|
AuthRequestID: "123",
|
||||||
|
ctx: CTX,
|
||||||
wantErr: true,
|
wantErr: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "success",
|
name: "success",
|
||||||
AuthRequestID: authRequestID,
|
AuthRequestID: func() string {
|
||||||
|
authRequestID, err := Instance.CreateOIDCAuthRequest(CTX, client.GetClientId(), Instance.Users[integration.UserTypeOrgOwner].ID, redirectURI)
|
||||||
|
require.NoError(t, err)
|
||||||
|
return authRequestID
|
||||||
|
}(),
|
||||||
|
ctx: CTX,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "without login client, no permission",
|
||||||
|
AuthRequestID: func() string {
|
||||||
|
client, err := Instance.CreateOIDCClientLoginVersion(CTX, redirectURI, logoutRedirectURI, project.GetId(), app.OIDCAppType_OIDC_APP_TYPE_NATIVE, app.OIDCAuthMethodType_OIDC_AUTH_METHOD_TYPE_NONE, false, loginV2)
|
||||||
|
require.NoError(t, err)
|
||||||
|
authRequestID, err := Instance.CreateOIDCAuthRequestWithoutLoginClientHeader(CTX, client.GetClientId(), redirectURI, "")
|
||||||
|
require.NoError(t, err)
|
||||||
|
return authRequestID
|
||||||
|
}(),
|
||||||
|
ctx: CTX,
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "without login client, with permission",
|
||||||
|
AuthRequestID: func() string {
|
||||||
|
client, err := Instance.CreateOIDCClientLoginVersion(CTX, redirectURI, logoutRedirectURI, project.GetId(), app.OIDCAppType_OIDC_APP_TYPE_NATIVE, app.OIDCAuthMethodType_OIDC_AUTH_METHOD_TYPE_NONE, false, loginV2)
|
||||||
|
require.NoError(t, err)
|
||||||
|
authRequestID, err := Instance.CreateOIDCAuthRequestWithoutLoginClientHeader(CTX, client.GetClientId(), redirectURI, "")
|
||||||
|
require.NoError(t, err)
|
||||||
|
return authRequestID
|
||||||
|
}(),
|
||||||
|
ctx: CTXLoginClient,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
got, err := Client.GetAuthRequest(CTX, &oidc_pb.GetAuthRequestRequest{
|
got, err := Client.GetAuthRequest(tt.ctx, &oidc_pb.GetAuthRequestRequest{
|
||||||
AuthRequestId: tt.AuthRequestID,
|
AuthRequestId: tt.AuthRequestID,
|
||||||
})
|
})
|
||||||
if tt.wantErr {
|
if tt.wantErr {
|
||||||
@ -83,7 +116,7 @@ func TestServer_GetAuthRequest(t *testing.T) {
|
|||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
authRequest := got.GetAuthRequest()
|
authRequest := got.GetAuthRequest()
|
||||||
assert.NotNil(t, authRequest)
|
assert.NotNil(t, authRequest)
|
||||||
assert.Equal(t, authRequestID, authRequest.GetId())
|
assert.Equal(t, tt.AuthRequestID, authRequest.GetId())
|
||||||
assert.WithinRange(t, authRequest.GetCreationDate().AsTime(), now.Add(-time.Second), now.Add(time.Second))
|
assert.WithinRange(t, authRequest.GetCreationDate().AsTime(), now.Add(-time.Second), now.Add(time.Second))
|
||||||
assert.Contains(t, authRequest.GetScope(), "openid")
|
assert.Contains(t, authRequest.GetScope(), "openid")
|
||||||
})
|
})
|
||||||
@ -95,6 +128,8 @@ func TestServer_CreateCallback(t *testing.T) {
|
|||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
client, err := Instance.CreateOIDCNativeClient(CTX, redirectURI, logoutRedirectURI, project.GetId(), false)
|
client, err := Instance.CreateOIDCNativeClient(CTX, redirectURI, logoutRedirectURI, project.GetId(), false)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
clientV2, err := Instance.CreateOIDCClientLoginVersion(CTX, redirectURI, logoutRedirectURI, project.GetId(), app.OIDCAppType_OIDC_APP_TYPE_NATIVE, app.OIDCAuthMethodType_OIDC_AUTH_METHOD_TYPE_NONE, false, loginV2)
|
||||||
|
require.NoError(t, err)
|
||||||
sessionResp, err := Instance.Client.SessionV2.CreateSession(CTX, &session.CreateSessionRequest{
|
sessionResp, err := Instance.Client.SessionV2.CreateSession(CTX, &session.CreateSessionRequest{
|
||||||
Checks: &session.Checks{
|
Checks: &session.Checks{
|
||||||
User: &session.CheckUser{
|
User: &session.CheckUser{
|
||||||
@ -108,6 +143,7 @@ func TestServer_CreateCallback(t *testing.T) {
|
|||||||
|
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
|
ctx context.Context
|
||||||
req *oidc_pb.CreateCallbackRequest
|
req *oidc_pb.CreateCallbackRequest
|
||||||
AuthError string
|
AuthError string
|
||||||
want *oidc_pb.CreateCallbackResponse
|
want *oidc_pb.CreateCallbackResponse
|
||||||
@ -116,6 +152,7 @@ func TestServer_CreateCallback(t *testing.T) {
|
|||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "Not found",
|
name: "Not found",
|
||||||
|
ctx: CTX,
|
||||||
req: &oidc_pb.CreateCallbackRequest{
|
req: &oidc_pb.CreateCallbackRequest{
|
||||||
AuthRequestId: "123",
|
AuthRequestId: "123",
|
||||||
CallbackKind: &oidc_pb.CreateCallbackRequest_Session{
|
CallbackKind: &oidc_pb.CreateCallbackRequest_Session{
|
||||||
@ -129,6 +166,7 @@ func TestServer_CreateCallback(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "session not found",
|
name: "session not found",
|
||||||
|
ctx: CTX,
|
||||||
req: &oidc_pb.CreateCallbackRequest{
|
req: &oidc_pb.CreateCallbackRequest{
|
||||||
AuthRequestId: func() string {
|
AuthRequestId: func() string {
|
||||||
authRequestID, err := Instance.CreateOIDCAuthRequest(CTX, client.GetClientId(), Instance.Users[integration.UserTypeOrgOwner].ID, redirectURI)
|
authRequestID, err := Instance.CreateOIDCAuthRequest(CTX, client.GetClientId(), Instance.Users[integration.UserTypeOrgOwner].ID, redirectURI)
|
||||||
@ -146,6 +184,7 @@ func TestServer_CreateCallback(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "session token invalid",
|
name: "session token invalid",
|
||||||
|
ctx: CTX,
|
||||||
req: &oidc_pb.CreateCallbackRequest{
|
req: &oidc_pb.CreateCallbackRequest{
|
||||||
AuthRequestId: func() string {
|
AuthRequestId: func() string {
|
||||||
authRequestID, err := Instance.CreateOIDCAuthRequest(CTX, client.GetClientId(), Instance.Users.Get(integration.UserTypeOrgOwner).ID, redirectURI)
|
authRequestID, err := Instance.CreateOIDCAuthRequest(CTX, client.GetClientId(), Instance.Users.Get(integration.UserTypeOrgOwner).ID, redirectURI)
|
||||||
@ -163,6 +202,7 @@ func TestServer_CreateCallback(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "fail callback",
|
name: "fail callback",
|
||||||
|
ctx: CTX,
|
||||||
req: &oidc_pb.CreateCallbackRequest{
|
req: &oidc_pb.CreateCallbackRequest{
|
||||||
AuthRequestId: func() string {
|
AuthRequestId: func() string {
|
||||||
authRequestID, err := Instance.CreateOIDCAuthRequest(CTX, client.GetClientId(), Instance.Users.Get(integration.UserTypeOrgOwner).ID, redirectURI)
|
authRequestID, err := Instance.CreateOIDCAuthRequest(CTX, client.GetClientId(), Instance.Users.Get(integration.UserTypeOrgOwner).ID, redirectURI)
|
||||||
@ -186,8 +226,35 @@ func TestServer_CreateCallback(t *testing.T) {
|
|||||||
},
|
},
|
||||||
wantErr: false,
|
wantErr: false,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "fail callback, no login client header",
|
||||||
|
ctx: CTXLoginClient,
|
||||||
|
req: &oidc_pb.CreateCallbackRequest{
|
||||||
|
AuthRequestId: func() string {
|
||||||
|
authRequestID, err := Instance.CreateOIDCAuthRequestWithoutLoginClientHeader(CTX, clientV2.GetClientId(), redirectURI, "")
|
||||||
|
require.NoError(t, err)
|
||||||
|
return authRequestID
|
||||||
|
}(),
|
||||||
|
CallbackKind: &oidc_pb.CreateCallbackRequest_Error{
|
||||||
|
Error: &oidc_pb.AuthorizationError{
|
||||||
|
Error: oidc_pb.ErrorReason_ERROR_REASON_ACCESS_DENIED,
|
||||||
|
ErrorDescription: gu.Ptr("nope"),
|
||||||
|
ErrorUri: gu.Ptr("https://example.com/docs"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
want: &oidc_pb.CreateCallbackResponse{
|
||||||
|
CallbackUrl: regexp.QuoteMeta(`oidcintegrationtest://callback?error=access_denied&error_description=nope&error_uri=https%3A%2F%2Fexample.com%2Fdocs&state=state`),
|
||||||
|
Details: &object.Details{
|
||||||
|
ChangeDate: timestamppb.Now(),
|
||||||
|
ResourceOwner: Instance.ID(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
wantErr: false,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: "code callback",
|
name: "code callback",
|
||||||
|
ctx: CTX,
|
||||||
req: &oidc_pb.CreateCallbackRequest{
|
req: &oidc_pb.CreateCallbackRequest{
|
||||||
AuthRequestId: func() string {
|
AuthRequestId: func() string {
|
||||||
authRequestID, err := Instance.CreateOIDCAuthRequest(CTX, client.GetClientId(), Instance.Users.Get(integration.UserTypeOrgOwner).ID, redirectURI)
|
authRequestID, err := Instance.CreateOIDCAuthRequest(CTX, client.GetClientId(), Instance.Users.Get(integration.UserTypeOrgOwner).ID, redirectURI)
|
||||||
@ -211,10 +278,54 @@ func TestServer_CreateCallback(t *testing.T) {
|
|||||||
wantErr: false,
|
wantErr: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "implicit",
|
name: "code callback, no login client header, no permission, error",
|
||||||
|
ctx: CTX,
|
||||||
req: &oidc_pb.CreateCallbackRequest{
|
req: &oidc_pb.CreateCallbackRequest{
|
||||||
AuthRequestId: func() string {
|
AuthRequestId: func() string {
|
||||||
client, err := Instance.CreateOIDCImplicitFlowClient(CTX, redirectURIImplicit)
|
authRequestID, err := Instance.CreateOIDCAuthRequestWithoutLoginClientHeader(CTX, clientV2.GetClientId(), redirectURI, "")
|
||||||
|
require.NoError(t, err)
|
||||||
|
return authRequestID
|
||||||
|
}(),
|
||||||
|
CallbackKind: &oidc_pb.CreateCallbackRequest_Session{
|
||||||
|
Session: &oidc_pb.Session{
|
||||||
|
SessionId: sessionResp.GetSessionId(),
|
||||||
|
SessionToken: sessionResp.GetSessionToken(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "code callback, no login client header, with permission",
|
||||||
|
ctx: CTXLoginClient,
|
||||||
|
req: &oidc_pb.CreateCallbackRequest{
|
||||||
|
AuthRequestId: func() string {
|
||||||
|
authRequestID, err := Instance.CreateOIDCAuthRequestWithoutLoginClientHeader(CTX, clientV2.GetClientId(), redirectURI, "")
|
||||||
|
require.NoError(t, err)
|
||||||
|
return authRequestID
|
||||||
|
}(),
|
||||||
|
CallbackKind: &oidc_pb.CreateCallbackRequest_Session{
|
||||||
|
Session: &oidc_pb.Session{
|
||||||
|
SessionId: sessionResp.GetSessionId(),
|
||||||
|
SessionToken: sessionResp.GetSessionToken(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
want: &oidc_pb.CreateCallbackResponse{
|
||||||
|
CallbackUrl: `oidcintegrationtest:\/\/callback\?code=(.*)&state=state`,
|
||||||
|
Details: &object.Details{
|
||||||
|
ChangeDate: timestamppb.Now(),
|
||||||
|
ResourceOwner: Instance.ID(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
wantErr: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "implicit",
|
||||||
|
ctx: CTX,
|
||||||
|
req: &oidc_pb.CreateCallbackRequest{
|
||||||
|
AuthRequestId: func() string {
|
||||||
|
client, err := Instance.CreateOIDCImplicitFlowClient(CTX, redirectURIImplicit, nil)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
authRequestID, err := Instance.CreateOIDCAuthRequestImplicit(CTX, client.GetClientId(), Instance.Users.Get(integration.UserTypeOrgOwner).ID, redirectURIImplicit)
|
authRequestID, err := Instance.CreateOIDCAuthRequestImplicit(CTX, client.GetClientId(), Instance.Users.Get(integration.UserTypeOrgOwner).ID, redirectURIImplicit)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
@ -236,10 +347,37 @@ func TestServer_CreateCallback(t *testing.T) {
|
|||||||
},
|
},
|
||||||
wantErr: false,
|
wantErr: false,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "implicit, no login client header",
|
||||||
|
ctx: CTXLoginClient,
|
||||||
|
req: &oidc_pb.CreateCallbackRequest{
|
||||||
|
AuthRequestId: func() string {
|
||||||
|
clientV2, err := Instance.CreateOIDCImplicitFlowClient(CTX, redirectURIImplicit, loginV2)
|
||||||
|
require.NoError(t, err)
|
||||||
|
authRequestID, err := Instance.CreateOIDCAuthRequestImplicitWithoutLoginClientHeader(CTX, clientV2.GetClientId(), redirectURIImplicit)
|
||||||
|
require.NoError(t, err)
|
||||||
|
return authRequestID
|
||||||
|
}(),
|
||||||
|
CallbackKind: &oidc_pb.CreateCallbackRequest_Session{
|
||||||
|
Session: &oidc_pb.Session{
|
||||||
|
SessionId: sessionResp.GetSessionId(),
|
||||||
|
SessionToken: sessionResp.GetSessionToken(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
want: &oidc_pb.CreateCallbackResponse{
|
||||||
|
CallbackUrl: `http:\/\/localhost:9999\/callback#access_token=(.*)&expires_in=(.*)&id_token=(.*)&state=state&token_type=Bearer`,
|
||||||
|
Details: &object.Details{
|
||||||
|
ChangeDate: timestamppb.Now(),
|
||||||
|
ResourceOwner: Instance.ID(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
wantErr: false,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
got, err := Client.CreateCallback(CTX, tt.req)
|
got, err := Client.CreateCallback(tt.ctx, tt.req)
|
||||||
if tt.wantErr {
|
if tt.wantErr {
|
||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
return
|
return
|
||||||
|
@ -214,7 +214,7 @@ func TestServer_CreateCallback(t *testing.T) {
|
|||||||
name: "implicit",
|
name: "implicit",
|
||||||
req: &oidc_pb.CreateCallbackRequest{
|
req: &oidc_pb.CreateCallbackRequest{
|
||||||
AuthRequestId: func() string {
|
AuthRequestId: func() string {
|
||||||
client, err := Instance.CreateOIDCImplicitFlowClient(CTX, redirectURIImplicit)
|
client, err := Instance.CreateOIDCImplicitFlowClient(CTX, redirectURIImplicit, nil)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
authRequestID, err := Instance.CreateOIDCAuthRequestImplicit(CTX, client.GetClientId(), Instance.Users.Get(integration.UserTypeOrgOwner).ID, redirectURIImplicit)
|
authRequestID, err := Instance.CreateOIDCAuthRequestImplicit(CTX, client.GetClientId(), Instance.Users.Get(integration.UserTypeOrgOwner).ID, redirectURIImplicit)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
@ -35,7 +35,7 @@ func TestMain(m *testing.M) {
|
|||||||
|
|
||||||
CTX = Instance.WithAuthorization(ctx, integration.UserTypeIAMOwner)
|
CTX = Instance.WithAuthorization(ctx, integration.UserTypeIAMOwner)
|
||||||
OwnerCTX = Instance.WithAuthorization(ctx, integration.UserTypeOrgOwner)
|
OwnerCTX = Instance.WithAuthorization(ctx, integration.UserTypeOrgOwner)
|
||||||
UserCTX = Instance.WithAuthorization(ctx, integration.UserTypeLogin)
|
UserCTX = Instance.WithAuthorization(ctx, integration.UserTypeNoPermission)
|
||||||
User = Instance.CreateHumanUser(CTX)
|
User = Instance.CreateHumanUser(CTX)
|
||||||
return m.Run()
|
return m.Run()
|
||||||
}())
|
}())
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
package project
|
package project
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"net/url"
|
||||||
|
|
||||||
"google.golang.org/protobuf/types/known/durationpb"
|
"google.golang.org/protobuf/types/known/durationpb"
|
||||||
|
|
||||||
object_grpc "github.com/zitadel/zitadel/internal/api/grpc/object"
|
object_grpc "github.com/zitadel/zitadel/internal/api/grpc/object"
|
||||||
@ -62,10 +64,24 @@ func AppOIDCConfigToPb(app *query.OIDCApp) *app_pb.App_OidcConfig {
|
|||||||
AllowedOrigins: app.AllowedOrigins,
|
AllowedOrigins: app.AllowedOrigins,
|
||||||
SkipNativeAppSuccessPage: app.SkipNativeAppSuccessPage,
|
SkipNativeAppSuccessPage: app.SkipNativeAppSuccessPage,
|
||||||
BackChannelLogoutUri: app.BackChannelLogoutURI,
|
BackChannelLogoutUri: app.BackChannelLogoutURI,
|
||||||
|
LoginVersion: loginVersionToPb(app.LoginVersion, app.LoginBaseURI),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func loginVersionToPb(version domain.LoginVersion, baseURI *string) *app_pb.LoginVersion {
|
||||||
|
switch version {
|
||||||
|
case domain.LoginVersionUnspecified:
|
||||||
|
return nil
|
||||||
|
case domain.LoginVersion1:
|
||||||
|
return &app_pb.LoginVersion{Version: &app_pb.LoginVersion_LoginV1{LoginV1: &app_pb.LoginV1{}}}
|
||||||
|
case domain.LoginVersion2:
|
||||||
|
return &app_pb.LoginVersion{Version: &app_pb.LoginVersion_LoginV2{LoginV2: &app_pb.LoginV2{BaseUri: baseURI}}}
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func AppSAMLConfigToPb(app *query.SAMLApp) app_pb.AppConfig {
|
func AppSAMLConfigToPb(app *query.SAMLApp) app_pb.AppConfig {
|
||||||
return &app_pb.App_SamlConfig{
|
return &app_pb.App_SamlConfig{
|
||||||
SamlConfig: &app_pb.SAMLConfig{
|
SamlConfig: &app_pb.SAMLConfig{
|
||||||
@ -311,3 +327,17 @@ func AppQueryToModel(appQuery *app_pb.AppQuery) (query.SearchQuery, error) {
|
|||||||
return nil, zerrors.ThrowInvalidArgument(nil, "APP-Add46", "List.Query.Invalid")
|
return nil, zerrors.ThrowInvalidArgument(nil, "APP-Add46", "List.Query.Invalid")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func LoginVersionToDomain(version *app_pb.LoginVersion) (domain.LoginVersion, string, error) {
|
||||||
|
switch v := version.GetVersion().(type) {
|
||||||
|
case nil:
|
||||||
|
return domain.LoginVersionUnspecified, "", nil
|
||||||
|
case *app_pb.LoginVersion_LoginV1:
|
||||||
|
return domain.LoginVersion1, "", nil
|
||||||
|
case *app_pb.LoginVersion_LoginV2:
|
||||||
|
_, err := url.Parse(v.LoginV2.GetBaseUri())
|
||||||
|
return domain.LoginVersion2, v.LoginV2.GetBaseUri(), err
|
||||||
|
default:
|
||||||
|
return domain.LoginVersionUnspecified, "", nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -69,7 +69,7 @@ func TestServer_SetContactEmail(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "email patch, no permission",
|
name: "email patch, no permission",
|
||||||
ctx: instance.WithAuthorization(CTX, integration.UserTypeLogin),
|
ctx: instance.WithAuthorization(CTX, integration.UserTypeNoPermission),
|
||||||
dep: func(req *user.SetContactEmailRequest) error {
|
dep: func(req *user.SetContactEmailRequest) error {
|
||||||
userResp := instance.CreateSchemaUser(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), schemaResp.GetDetails().GetId(), []byte("{\"name\": \"user\"}"))
|
userResp := instance.CreateSchemaUser(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), schemaResp.GetDetails().GetId(), []byte("{\"name\": \"user\"}"))
|
||||||
req.Id = userResp.GetDetails().GetId()
|
req.Id = userResp.GetDetails().GetId()
|
||||||
@ -412,7 +412,7 @@ func TestServer_VerifyContactEmail(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "email verify, no permission",
|
name: "email verify, no permission",
|
||||||
ctx: instance.WithAuthorization(CTX, integration.UserTypeLogin),
|
ctx: instance.WithAuthorization(CTX, integration.UserTypeNoPermission),
|
||||||
dep: func(req *user.VerifyContactEmailRequest) error {
|
dep: func(req *user.VerifyContactEmailRequest) error {
|
||||||
userResp := instance.CreateSchemaUser(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), schemaResp.GetDetails().GetId(), []byte("{\"name\": \"user\"}"))
|
userResp := instance.CreateSchemaUser(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), schemaResp.GetDetails().GetId(), []byte("{\"name\": \"user\"}"))
|
||||||
req.Id = userResp.GetDetails().GetId()
|
req.Id = userResp.GetDetails().GetId()
|
||||||
@ -601,7 +601,7 @@ func TestServer_ResendContactEmailCode(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "email resend, no permission",
|
name: "email resend, no permission",
|
||||||
ctx: instance.WithAuthorization(CTX, integration.UserTypeLogin),
|
ctx: instance.WithAuthorization(CTX, integration.UserTypeNoPermission),
|
||||||
dep: func(req *user.ResendContactEmailCodeRequest) error {
|
dep: func(req *user.ResendContactEmailCodeRequest) error {
|
||||||
userResp := instance.CreateSchemaUser(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), schemaResp.GetDetails().GetId(), []byte("{\"name\": \"user\"}"))
|
userResp := instance.CreateSchemaUser(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), schemaResp.GetDetails().GetId(), []byte("{\"name\": \"user\"}"))
|
||||||
req.Id = userResp.GetDetails().GetId()
|
req.Id = userResp.GetDetails().GetId()
|
||||||
|
@ -68,7 +68,7 @@ func TestServer_SetContactPhone(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "phone patch, no permission",
|
name: "phone patch, no permission",
|
||||||
ctx: instance.WithAuthorization(CTX, integration.UserTypeLogin),
|
ctx: instance.WithAuthorization(CTX, integration.UserTypeNoPermission),
|
||||||
dep: func(req *user.SetContactPhoneRequest) error {
|
dep: func(req *user.SetContactPhoneRequest) error {
|
||||||
userResp := instance.CreateSchemaUser(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), schemaResp.GetDetails().GetId(), []byte("{\"name\": \"user\"}"))
|
userResp := instance.CreateSchemaUser(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), schemaResp.GetDetails().GetId(), []byte("{\"name\": \"user\"}"))
|
||||||
req.Id = userResp.GetDetails().GetId()
|
req.Id = userResp.GetDetails().GetId()
|
||||||
@ -340,7 +340,7 @@ func TestServer_VerifyContactPhone(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "phone verify, no permission",
|
name: "phone verify, no permission",
|
||||||
ctx: instance.WithAuthorization(CTX, integration.UserTypeLogin),
|
ctx: instance.WithAuthorization(CTX, integration.UserTypeNoPermission),
|
||||||
dep: func(req *user.VerifyContactPhoneRequest) error {
|
dep: func(req *user.VerifyContactPhoneRequest) error {
|
||||||
userResp := instance.CreateSchemaUser(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), schemaResp.GetDetails().GetId(), []byte("{\"name\": \"user\"}"))
|
userResp := instance.CreateSchemaUser(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), schemaResp.GetDetails().GetId(), []byte("{\"name\": \"user\"}"))
|
||||||
req.Id = userResp.GetDetails().GetId()
|
req.Id = userResp.GetDetails().GetId()
|
||||||
@ -530,7 +530,7 @@ func TestServer_ResendContactPhoneCode(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "phone resend, no permission",
|
name: "phone resend, no permission",
|
||||||
ctx: instance.WithAuthorization(CTX, integration.UserTypeLogin),
|
ctx: instance.WithAuthorization(CTX, integration.UserTypeNoPermission),
|
||||||
dep: func(req *user.ResendContactPhoneCodeRequest) error {
|
dep: func(req *user.ResendContactPhoneCodeRequest) error {
|
||||||
userResp := instance.CreateSchemaUser(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), schemaResp.GetDetails().GetId(), []byte("{\"name\": \"user\"}"))
|
userResp := instance.CreateSchemaUser(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), schemaResp.GetDetails().GetId(), []byte("{\"name\": \"user\"}"))
|
||||||
req.Id = userResp.GetDetails().GetId()
|
req.Id = userResp.GetDetails().GetId()
|
||||||
|
@ -94,7 +94,7 @@ func TestServer_CreateUser(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "user create, no permission",
|
name: "user create, no permission",
|
||||||
ctx: instance.WithAuthorization(CTX, integration.UserTypeLogin),
|
ctx: instance.WithAuthorization(CTX, integration.UserTypeNoPermission),
|
||||||
req: &user.CreateUserRequest{
|
req: &user.CreateUserRequest{
|
||||||
Organization: &object.Organization{
|
Organization: &object.Organization{
|
||||||
Property: &object.Organization_OrgId{
|
Property: &object.Organization_OrgId{
|
||||||
@ -294,7 +294,7 @@ func TestServer_PatchUser(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "user patch, no permission",
|
name: "user patch, no permission",
|
||||||
ctx: instance.WithAuthorization(CTX, integration.UserTypeLogin),
|
ctx: instance.WithAuthorization(CTX, integration.UserTypeNoPermission),
|
||||||
dep: func(req *user.PatchUserRequest) error {
|
dep: func(req *user.PatchUserRequest) error {
|
||||||
userResp := instance.CreateSchemaUser(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), schemaResp.GetDetails().GetId(), []byte("{\"name\": \"user\"}"))
|
userResp := instance.CreateSchemaUser(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), schemaResp.GetDetails().GetId(), []byte("{\"name\": \"user\"}"))
|
||||||
req.Id = userResp.GetDetails().GetId()
|
req.Id = userResp.GetDetails().GetId()
|
||||||
@ -734,7 +734,7 @@ func TestServer_DeleteUser(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "user delete, no permission",
|
name: "user delete, no permission",
|
||||||
ctx: instance.WithAuthorization(CTX, integration.UserTypeLogin),
|
ctx: instance.WithAuthorization(CTX, integration.UserTypeNoPermission),
|
||||||
dep: func(req *user.DeleteUserRequest) error {
|
dep: func(req *user.DeleteUserRequest) error {
|
||||||
userResp := instance.CreateSchemaUser(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), schemaResp.GetDetails().GetId(), []byte("{\"name\": \"user\"}"))
|
userResp := instance.CreateSchemaUser(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), schemaResp.GetDetails().GetId(), []byte("{\"name\": \"user\"}"))
|
||||||
req.Id = userResp.GetDetails().GetId()
|
req.Id = userResp.GetDetails().GetId()
|
||||||
@ -950,7 +950,7 @@ func TestServer_LockUser(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "user lock, no permission",
|
name: "user lock, no permission",
|
||||||
ctx: instance.WithAuthorization(CTX, integration.UserTypeLogin),
|
ctx: instance.WithAuthorization(CTX, integration.UserTypeNoPermission),
|
||||||
dep: func(req *user.LockUserRequest) error {
|
dep: func(req *user.LockUserRequest) error {
|
||||||
userResp := instance.CreateSchemaUser(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), schemaResp.GetDetails().GetId(), []byte("{\"name\": \"user\"}"))
|
userResp := instance.CreateSchemaUser(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), schemaResp.GetDetails().GetId(), []byte("{\"name\": \"user\"}"))
|
||||||
req.Id = userResp.GetDetails().GetId()
|
req.Id = userResp.GetDetails().GetId()
|
||||||
@ -1152,7 +1152,7 @@ func TestServer_UnlockUser(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "user unlock, no permission",
|
name: "user unlock, no permission",
|
||||||
ctx: instance.WithAuthorization(CTX, integration.UserTypeLogin),
|
ctx: instance.WithAuthorization(CTX, integration.UserTypeNoPermission),
|
||||||
dep: func(req *user.UnlockUserRequest) error {
|
dep: func(req *user.UnlockUserRequest) error {
|
||||||
userResp := instance.CreateSchemaUser(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), schemaResp.GetDetails().GetId(), []byte("{\"name\": \"user\"}"))
|
userResp := instance.CreateSchemaUser(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), schemaResp.GetDetails().GetId(), []byte("{\"name\": \"user\"}"))
|
||||||
req.Id = userResp.GetDetails().GetId()
|
req.Id = userResp.GetDetails().GetId()
|
||||||
@ -1333,7 +1333,7 @@ func TestServer_DeactivateUser(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "user deactivate, no permission",
|
name: "user deactivate, no permission",
|
||||||
ctx: instance.WithAuthorization(CTX, integration.UserTypeLogin),
|
ctx: instance.WithAuthorization(CTX, integration.UserTypeNoPermission),
|
||||||
dep: func(req *user.DeactivateUserRequest) error {
|
dep: func(req *user.DeactivateUserRequest) error {
|
||||||
userResp := instance.CreateSchemaUser(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), schemaResp.GetDetails().GetId(), []byte("{\"name\": \"user\"}"))
|
userResp := instance.CreateSchemaUser(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), schemaResp.GetDetails().GetId(), []byte("{\"name\": \"user\"}"))
|
||||||
req.Id = userResp.GetDetails().GetId()
|
req.Id = userResp.GetDetails().GetId()
|
||||||
@ -1535,7 +1535,7 @@ func TestServer_ActivateUser(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "user activate, no permission",
|
name: "user activate, no permission",
|
||||||
ctx: instance.WithAuthorization(CTX, integration.UserTypeLogin),
|
ctx: instance.WithAuthorization(CTX, integration.UserTypeNoPermission),
|
||||||
dep: func(req *user.ActivateUserRequest) error {
|
dep: func(req *user.ActivateUserRequest) error {
|
||||||
userResp := instance.CreateSchemaUser(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), schemaResp.GetDetails().GetId(), []byte("{\"name\": \"user\"}"))
|
userResp := instance.CreateSchemaUser(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), schemaResp.GetDetails().GetId(), []byte("{\"name\": \"user\"}"))
|
||||||
req.Id = userResp.GetDetails().GetId()
|
req.Id = userResp.GetDetails().GetId()
|
||||||
|
@ -237,7 +237,7 @@ func TestServer_GetActiveIdentityProviders(t *testing.T) {
|
|||||||
{
|
{
|
||||||
name: "permission error",
|
name: "permission error",
|
||||||
args: args{
|
args: args{
|
||||||
ctx: instance.WithAuthorization(CTX, integration.UserTypeLogin),
|
ctx: instance.WithAuthorization(CTX, integration.UserTypeNoPermission),
|
||||||
req: &settings.GetActiveIdentityProvidersRequest{},
|
req: &settings.GetActiveIdentityProvidersRequest{},
|
||||||
},
|
},
|
||||||
wantErr: true,
|
wantErr: true,
|
||||||
|
@ -43,7 +43,7 @@ func TestMain(m *testing.M) {
|
|||||||
|
|
||||||
Instance = integration.NewInstance(ctx)
|
Instance = integration.NewInstance(ctx)
|
||||||
|
|
||||||
UserCTX = Instance.WithAuthorization(ctx, integration.UserTypeLogin)
|
UserCTX = Instance.WithAuthorization(ctx, integration.UserTypeNoPermission)
|
||||||
IamCTX = Instance.WithAuthorization(ctx, integration.UserTypeIAMOwner)
|
IamCTX = Instance.WithAuthorization(ctx, integration.UserTypeIAMOwner)
|
||||||
SystemCTX = integration.WithSystemAuthorization(ctx)
|
SystemCTX = integration.WithSystemAuthorization(ctx)
|
||||||
CTX = Instance.WithAuthorization(ctx, integration.UserTypeOrgOwner)
|
CTX = Instance.WithAuthorization(ctx, integration.UserTypeOrgOwner)
|
||||||
|
@ -41,7 +41,7 @@ func TestMain(m *testing.M) {
|
|||||||
|
|
||||||
Instance = integration.NewInstance(ctx)
|
Instance = integration.NewInstance(ctx)
|
||||||
|
|
||||||
UserCTX = Instance.WithAuthorization(ctx, integration.UserTypeLogin)
|
UserCTX = Instance.WithAuthorization(ctx, integration.UserTypeNoPermission)
|
||||||
IamCTX = Instance.WithAuthorization(ctx, integration.UserTypeIAMOwner)
|
IamCTX = Instance.WithAuthorization(ctx, integration.UserTypeIAMOwner)
|
||||||
SystemCTX = integration.WithSystemAuthorization(ctx)
|
SystemCTX = integration.WithSystemAuthorization(ctx)
|
||||||
CTX = Instance.WithAuthorization(ctx, integration.UserTypeOrgOwner)
|
CTX = Instance.WithAuthorization(ctx, integration.UserTypeOrgOwner)
|
||||||
|
@ -5,6 +5,7 @@ import (
|
|||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"net/url"
|
||||||
"slices"
|
"slices"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
@ -16,6 +17,7 @@ import (
|
|||||||
"github.com/zitadel/zitadel/internal/api/authz"
|
"github.com/zitadel/zitadel/internal/api/authz"
|
||||||
http_utils "github.com/zitadel/zitadel/internal/api/http"
|
http_utils "github.com/zitadel/zitadel/internal/api/http"
|
||||||
"github.com/zitadel/zitadel/internal/api/http/middleware"
|
"github.com/zitadel/zitadel/internal/api/http/middleware"
|
||||||
|
"github.com/zitadel/zitadel/internal/api/ui/login"
|
||||||
"github.com/zitadel/zitadel/internal/auth/repository/eventsourcing/handler"
|
"github.com/zitadel/zitadel/internal/auth/repository/eventsourcing/handler"
|
||||||
"github.com/zitadel/zitadel/internal/command"
|
"github.com/zitadel/zitadel/internal/command"
|
||||||
"github.com/zitadel/zitadel/internal/domain"
|
"github.com/zitadel/zitadel/internal/domain"
|
||||||
@ -26,7 +28,11 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
LoginClientHeader = "x-zitadel-login-client"
|
LoginClientHeader = "x-zitadel-login-client"
|
||||||
|
LoginPostLogoutRedirectParam = "post_logout_redirect"
|
||||||
|
LoginPath = "/login"
|
||||||
|
LogoutPath = "/logout"
|
||||||
|
LogoutDonePath = "/logout/done"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (o *OPStorage) CreateAuthRequest(ctx context.Context, req *oidc.AuthRequest, userID string) (_ op.AuthRequest, err error) {
|
func (o *OPStorage) CreateAuthRequest(ctx context.Context, req *oidc.AuthRequest, userID string) (_ op.AuthRequest, err error) {
|
||||||
@ -36,12 +42,34 @@ func (o *OPStorage) CreateAuthRequest(ctx context.Context, req *oidc.AuthRequest
|
|||||||
span.EndWithError(err)
|
span.EndWithError(err)
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
// for backwards compatibility we pass the login client if set
|
||||||
headers, _ := http_utils.HeadersFromCtx(ctx)
|
headers, _ := http_utils.HeadersFromCtx(ctx)
|
||||||
if loginClient := headers.Get(LoginClientHeader); loginClient != "" {
|
loginClient := headers.Get(LoginClientHeader)
|
||||||
|
|
||||||
|
// if the instance requires the v2 login, use it no matter what the application configured
|
||||||
|
if authz.GetFeatures(ctx).LoginV2.Required {
|
||||||
return o.createAuthRequestLoginClient(ctx, req, userID, loginClient)
|
return o.createAuthRequestLoginClient(ctx, req, userID, loginClient)
|
||||||
}
|
}
|
||||||
|
|
||||||
return o.createAuthRequest(ctx, req, userID)
|
version, err := o.query.OIDCClientLoginVersion(ctx, req.ClientID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
switch version {
|
||||||
|
case domain.LoginVersion1:
|
||||||
|
return o.createAuthRequest(ctx, req, userID)
|
||||||
|
case domain.LoginVersion2:
|
||||||
|
return o.createAuthRequestLoginClient(ctx, req, userID, loginClient)
|
||||||
|
case domain.LoginVersionUnspecified:
|
||||||
|
fallthrough
|
||||||
|
default:
|
||||||
|
// if undefined, use the v2 login if the header is sent, to retain the current behavior
|
||||||
|
if loginClient != "" {
|
||||||
|
return o.createAuthRequestLoginClient(ctx, req, userID, loginClient)
|
||||||
|
}
|
||||||
|
return o.createAuthRequest(ctx, req, userID)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o *OPStorage) createAuthRequestScopeAndAudience(ctx context.Context, clientID string, reqScope []string) (scope, audience []string, err error) {
|
func (o *OPStorage) createAuthRequestScopeAndAudience(ctx context.Context, clientID string, reqScope []string) (scope, audience []string, err error) {
|
||||||
@ -240,18 +268,35 @@ func (o *OPStorage) TerminateSessionFromRequest(ctx context.Context, endSessionR
|
|||||||
|
|
||||||
// check for the login client header
|
// check for the login client header
|
||||||
headers, _ := http_utils.HeadersFromCtx(ctx)
|
headers, _ := http_utils.HeadersFromCtx(ctx)
|
||||||
// in case there is no id_token_hint, redirect to the UI and let it decide which session to terminate
|
|
||||||
if headers.Get(LoginClientHeader) != "" && endSessionRequest.IDTokenHintClaims == nil {
|
// V2:
|
||||||
return o.defaultLogoutURLV2 + endSessionRequest.RedirectURI, nil
|
// In case there is no id_token_hint and login V2 is either required by feature
|
||||||
|
// or requested via header (backwards compatibility),
|
||||||
|
// we'll redirect to the UI (V2) and let it decide which session to terminate
|
||||||
|
//
|
||||||
|
// If there's no id_token_hint and for v1 logins, we handle them separately
|
||||||
|
if endSessionRequest.IDTokenHintClaims == nil &&
|
||||||
|
(authz.GetFeatures(ctx).LoginV2.Required || headers.Get(LoginClientHeader) != "") {
|
||||||
|
redirectURI := v2PostLogoutRedirectURI(endSessionRequest.RedirectURI)
|
||||||
|
// if no base uri is set, fallback to the default configured in the runtime config
|
||||||
|
if authz.GetFeatures(ctx).LoginV2.BaseURI == nil || authz.GetFeatures(ctx).LoginV2.BaseURI.String() == "" {
|
||||||
|
return o.defaultLogoutURLV2 + redirectURI, nil
|
||||||
|
}
|
||||||
|
return buildLoginV2LogoutURL(authz.GetFeatures(ctx).LoginV2.BaseURI, redirectURI), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// If there is no login client header and no id_token_hint or the id_token_hint does not have a session ID,
|
// V1:
|
||||||
// do a v1 Terminate session (which terminates all sessions of the user agent, identified by cookie).
|
// We check again for the id_token_hint param and if a session is set in it.
|
||||||
|
// All explicit V2 sessions with empty id_token_hint are handled above and all V2 session contain a sessionID
|
||||||
|
// So if any condition is not met, we handle the request as a V1 request and do a (v1) TerminateSession,
|
||||||
|
// which terminates all sessions of the user agent, identified by cookie.
|
||||||
if endSessionRequest.IDTokenHintClaims == nil || endSessionRequest.IDTokenHintClaims.SessionID == "" {
|
if endSessionRequest.IDTokenHintClaims == nil || endSessionRequest.IDTokenHintClaims.SessionID == "" {
|
||||||
return endSessionRequest.RedirectURI, o.TerminateSession(ctx, endSessionRequest.UserID, endSessionRequest.ClientID)
|
return endSessionRequest.RedirectURI, o.TerminateSession(ctx, endSessionRequest.UserID, endSessionRequest.ClientID)
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the sessionID is prefixed by V1, we also terminate a v1 session.
|
// V1:
|
||||||
|
// If the sessionID is prefixed by V1, we also terminate a v1 session, but based on the SingleV1SessionTermination feature flag,
|
||||||
|
// we either terminate all sessions of the user agent or only the specific session
|
||||||
if strings.HasPrefix(endSessionRequest.IDTokenHintClaims.SessionID, handler.IDPrefixV1) {
|
if strings.HasPrefix(endSessionRequest.IDTokenHintClaims.SessionID, handler.IDPrefixV1) {
|
||||||
err = o.terminateV1Session(ctx, endSessionRequest.UserID, endSessionRequest.IDTokenHintClaims.SessionID)
|
err = o.terminateV1Session(ctx, endSessionRequest.UserID, endSessionRequest.IDTokenHintClaims.SessionID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -260,12 +305,31 @@ func (o *OPStorage) TerminateSessionFromRequest(ctx context.Context, endSessionR
|
|||||||
return endSessionRequest.RedirectURI, nil
|
return endSessionRequest.RedirectURI, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// terminate the v2 session of the id_token_hint
|
// V2:
|
||||||
|
// Terminate the v2 session of the id_token_hint
|
||||||
_, err = o.command.TerminateSessionWithoutTokenCheck(ctx, endSessionRequest.IDTokenHintClaims.SessionID)
|
_, err = o.command.TerminateSessionWithoutTokenCheck(ctx, endSessionRequest.IDTokenHintClaims.SessionID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
return endSessionRequest.RedirectURI, nil
|
return v2PostLogoutRedirectURI(endSessionRequest.RedirectURI), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func buildLoginV2LogoutURL(baseURI *url.URL, redirectURI string) string {
|
||||||
|
baseURI.JoinPath(LogoutPath)
|
||||||
|
q := baseURI.Query()
|
||||||
|
q.Set(LoginPostLogoutRedirectParam, redirectURI)
|
||||||
|
baseURI.RawQuery = q.Encode()
|
||||||
|
return baseURI.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
// v2PostLogoutRedirectURI will take care that the post_logout_redirect_uri is correctly set for v2 logins.
|
||||||
|
// The default value set by the [op.SessionEnder] only handles V1 logins. In case the redirect_uri is set to the default
|
||||||
|
// we'll return the path for the v2 login.
|
||||||
|
func v2PostLogoutRedirectURI(redirectURI string) string {
|
||||||
|
if redirectURI != login.DefaultLoggedOutPath {
|
||||||
|
return redirectURI
|
||||||
|
}
|
||||||
|
return LogoutDonePath
|
||||||
}
|
}
|
||||||
|
|
||||||
// terminateV1Session terminates "v1" sessions created through the login UI.
|
// terminateV1Session terminates "v1" sessions created through the login UI.
|
||||||
|
@ -15,6 +15,10 @@ import (
|
|||||||
"github.com/zitadel/zitadel/internal/query"
|
"github.com/zitadel/zitadel/internal/query"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
LoginAuthRequestParam = "authRequest"
|
||||||
|
)
|
||||||
|
|
||||||
type Client struct {
|
type Client struct {
|
||||||
client *query.OIDCClient
|
client *query.OIDCClient
|
||||||
defaultLoginURL string
|
defaultLoginURL string
|
||||||
@ -49,10 +53,21 @@ func (c *Client) GetID() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) LoginURL(id string) string {
|
func (c *Client) LoginURL(id string) string {
|
||||||
if strings.HasPrefix(id, command.IDPrefixV2) {
|
// if the authRequest does not have the v2 prefix, it was created for login V1
|
||||||
|
if !strings.HasPrefix(id, command.IDPrefixV2) {
|
||||||
|
return c.defaultLoginURL + id
|
||||||
|
}
|
||||||
|
// any v2 login without a specific base uri will be sent to the configured login v2 UI
|
||||||
|
// this way we're also backwards compatible
|
||||||
|
if c.client.LoginBaseURI == nil || c.client.LoginBaseURI.URL().String() == "" {
|
||||||
return c.defaultLoginURLV2 + id
|
return c.defaultLoginURLV2 + id
|
||||||
}
|
}
|
||||||
return c.defaultLoginURL + id
|
// for clients with a specific URI (internal or external) we only need to add the auth request id
|
||||||
|
uri := c.client.LoginBaseURI.URL().JoinPath(LoginPath)
|
||||||
|
q := uri.Query()
|
||||||
|
q.Set(LoginAuthRequestParam, id)
|
||||||
|
uri.RawQuery = q.Encode()
|
||||||
|
return uri.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) RedirectURIs() []string {
|
func (c *Client) RedirectURIs() []string {
|
||||||
|
@ -29,157 +29,255 @@ var (
|
|||||||
|
|
||||||
func TestOPStorage_CreateAuthRequest(t *testing.T) {
|
func TestOPStorage_CreateAuthRequest(t *testing.T) {
|
||||||
clientID, _ := createClient(t, Instance)
|
clientID, _ := createClient(t, Instance)
|
||||||
|
clientIDV2, _ := createClientLoginV2(t, Instance)
|
||||||
|
|
||||||
id := createAuthRequest(t, Instance, clientID, redirectURI)
|
id := createAuthRequest(t, Instance, clientID, redirectURI)
|
||||||
require.Contains(t, id, command.IDPrefixV2)
|
require.Contains(t, id, command.IDPrefixV2)
|
||||||
|
|
||||||
|
id2 := createAuthRequestNoLoginClientHeader(t, Instance, clientIDV2, redirectURI)
|
||||||
|
require.Contains(t, id2, command.IDPrefixV2)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestOPStorage_CreateAccessToken_code(t *testing.T) {
|
func TestOPStorage_CreateAccessToken_code(t *testing.T) {
|
||||||
clientID, _ := createClient(t, Instance)
|
tests := []struct {
|
||||||
authRequestID := createAuthRequest(t, Instance, clientID, redirectURI)
|
name string
|
||||||
sessionID, sessionToken, startTime, changeTime := Instance.CreateVerifiedWebAuthNSession(t, CTXLOGIN, User.GetUserId())
|
clientID string
|
||||||
linkResp, err := Instance.Client.OIDCv2.CreateCallback(CTXLOGIN, &oidc_pb.CreateCallbackRequest{
|
authRequestID func(t testing.TB, instance *integration.Instance, clientID, redirectURI string, scope ...string) string
|
||||||
AuthRequestId: authRequestID,
|
}{
|
||||||
CallbackKind: &oidc_pb.CreateCallbackRequest_Session{
|
{
|
||||||
Session: &oidc_pb.Session{
|
name: "login header",
|
||||||
SessionId: sessionID,
|
clientID: func() string {
|
||||||
SessionToken: sessionToken,
|
clientID, _ := createClient(t, Instance)
|
||||||
},
|
return clientID
|
||||||
|
}(),
|
||||||
|
authRequestID: createAuthRequest,
|
||||||
},
|
},
|
||||||
})
|
{
|
||||||
require.NoError(t, err)
|
name: "login v2 config",
|
||||||
|
clientID: func() string {
|
||||||
// test code exchange
|
clientID, _ := createClientLoginV2(t, Instance)
|
||||||
code := assertCodeResponse(t, linkResp.GetCallbackUrl())
|
return clientID
|
||||||
tokens, err := exchangeTokens(t, Instance, clientID, code, redirectURI)
|
}(),
|
||||||
require.NoError(t, err)
|
authRequestID: createAuthRequestNoLoginClientHeader,
|
||||||
assertTokens(t, tokens, false)
|
|
||||||
assertIDTokenClaims(t, tokens.IDTokenClaims, User.GetUserId(), armPasskey, startTime, changeTime, sessionID)
|
|
||||||
|
|
||||||
// callback on a succeeded request must fail
|
|
||||||
linkResp, err = Instance.Client.OIDCv2.CreateCallback(CTXLOGIN, &oidc_pb.CreateCallbackRequest{
|
|
||||||
AuthRequestId: authRequestID,
|
|
||||||
CallbackKind: &oidc_pb.CreateCallbackRequest_Session{
|
|
||||||
Session: &oidc_pb.Session{
|
|
||||||
SessionId: sessionID,
|
|
||||||
SessionToken: sessionToken,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
})
|
}
|
||||||
require.Error(t, err)
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
authRequestID := tt.authRequestID(t, Instance, tt.clientID, redirectURI)
|
||||||
|
sessionID, sessionToken, startTime, changeTime := Instance.CreateVerifiedWebAuthNSession(t, CTXLOGIN, User.GetUserId())
|
||||||
|
linkResp, err := Instance.Client.OIDCv2.CreateCallback(CTXLOGIN, &oidc_pb.CreateCallbackRequest{
|
||||||
|
AuthRequestId: authRequestID,
|
||||||
|
CallbackKind: &oidc_pb.CreateCallbackRequest_Session{
|
||||||
|
Session: &oidc_pb.Session{
|
||||||
|
SessionId: sessionID,
|
||||||
|
SessionToken: sessionToken,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
// exchange with a used code must fail
|
// test code exchange
|
||||||
_, err = exchangeTokens(t, Instance, clientID, code, redirectURI)
|
code := assertCodeResponse(t, linkResp.GetCallbackUrl())
|
||||||
require.Error(t, err)
|
tokens, err := exchangeTokens(t, Instance, tt.clientID, code, redirectURI)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assertTokens(t, tokens, false)
|
||||||
|
assertIDTokenClaims(t, tokens.IDTokenClaims, User.GetUserId(), armPasskey, startTime, changeTime, sessionID)
|
||||||
|
|
||||||
|
// callback on a succeeded request must fail
|
||||||
|
linkResp, err = Instance.Client.OIDCv2.CreateCallback(CTXLOGIN, &oidc_pb.CreateCallbackRequest{
|
||||||
|
AuthRequestId: authRequestID,
|
||||||
|
CallbackKind: &oidc_pb.CreateCallbackRequest_Session{
|
||||||
|
Session: &oidc_pb.Session{
|
||||||
|
SessionId: sessionID,
|
||||||
|
SessionToken: sessionToken,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
require.Error(t, err)
|
||||||
|
|
||||||
|
// exchange with a used code must fail
|
||||||
|
_, err = exchangeTokens(t, Instance, tt.clientID, code, redirectURI)
|
||||||
|
require.Error(t, err)
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestOPStorage_CreateAccessToken_implicit(t *testing.T) {
|
func TestOPStorage_CreateAccessToken_implicit(t *testing.T) {
|
||||||
clientID := createImplicitClient(t)
|
tests := []struct {
|
||||||
authRequestID := createAuthRequestImplicit(t, clientID, redirectURIImplicit)
|
name string
|
||||||
sessionID, sessionToken, startTime, changeTime := Instance.CreateVerifiedWebAuthNSession(t, CTXLOGIN, User.GetUserId())
|
clientID string
|
||||||
linkResp, err := Instance.Client.OIDCv2.CreateCallback(CTXLOGIN, &oidc_pb.CreateCallbackRequest{
|
authRequestID func(t testing.TB, clientID, redirectURI string, scope ...string) string
|
||||||
AuthRequestId: authRequestID,
|
}{
|
||||||
CallbackKind: &oidc_pb.CreateCallbackRequest_Session{
|
{
|
||||||
Session: &oidc_pb.Session{
|
name: "login header",
|
||||||
SessionId: sessionID,
|
clientID: createImplicitClient(t),
|
||||||
SessionToken: sessionToken,
|
authRequestID: createAuthRequestImplicit,
|
||||||
},
|
|
||||||
},
|
},
|
||||||
})
|
{
|
||||||
require.NoError(t, err)
|
name: "login v2 config",
|
||||||
|
clientID: createImplicitClientNoLoginClientHeader(t),
|
||||||
// test implicit callback
|
authRequestID: createAuthRequestImplicitNoLoginClientHeader,
|
||||||
callback, err := url.Parse(linkResp.GetCallbackUrl())
|
|
||||||
require.NoError(t, err)
|
|
||||||
values, err := url.ParseQuery(callback.Fragment)
|
|
||||||
require.NoError(t, err)
|
|
||||||
accessToken := values.Get("access_token")
|
|
||||||
idToken := values.Get("id_token")
|
|
||||||
refreshToken := values.Get("refresh_token")
|
|
||||||
assert.NotEmpty(t, accessToken)
|
|
||||||
assert.NotEmpty(t, idToken)
|
|
||||||
assert.Empty(t, refreshToken)
|
|
||||||
assert.NotEmpty(t, values.Get("expires_in"))
|
|
||||||
assert.Equal(t, oidc.BearerToken, values.Get("token_type"))
|
|
||||||
assert.Equal(t, "state", values.Get("state"))
|
|
||||||
|
|
||||||
// check id_token / claims
|
|
||||||
provider, err := Instance.CreateRelyingParty(CTX, clientID, redirectURIImplicit)
|
|
||||||
require.NoError(t, err)
|
|
||||||
claims, err := rp.VerifyTokens[*oidc.IDTokenClaims](context.Background(), accessToken, idToken, provider.IDTokenVerifier())
|
|
||||||
require.NoError(t, err)
|
|
||||||
assertIDTokenClaims(t, claims, User.GetUserId(), armPasskey, startTime, changeTime, sessionID)
|
|
||||||
|
|
||||||
// callback on a succeeded request must fail
|
|
||||||
linkResp, err = Instance.Client.OIDCv2.CreateCallback(CTXLOGIN, &oidc_pb.CreateCallbackRequest{
|
|
||||||
AuthRequestId: authRequestID,
|
|
||||||
CallbackKind: &oidc_pb.CreateCallbackRequest_Session{
|
|
||||||
Session: &oidc_pb.Session{
|
|
||||||
SessionId: sessionID,
|
|
||||||
SessionToken: sessionToken,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
})
|
}
|
||||||
require.Error(t, err)
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
authRequestID := tt.authRequestID(t, tt.clientID, redirectURIImplicit)
|
||||||
|
sessionID, sessionToken, startTime, changeTime := Instance.CreateVerifiedWebAuthNSession(t, CTXLOGIN, User.GetUserId())
|
||||||
|
linkResp, err := Instance.Client.OIDCv2.CreateCallback(CTXLOGIN, &oidc_pb.CreateCallbackRequest{
|
||||||
|
AuthRequestId: authRequestID,
|
||||||
|
CallbackKind: &oidc_pb.CreateCallbackRequest_Session{
|
||||||
|
Session: &oidc_pb.Session{
|
||||||
|
SessionId: sessionID,
|
||||||
|
SessionToken: sessionToken,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// test implicit callback
|
||||||
|
callback, err := url.Parse(linkResp.GetCallbackUrl())
|
||||||
|
require.NoError(t, err)
|
||||||
|
values, err := url.ParseQuery(callback.Fragment)
|
||||||
|
require.NoError(t, err)
|
||||||
|
accessToken := values.Get("access_token")
|
||||||
|
idToken := values.Get("id_token")
|
||||||
|
refreshToken := values.Get("refresh_token")
|
||||||
|
assert.NotEmpty(t, accessToken)
|
||||||
|
assert.NotEmpty(t, idToken)
|
||||||
|
assert.Empty(t, refreshToken)
|
||||||
|
assert.NotEmpty(t, values.Get("expires_in"))
|
||||||
|
assert.Equal(t, oidc.BearerToken, values.Get("token_type"))
|
||||||
|
assert.Equal(t, "state", values.Get("state"))
|
||||||
|
|
||||||
|
// check id_token / claims
|
||||||
|
provider, err := Instance.CreateRelyingParty(CTX, tt.clientID, redirectURIImplicit)
|
||||||
|
require.NoError(t, err)
|
||||||
|
claims, err := rp.VerifyTokens[*oidc.IDTokenClaims](context.Background(), accessToken, idToken, provider.IDTokenVerifier())
|
||||||
|
require.NoError(t, err)
|
||||||
|
assertIDTokenClaims(t, claims, User.GetUserId(), armPasskey, startTime, changeTime, sessionID)
|
||||||
|
|
||||||
|
// callback on a succeeded request must fail
|
||||||
|
linkResp, err = Instance.Client.OIDCv2.CreateCallback(CTXLOGIN, &oidc_pb.CreateCallbackRequest{
|
||||||
|
AuthRequestId: authRequestID,
|
||||||
|
CallbackKind: &oidc_pb.CreateCallbackRequest_Session{
|
||||||
|
Session: &oidc_pb.Session{
|
||||||
|
SessionId: sessionID,
|
||||||
|
SessionToken: sessionToken,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
require.Error(t, err)
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestOPStorage_CreateAccessAndRefreshTokens_code(t *testing.T) {
|
func TestOPStorage_CreateAccessAndRefreshTokens_code(t *testing.T) {
|
||||||
clientID, _ := createClient(t, Instance)
|
tests := []struct {
|
||||||
authRequestID := createAuthRequest(t, Instance, clientID, redirectURI, oidc.ScopeOpenID, oidc.ScopeOfflineAccess)
|
name string
|
||||||
sessionID, sessionToken, startTime, changeTime := Instance.CreateVerifiedWebAuthNSession(t, CTXLOGIN, User.GetUserId())
|
clientID string
|
||||||
linkResp, err := Instance.Client.OIDCv2.CreateCallback(CTXLOGIN, &oidc_pb.CreateCallbackRequest{
|
authRequestID func(t testing.TB, instance *integration.Instance, clientID, redirectURI string, scope ...string) string
|
||||||
AuthRequestId: authRequestID,
|
}{
|
||||||
CallbackKind: &oidc_pb.CreateCallbackRequest_Session{
|
{
|
||||||
Session: &oidc_pb.Session{
|
name: "login header",
|
||||||
SessionId: sessionID,
|
clientID: func() string {
|
||||||
SessionToken: sessionToken,
|
clientID, _ := createClient(t, Instance)
|
||||||
},
|
return clientID
|
||||||
|
}(),
|
||||||
|
authRequestID: createAuthRequest,
|
||||||
},
|
},
|
||||||
})
|
{
|
||||||
require.NoError(t, err)
|
name: "login v2 config",
|
||||||
|
clientID: func() string {
|
||||||
|
clientID, _ := createClientLoginV2(t, Instance)
|
||||||
|
return clientID
|
||||||
|
}(),
|
||||||
|
authRequestID: createAuthRequestNoLoginClientHeader,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
authRequestID := tt.authRequestID(t, Instance, tt.clientID, redirectURI, oidc.ScopeOpenID, oidc.ScopeOfflineAccess)
|
||||||
|
sessionID, sessionToken, startTime, changeTime := Instance.CreateVerifiedWebAuthNSession(t, CTXLOGIN, User.GetUserId())
|
||||||
|
linkResp, err := Instance.Client.OIDCv2.CreateCallback(CTXLOGIN, &oidc_pb.CreateCallbackRequest{
|
||||||
|
AuthRequestId: authRequestID,
|
||||||
|
CallbackKind: &oidc_pb.CreateCallbackRequest_Session{
|
||||||
|
Session: &oidc_pb.Session{
|
||||||
|
SessionId: sessionID,
|
||||||
|
SessionToken: sessionToken,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
// test code exchange (expect refresh token to be returned)
|
// test code exchange (expect refresh token to be returned)
|
||||||
code := assertCodeResponse(t, linkResp.GetCallbackUrl())
|
code := assertCodeResponse(t, linkResp.GetCallbackUrl())
|
||||||
tokens, err := exchangeTokens(t, Instance, clientID, code, redirectURI)
|
tokens, err := exchangeTokens(t, Instance, tt.clientID, code, redirectURI)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assertTokens(t, tokens, true)
|
assertTokens(t, tokens, true)
|
||||||
assertIDTokenClaims(t, tokens.IDTokenClaims, User.GetUserId(), armPasskey, startTime, changeTime, sessionID)
|
assertIDTokenClaims(t, tokens.IDTokenClaims, User.GetUserId(), armPasskey, startTime, changeTime, sessionID)
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestOPStorage_CreateAccessAndRefreshTokens_refresh(t *testing.T) {
|
func TestOPStorage_CreateAccessAndRefreshTokens_refresh(t *testing.T) {
|
||||||
clientID, _ := createClient(t, Instance)
|
tests := []struct {
|
||||||
provider, err := Instance.CreateRelyingParty(CTX, clientID, redirectURI)
|
name string
|
||||||
require.NoError(t, err)
|
clientID string
|
||||||
authRequestID := createAuthRequest(t, Instance, clientID, redirectURI, oidc.ScopeOpenID, oidc.ScopeOfflineAccess)
|
authRequestID func(t testing.TB, instance *integration.Instance, clientID, redirectURI string, scope ...string) string
|
||||||
sessionID, sessionToken, startTime, changeTime := Instance.CreateVerifiedWebAuthNSession(t, CTXLOGIN, User.GetUserId())
|
}{
|
||||||
linkResp, err := Instance.Client.OIDCv2.CreateCallback(CTXLOGIN, &oidc_pb.CreateCallbackRequest{
|
{
|
||||||
AuthRequestId: authRequestID,
|
name: "login header",
|
||||||
CallbackKind: &oidc_pb.CreateCallbackRequest_Session{
|
clientID: func() string {
|
||||||
Session: &oidc_pb.Session{
|
clientID, _ := createClient(t, Instance)
|
||||||
SessionId: sessionID,
|
return clientID
|
||||||
SessionToken: sessionToken,
|
}(),
|
||||||
},
|
authRequestID: createAuthRequest,
|
||||||
},
|
},
|
||||||
})
|
{
|
||||||
require.NoError(t, err)
|
name: "login v2 config",
|
||||||
|
clientID: func() string {
|
||||||
|
clientID, _ := createClientLoginV2(t, Instance)
|
||||||
|
return clientID
|
||||||
|
}(),
|
||||||
|
authRequestID: createAuthRequestNoLoginClientHeader,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
provider, err := Instance.CreateRelyingParty(CTX, tt.clientID, redirectURI)
|
||||||
|
require.NoError(t, err)
|
||||||
|
authRequestID := tt.authRequestID(t, Instance, tt.clientID, redirectURI, oidc.ScopeOpenID, oidc.ScopeOfflineAccess)
|
||||||
|
sessionID, sessionToken, startTime, changeTime := Instance.CreateVerifiedWebAuthNSession(t, CTXLOGIN, User.GetUserId())
|
||||||
|
linkResp, err := Instance.Client.OIDCv2.CreateCallback(CTXLOGIN, &oidc_pb.CreateCallbackRequest{
|
||||||
|
AuthRequestId: authRequestID,
|
||||||
|
CallbackKind: &oidc_pb.CreateCallbackRequest_Session{
|
||||||
|
Session: &oidc_pb.Session{
|
||||||
|
SessionId: sessionID,
|
||||||
|
SessionToken: sessionToken,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
// code exchange
|
// code exchange
|
||||||
code := assertCodeResponse(t, linkResp.GetCallbackUrl())
|
code := assertCodeResponse(t, linkResp.GetCallbackUrl())
|
||||||
tokens, err := exchangeTokens(t, Instance, clientID, code, redirectURI)
|
tokens, err := exchangeTokens(t, Instance, tt.clientID, code, redirectURI)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assertTokens(t, tokens, true)
|
assertTokens(t, tokens, true)
|
||||||
assertIDTokenClaims(t, tokens.IDTokenClaims, User.GetUserId(), armPasskey, startTime, changeTime, sessionID)
|
assertIDTokenClaims(t, tokens.IDTokenClaims, User.GetUserId(), armPasskey, startTime, changeTime, sessionID)
|
||||||
|
|
||||||
// test actual refresh grant
|
// test actual refresh grant
|
||||||
newTokens, err := refreshTokens(t, clientID, tokens.RefreshToken)
|
newTokens, err := refreshTokens(t, tt.clientID, tokens.RefreshToken)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assertTokens(t, newTokens, true)
|
assertTokens(t, newTokens, true)
|
||||||
// auth time must still be the initial
|
// auth time must still be the initial
|
||||||
assertIDTokenClaims(t, newTokens.IDTokenClaims, User.GetUserId(), armPasskey, startTime, changeTime, sessionID)
|
assertIDTokenClaims(t, newTokens.IDTokenClaims, User.GetUserId(), armPasskey, startTime, changeTime, sessionID)
|
||||||
|
|
||||||
// refresh with an old refresh_token must fail
|
// refresh with an old refresh_token must fail
|
||||||
_, err = rp.RefreshTokens[*oidc.IDTokenClaims](CTX, provider, tokens.RefreshToken, "", "")
|
_, err = rp.RefreshTokens[*oidc.IDTokenClaims](CTX, provider, tokens.RefreshToken, "", "")
|
||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestOPStorage_RevokeToken_access_token(t *testing.T) {
|
func TestOPStorage_RevokeToken_access_token(t *testing.T) {
|
||||||
@ -454,47 +552,75 @@ func TestOPStorage_TerminateSession_refresh_grant(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestOPStorage_TerminateSession_empty_id_token_hint(t *testing.T) {
|
func TestOPStorage_TerminateSession_empty_id_token_hint(t *testing.T) {
|
||||||
clientID, _ := createClient(t, Instance)
|
tests := []struct {
|
||||||
provider, err := Instance.CreateRelyingParty(CTX, clientID, redirectURI)
|
name string
|
||||||
require.NoError(t, err)
|
clientID string
|
||||||
authRequestID := createAuthRequest(t, Instance, clientID, redirectURI)
|
authRequestID func(t testing.TB, instance *integration.Instance, clientID, redirectURI string, scope ...string) string
|
||||||
sessionID, sessionToken, startTime, changeTime := Instance.CreateVerifiedWebAuthNSession(t, CTXLOGIN, User.GetUserId())
|
logoutURL string
|
||||||
linkResp, err := Instance.Client.OIDCv2.CreateCallback(CTXLOGIN, &oidc_pb.CreateCallbackRequest{
|
}{
|
||||||
AuthRequestId: authRequestID,
|
{
|
||||||
CallbackKind: &oidc_pb.CreateCallbackRequest_Session{
|
name: "login header",
|
||||||
Session: &oidc_pb.Session{
|
clientID: func() string {
|
||||||
SessionId: sessionID,
|
clientID, _ := createClient(t, Instance)
|
||||||
SessionToken: sessionToken,
|
return clientID
|
||||||
},
|
}(),
|
||||||
|
authRequestID: createAuthRequest,
|
||||||
|
logoutURL: http_utils.BuildOrigin(Instance.Host(), Instance.Config.Secure) + Instance.Config.LogoutURLV2 + logoutRedirectURI + "?state=state",
|
||||||
},
|
},
|
||||||
})
|
{
|
||||||
require.NoError(t, err)
|
name: "login v2 config",
|
||||||
|
clientID: func() string {
|
||||||
|
clientID, _ := createClientLoginV2(t, Instance)
|
||||||
|
return clientID
|
||||||
|
}(),
|
||||||
|
authRequestID: createAuthRequestNoLoginClientHeader,
|
||||||
|
logoutURL: http_utils.BuildOrigin(Instance.Host(), Instance.Config.Secure) + Instance.Config.LogoutURLV2 + logoutRedirectURI + "?state=state",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
provider, err := Instance.CreateRelyingParty(CTX, tt.clientID, redirectURI)
|
||||||
|
require.NoError(t, err)
|
||||||
|
authRequestID := tt.authRequestID(t, Instance, tt.clientID, redirectURI)
|
||||||
|
sessionID, sessionToken, startTime, changeTime := Instance.CreateVerifiedWebAuthNSession(t, CTXLOGIN, User.GetUserId())
|
||||||
|
linkResp, err := Instance.Client.OIDCv2.CreateCallback(CTXLOGIN, &oidc_pb.CreateCallbackRequest{
|
||||||
|
AuthRequestId: authRequestID,
|
||||||
|
CallbackKind: &oidc_pb.CreateCallbackRequest_Session{
|
||||||
|
Session: &oidc_pb.Session{
|
||||||
|
SessionId: sessionID,
|
||||||
|
SessionToken: sessionToken,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
// test code exchange
|
// test code exchange
|
||||||
code := assertCodeResponse(t, linkResp.GetCallbackUrl())
|
code := assertCodeResponse(t, linkResp.GetCallbackUrl())
|
||||||
tokens, err := exchangeTokens(t, Instance, clientID, code, redirectURI)
|
tokens, err := exchangeTokens(t, Instance, tt.clientID, code, redirectURI)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assertTokens(t, tokens, false)
|
assertTokens(t, tokens, false)
|
||||||
assertIDTokenClaims(t, tokens.IDTokenClaims, User.GetUserId(), armPasskey, startTime, changeTime, sessionID)
|
assertIDTokenClaims(t, tokens.IDTokenClaims, User.GetUserId(), armPasskey, startTime, changeTime, sessionID)
|
||||||
|
|
||||||
postLogoutRedirect, err := rp.EndSession(CTX, provider, "", logoutRedirectURI, "state")
|
postLogoutRedirect, err := rp.EndSession(CTX, provider, "", logoutRedirectURI, "state")
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.Equal(t, http_utils.BuildOrigin(Instance.Host(), Instance.Config.Secure)+Instance.Config.LogoutURLV2+logoutRedirectURI+"?state=state", postLogoutRedirect.String())
|
assert.Equal(t, tt.logoutURL, postLogoutRedirect.String())
|
||||||
|
|
||||||
// userinfo must not fail until login UI terminated session
|
// userinfo must not fail until login UI terminated session
|
||||||
_, err = rp.Userinfo[*oidc.UserInfo](CTX, tokens.AccessToken, tokens.TokenType, tokens.IDTokenClaims.Subject, provider)
|
_, err = rp.Userinfo[*oidc.UserInfo](CTX, tokens.AccessToken, tokens.TokenType, tokens.IDTokenClaims.Subject, provider)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
// simulate termination by login UI
|
// simulate termination by login UI
|
||||||
_, err = Instance.Client.SessionV2.DeleteSession(CTXLOGIN, &session.DeleteSessionRequest{
|
_, err = Instance.Client.SessionV2.DeleteSession(CTXLOGIN, &session.DeleteSessionRequest{
|
||||||
SessionId: sessionID,
|
SessionId: sessionID,
|
||||||
SessionToken: gu.Ptr(sessionToken),
|
SessionToken: gu.Ptr(sessionToken),
|
||||||
})
|
})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
// userinfo must fail
|
// userinfo must fail
|
||||||
_, err = rp.Userinfo[*oidc.UserInfo](CTX, tokens.AccessToken, tokens.TokenType, tokens.IDTokenClaims.Subject, provider)
|
_, err = rp.Userinfo[*oidc.UserInfo](CTX, tokens.AccessToken, tokens.TokenType, tokens.IDTokenClaims.Subject, provider)
|
||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func exchangeTokens(t testing.TB, instance *integration.Instance, clientID, code, redirectURI string) (*oidc.Tokens[*oidc.IDTokenClaims], error) {
|
func exchangeTokens(t testing.TB, instance *integration.Instance, clientID, code, redirectURI string) (*oidc.Tokens[*oidc.IDTokenClaims], error) {
|
||||||
|
@ -18,6 +18,7 @@ import (
|
|||||||
|
|
||||||
"github.com/zitadel/zitadel/internal/domain"
|
"github.com/zitadel/zitadel/internal/domain"
|
||||||
"github.com/zitadel/zitadel/internal/integration"
|
"github.com/zitadel/zitadel/internal/integration"
|
||||||
|
"github.com/zitadel/zitadel/pkg/grpc/app"
|
||||||
"github.com/zitadel/zitadel/pkg/grpc/auth"
|
"github.com/zitadel/zitadel/pkg/grpc/auth"
|
||||||
mgmt "github.com/zitadel/zitadel/pkg/grpc/management"
|
mgmt "github.com/zitadel/zitadel/pkg/grpc/management"
|
||||||
oidc_pb "github.com/zitadel/zitadel/pkg/grpc/oidc/v2"
|
oidc_pb "github.com/zitadel/zitadel/pkg/grpc/oidc/v2"
|
||||||
@ -394,16 +395,27 @@ func Test_ZITADEL_API_terminated_session_user_disabled(t *testing.T) {
|
|||||||
|
|
||||||
func createClient(t testing.TB, instance *integration.Instance) (clientID, projectID string) {
|
func createClient(t testing.TB, instance *integration.Instance) (clientID, projectID string) {
|
||||||
return createClientWithOpts(t, instance, clientOpts{
|
return createClientWithOpts(t, instance, clientOpts{
|
||||||
redirectURI: redirectURI,
|
redirectURI: redirectURI,
|
||||||
logoutURI: logoutRedirectURI,
|
logoutURI: logoutRedirectURI,
|
||||||
devMode: false,
|
devMode: false,
|
||||||
|
LoginVersion: nil,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func createClientLoginV2(t testing.TB, instance *integration.Instance) (clientID, projectID string) {
|
||||||
|
return createClientWithOpts(t, instance, clientOpts{
|
||||||
|
redirectURI: redirectURI,
|
||||||
|
logoutURI: logoutRedirectURI,
|
||||||
|
devMode: false,
|
||||||
|
LoginVersion: &app.LoginVersion{Version: &app.LoginVersion_LoginV2{LoginV2: &app.LoginV2{BaseUri: nil}}},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
type clientOpts struct {
|
type clientOpts struct {
|
||||||
redirectURI string
|
redirectURI string
|
||||||
logoutURI string
|
logoutURI string
|
||||||
devMode bool
|
devMode bool
|
||||||
|
LoginVersion *app.LoginVersion
|
||||||
}
|
}
|
||||||
|
|
||||||
func createClientWithOpts(t testing.TB, instance *integration.Instance, opts clientOpts) (clientID, projectID string) {
|
func createClientWithOpts(t testing.TB, instance *integration.Instance, opts clientOpts) (clientID, projectID string) {
|
||||||
@ -411,13 +423,19 @@ func createClientWithOpts(t testing.TB, instance *integration.Instance, opts cli
|
|||||||
|
|
||||||
project, err := instance.CreateProject(ctx)
|
project, err := instance.CreateProject(ctx)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
app, err := instance.CreateOIDCNativeClient(ctx, opts.redirectURI, opts.logoutURI, project.GetId(), opts.devMode)
|
app, err := instance.CreateOIDCClientLoginVersion(ctx, opts.redirectURI, opts.logoutURI, project.GetId(), app.OIDCAppType_OIDC_APP_TYPE_NATIVE, app.OIDCAuthMethodType_OIDC_AUTH_METHOD_TYPE_NONE, opts.devMode, opts.LoginVersion)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
return app.GetClientId(), project.GetId()
|
return app.GetClientId(), project.GetId()
|
||||||
}
|
}
|
||||||
|
|
||||||
func createImplicitClient(t testing.TB) string {
|
func createImplicitClient(t testing.TB) string {
|
||||||
app, err := Instance.CreateOIDCImplicitFlowClient(CTX, redirectURIImplicit)
|
app, err := Instance.CreateOIDCImplicitFlowClient(CTX, redirectURIImplicit, nil)
|
||||||
|
require.NoError(t, err)
|
||||||
|
return app.GetClientId()
|
||||||
|
}
|
||||||
|
|
||||||
|
func createImplicitClientNoLoginClientHeader(t testing.TB) string {
|
||||||
|
app, err := Instance.CreateOIDCImplicitFlowClient(CTX, redirectURIImplicit, &app.LoginVersion{Version: &app.LoginVersion_LoginV2{LoginV2: &app.LoginV2{BaseUri: nil}}})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
return app.GetClientId()
|
return app.GetClientId()
|
||||||
}
|
}
|
||||||
@ -428,12 +446,24 @@ func createAuthRequest(t testing.TB, instance *integration.Instance, clientID, r
|
|||||||
return redURL
|
return redURL
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func createAuthRequestNoLoginClientHeader(t testing.TB, instance *integration.Instance, clientID, redirectURI string, scope ...string) string {
|
||||||
|
redURL, err := instance.CreateOIDCAuthRequestWithoutLoginClientHeader(CTX, clientID, redirectURI, "", scope...)
|
||||||
|
require.NoError(t, err)
|
||||||
|
return redURL
|
||||||
|
}
|
||||||
|
|
||||||
func createAuthRequestImplicit(t testing.TB, clientID, redirectURI string, scope ...string) string {
|
func createAuthRequestImplicit(t testing.TB, clientID, redirectURI string, scope ...string) string {
|
||||||
redURL, err := Instance.CreateOIDCAuthRequestImplicit(CTX, clientID, Instance.Users.Get(integration.UserTypeLogin).ID, redirectURI, scope...)
|
redURL, err := Instance.CreateOIDCAuthRequestImplicit(CTX, clientID, Instance.Users.Get(integration.UserTypeLogin).ID, redirectURI, scope...)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
return redURL
|
return redURL
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func createAuthRequestImplicitNoLoginClientHeader(t testing.TB, clientID, redirectURI string, scope ...string) string {
|
||||||
|
redURL, err := Instance.CreateOIDCAuthRequestImplicitWithoutLoginClientHeader(CTX, clientID, redirectURI, scope...)
|
||||||
|
require.NoError(t, err)
|
||||||
|
return redURL
|
||||||
|
}
|
||||||
|
|
||||||
func assertOIDCTime(t *testing.T, actual oidc.Time, expected time.Time) {
|
func assertOIDCTime(t *testing.T, actual oidc.Time, expected time.Time) {
|
||||||
assertOIDCTimeRange(t, actual, expected, expected)
|
assertOIDCTimeRange(t, actual, expected, expected)
|
||||||
}
|
}
|
||||||
|
@ -92,7 +92,9 @@ func (c *Commands) LinkSessionToAuthRequest(ctx context.Context, id, sessionID,
|
|||||||
return nil, nil, zerrors.ThrowPreconditionFailed(nil, "COMMAND-Sx208nt", "Errors.AuthRequest.AlreadyHandled")
|
return nil, nil, zerrors.ThrowPreconditionFailed(nil, "COMMAND-Sx208nt", "Errors.AuthRequest.AlreadyHandled")
|
||||||
}
|
}
|
||||||
if checkLoginClient && authz.GetCtxData(ctx).UserID != writeModel.LoginClient {
|
if checkLoginClient && authz.GetCtxData(ctx).UserID != writeModel.LoginClient {
|
||||||
return nil, nil, zerrors.ThrowPermissionDenied(nil, "COMMAND-rai9Y", "Errors.AuthRequest.WrongLoginClient")
|
if err := c.checkPermission(ctx, domain.PermissionSessionLink, writeModel.ResourceOwner, ""); err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
sessionWriteModel := NewSessionWriteModel(sessionID, authz.GetInstance(ctx).InstanceID())
|
sessionWriteModel := NewSessionWriteModel(sessionID, authz.GetInstance(ctx).InstanceID())
|
||||||
err = c.eventstore.FilterToQueryReducer(ctx, sessionWriteModel)
|
err = c.eventstore.FilterToQueryReducer(ctx, sessionWriteModel)
|
||||||
|
@ -25,7 +25,7 @@ import (
|
|||||||
func TestCommands_AddAuthRequest(t *testing.T) {
|
func TestCommands_AddAuthRequest(t *testing.T) {
|
||||||
mockCtx := authz.NewMockContext("instanceID", "orgID", "loginClient")
|
mockCtx := authz.NewMockContext("instanceID", "orgID", "loginClient")
|
||||||
type fields struct {
|
type fields struct {
|
||||||
eventstore *eventstore.Eventstore
|
eventstore func(*testing.T) *eventstore.Eventstore
|
||||||
idGenerator id.Generator
|
idGenerator id.Generator
|
||||||
}
|
}
|
||||||
type args struct {
|
type args struct {
|
||||||
@ -42,7 +42,7 @@ func TestCommands_AddAuthRequest(t *testing.T) {
|
|||||||
{
|
{
|
||||||
"already exists error",
|
"already exists error",
|
||||||
fields{
|
fields{
|
||||||
eventstore: eventstoreExpect(t,
|
eventstore: expectEventstore(
|
||||||
expectFilter(
|
expectFilter(
|
||||||
eventFromEventPusher(
|
eventFromEventPusher(
|
||||||
authrequest.NewAddedEvent(mockCtx, &authrequest.NewAggregate("V2_id", "instanceID").Aggregate,
|
authrequest.NewAddedEvent(mockCtx, &authrequest.NewAggregate("V2_id", "instanceID").Aggregate,
|
||||||
@ -78,7 +78,7 @@ func TestCommands_AddAuthRequest(t *testing.T) {
|
|||||||
{
|
{
|
||||||
"added",
|
"added",
|
||||||
fields{
|
fields{
|
||||||
eventstore: eventstoreExpect(t,
|
eventstore: expectEventstore(
|
||||||
expectFilter(),
|
expectFilter(),
|
||||||
expectPush(
|
expectPush(
|
||||||
authrequest.NewAddedEvent(mockCtx, &authrequest.NewAggregate("V2_id", "instanceID").Aggregate,
|
authrequest.NewAddedEvent(mockCtx, &authrequest.NewAggregate("V2_id", "instanceID").Aggregate,
|
||||||
@ -158,7 +158,7 @@ func TestCommands_AddAuthRequest(t *testing.T) {
|
|||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
c := &Commands{
|
c := &Commands{
|
||||||
eventstore: tt.fields.eventstore,
|
eventstore: tt.fields.eventstore(t),
|
||||||
idGenerator: tt.fields.idGenerator,
|
idGenerator: tt.fields.idGenerator,
|
||||||
}
|
}
|
||||||
got, err := c.AddAuthRequest(tt.args.ctx, tt.args.request)
|
got, err := c.AddAuthRequest(tt.args.ctx, tt.args.request)
|
||||||
@ -171,8 +171,9 @@ func TestCommands_AddAuthRequest(t *testing.T) {
|
|||||||
func TestCommands_LinkSessionToAuthRequest(t *testing.T) {
|
func TestCommands_LinkSessionToAuthRequest(t *testing.T) {
|
||||||
mockCtx := authz.NewMockContext("instanceID", "orgID", "loginClient")
|
mockCtx := authz.NewMockContext("instanceID", "orgID", "loginClient")
|
||||||
type fields struct {
|
type fields struct {
|
||||||
eventstore *eventstore.Eventstore
|
eventstore func(*testing.T) *eventstore.Eventstore
|
||||||
tokenVerifier func(ctx context.Context, sessionToken, sessionID, tokenID string) (err error)
|
tokenVerifier func(ctx context.Context, sessionToken, sessionID, tokenID string) (err error)
|
||||||
|
checkPermission domain.PermissionCheck
|
||||||
}
|
}
|
||||||
type args struct {
|
type args struct {
|
||||||
ctx context.Context
|
ctx context.Context
|
||||||
@ -195,7 +196,7 @@ func TestCommands_LinkSessionToAuthRequest(t *testing.T) {
|
|||||||
{
|
{
|
||||||
"authRequest not found",
|
"authRequest not found",
|
||||||
fields{
|
fields{
|
||||||
eventstore: eventstoreExpect(t,
|
eventstore: expectEventstore(
|
||||||
expectFilter(),
|
expectFilter(),
|
||||||
),
|
),
|
||||||
tokenVerifier: newMockTokenVerifierValid(),
|
tokenVerifier: newMockTokenVerifierValid(),
|
||||||
@ -212,7 +213,7 @@ func TestCommands_LinkSessionToAuthRequest(t *testing.T) {
|
|||||||
{
|
{
|
||||||
"authRequest not existing",
|
"authRequest not existing",
|
||||||
fields{
|
fields{
|
||||||
eventstore: eventstoreExpect(t,
|
eventstore: expectEventstore(
|
||||||
expectFilter(
|
expectFilter(
|
||||||
eventFromEventPusher(
|
eventFromEventPusher(
|
||||||
authrequest.NewAddedEvent(mockCtx, &authrequest.NewAggregate("id", "instanceID").Aggregate,
|
authrequest.NewAddedEvent(mockCtx, &authrequest.NewAggregate("id", "instanceID").Aggregate,
|
||||||
@ -252,9 +253,9 @@ func TestCommands_LinkSessionToAuthRequest(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"wrong login client",
|
"wrong login client / not permitted",
|
||||||
fields{
|
fields{
|
||||||
eventstore: eventstoreExpect(t,
|
eventstore: expectEventstore(
|
||||||
expectFilter(
|
expectFilter(
|
||||||
eventFromEventPusher(
|
eventFromEventPusher(
|
||||||
authrequest.NewAddedEvent(mockCtx, &authrequest.NewAggregate("id", "instanceID").Aggregate,
|
authrequest.NewAddedEvent(mockCtx, &authrequest.NewAggregate("id", "instanceID").Aggregate,
|
||||||
@ -278,7 +279,8 @@ func TestCommands_LinkSessionToAuthRequest(t *testing.T) {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
tokenVerifier: newMockTokenVerifierValid(),
|
tokenVerifier: newMockTokenVerifierValid(),
|
||||||
|
checkPermission: newMockPermissionCheckNotAllowed(),
|
||||||
},
|
},
|
||||||
args{
|
args{
|
||||||
ctx: authz.NewMockContext("instanceID", "orgID", "wrongLoginClient"),
|
ctx: authz.NewMockContext("instanceID", "orgID", "wrongLoginClient"),
|
||||||
@ -288,13 +290,13 @@ func TestCommands_LinkSessionToAuthRequest(t *testing.T) {
|
|||||||
checkLoginClient: true,
|
checkLoginClient: true,
|
||||||
},
|
},
|
||||||
res{
|
res{
|
||||||
wantErr: zerrors.ThrowPermissionDenied(nil, "COMMAND-rai9Y", "Errors.AuthRequest.WrongLoginClient"),
|
wantErr: zerrors.ThrowPermissionDenied(nil, "AUTHZ-HKJD33", "Errors.PermissionDenied"),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"session not existing",
|
"session not existing",
|
||||||
fields{
|
fields{
|
||||||
eventstore: eventstoreExpect(t,
|
eventstore: expectEventstore(
|
||||||
expectFilter(
|
expectFilter(
|
||||||
eventFromEventPusher(
|
eventFromEventPusher(
|
||||||
authrequest.NewAddedEvent(mockCtx, &authrequest.NewAggregate("V2_id", "instanceID").Aggregate,
|
authrequest.NewAddedEvent(mockCtx, &authrequest.NewAggregate("V2_id", "instanceID").Aggregate,
|
||||||
@ -333,7 +335,7 @@ func TestCommands_LinkSessionToAuthRequest(t *testing.T) {
|
|||||||
{
|
{
|
||||||
"session expired",
|
"session expired",
|
||||||
fields{
|
fields{
|
||||||
eventstore: eventstoreExpect(t,
|
eventstore: expectEventstore(
|
||||||
expectFilter(
|
expectFilter(
|
||||||
eventFromEventPusher(
|
eventFromEventPusher(
|
||||||
authrequest.NewAddedEvent(mockCtx, &authrequest.NewAggregate("V2_id", "instanceID").Aggregate,
|
authrequest.NewAddedEvent(mockCtx, &authrequest.NewAggregate("V2_id", "instanceID").Aggregate,
|
||||||
@ -395,7 +397,7 @@ func TestCommands_LinkSessionToAuthRequest(t *testing.T) {
|
|||||||
{
|
{
|
||||||
"invalid session token",
|
"invalid session token",
|
||||||
fields{
|
fields{
|
||||||
eventstore: eventstoreExpect(t,
|
eventstore: expectEventstore(
|
||||||
expectFilter(
|
expectFilter(
|
||||||
eventFromEventPusher(
|
eventFromEventPusher(
|
||||||
authrequest.NewAddedEvent(mockCtx, &authrequest.NewAggregate("V2_id", "instanceID").Aggregate,
|
authrequest.NewAddedEvent(mockCtx, &authrequest.NewAggregate("V2_id", "instanceID").Aggregate,
|
||||||
@ -446,7 +448,7 @@ func TestCommands_LinkSessionToAuthRequest(t *testing.T) {
|
|||||||
{
|
{
|
||||||
"linked",
|
"linked",
|
||||||
fields{
|
fields{
|
||||||
eventstore: eventstoreExpect(t,
|
eventstore: expectEventstore(
|
||||||
expectFilter(
|
expectFilter(
|
||||||
eventFromEventPusher(
|
eventFromEventPusher(
|
||||||
authrequest.NewAddedEvent(mockCtx, &authrequest.NewAggregate("V2_id", "instanceID").Aggregate,
|
authrequest.NewAddedEvent(mockCtx, &authrequest.NewAggregate("V2_id", "instanceID").Aggregate,
|
||||||
@ -534,7 +536,7 @@ func TestCommands_LinkSessionToAuthRequest(t *testing.T) {
|
|||||||
{
|
{
|
||||||
"linked with login client check",
|
"linked with login client check",
|
||||||
fields{
|
fields{
|
||||||
eventstore: eventstoreExpect(t,
|
eventstore: expectEventstore(
|
||||||
expectFilter(
|
expectFilter(
|
||||||
eventFromEventPusher(
|
eventFromEventPusher(
|
||||||
authrequest.NewAddedEvent(mockCtx, &authrequest.NewAggregate("V2_id", "instanceID").Aggregate,
|
authrequest.NewAddedEvent(mockCtx, &authrequest.NewAggregate("V2_id", "instanceID").Aggregate,
|
||||||
@ -620,12 +622,103 @@ func TestCommands_LinkSessionToAuthRequest(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"linked with permission",
|
||||||
|
fields{
|
||||||
|
eventstore: expectEventstore(
|
||||||
|
expectFilter(
|
||||||
|
eventFromEventPusher(
|
||||||
|
authrequest.NewAddedEvent(mockCtx, &authrequest.NewAggregate("V2_id", "instanceID").Aggregate,
|
||||||
|
"otherLoginClient",
|
||||||
|
"clientID",
|
||||||
|
"redirectURI",
|
||||||
|
"state",
|
||||||
|
"nonce",
|
||||||
|
[]string{"openid"},
|
||||||
|
[]string{"audience"},
|
||||||
|
domain.OIDCResponseTypeCode,
|
||||||
|
domain.OIDCResponseModeQuery,
|
||||||
|
nil,
|
||||||
|
nil,
|
||||||
|
nil,
|
||||||
|
nil,
|
||||||
|
nil,
|
||||||
|
nil,
|
||||||
|
true,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
expectFilter(
|
||||||
|
eventFromEventPusher(
|
||||||
|
session.NewAddedEvent(mockCtx,
|
||||||
|
&session.NewAggregate("sessionID", "instance1").Aggregate,
|
||||||
|
&domain.UserAgent{
|
||||||
|
FingerprintID: gu.Ptr("fp1"),
|
||||||
|
IP: net.ParseIP("1.2.3.4"),
|
||||||
|
Description: gu.Ptr("firefox"),
|
||||||
|
Header: http.Header{"foo": []string{"bar"}},
|
||||||
|
},
|
||||||
|
)),
|
||||||
|
eventFromEventPusher(
|
||||||
|
session.NewUserCheckedEvent(mockCtx, &session.NewAggregate("sessionID", "instance1").Aggregate,
|
||||||
|
"userID", "org1", testNow, &language.Afrikaans),
|
||||||
|
),
|
||||||
|
eventFromEventPusher(
|
||||||
|
session.NewPasswordCheckedEvent(mockCtx, &session.NewAggregate("sessionID", "instance1").Aggregate,
|
||||||
|
testNow),
|
||||||
|
),
|
||||||
|
eventFromEventPusherWithCreationDateNow(
|
||||||
|
session.NewLifetimeSetEvent(mockCtx, &session.NewAggregate("sessionID", "instance1").Aggregate,
|
||||||
|
2*time.Minute),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
expectPush(
|
||||||
|
authrequest.NewSessionLinkedEvent(mockCtx, &authrequest.NewAggregate("V2_id", "instanceID").Aggregate,
|
||||||
|
"sessionID",
|
||||||
|
"userID",
|
||||||
|
testNow,
|
||||||
|
[]domain.UserAuthMethodType{domain.UserAuthMethodTypePassword},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
tokenVerifier: newMockTokenVerifierValid(),
|
||||||
|
checkPermission: newMockPermissionCheckAllowed(),
|
||||||
|
},
|
||||||
|
args{
|
||||||
|
ctx: authz.NewMockContext("instanceID", "orgID", "loginClient"),
|
||||||
|
id: "V2_id",
|
||||||
|
sessionID: "sessionID",
|
||||||
|
sessionToken: "token",
|
||||||
|
checkLoginClient: true,
|
||||||
|
},
|
||||||
|
res{
|
||||||
|
details: &domain.ObjectDetails{ResourceOwner: "instanceID"},
|
||||||
|
authReq: &CurrentAuthRequest{
|
||||||
|
AuthRequest: &AuthRequest{
|
||||||
|
ID: "V2_id",
|
||||||
|
LoginClient: "otherLoginClient",
|
||||||
|
ClientID: "clientID",
|
||||||
|
RedirectURI: "redirectURI",
|
||||||
|
State: "state",
|
||||||
|
Nonce: "nonce",
|
||||||
|
Scope: []string{"openid"},
|
||||||
|
Audience: []string{"audience"},
|
||||||
|
ResponseType: domain.OIDCResponseTypeCode,
|
||||||
|
ResponseMode: domain.OIDCResponseModeQuery,
|
||||||
|
},
|
||||||
|
SessionID: "sessionID",
|
||||||
|
UserID: "userID",
|
||||||
|
AuthMethods: []domain.UserAuthMethodType{domain.UserAuthMethodTypePassword},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
c := &Commands{
|
c := &Commands{
|
||||||
eventstore: tt.fields.eventstore,
|
eventstore: tt.fields.eventstore(t),
|
||||||
sessionTokenVerifier: tt.fields.tokenVerifier,
|
sessionTokenVerifier: tt.fields.tokenVerifier,
|
||||||
|
checkPermission: tt.fields.checkPermission,
|
||||||
}
|
}
|
||||||
details, got, err := c.LinkSessionToAuthRequest(tt.args.ctx, tt.args.id, tt.args.sessionID, tt.args.sessionToken, tt.args.checkLoginClient)
|
details, got, err := c.LinkSessionToAuthRequest(tt.args.ctx, tt.args.id, tt.args.sessionID, tt.args.sessionToken, tt.args.checkLoginClient)
|
||||||
require.ErrorIs(t, err, tt.res.wantErr)
|
require.ErrorIs(t, err, tt.res.wantErr)
|
||||||
@ -642,7 +735,7 @@ func TestCommands_LinkSessionToAuthRequest(t *testing.T) {
|
|||||||
func TestCommands_FailAuthRequest(t *testing.T) {
|
func TestCommands_FailAuthRequest(t *testing.T) {
|
||||||
mockCtx := authz.NewMockContext("instanceID", "orgID", "loginClient")
|
mockCtx := authz.NewMockContext("instanceID", "orgID", "loginClient")
|
||||||
type fields struct {
|
type fields struct {
|
||||||
eventstore *eventstore.Eventstore
|
eventstore func(*testing.T) *eventstore.Eventstore
|
||||||
}
|
}
|
||||||
type args struct {
|
type args struct {
|
||||||
ctx context.Context
|
ctx context.Context
|
||||||
@ -663,7 +756,7 @@ func TestCommands_FailAuthRequest(t *testing.T) {
|
|||||||
{
|
{
|
||||||
"authRequest not existing",
|
"authRequest not existing",
|
||||||
fields{
|
fields{
|
||||||
eventstore: eventstoreExpect(t,
|
eventstore: expectEventstore(
|
||||||
expectFilter(),
|
expectFilter(),
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
@ -679,7 +772,7 @@ func TestCommands_FailAuthRequest(t *testing.T) {
|
|||||||
{
|
{
|
||||||
"failed",
|
"failed",
|
||||||
fields{
|
fields{
|
||||||
eventstore: eventstoreExpect(t,
|
eventstore: expectEventstore(
|
||||||
expectFilter(
|
expectFilter(
|
||||||
eventFromEventPusher(
|
eventFromEventPusher(
|
||||||
authrequest.NewAddedEvent(mockCtx, &authrequest.NewAggregate("V2_id", "instanceID").Aggregate,
|
authrequest.NewAddedEvent(mockCtx, &authrequest.NewAggregate("V2_id", "instanceID").Aggregate,
|
||||||
@ -735,7 +828,7 @@ func TestCommands_FailAuthRequest(t *testing.T) {
|
|||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
c := &Commands{
|
c := &Commands{
|
||||||
eventstore: tt.fields.eventstore,
|
eventstore: tt.fields.eventstore(t),
|
||||||
}
|
}
|
||||||
details, got, err := c.FailAuthRequest(tt.args.ctx, tt.args.id, tt.args.reason)
|
details, got, err := c.FailAuthRequest(tt.args.ctx, tt.args.id, tt.args.reason)
|
||||||
require.ErrorIs(t, err, tt.res.wantErr)
|
require.ErrorIs(t, err, tt.res.wantErr)
|
||||||
@ -748,7 +841,7 @@ func TestCommands_FailAuthRequest(t *testing.T) {
|
|||||||
func TestCommands_AddAuthRequestCode(t *testing.T) {
|
func TestCommands_AddAuthRequestCode(t *testing.T) {
|
||||||
mockCtx := authz.NewMockContext("instanceID", "orgID", "loginClient")
|
mockCtx := authz.NewMockContext("instanceID", "orgID", "loginClient")
|
||||||
type fields struct {
|
type fields struct {
|
||||||
eventstore *eventstore.Eventstore
|
eventstore func(*testing.T) *eventstore.Eventstore
|
||||||
}
|
}
|
||||||
type args struct {
|
type args struct {
|
||||||
ctx context.Context
|
ctx context.Context
|
||||||
@ -764,7 +857,7 @@ func TestCommands_AddAuthRequestCode(t *testing.T) {
|
|||||||
{
|
{
|
||||||
"empty code error",
|
"empty code error",
|
||||||
fields{
|
fields{
|
||||||
eventstore: eventstoreExpect(t),
|
eventstore: expectEventstore(),
|
||||||
},
|
},
|
||||||
args{
|
args{
|
||||||
ctx: mockCtx,
|
ctx: mockCtx,
|
||||||
@ -776,7 +869,7 @@ func TestCommands_AddAuthRequestCode(t *testing.T) {
|
|||||||
{
|
{
|
||||||
"no session linked error",
|
"no session linked error",
|
||||||
fields{
|
fields{
|
||||||
eventstore: eventstoreExpect(t,
|
eventstore: expectEventstore(
|
||||||
expectFilter(
|
expectFilter(
|
||||||
eventFromEventPusher(
|
eventFromEventPusher(
|
||||||
authrequest.NewAddedEvent(mockCtx, &authrequest.NewAggregate("V2_authRequestID", "instanceID").Aggregate,
|
authrequest.NewAddedEvent(mockCtx, &authrequest.NewAggregate("V2_authRequestID", "instanceID").Aggregate,
|
||||||
@ -814,7 +907,7 @@ func TestCommands_AddAuthRequestCode(t *testing.T) {
|
|||||||
{
|
{
|
||||||
"success",
|
"success",
|
||||||
fields{
|
fields{
|
||||||
eventstore: eventstoreExpect(t,
|
eventstore: expectEventstore(
|
||||||
expectFilter(
|
expectFilter(
|
||||||
eventFromEventPusher(
|
eventFromEventPusher(
|
||||||
authrequest.NewAddedEvent(mockCtx, &authrequest.NewAggregate("V2_authRequestID", "instanceID").Aggregate,
|
authrequest.NewAddedEvent(mockCtx, &authrequest.NewAggregate("V2_authRequestID", "instanceID").Aggregate,
|
||||||
@ -864,7 +957,7 @@ func TestCommands_AddAuthRequestCode(t *testing.T) {
|
|||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
c := &Commands{
|
c := &Commands{
|
||||||
eventstore: tt.fields.eventstore,
|
eventstore: tt.fields.eventstore(t),
|
||||||
}
|
}
|
||||||
err := c.AddAuthRequestCode(tt.args.ctx, tt.args.id, tt.args.code)
|
err := c.AddAuthRequestCode(tt.args.ctx, tt.args.id, tt.args.code)
|
||||||
assert.ErrorIs(t, tt.wantErr, err)
|
assert.ErrorIs(t, tt.wantErr, err)
|
||||||
|
@ -156,6 +156,8 @@ func TestCommandSide_AddInstanceDomain(t *testing.T) {
|
|||||||
[]string{"https://sub.test.ch"},
|
[]string{"https://sub.test.ch"},
|
||||||
false,
|
false,
|
||||||
"",
|
"",
|
||||||
|
domain.LoginVersionUnspecified,
|
||||||
|
"",
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -28,6 +28,7 @@ type InstanceFeatures struct {
|
|||||||
OIDCSingleV1SessionTermination *bool
|
OIDCSingleV1SessionTermination *bool
|
||||||
DisableUserTokenEvent *bool
|
DisableUserTokenEvent *bool
|
||||||
EnableBackChannelLogout *bool
|
EnableBackChannelLogout *bool
|
||||||
|
LoginV2 *feature.LoginV2
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *InstanceFeatures) isEmpty() bool {
|
func (m *InstanceFeatures) isEmpty() bool {
|
||||||
@ -43,7 +44,8 @@ func (m *InstanceFeatures) isEmpty() bool {
|
|||||||
m.DebugOIDCParentError == nil &&
|
m.DebugOIDCParentError == nil &&
|
||||||
m.OIDCSingleV1SessionTermination == nil &&
|
m.OIDCSingleV1SessionTermination == nil &&
|
||||||
m.DisableUserTokenEvent == nil &&
|
m.DisableUserTokenEvent == nil &&
|
||||||
m.EnableBackChannelLogout == nil
|
m.EnableBackChannelLogout == nil &&
|
||||||
|
m.LoginV2 == nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Commands) SetInstanceFeatures(ctx context.Context, f *InstanceFeatures) (*domain.ObjectDetails, error) {
|
func (c *Commands) SetInstanceFeatures(ctx context.Context, f *InstanceFeatures) (*domain.ObjectDetails, error) {
|
||||||
|
@ -41,6 +41,12 @@ func (m *InstanceFeaturesWriteModel) Reduce() (err error) {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
reduceInstanceFeature(&m.InstanceFeatures, key, e.Value)
|
reduceInstanceFeature(&m.InstanceFeatures, key, e.Value)
|
||||||
|
case *feature_v2.SetEvent[*feature.LoginV2]:
|
||||||
|
_, key, err := e.FeatureInfo()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
reduceInstanceFeature(&m.InstanceFeatures, key, e.Value)
|
||||||
case *feature_v2.SetEvent[[]feature.ImprovedPerformanceType]:
|
case *feature_v2.SetEvent[[]feature.ImprovedPerformanceType]:
|
||||||
_, key, err := e.FeatureInfo()
|
_, key, err := e.FeatureInfo()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -72,6 +78,7 @@ func (m *InstanceFeaturesWriteModel) Query() *eventstore.SearchQueryBuilder {
|
|||||||
feature_v2.InstanceOIDCSingleV1SessionTerminationEventType,
|
feature_v2.InstanceOIDCSingleV1SessionTerminationEventType,
|
||||||
feature_v2.InstanceDisableUserTokenEvent,
|
feature_v2.InstanceDisableUserTokenEvent,
|
||||||
feature_v2.InstanceEnableBackChannelLogout,
|
feature_v2.InstanceEnableBackChannelLogout,
|
||||||
|
feature_v2.InstanceLoginVersion,
|
||||||
).
|
).
|
||||||
Builder().ResourceOwner(m.ResourceOwner)
|
Builder().ResourceOwner(m.ResourceOwner)
|
||||||
}
|
}
|
||||||
@ -120,6 +127,8 @@ func reduceInstanceFeature(features *InstanceFeatures, key feature.Key, value an
|
|||||||
case feature.KeyEnableBackChannelLogout:
|
case feature.KeyEnableBackChannelLogout:
|
||||||
v := value.(bool)
|
v := value.(bool)
|
||||||
features.EnableBackChannelLogout = &v
|
features.EnableBackChannelLogout = &v
|
||||||
|
case feature.KeyLoginV2:
|
||||||
|
features.LoginV2 = value.(*feature.LoginV2)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -138,5 +147,6 @@ func (wm *InstanceFeaturesWriteModel) setCommands(ctx context.Context, f *Instan
|
|||||||
cmds = appendFeatureUpdate(ctx, cmds, aggregate, wm.OIDCSingleV1SessionTermination, f.OIDCSingleV1SessionTermination, feature_v2.InstanceOIDCSingleV1SessionTerminationEventType)
|
cmds = appendFeatureUpdate(ctx, cmds, aggregate, wm.OIDCSingleV1SessionTermination, f.OIDCSingleV1SessionTermination, feature_v2.InstanceOIDCSingleV1SessionTerminationEventType)
|
||||||
cmds = appendFeatureUpdate(ctx, cmds, aggregate, wm.DisableUserTokenEvent, f.DisableUserTokenEvent, feature_v2.InstanceDisableUserTokenEvent)
|
cmds = appendFeatureUpdate(ctx, cmds, aggregate, wm.DisableUserTokenEvent, f.DisableUserTokenEvent, feature_v2.InstanceDisableUserTokenEvent)
|
||||||
cmds = appendFeatureUpdate(ctx, cmds, aggregate, wm.EnableBackChannelLogout, f.EnableBackChannelLogout, feature_v2.InstanceEnableBackChannelLogout)
|
cmds = appendFeatureUpdate(ctx, cmds, aggregate, wm.EnableBackChannelLogout, f.EnableBackChannelLogout, feature_v2.InstanceEnableBackChannelLogout)
|
||||||
|
cmds = appendFeatureUpdate(ctx, cmds, aggregate, wm.LoginV2, f.LoginV2, feature_v2.InstanceLoginVersion)
|
||||||
return cmds
|
return cmds
|
||||||
}
|
}
|
||||||
|
@ -128,6 +128,8 @@ func oidcAppEvents(ctx context.Context, orgID, projectID, id, name, clientID str
|
|||||||
nil,
|
nil,
|
||||||
false,
|
false,
|
||||||
"",
|
"",
|
||||||
|
domain.LoginVersionUnspecified,
|
||||||
|
"",
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -441,6 +443,8 @@ func generatedDomainFilters(instanceID, orgID, projectID, appID, generatedDomain
|
|||||||
nil,
|
nil,
|
||||||
false,
|
false,
|
||||||
"",
|
"",
|
||||||
|
domain.LoginVersionUnspecified,
|
||||||
|
"",
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
expectFilter(
|
expectFilter(
|
||||||
|
@ -32,6 +32,8 @@ type addOIDCApp struct {
|
|||||||
AdditionalOrigins []string
|
AdditionalOrigins []string
|
||||||
SkipSuccessPageForNativeApp bool
|
SkipSuccessPageForNativeApp bool
|
||||||
BackChannelLogoutURI string
|
BackChannelLogoutURI string
|
||||||
|
LoginVersion domain.LoginVersion
|
||||||
|
LoginBaseURI string
|
||||||
|
|
||||||
ClientID string
|
ClientID string
|
||||||
ClientSecret string
|
ClientSecret string
|
||||||
@ -110,6 +112,8 @@ func (c *Commands) AddOIDCAppCommand(app *addOIDCApp) preparation.Validation {
|
|||||||
trimStringSliceWhiteSpaces(app.AdditionalOrigins),
|
trimStringSliceWhiteSpaces(app.AdditionalOrigins),
|
||||||
app.SkipSuccessPageForNativeApp,
|
app.SkipSuccessPageForNativeApp,
|
||||||
app.BackChannelLogoutURI,
|
app.BackChannelLogoutURI,
|
||||||
|
app.LoginVersion,
|
||||||
|
app.LoginBaseURI,
|
||||||
),
|
),
|
||||||
}, nil
|
}, nil
|
||||||
}, nil
|
}, nil
|
||||||
@ -202,6 +206,8 @@ func (c *Commands) addOIDCApplicationWithID(ctx context.Context, oidcApp *domain
|
|||||||
trimStringSliceWhiteSpaces(oidcApp.AdditionalOrigins),
|
trimStringSliceWhiteSpaces(oidcApp.AdditionalOrigins),
|
||||||
oidcApp.SkipNativeAppSuccessPage,
|
oidcApp.SkipNativeAppSuccessPage,
|
||||||
strings.TrimSpace(oidcApp.BackChannelLogoutURI),
|
strings.TrimSpace(oidcApp.BackChannelLogoutURI),
|
||||||
|
oidcApp.LoginVersion,
|
||||||
|
strings.TrimSpace(oidcApp.LoginBaseURI),
|
||||||
))
|
))
|
||||||
|
|
||||||
addedApplication.AppID = oidcApp.AppID
|
addedApplication.AppID = oidcApp.AppID
|
||||||
@ -260,6 +266,8 @@ func (c *Commands) ChangeOIDCApplication(ctx context.Context, oidc *domain.OIDCA
|
|||||||
trimStringSliceWhiteSpaces(oidc.AdditionalOrigins),
|
trimStringSliceWhiteSpaces(oidc.AdditionalOrigins),
|
||||||
oidc.SkipNativeAppSuccessPage,
|
oidc.SkipNativeAppSuccessPage,
|
||||||
strings.TrimSpace(oidc.BackChannelLogoutURI),
|
strings.TrimSpace(oidc.BackChannelLogoutURI),
|
||||||
|
oidc.LoginVersion,
|
||||||
|
strings.TrimSpace(oidc.LoginBaseURI),
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -37,6 +37,8 @@ type OIDCApplicationWriteModel struct {
|
|||||||
AdditionalOrigins []string
|
AdditionalOrigins []string
|
||||||
SkipNativeAppSuccessPage bool
|
SkipNativeAppSuccessPage bool
|
||||||
BackChannelLogoutURI string
|
BackChannelLogoutURI string
|
||||||
|
LoginVersion domain.LoginVersion
|
||||||
|
LoginBaseURI string
|
||||||
oidc bool
|
oidc bool
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -167,6 +169,8 @@ func (wm *OIDCApplicationWriteModel) appendAddOIDCEvent(e *project.OIDCConfigAdd
|
|||||||
wm.AdditionalOrigins = e.AdditionalOrigins
|
wm.AdditionalOrigins = e.AdditionalOrigins
|
||||||
wm.SkipNativeAppSuccessPage = e.SkipNativeAppSuccessPage
|
wm.SkipNativeAppSuccessPage = e.SkipNativeAppSuccessPage
|
||||||
wm.BackChannelLogoutURI = e.BackChannelLogoutURI
|
wm.BackChannelLogoutURI = e.BackChannelLogoutURI
|
||||||
|
wm.LoginVersion = e.LoginVersion
|
||||||
|
wm.LoginBaseURI = e.LoginBaseURI
|
||||||
}
|
}
|
||||||
|
|
||||||
func (wm *OIDCApplicationWriteModel) appendChangeOIDCEvent(e *project.OIDCConfigChangedEvent) {
|
func (wm *OIDCApplicationWriteModel) appendChangeOIDCEvent(e *project.OIDCConfigChangedEvent) {
|
||||||
@ -218,6 +222,12 @@ func (wm *OIDCApplicationWriteModel) appendChangeOIDCEvent(e *project.OIDCConfig
|
|||||||
if e.BackChannelLogoutURI != nil {
|
if e.BackChannelLogoutURI != nil {
|
||||||
wm.BackChannelLogoutURI = *e.BackChannelLogoutURI
|
wm.BackChannelLogoutURI = *e.BackChannelLogoutURI
|
||||||
}
|
}
|
||||||
|
if e.LoginVersion != nil {
|
||||||
|
wm.LoginVersion = *e.LoginVersion
|
||||||
|
}
|
||||||
|
if e.LoginBaseURI != nil {
|
||||||
|
wm.LoginBaseURI = *e.LoginBaseURI
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (wm *OIDCApplicationWriteModel) Query() *eventstore.SearchQueryBuilder {
|
func (wm *OIDCApplicationWriteModel) Query() *eventstore.SearchQueryBuilder {
|
||||||
@ -260,6 +270,8 @@ func (wm *OIDCApplicationWriteModel) NewChangedEvent(
|
|||||||
additionalOrigins []string,
|
additionalOrigins []string,
|
||||||
skipNativeAppSuccessPage bool,
|
skipNativeAppSuccessPage bool,
|
||||||
backChannelLogoutURI string,
|
backChannelLogoutURI string,
|
||||||
|
loginVersion domain.LoginVersion,
|
||||||
|
loginBaseURI string,
|
||||||
) (*project.OIDCConfigChangedEvent, bool, error) {
|
) (*project.OIDCConfigChangedEvent, bool, error) {
|
||||||
changes := make([]project.OIDCConfigChanges, 0)
|
changes := make([]project.OIDCConfigChanges, 0)
|
||||||
var err error
|
var err error
|
||||||
@ -312,6 +324,12 @@ func (wm *OIDCApplicationWriteModel) NewChangedEvent(
|
|||||||
if wm.BackChannelLogoutURI != backChannelLogoutURI {
|
if wm.BackChannelLogoutURI != backChannelLogoutURI {
|
||||||
changes = append(changes, project.ChangeBackChannelLogoutURI(backChannelLogoutURI))
|
changes = append(changes, project.ChangeBackChannelLogoutURI(backChannelLogoutURI))
|
||||||
}
|
}
|
||||||
|
if wm.LoginVersion != loginVersion {
|
||||||
|
changes = append(changes, project.ChangeLoginVersion(loginVersion))
|
||||||
|
}
|
||||||
|
if wm.LoginBaseURI != loginBaseURI {
|
||||||
|
changes = append(changes, project.ChangeLoginBaseURI(loginBaseURI))
|
||||||
|
}
|
||||||
|
|
||||||
if len(changes) == 0 {
|
if len(changes) == 0 {
|
||||||
return nil, false, nil
|
return nil, false, nil
|
||||||
|
@ -176,6 +176,8 @@ func TestAddOIDCApp(t *testing.T) {
|
|||||||
[]string{"https://sub.test.ch"},
|
[]string{"https://sub.test.ch"},
|
||||||
false,
|
false,
|
||||||
"",
|
"",
|
||||||
|
domain.LoginVersionUnspecified,
|
||||||
|
"",
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -242,6 +244,8 @@ func TestAddOIDCApp(t *testing.T) {
|
|||||||
nil,
|
nil,
|
||||||
false,
|
false,
|
||||||
"",
|
"",
|
||||||
|
domain.LoginVersionUnspecified,
|
||||||
|
"",
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -308,6 +312,8 @@ func TestAddOIDCApp(t *testing.T) {
|
|||||||
nil,
|
nil,
|
||||||
false,
|
false,
|
||||||
"",
|
"",
|
||||||
|
domain.LoginVersionUnspecified,
|
||||||
|
"",
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -374,6 +380,8 @@ func TestAddOIDCApp(t *testing.T) {
|
|||||||
nil,
|
nil,
|
||||||
false,
|
false,
|
||||||
"",
|
"",
|
||||||
|
domain.LoginVersionUnspecified,
|
||||||
|
"",
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -521,6 +529,8 @@ func TestCommandSide_AddOIDCApplication(t *testing.T) {
|
|||||||
[]string{"https://sub.test.ch"},
|
[]string{"https://sub.test.ch"},
|
||||||
true,
|
true,
|
||||||
"https://test.ch/backchannel",
|
"https://test.ch/backchannel",
|
||||||
|
domain.LoginVersion2,
|
||||||
|
"https://login.test.ch",
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -549,6 +559,8 @@ func TestCommandSide_AddOIDCApplication(t *testing.T) {
|
|||||||
AdditionalOrigins: []string{" https://sub.test.ch "},
|
AdditionalOrigins: []string{" https://sub.test.ch "},
|
||||||
SkipNativeAppSuccessPage: true,
|
SkipNativeAppSuccessPage: true,
|
||||||
BackChannelLogoutURI: " https://test.ch/backchannel ",
|
BackChannelLogoutURI: " https://test.ch/backchannel ",
|
||||||
|
LoginVersion: domain.LoginVersion2,
|
||||||
|
LoginBaseURI: " https://login.test.ch ",
|
||||||
},
|
},
|
||||||
resourceOwner: "org1",
|
resourceOwner: "org1",
|
||||||
},
|
},
|
||||||
@ -578,6 +590,8 @@ func TestCommandSide_AddOIDCApplication(t *testing.T) {
|
|||||||
AdditionalOrigins: []string{"https://sub.test.ch"},
|
AdditionalOrigins: []string{"https://sub.test.ch"},
|
||||||
SkipNativeAppSuccessPage: true,
|
SkipNativeAppSuccessPage: true,
|
||||||
BackChannelLogoutURI: "https://test.ch/backchannel",
|
BackChannelLogoutURI: "https://test.ch/backchannel",
|
||||||
|
LoginVersion: domain.LoginVersion2,
|
||||||
|
LoginBaseURI: "https://login.test.ch",
|
||||||
State: domain.AppStateActive,
|
State: domain.AppStateActive,
|
||||||
Compliance: &domain.Compliance{},
|
Compliance: &domain.Compliance{},
|
||||||
},
|
},
|
||||||
@ -622,6 +636,8 @@ func TestCommandSide_AddOIDCApplication(t *testing.T) {
|
|||||||
[]string{"https://sub.test.ch"},
|
[]string{"https://sub.test.ch"},
|
||||||
true,
|
true,
|
||||||
"https://test.ch/backchannel",
|
"https://test.ch/backchannel",
|
||||||
|
domain.LoginVersion2,
|
||||||
|
"https://login.test.ch",
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -650,6 +666,8 @@ func TestCommandSide_AddOIDCApplication(t *testing.T) {
|
|||||||
AdditionalOrigins: []string{"https://sub.test.ch"},
|
AdditionalOrigins: []string{"https://sub.test.ch"},
|
||||||
SkipNativeAppSuccessPage: true,
|
SkipNativeAppSuccessPage: true,
|
||||||
BackChannelLogoutURI: "https://test.ch/backchannel",
|
BackChannelLogoutURI: "https://test.ch/backchannel",
|
||||||
|
LoginVersion: domain.LoginVersion2,
|
||||||
|
LoginBaseURI: "https://login.test.ch",
|
||||||
},
|
},
|
||||||
resourceOwner: "org1",
|
resourceOwner: "org1",
|
||||||
},
|
},
|
||||||
@ -679,6 +697,8 @@ func TestCommandSide_AddOIDCApplication(t *testing.T) {
|
|||||||
AdditionalOrigins: []string{"https://sub.test.ch"},
|
AdditionalOrigins: []string{"https://sub.test.ch"},
|
||||||
SkipNativeAppSuccessPage: true,
|
SkipNativeAppSuccessPage: true,
|
||||||
BackChannelLogoutURI: "https://test.ch/backchannel",
|
BackChannelLogoutURI: "https://test.ch/backchannel",
|
||||||
|
LoginVersion: domain.LoginVersion2,
|
||||||
|
LoginBaseURI: "https://login.test.ch",
|
||||||
State: domain.AppStateActive,
|
State: domain.AppStateActive,
|
||||||
Compliance: &domain.Compliance{},
|
Compliance: &domain.Compliance{},
|
||||||
},
|
},
|
||||||
@ -712,7 +732,7 @@ func TestCommandSide_AddOIDCApplication(t *testing.T) {
|
|||||||
|
|
||||||
func TestCommandSide_ChangeOIDCApplication(t *testing.T) {
|
func TestCommandSide_ChangeOIDCApplication(t *testing.T) {
|
||||||
type fields struct {
|
type fields struct {
|
||||||
eventstore *eventstore.Eventstore
|
eventstore func(*testing.T) *eventstore.Eventstore
|
||||||
}
|
}
|
||||||
type args struct {
|
type args struct {
|
||||||
ctx context.Context
|
ctx context.Context
|
||||||
@ -732,9 +752,7 @@ func TestCommandSide_ChangeOIDCApplication(t *testing.T) {
|
|||||||
{
|
{
|
||||||
name: "invalid app, invalid argument error",
|
name: "invalid app, invalid argument error",
|
||||||
fields: fields{
|
fields: fields{
|
||||||
eventstore: eventstoreExpect(
|
eventstore: expectEventstore(),
|
||||||
t,
|
|
||||||
),
|
|
||||||
},
|
},
|
||||||
args: args{
|
args: args{
|
||||||
ctx: context.Background(),
|
ctx: context.Background(),
|
||||||
@ -753,9 +771,7 @@ func TestCommandSide_ChangeOIDCApplication(t *testing.T) {
|
|||||||
{
|
{
|
||||||
name: "missing appid, invalid argument error",
|
name: "missing appid, invalid argument error",
|
||||||
fields: fields{
|
fields: fields{
|
||||||
eventstore: eventstoreExpect(
|
eventstore: expectEventstore(),
|
||||||
t,
|
|
||||||
),
|
|
||||||
},
|
},
|
||||||
args: args{
|
args: args{
|
||||||
ctx: context.Background(),
|
ctx: context.Background(),
|
||||||
@ -777,9 +793,7 @@ func TestCommandSide_ChangeOIDCApplication(t *testing.T) {
|
|||||||
{
|
{
|
||||||
name: "missing aggregateid, invalid argument error",
|
name: "missing aggregateid, invalid argument error",
|
||||||
fields: fields{
|
fields: fields{
|
||||||
eventstore: eventstoreExpect(
|
eventstore: expectEventstore(),
|
||||||
t,
|
|
||||||
),
|
|
||||||
},
|
},
|
||||||
args: args{
|
args: args{
|
||||||
ctx: context.Background(),
|
ctx: context.Background(),
|
||||||
@ -801,8 +815,7 @@ func TestCommandSide_ChangeOIDCApplication(t *testing.T) {
|
|||||||
{
|
{
|
||||||
name: "app not existing, not found error",
|
name: "app not existing, not found error",
|
||||||
fields: fields{
|
fields: fields{
|
||||||
eventstore: eventstoreExpect(
|
eventstore: expectEventstore(
|
||||||
t,
|
|
||||||
expectFilter(),
|
expectFilter(),
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
@ -826,8 +839,7 @@ func TestCommandSide_ChangeOIDCApplication(t *testing.T) {
|
|||||||
{
|
{
|
||||||
name: "no changes, precondition error",
|
name: "no changes, precondition error",
|
||||||
fields: fields{
|
fields: fields{
|
||||||
eventstore: eventstoreExpect(
|
eventstore: expectEventstore(
|
||||||
t,
|
|
||||||
expectFilter(
|
expectFilter(
|
||||||
eventFromEventPusher(
|
eventFromEventPusher(
|
||||||
project.NewApplicationAddedEvent(context.Background(),
|
project.NewApplicationAddedEvent(context.Background(),
|
||||||
@ -858,6 +870,8 @@ func TestCommandSide_ChangeOIDCApplication(t *testing.T) {
|
|||||||
[]string{"https://sub.test.ch"},
|
[]string{"https://sub.test.ch"},
|
||||||
true,
|
true,
|
||||||
"https://test.ch/backchannel",
|
"https://test.ch/backchannel",
|
||||||
|
domain.LoginVersion2,
|
||||||
|
"https://login.test.ch",
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -887,6 +901,8 @@ func TestCommandSide_ChangeOIDCApplication(t *testing.T) {
|
|||||||
AdditionalOrigins: []string{"https://sub.test.ch"},
|
AdditionalOrigins: []string{"https://sub.test.ch"},
|
||||||
SkipNativeAppSuccessPage: true,
|
SkipNativeAppSuccessPage: true,
|
||||||
BackChannelLogoutURI: "https://test.ch/backchannel",
|
BackChannelLogoutURI: "https://test.ch/backchannel",
|
||||||
|
LoginVersion: domain.LoginVersion2,
|
||||||
|
LoginBaseURI: "https://login.test.ch",
|
||||||
},
|
},
|
||||||
resourceOwner: "org1",
|
resourceOwner: "org1",
|
||||||
},
|
},
|
||||||
@ -897,8 +913,7 @@ func TestCommandSide_ChangeOIDCApplication(t *testing.T) {
|
|||||||
{
|
{
|
||||||
name: "no changes whitespaces are ignored, precondition error",
|
name: "no changes whitespaces are ignored, precondition error",
|
||||||
fields: fields{
|
fields: fields{
|
||||||
eventstore: eventstoreExpect(
|
eventstore: expectEventstore(
|
||||||
t,
|
|
||||||
expectFilter(
|
expectFilter(
|
||||||
eventFromEventPusher(
|
eventFromEventPusher(
|
||||||
project.NewApplicationAddedEvent(context.Background(),
|
project.NewApplicationAddedEvent(context.Background(),
|
||||||
@ -929,6 +944,8 @@ func TestCommandSide_ChangeOIDCApplication(t *testing.T) {
|
|||||||
[]string{"https://sub.test.ch"},
|
[]string{"https://sub.test.ch"},
|
||||||
true,
|
true,
|
||||||
"https://test.ch/backchannel",
|
"https://test.ch/backchannel",
|
||||||
|
domain.LoginVersion2,
|
||||||
|
"https://login.test.ch",
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -958,6 +975,8 @@ func TestCommandSide_ChangeOIDCApplication(t *testing.T) {
|
|||||||
AdditionalOrigins: []string{" https://sub.test.ch "},
|
AdditionalOrigins: []string{" https://sub.test.ch "},
|
||||||
SkipNativeAppSuccessPage: true,
|
SkipNativeAppSuccessPage: true,
|
||||||
BackChannelLogoutURI: " https://test.ch/backchannel ",
|
BackChannelLogoutURI: " https://test.ch/backchannel ",
|
||||||
|
LoginVersion: domain.LoginVersion2,
|
||||||
|
LoginBaseURI: " https://login.test.ch ",
|
||||||
},
|
},
|
||||||
resourceOwner: "org1",
|
resourceOwner: "org1",
|
||||||
},
|
},
|
||||||
@ -968,8 +987,7 @@ func TestCommandSide_ChangeOIDCApplication(t *testing.T) {
|
|||||||
{
|
{
|
||||||
name: "change oidc app, ok",
|
name: "change oidc app, ok",
|
||||||
fields: fields{
|
fields: fields{
|
||||||
eventstore: eventstoreExpect(
|
eventstore: expectEventstore(
|
||||||
t,
|
|
||||||
expectFilter(
|
expectFilter(
|
||||||
eventFromEventPusher(
|
eventFromEventPusher(
|
||||||
project.NewApplicationAddedEvent(context.Background(),
|
project.NewApplicationAddedEvent(context.Background(),
|
||||||
@ -1000,6 +1018,8 @@ func TestCommandSide_ChangeOIDCApplication(t *testing.T) {
|
|||||||
[]string{"https://sub.test.ch"},
|
[]string{"https://sub.test.ch"},
|
||||||
true,
|
true,
|
||||||
"https://test.ch/backchannel",
|
"https://test.ch/backchannel",
|
||||||
|
domain.LoginVersion1,
|
||||||
|
"",
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -1035,6 +1055,8 @@ func TestCommandSide_ChangeOIDCApplication(t *testing.T) {
|
|||||||
AdditionalOrigins: []string{"https://sub.test.ch"},
|
AdditionalOrigins: []string{"https://sub.test.ch"},
|
||||||
SkipNativeAppSuccessPage: true,
|
SkipNativeAppSuccessPage: true,
|
||||||
BackChannelLogoutURI: "https://test.ch/backchannel",
|
BackChannelLogoutURI: "https://test.ch/backchannel",
|
||||||
|
LoginVersion: domain.LoginVersion2,
|
||||||
|
LoginBaseURI: "https://login.test.ch",
|
||||||
},
|
},
|
||||||
resourceOwner: "org1",
|
resourceOwner: "org1",
|
||||||
},
|
},
|
||||||
@ -1063,6 +1085,8 @@ func TestCommandSide_ChangeOIDCApplication(t *testing.T) {
|
|||||||
AdditionalOrigins: []string{"https://sub.test.ch"},
|
AdditionalOrigins: []string{"https://sub.test.ch"},
|
||||||
SkipNativeAppSuccessPage: true,
|
SkipNativeAppSuccessPage: true,
|
||||||
BackChannelLogoutURI: "https://test.ch/backchannel",
|
BackChannelLogoutURI: "https://test.ch/backchannel",
|
||||||
|
LoginVersion: domain.LoginVersion2,
|
||||||
|
LoginBaseURI: "https://login.test.ch",
|
||||||
Compliance: &domain.Compliance{},
|
Compliance: &domain.Compliance{},
|
||||||
State: domain.AppStateActive,
|
State: domain.AppStateActive,
|
||||||
},
|
},
|
||||||
@ -1072,7 +1096,7 @@ func TestCommandSide_ChangeOIDCApplication(t *testing.T) {
|
|||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
r := &Commands{
|
r := &Commands{
|
||||||
eventstore: tt.fields.eventstore,
|
eventstore: tt.fields.eventstore(t),
|
||||||
}
|
}
|
||||||
got, err := r.ChangeOIDCApplication(tt.args.ctx, tt.args.oidcApp, tt.args.resourceOwner)
|
got, err := r.ChangeOIDCApplication(tt.args.ctx, tt.args.oidcApp, tt.args.resourceOwner)
|
||||||
if tt.res.err == nil {
|
if tt.res.err == nil {
|
||||||
@ -1188,6 +1212,8 @@ func TestCommandSide_ChangeOIDCApplicationSecret(t *testing.T) {
|
|||||||
[]string{"https://sub.test.ch"},
|
[]string{"https://sub.test.ch"},
|
||||||
false,
|
false,
|
||||||
"",
|
"",
|
||||||
|
domain.LoginVersionUnspecified,
|
||||||
|
"",
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -1232,6 +1258,7 @@ func TestCommandSide_ChangeOIDCApplicationSecret(t *testing.T) {
|
|||||||
AdditionalOrigins: []string{"https://sub.test.ch"},
|
AdditionalOrigins: []string{"https://sub.test.ch"},
|
||||||
SkipNativeAppSuccessPage: false,
|
SkipNativeAppSuccessPage: false,
|
||||||
BackChannelLogoutURI: "",
|
BackChannelLogoutURI: "",
|
||||||
|
LoginVersion: domain.LoginVersionUnspecified,
|
||||||
State: domain.AppStateActive,
|
State: domain.AppStateActive,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -1270,6 +1297,8 @@ func newOIDCAppChangedEvent(ctx context.Context, appID, projectID, resourceOwner
|
|||||||
project.ChangeIDTokenRoleAssertion(false),
|
project.ChangeIDTokenRoleAssertion(false),
|
||||||
project.ChangeIDTokenUserinfoAssertion(false),
|
project.ChangeIDTokenUserinfoAssertion(false),
|
||||||
project.ChangeClockSkew(time.Second * 2),
|
project.ChangeClockSkew(time.Second * 2),
|
||||||
|
project.ChangeLoginVersion(domain.LoginVersion2),
|
||||||
|
project.ChangeLoginBaseURI("https://login.test.ch"),
|
||||||
}
|
}
|
||||||
event, _ := project.NewOIDCConfigChangedEvent(ctx,
|
event, _ := project.NewOIDCConfigChangedEvent(ctx,
|
||||||
&project.NewAggregate(projectID, resourceOwner).Aggregate,
|
&project.NewAggregate(projectID, resourceOwner).Aggregate,
|
||||||
@ -1347,6 +1376,8 @@ func TestCommands_VerifyOIDCClientSecret(t *testing.T) {
|
|||||||
[]string{"https://sub.test.ch"},
|
[]string{"https://sub.test.ch"},
|
||||||
false,
|
false,
|
||||||
"",
|
"",
|
||||||
|
domain.LoginVersionUnspecified,
|
||||||
|
"",
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -1383,6 +1414,8 @@ func TestCommands_VerifyOIDCClientSecret(t *testing.T) {
|
|||||||
[]string{"https://sub.test.ch"},
|
[]string{"https://sub.test.ch"},
|
||||||
false,
|
false,
|
||||||
"",
|
"",
|
||||||
|
domain.LoginVersionUnspecified,
|
||||||
|
"",
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -1418,6 +1451,8 @@ func TestCommands_VerifyOIDCClientSecret(t *testing.T) {
|
|||||||
[]string{"https://sub.test.ch"},
|
[]string{"https://sub.test.ch"},
|
||||||
false,
|
false,
|
||||||
"",
|
"",
|
||||||
|
domain.LoginVersionUnspecified,
|
||||||
|
"",
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -48,6 +48,8 @@ func oidcWriteModelToOIDCConfig(writeModel *OIDCApplicationWriteModel) *domain.O
|
|||||||
AdditionalOrigins: writeModel.AdditionalOrigins,
|
AdditionalOrigins: writeModel.AdditionalOrigins,
|
||||||
SkipNativeAppSuccessPage: writeModel.SkipNativeAppSuccessPage,
|
SkipNativeAppSuccessPage: writeModel.SkipNativeAppSuccessPage,
|
||||||
BackChannelLogoutURI: writeModel.BackChannelLogoutURI,
|
BackChannelLogoutURI: writeModel.BackChannelLogoutURI,
|
||||||
|
LoginVersion: writeModel.LoginVersion,
|
||||||
|
LoginBaseURI: writeModel.LoginBaseURI,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -20,6 +20,7 @@ type SystemFeatures struct {
|
|||||||
OIDCSingleV1SessionTermination *bool
|
OIDCSingleV1SessionTermination *bool
|
||||||
DisableUserTokenEvent *bool
|
DisableUserTokenEvent *bool
|
||||||
EnableBackChannelLogout *bool
|
EnableBackChannelLogout *bool
|
||||||
|
LoginV2 *feature.LoginV2
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *SystemFeatures) isEmpty() bool {
|
func (m *SystemFeatures) isEmpty() bool {
|
||||||
@ -33,7 +34,8 @@ func (m *SystemFeatures) isEmpty() bool {
|
|||||||
m.ImprovedPerformance == nil &&
|
m.ImprovedPerformance == nil &&
|
||||||
m.OIDCSingleV1SessionTermination == nil &&
|
m.OIDCSingleV1SessionTermination == nil &&
|
||||||
m.DisableUserTokenEvent == nil &&
|
m.DisableUserTokenEvent == nil &&
|
||||||
m.EnableBackChannelLogout == nil
|
m.EnableBackChannelLogout == nil &&
|
||||||
|
m.LoginV2 == nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Commands) SetSystemFeatures(ctx context.Context, f *SystemFeatures) (*domain.ObjectDetails, error) {
|
func (c *Commands) SetSystemFeatures(ctx context.Context, f *SystemFeatures) (*domain.ObjectDetails, error) {
|
||||||
|
@ -34,6 +34,12 @@ func (m *SystemFeaturesWriteModel) Reduce() error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
reduceSystemFeature(&m.SystemFeatures, key, e.Value)
|
reduceSystemFeature(&m.SystemFeatures, key, e.Value)
|
||||||
|
case *feature_v2.SetEvent[*feature.LoginV2]:
|
||||||
|
_, key, err := e.FeatureInfo()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
reduceSystemFeature(&m.SystemFeatures, key, e.Value)
|
||||||
case *feature_v2.SetEvent[[]feature.ImprovedPerformanceType]:
|
case *feature_v2.SetEvent[[]feature.ImprovedPerformanceType]:
|
||||||
_, key, err := e.FeatureInfo()
|
_, key, err := e.FeatureInfo()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -63,6 +69,7 @@ func (m *SystemFeaturesWriteModel) Query() *eventstore.SearchQueryBuilder {
|
|||||||
feature_v2.SystemOIDCSingleV1SessionTerminationEventType,
|
feature_v2.SystemOIDCSingleV1SessionTerminationEventType,
|
||||||
feature_v2.SystemDisableUserTokenEvent,
|
feature_v2.SystemDisableUserTokenEvent,
|
||||||
feature_v2.SystemEnableBackChannelLogout,
|
feature_v2.SystemEnableBackChannelLogout,
|
||||||
|
feature_v2.SystemLoginVersion,
|
||||||
).
|
).
|
||||||
Builder().ResourceOwner(m.ResourceOwner)
|
Builder().ResourceOwner(m.ResourceOwner)
|
||||||
}
|
}
|
||||||
@ -104,6 +111,8 @@ func reduceSystemFeature(features *SystemFeatures, key feature.Key, value any) {
|
|||||||
case feature.KeyEnableBackChannelLogout:
|
case feature.KeyEnableBackChannelLogout:
|
||||||
v := value.(bool)
|
v := value.(bool)
|
||||||
features.EnableBackChannelLogout = &v
|
features.EnableBackChannelLogout = &v
|
||||||
|
case feature.KeyLoginV2:
|
||||||
|
features.LoginV2 = value.(*feature.LoginV2)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -120,6 +129,7 @@ func (wm *SystemFeaturesWriteModel) setCommands(ctx context.Context, f *SystemFe
|
|||||||
cmds = appendFeatureUpdate(ctx, cmds, aggregate, wm.OIDCSingleV1SessionTermination, f.OIDCSingleV1SessionTermination, feature_v2.SystemOIDCSingleV1SessionTerminationEventType)
|
cmds = appendFeatureUpdate(ctx, cmds, aggregate, wm.OIDCSingleV1SessionTermination, f.OIDCSingleV1SessionTermination, feature_v2.SystemOIDCSingleV1SessionTerminationEventType)
|
||||||
cmds = appendFeatureUpdate(ctx, cmds, aggregate, wm.DisableUserTokenEvent, f.DisableUserTokenEvent, feature_v2.SystemDisableUserTokenEvent)
|
cmds = appendFeatureUpdate(ctx, cmds, aggregate, wm.DisableUserTokenEvent, f.DisableUserTokenEvent, feature_v2.SystemDisableUserTokenEvent)
|
||||||
cmds = appendFeatureUpdate(ctx, cmds, aggregate, wm.EnableBackChannelLogout, f.EnableBackChannelLogout, feature_v2.SystemEnableBackChannelLogout)
|
cmds = appendFeatureUpdate(ctx, cmds, aggregate, wm.EnableBackChannelLogout, f.EnableBackChannelLogout, feature_v2.SystemEnableBackChannelLogout)
|
||||||
|
cmds = appendFeatureUpdate(ctx, cmds, aggregate, wm.LoginV2, f.LoginV2, feature_v2.SystemLoginVersion)
|
||||||
return cmds
|
return cmds
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -39,3 +39,11 @@ func (a *ChangeApp) GetApplicationName() string {
|
|||||||
func (a *ChangeApp) GetState() AppState {
|
func (a *ChangeApp) GetState() AppState {
|
||||||
return a.State
|
return a.State
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type LoginVersion int32
|
||||||
|
|
||||||
|
const (
|
||||||
|
LoginVersionUnspecified LoginVersion = iota
|
||||||
|
LoginVersion1
|
||||||
|
LoginVersion2
|
||||||
|
)
|
||||||
|
@ -46,6 +46,8 @@ type OIDCApp struct {
|
|||||||
AdditionalOrigins []string
|
AdditionalOrigins []string
|
||||||
SkipNativeAppSuccessPage bool
|
SkipNativeAppSuccessPage bool
|
||||||
BackChannelLogoutURI string
|
BackChannelLogoutURI string
|
||||||
|
LoginVersion LoginVersion
|
||||||
|
LoginBaseURI string
|
||||||
|
|
||||||
State AppState
|
State AppState
|
||||||
}
|
}
|
||||||
|
@ -32,6 +32,8 @@ const (
|
|||||||
PermissionUserDelete = "user.delete"
|
PermissionUserDelete = "user.delete"
|
||||||
PermissionUserCredentialWrite = "user.credential.write"
|
PermissionUserCredentialWrite = "user.credential.write"
|
||||||
PermissionSessionWrite = "session.write"
|
PermissionSessionWrite = "session.write"
|
||||||
|
PermissionSessionRead = "session.read"
|
||||||
|
PermissionSessionLink = "session.link"
|
||||||
PermissionSessionDelete = "session.delete"
|
PermissionSessionDelete = "session.delete"
|
||||||
PermissionOrgRead = "org.read"
|
PermissionOrgRead = "org.read"
|
||||||
PermissionIDPRead = "iam.idp.read"
|
PermissionIDPRead = "iam.idp.read"
|
||||||
|
@ -1,6 +1,9 @@
|
|||||||
package feature
|
package feature
|
||||||
|
|
||||||
import "slices"
|
import (
|
||||||
|
"net/url"
|
||||||
|
"slices"
|
||||||
|
)
|
||||||
|
|
||||||
//go:generate enumer -type Key -transform snake -trimprefix Key
|
//go:generate enumer -type Key -transform snake -trimprefix Key
|
||||||
type Key int
|
type Key int
|
||||||
@ -19,6 +22,7 @@ const (
|
|||||||
KeyOIDCSingleV1SessionTermination
|
KeyOIDCSingleV1SessionTermination
|
||||||
KeyDisableUserTokenEvent
|
KeyDisableUserTokenEvent
|
||||||
KeyEnableBackChannelLogout
|
KeyEnableBackChannelLogout
|
||||||
|
KeyLoginV2
|
||||||
)
|
)
|
||||||
|
|
||||||
//go:generate enumer -type Level -transform snake -trimprefix Level
|
//go:generate enumer -type Level -transform snake -trimprefix Level
|
||||||
@ -47,6 +51,7 @@ type Features struct {
|
|||||||
OIDCSingleV1SessionTermination bool `json:"oidc_single_v1_session_termination,omitempty"`
|
OIDCSingleV1SessionTermination bool `json:"oidc_single_v1_session_termination,omitempty"`
|
||||||
DisableUserTokenEvent bool `json:"disable_user_token_event,omitempty"`
|
DisableUserTokenEvent bool `json:"disable_user_token_event,omitempty"`
|
||||||
EnableBackChannelLogout bool `json:"enable_back_channel_logout,omitempty"`
|
EnableBackChannelLogout bool `json:"enable_back_channel_logout,omitempty"`
|
||||||
|
LoginV2 LoginV2 `json:"login_v2,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type ImprovedPerformanceType int32
|
type ImprovedPerformanceType int32
|
||||||
@ -63,3 +68,8 @@ const (
|
|||||||
func (f Features) ShouldUseImprovedPerformance(typ ImprovedPerformanceType) bool {
|
func (f Features) ShouldUseImprovedPerformance(typ ImprovedPerformanceType) bool {
|
||||||
return slices.Contains(f.ImprovedPerformance, typ)
|
return slices.Contains(f.ImprovedPerformance, typ)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type LoginV2 struct {
|
||||||
|
Required bool `json:"required,omitempty"`
|
||||||
|
BaseURI *url.URL `json:"base_uri,omitempty"`
|
||||||
|
}
|
||||||
|
@ -7,11 +7,11 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
const _KeyName = "unspecifiedlogin_default_orgtrigger_introspection_projectionslegacy_introspectionuser_schematoken_exchangeactionsimproved_performanceweb_keydebug_oidc_parent_erroroidc_single_v1_session_terminationdisable_user_token_eventenable_back_channel_logout"
|
const _KeyName = "unspecifiedlogin_default_orgtrigger_introspection_projectionslegacy_introspectionuser_schematoken_exchangeactionsimproved_performanceweb_keydebug_oidc_parent_erroroidc_single_v1_session_terminationdisable_user_token_eventenable_back_channel_logoutlogin_v2"
|
||||||
|
|
||||||
var _KeyIndex = [...]uint8{0, 11, 28, 61, 81, 92, 106, 113, 133, 140, 163, 197, 221, 247}
|
var _KeyIndex = [...]uint8{0, 11, 28, 61, 81, 92, 106, 113, 133, 140, 163, 197, 221, 247, 255}
|
||||||
|
|
||||||
const _KeyLowerName = "unspecifiedlogin_default_orgtrigger_introspection_projectionslegacy_introspectionuser_schematoken_exchangeactionsimproved_performanceweb_keydebug_oidc_parent_erroroidc_single_v1_session_terminationdisable_user_token_eventenable_back_channel_logout"
|
const _KeyLowerName = "unspecifiedlogin_default_orgtrigger_introspection_projectionslegacy_introspectionuser_schematoken_exchangeactionsimproved_performanceweb_keydebug_oidc_parent_erroroidc_single_v1_session_terminationdisable_user_token_eventenable_back_channel_logoutlogin_v2"
|
||||||
|
|
||||||
func (i Key) String() string {
|
func (i Key) String() string {
|
||||||
if i < 0 || i >= Key(len(_KeyIndex)-1) {
|
if i < 0 || i >= Key(len(_KeyIndex)-1) {
|
||||||
@ -37,9 +37,10 @@ func _KeyNoOp() {
|
|||||||
_ = x[KeyOIDCSingleV1SessionTermination-(10)]
|
_ = x[KeyOIDCSingleV1SessionTermination-(10)]
|
||||||
_ = x[KeyDisableUserTokenEvent-(11)]
|
_ = x[KeyDisableUserTokenEvent-(11)]
|
||||||
_ = x[KeyEnableBackChannelLogout-(12)]
|
_ = x[KeyEnableBackChannelLogout-(12)]
|
||||||
|
_ = x[KeyLoginV2-(13)]
|
||||||
}
|
}
|
||||||
|
|
||||||
var _KeyValues = []Key{KeyUnspecified, KeyLoginDefaultOrg, KeyTriggerIntrospectionProjections, KeyLegacyIntrospection, KeyUserSchema, KeyTokenExchange, KeyActions, KeyImprovedPerformance, KeyWebKey, KeyDebugOIDCParentError, KeyOIDCSingleV1SessionTermination, KeyDisableUserTokenEvent, KeyEnableBackChannelLogout}
|
var _KeyValues = []Key{KeyUnspecified, KeyLoginDefaultOrg, KeyTriggerIntrospectionProjections, KeyLegacyIntrospection, KeyUserSchema, KeyTokenExchange, KeyActions, KeyImprovedPerformance, KeyWebKey, KeyDebugOIDCParentError, KeyOIDCSingleV1SessionTermination, KeyDisableUserTokenEvent, KeyEnableBackChannelLogout, KeyLoginV2}
|
||||||
|
|
||||||
var _KeyNameToValueMap = map[string]Key{
|
var _KeyNameToValueMap = map[string]Key{
|
||||||
_KeyName[0:11]: KeyUnspecified,
|
_KeyName[0:11]: KeyUnspecified,
|
||||||
@ -68,6 +69,8 @@ var _KeyNameToValueMap = map[string]Key{
|
|||||||
_KeyLowerName[197:221]: KeyDisableUserTokenEvent,
|
_KeyLowerName[197:221]: KeyDisableUserTokenEvent,
|
||||||
_KeyName[221:247]: KeyEnableBackChannelLogout,
|
_KeyName[221:247]: KeyEnableBackChannelLogout,
|
||||||
_KeyLowerName[221:247]: KeyEnableBackChannelLogout,
|
_KeyLowerName[221:247]: KeyEnableBackChannelLogout,
|
||||||
|
_KeyName[247:255]: KeyLoginV2,
|
||||||
|
_KeyLowerName[247:255]: KeyLoginV2,
|
||||||
}
|
}
|
||||||
|
|
||||||
var _KeyNames = []string{
|
var _KeyNames = []string{
|
||||||
@ -84,6 +87,7 @@ var _KeyNames = []string{
|
|||||||
_KeyName[163:197],
|
_KeyName[163:197],
|
||||||
_KeyName[197:221],
|
_KeyName[197:221],
|
||||||
_KeyName[221:247],
|
_KeyName[221:247],
|
||||||
|
_KeyName[247:255],
|
||||||
}
|
}
|
||||||
|
|
||||||
// KeyString retrieves an enum value from the enum constants string name.
|
// KeyString retrieves an enum value from the enum constants string name.
|
||||||
|
@ -49,6 +49,7 @@ const (
|
|||||||
UserTypeIAMOwner
|
UserTypeIAMOwner
|
||||||
UserTypeOrgOwner
|
UserTypeOrgOwner
|
||||||
UserTypeLogin
|
UserTypeLogin
|
||||||
|
UserTypeNoPermission
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@ -196,6 +197,7 @@ func (i *Instance) setupInstance(ctx context.Context, token string) {
|
|||||||
i.createMachineUserInstanceOwner(ctx, token)
|
i.createMachineUserInstanceOwner(ctx, token)
|
||||||
i.createMachineUserOrgOwner(ctx)
|
i.createMachineUserOrgOwner(ctx)
|
||||||
i.createLoginClient(ctx)
|
i.createLoginClient(ctx)
|
||||||
|
i.createMachineUserNoPermission(ctx)
|
||||||
i.createWebAuthNClient()
|
i.createWebAuthNClient()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -238,7 +240,17 @@ func (i *Instance) createMachineUserOrgOwner(ctx context.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (i *Instance) createLoginClient(ctx context.Context) {
|
func (i *Instance) createLoginClient(ctx context.Context) {
|
||||||
i.createMachineUser(ctx, UserTypeLogin)
|
_, err := i.Client.Admin.AddIAMMember(ctx, &admin.AddIAMMemberRequest{
|
||||||
|
UserId: i.createMachineUser(ctx, UserTypeLogin),
|
||||||
|
Roles: []string{"IAM_LOGIN_CLIENT"},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *Instance) createMachineUserNoPermission(ctx context.Context) {
|
||||||
|
i.createMachineUser(ctx, UserTypeNoPermission)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i *Instance) setClient(ctx context.Context) {
|
func (i *Instance) setClient(ctx context.Context) {
|
||||||
|
@ -25,7 +25,7 @@ import (
|
|||||||
user_v2 "github.com/zitadel/zitadel/pkg/grpc/user/v2"
|
user_v2 "github.com/zitadel/zitadel/pkg/grpc/user/v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (i *Instance) CreateOIDCClient(ctx context.Context, redirectURI, logoutRedirectURI, projectID string, appType app.OIDCAppType, authMethod app.OIDCAuthMethodType, devMode bool, grantTypes ...app.OIDCGrantType) (*management.AddOIDCAppResponse, error) {
|
func (i *Instance) CreateOIDCClientLoginVersion(ctx context.Context, redirectURI, logoutRedirectURI, projectID string, appType app.OIDCAppType, authMethod app.OIDCAuthMethodType, devMode bool, loginVersion *app.LoginVersion, grantTypes ...app.OIDCGrantType) (*management.AddOIDCAppResponse, error) {
|
||||||
if len(grantTypes) == 0 {
|
if len(grantTypes) == 0 {
|
||||||
grantTypes = []app.OIDCGrantType{app.OIDCGrantType_OIDC_GRANT_TYPE_AUTHORIZATION_CODE, app.OIDCGrantType_OIDC_GRANT_TYPE_REFRESH_TOKEN}
|
grantTypes = []app.OIDCGrantType{app.OIDCGrantType_OIDC_GRANT_TYPE_AUTHORIZATION_CODE, app.OIDCGrantType_OIDC_GRANT_TYPE_REFRESH_TOKEN}
|
||||||
}
|
}
|
||||||
@ -47,6 +47,7 @@ func (i *Instance) CreateOIDCClient(ctx context.Context, redirectURI, logoutRedi
|
|||||||
ClockSkew: nil,
|
ClockSkew: nil,
|
||||||
AdditionalOrigins: nil,
|
AdditionalOrigins: nil,
|
||||||
SkipNativeAppSuccessPage: false,
|
SkipNativeAppSuccessPage: false,
|
||||||
|
LoginVersion: loginVersion,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -66,6 +67,10 @@ func (i *Instance) CreateOIDCClient(ctx context.Context, redirectURI, logoutRedi
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (i *Instance) CreateOIDCClient(ctx context.Context, redirectURI, logoutRedirectURI, projectID string, appType app.OIDCAppType, authMethod app.OIDCAuthMethodType, devMode bool, grantTypes ...app.OIDCGrantType) (*management.AddOIDCAppResponse, error) {
|
||||||
|
return i.CreateOIDCClientLoginVersion(ctx, redirectURI, logoutRedirectURI, projectID, appType, authMethod, devMode, nil, grantTypes...)
|
||||||
|
}
|
||||||
|
|
||||||
func (i *Instance) CreateOIDCNativeClient(ctx context.Context, redirectURI, logoutRedirectURI, projectID string, devMode bool) (*management.AddOIDCAppResponse, error) {
|
func (i *Instance) CreateOIDCNativeClient(ctx context.Context, redirectURI, logoutRedirectURI, projectID string, devMode bool) (*management.AddOIDCAppResponse, error) {
|
||||||
return i.CreateOIDCClient(ctx, redirectURI, logoutRedirectURI, projectID, app.OIDCAppType_OIDC_APP_TYPE_NATIVE, app.OIDCAuthMethodType_OIDC_AUTH_METHOD_TYPE_NONE, devMode)
|
return i.CreateOIDCClient(ctx, redirectURI, logoutRedirectURI, projectID, app.OIDCAppType_OIDC_APP_TYPE_NATIVE, app.OIDCAuthMethodType_OIDC_AUTH_METHOD_TYPE_NONE, devMode)
|
||||||
}
|
}
|
||||||
@ -128,7 +133,7 @@ func (i *Instance) CreateOIDCInactivateProjectClient(ctx context.Context, redire
|
|||||||
return client, err
|
return client, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i *Instance) CreateOIDCImplicitFlowClient(ctx context.Context, redirectURI string) (*management.AddOIDCAppResponse, error) {
|
func (i *Instance) CreateOIDCImplicitFlowClient(ctx context.Context, redirectURI string, loginVersion *app.LoginVersion) (*management.AddOIDCAppResponse, error) {
|
||||||
project, err := i.Client.Mgmt.AddProject(ctx, &management.AddProjectRequest{
|
project, err := i.Client.Mgmt.AddProject(ctx, &management.AddProjectRequest{
|
||||||
Name: fmt.Sprintf("project-%d", time.Now().UnixNano()),
|
Name: fmt.Sprintf("project-%d", time.Now().UnixNano()),
|
||||||
})
|
})
|
||||||
@ -153,6 +158,7 @@ func (i *Instance) CreateOIDCImplicitFlowClient(ctx context.Context, redirectURI
|
|||||||
ClockSkew: nil,
|
ClockSkew: nil,
|
||||||
AdditionalOrigins: nil,
|
AdditionalOrigins: nil,
|
||||||
SkipNativeAppSuccessPage: false,
|
SkipNativeAppSuccessPage: false,
|
||||||
|
LoginVersion: loginVersion,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -209,15 +215,28 @@ const CodeVerifier = "codeVerifier"
|
|||||||
func (i *Instance) CreateOIDCAuthRequest(ctx context.Context, clientID, loginClient, redirectURI string, scope ...string) (authRequestID string, err error) {
|
func (i *Instance) CreateOIDCAuthRequest(ctx context.Context, clientID, loginClient, redirectURI string, scope ...string) (authRequestID string, err error) {
|
||||||
return i.CreateOIDCAuthRequestWithDomain(ctx, i.Domain, clientID, loginClient, redirectURI, scope...)
|
return i.CreateOIDCAuthRequestWithDomain(ctx, i.Domain, clientID, loginClient, redirectURI, scope...)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (i *Instance) CreateOIDCAuthRequestWithoutLoginClientHeader(ctx context.Context, clientID, redirectURI, loginBaseURI string, scope ...string) (authRequestID string, err error) {
|
||||||
|
return i.createOIDCAuthRequestWithDomain(ctx, i.Domain, clientID, redirectURI, "", loginBaseURI, scope...)
|
||||||
|
}
|
||||||
|
|
||||||
func (i *Instance) CreateOIDCAuthRequestWithDomain(ctx context.Context, domain, clientID, loginClient, redirectURI string, scope ...string) (authRequestID string, err error) {
|
func (i *Instance) CreateOIDCAuthRequestWithDomain(ctx context.Context, domain, clientID, loginClient, redirectURI string, scope ...string) (authRequestID string, err error) {
|
||||||
provider, err := i.CreateRelyingPartyForDomain(ctx, domain, clientID, redirectURI, scope...)
|
return i.createOIDCAuthRequestWithDomain(ctx, domain, clientID, redirectURI, loginClient, "", scope...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *Instance) createOIDCAuthRequestWithDomain(ctx context.Context, domain, clientID, redirectURI, loginClient, loginBaseURI string, scope ...string) (authRequestID string, err error) {
|
||||||
|
provider, err := i.CreateRelyingPartyForDomain(ctx, domain, clientID, redirectURI, loginClient, scope...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", fmt.Errorf("create relying party: %w", err)
|
return "", fmt.Errorf("create relying party: %w", err)
|
||||||
}
|
}
|
||||||
codeChallenge := oidc.NewSHACodeChallenge(CodeVerifier)
|
codeChallenge := oidc.NewSHACodeChallenge(CodeVerifier)
|
||||||
authURL := rp.AuthURL("state", provider, rp.WithCodeChallenge(codeChallenge))
|
authURL := rp.AuthURL("state", provider, rp.WithCodeChallenge(codeChallenge))
|
||||||
|
|
||||||
req, err := GetRequest(authURL, map[string]string{oidc_internal.LoginClientHeader: loginClient})
|
var headers map[string]string
|
||||||
|
if loginClient != "" {
|
||||||
|
headers = map[string]string{oidc_internal.LoginClientHeader: loginClient}
|
||||||
|
}
|
||||||
|
req, err := GetRequest(authURL, headers)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", fmt.Errorf("get request: %w", err)
|
return "", fmt.Errorf("get request: %w", err)
|
||||||
}
|
}
|
||||||
@ -227,14 +246,24 @@ func (i *Instance) CreateOIDCAuthRequestWithDomain(ctx context.Context, domain,
|
|||||||
return "", fmt.Errorf("check redirect: %w", err)
|
return "", fmt.Errorf("check redirect: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
prefixWithHost := provider.Issuer() + i.Config.LoginURLV2
|
if loginBaseURI == "" {
|
||||||
if !strings.HasPrefix(loc.String(), prefixWithHost) {
|
loginBaseURI = provider.Issuer() + i.Config.LoginURLV2
|
||||||
return "", fmt.Errorf("login location has not prefix %s, but is %s", prefixWithHost, loc.String())
|
|
||||||
}
|
}
|
||||||
return strings.TrimPrefix(loc.String(), prefixWithHost), nil
|
if !strings.HasPrefix(loc.String(), loginBaseURI) {
|
||||||
|
return "", fmt.Errorf("login location has not prefix %s, but is %s", loginBaseURI, loc.String())
|
||||||
|
}
|
||||||
|
return strings.TrimPrefix(loc.String(), loginBaseURI), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *Instance) CreateOIDCAuthRequestImplicitWithoutLoginClientHeader(ctx context.Context, clientID, redirectURI string, scope ...string) (authRequestID string, err error) {
|
||||||
|
return i.createOIDCAuthRequestImplicit(ctx, clientID, redirectURI, nil, scope...)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i *Instance) CreateOIDCAuthRequestImplicit(ctx context.Context, clientID, loginClient, redirectURI string, scope ...string) (authRequestID string, err error) {
|
func (i *Instance) CreateOIDCAuthRequestImplicit(ctx context.Context, clientID, loginClient, redirectURI string, scope ...string) (authRequestID string, err error) {
|
||||||
|
return i.createOIDCAuthRequestImplicit(ctx, clientID, redirectURI, map[string]string{oidc_internal.LoginClientHeader: loginClient}, scope...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *Instance) createOIDCAuthRequestImplicit(ctx context.Context, clientID, redirectURI string, headers map[string]string, scope ...string) (authRequestID string, err error) {
|
||||||
provider, err := i.CreateRelyingParty(ctx, clientID, redirectURI, scope...)
|
provider, err := i.CreateRelyingParty(ctx, clientID, redirectURI, scope...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
@ -249,7 +278,7 @@ func (i *Instance) CreateOIDCAuthRequestImplicit(ctx context.Context, clientID,
|
|||||||
parsed.RawQuery = queries.Encode()
|
parsed.RawQuery = queries.Encode()
|
||||||
authURL = parsed.String()
|
authURL = parsed.String()
|
||||||
|
|
||||||
req, err := GetRequest(authURL, map[string]string{oidc_internal.LoginClientHeader: loginClient})
|
req, err := GetRequest(authURL, headers)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
@ -271,14 +300,21 @@ func (i *Instance) OIDCIssuer() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (i *Instance) CreateRelyingParty(ctx context.Context, clientID, redirectURI string, scope ...string) (rp.RelyingParty, error) {
|
func (i *Instance) CreateRelyingParty(ctx context.Context, clientID, redirectURI string, scope ...string) (rp.RelyingParty, error) {
|
||||||
return i.CreateRelyingPartyForDomain(ctx, i.Domain, clientID, redirectURI, scope...)
|
return i.CreateRelyingPartyForDomain(ctx, i.Domain, clientID, redirectURI, i.Users.Get(UserTypeLogin).Username, scope...)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i *Instance) CreateRelyingPartyForDomain(ctx context.Context, domain, clientID, redirectURI string, scope ...string) (rp.RelyingParty, error) {
|
func (i *Instance) CreateRelyingPartyWithoutLoginClientHeader(ctx context.Context, clientID, redirectURI string, scope ...string) (rp.RelyingParty, error) {
|
||||||
|
return i.CreateRelyingPartyForDomain(ctx, i.Domain, clientID, redirectURI, "", scope...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *Instance) CreateRelyingPartyForDomain(ctx context.Context, domain, clientID, redirectURI, loginClientUsername string, scope ...string) (rp.RelyingParty, error) {
|
||||||
if len(scope) == 0 {
|
if len(scope) == 0 {
|
||||||
scope = []string{oidc.ScopeOpenID}
|
scope = []string{oidc.ScopeOpenID}
|
||||||
}
|
}
|
||||||
loginClient := &http.Client{Transport: &loginRoundTripper{http.DefaultTransport, i.Users.Get(UserTypeLogin).Username}}
|
if loginClientUsername == "" {
|
||||||
|
return rp.NewRelyingPartyOIDC(ctx, http_util.BuildHTTP(domain, i.Config.Port, i.Config.Secure), clientID, "", redirectURI, scope)
|
||||||
|
}
|
||||||
|
loginClient := &http.Client{Transport: &loginRoundTripper{http.DefaultTransport, loginClientUsername}}
|
||||||
return rp.NewRelyingPartyOIDC(ctx, http_util.BuildHTTP(domain, i.Config.Port, i.Config.Secure), clientID, "", redirectURI, scope, rp.WithHTTPClient(loginClient))
|
return rp.NewRelyingPartyOIDC(ctx, http_util.BuildHTTP(domain, i.Config.Port, i.Config.Secure), clientID, "", redirectURI, scope, rp.WithHTTPClient(loginClient))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -7,11 +7,11 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
const _UserTypeName = "unspecifiediam_ownerorg_ownerlogin"
|
const _UserTypeName = "unspecifiediam_ownerorg_ownerloginno_permission"
|
||||||
|
|
||||||
var _UserTypeIndex = [...]uint8{0, 11, 20, 29, 34}
|
var _UserTypeIndex = [...]uint8{0, 11, 20, 29, 34, 47}
|
||||||
|
|
||||||
const _UserTypeLowerName = "unspecifiediam_ownerorg_ownerlogin"
|
const _UserTypeLowerName = "unspecifiediam_ownerorg_ownerloginno_permission"
|
||||||
|
|
||||||
func (i UserType) String() string {
|
func (i UserType) String() string {
|
||||||
if i < 0 || i >= UserType(len(_UserTypeIndex)-1) {
|
if i < 0 || i >= UserType(len(_UserTypeIndex)-1) {
|
||||||
@ -28,9 +28,10 @@ func _UserTypeNoOp() {
|
|||||||
_ = x[UserTypeIAMOwner-(1)]
|
_ = x[UserTypeIAMOwner-(1)]
|
||||||
_ = x[UserTypeOrgOwner-(2)]
|
_ = x[UserTypeOrgOwner-(2)]
|
||||||
_ = x[UserTypeLogin-(3)]
|
_ = x[UserTypeLogin-(3)]
|
||||||
|
_ = x[UserTypeNoPermission-(4)]
|
||||||
}
|
}
|
||||||
|
|
||||||
var _UserTypeValues = []UserType{UserTypeUnspecified, UserTypeIAMOwner, UserTypeOrgOwner, UserTypeLogin}
|
var _UserTypeValues = []UserType{UserTypeUnspecified, UserTypeIAMOwner, UserTypeOrgOwner, UserTypeLogin, UserTypeNoPermission}
|
||||||
|
|
||||||
var _UserTypeNameToValueMap = map[string]UserType{
|
var _UserTypeNameToValueMap = map[string]UserType{
|
||||||
_UserTypeName[0:11]: UserTypeUnspecified,
|
_UserTypeName[0:11]: UserTypeUnspecified,
|
||||||
@ -41,6 +42,8 @@ var _UserTypeNameToValueMap = map[string]UserType{
|
|||||||
_UserTypeLowerName[20:29]: UserTypeOrgOwner,
|
_UserTypeLowerName[20:29]: UserTypeOrgOwner,
|
||||||
_UserTypeName[29:34]: UserTypeLogin,
|
_UserTypeName[29:34]: UserTypeLogin,
|
||||||
_UserTypeLowerName[29:34]: UserTypeLogin,
|
_UserTypeLowerName[29:34]: UserTypeLogin,
|
||||||
|
_UserTypeName[34:47]: UserTypeNoPermission,
|
||||||
|
_UserTypeLowerName[34:47]: UserTypeNoPermission,
|
||||||
}
|
}
|
||||||
|
|
||||||
var _UserTypeNames = []string{
|
var _UserTypeNames = []string{
|
||||||
@ -48,6 +51,7 @@ var _UserTypeNames = []string{
|
|||||||
_UserTypeName[11:20],
|
_UserTypeName[11:20],
|
||||||
_UserTypeName[20:29],
|
_UserTypeName[20:29],
|
||||||
_UserTypeName[29:34],
|
_UserTypeName[29:34],
|
||||||
|
_UserTypeName[34:47],
|
||||||
}
|
}
|
||||||
|
|
||||||
// UserTypeString retrieves an enum value from the enum constants string name.
|
// UserTypeString retrieves an enum value from the enum constants string name.
|
||||||
|
@ -89,7 +89,7 @@ func loginToClient(t *testing.T, instance *integration.Instance, clientID, redir
|
|||||||
}},
|
}},
|
||||||
})
|
})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
provider, err := instance.CreateRelyingPartyForDomain(iamOwnerCtx, instance.Domain, clientID, redirectURI)
|
provider, err := instance.CreateRelyingPartyForDomain(iamOwnerCtx, instance.Domain, clientID, redirectURI, instance.Users.Get(integration.UserTypeLogin).Username)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
callbackURL, err := url.Parse(callback.GetCallbackUrl())
|
callbackURL, err := url.Parse(callback.GetCallbackUrl())
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
@ -60,6 +60,8 @@ type OIDCApp struct {
|
|||||||
AllowedOrigins database.TextArray[string]
|
AllowedOrigins database.TextArray[string]
|
||||||
SkipNativeAppSuccessPage bool
|
SkipNativeAppSuccessPage bool
|
||||||
BackChannelLogoutURI string
|
BackChannelLogoutURI string
|
||||||
|
LoginVersion domain.LoginVersion
|
||||||
|
LoginBaseURI *string
|
||||||
}
|
}
|
||||||
|
|
||||||
type SAMLApp struct {
|
type SAMLApp struct {
|
||||||
@ -180,6 +182,10 @@ var (
|
|||||||
name: projection.AppOIDCConfigColumnAppID,
|
name: projection.AppOIDCConfigColumnAppID,
|
||||||
table: appOIDCConfigsTable,
|
table: appOIDCConfigsTable,
|
||||||
}
|
}
|
||||||
|
AppOIDCConfigColumnInstanceID = Column{
|
||||||
|
name: projection.AppOIDCConfigColumnInstanceID,
|
||||||
|
table: appOIDCConfigsTable,
|
||||||
|
}
|
||||||
AppOIDCConfigColumnVersion = Column{
|
AppOIDCConfigColumnVersion = Column{
|
||||||
name: projection.AppOIDCConfigColumnVersion,
|
name: projection.AppOIDCConfigColumnVersion,
|
||||||
table: appOIDCConfigsTable,
|
table: appOIDCConfigsTable,
|
||||||
@ -248,6 +254,14 @@ var (
|
|||||||
name: projection.AppOIDCConfigColumnBackChannelLogoutURI,
|
name: projection.AppOIDCConfigColumnBackChannelLogoutURI,
|
||||||
table: appOIDCConfigsTable,
|
table: appOIDCConfigsTable,
|
||||||
}
|
}
|
||||||
|
AppOIDCConfigColumnLoginVersion = Column{
|
||||||
|
name: projection.AppOIDCConfigColumnLoginVersion,
|
||||||
|
table: appOIDCConfigsTable,
|
||||||
|
}
|
||||||
|
AppOIDCConfigColumnLoginBaseURI = Column{
|
||||||
|
name: projection.AppOIDCConfigColumnLoginBaseURI,
|
||||||
|
table: appOIDCConfigsTable,
|
||||||
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
func (q *Queries) AppByProjectAndAppID(ctx context.Context, shouldTriggerBulk bool, projectID, appID string) (app *App, err error) {
|
func (q *Queries) AppByProjectAndAppID(ctx context.Context, shouldTriggerBulk bool, projectID, appID string) (app *App, err error) {
|
||||||
@ -501,6 +515,30 @@ func (q *Queries) SearchClientIDs(ctx context.Context, queries *AppSearchQueries
|
|||||||
return ids, nil
|
return ids, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (q *Queries) OIDCClientLoginVersion(ctx context.Context, clientID string) (loginVersion domain.LoginVersion, err error) {
|
||||||
|
ctx, span := tracing.NewSpan(ctx)
|
||||||
|
defer func() { span.EndWithError(err) }()
|
||||||
|
|
||||||
|
query, scan := prepareLoginVersionByClientID(ctx, q.client)
|
||||||
|
eq := sq.Eq{
|
||||||
|
AppOIDCConfigColumnInstanceID.identifier(): authz.GetInstance(ctx).InstanceID(),
|
||||||
|
AppOIDCConfigColumnClientID.identifier(): clientID,
|
||||||
|
}
|
||||||
|
stmt, args, err := query.Where(eq).ToSql()
|
||||||
|
if err != nil {
|
||||||
|
return domain.LoginVersionUnspecified, zerrors.ThrowInvalidArgument(err, "QUERY-WEh31", "Errors.Query.InvalidRequest")
|
||||||
|
}
|
||||||
|
|
||||||
|
err = q.client.QueryRowContext(ctx, func(row *sql.Row) error {
|
||||||
|
loginVersion, err = scan(row)
|
||||||
|
return err
|
||||||
|
}, stmt, args...)
|
||||||
|
if err != nil {
|
||||||
|
return domain.LoginVersionUnspecified, zerrors.ThrowInternal(err, "QUERY-W2gsa", "Errors.Internal")
|
||||||
|
}
|
||||||
|
return loginVersion, nil
|
||||||
|
}
|
||||||
|
|
||||||
func NewAppNameSearchQuery(method TextComparison, value string) (SearchQuery, error) {
|
func NewAppNameSearchQuery(method TextComparison, value string) (SearchQuery, error) {
|
||||||
return NewTextQuery(AppColumnName, value, method)
|
return NewTextQuery(AppColumnName, value, method)
|
||||||
}
|
}
|
||||||
@ -542,6 +580,8 @@ func prepareAppQuery(ctx context.Context, db prepareDatabase, activeOnly bool) (
|
|||||||
AppOIDCConfigColumnAdditionalOrigins.identifier(),
|
AppOIDCConfigColumnAdditionalOrigins.identifier(),
|
||||||
AppOIDCConfigColumnSkipNativeAppSuccessPage.identifier(),
|
AppOIDCConfigColumnSkipNativeAppSuccessPage.identifier(),
|
||||||
AppOIDCConfigColumnBackChannelLogoutURI.identifier(),
|
AppOIDCConfigColumnBackChannelLogoutURI.identifier(),
|
||||||
|
AppOIDCConfigColumnLoginVersion.identifier(),
|
||||||
|
AppOIDCConfigColumnLoginBaseURI.identifier(),
|
||||||
|
|
||||||
AppSAMLConfigColumnAppID.identifier(),
|
AppSAMLConfigColumnAppID.identifier(),
|
||||||
AppSAMLConfigColumnEntityID.identifier(),
|
AppSAMLConfigColumnEntityID.identifier(),
|
||||||
@ -607,6 +647,8 @@ func scanApp(row *sql.Row) (*App, error) {
|
|||||||
&oidcConfig.additionalOrigins,
|
&oidcConfig.additionalOrigins,
|
||||||
&oidcConfig.skipNativeAppSuccessPage,
|
&oidcConfig.skipNativeAppSuccessPage,
|
||||||
&oidcConfig.backChannelLogoutURI,
|
&oidcConfig.backChannelLogoutURI,
|
||||||
|
&oidcConfig.loginVersion,
|
||||||
|
&oidcConfig.loginBaseURI,
|
||||||
|
|
||||||
&samlConfig.appID,
|
&samlConfig.appID,
|
||||||
&samlConfig.entityID,
|
&samlConfig.entityID,
|
||||||
@ -657,6 +699,8 @@ func prepareOIDCAppQuery() (sq.SelectBuilder, func(*sql.Row) (*App, error)) {
|
|||||||
AppOIDCConfigColumnAdditionalOrigins.identifier(),
|
AppOIDCConfigColumnAdditionalOrigins.identifier(),
|
||||||
AppOIDCConfigColumnSkipNativeAppSuccessPage.identifier(),
|
AppOIDCConfigColumnSkipNativeAppSuccessPage.identifier(),
|
||||||
AppOIDCConfigColumnBackChannelLogoutURI.identifier(),
|
AppOIDCConfigColumnBackChannelLogoutURI.identifier(),
|
||||||
|
AppOIDCConfigColumnLoginVersion.identifier(),
|
||||||
|
AppOIDCConfigColumnLoginBaseURI.identifier(),
|
||||||
).From(appsTable.identifier()).
|
).From(appsTable.identifier()).
|
||||||
Join(join(AppOIDCConfigColumnAppID, AppColumnID)).
|
Join(join(AppOIDCConfigColumnAppID, AppColumnID)).
|
||||||
PlaceholderFormat(sq.Dollar), func(row *sql.Row) (*App, error) {
|
PlaceholderFormat(sq.Dollar), func(row *sql.Row) (*App, error) {
|
||||||
@ -694,6 +738,8 @@ func prepareOIDCAppQuery() (sq.SelectBuilder, func(*sql.Row) (*App, error)) {
|
|||||||
&oidcConfig.additionalOrigins,
|
&oidcConfig.additionalOrigins,
|
||||||
&oidcConfig.skipNativeAppSuccessPage,
|
&oidcConfig.skipNativeAppSuccessPage,
|
||||||
&oidcConfig.backChannelLogoutURI,
|
&oidcConfig.backChannelLogoutURI,
|
||||||
|
&oidcConfig.loginVersion,
|
||||||
|
&oidcConfig.loginBaseURI,
|
||||||
)
|
)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -906,6 +952,8 @@ func prepareAppsQuery(ctx context.Context, db prepareDatabase) (sq.SelectBuilder
|
|||||||
AppOIDCConfigColumnAdditionalOrigins.identifier(),
|
AppOIDCConfigColumnAdditionalOrigins.identifier(),
|
||||||
AppOIDCConfigColumnSkipNativeAppSuccessPage.identifier(),
|
AppOIDCConfigColumnSkipNativeAppSuccessPage.identifier(),
|
||||||
AppOIDCConfigColumnBackChannelLogoutURI.identifier(),
|
AppOIDCConfigColumnBackChannelLogoutURI.identifier(),
|
||||||
|
AppOIDCConfigColumnLoginVersion.identifier(),
|
||||||
|
AppOIDCConfigColumnLoginBaseURI.identifier(),
|
||||||
|
|
||||||
AppSAMLConfigColumnAppID.identifier(),
|
AppSAMLConfigColumnAppID.identifier(),
|
||||||
AppSAMLConfigColumnEntityID.identifier(),
|
AppSAMLConfigColumnEntityID.identifier(),
|
||||||
@ -959,6 +1007,8 @@ func prepareAppsQuery(ctx context.Context, db prepareDatabase) (sq.SelectBuilder
|
|||||||
&oidcConfig.additionalOrigins,
|
&oidcConfig.additionalOrigins,
|
||||||
&oidcConfig.skipNativeAppSuccessPage,
|
&oidcConfig.skipNativeAppSuccessPage,
|
||||||
&oidcConfig.backChannelLogoutURI,
|
&oidcConfig.backChannelLogoutURI,
|
||||||
|
&oidcConfig.loginVersion,
|
||||||
|
&oidcConfig.loginBaseURI,
|
||||||
|
|
||||||
&samlConfig.appID,
|
&samlConfig.appID,
|
||||||
&samlConfig.entityID,
|
&samlConfig.entityID,
|
||||||
@ -1013,6 +1063,21 @@ func prepareClientIDsQuery(ctx context.Context, db prepareDatabase) (sq.SelectBu
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func prepareLoginVersionByClientID(ctx context.Context, db prepareDatabase) (sq.SelectBuilder, func(*sql.Row) (domain.LoginVersion, error)) {
|
||||||
|
return sq.Select(
|
||||||
|
AppOIDCConfigColumnLoginVersion.identifier(),
|
||||||
|
).From(appOIDCConfigsTable.identifier()).
|
||||||
|
PlaceholderFormat(sq.Dollar), func(row *sql.Row) (domain.LoginVersion, error) {
|
||||||
|
var loginVersion sql.NullInt16
|
||||||
|
if err := row.Scan(
|
||||||
|
&loginVersion,
|
||||||
|
); err != nil {
|
||||||
|
return domain.LoginVersionUnspecified, zerrors.ThrowInternal(err, "QUERY-KL2io", "Errors.Internal")
|
||||||
|
}
|
||||||
|
return domain.LoginVersion(loginVersion.Int16), nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
type sqlOIDCConfig struct {
|
type sqlOIDCConfig struct {
|
||||||
appID sql.NullString
|
appID sql.NullString
|
||||||
version sql.NullInt32
|
version sql.NullInt32
|
||||||
@ -1032,6 +1097,8 @@ type sqlOIDCConfig struct {
|
|||||||
grantTypes database.NumberArray[domain.OIDCGrantType]
|
grantTypes database.NumberArray[domain.OIDCGrantType]
|
||||||
skipNativeAppSuccessPage sql.NullBool
|
skipNativeAppSuccessPage sql.NullBool
|
||||||
backChannelLogoutURI sql.NullString
|
backChannelLogoutURI sql.NullString
|
||||||
|
loginVersion sql.NullInt16
|
||||||
|
loginBaseURI sql.NullString
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c sqlOIDCConfig) set(app *App) {
|
func (c sqlOIDCConfig) set(app *App) {
|
||||||
@ -1056,6 +1123,10 @@ func (c sqlOIDCConfig) set(app *App) {
|
|||||||
GrantTypes: c.grantTypes,
|
GrantTypes: c.grantTypes,
|
||||||
SkipNativeAppSuccessPage: c.skipNativeAppSuccessPage.Bool,
|
SkipNativeAppSuccessPage: c.skipNativeAppSuccessPage.Bool,
|
||||||
BackChannelLogoutURI: c.backChannelLogoutURI.String,
|
BackChannelLogoutURI: c.backChannelLogoutURI.String,
|
||||||
|
LoginVersion: domain.LoginVersion(c.loginVersion.Int16),
|
||||||
|
}
|
||||||
|
if c.loginBaseURI.Valid {
|
||||||
|
app.OIDCConfig.LoginBaseURI = &c.loginBaseURI.String
|
||||||
}
|
}
|
||||||
compliance := domain.GetOIDCCompliance(app.OIDCConfig.Version, app.OIDCConfig.AppType, app.OIDCConfig.GrantTypes, app.OIDCConfig.ResponseTypes, app.OIDCConfig.AuthMethodType, app.OIDCConfig.RedirectURIs)
|
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
|
||||||
|
@ -11,6 +11,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
sq "github.com/Masterminds/squirrel"
|
sq "github.com/Masterminds/squirrel"
|
||||||
|
"github.com/muhlemmer/gu"
|
||||||
|
|
||||||
"github.com/zitadel/zitadel/internal/database"
|
"github.com/zitadel/zitadel/internal/database"
|
||||||
"github.com/zitadel/zitadel/internal/domain"
|
"github.com/zitadel/zitadel/internal/domain"
|
||||||
@ -49,6 +50,8 @@ var (
|
|||||||
` projections.apps7_oidc_configs.additional_origins,` +
|
` projections.apps7_oidc_configs.additional_origins,` +
|
||||||
` projections.apps7_oidc_configs.skip_native_app_success_page,` +
|
` projections.apps7_oidc_configs.skip_native_app_success_page,` +
|
||||||
` projections.apps7_oidc_configs.back_channel_logout_uri,` +
|
` projections.apps7_oidc_configs.back_channel_logout_uri,` +
|
||||||
|
` projections.apps7_oidc_configs.login_version,` +
|
||||||
|
` projections.apps7_oidc_configs.login_base_uri,` +
|
||||||
//saml config
|
//saml config
|
||||||
` projections.apps7_saml_configs.app_id,` +
|
` projections.apps7_saml_configs.app_id,` +
|
||||||
` projections.apps7_saml_configs.entity_id,` +
|
` projections.apps7_saml_configs.entity_id,` +
|
||||||
@ -93,6 +96,8 @@ var (
|
|||||||
` projections.apps7_oidc_configs.additional_origins,` +
|
` projections.apps7_oidc_configs.additional_origins,` +
|
||||||
` projections.apps7_oidc_configs.skip_native_app_success_page,` +
|
` projections.apps7_oidc_configs.skip_native_app_success_page,` +
|
||||||
` projections.apps7_oidc_configs.back_channel_logout_uri,` +
|
` projections.apps7_oidc_configs.back_channel_logout_uri,` +
|
||||||
|
` projections.apps7_oidc_configs.login_version,` +
|
||||||
|
` projections.apps7_oidc_configs.login_base_uri,` +
|
||||||
//saml config
|
//saml config
|
||||||
` projections.apps7_saml_configs.app_id,` +
|
` projections.apps7_saml_configs.app_id,` +
|
||||||
` projections.apps7_saml_configs.entity_id,` +
|
` projections.apps7_saml_configs.entity_id,` +
|
||||||
@ -166,6 +171,8 @@ var (
|
|||||||
"additional_origins",
|
"additional_origins",
|
||||||
"skip_native_app_success_page",
|
"skip_native_app_success_page",
|
||||||
"back_channel_logout_uri",
|
"back_channel_logout_uri",
|
||||||
|
"login_version",
|
||||||
|
"login_base_uri",
|
||||||
//saml config
|
//saml config
|
||||||
"app_id",
|
"app_id",
|
||||||
"entity_id",
|
"entity_id",
|
||||||
@ -238,6 +245,8 @@ func Test_AppsPrepare(t *testing.T) {
|
|||||||
nil,
|
nil,
|
||||||
nil,
|
nil,
|
||||||
nil,
|
nil,
|
||||||
|
nil,
|
||||||
|
nil,
|
||||||
// saml config
|
// saml config
|
||||||
nil,
|
nil,
|
||||||
nil,
|
nil,
|
||||||
@ -305,6 +314,8 @@ func Test_AppsPrepare(t *testing.T) {
|
|||||||
nil,
|
nil,
|
||||||
nil,
|
nil,
|
||||||
nil,
|
nil,
|
||||||
|
nil,
|
||||||
|
nil,
|
||||||
// saml config
|
// saml config
|
||||||
nil,
|
nil,
|
||||||
nil,
|
nil,
|
||||||
@ -375,6 +386,8 @@ func Test_AppsPrepare(t *testing.T) {
|
|||||||
nil,
|
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",
|
||||||
@ -447,6 +460,8 @@ func Test_AppsPrepare(t *testing.T) {
|
|||||||
database.TextArray[string]{"additional.origin"},
|
database.TextArray[string]{"additional.origin"},
|
||||||
false,
|
false,
|
||||||
"back.channel.logout.ch",
|
"back.channel.logout.ch",
|
||||||
|
domain.LoginVersionUnspecified,
|
||||||
|
nil,
|
||||||
// saml config
|
// saml config
|
||||||
nil,
|
nil,
|
||||||
nil,
|
nil,
|
||||||
@ -490,6 +505,8 @@ func Test_AppsPrepare(t *testing.T) {
|
|||||||
AllowedOrigins: database.TextArray[string]{"https://redirect.to", "additional.origin"},
|
AllowedOrigins: database.TextArray[string]{"https://redirect.to", "additional.origin"},
|
||||||
SkipNativeAppSuccessPage: false,
|
SkipNativeAppSuccessPage: false,
|
||||||
BackChannelLogoutURI: "back.channel.logout.ch",
|
BackChannelLogoutURI: "back.channel.logout.ch",
|
||||||
|
LoginVersion: domain.LoginVersionUnspecified,
|
||||||
|
LoginBaseURI: nil,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -535,6 +552,8 @@ func Test_AppsPrepare(t *testing.T) {
|
|||||||
database.TextArray[string]{"additional.origin"},
|
database.TextArray[string]{"additional.origin"},
|
||||||
false,
|
false,
|
||||||
"back.channel.logout.ch",
|
"back.channel.logout.ch",
|
||||||
|
domain.LoginVersionUnspecified,
|
||||||
|
nil,
|
||||||
// saml config
|
// saml config
|
||||||
nil,
|
nil,
|
||||||
nil,
|
nil,
|
||||||
@ -578,6 +597,8 @@ func Test_AppsPrepare(t *testing.T) {
|
|||||||
AllowedOrigins: database.TextArray[string]{"https://redirect.to", "additional.origin"},
|
AllowedOrigins: database.TextArray[string]{"https://redirect.to", "additional.origin"},
|
||||||
SkipNativeAppSuccessPage: false,
|
SkipNativeAppSuccessPage: false,
|
||||||
BackChannelLogoutURI: "back.channel.logout.ch",
|
BackChannelLogoutURI: "back.channel.logout.ch",
|
||||||
|
LoginVersion: domain.LoginVersionUnspecified,
|
||||||
|
LoginBaseURI: nil,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -623,6 +644,8 @@ func Test_AppsPrepare(t *testing.T) {
|
|||||||
database.TextArray[string]{"additional.origin"},
|
database.TextArray[string]{"additional.origin"},
|
||||||
false,
|
false,
|
||||||
"back.channel.logout.ch",
|
"back.channel.logout.ch",
|
||||||
|
domain.LoginVersionUnspecified,
|
||||||
|
nil,
|
||||||
// saml config
|
// saml config
|
||||||
nil,
|
nil,
|
||||||
nil,
|
nil,
|
||||||
@ -666,6 +689,8 @@ func Test_AppsPrepare(t *testing.T) {
|
|||||||
AllowedOrigins: database.TextArray[string]{"https://redirect.to", "additional.origin"},
|
AllowedOrigins: database.TextArray[string]{"https://redirect.to", "additional.origin"},
|
||||||
SkipNativeAppSuccessPage: false,
|
SkipNativeAppSuccessPage: false,
|
||||||
BackChannelLogoutURI: "back.channel.logout.ch",
|
BackChannelLogoutURI: "back.channel.logout.ch",
|
||||||
|
LoginVersion: domain.LoginVersionUnspecified,
|
||||||
|
LoginBaseURI: nil,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -711,6 +736,8 @@ func Test_AppsPrepare(t *testing.T) {
|
|||||||
database.TextArray[string]{"additional.origin"},
|
database.TextArray[string]{"additional.origin"},
|
||||||
false,
|
false,
|
||||||
"back.channel.logout.ch",
|
"back.channel.logout.ch",
|
||||||
|
domain.LoginVersionUnspecified,
|
||||||
|
nil,
|
||||||
// saml config
|
// saml config
|
||||||
nil,
|
nil,
|
||||||
nil,
|
nil,
|
||||||
@ -754,6 +781,8 @@ func Test_AppsPrepare(t *testing.T) {
|
|||||||
AllowedOrigins: database.TextArray[string]{"https://redirect.to", "additional.origin"},
|
AllowedOrigins: database.TextArray[string]{"https://redirect.to", "additional.origin"},
|
||||||
SkipNativeAppSuccessPage: false,
|
SkipNativeAppSuccessPage: false,
|
||||||
BackChannelLogoutURI: "back.channel.logout.ch",
|
BackChannelLogoutURI: "back.channel.logout.ch",
|
||||||
|
LoginVersion: domain.LoginVersionUnspecified,
|
||||||
|
LoginBaseURI: nil,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -799,6 +828,8 @@ func Test_AppsPrepare(t *testing.T) {
|
|||||||
database.TextArray[string]{"additional.origin"},
|
database.TextArray[string]{"additional.origin"},
|
||||||
false,
|
false,
|
||||||
"back.channel.logout.ch",
|
"back.channel.logout.ch",
|
||||||
|
domain.LoginVersionUnspecified,
|
||||||
|
nil,
|
||||||
// saml config
|
// saml config
|
||||||
nil,
|
nil,
|
||||||
nil,
|
nil,
|
||||||
@ -842,6 +873,8 @@ func Test_AppsPrepare(t *testing.T) {
|
|||||||
AllowedOrigins: database.TextArray[string]{"https://redirect.to", "additional.origin"},
|
AllowedOrigins: database.TextArray[string]{"https://redirect.to", "additional.origin"},
|
||||||
SkipNativeAppSuccessPage: false,
|
SkipNativeAppSuccessPage: false,
|
||||||
BackChannelLogoutURI: "back.channel.logout.ch",
|
BackChannelLogoutURI: "back.channel.logout.ch",
|
||||||
|
LoginVersion: domain.LoginVersionUnspecified,
|
||||||
|
LoginBaseURI: nil,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -887,6 +920,8 @@ func Test_AppsPrepare(t *testing.T) {
|
|||||||
database.TextArray[string]{"additional.origin"},
|
database.TextArray[string]{"additional.origin"},
|
||||||
true,
|
true,
|
||||||
"back.channel.logout.ch",
|
"back.channel.logout.ch",
|
||||||
|
domain.LoginVersionUnspecified,
|
||||||
|
nil,
|
||||||
// saml config
|
// saml config
|
||||||
nil,
|
nil,
|
||||||
nil,
|
nil,
|
||||||
@ -930,6 +965,8 @@ func Test_AppsPrepare(t *testing.T) {
|
|||||||
AllowedOrigins: database.TextArray[string]{"https://redirect.to", "additional.origin"},
|
AllowedOrigins: database.TextArray[string]{"https://redirect.to", "additional.origin"},
|
||||||
SkipNativeAppSuccessPage: true,
|
SkipNativeAppSuccessPage: true,
|
||||||
BackChannelLogoutURI: "back.channel.logout.ch",
|
BackChannelLogoutURI: "back.channel.logout.ch",
|
||||||
|
LoginVersion: domain.LoginVersionUnspecified,
|
||||||
|
LoginBaseURI: nil,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -975,6 +1012,8 @@ func Test_AppsPrepare(t *testing.T) {
|
|||||||
database.TextArray[string]{"additional.origin"},
|
database.TextArray[string]{"additional.origin"},
|
||||||
false,
|
false,
|
||||||
"back.channel.logout.ch",
|
"back.channel.logout.ch",
|
||||||
|
domain.LoginVersion2,
|
||||||
|
"https://login.ch/",
|
||||||
// saml config
|
// saml config
|
||||||
nil,
|
nil,
|
||||||
nil,
|
nil,
|
||||||
@ -1013,6 +1052,8 @@ func Test_AppsPrepare(t *testing.T) {
|
|||||||
nil,
|
nil,
|
||||||
nil,
|
nil,
|
||||||
nil,
|
nil,
|
||||||
|
nil,
|
||||||
|
nil,
|
||||||
// saml config
|
// saml config
|
||||||
nil,
|
nil,
|
||||||
nil,
|
nil,
|
||||||
@ -1051,6 +1092,8 @@ func Test_AppsPrepare(t *testing.T) {
|
|||||||
nil,
|
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",
|
||||||
@ -1094,6 +1137,8 @@ func Test_AppsPrepare(t *testing.T) {
|
|||||||
AllowedOrigins: database.TextArray[string]{"https://redirect.to", "additional.origin"},
|
AllowedOrigins: database.TextArray[string]{"https://redirect.to", "additional.origin"},
|
||||||
SkipNativeAppSuccessPage: false,
|
SkipNativeAppSuccessPage: false,
|
||||||
BackChannelLogoutURI: "back.channel.logout.ch",
|
BackChannelLogoutURI: "back.channel.logout.ch",
|
||||||
|
LoginVersion: domain.LoginVersion2,
|
||||||
|
LoginBaseURI: gu.Ptr("https://login.ch/"),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -1228,6 +1273,8 @@ func Test_AppPrepare(t *testing.T) {
|
|||||||
nil,
|
nil,
|
||||||
nil,
|
nil,
|
||||||
nil,
|
nil,
|
||||||
|
nil,
|
||||||
|
nil,
|
||||||
// saml config
|
// saml config
|
||||||
nil,
|
nil,
|
||||||
nil,
|
nil,
|
||||||
@ -1289,6 +1336,8 @@ func Test_AppPrepare(t *testing.T) {
|
|||||||
nil,
|
nil,
|
||||||
nil,
|
nil,
|
||||||
nil,
|
nil,
|
||||||
|
nil,
|
||||||
|
nil,
|
||||||
// saml config
|
// saml config
|
||||||
nil,
|
nil,
|
||||||
nil,
|
nil,
|
||||||
@ -1355,6 +1404,8 @@ func Test_AppPrepare(t *testing.T) {
|
|||||||
database.TextArray[string]{"additional.origin"},
|
database.TextArray[string]{"additional.origin"},
|
||||||
false,
|
false,
|
||||||
"back.channel.logout.ch",
|
"back.channel.logout.ch",
|
||||||
|
domain.LoginVersionUnspecified,
|
||||||
|
nil,
|
||||||
// saml config
|
// saml config
|
||||||
nil,
|
nil,
|
||||||
nil,
|
nil,
|
||||||
@ -1393,6 +1444,8 @@ func Test_AppPrepare(t *testing.T) {
|
|||||||
AllowedOrigins: database.TextArray[string]{"https://redirect.to", "additional.origin"},
|
AllowedOrigins: database.TextArray[string]{"https://redirect.to", "additional.origin"},
|
||||||
SkipNativeAppSuccessPage: false,
|
SkipNativeAppSuccessPage: false,
|
||||||
BackChannelLogoutURI: "back.channel.logout.ch",
|
BackChannelLogoutURI: "back.channel.logout.ch",
|
||||||
|
LoginVersion: domain.LoginVersionUnspecified,
|
||||||
|
LoginBaseURI: nil,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -1438,6 +1491,8 @@ func Test_AppPrepare(t *testing.T) {
|
|||||||
database.TextArray[string]{"additional.origin"},
|
database.TextArray[string]{"additional.origin"},
|
||||||
false,
|
false,
|
||||||
"back.channel.logout.ch",
|
"back.channel.logout.ch",
|
||||||
|
domain.LoginVersionUnspecified,
|
||||||
|
nil,
|
||||||
// saml config
|
// saml config
|
||||||
nil,
|
nil,
|
||||||
nil,
|
nil,
|
||||||
@ -1476,6 +1531,8 @@ func Test_AppPrepare(t *testing.T) {
|
|||||||
AllowedOrigins: database.TextArray[string]{"https://redirect.to", "additional.origin"},
|
AllowedOrigins: database.TextArray[string]{"https://redirect.to", "additional.origin"},
|
||||||
SkipNativeAppSuccessPage: false,
|
SkipNativeAppSuccessPage: false,
|
||||||
BackChannelLogoutURI: "back.channel.logout.ch",
|
BackChannelLogoutURI: "back.channel.logout.ch",
|
||||||
|
LoginVersion: domain.LoginVersionUnspecified,
|
||||||
|
LoginBaseURI: nil,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -1521,6 +1578,8 @@ func Test_AppPrepare(t *testing.T) {
|
|||||||
nil,
|
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",
|
||||||
@ -1588,6 +1647,8 @@ func Test_AppPrepare(t *testing.T) {
|
|||||||
database.TextArray[string]{"additional.origin"},
|
database.TextArray[string]{"additional.origin"},
|
||||||
false,
|
false,
|
||||||
"back.channel.logout.ch",
|
"back.channel.logout.ch",
|
||||||
|
domain.LoginVersionUnspecified,
|
||||||
|
nil,
|
||||||
// saml config
|
// saml config
|
||||||
nil,
|
nil,
|
||||||
nil,
|
nil,
|
||||||
@ -1626,6 +1687,8 @@ func Test_AppPrepare(t *testing.T) {
|
|||||||
AllowedOrigins: database.TextArray[string]{"https://redirect.to", "additional.origin"},
|
AllowedOrigins: database.TextArray[string]{"https://redirect.to", "additional.origin"},
|
||||||
SkipNativeAppSuccessPage: false,
|
SkipNativeAppSuccessPage: false,
|
||||||
BackChannelLogoutURI: "back.channel.logout.ch",
|
BackChannelLogoutURI: "back.channel.logout.ch",
|
||||||
|
LoginVersion: domain.LoginVersionUnspecified,
|
||||||
|
LoginBaseURI: nil,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -1671,6 +1734,8 @@ func Test_AppPrepare(t *testing.T) {
|
|||||||
database.TextArray[string]{"additional.origin"},
|
database.TextArray[string]{"additional.origin"},
|
||||||
false,
|
false,
|
||||||
"back.channel.logout.ch",
|
"back.channel.logout.ch",
|
||||||
|
domain.LoginVersionUnspecified,
|
||||||
|
nil,
|
||||||
// saml config
|
// saml config
|
||||||
nil,
|
nil,
|
||||||
nil,
|
nil,
|
||||||
@ -1709,6 +1774,8 @@ func Test_AppPrepare(t *testing.T) {
|
|||||||
AllowedOrigins: database.TextArray[string]{"https://redirect.to", "additional.origin"},
|
AllowedOrigins: database.TextArray[string]{"https://redirect.to", "additional.origin"},
|
||||||
SkipNativeAppSuccessPage: false,
|
SkipNativeAppSuccessPage: false,
|
||||||
BackChannelLogoutURI: "back.channel.logout.ch",
|
BackChannelLogoutURI: "back.channel.logout.ch",
|
||||||
|
LoginVersion: domain.LoginVersionUnspecified,
|
||||||
|
LoginBaseURI: nil,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -1754,6 +1821,8 @@ func Test_AppPrepare(t *testing.T) {
|
|||||||
database.TextArray[string]{"additional.origin"},
|
database.TextArray[string]{"additional.origin"},
|
||||||
false,
|
false,
|
||||||
"back.channel.logout.ch",
|
"back.channel.logout.ch",
|
||||||
|
domain.LoginVersionUnspecified,
|
||||||
|
nil,
|
||||||
// saml config
|
// saml config
|
||||||
nil,
|
nil,
|
||||||
nil,
|
nil,
|
||||||
@ -1792,6 +1861,8 @@ func Test_AppPrepare(t *testing.T) {
|
|||||||
AllowedOrigins: database.TextArray[string]{"https://redirect.to", "additional.origin"},
|
AllowedOrigins: database.TextArray[string]{"https://redirect.to", "additional.origin"},
|
||||||
SkipNativeAppSuccessPage: false,
|
SkipNativeAppSuccessPage: false,
|
||||||
BackChannelLogoutURI: "back.channel.logout.ch",
|
BackChannelLogoutURI: "back.channel.logout.ch",
|
||||||
|
LoginVersion: domain.LoginVersionUnspecified,
|
||||||
|
LoginBaseURI: nil,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -1837,6 +1908,8 @@ func Test_AppPrepare(t *testing.T) {
|
|||||||
database.TextArray[string]{"additional.origin"},
|
database.TextArray[string]{"additional.origin"},
|
||||||
false,
|
false,
|
||||||
"back.channel.logout.ch",
|
"back.channel.logout.ch",
|
||||||
|
domain.LoginVersionUnspecified,
|
||||||
|
nil,
|
||||||
// saml config
|
// saml config
|
||||||
nil,
|
nil,
|
||||||
nil,
|
nil,
|
||||||
@ -1875,6 +1948,8 @@ func Test_AppPrepare(t *testing.T) {
|
|||||||
AllowedOrigins: database.TextArray[string]{"https://redirect.to", "additional.origin"},
|
AllowedOrigins: database.TextArray[string]{"https://redirect.to", "additional.origin"},
|
||||||
SkipNativeAppSuccessPage: false,
|
SkipNativeAppSuccessPage: false,
|
||||||
BackChannelLogoutURI: "back.channel.logout.ch",
|
BackChannelLogoutURI: "back.channel.logout.ch",
|
||||||
|
LoginVersion: domain.LoginVersionUnspecified,
|
||||||
|
LoginBaseURI: nil,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -34,9 +34,9 @@ type AuthRequest struct {
|
|||||||
HintUserID *string
|
HintUserID *string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *AuthRequest) checkLoginClient(ctx context.Context) error {
|
func (a *AuthRequest) checkLoginClient(ctx context.Context, permissionCheck domain.PermissionCheck) error {
|
||||||
if uid := authz.GetCtxData(ctx).UserID; uid != a.LoginClient {
|
if uid := authz.GetCtxData(ctx).UserID; uid != a.LoginClient {
|
||||||
return zerrors.ThrowPermissionDenied(nil, "OIDCv2-aL0ag", "Errors.AuthRequest.WrongLoginClient")
|
return permissionCheck(ctx, domain.PermissionSessionRead, authz.GetInstance(ctx).InstanceID(), "")
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -89,7 +89,7 @@ func (q *Queries) AuthRequestByID(ctx context.Context, shouldTriggerBulk bool, i
|
|||||||
dst.UiLocales = locales
|
dst.UiLocales = locales
|
||||||
|
|
||||||
if checkLoginClient {
|
if checkLoginClient {
|
||||||
if err = dst.checkLoginClient(ctx); err != nil {
|
if err = dst.checkLoginClient(ctx, q.checkPermission); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package query
|
package query
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"database/sql"
|
"database/sql"
|
||||||
"database/sql/driver"
|
"database/sql/driver"
|
||||||
_ "embed"
|
_ "embed"
|
||||||
@ -45,11 +46,12 @@ func TestQueries_AuthRequestByID(t *testing.T) {
|
|||||||
checkLoginClient bool
|
checkLoginClient bool
|
||||||
}
|
}
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
args args
|
args args
|
||||||
expect sqlExpectation
|
expect sqlExpectation
|
||||||
want *AuthRequest
|
permissionCheck domain.PermissionCheck
|
||||||
wantErr error
|
want *AuthRequest
|
||||||
|
wantErr error
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "success, all values",
|
name: "success, all values",
|
||||||
@ -138,7 +140,7 @@ func TestQueries_AuthRequestByID(t *testing.T) {
|
|||||||
wantErr: zerrors.ThrowInternal(sql.ErrConnDone, "QUERY-Ou8ue", "Errors.Internal"),
|
wantErr: zerrors.ThrowInternal(sql.ErrConnDone, "QUERY-Ou8ue", "Errors.Internal"),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "wrong login client",
|
name: "wrong login client / not permitted",
|
||||||
args: args{
|
args: args{
|
||||||
shouldTriggerBulk: false,
|
shouldTriggerBulk: false,
|
||||||
id: "123",
|
id: "123",
|
||||||
@ -157,7 +159,47 @@ func TestQueries_AuthRequestByID(t *testing.T) {
|
|||||||
nil,
|
nil,
|
||||||
nil,
|
nil,
|
||||||
}, "123", "instanceID"),
|
}, "123", "instanceID"),
|
||||||
wantErr: zerrors.ThrowPermissionDeniedf(nil, "OIDCv2-aL0ag", "Errors.AuthRequest.WrongLoginClient"),
|
permissionCheck: func(ctx context.Context, permission, orgID, resourceID string) (err error) {
|
||||||
|
return zerrors.ThrowPermissionDenied(nil, "id", "not permitted")
|
||||||
|
},
|
||||||
|
wantErr: zerrors.ThrowPermissionDenied(nil, "id", "not permitted"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "other login client / permitted",
|
||||||
|
args: args{
|
||||||
|
shouldTriggerBulk: false,
|
||||||
|
id: "123",
|
||||||
|
checkLoginClient: true,
|
||||||
|
},
|
||||||
|
expect: mockQuery(expQuery, cols, []driver.Value{
|
||||||
|
"id",
|
||||||
|
testNow,
|
||||||
|
"otherLoginClient",
|
||||||
|
"clientID",
|
||||||
|
database.TextArray[string]{"a", "b", "c"},
|
||||||
|
"example.com",
|
||||||
|
database.NumberArray[domain.Prompt]{domain.PromptLogin, domain.PromptConsent},
|
||||||
|
database.TextArray[string]{"en", "fi"},
|
||||||
|
nil,
|
||||||
|
nil,
|
||||||
|
nil,
|
||||||
|
}, "123", "instanceID"),
|
||||||
|
permissionCheck: func(ctx context.Context, permission, orgID, resourceID string) (err error) {
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
want: &AuthRequest{
|
||||||
|
ID: "id",
|
||||||
|
CreationDate: testNow,
|
||||||
|
LoginClient: "otherLoginClient",
|
||||||
|
ClientID: "clientID",
|
||||||
|
Scope: []string{"a", "b", "c"},
|
||||||
|
RedirectURI: "example.com",
|
||||||
|
Prompt: []domain.Prompt{domain.PromptLogin, domain.PromptConsent},
|
||||||
|
UiLocales: []string{"en", "fi"},
|
||||||
|
LoginHint: nil,
|
||||||
|
MaxAge: nil,
|
||||||
|
HintUserID: nil,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
@ -168,6 +210,7 @@ func TestQueries_AuthRequestByID(t *testing.T) {
|
|||||||
DB: db,
|
DB: db,
|
||||||
Database: &prepareDB{},
|
Database: &prepareDB{},
|
||||||
},
|
},
|
||||||
|
checkPermission: tt.permissionCheck,
|
||||||
}
|
}
|
||||||
ctx := authz.NewMockContext("instanceID", "orgID", "loginClient")
|
ctx := authz.NewMockContext("instanceID", "orgID", "loginClient")
|
||||||
|
|
||||||
|
@ -21,6 +21,7 @@ type InstanceFeatures struct {
|
|||||||
OIDCSingleV1SessionTermination FeatureSource[bool]
|
OIDCSingleV1SessionTermination FeatureSource[bool]
|
||||||
DisableUserTokenEvent FeatureSource[bool]
|
DisableUserTokenEvent FeatureSource[bool]
|
||||||
EnableBackChannelLogout FeatureSource[bool]
|
EnableBackChannelLogout FeatureSource[bool]
|
||||||
|
LoginV2 FeatureSource[*feature.LoginV2]
|
||||||
}
|
}
|
||||||
|
|
||||||
func (q *Queries) GetInstanceFeatures(ctx context.Context, cascade bool) (_ *InstanceFeatures, err error) {
|
func (q *Queries) GetInstanceFeatures(ctx context.Context, cascade bool) (_ *InstanceFeatures, err error) {
|
||||||
|
@ -42,6 +42,8 @@ func (m *InstanceFeaturesReadModel) Reduce() (err error) {
|
|||||||
)
|
)
|
||||||
case *feature_v2.SetEvent[bool]:
|
case *feature_v2.SetEvent[bool]:
|
||||||
err = reduceInstanceFeatureSet(m.instance, e)
|
err = reduceInstanceFeatureSet(m.instance, e)
|
||||||
|
case *feature_v2.SetEvent[*feature.LoginV2]:
|
||||||
|
err = reduceInstanceFeatureSet(m.instance, e)
|
||||||
case *feature_v2.SetEvent[[]feature.ImprovedPerformanceType]:
|
case *feature_v2.SetEvent[[]feature.ImprovedPerformanceType]:
|
||||||
err = reduceInstanceFeatureSet(m.instance, e)
|
err = reduceInstanceFeatureSet(m.instance, e)
|
||||||
}
|
}
|
||||||
@ -72,6 +74,7 @@ func (m *InstanceFeaturesReadModel) Query() *eventstore.SearchQueryBuilder {
|
|||||||
feature_v2.InstanceOIDCSingleV1SessionTerminationEventType,
|
feature_v2.InstanceOIDCSingleV1SessionTerminationEventType,
|
||||||
feature_v2.InstanceDisableUserTokenEvent,
|
feature_v2.InstanceDisableUserTokenEvent,
|
||||||
feature_v2.InstanceEnableBackChannelLogout,
|
feature_v2.InstanceEnableBackChannelLogout,
|
||||||
|
feature_v2.InstanceLoginVersion,
|
||||||
).
|
).
|
||||||
Builder().ResourceOwner(m.ResourceOwner)
|
Builder().ResourceOwner(m.ResourceOwner)
|
||||||
}
|
}
|
||||||
@ -98,6 +101,7 @@ func (m *InstanceFeaturesReadModel) populateFromSystem() bool {
|
|||||||
m.instance.OIDCSingleV1SessionTermination = m.system.OIDCSingleV1SessionTermination
|
m.instance.OIDCSingleV1SessionTermination = m.system.OIDCSingleV1SessionTermination
|
||||||
m.instance.DisableUserTokenEvent = m.system.DisableUserTokenEvent
|
m.instance.DisableUserTokenEvent = m.system.DisableUserTokenEvent
|
||||||
m.instance.EnableBackChannelLogout = m.system.EnableBackChannelLogout
|
m.instance.EnableBackChannelLogout = m.system.EnableBackChannelLogout
|
||||||
|
m.instance.LoginV2 = m.system.LoginV2
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -133,6 +137,8 @@ func reduceInstanceFeatureSet[T any](features *InstanceFeatures, event *feature_
|
|||||||
features.DisableUserTokenEvent.set(level, event.Value)
|
features.DisableUserTokenEvent.set(level, event.Value)
|
||||||
case feature.KeyEnableBackChannelLogout:
|
case feature.KeyEnableBackChannelLogout:
|
||||||
features.EnableBackChannelLogout.set(level, event.Value)
|
features.EnableBackChannelLogout.set(level, event.Value)
|
||||||
|
case feature.KeyLoginV2:
|
||||||
|
features.LoginV2.set(level, event.Value)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -4,7 +4,9 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"database/sql"
|
"database/sql"
|
||||||
_ "embed"
|
_ "embed"
|
||||||
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
|
"net/url"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/zitadel/zitadel/internal/api/authz"
|
"github.com/zitadel/zitadel/internal/api/authz"
|
||||||
@ -39,10 +41,32 @@ type OIDCClient struct {
|
|||||||
PublicKeys map[string][]byte `json:"public_keys,omitempty"`
|
PublicKeys map[string][]byte `json:"public_keys,omitempty"`
|
||||||
ProjectID string `json:"project_id,omitempty"`
|
ProjectID string `json:"project_id,omitempty"`
|
||||||
ProjectRoleAssertion bool `json:"project_role_assertion,omitempty"`
|
ProjectRoleAssertion bool `json:"project_role_assertion,omitempty"`
|
||||||
|
LoginVersion domain.LoginVersion `json:"login_version,omitempty"`
|
||||||
|
LoginBaseURI *URL `json:"login_base_uri,omitempty"`
|
||||||
ProjectRoleKeys []string `json:"project_role_keys,omitempty"`
|
ProjectRoleKeys []string `json:"project_role_keys,omitempty"`
|
||||||
Settings *OIDCSettings `json:"settings,omitempty"`
|
Settings *OIDCSettings `json:"settings,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type URL url.URL
|
||||||
|
|
||||||
|
func (c *URL) URL() *url.URL {
|
||||||
|
return (*url.URL)(c)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *URL) UnmarshalJSON(src []byte) error {
|
||||||
|
var s string
|
||||||
|
err := json.Unmarshal(src, &s)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
u, err := url.Parse(s)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
*c = URL(*u)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
//go:embed oidc_client_by_id.sql
|
//go:embed oidc_client_by_id.sql
|
||||||
var oidcClientQuery string
|
var oidcClientQuery string
|
||||||
|
|
||||||
@ -59,7 +83,13 @@ func (q *Queries) ActiveOIDCClientByID(ctx context.Context, clientID string, get
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, zerrors.ThrowInternal(err, "QUERY-ieR7R", "Errors.Internal")
|
return nil, zerrors.ThrowInternal(err, "QUERY-ieR7R", "Errors.Internal")
|
||||||
}
|
}
|
||||||
if authz.GetInstance(ctx).ConsoleClientID() == clientID {
|
instance := authz.GetInstance(ctx)
|
||||||
|
loginV2 := instance.Features().LoginV2
|
||||||
|
if loginV2.Required {
|
||||||
|
client.LoginVersion = domain.LoginVersion2
|
||||||
|
client.LoginBaseURI = (*URL)(loginV2.BaseURI)
|
||||||
|
}
|
||||||
|
if instance.ConsoleClientID() == clientID {
|
||||||
client.RedirectURIs = append(client.RedirectURIs, http_util.DomainContext(ctx).Origin()+path.RedirectPath)
|
client.RedirectURIs = append(client.RedirectURIs, http_util.DomainContext(ctx).Origin()+path.RedirectPath)
|
||||||
client.PostLogoutRedirectURIs = append(client.PostLogoutRedirectURIs, http_util.DomainContext(ctx).Origin()+path.PostLogoutPath)
|
client.PostLogoutRedirectURIs = append(client.PostLogoutRedirectURIs, http_util.DomainContext(ctx).Origin()+path.PostLogoutPath)
|
||||||
}
|
}
|
||||||
|
@ -3,7 +3,8 @@ with client as (
|
|||||||
c.app_id, a.state, c.client_id, c.back_channel_logout_uri, c.client_secret, c.redirect_uris, c.response_types,
|
c.app_id, a.state, c.client_id, c.back_channel_logout_uri, c.client_secret, c.redirect_uris, c.response_types,
|
||||||
c.grant_types, c.application_type, c.auth_method_type, c.post_logout_redirect_uris, c.is_dev_mode,
|
c.grant_types, c.application_type, c.auth_method_type, c.post_logout_redirect_uris, c.is_dev_mode,
|
||||||
c.access_token_type, c.access_token_role_assertion, c.id_token_role_assertion,
|
c.access_token_type, c.access_token_role_assertion, c.id_token_role_assertion,
|
||||||
c.id_token_userinfo_assertion, c.clock_skew, c.additional_origins, a.project_id, p.project_role_assertion
|
c.id_token_userinfo_assertion, c.clock_skew, c.additional_origins, a.project_id, p.project_role_assertion,
|
||||||
|
c.login_version, c.login_base_uri
|
||||||
from projections.apps7_oidc_configs c
|
from projections.apps7_oidc_configs c
|
||||||
join projections.apps7 a on a.id = c.app_id and a.instance_id = c.instance_id and a.state = 1
|
join projections.apps7 a on a.id = c.app_id and a.instance_id = c.instance_id and a.state = 1
|
||||||
join projections.projects4 p on p.id = a.project_id and p.instance_id = a.instance_id and p.state = 1
|
join projections.projects4 p on p.id = a.project_id and p.instance_id = a.instance_id and p.state = 1
|
||||||
|
@ -59,6 +59,8 @@ const (
|
|||||||
AppOIDCConfigColumnAdditionalOrigins = "additional_origins"
|
AppOIDCConfigColumnAdditionalOrigins = "additional_origins"
|
||||||
AppOIDCConfigColumnSkipNativeAppSuccessPage = "skip_native_app_success_page"
|
AppOIDCConfigColumnSkipNativeAppSuccessPage = "skip_native_app_success_page"
|
||||||
AppOIDCConfigColumnBackChannelLogoutURI = "back_channel_logout_uri"
|
AppOIDCConfigColumnBackChannelLogoutURI = "back_channel_logout_uri"
|
||||||
|
AppOIDCConfigColumnLoginVersion = "login_version"
|
||||||
|
AppOIDCConfigColumnLoginBaseURI = "login_base_uri"
|
||||||
|
|
||||||
appSAMLTableSuffix = "saml_configs"
|
appSAMLTableSuffix = "saml_configs"
|
||||||
AppSAMLConfigColumnAppID = "app_id"
|
AppSAMLConfigColumnAppID = "app_id"
|
||||||
@ -127,6 +129,8 @@ func (*appProjection) Init() *old_handler.Check {
|
|||||||
handler.NewColumn(AppOIDCConfigColumnAdditionalOrigins, handler.ColumnTypeTextArray, handler.Nullable()),
|
handler.NewColumn(AppOIDCConfigColumnAdditionalOrigins, handler.ColumnTypeTextArray, handler.Nullable()),
|
||||||
handler.NewColumn(AppOIDCConfigColumnSkipNativeAppSuccessPage, handler.ColumnTypeBool, handler.Default(false)),
|
handler.NewColumn(AppOIDCConfigColumnSkipNativeAppSuccessPage, handler.ColumnTypeBool, handler.Default(false)),
|
||||||
handler.NewColumn(AppOIDCConfigColumnBackChannelLogoutURI, handler.ColumnTypeText, handler.Nullable()),
|
handler.NewColumn(AppOIDCConfigColumnBackChannelLogoutURI, handler.ColumnTypeText, handler.Nullable()),
|
||||||
|
handler.NewColumn(AppOIDCConfigColumnLoginVersion, handler.ColumnTypeEnum, handler.Nullable()),
|
||||||
|
handler.NewColumn(AppOIDCConfigColumnLoginBaseURI, handler.ColumnTypeText, handler.Nullable()),
|
||||||
},
|
},
|
||||||
handler.NewPrimaryKey(AppOIDCConfigColumnInstanceID, AppOIDCConfigColumnAppID),
|
handler.NewPrimaryKey(AppOIDCConfigColumnInstanceID, AppOIDCConfigColumnAppID),
|
||||||
appOIDCTableSuffix,
|
appOIDCTableSuffix,
|
||||||
@ -503,6 +507,8 @@ func (p *appProjection) reduceOIDCConfigAdded(event eventstore.Event) (*handler.
|
|||||||
handler.NewCol(AppOIDCConfigColumnAdditionalOrigins, database.TextArray[string](e.AdditionalOrigins)),
|
handler.NewCol(AppOIDCConfigColumnAdditionalOrigins, database.TextArray[string](e.AdditionalOrigins)),
|
||||||
handler.NewCol(AppOIDCConfigColumnSkipNativeAppSuccessPage, e.SkipNativeAppSuccessPage),
|
handler.NewCol(AppOIDCConfigColumnSkipNativeAppSuccessPage, e.SkipNativeAppSuccessPage),
|
||||||
handler.NewCol(AppOIDCConfigColumnBackChannelLogoutURI, e.BackChannelLogoutURI),
|
handler.NewCol(AppOIDCConfigColumnBackChannelLogoutURI, e.BackChannelLogoutURI),
|
||||||
|
handler.NewCol(AppOIDCConfigColumnLoginVersion, e.LoginVersion),
|
||||||
|
handler.NewCol(AppOIDCConfigColumnLoginBaseURI, e.LoginBaseURI),
|
||||||
},
|
},
|
||||||
handler.WithTableSuffix(appOIDCTableSuffix),
|
handler.WithTableSuffix(appOIDCTableSuffix),
|
||||||
),
|
),
|
||||||
@ -525,7 +531,7 @@ func (p *appProjection) reduceOIDCConfigChanged(event eventstore.Event) (*handle
|
|||||||
return nil, zerrors.ThrowInvalidArgumentf(nil, "HANDL-GNHU1", "reduce.wrong.event.type %s", project.OIDCConfigChangedType)
|
return nil, zerrors.ThrowInvalidArgumentf(nil, "HANDL-GNHU1", "reduce.wrong.event.type %s", project.OIDCConfigChangedType)
|
||||||
}
|
}
|
||||||
|
|
||||||
cols := make([]handler.Column, 0, 16)
|
cols := make([]handler.Column, 0, 18)
|
||||||
if e.Version != nil {
|
if e.Version != nil {
|
||||||
cols = append(cols, handler.NewCol(AppOIDCConfigColumnVersion, *e.Version))
|
cols = append(cols, handler.NewCol(AppOIDCConfigColumnVersion, *e.Version))
|
||||||
}
|
}
|
||||||
@ -574,6 +580,12 @@ func (p *appProjection) reduceOIDCConfigChanged(event eventstore.Event) (*handle
|
|||||||
if e.BackChannelLogoutURI != nil {
|
if e.BackChannelLogoutURI != nil {
|
||||||
cols = append(cols, handler.NewCol(AppOIDCConfigColumnBackChannelLogoutURI, *e.BackChannelLogoutURI))
|
cols = append(cols, handler.NewCol(AppOIDCConfigColumnBackChannelLogoutURI, *e.BackChannelLogoutURI))
|
||||||
}
|
}
|
||||||
|
if e.LoginVersion != nil {
|
||||||
|
cols = append(cols, handler.NewCol(AppOIDCConfigColumnLoginVersion, *e.LoginVersion))
|
||||||
|
}
|
||||||
|
if e.LoginBaseURI != nil {
|
||||||
|
cols = append(cols, handler.NewCol(AppOIDCConfigColumnLoginBaseURI, *e.LoginBaseURI))
|
||||||
|
}
|
||||||
|
|
||||||
if len(cols) == 0 {
|
if len(cols) == 0 {
|
||||||
return handler.NewNoOpStatement(e), nil
|
return handler.NewNoOpStatement(e), nil
|
||||||
|
@ -559,7 +559,9 @@ func TestAppProjection_reduces(t *testing.T) {
|
|||||||
"clockSkew": 1000,
|
"clockSkew": 1000,
|
||||||
"additionalOrigins": ["origin.one.ch", "origin.two.ch"],
|
"additionalOrigins": ["origin.one.ch", "origin.two.ch"],
|
||||||
"skipNativeAppSuccessPage": true,
|
"skipNativeAppSuccessPage": true,
|
||||||
"backChannelLogoutURI": "back.channel.one.ch"
|
"backChannelLogoutURI": "back.channel.one.ch",
|
||||||
|
"loginVersion": 2,
|
||||||
|
"loginBaseURI": "https://login.ch/"
|
||||||
}`),
|
}`),
|
||||||
), project.OIDCConfigAddedEventMapper),
|
), project.OIDCConfigAddedEventMapper),
|
||||||
},
|
},
|
||||||
@ -570,7 +572,7 @@ func TestAppProjection_reduces(t *testing.T) {
|
|||||||
executer: &testExecuter{
|
executer: &testExecuter{
|
||||||
executions: []execution{
|
executions: []execution{
|
||||||
{
|
{
|
||||||
expectedStmt: "INSERT INTO projections.apps7_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, back_channel_logout_uri) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20)",
|
expectedStmt: "INSERT INTO projections.apps7_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, back_channel_logout_uri, login_version, login_base_uri) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21, $22)",
|
||||||
expectedArgs: []interface{}{
|
expectedArgs: []interface{}{
|
||||||
"app-id",
|
"app-id",
|
||||||
"instance-id",
|
"instance-id",
|
||||||
@ -592,6 +594,8 @@ func TestAppProjection_reduces(t *testing.T) {
|
|||||||
database.TextArray[string]{"origin.one.ch", "origin.two.ch"},
|
database.TextArray[string]{"origin.one.ch", "origin.two.ch"},
|
||||||
true,
|
true,
|
||||||
"back.channel.one.ch",
|
"back.channel.one.ch",
|
||||||
|
domain.LoginVersion2,
|
||||||
|
"https://login.ch/",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -633,7 +637,9 @@ func TestAppProjection_reduces(t *testing.T) {
|
|||||||
"clockSkew": 1000,
|
"clockSkew": 1000,
|
||||||
"additionalOrigins": ["origin.one.ch", "origin.two.ch"],
|
"additionalOrigins": ["origin.one.ch", "origin.two.ch"],
|
||||||
"skipNativeAppSuccessPage": true,
|
"skipNativeAppSuccessPage": true,
|
||||||
"backChannelLogoutURI": "back.channel.one.ch"
|
"backChannelLogoutURI": "back.channel.one.ch",
|
||||||
|
"loginVersion": 2,
|
||||||
|
"loginBaseURI": "https://login.ch/"
|
||||||
}`),
|
}`),
|
||||||
), project.OIDCConfigAddedEventMapper),
|
), project.OIDCConfigAddedEventMapper),
|
||||||
},
|
},
|
||||||
@ -644,7 +650,7 @@ func TestAppProjection_reduces(t *testing.T) {
|
|||||||
executer: &testExecuter{
|
executer: &testExecuter{
|
||||||
executions: []execution{
|
executions: []execution{
|
||||||
{
|
{
|
||||||
expectedStmt: "INSERT INTO projections.apps7_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, back_channel_logout_uri) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20)",
|
expectedStmt: "INSERT INTO projections.apps7_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, back_channel_logout_uri, login_version, login_base_uri) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21, $22)",
|
||||||
expectedArgs: []interface{}{
|
expectedArgs: []interface{}{
|
||||||
"app-id",
|
"app-id",
|
||||||
"instance-id",
|
"instance-id",
|
||||||
@ -666,6 +672,8 @@ func TestAppProjection_reduces(t *testing.T) {
|
|||||||
database.TextArray[string]{"origin.one.ch", "origin.two.ch"},
|
database.TextArray[string]{"origin.one.ch", "origin.two.ch"},
|
||||||
true,
|
true,
|
||||||
"back.channel.one.ch",
|
"back.channel.one.ch",
|
||||||
|
domain.LoginVersion2,
|
||||||
|
"https://login.ch/",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -705,7 +713,8 @@ func TestAppProjection_reduces(t *testing.T) {
|
|||||||
"clockSkew": 1000,
|
"clockSkew": 1000,
|
||||||
"additionalOrigins": ["origin.one.ch", "origin.two.ch"],
|
"additionalOrigins": ["origin.one.ch", "origin.two.ch"],
|
||||||
"skipNativeAppSuccessPage": true,
|
"skipNativeAppSuccessPage": true,
|
||||||
"backChannelLogoutURI": "back.channel.one.ch"
|
"backChannelLogoutURI": "back.channel.one.ch",
|
||||||
|
"loginVersion": 2
|
||||||
}`),
|
}`),
|
||||||
), project.OIDCConfigChangedEventMapper),
|
), project.OIDCConfigChangedEventMapper),
|
||||||
},
|
},
|
||||||
@ -716,7 +725,7 @@ func TestAppProjection_reduces(t *testing.T) {
|
|||||||
executer: &testExecuter{
|
executer: &testExecuter{
|
||||||
executions: []execution{
|
executions: []execution{
|
||||||
{
|
{
|
||||||
expectedStmt: "UPDATE projections.apps7_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, back_channel_logout_uri) = ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16) WHERE (app_id = $17) AND (instance_id = $18)",
|
expectedStmt: "UPDATE projections.apps7_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, back_channel_logout_uri, login_version) = ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17) WHERE (app_id = $18) AND (instance_id = $19)",
|
||||||
expectedArgs: []interface{}{
|
expectedArgs: []interface{}{
|
||||||
domain.OIDCVersionV1,
|
domain.OIDCVersionV1,
|
||||||
database.TextArray[string]{"redirect.one.ch", "redirect.two.ch"},
|
database.TextArray[string]{"redirect.one.ch", "redirect.two.ch"},
|
||||||
@ -734,6 +743,7 @@ func TestAppProjection_reduces(t *testing.T) {
|
|||||||
database.TextArray[string]{"origin.one.ch", "origin.two.ch"},
|
database.TextArray[string]{"origin.one.ch", "origin.two.ch"},
|
||||||
true,
|
true,
|
||||||
"back.channel.one.ch",
|
"back.channel.one.ch",
|
||||||
|
domain.LoginVersion2,
|
||||||
"app-id",
|
"app-id",
|
||||||
"instance-id",
|
"instance-id",
|
||||||
},
|
},
|
||||||
|
@ -108,6 +108,10 @@ func (*instanceFeatureProjection) Reducers() []handler.AggregateReducer {
|
|||||||
Event: feature_v2.InstanceEnableBackChannelLogout,
|
Event: feature_v2.InstanceEnableBackChannelLogout,
|
||||||
Reduce: reduceInstanceSetFeature[bool],
|
Reduce: reduceInstanceSetFeature[bool],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
Event: feature_v2.InstanceLoginVersion,
|
||||||
|
Reduce: reduceInstanceSetFeature[*feature.LoginV2],
|
||||||
|
},
|
||||||
{
|
{
|
||||||
Event: instance.InstanceRemovedEventType,
|
Event: instance.InstanceRemovedEventType,
|
||||||
Reduce: reduceInstanceRemovedHelper(InstanceDomainInstanceIDCol),
|
Reduce: reduceInstanceRemovedHelper(InstanceDomainInstanceIDCol),
|
||||||
|
@ -88,6 +88,10 @@ func (*systemFeatureProjection) Reducers() []handler.AggregateReducer {
|
|||||||
Event: feature_v2.SystemEnableBackChannelLogout,
|
Event: feature_v2.SystemEnableBackChannelLogout,
|
||||||
Reduce: reduceSystemSetFeature[bool],
|
Reduce: reduceSystemSetFeature[bool],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
Event: feature_v2.SystemLoginVersion,
|
||||||
|
Reduce: reduceSystemSetFeature[*feature.LoginV2],
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
}
|
}
|
||||||
|
@ -30,6 +30,7 @@ type SystemFeatures struct {
|
|||||||
OIDCSingleV1SessionTermination FeatureSource[bool]
|
OIDCSingleV1SessionTermination FeatureSource[bool]
|
||||||
DisableUserTokenEvent FeatureSource[bool]
|
DisableUserTokenEvent FeatureSource[bool]
|
||||||
EnableBackChannelLogout FeatureSource[bool]
|
EnableBackChannelLogout FeatureSource[bool]
|
||||||
|
LoginV2 FeatureSource[*feature.LoginV2]
|
||||||
}
|
}
|
||||||
|
|
||||||
func (q *Queries) GetSystemFeatures(ctx context.Context) (_ *SystemFeatures, err error) {
|
func (q *Queries) GetSystemFeatures(ctx context.Context) (_ *SystemFeatures, err error) {
|
||||||
|
@ -32,6 +32,11 @@ func (m *SystemFeaturesReadModel) Reduce() error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
case *feature_v2.SetEvent[*feature.LoginV2]:
|
||||||
|
err := reduceSystemFeatureSet(m.system, e)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
case *feature_v2.SetEvent[[]feature.ImprovedPerformanceType]:
|
case *feature_v2.SetEvent[[]feature.ImprovedPerformanceType]:
|
||||||
err := reduceSystemFeatureSet(m.system, e)
|
err := reduceSystemFeatureSet(m.system, e)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -60,6 +65,7 @@ func (m *SystemFeaturesReadModel) Query() *eventstore.SearchQueryBuilder {
|
|||||||
feature_v2.SystemOIDCSingleV1SessionTerminationEventType,
|
feature_v2.SystemOIDCSingleV1SessionTerminationEventType,
|
||||||
feature_v2.SystemDisableUserTokenEvent,
|
feature_v2.SystemDisableUserTokenEvent,
|
||||||
feature_v2.SystemEnableBackChannelLogout,
|
feature_v2.SystemEnableBackChannelLogout,
|
||||||
|
feature_v2.SystemLoginVersion,
|
||||||
).
|
).
|
||||||
Builder().ResourceOwner(m.ResourceOwner)
|
Builder().ResourceOwner(m.ResourceOwner)
|
||||||
}
|
}
|
||||||
@ -97,6 +103,8 @@ func reduceSystemFeatureSet[T any](features *SystemFeatures, event *feature_v2.S
|
|||||||
features.DisableUserTokenEvent.set(level, event.Value)
|
features.DisableUserTokenEvent.set(level, event.Value)
|
||||||
case feature.KeyEnableBackChannelLogout:
|
case feature.KeyEnableBackChannelLogout:
|
||||||
features.EnableBackChannelLogout.set(level, event.Value)
|
features.EnableBackChannelLogout.set(level, event.Value)
|
||||||
|
case feature.KeyLoginV2:
|
||||||
|
features.LoginV2.set(level, event.Value)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -17,6 +17,7 @@ func init() {
|
|||||||
eventstore.RegisterFilterEventMapper(AggregateType, SystemOIDCSingleV1SessionTerminationEventType, eventstore.GenericEventMapper[SetEvent[bool]])
|
eventstore.RegisterFilterEventMapper(AggregateType, SystemOIDCSingleV1SessionTerminationEventType, eventstore.GenericEventMapper[SetEvent[bool]])
|
||||||
eventstore.RegisterFilterEventMapper(AggregateType, SystemDisableUserTokenEvent, eventstore.GenericEventMapper[SetEvent[bool]])
|
eventstore.RegisterFilterEventMapper(AggregateType, SystemDisableUserTokenEvent, eventstore.GenericEventMapper[SetEvent[bool]])
|
||||||
eventstore.RegisterFilterEventMapper(AggregateType, SystemEnableBackChannelLogout, eventstore.GenericEventMapper[SetEvent[bool]])
|
eventstore.RegisterFilterEventMapper(AggregateType, SystemEnableBackChannelLogout, eventstore.GenericEventMapper[SetEvent[bool]])
|
||||||
|
eventstore.RegisterFilterEventMapper(AggregateType, SystemLoginVersion, eventstore.GenericEventMapper[SetEvent[*feature.LoginV2]])
|
||||||
|
|
||||||
eventstore.RegisterFilterEventMapper(AggregateType, InstanceResetEventType, eventstore.GenericEventMapper[ResetEvent])
|
eventstore.RegisterFilterEventMapper(AggregateType, InstanceResetEventType, eventstore.GenericEventMapper[ResetEvent])
|
||||||
eventstore.RegisterFilterEventMapper(AggregateType, InstanceLoginDefaultOrgEventType, eventstore.GenericEventMapper[SetEvent[bool]])
|
eventstore.RegisterFilterEventMapper(AggregateType, InstanceLoginDefaultOrgEventType, eventstore.GenericEventMapper[SetEvent[bool]])
|
||||||
@ -31,4 +32,5 @@ func init() {
|
|||||||
eventstore.RegisterFilterEventMapper(AggregateType, InstanceOIDCSingleV1SessionTerminationEventType, eventstore.GenericEventMapper[SetEvent[bool]])
|
eventstore.RegisterFilterEventMapper(AggregateType, InstanceOIDCSingleV1SessionTerminationEventType, eventstore.GenericEventMapper[SetEvent[bool]])
|
||||||
eventstore.RegisterFilterEventMapper(AggregateType, InstanceDisableUserTokenEvent, eventstore.GenericEventMapper[SetEvent[bool]])
|
eventstore.RegisterFilterEventMapper(AggregateType, InstanceDisableUserTokenEvent, eventstore.GenericEventMapper[SetEvent[bool]])
|
||||||
eventstore.RegisterFilterEventMapper(AggregateType, InstanceEnableBackChannelLogout, eventstore.GenericEventMapper[SetEvent[bool]])
|
eventstore.RegisterFilterEventMapper(AggregateType, InstanceEnableBackChannelLogout, eventstore.GenericEventMapper[SetEvent[bool]])
|
||||||
|
eventstore.RegisterFilterEventMapper(AggregateType, InstanceLoginVersion, eventstore.GenericEventMapper[SetEvent[*feature.LoginV2]])
|
||||||
}
|
}
|
||||||
|
@ -22,6 +22,7 @@ var (
|
|||||||
SystemOIDCSingleV1SessionTerminationEventType = setEventTypeFromFeature(feature.LevelSystem, feature.KeyOIDCSingleV1SessionTermination)
|
SystemOIDCSingleV1SessionTerminationEventType = setEventTypeFromFeature(feature.LevelSystem, feature.KeyOIDCSingleV1SessionTermination)
|
||||||
SystemDisableUserTokenEvent = setEventTypeFromFeature(feature.LevelSystem, feature.KeyDisableUserTokenEvent)
|
SystemDisableUserTokenEvent = setEventTypeFromFeature(feature.LevelSystem, feature.KeyDisableUserTokenEvent)
|
||||||
SystemEnableBackChannelLogout = setEventTypeFromFeature(feature.LevelSystem, feature.KeyEnableBackChannelLogout)
|
SystemEnableBackChannelLogout = setEventTypeFromFeature(feature.LevelSystem, feature.KeyEnableBackChannelLogout)
|
||||||
|
SystemLoginVersion = setEventTypeFromFeature(feature.LevelSystem, feature.KeyLoginV2)
|
||||||
|
|
||||||
InstanceResetEventType = resetEventTypeFromFeature(feature.LevelInstance)
|
InstanceResetEventType = resetEventTypeFromFeature(feature.LevelInstance)
|
||||||
InstanceLoginDefaultOrgEventType = setEventTypeFromFeature(feature.LevelInstance, feature.KeyLoginDefaultOrg)
|
InstanceLoginDefaultOrgEventType = setEventTypeFromFeature(feature.LevelInstance, feature.KeyLoginDefaultOrg)
|
||||||
@ -36,6 +37,7 @@ var (
|
|||||||
InstanceOIDCSingleV1SessionTerminationEventType = setEventTypeFromFeature(feature.LevelInstance, feature.KeyOIDCSingleV1SessionTermination)
|
InstanceOIDCSingleV1SessionTerminationEventType = setEventTypeFromFeature(feature.LevelInstance, feature.KeyOIDCSingleV1SessionTermination)
|
||||||
InstanceDisableUserTokenEvent = setEventTypeFromFeature(feature.LevelInstance, feature.KeyDisableUserTokenEvent)
|
InstanceDisableUserTokenEvent = setEventTypeFromFeature(feature.LevelInstance, feature.KeyDisableUserTokenEvent)
|
||||||
InstanceEnableBackChannelLogout = setEventTypeFromFeature(feature.LevelInstance, feature.KeyEnableBackChannelLogout)
|
InstanceEnableBackChannelLogout = setEventTypeFromFeature(feature.LevelInstance, feature.KeyEnableBackChannelLogout)
|
||||||
|
InstanceLoginVersion = setEventTypeFromFeature(feature.LevelInstance, feature.KeyLoginV2)
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -44,6 +44,8 @@ type OIDCConfigAddedEvent struct {
|
|||||||
AdditionalOrigins []string `json:"additionalOrigins,omitempty"`
|
AdditionalOrigins []string `json:"additionalOrigins,omitempty"`
|
||||||
SkipNativeAppSuccessPage bool `json:"skipNativeAppSuccessPage,omitempty"`
|
SkipNativeAppSuccessPage bool `json:"skipNativeAppSuccessPage,omitempty"`
|
||||||
BackChannelLogoutURI string `json:"backChannelLogoutURI,omitempty"`
|
BackChannelLogoutURI string `json:"backChannelLogoutURI,omitempty"`
|
||||||
|
LoginVersion domain.LoginVersion `json:"loginVersion,omitempty"`
|
||||||
|
LoginBaseURI string `json:"loginBaseURI,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *OIDCConfigAddedEvent) Payload() interface{} {
|
func (e *OIDCConfigAddedEvent) Payload() interface{} {
|
||||||
@ -76,6 +78,8 @@ func NewOIDCConfigAddedEvent(
|
|||||||
additionalOrigins []string,
|
additionalOrigins []string,
|
||||||
skipNativeAppSuccessPage bool,
|
skipNativeAppSuccessPage bool,
|
||||||
backChannelLogoutURI string,
|
backChannelLogoutURI string,
|
||||||
|
loginVersion domain.LoginVersion,
|
||||||
|
loginBaseURI string,
|
||||||
) *OIDCConfigAddedEvent {
|
) *OIDCConfigAddedEvent {
|
||||||
return &OIDCConfigAddedEvent{
|
return &OIDCConfigAddedEvent{
|
||||||
BaseEvent: *eventstore.NewBaseEventForPush(
|
BaseEvent: *eventstore.NewBaseEventForPush(
|
||||||
@ -102,6 +106,8 @@ func NewOIDCConfigAddedEvent(
|
|||||||
AdditionalOrigins: additionalOrigins,
|
AdditionalOrigins: additionalOrigins,
|
||||||
SkipNativeAppSuccessPage: skipNativeAppSuccessPage,
|
SkipNativeAppSuccessPage: skipNativeAppSuccessPage,
|
||||||
BackChannelLogoutURI: backChannelLogoutURI,
|
BackChannelLogoutURI: backChannelLogoutURI,
|
||||||
|
LoginVersion: loginVersion,
|
||||||
|
LoginBaseURI: loginBaseURI,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -190,7 +196,13 @@ func (e *OIDCConfigAddedEvent) Validate(cmd eventstore.Command) bool {
|
|||||||
if e.SkipNativeAppSuccessPage != c.SkipNativeAppSuccessPage {
|
if e.SkipNativeAppSuccessPage != c.SkipNativeAppSuccessPage {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
return e.BackChannelLogoutURI == c.BackChannelLogoutURI
|
if e.BackChannelLogoutURI != c.BackChannelLogoutURI {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if e.LoginVersion != c.LoginVersion {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return e.LoginBaseURI == c.LoginBaseURI
|
||||||
}
|
}
|
||||||
|
|
||||||
func OIDCConfigAddedEventMapper(event eventstore.Event) (eventstore.Event, error) {
|
func OIDCConfigAddedEventMapper(event eventstore.Event) (eventstore.Event, error) {
|
||||||
@ -226,6 +238,8 @@ type OIDCConfigChangedEvent struct {
|
|||||||
AdditionalOrigins *[]string `json:"additionalOrigins,omitempty"`
|
AdditionalOrigins *[]string `json:"additionalOrigins,omitempty"`
|
||||||
SkipNativeAppSuccessPage *bool `json:"skipNativeAppSuccessPage,omitempty"`
|
SkipNativeAppSuccessPage *bool `json:"skipNativeAppSuccessPage,omitempty"`
|
||||||
BackChannelLogoutURI *string `json:"backChannelLogoutURI,omitempty"`
|
BackChannelLogoutURI *string `json:"backChannelLogoutURI,omitempty"`
|
||||||
|
LoginVersion *domain.LoginVersion `json:"loginVersion,omitempty"`
|
||||||
|
LoginBaseURI *string `json:"loginBaseURI,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *OIDCConfigChangedEvent) Payload() interface{} {
|
func (e *OIDCConfigChangedEvent) Payload() interface{} {
|
||||||
@ -358,6 +372,18 @@ func ChangeBackChannelLogoutURI(backChannelLogoutURI string) func(event *OIDCCon
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func ChangeLoginVersion(loginVersion domain.LoginVersion) func(event *OIDCConfigChangedEvent) {
|
||||||
|
return func(e *OIDCConfigChangedEvent) {
|
||||||
|
e.LoginVersion = &loginVersion
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ChangeLoginBaseURI(loginBaseURI string) func(event *OIDCConfigChangedEvent) {
|
||||||
|
return func(e *OIDCConfigChangedEvent) {
|
||||||
|
e.LoginBaseURI = &loginBaseURI
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func OIDCConfigChangedEventMapper(event eventstore.Event) (eventstore.Event, error) {
|
func OIDCConfigChangedEventMapper(event eventstore.Event) (eventstore.Event, error) {
|
||||||
e := &OIDCConfigChangedEvent{
|
e := &OIDCConfigChangedEvent{
|
||||||
BaseEvent: *eventstore.BaseEventFromRepo(event),
|
BaseEvent: *eventstore.BaseEventFromRepo(event),
|
||||||
|
@ -174,6 +174,11 @@ message OIDCConfig {
|
|||||||
description: "ZITADEL will use this URI to notify the application about terminated session according to the OIDC Back-Channel Logout (https://openid.net/specs/openid-connect-backchannel-1_0.html)";
|
description: "ZITADEL will use this URI to notify the application about terminated session according to the OIDC Back-Channel Logout (https://openid.net/specs/openid-connect-backchannel-1_0.html)";
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
LoginVersion login_version = 22 [
|
||||||
|
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||||
|
description: "Specify the preferred login UI, where the user is redirected to for authentication. If unset, the login UI is chosen by the instance default.";
|
||||||
|
}
|
||||||
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
enum OIDCResponseType {
|
enum OIDCResponseType {
|
||||||
@ -239,3 +244,17 @@ message APIConfig {
|
|||||||
}
|
}
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
message LoginVersion {
|
||||||
|
oneof version {
|
||||||
|
LoginV1 login_v1 = 1;
|
||||||
|
LoginV2 login_v2 = 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
message LoginV1 {}
|
||||||
|
|
||||||
|
message LoginV2 {
|
||||||
|
// Optionally specify a base uri of the login UI. If unspecified the default URI will be used.
|
||||||
|
optional string base_uri = 1;
|
||||||
|
}
|
@ -49,6 +49,16 @@ message ImprovedPerformanceFeatureFlag {
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
message LoginV2FeatureFlag {
|
||||||
|
bool required = 1;
|
||||||
|
optional string base_uri = 2;
|
||||||
|
Source source = 3 [
|
||||||
|
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||||
|
description: "The source where the setting of the feature was defined. The source may be the resource itself or a resource owner through inheritance.";
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
enum ImprovedPerformance {
|
enum ImprovedPerformance {
|
||||||
IMPROVED_PERFORMANCE_UNSPECIFIED = 0;
|
IMPROVED_PERFORMANCE_UNSPECIFIED = 0;
|
||||||
// Uses the eventstore to query the org by id
|
// Uses the eventstore to query the org by id
|
||||||
@ -65,4 +75,11 @@ enum ImprovedPerformance {
|
|||||||
// users are checked against verified domains
|
// users are checked against verified domains
|
||||||
// from other organizations.
|
// from other organizations.
|
||||||
IMPROVED_PERFORMANCE_ORG_DOMAIN_VERIFIED = 5;
|
IMPROVED_PERFORMANCE_ORG_DOMAIN_VERIFIED = 5;
|
||||||
|
}
|
||||||
|
|
||||||
|
message LoginV2 {
|
||||||
|
// Require that all users must use the new login UI. If enabled, all users will be redirected to the login V2 regardless of the application's preference.
|
||||||
|
bool required = 1;
|
||||||
|
// Optionally specify a base uri of the login UI. If unspecified the default URI will be used.
|
||||||
|
optional string base_uri = 2;
|
||||||
}
|
}
|
@ -93,6 +93,12 @@ message SetInstanceFeaturesRequest{
|
|||||||
description: "If the flag is enabled, you'll be able to use the OIDC Back-Channel Logout to be notified in your application about terminated user sessions.";
|
description: "If the flag is enabled, you'll be able to use the OIDC Back-Channel Logout to be notified in your application about terminated user sessions.";
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
|
optional LoginV2 login_v2 = 13 [
|
||||||
|
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||||
|
description: "Specify the login UI for all users and applications regardless of their preference.";
|
||||||
|
}
|
||||||
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
message SetInstanceFeaturesResponse {
|
message SetInstanceFeaturesResponse {
|
||||||
@ -199,4 +205,11 @@ message GetInstanceFeaturesResponse {
|
|||||||
description: "If the flag is enabled, you'll be able to use the OIDC Back-Channel Logout to be notified in your application about terminated user sessions.";
|
description: "If the flag is enabled, you'll be able to use the OIDC Back-Channel Logout to be notified in your application about terminated user sessions.";
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
|
LoginV2FeatureFlag login_v2 = 14 [
|
||||||
|
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||||
|
example: "true";
|
||||||
|
description: "If the flag is set, all users will be redirected to the login V2 regardless of the application's preference.";
|
||||||
|
}
|
||||||
|
];
|
||||||
}
|
}
|
||||||
|
@ -82,6 +82,12 @@ message SetSystemFeaturesRequest{
|
|||||||
description: "If the flag is enabled, you'll be able to use the OIDC Back-Channel Logout to be notified in your application about terminated user sessions.";
|
description: "If the flag is enabled, you'll be able to use the OIDC Back-Channel Logout to be notified in your application about terminated user sessions.";
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
|
optional LoginV2 login_v2 = 11 [
|
||||||
|
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||||
|
description: "Specify the login UI for all users and applications regardless of their preference.";
|
||||||
|
}
|
||||||
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
message SetSystemFeaturesResponse {
|
message SetSystemFeaturesResponse {
|
||||||
@ -167,4 +173,11 @@ message GetSystemFeaturesResponse {
|
|||||||
description: "If the flag is enabled, you'll be able to use the OIDC Back-Channel Logout to be notified in your application about terminated user sessions.";
|
description: "If the flag is enabled, you'll be able to use the OIDC Back-Channel Logout to be notified in your application about terminated user sessions.";
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
|
LoginV2FeatureFlag login_v2 = 12 [
|
||||||
|
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||||
|
example: "true";
|
||||||
|
description: "If the flag is set, all users will be redirected to the login V2 regardless of the application's preference.";
|
||||||
|
}
|
||||||
|
];
|
||||||
}
|
}
|
||||||
|
@ -9808,6 +9808,11 @@ message AddOIDCAppRequest {
|
|||||||
description: "ZITADEL will use this URI to notify the application about terminated session according to the OIDC Back-Channel Logout (https://openid.net/specs/openid-connect-backchannel-1_0.html)";
|
description: "ZITADEL will use this URI to notify the application about terminated session according to the OIDC Back-Channel Logout (https://openid.net/specs/openid-connect-backchannel-1_0.html)";
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
zitadel.app.v1.LoginVersion login_version = 19 [
|
||||||
|
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||||
|
description: "Specify the preferred login UI, where the user is redirected to for authentication. If unset, the login UI is chosen by the instance default.";
|
||||||
|
}
|
||||||
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
message AddOIDCAppResponse {
|
message AddOIDCAppResponse {
|
||||||
@ -9989,6 +9994,11 @@ message UpdateOIDCAppConfigRequest {
|
|||||||
description: "ZITADEL will use this URI to notify the application about terminated session according to the OIDC Back-Channel Logout (https://openid.net/specs/openid-connect-backchannel-1_0.html)";
|
description: "ZITADEL will use this URI to notify the application about terminated session according to the OIDC Back-Channel Logout (https://openid.net/specs/openid-connect-backchannel-1_0.html)";
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
zitadel.app.v1.LoginVersion login_version = 18 [
|
||||||
|
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||||
|
description: "Specify the preferred login UI, where the user is redirected to for authentication. If unset, the login UI is chosen by the instance default.";
|
||||||
|
}
|
||||||
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
message UpdateOIDCAppConfigResponse {
|
message UpdateOIDCAppConfigResponse {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user