fix: clean up writemodels user v3

This commit is contained in:
Stefan Benz 2024-09-25 19:58:26 +02:00
parent 7ce0ebd07f
commit 4efe7d1786
No known key found for this signature in database
GPG Key ID: 071AA751ED4F9D31
14 changed files with 391 additions and 247 deletions

View File

@ -3,8 +3,6 @@ package user
import ( import (
"context" "context"
"github.com/muhlemmer/gu"
resource_object "github.com/zitadel/zitadel/internal/api/grpc/resources/object/v3alpha" resource_object "github.com/zitadel/zitadel/internal/api/grpc/resources/object/v3alpha"
"github.com/zitadel/zitadel/internal/command" "github.com/zitadel/zitadel/internal/command"
"github.com/zitadel/zitadel/internal/domain" "github.com/zitadel/zitadel/internal/domain"
@ -61,7 +59,7 @@ func (s *Server) RequestPasswordReset(ctx context.Context, req *user.RequestPass
} }
return &user.RequestPasswordResetResponse{ return &user.RequestPasswordResetResponse{
Details: resource_object.DomainToDetailsPb(details, object.OwnerType_OWNER_TYPE_ORG, details.ResourceOwner), Details: resource_object.DomainToDetailsPb(details, object.OwnerType_OWNER_TYPE_ORG, details.ResourceOwner),
VerificationCode: gu.Ptr(schemauser.PlainCode), VerificationCode: schemauser.PlainCode,
}, nil }, nil
} }

View File

@ -66,7 +66,7 @@ func (c *Commands) CreateSchemaUser(ctx context.Context, user *CreateSchemaUser)
return nil, err return nil, err
} }
events, codeEmail, codePhone, err := writeModel.NewCreated(ctx, events, codeEmail, codePhone, err := writeModel.NewCreate(ctx,
schemaWriteModel, schemaWriteModel,
user.Data, user.Data,
user.Email, user.Email,
@ -151,10 +151,8 @@ func (c *Commands) ChangeSchemaUser(ctx context.Context, user *ChangeSchemaUser)
if err != nil { if err != nil {
return nil, err return nil, err
} }
if !writeModel.Exists() {
return nil, zerrors.ThrowPreconditionFailed(nil, "COMMAND-Nn8CRVlkeZ", "Errors.User.NotFound")
}
// use already used schemaID, if no new schemaID is defined
schemaID := writeModel.SchemaID schemaID := writeModel.SchemaID
if user.SchemaUser != nil && user.SchemaUser.SchemaID != "" { if user.SchemaUser != nil && user.SchemaUser.SchemaID != "" {
schemaID = user.SchemaUser.SchemaID schemaID = user.SchemaUser.SchemaID
@ -205,7 +203,7 @@ func existingSchema(ctx context.Context, c *Commands, resourceOwner, id string)
return nil, err return nil, err
} }
if !writeModel.Exists() { if !writeModel.Exists() {
return nil, zerrors.ThrowPreconditionFailed(nil, "COMMAND-VLDTtxT3If", "Errors.UserSchema.NotExists") return nil, zerrors.ThrowNotFound(nil, "COMMAND-VLDTtxT3If", "Errors.UserSchema.NotExists")
} }
return writeModel, nil return writeModel, nil
} }

View File

@ -110,7 +110,7 @@ func TestCommands_ChangeSchemaUserEmail(t *testing.T) {
}, },
res{ res{
err: func(err error) bool { err: func(err error) bool {
return errors.Is(err, zerrors.ThrowNotFound(nil, "COMMAND-nJ0TQFuRmP", "Errors.User.NotFound")) return errors.Is(err, zerrors.ThrowNotFound(nil, "COMMAND-syHyCsGmvM", "Errors.User.NotFound"))
}, },
}, },
}, },
@ -416,7 +416,7 @@ func TestCommands_VerifySchemaUserEmail(t *testing.T) {
}, },
res{ res{
err: func(err error) bool { err: func(err error) bool {
return errors.Is(err, zerrors.ThrowNotFound(nil, "COMMAND-qbGyMPvjvj", "Errors.User.NotFound")) return errors.Is(err, zerrors.ThrowNotFound(nil, "COMMAND-syHyCsGmvM", "Errors.User.NotFound"))
}, },
}, },
}, },
@ -749,7 +749,7 @@ func TestCommands_ResendSchemaUserEmailCode(t *testing.T) {
}, },
res{ res{
err: func(err error) bool { err: func(err error) bool {
return errors.Is(err, zerrors.ThrowNotFound(nil, "COMMAND-EajeF6ypOV", "Errors.User.NotFound")) return errors.Is(err, zerrors.ThrowNotFound(nil, "COMMAND-syHyCsGmvM", "Errors.User.NotFound"))
}, },
}, },
}, },

View File

