Merge remote-tracking branch 'origin/master' into new-eventstore

This commit is contained in:
adlerhurst
2020-11-18 10:36:02 +01:00
380 changed files with 11746 additions and 109998 deletions

View File

@@ -48,8 +48,8 @@ type AuthRequestRepo struct {
PasswordCheckLifeTime time.Duration
ExternalLoginCheckLifeTime time.Duration
MfaInitSkippedLifeTime time.Duration
MfaSoftwareCheckLifeTime time.Duration
MfaHardwareCheckLifeTime time.Duration
SecondFactorCheckLifeTime time.Duration
MultiFactorCheckLifeTime time.Duration
IAMID string
}
@@ -109,6 +109,9 @@ func (repo *AuthRequestRepo) CreateAuthRequest(ctx context.Context, request *mod
return nil, err
}
request.Audience = appIDs
projectIDAud := request.GetScopeProjectIDsForAud()
request.Audience = append(request.Audience, projectIDAud...)
request.AppendAudIfNotExisting(app.ProjectID)
if request.LoginHint != "" {
err = repo.checkLoginName(ctx, request, request.LoginHint)
logging.LogWithFields("EVENT-aG311", "login name", request.LoginHint, "id", request.ID, "applicationID", request.ApplicationID).OnError(err).Debug("login hint invalid")
@@ -150,6 +153,10 @@ func (repo *AuthRequestRepo) AuthRequestByCode(ctx context.Context, code string)
if err != nil {
return nil, err
}
err = repo.fillLoginPolicy(ctx, request)
if err != nil {
return nil, err
}
steps, err := repo.nextSteps(ctx, request, true)
if err != nil {
return nil, err
@@ -559,7 +566,11 @@ func (repo *AuthRequestRepo) nextSteps(ctx context.Context, request *model.AuthR
request.AuthTime = userSession.PasswordVerification
}
if step, ok := repo.mfaChecked(userSession, request, user); !ok {
step, ok, err := repo.mfaChecked(userSession, request, user)
if err != nil {
return nil, err
}
if !ok {
return append(steps, step), nil
}
@@ -611,44 +622,48 @@ func (repo *AuthRequestRepo) usersForUserSelection(request *model.AuthRequest) (
return users, nil
}
func (repo *AuthRequestRepo) mfaChecked(userSession *user_model.UserSessionView, request *model.AuthRequest, user *user_model.UserView) (model.NextStep, bool) {
func (repo *AuthRequestRepo) mfaChecked(userSession *user_model.UserSessionView, request *model.AuthRequest, user *user_model.UserView) (model.NextStep, bool, error) {
mfaLevel := request.MfaLevel()
promptRequired := user.MfaMaxSetUp < mfaLevel
if promptRequired || !repo.mfaSkippedOrSetUp(user) {
promptRequired := (user.MfaMaxSetUp < mfaLevel) || !user.HasRequiredOrgMFALevel(request.LoginPolicy)
if promptRequired || !repo.mfaSkippedOrSetUp(user, request.LoginPolicy) {
types := user.MfaTypesSetupPossible(mfaLevel, request.LoginPolicy)
if promptRequired && len(types) == 0 {
return nil, false, errors.ThrowPreconditionFailed(nil, "LOGIN-5Hm8s", "Errors.Login.LoginPolicy.MFA.ForceAndNotConfigured")
}
return &model.MfaPromptStep{
Required: promptRequired,
MfaProviders: user.MfaTypesSetupPossible(mfaLevel),
}, false
MfaProviders: types,
}, false, nil
}
switch mfaLevel {
default:
fallthrough
case model.MfaLevelNotSetUp:
if user.MfaMaxSetUp == model.MfaLevelNotSetUp {
return nil, true
case model.MFALevelNotSetUp:
if user.MfaMaxSetUp == model.MFALevelNotSetUp {
return nil, true, nil
}
fallthrough
case model.MfaLevelSoftware:
if checkVerificationTime(userSession.MfaSoftwareVerification, repo.MfaSoftwareCheckLifeTime) {
request.MfasVerified = append(request.MfasVerified, userSession.MfaSoftwareVerificationType)
request.AuthTime = userSession.MfaSoftwareVerification
return nil, true
case model.MFALevelSecondFactor:
if checkVerificationTime(userSession.SecondFactorVerification, repo.SecondFactorCheckLifeTime) {
request.MfasVerified = append(request.MfasVerified, userSession.SecondFactorVerificationType)
request.AuthTime = userSession.SecondFactorVerification
return nil, true, nil
}
fallthrough
case model.MfaLevelHardware:
if checkVerificationTime(userSession.MfaHardwareVerification, repo.MfaHardwareCheckLifeTime) {
request.MfasVerified = append(request.MfasVerified, userSession.MfaHardwareVerificationType)
request.AuthTime = userSession.MfaHardwareVerification
return nil, true
case model.MFALevelMultiFactor:
if checkVerificationTime(userSession.MultiFactorVerification, repo.MultiFactorCheckLifeTime) {
request.MfasVerified = append(request.MfasVerified, userSession.MultiFactorVerificationType)
request.AuthTime = userSession.MultiFactorVerification
return nil, true, nil
}
}
return &model.MfaVerificationStep{
MfaProviders: user.MfaTypesAllowed(mfaLevel),
}, false
MfaProviders: user.MfaTypesAllowed(mfaLevel, request.LoginPolicy),
}, false, nil
}
func (repo *AuthRequestRepo) mfaSkippedOrSetUp(user *user_model.UserView) bool {
if user.MfaMaxSetUp > model.MfaLevelNotSetUp {
func (repo *AuthRequestRepo) mfaSkippedOrSetUp(user *user_model.UserView, policy *iam_model.LoginPolicyView) bool {
if user.MfaMaxSetUp > model.MFALevelNotSetUp {
return true
}
return checkVerificationTime(user.MfaInitSkipped, repo.MfaInitSkippedLifeTime)

View File

@@ -3,6 +3,8 @@ package eventstore
import (
"context"
"encoding/json"
iam_model "github.com/caos/zitadel/internal/iam/model"
iam_view_model "github.com/caos/zitadel/internal/iam/repository/view/model"
"testing"
"time"
@@ -46,7 +48,7 @@ func (m *mockViewErrUserSession) UserSessionsByAgentID(string) ([]*user_view_mod
type mockViewUserSession struct {
ExternalLoginVerification time.Time
PasswordVerification time.Time
MfaSoftwareVerification time.Time
SecondFactorVerification time.Time
Users []mockUser
}
@@ -59,7 +61,7 @@ func (m *mockViewUserSession) UserSessionByIDs(string, string) (*user_view_model
return &user_view_model.UserSessionView{
ExternalLoginVerification: m.ExternalLoginVerification,
PasswordVerification: m.PasswordVerification,
MfaSoftwareVerification: m.MfaSoftwareVerification,
SecondFactorVerification: m.SecondFactorVerification,
}, nil
}
@@ -116,6 +118,14 @@ type mockViewUser struct {
MfaInitSkipped time.Time
}
type mockLoginPolicy struct {
policy *iam_view_model.LoginPolicyView
}
func (m *mockLoginPolicy) LoginPolicyByAggregateID(id string) (*iam_view_model.LoginPolicyView, error) {
return m.policy, nil
}
func (m *mockViewUser) UserByID(string) (*user_view_model.UserView, error) {
return &user_view_model.UserView{
State: int32(user_model.UserStateActive),
@@ -186,11 +196,12 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) {
userEventProvider userEventProvider
orgViewProvider orgViewProvider
userGrantProvider userGrantProvider
loginPolicyProvider loginPolicyViewProvider
PasswordCheckLifeTime time.Duration
ExternalLoginCheckLifeTime time.Duration
MfaInitSkippedLifeTime time.Duration
MfaSoftwareCheckLifeTime time.Duration
MfaHardwareCheckLifeTime time.Duration
SecondFactorCheckLifeTime time.Duration
MultiFactorCheckLifeTime time.Duration
}
type args struct {
request *model.AuthRequest
@@ -417,15 +428,15 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) {
"external user (no external verification), external login step",
fields{
userSessionViewProvider: &mockViewUserSession{
MfaSoftwareVerification: time.Now().UTC().Add(-5 * time.Minute),
SecondFactorVerification: time.Now().UTC().Add(-5 * time.Minute),
},
userViewProvider: &mockViewUser{
IsEmailVerified: true,
MfaMaxSetUp: int32(model.MfaLevelSoftware),
MfaMaxSetUp: int32(model.MFALevelSecondFactor),
},
userEventProvider: &mockEventUser{},
orgViewProvider: &mockViewOrg{State: org_model.OrgStateActive},
MfaSoftwareCheckLifeTime: 18 * time.Hour,
userEventProvider: &mockEventUser{},
orgViewProvider: &mockViewOrg{State: org_model.OrgStateActive},
SecondFactorCheckLifeTime: 18 * time.Hour,
},
args{&model.AuthRequest{UserID: "UserID", SelectedIDPConfigID: "IDPConfigID"}, false},
[]model.NextStep{&model.ExternalLoginStep{}},
@@ -436,19 +447,29 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) {
fields{
userSessionViewProvider: &mockViewUserSession{
ExternalLoginVerification: time.Now().UTC().Add(-5 * time.Minute),
MfaSoftwareVerification: time.Now().UTC().Add(-5 * time.Minute),
SecondFactorVerification: time.Now().UTC().Add(-5 * time.Minute),
},
userViewProvider: &mockViewUser{
IsEmailVerified: true,
MfaMaxSetUp: int32(model.MfaLevelSoftware),
MfaMaxSetUp: int32(model.MFALevelSecondFactor),
},
userEventProvider: &mockEventUser{},
orgViewProvider: &mockViewOrg{State: org_model.OrgStateActive},
userGrantProvider: &mockUserGrants{},
loginPolicyProvider: &mockLoginPolicy{
policy: &iam_view_model.LoginPolicyView{},
},
userEventProvider: &mockEventUser{},
orgViewProvider: &mockViewOrg{State: org_model.OrgStateActive},
userGrantProvider: &mockUserGrants{},
ExternalLoginCheckLifeTime: 10 * 24 * time.Hour,
MfaSoftwareCheckLifeTime: 18 * time.Hour,
SecondFactorCheckLifeTime: 18 * time.Hour,
},
args{&model.AuthRequest{UserID: "UserID", SelectedIDPConfigID: "IDPConfigID", Request: &model.AuthRequestOIDC{}}, false},
args{
&model.AuthRequest{
UserID: "UserID",
SelectedIDPConfigID: "IDPConfigID",
Request: &model.AuthRequestOIDC{},
LoginPolicy: &iam_model.LoginPolicyView{},
},
false},
[]model.NextStep{&model.RedirectToCallbackStep{}},
nil,
},
@@ -471,21 +492,27 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) {
"external user (no password check needed), callback",
fields{
userSessionViewProvider: &mockViewUserSession{
MfaSoftwareVerification: time.Now().UTC().Add(-5 * time.Minute),
SecondFactorVerification: time.Now().UTC().Add(-5 * time.Minute),
ExternalLoginVerification: time.Now().UTC().Add(-5 * time.Minute),
},
userViewProvider: &mockViewUser{
PasswordSet: true,
IsEmailVerified: true,
MfaMaxSetUp: int32(model.MfaLevelSoftware),
MfaMaxSetUp: int32(model.MFALevelSecondFactor),
},
userEventProvider: &mockEventUser{},
orgViewProvider: &mockViewOrg{State: org_model.OrgStateActive},
userGrantProvider: &mockUserGrants{},
MfaSoftwareCheckLifeTime: 18 * time.Hour,
SecondFactorCheckLifeTime: 18 * time.Hour,
ExternalLoginCheckLifeTime: 10 * 24 * time.Hour,
},
args{&model.AuthRequest{UserID: "UserID", SelectedIDPConfigID: "IDPConfigID", Request: &model.AuthRequestOIDC{}}, false},
args{
&model.AuthRequest{
UserID: "UserID",
SelectedIDPConfigID: "IDPConfigID",
Request: &model.AuthRequestOIDC{},
LoginPolicy: &iam_model.LoginPolicyView{},
}, false},
[]model.NextStep{&model.RedirectToCallbackStep{}},
nil,
},
@@ -498,16 +525,22 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) {
userViewProvider: &mockViewUser{
PasswordSet: true,
OTPState: int32(user_model.MfaStateReady),
MfaMaxSetUp: int32(model.MfaLevelSoftware),
MfaMaxSetUp: int32(model.MFALevelSecondFactor),
},
userEventProvider: &mockEventUser{},
orgViewProvider: &mockViewOrg{State: org_model.OrgStateActive},
PasswordCheckLifeTime: 10 * 24 * time.Hour,
MfaSoftwareCheckLifeTime: 18 * time.Hour,
userEventProvider: &mockEventUser{},
orgViewProvider: &mockViewOrg{State: org_model.OrgStateActive},
PasswordCheckLifeTime: 10 * 24 * time.Hour,
SecondFactorCheckLifeTime: 18 * time.Hour,
},
args{&model.AuthRequest{UserID: "UserID"}, false},
args{
&model.AuthRequest{
UserID: "UserID",
LoginPolicy: &iam_model.LoginPolicyView{
SecondFactors: []iam_model.SecondFactorType{iam_model.SecondFactorTypeOTP},
},
}, false},
[]model.NextStep{&model.MfaVerificationStep{
MfaProviders: []model.MfaType{model.MfaTypeOTP},
MfaProviders: []model.MFAType{model.MFATypeOTP},
}},
nil,
},
@@ -521,17 +554,24 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) {
userViewProvider: &mockViewUser{
PasswordSet: true,
OTPState: int32(user_model.MfaStateReady),
MfaMaxSetUp: int32(model.MfaLevelSoftware),
MfaMaxSetUp: int32(model.MFALevelSecondFactor),
},
userEventProvider: &mockEventUser{},
orgViewProvider: &mockViewOrg{State: org_model.OrgStateActive},
PasswordCheckLifeTime: 10 * 24 * time.Hour,
ExternalLoginCheckLifeTime: 10 * 24 * time.Hour,
MfaSoftwareCheckLifeTime: 18 * time.Hour,
SecondFactorCheckLifeTime: 18 * time.Hour,
},
args{&model.AuthRequest{UserID: "UserID", SelectedIDPConfigID: "IDPConfigID"}, false},
args{
&model.AuthRequest{
UserID: "UserID",
SelectedIDPConfigID: "IDPConfigID",
LoginPolicy: &iam_model.LoginPolicyView{
SecondFactors: []iam_model.SecondFactorType{iam_model.SecondFactorTypeOTP},
},
}, false},
[]model.NextStep{&model.MfaVerificationStep{
MfaProviders: []model.MfaType{model.MfaTypeOTP},
MfaProviders: []model.MFAType{model.MFATypeOTP},
}},
nil,
},
@@ -539,21 +579,27 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) {
"password change required and email verified, password change step",
fields{
userSessionViewProvider: &mockViewUserSession{
PasswordVerification: time.Now().UTC().Add(-5 * time.Minute),
MfaSoftwareVerification: time.Now().UTC().Add(-5 * time.Minute),
PasswordVerification: time.Now().UTC().Add(-5 * time.Minute),
SecondFactorVerification: time.Now().UTC().Add(-5 * time.Minute),
},
userViewProvider: &mockViewUser{
PasswordSet: true,
PasswordChangeRequired: true,
IsEmailVerified: true,
MfaMaxSetUp: int32(model.MfaLevelSoftware),
MfaMaxSetUp: int32(model.MFALevelSecondFactor),
},
userEventProvider: &mockEventUser{},
orgViewProvider: &mockViewOrg{State: org_model.OrgStateActive},
PasswordCheckLifeTime: 10 * 24 * time.Hour,
MfaSoftwareCheckLifeTime: 18 * time.Hour,
userEventProvider: &mockEventUser{},
orgViewProvider: &mockViewOrg{State: org_model.OrgStateActive},
PasswordCheckLifeTime: 10 * 24 * time.Hour,
SecondFactorCheckLifeTime: 18 * time.Hour,
},
args{&model.AuthRequest{UserID: "UserID"}, false},
args{
&model.AuthRequest{
UserID: "UserID",
LoginPolicy: &iam_model.LoginPolicyView{
SecondFactors: []iam_model.SecondFactorType{iam_model.SecondFactorTypeOTP},
},
}, false},
[]model.NextStep{&model.ChangePasswordStep{}},
nil,
},
@@ -561,19 +607,24 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) {
"email not verified and no password change required, mail verification step",
fields{
userSessionViewProvider: &mockViewUserSession{
PasswordVerification: time.Now().UTC().Add(-5 * time.Minute),
MfaSoftwareVerification: time.Now().UTC().Add(-5 * time.Minute),
PasswordVerification: time.Now().UTC().Add(-5 * time.Minute),
SecondFactorVerification: time.Now().UTC().Add(-5 * time.Minute),
},
userViewProvider: &mockViewUser{
PasswordSet: true,
MfaMaxSetUp: int32(model.MfaLevelSoftware),
MfaMaxSetUp: int32(model.MFALevelSecondFactor),
},
userEventProvider: &mockEventUser{},
orgViewProvider: &mockViewOrg{State: org_model.OrgStateActive},
PasswordCheckLifeTime: 10 * 24 * time.Hour,
MfaSoftwareCheckLifeTime: 18 * time.Hour,
userEventProvider: &mockEventUser{},
orgViewProvider: &mockViewOrg{State: org_model.OrgStateActive},
PasswordCheckLifeTime: 10 * 24 * time.Hour,
SecondFactorCheckLifeTime: 18 * time.Hour,
},
args{&model.AuthRequest{UserID: "UserID"}, false},
args{&model.AuthRequest{
UserID: "UserID",
LoginPolicy: &iam_model.LoginPolicyView{
SecondFactors: []iam_model.SecondFactorType{iam_model.SecondFactorTypeOTP},
},
}, false},
[]model.NextStep{&model.VerifyEMailStep{}},
nil,
},
@@ -581,20 +632,25 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) {
"email not verified and password change required, mail verification step",
fields{
userSessionViewProvider: &mockViewUserSession{
PasswordVerification: time.Now().UTC().Add(-5 * time.Minute),
MfaSoftwareVerification: time.Now().UTC().Add(-5 * time.Minute),
PasswordVerification: time.Now().UTC().Add(-5 * time.Minute),
SecondFactorVerification: time.Now().UTC().Add(-5 * time.Minute),
},
userViewProvider: &mockViewUser{
PasswordSet: true,
PasswordChangeRequired: true,
MfaMaxSetUp: int32(model.MfaLevelSoftware),
MfaMaxSetUp: int32(model.MFALevelSecondFactor),
},
userEventProvider: &mockEventUser{},
orgViewProvider: &mockViewOrg{State: org_model.OrgStateActive},
PasswordCheckLifeTime: 10 * 24 * time.Hour,
MfaSoftwareCheckLifeTime: 18 * time.Hour,
userEventProvider: &mockEventUser{},
orgViewProvider: &mockViewOrg{State: org_model.OrgStateActive},
PasswordCheckLifeTime: 10 * 24 * time.Hour,
SecondFactorCheckLifeTime: 18 * time.Hour,
},
args{&model.AuthRequest{UserID: "UserID"}, false},
args{&model.AuthRequest{
UserID: "UserID",
LoginPolicy: &iam_model.LoginPolicyView{
SecondFactors: []iam_model.SecondFactorType{iam_model.SecondFactorTypeOTP},
},
}, false},
[]model.NextStep{&model.ChangePasswordStep{}, &model.VerifyEMailStep{}},
nil,
},
@@ -602,21 +658,27 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) {
"email verified and no password change required, redirect to callback step",
fields{
userSessionViewProvider: &mockViewUserSession{
PasswordVerification: time.Now().UTC().Add(-5 * time.Minute),
MfaSoftwareVerification: time.Now().UTC().Add(-5 * time.Minute),
PasswordVerification: time.Now().UTC().Add(-5 * time.Minute),
SecondFactorVerification: time.Now().UTC().Add(-5 * time.Minute),
},
userViewProvider: &mockViewUser{
PasswordSet: true,
IsEmailVerified: true,
MfaMaxSetUp: int32(model.MfaLevelSoftware),
MfaMaxSetUp: int32(model.MFALevelSecondFactor),
},
userEventProvider: &mockEventUser{},
orgViewProvider: &mockViewOrg{State: org_model.OrgStateActive},
userGrantProvider: &mockUserGrants{},
PasswordCheckLifeTime: 10 * 24 * time.Hour,
MfaSoftwareCheckLifeTime: 18 * time.Hour,
userEventProvider: &mockEventUser{},
orgViewProvider: &mockViewOrg{State: org_model.OrgStateActive},
userGrantProvider: &mockUserGrants{},
PasswordCheckLifeTime: 10 * 24 * time.Hour,
SecondFactorCheckLifeTime: 18 * time.Hour,
},
args{&model.AuthRequest{UserID: "UserID", Request: &model.AuthRequestOIDC{}}, false},
args{&model.AuthRequest{
UserID: "UserID",
Request: &model.AuthRequestOIDC{},
LoginPolicy: &iam_model.LoginPolicyView{
SecondFactors: []iam_model.SecondFactorType{iam_model.SecondFactorTypeOTP},
},
}, false},
[]model.NextStep{&model.RedirectToCallbackStep{}},
nil,
},
@@ -624,21 +686,28 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) {
"prompt none, checkLoggedIn true and authenticated, redirect to callback step",
fields{
userSessionViewProvider: &mockViewUserSession{
PasswordVerification: time.Now().UTC().Add(-5 * time.Minute),
MfaSoftwareVerification: time.Now().UTC().Add(-5 * time.Minute),
PasswordVerification: time.Now().UTC().Add(-5 * time.Minute),
SecondFactorVerification: time.Now().UTC().Add(-5 * time.Minute),
},
userViewProvider: &mockViewUser{
PasswordSet: true,
IsEmailVerified: true,
MfaMaxSetUp: int32(model.MfaLevelSoftware),
MfaMaxSetUp: int32(model.MFALevelSecondFactor),
},
userEventProvider: &mockEventUser{},
orgViewProvider: &mockViewOrg{State: org_model.OrgStateActive},
userGrantProvider: &mockUserGrants{},
PasswordCheckLifeTime: 10 * 24 * time.Hour,
MfaSoftwareCheckLifeTime: 18 * time.Hour,
userEventProvider: &mockEventUser{},
orgViewProvider: &mockViewOrg{State: org_model.OrgStateActive},
userGrantProvider: &mockUserGrants{},
PasswordCheckLifeTime: 10 * 24 * time.Hour,
SecondFactorCheckLifeTime: 18 * time.Hour,
},
args{&model.AuthRequest{UserID: "UserID", Prompt: model.PromptNone, Request: &model.AuthRequestOIDC{}}, true},
args{&model.AuthRequest{
UserID: "UserID",
Prompt: model.PromptNone,
Request: &model.AuthRequestOIDC{},
LoginPolicy: &iam_model.LoginPolicyView{
SecondFactors: []iam_model.SecondFactorType{iam_model.SecondFactorTypeOTP},
},
}, true},
[]model.NextStep{&model.RedirectToCallbackStep{}},
nil,
},
@@ -646,13 +715,13 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) {
"prompt none, checkLoggedIn true, authenticated and required user grants missing, grant required step",
fields{
userSessionViewProvider: &mockViewUserSession{
PasswordVerification: time.Now().UTC().Add(-5 * time.Minute),
MfaSoftwareVerification: time.Now().UTC().Add(-5 * time.Minute),
PasswordVerification: time.Now().UTC().Add(-5 * time.Minute),
SecondFactorVerification: time.Now().UTC().Add(-5 * time.Minute),
},
userViewProvider: &mockViewUser{
PasswordSet: true,
IsEmailVerified: true,
MfaMaxSetUp: int32(model.MfaLevelSoftware),
MfaMaxSetUp: int32(model.MFALevelSecondFactor),
},
userEventProvider: &mockEventUser{},
orgViewProvider: &mockViewOrg{State: org_model.OrgStateActive},
@@ -660,10 +729,17 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) {
roleCheck: true,
userGrants: 0,
},
PasswordCheckLifeTime: 10 * 24 * time.Hour,
MfaSoftwareCheckLifeTime: 18 * time.Hour,
PasswordCheckLifeTime: 10 * 24 * time.Hour,
SecondFactorCheckLifeTime: 18 * time.Hour,
},
args{&model.AuthRequest{UserID: "UserID", Prompt: model.PromptNone, Request: &model.AuthRequestOIDC{}}, true},
args{&model.AuthRequest{
UserID: "UserID",
Prompt: model.PromptNone,
Request: &model.AuthRequestOIDC{},
LoginPolicy: &iam_model.LoginPolicyView{
SecondFactors: []iam_model.SecondFactorType{iam_model.SecondFactorTypeOTP},
},
}, true},
[]model.NextStep{&model.GrantRequiredStep{}},
nil,
},
@@ -671,13 +747,13 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) {
"prompt none, checkLoggedIn true, authenticated and required user grants exist, redirect to callback step",
fields{
userSessionViewProvider: &mockViewUserSession{
PasswordVerification: time.Now().UTC().Add(-5 * time.Minute),
MfaSoftwareVerification: time.Now().UTC().Add(-5 * time.Minute),
PasswordVerification: time.Now().UTC().Add(-5 * time.Minute),
SecondFactorVerification: time.Now().UTC().Add(-5 * time.Minute),
},
userViewProvider: &mockViewUser{
PasswordSet: true,
IsEmailVerified: true,
MfaMaxSetUp: int32(model.MfaLevelSoftware),
MfaMaxSetUp: int32(model.MFALevelSecondFactor),
},
userEventProvider: &mockEventUser{},
orgViewProvider: &mockViewOrg{State: org_model.OrgStateActive},
@@ -685,10 +761,17 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) {
roleCheck: true,
userGrants: 2,
},
PasswordCheckLifeTime: 10 * 24 * time.Hour,
MfaSoftwareCheckLifeTime: 18 * time.Hour,
PasswordCheckLifeTime: 10 * 24 * time.Hour,
SecondFactorCheckLifeTime: 18 * time.Hour,
},
args{&model.AuthRequest{UserID: "UserID", Prompt: model.PromptNone, Request: &model.AuthRequestOIDC{}}, true},
args{&model.AuthRequest{
UserID: "UserID",
Prompt: model.PromptNone,
Request: &model.AuthRequestOIDC{},
LoginPolicy: &iam_model.LoginPolicyView{
SecondFactors: []iam_model.SecondFactorType{iam_model.SecondFactorTypeOTP},
},
}, true},
[]model.NextStep{&model.RedirectToCallbackStep{}},
nil,
},
@@ -696,16 +779,16 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) {
"linking users, password step",
fields{
userSessionViewProvider: &mockViewUserSession{
MfaSoftwareVerification: time.Now().UTC().Add(-5 * time.Minute),
SecondFactorVerification: time.Now().UTC().Add(-5 * time.Minute),
},
userViewProvider: &mockViewUser{
PasswordSet: true,
IsEmailVerified: true,
MfaMaxSetUp: int32(model.MfaLevelSoftware),
MfaMaxSetUp: int32(model.MFALevelSecondFactor),
},
userEventProvider: &mockEventUser{},
orgViewProvider: &mockViewOrg{State: org_model.OrgStateActive},
MfaSoftwareCheckLifeTime: 18 * time.Hour,
userEventProvider: &mockEventUser{},
orgViewProvider: &mockViewOrg{State: org_model.OrgStateActive},
SecondFactorCheckLifeTime: 18 * time.Hour,
},
args{
&model.AuthRequest{
@@ -720,24 +803,27 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) {
"linking users, linking step",
fields{
userSessionViewProvider: &mockViewUserSession{
PasswordVerification: time.Now().UTC().Add(-5 * time.Minute),
MfaSoftwareVerification: time.Now().UTC().Add(-5 * time.Minute),
PasswordVerification: time.Now().UTC().Add(-5 * time.Minute),
SecondFactorVerification: time.Now().UTC().Add(-5 * time.Minute),
},
userViewProvider: &mockViewUser{
PasswordSet: true,
IsEmailVerified: true,
MfaMaxSetUp: int32(model.MfaLevelSoftware),
MfaMaxSetUp: int32(model.MFALevelSecondFactor),
},
userEventProvider: &mockEventUser{},
orgViewProvider: &mockViewOrg{State: org_model.OrgStateActive},
MfaSoftwareCheckLifeTime: 18 * time.Hour,
PasswordCheckLifeTime: 10 * 24 * time.Hour,
userEventProvider: &mockEventUser{},
orgViewProvider: &mockViewOrg{State: org_model.OrgStateActive},
SecondFactorCheckLifeTime: 18 * time.Hour,
PasswordCheckLifeTime: 10 * 24 * time.Hour,
},
args{
&model.AuthRequest{
UserID: "UserID",
SelectedIDPConfigID: "IDPConfigID",
LinkingUsers: []*model.ExternalUser{{IDPConfigID: "IDPConfigID", ExternalUserID: "UserID", DisplayName: "DisplayName"}},
LoginPolicy: &iam_model.LoginPolicyView{
SecondFactors: []iam_model.SecondFactorType{iam_model.SecondFactorTypeOTP},
},
}, false},
[]model.NextStep{&model.LinkUsersStep{}},
nil,
@@ -754,11 +840,12 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) {
UserEventProvider: tt.fields.userEventProvider,
OrgViewProvider: tt.fields.orgViewProvider,
UserGrantProvider: tt.fields.userGrantProvider,
LoginPolicyViewProvider: tt.fields.loginPolicyProvider,
PasswordCheckLifeTime: tt.fields.PasswordCheckLifeTime,
ExternalLoginCheckLifeTime: tt.fields.ExternalLoginCheckLifeTime,
MfaInitSkippedLifeTime: tt.fields.MfaInitSkippedLifeTime,
MfaSoftwareCheckLifeTime: tt.fields.MfaSoftwareCheckLifeTime,
MfaHardwareCheckLifeTime: tt.fields.MfaHardwareCheckLifeTime,
SecondFactorCheckLifeTime: tt.fields.SecondFactorCheckLifeTime,
MultiFactorCheckLifeTime: tt.fields.MultiFactorCheckLifeTime,
}
got, err := repo.nextSteps(context.Background(), tt.args.request, tt.args.checkLoggedIn)
if (err != nil && tt.wantErr == nil) || (tt.wantErr != nil && !tt.wantErr(err)) {
@@ -772,14 +859,15 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) {
func TestAuthRequestRepo_mfaChecked(t *testing.T) {
type fields struct {
MfaInitSkippedLifeTime time.Duration
MfaSoftwareCheckLifeTime time.Duration
MfaHardwareCheckLifeTime time.Duration
MfaInitSkippedLifeTime time.Duration
SecondFactorCheckLifeTime time.Duration
MultiFactorCheckLifeTime time.Duration
}
type args struct {
userSession *user_model.UserSessionView
request *model.AuthRequest
user *user_model.UserView
policy *iam_model.LoginPolicyView
}
tests := []struct {
name string
@@ -787,6 +875,7 @@ func TestAuthRequestRepo_mfaChecked(t *testing.T) {
args args
want model.NextStep
wantChecked bool
errFunc func(err error) bool
}{
//{
// "required, prompt and false", //TODO: enable when LevelsOfAssurance is checked
@@ -799,25 +888,78 @@ func TestAuthRequestRepo_mfaChecked(t *testing.T) {
// },
// false,
//},
{
"not set up, forced by policy, no mfas configured, error",
fields{
MfaInitSkippedLifeTime: 30 * 24 * time.Hour,
},
args{
request: &model.AuthRequest{
LoginPolicy: &iam_model.LoginPolicyView{
ForceMFA: true,
},
},
user: &user_model.UserView{
HumanView: &user_model.HumanView{
MfaMaxSetUp: model.MFALevelNotSetUp,
},
},
},
nil,
false,
errors.IsPreconditionFailed,
},
{
"not set up, prompt and false",
fields{
MfaInitSkippedLifeTime: 30 * 24 * time.Hour,
},
args{
request: &model.AuthRequest{},
request: &model.AuthRequest{
LoginPolicy: &iam_model.LoginPolicyView{
SecondFactors: []iam_model.SecondFactorType{iam_model.SecondFactorTypeOTP},
},
},
user: &user_model.UserView{
HumanView: &user_model.HumanView{
MfaMaxSetUp: model.MfaLevelNotSetUp,
MfaMaxSetUp: model.MFALevelNotSetUp,
},
},
},
&model.MfaPromptStep{
MfaProviders: []model.MfaType{
model.MfaTypeOTP,
MfaProviders: []model.MFAType{
model.MFATypeOTP,
},
},
false,
nil,
},
{
"not set up, forced by org, true",
fields{
MfaInitSkippedLifeTime: 30 * 24 * time.Hour,
},
args{
request: &model.AuthRequest{
LoginPolicy: &iam_model.LoginPolicyView{
ForceMFA: true,
SecondFactors: []iam_model.SecondFactorType{iam_model.SecondFactorTypeOTP},
},
},
user: &user_model.UserView{
HumanView: &user_model.HumanView{
MfaMaxSetUp: model.MFALevelNotSetUp,
},
},
},
&model.MfaPromptStep{
Required: true,
MfaProviders: []model.MFAType{
model.MFATypeOTP,
},
},
false,
nil,
},
{
"not set up and skipped, true",
@@ -825,45 +967,55 @@ func TestAuthRequestRepo_mfaChecked(t *testing.T) {
MfaInitSkippedLifeTime: 30 * 24 * time.Hour,
},
args{
request: &model.AuthRequest{},
request: &model.AuthRequest{
LoginPolicy: &iam_model.LoginPolicyView{},
},
user: &user_model.UserView{
HumanView: &user_model.HumanView{
MfaMaxSetUp: model.MfaLevelNotSetUp,
MfaMaxSetUp: model.MFALevelNotSetUp,
MfaInitSkipped: time.Now().UTC(),
},
},
},
nil,
true,
nil,
},
{
"checked mfa software, true",
"checked second factor, true",
fields{
MfaSoftwareCheckLifeTime: 18 * time.Hour,
SecondFactorCheckLifeTime: 18 * time.Hour,
},
args{
request: &model.AuthRequest{},
request: &model.AuthRequest{
LoginPolicy: &iam_model.LoginPolicyView{},
},
user: &user_model.UserView{
HumanView: &user_model.HumanView{
MfaMaxSetUp: model.MfaLevelSoftware,
MfaMaxSetUp: model.MFALevelSecondFactor,
OTPState: user_model.MfaStateReady,
},
},
userSession: &user_model.UserSessionView{MfaSoftwareVerification: time.Now().UTC().Add(-5 * time.Hour)},
userSession: &user_model.UserSessionView{SecondFactorVerification: time.Now().UTC().Add(-5 * time.Hour)},
},
nil,
true,
nil,
},
{
"not checked, check and false",
fields{
MfaSoftwareCheckLifeTime: 18 * time.Hour,
SecondFactorCheckLifeTime: 18 * time.Hour,
},
args{
request: &model.AuthRequest{},
request: &model.AuthRequest{
LoginPolicy: &iam_model.LoginPolicyView{
SecondFactors: []iam_model.SecondFactorType{iam_model.SecondFactorTypeOTP},
},
},
user: &user_model.UserView{
HumanView: &user_model.HumanView{
MfaMaxSetUp: model.MfaLevelSoftware,
MfaMaxSetUp: model.MFALevelSecondFactor,
OTPState: user_model.MfaStateReady,
},
},
@@ -871,19 +1023,24 @@ func TestAuthRequestRepo_mfaChecked(t *testing.T) {
},
&model.MfaVerificationStep{
MfaProviders: []model.MfaType{model.MfaTypeOTP},
MfaProviders: []model.MFAType{model.MFATypeOTP},
},
false,
nil,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
repo := &AuthRequestRepo{
MfaInitSkippedLifeTime: tt.fields.MfaInitSkippedLifeTime,
MfaSoftwareCheckLifeTime: tt.fields.MfaSoftwareCheckLifeTime,
MfaHardwareCheckLifeTime: tt.fields.MfaHardwareCheckLifeTime,
MfaInitSkippedLifeTime: tt.fields.MfaInitSkippedLifeTime,
SecondFactorCheckLifeTime: tt.fields.SecondFactorCheckLifeTime,
MultiFactorCheckLifeTime: tt.fields.MultiFactorCheckLifeTime,
}
got, ok, err := repo.mfaChecked(tt.args.userSession, tt.args.request, tt.args.user)
if (tt.errFunc != nil && !tt.errFunc(err)) || (err != nil && tt.errFunc == nil) {
t.Errorf("got wrong err: %v ", err)
return
}
got, ok := repo.mfaChecked(tt.args.userSession, tt.args.request, tt.args.user)
if ok != tt.wantChecked {
t.Errorf("mfaChecked() checked = %v, want %v", ok, tt.wantChecked)
}
@@ -897,7 +1054,8 @@ func TestAuthRequestRepo_mfaSkippedOrSetUp(t *testing.T) {
MfaInitSkippedLifeTime time.Duration
}
type args struct {
user *user_model.UserView
user *user_model.UserView
policy *iam_model.LoginPolicyView
}
tests := []struct {
name string
@@ -908,11 +1066,16 @@ func TestAuthRequestRepo_mfaSkippedOrSetUp(t *testing.T) {
{
"mfa set up, true",
fields{},
args{&user_model.UserView{
HumanView: &user_model.HumanView{
MfaMaxSetUp: model.MfaLevelSoftware,
args{
&user_model.UserView{
HumanView: &user_model.HumanView{
MfaMaxSetUp: model.MFALevelSecondFactor,
},
},
}},
&iam_model.LoginPolicyView{
SecondFactors: []iam_model.SecondFactorType{iam_model.SecondFactorTypeOTP},
},
},
true,
},
{
@@ -920,12 +1083,17 @@ func TestAuthRequestRepo_mfaSkippedOrSetUp(t *testing.T) {
fields{
MfaInitSkippedLifeTime: 30 * 24 * time.Hour,
},
args{&user_model.UserView{
HumanView: &user_model.HumanView{
MfaMaxSetUp: -1,
MfaInitSkipped: time.Now().UTC().Add(-10 * time.Hour),
args{
&user_model.UserView{
HumanView: &user_model.HumanView{
MfaMaxSetUp: -1,
MfaInitSkipped: time.Now().UTC().Add(-10 * time.Hour),
},
},
}},
&iam_model.LoginPolicyView{
SecondFactors: []iam_model.SecondFactorType{iam_model.SecondFactorTypeOTP},
},
},
true,
},
{
@@ -933,12 +1101,17 @@ func TestAuthRequestRepo_mfaSkippedOrSetUp(t *testing.T) {
fields{
MfaInitSkippedLifeTime: 30 * 24 * time.Hour,
},
args{&user_model.UserView{
HumanView: &user_model.HumanView{
MfaMaxSetUp: -1,
MfaInitSkipped: time.Now().UTC().Add(-40 * 24 * time.Hour),
args{
&user_model.UserView{
HumanView: &user_model.HumanView{
MfaMaxSetUp: -1,
MfaInitSkipped: time.Now().UTC().Add(-40 * 24 * time.Hour),
},
},
}},
&iam_model.LoginPolicyView{
SecondFactors: []iam_model.SecondFactorType{iam_model.SecondFactorTypeOTP},
},
},
false,
},
}
@@ -947,7 +1120,7 @@ func TestAuthRequestRepo_mfaSkippedOrSetUp(t *testing.T) {
repo := &AuthRequestRepo{
MfaInitSkippedLifeTime: tt.fields.MfaInitSkippedLifeTime,
}
if got := repo.mfaSkippedOrSetUp(tt.args.user); got != tt.want {
if got := repo.mfaSkippedOrSetUp(tt.args.user, tt.args.policy); got != tt.want {
t.Errorf("mfaSkippedOrSetUp() = %v, want %v", got, tt.want)
}
})
@@ -996,9 +1169,9 @@ func Test_userSessionByIDs(t *testing.T) {
eventProvider: &mockEventErrUser{},
},
&user_model.UserSessionView{
PasswordVerification: time.Now().UTC().Round(1 * time.Second),
MfaSoftwareVerification: time.Time{},
MfaHardwareVerification: time.Time{},
PasswordVerification: time.Now().UTC().Round(1 * time.Second),
SecondFactorVerification: time.Time{},
MultiFactorVerification: time.Time{},
},
nil,
},
@@ -1019,9 +1192,9 @@ func Test_userSessionByIDs(t *testing.T) {
},
},
&user_model.UserSessionView{
PasswordVerification: time.Now().UTC().Round(1 * time.Second),
MfaSoftwareVerification: time.Time{},
MfaHardwareVerification: time.Time{},
PasswordVerification: time.Now().UTC().Round(1 * time.Second),
SecondFactorVerification: time.Time{},
MultiFactorVerification: time.Time{},
},
nil,
},
@@ -1046,9 +1219,9 @@ func Test_userSessionByIDs(t *testing.T) {
},
},
&user_model.UserSessionView{
PasswordVerification: time.Now().UTC().Round(1 * time.Second),
MfaSoftwareVerification: time.Time{},
MfaHardwareVerification: time.Time{},
PasswordVerification: time.Now().UTC().Round(1 * time.Second),
SecondFactorVerification: time.Time{},
MultiFactorVerification: time.Time{},
},
nil,
},
@@ -1073,9 +1246,9 @@ func Test_userSessionByIDs(t *testing.T) {
},
},
&user_model.UserSessionView{
PasswordVerification: time.Now().UTC().Round(1 * time.Second),
MfaSoftwareVerification: time.Now().UTC().Round(1 * time.Second),
ChangeDate: time.Now().UTC().Round(1 * time.Second),
PasswordVerification: time.Now().UTC().Round(1 * time.Second),
SecondFactorVerification: time.Now().UTC().Round(1 * time.Second),
ChangeDate: time.Now().UTC().Round(1 * time.Second),
},
nil,
},

View File

@@ -394,7 +394,12 @@ func (repo *UserRepo) MyUserChanges(ctx context.Context, lastSequence uint64, li
change.ModifierName = change.ModifierID
user, _ := repo.UserEvents.UserByID(ctx, change.ModifierID)
if user != nil {
change.ModifierName = user.DisplayName
if user.Human != nil {
change.ModifierName = user.DisplayName
}
if user.Machine != nil {
change.ModifierName = user.Machine.Name
}
}
}
return changes, nil