mirror of
https://github.com/zitadel/zitadel.git
synced 2024-12-15 12:27:59 +00:00
221 lines
6.3 KiB
Go
221 lines
6.3 KiB
Go
|
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
|
||
|
}
|