@ -215,7 +215,7 @@ func (wm *UserV3WriteModel) Query() *eventstore.SearchQueryBuilder {
EventTypes(eventtypes...).Builder() EventTypes(eventtypes...).Builder()
} }
func (wm *UserV3WriteModel) NewCreated( func (wm *UserV3WriteModel) NewCreate(
ctx context.Context, ctx context.Context,
schemaWM *UserSchemaWriteModel, schemaWM *UserSchemaWriteModel,
data json.RawMessage, data json.RawMessage,
@ -223,11 +223,11 @@ func (wm *UserV3WriteModel) NewCreated(
phone *Phone, phone *Phone,
code func(context.Context) (*EncryptedCode, error), code func(context.Context) (*EncryptedCode, error),
) (_ []eventstore.Command, codeEmail string, codePhone string, err error) { ) (_ []eventstore.Command, codeEmail string, codePhone string, err error) {
if err := wm.checkPermissionWrite(ctx, wm.ResourceOwner, wm.AggregateID); err != nil { if err := wm.checkPermissionWrite(ctx); err != nil {
return nil, "", "", err return nil, "", "", err
} }
if wm.Exists() { if err := wm.NotExists(); err != nil {
return nil, "", "", zerrors.ThrowPreconditionFailed(nil, "COMMAND-Nn8CRVlkeZ", "Errors.User.AlreadyExists") return nil, "", "", err
} }
schemaID, schemaRevision, err := wm.validateData(ctx, data, schemaWM) schemaID, schemaRevision, err := wm.validateData(ctx, data, schemaWM)
if err != nil { if err != nil {
@ -315,11 +315,11 @@ func (wm *UserV3WriteModel) NewUpdate(
phone *Phone, phone *Phone,
code func(context.Context) (*EncryptedCode, error), code func(context.Context) (*EncryptedCode, error),
) (_ []eventstore.Command, codeEmail string, codePhone string, err error) { ) (_ []eventstore.Command, codeEmail string, codePhone string, err error) {
if err := wm.checkPermissionWrite(ctx, wm.ResourceOwner, wm.AggregateID); err != nil { if err := wm.checkPermissionWrite(ctx); err != nil {
return nil, "", "", err return nil, "", "", err
} }
if !wm.Exists() { if err := wm.Exists(); err != nil {
return nil, "", "", zerrors.ThrowPreconditionFailed(nil, "COMMAND-Nn8CRVlkeZ", "Errors.User.NotFound") return nil, "", "", err
} }
events := make([]eventstore.Command, 0) events := make([]eventstore.Command, 0)
if user != nil { if user != nil {
@ -390,14 +390,13 @@ func (wm *UserV3WriteModel) newUpdatedEvents(
func (wm *UserV3WriteModel) NewDelete( func (wm *UserV3WriteModel) NewDelete(
ctx context.Context, ctx context.Context,
) (_ []eventstore.Command, err error) { ) (_ []eventstore.Command, err error) {
if !wm.Exists() { if err := wm.Exists(); err != nil {
return nil, zerrors.ThrowNotFound(nil, "COMMAND-syHyCsGmvM", "Errors.User.NotFound") return nil, err
} }
if err := wm.checkPermissionDelete(ctx, wm.ResourceOwner, wm.AggregateID); err != nil { if err := wm.checkPermissionDelete(ctx); err != nil {
return nil, err return nil, err
} }
return []eventstore.Command{schemauser.NewDeletedEvent(ctx, UserV3AggregateFromWriteModel(&wm.WriteModel))}, nil return []eventstore.Command{schemauser.NewDeletedEvent(ctx, UserV3AggregateFromWriteModel(&wm.WriteModel))}, nil
} }
func UserV3AggregateFromWriteModel(wm *eventstore.WriteModel) *eventstore.Aggregate { func UserV3AggregateFromWriteModel(wm *eventstore.WriteModel) *eventstore.Aggregate {
@ -410,37 +409,43 @@ func UserV3AggregateFromWriteModel(wm *eventstore.WriteModel) *eventstore.Aggreg
} }
} }
func (wm *UserV3WriteModel) Exists() bool { func (wm *UserV3WriteModel) NotExists() error {
return wm.State != domain.UserStateDeleted && wm.State != domain.UserStateUnspecified if err := wm.Exists(); err != nil {
return nil
}
return zerrors.ThrowPreconditionFailed(nil, "COMMAND-Nn8CRVlkeZ", "Errors.User.AlreadyExists")
} }
func (wm *UserV3WriteModel) checkPermissionWrite( func (wm *UserV3WriteModel) Exists() error {
ctx context.Context, if wm.State != domain.UserStateDeleted && wm.State != domain.UserStateUnspecified {
resourceOwner string, return nil
userID string, }
) error { return zerrors.ThrowNotFound(nil, "COMMAND-syHyCsGmvM", "Errors.User.NotFound")
}
func (wm *UserV3WriteModel) checkPermissionWrite(ctx context.Context) error {
if wm.writePermissionCheck { if wm.writePermissionCheck {
return nil return nil
} }
if userID != "" && userID == authz.GetCtxData(ctx).UserID { if wm.AggregateID == authz.GetCtxData(ctx).UserID {
return nil return nil
} }
if err := wm.checkPermission(ctx, domain.PermissionUserWrite, resourceOwner, userID); err != nil { if err := wm.checkPermission(ctx, domain.PermissionUserWrite, wm.ResourceOwner, wm.AggregateID); err != nil {
return err return err
} }
wm.writePermissionCheck = true wm.writePermissionCheck = true
return nil return nil
} }
func (wm *UserV3WriteModel) checkPermissionDelete( func (wm *UserV3WriteModel) checkPermissionDelete(ctx context.Context) error {
ctx context.Context, if wm.AggregateID == authz.GetCtxData(ctx).UserID {
resourceOwner string,
userID string,
) error {
if userID != "" && userID == authz.GetCtxData(ctx).UserID {
return nil return nil
} }
return wm.checkPermission(ctx, domain.PermissionUserDelete, resourceOwner, userID) return wm.checkPermission(ctx, domain.PermissionUserDelete, wm.ResourceOwner, wm.AggregateID)
}
func (wm *UserV3WriteModel) checkPermissionStateChange(ctx context.Context) error {
return wm.checkPermission(ctx, domain.PermissionUserWrite, wm.ResourceOwner, wm.AggregateID)
} }
func (wm *UserV3WriteModel) NewEmailCreate( func (wm *UserV3WriteModel) NewEmailCreate(
@ -448,7 +453,7 @@ func (wm *UserV3WriteModel) NewEmailCreate(
email *Email, email *Email,
code func(context.Context) (*EncryptedCode, error), code func(context.Context) (*EncryptedCode, error),
) (_ []eventstore.Command, plainCode string, err error) { ) (_ []eventstore.Command, plainCode string, err error) {
if err := wm.checkPermissionWrite(ctx, wm.ResourceOwner, wm.AggregateID); err != nil { if err := wm.checkPermissionWrite(ctx); err != nil {
return nil, "", err return nil, "", err
} }
if email == nil || wm.Email == string(email.Address) { if email == nil || wm.Email == string(email.Address) {
@ -483,8 +488,8 @@ func (wm *UserV3WriteModel) NewEmailUpdate(
if !wm.EmailWM { if !wm.EmailWM {
return nil, "", nil return nil, "", nil
} }
if !wm.Exists() { if err := wm.Exists(); err != nil {
return nil, "", zerrors.ThrowNotFound(nil, "COMMAND-nJ0TQFuRmP", "Errors.User.NotFound") return nil, "", err
} }
return wm.NewEmailCreate(ctx, email, code) return wm.NewEmailCreate(ctx, email, code)
} }
@ -496,10 +501,10 @@ func (wm *UserV3WriteModel) NewEmailVerify(
if !wm.EmailWM { if !wm.EmailWM {
return nil, nil return nil, nil
} }
if !wm.Exists() { if err := wm.Exists(); err != nil {
return nil, zerrors.ThrowNotFound(nil, "COMMAND-qbGyMPvjvj", "Errors.User.NotFound") return nil, err
} }
if err := wm.checkPermissionWrite(ctx, wm.ResourceOwner, wm.AggregateID); err != nil { if err := wm.checkPermissionWrite(ctx); err != nil {
return nil, err return nil, err
} }
if wm.EmailCode == nil { if wm.EmailCode == nil {
@ -526,10 +531,10 @@ func (wm *UserV3WriteModel) NewResendEmailCode(
if !wm.EmailWM { if !wm.EmailWM {
return nil, "", nil return nil, "", nil
} }
if !wm.Exists() { if err := wm.Exists(); err != nil {
return nil, "", zerrors.ThrowNotFound(nil, "COMMAND-EajeF6ypOV", "Errors.User.NotFound") return nil, "", err
} }
if err := wm.checkPermissionWrite(ctx, wm.ResourceOwner, wm.AggregateID); err != nil { if err := wm.checkPermissionWrite(ctx); err != nil {
return nil, "", err return nil, "", err
} }
if wm.EmailCode == nil { if wm.EmailCode == nil {
@ -569,7 +574,7 @@ func (wm *UserV3WriteModel) NewPhoneCreate(
phone *Phone, phone *Phone,
code func(context.Context) (*EncryptedCode, error), code func(context.Context) (*EncryptedCode, error),
) (_ []eventstore.Command, plainCode string, err error) { ) (_ []eventstore.Command, plainCode string, err error) {
if err := wm.checkPermissionWrite(ctx, wm.ResourceOwner, wm.AggregateID); err != nil { if err := wm.checkPermissionWrite(ctx); err != nil {
return nil, "", err return nil, "", err
} }
if phone == nil || wm.Phone == string(phone.Number) { if phone == nil || wm.Phone == string(phone.Number) {
@ -604,8 +609,8 @@ func (wm *UserV3WriteModel) NewPhoneUpdate(
if !wm.PhoneWM { if !wm.PhoneWM {
return nil, "", nil return nil, "", nil
} }
if !wm.Exists() { if err := wm.Exists(); err != nil {
return nil, "", zerrors.ThrowNotFound(nil, "COMMAND-b33QAVgel6", "Errors.User.NotFound") return nil, "", err
} }
return wm.NewPhoneCreate(ctx, phone, code) return wm.NewPhoneCreate(ctx, phone, code)
} }
@ -617,10 +622,10 @@ func (wm *UserV3WriteModel) NewPhoneVerify(
if !wm.PhoneWM { if !wm.PhoneWM {
return nil, nil return nil, nil
} }
if !wm.Exists() { if err := wm.Exists(); err != nil {
return nil, zerrors.ThrowNotFound(nil, "COMMAND-bx2OLtgGNS", "Errors.User.NotFound") return nil, err
} }
if err := wm.checkPermissionWrite(ctx, wm.ResourceOwner, wm.AggregateID); err != nil { if err := wm.checkPermissionWrite(ctx); err != nil {
return nil, err return nil, err
} }
if wm.PhoneCode == nil { if wm.PhoneCode == nil {
@ -646,10 +651,10 @@ func (wm *UserV3WriteModel) NewResendPhoneCode(
if !wm.PhoneWM { if !wm.PhoneWM {
return nil, "", nil return nil, "", nil
} }
if !wm.Exists() { if err := wm.Exists(); err != nil {
return nil, "", zerrors.ThrowNotFound(nil, "COMMAND-z8Bu9vuL9s", "Errors.User.NotFound") return nil, "", err
} }
if err := wm.checkPermissionWrite(ctx, wm.ResourceOwner, wm.AggregateID); err != nil { if err := wm.checkPermissionWrite(ctx); err != nil {
return nil, "", err return nil, "", err
} }
if wm.PhoneCode == nil { if wm.PhoneCode == nil {
@ -681,3 +686,59 @@ func (wm *UserV3WriteModel) newPhoneCodeAddedEvent(
isReturnCode, isReturnCode,
), plainCode, nil ), plainCode, nil
} }
func (wm *UserV3WriteModel) NewLock(ctx context.Context) (_ []eventstore.Command, err error) {
if err := wm.Exists(); err != nil {
return nil, err
}
// can only be locked when not already locked
if wm.Locked {
return nil, zerrors.ThrowNotFound(nil, "COMMAND-G4LOrnjY7q", "Errors.User.NotFound")
}
if err := wm.checkPermissionStateChange(ctx); err != nil {
return nil, err
}
return []eventstore.Command{schemauser.NewLockedEvent(ctx, UserV3AggregateFromWriteModel(&wm.WriteModel))}, nil
}
func (wm *UserV3WriteModel) NewUnlock(ctx context.Context) (_ []eventstore.Command, err error) {
if err := wm.Exists(); err != nil {
return nil, err
}
// can only be unlocked when locked
if !wm.Locked {
return nil, zerrors.ThrowNotFound(nil, "COMMAND-gpBv46Lh9m", "Errors.User.NotFound")
}
if err := wm.checkPermissionStateChange(ctx); err != nil {
return nil, err
}
return []eventstore.Command{schemauser.NewUnlockedEvent(ctx, UserV3AggregateFromWriteModel(&wm.WriteModel))}, nil
}
func (wm *UserV3WriteModel) NewDeactivate(ctx context.Context) (_ []eventstore.Command, err error) {
if err := wm.Exists(); err != nil {
return nil, err
}
// can only be deactivated when active
if wm.State != domain.UserStateActive {
return nil, zerrors.ThrowNotFound(nil, "COMMAND-Ob6lR5iFTe", "Errors.User.NotFound")
}
if err := wm.checkPermissionStateChange(ctx); err != nil {
return nil, err
}
return []eventstore.Command{schemauser.NewDeactivatedEvent(ctx, UserV3AggregateFromWriteModel(&wm.WriteModel))}, nil
}
func (wm *UserV3WriteModel) NewActivate(ctx context.Context) (_ []eventstore.Command, err error) {
if err := wm.Exists(); err != nil {
return nil, err
}
// can only be activated when inactive
if wm.State != domain.UserStateInactive {
return nil, zerrors.ThrowNotFound(nil, "COMMAND-rQjbBr4J3j", "Errors.User.NotFound")
}
if err := wm.checkPermissionStateChange(ctx); err != nil {
return nil, err
}
return []eventstore.Command{schemauser.NewActivatedEvent(ctx, UserV3AggregateFromWriteModel(&wm.WriteModel))}, nil
}

View File

@ -6,7 +6,6 @@ import (
"github.com/zitadel/zitadel/internal/crypto" "github.com/zitadel/zitadel/internal/crypto"
"github.com/zitadel/zitadel/internal/domain" "github.com/zitadel/zitadel/internal/domain"
"github.com/zitadel/zitadel/internal/repository/user/authenticator"
"github.com/zitadel/zitadel/internal/telemetry/tracing" "github.com/zitadel/zitadel/internal/telemetry/tracing"
"github.com/zitadel/zitadel/internal/zerrors" "github.com/zitadel/zitadel/internal/zerrors"
) )
@ -46,6 +45,7 @@ func (c *Commands) SetSchemaUserPassword(ctx context.Context, user *SetSchemaUse
} }
schemaUser := &schemaUserPassword{ schemaUser := &schemaUserPassword{
Create: true,
ResourceOwner: user.ResourceOwner, ResourceOwner: user.ResourceOwner,
UserID: user.UserID, UserID: user.UserID,
VerificationCode: user.VerificationCode, VerificationCode: user.VerificationCode,
@ -54,27 +54,15 @@ func (c *Commands) SetSchemaUserPassword(ctx context.Context, user *SetSchemaUse
EncodedPasswordHash: user.EncodedPasswordHash, EncodedPasswordHash: user.EncodedPasswordHash,
} }
existing, err := c.getSchemaUserPasswordWithVerification(ctx, schemaUser) writeModel, err := c.getSchemaUserPasswordWithVerification(ctx, schemaUser)
if err != nil { if err != nil {
return nil, err return nil, err
} }
resourceOwner := existing.ResourceOwner
// when no password was set yet
if existing.EncodedHash == "" {
existingUser, err := c.getSchemaUserWMForState(ctx, user.ResourceOwner, user.UserID)
if err != nil {
return nil, err
}
if !existingUser.Exists() {
return nil, zerrors.ThrowNotFound(nil, "COMMAND-TODO", "Errors.User.Password.NotFound")
}
resourceOwner = existingUser.ResourceOwner
}
// If password is provided, let's check if is compliant with the policy. // If password is provided, let's check if is compliant with the policy.
// If only a encodedPassword is passed, we can skip this. // If only a encodedPassword is passed, we can skip this.
if user.Password != "" { if user.Password != "" {
if err = c.checkPasswordComplexity(ctx, user.Password, resourceOwner); err != nil { if err = c.checkPasswordComplexity(ctx, user.Password, writeModel.ResourceOwner); err != nil {
return nil, err return nil, err
} }
} }
@ -87,18 +75,14 @@ func (c *Commands) SetSchemaUserPassword(ctx context.Context, user *SetSchemaUse
} }
} }
events, err := c.eventstore.Push(ctx, events, err := writeModel.NewCreate(ctx,
authenticator.NewPasswordCreatedEvent(ctx,
&authenticator.NewAggregate(user.UserID, resourceOwner).Aggregate,
existing.UserID,
encodedPassword, encodedPassword,
user.ChangeRequired, user.ChangeRequired,
),
) )
if err != nil { if err != nil {
return nil, err return nil, err
} }
return pushedEventsToObjectDetails(events), nil return c.pushAppendAndReduceDetails(ctx, writeModel, events...)
} }
type RequestSchemaUserPasswordReset struct { type RequestSchemaUserPasswordReset struct {
@ -107,64 +91,48 @@ type RequestSchemaUserPasswordReset struct {
URLTemplate string URLTemplate string
NotificationType domain.NotificationType NotificationType domain.NotificationType
PlainCode string PlainCode *string
ReturnCode bool ReturnCode bool
} }
func (c *Commands) RequestSchemaUserPasswordReset(ctx context.Context, user *RequestSchemaUserPasswordReset) (_ *domain.ObjectDetails, err error) { func (c *Commands) RequestSchemaUserPasswordReset(ctx context.Context, user *RequestSchemaUserPasswordReset) (_ *domain.ObjectDetails, err error) {
existing, err := c.getSchemaUserPasswordExists(ctx, user.ResourceOwner, user.UserID) writeModel, err := existsSchemaUserPasswordWithPermission(ctx, c, user.ResourceOwner, user.UserID)
if err != nil {
return nil, err
}
if existing.EncodedHash == "" {
return nil, zerrors.ThrowNotFound(nil, "COMMAND-TODO", "Errors.User.Password.NotFound")
}
code, err := c.newEncryptedCode(ctx, c.eventstore.Filter, domain.SecretGeneratorTypePasswordResetCode, c.userEncryption) //nolint:staticcheck
if err != nil { if err != nil {
return nil, err return nil, err
} }
events, err := c.eventstore.Push(ctx, events, plainCode, err := writeModel.NewAddCode(ctx,
authenticator.NewPasswordCodeAddedEvent(ctx,
&authenticator.NewAggregate(existing.UserID, existing.ResourceOwner).Aggregate,
code.Crypted,
code.Expiry,
user.NotificationType, user.NotificationType,
user.URLTemplate, user.URLTemplate,
user.ReturnCode, user.ReturnCode,
), func(ctx context.Context) (*EncryptedCode, error) {
return c.newEncryptedCode(ctx, c.eventstore.Filter, domain.SecretGeneratorTypePasswordResetCode, c.userEncryption) //nolint:staticcheck
},
) )
if err != nil { if err != nil {
return nil, err return nil, err
} }
if user.ReturnCode { if plainCode != "" {
user.PlainCode = code.Plain user.PlainCode = &plainCode
} }
return pushedEventsToObjectDetails(events), nil return c.pushAppendAndReduceDetails(ctx, writeModel, events...)
} }
func (c *Commands) DeleteSchemaUserPassword(ctx context.Context, resourceOwner, id string) (_ *domain.ObjectDetails, err error) { func (c *Commands) DeleteSchemaUserPassword(ctx context.Context, resourceOwner, id string) (_ *domain.ObjectDetails, err error) {
existing, err := c.getSchemaUserPasswordExists(ctx, resourceOwner, id) writeModel, err := existsSchemaUserPasswordWithPermission(ctx, c, resourceOwner, id)
if err != nil { if err != nil {
return nil, err return nil, err
} }
if existing.EncodedHash == "" {
return nil, zerrors.ThrowNotFound(nil, "TODO", "TODO")
}
events, err := c.eventstore.Push(ctx, events, err := writeModel.NewDelete(ctx)
authenticator.NewPasswordDeletedEvent(ctx,
&authenticator.NewAggregate(id, existing.ResourceOwner).Aggregate,
),
)
if err != nil { if err != nil {
return nil, err return nil, err
} }
return pushedEventsToObjectDetails(events), nil return c.pushAppendAndReduceDetails(ctx, writeModel, events...)
} }
type schemaUserPassword struct { type schemaUserPassword struct {
Create bool
ResourceOwner string ResourceOwner string
UserID string UserID string
VerificationCode string VerificationCode string
@ -173,18 +141,37 @@ type schemaUserPassword struct {
EncodedPasswordHash string EncodedPasswordHash string
} }
func (c *Commands) getSchemaUserPasswordExists(ctx context.Context, resourceOwner, id string) (*PasswordV3WriteModel, error) { func (c *Commands) getSchemaUserPasswordWM(ctx context.Context, resourceOwner, id string) (*PasswordV3WriteModel, error) {
return c.getSchemaUserPasswordWithVerification(ctx, &schemaUserPassword{ResourceOwner: resourceOwner, UserID: id}) writeModel := NewPasswordV3WriteModel(resourceOwner, id, c.checkPermission)
if err := c.eventstore.FilterToQueryReducer(ctx, writeModel); err != nil {
return nil, err
}
return writeModel, nil
}
func existsSchemaUserPasswordWithPermission(ctx context.Context, c *Commands, resourceOwner, id string) (*PasswordV3WriteModel, error) {
writeModel, err := c.getSchemaUserPasswordWithVerification(ctx, &schemaUserPassword{ResourceOwner: resourceOwner, UserID: id})
if err != nil {
return nil, err
}
return writeModel, writeModel.Exists()
} }
func (c *Commands) getSchemaUserPasswordWithVerification(ctx context.Context, user *schemaUserPassword) (*PasswordV3WriteModel, error) { func (c *Commands) getSchemaUserPasswordWithVerification(ctx context.Context, user *schemaUserPassword) (*PasswordV3WriteModel, error) {
if user.UserID == "" { if user.UserID == "" {
return nil, zerrors.ThrowInvalidArgument(nil, "COMMAND-PoSU5BOZCi", "Errors.IDMissing") return nil, zerrors.ThrowInvalidArgument(nil, "COMMAND-PoSU5BOZCi", "Errors.IDMissing")
} }
writeModel := NewPasswordV3WriteModel(user.ResourceOwner, user.UserID) writeModel, err := c.getSchemaUserPasswordWM(ctx, user.ResourceOwner, user.UserID)
if err := c.eventstore.FilterToQueryReducer(ctx, writeModel); err != nil { if err != nil {
return nil, err return nil, err
} }
if err := writeModel.Exists(); user.Create && err != nil {
schemauser, err := existingSchemaUser(ctx, c, user.ResourceOwner, user.UserID)
if err != nil {
return nil, err
}
writeModel.ResourceOwner = schemauser.ResourceOwner
}
// if no verification is set, the user must have the permission to change the password // if no verification is set, the user must have the permission to change the password
verification := c.setSchemaUserPasswordWithPermission(writeModel.UserID, writeModel.ResourceOwner) verification := c.setSchemaUserPasswordWithPermission(writeModel.UserID, writeModel.ResourceOwner)

View File

@ -1,11 +1,14 @@
package command package command
import ( import (
"context"
"time" "time"
"github.com/zitadel/zitadel/internal/crypto" "github.com/zitadel/zitadel/internal/crypto"
"github.com/zitadel/zitadel/internal/domain"
"github.com/zitadel/zitadel/internal/eventstore" "github.com/zitadel/zitadel/internal/eventstore"
"github.com/zitadel/zitadel/internal/repository/user/authenticator" "github.com/zitadel/zitadel/internal/repository/user/authenticator"
"github.com/zitadel/zitadel/internal/zerrors"
) )
type PasswordV3WriteModel struct { type PasswordV3WriteModel struct {
@ -18,15 +21,22 @@ type PasswordV3WriteModel struct {
Code *crypto.CryptoValue Code *crypto.CryptoValue
CodeCreationDate time.Time CodeCreationDate time.Time
CodeExpiry time.Duration CodeExpiry time.Duration
checkPermission domain.PermissionCheck
} }
func NewPasswordV3WriteModel(resourceOwner, id string) *PasswordV3WriteModel { func (wm *PasswordV3WriteModel) GetWriteModel() *eventstore.WriteModel {
return &wm.WriteModel
}
func NewPasswordV3WriteModel(resourceOwner, id string, checkPermission domain.PermissionCheck) *PasswordV3WriteModel {
return &PasswordV3WriteModel{ return &PasswordV3WriteModel{
WriteModel: eventstore.WriteModel{ WriteModel: eventstore.WriteModel{
AggregateID: id, AggregateID: id,
ResourceOwner: resourceOwner, ResourceOwner: resourceOwner,
}, },
UserID: id, UserID: id,
checkPermission: checkPermission,
} }
} }
@ -64,3 +74,60 @@ func (wm *PasswordV3WriteModel) Query() *eventstore.SearchQueryBuilder {
authenticator.PasswordCodeAddedType, authenticator.PasswordCodeAddedType,
).Builder() ).Builder()
} }
func (wm *PasswordV3WriteModel) NewCreate(
ctx context.Context,
encodeHash string,
changeRequired bool,
) ([]eventstore.Command, error) {
return []eventstore.Command{
authenticator.NewPasswordCreatedEvent(ctx,
AuthenticatorAggregateFromWriteModel(wm.GetWriteModel()),
wm.UserID,
encodeHash,
changeRequired,
),
}, nil
}
func (wm *PasswordV3WriteModel) NewAddCode(
ctx context.Context,
notificationType domain.NotificationType,
urlTemplate string,
codeReturned bool,
code func(context.Context) (*EncryptedCode, error),
) (_ []eventstore.Command, plainCode string, err error) {
crypt, err := code(ctx)
if err != nil {
return nil, "", err
}
events := []eventstore.Command{
authenticator.NewPasswordCodeAddedEvent(ctx,
AuthenticatorAggregateFromWriteModel(wm.GetWriteModel()),
crypt.Crypted,
crypt.Expiry,
notificationType,
urlTemplate,
codeReturned,
),
}
if codeReturned {
plainCode = crypt.Plain
}
return events, plainCode, nil
}
func (wm *PasswordV3WriteModel) NewDelete(ctx context.Context) ([]eventstore.Command, error) {
if err := wm.Exists(); err != nil {
return nil, err
}
return []eventstore.Command{authenticator.NewPasswordDeletedEvent(ctx, AuthenticatorAggregateFromWriteModel(wm.GetWriteModel()))}, nil
}
func (wm *PasswordV3WriteModel) Exists() error {
if wm.EncodedHash == "" {
return zerrors.ThrowNotFound(nil, "COMMAND-Joi3utDPIh", "Errors.User.Password.NotFound")
}
return nil
}

View File

@ -121,7 +121,7 @@ func TestCommands_SetSchemaUserPassword(t *testing.T) {
}, },
res{ res{
err: func(err error) bool { err: func(err error) bool {
return errors.Is(err, zerrors.ThrowNotFound(nil, "COMMAND-TODO", "Errors.User.Password.NotFound")) return errors.Is(err, zerrors.ThrowNotFound(nil, "COMMAND-syHyCsGmvM", "Errors.User.NotFound"))
}, },
}, },
}, },
@ -130,6 +130,7 @@ func TestCommands_SetSchemaUserPassword(t *testing.T) {
fields{ fields{
eventstore: expectEventstore( eventstore: expectEventstore(
expectFilter(), expectFilter(),
filterSchemaUserExisting(),
), ),
checkPermission: newMockPermissionCheckNotAllowed(), checkPermission: newMockPermissionCheckNotAllowed(),
}, },
@ -609,7 +610,7 @@ func TestCommands_RequestSchemaUserPasswordReset(t *testing.T) {
}, },
res{ res{
err: func(err error) bool { err: func(err error) bool {
return errors.Is(err, zerrors.ThrowNotFound(nil, "COMMAND-TODO", "Errors.User.Password.NotFound")) return errors.Is(err, zerrors.ThrowNotFound(nil, "COMMAND-Joi3utDPIh", "Errors.User.Password.NotFound"))
}, },
}, },
}, },
@ -768,7 +769,8 @@ func TestCommands_RequestSchemaUserPasswordReset(t *testing.T) {
assertObjectDetails(t, tt.res.details, details) assertObjectDetails(t, tt.res.details, details)
} }
if tt.res.plainCode != "" { if tt.res.plainCode != "" {
assert.Equal(t, tt.res.plainCode, tt.args.user.PlainCode) assert.NotNil(t, tt.args.user.PlainCode)
assert.Equal(t, tt.res.plainCode, *tt.args.user.PlainCode)
} }
}) })
} }
@ -824,7 +826,7 @@ func TestCommands_DeleteSchemaUserPassword(t *testing.T) {
}, },
res{ res{
err: func(err error) bool { err: func(err error) bool {
return errors.Is(err, zerrors.ThrowNotFound(nil, "TODO", "TODO")) return errors.Is(err, zerrors.ThrowNotFound(nil, "COMMAND-Joi3utDPIh", "Errors.User.Password.NotFound"))
}, },
}, },
}, },
@ -858,7 +860,7 @@ func TestCommands_DeleteSchemaUserPassword(t *testing.T) {
}, },
res{ res{
err: func(err error) bool { err: func(err error) bool {
return errors.Is(err, zerrors.ThrowNotFound(nil, "TODO", "TODO")) return errors.Is(err, zerrors.ThrowNotFound(nil, "COMMAND-Joi3utDPIh", "Errors.User.Password.NotFound"))
}, },
}, },
}, },

