feat: integrate passwap for human user password hashing (#6196)

* feat: use passwap for human user passwords

* fix tests

* passwap config

* add the event mapper

* cleanup query side and api

* solve linting errors

* regression test

* try to fix linter errors again

* pass systemdefaults into externalConfigChange migration

* fix: user password set in auth view

* pin passwap v0.2.0

* v2: validate hashed password hash based on prefix

* resolve remaining comments

* add error tag and translation for unsupported hash encoding

* fix unit test

---------

Co-authored-by: Livio Spring <livio.a@gmail.com>
This commit is contained in:
Tim Möhlmann
2023-07-14 09:49:57 +03:00
committed by GitHub
parent 6fcfa63f54
commit 4589ddad4a
56 changed files with 1853 additions and 775 deletions

View File

@@ -49,7 +49,8 @@ type Commands struct {
smtpEncryption crypto.EncryptionAlgorithm
smsEncryption crypto.EncryptionAlgorithm
userEncryption crypto.EncryptionAlgorithm
userPasswordAlg crypto.HashAlgorithm
userPasswordHasher *crypto.PasswordHasher
codeAlg crypto.HashAlgorithm
machineKeySize int
applicationKeySize int
domainVerificationAlg crypto.EncryptionAlgorithm
@@ -140,7 +141,11 @@ func StartCommands(
oidcsession.RegisterEventMappers(repo.eventstore)
milestone.RegisterEventMappers(repo.eventstore)
repo.userPasswordAlg = crypto.NewBCrypt(defaults.SecretGenerators.PasswordSaltCost)
repo.codeAlg = crypto.NewBCrypt(defaults.SecretGenerators.PasswordSaltCost)
repo.userPasswordHasher, err = defaults.PasswordHasher.PasswordHasher()
if err != nil {
return nil, err
}
repo.machineKeySize = int(defaults.SecretGenerators.MachineKeySize)
repo.applicationKeySize = int(defaults.SecretGenerators.ApplicationKeySize)

View File

@@ -335,7 +335,7 @@ func (c *Commands) SetUpInstance(ctx context.Context, setup *InstanceSetup) (str
} else if setup.Org.Human != nil {
setup.Org.Human.ID = userID
validations = append(validations,
c.AddHumanCommand(setup.Org.Human, orgID, c.userPasswordAlg, c.userEncryption, true),
c.AddHumanCommand(setup.Org.Human, orgID, c.userPasswordHasher, c.userEncryption, true),
)
}

View File

@@ -3,10 +3,13 @@ package command
import (
"context"
"database/sql"
"strings"
"testing"
"time"
"github.com/golang/mock/gomock"
"github.com/zitadel/passwap"
"github.com/zitadel/passwap/verifier"
"golang.org/x/text/language"
"github.com/zitadel/zitadel/internal/crypto"
@@ -293,3 +296,38 @@ func newMockPermissionCheckNotAllowed() domain.PermissionCheck {
return errors.ThrowPermissionDenied(nil, "AUTHZ-HKJD33", "Errors.PermissionDenied")
}
}
type plainHasher struct {
x string // arbitrary info that triggers update when different from encoding
}
func (h plainHasher) Hash(password string) (string, error) {
return strings.Join([]string{"", "plain", h.x, password}, "$"), nil
}
func (h plainHasher) Verify(encoded, password string) (verifier.Result, error) {
nodes := strings.Split(encoded, "$")
if len(nodes) != 4 || nodes[1] != "plain" {
return verifier.Skip, nil
}
if nodes[3] != password {
return verifier.Fail, nil
}
if nodes[2] != h.x {
return verifier.NeedUpdate, nil
}
return verifier.OK, nil
}
// mockPasswordHasher creates a swapper for plain (cleartext) password used in tests.
// x can be set to arbitrary info which triggers updates when different from the
// setting in the encoded hashes. (normally cost parameters)
//
// With `x` set to "foo", the following encoded string would be produced by Hash:
// $plain$foo$password
func mockPasswordHasher(x string) *crypto.PasswordHasher {
return &crypto.PasswordHasher{
Swapper: passwap.NewSwapper(plainHasher{x: x}),
Prefixes: []string{"$plain$"},
}
}

View File

@@ -42,7 +42,7 @@ func (c *Commands) setUpOrgWithIDs(ctx context.Context, o *OrgSetup, orgID strin
var pat *PersonalAccessToken
if o.Human != nil {
o.Human.ID = userID
validations = append(validations, c.AddHumanCommand(o.Human, orgID, c.userPasswordAlg, c.userEncryption, true))
validations = append(validations, c.AddHumanCommand(o.Human, orgID, c.userPasswordHasher, c.userEncryption, true))
} else if o.Machine != nil {
validations = append(validations, AddMachineCommand(userAgg, o.Machine.Machine))
if o.Machine.Pat != nil {

View File

@@ -244,7 +244,7 @@ func (c *Commands) VerifyAPIClientSecret(ctx context.Context, projectID, appID,
projectAgg := ProjectAggregateFromWriteModel(&app.WriteModel)
ctx, spanPasswordComparison := tracing.NewNamedSpan(ctx, "crypto.CompareHash")
err = crypto.CompareHash(app.ClientSecret, []byte(secret), c.userPasswordAlg)
err = crypto.CompareHash(app.ClientSecret, []byte(secret), c.codeAlg)
spanPasswordComparison.EndWithError(err)
if err == nil {
_, err = c.eventstore.Push(ctx, project_repo.NewAPIConfigSecretCheckSucceededEvent(ctx, projectAgg, app.AppID))

View File

@@ -326,7 +326,7 @@ func (c *Commands) VerifyOIDCClientSecret(ctx context.Context, projectID, appID,
projectAgg := ProjectAggregateFromWriteModel(&app.WriteModel)
ctx, spanPasswordComparison := tracing.NewNamedSpan(ctx, "crypto.CompareHash")
err = crypto.CompareHash(app.ClientSecret, []byte(secret), c.userPasswordAlg)
err = crypto.CompareHash(app.ClientSecret, []byte(secret), c.codeAlg)
spanPasswordComparison.EndWithError(err)
if err == nil {
_, err = c.eventstore.Push(ctx, project_repo.NewOIDCConfigSecretCheckSucceededEvent(ctx, projectAgg, app.AppID))

View File

@@ -1,6 +1,7 @@
package command
import (
"bytes"
"context"
"encoding/base64"
"fmt"
@@ -13,30 +14,34 @@ import (
"github.com/zitadel/zitadel/internal/eventstore"
"github.com/zitadel/zitadel/internal/id"
"github.com/zitadel/zitadel/internal/repository/session"
"github.com/zitadel/zitadel/internal/repository/user"
usr_repo "github.com/zitadel/zitadel/internal/repository/user"
"github.com/zitadel/zitadel/internal/telemetry/tracing"
)
type SessionCommand func(ctx context.Context, cmd *SessionCommands) error
type SessionCommands struct {
cmds []SessionCommand
sessionCommands []SessionCommand
sessionWriteModel *SessionWriteModel
passwordWriteModel *HumanPasswordWriteModel
intentWriteModel *IDPIntentWriteModel
eventstore *eventstore.Eventstore
userPasswordAlg crypto.HashAlgorithm
intentAlg crypto.EncryptionAlgorithm
createToken func(sessionID string) (id string, token string, err error)
now func() time.Time
eventCommands []eventstore.Command
hasher *crypto.PasswordHasher
intentAlg crypto.EncryptionAlgorithm
createToken func(sessionID string) (id string, token string, err error)
now func() time.Time
}
func (c *Commands) NewSessionCommands(cmds []SessionCommand, session *SessionWriteModel) *SessionCommands {
return &SessionCommands{
cmds: cmds,
sessionCommands: cmds,
sessionWriteModel: session,
eventstore: c.eventstore,
userPasswordAlg: c.userPasswordAlg,
hasher: c.userPasswordHasher,
intentAlg: c.idpConfigEncryption,
createToken: c.sessionTokenCreator,
now: time.Now,
@@ -49,7 +54,7 @@ func CheckUser(id string) SessionCommand {
if cmd.sessionWriteModel.UserID != "" && id != "" && cmd.sessionWriteModel.UserID != id {
return caos_errs.ThrowInvalidArgument(nil, "", "user change not possible")
}
return cmd.sessionWriteModel.UserChecked(ctx, id, cmd.now())
return cmd.UserChecked(ctx, id, cmd.now())
}
}
@@ -68,17 +73,21 @@ func CheckPassword(password string) SessionCommand {
return caos_errs.ThrowPreconditionFailed(nil, "COMMAND-Df4b3", "Errors.User.NotFound")
}
if cmd.passwordWriteModel.Secret == nil {
if cmd.passwordWriteModel.EncodedHash == "" {
return caos_errs.ThrowPreconditionFailed(nil, "COMMAND-WEf3t", "Errors.User.Password.NotSet")
}
ctx, spanPasswordComparison := tracing.NewNamedSpan(ctx, "crypto.CompareHash")
err = crypto.CompareHash(cmd.passwordWriteModel.Secret, []byte(password), cmd.userPasswordAlg)
ctx, spanPasswordComparison := tracing.NewNamedSpan(ctx, "passwap.Verify")
updated, err := cmd.hasher.Verify(cmd.passwordWriteModel.EncodedHash, password)
spanPasswordComparison.EndWithError(err)
if err != nil {
//TODO: maybe we want to reset the session in the future https://github.com/zitadel/zitadel/issues/5807
return caos_errs.ThrowInvalidArgument(err, "COMMAND-SAF3g", "Errors.User.Password.Invalid")
}
cmd.sessionWriteModel.PasswordChecked(ctx, cmd.now())
if updated != "" {
cmd.eventCommands = append(cmd.eventCommands, user.NewHumanPasswordHashUpdatedEvent(ctx, UserAggregateFromWriteModel(&cmd.passwordWriteModel.WriteModel), updated))
}
cmd.PasswordChecked(ctx, cmd.now())
return nil
}
}
@@ -114,14 +123,14 @@ func CheckIntent(intentID, token string) SessionCommand {
return caos_errs.ThrowPreconditionFailed(nil, "COMMAND-O8xk3w", "Errors.Intent.OtherUser")
}
}
cmd.sessionWriteModel.IntentChecked(ctx, cmd.now())
cmd.IntentChecked(ctx, cmd.now())
return nil
}
}
// Exec will execute the commands specified and returns an error on the first occurrence
func (s *SessionCommands) Exec(ctx context.Context) error {
for _, cmd := range s.cmds {
for _, cmd := range s.sessionCommands {
if err := cmd(ctx, s); err != nil {
return err
}
@@ -129,6 +138,66 @@ func (s *SessionCommands) Exec(ctx context.Context) error {
return nil
}
func (s *SessionCommands) Start(ctx context.Context, domain string) {
s.eventCommands = append(s.eventCommands, session.NewAddedEvent(ctx, s.sessionWriteModel.aggregate, domain))
// set the domain so checks can use it
s.sessionWriteModel.Domain = domain
}
func (s *SessionCommands) UserChecked(ctx context.Context, userID string, checkedAt time.Time) error {
s.eventCommands = append(s.eventCommands, session.NewUserCheckedEvent(ctx, s.sessionWriteModel.aggregate, userID, checkedAt))
// set the userID so other checks can use it
s.sessionWriteModel.UserID = userID
return nil
}
func (s *SessionCommands) PasswordChecked(ctx context.Context, checkedAt time.Time) {
s.eventCommands = append(s.eventCommands, session.NewPasswordCheckedEvent(ctx, s.sessionWriteModel.aggregate, checkedAt))
}
func (s *SessionCommands) IntentChecked(ctx context.Context, checkedAt time.Time) {
s.eventCommands = append(s.eventCommands, session.NewIntentCheckedEvent(ctx, s.sessionWriteModel.aggregate, checkedAt))
}
func (s *SessionCommands) PasskeyChallenged(ctx context.Context, challenge string, allowedCrentialIDs [][]byte, userVerification domain.UserVerificationRequirement) {
s.eventCommands = append(s.eventCommands, session.NewPasskeyChallengedEvent(ctx, s.sessionWriteModel.aggregate, challenge, allowedCrentialIDs, userVerification))
}
func (s *SessionCommands) PasskeyChecked(ctx context.Context, checkedAt time.Time, tokenID string, signCount uint32) {
s.eventCommands = append(s.eventCommands,
session.NewPasskeyCheckedEvent(ctx, s.sessionWriteModel.aggregate, checkedAt),
usr_repo.NewHumanPasswordlessSignCountChangedEvent(ctx, s.sessionWriteModel.aggregate, tokenID, signCount),
)
}
func (s *SessionCommands) SetToken(ctx context.Context, tokenID string) {
s.eventCommands = append(s.eventCommands, session.NewTokenSetEvent(ctx, s.sessionWriteModel.aggregate, tokenID))
}
func (s *SessionCommands) ChangeMetadata(ctx context.Context, metadata map[string][]byte) {
var changed bool
for key, value := range metadata {
currentValue, exists := s.sessionWriteModel.Metadata[key]
if len(value) != 0 {
// if a value is provided, and it's not equal, change it
if !bytes.Equal(currentValue, value) {
s.sessionWriteModel.Metadata[key] = value
changed = true
}
} else {
// if there's no / an empty value, we only need to remove it on existing entries
if exists {
delete(s.sessionWriteModel.Metadata, key)
changed = true
}
}
}
if changed {
s.eventCommands = append(s.eventCommands, session.NewMetadataSetEvent(ctx, s.sessionWriteModel.aggregate, s.sessionWriteModel.Metadata))
}
}
func (s *SessionCommands) gethumanWriteModel(ctx context.Context) (*HumanWriteModel, error) {
if s.sessionWriteModel.UserID == "" {
return nil, caos_errs.ThrowPreconditionFailed(nil, "COMMAND-eeR2e", "Errors.User.UserIDMissing")
@@ -145,7 +214,7 @@ func (s *SessionCommands) gethumanWriteModel(ctx context.Context) (*HumanWriteMo
}
func (s *SessionCommands) commands(ctx context.Context) (string, []eventstore.Command, error) {
if len(s.sessionWriteModel.commands) == 0 {
if len(s.eventCommands) == 0 {
return "", nil, nil
}
@@ -153,8 +222,8 @@ func (s *SessionCommands) commands(ctx context.Context) (string, []eventstore.Co
if err != nil {
return "", nil, err
}
s.sessionWriteModel.SetToken(ctx, tokenID)
return token, s.sessionWriteModel.commands, nil
s.SetToken(ctx, tokenID)
return token, s.eventCommands, nil
}
func (c *Commands) CreateSession(ctx context.Context, cmds []SessionCommand, sessionDomain string, metadata map[string][]byte) (set *SessionChanged, err error) {
@@ -167,8 +236,8 @@ func (c *Commands) CreateSession(ctx context.Context, cmds []SessionCommand, ses
if err != nil {
return nil, err
}
sessionWriteModel.Start(ctx, sessionDomain)
cmd := c.NewSessionCommands(cmds, sessionWriteModel)
cmd.Start(ctx, sessionDomain)
return c.updateSession(ctx, cmd, metadata)
}
@@ -217,7 +286,7 @@ func (c *Commands) updateSession(ctx context.Context, checks *SessionCommands, m
// TODO: how to handle failed checks (e.g. pw wrong) https://github.com/zitadel/zitadel/issues/5807
return nil, err
}
checks.sessionWriteModel.ChangeMetadata(ctx, metadata)
checks.ChangeMetadata(ctx, metadata)
sessionToken, cmds, err := checks.commands(ctx)
if err != nil {
return nil, err

View File

@@ -1,15 +1,12 @@
package command
import (
"bytes"
"context"
"time"
"github.com/zitadel/zitadel/internal/domain"
caos_errs "github.com/zitadel/zitadel/internal/errors"
"github.com/zitadel/zitadel/internal/eventstore"
"github.com/zitadel/zitadel/internal/repository/session"
usr_repo "github.com/zitadel/zitadel/internal/repository/user"
)
type PasskeyChallengeModel struct {
@@ -48,7 +45,6 @@ type SessionWriteModel struct {
PasskeyChallenge *PasskeyChallengeModel
commands []eventstore.Command
aggregate *eventstore.Aggregate
}
@@ -151,66 +147,6 @@ func (wm *SessionWriteModel) reduceTerminate() {
wm.State = domain.SessionStateTerminated
}
func (wm *SessionWriteModel) Start(ctx context.Context, domain string) {
wm.commands = append(wm.commands, session.NewAddedEvent(ctx, wm.aggregate, domain))
// set the domain so checks can use it
wm.Domain = domain
}
func (wm *SessionWriteModel) UserChecked(ctx context.Context, userID string, checkedAt time.Time) error {
wm.commands = append(wm.commands, session.NewUserCheckedEvent(ctx, wm.aggregate, userID, checkedAt))
// set the userID so other checks can use it
wm.UserID = userID
return nil
}
func (wm *SessionWriteModel) PasswordChecked(ctx context.Context, checkedAt time.Time) {
wm.commands = append(wm.commands, session.NewPasswordCheckedEvent(ctx, wm.aggregate, checkedAt))
}
func (wm *SessionWriteModel) IntentChecked(ctx context.Context, checkedAt time.Time) {
wm.commands = append(wm.commands, session.NewIntentCheckedEvent(ctx, wm.aggregate, checkedAt))
}
func (wm *SessionWriteModel) PasskeyChallenged(ctx context.Context, challenge string, allowedCrentialIDs [][]byte, userVerification domain.UserVerificationRequirement) {
wm.commands = append(wm.commands, session.NewPasskeyChallengedEvent(ctx, wm.aggregate, challenge, allowedCrentialIDs, userVerification))
}
func (wm *SessionWriteModel) PasskeyChecked(ctx context.Context, checkedAt time.Time, tokenID string, signCount uint32) {
wm.commands = append(wm.commands,
session.NewPasskeyCheckedEvent(ctx, wm.aggregate, checkedAt),
usr_repo.NewHumanPasswordlessSignCountChangedEvent(ctx, wm.aggregate, tokenID, signCount),
)
}
func (wm *SessionWriteModel) SetToken(ctx context.Context, tokenID string) {
wm.commands = append(wm.commands, session.NewTokenSetEvent(ctx, wm.aggregate, tokenID))
}
func (wm *SessionWriteModel) ChangeMetadata(ctx context.Context, metadata map[string][]byte) {
var changed bool
for key, value := range metadata {
currentValue, exists := wm.Metadata[key]
if len(value) != 0 {
// if a value is provided, and it's not equal, change it
if !bytes.Equal(currentValue, value) {
wm.Metadata[key] = value
changed = true
}
} else {
// if there's no / an empty value, we only need to remove it on existing entries
if exists {
delete(wm.Metadata, key)
changed = true
}
}
}
if changed {
wm.commands = append(wm.commands, session.NewMetadataSetEvent(ctx, wm.aggregate, wm.Metadata))
}
}
// AuthenticationTime returns the time the user authenticated using the latest time of all checks
func (wm *SessionWriteModel) AuthenticationTime() time.Time {
var authTime time.Time

View File

@@ -51,7 +51,7 @@ func (c *Commands) CreatePasskeyChallenge(userVerification domain.UserVerificati
return caos_errs.ThrowInternal(err, "COMMAND-Yah6A", "Errors.Internal")
}
cmd.sessionWriteModel.PasskeyChallenged(ctx, webAuthNLogin.Challenge, webAuthNLogin.AllowedCredentialIDs, webAuthNLogin.UserVerification)
cmd.PasskeyChallenged(ctx, webAuthNLogin.Challenge, webAuthNLogin.AllowedCredentialIDs, webAuthNLogin.UserVerification)
return nil
}
}
@@ -78,7 +78,7 @@ func (c *Commands) CheckPasskey(credentialAssertionData json.Marshaler) SessionC
if token == nil {
return caos_errs.ThrowPreconditionFailed(nil, "COMMAND-Aej7i", "Errors.User.WebAuthN.NotFound")
}
cmd.sessionWriteModel.PasskeyChecked(ctx, cmd.now(), token.WebAuthNTokenID, signCount)
cmd.PasskeyChecked(ctx, cmd.now(), token.WebAuthNTokenID, signCount)
return nil
}
}

View File

@@ -140,7 +140,6 @@ func TestSessionCommands_getHumanWriteModel(t *testing.T) {
func TestCommands_CreateSession(t *testing.T) {
type fields struct {
eventstore *eventstore.Eventstore
idGenerator id.Generator
tokenCreator func(sessionID string) (string, string, error)
}
@@ -158,6 +157,7 @@ func TestCommands_CreateSession(t *testing.T) {
name string
fields fields
args args
expect []expect
res res
}{
{
@@ -168,6 +168,7 @@ func TestCommands_CreateSession(t *testing.T) {
args{
ctx: context.Background(),
},
[]expect{},
res{
err: caos_errs.ThrowInternal(nil, "id", "generator failed"),
},
@@ -176,13 +177,13 @@ func TestCommands_CreateSession(t *testing.T) {
"eventstore failed",
fields{
idGenerator: mock.NewIDGeneratorExpectIDs(t, "sessionID"),
eventstore: eventstoreExpect(t,
expectFilterError(caos_errs.ThrowInternal(nil, "id", "filter failed")),
),
},
args{
ctx: context.Background(),
},
[]expect{
expectFilterError(caos_errs.ThrowInternal(nil, "id", "filter failed")),
},
res{
err: caos_errs.ThrowInternal(nil, "id", "filter failed"),
},
@@ -191,17 +192,6 @@ func TestCommands_CreateSession(t *testing.T) {
"empty session",
fields{
idGenerator: mock.NewIDGeneratorExpectIDs(t, "sessionID"),
eventstore: eventstoreExpect(t,
expectFilter(),
expectPush(
eventPusherToEvents(
session.NewAddedEvent(context.Background(), &session.NewAggregate("sessionID", "org1").Aggregate, ""),
session.NewTokenSetEvent(context.Background(), &session.NewAggregate("sessionID", "org1").Aggregate,
"tokenID",
),
),
),
),
tokenCreator: func(sessionID string) (string, string, error) {
return "tokenID",
"token",
@@ -211,6 +201,17 @@ func TestCommands_CreateSession(t *testing.T) {
args{
ctx: authz.NewMockContext("", "org1", ""),
},
[]expect{
expectFilter(),
expectPush(
eventPusherToEvents(
session.NewAddedEvent(context.Background(), &session.NewAggregate("sessionID", "org1").Aggregate, ""),
session.NewTokenSetEvent(context.Background(), &session.NewAggregate("sessionID", "org1").Aggregate,
"tokenID",
),
),
),
},
res{
want: &SessionChanged{
ObjectDetails: &domain.ObjectDetails{ResourceOwner: "org1"},
@@ -223,17 +224,6 @@ func TestCommands_CreateSession(t *testing.T) {
"empty session with domain",
fields{
idGenerator: mock.NewIDGeneratorExpectIDs(t, "sessionID"),
eventstore: eventstoreExpect(t,
expectFilter(),
expectPush(
eventPusherToEvents(
session.NewAddedEvent(context.Background(), &session.NewAggregate("sessionID", "org1").Aggregate, "domain.tld"),
session.NewTokenSetEvent(context.Background(), &session.NewAggregate("sessionID", "org1").Aggregate,
"tokenID",
),
),
),
),
tokenCreator: func(sessionID string) (string, string, error) {
return "tokenID",
"token",
@@ -244,6 +234,17 @@ func TestCommands_CreateSession(t *testing.T) {
ctx: authz.NewMockContext("", "org1", ""),
domain: "domain.tld",
},
[]expect{
expectFilter(),
expectPush(
eventPusherToEvents(
session.NewAddedEvent(context.Background(), &session.NewAggregate("sessionID", "org1").Aggregate, "domain.tld"),
session.NewTokenSetEvent(context.Background(), &session.NewAggregate("sessionID", "org1").Aggregate,
"tokenID",
),
),
),
},
res{
want: &SessionChanged{
ObjectDetails: &domain.ObjectDetails{ResourceOwner: "org1"},
@@ -257,7 +258,7 @@ func TestCommands_CreateSession(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
c := &Commands{
eventstore: tt.fields.eventstore,
eventstore: eventstoreExpect(t, tt.expect...),
idGenerator: tt.fields.idGenerator,
sessionTokenCreator: tt.fields.tokenCreator,
}
@@ -432,7 +433,7 @@ func TestCommands_updateSession(t *testing.T) {
ctx: context.Background(),
checks: &SessionCommands{
sessionWriteModel: NewSessionWriteModel("sessionID", "org1"),
cmds: []SessionCommand{
sessionCommands: []SessionCommand{
func(ctx context.Context, cmd *SessionCommands) error {
return caos_errs.ThrowInternal(nil, "id", "check failed")
},
@@ -452,7 +453,7 @@ func TestCommands_updateSession(t *testing.T) {
ctx: context.Background(),
checks: &SessionCommands{
sessionWriteModel: NewSessionWriteModel("sessionID", "org1"),
cmds: []SessionCommand{},
sessionCommands: []SessionCommand{},
},
},
res{
@@ -487,7 +488,7 @@ func TestCommands_updateSession(t *testing.T) {
ctx: context.Background(),
checks: &SessionCommands{
sessionWriteModel: NewSessionWriteModel("sessionID", "org1"),
cmds: []SessionCommand{
sessionCommands: []SessionCommand{
CheckUser("userID"),
CheckPassword("password"),
},
@@ -499,12 +500,7 @@ func TestCommands_updateSession(t *testing.T) {
),
eventFromEventPusher(
user.NewHumanPasswordChangedEvent(context.Background(), &user.NewAggregate("userID", "org1").Aggregate,
&crypto.CryptoValue{
CryptoType: crypto.TypeHash,
Algorithm: "hash",
KeyID: "",
Crypted: []byte("password"),
}, false, ""),
"$plain$x$password", false, ""),
),
),
),
@@ -513,7 +509,7 @@ func TestCommands_updateSession(t *testing.T) {
"token",
nil
},
userPasswordAlg: crypto.CreateMockHashAlg(gomock.NewController(t)),
hasher: mockPasswordHasher("x"),
now: func() time.Time {
return testNow
},
@@ -541,7 +537,7 @@ func TestCommands_updateSession(t *testing.T) {
ctx: context.Background(),
checks: &SessionCommands{
sessionWriteModel: NewSessionWriteModel("sessionID", "org1"),
cmds: []SessionCommand{
sessionCommands: []SessionCommand{
CheckUser("userID"),
CheckIntent("intent", "aW50ZW50"),
},
@@ -580,7 +576,7 @@ func TestCommands_updateSession(t *testing.T) {
ctx: context.Background(),
checks: &SessionCommands{
sessionWriteModel: NewSessionWriteModel("sessionID", "org1"),
cmds: []SessionCommand{
sessionCommands: []SessionCommand{
CheckUser("userID"),
CheckIntent("intent", "aW50ZW50"),
},
@@ -629,7 +625,7 @@ func TestCommands_updateSession(t *testing.T) {
ctx: context.Background(),
checks: &SessionCommands{
sessionWriteModel: NewSessionWriteModel("sessionID", "org1"),
cmds: []SessionCommand{
sessionCommands: []SessionCommand{
CheckUser("userID"),
CheckIntent("intent2", "aW50ZW50"),
},
@@ -674,7 +670,7 @@ func TestCommands_updateSession(t *testing.T) {
ctx: context.Background(),
checks: &SessionCommands{
sessionWriteModel: NewSessionWriteModel("sessionID", "org1"),
cmds: []SessionCommand{
sessionCommands: []SessionCommand{
CheckUser("userID"),
CheckIntent("intent", "aW50ZW50"),
},

View File

@@ -49,8 +49,8 @@ type AddHuman struct {
Phone Phone
// Password is optional
Password string
// BcryptedPassword is optional
BcryptedPassword string
// EncodedPasswordHash is optional
EncodedPasswordHash string
// PasswordChangeRequired is used if the `Password`-field is set
PasswordChangeRequired bool
Passwordless bool
@@ -74,7 +74,7 @@ type AddLink struct {
IDPExternalID string
}
func (h *AddHuman) Validate() (err error) {
func (h *AddHuman) Validate(hasher *crypto.PasswordHasher) (err error) {
if err := h.Email.Validate(); err != nil {
return err
}
@@ -101,6 +101,11 @@ func (h *AddHuman) Validate() (err error) {
return err
}
}
if h.EncodedPasswordHash != "" {
if !hasher.EncodingSupported(h.EncodedPasswordHash) {
return errors.ThrowInvalidArgument(nil, "USER-JDk4t", "Errors.User.Password.NotSupported")
}
}
return nil
}
@@ -127,7 +132,7 @@ func (c *Commands) AddHuman(ctx context.Context, resourceOwner string, human *Ad
c.AddHumanCommand(
human,
resourceOwner,
c.userPasswordAlg,
c.userPasswordHasher,
c.userEncryption,
allowInitMail,
))
@@ -151,12 +156,13 @@ func (c *Commands) AddHuman(ctx context.Context, resourceOwner string, human *Ad
type humanCreationCommand interface {
eventstore.Command
AddPhoneData(phoneNumber domain.PhoneNumber)
AddPasswordData(secret *crypto.CryptoValue, changeRequired bool)
AddPasswordData(encoded string, changeRequired bool)
}
func (c *Commands) AddHumanCommand(human *AddHuman, orgID string, passwordAlg crypto.HashAlgorithm, codeAlg crypto.EncryptionAlgorithm, allowInitMail bool) preparation.Validation {
//nolint:gocognit
func (c *Commands) AddHumanCommand(human *AddHuman, orgID string, hasher *crypto.PasswordHasher, codeAlg crypto.EncryptionAlgorithm, allowInitMail bool) preparation.Validation {
return func() (_ preparation.CreateCommands, err error) {
if err := human.Validate(); err != nil {
if err := human.Validate(hasher); err != nil {
return nil, err
}
@@ -210,7 +216,7 @@ func (c *Commands) AddHumanCommand(human *AddHuman, orgID string, passwordAlg cr
createCmd.AddPhoneData(human.Phone.Number)
}
if err := addHumanCommandPassword(ctx, filter, createCmd, human, passwordAlg); err != nil {
if err := addHumanCommandPassword(ctx, filter, createCmd, human, hasher); err != nil {
return nil, err
}
@@ -316,13 +322,13 @@ func (c *Commands) addHumanCommandCheckID(ctx context.Context, filter preparatio
return nil
}
func addHumanCommandPassword(ctx context.Context, filter preparation.FilterToQueryReducer, createCmd humanCreationCommand, human *AddHuman, passwordAlg crypto.HashAlgorithm) (err error) {
func addHumanCommandPassword(ctx context.Context, filter preparation.FilterToQueryReducer, createCmd humanCreationCommand, human *AddHuman, hasher *crypto.PasswordHasher) (err error) {
if human.Password != "" {
if err = humanValidatePassword(ctx, filter, human.Password); err != nil {
return err
}
secret, err := crypto.Hash([]byte(human.Password), passwordAlg)
secret, err := hasher.Hash(human.Password)
if err != nil {
return err
}
@@ -330,8 +336,8 @@ func addHumanCommandPassword(ctx context.Context, filter preparation.FilterToQue
return nil
}
if human.BcryptedPassword != "" {
createCmd.AddPasswordData(crypto.FillHash([]byte(human.BcryptedPassword), passwordAlg), human.PasswordChangeRequired)
if human.EncodedPasswordHash != "" {
createCmd.AddPasswordData(human.EncodedPasswordHash, human.PasswordChangeRequired)
}
return nil
}
@@ -578,7 +584,7 @@ func (c *Commands) createHuman(ctx context.Context, orgID string, human *domain.
human.EnsureDisplayName()
if human.Password != nil {
if err := human.HashPasswordIfExisting(pwPolicy, c.userPasswordAlg, human.Password.ChangeRequired); err != nil {
if err := human.HashPasswordIfExisting(pwPolicy, c.userPasswordHasher, human.Password.ChangeRequired); err != nil {
return nil, nil, err
}
}
@@ -677,10 +683,10 @@ func createAddHumanEvent(ctx context.Context, aggregate *eventstore.Aggregate, h
human.StreetAddress)
}
if human.Password != nil {
addEvent.AddPasswordData(human.Password.SecretCrypto, human.Password.ChangeRequired)
addEvent.AddPasswordData(human.Password.EncodedSecret, human.Password.ChangeRequired)
}
if human.HashedPassword != nil {
addEvent.AddPasswordData(human.HashedPassword.SecretCrypto, false)
if human.HashedPassword != "" {
addEvent.AddPasswordData(human.HashedPassword, false)
}
return addEvent
}
@@ -711,10 +717,10 @@ func createRegisterHumanEvent(ctx context.Context, aggregate *eventstore.Aggrega
human.StreetAddress)
}
if human.Password != nil {
addEvent.AddPasswordData(human.Password.SecretCrypto, human.Password.ChangeRequired)
addEvent.AddPasswordData(human.Password.EncodedSecret, human.Password.ChangeRequired)
}
if human.HashedPassword != nil {
addEvent.AddPasswordData(human.HashedPassword.SecretCrypto, false)
if human.HashedPassword != "" {
addEvent.AddPasswordData(human.HashedPassword, false)
}
return addEvent
}

View File

@@ -4,6 +4,7 @@ import (
"context"
"github.com/zitadel/logging"
"github.com/zitadel/zitadel/internal/crypto"
"github.com/zitadel/zitadel/internal/domain"
caos_errs "github.com/zitadel/zitadel/internal/errors"
@@ -49,7 +50,7 @@ func (c *Commands) ResendInitialMail(ctx context.Context, userID string, email d
return writeModelToObjectDetails(&existingCode.WriteModel), nil
}
func (c *Commands) HumanVerifyInitCode(ctx context.Context, userID, resourceOwner, code, passwordString string, initCodeGenerator crypto.Generator) error {
func (c *Commands) HumanVerifyInitCode(ctx context.Context, userID, resourceOwner, code, password string, initCodeGenerator crypto.Generator) error {
if userID == "" {
return caos_errs.ThrowInvalidArgument(nil, "COMMAND-mkM9f", "Errors.User.UserIDMissing")
}
@@ -72,26 +73,22 @@ func (c *Commands) HumanVerifyInitCode(ctx context.Context, userID, resourceOwne
logging.WithFields("userID", userAgg.ID).OnError(err).Error("NewHumanInitializedCheckFailedEvent push failed")
return caos_errs.ThrowInvalidArgument(err, "COMMAND-11v6G", "Errors.User.Code.Invalid")
}
events := []eventstore.Command{
commands := []eventstore.Command{
user.NewHumanInitializedCheckSucceededEvent(ctx, userAgg),
}
if !existingCode.IsEmailVerified {
events = append(events, user.NewHumanEmailVerifiedEvent(ctx, userAgg))
commands = append(commands, user.NewHumanEmailVerifiedEvent(ctx, userAgg))
}
if passwordString != "" {
if password != "" {
passwordWriteModel := NewHumanPasswordWriteModel(userID, existingCode.ResourceOwner)
passwordWriteModel.UserState = domain.UserStateActive
password := &domain.Password{
SecretString: passwordString,
ChangeRequired: false,
}
passwordEvent, err := c.changePassword(ctx, "", password, userAgg, passwordWriteModel)
passwordCommand, err := c.setPasswordCommand(ctx, passwordWriteModel, password, false)
if err != nil {
return err
}
events = append(events, passwordEvent)
commands = append(commands, passwordCommand)
}
_, err = c.eventstore.Push(ctx, events...)
_, err = c.eventstore.Push(ctx, commands...)
return err
}

View File

@@ -5,7 +5,6 @@ import (
"testing"
"time"
"github.com/golang/mock/gomock"
"github.com/stretchr/testify/assert"
"golang.org/x/text/language"
@@ -305,8 +304,8 @@ func TestCommandSide_ResendInitialMail(t *testing.T) {
func TestCommandSide_VerifyInitCode(t *testing.T) {
type fields struct {
eventstore *eventstore.Eventstore
userPasswordAlg crypto.HashAlgorithm
eventstore *eventstore.Eventstore
userPasswordHasher *crypto.PasswordHasher
}
type args struct {
ctx context.Context
@@ -584,18 +583,13 @@ func TestCommandSide_VerifyInitCode(t *testing.T) {
eventFromEventPusher(
user.NewHumanPasswordChangedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
&crypto.CryptoValue{
CryptoType: crypto.TypeHash,
Algorithm: "hash",
KeyID: "",
Crypted: []byte("password"),
},
"$plain$x$password",
false,
"")),
},
),
),
userPasswordAlg: crypto.CreateMockHashAlg(gomock.NewController(t)),
userPasswordHasher: mockPasswordHasher("x"),
},
args: args{
ctx: context.Background(),
@@ -615,8 +609,8 @@ func TestCommandSide_VerifyInitCode(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
r := &Commands{
eventstore: tt.fields.eventstore,
userPasswordAlg: tt.fields.userPasswordAlg,
eventstore: tt.fields.eventstore,
userPasswordHasher: tt.fields.userPasswordHasher,
}
err := r.HumanVerifyInitCode(tt.args.ctx, tt.args.userID, tt.args.resourceOwner, tt.args.code, tt.args.password, tt.args.secretGenerator)
if tt.res.err == nil {

View File

@@ -2,8 +2,10 @@ package command
import (
"context"
"errors"
"github.com/zitadel/logging"
"github.com/zitadel/passwap"
"github.com/zitadel/zitadel/internal/crypto"
"github.com/zitadel/zitadel/internal/domain"
@@ -13,80 +15,75 @@ import (
"github.com/zitadel/zitadel/internal/telemetry/tracing"
)
func (c *Commands) SetPassword(ctx context.Context, orgID, userID, passwordString string, oneTime bool) (objectDetails *domain.ObjectDetails, err error) {
func (c *Commands) SetPassword(ctx context.Context, orgID, userID, password string, oneTime bool) (objectDetails *domain.ObjectDetails, err error) {
ctx, span := tracing.NewSpan(ctx)
defer func() { span.EndWithError(err) }()
if userID == "" {
return nil, caos_errs.ThrowInvalidArgument(nil, "COMMAND-3M0fs", "Errors.IDMissing")
}
existingPassword, err := c.passwordWriteModel(ctx, userID, orgID)
wm, err := c.passwordWriteModel(ctx, userID, orgID)
if err != nil {
return nil, err
}
if !existingPassword.UserState.Exists() {
if !wm.UserState.Exists() {
return nil, caos_errs.ThrowPreconditionFailed(nil, "COMMAND-3M0fs", "Errors.User.NotFound")
}
if err = c.checkPermission(ctx, domain.PermissionUserWrite, existingPassword.ResourceOwner, userID); err != nil {
if err = c.checkPermission(ctx, domain.PermissionUserWrite, wm.ResourceOwner, userID); err != nil {
return nil, err
}
password := &domain.Password{
SecretString: passwordString,
ChangeRequired: oneTime,
}
userAgg := UserAggregateFromWriteModel(&existingPassword.WriteModel)
passwordEvent, err := c.changePassword(ctx, "", password, userAgg, existingPassword)
if err != nil {
return nil, err
}
pushedEvents, err := c.eventstore.Push(ctx, passwordEvent)
if err != nil {
return nil, err
}
err = AppendAndReduce(existingPassword, pushedEvents...)
if err != nil {
return nil, err
}
return writeModelToObjectDetails(&existingPassword.WriteModel), nil
return c.setPassword(ctx, wm, password, oneTime)
}
func (c *Commands) SetPasswordWithVerifyCode(ctx context.Context, orgID, userID, code, passwordString, userAgentID string) (objectDetails *domain.ObjectDetails, err error) {
func (c *Commands) SetPasswordWithVerifyCode(ctx context.Context, orgID, userID, code, password, userAgentID string) (objectDetails *domain.ObjectDetails, err error) {
ctx, span := tracing.NewSpan(ctx)
defer func() { span.EndWithError(err) }()
if userID == "" {
return nil, caos_errs.ThrowInvalidArgument(nil, "COMMAND-3M9fs", "Errors.IDMissing")
}
if passwordString == "" {
if password == "" {
return nil, caos_errs.ThrowInvalidArgument(nil, "COMMAND-Mf0sd", "Errors.User.Password.Empty")
}
existingCode, err := c.passwordWriteModel(ctx, userID, orgID)
wm, err := c.passwordWriteModel(ctx, userID, orgID)
if err != nil {
return nil, err
}
if existingCode.Code == nil || existingCode.UserState == domain.UserStateUnspecified || existingCode.UserState == domain.UserStateDeleted {
if wm.Code == nil || wm.UserState == domain.UserStateUnspecified || wm.UserState == domain.UserStateDeleted {
return nil, caos_errs.ThrowPreconditionFailed(nil, "COMMAND-2M9fs", "Errors.User.Code.NotFound")
}
err = crypto.VerifyCodeWithAlgorithm(existingCode.CodeCreationDate, existingCode.CodeExpiry, existingCode.Code, code, c.userEncryption)
err = crypto.VerifyCodeWithAlgorithm(wm.CodeCreationDate, wm.CodeExpiry, wm.Code, code, c.userEncryption)
if err != nil {
return nil, err
}
password := &domain.Password{
SecretString: passwordString,
ChangeRequired: false,
}
userAgg := UserAggregateFromWriteModel(&existingCode.WriteModel)
passwordEvent, err := c.changePassword(ctx, userAgentID, password, userAgg, existingCode)
return c.setPassword(ctx, wm, password, false)
}
func (c *Commands) setPassword(ctx context.Context, wm *HumanPasswordWriteModel, password string, changeRequired bool) (objectDetails *domain.ObjectDetails, err error) {
command, err := c.setPasswordCommand(ctx, wm, password, changeRequired)
if err != nil {
return nil, err
}
err = c.pushAppendAndReduce(ctx, existingCode, passwordEvent)
err = c.pushAppendAndReduce(ctx, wm, command)
if err != nil {
return nil, err
}
return writeModelToObjectDetails(&existingCode.WriteModel), nil
return writeModelToObjectDetails(&wm.WriteModel), nil
}
func (c *Commands) setPasswordCommand(ctx context.Context, wm *HumanPasswordWriteModel, password string, changeRequired bool) (_ eventstore.Command, err error) {
if err = c.canUpdatePassword(ctx, password, wm); err != nil {
return nil, err
}
ctx, span := tracing.NewNamedSpan(ctx, "passwap.Hash")
encoded, err := c.userPasswordHasher.Hash(password)
span.EndWithError(err)
if err = convertPasswapErr(err); err != nil {
return nil, err
}
return user.NewHumanPasswordChangedEvent(ctx, UserAggregateFromWriteModel(&wm.WriteModel), encoded, changeRequired, ""), nil
}
func (c *Commands) ChangePassword(ctx context.Context, orgID, userID, oldPassword, newPassword, userAgentID string) (objectDetails *domain.ObjectDetails, err error) {
@@ -99,59 +96,50 @@ func (c *Commands) ChangePassword(ctx context.Context, orgID, userID, oldPasswor
if oldPassword == "" || newPassword == "" {
return nil, caos_errs.ThrowInvalidArgument(nil, "COMMAND-3M0fs", "Errors.User.Password.Empty")
}
existingPassword, err := c.passwordWriteModel(ctx, userID, orgID)
wm, err := c.passwordWriteModel(ctx, userID, orgID)
if err != nil {
return nil, err
}
if existingPassword.Secret == nil {
if wm.EncodedHash == "" {
return nil, caos_errs.ThrowPreconditionFailed(nil, "COMMAND-Fds3s", "Errors.User.Password.Empty")
}
ctx, spanPasswordComparison := tracing.NewNamedSpan(ctx, "crypto.CompareHash")
err = crypto.CompareHash(existingPassword.Secret, []byte(oldPassword), c.userPasswordAlg)
spanPasswordComparison.EndWithError(err)
if err != nil {
return nil, caos_errs.ThrowInvalidArgument(nil, "COMMAND-3M0fs", "Errors.User.Password.Invalid")
}
password := &domain.Password{
SecretString: newPassword,
ChangeRequired: false,
if err = c.canUpdatePassword(ctx, newPassword, wm); err != nil {
return nil, err
}
userAgg := UserAggregateFromWriteModel(&existingPassword.WriteModel)
command, err := c.changePassword(ctx, userAgentID, password, userAgg, existingPassword)
ctx, spanPasswap := tracing.NewNamedSpan(ctx, "passwap.VerifyAndUpdate")
updated, err := c.userPasswordHasher.VerifyAndUpdate(wm.EncodedHash, oldPassword, newPassword)
spanPasswap.EndWithError(err)
if err = convertPasswapErr(err); err != nil {
return nil, err
}
err = c.pushAppendAndReduce(ctx, wm,
user.NewHumanPasswordChangedEvent(ctx, UserAggregateFromWriteModel(&wm.WriteModel), updated, false, userAgentID))
if err != nil {
return nil, err
}
pushedEvents, err := c.eventstore.Push(ctx, command)
if err != nil {
return nil, err
}
err = AppendAndReduce(existingPassword, pushedEvents...)
if err != nil {
return nil, err
}
return writeModelToObjectDetails(&existingPassword.WriteModel), nil
return writeModelToObjectDetails(&wm.WriteModel), nil
}
func (c *Commands) changePassword(ctx context.Context, userAgentID string, password *domain.Password, userAgg *eventstore.Aggregate, existingPassword *HumanPasswordWriteModel) (event eventstore.Command, err error) {
func (c *Commands) canUpdatePassword(ctx context.Context, newPassword string, wm *HumanPasswordWriteModel) (err error) {
ctx, span := tracing.NewSpan(ctx)
defer func() { span.EndWithError(err) }()
if existingPassword.UserState == domain.UserStateUnspecified || existingPassword.UserState == domain.UserStateDeleted {
return nil, caos_errs.ThrowNotFound(nil, "COMMAND-G8dh3", "Errors.User.Password.NotFound")
if wm.UserState == domain.UserStateUnspecified || wm.UserState == domain.UserStateDeleted {
return caos_errs.ThrowNotFound(nil, "COMMAND-G8dh3", "Errors.User.Password.NotFound")
}
if existingPassword.UserState == domain.UserStateInitial {
return nil, caos_errs.ThrowPreconditionFailed(nil, "COMMAND-M9dse", "Errors.User.NotInitialised")
if wm.UserState == domain.UserStateInitial {
return caos_errs.ThrowPreconditionFailed(nil, "COMMAND-M9dse", "Errors.User.NotInitialised")
}
pwPolicy, err := c.getOrgPasswordComplexityPolicy(ctx, userAgg.ResourceOwner)
policy, err := c.getOrgPasswordComplexityPolicy(ctx, wm.ResourceOwner)
if err != nil {
return nil, err
return err
}
if err := password.HashPasswordIfExisting(pwPolicy, c.userPasswordAlg); err != nil {
return nil, err
if err := policy.Check(newPassword); err != nil {
return err
}
return user.NewHumanPasswordChangedEvent(ctx, userAgg, password.SecretCrypto, password.ChangeRequired, userAgentID), nil
return nil
}
func (c *Commands) RequestSetPassword(ctx context.Context, userID, resourceOwner string, notifyType domain.NotificationType, passwordVerificationCode crypto.Generator) (objectDetails *domain.ObjectDetails, err error) {
@@ -238,37 +226,44 @@ func (c *Commands) HumanCheckPassword(ctx context.Context, orgID, userID, passwo
return caos_errs.ThrowPreconditionFailed(err, "COMMAND-Dft32", "Errors.Org.LoginPolicy.UsernamePasswordNotAllowed")
}
existingPassword, err := c.passwordWriteModel(ctx, userID, orgID)
wm, err := c.passwordWriteModel(ctx, userID, orgID)
if err != nil {
return err
}
if existingPassword.UserState == domain.UserStateUnspecified || existingPassword.UserState == domain.UserStateDeleted {
if wm.UserState == domain.UserStateUnspecified || wm.UserState == domain.UserStateDeleted {
return caos_errs.ThrowPreconditionFailed(nil, "COMMAND-3n77z", "Errors.User.NotFound")
}
if existingPassword.Secret == nil {
if wm.EncodedHash == "" {
return caos_errs.ThrowPreconditionFailed(nil, "COMMAND-3n77z", "Errors.User.Password.NotSet")
}
userAgg := UserAggregateFromWriteModel(&existingPassword.WriteModel)
ctx, spanPasswordComparison := tracing.NewNamedSpan(ctx, "crypto.CompareHash")
err = crypto.CompareHash(existingPassword.Secret, []byte(password), c.userPasswordAlg)
userAgg := UserAggregateFromWriteModel(&wm.WriteModel)
ctx, spanPasswordComparison := tracing.NewNamedSpan(ctx, "passwap.Verify")
updated, err := c.userPasswordHasher.Verify(wm.EncodedHash, password)
spanPasswordComparison.EndWithError(err)
err = convertPasswapErr(err)
commands := make([]eventstore.Command, 0, 2)
if err == nil {
_, err = c.eventstore.Push(ctx, user.NewHumanPasswordCheckSucceededEvent(ctx, userAgg, authRequestDomainToAuthRequestInfo(authRequest)))
commands = append(commands, user.NewHumanPasswordCheckSucceededEvent(ctx, userAgg, authRequestDomainToAuthRequestInfo(authRequest)))
if updated != "" {
commands = append(commands, user.NewHumanPasswordHashUpdatedEvent(ctx, userAgg, updated))
}
_, err = c.eventstore.Push(ctx, commands...)
return err
}
events := make([]eventstore.Command, 0)
events = append(events, user.NewHumanPasswordCheckFailedEvent(ctx, userAgg, authRequestDomainToAuthRequestInfo(authRequest)))
commands = append(commands, user.NewHumanPasswordCheckFailedEvent(ctx, userAgg, authRequestDomainToAuthRequestInfo(authRequest)))
if lockoutPolicy != nil && lockoutPolicy.MaxPasswordAttempts > 0 {
if existingPassword.PasswordCheckFailedCount+1 >= lockoutPolicy.MaxPasswordAttempts {
events = append(events, user.NewUserLockedEvent(ctx, userAgg))
if wm.PasswordCheckFailedCount+1 >= lockoutPolicy.MaxPasswordAttempts {
commands = append(commands, user.NewUserLockedEvent(ctx, userAgg))
}
}
_, err = c.eventstore.Push(ctx, events...)
logging.Log("COMMAND-9fj7s").OnError(err).Error("error create password check failed event")
return caos_errs.ThrowInvalidArgument(nil, "COMMAND-452ad", "Errors.User.Password.Invalid")
_, pushErr := c.eventstore.Push(ctx, commands...)
logging.OnError(pushErr).Error("error create password check failed event")
return err
}
func (c *Commands) passwordWriteModel(ctx context.Context, userID, resourceOwner string) (writeModel *HumanPasswordWriteModel, err error) {
@@ -282,3 +277,16 @@ func (c *Commands) passwordWriteModel(ctx context.Context, userID, resourceOwner
}
return writeModel, nil
}
func convertPasswapErr(err error) error {
if err == nil {
return nil
}
if errors.Is(err, passwap.ErrPasswordMismatch) {
return caos_errs.ThrowInvalidArgument(err, "COMMAND-3M0fs", "Errors.User.Password.Invalid")
}
if errors.Is(err, passwap.ErrPasswordNoChange) {
return caos_errs.ThrowPreconditionFailed(err, "COMMAND-Aesh5", "Errors.User.Password.NotChanged")
}
return caos_errs.ThrowInternal(err, "COMMAND-CahN2", "Errors.Internal")
}

View File

@@ -13,7 +13,7 @@ import (
type HumanPasswordWriteModel struct {
eventstore.WriteModel
Secret *crypto.CryptoValue
EncodedHash string
SecretChangeRequired bool
Code *crypto.CryptoValue
@@ -37,11 +37,11 @@ func (wm *HumanPasswordWriteModel) Reduce() error {
for _, event := range wm.Events {
switch e := event.(type) {
case *user.HumanAddedEvent:
wm.Secret = e.Secret
wm.EncodedHash = user.SecretOrEncodedHash(e.Secret, e.EncodedHash)
wm.SecretChangeRequired = e.ChangeRequired
wm.UserState = domain.UserStateActive
case *user.HumanRegisteredEvent:
wm.Secret = e.Secret
wm.EncodedHash = user.SecretOrEncodedHash(e.Secret, e.EncodedHash)
wm.SecretChangeRequired = e.ChangeRequired
wm.UserState = domain.UserStateActive
case *user.HumanInitialCodeAddedEvent:
@@ -49,7 +49,7 @@ func (wm *HumanPasswordWriteModel) Reduce() error {
case *user.HumanInitializedCheckSucceededEvent:
wm.UserState = domain.UserStateActive
case *user.HumanPasswordChangedEvent:
wm.Secret = e.Secret
wm.EncodedHash = user.SecretOrEncodedHash(e.Secret, e.EncodedHash)
wm.SecretChangeRequired = e.ChangeRequired
wm.Code = nil
wm.PasswordCheckFailedCount = 0
@@ -69,6 +69,8 @@ func (wm *HumanPasswordWriteModel) Reduce() error {
wm.PasswordCheckFailedCount = 0
case *user.UserRemovedEvent:
wm.UserState = domain.UserStateDeleted
case *user.HumanPasswordHashUpdatedEvent:
wm.EncodedHash = e.EncodedHash
}
}
return wm.WriteModel.Reduce()
@@ -98,7 +100,8 @@ func (wm *HumanPasswordWriteModel) Query() *eventstore.SearchQueryBuilder {
user.UserV1PasswordCodeAddedType,
user.UserV1EmailVerifiedType,
user.UserV1PasswordCheckFailedType,
user.UserV1PasswordCheckSucceededType).
user.UserV1PasswordCheckSucceededType,
user.UserV1PasswordHashUpdatedType).
Builder()
if wm.ResourceOwner != "" {

View File

@@ -3,11 +3,13 @@ package command
import (
"context"
"errors"
"io"
"testing"
"time"
"github.com/golang/mock/gomock"
"github.com/stretchr/testify/assert"
"github.com/zitadel/passwap"
"golang.org/x/text/language"
"github.com/zitadel/zitadel/internal/crypto"
@@ -21,9 +23,9 @@ import (
func TestCommandSide_SetOneTimePassword(t *testing.T) {
type fields struct {
eventstore *eventstore.Eventstore
userPasswordAlg crypto.HashAlgorithm
checkPermission domain.PermissionCheck
eventstore *eventstore.Eventstore
userPasswordHasher *crypto.PasswordHasher
checkPermission domain.PermissionCheck
}
type args struct {
ctx context.Context
@@ -101,8 +103,8 @@ func TestCommandSide_SetOneTimePassword(t *testing.T) {
),
),
),
userPasswordAlg: crypto.CreateMockHashAlg(gomock.NewController(t)),
checkPermission: newMockPermissionCheckNotAllowed(),
userPasswordHasher: mockPasswordHasher("x"),
checkPermission: newMockPermissionCheckNotAllowed(),
},
args: args{
ctx: context.Background(),
@@ -160,12 +162,7 @@ func TestCommandSide_SetOneTimePassword(t *testing.T) {
eventFromEventPusher(
user.NewHumanPasswordChangedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
&crypto.CryptoValue{
CryptoType: crypto.TypeHash,
Algorithm: "hash",
KeyID: "",
Crypted: []byte("password"),
},
"$plain$x$password",
true,
"",
),
@@ -173,8 +170,8 @@ func TestCommandSide_SetOneTimePassword(t *testing.T) {
},
),
),
userPasswordAlg: crypto.CreateMockHashAlg(gomock.NewController(t)),
checkPermission: newMockPermissionCheckAllowed(),
userPasswordHasher: mockPasswordHasher("x"),
checkPermission: newMockPermissionCheckAllowed(),
},
args: args{
ctx: context.Background(),
@@ -232,12 +229,7 @@ func TestCommandSide_SetOneTimePassword(t *testing.T) {
eventFromEventPusher(
user.NewHumanPasswordChangedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
&crypto.CryptoValue{
CryptoType: crypto.TypeHash,
Algorithm: "hash",
KeyID: "",
Crypted: []byte("password"),
},
"$plain$x$password",
false,
"",
),
@@ -245,8 +237,8 @@ func TestCommandSide_SetOneTimePassword(t *testing.T) {
},
),
),
userPasswordAlg: crypto.CreateMockHashAlg(gomock.NewController(t)),
checkPermission: newMockPermissionCheckAllowed(),
userPasswordHasher: mockPasswordHasher("x"),
checkPermission: newMockPermissionCheckAllowed(),
},
args: args{
ctx: context.Background(),
@@ -265,9 +257,9 @@ func TestCommandSide_SetOneTimePassword(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
r := &Commands{
eventstore: tt.fields.eventstore,
userPasswordAlg: tt.fields.userPasswordAlg,
checkPermission: tt.fields.checkPermission,
eventstore: tt.fields.eventstore,
userPasswordHasher: tt.fields.userPasswordHasher,
checkPermission: tt.fields.checkPermission,
}
got, err := r.SetPassword(tt.args.ctx, tt.args.resourceOwner, tt.args.userID, tt.args.password, tt.args.oneTime)
if tt.res.err == nil {
@@ -285,9 +277,9 @@ func TestCommandSide_SetOneTimePassword(t *testing.T) {
func TestCommandSide_SetPasswordWithVerifyCode(t *testing.T) {
type fields struct {
eventstore *eventstore.Eventstore
userEncryption crypto.EncryptionAlgorithm
userPasswordAlg crypto.HashAlgorithm
eventstore *eventstore.Eventstore
userEncryption crypto.EncryptionAlgorithm
userPasswordHasher *crypto.PasswordHasher
}
type args struct {
ctx context.Context
@@ -494,12 +486,7 @@ func TestCommandSide_SetPasswordWithVerifyCode(t *testing.T) {
eventFromEventPusher(
user.NewHumanPasswordChangedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
&crypto.CryptoValue{
CryptoType: crypto.TypeHash,
Algorithm: "hash",
KeyID: "",
Crypted: []byte("password"),
},
"$plain$x$password",
false,
"",
),
@@ -507,8 +494,8 @@ func TestCommandSide_SetPasswordWithVerifyCode(t *testing.T) {
},
),
),
userPasswordAlg: crypto.CreateMockHashAlg(gomock.NewController(t)),
userEncryption: crypto.CreateMockEncryptionAlg(gomock.NewController(t)),
userPasswordHasher: mockPasswordHasher("x"),
userEncryption: crypto.CreateMockEncryptionAlg(gomock.NewController(t)),
},
args: args{
ctx: context.Background(),
@@ -527,9 +514,9 @@ func TestCommandSide_SetPasswordWithVerifyCode(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
r := &Commands{
eventstore: tt.fields.eventstore,
userPasswordAlg: tt.fields.userPasswordAlg,
userEncryption: tt.fields.userEncryption,
eventstore: tt.fields.eventstore,
userPasswordHasher: tt.fields.userPasswordHasher,
userEncryption: tt.fields.userEncryption,
}
got, err := r.SetPasswordWithVerifyCode(tt.args.ctx, tt.args.resourceOwner, tt.args.userID, tt.args.code, tt.args.password, tt.args.agentID)
if tt.res.err == nil {
@@ -547,8 +534,7 @@ func TestCommandSide_SetPasswordWithVerifyCode(t *testing.T) {
func TestCommandSide_ChangePassword(t *testing.T) {
type fields struct {
eventstore *eventstore.Eventstore
userPasswordAlg crypto.HashAlgorithm
userPasswordHasher *crypto.PasswordHasher
}
type args struct {
ctx context.Context
@@ -566,67 +552,54 @@ func TestCommandSide_ChangePassword(t *testing.T) {
name string
fields fields
args args
expect []expect
res res
}{
{
name: "userid missing, invalid argument error",
fields: fields{
eventstore: eventstoreExpect(
t,
),
},
name: "userid missing, invalid argument error",
fields: fields{},
args: args{
ctx: context.Background(),
oldPassword: "password",
newPassword: "password1",
resourceOwner: "org1",
},
expect: []expect{},
res: res{
err: caos_errs.IsErrorInvalidArgument,
},
},
{
name: "old password missing, invalid argument error",
fields: fields{
eventstore: eventstoreExpect(
t,
),
},
name: "old password missing, invalid argument error",
fields: fields{},
args: args{
ctx: context.Background(),
userID: "user1",
newPassword: "password1",
resourceOwner: "org1",
},
expect: []expect{},
res: res{
err: caos_errs.IsErrorInvalidArgument,
},
},
{
name: "new password missing, invalid argument error",
fields: fields{
eventstore: eventstoreExpect(
t,
),
},
name: "new password missing, invalid argument error",
fields: fields{},
args: args{
ctx: context.Background(),
userID: "user1",
oldPassword: "password",
resourceOwner: "org1",
},
expect: []expect{},
res: res{
err: caos_errs.IsErrorInvalidArgument,
},
},
{
name: "user not existing, precondition error",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(),
),
},
name: "user not existing, precondition error",
fields: fields{},
args: args{
ctx: context.Background(),
userID: "user1",
@@ -634,6 +607,9 @@ func TestCommandSide_ChangePassword(t *testing.T) {
oldPassword: "password",
newPassword: "password1",
},
expect: []expect{
expectFilter(),
},
res: res{
err: caos_errs.IsPreconditionFailed,
},
@@ -641,26 +617,7 @@ func TestCommandSide_ChangePassword(t *testing.T) {
{
name: "existing password empty, precondition error",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(
eventFromEventPusher(
user.NewHumanAddedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
"username",
"firstname",
"lastname",
"nickname",
"displayname",
language.German,
domain.GenderUnspecified,
"email@test.ch",
true,
),
),
),
),
userPasswordAlg: crypto.CreateMockHashAlg(gomock.NewController(t)),
userPasswordHasher: mockPasswordHasher("x"),
},
args: args{
ctx: context.Background(),
@@ -669,49 +626,32 @@ func TestCommandSide_ChangePassword(t *testing.T) {
newPassword: "password1",
resourceOwner: "org1",
},
expect: []expect{
expectFilter(
eventFromEventPusher(
user.NewHumanAddedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
"username",
"firstname",
"lastname",
"nickname",
"displayname",
language.German,
domain.GenderUnspecified,
"email@test.ch",
true,
),
),
),
},
res: res{
err: caos_errs.IsPreconditionFailed,
},
},
{
name: "password not matching, precondition error",
name: "password not matching, invalid argument error",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(
eventFromEventPusher(
user.NewHumanAddedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
"username",
"firstname",
"lastname",
"nickname",
"displayname",
language.German,
domain.GenderUnspecified,
"email@test.ch",
true,
),
),
eventFromEventPusher(
user.NewHumanEmailVerifiedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
),
),
eventFromEventPusher(
user.NewHumanPasswordChangedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
&crypto.CryptoValue{
CryptoType: crypto.TypeHash,
Algorithm: "hash",
KeyID: "",
Crypted: []byte("password"),
},
false,
"")),
),
),
userPasswordAlg: crypto.CreateMockHashAlg(gomock.NewController(t)),
userPasswordHasher: mockPasswordHasher("x"),
},
args: args{
ctx: context.Background(),
@@ -720,6 +660,47 @@ func TestCommandSide_ChangePassword(t *testing.T) {
newPassword: "password1",
resourceOwner: "org1",
},
expect: []expect{
expectFilter(
eventFromEventPusher(
user.NewHumanAddedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
"username",
"firstname",
"lastname",
"nickname",
"displayname",
language.German,
domain.GenderUnspecified,
"email@test.ch",
true,
),
),
eventFromEventPusher(
user.NewHumanEmailVerifiedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
),
),
eventFromEventPusher(
user.NewHumanPasswordChangedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
"$plain$x$password",
false,
"")),
),
expectFilter(
eventFromEventPusher(
org.NewPasswordComplexityPolicyAddedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
1,
false,
false,
false,
false,
),
),
),
},
res: res{
err: caos_errs.IsErrorInvalidArgument,
},
@@ -727,71 +708,7 @@ func TestCommandSide_ChangePassword(t *testing.T) {
{
name: "change password, ok",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(
eventFromEventPusher(
user.NewHumanAddedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
"username",
"firstname",
"lastname",
"nickname",
"displayname",
language.German,
domain.GenderUnspecified,
"email@test.ch",
true,
),
),
eventFromEventPusher(
user.NewHumanEmailVerifiedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
),
),
eventFromEventPusher(
user.NewHumanPasswordChangedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
&crypto.CryptoValue{
CryptoType: crypto.TypeHash,
Algorithm: "hash",
KeyID: "",
Crypted: []byte("password"),
},
false,
"")),
),
expectFilter(
eventFromEventPusher(
org.NewPasswordComplexityPolicyAddedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
1,
false,
false,
false,
false,
),
),
),
expectPush(
[]*repository.Event{
eventFromEventPusher(
user.NewHumanPasswordChangedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
&crypto.CryptoValue{
CryptoType: crypto.TypeHash,
Algorithm: "hash",
KeyID: "",
Crypted: []byte("password1"),
},
false,
"",
),
),
},
),
),
userPasswordAlg: crypto.CreateMockHashAlg(gomock.NewController(t)),
userPasswordHasher: mockPasswordHasher("x"),
},
args: args{
ctx: context.Background(),
@@ -800,6 +717,59 @@ func TestCommandSide_ChangePassword(t *testing.T) {
oldPassword: "password",
newPassword: "password1",
},
expect: []expect{
expectFilter(
eventFromEventPusher(
user.NewHumanAddedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
"username",
"firstname",
"lastname",
"nickname",
"displayname",
language.German,
domain.GenderUnspecified,
"email@test.ch",
true,
),
),
eventFromEventPusher(
user.NewHumanEmailVerifiedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
),
),
eventFromEventPusher(
user.NewHumanPasswordChangedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
"$plain$x$password",
false,
"")),
),
expectFilter(
eventFromEventPusher(
org.NewPasswordComplexityPolicyAddedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
1,
false,
false,
false,
false,
),
),
),
expectPush(
[]*repository.Event{
eventFromEventPusher(
user.NewHumanPasswordChangedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
"$plain$x$password1",
false,
"",
),
),
},
),
},
res: res{
want: &domain.ObjectDetails{
ResourceOwner: "org1",
@@ -810,8 +780,8 @@ func TestCommandSide_ChangePassword(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
r := &Commands{
eventstore: tt.fields.eventstore,
userPasswordAlg: tt.fields.userPasswordAlg,
eventstore: eventstoreExpect(t, tt.expect...),
userPasswordHasher: tt.fields.userPasswordHasher,
}
got, err := r.ChangePassword(tt.args.ctx, tt.args.resourceOwner, tt.args.userID, tt.args.oldPassword, tt.args.newPassword, tt.args.agentID)
if tt.res.err == nil {
@@ -1123,8 +1093,8 @@ func TestCommandSide_PasswordCodeSent(t *testing.T) {
func TestCommandSide_CheckPassword(t *testing.T) {
type fields struct {
eventstore *eventstore.Eventstore
userPasswordAlg crypto.HashAlgorithm
eventstore *eventstore.Eventstore
userPasswordHasher *crypto.PasswordHasher
}
type args struct {
ctx context.Context
@@ -1320,7 +1290,7 @@ func TestCommandSide_CheckPassword(t *testing.T) {
),
),
),
userPasswordAlg: crypto.CreateMockHashAlg(gomock.NewController(t)),
userPasswordHasher: mockPasswordHasher("x"),
},
args: args{
ctx: context.Background(),
@@ -1383,12 +1353,7 @@ func TestCommandSide_CheckPassword(t *testing.T) {
eventFromEventPusher(
user.NewHumanPasswordChangedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
&crypto.CryptoValue{
CryptoType: crypto.TypeHash,
Algorithm: "hash",
KeyID: "",
Crypted: []byte("password"),
},
"$plain$x$password",
false,
"")),
),
@@ -1406,7 +1371,7 @@ func TestCommandSide_CheckPassword(t *testing.T) {
},
),
),
userPasswordAlg: crypto.CreateMockHashAlg(gomock.NewController(t)),
userPasswordHasher: mockPasswordHasher("x"),
},
args: args{
ctx: context.Background(),
@@ -1474,12 +1439,7 @@ func TestCommandSide_CheckPassword(t *testing.T) {
eventFromEventPusher(
user.NewHumanPasswordChangedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
&crypto.CryptoValue{
CryptoType: crypto.TypeHash,
Algorithm: "hash",
KeyID: "",
Crypted: []byte("password"),
},
"$plain$x$password",
false,
"")),
),
@@ -1502,7 +1462,7 @@ func TestCommandSide_CheckPassword(t *testing.T) {
},
),
),
userPasswordAlg: crypto.CreateMockHashAlg(gomock.NewController(t)),
userPasswordHasher: mockPasswordHasher("x"),
},
args: args{
ctx: context.Background(),
@@ -1572,12 +1532,7 @@ func TestCommandSide_CheckPassword(t *testing.T) {
eventFromEventPusher(
user.NewHumanPasswordChangedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
&crypto.CryptoValue{
CryptoType: crypto.TypeHash,
Algorithm: "hash",
KeyID: "",
Crypted: []byte("password"),
},
"$plain$x$password",
false,
"")),
),
@@ -1595,7 +1550,195 @@ func TestCommandSide_CheckPassword(t *testing.T) {
},
),
),
userPasswordAlg: crypto.CreateMockHashAlg(gomock.NewController(t)),
userPasswordHasher: mockPasswordHasher("x"),
},
args: args{
ctx: context.Background(),
userID: "user1",
resourceOwner: "org1",
password: "password",
authReq: &domain.AuthRequest{
ID: "request1",
AgentID: "agent1",
},
},
res: res{},
},
{
name: "check password, ok, updated hash",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(
eventFromEventPusher(
org.NewLoginPolicyAddedEvent(context.Background(),
&org.NewAggregate("org1").Aggregate,
true,
false,
false,
false,
false,
false,
false,
false,
false,
domain.PasswordlessTypeNotAllowed,
"",
time.Hour*1,
time.Hour*2,
time.Hour*3,
time.Hour*4,
time.Hour*5,
),
),
),
expectFilter(
eventFromEventPusher(
user.NewHumanAddedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
"username",
"firstname",
"lastname",
"nickname",
"displayname",
language.German,
domain.GenderUnspecified,
"email@test.ch",
true,
),
),
eventFromEventPusher(
user.NewHumanEmailVerifiedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
),
),
eventFromEventPusher(
user.NewHumanPasswordChangedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
"$plain$v$password",
false,
"")),
),
expectPush(
[]*repository.Event{
eventFromEventPusher(
user.NewHumanPasswordCheckSucceededEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
&user.AuthRequestInfo{
ID: "request1",
UserAgentID: "agent1",
},
),
),
eventFromEventPusher(
user.NewHumanPasswordHashUpdatedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
"$plain$x$password",
),
),
},
),
),
userPasswordHasher: mockPasswordHasher("x"),
},
args: args{
ctx: context.Background(),
userID: "user1",
resourceOwner: "org1",
password: "password",
authReq: &domain.AuthRequest{
ID: "request1",
AgentID: "agent1",
},
},
res: res{},
},
{
name: "regression test old version event",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(
eventFromEventPusher(
org.NewLoginPolicyAddedEvent(context.Background(),
&org.NewAggregate("org1").Aggregate,
true,
false,
false,
false,
false,
false,
false,
false,
false,
domain.PasswordlessTypeNotAllowed,
"",
time.Hour*1,
time.Hour*2,
time.Hour*3,
time.Hour*4,
time.Hour*5,
),
),
),
expectFilter(
eventFromEventPusher(
user.NewHumanAddedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
"username",
"firstname",
"lastname",
"nickname",
"displayname",
language.German,
domain.GenderUnspecified,
"email@test.ch",
true,
),
),
eventFromEventPusher(
user.NewHumanEmailVerifiedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
),
),
eventFromEventPusher(
&user.HumanPasswordChangedEvent{
BaseEvent: *eventstore.NewBaseEventForPush(
context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
user.HumanPasswordChangedType,
),
Secret: &crypto.CryptoValue{
CryptoType: crypto.TypeHash,
Algorithm: "plain",
KeyID: "",
Crypted: []byte("$plain$v$password"),
},
ChangeRequired: false,
},
),
),
expectPush(
[]*repository.Event{
eventFromEventPusher(
user.NewHumanPasswordCheckSucceededEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
&user.AuthRequestInfo{
ID: "request1",
UserAgentID: "agent1",
},
),
),
eventFromEventPusher(
user.NewHumanPasswordHashUpdatedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
"$plain$x$password",
),
),
},
),
),
userPasswordHasher: mockPasswordHasher("x"),
},
args: args{
ctx: context.Background(),
@@ -1613,8 +1756,8 @@ func TestCommandSide_CheckPassword(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
r := &Commands{
eventstore: tt.fields.eventstore,
userPasswordAlg: tt.fields.userPasswordAlg,
eventstore: tt.fields.eventstore,
userPasswordHasher: tt.fields.userPasswordHasher,
}
err := r.HumanCheckPassword(tt.args.ctx, tt.args.resourceOwner, tt.args.userID, tt.args.password, tt.args.authReq, tt.args.lockoutPolicy)
if tt.res.err == nil {
@@ -1626,3 +1769,41 @@ func TestCommandSide_CheckPassword(t *testing.T) {
})
}
}
func Test_convertPasswapErr(t *testing.T) {
type args struct {
err error
}
tests := []struct {
name string
args args
wantErr error
}{
{
name: "nil",
args: args{nil},
wantErr: nil,
},
{
name: "mismatch",
args: args{passwap.ErrPasswordMismatch},
wantErr: caos_errs.ThrowInvalidArgument(passwap.ErrPasswordMismatch, "COMMAND-3M0fs", "Errors.User.Password.Invalid"),
},
{
name: "no change",
args: args{passwap.ErrPasswordNoChange},
wantErr: caos_errs.ThrowPreconditionFailed(passwap.ErrPasswordNoChange, "COMMAND-Aesh5", "Errors.User.Password.NotChanged"),
},
{
name: "other",
args: args{io.ErrClosedPipe},
wantErr: caos_errs.ThrowInternal(io.ErrClosedPipe, "COMMAND-CahN2", "Errors.Internal"),
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := convertPasswapErr(tt.args.err)
assert.ErrorIs(t, err, tt.wantErr)
})
}
}

View File

@@ -26,11 +26,11 @@ import (
func TestCommandSide_AddHuman(t *testing.T) {
type fields struct {
eventstore func(t *testing.T) *eventstore.Eventstore
idGenerator id.Generator
userPasswordAlg crypto.HashAlgorithm
codeAlg crypto.EncryptionAlgorithm
newCode cryptoCodeFunc
eventstore func(t *testing.T) *eventstore.Eventstore
idGenerator id.Generator
userPasswordHasher *crypto.PasswordHasher
codeAlg crypto.EncryptionAlgorithm
newCode cryptoCodeFunc
}
type args struct {
ctx context.Context
@@ -104,7 +104,7 @@ func TestCommandSide_AddHuman(t *testing.T) {
eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(
newAddHumanEvent("password", true, ""),
newAddHumanEvent("$plain$x$password", true, ""),
),
),
),
@@ -307,7 +307,7 @@ func TestCommandSide_AddHuman(t *testing.T) {
expectPush(
[]*repository.Event{
eventFromEventPusher(
newAddHumanEvent("password", false, ""),
newAddHumanEvent("$plain$x$password", false, ""),
),
eventFromEventPusher(
user.NewHumanInitialCodeAddedEvent(context.Background(),
@@ -325,10 +325,10 @@ func TestCommandSide_AddHuman(t *testing.T) {
uniqueConstraintsFromEventConstraint(user.NewAddUsernameUniqueConstraint("username", "org1", true)),
),
),
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "user1"),
userPasswordAlg: crypto.CreateMockHashAlg(gomock.NewController(t)),
codeAlg: crypto.CreateMockEncryptionAlg(gomock.NewController(t)),
newCode: mockCode("userinit", time.Hour),
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "user1"),
userPasswordHasher: mockPasswordHasher("x"),
codeAlg: crypto.CreateMockEncryptionAlg(gomock.NewController(t)),
newCode: mockCode("userinit", time.Hour),
},
args: args{
ctx: context.Background(),
@@ -383,7 +383,7 @@ func TestCommandSide_AddHuman(t *testing.T) {
expectPush(
[]*repository.Event{
eventFromEventPusher(
newAddHumanEvent("password", false, ""),
newAddHumanEvent("$plain$x$password", false, ""),
),
eventFromEventPusher(
user.NewHumanEmailCodeAddedEventV2(context.Background(),
@@ -403,10 +403,10 @@ func TestCommandSide_AddHuman(t *testing.T) {
uniqueConstraintsFromEventConstraint(user.NewAddUsernameUniqueConstraint("username", "org1", true)),
),
),
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "user1"),
userPasswordAlg: crypto.CreateMockHashAlg(gomock.NewController(t)),
codeAlg: crypto.CreateMockEncryptionAlg(gomock.NewController(t)),
newCode: mockCode("emailCode", time.Hour),
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "user1"),
userPasswordHasher: mockPasswordHasher("x"),
codeAlg: crypto.CreateMockEncryptionAlg(gomock.NewController(t)),
newCode: mockCode("emailCode", time.Hour),
},
args: args{
ctx: context.Background(),
@@ -462,7 +462,7 @@ func TestCommandSide_AddHuman(t *testing.T) {
expectPush(
[]*repository.Event{
eventFromEventPusher(
newAddHumanEvent("password", false, ""),
newAddHumanEvent("$plain$x$password", false, ""),
),
eventFromEventPusher(
user.NewHumanEmailCodeAddedEventV2(context.Background(),
@@ -482,10 +482,10 @@ func TestCommandSide_AddHuman(t *testing.T) {
uniqueConstraintsFromEventConstraint(user.NewAddUsernameUniqueConstraint("username", "org1", true)),
),
),
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "user1"),
userPasswordAlg: crypto.CreateMockHashAlg(gomock.NewController(t)),
codeAlg: crypto.CreateMockEncryptionAlg(gomock.NewController(t)),
newCode: mockCode("emailCode", time.Hour),
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "user1"),
userPasswordHasher: mockPasswordHasher("x"),
codeAlg: crypto.CreateMockEncryptionAlg(gomock.NewController(t)),
newCode: mockCode("emailCode", time.Hour),
},
args: args{
ctx: context.Background(),
@@ -542,7 +542,7 @@ func TestCommandSide_AddHuman(t *testing.T) {
expectPush(
[]*repository.Event{
eventFromEventPusher(
newAddHumanEvent("password", true, ""),
newAddHumanEvent("$plain$x$password", true, ""),
),
eventFromEventPusher(
user.NewHumanEmailVerifiedEvent(context.Background(),
@@ -552,9 +552,9 @@ func TestCommandSide_AddHuman(t *testing.T) {
uniqueConstraintsFromEventConstraint(user.NewAddUsernameUniqueConstraint("username", "org1", true)),
),
),
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "user1"),
userPasswordAlg: crypto.CreateMockHashAlg(gomock.NewController(t)),
codeAlg: crypto.CreateMockEncryptionAlg(gomock.NewController(t)),
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "user1"),
userPasswordHasher: mockPasswordHasher("x"),
codeAlg: crypto.CreateMockEncryptionAlg(gomock.NewController(t)),
},
args: args{
ctx: context.Background(),
@@ -611,7 +611,7 @@ func TestCommandSide_AddHuman(t *testing.T) {
expectPush(
[]*repository.Event{
eventFromEventPusher(
newAddHumanEvent("password", true, ""),
newAddHumanEvent("$plain$x$password", true, ""),
),
eventFromEventPusher(
user.NewHumanEmailVerifiedEvent(context.Background(),
@@ -621,9 +621,9 @@ func TestCommandSide_AddHuman(t *testing.T) {
uniqueConstraintsFromEventConstraint(user.NewAddUsernameUniqueConstraint("username", "org1", true)),
),
),
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "user1"),
userPasswordAlg: crypto.CreateMockHashAlg(gomock.NewController(t)),
codeAlg: crypto.CreateMockEncryptionAlg(gomock.NewController(t)),
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "user1"),
userPasswordHasher: mockPasswordHasher("x"),
codeAlg: crypto.CreateMockEncryptionAlg(gomock.NewController(t)),
},
args: args{
ctx: context.Background(),
@@ -680,7 +680,7 @@ func TestCommandSide_AddHuman(t *testing.T) {
expectPush(
[]*repository.Event{
eventFromEventPusher(
newAddHumanEvent("password", true, ""),
newAddHumanEvent("$plain$x$password", true, ""),
),
eventFromEventPusher(
user.NewHumanEmailVerifiedEvent(context.Background(),
@@ -690,9 +690,9 @@ func TestCommandSide_AddHuman(t *testing.T) {
uniqueConstraintsFromEventConstraint(user.NewAddUsernameUniqueConstraint("username", "org1", false)),
),
),
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "user1"),
userPasswordAlg: crypto.CreateMockHashAlg(gomock.NewController(t)),
codeAlg: crypto.CreateMockEncryptionAlg(gomock.NewController(t)),
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "user1"),
userPasswordHasher: mockPasswordHasher("x"),
codeAlg: crypto.CreateMockEncryptionAlg(gomock.NewController(t)),
},
args: args{
ctx: context.Background(),
@@ -743,9 +743,9 @@ func TestCommandSide_AddHuman(t *testing.T) {
),
),
),
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "user1"),
userPasswordAlg: crypto.CreateMockHashAlg(gomock.NewController(t)),
codeAlg: crypto.CreateMockEncryptionAlg(gomock.NewController(t)),
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "user1"),
userPasswordHasher: mockPasswordHasher("x"),
codeAlg: crypto.CreateMockEncryptionAlg(gomock.NewController(t)),
},
args: args{
ctx: context.Background(),
@@ -822,12 +822,7 @@ func TestCommandSide_AddHuman(t *testing.T) {
"email@test.ch",
true,
)
event.AddPasswordData(&crypto.CryptoValue{
CryptoType: crypto.TypeHash,
Algorithm: "hash",
KeyID: "",
Crypted: []byte("password"),
}, true)
event.AddPasswordData("$plain$x$password", true)
return event
}(),
),
@@ -839,9 +834,9 @@ func TestCommandSide_AddHuman(t *testing.T) {
uniqueConstraintsFromEventConstraint(user.NewAddUsernameUniqueConstraint("username@test.ch", "org1", false)),
),
),
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "user1"),
userPasswordAlg: crypto.CreateMockHashAlg(gomock.NewController(t)),
codeAlg: crypto.CreateMockEncryptionAlg(gomock.NewController(t)),
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "user1"),
userPasswordHasher: mockPasswordHasher("x"),
codeAlg: crypto.CreateMockEncryptionAlg(gomock.NewController(t)),
},
args: args{
ctx: context.Background(),
@@ -899,7 +894,7 @@ func TestCommandSide_AddHuman(t *testing.T) {
expectPush(
[]*repository.Event{
eventFromEventPusher(
newAddHumanEvent("password", false, "+41711234567"),
newAddHumanEvent("$plain$x$password", false, "+41711234567"),
),
eventFromEventPusher(
user.NewHumanEmailVerifiedEvent(
@@ -921,10 +916,10 @@ func TestCommandSide_AddHuman(t *testing.T) {
uniqueConstraintsFromEventConstraint(user.NewAddUsernameUniqueConstraint("username", "org1", true)),
),
),
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "user1"),
userPasswordAlg: crypto.CreateMockHashAlg(gomock.NewController(t)),
codeAlg: crypto.CreateMockEncryptionAlg(gomock.NewController(t)),
newCode: mockCode("phonecode", time.Hour),
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "user1"),
userPasswordHasher: mockPasswordHasher("x"),
codeAlg: crypto.CreateMockEncryptionAlg(gomock.NewController(t)),
newCode: mockCode("phonecode", time.Hour),
},
args: args{
ctx: context.Background(),
@@ -1107,11 +1102,11 @@ func TestCommandSide_AddHuman(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
r := &Commands{
eventstore: tt.fields.eventstore(t),
userPasswordAlg: tt.fields.userPasswordAlg,
userEncryption: tt.fields.codeAlg,
idGenerator: tt.fields.idGenerator,
newCode: tt.fields.newCode,
eventstore: tt.fields.eventstore(t),
userPasswordHasher: tt.fields.userPasswordHasher,
userEncryption: tt.fields.codeAlg,
idGenerator: tt.fields.idGenerator,
newCode: tt.fields.newCode,
}
err := r.AddHuman(tt.args.ctx, tt.args.orgID, tt.args.human, tt.args.allowInitMail)
if tt.res.err == nil {
@@ -1133,9 +1128,9 @@ func TestCommandSide_AddHuman(t *testing.T) {
func TestCommandSide_ImportHuman(t *testing.T) {
type fields struct {
eventstore *eventstore.Eventstore
idGenerator id.Generator
userPasswordAlg crypto.HashAlgorithm
eventstore *eventstore.Eventstore
idGenerator id.Generator
userPasswordHasher *crypto.PasswordHasher
}
type args struct {
ctx context.Context
@@ -1319,7 +1314,7 @@ func TestCommandSide_ImportHuman(t *testing.T) {
expectPush(
[]*repository.Event{
eventFromEventPusher(
newAddHumanEvent("password", true, ""),
newAddHumanEvent("$plain$x$password", true, ""),
),
eventFromEventPusher(
user.NewHumanInitialCodeAddedEvent(context.Background(),
@@ -1337,8 +1332,8 @@ func TestCommandSide_ImportHuman(t *testing.T) {
uniqueConstraintsFromEventConstraint(user.NewAddUsernameUniqueConstraint("username", "org1", true)),
),
),
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "user1"),
userPasswordAlg: crypto.CreateMockHashAlg(gomock.NewController(t)),
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "user1"),
userPasswordHasher: mockPasswordHasher("x"),
},
args: args{
ctx: context.Background(),
@@ -1410,7 +1405,7 @@ func TestCommandSide_ImportHuman(t *testing.T) {
expectPush(
[]*repository.Event{
eventFromEventPusher(
newAddHumanEvent("password", false, ""),
newAddHumanEvent("$plain$x$password", false, ""),
),
eventFromEventPusher(
user.NewHumanEmailVerifiedEvent(context.Background(),
@@ -1420,8 +1415,8 @@ func TestCommandSide_ImportHuman(t *testing.T) {
uniqueConstraintsFromEventConstraint(user.NewAddUsernameUniqueConstraint("username", "org1", true)),
),
),
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "user1"),
userPasswordAlg: crypto.CreateMockHashAlg(gomock.NewController(t)),
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "user1"),
userPasswordHasher: mockPasswordHasher("x"),
},
args: args{
ctx: context.Background(),
@@ -1519,8 +1514,8 @@ func TestCommandSide_ImportHuman(t *testing.T) {
uniqueConstraintsFromEventConstraint(user.NewAddUsernameUniqueConstraint("username", "org1", true)),
),
),
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "user1", "code1"),
userPasswordAlg: crypto.CreateMockHashAlg(gomock.NewController(t)),
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "user1", "code1"),
userPasswordHasher: mockPasswordHasher("x"),
},
args: args{
ctx: context.Background(),
@@ -1603,7 +1598,7 @@ func TestCommandSide_ImportHuman(t *testing.T) {
expectPush(
[]*repository.Event{
eventFromEventPusher(
newAddHumanEvent("password", false, ""),
newAddHumanEvent("$plain$x$password", false, ""),
),
eventFromEventPusher(
user.NewHumanEmailVerifiedEvent(context.Background(),
@@ -1626,8 +1621,8 @@ func TestCommandSide_ImportHuman(t *testing.T) {
uniqueConstraintsFromEventConstraint(user.NewAddUsernameUniqueConstraint("username", "org1", true)),
),
),
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "user1", "code1"),
userPasswordAlg: crypto.CreateMockHashAlg(gomock.NewController(t)),
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "user1", "code1"),
userPasswordHasher: mockPasswordHasher("x"),
},
args: args{
ctx: context.Background(),
@@ -1713,7 +1708,7 @@ func TestCommandSide_ImportHuman(t *testing.T) {
expectPush(
[]*repository.Event{
eventFromEventPusher(
newAddHumanEvent("password", false, "+41711234567"),
newAddHumanEvent("$plain$x$password", false, "+41711234567"),
),
eventFromEventPusher(
user.NewHumanInitialCodeAddedEvent(context.Background(),
@@ -1741,8 +1736,8 @@ func TestCommandSide_ImportHuman(t *testing.T) {
uniqueConstraintsFromEventConstraint(user.NewAddUsernameUniqueConstraint("username", "org1", true)),
),
),
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "user1"),
userPasswordAlg: crypto.CreateMockHashAlg(gomock.NewController(t)),
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "user1"),
userPasswordHasher: mockPasswordHasher("x"),
},
args: args{
ctx: context.Background(),
@@ -1820,7 +1815,7 @@ func TestCommandSide_ImportHuman(t *testing.T) {
expectPush(
[]*repository.Event{
eventFromEventPusher(
newAddHumanEvent("password", false, "+41711234567"),
newAddHumanEvent("$plain$x$password", false, "+41711234567"),
),
eventFromEventPusher(
user.NewHumanInitialCodeAddedEvent(context.Background(),
@@ -1842,8 +1837,8 @@ func TestCommandSide_ImportHuman(t *testing.T) {
uniqueConstraintsFromEventConstraint(user.NewAddUsernameUniqueConstraint("username", "org1", true)),
),
),
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "user1"),
userPasswordAlg: crypto.CreateMockHashAlg(gomock.NewController(t)),
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "user1"),
userPasswordHasher: mockPasswordHasher("x"),
},
args: args{
ctx: context.Background(),
@@ -1973,8 +1968,8 @@ func TestCommandSide_ImportHuman(t *testing.T) {
uniqueConstraintsFromEventConstraint(user.NewAddUserIDPLinkUniqueConstraint("idpID", "externalID")),
),
),
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "user1"),
userPasswordAlg: crypto.CreateMockHashAlg(gomock.NewController(t)),
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "user1"),
userPasswordHasher: mockPasswordHasher("x"),
},
args: args{
ctx: context.Background(),
@@ -2025,9 +2020,9 @@ func TestCommandSide_ImportHuman(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
r := &Commands{
eventstore: tt.fields.eventstore,
idGenerator: tt.fields.idGenerator,
userPasswordAlg: tt.fields.userPasswordAlg,
eventstore: tt.fields.eventstore,
idGenerator: tt.fields.idGenerator,
userPasswordHasher: tt.fields.userPasswordHasher,
}
gotHuman, gotCode, err := r.ImportHuman(tt.args.ctx, tt.args.orgID, tt.args.human, tt.args.passwordless, tt.args.links, tt.args.secretGenerator, tt.args.secretGenerator, tt.args.secretGenerator, tt.args.secretGenerator)
if tt.res.err == nil {
@@ -2046,9 +2041,9 @@ func TestCommandSide_ImportHuman(t *testing.T) {
func TestCommandSide_RegisterHuman(t *testing.T) {
type fields struct {
eventstore *eventstore.Eventstore
idGenerator id.Generator
userPasswordAlg crypto.HashAlgorithm
eventstore *eventstore.Eventstore
idGenerator id.Generator
userPasswordHasher *crypto.PasswordHasher
}
type args struct {
ctx context.Context
@@ -2521,7 +2516,7 @@ func TestCommandSide_RegisterHuman(t *testing.T) {
expectPush(
[]*repository.Event{
eventFromEventPusher(
newRegisterHumanEvent("email@test.ch", "password", false, ""),
newRegisterHumanEvent("email@test.ch", "$plain$x$password", false, ""),
),
eventFromEventPusher(
user.NewHumanInitialCodeAddedEvent(context.Background(),
@@ -2539,8 +2534,8 @@ func TestCommandSide_RegisterHuman(t *testing.T) {
uniqueConstraintsFromEventConstraint(user.NewAddUsernameUniqueConstraint("email@test.ch", "org1", false)),
),
),
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "user1"),
userPasswordAlg: crypto.CreateMockHashAlg(gomock.NewController(t)),
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "user1"),
userPasswordHasher: mockPasswordHasher("x"),
},
args: args{
ctx: context.Background(),
@@ -2632,7 +2627,7 @@ func TestCommandSide_RegisterHuman(t *testing.T) {
expectPush(
[]*repository.Event{
eventFromEventPusher(
newRegisterHumanEvent("username", "password", false, ""),
newRegisterHumanEvent("username", "$plain$x$password", false, ""),
),
eventFromEventPusher(
user.NewHumanInitialCodeAddedEvent(context.Background(),
@@ -2650,8 +2645,8 @@ func TestCommandSide_RegisterHuman(t *testing.T) {
uniqueConstraintsFromEventConstraint(user.NewAddUsernameUniqueConstraint("username", "org1", false)),
),
),
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "user1"),
userPasswordAlg: crypto.CreateMockHashAlg(gomock.NewController(t)),
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "user1"),
userPasswordHasher: mockPasswordHasher("x"),
},
args: args{
ctx: context.Background(),
@@ -2744,7 +2739,7 @@ func TestCommandSide_RegisterHuman(t *testing.T) {
expectPush(
[]*repository.Event{
eventFromEventPusher(
newRegisterHumanEvent("username", "password", false, ""),
newRegisterHumanEvent("username", "$plain$x$password", false, ""),
),
eventFromEventPusher(
user.NewHumanInitialCodeAddedEvent(context.Background(),
@@ -2762,8 +2757,8 @@ func TestCommandSide_RegisterHuman(t *testing.T) {
uniqueConstraintsFromEventConstraint(user.NewAddUsernameUniqueConstraint("username", "org1", true)),
),
),
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "user1"),
userPasswordAlg: crypto.CreateMockHashAlg(gomock.NewController(t)),
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "user1"),
userPasswordHasher: mockPasswordHasher("x"),
},
args: args{
ctx: context.Background(),
@@ -2856,7 +2851,7 @@ func TestCommandSide_RegisterHuman(t *testing.T) {
expectPush(
[]*repository.Event{
eventFromEventPusher(
newRegisterHumanEvent("username", "password", false, ""),
newRegisterHumanEvent("username", "$plain$x$password", false, ""),
),
eventFromEventPusher(
user.NewHumanEmailVerifiedEvent(context.Background(),
@@ -2866,8 +2861,8 @@ func TestCommandSide_RegisterHuman(t *testing.T) {
uniqueConstraintsFromEventConstraint(user.NewAddUsernameUniqueConstraint("username", "org1", true)),
),
),
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "user1"),
userPasswordAlg: crypto.CreateMockHashAlg(gomock.NewController(t)),
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "user1"),
userPasswordHasher: mockPasswordHasher("x"),
},
args: args{
ctx: context.Background(),
@@ -2962,7 +2957,7 @@ func TestCommandSide_RegisterHuman(t *testing.T) {
expectPush(
[]*repository.Event{
eventFromEventPusher(
newRegisterHumanEvent("username", "password", false, "+41711234567"),
newRegisterHumanEvent("username", "$plain$x$password", false, "+41711234567"),
),
eventFromEventPusher(
user.NewHumanInitialCodeAddedEvent(context.Background(),
@@ -2990,8 +2985,8 @@ func TestCommandSide_RegisterHuman(t *testing.T) {
uniqueConstraintsFromEventConstraint(user.NewAddUsernameUniqueConstraint("username", "org1", true)),
),
),
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "user1"),
userPasswordAlg: crypto.CreateMockHashAlg(gomock.NewController(t)),
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "user1"),
userPasswordHasher: mockPasswordHasher("x"),
},
args: args{
ctx: context.Background(),
@@ -3090,7 +3085,7 @@ func TestCommandSide_RegisterHuman(t *testing.T) {
expectPush(
[]*repository.Event{
eventFromEventPusher(
newRegisterHumanEvent("username", "password", false, "+41711234567"),
newRegisterHumanEvent("username", "$plain$x$password", false, "+41711234567"),
),
eventFromEventPusher(
user.NewHumanInitialCodeAddedEvent(context.Background(),
@@ -3112,8 +3107,8 @@ func TestCommandSide_RegisterHuman(t *testing.T) {
uniqueConstraintsFromEventConstraint(user.NewAddUsernameUniqueConstraint("username", "org1", true)),
),
),
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "user1"),
userPasswordAlg: crypto.CreateMockHashAlg(gomock.NewController(t)),
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "user1"),
userPasswordHasher: mockPasswordHasher("x"),
},
args: args{
ctx: context.Background(),
@@ -3245,7 +3240,7 @@ func TestCommandSide_RegisterHuman(t *testing.T) {
expectPush(
[]*repository.Event{
eventFromEventPusher(
newRegisterHumanEvent("username", "password", false, ""),
newRegisterHumanEvent("username", "$plain$x$password", false, ""),
),
eventFromEventPusher(
user.NewUserIDPLinkAddedEvent(context.Background(),
@@ -3264,8 +3259,8 @@ func TestCommandSide_RegisterHuman(t *testing.T) {
uniqueConstraintsFromEventConstraint(user.NewAddUserIDPLinkUniqueConstraint("idpID", "externalID")),
),
),
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "user1"),
userPasswordAlg: crypto.CreateMockHashAlg(gomock.NewController(t)),
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "user1"),
userPasswordHasher: mockPasswordHasher("x"),
},
args: args{
ctx: context.Background(),
@@ -3316,9 +3311,9 @@ func TestCommandSide_RegisterHuman(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
r := &Commands{
eventstore: tt.fields.eventstore,
idGenerator: tt.fields.idGenerator,
userPasswordAlg: tt.fields.userPasswordAlg,
eventstore: tt.fields.eventstore,
idGenerator: tt.fields.idGenerator,
userPasswordHasher: tt.fields.userPasswordHasher,
}
got, err := r.RegisterHuman(tt.args.ctx, tt.args.orgID, tt.args.human, tt.args.link, tt.args.orgMemberRoles, tt.args.secretGenerator, tt.args.secretGenerator, tt.args.secretGenerator)
if tt.res.err == nil {
@@ -3657,13 +3652,7 @@ func newAddHumanEvent(password string, changeRequired bool, phone string) *user.
true,
)
if password != "" {
event.AddPasswordData(&crypto.CryptoValue{
CryptoType: crypto.TypeHash,
Algorithm: "hash",
KeyID: "",
Crypted: []byte(password),
},
changeRequired)
event.AddPasswordData(password, changeRequired)
}
if phone != "" {
event.AddPhoneData(domain.PhoneNumber(phone))
@@ -3685,13 +3674,7 @@ func newRegisterHumanEvent(username, password string, changeRequired bool, phone
true,
)
if password != "" {
event.AddPasswordData(&crypto.CryptoValue{
CryptoType: crypto.TypeHash,
Algorithm: "hash",
KeyID: "",
Crypted: []byte(password),
},
changeRequired)
event.AddPasswordData(password, changeRequired)
}
if phone != "" {
event.AddPhoneData(domain.PhoneNumber(phone))
@@ -3706,7 +3689,7 @@ func TestAddHumanCommand(t *testing.T) {
type args struct {
human *AddHuman
orgID string
passwordAlg crypto.HashAlgorithm
hasher *crypto.PasswordHasher
filter preparation.FilterToQueryReducer
codeAlg crypto.EncryptionAlgorithm
allowInitMail bool
@@ -3763,6 +3746,24 @@ func TestAddHumanCommand(t *testing.T) {
ValidationErr: caos_errs.ThrowInvalidArgument(nil, "USER-4hB7d", "Errors.User.Profile.LastNameEmpty"),
},
},
{
name: "unsupported password hash encoding",
args: args{
human: &AddHuman{
Email: Email{Address: "support@zitadel.com", Verified: true},
PreferredLanguage: language.English,
FirstName: "gigi",
LastName: "giraffe",
EncodedPasswordHash: "$foo$x$password",
Username: "username",
},
orgID: "ro",
hasher: mockPasswordHasher("x"),
},
want: Want{
ValidationErr: caos_errs.ThrowInvalidArgument(nil, "USER-JDk4t", "Errors.User.Password.NotSupported"),
},
},
{
name: "invalid password",
fields: fields{
@@ -3828,9 +3829,9 @@ func TestAddHumanCommand(t *testing.T) {
Password: "password",
Username: "username",
},
orgID: "ro",
passwordAlg: crypto.CreateMockHashAlg(gomock.NewController(t)),
codeAlg: crypto.CreateMockEncryptionAlg(gomock.NewController(t)),
orgID: "ro",
hasher: mockPasswordHasher("x"),
codeAlg: crypto.CreateMockEncryptionAlg(gomock.NewController(t)),
filter: NewMultiFilter().Append(
func(ctx context.Context, queryFactory *eventstore.SearchQueryBuilder) ([]eventstore.Event, error) {
return []eventstore.Event{}, nil
@@ -3879,12 +3880,79 @@ func TestAddHumanCommand(t *testing.T) {
"support@zitadel.com",
true,
)
event.AddPasswordData(&crypto.CryptoValue{
CryptoType: crypto.TypeHash,
Algorithm: "hash",
KeyID: "",
Crypted: []byte("password"),
}, false)
event.AddPasswordData("$plain$x$password", false)
return event
}(),
user.NewHumanEmailVerifiedEvent(context.Background(), &agg.Aggregate),
},
},
},
{
name: "hashed password",
fields: fields{
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "id"),
},
args: args{
human: &AddHuman{
Email: Email{Address: "support@zitadel.com", Verified: true},
PreferredLanguage: language.English,
FirstName: "gigi",
LastName: "giraffe",
EncodedPasswordHash: "$plain$x$password",
Username: "username",
},
orgID: "ro",
hasher: mockPasswordHasher("x"),
codeAlg: crypto.CreateMockEncryptionAlg(gomock.NewController(t)),
filter: NewMultiFilter().Append(
func(ctx context.Context, queryFactory *eventstore.SearchQueryBuilder) ([]eventstore.Event, error) {
return []eventstore.Event{}, nil
}).
Append(
func(ctx context.Context, queryFactory *eventstore.SearchQueryBuilder) ([]eventstore.Event, error) {
return []eventstore.Event{
org.NewDomainPolicyAddedEvent(
ctx,
&org.NewAggregate("id").Aggregate,
true,
true,
true,
),
}, nil
}).
Append(
func(ctx context.Context, queryFactory *eventstore.SearchQueryBuilder) ([]eventstore.Event, error) {
return []eventstore.Event{
org.NewPasswordComplexityPolicyAddedEvent(
ctx,
&org.NewAggregate("id").Aggregate,
2,
false,
false,
false,
false,
),
}, nil
}).
Filter(),
},
want: Want{
Commands: []eventstore.Command{
func() *user.HumanAddedEvent {
event := user.NewHumanAddedEvent(
context.Background(),
&agg.Aggregate,
"username",
"gigi",
"giraffe",
"",
"gigi giraffe",
language.English,
0,
"support@zitadel.com",
true,
)
event.AddPasswordData("$plain$x$password", false)
return event
}(),
user.NewHumanEmailVerifiedEvent(context.Background(), &agg.Aggregate),
@@ -3897,7 +3965,7 @@ func TestAddHumanCommand(t *testing.T) {
c := &Commands{
idGenerator: tt.fields.idGenerator,
}
AssertValidation(t, context.Background(), c.AddHumanCommand(tt.args.human, tt.args.orgID, tt.args.passwordAlg, tt.args.codeAlg, tt.args.allowInitMail), tt.args.filter, tt.want)
AssertValidation(t, context.Background(), c.AddHumanCommand(tt.args.human, tt.args.orgID, tt.args.hasher, tt.args.codeAlg, tt.args.allowInitMail), tt.args.filter, tt.want)
})
}
}

View File

@@ -113,7 +113,7 @@ func prepareRemoveMachineSecret(a *user.Aggregate) preparation.Validation {
func (c *Commands) VerifyMachineSecret(ctx context.Context, userID string, resourceOwner string, secret string) (*domain.ObjectDetails, error) {
agg := user.NewAggregate(userID, resourceOwner)
cmds, err := preparation.PrepareCommands(ctx, c.eventstore.Filter, prepareVerifyMachineSecret(agg, secret, c.userPasswordAlg))
cmds, err := preparation.PrepareCommands(ctx, c.eventstore.Filter, prepareVerifyMachineSecret(agg, secret, c.codeAlg))
if err != nil {
return nil, err
}

View File

@@ -529,8 +529,8 @@ func TestCommandSide_VerifyMachineSecret(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
r := &Commands{
eventstore: tt.fields.eventstore,
userPasswordAlg: crypto.NewBCrypt(14),
eventstore: tt.fields.eventstore,
codeAlg: crypto.NewBCrypt(14),
}
got, err := r.VerifyMachineSecret(tt.args.ctx, tt.args.userID, tt.args.resourceOwner, tt.args.secret)
if tt.res.err == nil {