fix: user session with external login (#797)

* fix: user session with external login

* fix: tests

* fix: tests

* fix: change idp config name
This commit is contained in:
Fabi 2020-10-02 08:02:09 +02:00 committed by GitHub
parent 124988e2d2
commit 198370325d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
24 changed files with 267 additions and 101 deletions

View File

@ -52,6 +52,7 @@ SystemDefaults:
EncryptionKeyID: $ZITADEL_OTP_VERIFICATION_KEY EncryptionKeyID: $ZITADEL_OTP_VERIFICATION_KEY
VerificationLifetimes: VerificationLifetimes:
PasswordCheck: 240h #10d PasswordCheck: 240h #10d
ExternalLoginCheck: 240h #10d
MfaInitSkip: 720h #30d MfaInitSkip: 720h #30d
MfaSoftwareCheck: 18h MfaSoftwareCheck: 18h
MfaHardwareCheck: 12h MfaHardwareCheck: 12h

View File

@ -6,6 +6,7 @@ import (
"github.com/caos/zitadel/internal/config/systemdefaults" "github.com/caos/zitadel/internal/config/systemdefaults"
caos_errs "github.com/caos/zitadel/internal/errors" caos_errs "github.com/caos/zitadel/internal/errors"
"github.com/caos/zitadel/internal/iam/repository/eventsourcing" "github.com/caos/zitadel/internal/iam/repository/eventsourcing"
iam_view_model "github.com/caos/zitadel/internal/iam/repository/view/model"
org_es "github.com/caos/zitadel/internal/org/repository/eventsourcing" org_es "github.com/caos/zitadel/internal/org/repository/eventsourcing"
org_es_model "github.com/caos/zitadel/internal/org/repository/eventsourcing/model" org_es_model "github.com/caos/zitadel/internal/org/repository/eventsourcing/model"
"github.com/caos/zitadel/internal/user/repository/eventsourcing/model" "github.com/caos/zitadel/internal/user/repository/eventsourcing/model"
@ -80,16 +81,21 @@ func (m *ExternalIDP) processUser(event *models.Event) (err error) {
func (m *ExternalIDP) processIdpConfig(event *models.Event) (err error) { func (m *ExternalIDP) processIdpConfig(event *models.Event) (err error) {
switch event.Type { switch event.Type {
case iam_es_model.IDPConfigChanged, org_es_model.IDPConfigChanged: case iam_es_model.IDPConfigChanged, org_es_model.IDPConfigChanged:
configView := new(iam_view_model.IDPConfigView)
config := new(iam_model.IDPConfig) config := new(iam_model.IDPConfig)
config.AppendEvent(event) if event.Type == iam_es_model.IDPConfigChanged {
exterinalIDPs, err := m.view.ExternalIDPsByIDPConfigID(config.IDPConfigID) configView.AppendEvent(iam_model.IDPProviderTypeSystem, event)
} else {
configView.AppendEvent(iam_model.IDPProviderTypeOrg, event)
}
exterinalIDPs, err := m.view.ExternalIDPsByIDPConfigID(configView.IDPConfigID)
if err != nil { if err != nil {
return err return err
} }
if event.AggregateType == iam_es_model.IAMAggregate { if event.AggregateType == iam_es_model.IAMAggregate {
config, err = m.iamEvents.GetIDPConfig(context.Background(), config.AggregateID, config.IDPConfigID) config, err = m.iamEvents.GetIDPConfig(context.Background(), event.AggregateID, configView.IDPConfigID)
} else { } else {
config, err = m.orgEvents.GetIDPConfig(context.Background(), config.AggregateID, config.IDPConfigID) config, err = m.orgEvents.GetIDPConfig(context.Background(), event.AggregateID, configView.IDPConfigID)
} }
if err != nil { if err != nil {
return err return err

View File

@ -16,13 +16,13 @@ type AuthRequestRepository interface {
SaveAuthCode(ctx context.Context, id, code, userAgentID string) error SaveAuthCode(ctx context.Context, id, code, userAgentID string) error
DeleteAuthRequest(ctx context.Context, id string) error DeleteAuthRequest(ctx context.Context, id string) error
CheckLoginName(ctx context.Context, id, loginName, userAgentID string) error CheckLoginName(ctx context.Context, id, loginName, userAgentID string) error
CheckExternalUserLogin(ctx context.Context, authReqID, userAgentID string, user *model.ExternalUser) error CheckExternalUserLogin(ctx context.Context, authReqID, userAgentID string, user *model.ExternalUser, info *model.BrowserInfo) error
SelectUser(ctx context.Context, id, userID, userAgentID string) error SelectUser(ctx context.Context, id, userID, userAgentID string) error
SelectExternalIDP(ctx context.Context, authReqID, idpConfigID, userAgentID string) error SelectExternalIDP(ctx context.Context, authReqID, idpConfigID, userAgentID string) error
VerifyPassword(ctx context.Context, id, userID, password, userAgentID string, info *model.BrowserInfo) error VerifyPassword(ctx context.Context, id, userID, password, userAgentID string, info *model.BrowserInfo) error
VerifyMfaOTP(ctx context.Context, agentID, authRequestID, code, userAgentID string, info *model.BrowserInfo) error VerifyMfaOTP(ctx context.Context, agentID, authRequestID, code, userAgentID string, info *model.BrowserInfo) error
LinkExternalUsers(ctx context.Context, authReqID, userAgentID string) error LinkExternalUsers(ctx context.Context, authReqID, userAgentID string, info *model.BrowserInfo) error
AutoRegisterExternalUser(ctx context.Context, user *user_model.User, externalIDP *user_model.ExternalIDP, member *org_model.OrgMember, authReqID, userAgentID, resourceOwner string) error AutoRegisterExternalUser(ctx context.Context, user *user_model.User, externalIDP *user_model.ExternalIDP, member *org_model.OrgMember, authReqID, userAgentID, resourceOwner string, info *model.BrowserInfo) error
ResetLinkingUsers(ctx context.Context, authReqID, userAgentID string) error ResetLinkingUsers(ctx context.Context, authReqID, userAgentID string) error
GetOrgByPrimaryDomain(primaryDomain string) (*org_model.OrgView, error) GetOrgByPrimaryDomain(primaryDomain string) (*org_model.OrgView, error)
} }

View File

@ -43,10 +43,11 @@ type AuthRequestRepo struct {
IdGenerator id.Generator IdGenerator id.Generator
PasswordCheckLifeTime time.Duration PasswordCheckLifeTime time.Duration
MfaInitSkippedLifeTime time.Duration ExternalLoginCheckLifeTime time.Duration
MfaSoftwareCheckLifeTime time.Duration MfaInitSkippedLifeTime time.Duration
MfaHardwareCheckLifeTime time.Duration MfaSoftwareCheckLifeTime time.Duration
MfaHardwareCheckLifeTime time.Duration
IAMID string IAMID string
} }
@ -164,7 +165,7 @@ func (repo *AuthRequestRepo) SelectExternalIDP(ctx context.Context, authReqID, i
return repo.AuthRequests.UpdateAuthRequest(ctx, request) return repo.AuthRequests.UpdateAuthRequest(ctx, request)
} }
func (repo *AuthRequestRepo) CheckExternalUserLogin(ctx context.Context, authReqID, userAgentID string, externalUser *model.ExternalUser) error { func (repo *AuthRequestRepo) CheckExternalUserLogin(ctx context.Context, authReqID, userAgentID string, externalUser *model.ExternalUser, info *model.BrowserInfo) error {
request, err := repo.getAuthRequest(ctx, authReqID, userAgentID) request, err := repo.getAuthRequest(ctx, authReqID, userAgentID)
if err != nil { if err != nil {
return err return err
@ -176,6 +177,11 @@ func (repo *AuthRequestRepo) CheckExternalUserLogin(ctx context.Context, authReq
if err != nil { if err != nil {
return err return err
} }
err = repo.UserEvents.ExternalLoginChecked(ctx, request.UserID, request.WithCurrentInfo(info))
if err != nil {
return err
}
return repo.AuthRequests.UpdateAuthRequest(ctx, request) return repo.AuthRequests.UpdateAuthRequest(ctx, request)
} }
@ -219,7 +225,7 @@ func (repo *AuthRequestRepo) VerifyMfaOTP(ctx context.Context, authRequestID, us
return repo.UserEvents.CheckMfaOTP(ctx, userID, code, request.WithCurrentInfo(info)) return repo.UserEvents.CheckMfaOTP(ctx, userID, code, request.WithCurrentInfo(info))
} }
func (repo *AuthRequestRepo) LinkExternalUsers(ctx context.Context, authReqID, userAgentID string) error { func (repo *AuthRequestRepo) LinkExternalUsers(ctx context.Context, authReqID, userAgentID string, info *model.BrowserInfo) error {
request, err := repo.getAuthRequest(ctx, authReqID, userAgentID) request, err := repo.getAuthRequest(ctx, authReqID, userAgentID)
if err != nil { if err != nil {
return err return err
@ -228,6 +234,10 @@ func (repo *AuthRequestRepo) LinkExternalUsers(ctx context.Context, authReqID, u
if err != nil { if err != nil {
return err return err
} }
err = repo.UserEvents.ExternalLoginChecked(ctx, request.UserID, request.WithCurrentInfo(info))
if err != nil {
return err
}
request.LinkingUsers = nil request.LinkingUsers = nil
return repo.AuthRequests.UpdateAuthRequest(ctx, request) return repo.AuthRequests.UpdateAuthRequest(ctx, request)
} }
@ -242,7 +252,7 @@ func (repo *AuthRequestRepo) ResetLinkingUsers(ctx context.Context, authReqID, u
return repo.AuthRequests.UpdateAuthRequest(ctx, request) return repo.AuthRequests.UpdateAuthRequest(ctx, request)
} }
func (repo *AuthRequestRepo) AutoRegisterExternalUser(ctx context.Context, registerUser *user_model.User, externalIDP *user_model.ExternalIDP, orgMember *org_model.OrgMember, authReqID, userAgentID, resourceOwner string) error { func (repo *AuthRequestRepo) AutoRegisterExternalUser(ctx context.Context, registerUser *user_model.User, externalIDP *user_model.ExternalIDP, orgMember *org_model.OrgMember, authReqID, userAgentID, resourceOwner string, info *model.BrowserInfo) error {
request, err := repo.getAuthRequest(ctx, authReqID, userAgentID) request, err := repo.getAuthRequest(ctx, authReqID, userAgentID)
if err != nil { if err != nil {
return err return err
@ -277,8 +287,13 @@ func (repo *AuthRequestRepo) AutoRegisterExternalUser(ctx context.Context, regis
return err return err
} }
request.UserID = user.AggregateID request.UserID = user.AggregateID
request.UserOrgID = user.ResourceOwner
request.SelectedIDPConfigID = externalIDP.IDPConfigID request.SelectedIDPConfigID = externalIDP.IDPConfigID
request.LinkingUsers = nil request.LinkingUsers = nil
err = repo.UserEvents.ExternalLoginChecked(ctx, request.UserID, request.WithCurrentInfo(info))
if err != nil {
return err
}
return repo.AuthRequests.UpdateAuthRequest(ctx, request) return repo.AuthRequests.UpdateAuthRequest(ctx, request)
} }
@ -475,7 +490,11 @@ func (repo *AuthRequestRepo) nextSteps(ctx context.Context, request *model.AuthR
return nil, err return nil, err
} }
if request.SelectedIDPConfigID == "" || (request.SelectedIDPConfigID != "" && request.LinkingUsers != nil && len(request.LinkingUsers) > 0) { if (request.SelectedIDPConfigID != "" || userSession.SelectedIDPConfigID != "") && (request.LinkingUsers == nil || len(request.LinkingUsers) == 0) {
if !checkVerificationTime(userSession.ExternalLoginVerification, repo.ExternalLoginCheckLifeTime) {
return append(steps, &model.ExternalLoginStep{}), nil
}
} else if (request.SelectedIDPConfigID == "" && userSession.SelectedIDPConfigID == "") || (request.SelectedIDPConfigID != "" && request.LinkingUsers != nil && len(request.LinkingUsers) > 0) {
if user.InitRequired { if user.InitRequired {
return append(steps, &model.InitUserStep{PasswordSet: user.PasswordSet}), nil return append(steps, &model.InitUserStep{PasswordSet: user.PasswordSet}), nil
} }
@ -643,6 +662,7 @@ func userSessionByIDs(ctx context.Context, provider userSessionViewProvider, eve
es_model.UserDeactivated, es_model.UserDeactivated,
es_model.HumanPasswordCheckSucceeded, es_model.HumanPasswordCheckSucceeded,
es_model.HumanPasswordCheckFailed, es_model.HumanPasswordCheckFailed,
es_model.HumanExternalLoginCheckSucceeded,
es_model.HumanMFAOTPCheckSucceeded, es_model.HumanMFAOTPCheckSucceeded,
es_model.HumanMFAOTPCheckFailed, es_model.HumanMFAOTPCheckFailed,
es_model.HumanSignedOut: es_model.HumanSignedOut:
@ -689,15 +709,23 @@ func activeUserByID(ctx context.Context, userViewProvider userViewProvider, user
} }
func userByID(ctx context.Context, viewProvider userViewProvider, eventProvider userEventProvider, userID string) (*user_model.UserView, error) { func userByID(ctx context.Context, viewProvider userViewProvider, eventProvider userEventProvider, userID string) (*user_model.UserView, error) {
user, err := viewProvider.UserByID(userID) user, viewErr := viewProvider.UserByID(userID)
if err != nil { if viewErr != nil && !errors.IsNotFound(viewErr) {
return nil, err return nil, viewErr
} else if user == nil {
user = new(user_view_model.UserView)
} }
events, err := eventProvider.UserEventsByID(ctx, userID, user.Sequence) events, err := eventProvider.UserEventsByID(ctx, userID, user.Sequence)
if err != nil { if err != nil {
logging.Log("EVENT-dfg42").WithError(err).Debug("error retrieving new events") logging.Log("EVENT-dfg42").WithError(err).Debug("error retrieving new events")
return user_view_model.UserToModel(user), nil return user_view_model.UserToModel(user), nil
} }
if len(events) == 0 {
if viewErr != nil {
return nil, viewErr
}
return user_view_model.UserToModel(user), viewErr
}
userCopy := *user userCopy := *user
for _, event := range events { for _, event := range events {
if err := userCopy.AppendEvent(event); err != nil { if err := userCopy.AppendEvent(event); err != nil {

View File

@ -42,9 +42,10 @@ func (m *mockViewErrUserSession) UserSessionsByAgentID(string) ([]*user_view_mod
} }
type mockViewUserSession struct { type mockViewUserSession struct {
PasswordVerification time.Time ExternalLoginVerification time.Time
MfaSoftwareVerification time.Time PasswordVerification time.Time
Users []mockUser MfaSoftwareVerification time.Time
Users []mockUser
} }
type mockUser struct { type mockUser struct {
@ -54,8 +55,9 @@ type mockUser struct {
func (m *mockViewUserSession) UserSessionByIDs(string, string) (*user_view_model.UserSessionView, error) { func (m *mockViewUserSession) UserSessionByIDs(string, string) (*user_view_model.UserSessionView, error) {
return &user_view_model.UserSessionView{ return &user_view_model.UserSessionView{
PasswordVerification: m.PasswordVerification, ExternalLoginVerification: m.ExternalLoginVerification,
MfaSoftwareVerification: m.MfaSoftwareVerification, PasswordVerification: m.PasswordVerification,
MfaSoftwareVerification: m.MfaSoftwareVerification,
}, nil }, nil
} }
@ -157,17 +159,18 @@ func (m *mockViewErrOrg) OrgByPrimaryDomain(string) (*org_view_model.OrgView, er
func TestAuthRequestRepo_nextSteps(t *testing.T) { func TestAuthRequestRepo_nextSteps(t *testing.T) {
type fields struct { type fields struct {
UserEvents *user_event.UserEventstore UserEvents *user_event.UserEventstore
AuthRequests *cache.AuthRequestCache AuthRequests *cache.AuthRequestCache
View *view.View View *view.View
userSessionViewProvider userSessionViewProvider userSessionViewProvider userSessionViewProvider
userViewProvider userViewProvider userViewProvider userViewProvider
userEventProvider userEventProvider userEventProvider userEventProvider
orgViewProvider orgViewProvider orgViewProvider orgViewProvider
PasswordCheckLifeTime time.Duration PasswordCheckLifeTime time.Duration
MfaInitSkippedLifeTime time.Duration ExternalLoginCheckLifeTime time.Duration
MfaSoftwareCheckLifeTime time.Duration MfaInitSkippedLifeTime time.Duration
MfaHardwareCheckLifeTime time.Duration MfaSoftwareCheckLifeTime time.Duration
MfaHardwareCheckLifeTime time.Duration
} }
type args struct { type args struct {
request *model.AuthRequest request *model.AuthRequest
@ -391,7 +394,7 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) {
nil, nil,
}, },
{ {
"external user (no password set), callback", "external user (no external verification), external login step",
fields{ fields{
userSessionViewProvider: &mockViewUserSession{ userSessionViewProvider: &mockViewUserSession{
MfaSoftwareVerification: time.Now().UTC().Add(-5 * time.Minute), MfaSoftwareVerification: time.Now().UTC().Add(-5 * time.Minute),
@ -405,6 +408,26 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) {
MfaSoftwareCheckLifeTime: 18 * time.Hour, MfaSoftwareCheckLifeTime: 18 * time.Hour,
}, },
args{&model.AuthRequest{UserID: "UserID", SelectedIDPConfigID: "IDPConfigID"}, false}, args{&model.AuthRequest{UserID: "UserID", SelectedIDPConfigID: "IDPConfigID"}, false},
[]model.NextStep{&model.ExternalLoginStep{}},
nil,
},
{
"external user (external verification set), callback",
fields{
userSessionViewProvider: &mockViewUserSession{
ExternalLoginVerification: time.Now().UTC().Add(-5 * time.Minute),
MfaSoftwareVerification: time.Now().UTC().Add(-5 * time.Minute),
},
userViewProvider: &mockViewUser{
IsEmailVerified: true,
MfaMaxSetUp: int32(model.MfaLevelSoftware),
},
userEventProvider: &mockEventUser{},
orgViewProvider: &mockViewOrg{State: org_model.OrgStateActive},
ExternalLoginCheckLifeTime: 10 * 24 * time.Hour,
MfaSoftwareCheckLifeTime: 18 * time.Hour,
},
args{&model.AuthRequest{UserID: "UserID", SelectedIDPConfigID: "IDPConfigID"}, false},
[]model.NextStep{&model.RedirectToCallbackStep{}}, []model.NextStep{&model.RedirectToCallbackStep{}},
nil, nil,
}, },
@ -427,16 +450,18 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) {
"external user (no password check needed), callback", "external user (no password check needed), callback",
fields{ fields{
userSessionViewProvider: &mockViewUserSession{ userSessionViewProvider: &mockViewUserSession{
MfaSoftwareVerification: time.Now().UTC().Add(-5 * time.Minute), MfaSoftwareVerification: time.Now().UTC().Add(-5 * time.Minute),
ExternalLoginVerification: time.Now().UTC().Add(-5 * time.Minute),
}, },
userViewProvider: &mockViewUser{ userViewProvider: &mockViewUser{
PasswordSet: true, PasswordSet: true,
IsEmailVerified: true, IsEmailVerified: true,
MfaMaxSetUp: int32(model.MfaLevelSoftware), MfaMaxSetUp: int32(model.MfaLevelSoftware),
}, },
userEventProvider: &mockEventUser{}, userEventProvider: &mockEventUser{},
orgViewProvider: &mockViewOrg{State: org_model.OrgStateActive}, orgViewProvider: &mockViewOrg{State: org_model.OrgStateActive},
MfaSoftwareCheckLifeTime: 18 * time.Hour, MfaSoftwareCheckLifeTime: 18 * time.Hour,
ExternalLoginCheckLifeTime: 10 * 24 * time.Hour,
}, },
args{&model.AuthRequest{UserID: "UserID", SelectedIDPConfigID: "IDPConfigID"}, false}, args{&model.AuthRequest{UserID: "UserID", SelectedIDPConfigID: "IDPConfigID"}, false},
[]model.NextStep{&model.RedirectToCallbackStep{}}, []model.NextStep{&model.RedirectToCallbackStep{}},
@ -468,17 +493,19 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) {
"external user, mfa not verified, mfa check step", "external user, mfa not verified, mfa check step",
fields{ fields{
userSessionViewProvider: &mockViewUserSession{ userSessionViewProvider: &mockViewUserSession{
PasswordVerification: time.Now().UTC().Add(-5 * time.Minute), PasswordVerification: time.Now().UTC().Add(-5 * time.Minute),
ExternalLoginVerification: time.Now().UTC().Add(-5 * time.Minute),
}, },
userViewProvider: &mockViewUser{ userViewProvider: &mockViewUser{
PasswordSet: true, PasswordSet: true,
OTPState: int32(user_model.MfaStateReady), OTPState: int32(user_model.MfaStateReady),
MfaMaxSetUp: int32(model.MfaLevelSoftware), MfaMaxSetUp: int32(model.MfaLevelSoftware),
}, },
userEventProvider: &mockEventUser{}, userEventProvider: &mockEventUser{},
orgViewProvider: &mockViewOrg{State: org_model.OrgStateActive}, orgViewProvider: &mockViewOrg{State: org_model.OrgStateActive},
PasswordCheckLifeTime: 10 * 24 * time.Hour, PasswordCheckLifeTime: 10 * 24 * time.Hour,
MfaSoftwareCheckLifeTime: 18 * time.Hour, ExternalLoginCheckLifeTime: 10 * 24 * time.Hour,
MfaSoftwareCheckLifeTime: 18 * time.Hour,
}, },
args{&model.AuthRequest{UserID: "UserID", SelectedIDPConfigID: "IDPConfigID"}, false}, args{&model.AuthRequest{UserID: "UserID", SelectedIDPConfigID: "IDPConfigID"}, false},
[]model.NextStep{&model.MfaVerificationStep{ []model.NextStep{&model.MfaVerificationStep{
@ -645,17 +672,18 @@ func TestAuthRequestRepo_nextSteps(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) {
repo := &AuthRequestRepo{ repo := &AuthRequestRepo{
UserEvents: tt.fields.UserEvents, UserEvents: tt.fields.UserEvents,
AuthRequests: tt.fields.AuthRequests, AuthRequests: tt.fields.AuthRequests,
View: tt.fields.View, View: tt.fields.View,
UserSessionViewProvider: tt.fields.userSessionViewProvider, UserSessionViewProvider: tt.fields.userSessionViewProvider,
UserViewProvider: tt.fields.userViewProvider, UserViewProvider: tt.fields.userViewProvider,
UserEventProvider: tt.fields.userEventProvider, UserEventProvider: tt.fields.userEventProvider,
OrgViewProvider: tt.fields.orgViewProvider, OrgViewProvider: tt.fields.orgViewProvider,
PasswordCheckLifeTime: tt.fields.PasswordCheckLifeTime, PasswordCheckLifeTime: tt.fields.PasswordCheckLifeTime,
MfaInitSkippedLifeTime: tt.fields.MfaInitSkippedLifeTime, ExternalLoginCheckLifeTime: tt.fields.ExternalLoginCheckLifeTime,
MfaSoftwareCheckLifeTime: tt.fields.MfaSoftwareCheckLifeTime, MfaInitSkippedLifeTime: tt.fields.MfaInitSkippedLifeTime,
MfaHardwareCheckLifeTime: tt.fields.MfaHardwareCheckLifeTime, MfaSoftwareCheckLifeTime: tt.fields.MfaSoftwareCheckLifeTime,
MfaHardwareCheckLifeTime: tt.fields.MfaHardwareCheckLifeTime,
} }
got, err := repo.nextSteps(context.Background(), tt.args.request, tt.args.checkLoggedIn) got, err := repo.nextSteps(context.Background(), tt.args.request, tt.args.checkLoggedIn)
if (err != nil && tt.wantErr == nil) || (tt.wantErr != nil && !tt.wantErr(err)) { if (err != nil && tt.wantErr == nil) || (tt.wantErr != nil && !tt.wantErr(err)) {
@ -1024,7 +1052,9 @@ func Test_userByID(t *testing.T) {
{ {
"not found, not found error", "not found, not found error",
args{ args{
viewProvider: &mockViewNoUser{}, userID: "userID",
viewProvider: &mockViewNoUser{},
eventProvider: &mockEventUser{},
}, },
nil, nil,
errors.IsNotFound, errors.IsNotFound,

View File

@ -5,17 +5,17 @@ import (
"github.com/caos/logging" "github.com/caos/logging"
"github.com/caos/zitadel/internal/config/systemdefaults" "github.com/caos/zitadel/internal/config/systemdefaults"
caos_errs "github.com/caos/zitadel/internal/errors" caos_errs "github.com/caos/zitadel/internal/errors"
"github.com/caos/zitadel/internal/iam/repository/eventsourcing"
org_es "github.com/caos/zitadel/internal/org/repository/eventsourcing"
org_es_model "github.com/caos/zitadel/internal/org/repository/eventsourcing/model"
"github.com/caos/zitadel/internal/user/repository/eventsourcing/model"
usr_view_model "github.com/caos/zitadel/internal/user/repository/view/model"
"github.com/caos/zitadel/internal/eventstore/models" "github.com/caos/zitadel/internal/eventstore/models"
es_models "github.com/caos/zitadel/internal/eventstore/models" es_models "github.com/caos/zitadel/internal/eventstore/models"
"github.com/caos/zitadel/internal/eventstore/spooler" "github.com/caos/zitadel/internal/eventstore/spooler"
iam_model "github.com/caos/zitadel/internal/iam/model" iam_model "github.com/caos/zitadel/internal/iam/model"
"github.com/caos/zitadel/internal/iam/repository/eventsourcing"
iam_es_model "github.com/caos/zitadel/internal/iam/repository/eventsourcing/model" iam_es_model "github.com/caos/zitadel/internal/iam/repository/eventsourcing/model"
iam_view_model "github.com/caos/zitadel/internal/iam/repository/view/model"
org_es "github.com/caos/zitadel/internal/org/repository/eventsourcing"
org_es_model "github.com/caos/zitadel/internal/org/repository/eventsourcing/model"
"github.com/caos/zitadel/internal/user/repository/eventsourcing/model"
usr_view_model "github.com/caos/zitadel/internal/user/repository/view/model"
) )
type ExternalIDP struct { type ExternalIDP struct {
@ -80,16 +80,21 @@ func (m *ExternalIDP) processUser(event *models.Event) (err error) {
func (m *ExternalIDP) processIdpConfig(event *models.Event) (err error) { func (m *ExternalIDP) processIdpConfig(event *models.Event) (err error) {
switch event.Type { switch event.Type {
case iam_es_model.IDPConfigChanged, org_es_model.IDPConfigChanged: case iam_es_model.IDPConfigChanged, org_es_model.IDPConfigChanged:
configView := new(iam_view_model.IDPConfigView)
config := new(iam_model.IDPConfig) config := new(iam_model.IDPConfig)
config.AppendEvent(event) if event.Type == iam_es_model.IDPConfigChanged {
exterinalIDPs, err := m.view.ExternalIDPsByIDPConfigID(config.IDPConfigID) configView.AppendEvent(iam_model.IDPProviderTypeSystem, event)
} else {
configView.AppendEvent(iam_model.IDPProviderTypeOrg, event)
}
exterinalIDPs, err := m.view.ExternalIDPsByIDPConfigID(configView.IDPConfigID)
if err != nil { if err != nil {
return err return err
} }
if event.AggregateType == iam_es_model.IAMAggregate { if event.AggregateType == iam_es_model.IAMAggregate {
config, err = m.iamEvents.GetIDPConfig(context.Background(), config.AggregateID, config.IDPConfigID) config, err = m.iamEvents.GetIDPConfig(context.Background(), event.AggregateID, configView.IDPConfigID)
} else { } else {
config, err = m.orgEvents.GetIDPConfig(context.Background(), config.AggregateID, config.IDPConfigID) config, err = m.orgEvents.GetIDPConfig(context.Background(), event.AggregateID, configView.IDPConfigID)
} }
if err != nil { if err != nil {
return err return err

View File

@ -45,6 +45,7 @@ func (u *UserSession) Reduce(event *models.Event) (err error) {
es_model.SignedOut, es_model.SignedOut,
es_model.HumanPasswordCheckSucceeded, es_model.HumanPasswordCheckSucceeded,
es_model.HumanPasswordCheckFailed, es_model.HumanPasswordCheckFailed,
es_model.HumanExternalLoginCheckSucceeded,
es_model.HumanMFAOTPCheckSucceeded, es_model.HumanMFAOTPCheckSucceeded,
es_model.HumanMFAOTPCheckFailed, es_model.HumanMFAOTPCheckFailed,
es_model.HumanSignedOut: es_model.HumanSignedOut:

View File

@ -136,23 +136,24 @@ func Start(conf Config, authZ authz.Config, systemDefaults sd.SystemDefaults, au
View: view, View: view,
}, },
eventstore.AuthRequestRepo{ eventstore.AuthRequestRepo{
UserEvents: user, UserEvents: user,
OrgEvents: org, OrgEvents: org,
PolicyEvents: policy, PolicyEvents: policy,
AuthRequests: authReq, AuthRequests: authReq,
View: view, View: view,
UserSessionViewProvider: view, UserSessionViewProvider: view,
UserViewProvider: view, UserViewProvider: view,
UserEventProvider: user, UserEventProvider: user,
OrgViewProvider: view, OrgViewProvider: view,
IDPProviderViewProvider: view, IDPProviderViewProvider: view,
LoginPolicyViewProvider: view, LoginPolicyViewProvider: view,
IdGenerator: idGenerator, IdGenerator: idGenerator,
PasswordCheckLifeTime: systemDefaults.VerificationLifetimes.PasswordCheck.Duration, PasswordCheckLifeTime: systemDefaults.VerificationLifetimes.PasswordCheck.Duration,
MfaInitSkippedLifeTime: systemDefaults.VerificationLifetimes.MfaInitSkip.Duration, ExternalLoginCheckLifeTime: systemDefaults.VerificationLifetimes.PasswordCheck.Duration,
MfaSoftwareCheckLifeTime: systemDefaults.VerificationLifetimes.MfaSoftwareCheck.Duration, MfaInitSkippedLifeTime: systemDefaults.VerificationLifetimes.MfaInitSkip.Duration,
MfaHardwareCheckLifeTime: systemDefaults.VerificationLifetimes.MfaHardwareCheck.Duration, MfaSoftwareCheckLifeTime: systemDefaults.VerificationLifetimes.MfaSoftwareCheck.Duration,
IAMID: systemDefaults.IamID, MfaHardwareCheckLifeTime: systemDefaults.VerificationLifetimes.MfaHardwareCheck.Duration,
IAMID: systemDefaults.IamID,
}, },
eventstore.TokenRepo{View: view}, eventstore.TokenRepo{View: view},
eventstore.KeyRepository{ eventstore.KeyRepository{

View File

@ -21,6 +21,7 @@ const (
NextStepChangeUsername NextStepChangeUsername
NextStepLinkUsers NextStepLinkUsers
NextStepExternalNotFoundOption NextStepExternalNotFoundOption
NextStepExternalLogin
) )
type UserSessionState int32 type UserSessionState int32
@ -71,6 +72,14 @@ func (s *PasswordStep) Type() NextStepType {
return NextStepPassword return NextStepPassword
} }
type ExternalLoginStep struct {
SelectedIDPConfigID string
}
func (s *ExternalLoginStep) Type() NextStepType {
return NextStepExternalLogin
}
type ChangePasswordStep struct{} type ChangePasswordStep struct{}
func (s *ChangePasswordStep) Type() NextStepType { func (s *ChangePasswordStep) Type() NextStepType {

View File

@ -53,10 +53,11 @@ type OTPConfig struct {
} }
type VerificationLifetimes struct { type VerificationLifetimes struct {
PasswordCheck types.Duration PasswordCheck types.Duration
MfaInitSkip types.Duration ExternalLoginCheck types.Duration
MfaSoftwareCheck types.Duration MfaInitSkip types.Duration
MfaHardwareCheck types.Duration MfaSoftwareCheck types.Duration
MfaHardwareCheck types.Duration
} }
type DefaultPolicies struct { type DefaultPolicies struct {

View File

@ -6,6 +6,7 @@ import (
"github.com/caos/zitadel/internal/config/systemdefaults" "github.com/caos/zitadel/internal/config/systemdefaults"
caos_errs "github.com/caos/zitadel/internal/errors" caos_errs "github.com/caos/zitadel/internal/errors"
"github.com/caos/zitadel/internal/iam/repository/eventsourcing" "github.com/caos/zitadel/internal/iam/repository/eventsourcing"
iam_view_model "github.com/caos/zitadel/internal/iam/repository/view/model"
org_es "github.com/caos/zitadel/internal/org/repository/eventsourcing" org_es "github.com/caos/zitadel/internal/org/repository/eventsourcing"
org_es_model "github.com/caos/zitadel/internal/org/repository/eventsourcing/model" org_es_model "github.com/caos/zitadel/internal/org/repository/eventsourcing/model"
"github.com/caos/zitadel/internal/user/repository/eventsourcing/model" "github.com/caos/zitadel/internal/user/repository/eventsourcing/model"
@ -80,16 +81,21 @@ func (m *ExternalIDP) processUser(event *models.Event) (err error) {
func (m *ExternalIDP) processIdpConfig(event *models.Event) (err error) { func (m *ExternalIDP) processIdpConfig(event *models.Event) (err error) {
switch event.Type { switch event.Type {
case iam_es_model.IDPConfigChanged, org_es_model.IDPConfigChanged: case iam_es_model.IDPConfigChanged, org_es_model.IDPConfigChanged:
configView := new(iam_view_model.IDPConfigView)
config := new(iam_model.IDPConfig) config := new(iam_model.IDPConfig)
config.AppendEvent(event) if event.Type == iam_es_model.IDPConfigChanged {
exterinalIDPs, err := m.view.ExternalIDPsByIDPConfigID(config.IDPConfigID) configView.AppendEvent(iam_model.IDPProviderTypeSystem, event)
} else {
configView.AppendEvent(iam_model.IDPProviderTypeOrg, event)
}
exterinalIDPs, err := m.view.ExternalIDPsByIDPConfigID(configView.IDPConfigID)
if err != nil { if err != nil {
return err return err
} }
if event.AggregateType == iam_es_model.IAMAggregate { if event.AggregateType == iam_es_model.IAMAggregate {
config, err = m.iamEvents.GetIDPConfig(context.Background(), config.AggregateID, config.IDPConfigID) config, err = m.iamEvents.GetIDPConfig(context.Background(), event.AggregateID, configView.IDPConfigID)
} else { } else {
config, err = m.orgEvents.GetIDPConfig(context.Background(), config.AggregateID, config.IDPConfigID) config, err = m.orgEvents.GetIDPConfig(context.Background(), event.AggregateID, configView.IDPConfigID)
} }
if err != nil { if err != nil {
return err return err

View File

@ -6,6 +6,7 @@ import (
http_mw "github.com/caos/zitadel/internal/api/http/middleware" http_mw "github.com/caos/zitadel/internal/api/http/middleware"
"github.com/caos/zitadel/internal/auth_request/model" "github.com/caos/zitadel/internal/auth_request/model"
"github.com/caos/zitadel/internal/crypto" "github.com/caos/zitadel/internal/crypto"
"github.com/caos/zitadel/internal/errors"
caos_errors "github.com/caos/zitadel/internal/errors" caos_errors "github.com/caos/zitadel/internal/errors"
"github.com/caos/zitadel/internal/eventstore/models" "github.com/caos/zitadel/internal/eventstore/models"
iam_model "github.com/caos/zitadel/internal/iam/model" iam_model "github.com/caos/zitadel/internal/iam/model"
@ -40,6 +41,15 @@ type externalNotFoundOptionData struct {
baseData baseData
} }
func (l *Login) handleExternalLoginStep(w http.ResponseWriter, r *http.Request, authReq *model.AuthRequest, selectedIDPConfigID string) {
for _, idp := range authReq.AllowedExternalIDPs {
if idp.IDPConfigID == selectedIDPConfigID {
l.handleIDP(w, r, authReq, selectedIDPConfigID)
}
}
l.renderLogin(w, r, authReq, errors.ThrowInvalidArgument(nil, "VIEW-Fsj7f", "Errors.User.ExternalIDP.NotAllowed"))
}
func (l *Login) handleExternalLogin(w http.ResponseWriter, r *http.Request) { func (l *Login) handleExternalLogin(w http.ResponseWriter, r *http.Request) {
data := new(externalIDPData) data := new(externalIDPData)
authReq, err := l.getAuthRequestAndParseData(r, data) authReq, err := l.getAuthRequestAndParseData(r, data)
@ -51,7 +61,11 @@ func (l *Login) handleExternalLogin(w http.ResponseWriter, r *http.Request) {
http.Redirect(w, r, l.zitadelURL, http.StatusFound) http.Redirect(w, r, l.zitadelURL, http.StatusFound)
return return
} }
idpConfig, err := l.getIDPConfigByID(r, data.IDPConfigID) l.handleIDP(w, r, authReq, data.IDPConfigID)
}
func (l *Login) handleIDP(w http.ResponseWriter, r *http.Request, authReq *model.AuthRequest, selectedIDPConfigID string) {
idpConfig, err := l.getIDPConfigByID(r, selectedIDPConfigID)
if err != nil { if err != nil {
l.renderError(w, r, authReq, err) l.renderError(w, r, authReq, err)
return return
@ -117,7 +131,7 @@ func (l *Login) getRPConfig(w http.ResponseWriter, r *http.Request, authReq *mod
func (l *Login) handleExternalUserAuthenticated(w http.ResponseWriter, r *http.Request, authReq *model.AuthRequest, idpConfig *iam_model.IDPConfigView, userAgentID string, tokens *oidc.Tokens) { func (l *Login) handleExternalUserAuthenticated(w http.ResponseWriter, r *http.Request, authReq *model.AuthRequest, idpConfig *iam_model.IDPConfigView, userAgentID string, tokens *oidc.Tokens) {
externalUser := l.mapTokenToLoginUser(tokens, idpConfig) externalUser := l.mapTokenToLoginUser(tokens, idpConfig)
err := l.authRepo.CheckExternalUserLogin(r.Context(), authReq.ID, userAgentID, externalUser) err := l.authRepo.CheckExternalUserLogin(r.Context(), authReq.ID, userAgentID, externalUser, model.BrowserInfoFromRequest(r))
if err != nil { if err != nil {
l.renderExternalNotFoundOption(w, r, authReq, nil) l.renderExternalNotFoundOption(w, r, authReq, nil)
return return
@ -196,7 +210,7 @@ func (l *Login) handleAutoRegister(w http.ResponseWriter, r *http.Request, authR
userAgentID, _ := http_mw.UserAgentIDFromCtx(r.Context()) userAgentID, _ := http_mw.UserAgentIDFromCtx(r.Context())
user, externalIDP := l.mapExternalUserToLoginUser(orgIamPolicy, authReq.LinkingUsers[len(authReq.LinkingUsers)-1], idpConfig) user, externalIDP := l.mapExternalUserToLoginUser(orgIamPolicy, authReq.LinkingUsers[len(authReq.LinkingUsers)-1], idpConfig)
err = l.authRepo.AutoRegisterExternalUser(setContext(r.Context(), resourceOwner), user, externalIDP, member, authReq.ID, userAgentID, resourceOwner) err = l.authRepo.AutoRegisterExternalUser(setContext(r.Context(), resourceOwner), user, externalIDP, member, authReq.ID, userAgentID, resourceOwner, model.BrowserInfoFromRequest(r))
if err != nil { if err != nil {
l.renderExternalNotFoundOption(w, r, authReq, err) l.renderExternalNotFoundOption(w, r, authReq, err)
return return

View File

@ -13,7 +13,7 @@ const (
func (l *Login) linkUsers(w http.ResponseWriter, r *http.Request, authReq *model.AuthRequest, err error) { func (l *Login) linkUsers(w http.ResponseWriter, r *http.Request, authReq *model.AuthRequest, err error) {
userAgentID, _ := http_mw.UserAgentIDFromCtx(r.Context()) userAgentID, _ := http_mw.UserAgentIDFromCtx(r.Context())
err = l.authRepo.LinkExternalUsers(setContext(r.Context(), authReq.UserOrgID), authReq.ID, userAgentID) err = l.authRepo.LinkExternalUsers(setContext(r.Context(), authReq.UserOrgID), authReq.ID, userAgentID, model.BrowserInfoFromRequest(r))
l.renderLinkUsersDone(w, r, authReq, err) l.renderLinkUsersDone(w, r, authReq, err)
} }

View File

@ -156,6 +156,7 @@ func (l *Login) renderNextStep(w http.ResponseWriter, r *http.Request, authReq *
authReq, err := l.authRepo.AuthRequestByID(r.Context(), authReq.ID, userAgentID) authReq, err := l.authRepo.AuthRequestByID(r.Context(), authReq.ID, userAgentID)
if err != nil { if err != nil {
l.renderInternalError(w, r, authReq, caos_errs.ThrowInternal(nil, "APP-sio0W", "could not get authreq")) l.renderInternalError(w, r, authReq, caos_errs.ThrowInternal(nil, "APP-sio0W", "could not get authreq"))
return
} }
if len(authReq.PossibleSteps) == 0 { if len(authReq.PossibleSteps) == 0 {
l.renderInternalError(w, r, authReq, caos_errs.ThrowInternal(nil, "APP-9sdp4", "no possible steps")) l.renderInternalError(w, r, authReq, caos_errs.ThrowInternal(nil, "APP-9sdp4", "no possible steps"))
@ -208,6 +209,8 @@ func (l *Login) chooseNextStep(w http.ResponseWriter, r *http.Request, authReq *
l.linkUsers(w, r, authReq, err) l.linkUsers(w, r, authReq, err)
case *model.ExternalNotFoundOptionStep: case *model.ExternalNotFoundOptionStep:
l.renderExternalNotFoundOption(w, r, authReq, err) l.renderExternalNotFoundOption(w, r, authReq, err)
case *model.ExternalLoginStep:
l.handleExternalLoginStep(w, r, authReq, step.SelectedIDPConfigID)
default: default:
l.renderInternalError(w, r, authReq, caos_errs.ThrowInternal(nil, "APP-ds3QF", "step no possible")) l.renderInternalError(w, r, authReq, caos_errs.ThrowInternal(nil, "APP-ds3QF", "step no possible"))
} }

View File

@ -225,5 +225,6 @@ Errors:
NotActive: Benutzer ist nicht aktiv NotActive: Benutzer ist nicht aktiv
ExternalIDP: ExternalIDP:
IDPTypeNotImplemented: IDP Typ ist nicht implementiert IDPTypeNotImplemented: IDP Typ ist nicht implementiert
NotAllowed: Externer Login Provider ist nicht erlaubt
optional: (optional) optional: (optional)

View File

@ -225,6 +225,7 @@ Errors:
NotActive: User is not active NotActive: User is not active
ExternalIDP: ExternalIDP:
IDPTypeNotImplemented: IDP Type is not implemented IDPTypeNotImplemented: IDP Type is not implemented
NotAllowed: External Login Provider not allowed
optional: (optional) optional: (optional)

View File

@ -17,7 +17,9 @@ type UserSessionView struct {
UserName string UserName string
LoginName string LoginName string
DisplayName string DisplayName string
SelectedIDPConfigID string
PasswordVerification time.Time PasswordVerification time.Time
ExternalLoginVerification time.Time
MfaSoftwareVerification time.Time MfaSoftwareVerification time.Time
MfaSoftwareVerificationType req_model.MfaType MfaSoftwareVerificationType req_model.MfaType
MfaHardwareVerification time.Time MfaHardwareVerification time.Time

View File

@ -592,6 +592,25 @@ func (es *UserEventstore) SetPassword(ctx context.Context, policy *policy_model.
return err return err
} }
func (es *UserEventstore) ExternalLoginChecked(ctx context.Context, userID string, authRequest *req_model.AuthRequest) error {
user, err := es.UserByID(ctx, userID)
if err != nil {
return err
}
if user.Human == nil {
return errors.ThrowPreconditionFailed(nil, "EVENT-Gns8i", "Errors.User.NotHuman")
}
repoUser := model.UserFromModel(user)
repoAuthRequest := model.AuthRequestFromModel(authRequest)
agg := ExternalLoginCheckSucceededAggregate(es.AggregateCreator(), repoUser, repoAuthRequest)
err = es_sdk.Push(ctx, es.PushAggregates, repoUser.AppendEvents, agg)
if err != nil {
return err
}
es.userCache.cacheUser(repoUser)
return nil
}
func (es *UserEventstore) ChangeMachine(ctx context.Context, machine *usr_model.Machine) (*usr_model.Machine, error) { func (es *UserEventstore) ChangeMachine(ctx context.Context, machine *usr_model.Machine) (*usr_model.Machine, error) {
user, err := es.UserByID(ctx, machine.AggregateID) user, err := es.UserByID(ctx, machine.AggregateID)
if err != nil { if err != nil {

View File

@ -1,22 +1,28 @@
package model package model
import ( import (
"encoding/json"
"github.com/caos/logging"
caos_errs "github.com/caos/zitadel/internal/errors"
es_models "github.com/caos/zitadel/internal/eventstore/models"
"net" "net"
"github.com/caos/zitadel/internal/auth_request/model" "github.com/caos/zitadel/internal/auth_request/model"
) )
type AuthRequest struct { type AuthRequest struct {
ID string `json:"id,omitempty"` ID string `json:"id,omitempty"`
UserAgentID string `json:"userAgentID,omitempty"` UserAgentID string `json:"userAgentID,omitempty"`
SelectedIDPConfigID string `json:"selectedIDPConfigID,omitempty"`
*BrowserInfo *BrowserInfo
} }
func AuthRequestFromModel(request *model.AuthRequest) *AuthRequest { func AuthRequestFromModel(request *model.AuthRequest) *AuthRequest {
return &AuthRequest{ return &AuthRequest{
ID: request.ID, ID: request.ID,
UserAgentID: request.AgentID, UserAgentID: request.AgentID,
BrowserInfo: BrowserInfoFromModel(request.BrowserInfo), BrowserInfo: BrowserInfoFromModel(request.BrowserInfo),
SelectedIDPConfigID: request.SelectedIDPConfigID,
} }
} }
@ -33,3 +39,11 @@ func BrowserInfoFromModel(info *model.BrowserInfo) *BrowserInfo {
RemoteIP: info.RemoteIP, RemoteIP: info.RemoteIP,
} }
} }
func (a *AuthRequest) SetData(event *es_models.Event) error {
if err := json.Unmarshal(event.Data, a); err != nil {
logging.Log("EVEN-T5df6").WithError(err).Error("could not unmarshal event data")
return caos_errs.ThrowInternal(err, "MODEL-yGmhh", "could not unmarshal event")
}
return nil
}

View File

@ -84,6 +84,8 @@ const (
HumanPasswordCheckSucceeded models.EventType = "user.human.password.check.succeeded" HumanPasswordCheckSucceeded models.EventType = "user.human.password.check.succeeded"
HumanPasswordCheckFailed models.EventType = "user.human.password.check.failed" HumanPasswordCheckFailed models.EventType = "user.human.password.check.failed"
HumanExternalLoginCheckSucceeded models.EventType = "user.human.externallogin.check.succeeded"
HumanExternalIDPReserved models.EventType = "user.human.externalidp.reserved" HumanExternalIDPReserved models.EventType = "user.human.externalidp.reserved"
HumanExternalIDPReleased models.EventType = "user.human.externalidp.released" HumanExternalIDPReleased models.EventType = "user.human.externalidp.released"

View File

@ -445,6 +445,16 @@ func PasswordCodeSentAggregate(aggCreator *es_models.AggregateCreator, user *mod
} }
} }
func ExternalLoginCheckSucceededAggregate(aggCreator *es_models.AggregateCreator, user *model.User, check *model.AuthRequest) es_sdk.AggregateFunc {
return func(ctx context.Context) (*es_models.Aggregate, error) {
agg, err := UserAggregate(ctx, aggCreator, user)
if err != nil {
return nil, err
}
return agg.AppendEvent(model.HumanExternalLoginCheckSucceeded, check)
}
}
func MachineChangeAggregate(aggCreator *es_models.AggregateCreator, user *model.User, machine *model.Machine) func(ctx context.Context) (*es_models.Aggregate, error) { func MachineChangeAggregate(aggCreator *es_models.AggregateCreator, user *model.User, machine *model.Machine) func(ctx context.Context) (*es_models.Aggregate, error) {
return func(ctx context.Context) (*es_models.Aggregate, error) { return func(ctx context.Context) (*es_models.Aggregate, error) {
if machine == nil { if machine == nil {

View File

@ -30,7 +30,9 @@ type UserSessionView struct {
UserName string `json:"-" gorm:"column:user_name"` UserName string `json:"-" gorm:"column:user_name"`
LoginName string `json:"-" gorm:"column:login_name"` LoginName string `json:"-" gorm:"column:login_name"`
DisplayName string `json:"-" gorm:"column:user_display_name"` DisplayName string `json:"-" gorm:"column:user_display_name"`
SelectedIDPConfigID string `json:"selectedIDPConfigID" gorm:"column:selected_idp_config_id"`
PasswordVerification time.Time `json:"-" gorm:"column:password_verification"` PasswordVerification time.Time `json:"-" gorm:"column:password_verification"`
ExternalLoginVerification time.Time `json:"-" gorm:"column:external_login_verification"`
MfaSoftwareVerification time.Time `json:"-" gorm:"column:mfa_software_verification"` MfaSoftwareVerification time.Time `json:"-" gorm:"column:mfa_software_verification"`
MfaSoftwareVerificationType int32 `json:"-" gorm:"column:mfa_software_verification_type"` MfaSoftwareVerificationType int32 `json:"-" gorm:"column:mfa_software_verification_type"`
MfaHardwareVerification time.Time `json:"-" gorm:"column:mfa_hardware_verification"` MfaHardwareVerification time.Time `json:"-" gorm:"column:mfa_hardware_verification"`
@ -58,7 +60,9 @@ func UserSessionToModel(userSession *UserSessionView) *model.UserSessionView {
UserName: userSession.UserName, UserName: userSession.UserName,
LoginName: userSession.LoginName, LoginName: userSession.LoginName,
DisplayName: userSession.DisplayName, DisplayName: userSession.DisplayName,
SelectedIDPConfigID: userSession.SelectedIDPConfigID,
PasswordVerification: userSession.PasswordVerification, PasswordVerification: userSession.PasswordVerification,
ExternalLoginVerification: userSession.ExternalLoginVerification,
MfaSoftwareVerification: userSession.MfaSoftwareVerification, MfaSoftwareVerification: userSession.MfaSoftwareVerification,
MfaSoftwareVerificationType: req_model.MfaType(userSession.MfaSoftwareVerificationType), MfaSoftwareVerificationType: req_model.MfaType(userSession.MfaSoftwareVerificationType),
MfaHardwareVerification: userSession.MfaHardwareVerification, MfaHardwareVerification: userSession.MfaHardwareVerification,
@ -83,6 +87,12 @@ func (v *UserSessionView) AppendEvent(event *models.Event) {
es_model.HumanPasswordCheckSucceeded: es_model.HumanPasswordCheckSucceeded:
v.PasswordVerification = event.CreationDate v.PasswordVerification = event.CreationDate
v.State = int32(req_model.UserSessionStateActive) v.State = int32(req_model.UserSessionStateActive)
case es_model.HumanExternalLoginCheckSucceeded:
data := new(es_model.AuthRequest)
data.SetData(event)
v.ExternalLoginVerification = event.CreationDate
v.SelectedIDPConfigID = data.SelectedIDPConfigID
v.State = int32(req_model.UserSessionStateActive)
case es_model.UserPasswordCheckFailed, case es_model.UserPasswordCheckFailed,
es_model.UserPasswordChanged, es_model.UserPasswordChanged,
es_model.HumanPasswordCheckFailed, es_model.HumanPasswordCheckFailed,

View File

@ -20,7 +20,7 @@ func UserMembershipByIDs(db *gorm.DB, table, userID, aggregateID, objectID strin
query := repository.PrepareGetByQuery(table, userIDQuery, aggregateIDQuery, objectIDQuery, memberTypeQuery) query := repository.PrepareGetByQuery(table, userIDQuery, aggregateIDQuery, objectIDQuery, memberTypeQuery)
err := query(db, memberships) err := query(db, memberships)
if caos_errs.IsNotFound(err) { if caos_errs.IsNotFound(err) {
return nil, caos_errs.ThrowNotFound(nil, "VIEW-sj8Sw", "Errors.UserMembership.NotFound") return nil, caos_errs.ThrowNotFound(nil, "VIEW-5Tsji", "Errors.UserMembership.NotFound")
} }
return memberships, err return memberships, err
} }

View File

@ -0,0 +1,2 @@
ALTER TABLE auth.user_sessions ADD COLUMN external_login_verification TIMESTAMPTZ;
ALTER TABLE auth.user_sessions ADD COLUMN selected_idp_config_id TEXT;