View File

@ -90,7 +90,7 @@ func TestCommands_ChangeSchemaUserPhone(t *testing.T) {
}, },
res{ res{
err: func(err error) bool { err: func(err error) bool {
return errors.Is(err, zerrors.ThrowNotFound(nil, "COMMAND-b33QAVgel6", "Errors.User.NotFound")) return errors.Is(err, zerrors.ThrowNotFound(nil, "COMMAND-syHyCsGmvM", "Errors.User.NotFound"))
}, },
}, },
}, },
@ -394,7 +394,7 @@ func TestCommands_VerifySchemaUserPhone(t *testing.T) {
}, },
res{ res{
err: func(err error) bool { err: func(err error) bool {
return errors.Is(err, zerrors.ThrowNotFound(nil, "COMMAND-bx2OLtgGNS", "Errors.User.NotFound")) return errors.Is(err, zerrors.ThrowNotFound(nil, "COMMAND-syHyCsGmvM", "Errors.User.NotFound"))
}, },
}, },
}, },
@ -722,7 +722,7 @@ func TestCommands_ResendSchemaUserPhoneCode(t *testing.T) {
}, },
res{ res{
err: func(err error) bool { err: func(err error) bool {
return errors.Is(err, zerrors.ThrowNotFound(nil, "COMMAND-z8Bu9vuL9s", "Errors.User.NotFound")) return errors.Is(err, zerrors.ThrowNotFound(nil, "COMMAND-syHyCsGmvM", "Errors.User.NotFound"))
}, },
}, },
}, },

