feat: Lockout policy (#2121)

* feat: lock users if lockout policy is set

* feat: setup

* feat: lock user on password failes

* feat: render error

* feat: lock user on command side

* feat: auth_req tests

* feat: lockout policy docs

* feat: remove show lockout failures from proto

* fix: console lockout

* feat: tests

* fix: tests

* unlock function

* add unlock button

* fix migration version

* lockout policy

* lint

* Update internal/auth/repository/eventsourcing/eventstore/auth_request.go

Co-authored-by: Silvan <silvan.reusser@gmail.com>

* fix: err message

* Update internal/command/setup_step4.go

Co-authored-by: Silvan <silvan.reusser@gmail.com>

Co-authored-by: Max Peintner <max@caos.ch>
Co-authored-by: Livio Amstutz <livio.a@gmail.com>
Co-authored-by: Silvan <silvan.reusser@gmail.com>
This commit is contained in:
Fabi
2021-08-11 08:36:32 +02:00
committed by GitHub
parent 272e411e27
commit bc951985ed
101 changed files with 2170 additions and 1574 deletions

View File

@@ -35,14 +35,15 @@ type AuthRequestRepo struct {
View *view.View
Eventstore v1.Eventstore
UserSessionViewProvider userSessionViewProvider
UserViewProvider userViewProvider
UserCommandProvider userCommandProvider
UserEventProvider userEventProvider
OrgViewProvider orgViewProvider
LoginPolicyViewProvider loginPolicyViewProvider
IDPProviderViewProvider idpProviderViewProvider
UserGrantProvider userGrantProvider
UserSessionViewProvider userSessionViewProvider
UserViewProvider userViewProvider
UserCommandProvider userCommandProvider
UserEventProvider userEventProvider
OrgViewProvider orgViewProvider
LoginPolicyViewProvider loginPolicyViewProvider
LockoutPolicyViewProvider lockoutPolicyViewProvider
IDPProviderViewProvider idpProviderViewProvider
UserGrantProvider userGrantProvider
IdGenerator id.Generator
@@ -69,6 +70,10 @@ type loginPolicyViewProvider interface {
LoginPolicyByAggregateID(string) (*iam_view_model.LoginPolicyView, error)
}
type lockoutPolicyViewProvider interface {
LockoutPolicyByAggregateID(string) (*iam_view_model.LockoutPolicyView, error)
}
type idpProviderViewProvider interface {
IDPProvidersByAggregateIDAndState(string, iam_model.IDPConfigState) ([]*iam_view_model.IDPProviderView, error)
}
@@ -240,7 +245,7 @@ func (repo *AuthRequestRepo) SelectUser(ctx context.Context, id, userID, userAge
if err != nil {
return err
}
user, err := activeUserByID(ctx, repo.UserViewProvider, repo.UserEventProvider, repo.OrgViewProvider, userID)
user, err := activeUserByID(ctx, repo.UserViewProvider, repo.UserEventProvider, repo.OrgViewProvider, repo.LockoutPolicyViewProvider, userID)
if err != nil {
return err
}
@@ -262,7 +267,11 @@ func (repo *AuthRequestRepo) VerifyPassword(ctx context.Context, id, userID, res
if err != nil {
return err
}
return repo.Command.HumanCheckPassword(ctx, resourceOwner, userID, password, request.WithCurrentInfo(info))
policy, err := repo.getLockoutPolicy(ctx, resourceOwner)
if err != nil {
return err
}
return repo.Command.HumanCheckPassword(ctx, resourceOwner, userID, password, request.WithCurrentInfo(info), policy)
}
func (repo *AuthRequestRepo) VerifyMFAOTP(ctx context.Context, authRequestID, userID, resourceOwner, code, userAgentID string, info *domain.BrowserInfo) (err error) {
@@ -414,6 +423,10 @@ func (repo *AuthRequestRepo) getAuthRequestEnsureUser(ctx context.Context, authR
if request.UserID != userID {
return nil, errors.ThrowPreconditionFailed(nil, "EVENT-GBH32", "Errors.User.NotMatchingUserID")
}
_, err = activeUserByID(ctx, repo.UserViewProvider, repo.UserEventProvider, repo.OrgViewProvider, repo.LockoutPolicyViewProvider, request.UserID)
if err != nil {
return nil, err
}
return request, nil
}
@@ -466,6 +479,11 @@ func (repo *AuthRequestRepo) fillPolicies(ctx context.Context, request *domain.A
if idpProviders != nil {
request.AllowedExternalIDPs = idpProviders
}
lockoutPolicy, err := repo.getLockoutPolicy(ctx, orgID)
if err != nil {
return err
}
request.LockoutPolicy = lockoutPolicy
privacyPolicy, err := repo.getPrivacyPolicy(ctx, orgID)
if err != nil {
return err
@@ -587,7 +605,7 @@ func (repo *AuthRequestRepo) nextSteps(ctx context.Context, request *domain.Auth
}
return steps, nil
}
user, err := activeUserByID(ctx, repo.UserViewProvider, repo.UserEventProvider, repo.OrgViewProvider, request.UserID)
user, err := activeUserByID(ctx, repo.UserViewProvider, repo.UserEventProvider, repo.OrgViewProvider, repo.LockoutPolicyViewProvider, request.UserID)
if err != nil {
return nil, err
}
@@ -795,6 +813,21 @@ func (repo *AuthRequestRepo) getPrivacyPolicy(ctx context.Context, orgID string)
return policy.ToDomain(), err
}
func (repo *AuthRequestRepo) getLockoutPolicy(ctx context.Context, orgID string) (*domain.LockoutPolicy, error) {
policy, err := repo.View.LockoutPolicyByAggregateID(orgID)
if errors.IsNotFound(err) {
policy, err = repo.View.LockoutPolicyByAggregateID(repo.IAMID)
if err != nil {
return nil, err
}
policy.Default = true
}
if err != nil {
return nil, err
}
return policy.ToDomain(), err
}
func (repo *AuthRequestRepo) getLabelPolicy(ctx context.Context, orgID string) (*domain.LabelPolicy, error) {
policy, err := repo.View.LabelPolicyByAggregateIDAndState(orgID, int32(domain.LabelPolicyStateActive))
if errors.IsNotFound(err) {
@@ -921,7 +954,8 @@ func userSessionByIDs(ctx context.Context, provider userSessionViewProvider, eve
return user_view_model.UserSessionToModel(&sessionCopy, provider.PrefixAvatarURL()), nil
}
func activeUserByID(ctx context.Context, userViewProvider userViewProvider, userEventProvider userEventProvider, orgViewProvider orgViewProvider, userID string) (*user_model.UserView, error) {
func activeUserByID(ctx context.Context, userViewProvider userViewProvider, userEventProvider userEventProvider, orgViewProvider orgViewProvider, lockoutPolicyProvider lockoutPolicyViewProvider, userID string) (*user_model.UserView, error) {
// PLANNED: Check LockoutPolicy
user, err := userByID(ctx, userViewProvider, userEventProvider, userID)
if err != nil {
return nil, err
@@ -930,7 +964,6 @@ func activeUserByID(ctx context.Context, userViewProvider userViewProvider, user
if user.HumanView == nil {
return nil, errors.ThrowPreconditionFailed(nil, "EVENT-Lm69x", "Errors.User.NotHuman")
}
if user.State == user_model.UserStateLocked || user.State == user_model.UserStateSuspend {
return nil, errors.ThrowPreconditionFailed(nil, "EVENT-FJ262", "Errors.User.Locked")
}

View File

@@ -152,6 +152,14 @@ func (m *mockLoginPolicy) LoginPolicyByAggregateID(id string) (*iam_view_model.L
return m.policy, nil
}
type mockLockoutPolicy struct {
policy *iam_view_model.LockoutPolicyView
}
func (m *mockLockoutPolicy) LockoutPolicyByAggregateID(id string) (*iam_view_model.LockoutPolicyView, 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),
@@ -229,6 +237,7 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) {
orgViewProvider orgViewProvider
userGrantProvider userGrantProvider
loginPolicyProvider loginPolicyViewProvider
lockoutPolicyProvider lockoutPolicyViewProvider
PasswordCheckLifeTime time.Duration
ExternalLoginCheckLifeTime time.Duration
MFAInitSkippedLifeTime time.Duration
@@ -404,6 +413,11 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) {
},
},
orgViewProvider: &mockViewOrg{State: org_model.OrgStateActive},
lockoutPolicyProvider: &mockLockoutPolicy{
policy: &iam_view_model.LockoutPolicyView{
ShowLockOutFailures: true,
},
},
},
args{&domain.AuthRequest{UserID: "UserID"}, false},
nil,
@@ -420,6 +434,11 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) {
},
},
orgViewProvider: &mockViewOrg{State: org_model.OrgStateActive},
lockoutPolicyProvider: &mockLockoutPolicy{
policy: &iam_view_model.LockoutPolicyView{
ShowLockOutFailures: true,
},
},
},
args{&domain.AuthRequest{UserID: "UserID"}, false},
nil,
@@ -431,6 +450,11 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) {
userViewProvider: &mockViewUser{},
userEventProvider: &mockEventUser{},
orgViewProvider: &mockViewErrOrg{},
lockoutPolicyProvider: &mockLockoutPolicy{
policy: &iam_view_model.LockoutPolicyView{
ShowLockOutFailures: true,
},
},
},
args{&domain.AuthRequest{UserID: "UserID"}, false},
nil,
@@ -442,6 +466,11 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) {
userViewProvider: &mockViewUser{},
userEventProvider: &mockEventUser{},
orgViewProvider: &mockViewOrg{State: org_model.OrgStateInactive},
lockoutPolicyProvider: &mockLockoutPolicy{
policy: &iam_view_model.LockoutPolicyView{
ShowLockOutFailures: true,
},
},
},
args{&domain.AuthRequest{UserID: "UserID"}, false},
nil,
@@ -456,6 +485,11 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) {
},
userEventProvider: &mockEventUser{},
orgViewProvider: &mockViewOrg{State: org_model.OrgStateActive},
lockoutPolicyProvider: &mockLockoutPolicy{
policy: &iam_view_model.LockoutPolicyView{
ShowLockOutFailures: true,
},
},
},
args{&domain.AuthRequest{UserID: "UserID", LoginPolicy: &domain.LoginPolicy{}}, false},
[]domain.NextStep{&domain.PasswordStep{}},
@@ -468,6 +502,11 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) {
userViewProvider: &mockViewUser{},
userEventProvider: &mockEventUser{},
orgViewProvider: &mockViewOrg{State: org_model.OrgStateActive},
lockoutPolicyProvider: &mockLockoutPolicy{
policy: &iam_view_model.LockoutPolicyView{
ShowLockOutFailures: true,
},
},
},
args{&domain.AuthRequest{UserID: "UserID"}, false},
nil,
@@ -483,6 +522,11 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) {
},
userEventProvider: &mockEventUser{},
orgViewProvider: &mockViewOrg{State: org_model.OrgStateActive},
lockoutPolicyProvider: &mockLockoutPolicy{
policy: &iam_view_model.LockoutPolicyView{
ShowLockOutFailures: true,
},
},
},
args{&domain.AuthRequest{UserID: "UserID"}, false},
[]domain.NextStep{&domain.InitUserStep{
@@ -500,6 +544,11 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) {
userEventProvider: &mockEventUser{},
orgViewProvider: &mockViewOrg{State: org_model.OrgStateActive},
MultiFactorCheckLifeTime: 10 * time.Hour,
lockoutPolicyProvider: &mockLockoutPolicy{
policy: &iam_view_model.LockoutPolicyView{
ShowLockOutFailures: true,
},
},
},
args{&domain.AuthRequest{UserID: "UserID", LoginPolicy: &domain.LoginPolicy{PasswordlessType: domain.PasswordlessTypeAllowed}}, false},
[]domain.NextStep{&domain.PasswordlessRegistrationPromptStep{}},
@@ -512,8 +561,13 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) {
userViewProvider: &mockViewUser{
PasswordlessTokens: user_view_model.WebAuthNTokens{&user_view_model.WebAuthNView{ID: "id", State: int32(user_model.MFAStateReady)}},
},
userEventProvider: &mockEventUser{},
orgViewProvider: &mockViewOrg{State: org_model.OrgStateActive},
userEventProvider: &mockEventUser{},
orgViewProvider: &mockViewOrg{State: org_model.OrgStateActive},
lockoutPolicyProvider: &mockLockoutPolicy{
policy: &iam_view_model.LockoutPolicyView{
ShowLockOutFailures: true,
},
},
MultiFactorCheckLifeTime: 10 * time.Hour,
},
args{&domain.AuthRequest{UserID: "UserID", LoginPolicy: &domain.LoginPolicy{PasswordlessType: domain.PasswordlessTypeAllowed}}, false},
@@ -528,8 +582,13 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) {
PasswordSet: true,
PasswordlessTokens: user_view_model.WebAuthNTokens{&user_view_model.WebAuthNView{ID: "id", State: int32(user_model.MFAStateReady)}},
},
userEventProvider: &mockEventUser{},
orgViewProvider: &mockViewOrg{State: org_model.OrgStateActive},
userEventProvider: &mockEventUser{},
orgViewProvider: &mockViewOrg{State: org_model.OrgStateActive},
lockoutPolicyProvider: &mockLockoutPolicy{
policy: &iam_view_model.LockoutPolicyView{
ShowLockOutFailures: true,
},
},
MultiFactorCheckLifeTime: 10 * time.Hour,
},
args{&domain.AuthRequest{UserID: "UserID", LoginPolicy: &domain.LoginPolicy{PasswordlessType: domain.PasswordlessTypeAllowed}}, false},
@@ -550,7 +609,12 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) {
IsEmailVerified: false,
MFAMaxSetUp: int32(model.MFALevelMultiFactor),
},
userEventProvider: &mockEventUser{},
userEventProvider: &mockEventUser{},
lockoutPolicyProvider: &mockLockoutPolicy{
policy: &iam_view_model.LockoutPolicyView{
ShowLockOutFailures: true,
},
},
orgViewProvider: &mockViewOrg{State: org_model.OrgStateActive},
MultiFactorCheckLifeTime: 10 * time.Hour,
},
@@ -572,7 +636,12 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) {
PasswordInitRequired: true,
},
userEventProvider: &mockEventUser{},
orgViewProvider: &mockViewOrg{State: org_model.OrgStateActive},
lockoutPolicyProvider: &mockLockoutPolicy{
policy: &iam_view_model.LockoutPolicyView{
ShowLockOutFailures: true,
},
},
orgViewProvider: &mockViewOrg{State: org_model.OrgStateActive},
},
args{&domain.AuthRequest{UserID: "UserID", LoginPolicy: &domain.LoginPolicy{}}, false},
[]domain.NextStep{&domain.InitPasswordStep{}},
@@ -588,7 +657,12 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) {
IsEmailVerified: true,
MFAMaxSetUp: int32(model.MFALevelSecondFactor),
},
userEventProvider: &mockEventUser{},
userEventProvider: &mockEventUser{},
lockoutPolicyProvider: &mockLockoutPolicy{
policy: &iam_view_model.LockoutPolicyView{
ShowLockOutFailures: true,
},
},
orgViewProvider: &mockViewOrg{State: org_model.OrgStateActive},
SecondFactorCheckLifeTime: 18 * time.Hour,
},
@@ -613,6 +687,11 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) {
loginPolicyProvider: &mockLoginPolicy{
policy: &iam_view_model.LoginPolicyView{},
},
lockoutPolicyProvider: &mockLockoutPolicy{
policy: &iam_view_model.LockoutPolicyView{
ShowLockOutFailures: true,
},
},
ExternalLoginCheckLifeTime: 10 * 24 * time.Hour,
SecondFactorCheckLifeTime: 18 * time.Hour,
},
@@ -634,8 +713,13 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) {
userViewProvider: &mockViewUser{
PasswordSet: true,
},
userEventProvider: &mockEventUser{},
orgViewProvider: &mockViewOrg{State: org_model.OrgStateActive},
userEventProvider: &mockEventUser{},
orgViewProvider: &mockViewOrg{State: org_model.OrgStateActive},
lockoutPolicyProvider: &mockLockoutPolicy{
policy: &iam_view_model.LockoutPolicyView{
ShowLockOutFailures: true,
},
},
PasswordCheckLifeTime: 10 * 24 * time.Hour,
},
args{&domain.AuthRequest{UserID: "UserID", LoginPolicy: &domain.LoginPolicy{}}, false},
@@ -654,9 +738,14 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) {
IsEmailVerified: true,
MFAMaxSetUp: int32(model.MFALevelSecondFactor),
},
userEventProvider: &mockEventUser{},
orgViewProvider: &mockViewOrg{State: org_model.OrgStateActive},
userGrantProvider: &mockUserGrants{},
userEventProvider: &mockEventUser{},
orgViewProvider: &mockViewOrg{State: org_model.OrgStateActive},
userGrantProvider: &mockUserGrants{},
lockoutPolicyProvider: &mockLockoutPolicy{
policy: &iam_view_model.LockoutPolicyView{
ShowLockOutFailures: true,
},
},
SecondFactorCheckLifeTime: 18 * time.Hour,
ExternalLoginCheckLifeTime: 10 * 24 * time.Hour,
},
@@ -682,8 +771,13 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) {
OTPState: int32(user_model.MFAStateReady),
MFAMaxSetUp: int32(model.MFALevelMultiFactor),
},
userEventProvider: &mockEventUser{},
orgViewProvider: &mockViewOrg{State: org_model.OrgStateActive},
userEventProvider: &mockEventUser{},
orgViewProvider: &mockViewOrg{State: org_model.OrgStateActive},
lockoutPolicyProvider: &mockLockoutPolicy{
policy: &iam_view_model.LockoutPolicyView{
ShowLockOutFailures: true,
},
},
PasswordCheckLifeTime: 10 * 24 * time.Hour,
SecondFactorCheckLifeTime: 18 * time.Hour,
},
@@ -710,8 +804,13 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) {
OTPState: int32(user_model.MFAStateReady),
MFAMaxSetUp: int32(model.MFALevelSecondFactor),
},
userEventProvider: &mockEventUser{},
orgViewProvider: &mockViewOrg{State: org_model.OrgStateActive},
userEventProvider: &mockEventUser{},
orgViewProvider: &mockViewOrg{State: org_model.OrgStateActive},
lockoutPolicyProvider: &mockLockoutPolicy{
policy: &iam_view_model.LockoutPolicyView{
ShowLockOutFailures: true,
},
},
PasswordCheckLifeTime: 10 * 24 * time.Hour,
SecondFactorCheckLifeTime: 18 * time.Hour,
},
@@ -739,8 +838,13 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) {
OTPState: int32(user_model.MFAStateReady),
MFAMaxSetUp: int32(model.MFALevelSecondFactor),
},
userEventProvider: &mockEventUser{},
orgViewProvider: &mockViewOrg{State: org_model.OrgStateActive},
userEventProvider: &mockEventUser{},
orgViewProvider: &mockViewOrg{State: org_model.OrgStateActive},
lockoutPolicyProvider: &mockLockoutPolicy{
policy: &iam_view_model.LockoutPolicyView{
ShowLockOutFailures: true,
},
},
PasswordCheckLifeTime: 10 * 24 * time.Hour,
ExternalLoginCheckLifeTime: 10 * 24 * time.Hour,
SecondFactorCheckLifeTime: 18 * time.Hour,
@@ -771,8 +875,13 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) {
IsEmailVerified: true,
MFAMaxSetUp: int32(model.MFALevelSecondFactor),
},
userEventProvider: &mockEventUser{},
orgViewProvider: &mockViewOrg{State: org_model.OrgStateActive},
userEventProvider: &mockEventUser{},
orgViewProvider: &mockViewOrg{State: org_model.OrgStateActive},
lockoutPolicyProvider: &mockLockoutPolicy{
policy: &iam_view_model.LockoutPolicyView{
ShowLockOutFailures: true,
},
},
PasswordCheckLifeTime: 10 * 24 * time.Hour,
SecondFactorCheckLifeTime: 18 * time.Hour,
},
@@ -797,8 +906,13 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) {
PasswordSet: true,
MFAMaxSetUp: int32(model.MFALevelSecondFactor),
},
userEventProvider: &mockEventUser{},
orgViewProvider: &mockViewOrg{State: org_model.OrgStateActive},
userEventProvider: &mockEventUser{},
orgViewProvider: &mockViewOrg{State: org_model.OrgStateActive},
lockoutPolicyProvider: &mockLockoutPolicy{
policy: &iam_view_model.LockoutPolicyView{
ShowLockOutFailures: true,
},
},
PasswordCheckLifeTime: 10 * 24 * time.Hour,
SecondFactorCheckLifeTime: 18 * time.Hour,
},
@@ -823,8 +937,13 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) {
PasswordChangeRequired: true,
MFAMaxSetUp: int32(model.MFALevelSecondFactor),
},
userEventProvider: &mockEventUser{},
orgViewProvider: &mockViewOrg{State: org_model.OrgStateActive},
userEventProvider: &mockEventUser{},
orgViewProvider: &mockViewOrg{State: org_model.OrgStateActive},
lockoutPolicyProvider: &mockLockoutPolicy{
policy: &iam_view_model.LockoutPolicyView{
ShowLockOutFailures: true,
},
},
PasswordCheckLifeTime: 10 * 24 * time.Hour,
SecondFactorCheckLifeTime: 18 * time.Hour,
},
@@ -849,9 +968,14 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) {
IsEmailVerified: true,
MFAMaxSetUp: int32(model.MFALevelSecondFactor),
},
userEventProvider: &mockEventUser{},
orgViewProvider: &mockViewOrg{State: org_model.OrgStateActive},
userGrantProvider: &mockUserGrants{},
userEventProvider: &mockEventUser{},
orgViewProvider: &mockViewOrg{State: org_model.OrgStateActive},
userGrantProvider: &mockUserGrants{},
lockoutPolicyProvider: &mockLockoutPolicy{
policy: &iam_view_model.LockoutPolicyView{
ShowLockOutFailures: true,
},
},
PasswordCheckLifeTime: 10 * 24 * time.Hour,
SecondFactorCheckLifeTime: 18 * time.Hour,
},
@@ -877,9 +1001,14 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) {
IsEmailVerified: true,
MFAMaxSetUp: int32(model.MFALevelSecondFactor),
},
userEventProvider: &mockEventUser{},
orgViewProvider: &mockViewOrg{State: org_model.OrgStateActive},
userGrantProvider: &mockUserGrants{},
userEventProvider: &mockEventUser{},
orgViewProvider: &mockViewOrg{State: org_model.OrgStateActive},
userGrantProvider: &mockUserGrants{},
lockoutPolicyProvider: &mockLockoutPolicy{
policy: &iam_view_model.LockoutPolicyView{
ShowLockOutFailures: true,
},
},
PasswordCheckLifeTime: 10 * 24 * time.Hour,
SecondFactorCheckLifeTime: 18 * time.Hour,
},
@@ -912,6 +1041,11 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) {
roleCheck: true,
userGrants: 0,
},
lockoutPolicyProvider: &mockLockoutPolicy{
policy: &iam_view_model.LockoutPolicyView{
ShowLockOutFailures: true,
},
},
PasswordCheckLifeTime: 10 * 24 * time.Hour,
SecondFactorCheckLifeTime: 18 * time.Hour,
},
@@ -944,6 +1078,11 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) {
roleCheck: true,
userGrants: 2,
},
lockoutPolicyProvider: &mockLockoutPolicy{
policy: &iam_view_model.LockoutPolicyView{
ShowLockOutFailures: true,
},
},
PasswordCheckLifeTime: 10 * 24 * time.Hour,
SecondFactorCheckLifeTime: 18 * time.Hour,
},
@@ -969,6 +1108,11 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) {
IsEmailVerified: true,
MFAMaxSetUp: int32(model.MFALevelSecondFactor),
},
lockoutPolicyProvider: &mockLockoutPolicy{
policy: &iam_view_model.LockoutPolicyView{
ShowLockOutFailures: true,
},
},
userEventProvider: &mockEventUser{},
orgViewProvider: &mockViewOrg{State: org_model.OrgStateActive},
SecondFactorCheckLifeTime: 18 * time.Hour,
@@ -995,8 +1139,13 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) {
IsEmailVerified: true,
MFAMaxSetUp: int32(model.MFALevelSecondFactor),
},
userEventProvider: &mockEventUser{},
orgViewProvider: &mockViewOrg{State: org_model.OrgStateActive},
userEventProvider: &mockEventUser{},
orgViewProvider: &mockViewOrg{State: org_model.OrgStateActive},
lockoutPolicyProvider: &mockLockoutPolicy{
policy: &iam_view_model.LockoutPolicyView{
ShowLockOutFailures: true,
},
},
SecondFactorCheckLifeTime: 18 * time.Hour,
PasswordCheckLifeTime: 10 * 24 * time.Hour,
},
@@ -1024,6 +1173,7 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) {
OrgViewProvider: tt.fields.orgViewProvider,
UserGrantProvider: tt.fields.userGrantProvider,
LoginPolicyViewProvider: tt.fields.loginPolicyProvider,
LockoutPolicyViewProvider: tt.fields.lockoutPolicyProvider,
PasswordCheckLifeTime: tt.fields.PasswordCheckLifeTime,
ExternalLoginCheckLifeTime: tt.fields.ExternalLoginCheckLifeTime,
MFAInitSkippedLifeTime: tt.fields.MFAInitSkippedLifeTime,

View File

@@ -73,6 +73,7 @@ func Register(configs Configs, bulkLimit, errorCount uint64, view *view.View, es
newPrivacyPolicy(handler{view, bulkLimit, configs.cycleDuration("PrivacyPolicy"), errorCount, es}),
newCustomText(handler{view, bulkLimit, configs.cycleDuration("CustomTexts"), errorCount, es}),
newMetadata(handler{view, bulkLimit, configs.cycleDuration("Metadata"), errorCount, es}),
newLockoutPolicy(handler{view, bulkLimit, configs.cycleDuration("LockoutPolicy"), errorCount, es}),
}
}

