mirror of
https://github.com/zitadel/zitadel.git
synced 2025-08-11 21:17:32 +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:
@@ -92,7 +92,9 @@ func (c *Commands) LinkSessionToAuthRequest(ctx context.Context, id, sessionID,
|
||||
return nil, nil, zerrors.ThrowPreconditionFailed(nil, "COMMAND-Sx208nt", "Errors.AuthRequest.AlreadyHandled")
|
||||
}
|
||||
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())
|
||||
err = c.eventstore.FilterToQueryReducer(ctx, sessionWriteModel)
|
||||
|
@@ -25,7 +25,7 @@ import (
|
||||
func TestCommands_AddAuthRequest(t *testing.T) {
|
||||
mockCtx := authz.NewMockContext("instanceID", "orgID", "loginClient")
|
||||
type fields struct {
|
||||
eventstore *eventstore.Eventstore
|
||||
eventstore func(*testing.T) *eventstore.Eventstore
|
||||
idGenerator id.Generator
|
||||
}
|
||||
type args struct {
|
||||
@@ -42,7 +42,7 @@ func TestCommands_AddAuthRequest(t *testing.T) {
|
||||
{
|
||||
"already exists error",
|
||||
fields{
|
||||
eventstore: eventstoreExpect(t,
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
authrequest.NewAddedEvent(mockCtx, &authrequest.NewAggregate("V2_id", "instanceID").Aggregate,
|
||||
@@ -78,7 +78,7 @@ func TestCommands_AddAuthRequest(t *testing.T) {
|
||||
{
|
||||
"added",
|
||||
fields{
|
||||
eventstore: eventstoreExpect(t,
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(),
|
||||
expectPush(
|
||||
authrequest.NewAddedEvent(mockCtx, &authrequest.NewAggregate("V2_id", "instanceID").Aggregate,
|
||||
@@ -158,7 +158,7 @@ func TestCommands_AddAuthRequest(t *testing.T) {
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
c := &Commands{
|
||||
eventstore: tt.fields.eventstore,
|
||||
eventstore: tt.fields.eventstore(t),
|
||||
idGenerator: tt.fields.idGenerator,
|
||||
}
|
||||
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) {
|
||||
mockCtx := authz.NewMockContext("instanceID", "orgID", "loginClient")
|
||||
type fields struct {
|
||||
eventstore *eventstore.Eventstore
|
||||
tokenVerifier func(ctx context.Context, sessionToken, sessionID, tokenID string) (err error)
|
||||
eventstore func(*testing.T) *eventstore.Eventstore
|
||||
tokenVerifier func(ctx context.Context, sessionToken, sessionID, tokenID string) (err error)
|
||||
checkPermission domain.PermissionCheck
|
||||
}
|
||||
type args struct {
|
||||
ctx context.Context
|
||||
@@ -195,7 +196,7 @@ func TestCommands_LinkSessionToAuthRequest(t *testing.T) {
|
||||
{
|
||||
"authRequest not found",
|
||||
fields{
|
||||
eventstore: eventstoreExpect(t,
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(),
|
||||
),
|
||||
tokenVerifier: newMockTokenVerifierValid(),
|
||||
@@ -212,7 +213,7 @@ func TestCommands_LinkSessionToAuthRequest(t *testing.T) {
|
||||
{
|
||||
"authRequest not existing",
|
||||
fields{
|
||||
eventstore: eventstoreExpect(t,
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
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{
|
||||
eventstore: eventstoreExpect(t,
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
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{
|
||||
ctx: authz.NewMockContext("instanceID", "orgID", "wrongLoginClient"),
|
||||
@@ -288,13 +290,13 @@ func TestCommands_LinkSessionToAuthRequest(t *testing.T) {
|
||||
checkLoginClient: true,
|
||||
},
|
||||
res{
|
||||
wantErr: zerrors.ThrowPermissionDenied(nil, "COMMAND-rai9Y", "Errors.AuthRequest.WrongLoginClient"),
|
||||
wantErr: zerrors.ThrowPermissionDenied(nil, "AUTHZ-HKJD33", "Errors.PermissionDenied"),
|
||||
},
|
||||
},
|
||||
{
|
||||
"session not existing",
|
||||
fields{
|
||||
eventstore: eventstoreExpect(t,
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
authrequest.NewAddedEvent(mockCtx, &authrequest.NewAggregate("V2_id", "instanceID").Aggregate,
|
||||
@@ -333,7 +335,7 @@ func TestCommands_LinkSessionToAuthRequest(t *testing.T) {
|
||||
{
|
||||
"session expired",
|
||||
fields{
|
||||
eventstore: eventstoreExpect(t,
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
authrequest.NewAddedEvent(mockCtx, &authrequest.NewAggregate("V2_id", "instanceID").Aggregate,
|
||||
@@ -395,7 +397,7 @@ func TestCommands_LinkSessionToAuthRequest(t *testing.T) {
|
||||
{
|
||||
"invalid session token",
|
||||
fields{
|
||||
eventstore: eventstoreExpect(t,
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
authrequest.NewAddedEvent(mockCtx, &authrequest.NewAggregate("V2_id", "instanceID").Aggregate,
|
||||
@@ -446,7 +448,7 @@ func TestCommands_LinkSessionToAuthRequest(t *testing.T) {
|
||||
{
|
||||
"linked",
|
||||
fields{
|
||||
eventstore: eventstoreExpect(t,
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
authrequest.NewAddedEvent(mockCtx, &authrequest.NewAggregate("V2_id", "instanceID").Aggregate,
|
||||
@@ -534,7 +536,7 @@ func TestCommands_LinkSessionToAuthRequest(t *testing.T) {
|
||||
{
|
||||
"linked with login client check",
|
||||
fields{
|
||||
eventstore: eventstoreExpect(t,
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
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 {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
c := &Commands{
|
||||
eventstore: tt.fields.eventstore,
|
||||
eventstore: tt.fields.eventstore(t),
|
||||
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)
|
||||
require.ErrorIs(t, err, tt.res.wantErr)
|
||||
@@ -642,7 +735,7 @@ func TestCommands_LinkSessionToAuthRequest(t *testing.T) {
|
||||
func TestCommands_FailAuthRequest(t *testing.T) {
|
||||
mockCtx := authz.NewMockContext("instanceID", "orgID", "loginClient")
|
||||
type fields struct {
|
||||
eventstore *eventstore.Eventstore
|
||||
eventstore func(*testing.T) *eventstore.Eventstore
|
||||
}
|
||||
type args struct {
|
||||
ctx context.Context
|
||||
@@ -663,7 +756,7 @@ func TestCommands_FailAuthRequest(t *testing.T) {
|
||||
{
|
||||
"authRequest not existing",
|
||||
fields{
|
||||
eventstore: eventstoreExpect(t,
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(),
|
||||
),
|
||||
},
|
||||
@@ -679,7 +772,7 @@ func TestCommands_FailAuthRequest(t *testing.T) {
|
||||
{
|
||||
"failed",
|
||||
fields{
|
||||
eventstore: eventstoreExpect(t,
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
authrequest.NewAddedEvent(mockCtx, &authrequest.NewAggregate("V2_id", "instanceID").Aggregate,
|
||||
@@ -735,7 +828,7 @@ func TestCommands_FailAuthRequest(t *testing.T) {
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
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)
|
||||
require.ErrorIs(t, err, tt.res.wantErr)
|
||||
@@ -748,7 +841,7 @@ func TestCommands_FailAuthRequest(t *testing.T) {
|
||||
func TestCommands_AddAuthRequestCode(t *testing.T) {
|
||||
mockCtx := authz.NewMockContext("instanceID", "orgID", "loginClient")
|
||||
type fields struct {
|
||||
eventstore *eventstore.Eventstore
|
||||
eventstore func(*testing.T) *eventstore.Eventstore
|
||||
}
|
||||
type args struct {
|
||||
ctx context.Context
|
||||
@@ -764,7 +857,7 @@ func TestCommands_AddAuthRequestCode(t *testing.T) {
|
||||
{
|
||||
"empty code error",
|
||||
fields{
|
||||
eventstore: eventstoreExpect(t),
|
||||
eventstore: expectEventstore(),
|
||||
},
|
||||
args{
|
||||
ctx: mockCtx,
|
||||
@@ -776,7 +869,7 @@ func TestCommands_AddAuthRequestCode(t *testing.T) {
|
||||
{
|
||||
"no session linked error",
|
||||
fields{
|
||||
eventstore: eventstoreExpect(t,
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
authrequest.NewAddedEvent(mockCtx, &authrequest.NewAggregate("V2_authRequestID", "instanceID").Aggregate,
|
||||
@@ -814,7 +907,7 @@ func TestCommands_AddAuthRequestCode(t *testing.T) {
|
||||
{
|
||||
"success",
|
||||
fields{
|
||||
eventstore: eventstoreExpect(t,
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
authrequest.NewAddedEvent(mockCtx, &authrequest.NewAggregate("V2_authRequestID", "instanceID").Aggregate,
|
||||
@@ -864,7 +957,7 @@ func TestCommands_AddAuthRequestCode(t *testing.T) {
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
c := &Commands{
|
||||
eventstore: tt.fields.eventstore,
|
||||
eventstore: tt.fields.eventstore(t),
|
||||
}
|
||||
err := c.AddAuthRequestCode(tt.args.ctx, tt.args.id, tt.args.code)
|
||||
assert.ErrorIs(t, tt.wantErr, err)
|
||||
|
@@ -156,6 +156,8 @@ func TestCommandSide_AddInstanceDomain(t *testing.T) {
|
||||
[]string{"https://sub.test.ch"},
|
||||
false,
|
||||
"",
|
||||
domain.LoginVersionUnspecified,
|
||||
"",
|
||||
),
|
||||
),
|
||||
),
|
||||
|
@@ -28,6 +28,7 @@ type InstanceFeatures struct {
|
||||
OIDCSingleV1SessionTermination *bool
|
||||
DisableUserTokenEvent *bool
|
||||
EnableBackChannelLogout *bool
|
||||
LoginV2 *feature.LoginV2
|
||||
}
|
||||
|
||||
func (m *InstanceFeatures) isEmpty() bool {
|
||||
@@ -43,7 +44,8 @@ func (m *InstanceFeatures) isEmpty() bool {
|
||||
m.DebugOIDCParentError == nil &&
|
||||
m.OIDCSingleV1SessionTermination == 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) {
|
||||
|
@@ -41,6 +41,12 @@ func (m *InstanceFeaturesWriteModel) Reduce() (err error) {
|
||||
return err
|
||||
}
|
||||
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]:
|
||||
_, key, err := e.FeatureInfo()
|
||||
if err != nil {
|
||||
@@ -72,6 +78,7 @@ func (m *InstanceFeaturesWriteModel) Query() *eventstore.SearchQueryBuilder {
|
||||
feature_v2.InstanceOIDCSingleV1SessionTerminationEventType,
|
||||
feature_v2.InstanceDisableUserTokenEvent,
|
||||
feature_v2.InstanceEnableBackChannelLogout,
|
||||
feature_v2.InstanceLoginVersion,
|
||||
).
|
||||
Builder().ResourceOwner(m.ResourceOwner)
|
||||
}
|
||||
@@ -120,6 +127,8 @@ func reduceInstanceFeature(features *InstanceFeatures, key feature.Key, value an
|
||||
case feature.KeyEnableBackChannelLogout:
|
||||
v := value.(bool)
|
||||
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.DisableUserTokenEvent, f.DisableUserTokenEvent, feature_v2.InstanceDisableUserTokenEvent)
|
||||
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
|
||||
}
|
||||
|
@@ -128,6 +128,8 @@ func oidcAppEvents(ctx context.Context, orgID, projectID, id, name, clientID str
|
||||
nil,
|
||||
false,
|
||||
"",
|
||||
domain.LoginVersionUnspecified,
|
||||
"",
|
||||
),
|
||||
}
|
||||
}
|
||||
@@ -441,6 +443,8 @@ func generatedDomainFilters(instanceID, orgID, projectID, appID, generatedDomain
|
||||
nil,
|
||||
false,
|
||||
"",
|
||||
domain.LoginVersionUnspecified,
|
||||
"",
|
||||
),
|
||||
),
|
||||
expectFilter(
|
||||
|
@@ -32,6 +32,8 @@ type addOIDCApp struct {
|
||||
AdditionalOrigins []string
|
||||
SkipSuccessPageForNativeApp bool
|
||||
BackChannelLogoutURI string
|
||||
LoginVersion domain.LoginVersion
|
||||
LoginBaseURI string
|
||||
|
||||
ClientID string
|
||||
ClientSecret string
|
||||
@@ -110,6 +112,8 @@ func (c *Commands) AddOIDCAppCommand(app *addOIDCApp) preparation.Validation {
|
||||
trimStringSliceWhiteSpaces(app.AdditionalOrigins),
|
||||
app.SkipSuccessPageForNativeApp,
|
||||
app.BackChannelLogoutURI,
|
||||
app.LoginVersion,
|
||||
app.LoginBaseURI,
|
||||
),
|
||||
}, nil
|
||||
}, nil
|
||||
@@ -202,6 +206,8 @@ func (c *Commands) addOIDCApplicationWithID(ctx context.Context, oidcApp *domain
|
||||
trimStringSliceWhiteSpaces(oidcApp.AdditionalOrigins),
|
||||
oidcApp.SkipNativeAppSuccessPage,
|
||||
strings.TrimSpace(oidcApp.BackChannelLogoutURI),
|
||||
oidcApp.LoginVersion,
|
||||
strings.TrimSpace(oidcApp.LoginBaseURI),
|
||||
))
|
||||
|
||||
addedApplication.AppID = oidcApp.AppID
|
||||
@@ -260,6 +266,8 @@ func (c *Commands) ChangeOIDCApplication(ctx context.Context, oidc *domain.OIDCA
|
||||
trimStringSliceWhiteSpaces(oidc.AdditionalOrigins),
|
||||
oidc.SkipNativeAppSuccessPage,
|
||||
strings.TrimSpace(oidc.BackChannelLogoutURI),
|
||||
oidc.LoginVersion,
|
||||
strings.TrimSpace(oidc.LoginBaseURI),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@@ -37,6 +37,8 @@ type OIDCApplicationWriteModel struct {
|
||||
AdditionalOrigins []string
|
||||
SkipNativeAppSuccessPage bool
|
||||
BackChannelLogoutURI string
|
||||
LoginVersion domain.LoginVersion
|
||||
LoginBaseURI string
|
||||
oidc bool
|
||||
}
|
||||
|
||||
@@ -167,6 +169,8 @@ func (wm *OIDCApplicationWriteModel) appendAddOIDCEvent(e *project.OIDCConfigAdd
|
||||
wm.AdditionalOrigins = e.AdditionalOrigins
|
||||
wm.SkipNativeAppSuccessPage = e.SkipNativeAppSuccessPage
|
||||
wm.BackChannelLogoutURI = e.BackChannelLogoutURI
|
||||
wm.LoginVersion = e.LoginVersion
|
||||
wm.LoginBaseURI = e.LoginBaseURI
|
||||
}
|
||||
|
||||
func (wm *OIDCApplicationWriteModel) appendChangeOIDCEvent(e *project.OIDCConfigChangedEvent) {
|
||||
@@ -218,6 +222,12 @@ func (wm *OIDCApplicationWriteModel) appendChangeOIDCEvent(e *project.OIDCConfig
|
||||
if e.BackChannelLogoutURI != nil {
|
||||
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 {
|
||||
@@ -260,6 +270,8 @@ func (wm *OIDCApplicationWriteModel) NewChangedEvent(
|
||||
additionalOrigins []string,
|
||||
skipNativeAppSuccessPage bool,
|
||||
backChannelLogoutURI string,
|
||||
loginVersion domain.LoginVersion,
|
||||
loginBaseURI string,
|
||||
) (*project.OIDCConfigChangedEvent, bool, error) {
|
||||
changes := make([]project.OIDCConfigChanges, 0)
|
||||
var err error
|
||||
@@ -312,6 +324,12 @@ func (wm *OIDCApplicationWriteModel) NewChangedEvent(
|
||||
if wm.BackChannelLogoutURI != 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 {
|
||||
return nil, false, nil
|
||||
|
@@ -176,6 +176,8 @@ func TestAddOIDCApp(t *testing.T) {
|
||||
[]string{"https://sub.test.ch"},
|
||||
false,
|
||||
"",
|
||||
domain.LoginVersionUnspecified,
|
||||
"",
|
||||
),
|
||||
},
|
||||
},
|
||||
@@ -242,6 +244,8 @@ func TestAddOIDCApp(t *testing.T) {
|
||||
nil,
|
||||
false,
|
||||
"",
|
||||
domain.LoginVersionUnspecified,
|
||||
"",
|
||||
),
|
||||
},
|
||||
},
|
||||
@@ -308,6 +312,8 @@ func TestAddOIDCApp(t *testing.T) {
|
||||
nil,
|
||||
false,
|
||||
"",
|
||||
domain.LoginVersionUnspecified,
|
||||
"",
|
||||
),
|
||||
},
|
||||
},
|
||||
@@ -374,6 +380,8 @@ func TestAddOIDCApp(t *testing.T) {
|
||||
nil,
|
||||
false,
|
||||
"",
|
||||
domain.LoginVersionUnspecified,
|
||||
"",
|
||||
),
|
||||
},
|
||||
},
|
||||
@@ -521,6 +529,8 @@ func TestCommandSide_AddOIDCApplication(t *testing.T) {
|
||||
[]string{"https://sub.test.ch"},
|
||||
true,
|
||||
"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 "},
|
||||
SkipNativeAppSuccessPage: true,
|
||||
BackChannelLogoutURI: " https://test.ch/backchannel ",
|
||||
LoginVersion: domain.LoginVersion2,
|
||||
LoginBaseURI: " https://login.test.ch ",
|
||||
},
|
||||
resourceOwner: "org1",
|
||||
},
|
||||
@@ -578,6 +590,8 @@ func TestCommandSide_AddOIDCApplication(t *testing.T) {
|
||||
AdditionalOrigins: []string{"https://sub.test.ch"},
|
||||
SkipNativeAppSuccessPage: true,
|
||||
BackChannelLogoutURI: "https://test.ch/backchannel",
|
||||
LoginVersion: domain.LoginVersion2,
|
||||
LoginBaseURI: "https://login.test.ch",
|
||||
State: domain.AppStateActive,
|
||||
Compliance: &domain.Compliance{},
|
||||
},
|
||||
@@ -622,6 +636,8 @@ func TestCommandSide_AddOIDCApplication(t *testing.T) {
|
||||
[]string{"https://sub.test.ch"},
|
||||
true,
|
||||
"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"},
|
||||
SkipNativeAppSuccessPage: true,
|
||||
BackChannelLogoutURI: "https://test.ch/backchannel",
|
||||
LoginVersion: domain.LoginVersion2,
|
||||
LoginBaseURI: "https://login.test.ch",
|
||||
},
|
||||
resourceOwner: "org1",
|
||||
},
|
||||
@@ -679,6 +697,8 @@ func TestCommandSide_AddOIDCApplication(t *testing.T) {
|
||||
AdditionalOrigins: []string{"https://sub.test.ch"},
|
||||
SkipNativeAppSuccessPage: true,
|
||||
BackChannelLogoutURI: "https://test.ch/backchannel",
|
||||
LoginVersion: domain.LoginVersion2,
|
||||
LoginBaseURI: "https://login.test.ch",
|
||||
State: domain.AppStateActive,
|
||||
Compliance: &domain.Compliance{},
|
||||
},
|
||||
@@ -712,7 +732,7 @@ func TestCommandSide_AddOIDCApplication(t *testing.T) {
|
||||
|
||||
func TestCommandSide_ChangeOIDCApplication(t *testing.T) {
|
||||
type fields struct {
|
||||
eventstore *eventstore.Eventstore
|
||||
eventstore func(*testing.T) *eventstore.Eventstore
|
||||
}
|
||||
type args struct {
|
||||
ctx context.Context
|
||||
@@ -732,9 +752,7 @@ func TestCommandSide_ChangeOIDCApplication(t *testing.T) {
|
||||
{
|
||||
name: "invalid app, invalid argument error",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
),
|
||||
eventstore: expectEventstore(),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
@@ -753,9 +771,7 @@ func TestCommandSide_ChangeOIDCApplication(t *testing.T) {
|
||||
{
|
||||
name: "missing appid, invalid argument error",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
),
|
||||
eventstore: expectEventstore(),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
@@ -777,9 +793,7 @@ func TestCommandSide_ChangeOIDCApplication(t *testing.T) {
|
||||
{
|
||||
name: "missing aggregateid, invalid argument error",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
),
|
||||
eventstore: expectEventstore(),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
@@ -801,8 +815,7 @@ func TestCommandSide_ChangeOIDCApplication(t *testing.T) {
|
||||
{
|
||||
name: "app not existing, not found error",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(),
|
||||
),
|
||||
},
|
||||
@@ -826,8 +839,7 @@ func TestCommandSide_ChangeOIDCApplication(t *testing.T) {
|
||||
{
|
||||
name: "no changes, precondition error",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
project.NewApplicationAddedEvent(context.Background(),
|
||||
@@ -858,6 +870,8 @@ func TestCommandSide_ChangeOIDCApplication(t *testing.T) {
|
||||
[]string{"https://sub.test.ch"},
|
||||
true,
|
||||
"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"},
|
||||
SkipNativeAppSuccessPage: true,
|
||||
BackChannelLogoutURI: "https://test.ch/backchannel",
|
||||
LoginVersion: domain.LoginVersion2,
|
||||
LoginBaseURI: "https://login.test.ch",
|
||||
},
|
||||
resourceOwner: "org1",
|
||||
},
|
||||
@@ -897,8 +913,7 @@ func TestCommandSide_ChangeOIDCApplication(t *testing.T) {
|
||||
{
|
||||
name: "no changes whitespaces are ignored, precondition error",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
project.NewApplicationAddedEvent(context.Background(),
|
||||
@@ -929,6 +944,8 @@ func TestCommandSide_ChangeOIDCApplication(t *testing.T) {
|
||||
[]string{"https://sub.test.ch"},
|
||||
true,
|
||||
"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 "},
|
||||
SkipNativeAppSuccessPage: true,
|
||||
BackChannelLogoutURI: " https://test.ch/backchannel ",
|
||||
LoginVersion: domain.LoginVersion2,
|
||||
LoginBaseURI: " https://login.test.ch ",
|
||||
},
|
||||
resourceOwner: "org1",
|
||||
},
|
||||
@@ -968,8 +987,7 @@ func TestCommandSide_ChangeOIDCApplication(t *testing.T) {
|
||||
{
|
||||
name: "change oidc app, ok",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
project.NewApplicationAddedEvent(context.Background(),
|
||||
@@ -1000,6 +1018,8 @@ func TestCommandSide_ChangeOIDCApplication(t *testing.T) {
|
||||
[]string{"https://sub.test.ch"},
|
||||
true,
|
||||
"https://test.ch/backchannel",
|
||||
domain.LoginVersion1,
|
||||
"",
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -1035,6 +1055,8 @@ func TestCommandSide_ChangeOIDCApplication(t *testing.T) {
|
||||
AdditionalOrigins: []string{"https://sub.test.ch"},
|
||||
SkipNativeAppSuccessPage: true,
|
||||
BackChannelLogoutURI: "https://test.ch/backchannel",
|
||||
LoginVersion: domain.LoginVersion2,
|
||||
LoginBaseURI: "https://login.test.ch",
|
||||
},
|
||||
resourceOwner: "org1",
|
||||
},
|
||||
@@ -1063,6 +1085,8 @@ func TestCommandSide_ChangeOIDCApplication(t *testing.T) {
|
||||
AdditionalOrigins: []string{"https://sub.test.ch"},
|
||||
SkipNativeAppSuccessPage: true,
|
||||
BackChannelLogoutURI: "https://test.ch/backchannel",
|
||||
LoginVersion: domain.LoginVersion2,
|
||||
LoginBaseURI: "https://login.test.ch",
|
||||
Compliance: &domain.Compliance{},
|
||||
State: domain.AppStateActive,
|
||||
},
|
||||
@@ -1072,7 +1096,7 @@ func TestCommandSide_ChangeOIDCApplication(t *testing.T) {
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
r := &Commands{
|
||||
eventstore: tt.fields.eventstore,
|
||||
eventstore: tt.fields.eventstore(t),
|
||||
}
|
||||
got, err := r.ChangeOIDCApplication(tt.args.ctx, tt.args.oidcApp, tt.args.resourceOwner)
|
||||
if tt.res.err == nil {
|
||||
@@ -1188,6 +1212,8 @@ func TestCommandSide_ChangeOIDCApplicationSecret(t *testing.T) {
|
||||
[]string{"https://sub.test.ch"},
|
||||
false,
|
||||
"",
|
||||
domain.LoginVersionUnspecified,
|
||||
"",
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -1232,6 +1258,7 @@ func TestCommandSide_ChangeOIDCApplicationSecret(t *testing.T) {
|
||||
AdditionalOrigins: []string{"https://sub.test.ch"},
|
||||
SkipNativeAppSuccessPage: false,
|
||||
BackChannelLogoutURI: "",
|
||||
LoginVersion: domain.LoginVersionUnspecified,
|
||||
State: domain.AppStateActive,
|
||||
},
|
||||
},
|
||||
@@ -1270,6 +1297,8 @@ func newOIDCAppChangedEvent(ctx context.Context, appID, projectID, resourceOwner
|
||||
project.ChangeIDTokenRoleAssertion(false),
|
||||
project.ChangeIDTokenUserinfoAssertion(false),
|
||||
project.ChangeClockSkew(time.Second * 2),
|
||||
project.ChangeLoginVersion(domain.LoginVersion2),
|
||||
project.ChangeLoginBaseURI("https://login.test.ch"),
|
||||
}
|
||||
event, _ := project.NewOIDCConfigChangedEvent(ctx,
|
||||
&project.NewAggregate(projectID, resourceOwner).Aggregate,
|
||||
@@ -1347,6 +1376,8 @@ func TestCommands_VerifyOIDCClientSecret(t *testing.T) {
|
||||
[]string{"https://sub.test.ch"},
|
||||
false,
|
||||
"",
|
||||
domain.LoginVersionUnspecified,
|
||||
"",
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -1383,6 +1414,8 @@ func TestCommands_VerifyOIDCClientSecret(t *testing.T) {
|
||||
[]string{"https://sub.test.ch"},
|
||||
false,
|
||||
"",
|
||||
domain.LoginVersionUnspecified,
|
||||
"",
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -1418,6 +1451,8 @@ func TestCommands_VerifyOIDCClientSecret(t *testing.T) {
|
||||
[]string{"https://sub.test.ch"},
|
||||
false,
|
||||
"",
|
||||
domain.LoginVersionUnspecified,
|
||||
"",
|
||||
),
|
||||
),
|
||||
),
|
||||
|
@@ -48,6 +48,8 @@ func oidcWriteModelToOIDCConfig(writeModel *OIDCApplicationWriteModel) *domain.O
|
||||
AdditionalOrigins: writeModel.AdditionalOrigins,
|
||||
SkipNativeAppSuccessPage: writeModel.SkipNativeAppSuccessPage,
|
||||
BackChannelLogoutURI: writeModel.BackChannelLogoutURI,
|
||||
LoginVersion: writeModel.LoginVersion,
|
||||
LoginBaseURI: writeModel.LoginBaseURI,
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -20,6 +20,7 @@ type SystemFeatures struct {
|
||||
OIDCSingleV1SessionTermination *bool
|
||||
DisableUserTokenEvent *bool
|
||||
EnableBackChannelLogout *bool
|
||||
LoginV2 *feature.LoginV2
|
||||
}
|
||||
|
||||
func (m *SystemFeatures) isEmpty() bool {
|
||||
@@ -33,7 +34,8 @@ func (m *SystemFeatures) isEmpty() bool {
|
||||
m.ImprovedPerformance == nil &&
|
||||
m.OIDCSingleV1SessionTermination == 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) {
|
||||
|
@@ -34,6 +34,12 @@ func (m *SystemFeaturesWriteModel) Reduce() error {
|
||||
return err
|
||||
}
|
||||
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]:
|
||||
_, key, err := e.FeatureInfo()
|
||||
if err != nil {
|
||||
@@ -63,6 +69,7 @@ func (m *SystemFeaturesWriteModel) Query() *eventstore.SearchQueryBuilder {
|
||||
feature_v2.SystemOIDCSingleV1SessionTerminationEventType,
|
||||
feature_v2.SystemDisableUserTokenEvent,
|
||||
feature_v2.SystemEnableBackChannelLogout,
|
||||
feature_v2.SystemLoginVersion,
|
||||
).
|
||||
Builder().ResourceOwner(m.ResourceOwner)
|
||||
}
|
||||
@@ -104,6 +111,8 @@ func reduceSystemFeature(features *SystemFeatures, key feature.Key, value any) {
|
||||
case feature.KeyEnableBackChannelLogout:
|
||||
v := value.(bool)
|
||||
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.DisableUserTokenEvent, f.DisableUserTokenEvent, feature_v2.SystemDisableUserTokenEvent)
|
||||
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
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user