View File

@ -4,7 +4,6 @@ import (
"context" "context"
"github.com/zitadel/zitadel/internal/domain" "github.com/zitadel/zitadel/internal/domain"
"github.com/zitadel/zitadel/internal/repository/user/schemauser"
"github.com/zitadel/zitadel/internal/zerrors" "github.com/zitadel/zitadel/internal/zerrors"
) )
@ -16,18 +15,11 @@ func (c *Commands) LockSchemaUser(ctx context.Context, resourceOwner, id string)
if err != nil { if err != nil {
return nil, err return nil, err
} }
if !writeModel.Exists() || writeModel.Locked { events, err := writeModel.NewLock(ctx)
return nil, zerrors.ThrowNotFound(nil, "COMMAND-G4LOrnjY7q", "Errors.User.NotFound") if err != nil {
}
if err := c.checkPermissionUpdateUserState(ctx, writeModel.ResourceOwner, writeModel.AggregateID); err != nil {
return nil, err return nil, err
} }
if err := c.pushAppendAndReduce(ctx, writeModel, return c.pushAppendAndReduceDetails(ctx, writeModel, events...)
schemauser.NewLockedEvent(ctx, UserV3AggregateFromWriteModel(&writeModel.WriteModel)),
); err != nil {
return nil, err
}
return writeModelToObjectDetails(&writeModel.WriteModel), nil
} }
func (c *Commands) UnlockSchemaUser(ctx context.Context, resourceOwner, id string) (*domain.ObjectDetails, error) { func (c *Commands) UnlockSchemaUser(ctx context.Context, resourceOwner, id string) (*domain.ObjectDetails, error) {
@ -38,18 +30,11 @@ func (c *Commands) UnlockSchemaUser(ctx context.Context, resourceOwner, id strin
if err != nil { if err != nil {
return nil, err return nil, err
} }
if !writeModel.Exists() || !writeModel.Locked { events, err := writeModel.NewUnlock(ctx)
return nil, zerrors.ThrowNotFound(nil, "COMMAND-gpBv46Lh9m", "Errors.User.NotFound") if err != nil {
}
if err := c.checkPermissionUpdateUserState(ctx, writeModel.ResourceOwner, writeModel.AggregateID); err != nil {
return nil, err return nil, err
} }
if err := c.pushAppendAndReduce(ctx, writeModel, return c.pushAppendAndReduceDetails(ctx, writeModel, events...)
schemauser.NewUnlockedEvent(ctx, UserV3AggregateFromWriteModel(&writeModel.WriteModel)),
); err != nil {
return nil, err
}
return writeModelToObjectDetails(&writeModel.WriteModel), nil
} }
func (c *Commands) DeactivateSchemaUser(ctx context.Context, resourceOwner, id string) (*domain.ObjectDetails, error) { func (c *Commands) DeactivateSchemaUser(ctx context.Context, resourceOwner, id string) (*domain.ObjectDetails, error) {
@ -60,18 +45,11 @@ func (c *Commands) DeactivateSchemaUser(ctx context.Context, resourceOwner, id s
if err != nil { if err != nil {
return nil, err return nil, err
} }
if writeModel.State != domain.UserStateActive { events, err := writeModel.NewDeactivate(ctx)
return nil, zerrors.ThrowNotFound(nil, "COMMAND-Ob6lR5iFTe", "Errors.User.NotFound") if err != nil {
}
if err := c.checkPermissionUpdateUserState(ctx, writeModel.ResourceOwner, writeModel.AggregateID); err != nil {
return nil, err return nil, err
} }
if err := c.pushAppendAndReduce(ctx, writeModel, return c.pushAppendAndReduceDetails(ctx, writeModel, events...)
schemauser.NewDeactivatedEvent(ctx, UserV3AggregateFromWriteModel(&writeModel.WriteModel)),
); err != nil {
return nil, err
}
return writeModelToObjectDetails(&writeModel.WriteModel), nil
} }
func (c *Commands) ActivateSchemaUser(ctx context.Context, resourceOwner, id string) (*domain.ObjectDetails, error) { func (c *Commands) ActivateSchemaUser(ctx context.Context, resourceOwner, id string) (*domain.ObjectDetails, error) {
@ -82,22 +60,11 @@ func (c *Commands) ActivateSchemaUser(ctx context.Context, resourceOwner, id str
if err != nil { if err != nil {
return nil, err return nil, err
} }
if writeModel.State != domain.UserStateInactive { events, err := writeModel.NewActivate(ctx)
return nil, zerrors.ThrowNotFound(nil, "COMMAND-rQjbBr4J3j", "Errors.User.NotFound") if err != nil {
}
if err := c.checkPermissionUpdateUserState(ctx, writeModel.ResourceOwner, writeModel.AggregateID); err != nil {
return nil, err return nil, err
} }
if err := c.pushAppendAndReduce(ctx, writeModel, return c.pushAppendAndReduceDetails(ctx, writeModel, events...)
schemauser.NewActivatedEvent(ctx, UserV3AggregateFromWriteModel(&writeModel.WriteModel)),
); err != nil {
return nil, err
}
return writeModelToObjectDetails(&writeModel.WriteModel), nil
}
func (c *Commands) checkPermissionUpdateUserState(ctx context.Context, resourceOwner, userID string) error {
return c.checkPermission(ctx, domain.PermissionUserWrite, resourceOwner, userID)
} }
func (c *Commands) getSchemaUserWMForState(ctx context.Context, resourceOwner, id string) (*UserV3WriteModel, error) { func (c *Commands) getSchemaUserWMForState(ctx context.Context, resourceOwner, id string) (*UserV3WriteModel, error) {
@ -107,3 +74,11 @@ func (c *Commands) getSchemaUserWMForState(ctx context.Context, resourceOwner, i
} }
return writeModel, nil return writeModel, nil
} }
func existingSchemaUser(ctx context.Context, c *Commands, resourceOwner, id string) (*UserV3WriteModel, error) {
writeModel, err := c.getSchemaUserWMForState(ctx, resourceOwner, id)
if err != nil {
return nil, err
}
return writeModel, writeModel.Exists()
}

View File

@ -66,7 +66,7 @@ func TestCommandSide_LockSchemaUser(t *testing.T) {
}, },
res: res{ res: res{
err: func(err error) bool { err: func(err error) bool {
return errors.Is(err, zerrors.ThrowNotFound(nil, "COMMAND-G4LOrnjY7q", "Errors.User.NotFound")) return errors.Is(err, zerrors.ThrowNotFound(nil, "COMMAND-syHyCsGmvM", "Errors.User.NotFound"))
}, },
}, },
}, },
@ -100,7 +100,7 @@ func TestCommandSide_LockSchemaUser(t *testing.T) {
}, },
res: res{ res: res{
err: func(err error) bool { err: func(err error) bool {
return errors.Is(err, zerrors.ThrowNotFound(nil, "COMMAND-G4LOrnjY7q", "Errors.User.NotFound")) return errors.Is(err, zerrors.ThrowNotFound(nil, "COMMAND-syHyCsGmvM", "Errors.User.NotFound"))
}, },
}, },
}, },
@ -274,7 +274,7 @@ func TestCommandSide_UnlockSchemaUser(t *testing.T) {
}, },
res: res{ res: res{
err: func(err error) bool { err: func(err error) bool {
return errors.Is(err, zerrors.ThrowNotFound(nil, "COMMAND-gpBv46Lh9m", "Errors.User.NotFound")) return errors.Is(err, zerrors.ThrowNotFound(nil, "COMMAND-syHyCsGmvM", "Errors.User.NotFound"))
}, },
}, },
}, },
@ -308,7 +308,7 @@ func TestCommandSide_UnlockSchemaUser(t *testing.T) {
}, },
res: res{ res: res{
err: func(err error) bool { err: func(err error) bool {
return errors.Is(err, zerrors.ThrowNotFound(nil, "COMMAND-gpBv46Lh9m", "Errors.User.NotFound")) return errors.Is(err, zerrors.ThrowNotFound(nil, "COMMAND-syHyCsGmvM", "Errors.User.NotFound"))
}, },
}, },
}, },
@ -487,7 +487,7 @@ func TestCommandSide_DeactivateSchemaUser(t *testing.T) {
}, },
res: res{ res: res{
err: func(err error) bool { err: func(err error) bool {
return errors.Is(err, zerrors.ThrowNotFound(nil, "COMMAND-Ob6lR5iFTe", "Errors.User.NotFound")) return errors.Is(err, zerrors.ThrowNotFound(nil, "COMMAND-syHyCsGmvM", "Errors.User.NotFound"))
}, },
}, },
}, },
@ -521,7 +521,7 @@ func TestCommandSide_DeactivateSchemaUser(t *testing.T) {
}, },
res: res{ res: res{
err: func(err error) bool { err: func(err error) bool {
return errors.Is(err, zerrors.ThrowNotFound(nil, "COMMAND-Ob6lR5iFTe", "Errors.User.NotFound")) return errors.Is(err, zerrors.ThrowNotFound(nil, "COMMAND-syHyCsGmvM", "Errors.User.NotFound"))
}, },
}, },
}, },
@ -695,7 +695,7 @@ func TestCommandSide_ReactivateSchemaUser(t *testing.T) {
}, },
res: res{ res: res{
err: func(err error) bool { err: func(err error) bool {
return errors.Is(err, zerrors.ThrowNotFound(nil, "COMMAND-rQjbBr4J3j", "Errors.User.NotFound")) return errors.Is(err, zerrors.ThrowNotFound(nil, "COMMAND-syHyCsGmvM", "Errors.User.NotFound"))
}, },
}, },
}, },
@ -729,7 +729,7 @@ func TestCommandSide_ReactivateSchemaUser(t *testing.T) {
}, },
res: res{ res: res{
err: func(err error) bool { err: func(err error) bool {
return errors.Is(err, zerrors.ThrowNotFound(nil, "COMMAND-rQjbBr4J3j", "Errors.User.NotFound")) return errors.Is(err, zerrors.ThrowNotFound(nil, "COMMAND-syHyCsGmvM", "Errors.User.NotFound"))
}, },
}, },
}, },

