mirror of
https://github.com/zitadel/zitadel.git
synced 2025-01-07 21:47:42 +00:00
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:
parent
124988e2d2
commit
198370325d
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
|
@ -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 {
|
||||||
|
@ -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,
|
||||||
|
@ -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
|
||||||
|
@ -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:
|
||||||
|
@ -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{
|
||||||
|
@ -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 {
|
||||||
|
@ -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 {
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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"))
|
||||||
}
|
}
|
||||||
|
@ -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)
|
||||||
|
@ -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)
|
||||||
|
@ -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
|
||||||
|
@ -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 {
|
||||||
|
@ -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
|
||||||
|
}
|
||||||
|
@ -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"
|
||||||
|
|
||||||
|
@ -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 {
|
||||||
|
@ -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,
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
2
migrations/cockroach/V1.16__user_session.sql
Normal file
2
migrations/cockroach/V1.16__user_session.sql
Normal 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;
|
Loading…
x
Reference in New Issue
Block a user