mirror of
https://github.com/zitadel/zitadel.git
synced 2025-04-21 22:11:44 +00:00
fix(OIDC): back channel logout work for custom UI (#9487)
# Which Problems Are Solved When using a custom / new login UI and an OIDC application with registered BackChannelLogoutUI, no logout requests were sent to the URI when the user signed out. Additionally, as described in #9427, an error was logged: `level=error msg="event of type *session.TerminateEvent doesn't implement OriginEvent" caller="/home/runner/work/zitadel/zitadel/internal/notification/handlers/origin.go:24"` # How the Problems Are Solved - Properly pass `TriggerOrigin` information to session.TerminateEvent creation and implement `OriginEvent` interface. - Implemented `RegisterLogout` in `CreateOIDCSessionFromAuthRequest` and `CreateOIDCSessionFromDeviceAuth`, both used when interacting with the OIDC v2 API. - Both functions now receive the `BackChannelLogoutURI` of the client from the OIDC layer. # Additional Changes None # Additional Context - closes #9427
This commit is contained in:
parent
e6ce1af003
commit
ed697bbd69
@ -568,6 +568,7 @@ func (s *Server) CreateTokenCallbackURL(ctx context.Context, req op.AuthRequest)
|
|||||||
req.GetID(),
|
req.GetID(),
|
||||||
implicitFlowComplianceChecker(),
|
implicitFlowComplianceChecker(),
|
||||||
slices.Contains(client.GrantTypes(), oidc.GrantTypeRefreshToken),
|
slices.Contains(client.GrantTypes(), oidc.GrantTypeRefreshToken),
|
||||||
|
client.client.BackChannelLogoutURI,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
|
@ -41,6 +41,7 @@ func (s *Server) CodeExchange(ctx context.Context, r *op.ClientRequest[oidc.Acce
|
|||||||
plainCode,
|
plainCode,
|
||||||
codeExchangeComplianceChecker(client, r.Data),
|
codeExchangeComplianceChecker(client, r.Data),
|
||||||
slices.Contains(client.GrantTypes(), oidc.GrantTypeRefreshToken),
|
slices.Contains(client.GrantTypes(), oidc.GrantTypeRefreshToken),
|
||||||
|
client.client.BackChannelLogoutURI,
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
session, err = s.codeExchangeV1(ctx, client, r.Data, r.Data.Code)
|
session, err = s.codeExchangeV1(ctx, client, r.Data, r.Data.Code)
|
||||||
|
@ -25,7 +25,7 @@ func (s *Server) DeviceToken(ctx context.Context, r *op.ClientRequest[oidc.Devic
|
|||||||
if !ok {
|
if !ok {
|
||||||
return nil, zerrors.ThrowInternal(nil, "OIDC-Ae2ph", "Error.Internal")
|
return nil, zerrors.ThrowInternal(nil, "OIDC-Ae2ph", "Error.Internal")
|
||||||
}
|
}
|
||||||
session, err := s.command.CreateOIDCSessionFromDeviceAuth(ctx, r.Data.DeviceCode)
|
session, err := s.command.CreateOIDCSessionFromDeviceAuth(ctx, r.Data.DeviceCode, client.client.BackChannelLogoutURI)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
return response(s.accessTokenResponseFromSession(ctx, client, session, "", client.client.ProjectID, client.client.ProjectRoleAssertion, client.client.AccessTokenRoleAssertion, client.client.IDTokenRoleAssertion, client.client.IDTokenUserinfoAssertion))
|
return response(s.accessTokenResponseFromSession(ctx, client, session, "", client.client.ProjectID, client.client.ProjectRoleAssertion, client.client.AccessTokenRoleAssertion, client.client.IDTokenRoleAssertion, client.client.IDTokenUserinfoAssertion))
|
||||||
}
|
}
|
||||||
|
@ -174,7 +174,7 @@ func (e DeviceAuthStateError) Error() string {
|
|||||||
// As devices can poll at various intervals, an explicit state takes precedence over expiry.
|
// As devices can poll at various intervals, an explicit state takes precedence over expiry.
|
||||||
// This is to prevent cases where users might approve or deny the authorization on time, but the next poll
|
// This is to prevent cases where users might approve or deny the authorization on time, but the next poll
|
||||||
// happens after expiry.
|
// happens after expiry.
|
||||||
func (c *Commands) CreateOIDCSessionFromDeviceAuth(ctx context.Context, deviceCode string) (_ *OIDCSession, err error) {
|
func (c *Commands) CreateOIDCSessionFromDeviceAuth(ctx context.Context, deviceCode, backChannelLogoutURI string) (_ *OIDCSession, err error) {
|
||||||
ctx, span := tracing.NewSpan(ctx)
|
ctx, span := tracing.NewSpan(ctx)
|
||||||
defer func() { span.EndWithError(err) }()
|
defer func() { span.EndWithError(err) }()
|
||||||
|
|
||||||
@ -219,6 +219,7 @@ func (c *Commands) CreateOIDCSessionFromDeviceAuth(ctx context.Context, deviceCo
|
|||||||
deviceAuthModel.PreferredLanguage,
|
deviceAuthModel.PreferredLanguage,
|
||||||
deviceAuthModel.UserAgent,
|
deviceAuthModel.UserAgent,
|
||||||
)
|
)
|
||||||
|
cmd.RegisterLogout(ctx, deviceAuthModel.SessionID, deviceAuthModel.UserID, deviceAuthModel.ClientID, backChannelLogoutURI)
|
||||||
if err = cmd.AddAccessToken(ctx, deviceAuthModel.Scopes, deviceAuthModel.UserID, deviceAuthModel.UserOrgID, domain.TokenReasonAuthRequest, nil); err != nil {
|
if err = cmd.AddAccessToken(ctx, deviceAuthModel.Scopes, deviceAuthModel.UserID, deviceAuthModel.UserOrgID, domain.TokenReasonAuthRequest, nil); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -19,11 +19,13 @@ import (
|
|||||||
"github.com/zitadel/zitadel/internal/crypto"
|
"github.com/zitadel/zitadel/internal/crypto"
|
||||||
"github.com/zitadel/zitadel/internal/domain"
|
"github.com/zitadel/zitadel/internal/domain"
|
||||||
"github.com/zitadel/zitadel/internal/eventstore"
|
"github.com/zitadel/zitadel/internal/eventstore"
|
||||||
|
"github.com/zitadel/zitadel/internal/feature"
|
||||||
"github.com/zitadel/zitadel/internal/id"
|
"github.com/zitadel/zitadel/internal/id"
|
||||||
"github.com/zitadel/zitadel/internal/id/mock"
|
"github.com/zitadel/zitadel/internal/id/mock"
|
||||||
"github.com/zitadel/zitadel/internal/repository/deviceauth"
|
"github.com/zitadel/zitadel/internal/repository/deviceauth"
|
||||||
"github.com/zitadel/zitadel/internal/repository/oidcsession"
|
"github.com/zitadel/zitadel/internal/repository/oidcsession"
|
||||||
"github.com/zitadel/zitadel/internal/repository/session"
|
"github.com/zitadel/zitadel/internal/repository/session"
|
||||||
|
"github.com/zitadel/zitadel/internal/repository/sessionlogout"
|
||||||
"github.com/zitadel/zitadel/internal/repository/user"
|
"github.com/zitadel/zitadel/internal/repository/user"
|
||||||
"github.com/zitadel/zitadel/internal/zerrors"
|
"github.com/zitadel/zitadel/internal/zerrors"
|
||||||
)
|
)
|
||||||
@ -704,8 +706,9 @@ func TestCommands_CreateOIDCSessionFromDeviceAuth(t *testing.T) {
|
|||||||
keyAlgorithm crypto.EncryptionAlgorithm
|
keyAlgorithm crypto.EncryptionAlgorithm
|
||||||
}
|
}
|
||||||
type args struct {
|
type args struct {
|
||||||
ctx context.Context
|
ctx context.Context
|
||||||
deviceCode string
|
deviceCode string
|
||||||
|
backChannelLogoutURI string
|
||||||
}
|
}
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
@ -724,6 +727,7 @@ func TestCommands_CreateOIDCSessionFromDeviceAuth(t *testing.T) {
|
|||||||
args: args{
|
args: args{
|
||||||
ctx,
|
ctx,
|
||||||
"device1",
|
"device1",
|
||||||
|
"",
|
||||||
},
|
},
|
||||||
wantErr: io.ErrClosedPipe,
|
wantErr: io.ErrClosedPipe,
|
||||||
},
|
},
|
||||||
@ -748,6 +752,7 @@ func TestCommands_CreateOIDCSessionFromDeviceAuth(t *testing.T) {
|
|||||||
args: args{
|
args: args{
|
||||||
ctx,
|
ctx,
|
||||||
"123",
|
"123",
|
||||||
|
"",
|
||||||
},
|
},
|
||||||
wantErr: DeviceAuthStateError(domain.DeviceAuthStateInitiated),
|
wantErr: DeviceAuthStateError(domain.DeviceAuthStateInitiated),
|
||||||
},
|
},
|
||||||
@ -761,6 +766,7 @@ func TestCommands_CreateOIDCSessionFromDeviceAuth(t *testing.T) {
|
|||||||
args: args{
|
args: args{
|
||||||
ctx,
|
ctx,
|
||||||
"123",
|
"123",
|
||||||
|
"",
|
||||||
},
|
},
|
||||||
wantErr: zerrors.ThrowNotFound(nil, "COMMAND-ua1Vo", "Errors.DeviceAuth.NotFound"),
|
wantErr: zerrors.ThrowNotFound(nil, "COMMAND-ua1Vo", "Errors.DeviceAuth.NotFound"),
|
||||||
},
|
},
|
||||||
@ -789,6 +795,7 @@ func TestCommands_CreateOIDCSessionFromDeviceAuth(t *testing.T) {
|
|||||||
args: args{
|
args: args{
|
||||||
ctx,
|
ctx,
|
||||||
"123",
|
"123",
|
||||||
|
"",
|
||||||
},
|
},
|
||||||
wantErr: DeviceAuthStateError(domain.DeviceAuthStateExpired),
|
wantErr: DeviceAuthStateError(domain.DeviceAuthStateExpired),
|
||||||
},
|
},
|
||||||
@ -820,6 +827,7 @@ func TestCommands_CreateOIDCSessionFromDeviceAuth(t *testing.T) {
|
|||||||
args: args{
|
args: args{
|
||||||
ctx,
|
ctx,
|
||||||
"123",
|
"123",
|
||||||
|
"",
|
||||||
},
|
},
|
||||||
wantErr: DeviceAuthStateError(domain.DeviceAuthStateExpired),
|
wantErr: DeviceAuthStateError(domain.DeviceAuthStateExpired),
|
||||||
},
|
},
|
||||||
@ -851,6 +859,7 @@ func TestCommands_CreateOIDCSessionFromDeviceAuth(t *testing.T) {
|
|||||||
args: args{
|
args: args{
|
||||||
ctx,
|
ctx,
|
||||||
"123",
|
"123",
|
||||||
|
"",
|
||||||
},
|
},
|
||||||
wantErr: DeviceAuthStateError(domain.DeviceAuthStateDenied),
|
wantErr: DeviceAuthStateError(domain.DeviceAuthStateDenied),
|
||||||
},
|
},
|
||||||
@ -888,6 +897,7 @@ func TestCommands_CreateOIDCSessionFromDeviceAuth(t *testing.T) {
|
|||||||
args: args{
|
args: args{
|
||||||
ctx,
|
ctx,
|
||||||
"123",
|
"123",
|
||||||
|
"",
|
||||||
},
|
},
|
||||||
wantErr: DeviceAuthStateError(domain.DeviceAuthStateDone),
|
wantErr: DeviceAuthStateError(domain.DeviceAuthStateDone),
|
||||||
},
|
},
|
||||||
@ -951,6 +961,7 @@ func TestCommands_CreateOIDCSessionFromDeviceAuth(t *testing.T) {
|
|||||||
args: args{
|
args: args{
|
||||||
ctx,
|
ctx,
|
||||||
"123",
|
"123",
|
||||||
|
"",
|
||||||
},
|
},
|
||||||
wantErr: zerrors.ThrowPreconditionFailed(nil, "OIDCS-kj3g2", "Errors.User.NotActive"),
|
wantErr: zerrors.ThrowPreconditionFailed(nil, "OIDCS-kj3g2", "Errors.User.NotActive"),
|
||||||
},
|
},
|
||||||
@ -1030,6 +1041,114 @@ func TestCommands_CreateOIDCSessionFromDeviceAuth(t *testing.T) {
|
|||||||
args: args{
|
args: args{
|
||||||
ctx,
|
ctx,
|
||||||
"123",
|
"123",
|
||||||
|
"",
|
||||||
|
},
|
||||||
|
want: &OIDCSession{
|
||||||
|
TokenID: "V2_oidcSessionID-at_accessTokenID",
|
||||||
|
ClientID: "clientID",
|
||||||
|
UserID: "userID",
|
||||||
|
Audience: []string{"audience"},
|
||||||
|
Expiration: time.Time{}.Add(time.Hour),
|
||||||
|
Scope: []string{"openid", "offline_access"},
|
||||||
|
AuthMethods: []domain.UserAuthMethodType{domain.UserAuthMethodTypePassword},
|
||||||
|
AuthTime: testNow,
|
||||||
|
PreferredLanguage: &language.Afrikaans,
|
||||||
|
UserAgent: &domain.UserAgent{
|
||||||
|
FingerprintID: gu.Ptr("fp1"),
|
||||||
|
IP: net.ParseIP("1.2.3.4"),
|
||||||
|
Description: gu.Ptr("firefox"),
|
||||||
|
Header: http.Header{"foo": []string{"bar"}},
|
||||||
|
},
|
||||||
|
Reason: domain.TokenReasonAuthRequest,
|
||||||
|
SessionID: "sessionID",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "approved with backChannelLogout (feature enabled), success",
|
||||||
|
fields: fields{
|
||||||
|
eventstore: expectEventstore(
|
||||||
|
expectFilter(
|
||||||
|
eventFromEventPusherWithInstanceID(
|
||||||
|
"instance1",
|
||||||
|
deviceauth.NewAddedEvent(
|
||||||
|
ctx,
|
||||||
|
deviceauth.NewAggregate("123", "instance1"),
|
||||||
|
"clientID", "123", "456", time.Now().Add(-time.Minute),
|
||||||
|
[]string{"openid", "offline_access"},
|
||||||
|
[]string{"audience"}, false,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
eventFromEventPusherWithInstanceID(
|
||||||
|
"instance1",
|
||||||
|
deviceauth.NewApprovedEvent(ctx,
|
||||||
|
deviceauth.NewAggregate("123", "instance1"),
|
||||||
|
"userID", "org1",
|
||||||
|
[]domain.UserAuthMethodType{domain.UserAuthMethodTypePassword},
|
||||||
|
testNow, &language.Afrikaans, &domain.UserAgent{
|
||||||
|
FingerprintID: gu.Ptr("fp1"),
|
||||||
|
IP: net.ParseIP("1.2.3.4"),
|
||||||
|
Description: gu.Ptr("firefox"),
|
||||||
|
Header: http.Header{"foo": []string{"bar"}},
|
||||||
|
},
|
||||||
|
"sessionID",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
expectFilter(
|
||||||
|
user.NewHumanAddedEvent(
|
||||||
|
ctx,
|
||||||
|
&user.NewAggregate("userID", "org1").Aggregate,
|
||||||
|
"username",
|
||||||
|
"firstname",
|
||||||
|
"lastname",
|
||||||
|
"nickname",
|
||||||
|
"displayname",
|
||||||
|
language.English,
|
||||||
|
domain.GenderUnspecified,
|
||||||
|
"email",
|
||||||
|
false,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
expectFilter(), // token lifetime
|
||||||
|
expectPush(
|
||||||
|
oidcsession.NewAddedEvent(context.Background(), &oidcsession.NewAggregate("V2_oidcSessionID", "org1").Aggregate,
|
||||||
|
"userID", "org1", "sessionID", "clientID", []string{"audience"}, []string{"openid", "offline_access"},
|
||||||
|
[]domain.UserAuthMethodType{domain.UserAuthMethodTypePassword}, testNow, "", &language.Afrikaans, &domain.UserAgent{
|
||||||
|
FingerprintID: gu.Ptr("fp1"),
|
||||||
|
IP: net.ParseIP("1.2.3.4"),
|
||||||
|
Description: gu.Ptr("firefox"),
|
||||||
|
Header: http.Header{"foo": []string{"bar"}},
|
||||||
|
},
|
||||||
|
),
|
||||||
|
sessionlogout.NewBackChannelLogoutRegisteredEvent(context.Background(),
|
||||||
|
&sessionlogout.NewAggregate("sessionID", "instance1").Aggregate,
|
||||||
|
"V2_oidcSessionID",
|
||||||
|
"userID",
|
||||||
|
"clientID",
|
||||||
|
"backChannelLogoutURI",
|
||||||
|
),
|
||||||
|
oidcsession.NewAccessTokenAddedEvent(context.Background(),
|
||||||
|
&oidcsession.NewAggregate("V2_oidcSessionID", "org1").Aggregate,
|
||||||
|
"at_accessTokenID", []string{"openid", "offline_access"}, time.Hour, domain.TokenReasonAuthRequest, nil,
|
||||||
|
),
|
||||||
|
user.NewUserTokenV2AddedEvent(context.Background(), &user.NewAggregate("userID", "org1").Aggregate, "at_accessTokenID"),
|
||||||
|
deviceauth.NewDoneEvent(ctx,
|
||||||
|
deviceauth.NewAggregate("123", "instance1"),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
idGenerator: mock.NewIDGeneratorExpectIDs(t, "oidcSessionID", "accessTokenID"),
|
||||||
|
defaultAccessTokenLifetime: time.Hour,
|
||||||
|
defaultRefreshTokenLifetime: 7 * 24 * time.Hour,
|
||||||
|
defaultRefreshTokenIdleLifetime: 24 * time.Hour,
|
||||||
|
keyAlgorithm: crypto.CreateMockEncryptionAlg(gomock.NewController(t)),
|
||||||
|
},
|
||||||
|
args: args{
|
||||||
|
authz.WithFeatures(ctx, feature.Features{
|
||||||
|
EnableBackChannelLogout: true,
|
||||||
|
}),
|
||||||
|
"123",
|
||||||
|
"backChannelLogoutURI",
|
||||||
},
|
},
|
||||||
want: &OIDCSession{
|
want: &OIDCSession{
|
||||||
TokenID: "V2_oidcSessionID-at_accessTokenID",
|
TokenID: "V2_oidcSessionID-at_accessTokenID",
|
||||||
@ -1130,6 +1249,7 @@ func TestCommands_CreateOIDCSessionFromDeviceAuth(t *testing.T) {
|
|||||||
args: args{
|
args: args{
|
||||||
ctx,
|
ctx,
|
||||||
"123",
|
"123",
|
||||||
|
"",
|
||||||
},
|
},
|
||||||
want: &OIDCSession{
|
want: &OIDCSession{
|
||||||
TokenID: "V2_oidcSessionID-at_accessTokenID",
|
TokenID: "V2_oidcSessionID-at_accessTokenID",
|
||||||
@ -1163,7 +1283,7 @@ func TestCommands_CreateOIDCSessionFromDeviceAuth(t *testing.T) {
|
|||||||
defaultRefreshTokenIdleLifetime: tt.fields.defaultRefreshTokenIdleLifetime,
|
defaultRefreshTokenIdleLifetime: tt.fields.defaultRefreshTokenIdleLifetime,
|
||||||
keyAlgorithm: tt.fields.keyAlgorithm,
|
keyAlgorithm: tt.fields.keyAlgorithm,
|
||||||
}
|
}
|
||||||
got, err := c.CreateOIDCSessionFromDeviceAuth(tt.args.ctx, tt.args.deviceCode)
|
got, err := c.CreateOIDCSessionFromDeviceAuth(tt.args.ctx, tt.args.deviceCode, tt.args.backChannelLogoutURI)
|
||||||
c.jobs.Wait()
|
c.jobs.Wait()
|
||||||
|
|
||||||
require.ErrorIs(t, err, tt.wantErr)
|
require.ErrorIs(t, err, tt.wantErr)
|
||||||
|
@ -55,7 +55,13 @@ type AuthRequestComplianceChecker func(context.Context, *AuthRequestWriteModel)
|
|||||||
// CreateOIDCSessionFromAuthRequest creates a new OIDC Session, creates an access token and refresh token.
|
// CreateOIDCSessionFromAuthRequest creates a new OIDC Session, creates an access token and refresh token.
|
||||||
// It returns the access token id, expiration and the refresh token.
|
// It returns the access token id, expiration and the refresh token.
|
||||||
// If the underlying [AuthRequest] is a OIDC Auth Code Flow, it will set the code as exchanged.
|
// If the underlying [AuthRequest] is a OIDC Auth Code Flow, it will set the code as exchanged.
|
||||||
func (c *Commands) CreateOIDCSessionFromAuthRequest(ctx context.Context, authReqId string, complianceCheck AuthRequestComplianceChecker, needRefreshToken bool) (session *OIDCSession, state string, err error) {
|
func (c *Commands) CreateOIDCSessionFromAuthRequest(
|
||||||
|
ctx context.Context,
|
||||||
|
authReqId string,
|
||||||
|
complianceCheck AuthRequestComplianceChecker,
|
||||||
|
needRefreshToken bool,
|
||||||
|
backChannelLogoutURI string,
|
||||||
|
) (session *OIDCSession, state string, err error) {
|
||||||
ctx, span := tracing.NewSpan(ctx)
|
ctx, span := tracing.NewSpan(ctx)
|
||||||
defer func() { span.EndWithError(err) }()
|
defer func() { span.EndWithError(err) }()
|
||||||
|
|
||||||
@ -108,6 +114,7 @@ func (c *Commands) CreateOIDCSessionFromAuthRequest(ctx context.Context, authReq
|
|||||||
sessionModel.PreferredLanguage,
|
sessionModel.PreferredLanguage,
|
||||||
sessionModel.UserAgent,
|
sessionModel.UserAgent,
|
||||||
)
|
)
|
||||||
|
cmd.RegisterLogout(ctx, sessionModel.AggregateID, sessionModel.UserID, authReqModel.ClientID, backChannelLogoutURI)
|
||||||
|
|
||||||
if authReqModel.ResponseType != domain.OIDCResponseTypeIDToken {
|
if authReqModel.ResponseType != domain.OIDCResponseTypeIDToken {
|
||||||
if err = cmd.AddAccessToken(ctx, authReqModel.Scope, sessionModel.UserID, sessionModel.UserResourceOwner, domain.TokenReasonAuthRequest, nil); err != nil {
|
if err = cmd.AddAccessToken(ctx, authReqModel.Scope, sessionModel.UserID, sessionModel.UserResourceOwner, domain.TokenReasonAuthRequest, nil); err != nil {
|
||||||
|
@ -49,10 +49,11 @@ func TestCommands_CreateOIDCSessionFromAuthRequest(t *testing.T) {
|
|||||||
keyAlgorithm crypto.EncryptionAlgorithm
|
keyAlgorithm crypto.EncryptionAlgorithm
|
||||||
}
|
}
|
||||||
type args struct {
|
type args struct {
|
||||||
ctx context.Context
|
ctx context.Context
|
||||||
authRequestID string
|
authRequestID string
|
||||||
complianceCheck AuthRequestComplianceChecker
|
complianceCheck AuthRequestComplianceChecker
|
||||||
needRefreshToken bool
|
needRefreshToken bool
|
||||||
|
backChannelLogoutURI string
|
||||||
}
|
}
|
||||||
type res struct {
|
type res struct {
|
||||||
session *OIDCSession
|
session *OIDCSession
|
||||||
@ -438,6 +439,151 @@ func TestCommands_CreateOIDCSessionFromAuthRequest(t *testing.T) {
|
|||||||
state: "state",
|
state: "state",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"add successful, backChannelLogout (feature enabled)",
|
||||||
|
fields{
|
||||||
|
eventstore: expectEventstore(
|
||||||
|
expectFilter(
|
||||||
|
eventFromEventPusher(
|
||||||
|
authrequest.NewAddedEvent(context.Background(), &authrequest.NewAggregate("V2_authRequestID", "instanceID").Aggregate,
|
||||||
|
"loginClient",
|
||||||
|
"clientID",
|
||||||
|
"redirectURI",
|
||||||
|
"state",
|
||||||
|
"nonce",
|
||||||
|
[]string{"openid", "offline_access"},
|
||||||
|
[]string{"audience"},
|
||||||
|
domain.OIDCResponseTypeCode,
|
||||||
|
domain.OIDCResponseModeQuery,
|
||||||
|
&domain.OIDCCodeChallenge{
|
||||||
|
Challenge: "challenge",
|
||||||
|
Method: domain.CodeChallengeMethodS256,
|
||||||
|
},
|
||||||
|
[]domain.Prompt{domain.PromptNone},
|
||||||
|
[]string{"en", "de"},
|
||||||
|
gu.Ptr(time.Duration(0)),
|
||||||
|
gu.Ptr("loginHint"),
|
||||||
|
gu.Ptr("hintUserID"),
|
||||||
|
true,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
eventFromEventPusher(
|
||||||
|
authrequest.NewCodeAddedEvent(context.Background(), &authrequest.NewAggregate("V2_authRequestID", "instanceID").Aggregate),
|
||||||
|
),
|
||||||
|
eventFromEventPusher(
|
||||||
|
authrequest.NewSessionLinkedEvent(context.Background(), &authrequest.NewAggregate("V2_authRequestID", "instanceID").Aggregate,
|
||||||
|
"sessionID",
|
||||||
|
"userID",
|
||||||
|
testNow,
|
||||||
|
[]domain.UserAuthMethodType{domain.UserAuthMethodTypePassword},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
expectFilter(
|
||||||
|
eventFromEventPusher(
|
||||||
|
session.NewAddedEvent(context.Background(),
|
||||||
|
&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(context.Background(), &session.NewAggregate("sessionID", "instanceID").Aggregate,
|
||||||
|
"userID", "org1", testNow, &language.Afrikaans),
|
||||||
|
),
|
||||||
|
eventFromEventPusher(
|
||||||
|
session.NewPasswordCheckedEvent(context.Background(), &session.NewAggregate("sessionID", "instanceID").Aggregate,
|
||||||
|
testNow),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
expectFilter(
|
||||||
|
user.NewHumanAddedEvent(
|
||||||
|
context.Background(),
|
||||||
|
&user.NewAggregate("userID", "org1").Aggregate,
|
||||||
|
"username",
|
||||||
|
"firstname",
|
||||||
|
"lastname",
|
||||||
|
"nickname",
|
||||||
|
"displayname",
|
||||||
|
language.Afrikaans,
|
||||||
|
domain.GenderUnspecified,
|
||||||
|
"email",
|
||||||
|
false,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
expectFilter(), // token lifetime
|
||||||
|
expectPush(
|
||||||
|
authrequest.NewCodeExchangedEvent(context.Background(), &authrequest.NewAggregate("V2_authRequestID", "instanceID").Aggregate),
|
||||||
|
oidcsession.NewAddedEvent(context.Background(), &oidcsession.NewAggregate("V2_oidcSessionID", "org1").Aggregate,
|
||||||
|
"userID", "org1", "sessionID", "clientID", []string{"audience"}, []string{"openid", "offline_access"},
|
||||||
|
[]domain.UserAuthMethodType{domain.UserAuthMethodTypePassword}, testNow, "nonce", &language.Afrikaans,
|
||||||
|
&domain.UserAgent{
|
||||||
|
FingerprintID: gu.Ptr("fp1"),
|
||||||
|
IP: net.ParseIP("1.2.3.4"),
|
||||||
|
Description: gu.Ptr("firefox"),
|
||||||
|
Header: http.Header{"foo": []string{"bar"}},
|
||||||
|
},
|
||||||
|
),
|
||||||
|
sessionlogout.NewBackChannelLogoutRegisteredEvent(context.Background(),
|
||||||
|
&sessionlogout.NewAggregate("sessionID", "instanceID").Aggregate,
|
||||||
|
"V2_oidcSessionID",
|
||||||
|
"userID",
|
||||||
|
"clientID",
|
||||||
|
"backChannelLogoutURI",
|
||||||
|
),
|
||||||
|
oidcsession.NewAccessTokenAddedEvent(context.Background(), &oidcsession.NewAggregate("V2_oidcSessionID", "org1").Aggregate,
|
||||||
|
"at_accessTokenID", []string{"openid", "offline_access"}, time.Hour, domain.TokenReasonAuthRequest, nil),
|
||||||
|
user.NewUserTokenV2AddedEvent(context.Background(), &user.NewAggregate("userID", "org1").Aggregate, "at_accessTokenID"),
|
||||||
|
oidcsession.NewRefreshTokenAddedEvent(context.Background(), &oidcsession.NewAggregate("V2_oidcSessionID", "org1").Aggregate,
|
||||||
|
"rt_refreshTokenID", 7*24*time.Hour, 24*time.Hour),
|
||||||
|
authrequest.NewSucceededEvent(context.Background(), &authrequest.NewAggregate("V2_authRequestID", "instanceID").Aggregate),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
idGenerator: mock.NewIDGeneratorExpectIDs(t, "oidcSessionID", "accessTokenID", "refreshTokenID"),
|
||||||
|
defaultAccessTokenLifetime: time.Hour,
|
||||||
|
defaultRefreshTokenLifetime: 7 * 24 * time.Hour,
|
||||||
|
defaultRefreshTokenIdleLifetime: 24 * time.Hour,
|
||||||
|
keyAlgorithm: crypto.CreateMockEncryptionAlg(gomock.NewController(t)),
|
||||||
|
},
|
||||||
|
args{
|
||||||
|
|
||||||
|
ctx: authz.WithFeatures(authz.WithInstanceID(context.Background(), "instanceID"), feature.Features{
|
||||||
|
EnableBackChannelLogout: true,
|
||||||
|
}),
|
||||||
|
authRequestID: "V2_authRequestID",
|
||||||
|
complianceCheck: mockAuthRequestComplianceChecker(nil),
|
||||||
|
needRefreshToken: true,
|
||||||
|
backChannelLogoutURI: "backChannelLogoutURI",
|
||||||
|
},
|
||||||
|
res{
|
||||||
|
session: &OIDCSession{
|
||||||
|
SessionID: "sessionID",
|
||||||
|
TokenID: "V2_oidcSessionID-at_accessTokenID",
|
||||||
|
ClientID: "clientID",
|
||||||
|
UserID: "userID",
|
||||||
|
Audience: []string{"audience"},
|
||||||
|
Expiration: time.Time{}.Add(time.Hour),
|
||||||
|
Scope: []string{"openid", "offline_access"},
|
||||||
|
AuthMethods: []domain.UserAuthMethodType{domain.UserAuthMethodTypePassword},
|
||||||
|
AuthTime: testNow,
|
||||||
|
Nonce: "nonce",
|
||||||
|
PreferredLanguage: &language.Afrikaans,
|
||||||
|
UserAgent: &domain.UserAgent{
|
||||||
|
FingerprintID: gu.Ptr("fp1"),
|
||||||
|
IP: net.ParseIP("1.2.3.4"),
|
||||||
|
Description: gu.Ptr("firefox"),
|
||||||
|
Header: http.Header{"foo": []string{"bar"}},
|
||||||
|
},
|
||||||
|
Reason: domain.TokenReasonAuthRequest,
|
||||||
|
RefreshToken: "VjJfb2lkY1Nlc3Npb25JRC1ydF9yZWZyZXNoVG9rZW5JRDp1c2VySUQ", //V2_oidcSessionID-rt_refreshTokenID:userID
|
||||||
|
},
|
||||||
|
state: "state",
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"disable user token event",
|
"disable user token event",
|
||||||
fields{
|
fields{
|
||||||
@ -708,7 +854,7 @@ func TestCommands_CreateOIDCSessionFromAuthRequest(t *testing.T) {
|
|||||||
keyAlgorithm: tt.fields.keyAlgorithm,
|
keyAlgorithm: tt.fields.keyAlgorithm,
|
||||||
}
|
}
|
||||||
c.setMilestonesCompletedForTest("instanceID")
|
c.setMilestonesCompletedForTest("instanceID")
|
||||||
gotSession, gotState, err := c.CreateOIDCSessionFromAuthRequest(tt.args.ctx, tt.args.authRequestID, tt.args.complianceCheck, tt.args.needRefreshToken)
|
gotSession, gotState, err := c.CreateOIDCSessionFromAuthRequest(tt.args.ctx, tt.args.authRequestID, tt.args.complianceCheck, tt.args.needRefreshToken, tt.args.backChannelLogoutURI)
|
||||||
require.ErrorIs(t, err, tt.res.err)
|
require.ErrorIs(t, err, tt.res.err)
|
||||||
|
|
||||||
if gotSession != nil {
|
if gotSession != nil {
|
||||||
|
@ -660,7 +660,7 @@ func NewLifetimeSetEvent(
|
|||||||
type TerminateEvent struct {
|
type TerminateEvent struct {
|
||||||
eventstore.BaseEvent `json:"-"`
|
eventstore.BaseEvent `json:"-"`
|
||||||
|
|
||||||
TriggerOrigin string `json:"triggerOrigin,omitempty"`
|
TriggeredAtOrigin string `json:"triggerOrigin,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *TerminateEvent) Payload() interface{} {
|
func (e *TerminateEvent) Payload() interface{} {
|
||||||
@ -671,6 +671,10 @@ func (e *TerminateEvent) UniqueConstraints() []*eventstore.UniqueConstraint {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (e *TerminateEvent) TriggerOrigin() string {
|
||||||
|
return e.TriggeredAtOrigin
|
||||||
|
}
|
||||||
|
|
||||||
func NewTerminateEvent(
|
func NewTerminateEvent(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
aggregate *eventstore.Aggregate,
|
aggregate *eventstore.Aggregate,
|
||||||
@ -681,6 +685,7 @@ func NewTerminateEvent(
|
|||||||
aggregate,
|
aggregate,
|
||||||
TerminateType,
|
TerminateType,
|
||||||
),
|
),
|
||||||
|
TriggeredAtOrigin: http.DomainContext(ctx).Origin(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user