View File

@ -97,7 +97,7 @@ func TestCommands_CreateSchemaUser(t *testing.T) {
}, },
res{ res{
err: func(err error) bool { err: func(err error) bool {
return errors.Is(err, zerrors.ThrowPreconditionFailed(nil, "COMMAND-VLDTtxT3If", "Errors.UserSchema.NotExists")) return errors.Is(err, zerrors.ThrowNotFound(nil, "COMMAND-VLDTtxT3If", "Errors.UserSchema.NotExists"))
}, },
}, },
}, },
@ -1008,7 +1008,7 @@ func TestCommands_ChangeSchemaUser(t *testing.T) {
}, },
res{ res{
err: func(err error) bool { err: func(err error) bool {
return errors.Is(err, zerrors.ThrowPreconditionFailed(nil, "COMMAND-VLDTtxT3If", "Errors.UserSchema.NotExists")) return errors.Is(err, zerrors.ThrowNotFound(nil, "COMMAND-VLDTtxT3If", "Errors.UserSchema.NotExists"))
}, },
}, },
}, },

View File

@ -4,7 +4,6 @@ import (
"context" "context"
"github.com/zitadel/zitadel/internal/domain" "github.com/zitadel/zitadel/internal/domain"
"github.com/zitadel/zitadel/internal/repository/user/authenticator"
"github.com/zitadel/zitadel/internal/zerrors" "github.com/zitadel/zitadel/internal/zerrors"
) )
@ -17,91 +16,60 @@ type AddUsername struct {
} }
func (c *Commands) AddUsername(ctx context.Context, username *AddUsername) (*domain.ObjectDetails, error) { func (c *Commands) AddUsername(ctx context.Context, username *AddUsername) (*domain.ObjectDetails, error) {
existing, err := existingSchemaUserWithPermission(ctx, c, username.ResourceOwner, username.UserID) if username.UserID == "" {
return nil, zerrors.ThrowInvalidArgument(nil, "COMMAND-aS3Vz5t6BS", "Errors.IDMissing")
}
schemauser, err := existingSchemaUser(ctx, c, username.ResourceOwner, username.UserID)
if err != nil { if err != nil {
return nil, err return nil, err
} }
_, err = existingSchema(ctx, c, "", schemauser.SchemaID)
if err != nil {
return nil, err
}
// TODO check for possible authenticators
id, err := c.idGenerator.Next() id, err := c.idGenerator.Next()
if err != nil { if err != nil {
return nil, err return nil, err
} }
events, err := c.eventstore.Push(ctx, writeModel, err := c.getSchemaUsernameWM(ctx, schemauser.ResourceOwner, schemauser.AggregateID, id)
authenticator.NewUsernameCreatedEvent(ctx,
&authenticator.NewAggregate(id, existing.ResourceOwner).Aggregate,
existing.AggregateID,
username.IsOrgSpecific,
username.Username,
),
)
if err != nil { if err != nil {
return nil, err return nil, err
} }
return pushedEventsToObjectDetails(events), nil
events, err := writeModel.NewCreate(ctx, username.IsOrgSpecific, username.Username)
if err != nil {
return nil, err
}
return c.pushAppendAndReduceDetails(ctx, writeModel, events...)
} }
func (c *Commands) DeleteUsername(ctx context.Context, resourceOwner, userID, id string) (_ *domain.ObjectDetails, err error) { func (c *Commands) DeleteUsername(ctx context.Context, resourceOwner, userID, id string) (_ *domain.ObjectDetails, err error) {
existing, err := c.getSchemaUsernameExistsWithPermission(ctx, resourceOwner, userID, id)
if err != nil {
return nil, err
}
events, err := c.eventstore.Push(ctx,
authenticator.NewUsernameDeletedEvent(ctx,
&authenticator.NewAggregate(id, existing.ResourceOwner).Aggregate,
existing.IsOrgSpecific,
existing.Username,
),
)
if err != nil {
return nil, err
}
return pushedEventsToObjectDetails(events), nil
}
func (c *Commands) getSchemaUsernameExistsWithPermission(ctx context.Context, resourceOwner, userID, id string) (*UsernameV3WriteModel, error) {
if userID == "" { if userID == "" {
return nil, zerrors.ThrowInvalidArgument(nil, "COMMAND-J6ybG5WZiy", "Errors.IDMissing") return nil, zerrors.ThrowInvalidArgument(nil, "COMMAND-J6ybG5WZiy", "Errors.IDMissing")
} }
if id == "" { if id == "" {
return nil, zerrors.ThrowInvalidArgument(nil, "COMMAND-PoSU5BOZCi", "Errors.IDMissing") return nil, zerrors.ThrowInvalidArgument(nil, "COMMAND-PoSU5BOZCi", "Errors.IDMissing")
} }
writeModel := NewUsernameV3WriteModel(resourceOwner, userID, id)
if err := c.eventstore.FilterToQueryReducer(ctx, writeModel); err != nil { writeModel, err := c.getSchemaUsernameWM(ctx, resourceOwner, userID, id)
if err != nil {
return nil, err return nil, err
} }
if writeModel.Username == "" {
return nil, zerrors.ThrowNotFound(nil, "TODO", "TODO") events, err := writeModel.NewDelete(ctx)
if err != nil {
return nil, err
}
return c.pushAppendAndReduceDetails(ctx, writeModel, events...)
} }
if err := c.checkPermissionUpdateUser(ctx, writeModel.ResourceOwner, writeModel.UserID); err != nil { func (c *Commands) getSchemaUsernameWM(ctx context.Context, resourceOwner, userID, id string) (*UsernameV3WriteModel, error) {
writeModel := NewUsernameV3WriteModel(resourceOwner, userID, id, c.checkPermission)
if err := c.eventstore.FilterToQueryReducer(ctx, writeModel); err != nil {
return nil, err return nil, err
} }
return writeModel, nil return writeModel, nil
} }
func existingSchemaUserWithPermission(ctx context.Context, c *Commands, resourceOwner, userID string) (*UserV3WriteModel, error) {
if userID == "" {
return nil, zerrors.ThrowInvalidArgument(nil, "COMMAND-aS3Vz5t6BS", "Errors.IDMissing")
}
existingUser, err := c.getSchemaUserWMForState(ctx, resourceOwner, userID)
if err != nil {
return nil, err
}
if !existingUser.Exists() {
return nil, zerrors.ThrowNotFound(nil, "COMMAND-6T2xrOHxTx", "Errors.User.NotFound")
}
if err := c.checkPermissionUpdateUser(ctx, existingUser.ResourceOwner, existingUser.AggregateID); err != nil {
return nil, err
}
existingSchema, err := c.getSchemaWriteModelByID(ctx, "", existingUser.SchemaID)
if err != nil {
return nil, err
}
if !existingSchema.Exists() {
return nil, zerrors.ThrowNotFound(nil, "COMMAND-6T2xrOHxTx", "TODO")
}
// TODO possible authenticators check
return existingUser, nil
}

