feat: Login verification lifetimes (#3190)

* feat: add login check lifetimes to login policy

* feat: org features test

* feat: read lifetimes from loginpolicy
This commit is contained in:
Fabi
2022-02-21 16:05:02 +01:00
committed by GitHub
parent 7d235e3eed
commit f05d4063bf
33 changed files with 1188 additions and 421 deletions

View File

@@ -14,19 +14,24 @@ import (
)
type LoginPolicy struct {
OrgID string
CreationDate time.Time
ChangeDate time.Time
Sequence uint64
AllowRegister bool
AllowUsernamePassword bool
AllowExternalIDPs bool
ForceMFA bool
SecondFactors []domain.SecondFactorType
MultiFactors []domain.MultiFactorType
PasswordlessType domain.PasswordlessType
IsDefault bool
HidePasswordReset bool
OrgID string
CreationDate time.Time
ChangeDate time.Time
Sequence uint64
AllowRegister bool
AllowUsernamePassword bool
AllowExternalIDPs bool
ForceMFA bool
SecondFactors []domain.SecondFactorType
MultiFactors []domain.MultiFactorType
PasswordlessType domain.PasswordlessType
IsDefault bool
HidePasswordReset bool
PasswordCheckLifetime time.Duration
ExternalLoginCheckLifetime time.Duration
MFAInitSkipLifetime time.Duration
SecondFactorCheckLifetime time.Duration
MultiFactorCheckLifetime time.Duration
}
type SecondFactors struct {
@@ -95,6 +100,26 @@ var (
name: projection.LoginPolicyHidePWResetCol,
table: loginPolicyTable,
}
LoginPolicyColumnPasswordCheckLifetime = Column{
name: projection.PasswordCheckLifetimeCol,
table: loginPolicyTable,
}
LoginPolicyColumnExternalLoginCheckLifetime = Column{
name: projection.ExternalLoginCheckLifetimeCol,
table: loginPolicyTable,
}
LoginPolicyColumnMFAInitSkipLifetime = Column{
name: projection.MFAInitSkipLifetimeCol,
table: loginPolicyTable,
}
LoginPolicyColumnSecondFactorCheckLifetime = Column{
name: projection.SecondFactorCheckLifetimeCol,
table: loginPolicyTable,
}
LoginPolicyColumnMultiFacotrCheckLifetime = Column{
name: projection.MultiFactorCheckLifetimeCol,
table: loginPolicyTable,
}
)
func (q *Queries) LoginPolicyByID(ctx context.Context, orgID string) (*LoginPolicy, error) {
@@ -234,6 +259,11 @@ func prepareLoginPolicyQuery() (sq.SelectBuilder, func(*sql.Row) (*LoginPolicy,
LoginPolicyColumnPasswordlessType.identifier(),
LoginPolicyColumnIsDefault.identifier(),
LoginPolicyColumnHidePasswordReset.identifier(),
LoginPolicyColumnPasswordCheckLifetime.identifier(),
LoginPolicyColumnExternalLoginCheckLifetime.identifier(),
LoginPolicyColumnMFAInitSkipLifetime.identifier(),
LoginPolicyColumnSecondFactorCheckLifetime.identifier(),
LoginPolicyColumnMultiFacotrCheckLifetime.identifier(),
).From(loginPolicyTable.identifier()).PlaceholderFormat(sq.Dollar),
func(row *sql.Row) (*LoginPolicy, error) {
p := new(LoginPolicy)
@@ -253,6 +283,11 @@ func prepareLoginPolicyQuery() (sq.SelectBuilder, func(*sql.Row) (*LoginPolicy,
&p.PasswordlessType,
&p.IsDefault,
&p.HidePasswordReset,
&p.PasswordCheckLifetime,
&p.ExternalLoginCheckLifetime,
&p.MFAInitSkipLifetime,
&p.SecondFactorCheckLifetime,
&p.MultiFactorCheckLifetime,
)
if err != nil {
if errs.Is(err, sql.ErrNoRows) {

View File

@@ -7,6 +7,7 @@ import (
"fmt"
"regexp"
"testing"
"time"
"github.com/caos/zitadel/internal/domain"
errs "github.com/caos/zitadel/internal/errors"
@@ -41,7 +42,12 @@ func Test_LoginPolicyPrepares(t *testing.T) {
` zitadel.projections.login_policies.multi_factors,`+
` zitadel.projections.login_policies.passwordless_type,`+
` zitadel.projections.login_policies.is_default,`+
` zitadel.projections.login_policies.hide_password_reset`+
` zitadel.projections.login_policies.hide_password_reset,`+
` zitadel.projections.login_policies.password_check_lifetime,`+
` zitadel.projections.login_policies.external_login_check_lifetime,`+
` zitadel.projections.login_policies.mfa_init_skip_lifetime,`+
` zitadel.projections.login_policies.second_factor_check_lifetime,`+
` zitadel.projections.login_policies.multi_factor_check_lifetime`+
` FROM zitadel.projections.login_policies`),
nil,
nil,
@@ -72,7 +78,12 @@ func Test_LoginPolicyPrepares(t *testing.T) {
` zitadel.projections.login_policies.multi_factors,`+
` zitadel.projections.login_policies.passwordless_type,`+
` zitadel.projections.login_policies.is_default,`+
` zitadel.projections.login_policies.hide_password_reset`+
` zitadel.projections.login_policies.hide_password_reset,`+
` zitadel.projections.login_policies.password_check_lifetime,`+
` zitadel.projections.login_policies.external_login_check_lifetime,`+
` zitadel.projections.login_policies.mfa_init_skip_lifetime,`+
` zitadel.projections.login_policies.second_factor_check_lifetime,`+
` zitadel.projections.login_policies.multi_factor_check_lifetime`+
` FROM zitadel.projections.login_policies`),
[]string{
"aggregate_id",
@@ -88,6 +99,11 @@ func Test_LoginPolicyPrepares(t *testing.T) {
"passwordless_type",
"is_default",
"hide_password_reset",
"password_check_lifetime",
"external_login_check_lifetime",
"mfa_init_skip_lifetime",
"second_factor_check_lifetime",
"multi_factor_check_lifetime",
},
[]driver.Value{
"ro",
@@ -103,23 +119,33 @@ func Test_LoginPolicyPrepares(t *testing.T) {
domain.PasswordlessTypeAllowed,
true,
true,
time.Hour * 2,
time.Hour * 2,
time.Hour * 2,
time.Hour * 2,
time.Hour * 2,
},
),
},
object: &LoginPolicy{
OrgID: "ro",
CreationDate: testNow,
ChangeDate: testNow,
Sequence: 20211109,
AllowRegister: true,
AllowUsernamePassword: true,
AllowExternalIDPs: true,
ForceMFA: true,
SecondFactors: []domain.SecondFactorType{domain.SecondFactorTypeOTP},
MultiFactors: []domain.MultiFactorType{domain.MultiFactorTypeU2FWithPIN},
PasswordlessType: domain.PasswordlessTypeAllowed,
IsDefault: true,
HidePasswordReset: true,
OrgID: "ro",
CreationDate: testNow,
ChangeDate: testNow,
Sequence: 20211109,
AllowRegister: true,
AllowUsernamePassword: true,
AllowExternalIDPs: true,
ForceMFA: true,
SecondFactors: []domain.SecondFactorType{domain.SecondFactorTypeOTP},
MultiFactors: []domain.MultiFactorType{domain.MultiFactorTypeU2FWithPIN},
PasswordlessType: domain.PasswordlessTypeAllowed,
IsDefault: true,
HidePasswordReset: true,
PasswordCheckLifetime: time.Hour * 2,
ExternalLoginCheckLifetime: time.Hour * 2,
MFAInitSkipLifetime: time.Hour * 2,
SecondFactorCheckLifetime: time.Hour * 2,
MultiFactorCheckLifetime: time.Hour * 2,
},
},
{
@@ -139,7 +165,12 @@ func Test_LoginPolicyPrepares(t *testing.T) {
` zitadel.projections.login_policies.multi_factors,`+
` zitadel.projections.login_policies.passwordless_type,`+
` zitadel.projections.login_policies.is_default,`+
` zitadel.projections.login_policies.hide_password_reset`+
` zitadel.projections.login_policies.hide_password_reset,`+
` zitadel.projections.login_policies.password_check_lifetime,`+
` zitadel.projections.login_policies.external_login_check_lifetime,`+
` zitadel.projections.login_policies.mfa_init_skip_lifetime,`+
` zitadel.projections.login_policies.second_factor_check_lifetime,`+
` zitadel.projections.login_policies.multi_factor_check_lifetime`+
` FROM zitadel.projections.login_policies`),
sql.ErrConnDone,
),

View File

@@ -111,6 +111,11 @@ const (
LoginPolicyPasswordlessTypeCol = "passwordless_type"
LoginPolicyIsDefaultCol = "is_default"
LoginPolicyHidePWResetCol = "hide_password_reset"
PasswordCheckLifetimeCol = "password_check_lifetime"
ExternalLoginCheckLifetimeCol = "external_login_check_lifetime"
MFAInitSkipLifetimeCol = "mfa_init_skip_lifetime"
SecondFactorCheckLifetimeCol = "second_factor_check_lifetime"
MultiFactorCheckLifetimeCol = "multi_factor_check_lifetime"
)
func (p *LoginPolicyProjection) reduceLoginPolicyAdded(event eventstore.Event) (*handler.Statement, error) {
@@ -140,6 +145,11 @@ func (p *LoginPolicyProjection) reduceLoginPolicyAdded(event eventstore.Event) (
handler.NewCol(LoginPolicyPasswordlessTypeCol, policyEvent.PasswordlessType),
handler.NewCol(LoginPolicyIsDefaultCol, isDefault),
handler.NewCol(LoginPolicyHidePWResetCol, policyEvent.HidePasswordReset),
handler.NewCol(PasswordCheckLifetimeCol, policyEvent.PasswordCheckLifetime),
handler.NewCol(ExternalLoginCheckLifetimeCol, policyEvent.ExternalLoginCheckLifetime),
handler.NewCol(MFAInitSkipLifetimeCol, policyEvent.MFAInitSkipLifetime),
handler.NewCol(SecondFactorCheckLifetimeCol, policyEvent.SecondFactorCheckLifetime),
handler.NewCol(MultiFactorCheckLifetimeCol, policyEvent.MultiFactorCheckLifetime),
}), nil
}
@@ -177,6 +187,22 @@ func (p *LoginPolicyProjection) reduceLoginPolicyChanged(event eventstore.Event)
if policyEvent.HidePasswordReset != nil {
cols = append(cols, handler.NewCol(LoginPolicyHidePWResetCol, *policyEvent.HidePasswordReset))
}
if policyEvent.PasswordCheckLifetime != nil {
cols = append(cols, handler.NewCol(PasswordCheckLifetimeCol, *policyEvent.PasswordCheckLifetime))
}
if policyEvent.ExternalLoginCheckLifetime != nil {
cols = append(cols, handler.NewCol(ExternalLoginCheckLifetimeCol, *policyEvent.ExternalLoginCheckLifetime))
}
if policyEvent.MFAInitSkipLifetime != nil {
cols = append(cols, handler.NewCol(MFAInitSkipLifetimeCol, *policyEvent.MFAInitSkipLifetime))
}
if policyEvent.SecondFactorCheckLifetime != nil {
cols = append(cols, handler.NewCol(SecondFactorCheckLifetimeCol, *policyEvent.SecondFactorCheckLifetime))
}
if policyEvent.MultiFactorCheckLifetime != nil {
cols = append(cols, handler.NewCol(MultiFactorCheckLifetimeCol, *policyEvent.MultiFactorCheckLifetime))
}
return crdb.NewUpdateStatement(
&policyEvent,
cols,

View File

@@ -2,6 +2,7 @@ package projection
import (
"testing"
"time"
"github.com/caos/zitadel/internal/domain"
"github.com/caos/zitadel/internal/errors"
@@ -29,13 +30,18 @@ func TestLoginPolicyProjection_reduces(t *testing.T) {
repository.EventType(org.LoginPolicyAddedEventType),
org.AggregateType,
[]byte(`{
"allowUsernamePassword": true,
"allowRegister": true,
"allowExternalIdp": false,
"forceMFA": false,
"hidePasswordReset": true,
"passwordlessType": 1
}`),
"allowUsernamePassword": true,
"allowRegister": true,
"allowExternalIdp": false,
"forceMFA": false,
"hidePasswordReset": true,
"passwordlessType": 1,
"passwordCheckLifetime": 10000000,
"externalLoginCheckLifetime": 10000000,
"mfaInitSkipLifetime": 10000000,
"secondFactorCheckLifetime": 10000000,
"multiFactorCheckLifetime": 10000000
}`),
), org.LoginPolicyAddedEventMapper),
},
reduce: (&LoginPolicyProjection{}).reduceLoginPolicyAdded,
@@ -47,7 +53,7 @@ func TestLoginPolicyProjection_reduces(t *testing.T) {
executer: &testExecuter{
executions: []execution{
{
expectedStmt: "INSERT INTO zitadel.projections.login_policies (aggregate_id, creation_date, change_date, sequence, allow_register, allow_username_password, allow_external_idps, force_mfa, passwordless_type, is_default, hide_password_reset) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11)",
expectedStmt: "INSERT INTO zitadel.projections.login_policies (aggregate_id, creation_date, change_date, sequence, allow_register, allow_username_password, allow_external_idps, force_mfa, passwordless_type, is_default, hide_password_reset, password_check_lifetime, external_login_check_lifetime, mfa_init_skip_lifetime, second_factor_check_lifetime, multi_factor_check_lifetime) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16)",
expectedArgs: []interface{}{
"agg-id",
anyArg{},
@@ -60,6 +66,11 @@ func TestLoginPolicyProjection_reduces(t *testing.T) {
domain.PasswordlessTypeAllowed,
false,
true,
time.Millisecond * 10,
time.Millisecond * 10,
time.Millisecond * 10,
time.Millisecond * 10,
time.Millisecond * 10,
},
},
},
@@ -74,13 +85,18 @@ func TestLoginPolicyProjection_reduces(t *testing.T) {
repository.EventType(org.LoginPolicyChangedEventType),
org.AggregateType,
[]byte(`{
"allowUsernamePassword": true,
"allowRegister": true,
"allowExternalIdp": true,
"forceMFA": true,
"hidePasswordReset": true,
"passwordlessType": 1
}`),
"allowUsernamePassword": true,
"allowRegister": true,
"allowExternalIdp": true,
"forceMFA": true,
"hidePasswordReset": true,
"passwordlessType": 1,
"passwordCheckLifetime": 10000000,
"externalLoginCheckLifetime": 10000000,
"mfaInitSkipLifetime": 10000000,
"secondFactorCheckLifetime": 10000000,
"multiFactorCheckLifetime": 10000000
}`),
), org.LoginPolicyChangedEventMapper),
},
want: wantReduce{
@@ -91,7 +107,7 @@ func TestLoginPolicyProjection_reduces(t *testing.T) {
executer: &testExecuter{
executions: []execution{
{
expectedStmt: "UPDATE zitadel.projections.login_policies SET (change_date, sequence, allow_register, allow_username_password, allow_external_idps, force_mfa, passwordless_type, hide_password_reset) = ($1, $2, $3, $4, $5, $6, $7, $8) WHERE (aggregate_id = $9)",
expectedStmt: "UPDATE zitadel.projections.login_policies SET (change_date, sequence, allow_register, allow_username_password, allow_external_idps, force_mfa, passwordless_type, hide_password_reset, password_check_lifetime, external_login_check_lifetime, mfa_init_skip_lifetime, second_factor_check_lifetime, multi_factor_check_lifetime) = ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13) WHERE (aggregate_id = $14)",
expectedArgs: []interface{}{
anyArg{},
uint64(15),
@@ -101,6 +117,11 @@ func TestLoginPolicyProjection_reduces(t *testing.T) {
true,
domain.PasswordlessTypeAllowed,
true,
time.Millisecond * 10,
time.Millisecond * 10,
time.Millisecond * 10,
time.Millisecond * 10,
time.Millisecond * 10,
"agg-id",
},
},
@@ -271,12 +292,17 @@ func TestLoginPolicyProjection_reduces(t *testing.T) {
repository.EventType(iam.LoginPolicyAddedEventType),
iam.AggregateType,
[]byte(`{
"allowUsernamePassword": true,
"allowRegister": true,
"allowExternalIdp": false,
"forceMFA": false,
"hidePasswordReset": true,
"passwordlessType": 1
"allowUsernamePassword": true,
"allowRegister": true,
"allowExternalIdp": false,
"forceMFA": false,
"hidePasswordReset": true,
"passwordlessType": 1,
"passwordCheckLifetime": 10000000,
"externalLoginCheckLifetime": 10000000,
"mfaInitSkipLifetime": 10000000,
"secondFactorCheckLifetime": 10000000,
"multiFactorCheckLifetime": 10000000
}`),
), iam.LoginPolicyAddedEventMapper),
},
@@ -288,7 +314,7 @@ func TestLoginPolicyProjection_reduces(t *testing.T) {
executer: &testExecuter{
executions: []execution{
{
expectedStmt: "INSERT INTO zitadel.projections.login_policies (aggregate_id, creation_date, change_date, sequence, allow_register, allow_username_password, allow_external_idps, force_mfa, passwordless_type, is_default, hide_password_reset) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11)",
expectedStmt: "INSERT INTO zitadel.projections.login_policies (aggregate_id, creation_date, change_date, sequence, allow_register, allow_username_password, allow_external_idps, force_mfa, passwordless_type, is_default, hide_password_reset, password_check_lifetime, external_login_check_lifetime, mfa_init_skip_lifetime, second_factor_check_lifetime, multi_factor_check_lifetime) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16)",
expectedArgs: []interface{}{
"agg-id",
anyArg{},
@@ -301,6 +327,11 @@ func TestLoginPolicyProjection_reduces(t *testing.T) {
domain.PasswordlessTypeAllowed,
true,
true,
time.Millisecond * 10,
time.Millisecond * 10,
time.Millisecond * 10,
time.Millisecond * 10,
time.Millisecond * 10,
},
},
},