mirror of
https://github.com/zitadel/zitadel.git
synced 2025-08-12 08:27:32 +00:00
fix: idp usage (#4571)
* fix: send email verification instead of init code for idp users * fix: select single idp of external only users * fix: use single idp on login
This commit is contained in:
@@ -48,6 +48,7 @@ type AuthRequestRepo struct {
|
||||
LockoutPolicyViewProvider lockoutPolicyViewProvider
|
||||
PrivacyPolicyProvider privacyPolicyProvider
|
||||
IDPProviderViewProvider idpProviderViewProvider
|
||||
IDPUserLinksProvider idpUserLinksProvider
|
||||
UserGrantProvider userGrantProvider
|
||||
ProjectProvider projectProvider
|
||||
ApplicationProvider applicationProvider
|
||||
@@ -83,6 +84,10 @@ type idpProviderViewProvider interface {
|
||||
IDPProvidersByAggregateIDAndState(string, string, iam_model.IDPConfigState) ([]*iam_view_model.IDPProviderView, error)
|
||||
}
|
||||
|
||||
type idpUserLinksProvider interface {
|
||||
IDPUserLinks(ctx context.Context, queries *query.IDPUserLinksSearchQuery) (*query.IDPUserLinks, error)
|
||||
}
|
||||
|
||||
type userEventProvider interface {
|
||||
UserEventsByID(ctx context.Context, id string, sequence uint64) ([]*es_models.Event, error)
|
||||
}
|
||||
@@ -469,11 +474,15 @@ func (repo *AuthRequestRepo) AutoRegisterExternalUser(ctx context.Context, regis
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
emailCodeGenerator, err := repo.Query.InitEncryptionGenerator(ctx, domain.SecretGeneratorTypeVerifyEmailCode, repo.UserCodeAlg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
phoneCodeGenerator, err := repo.Query.InitEncryptionGenerator(ctx, domain.SecretGeneratorTypeVerifyPhoneCode, repo.UserCodeAlg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
human, err := repo.Command.RegisterHuman(ctx, resourceOwner, registerUser, externalIDP, orgMemberRoles, initCodeGenerator, phoneCodeGenerator)
|
||||
human, err := repo.Command.RegisterHuman(ctx, resourceOwner, registerUser, externalIDP, orgMemberRoles, initCodeGenerator, emailCodeGenerator, phoneCodeGenerator)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -909,11 +918,18 @@ func (repo *AuthRequestRepo) nextSteps(ctx context.Context, request *domain.Auth
|
||||
}
|
||||
|
||||
isInternalLogin := request.SelectedIDPConfigID == "" && userSession.SelectedIDPConfigID == ""
|
||||
if !isInternalLogin && len(request.LinkingUsers) == 0 && !checkVerificationTimeMaxAge(userSession.ExternalLoginVerification, request.LoginPolicy.ExternalLoginCheckLifetime, request) {
|
||||
idps, err := checkExternalIDPsOfUser(ctx, repo.IDPUserLinksProvider, user.ID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if (!isInternalLogin || len(idps.Links) > 0) && len(request.LinkingUsers) == 0 && !checkVerificationTimeMaxAge(userSession.ExternalLoginVerification, request.LoginPolicy.ExternalLoginCheckLifetime, request) {
|
||||
selectedIDPConfigID := request.SelectedIDPConfigID
|
||||
if selectedIDPConfigID == "" {
|
||||
selectedIDPConfigID = userSession.SelectedIDPConfigID
|
||||
}
|
||||
if selectedIDPConfigID == "" {
|
||||
selectedIDPConfigID = idps.Links[0].IDPID
|
||||
}
|
||||
return append(steps, &domain.ExternalLoginStep{SelectedIDPConfigID: selectedIDPConfigID}), nil
|
||||
}
|
||||
if isInternalLogin || (!isInternalLogin && len(request.LinkingUsers) > 0) {
|
||||
@@ -976,6 +992,14 @@ func (repo *AuthRequestRepo) nextSteps(ctx context.Context, request *domain.Auth
|
||||
return append(steps, &domain.RedirectToCallbackStep{}), nil
|
||||
}
|
||||
|
||||
func checkExternalIDPsOfUser(ctx context.Context, idpUserLinksProvider idpUserLinksProvider, userID string) (*query.IDPUserLinks, error) {
|
||||
userIDQuery, err := query.NewIDPUserLinksUserIDSearchQuery(userID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return idpUserLinksProvider.IDPUserLinks(ctx, &query.IDPUserLinksSearchQuery{Queries: []query.SearchQuery{userIDQuery}})
|
||||
}
|
||||
|
||||
func (repo *AuthRequestRepo) usersForUserSelection(request *domain.AuthRequest) ([]domain.UserSelection, error) {
|
||||
userSessions, err := userSessionsByUserAgentID(repo.UserSessionViewProvider, request.AgentID, request.InstanceID)
|
||||
if err != nil {
|
||||
|
@@ -234,6 +234,14 @@ func (m *mockApp) AppByOIDCClientID(ctx context.Context, id string) (*query.App,
|
||||
return nil, errors.ThrowNotFound(nil, "ERROR", "error")
|
||||
}
|
||||
|
||||
type mockIDPUserLinks struct {
|
||||
idps []*query.IDPUserLink
|
||||
}
|
||||
|
||||
func (m *mockIDPUserLinks) IDPUserLinks(ctx context.Context, queries *query.IDPUserLinksSearchQuery) (*query.IDPUserLinks, error) {
|
||||
return &query.IDPUserLinks{Links: m.idps}, nil
|
||||
}
|
||||
|
||||
func TestAuthRequestRepo_nextSteps(t *testing.T) {
|
||||
type fields struct {
|
||||
AuthRequests *cache.AuthRequestCache
|
||||
@@ -247,6 +255,7 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) {
|
||||
applicationProvider applicationProvider
|
||||
loginPolicyProvider loginPolicyViewProvider
|
||||
lockoutPolicyProvider lockoutPolicyViewProvider
|
||||
idpUserLinksProvider idpUserLinksProvider
|
||||
}
|
||||
type args struct {
|
||||
request *domain.AuthRequest
|
||||
@@ -498,6 +507,7 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) {
|
||||
ShowFailures: true,
|
||||
},
|
||||
},
|
||||
idpUserLinksProvider: &mockIDPUserLinks{},
|
||||
},
|
||||
args{&domain.AuthRequest{UserID: "UserID", LoginPolicy: &domain.LoginPolicy{}}, false},
|
||||
[]domain.NextStep{&domain.PasswordStep{}},
|
||||
@@ -515,6 +525,7 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) {
|
||||
ShowFailures: true,
|
||||
},
|
||||
},
|
||||
idpUserLinksProvider: &mockIDPUserLinks{},
|
||||
},
|
||||
args{&domain.AuthRequest{UserID: "UserID", LoginPolicy: &domain.LoginPolicy{}}, false},
|
||||
nil,
|
||||
@@ -535,6 +546,7 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) {
|
||||
ShowFailures: true,
|
||||
},
|
||||
},
|
||||
idpUserLinksProvider: &mockIDPUserLinks{},
|
||||
},
|
||||
args{&domain.AuthRequest{UserID: "UserID", LoginPolicy: &domain.LoginPolicy{}}, false},
|
||||
[]domain.NextStep{&domain.InitUserStep{
|
||||
@@ -561,6 +573,7 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) {
|
||||
MultiFactorCheckLifetime: 10 * time.Hour,
|
||||
},
|
||||
},
|
||||
idpUserLinksProvider: &mockIDPUserLinks{},
|
||||
},
|
||||
args{&domain.AuthRequest{UserID: "UserID", LoginPolicy: &domain.LoginPolicy{PasswordlessType: domain.PasswordlessTypeAllowed}}, false},
|
||||
[]domain.NextStep{&domain.PasswordlessRegistrationPromptStep{}},
|
||||
@@ -585,6 +598,7 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) {
|
||||
MultiFactorCheckLifetime: 10 * time.Hour,
|
||||
},
|
||||
},
|
||||
idpUserLinksProvider: &mockIDPUserLinks{},
|
||||
},
|
||||
args{&domain.AuthRequest{UserID: "UserID", LoginPolicy: &domain.LoginPolicy{PasswordlessType: domain.PasswordlessTypeAllowed}}, false},
|
||||
[]domain.NextStep{&domain.PasswordlessStep{}},
|
||||
@@ -610,6 +624,7 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) {
|
||||
MultiFactorCheckLifetime: 10 * time.Hour,
|
||||
},
|
||||
},
|
||||
idpUserLinksProvider: &mockIDPUserLinks{},
|
||||
},
|
||||
args{&domain.AuthRequest{UserID: "UserID", LoginPolicy: &domain.LoginPolicy{PasswordlessType: domain.PasswordlessTypeAllowed}}, false},
|
||||
[]domain.NextStep{&domain.PasswordlessStep{PasswordSet: true}},
|
||||
@@ -635,7 +650,8 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) {
|
||||
ShowFailures: true,
|
||||
},
|
||||
},
|
||||
orgViewProvider: &mockViewOrg{State: domain.OrgStateActive},
|
||||
orgViewProvider: &mockViewOrg{State: domain.OrgStateActive},
|
||||
idpUserLinksProvider: &mockIDPUserLinks{},
|
||||
},
|
||||
args{&domain.AuthRequest{
|
||||
UserID: "UserID",
|
||||
@@ -661,14 +677,15 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) {
|
||||
ShowFailures: true,
|
||||
},
|
||||
},
|
||||
orgViewProvider: &mockViewOrg{State: domain.OrgStateActive},
|
||||
orgViewProvider: &mockViewOrg{State: domain.OrgStateActive},
|
||||
idpUserLinksProvider: &mockIDPUserLinks{},
|
||||
},
|
||||
args{&domain.AuthRequest{UserID: "UserID", LoginPolicy: &domain.LoginPolicy{}}, false},
|
||||
[]domain.NextStep{&domain.InitPasswordStep{}},
|
||||
nil,
|
||||
},
|
||||
{
|
||||
"external user (no external verification), external login step",
|
||||
"external user (idp selected, no external verification), external login step",
|
||||
fields{
|
||||
userSessionViewProvider: &mockViewUserSession{
|
||||
SecondFactorVerification: testNow.Add(-5 * time.Minute),
|
||||
@@ -689,6 +706,7 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) {
|
||||
SecondFactorCheckLifetime: 18 * time.Hour,
|
||||
},
|
||||
},
|
||||
idpUserLinksProvider: &mockIDPUserLinks{},
|
||||
},
|
||||
args{&domain.AuthRequest{
|
||||
UserID: "UserID",
|
||||
@@ -699,6 +717,40 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) {
|
||||
[]domain.NextStep{&domain.ExternalLoginStep{SelectedIDPConfigID: "IDPConfigID"}},
|
||||
nil,
|
||||
},
|
||||
{
|
||||
"external user (only idp available, no external verification), external login step",
|
||||
fields{
|
||||
userSessionViewProvider: &mockViewUserSession{
|
||||
SecondFactorVerification: testNow.Add(-5 * time.Minute),
|
||||
},
|
||||
userViewProvider: &mockViewUser{
|
||||
IsEmailVerified: true,
|
||||
MFAMaxSetUp: int32(domain.MFALevelSecondFactor),
|
||||
},
|
||||
userEventProvider: &mockEventUser{},
|
||||
lockoutPolicyProvider: &mockLockoutPolicy{
|
||||
policy: &query.LockoutPolicy{
|
||||
ShowFailures: true,
|
||||
},
|
||||
},
|
||||
orgViewProvider: &mockViewOrg{State: domain.OrgStateActive},
|
||||
loginPolicyProvider: &mockLoginPolicy{
|
||||
policy: &query.LoginPolicy{
|
||||
SecondFactorCheckLifetime: 18 * time.Hour,
|
||||
},
|
||||
},
|
||||
idpUserLinksProvider: &mockIDPUserLinks{
|
||||
idps: []*query.IDPUserLink{{IDPID: "IDPConfigID"}},
|
||||
},
|
||||
},
|
||||
args{&domain.AuthRequest{
|
||||
UserID: "UserID",
|
||||
LoginPolicy: &domain.LoginPolicy{
|
||||
SecondFactorCheckLifetime: 18 * time.Hour,
|
||||
}}, false},
|
||||
[]domain.NextStep{&domain.ExternalLoginStep{SelectedIDPConfigID: "IDPConfigID"}},
|
||||
nil,
|
||||
},
|
||||
{
|
||||
"external user (external verification set), callback",
|
||||
fields{
|
||||
@@ -720,6 +772,7 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) {
|
||||
ShowFailures: true,
|
||||
},
|
||||
},
|
||||
idpUserLinksProvider: &mockIDPUserLinks{},
|
||||
},
|
||||
args{
|
||||
&domain.AuthRequest{
|
||||
@@ -754,6 +807,7 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) {
|
||||
PasswordCheckLifetime: 10 * 24 * time.Hour,
|
||||
},
|
||||
},
|
||||
idpUserLinksProvider: &mockIDPUserLinks{},
|
||||
},
|
||||
args{&domain.AuthRequest{UserID: "UserID", LoginPolicy: &domain.LoginPolicy{}}, false},
|
||||
[]domain.NextStep{&domain.PasswordStep{}},
|
||||
@@ -781,6 +835,7 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) {
|
||||
ShowFailures: true,
|
||||
},
|
||||
},
|
||||
idpUserLinksProvider: &mockIDPUserLinks{},
|
||||
},
|
||||
args{
|
||||
&domain.AuthRequest{
|
||||
@@ -814,6 +869,7 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) {
|
||||
ShowFailures: true,
|
||||
},
|
||||
},
|
||||
idpUserLinksProvider: &mockIDPUserLinks{},
|
||||
},
|
||||
args{
|
||||
&domain.AuthRequest{
|
||||
@@ -847,6 +903,7 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) {
|
||||
ShowFailures: true,
|
||||
},
|
||||
},
|
||||
idpUserLinksProvider: &mockIDPUserLinks{},
|
||||
},
|
||||
args{
|
||||
&domain.AuthRequest{
|
||||
@@ -881,6 +938,7 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) {
|
||||
ShowFailures: true,
|
||||
},
|
||||
},
|
||||
idpUserLinksProvider: &mockIDPUserLinks{},
|
||||
},
|
||||
args{
|
||||
&domain.AuthRequest{
|
||||
@@ -918,6 +976,7 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) {
|
||||
ShowFailures: true,
|
||||
},
|
||||
},
|
||||
idpUserLinksProvider: &mockIDPUserLinks{},
|
||||
},
|
||||
args{
|
||||
&domain.AuthRequest{
|
||||
@@ -949,6 +1008,7 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) {
|
||||
ShowFailures: true,
|
||||
},
|
||||
},
|
||||
idpUserLinksProvider: &mockIDPUserLinks{},
|
||||
},
|
||||
args{&domain.AuthRequest{
|
||||
UserID: "UserID",
|
||||
@@ -980,6 +1040,7 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) {
|
||||
ShowFailures: true,
|
||||
},
|
||||
},
|
||||
idpUserLinksProvider: &mockIDPUserLinks{},
|
||||
},
|
||||
args{&domain.AuthRequest{
|
||||
UserID: "UserID",
|
||||
@@ -1014,6 +1075,7 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) {
|
||||
ShowFailures: true,
|
||||
},
|
||||
},
|
||||
idpUserLinksProvider: &mockIDPUserLinks{},
|
||||
},
|
||||
args{&domain.AuthRequest{
|
||||
UserID: "UserID",
|
||||
@@ -1049,6 +1111,7 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) {
|
||||
ShowFailures: true,
|
||||
},
|
||||
},
|
||||
idpUserLinksProvider: &mockIDPUserLinks{},
|
||||
},
|
||||
args{&domain.AuthRequest{
|
||||
UserID: "UserID",
|
||||
@@ -1085,6 +1148,7 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) {
|
||||
ShowFailures: true,
|
||||
},
|
||||
},
|
||||
idpUserLinksProvider: &mockIDPUserLinks{},
|
||||
},
|
||||
args{&domain.AuthRequest{
|
||||
UserID: "UserID",
|
||||
@@ -1123,6 +1187,7 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) {
|
||||
ShowFailures: true,
|
||||
},
|
||||
},
|
||||
idpUserLinksProvider: &mockIDPUserLinks{},
|
||||
},
|
||||
args{&domain.AuthRequest{
|
||||
UserID: "UserID",
|
||||
@@ -1162,6 +1227,7 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) {
|
||||
ShowFailures: true,
|
||||
},
|
||||
},
|
||||
idpUserLinksProvider: &mockIDPUserLinks{},
|
||||
},
|
||||
args{&domain.AuthRequest{
|
||||
UserID: "UserID",
|
||||
@@ -1200,6 +1266,7 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) {
|
||||
ShowFailures: true,
|
||||
},
|
||||
},
|
||||
idpUserLinksProvider: &mockIDPUserLinks{},
|
||||
},
|
||||
args{&domain.AuthRequest{
|
||||
UserID: "UserID",
|
||||
@@ -1239,6 +1306,7 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) {
|
||||
ShowFailures: true,
|
||||
},
|
||||
},
|
||||
idpUserLinksProvider: &mockIDPUserLinks{},
|
||||
},
|
||||
args{&domain.AuthRequest{
|
||||
UserID: "UserID",
|
||||
@@ -1274,8 +1342,9 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) {
|
||||
SecondFactorCheckLifetime: 18 * time.Hour,
|
||||
},
|
||||
},
|
||||
userEventProvider: &mockEventUser{},
|
||||
orgViewProvider: &mockViewOrg{State: domain.OrgStateActive},
|
||||
userEventProvider: &mockEventUser{},
|
||||
orgViewProvider: &mockViewOrg{State: domain.OrgStateActive},
|
||||
idpUserLinksProvider: &mockIDPUserLinks{},
|
||||
},
|
||||
args{
|
||||
&domain.AuthRequest{
|
||||
@@ -1306,6 +1375,7 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) {
|
||||
ShowFailures: true,
|
||||
},
|
||||
},
|
||||
idpUserLinksProvider: &mockIDPUserLinks{},
|
||||
},
|
||||
args{
|
||||
&domain.AuthRequest{
|
||||
@@ -1336,6 +1406,7 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) {
|
||||
ApplicationProvider: tt.fields.applicationProvider,
|
||||
LoginPolicyViewProvider: tt.fields.loginPolicyProvider,
|
||||
LockoutPolicyViewProvider: tt.fields.lockoutPolicyProvider,
|
||||
IDPUserLinksProvider: tt.fields.idpUserLinksProvider,
|
||||
}
|
||||
got, err := repo.nextSteps(context.Background(), tt.args.request, tt.args.checkLoggedIn)
|
||||
if (err != nil && tt.wantErr == nil) || (tt.wantErr != nil && !tt.wantErr(err)) {
|
||||
|
@@ -80,6 +80,7 @@ func Start(conf Config, systemDefaults sd.SystemDefaults, command *command.Comma
|
||||
UserCommandProvider: command,
|
||||
UserEventProvider: &userRepo,
|
||||
IDPProviderViewProvider: view,
|
||||
IDPUserLinksProvider: queries,
|
||||
LockoutPolicyViewProvider: queries,
|
||||
LoginPolicyViewProvider: queries,
|
||||
UserGrantProvider: queryView,
|
||||
|
Reference in New Issue
Block a user