View File

@ -1,8 +1,13 @@
package command package command
import ( import (
"context"
"github.com/zitadel/zitadel/internal/api/authz"
"github.com/zitadel/zitadel/internal/domain"
"github.com/zitadel/zitadel/internal/eventstore" "github.com/zitadel/zitadel/internal/eventstore"
"github.com/zitadel/zitadel/internal/repository/user/authenticator" "github.com/zitadel/zitadel/internal/repository/user/authenticator"
"github.com/zitadel/zitadel/internal/zerrors"
) )
type UsernameV3WriteModel struct { type UsernameV3WriteModel struct {
@ -10,15 +15,22 @@ type UsernameV3WriteModel struct {
UserID string UserID string
Username string Username string
IsOrgSpecific bool IsOrgSpecific bool
checkPermission domain.PermissionCheck
} }
func NewUsernameV3WriteModel(resourceOwner, userID, id string) *UsernameV3WriteModel { func (wm *UsernameV3WriteModel) GetWriteModel() *eventstore.WriteModel {
return &wm.WriteModel
}
func NewUsernameV3WriteModel(resourceOwner, userID, id string, checkPermission domain.PermissionCheck) *UsernameV3WriteModel {
return &UsernameV3WriteModel{ return &UsernameV3WriteModel{
WriteModel: eventstore.WriteModel{ WriteModel: eventstore.WriteModel{
AggregateID: id, AggregateID: id,
ResourceOwner: resourceOwner, ResourceOwner: resourceOwner,
}, },
UserID: userID, UserID: userID,
checkPermission: checkPermission,
} }
} }
@ -52,3 +64,74 @@ func (wm *UsernameV3WriteModel) Query() *eventstore.SearchQueryBuilder {
authenticator.UsernameDeletedType, authenticator.UsernameDeletedType,
).Builder() ).Builder()
} }
func (wm *UsernameV3WriteModel) checkPermissionWrite(ctx context.Context) error {
if wm.UserID == authz.GetCtxData(ctx).UserID {
return nil
}
if err := wm.checkPermission(ctx, domain.PermissionUserWrite, wm.ResourceOwner, wm.UserID); err != nil {
return err
}
return nil
}
func (wm *UsernameV3WriteModel) NewCreate(
ctx context.Context,
isOrgSpecific bool,
username string,
) ([]eventstore.Command, error) {
if err := wm.NotExists(); err != nil {
return nil, err
}
if err := wm.checkPermissionWrite(ctx); err != nil {
return nil, err
}
return []eventstore.Command{
authenticator.NewUsernameCreatedEvent(ctx,
AuthenticatorAggregateFromWriteModel(wm.GetWriteModel()),
wm.UserID,
isOrgSpecific,
username,
),
}, nil
}
func (wm *UsernameV3WriteModel) NewDelete(ctx context.Context) ([]eventstore.Command, error) {
if err := wm.Exists(); err != nil {
return nil, err
}
if err := wm.checkPermissionWrite(ctx); err != nil {
return nil, err
}
return []eventstore.Command{
authenticator.NewUsernameDeletedEvent(ctx,
AuthenticatorAggregateFromWriteModel(wm.GetWriteModel()),
wm.IsOrgSpecific,
wm.Username,
),
}, nil
}
func (wm *UsernameV3WriteModel) Exists() error {
if wm.Username == "" {
return zerrors.ThrowNotFound(nil, "TODO", "TODO")
}
return nil
}
func (wm *UsernameV3WriteModel) NotExists() error {
if err := wm.Exists(); err != nil {
return nil
}
return zerrors.ThrowAlreadyExists(nil, "TODO", "TODO")
}
func AuthenticatorAggregateFromWriteModel(wm *eventstore.WriteModel) *eventstore.Aggregate {
return &eventstore.Aggregate{
ID: wm.AggregateID,
Type: authenticator.AggregateType,
ResourceOwner: wm.ResourceOwner,
InstanceID: wm.InstanceID,
Version: authenticator.AggregateVersion,
}
}

