fix: review changes

This commit is contained in:
Stefan Benz 2024-10-02 13:41:10 +02:00
parent 5fa3598f69
commit 8f0f278aa0
No known key found for this signature in database
GPG Key ID: 071AA751ED4F9D31
8 changed files with 239 additions and 99 deletions

View File

@ -65,7 +65,7 @@ func (c *Commands) requestPasswordReset(ctx context.Context, userID string, retu
if err != nil {
return nil, nil, err
}
cmd := user.NewHumanPasswordCodeAddedEventV2(ctx, UserAggregateFromWriteModel(&model.WriteModel), passwordCode.CryptedCode(), passwordCode.CodeExpiry(), notificationType, urlTmpl, returnCode, generatorID)
cmd := user.NewHumanPasswordCodeAddedEventV2(ctx, UserAggregateFromWriteModelCtx(ctx, &model.WriteModel), passwordCode.CryptedCode(), passwordCode.CodeExpiry(), notificationType, urlTmpl, returnCode, generatorID)
if returnCode {
plainCode = &passwordCode.Plain

View File

@ -3,8 +3,12 @@ package command
import (
"context"
"encoding/json"
"time"
"github.com/zitadel/zitadel/internal/crypto"
"github.com/zitadel/zitadel/internal/domain"
"github.com/zitadel/zitadel/internal/notification/senders"
"github.com/zitadel/zitadel/internal/telemetry/tracing"
"github.com/zitadel/zitadel/internal/zerrors"
)
@ -247,13 +251,34 @@ func existingSchema(ctx context.Context, c *Commands, resourceOwner, id string)
return writeModel, nil
}
func existingSchemaWithAuthenticator(ctx context.Context, c *Commands, resourceOwner, id string, authenticator domain.AuthenticatorType) (*UserSchemaWriteModel, error) {
writeModel, err := c.getSchemaWriteModelByID(ctx, resourceOwner, id)
func schemaUserVerifyCode(
ctx context.Context,
codeCreationDate time.Time,
codeExpiry time.Duration,
encryptedCode *crypto.CryptoValue,
codeProviderID string,
codeVerificationID string,
code string,
codeAlg crypto.EncryptionAlgorithm,
getCodeVerifier func(ctx context.Context, id string) (_ senders.CodeGenerator, err error),
) (err error) {
if codeProviderID == "" {
if encryptedCode == nil {
return zerrors.ThrowPreconditionFailed(nil, "COMMAND-05Pe3gq4FQ", "Errors.User.Code.NotFound")
}
_, spanCrypto := tracing.NewNamedSpan(ctx, "crypto.VerifyCode")
defer func() {
spanCrypto.EndWithError(err)
}()
return crypto.VerifyCode(codeCreationDate, codeExpiry, encryptedCode, code, codeAlg)
}
if getCodeVerifier == nil {
return zerrors.ThrowPreconditionFailed(nil, "COMMAND-S8kTrxy0aH", "Errors.User.Code.NotConfigured")
}
verifier, err := getCodeVerifier(ctx, codeProviderID)
if err != nil {
return nil, err
return err
}
if !writeModel.Exists() {
return nil, zerrors.ThrowNotFound(nil, "COMMAND-VLDTtxT3If", "Errors.UserSchema.NotExists")
}
return writeModel, nil
return verifier.VerifyCode(codeVerificationID, code)
}

View File

@ -34,6 +34,8 @@ type UserV3WriteModel struct {
IsPhoneVerified bool
PhoneVerifiedFailedCount int
PhoneCode *VerifyCode
PhoneCodeGeneratorID string
PhoneCodeVerificationID string
Data json.RawMessage
@ -156,12 +158,16 @@ func (wm *UserV3WriteModel) Reduce() error {
CreationDate: e.CreationDate(),
Expiry: e.Expiry,
}
wm.PhoneCodeGeneratorID = e.GeneratorID
case *schemauser.PhoneVerifiedEvent:
wm.PhoneVerifiedFailedCount = 0
wm.IsPhoneVerified = true
wm.PhoneCode = nil
case *schemauser.PhoneVerificationFailedEvent:
wm.PhoneVerifiedFailedCount += 1
case *schemauser.PhoneCodeSentEvent:
wm.PhoneCodeGeneratorID = e.GeneratorInfo.GetID()
wm.PhoneCodeVerificationID = e.GeneratorInfo.GetVerificationID()
case *schemauser.LockedEvent:
wm.Locked = true
case *schemauser.UnlockedEvent:
@ -207,6 +213,7 @@ func (wm *UserV3WriteModel) Query() *eventstore.SearchQueryBuilder {
schemauser.PhoneVerifiedType,
schemauser.PhoneCodeAddedType,
schemauser.PhoneVerificationFailedType,
schemauser.PhoneCodeSentType,
)
}
return builder.AddQuery().
@ -619,7 +626,7 @@ func (wm *UserV3WriteModel) NewPhoneUpdate(
func (wm *UserV3WriteModel) NewPhoneVerify(
ctx context.Context,
verify func(creationDate time.Time, expiry time.Duration, cryptoCode *crypto.CryptoValue) error,
verify func(context.Context, time.Time, time.Duration, *crypto.CryptoValue, string, string) error,
) ([]eventstore.Command, error) {
if !wm.PhoneWM {
return nil, nil
@ -633,7 +640,7 @@ func (wm *UserV3WriteModel) NewPhoneVerify(
if wm.PhoneCode == nil {
return nil, nil
}
if err := verify(wm.PhoneCode.CreationDate, wm.PhoneCode.Expiry, wm.PhoneCode.Code); err != nil {
if err := verify(ctx, wm.PhoneCode.CreationDate, wm.PhoneCode.Expiry, wm.PhoneCode.Code, wm.PhoneCodeGeneratorID, wm.PhoneCodeVerificationID); err != nil {
return nil, err
}
return []eventstore.Command{wm.newPhoneVerifiedEvent(ctx)}, nil

View File

@ -239,27 +239,8 @@ func (c *Commands) setSchemaUserPasswordWithVerifyCode(
codeVerificationID string,
code string,
) setPasswordVerification {
if codeProviderID == "" {
return func(ctx context.Context) (newEncodedPassword string, err error) {
if encryptedCode == nil {
return "", zerrors.ThrowPreconditionFailed(nil, "COMMAND-05Pe3gq4FQ", "Errors.User.Code.NotFound")
}
_, spanCrypto := tracing.NewNamedSpan(ctx, "crypto.VerifyCode")
defer func() {
spanCrypto.EndWithError(err)
}()
return "", crypto.VerifyCode(codeCreationDate, codeExpiry, encryptedCode, code, c.userEncryption)
}
}
return func(ctx context.Context) (newEncodedPassword string, err error) {
if c.phoneCodeVerifier == nil {
return "", zerrors.ThrowPreconditionFailed(nil, "COMMAND-S8kTrxy0aH", "Errors.User.Code.NotConfigured")
}
verifier, err := c.phoneCodeVerifier(ctx, codeProviderID)
if err != nil {
return "", err
}
return "", verifier.VerifyCode(codeVerificationID, code)
return func(ctx context.Context) (_ string, err error) {
return "", schemaUserVerifyCode(ctx, codeCreationDate, codeExpiry, encryptedCode, codeProviderID, codeVerificationID, code, c.userEncryption, c.phoneCodeVerifier)
}
}

View File

@ -14,6 +14,8 @@ import (
"github.com/zitadel/zitadel/internal/crypto"
"github.com/zitadel/zitadel/internal/domain"
"github.com/zitadel/zitadel/internal/eventstore"
"github.com/zitadel/zitadel/internal/notification/senders"
"github.com/zitadel/zitadel/internal/notification/senders/mock"
"github.com/zitadel/zitadel/internal/repository/org"
"github.com/zitadel/zitadel/internal/repository/user/authenticator"
"github.com/zitadel/zitadel/internal/zerrors"
@ -130,6 +132,15 @@ func filterSchemaUserPasswordCodeExternalExisting() expect {
"id1",
),
),
eventFromEventPusherWithCreationDateNow(
authenticator.NewPasswordCodeSentEvent(context.Background(),
&authenticator.NewAggregate("user1", "org1").Aggregate,
&senders.CodeGeneratorInfo{
ID: "id",
VerificationID: "verificationID",
},
),
),
)
}
@ -139,6 +150,7 @@ func TestCommands_SetSchemaUserPassword(t *testing.T) {
userPasswordHasher *crypto.Hasher
checkPermission domain.PermissionCheck
codeAlg crypto.EncryptionAlgorithm
phoneCodeVerifier func(ctx context.Context, id string) (senders.CodeGenerator, error)
}
type args struct {
ctx context.Context
@ -565,39 +577,88 @@ func TestCommands_SetSchemaUserPassword(t *testing.T) {
},
},
},
{
"password set, code external, not configured",
fields{
eventstore: expectEventstore(
filterSchemaUserExisting(),
filterSchemaExisting(),
filterSchemaUserPasswordCodeExternalExisting(),
),
userPasswordHasher: mockPasswordHasher("x"),
codeAlg: crypto.CreateMockEncryptionAlg(gomock.NewController(t)),
},
args{
ctx: authz.NewMockContext("instanceID", "", ""),
user: &SetSchemaUserPassword{
UserID: "user1",
Password: &SchemaUserPassword{
Password: "password2",
ChangeRequired: false,
},
Verification: &SchemaUserPasswordVerification{
Code: "code",
},
},
},
res{
err: func(err error) bool {
return errors.Is(err, zerrors.ThrowPreconditionFailed(nil, "COMMAND-S8kTrxy0aH", "Errors.User.Code.NotConfigured"))
},
},
},
{
"password set, code external, ok",
fields{
eventstore: expectEventstore(
filterSchemaUserExisting(),
filterSchemaExisting(),
expectFilter(
eventFromEventPusher(
authenticator.NewPasswordCreatedEvent(
context.Background(),
&authenticator.NewAggregate("user1", "org1").Aggregate,
"user1",
"$plain$x$password",
false,
),
),
eventFromEventPusherWithCreationDateNow(
authenticator.NewPasswordCodeAddedEvent(context.Background(),
&authenticator.NewAggregate("user1", "org1").Aggregate,
&crypto.CryptoValue{
CryptoType: crypto.TypeEncryption,
Algorithm: "enc",
KeyID: "id",
Crypted: []byte("code"),
},
time.Hour*1,
domain.NotificationTypeEmail,
"",
false,
"",
),
filterSchemaUserPasswordCodeExternalExisting(),
filterPasswordComplexityPolicyExisting(),
expectPush(
authenticator.NewPasswordCreatedEvent(
context.Background(),
&authenticator.NewAggregate("user1", "org1").Aggregate,
"user1",
"$plain$x$password2",
false,
),
),
),
userPasswordHasher: mockPasswordHasher("x"),
codeAlg: crypto.CreateMockEncryptionAlg(gomock.NewController(t)),
phoneCodeVerifier: func(ctx context.Context, id string) (senders.CodeGenerator, error) {
sender := mock.NewMockCodeGenerator(gomock.NewController(t))
sender.EXPECT().VerifyCode("verificationID", "code").Return(nil)
return sender, nil
},
},
args{
ctx: authz.NewMockContext("instanceID", "", ""),
user: &SetSchemaUserPassword{
UserID: "user1",
Password: &SchemaUserPassword{
Password: "password2",
ChangeRequired: false,
},
Verification: &SchemaUserPasswordVerification{
Code: "code",
},
},
},
res{
details: &domain.ObjectDetails{
ResourceOwner: "org1",
},
},
},
{
"password set, code return, ok",
fields{
eventstore: expectEventstore(
filterSchemaUserExisting(),
filterSchemaExisting(),
filterSchemaUserPasswordCodeReturnExisting(),
filterPasswordComplexityPolicyExisting(),
expectPush(
authenticator.NewPasswordCreatedEvent(
@ -725,6 +786,7 @@ func TestCommands_SetSchemaUserPassword(t *testing.T) {
checkPermission: tt.fields.checkPermission,
userPasswordHasher: tt.fields.userPasswordHasher,
userEncryption: tt.fields.codeAlg,
phoneCodeVerifier: tt.fields.phoneCodeVerifier,
}
details, err := c.SetSchemaUserPassword(tt.args.ctx, tt.args.user)
if tt.res.err == nil {

View File

@ -64,8 +64,8 @@ func (c *Commands) VerifySchemaUserPhone(ctx context.Context, resourceOwner, id,
}
events, err := writeModel.NewPhoneVerify(ctx,
func(creationDate time.Time, expiry time.Duration, cryptoCode *crypto.CryptoValue) error {
return crypto.VerifyCode(creationDate, expiry, cryptoCode, code, c.userEncryption)
func(ctx context.Context, creationDate time.Time, expiry time.Duration, cryptoCode *crypto.CryptoValue, generatorID string, verificationID string) error {
return schemaUserVerifyCode(ctx, creationDate, expiry, cryptoCode, generatorID, verificationID, code, c.userEncryption, c.phoneCodeVerifier)
},
)
if err != nil {

View File

@ -14,7 +14,10 @@ import (
"github.com/zitadel/zitadel/internal/crypto"
"github.com/zitadel/zitadel/internal/domain"
"github.com/zitadel/zitadel/internal/eventstore"
"github.com/zitadel/zitadel/internal/notification/senders"
"github.com/zitadel/zitadel/internal/notification/senders/mock"
"github.com/zitadel/zitadel/internal/repository/instance"
"github.com/zitadel/zitadel/internal/repository/user/authenticator"
"github.com/zitadel/zitadel/internal/repository/user/schema"
"github.com/zitadel/zitadel/internal/repository/user/schemauser"
"github.com/zitadel/zitadel/internal/zerrors"
@ -123,7 +126,7 @@ func filterSchemaUserPhoneCodeExisting() expect {
"+41791234567",
),
),
eventFromEventPusher(
eventFromEventPusherWithCreationDateNow(
schemauser.NewPhoneCodeAddedEvent(
context.Background(),
&schemauser.NewAggregate("user1", "org1").Aggregate,
@ -141,6 +144,48 @@ func filterSchemaUserPhoneCodeExisting() expect {
)
}
func filterSchemaUserPhoneCodeExternalExisting() expect {
return expectFilter(
eventFromEventPusher(
schemauser.NewCreatedEvent(
context.Background(),
&schemauser.NewAggregate("user1", "org1").Aggregate,
"id1",
1,
json.RawMessage(`{
"name": "user1"
}`),
),
),
eventFromEventPusher(
schemauser.NewPhoneUpdatedEvent(
context.Background(),
&schemauser.NewAggregate("user1", "org1").Aggregate,
"+41791234567",
),
),
eventFromEventPusher(
schemauser.NewPhoneCodeAddedEvent(
context.Background(),
&schemauser.NewAggregate("user1", "org1").Aggregate,
nil,
0,
false,
"id",
),
),
eventFromEventPusherWithCreationDateNow(
schemauser.NewPhoneCodeSentEvent(context.Background(),
&authenticator.NewAggregate("user1", "org1").Aggregate,
&senders.CodeGeneratorInfo{
ID: "id",
VerificationID: "verificationID",
},
),
),
)
}
func TestCommands_ChangeSchemaUserPhone(t *testing.T) {
type fields struct {
eventstore func(t *testing.T) *eventstore.Eventstore
@ -461,8 +506,9 @@ func TestCommands_ChangeSchemaUserPhone(t *testing.T) {
func TestCommands_VerifySchemaUserPhone(t *testing.T) {
type fields struct {
eventstore func(t *testing.T) *eventstore.Eventstore
checkPermission domain.PermissionCheck
eventstore func(t *testing.T) *eventstore.Eventstore
checkPermission domain.PermissionCheck
phoneCodeVerifier func(ctx context.Context, id string) (senders.CodeGenerator, error)
}
type args struct {
ctx context.Context
@ -696,41 +742,7 @@ func TestCommands_VerifySchemaUserPhone(t *testing.T) {
"phone verify, ok",
fields{
eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(
schemauser.NewCreatedEvent(
context.Background(),
&schemauser.NewAggregate("user1", "org1").Aggregate,
"id1",
1,
json.RawMessage(`{
"name": "user1"
}`),
),
),
eventFromEventPusher(
schemauser.NewPhoneUpdatedEvent(
context.Background(),
&schemauser.NewAggregate("user1", "org1").Aggregate,
"+41791234567",
),
),
eventFromEventPusherWithCreationDateNow(
schemauser.NewPhoneCodeAddedEvent(
context.Background(),
&schemauser.NewAggregate("user1", "org1").Aggregate,
&crypto.CryptoValue{
CryptoType: crypto.TypeEncryption,
Algorithm: "enc",
KeyID: "id",
Crypted: []byte("phoneverify"),
},
time.Hour*1,
false,
"",
),
),
),
filterSchemaUserPhoneCodeExisting(),
expectPush(
eventFromEventPusher(
schemauser.NewPhoneVerifiedEvent(
@ -753,13 +765,65 @@ func TestCommands_VerifySchemaUserPhone(t *testing.T) {
},
},
},
{
"phone verify, external, not copnfigured",
fields{
eventstore: expectEventstore(
filterSchemaUserPhoneCodeExternalExisting(),
),
checkPermission: newMockPermissionCheckAllowed(),
},
args{
ctx: authz.NewMockContext("instanceID", "", ""),
id: "user1",
code: "phoneverify",
},
res{
err: func(err error) bool {
return errors.Is(err, zerrors.ThrowPreconditionFailed(nil, "COMMAND-S8kTrxy0aH", "Errors.User.Code.NotConfigured"))
},
},
},
{
"phone verify, external, ok",
fields{
eventstore: expectEventstore(
filterSchemaUserPhoneCodeExternalExisting(),
expectPush(
eventFromEventPusher(
schemauser.NewPhoneVerifiedEvent(
context.Background(),
&schemauser.NewAggregate("user1", "org1").Aggregate,
),
),
),
),
checkPermission: newMockPermissionCheckAllowed(),
phoneCodeVerifier: func(ctx context.Context, id string) (senders.CodeGenerator, error) {
sender := mock.NewMockCodeGenerator(gomock.NewController(t))
sender.EXPECT().VerifyCode("verificationID", "code").Return(nil)
return sender, nil
},
},
args{
ctx: authz.NewMockContext("instanceID", "", ""),
id: "user1",
code: "code",
},
res{
details: &domain.ObjectDetails{
ResourceOwner: "org1",
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
c := &Commands{
eventstore: tt.fields.eventstore(t),
checkPermission: tt.fields.checkPermission,
userEncryption: crypto.CreateMockEncryptionAlg(gomock.NewController(t)),
eventstore: tt.fields.eventstore(t),
checkPermission: tt.fields.checkPermission,
userEncryption: crypto.CreateMockEncryptionAlg(gomock.NewController(t)),
phoneCodeVerifier: tt.fields.phoneCodeVerifier,
}
details, err := c.VerifySchemaUserPhone(tt.args.ctx, tt.args.resourceOwner, tt.args.id, tt.args.code)
if tt.res.err == nil {

View File

@ -7,6 +7,7 @@ func init() {
eventstore.RegisterFilterEventMapper(AggregateType, UsernameDeletedType, eventstore.GenericEventMapper[UsernameDeletedEvent])
eventstore.RegisterFilterEventMapper(AggregateType, PasswordCreatedType, eventstore.GenericEventMapper[PasswordCreatedEvent])
eventstore.RegisterFilterEventMapper(AggregateType, PasswordCodeAddedType, eventstore.GenericEventMapper[PasswordCodeAddedEvent])
eventstore.RegisterFilterEventMapper(AggregateType, PasswordCodeSentType, eventstore.GenericEventMapper[PasswordCodeSentEvent])
eventstore.RegisterFilterEventMapper(AggregateType, PasswordDeletedType, eventstore.GenericEventMapper[PasswordDeletedEvent])
eventstore.RegisterFilterEventMapper(AggregateType, PublicKeyCreatedType, eventstore.GenericEventMapper[PublicKeyCreatedEvent])
eventstore.RegisterFilterEventMapper(AggregateType, PublicKeyDeletedType, eventstore.GenericEventMapper[PublicKeyDeletedEvent])