feat: New user (#1153)

* fix: use pointer in events

* fix: change user requests to command side

* fix: org policy

* fix: profile
This commit is contained in:
Fabi
2021-01-06 11:12:56 +01:00
committed by GitHub
parent 61d16e4621
commit 65a8efeb0e
51 changed files with 810 additions and 283 deletions
@@ -45,16 +45,15 @@ func (wm *IAMLabelPolicyWriteModel) NewChangedEvent(
primaryColor,
secondaryColor string,
) (*iam.LabelPolicyChangedEvent, bool) {
hasChanged := false
changedEvent := iam.NewLabelPolicyChangedEvent(ctx)
if wm.PrimaryColor != primaryColor {
hasChanged = true
changedEvent.PrimaryColor = primaryColor
changedEvent.PrimaryColor = &primaryColor
}
if wm.SecondaryColor != secondaryColor {
hasChanged = true
changedEvent.SecondaryColor = secondaryColor
changedEvent.SecondaryColor = &secondaryColor
}
return changedEvent, hasChanged
}
@@ -58,23 +58,23 @@ func (wm *IAMLoginPolicyWriteModel) NewChangedEvent(
changedEvent := iam.NewLoginPolicyChangedEvent(ctx)
if wm.AllowUserNamePassword == allowUsernamePassword {
hasChanged = true
changedEvent.AllowUserNamePassword = allowUsernamePassword
changedEvent.AllowUserNamePassword = &allowUsernamePassword
}
if wm.AllowRegister == allowRegister {
hasChanged = true
changedEvent.AllowRegister = allowRegister
changedEvent.AllowRegister = &allowRegister
}
if wm.AllowExternalIDP == allowExternalIDP {
hasChanged = true
changedEvent.AllowExternalIDP = allowExternalIDP
changedEvent.AllowExternalIDP = &allowExternalIDP
}
if wm.ForceMFA != forceMFA {
hasChanged = true
changedEvent.ForceMFA = forceMFA
changedEvent.ForceMFA = &forceMFA
}
if passwordlessType.Valid() && wm.PasswordlessType != passwordlessType {
hasChanged = true
changedEvent.PasswordlessType = passwordlessType
changedEvent.PasswordlessType = &passwordlessType
}
return changedEvent, hasChanged
}
@@ -45,7 +45,7 @@ func (wm *IAMOrgIAMPolicyWriteModel) NewChangedEvent(ctx context.Context, userLo
changedEvent := iam.NewOrgIAMPolicyChangedEvent(ctx)
if wm.UserLoginMustBeDomain != userLoginMustBeDomain {
hasChanged = true
changedEvent.UserLoginMustBeDomain = userLoginMustBeDomain
changedEvent.UserLoginMustBeDomain = &userLoginMustBeDomain
}
return changedEvent, hasChanged
}
@@ -45,11 +45,11 @@ func (wm *IAMPasswordAgePolicyWriteModel) NewChangedEvent(ctx context.Context, e
changedEvent := iam.NewPasswordAgePolicyChangedEvent(ctx)
if wm.ExpireWarnDays != expireWarnDays {
hasChanged = true
changedEvent.ExpireWarnDays = expireWarnDays
changedEvent.ExpireWarnDays = &expireWarnDays
}
if wm.MaxAgeDays != maxAgeDays {
hasChanged = true
changedEvent.MaxAgeDays = maxAgeDays
changedEvent.MaxAgeDays = &maxAgeDays
}
return changedEvent, hasChanged
}
@@ -53,23 +53,23 @@ func (wm *IAMPasswordComplexityPolicyWriteModel) NewChangedEvent(
changedEvent := iam.NewPasswordComplexityPolicyChangedEvent(ctx)
if wm.MinLength != minLength {
hasChanged = true
changedEvent.MinLength = minLength
changedEvent.MinLength = &minLength
}
if wm.HasLowercase != hasLowercase {
hasChanged = true
changedEvent.HasLowercase = hasLowercase
changedEvent.HasLowercase = &hasLowercase
}
if wm.HasUpperCase != hasUppercase {
hasChanged = true
changedEvent.HasUpperCase = hasUppercase
changedEvent.HasUpperCase = &hasUppercase
}
if wm.HasNumber != hasNumber {
hasChanged = true
changedEvent.HasNumber = hasNumber
changedEvent.HasNumber = &hasNumber
}
if wm.HasSymbol != hasSymbol {
hasChanged = true
changedEvent.HasSymbol = hasSymbol
changedEvent.HasSymbol = &hasSymbol
}
return changedEvent, hasChanged
}
@@ -45,11 +45,11 @@ func (wm *IAMPasswordLockoutPolicyWriteModel) NewChangedEvent(ctx context.Contex
changedEvent := iam.NewPasswordLockoutPolicyChangedEvent(ctx)
if wm.MaxAttempts != maxAttempts {
hasChanged = true
changedEvent.MaxAttempts = maxAttempts
changedEvent.MaxAttempts = &maxAttempts
}
if wm.ShowLockOutFailures != showLockoutFailure {
hasChanged = true
changedEvent.ShowLockOutFailures = showLockoutFailure
changedEvent.ShowLockOutFailures = &showLockoutFailure
}
return changedEvent, hasChanged
}
@@ -45,7 +45,7 @@ func (wm *ORGOrgIAMPolicyWriteModel) NewChangedEvent(ctx context.Context, userLo
changedEvent := org.NewOrgIAMPolicyChangedEvent(ctx)
if wm.UserLoginMustBeDomain != userLoginMustBeDomain {
hasChanged = true
changedEvent.UserLoginMustBeDomain = userLoginMustBeDomain
changedEvent.UserLoginMustBeDomain = &userLoginMustBeDomain
}
return changedEvent, hasChanged
}
@@ -53,23 +53,23 @@ func (wm *OrgPasswordComplexityPolicyWriteModel) NewChangedEvent(
changedEvent := org.NewPasswordComplexityPolicyChangedEvent(ctx)
if wm.MinLength != minLength {
hasChanged = true
changedEvent.MinLength = minLength
changedEvent.MinLength = &minLength
}
if wm.HasLowercase != hasLowercase {
hasChanged = true
changedEvent.HasLowercase = hasLowercase
changedEvent.HasLowercase = &hasLowercase
}
if wm.HasUpperCase != hasUppercase {
hasChanged = true
changedEvent.HasUpperCase = hasUppercase
changedEvent.HasUpperCase = &hasUppercase
}
if wm.HasNumber != hasNumber {
hasChanged = true
changedEvent.HasNumber = hasNumber
changedEvent.HasNumber = &hasNumber
}
if wm.HasSymbol != hasSymbol {
hasChanged = true
changedEvent.HasSymbol = hasSymbol
changedEvent.HasSymbol = &hasSymbol
}
return changedEvent, hasChanged
}
+6 -2
View File
@@ -21,8 +21,12 @@ func (wm *LabelPolicyWriteModel) Reduce() error {
wm.SecondaryColor = e.SecondaryColor
wm.IsActive = true
case *policy.LabelPolicyChangedEvent:
wm.PrimaryColor = e.PrimaryColor
wm.SecondaryColor = e.SecondaryColor
if e.PrimaryColor != nil {
wm.PrimaryColor = *e.PrimaryColor
}
if e.SecondaryColor != nil {
wm.SecondaryColor = *e.SecondaryColor
}
case *policy.LabelPolicyRemovedEvent:
wm.IsActive = false
}
+15 -5
View File
@@ -28,11 +28,21 @@ func (wm *LoginPolicyWriteModel) Reduce() error {
wm.PasswordlessType = e.PasswordlessType
wm.IsActive = true
case *policy.LoginPolicyChangedEvent:
wm.AllowRegister = e.AllowRegister
wm.AllowUserNamePassword = e.AllowUserNamePassword
wm.AllowExternalIDP = e.AllowExternalIDP
wm.ForceMFA = e.ForceMFA
wm.PasswordlessType = e.PasswordlessType
if e.AllowRegister != nil {
wm.AllowRegister = *e.AllowRegister
}
if e.AllowUserNamePassword != nil {
wm.AllowUserNamePassword = *e.AllowUserNamePassword
}
if e.AllowExternalIDP != nil {
wm.AllowExternalIDP = *e.AllowExternalIDP
}
if e.ForceMFA != nil {
wm.ForceMFA = *e.ForceMFA
}
if e.PasswordlessType != nil {
wm.PasswordlessType = *e.PasswordlessType
}
case *policy.LoginPolicyRemovedEvent:
wm.IsActive = false
}
+3 -1
View File
@@ -19,7 +19,9 @@ func (wm *PolicyOrgIAMWriteModel) Reduce() error {
wm.UserLoginMustBeDomain = e.UserLoginMustBeDomain
wm.IsActive = true
case *policy.OrgIAMPolicyChangedEvent:
wm.UserLoginMustBeDomain = e.UserLoginMustBeDomain
if e.UserLoginMustBeDomain != nil {
wm.UserLoginMustBeDomain = *e.UserLoginMustBeDomain
}
}
}
return wm.WriteModel.Reduce()
@@ -21,8 +21,12 @@ func (wm *PasswordAgePolicyWriteModel) Reduce() error {
wm.MaxAgeDays = e.MaxAgeDays
wm.IsActive = true
case *policy.PasswordAgePolicyChangedEvent:
wm.ExpireWarnDays = e.ExpireWarnDays
wm.MaxAgeDays = e.MaxAgeDays
if e.ExpireWarnDays != nil {
wm.ExpireWarnDays = *e.ExpireWarnDays
}
if e.ExpireWarnDays != nil {
wm.ExpireWarnDays = *e.ExpireWarnDays
}
case *policy.PasswordAgePolicyRemovedEvent:
wm.IsActive = false
}
@@ -27,11 +27,21 @@ func (wm *PasswordComplexityPolicyWriteModel) Reduce() error {
wm.HasSymbol = e.HasSymbol
wm.IsActive = true
case *policy.PasswordComplexityPolicyChangedEvent:
wm.MinLength = e.MinLength
wm.HasLowercase = e.HasLowercase
wm.HasUpperCase = e.HasUpperCase
wm.HasNumber = e.HasNumber
wm.HasSymbol = e.HasSymbol
if e.MinLength != nil {
wm.MinLength = *e.MinLength
}
if e.HasLowercase != nil {
wm.HasLowercase = *e.HasLowercase
}
if e.HasUpperCase != nil {
wm.HasUpperCase = *e.HasUpperCase
}
if e.HasNumber != nil {
wm.HasNumber = *e.HasNumber
}
if e.HasSymbol != nil {
wm.HasSymbol = *e.HasSymbol
}
case *policy.PasswordComplexityPolicyRemovedEvent:
wm.IsActive = false
}
@@ -21,8 +21,12 @@ func (wm *PasswordLockoutPolicyWriteModel) Reduce() error {
wm.ShowLockOutFailures = e.ShowLockOutFailures
wm.IsActive = true
case *policy.PasswordLockoutPolicyChangedEvent:
wm.MaxAttempts = e.MaxAttempts
wm.ShowLockOutFailures = e.ShowLockOutFailures
if e.MaxAttempts != nil {
wm.MaxAttempts = *e.MaxAttempts
}
if e.ShowLockOutFailures != nil {
wm.ShowLockOutFailures = *e.ShowLockOutFailures
}
case *policy.PasswordLockoutPolicyRemovedEvent:
wm.IsActive = false
}
+25 -4
View File
@@ -20,7 +20,11 @@ func (r *CommandSide) AddUser(ctx context.Context, user *domain.User) (*domain.U
}
return &domain.User{UserName: user.UserName, Human: human}, nil
} else if user.Machine != nil {
machine, err := r.AddMachine(ctx, user.ResourceOwner, user.UserName, user.Machine)
if err != nil {
return nil, err
}
return &domain.User{UserName: user.UserName, Machine: machine}, nil
}
return nil, caos_errs.ThrowInvalidArgument(nil, "COMMAND-8K0df", "Errors.User.TypeUndefined")
}
@@ -31,9 +35,11 @@ func (r *CommandSide) RegisterUser(ctx context.Context, user *domain.User) (*dom
}
if user.Human != nil {
} else if user.Machine != nil {
human, err := r.RegisterHuman(ctx, user.ResourceOwner, user.UserName, user.Human, nil)
if err != nil {
return nil, err
}
return &domain.User{UserName: user.UserName, Human: human}, nil
}
return nil, caos_errs.ThrowInvalidArgument(nil, "COMMAND-8K0df", "Errors.User.TypeUndefined")
}
@@ -122,6 +128,21 @@ func (r *CommandSide) UnlockUser(ctx context.Context, userID string) (*domain.Us
return writeModelToUser(existingUser), nil
}
func (r *CommandSide) RemoveUser(ctx context.Context, userID string) error {
existingUser, err := r.userWriteModelByID(ctx, userID)
if err != nil {
return err
}
if existingUser.UserState != domain.UserStateDeleted {
return caos_errs.ThrowAlreadyExists(nil, "COMMAND-5M0od", "Errors.User.NotFound")
}
userAgg := UserAggregateFromWriteModel(&existingUser.WriteModel)
userAgg.PushEvents(user.NewUserRemovedEvent(ctx))
//TODO: release unqie username
return r.eventstore.PushAggregate(ctx, existingUser, userAgg)
}
func (r *CommandSide) userWriteModelByID(ctx context.Context, userID string) (writeModel *UserWriteModel, err error) {
ctx, span := tracing.NewSpan(ctx)
defer func() { span.EndWithError(err) }()
+15 -8
View File
@@ -1,7 +1,6 @@
package command
import (
"github.com/caos/zitadel/internal/user/model"
"github.com/caos/zitadel/internal/v2/domain"
)
@@ -38,28 +37,28 @@ func writeModelToHuman(wm *HumanWriteModel) *domain.Human {
}
}
func writeModelToProfile(wm *HumanProfileWriteModel) *model.Profile {
return &model.Profile{
func writeModelToProfile(wm *HumanProfileWriteModel) *domain.Profile {
return &domain.Profile{
ObjectRoot: writeModelToObjectRoot(wm.WriteModel),
FirstName: wm.FirstName,
LastName: wm.LastName,
NickName: wm.NickName,
DisplayName: wm.DisplayName,
PreferredLanguage: wm.PreferredLanguage,
Gender: model.Gender(wm.Gender),
Gender: wm.Gender,
}
}
func writeModelToEmail(wm *HumanEmailWriteModel) *model.Email {
return &model.Email{
func writeModelToEmail(wm *HumanEmailWriteModel) *domain.Email {
return &domain.Email{
ObjectRoot: writeModelToObjectRoot(wm.WriteModel),
EmailAddress: wm.Email,
IsEmailVerified: wm.IsEmailVerified,
}
}
func writeModelToAddress(wm *HumanAddressWriteModel) *model.Address {
return &model.Address{
func writeModelToAddress(wm *HumanAddressWriteModel) *domain.Address {
return &domain.Address{
ObjectRoot: writeModelToObjectRoot(wm.WriteModel),
Country: wm.Country,
Locality: wm.Locality,
@@ -68,3 +67,11 @@ func writeModelToAddress(wm *HumanAddressWriteModel) *model.Address {
StreetAddress: wm.StreetAddress,
}
}
func writeModelToMachine(wm *MachineWriteModel) *domain.Machine {
return &domain.Machine{
ObjectRoot: writeModelToObjectRoot(wm.WriteModel),
Name: wm.Name,
Description: wm.Description,
}
}
+116 -19
View File
@@ -32,32 +32,50 @@ func (r *CommandSide) AddHuman(ctx context.Context, orgID, username string, huma
human.HashPasswordIfExisting(pwPolicy, r.userPasswordAlg, true)
userAgg := UserAggregateFromWriteModel(&addedHuman.WriteModel)
userAgg.PushEvents(
user.NewHumanAddedEvent(
ctx,
username,
human.FirstName,
human.LastName,
human.NickName,
human.DisplayName,
human.PreferredLanguage,
human.Gender,
human.EmailAddress,
human.PhoneNumber,
addEvent := user.NewHumanAddedEvent(
ctx,
username,
human.FirstName,
human.LastName,
human.NickName,
human.DisplayName,
human.PreferredLanguage,
human.Gender,
human.EmailAddress,
)
if human.Phone != nil {
addEvent.AddPhoneData(human.PhoneNumber)
}
if human.Address != nil {
addEvent.AddAddressData(
human.Country,
human.Locality,
human.PostalCode,
human.Region,
human.StreetAddress,
),
)
//TODO: HashPassword If existing
//TODO: Generate Init Code if needed
//TODO: Generate Phone Code if needed
human.StreetAddress)
}
if human.Password != nil {
addEvent.AddPasswordData(human.SecretCrypto, human.ChangeRequired)
}
userAgg.PushEvents(addEvent)
if human.IsInitialState() {
initCode, err := domain.NewInitUserCode(r.initializeUserCode)
if err != nil {
return nil, err
}
user.NewHumanInitialCodeAddedEvent(ctx, initCode.Code, initCode.Expiry)
}
if human.Email != nil && human.EmailAddress != "" && human.IsEmailVerified {
userAgg.PushEvents(user.NewHumanEmailVerifiedEvent(ctx))
}
if human.Phone != nil && human.PhoneNumber != "" && human.IsPhoneVerified {
if human.Phone != nil && human.PhoneNumber != "" && !human.IsPhoneVerified {
phoneCode, err := domain.NewPhoneCode(r.phoneVerificationCode)
if err != nil {
return nil, err
}
user.NewHumanPhoneCodeAddedEvent(ctx, phoneCode.Code, phoneCode.Expiry)
} else if human.Phone != nil && human.PhoneNumber != "" && human.IsPhoneVerified {
userAgg.PushEvents(user.NewHumanPhoneVerifiedEvent(ctx))
}
@@ -68,3 +86,82 @@ func (r *CommandSide) AddHuman(ctx context.Context, orgID, username string, huma
return writeModelToHuman(addedHuman), nil
}
func (r *CommandSide) RegisterHuman(ctx context.Context, orgID, username string, human *domain.Human, externalIDP *domain.ExternalIDP) (*domain.Human, error) {
if !human.IsValid() || externalIDP == nil && (human.Password == nil || human.SecretString == "") {
return nil, caos_errs.ThrowPreconditionFailed(nil, "COMMAND-9dk45", "Errors.User.Invalid")
}
userID, err := r.idGenerator.Next()
if err != nil {
return nil, err
}
human.AggregateID = userID
orgIAMPolicy, err := r.GetOrgIAMPolicy(ctx, orgID)
if err != nil {
return nil, err
}
pwPolicy, err := r.GetOrgPasswordComplexityPolicy(ctx, orgID)
if err != nil {
return nil, err
}
addedHuman := NewHumanWriteModel(human.AggregateID)
//TODO: Check Unique Username or unique external idp
human.CheckOrgIAMPolicy(username, orgIAMPolicy)
human.SetNamesAsDisplayname()
human.HashPasswordIfExisting(pwPolicy, r.userPasswordAlg, true)
userAgg := UserAggregateFromWriteModel(&addedHuman.WriteModel)
addEvent := user.NewHumanRegisteredEvent(
ctx,
username,
human.FirstName,
human.LastName,
human.NickName,
human.DisplayName,
human.PreferredLanguage,
human.Gender,
human.EmailAddress,
)
if human.Phone != nil {
addEvent.AddPhoneData(human.PhoneNumber)
}
if human.Address != nil {
addEvent.AddAddressData(
human.Country,
human.Locality,
human.PostalCode,
human.Region,
human.StreetAddress)
}
if human.Password != nil {
addEvent.AddPasswordData(human.SecretCrypto, human.ChangeRequired)
}
userAgg.PushEvents(addEvent)
//TODO: Add External IDP Event
if human.IsInitialState() {
initCode, err := domain.NewInitUserCode(r.initializeUserCode)
if err != nil {
return nil, err
}
user.NewHumanInitialCodeAddedEvent(ctx, initCode.Code, initCode.Expiry)
}
if human.Email != nil && human.EmailAddress != "" && human.IsEmailVerified {
userAgg.PushEvents(user.NewHumanEmailVerifiedEvent(ctx))
}
if human.Phone != nil && human.PhoneNumber != "" && !human.IsPhoneVerified {
phoneCode, err := domain.NewPhoneCode(r.phoneVerificationCode)
if err != nil {
return nil, err
}
user.NewHumanPhoneCodeAddedEvent(ctx, phoneCode.Code, phoneCode.Expiry)
}
err = r.eventstore.PushAggregate(ctx, addedHuman, userAgg)
if err != nil {
return nil, err
}
return writeModelToHuman(addedHuman), nil
}
+1 -2
View File
@@ -4,11 +4,10 @@ import (
"context"
caos_errs "github.com/caos/zitadel/internal/errors"
"github.com/caos/zitadel/internal/telemetry/tracing"
usr_model "github.com/caos/zitadel/internal/user/model"
"github.com/caos/zitadel/internal/v2/domain"
)
func (r *CommandSide) ChangeHumanAddress(ctx context.Context, address *usr_model.Address) (*usr_model.Address, error) {
func (r *CommandSide) ChangeHumanAddress(ctx context.Context, address *domain.Address) (*domain.Address, error) {
existingAddress, err := r.addressWriteModel(ctx, address.AggregateID)
if err != nil {
return nil, err
+20 -10
View File
@@ -58,11 +58,21 @@ func (wm *HumanAddressWriteModel) Reduce() error {
wm.StreetAddress = e.StreetAddress
wm.UserState = domain.UserStateActive
case *user.HumanAddressChangedEvent:
wm.Country = e.Country
wm.Locality = e.Locality
wm.PostalCode = e.PostalCode
wm.Region = e.Region
wm.StreetAddress = e.StreetAddress
if e.Country != nil {
wm.Country = *e.Country
}
if e.Locality != nil {
wm.Locality = *e.Locality
}
if e.PostalCode != nil {
wm.PostalCode = *e.PostalCode
}
if e.Region != nil {
wm.Region = *e.Region
}
if e.StreetAddress != nil {
wm.StreetAddress = *e.StreetAddress
}
case *user.UserRemovedEvent:
wm.UserState = domain.UserStateDeleted
}
@@ -87,23 +97,23 @@ func (wm *HumanAddressWriteModel) NewChangedEvent(
changedEvent := user.NewHumanAddressChangedEvent(ctx)
if wm.Country != country {
hasChanged = true
changedEvent.Country = country
changedEvent.Country = &country
}
if wm.Locality != locality {
hasChanged = true
changedEvent.Locality = locality
changedEvent.Locality = &locality
}
if wm.PostalCode != postalCode {
hasChanged = true
changedEvent.PostalCode = postalCode
changedEvent.PostalCode = &postalCode
}
if wm.Region != region {
hasChanged = true
changedEvent.Region = region
changedEvent.Region = &region
}
if wm.StreetAddress != streetAddress {
hasChanged = true
changedEvent.StreetAddress = streetAddress
changedEvent.StreetAddress = &streetAddress
}
return changedEvent, hasChanged
}
+1 -2
View File
@@ -4,11 +4,10 @@ import (
"context"
caos_errs "github.com/caos/zitadel/internal/errors"
"github.com/caos/zitadel/internal/telemetry/tracing"
usr_model "github.com/caos/zitadel/internal/user/model"
"github.com/caos/zitadel/internal/v2/domain"
)
func (r *CommandSide) ChangeHumanEmail(ctx context.Context, email *usr_model.Email) (*usr_model.Email, error) {
func (r *CommandSide) ChangeHumanEmail(ctx context.Context, email *domain.Email) (*domain.Email, error) {
if !email.IsValid() {
return nil, caos_errs.ThrowPreconditionFailed(nil, "COMMAND-4M9sf", "Errors.Email.Invalid")
}
+134 -19
View File
@@ -1,6 +1,7 @@
package command
import (
"github.com/caos/zitadel/internal/crypto"
"github.com/caos/zitadel/internal/eventstore/v2"
"github.com/caos/zitadel/internal/v2/domain"
"github.com/caos/zitadel/internal/v2/repository/user"
@@ -31,6 +32,9 @@ type HumanWriteModel struct {
Region string
StreetAddress string
Secret *crypto.CryptoValue
SecretChangeRequired bool
UserState domain.UserState
}
@@ -45,21 +49,20 @@ func NewHumanWriteModel(userID string) *HumanWriteModel {
func (wm *HumanWriteModel) AppendEvents(events ...eventstore.EventReader) {
for _, event := range events {
switch e := event.(type) {
case *user.HumanEmailChangedEvent:
wm.AppendEvents(e)
case *user.HumanEmailVerifiedEvent:
wm.AppendEvents(e)
case *user.HumanAddedEvent, *user.HumanRegisteredEvent:
wm.AppendEvents(e)
case *user.UserDeactivatedEvent:
wm.AppendEvents(e)
case *user.UserReactivatedEvent:
wm.AppendEvents(e)
case *user.UserLockedEvent:
wm.AppendEvents(e)
case *user.UserUnlockedEvent:
wm.AppendEvents(e)
case *user.UserRemovedEvent:
case *user.HumanAddedEvent,
*user.HumanRegisteredEvent,
*user.HumanProfileChangedEvent,
*user.HumanEmailChangedEvent,
*user.HumanEmailVerifiedEvent,
*user.HumanPhoneChangedEvent,
*user.HumanPhoneVerifiedEvent,
*user.HumanAddressChangedEvent,
*user.HumanPasswordChangedEvent,
*user.UserDeactivatedEvent,
*user.UserReactivatedEvent,
*user.UserLockedEvent,
*user.UserUnlockedEvent,
*user.UserRemovedEvent:
wm.AppendEvents(e)
}
}
@@ -70,11 +73,21 @@ func (wm *HumanWriteModel) Reduce() error {
for _, event := range wm.Events {
switch e := event.(type) {
case *user.HumanAddedEvent:
wm.UserName = e.UserName
wm.UserState = domain.UserStateInitial
wm.reduceHumanAddedEvent(e)
case *user.HumanRegisteredEvent:
wm.UserName = e.UserName
wm.UserState = domain.UserStateInitial
wm.reduceHumanRegisteredEvent(e)
case *user.HumanProfileChangedEvent:
wm.reduceHumanProfileChangedEvent(e)
case *user.HumanEmailChangedEvent:
wm.reduceHumanEmailChangedEvent(e)
case *user.HumanEmailVerifiedEvent:
wm.reduceHumanEmailVerifiedEvent()
case *user.HumanPhoneChangedEvent:
wm.reduceHumanPhoneChangedEvent(e)
case *user.HumanPhoneVerifiedEvent:
wm.reduceHumanPhoneVerifiedEvent()
case *user.HumanPasswordChangedEvent:
wm.reduceHumanPasswordChangedEvent(e)
case *user.UserLockedEvent:
if wm.UserState != domain.UserStateDeleted {
wm.UserState = domain.UserStateLocked
@@ -102,3 +115,105 @@ func (wm *HumanWriteModel) Query() *eventstore.SearchQueryBuilder {
return eventstore.NewSearchQueryBuilder(eventstore.ColumnsEvent, user.AggregateType).
AggregateIDs(wm.AggregateID)
}
func (wm *HumanWriteModel) reduceHumanAddedEvent(e *user.HumanAddedEvent) {
wm.UserName = e.UserName
wm.FirstName = e.FirstName
wm.LastName = e.LastName
wm.NickName = e.NickName
wm.DisplayName = e.DisplayName
wm.PreferredLanguage = e.PreferredLanguage
wm.Gender = e.Gender
wm.Email = e.EmailAddress
wm.Phone = e.PhoneNumber
wm.Country = e.Country
wm.Locality = e.Locality
wm.PostalCode = e.PostalCode
wm.Region = e.Region
wm.StreetAddress = e.StreetAddress
wm.Secret = e.Secret
wm.SecretChangeRequired = e.ChangeRequired
wm.UserState = domain.UserStateInitial
}
func (wm *HumanWriteModel) reduceHumanRegisteredEvent(e *user.HumanRegisteredEvent) {
wm.UserName = e.UserName
wm.FirstName = e.FirstName
wm.LastName = e.LastName
wm.NickName = e.NickName
wm.DisplayName = e.DisplayName
wm.PreferredLanguage = e.PreferredLanguage
wm.Gender = e.Gender
wm.Email = e.EmailAddress
wm.Phone = e.PhoneNumber
wm.Country = e.Country
wm.Locality = e.Locality
wm.PostalCode = e.PostalCode
wm.Region = e.Region
wm.StreetAddress = e.StreetAddress
wm.Secret = e.Secret
wm.SecretChangeRequired = e.ChangeRequired
wm.UserState = domain.UserStateInitial
}
func (wm *HumanWriteModel) reduceHumanProfileChangedEvent(e *user.HumanProfileChangedEvent) {
if e.FirstName != "" {
wm.FirstName = e.FirstName
}
if e.LastName != "" {
wm.LastName = e.LastName
}
if e.NickName != nil {
wm.NickName = *e.NickName
}
if e.DisplayName != nil {
wm.DisplayName = *e.DisplayName
}
if e.PreferredLanguage != nil {
wm.PreferredLanguage = *e.PreferredLanguage
}
if e.Gender != nil {
wm.Gender = *e.Gender
}
}
func (wm *HumanWriteModel) reduceHumanEmailChangedEvent(e *user.HumanEmailChangedEvent) {
wm.Email = e.EmailAddress
wm.IsEmailVerified = false
}
func (wm *HumanWriteModel) reduceHumanEmailVerifiedEvent() {
wm.IsEmailVerified = true
}
func (wm *HumanWriteModel) reduceHumanPhoneChangedEvent(e *user.HumanPhoneChangedEvent) {
wm.Phone = e.PhoneNumber
wm.IsPhoneVerified = false
}
func (wm *HumanWriteModel) reduceHumanPhoneVerifiedEvent() {
wm.IsPhoneVerified = true
}
func (wm *HumanWriteModel) reduceHumanAddressChangedEvent(e *user.HumanAddressChangedEvent) {
if e.Country != nil {
wm.Country = *e.Country
}
if e.Locality != nil {
wm.Locality = *e.Locality
}
if e.PostalCode != nil {
wm.PostalCode = *e.PostalCode
}
if e.Region != nil {
wm.Region = *e.Region
}
if e.StreetAddress != nil {
wm.StreetAddress = *e.StreetAddress
}
}
func (wm *HumanWriteModel) reduceHumanPasswordChangedEvent(e *user.HumanPasswordChangedEvent) {
wm.Secret = e.Secret
wm.SecretChangeRequired = e.ChangeRequired
}
+1 -2
View File
@@ -4,11 +4,10 @@ import (
"context"
caos_errs "github.com/caos/zitadel/internal/errors"
"github.com/caos/zitadel/internal/telemetry/tracing"
usr_model "github.com/caos/zitadel/internal/user/model"
"github.com/caos/zitadel/internal/v2/domain"
)
func (r *CommandSide) ChangeHumanProfile(ctx context.Context, profile *usr_model.Profile) (*usr_model.Profile, error) {
func (r *CommandSide) ChangeHumanProfile(ctx context.Context, profile *domain.Profile) (*domain.Profile, error) {
if !profile.IsValid() {
return nil, caos_errs.ThrowPreconditionFailed(nil, "COMMAND-8io0d", "Errors.User.Profile.Invalid")
}
+22 -10
View File
@@ -62,12 +62,24 @@ func (wm *HumanProfileWriteModel) Reduce() error {
wm.Gender = e.Gender
wm.UserState = domain.UserStateActive
case *user.HumanProfileChangedEvent:
wm.FirstName = e.FirstName
wm.LastName = e.LastName
wm.NickName = e.NickName
wm.DisplayName = e.DisplayName
wm.PreferredLanguage = e.PreferredLanguage
wm.Gender = e.Gender
if e.FirstName != "" {
wm.FirstName = e.FirstName
}
if e.LastName != "" {
wm.LastName = e.LastName
}
if e.NickName != nil {
wm.NickName = *e.NickName
}
if e.DisplayName != nil {
wm.DisplayName = *e.DisplayName
}
if e.PreferredLanguage != nil {
wm.PreferredLanguage = *e.PreferredLanguage
}
if e.Gender != nil {
wm.Gender = *e.Gender
}
case *user.UserRemovedEvent:
wm.UserState = domain.UserStateDeleted
}
@@ -101,19 +113,19 @@ func (wm *HumanProfileWriteModel) NewChangedEvent(
}
if wm.NickName != nickName {
hasChanged = true
changedEvent.NickName = nickName
changedEvent.NickName = &nickName
}
if wm.DisplayName != displayName {
hasChanged = true
changedEvent.DisplayName = displayName
changedEvent.DisplayName = &displayName
}
if wm.PreferredLanguage != preferredLanguage {
hasChanged = true
changedEvent.PreferredLanguage = preferredLanguage
changedEvent.PreferredLanguage = &preferredLanguage
}
if gender.Valid() && wm.Gender != gender {
hasChanged = true
changedEvent.Gender = gender
changedEvent.Gender = &gender
}
return changedEvent, hasChanged
+78
View File
@@ -0,0 +1,78 @@
package command
import (
"context"
caos_errs "github.com/caos/zitadel/internal/errors"
"github.com/caos/zitadel/internal/telemetry/tracing"
"github.com/caos/zitadel/internal/v2/domain"
"github.com/caos/zitadel/internal/v2/repository/user"
)
func (r *CommandSide) AddMachine(ctx context.Context, orgID, username string, machine *domain.Machine) (*domain.Machine, error) {
if !machine.IsValid() {
return nil, caos_errs.ThrowInvalidArgument(nil, "COMMAND-5M0ds", "Errors.User.Invalid")
}
userID, err := r.idGenerator.Next()
if err != nil {
return nil, err
}
//TODO: Check Unique username
machine.AggregateID = userID
orgIAMPolicy, err := r.GetOrgIAMPolicy(ctx, orgID)
if err != nil {
return nil, err
}
if !orgIAMPolicy.UserLoginMustBeDomain {
return nil, caos_errs.ThrowInvalidArgument(nil, "COMMAND-6M0ds", "Errors.User.Invalid")
}
addedMachine := NewMachineWriteModel(machine.AggregateID)
userAgg := UserAggregateFromWriteModel(&addedMachine.WriteModel)
userAgg.PushEvents(
user.NewMachineAddedEvent(
ctx,
username,
machine.Name,
machine.Description,
),
)
return writeModelToMachine(addedMachine), nil
}
func (r *CommandSide) ChangeMachine(ctx context.Context, machine *domain.Machine) (*domain.Machine, error) {
existingUser, err := r.machineWriteModelByID(ctx, machine.AggregateID)
if err != nil {
return nil, err
}
if existingUser.UserState == domain.UserStateDeleted || existingUser.UserState == domain.UserStateUnspecified {
return nil, caos_errs.ThrowAlreadyExists(nil, "COMMAND-5M0od", "Errors.User.NotFound")
}
changedEvent, hasChanged := existingUser.NewChangedEvent(ctx, machine.Name, machine.Description)
if !hasChanged {
return nil, caos_errs.ThrowAlreadyExists(nil, "COMMAND-2M9fs", "Errors.User.Email.NotChanged")
}
userAgg := UserAggregateFromWriteModel(&existingUser.WriteModel)
userAgg.PushEvents(changedEvent)
err = r.eventstore.PushAggregate(ctx, existingUser, userAgg)
if err != nil {
return nil, err
}
return writeModelToMachine(existingUser), nil
}
func (r *CommandSide) machineWriteModelByID(ctx context.Context, userID string) (writeModel *MachineWriteModel, err error) {
if userID == "" {
return nil, caos_errs.ThrowInvalidArgument(nil, "COMMAND-5M0ds", "Errors.User.UserIDMissing")
}
ctx, span := tracing.NewSpan(ctx)
defer func() { span.EndWithError(err) }()
writeModel = NewMachineWriteModel(userID)
err = r.eventstore.FilterToQueryReducer(ctx, writeModel)
if err != nil {
return nil, err
}
return writeModel, nil
}
@@ -0,0 +1,109 @@
package command
import (
"context"
"github.com/caos/zitadel/internal/eventstore/v2"
"github.com/caos/zitadel/internal/v2/domain"
"github.com/caos/zitadel/internal/v2/repository/user"
)
type MachineWriteModel struct {
eventstore.WriteModel
UserName string
Name string
Description string
UserState domain.UserState
}
func NewMachineWriteModel(userID string) *MachineWriteModel {
return &MachineWriteModel{
WriteModel: eventstore.WriteModel{
AggregateID: userID,
},
}
}
func (wm *MachineWriteModel) AppendEvents(events ...eventstore.EventReader) {
for _, event := range events {
switch e := event.(type) {
case *user.MachineAddedEvent:
wm.AppendEvents(e)
case *user.MachineChangedEvent:
wm.AppendEvents(e)
case *user.UserDeactivatedEvent:
wm.AppendEvents(e)
case *user.UserReactivatedEvent:
wm.AppendEvents(e)
case *user.UserLockedEvent:
wm.AppendEvents(e)
case *user.UserUnlockedEvent:
wm.AppendEvents(e)
case *user.UserRemovedEvent:
wm.AppendEvents(e)
}
}
}
//TODO: Compute State? initial/active
func (wm *MachineWriteModel) Reduce() error {
for _, event := range wm.Events {
switch e := event.(type) {
case *user.MachineAddedEvent:
wm.UserName = e.UserName
wm.Name = e.Name
wm.Description = e.Description
wm.UserState = domain.UserStateActive
case *user.MachineChangedEvent:
if e.Name != nil {
wm.Name = *e.Name
}
if e.Description != nil {
wm.Description = *e.Description
}
case *user.UserLockedEvent:
if wm.UserState != domain.UserStateDeleted {
wm.UserState = domain.UserStateLocked
}
case *user.UserUnlockedEvent:
if wm.UserState != domain.UserStateDeleted {
wm.UserState = domain.UserStateActive
}
case *user.UserDeactivatedEvent:
if wm.UserState != domain.UserStateDeleted {
wm.UserState = domain.UserStateInactive
}
case *user.UserReactivatedEvent:
if wm.UserState != domain.UserStateDeleted {
wm.UserState = domain.UserStateActive
}
case *user.UserRemovedEvent:
wm.UserState = domain.UserStateDeleted
}
}
return wm.WriteModel.Reduce()
}
func (wm *MachineWriteModel) Query() *eventstore.SearchQueryBuilder {
return eventstore.NewSearchQueryBuilder(eventstore.ColumnsEvent, user.AggregateType).
AggregateIDs(wm.AggregateID)
}
func (wm *MachineWriteModel) NewChangedEvent(
ctx context.Context,
name,
description string,
) (*user.MachineChangedEvent, bool) {
hasChanged := false
changedEvent := user.NewMachineChangedEvent(ctx)
if wm.Name != name {
hasChanged = true
changedEvent.Name = &name
}
if wm.Description != description {
hasChanged = true
changedEvent.Description = &description
}
return changedEvent, hasChanged
}