View File

@ -123,7 +123,7 @@ func TestCommands_AddUsername(t *testing.T) {
}, },
res{ res{
err: func(err error) bool { err: func(err error) bool {
return errors.Is(err, zerrors.ThrowNotFound(nil, "COMMAND-6T2xrOHxTx", "Errors.User.NotFound")) return errors.Is(err, zerrors.ThrowNotFound(nil, "COMMAND-syHyCsGmvM", "Errors.User.NotFound"))
}, },
}, },
}, },
@ -132,8 +132,11 @@ func TestCommands_AddUsername(t *testing.T) {
fields{ fields{
eventstore: expectEventstore( eventstore: expectEventstore(
filterSchemaUserExisting(), filterSchemaUserExisting(),
filterSchemaExisting(),
expectFilter(),
), ),
checkPermission: newMockPermissionCheckNotAllowed(), checkPermission: newMockPermissionCheckNotAllowed(),
idGenerator: mock.ExpectID(t, "username1"),
}, },
args{ args{
ctx: authz.NewMockContext("instanceID", "", ""), ctx: authz.NewMockContext("instanceID", "", ""),
@ -164,7 +167,7 @@ func TestCommands_AddUsername(t *testing.T) {
}, },
res{ res{
err: func(err error) bool { err: func(err error) bool {
return errors.Is(err, zerrors.ThrowNotFound(nil, "COMMAND-6T2xrOHxTx", "TODO")) return errors.Is(err, zerrors.ThrowNotFound(nil, "COMMAND-VLDTtxT3If", "Errors.UserSchema.NotExists"))
}, },
}, },
}, },
@ -174,6 +177,7 @@ func TestCommands_AddUsername(t *testing.T) {
eventstore: expectEventstore( eventstore: expectEventstore(
filterSchemaUserExisting(), filterSchemaUserExisting(),
filterSchemaExisting(), filterSchemaExisting(),
expectFilter(),
expectPush( expectPush(
authenticator.NewUsernameCreatedEvent( authenticator.NewUsernameCreatedEvent(
context.Background(), context.Background(),
@ -207,6 +211,7 @@ func TestCommands_AddUsername(t *testing.T) {
eventstore: expectEventstore( eventstore: expectEventstore(
filterSchemaUserExisting(), filterSchemaUserExisting(),
filterSchemaExisting(), filterSchemaExisting(),
expectFilter(),
expectPush( expectPush(
authenticator.NewUsernameCreatedEvent( authenticator.NewUsernameCreatedEvent(
context.Background(), context.Background(),