feat: add schema user create and remove (#8494)

# Which Problems Are Solved

Added functionality that user with a userschema can be created and
removed.

# How the Problems Are Solved

Added logic and moved APIs so that everything is API v3 conform.

# Additional Changes

- move of user and userschema API to resources folder
- changed testing and parameters
- some renaming

# Additional Context

closes #7308

---------

Co-authored-by: Elio Bischof <elio@zitadel.com>
This commit is contained in:
Stefan Benz
2024-08-28 21:46:45 +02:00
committed by GitHub
parent 90b908c361
commit 41ae35f2ef
61 changed files with 5766 additions and 2247 deletions

View File

@@ -12,6 +12,8 @@ import (
)
type CreateUserSchema struct {
Details *domain.ObjectDetails
ResourceOwner string
Type string
Schema json.RawMessage
@@ -33,7 +35,9 @@ func (s *CreateUserSchema) Valid() error {
return nil
}
type UpdateUserSchema struct {
type ChangeUserSchema struct {
Details *domain.ObjectDetails
ID string
ResourceOwner string
Type *string
@@ -41,7 +45,7 @@ type UpdateUserSchema struct {
PossibleAuthenticators []domain.AuthenticatorType
}
func (s *UpdateUserSchema) Valid() error {
func (s *ChangeUserSchema) Valid() error {
if s.ID == "" {
return zerrors.ThrowInvalidArgument(nil, "COMMA-H5421", "Errors.IDMissing")
}
@@ -59,40 +63,43 @@ func (s *UpdateUserSchema) Valid() error {
return nil
}
func (c *Commands) CreateUserSchema(ctx context.Context, userSchema *CreateUserSchema) (string, *domain.ObjectDetails, error) {
func (c *Commands) CreateUserSchema(ctx context.Context, userSchema *CreateUserSchema) error {
if err := userSchema.Valid(); err != nil {
return "", nil, err
return err
}
if userSchema.ResourceOwner == "" {
return "", nil, zerrors.ThrowInvalidArgument(nil, "COMMA-J3hhj", "Errors.ResourceOwnerMissing")
return zerrors.ThrowInvalidArgument(nil, "COMMA-J3hhj", "Errors.ResourceOwnerMissing")
}
id, err := c.idGenerator.Next()
if err != nil {
return "", nil, err
return err
}
writeModel := NewUserSchemaWriteModel(id, userSchema.ResourceOwner)
err = c.pushAppendAndReduce(ctx, writeModel,
writeModel, err := c.getSchemaWriteModelByID(ctx, userSchema.ResourceOwner, id)
if err != nil {
return err
}
if err := c.pushAppendAndReduce(ctx, writeModel,
schema.NewCreatedEvent(ctx,
UserSchemaAggregateFromWriteModel(&writeModel.WriteModel),
userSchema.Type, userSchema.Schema, userSchema.PossibleAuthenticators,
),
)
if err != nil {
return "", nil, err
); err != nil {
return err
}
return id, writeModelToObjectDetails(&writeModel.WriteModel), nil
userSchema.Details = writeModelToObjectDetails(&writeModel.WriteModel)
return nil
}
func (c *Commands) UpdateUserSchema(ctx context.Context, userSchema *UpdateUserSchema) (*domain.ObjectDetails, error) {
func (c *Commands) ChangeUserSchema(ctx context.Context, userSchema *ChangeUserSchema) error {
if err := userSchema.Valid(); err != nil {
return nil, err
return err
}
writeModel := NewUserSchemaWriteModel(userSchema.ID, userSchema.ResourceOwner)
if err := c.eventstore.FilterToQueryReducer(ctx, writeModel); err != nil {
return nil, err
writeModel, err := c.getSchemaWriteModelByID(ctx, userSchema.ResourceOwner, userSchema.ID)
if err != nil {
return err
}
if writeModel.State != domain.UserSchemaStateActive {
return nil, zerrors.ThrowPreconditionFailed(nil, "COMMA-HB3e1", "Errors.UserSchema.NotActive")
return zerrors.ThrowPreconditionFailed(nil, "COMMA-HB3e1", "Errors.UserSchema.NotActive")
}
updatedEvent := writeModel.NewUpdatedEvent(
ctx,
@@ -102,29 +109,30 @@ func (c *Commands) UpdateUserSchema(ctx context.Context, userSchema *UpdateUserS
userSchema.PossibleAuthenticators,
)
if updatedEvent == nil {
return writeModelToObjectDetails(&writeModel.WriteModel), nil
userSchema.Details = writeModelToObjectDetails(&writeModel.WriteModel)
return nil
}
if err := c.pushAppendAndReduce(ctx, writeModel, updatedEvent); err != nil {
return nil, err
return err
}
return writeModelToObjectDetails(&writeModel.WriteModel), nil
userSchema.Details = writeModelToObjectDetails(&writeModel.WriteModel)
return nil
}
func (c *Commands) DeactivateUserSchema(ctx context.Context, id, resourceOwner string) (*domain.ObjectDetails, error) {
if id == "" {
return nil, zerrors.ThrowInvalidArgument(nil, "COMMA-Vvf3w", "Errors.IDMissing")
}
writeModel := NewUserSchemaWriteModel(id, resourceOwner)
if err := c.eventstore.FilterToQueryReducer(ctx, writeModel); err != nil {
writeModel, err := c.getSchemaWriteModelByID(ctx, resourceOwner, id)
if err != nil {
return nil, err
}
if writeModel.State != domain.UserSchemaStateActive {
return nil, zerrors.ThrowPreconditionFailed(nil, "COMMA-E4t4z", "Errors.UserSchema.NotActive")
}
err := c.pushAppendAndReduce(ctx, writeModel,
if err := c.pushAppendAndReduce(ctx, writeModel,
schema.NewDeactivatedEvent(ctx, UserSchemaAggregateFromWriteModel(&writeModel.WriteModel)),
)
if err != nil {
); err != nil {
return nil, err
}
return writeModelToObjectDetails(&writeModel.WriteModel), nil
@@ -134,17 +142,16 @@ func (c *Commands) ReactivateUserSchema(ctx context.Context, id, resourceOwner s
if id == "" {
return nil, zerrors.ThrowInvalidArgument(nil, "COMMA-wq3Gw", "Errors.IDMissing")
}
writeModel := NewUserSchemaWriteModel(id, resourceOwner)
if err := c.eventstore.FilterToQueryReducer(ctx, writeModel); err != nil {
writeModel, err := c.getSchemaWriteModelByID(ctx, resourceOwner, id)
if err != nil {
return nil, err
}
if writeModel.State != domain.UserSchemaStateInactive {
return nil, zerrors.ThrowPreconditionFailed(nil, "COMMA-DGzh5", "Errors.UserSchema.NotInactive")
}
err := c.pushAppendAndReduce(ctx, writeModel,
if err := c.pushAppendAndReduce(ctx, writeModel,
schema.NewReactivatedEvent(ctx, UserSchemaAggregateFromWriteModel(&writeModel.WriteModel)),
)
if err != nil {
); err != nil {
return nil, err
}
return writeModelToObjectDetails(&writeModel.WriteModel), nil
@@ -154,18 +161,17 @@ func (c *Commands) DeleteUserSchema(ctx context.Context, id, resourceOwner strin
if id == "" {
return nil, zerrors.ThrowInvalidArgument(nil, "COMMA-E22gg", "Errors.IDMissing")
}
writeModel := NewUserSchemaWriteModel(id, resourceOwner)
if err := c.eventstore.FilterToQueryReducer(ctx, writeModel); err != nil {
writeModel, err := c.getSchemaWriteModelByID(ctx, resourceOwner, id)
if err != nil {
return nil, err
}
if !writeModel.Exists() {
return nil, zerrors.ThrowPreconditionFailed(nil, "COMMA-Grg41", "Errors.UserSchema.NotExists")
}
// TODO: check for users based on that schema; this is only possible with / after https://github.com/zitadel/zitadel/issues/7308
err := c.pushAppendAndReduce(ctx, writeModel,
if err := c.pushAppendAndReduce(ctx, writeModel,
schema.NewDeletedEvent(ctx, UserSchemaAggregateFromWriteModel(&writeModel.WriteModel), writeModel.SchemaType),
)
if err != nil {
); err != nil {
return nil, err
}
return writeModelToObjectDetails(&writeModel.WriteModel), nil
@@ -178,3 +184,11 @@ func validateUserSchema(userSchema json.RawMessage) error {
}
return nil
}
func (c *Commands) getSchemaWriteModelByID(ctx context.Context, resourceOwner, id string) (*UserSchemaWriteModel, error) {
writeModel := NewUserSchemaWriteModel(resourceOwner, id, "")
if err := c.eventstore.FilterToQueryReducer(ctx, writeModel); err != nil {
return nil, err
}
return writeModel, nil
}

View File

@@ -19,14 +19,16 @@ type UserSchemaWriteModel struct {
Schema json.RawMessage
PossibleAuthenticators []domain.AuthenticatorType
State domain.UserSchemaState
Revision uint64
}
func NewUserSchemaWriteModel(schemaID, resourceOwner string) *UserSchemaWriteModel {
func NewUserSchemaWriteModel(resourceOwner, schemaID, ty string) *UserSchemaWriteModel {
return &UserSchemaWriteModel{
WriteModel: eventstore.WriteModel{
AggregateID: schemaID,
ResourceOwner: resourceOwner,
},
SchemaType: ty,
}
}
@@ -38,10 +40,14 @@ func (wm *UserSchemaWriteModel) Reduce() error {
wm.Schema = e.Schema
wm.PossibleAuthenticators = e.PossibleAuthenticators
wm.State = domain.UserSchemaStateActive
wm.Revision = 1
case *schema.UpdatedEvent:
if e.SchemaType != nil {
wm.SchemaType = *e.SchemaType
}
if e.SchemaRevision != nil {
wm.Revision = *e.SchemaRevision
}
if len(e.Schema) > 0 {
wm.Schema = e.Schema
}
@@ -60,7 +66,7 @@ func (wm *UserSchemaWriteModel) Reduce() error {
}
func (wm *UserSchemaWriteModel) Query() *eventstore.SearchQueryBuilder {
return eventstore.NewSearchQueryBuilder(eventstore.ColumnsEvent).
query := eventstore.NewSearchQueryBuilder(eventstore.ColumnsEvent).
ResourceOwner(wm.ResourceOwner).
AddQuery().
AggregateTypes(schema.AggregateType).
@@ -71,8 +77,13 @@ func (wm *UserSchemaWriteModel) Query() *eventstore.SearchQueryBuilder {
schema.DeactivatedType,
schema.ReactivatedType,
schema.DeletedType,
).
Builder()
)
if wm.SchemaType != "" {
query = query.EventData(map[string]interface{}{"schemaType": wm.SchemaType})
}
return query.Builder()
}
func (wm *UserSchemaWriteModel) NewUpdatedEvent(
ctx context.Context,
@@ -87,6 +98,8 @@ func (wm *UserSchemaWriteModel) NewUpdatedEvent(
}
if !bytes.Equal(wm.Schema, userSchema) {
changes = append(changes, schema.ChangeSchema(userSchema))
// change revision if the content of the schema changed
changes = append(changes, schema.IncreaseRevision(wm.Revision))
}
if len(possibleAuthenticators) > 0 && slices.Compare(wm.PossibleAuthenticators, possibleAuthenticators) != 0 {
changes = append(changes, schema.ChangePossibleAuthenticators(possibleAuthenticators))

View File

@@ -27,7 +27,6 @@ func TestCommands_CreateUserSchema(t *testing.T) {
userSchema *CreateUserSchema
}
type res struct {
id string
details *domain.ObjectDetails
err error
}
@@ -107,6 +106,7 @@ func TestCommands_CreateUserSchema(t *testing.T) {
"empty user schema created",
fields{
eventstore: expectEventstore(
expectFilter(),
expectPush(
schema.NewCreatedEvent(
context.Background(),
@@ -131,8 +131,8 @@ func TestCommands_CreateUserSchema(t *testing.T) {
},
},
res{
id: "id1",
details: &domain.ObjectDetails{
ID: "id1",
ResourceOwner: "instanceID",
},
},
@@ -141,6 +141,7 @@ func TestCommands_CreateUserSchema(t *testing.T) {
"user schema created",
fields{
eventstore: expectEventstore(
expectFilter(),
expectPush(
schema.NewCreatedEvent(
context.Background(),
@@ -181,8 +182,8 @@ func TestCommands_CreateUserSchema(t *testing.T) {
},
},
res{
id: "id1",
details: &domain.ObjectDetails{
ID: "id1",
ResourceOwner: "instanceID",
},
},
@@ -220,6 +221,7 @@ func TestCommands_CreateUserSchema(t *testing.T) {
"user schema with permission created",
fields{
eventstore: expectEventstore(
expectFilter(),
expectPush(
schema.NewCreatedEvent(
context.Background(),
@@ -266,8 +268,8 @@ func TestCommands_CreateUserSchema(t *testing.T) {
},
},
res{
id: "id1",
details: &domain.ObjectDetails{
ID: "id1",
ResourceOwner: "instanceID",
},
},
@@ -279,21 +281,20 @@ func TestCommands_CreateUserSchema(t *testing.T) {
eventstore: tt.fields.eventstore(t),
idGenerator: tt.fields.idGenerator,
}
gotID, gotDetails, err := c.CreateUserSchema(tt.args.ctx, tt.args.userSchema)
assert.Equal(t, tt.res.id, gotID)
assertObjectDetails(t, tt.res.details, gotDetails)
err := c.CreateUserSchema(tt.args.ctx, tt.args.userSchema)
assertObjectDetails(t, tt.res.details, tt.args.userSchema.Details)
assert.ErrorIs(t, err, tt.res.err)
})
}
}
func TestCommands_UpdateUserSchema(t *testing.T) {
func TestCommands_ChangeUserSchema(t *testing.T) {
type fields struct {
eventstore func(t *testing.T) *eventstore.Eventstore
}
type args struct {
ctx context.Context
userSchema *UpdateUserSchema
userSchema *ChangeUserSchema
}
type res struct {
details *domain.ObjectDetails
@@ -312,7 +313,7 @@ func TestCommands_UpdateUserSchema(t *testing.T) {
},
args{
ctx: authz.NewMockContext("instanceID", "", ""),
userSchema: &UpdateUserSchema{},
userSchema: &ChangeUserSchema{},
},
res{
err: zerrors.ThrowInvalidArgument(nil, "COMMA-H5421", "Errors.IDMissing"),
@@ -325,7 +326,7 @@ func TestCommands_UpdateUserSchema(t *testing.T) {
},
args{
ctx: authz.NewMockContext("instanceID", "", ""),
userSchema: &UpdateUserSchema{
userSchema: &ChangeUserSchema{
ID: "id1",
Type: gu.Ptr(""),
},
@@ -341,7 +342,7 @@ func TestCommands_UpdateUserSchema(t *testing.T) {
},
args{
ctx: authz.NewMockContext("instanceID", "", ""),
userSchema: &UpdateUserSchema{
userSchema: &ChangeUserSchema{
ID: "id1",
},
},
@@ -356,7 +357,7 @@ func TestCommands_UpdateUserSchema(t *testing.T) {
},
args{
ctx: authz.NewMockContext("instanceID", "", ""),
userSchema: &UpdateUserSchema{
userSchema: &ChangeUserSchema{
ID: "id1",
Schema: json.RawMessage(`{
"properties": {
@@ -379,7 +380,7 @@ func TestCommands_UpdateUserSchema(t *testing.T) {
},
args{
ctx: authz.NewMockContext("instanceID", "", ""),
userSchema: &UpdateUserSchema{
userSchema: &ChangeUserSchema{
ID: "id1",
Schema: json.RawMessage(`{}`),
PossibleAuthenticators: []domain.AuthenticatorType{
@@ -400,7 +401,7 @@ func TestCommands_UpdateUserSchema(t *testing.T) {
},
args{
ctx: authz.NewMockContext("instanceID", "", ""),
userSchema: &UpdateUserSchema{
userSchema: &ChangeUserSchema{
ID: "id1",
Type: gu.Ptr("type"),
Schema: json.RawMessage(`{}`),
@@ -432,7 +433,7 @@ func TestCommands_UpdateUserSchema(t *testing.T) {
},
args{
ctx: authz.NewMockContext("instanceID", "", ""),
userSchema: &UpdateUserSchema{
userSchema: &ChangeUserSchema{
ID: "id1",
Type: gu.Ptr("type"),
Schema: json.RawMessage(`{}`),
@@ -473,7 +474,7 @@ func TestCommands_UpdateUserSchema(t *testing.T) {
},
args{
ctx: authz.NewMockContext("instanceID", "", ""),
userSchema: &UpdateUserSchema{
userSchema: &ChangeUserSchema{
ID: "id1",
Schema: json.RawMessage(`{}`),
Type: gu.Ptr("newType"),
@@ -515,7 +516,9 @@ func TestCommands_UpdateUserSchema(t *testing.T) {
schema.NewUpdatedEvent(
context.Background(),
&schema.NewAggregate("id1", "instanceID").Aggregate,
[]schema.Changes{schema.ChangeSchema(json.RawMessage(`{
[]schema.Changes{
schema.IncreaseRevision(1),
schema.ChangeSchema(json.RawMessage(`{
"$schema": "urn:zitadel:schema:v1",
"type": "object",
"properties": {
@@ -539,7 +542,7 @@ func TestCommands_UpdateUserSchema(t *testing.T) {
},
args{
ctx: authz.NewMockContext("instanceID", "", ""),
userSchema: &UpdateUserSchema{
userSchema: &ChangeUserSchema{
ID: "id1",
Schema: json.RawMessage(`{
"$schema": "urn:zitadel:schema:v1",
@@ -597,7 +600,7 @@ func TestCommands_UpdateUserSchema(t *testing.T) {
},
args{
ctx: authz.NewMockContext("instanceID", "", ""),
userSchema: &UpdateUserSchema{
userSchema: &ChangeUserSchema{
ID: "id1",
Schema: json.RawMessage(`{}`),
PossibleAuthenticators: []domain.AuthenticatorType{
@@ -618,9 +621,9 @@ func TestCommands_UpdateUserSchema(t *testing.T) {
c := &Commands{
eventstore: tt.fields.eventstore(t),
}
got, err := c.UpdateUserSchema(tt.args.ctx, tt.args.userSchema)
err := c.ChangeUserSchema(tt.args.ctx, tt.args.userSchema)
assert.ErrorIs(t, err, tt.res.err)
assertObjectDetails(t, tt.res.details, got)
assertObjectDetails(t, tt.res.details, tt.args.userSchema.Details)
})
}
}

220
internal/command/user_v3.go Normal file
View File

@@ -0,0 +1,220 @@
package command
import (
"bytes"
"context"
"encoding/json"
"github.com/zitadel/zitadel/internal/api/authz"
"github.com/zitadel/zitadel/internal/crypto"
"github.com/zitadel/zitadel/internal/domain"
domain_schema "github.com/zitadel/zitadel/internal/domain/schema"
"github.com/zitadel/zitadel/internal/eventstore"
"github.com/zitadel/zitadel/internal/repository/user/schemauser"
"github.com/zitadel/zitadel/internal/zerrors"
)
type CreateSchemaUser struct {
Details *domain.ObjectDetails
ResourceOwner string
SchemaID string
schemaRevision uint64
ID string
Data json.RawMessage
Email *Email
ReturnCodeEmail string
Phone *Phone
ReturnCodePhone string
}
func (s *CreateSchemaUser) Valid(ctx context.Context, c *Commands) (err error) {
if s.ResourceOwner == "" {
return zerrors.ThrowInvalidArgument(nil, "COMMAND-urEJKa1tJM", "Errors.ResourceOwnerMissing")
}
if s.SchemaID == "" {
return zerrors.ThrowInvalidArgument(nil, "COMMAND-TFo06JgnF2", "Errors.UserSchema.ID.Missing")
}
schemaWriteModel, err := c.getSchemaWriteModelByID(ctx, "", s.SchemaID)
if err != nil {
return err
}
if !schemaWriteModel.Exists() {
return zerrors.ThrowPreconditionFailed(nil, "COMMAND-N9QOuN4F7o", "Errors.UserSchema.NotExists")
}
s.schemaRevision = schemaWriteModel.Revision
if s.ID == "" {
s.ID, err = c.idGenerator.Next()
if err != nil {
return err
}
}
// get role for permission check in schema through extension
role, err := c.getSchemaRoleForWrite(ctx, s.ResourceOwner, s.ID)
if err != nil {
return err
}
schema, err := domain_schema.NewSchema(role, bytes.NewReader(schemaWriteModel.Schema))
if err != nil {
return err
}
var v interface{}
if err := json.Unmarshal(s.Data, &v); err != nil {
return zerrors.ThrowInvalidArgument(nil, "COMMAND-7o3ZGxtXUz", "Errors.User.Invalid")
}
if err := schema.Validate(v); err != nil {
return zerrors.ThrowPreconditionFailed(nil, "COMMAND-SlKXqLSeL6", "Errors.UserSchema.Data.Invalid")
}
if s.Email != nil && s.Email.Address != "" {
if err := s.Email.Validate(); err != nil {
return err
}
}
if s.Phone != nil && s.Phone.Number != "" {
if s.Phone.Number, err = s.Phone.Number.Normalize(); err != nil {
return err
}
}
return nil
}
func (c *Commands) getSchemaRoleForWrite(ctx context.Context, resourceOwner, userID string) (domain_schema.Role, error) {
if userID == authz.GetCtxData(ctx).UserID {
return domain_schema.RoleSelf, nil
}
if err := c.checkPermission(ctx, domain.PermissionUserWrite, resourceOwner, userID); err != nil {
return domain_schema.RoleUnspecified, err
}
return domain_schema.RoleOwner, nil
}
func (c *Commands) CreateSchemaUser(ctx context.Context, user *CreateSchemaUser, alg crypto.EncryptionAlgorithm) (err error) {
if err := user.Valid(ctx, c); err != nil {
return err
}
writeModel, err := c.getSchemaUserExists(ctx, user.ResourceOwner, user.ID)
if err != nil {
return err
}
if writeModel.Exists() {
return zerrors.ThrowPreconditionFailed(nil, "COMMAND-Nn8CRVlkeZ", "Errors.User.AlreadyExists")
}
userAgg := UserV3AggregateFromWriteModel(&writeModel.WriteModel)
events := []eventstore.Command{
schemauser.NewCreatedEvent(ctx,
userAgg,
user.SchemaID, user.schemaRevision, user.Data,
),
}
if user.Email != nil {
events, user.ReturnCodeEmail, err = c.updateSchemaUserEmail(ctx, events, userAgg, user.Email, alg)
if err != nil {
return err
}
}
if user.Phone != nil {
events, user.ReturnCodePhone, err = c.updateSchemaUserPhone(ctx, events, userAgg, user.Phone, alg)
if err != nil {
return err
}
}
if err := c.pushAppendAndReduce(ctx, writeModel, events...); err != nil {
return err
}
user.Details = writeModelToObjectDetails(&writeModel.WriteModel)
return nil
}
func (c *Commands) DeleteSchemaUser(ctx context.Context, id string) (*domain.ObjectDetails, error) {
if id == "" {
return nil, zerrors.ThrowInvalidArgument(nil, "COMMAND-Vs4wJCME7T", "Errors.IDMissing")
}
writeModel, err := c.getSchemaUserExists(ctx, "", id)
if err != nil {
return nil, err
}
if !writeModel.Exists() {
return nil, zerrors.ThrowNotFound(nil, "COMMAND-syHyCsGmvM", "Errors.User.NotFound")
}
if err := c.checkPermissionDeleteUser(ctx, writeModel.ResourceOwner, writeModel.AggregateID); err != nil {
return nil, err
}
if err := c.pushAppendAndReduce(ctx, writeModel,
schemauser.NewDeletedEvent(ctx, UserV3AggregateFromWriteModel(&writeModel.WriteModel)),
); err != nil {
return nil, err
}
return writeModelToObjectDetails(&writeModel.WriteModel), nil
}
func (c *Commands) updateSchemaUserEmail(ctx context.Context, events []eventstore.Command, agg *eventstore.Aggregate, email *Email, alg crypto.EncryptionAlgorithm) (_ []eventstore.Command, plainCode string, err error) {
events = append(events, schemauser.NewEmailUpdatedEvent(ctx,
agg,
email.Address,
))
if email.Verified {
events = append(events, schemauser.NewEmailVerifiedEvent(ctx, agg))
} else {
cryptoCode, err := c.newEmailCode(ctx, c.eventstore.Filter, alg) //nolint:staticcheck
if err != nil {
return nil, "", err
}
if email.ReturnCode {
plainCode = cryptoCode.Plain
}
events = append(events, schemauser.NewEmailCodeAddedEvent(ctx, agg,
cryptoCode.Crypted,
cryptoCode.Expiry,
email.URLTemplate,
email.ReturnCode,
))
}
return events, plainCode, nil
}
func (c *Commands) updateSchemaUserPhone(ctx context.Context, events []eventstore.Command, agg *eventstore.Aggregate, phone *Phone, alg crypto.EncryptionAlgorithm) (_ []eventstore.Command, plainCode string, err error) {
events = append(events, schemauser.NewPhoneChangedEvent(ctx,
agg,
phone.Number,
))
if phone.Verified {
events = append(events, schemauser.NewPhoneVerifiedEvent(ctx, agg))
} else {
cryptoCode, err := c.newPhoneCode(ctx, c.eventstore.Filter, alg) //nolint:staticcheck
if err != nil {
return nil, "", err
}
if phone.ReturnCode {
plainCode = cryptoCode.Plain
}
events = append(events, schemauser.NewPhoneCodeAddedEvent(ctx, agg,
cryptoCode.Crypted,
cryptoCode.Expiry,
phone.ReturnCode,
))
}
return events, plainCode, nil
}
func (c *Commands) getSchemaUserExists(ctx context.Context, resourceOwner, id string) (*UserV3WriteModel, error) {
writeModel := NewExistsUserV3WriteModel(resourceOwner, id)
if err := c.eventstore.FilterToQueryReducer(ctx, writeModel); err != nil {
return nil, err
}
return writeModel, nil
}

View File

@@ -0,0 +1,174 @@
package command
import (
"bytes"
"context"
"encoding/json"
"github.com/zitadel/zitadel/internal/domain"
"github.com/zitadel/zitadel/internal/eventstore"
"github.com/zitadel/zitadel/internal/repository/user/schemauser"
)
type UserV3WriteModel struct {
eventstore.WriteModel
PhoneWM bool
EmailWM bool
DataWM bool
SchemaID string
SchemaRevision uint64
Email string
IsEmailVerified bool
EmailVerifiedFailedCount int
Phone string
IsPhoneVerified bool
PhoneVerifiedFailedCount int
Data json.RawMessage
State domain.UserState
}
func NewExistsUserV3WriteModel(resourceOwner, userID string) *UserV3WriteModel {
return &UserV3WriteModel{
WriteModel: eventstore.WriteModel{
AggregateID: userID,
ResourceOwner: resourceOwner,
},
PhoneWM: false,
EmailWM: false,
DataWM: false,
}
}
func NewUserV3WriteModel(resourceOwner, userID string) *UserV3WriteModel {
return &UserV3WriteModel{
WriteModel: eventstore.WriteModel{
AggregateID: userID,
ResourceOwner: resourceOwner,
},
PhoneWM: true,
EmailWM: true,
DataWM: true,
}
}
func (wm *UserV3WriteModel) Reduce() error {
for _, event := range wm.Events {
switch e := event.(type) {
case *schemauser.CreatedEvent:
wm.SchemaID = e.SchemaID
wm.SchemaRevision = 1
wm.Data = e.Data
wm.State = domain.UserStateActive
case *schemauser.UpdatedEvent:
if e.SchemaID != nil {
wm.SchemaID = *e.SchemaID
}
if e.SchemaRevision != nil {
wm.SchemaRevision = *e.SchemaRevision
}
if len(e.Data) > 0 {
wm.Data = e.Data
}
case *schemauser.DeletedEvent:
wm.State = domain.UserStateDeleted
case *schemauser.EmailUpdatedEvent:
wm.Email = string(e.EmailAddress)
case *schemauser.EmailCodeAddedEvent:
wm.IsEmailVerified = false
wm.EmailVerifiedFailedCount = 0
case *schemauser.EmailVerifiedEvent:
wm.IsEmailVerified = true
wm.EmailVerifiedFailedCount = 0
case *schemauser.EmailVerificationFailedEvent:
wm.EmailVerifiedFailedCount += 1
case *schemauser.PhoneChangedEvent:
wm.Phone = string(e.PhoneNumber)
case *schemauser.PhoneCodeAddedEvent:
wm.IsPhoneVerified = false
wm.PhoneVerifiedFailedCount = 0
case *schemauser.PhoneVerifiedEvent:
wm.PhoneVerifiedFailedCount = 0
wm.IsPhoneVerified = true
case *schemauser.PhoneVerificationFailedEvent:
wm.PhoneVerifiedFailedCount += 1
}
}
return wm.WriteModel.Reduce()
}
func (wm *UserV3WriteModel) Query() *eventstore.SearchQueryBuilder {
query := eventstore.NewSearchQueryBuilder(eventstore.ColumnsEvent).
ResourceOwner(wm.ResourceOwner).
AddQuery().
AggregateTypes(schemauser.AggregateType).
AggregateIDs(wm.AggregateID).
EventTypes(
schemauser.CreatedType,
schemauser.DeletedType,
)
if wm.DataWM {
query = query.EventTypes(
schemauser.UpdatedType,
)
}
if wm.EmailWM {
query = query.EventTypes(
schemauser.EmailUpdatedType,
schemauser.EmailVerifiedType,
schemauser.EmailCodeAddedType,
schemauser.EmailVerificationFailedType,
)
}
if wm.PhoneWM {
query = query.EventTypes(
schemauser.PhoneUpdatedType,
schemauser.PhoneVerifiedType,
schemauser.PhoneCodeAddedType,
schemauser.PhoneVerificationFailedType,
)
}
return query.Builder()
}
func (wm *UserV3WriteModel) NewUpdatedEvent(
ctx context.Context,
agg *eventstore.Aggregate,
schemaID *string,
schemaRevision *uint64,
data json.RawMessage,
) *schemauser.UpdatedEvent {
changes := make([]schemauser.Changes, 0)
if schemaID != nil && wm.SchemaID != *schemaID {
changes = append(changes, schemauser.ChangeSchemaID(wm.SchemaID, *schemaID))
}
if schemaRevision != nil && wm.SchemaRevision != *schemaRevision {
changes = append(changes, schemauser.ChangeSchemaRevision(wm.SchemaRevision, *schemaRevision))
}
if !bytes.Equal(wm.Data, data) {
changes = append(changes, schemauser.ChangeData(data))
}
if len(changes) == 0 {
return nil
}
return schemauser.NewUpdatedEvent(ctx, agg, changes)
}
func UserV3AggregateFromWriteModel(wm *eventstore.WriteModel) *eventstore.Aggregate {
return &eventstore.Aggregate{
ID: wm.AggregateID,
Type: schemauser.AggregateType,
ResourceOwner: wm.ResourceOwner,
InstanceID: wm.InstanceID,
Version: schemauser.AggregateVersion,
}
}
func (wm *UserV3WriteModel) Exists() bool {
return wm.State != domain.UserStateDeleted && wm.State != domain.UserStateUnspecified
}

File diff suppressed because it is too large Load Diff