feat(OIDC): add back channel logout (#8837)

# Which Problems Are Solved

Currently ZITADEL supports RP-initiated logout for clients. Back-channel
logout ensures that user sessions are terminated across all connected
applications, even if the user closes their browser or loses
connectivity providing a more secure alternative for certain use cases.

# How the Problems Are Solved

If the feature is activated and the client used for the authentication
has a back_channel_logout_uri configured, a
`session_logout.back_channel` will be registered. Once a user terminates
their session, a (notification) handler will send a SET (form POST) to
the registered uri containing a logout_token (with the user's ID and
session ID).

- A new feature "back_channel_logout" is added on system and instance
level
- A `back_channel_logout_uri` can be managed on OIDC applications
- Added a `session_logout` aggregate to register and inform about sent
`back_channel` notifications
- Added a `SecurityEventToken` channel and `Form`message type in the
notification handlers
- Added `TriggeredAtOrigin` fields to `HumanSignedOut` and
`TerminateSession` events for notification handling
- Exported various functions and types in the `oidc` package to be able
to reuse for token signing in the back_channel notifier.
- To prevent that current existing session termination events will be
handled, a setup step is added to set the `current_states` for the
`projections.notifications_back_channel_logout` to the current position

- [x] requires https://github.com/zitadel/oidc/pull/671

# Additional Changes

- Updated all OTEL dependencies to v1.29.0, since OIDC already updated
some of them to that version.
- Single Session Termination feature is correctly checked (fixed feature
mapping)

# Additional Context

- closes https://github.com/zitadel/zitadel/issues/8467
- TODO:
  - Documentation
  - UI to be done: https://github.com/zitadel/zitadel/issues/8469

---------

Co-authored-by: Hidde Wieringa <hidde@hiddewieringa.nl>
This commit is contained in:
Livio Spring
2024-10-31 15:57:17 +01:00
committed by GitHub
parent 9cf67f30b8
commit 041af26917
87 changed files with 1778 additions and 280 deletions

View File

@@ -59,6 +59,7 @@ type OIDCApp struct {
AdditionalOrigins database.TextArray[string]
AllowedOrigins database.TextArray[string]
SkipNativeAppSuccessPage bool
BackChannelLogoutURI string
}
type SAMLApp struct {
@@ -243,6 +244,10 @@ var (
name: projection.AppOIDCConfigColumnSkipNativeAppSuccessPage,
table: appOIDCConfigsTable,
}
AppOIDCConfigColumnBackChannelLogoutURI = Column{
name: projection.AppOIDCConfigColumnBackChannelLogoutURI,
table: appOIDCConfigsTable,
}
)
func (q *Queries) AppByProjectAndAppID(ctx context.Context, shouldTriggerBulk bool, projectID, appID string) (app *App, err error) {
@@ -536,6 +541,7 @@ func prepareAppQuery(ctx context.Context, db prepareDatabase, activeOnly bool) (
AppOIDCConfigColumnClockSkew.identifier(),
AppOIDCConfigColumnAdditionalOrigins.identifier(),
AppOIDCConfigColumnSkipNativeAppSuccessPage.identifier(),
AppOIDCConfigColumnBackChannelLogoutURI.identifier(),
AppSAMLConfigColumnAppID.identifier(),
AppSAMLConfigColumnEntityID.identifier(),
@@ -600,6 +606,7 @@ func scanApp(row *sql.Row) (*App, error) {
&oidcConfig.clockSkew,
&oidcConfig.additionalOrigins,
&oidcConfig.skipNativeAppSuccessPage,
&oidcConfig.backChannelLogoutURI,
&samlConfig.appID,
&samlConfig.entityID,
@@ -649,6 +656,7 @@ func prepareOIDCAppQuery() (sq.SelectBuilder, func(*sql.Row) (*App, error)) {
AppOIDCConfigColumnClockSkew.identifier(),
AppOIDCConfigColumnAdditionalOrigins.identifier(),
AppOIDCConfigColumnSkipNativeAppSuccessPage.identifier(),
AppOIDCConfigColumnBackChannelLogoutURI.identifier(),
).From(appsTable.identifier()).
Join(join(AppOIDCConfigColumnAppID, AppColumnID)).
PlaceholderFormat(sq.Dollar), func(row *sql.Row) (*App, error) {
@@ -685,6 +693,7 @@ func prepareOIDCAppQuery() (sq.SelectBuilder, func(*sql.Row) (*App, error)) {
&oidcConfig.clockSkew,
&oidcConfig.additionalOrigins,
&oidcConfig.skipNativeAppSuccessPage,
&oidcConfig.backChannelLogoutURI,
)
if err != nil {
@@ -896,6 +905,7 @@ func prepareAppsQuery(ctx context.Context, db prepareDatabase) (sq.SelectBuilder
AppOIDCConfigColumnClockSkew.identifier(),
AppOIDCConfigColumnAdditionalOrigins.identifier(),
AppOIDCConfigColumnSkipNativeAppSuccessPage.identifier(),
AppOIDCConfigColumnBackChannelLogoutURI.identifier(),
AppSAMLConfigColumnAppID.identifier(),
AppSAMLConfigColumnEntityID.identifier(),
@@ -948,6 +958,7 @@ func prepareAppsQuery(ctx context.Context, db prepareDatabase) (sq.SelectBuilder
&oidcConfig.clockSkew,
&oidcConfig.additionalOrigins,
&oidcConfig.skipNativeAppSuccessPage,
&oidcConfig.backChannelLogoutURI,
&samlConfig.appID,
&samlConfig.entityID,
@@ -1020,6 +1031,7 @@ type sqlOIDCConfig struct {
responseTypes database.NumberArray[domain.OIDCResponseType]
grantTypes database.NumberArray[domain.OIDCGrantType]
skipNativeAppSuccessPage sql.NullBool
backChannelLogoutURI sql.NullString
}
func (c sqlOIDCConfig) set(app *App) {
@@ -1043,6 +1055,7 @@ func (c sqlOIDCConfig) set(app *App) {
ResponseTypes: c.responseTypes,
GrantTypes: c.grantTypes,
SkipNativeAppSuccessPage: c.skipNativeAppSuccessPage.Bool,
BackChannelLogoutURI: c.backChannelLogoutURI.String,
}
compliance := domain.GetOIDCCompliance(app.OIDCConfig.Version, app.OIDCConfig.AppType, app.OIDCConfig.GrantTypes, app.OIDCConfig.ResponseTypes, app.OIDCConfig.AuthMethodType, app.OIDCConfig.RedirectURIs)
app.OIDCConfig.ComplianceProblems = compliance.Problems

View File

@@ -48,6 +48,7 @@ var (
` projections.apps7_oidc_configs.clock_skew,` +
` projections.apps7_oidc_configs.additional_origins,` +
` projections.apps7_oidc_configs.skip_native_app_success_page,` +
` projections.apps7_oidc_configs.back_channel_logout_uri,` +
//saml config
` projections.apps7_saml_configs.app_id,` +
` projections.apps7_saml_configs.entity_id,` +
@@ -91,6 +92,7 @@ var (
` projections.apps7_oidc_configs.clock_skew,` +
` projections.apps7_oidc_configs.additional_origins,` +
` projections.apps7_oidc_configs.skip_native_app_success_page,` +
` projections.apps7_oidc_configs.back_channel_logout_uri,` +
//saml config
` projections.apps7_saml_configs.app_id,` +
` projections.apps7_saml_configs.entity_id,` +
@@ -163,6 +165,7 @@ var (
"clock_skew",
"additional_origins",
"skip_native_app_success_page",
"back_channel_logout_uri",
//saml config
"app_id",
"entity_id",
@@ -234,6 +237,7 @@ func Test_AppsPrepare(t *testing.T) {
nil,
nil,
nil,
nil,
// saml config
nil,
nil,
@@ -300,6 +304,7 @@ func Test_AppsPrepare(t *testing.T) {
nil,
nil,
nil,
nil,
// saml config
nil,
nil,
@@ -369,6 +374,7 @@ func Test_AppsPrepare(t *testing.T) {
nil,
nil,
nil,
nil,
// saml config
"app-id",
"https://test.com/saml/metadata",
@@ -440,6 +446,7 @@ func Test_AppsPrepare(t *testing.T) {
1 * time.Second,
database.TextArray[string]{"additional.origin"},
false,
"back.channel.logout.ch",
// saml config
nil,
nil,
@@ -482,6 +489,7 @@ func Test_AppsPrepare(t *testing.T) {
ComplianceProblems: nil,
AllowedOrigins: database.TextArray[string]{"https://redirect.to", "additional.origin"},
SkipNativeAppSuccessPage: false,
BackChannelLogoutURI: "back.channel.logout.ch",
},
},
},
@@ -526,6 +534,7 @@ func Test_AppsPrepare(t *testing.T) {
1 * time.Second,
database.TextArray[string]{"additional.origin"},
false,
"back.channel.logout.ch",
// saml config
nil,
nil,
@@ -568,6 +577,7 @@ func Test_AppsPrepare(t *testing.T) {
ComplianceProblems: nil,
AllowedOrigins: database.TextArray[string]{"https://redirect.to", "additional.origin"},
SkipNativeAppSuccessPage: false,
BackChannelLogoutURI: "back.channel.logout.ch",
},
},
},
@@ -612,6 +622,7 @@ func Test_AppsPrepare(t *testing.T) {
1 * time.Second,
database.TextArray[string]{"additional.origin"},
false,
"back.channel.logout.ch",
// saml config
nil,
nil,
@@ -654,6 +665,7 @@ func Test_AppsPrepare(t *testing.T) {
ComplianceProblems: nil,
AllowedOrigins: database.TextArray[string]{"https://redirect.to", "additional.origin"},
SkipNativeAppSuccessPage: false,
BackChannelLogoutURI: "back.channel.logout.ch",
},
},
},
@@ -698,6 +710,7 @@ func Test_AppsPrepare(t *testing.T) {
1 * time.Second,
database.TextArray[string]{"additional.origin"},
false,
"back.channel.logout.ch",
// saml config
nil,
nil,
@@ -740,6 +753,7 @@ func Test_AppsPrepare(t *testing.T) {
ComplianceProblems: nil,
AllowedOrigins: database.TextArray[string]{"https://redirect.to", "additional.origin"},
SkipNativeAppSuccessPage: false,
BackChannelLogoutURI: "back.channel.logout.ch",
},
},
},
@@ -784,6 +798,7 @@ func Test_AppsPrepare(t *testing.T) {
1 * time.Second,
database.TextArray[string]{"additional.origin"},
false,
"back.channel.logout.ch",
// saml config
nil,
nil,
@@ -826,6 +841,7 @@ func Test_AppsPrepare(t *testing.T) {
ComplianceProblems: nil,
AllowedOrigins: database.TextArray[string]{"https://redirect.to", "additional.origin"},
SkipNativeAppSuccessPage: false,
BackChannelLogoutURI: "back.channel.logout.ch",
},
},
},
@@ -870,6 +886,7 @@ func Test_AppsPrepare(t *testing.T) {
1 * time.Second,
database.TextArray[string]{"additional.origin"},
true,
"back.channel.logout.ch",
// saml config
nil,
nil,
@@ -912,6 +929,7 @@ func Test_AppsPrepare(t *testing.T) {
ComplianceProblems: nil,
AllowedOrigins: database.TextArray[string]{"https://redirect.to", "additional.origin"},
SkipNativeAppSuccessPage: true,
BackChannelLogoutURI: "back.channel.logout.ch",
},
},
},
@@ -956,6 +974,7 @@ func Test_AppsPrepare(t *testing.T) {
1 * time.Second,
database.TextArray[string]{"additional.origin"},
false,
"back.channel.logout.ch",
// saml config
nil,
nil,
@@ -993,6 +1012,7 @@ func Test_AppsPrepare(t *testing.T) {
nil,
nil,
nil,
nil,
// saml config
nil,
nil,
@@ -1030,6 +1050,7 @@ func Test_AppsPrepare(t *testing.T) {
nil,
nil,
nil,
nil,
// saml config
"saml-app-id",
"https://test.com/saml/metadata",
@@ -1072,6 +1093,7 @@ func Test_AppsPrepare(t *testing.T) {
ComplianceProblems: nil,
AllowedOrigins: database.TextArray[string]{"https://redirect.to", "additional.origin"},
SkipNativeAppSuccessPage: false,
BackChannelLogoutURI: "back.channel.logout.ch",
},
},
{
@@ -1205,6 +1227,7 @@ func Test_AppPrepare(t *testing.T) {
nil,
nil,
nil,
nil,
// saml config
nil,
nil,
@@ -1265,6 +1288,7 @@ func Test_AppPrepare(t *testing.T) {
nil,
nil,
nil,
nil,
// saml config
nil,
nil,
@@ -1330,6 +1354,7 @@ func Test_AppPrepare(t *testing.T) {
1 * time.Second,
database.TextArray[string]{"additional.origin"},
false,
"back.channel.logout.ch",
// saml config
nil,
nil,
@@ -1367,6 +1392,7 @@ func Test_AppPrepare(t *testing.T) {
ComplianceProblems: nil,
AllowedOrigins: database.TextArray[string]{"https://redirect.to", "additional.origin"},
SkipNativeAppSuccessPage: false,
BackChannelLogoutURI: "back.channel.logout.ch",
},
},
},
@@ -1411,6 +1437,7 @@ func Test_AppPrepare(t *testing.T) {
1 * time.Second,
database.TextArray[string]{"additional.origin"},
false,
"back.channel.logout.ch",
// saml config
nil,
nil,
@@ -1448,6 +1475,7 @@ func Test_AppPrepare(t *testing.T) {
ComplianceProblems: nil,
AllowedOrigins: database.TextArray[string]{"https://redirect.to", "additional.origin"},
SkipNativeAppSuccessPage: false,
BackChannelLogoutURI: "back.channel.logout.ch",
},
},
},
@@ -1492,6 +1520,7 @@ func Test_AppPrepare(t *testing.T) {
nil,
nil,
nil,
nil,
// saml config
"app-id",
"https://test.com/saml/metadata",
@@ -1558,6 +1587,7 @@ func Test_AppPrepare(t *testing.T) {
1 * time.Second,
database.TextArray[string]{"additional.origin"},
false,
"back.channel.logout.ch",
// saml config
nil,
nil,
@@ -1595,6 +1625,7 @@ func Test_AppPrepare(t *testing.T) {
ComplianceProblems: nil,
AllowedOrigins: database.TextArray[string]{"https://redirect.to", "additional.origin"},
SkipNativeAppSuccessPage: false,
BackChannelLogoutURI: "back.channel.logout.ch",
},
},
},
@@ -1639,6 +1670,7 @@ func Test_AppPrepare(t *testing.T) {
1 * time.Second,
database.TextArray[string]{"additional.origin"},
false,
"back.channel.logout.ch",
// saml config
nil,
nil,
@@ -1676,6 +1708,7 @@ func Test_AppPrepare(t *testing.T) {
ComplianceProblems: nil,
AllowedOrigins: database.TextArray[string]{"https://redirect.to", "additional.origin"},
SkipNativeAppSuccessPage: false,
BackChannelLogoutURI: "back.channel.logout.ch",
},
},
},
@@ -1720,6 +1753,7 @@ func Test_AppPrepare(t *testing.T) {
1 * time.Second,
database.TextArray[string]{"additional.origin"},
false,
"back.channel.logout.ch",
// saml config
nil,
nil,
@@ -1757,6 +1791,7 @@ func Test_AppPrepare(t *testing.T) {
ComplianceProblems: nil,
AllowedOrigins: database.TextArray[string]{"https://redirect.to", "additional.origin"},
SkipNativeAppSuccessPage: false,
BackChannelLogoutURI: "back.channel.logout.ch",
},
},
},
@@ -1801,6 +1836,7 @@ func Test_AppPrepare(t *testing.T) {
1 * time.Second,
database.TextArray[string]{"additional.origin"},
false,
"back.channel.logout.ch",
// saml config
nil,
nil,
@@ -1838,6 +1874,7 @@ func Test_AppPrepare(t *testing.T) {
ComplianceProblems: nil,
AllowedOrigins: database.TextArray[string]{"https://redirect.to", "additional.origin"},
SkipNativeAppSuccessPage: false,
BackChannelLogoutURI: "back.channel.logout.ch",
},
},
},

View File

@@ -20,6 +20,7 @@ type InstanceFeatures struct {
DebugOIDCParentError FeatureSource[bool]
OIDCSingleV1SessionTermination FeatureSource[bool]
DisableUserTokenEvent FeatureSource[bool]
EnableBackChannelLogout FeatureSource[bool]
}
func (q *Queries) GetInstanceFeatures(ctx context.Context, cascade bool) (_ *InstanceFeatures, err error) {

View File

@@ -71,6 +71,7 @@ func (m *InstanceFeaturesReadModel) Query() *eventstore.SearchQueryBuilder {
feature_v2.InstanceDebugOIDCParentErrorEventType,
feature_v2.InstanceOIDCSingleV1SessionTerminationEventType,
feature_v2.InstanceDisableUserTokenEvent,
feature_v2.InstanceEnableBackChannelLogout,
).
Builder().ResourceOwner(m.ResourceOwner)
}
@@ -96,6 +97,7 @@ func (m *InstanceFeaturesReadModel) populateFromSystem() bool {
m.instance.ImprovedPerformance = m.system.ImprovedPerformance
m.instance.OIDCSingleV1SessionTermination = m.system.OIDCSingleV1SessionTermination
m.instance.DisableUserTokenEvent = m.system.DisableUserTokenEvent
m.instance.EnableBackChannelLogout = m.system.EnableBackChannelLogout
return true
}
@@ -129,6 +131,8 @@ func reduceInstanceFeatureSet[T any](features *InstanceFeatures, event *feature_
features.OIDCSingleV1SessionTermination.set(level, event.Value)
case feature.KeyDisableUserTokenEvent:
features.DisableUserTokenEvent.set(level, event.Value)
case feature.KeyEnableBackChannelLogout:
features.EnableBackChannelLogout.set(level, event.Value)
}
return nil
}

View File

@@ -21,6 +21,7 @@ type OIDCClient struct {
AppID string `json:"app_id,omitempty"`
State domain.AppState `json:"state,omitempty"`
ClientID string `json:"client_id,omitempty"`
BackChannelLogoutURI string `json:"back_channel_logout_uri,omitempty"`
HashedSecret string `json:"client_secret,omitempty"`
RedirectURIs []string `json:"redirect_uris,omitempty"`
ResponseTypes []domain.OIDCResponseType `json:"response_types,omitempty"`

View File

@@ -1,7 +1,7 @@
with client as (
select c.instance_id,
c.app_id, a.state, c.client_id, 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.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.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
from projections.apps7_oidc_configs c

View File

@@ -58,6 +58,7 @@ const (
AppOIDCConfigColumnClockSkew = "clock_skew"
AppOIDCConfigColumnAdditionalOrigins = "additional_origins"
AppOIDCConfigColumnSkipNativeAppSuccessPage = "skip_native_app_success_page"
AppOIDCConfigColumnBackChannelLogoutURI = "back_channel_logout_uri"
appSAMLTableSuffix = "saml_configs"
AppSAMLConfigColumnAppID = "app_id"
@@ -125,6 +126,7 @@ func (*appProjection) Init() *old_handler.Check {
handler.NewColumn(AppOIDCConfigColumnClockSkew, handler.ColumnTypeInt64, handler.Default(0)),
handler.NewColumn(AppOIDCConfigColumnAdditionalOrigins, handler.ColumnTypeTextArray, handler.Nullable()),
handler.NewColumn(AppOIDCConfigColumnSkipNativeAppSuccessPage, handler.ColumnTypeBool, handler.Default(false)),
handler.NewColumn(AppOIDCConfigColumnBackChannelLogoutURI, handler.ColumnTypeText, handler.Nullable()),
},
handler.NewPrimaryKey(AppOIDCConfigColumnInstanceID, AppOIDCConfigColumnAppID),
appOIDCTableSuffix,
@@ -500,6 +502,7 @@ func (p *appProjection) reduceOIDCConfigAdded(event eventstore.Event) (*handler.
handler.NewCol(AppOIDCConfigColumnClockSkew, e.ClockSkew),
handler.NewCol(AppOIDCConfigColumnAdditionalOrigins, database.TextArray[string](e.AdditionalOrigins)),
handler.NewCol(AppOIDCConfigColumnSkipNativeAppSuccessPage, e.SkipNativeAppSuccessPage),
handler.NewCol(AppOIDCConfigColumnBackChannelLogoutURI, e.BackChannelLogoutURI),
},
handler.WithTableSuffix(appOIDCTableSuffix),
),
@@ -522,7 +525,7 @@ func (p *appProjection) reduceOIDCConfigChanged(event eventstore.Event) (*handle
return nil, zerrors.ThrowInvalidArgumentf(nil, "HANDL-GNHU1", "reduce.wrong.event.type %s", project.OIDCConfigChangedType)
}
cols := make([]handler.Column, 0, 15)
cols := make([]handler.Column, 0, 16)
if e.Version != nil {
cols = append(cols, handler.NewCol(AppOIDCConfigColumnVersion, *e.Version))
}
@@ -568,6 +571,9 @@ func (p *appProjection) reduceOIDCConfigChanged(event eventstore.Event) (*handle
if e.SkipNativeAppSuccessPage != nil {
cols = append(cols, handler.NewCol(AppOIDCConfigColumnSkipNativeAppSuccessPage, *e.SkipNativeAppSuccessPage))
}
if e.BackChannelLogoutURI != nil {
cols = append(cols, handler.NewCol(AppOIDCConfigColumnBackChannelLogoutURI, *e.BackChannelLogoutURI))
}
if len(cols) == 0 {
return handler.NewNoOpStatement(e), nil

View File

@@ -558,7 +558,8 @@ func TestAppProjection_reduces(t *testing.T) {
"idTokenUserinfoAssertion": true,
"clockSkew": 1000,
"additionalOrigins": ["origin.one.ch", "origin.two.ch"],
"skipNativeAppSuccessPage": true
"skipNativeAppSuccessPage": true,
"backChannelLogoutURI": "back.channel.one.ch"
}`),
), project.OIDCConfigAddedEventMapper),
},
@@ -569,7 +570,7 @@ func TestAppProjection_reduces(t *testing.T) {
executer: &testExecuter{
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) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19)",
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)",
expectedArgs: []interface{}{
"app-id",
"instance-id",
@@ -590,6 +591,7 @@ func TestAppProjection_reduces(t *testing.T) {
1 * time.Microsecond,
database.TextArray[string]{"origin.one.ch", "origin.two.ch"},
true,
"back.channel.one.ch",
},
},
{
@@ -630,7 +632,8 @@ func TestAppProjection_reduces(t *testing.T) {
"idTokenUserinfoAssertion": true,
"clockSkew": 1000,
"additionalOrigins": ["origin.one.ch", "origin.two.ch"],
"skipNativeAppSuccessPage": true
"skipNativeAppSuccessPage": true,
"backChannelLogoutURI": "back.channel.one.ch"
}`),
), project.OIDCConfigAddedEventMapper),
},
@@ -641,7 +644,7 @@ func TestAppProjection_reduces(t *testing.T) {
executer: &testExecuter{
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) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19)",
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)",
expectedArgs: []interface{}{
"app-id",
"instance-id",
@@ -662,6 +665,7 @@ func TestAppProjection_reduces(t *testing.T) {
1 * time.Microsecond,
database.TextArray[string]{"origin.one.ch", "origin.two.ch"},
true,
"back.channel.one.ch",
},
},
{
@@ -700,8 +704,8 @@ func TestAppProjection_reduces(t *testing.T) {
"idTokenUserinfoAssertion": true,
"clockSkew": 1000,
"additionalOrigins": ["origin.one.ch", "origin.two.ch"],
"skipNativeAppSuccessPage": true
"skipNativeAppSuccessPage": true,
"backChannelLogoutURI": "back.channel.one.ch"
}`),
), project.OIDCConfigChangedEventMapper),
},
@@ -712,7 +716,7 @@ func TestAppProjection_reduces(t *testing.T) {
executer: &testExecuter{
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) = ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15) WHERE (app_id = $16) AND (instance_id = $17)",
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)",
expectedArgs: []interface{}{
domain.OIDCVersionV1,
database.TextArray[string]{"redirect.one.ch", "redirect.two.ch"},
@@ -729,6 +733,7 @@ func TestAppProjection_reduces(t *testing.T) {
1 * time.Microsecond,
database.TextArray[string]{"origin.one.ch", "origin.two.ch"},
true,
"back.channel.one.ch",
"app-id",
"instance-id",
},

View File

@@ -104,6 +104,10 @@ func (*instanceFeatureProjection) Reducers() []handler.AggregateReducer {
Event: feature_v2.InstanceDisableUserTokenEvent,
Reduce: reduceInstanceSetFeature[bool],
},
{
Event: feature_v2.InstanceEnableBackChannelLogout,
Reduce: reduceInstanceSetFeature[bool],
},
{
Event: instance.InstanceRemovedEventType,
Reduce: reduceInstanceRemovedHelper(InstanceDomainInstanceIDCol),

View File

@@ -84,6 +84,10 @@ func (*systemFeatureProjection) Reducers() []handler.AggregateReducer {
Event: feature_v2.SystemDisableUserTokenEvent,
Reduce: reduceSystemSetFeature[bool],
},
{
Event: feature_v2.SystemEnableBackChannelLogout,
Reduce: reduceSystemSetFeature[bool],
},
},
}}
}

View File

@@ -29,6 +29,7 @@ type SystemFeatures struct {
ImprovedPerformance FeatureSource[[]feature.ImprovedPerformanceType]
OIDCSingleV1SessionTermination FeatureSource[bool]
DisableUserTokenEvent FeatureSource[bool]
EnableBackChannelLogout FeatureSource[bool]
}
func (q *Queries) GetSystemFeatures(ctx context.Context) (_ *SystemFeatures, err error) {

View File

@@ -59,6 +59,7 @@ func (m *SystemFeaturesReadModel) Query() *eventstore.SearchQueryBuilder {
feature_v2.SystemImprovedPerformanceEventType,
feature_v2.SystemOIDCSingleV1SessionTerminationEventType,
feature_v2.SystemDisableUserTokenEvent,
feature_v2.SystemEnableBackChannelLogout,
).
Builder().ResourceOwner(m.ResourceOwner)
}
@@ -94,6 +95,8 @@ func reduceSystemFeatureSet[T any](features *SystemFeatures, event *feature_v2.S
features.OIDCSingleV1SessionTermination.set(level, event.Value)
case feature.KeyDisableUserTokenEvent:
features.DisableUserTokenEvent.set(level, event.Value)
case feature.KeyEnableBackChannelLogout:
features.EnableBackChannelLogout.set(level, event.Value)
}
return nil
}