feat: Policy check (#149)

* check password complexity policy

* check password complexity policy

* fix tests

* Update internal/admin/repository/eventsourcing/setup/setup.go

Co-authored-by: Livio Amstutz <livio.a@gmail.com>

* changes for mr

Co-authored-by: Livio Amstutz <livio.a@gmail.com>
This commit is contained in:
Fabi
2020-05-29 08:44:01 +02:00
committed by GitHub
parent 5a7d44327e
commit a4c7b39552
15 changed files with 477 additions and 61 deletions

View File

@@ -3,6 +3,8 @@ package eventsourcing
import (
"context"
"github.com/caos/zitadel/internal/id"
policy_model "github.com/caos/zitadel/internal/policy/model"
"github.com/pquerna/otp/totp"
req_model "github.com/caos/zitadel/internal/auth_request/model"
@@ -87,20 +89,19 @@ func (es *UserEventstore) UserByID(ctx context.Context, id string) (*usr_model.U
return model.UserToModel(user), nil
}
func (es *UserEventstore) PrepareCreateUser(ctx context.Context, user *usr_model.User, resourceOwner string) (*model.User, []*es_models.Aggregate, error) {
func (es *UserEventstore) PrepareCreateUser(ctx context.Context, user *usr_model.User, policy *policy_model.PasswordComplexityPolicy, resourceOwner string) (*model.User, []*es_models.Aggregate, error) {
user.SetEmailAsUsername()
if !user.IsValid() {
return nil, nil, caos_errs.ThrowPreconditionFailed(nil, "EVENT-9dk45", "User is invalid")
}
//TODO: Check Uniqueness
id, err := es.idGenerator.Next()
if err != nil {
return nil, nil, err
}
user.AggregateID = id
err = user.HashPasswordIfExisting(es.PasswordAlg, true)
err = user.HashPasswordIfExisting(policy, es.PasswordAlg, true)
if err != nil {
return nil, nil, err
}
@@ -122,8 +123,8 @@ func (es *UserEventstore) PrepareCreateUser(ctx context.Context, user *usr_model
return repoUser, createAggregates, err
}
func (es *UserEventstore) CreateUser(ctx context.Context, user *usr_model.User) (*usr_model.User, error) {
repoUser, aggregates, err := es.PrepareCreateUser(ctx, user, "")
func (es *UserEventstore) CreateUser(ctx context.Context, user *usr_model.User, policy *policy_model.PasswordComplexityPolicy) (*usr_model.User, error) {
repoUser, aggregates, err := es.PrepareCreateUser(ctx, user, policy, "")
if err != nil {
return nil, err
}
@@ -137,19 +138,18 @@ func (es *UserEventstore) CreateUser(ctx context.Context, user *usr_model.User)
return model.UserToModel(repoUser), nil
}
func (es *UserEventstore) PrepareRegisterUser(ctx context.Context, user *usr_model.User, resourceOwner string) (*model.User, []*es_models.Aggregate, error) {
func (es *UserEventstore) PrepareRegisterUser(ctx context.Context, user *usr_model.User, policy *policy_model.PasswordComplexityPolicy, resourceOwner string) (*model.User, []*es_models.Aggregate, error) {
user.SetEmailAsUsername()
if !user.IsValid() || user.Password == nil || user.SecretString == "" {
return nil, nil, caos_errs.ThrowPreconditionFailed(nil, "EVENT-9dk45", "user is invalid")
}
//TODO: Check Uniqueness
id, err := es.idGenerator.Next()
if err != nil {
return nil, nil, err
}
user.AggregateID = id
err = user.HashPasswordIfExisting(es.PasswordAlg, false)
err = user.HashPasswordIfExisting(policy, es.PasswordAlg, false)
if err != nil {
return nil, nil, err
}
@@ -165,8 +165,8 @@ func (es *UserEventstore) PrepareRegisterUser(ctx context.Context, user *usr_mod
return repoUser, aggregates, err
}
func (es *UserEventstore) RegisterUser(ctx context.Context, user *usr_model.User, resourceOwner string) (*usr_model.User, error) {
repoUser, createAggregates, err := es.PrepareRegisterUser(ctx, user, resourceOwner)
func (es *UserEventstore) RegisterUser(ctx context.Context, user *usr_model.User, policy *policy_model.PasswordComplexityPolicy, resourceOwner string) (*usr_model.User, error) {
repoUser, createAggregates, err := es.PrepareRegisterUser(ctx, user, policy, resourceOwner)
if err != nil {
return nil, err
}
@@ -380,15 +380,15 @@ func (es *UserEventstore) setPasswordCheckResult(ctx context.Context, user *usr_
return nil
}
func (es *UserEventstore) SetOneTimePassword(ctx context.Context, password *usr_model.Password) (*usr_model.Password, error) {
func (es *UserEventstore) SetOneTimePassword(ctx context.Context, policy *policy_model.PasswordComplexityPolicy, password *usr_model.Password) (*usr_model.Password, error) {
user, err := es.UserByID(ctx, password.AggregateID)
if err != nil {
return nil, err
}
return es.changedPassword(ctx, user, password.SecretString, true)
return es.changedPassword(ctx, user, policy, password.SecretString, true)
}
func (es *UserEventstore) SetPassword(ctx context.Context, userID, code, password string) error {
func (es *UserEventstore) SetPassword(ctx context.Context, policy *policy_model.PasswordComplexityPolicy, userID, code, password string) error {
user, err := es.UserByID(ctx, userID)
if err != nil {
return err
@@ -399,11 +399,11 @@ func (es *UserEventstore) SetPassword(ctx context.Context, userID, code, passwor
if err := crypto.VerifyCode(user.PasswordCode.CreationDate, user.PasswordCode.Expiry, user.PasswordCode.Code, code, es.PasswordVerificationCode); err != nil {
return caos_errs.ThrowPreconditionFailed(err, "EVENT-sd6DF", "code invalid")
}
_, err = es.changedPassword(ctx, user, password, false)
_, err = es.changedPassword(ctx, user, policy, password, false)
return err
}
func (es *UserEventstore) ChangePassword(ctx context.Context, userID, old, new string) (*usr_model.Password, error) {
func (es *UserEventstore) ChangePassword(ctx context.Context, policy *policy_model.PasswordComplexityPolicy, userID, old, new string) (*usr_model.Password, error) {
user, err := es.UserByID(ctx, userID)
if err != nil {
return nil, err
@@ -414,16 +414,16 @@ func (es *UserEventstore) ChangePassword(ctx context.Context, userID, old, new s
if err := crypto.CompareHash(user.Password.SecretCrypto, []byte(old), es.PasswordAlg); err != nil {
return nil, caos_errs.ThrowInvalidArgument(nil, "EVENT-s56a3", "invalid password")
}
return es.changedPassword(ctx, user, new, false)
return es.changedPassword(ctx, user, policy, new, false)
}
func (es *UserEventstore) changedPassword(ctx context.Context, user *usr_model.User, password string, onetime bool) (*usr_model.Password, error) {
//TODO: check password policy
secret, err := crypto.Hash([]byte(password), es.PasswordAlg)
func (es *UserEventstore) changedPassword(ctx context.Context, user *usr_model.User, policy *policy_model.PasswordComplexityPolicy, password string, onetime bool) (*usr_model.Password, error) {
pw := &usr_model.Password{SecretString: password}
err := pw.HashPasswordIfExisting(policy, es.PasswordAlg, onetime)
if err != nil {
return nil, err
}
repoPassword := &model.Password{Secret: secret, ChangeRequired: onetime}
repoPassword := model.PasswordFromModel(pw)
repoUser := model.UserFromModel(user)
agg := PasswordChangeAggregate(es.AggregateCreator(), repoUser, repoPassword)
err = es_sdk.Push(ctx, es.PushAggregates, repoUser.AppendEvents, agg)

View File

@@ -2,6 +2,7 @@ package eventsourcing
import (
"context"
policy_model "github.com/caos/zitadel/internal/policy/model"
"net"
"testing"
"time"
@@ -83,9 +84,10 @@ func TestUserByID(t *testing.T) {
func TestCreateUser(t *testing.T) {
ctrl := gomock.NewController(t)
type args struct {
es *UserEventstore
ctx context.Context
user *model.User
es *UserEventstore
ctx context.Context
user *model.User
policy *policy_model.PasswordComplexityPolicy
}
type res struct {
user *model.User
@@ -113,6 +115,7 @@ func TestCreateUser(t *testing.T) {
IsEmailVerified: true,
},
},
policy: &policy_model.PasswordComplexityPolicy{},
},
res: res{
user: &model.User{ObjectRoot: es_models.ObjectRoot{Sequence: 1},
@@ -143,6 +146,7 @@ func TestCreateUser(t *testing.T) {
IsEmailVerified: true,
},
},
policy: &policy_model.PasswordComplexityPolicy{},
},
res: res{
user: &model.User{ObjectRoot: es_models.ObjectRoot{Sequence: 1},
@@ -178,6 +182,7 @@ func TestCreateUser(t *testing.T) {
IsPhoneVerified: true,
},
},
policy: &policy_model.PasswordComplexityPolicy{},
},
res: res{
user: &model.User{ObjectRoot: es_models.ObjectRoot{Sequence: 1},
@@ -214,6 +219,7 @@ func TestCreateUser(t *testing.T) {
IsEmailVerified: true,
},
},
policy: &policy_model.PasswordComplexityPolicy{},
},
res: res{
user: &model.User{ObjectRoot: es_models.ObjectRoot{Sequence: 1},
@@ -231,6 +237,18 @@ func TestCreateUser(t *testing.T) {
},
{
name: "create user invalid",
args: args{
es: GetMockManipulateUser(ctrl),
ctx: auth.NewMockContext("orgID", "userID"),
user: &model.User{ObjectRoot: es_models.ObjectRoot{AggregateID: "AggregateID", Sequence: 1}},
policy: &policy_model.PasswordComplexityPolicy{},
},
res: res{
errFunc: caos_errs.IsPreconditionFailed,
},
},
{
name: "create user policy nil",
args: args{
es: GetMockManipulateUser(ctrl),
ctx: auth.NewMockContext("orgID", "userID"),
@@ -243,7 +261,7 @@ func TestCreateUser(t *testing.T) {
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result, err := tt.args.es.CreateUser(tt.args.ctx, tt.args.user)
result, err := tt.args.es.CreateUser(tt.args.ctx, tt.args.user, tt.args.policy)
if tt.res.errFunc == nil && result.AggregateID == "" {
t.Errorf("result has no id")
@@ -275,6 +293,7 @@ func TestRegisterUser(t *testing.T) {
ctx context.Context
user *model.User
resourceOwner string
policy *policy_model.PasswordComplexityPolicy
}
type res struct {
user *model.User
@@ -304,6 +323,7 @@ func TestRegisterUser(t *testing.T) {
SecretString: "Password",
},
},
policy: &policy_model.PasswordComplexityPolicy{},
resourceOwner: "ResourceOwner",
},
res: res{
@@ -336,6 +356,7 @@ func TestRegisterUser(t *testing.T) {
SecretString: "Password",
},
},
policy: &policy_model.PasswordComplexityPolicy{},
resourceOwner: "ResourceOwner",
},
res: res{
@@ -357,6 +378,7 @@ func TestRegisterUser(t *testing.T) {
es: GetMockManipulateUser(ctrl),
ctx: auth.NewMockContext("orgID", "userID"),
user: &model.User{ObjectRoot: es_models.ObjectRoot{Sequence: 1}},
policy: &policy_model.PasswordComplexityPolicy{},
resourceOwner: "ResourceOwner",
},
res: res{
@@ -378,6 +400,7 @@ func TestRegisterUser(t *testing.T) {
EmailAddress: "EmailAddress",
},
},
policy: &policy_model.PasswordComplexityPolicy{},
resourceOwner: "ResourceOwner",
},
res: res{
@@ -386,6 +409,27 @@ func TestRegisterUser(t *testing.T) {
},
{
name: "no resourceowner",
args: args{
es: GetMockManipulateUser(ctrl),
ctx: auth.NewMockContext("orgID", "userID"),
user: &model.User{ObjectRoot: es_models.ObjectRoot{Sequence: 1},
Profile: &model.Profile{
UserName: "EmailAddress",
FirstName: "FirstName",
LastName: "LastName",
},
Email: &model.Email{
EmailAddress: "EmailAddress",
},
},
policy: &policy_model.PasswordComplexityPolicy{},
},
res: res{
errFunc: caos_errs.IsPreconditionFailed,
},
},
{
name: "no policy",
args: args{
es: GetMockManipulateUser(ctrl),
ctx: auth.NewMockContext("orgID", "userID"),
@@ -407,7 +451,7 @@ func TestRegisterUser(t *testing.T) {
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result, err := tt.args.es.RegisterUser(tt.args.ctx, tt.args.user, tt.args.resourceOwner)
result, err := tt.args.es.RegisterUser(tt.args.ctx, tt.args.user, tt.args.policy, tt.args.resourceOwner)
if tt.res.errFunc == nil && result.AggregateID == "" {
t.Errorf("result has no id")
@@ -1029,6 +1073,7 @@ func TestSetOneTimePassword(t *testing.T) {
type args struct {
es *UserEventstore
ctx context.Context
policy *policy_model.PasswordComplexityPolicy
password *model.Password
}
type res struct {
@@ -1045,6 +1090,7 @@ func TestSetOneTimePassword(t *testing.T) {
args: args{
es: GetMockManipulateUserWithPasswordCodeGen(ctrl, repo_model.User{ObjectRoot: es_models.ObjectRoot{AggregateID: "AggregateID"}}),
ctx: auth.NewMockContext("orgID", "userID"),
policy: &policy_model.PasswordComplexityPolicy{},
password: &model.Password{ObjectRoot: es_models.ObjectRoot{AggregateID: "AggregateID"}, SecretString: "Password"},
},
res: res{
@@ -1056,6 +1102,7 @@ func TestSetOneTimePassword(t *testing.T) {
args: args{
es: GetMockManipulateUser(ctrl),
ctx: auth.NewMockContext("orgID", "userID"),
policy: &policy_model.PasswordComplexityPolicy{},
password: &model.Password{ObjectRoot: es_models.ObjectRoot{AggregateID: ""}, SecretString: "Password"},
},
res: res{
@@ -1067,6 +1114,7 @@ func TestSetOneTimePassword(t *testing.T) {
args: args{
es: GetMockManipulateUserNoEvents(ctrl),
ctx: auth.NewMockContext("orgID", "userID"),
policy: &policy_model.PasswordComplexityPolicy{},
password: &model.Password{ObjectRoot: es_models.ObjectRoot{AggregateID: "AggregateID"}, SecretString: "Password"},
},
res: res{
@@ -1076,7 +1124,7 @@ func TestSetOneTimePassword(t *testing.T) {
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result, err := tt.args.es.SetOneTimePassword(tt.args.ctx, tt.args.password)
result, err := tt.args.es.SetOneTimePassword(tt.args.ctx, tt.args.policy, tt.args.password)
if tt.res.errFunc == nil && result.AggregateID == "" {
t.Errorf("result has no id")
@@ -1226,6 +1274,7 @@ func TestSetPassword(t *testing.T) {
type args struct {
es *UserEventstore
ctx context.Context
policy *policy_model.PasswordComplexityPolicy
userID string
code string
password string
@@ -1253,6 +1302,7 @@ func TestSetPassword(t *testing.T) {
},
),
ctx: auth.NewMockContext("orgID", "userID"),
policy: &policy_model.PasswordComplexityPolicy{},
userID: "userID",
code: "code",
password: "password",
@@ -1264,6 +1314,7 @@ func TestSetPassword(t *testing.T) {
args: args{
es: GetMockManipulateUser(ctrl),
ctx: auth.NewMockContext("orgID", "userID"),
policy: &policy_model.PasswordComplexityPolicy{},
userID: "",
code: "code",
password: "password",
@@ -1277,6 +1328,7 @@ func TestSetPassword(t *testing.T) {
args: args{
es: GetMockManipulateUserNoEvents(ctrl),
ctx: auth.NewMockContext("orgID", "userID"),
policy: &policy_model.PasswordComplexityPolicy{},
userID: "userID",
code: "code",
password: "password",
@@ -1294,6 +1346,7 @@ func TestSetPassword(t *testing.T) {
},
),
ctx: auth.NewMockContext("orgID", "userID"),
policy: &policy_model.PasswordComplexityPolicy{},
userID: "userID",
code: "code",
password: "password",
@@ -1317,6 +1370,7 @@ func TestSetPassword(t *testing.T) {
},
),
ctx: auth.NewMockContext("orgID", "userID"),
policy: &policy_model.PasswordComplexityPolicy{},
userID: "userID",
code: "code",
password: "password",
@@ -1328,7 +1382,7 @@ func TestSetPassword(t *testing.T) {
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := tt.args.es.SetPassword(tt.args.ctx, tt.args.userID, tt.args.code, tt.args.password)
err := tt.args.es.SetPassword(tt.args.ctx, tt.args.policy, tt.args.userID, tt.args.code, tt.args.password)
if tt.res.errFunc == nil && err != nil {
t.Errorf("result has error: %v", err)
@@ -1345,6 +1399,7 @@ func TestChangePassword(t *testing.T) {
type args struct {
es *UserEventstore
ctx context.Context
policy *policy_model.PasswordComplexityPolicy
userID string
old string
new string
@@ -1372,6 +1427,7 @@ func TestChangePassword(t *testing.T) {
},
),
ctx: auth.NewMockContext("orgID", "userID"),
policy: &policy_model.PasswordComplexityPolicy{},
userID: "userID",
old: "old",
new: "new",
@@ -1385,6 +1441,7 @@ func TestChangePassword(t *testing.T) {
args: args{
es: GetMockManipulateUser(ctrl),
ctx: auth.NewMockContext("orgID", "userID"),
policy: &policy_model.PasswordComplexityPolicy{},
userID: "",
old: "old",
new: "new",
@@ -1398,6 +1455,7 @@ func TestChangePassword(t *testing.T) {
args: args{
es: GetMockManipulateUserNoEvents(ctrl),
ctx: auth.NewMockContext("orgID", "userID"),
policy: &policy_model.PasswordComplexityPolicy{},
userID: "userID",
old: "old",
new: "new",
@@ -1415,6 +1473,7 @@ func TestChangePassword(t *testing.T) {
},
),
ctx: auth.NewMockContext("orgID", "userID"),
policy: &policy_model.PasswordComplexityPolicy{},
userID: "userID",
old: "old",
new: "new",
@@ -1437,6 +1496,7 @@ func TestChangePassword(t *testing.T) {
},
),
ctx: auth.NewMockContext("orgID", "userID"),
policy: &policy_model.PasswordComplexityPolicy{},
userID: "userID",
old: "old",
new: "new",
@@ -1445,10 +1505,32 @@ func TestChangePassword(t *testing.T) {
errFunc: caos_errs.IsErrorInvalidArgument,
},
},
{
name: "no policy",
args: args{
es: GetMockManipulateUserWithPasswordAndEmailCodeGen(ctrl,
repo_model.User{
ObjectRoot: es_models.ObjectRoot{AggregateID: "AggregateID"},
Password: &repo_model.Password{Secret: &crypto.CryptoValue{
CryptoType: crypto.TypeHash,
Algorithm: "hash",
Crypted: []byte("old"),
}},
},
),
ctx: auth.NewMockContext("orgID", "userID"),
userID: "userID",
old: "old",
new: "new",
},
res: res{
errFunc: caos_errs.IsPreconditionFailed,
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result, err := tt.args.es.ChangePassword(tt.args.ctx, tt.args.userID, tt.args.old, tt.args.new)
result, err := tt.args.es.ChangePassword(tt.args.ctx, tt.args.policy, tt.args.userID, tt.args.old, tt.args.new)
if tt.res.errFunc == nil && result.AggregateID == "" {
t.Errorf("result has no id")