View File

@@ -0,0 +1,110 @@
package handler
import (
"github.com/caos/logging"
"github.com/caos/zitadel/internal/eventstore/v1"
es_models "github.com/caos/zitadel/internal/eventstore/v1/models"
"github.com/caos/zitadel/internal/eventstore/v1/query"
"github.com/caos/zitadel/internal/eventstore/v1/spooler"
iam_es_model "github.com/caos/zitadel/internal/iam/repository/eventsourcing/model"
iam_model "github.com/caos/zitadel/internal/iam/repository/view/model"
org_es_model "github.com/caos/zitadel/internal/org/repository/eventsourcing/model"
)
const (
lockoutPolicyTable = "auth.lockout_policies"
)
type LockoutPolicy struct {
handler
subscription *v1.Subscription
}
func newLockoutPolicy(handler handler) *LockoutPolicy {
h := &LockoutPolicy{
handler: handler,
}
h.subscribe()
return h
}
func (p *LockoutPolicy) subscribe() {
p.subscription = p.es.Subscribe(p.AggregateTypes()...)
go func() {
for event := range p.subscription.Events {
query.ReduceEvent(p, event)
}
}()
}
func (p *LockoutPolicy) ViewModel() string {
return lockoutPolicyTable
}
func (p *LockoutPolicy) Subscription() *v1.Subscription {
return p.subscription
}
func (_ *LockoutPolicy) AggregateTypes() []es_models.AggregateType {
return []es_models.AggregateType{org_es_model.OrgAggregate, iam_es_model.IAMAggregate}
}
func (p *LockoutPolicy) CurrentSequence() (uint64, error) {
sequence, err := p.view.GetLatestLockoutPolicySequence()
if err != nil {
return 0, err
}
return sequence.CurrentSequence, nil
}
func (p *LockoutPolicy) EventQuery() (*es_models.SearchQuery, error) {
sequence, err := p.view.GetLatestLockoutPolicySequence()
if err != nil {
return nil, err
}
return es_models.NewSearchQuery().
AggregateTypeFilter(p.AggregateTypes()...).
LatestSequenceFilter(sequence.CurrentSequence), nil
}
func (p *LockoutPolicy) Reduce(event *es_models.Event) (err error) {
switch event.AggregateType {
case org_es_model.OrgAggregate, iam_es_model.IAMAggregate:
err = p.processLockoutPolicy(event)
}
return err
}
func (p *LockoutPolicy) processLockoutPolicy(event *es_models.Event) (err error) {
policy := new(iam_model.LockoutPolicyView)
switch event.Type {
case iam_es_model.LockoutPolicyAdded, org_es_model.LockoutPolicyAdded:
err = policy.AppendEvent(event)
case iam_es_model.LockoutPolicyChanged, org_es_model.LockoutPolicyChanged:
policy, err = p.view.LockoutPolicyByAggregateID(event.AggregateID)
if err != nil {
return err
}
err = policy.AppendEvent(event)
case org_es_model.LockoutPolicyRemoved:
return p.view.DeleteLockoutPolicy(event.AggregateID, event)
default:
return p.view.ProcessedLockoutPolicySequence(event)
}
if err != nil {
return err
}
return p.view.PutLockoutPolicy(policy, event)
}
func (p *LockoutPolicy) OnError(event *es_models.Event, err error) error {
logging.LogWithFields("SPOOL-0pos2", "id", event.AggregateID).WithError(err).Warn("something went wrong in passwordLockout policy handler")
return spooler.HandleError(event, err, p.view.GetLatestLockoutPolicyFailedEvent, p.view.ProcessedLockoutPolicyFailedEvent, p.view.ProcessedLockoutPolicySequence, p.errorCountUntilSkip)
}
func (p *LockoutPolicy) OnSuccess() error {
return spooler.HandleSuccess(p.view.UpdateLockoutPolicySpoolerRunTimestamp)
}

View File

@@ -109,6 +109,7 @@ func Start(conf Config, authZ authz.Config, systemDefaults sd.SystemDefaults, co
OrgViewProvider: view,
IDPProviderViewProvider: view,
LoginPolicyViewProvider: view,
LockoutPolicyViewProvider: view,
UserGrantProvider: view,
IdGenerator: idGenerator,
PasswordCheckLifeTime: systemDefaults.VerificationLifetimes.PasswordCheck.Duration,

View File

@@ -0,0 +1,53 @@
package view
import (
"github.com/caos/zitadel/internal/errors"
"github.com/caos/zitadel/internal/eventstore/v1/models"
"github.com/caos/zitadel/internal/iam/repository/view"
"github.com/caos/zitadel/internal/iam/repository/view/model"
global_view "github.com/caos/zitadel/internal/view/repository"
)
const (
passwordLockoutPolicyTable = "auth.lockout_policies"
)
func (v *View) LockoutPolicyByAggregateID(aggregateID string) (*model.LockoutPolicyView, error) {
return view.GetLockoutPolicyByAggregateID(v.Db, passwordLockoutPolicyTable, aggregateID)
}
func (v *View) PutLockoutPolicy(policy *model.LockoutPolicyView, event *models.Event) error {
err := view.PutLockoutPolicy(v.Db, passwordLockoutPolicyTable, policy)
if err != nil {
return err
}
return v.ProcessedLockoutPolicySequence(event)
}
func (v *View) DeleteLockoutPolicy(aggregateID string, event *models.Event) error {
err := view.DeleteLockoutPolicy(v.Db, passwordLockoutPolicyTable, aggregateID)
if err != nil && !errors.IsNotFound(err) {
return err
}
return v.ProcessedLockoutPolicySequence(event)
}
func (v *View) GetLatestLockoutPolicySequence() (*global_view.CurrentSequence, error) {
return v.latestSequence(passwordLockoutPolicyTable)
}
func (v *View) ProcessedLockoutPolicySequence(event *models.Event) error {
return v.saveCurrentSequence(passwordLockoutPolicyTable, event)
}
func (v *View) UpdateLockoutPolicySpoolerRunTimestamp() error {
return v.updateSpoolerRunSequence(passwordLockoutPolicyTable)
}
func (v *View) GetLatestLockoutPolicyFailedEvent(sequence uint64) (*global_view.FailedEvent, error) {
return v.latestFailedEvent(passwordLockoutPolicyTable, sequence)
}
func (v *View) ProcessedLockoutPolicyFailedEvent(failedEvent *global_view.FailedEvent) error {
return v.saveFailedEvent(failedEvent)
}