diff --git a/internal/api/grpc/user/v2/user.go b/internal/api/grpc/user/v2/user.go index a058e7e750..02b7e06282 100644 --- a/internal/api/grpc/user/v2/user.go +++ b/internal/api/grpc/user/v2/user.go @@ -6,6 +6,7 @@ import ( "golang.org/x/text/language" + "github.com/zitadel/zitadel/internal/api/authz" "github.com/zitadel/zitadel/internal/api/grpc/object/v2" "github.com/zitadel/zitadel/internal/command" "github.com/zitadel/zitadel/internal/domain" @@ -18,7 +19,11 @@ func (s *Server) AddHumanUser(ctx context.Context, req *user.AddHumanUserRequest if err != nil { return nil, err } - err = s.command.AddHuman(ctx, req.GetOrganisation().GetOrgId(), human, false) + orgID := req.GetOrganisation().GetOrgId() + if orgID == "" { + orgID = authz.GetCtxData(ctx).OrgID + } + err = s.command.AddHuman(ctx, orgID, human, false) if err != nil { return nil, err } diff --git a/internal/command/instance.go b/internal/command/instance.go index b8e00c1c0a..415fae3a2a 100644 --- a/internal/command/instance.go +++ b/internal/command/instance.go @@ -333,9 +333,9 @@ func (c *Commands) SetUpInstance(ctx context.Context, setup *InstanceSetup) (str validations = append(validations, prepareAddUserMachineKey(machineKey, c.machineKeySize)) } } else if setup.Org.Human != nil { - setup.Org.Human.ID = userAgg.ID + setup.Org.Human.ID = userID validations = append(validations, - c.AddHumanCommand(userAgg, setup.Org.Human, c.userPasswordAlg, c.userEncryption, true), + c.AddHumanCommand(setup.Org.Human, orgID, c.userPasswordAlg, c.userEncryption, true), ) } diff --git a/internal/command/org.go b/internal/command/org.go index 71e2c3e185..c9fd3dbd4a 100644 --- a/internal/command/org.go +++ b/internal/command/org.go @@ -11,7 +11,7 @@ import ( "github.com/zitadel/zitadel/internal/eventstore" "github.com/zitadel/zitadel/internal/repository/org" "github.com/zitadel/zitadel/internal/repository/project" - user_repo "github.com/zitadel/zitadel/internal/repository/user" + "github.com/zitadel/zitadel/internal/repository/user" ) type OrgSetup struct { @@ -22,22 +22,13 @@ type OrgSetup struct { Roles []string } -func (c *Commands) SetUpOrgWithIDs(ctx context.Context, o *OrgSetup, orgID, userID string, userIDs ...string) (string, *domain.ObjectDetails, error) { - existingOrg, err := c.getOrgWriteModelByID(ctx, orgID) +func (c *Commands) setUpOrgWithIDs(ctx context.Context, o *OrgSetup, orgID string, userIDs ...string) (userID string, token string, machineKey *MachineKey, details *domain.ObjectDetails, err error) { + userID, err = c.idGenerator.Next() if err != nil { - return "", nil, err + return "", "", nil, nil, err } - if existingOrg != nil { - return "", nil, errors.ThrowPreconditionFailed(nil, "COMMAND-poaj2", "Errors.Org.AlreadyExisting") - } - - userID, _, _, details, err := c.setUpOrgWithIDs(ctx, o, orgID, userID, userIDs...) - return userID, details, err -} - -func (c *Commands) setUpOrgWithIDs(ctx context.Context, o *OrgSetup, orgID, userID string, userIDs ...string) (string, string, *MachineKey, *domain.ObjectDetails, error) { orgAgg := org.NewAggregate(orgID) - userAgg := user_repo.NewAggregate(userID, orgID) + userAgg := user.NewAggregate(userID, orgID) roles := []string{domain.RoleOrgOwner} if len(o.Roles) > 0 { @@ -49,9 +40,9 @@ func (c *Commands) setUpOrgWithIDs(ctx context.Context, o *OrgSetup, orgID, user } var pat *PersonalAccessToken - var machineKey *MachineKey if o.Human != nil { - validations = append(validations, c.AddHumanCommand(userAgg, o.Human, c.userPasswordAlg, c.userEncryption, true)) + o.Human.ID = userID + validations = append(validations, c.AddHumanCommand(o.Human, orgID, c.userPasswordAlg, c.userEncryption, true)) } else if o.Machine != nil { validations = append(validations, AddMachineCommand(userAgg, o.Machine.Machine)) if o.Machine.Pat != nil { @@ -89,7 +80,6 @@ func (c *Commands) setUpOrgWithIDs(ctx context.Context, o *OrgSetup, orgID, user return "", "", nil, nil, err } - var token string if pat != nil { token = pat.Token } @@ -107,12 +97,7 @@ func (c *Commands) SetUpOrg(ctx context.Context, o *OrgSetup, userIDs ...string) return "", nil, err } - userID, err := c.idGenerator.Next() - if err != nil { - return "", nil, err - } - - userID, _, _, details, err := c.setUpOrgWithIDs(ctx, o, orgID, userID, userIDs...) + userID, _, _, details, err := c.setUpOrgWithIDs(ctx, o, orgID, userIDs...) return userID, details, err } @@ -365,9 +350,9 @@ func OrgUserIDPLinks(ctx context.Context, filter preparation.FilterToQueryReduce ResourceOwner(orgID). OrderAsc(). AddQuery(). - AggregateTypes(user_repo.AggregateType). + AggregateTypes(user.AggregateType). EventTypes( - user_repo.UserIDPLinkAddedType, user_repo.UserIDPLinkRemovedType, user_repo.UserIDPLinkCascadeRemovedType, + user.UserIDPLinkAddedType, user.UserIDPLinkRemovedType, user.UserIDPLinkCascadeRemovedType, ).Builder()) if err != nil { return nil, err @@ -375,13 +360,13 @@ func OrgUserIDPLinks(ctx context.Context, filter preparation.FilterToQueryReduce links := make([]*domain.UserIDPLink, 0) for _, event := range events { switch eventTyped := event.(type) { - case *user_repo.UserIDPLinkAddedEvent: + case *user.UserIDPLinkAddedEvent: links = append(links, &domain.UserIDPLink{ IDPConfigID: eventTyped.IDPConfigID, ExternalUserID: eventTyped.ExternalUserID, DisplayName: eventTyped.DisplayName, }) - case *user_repo.UserIDPLinkRemovedEvent: + case *user.UserIDPLinkRemovedEvent: for i := range links { if links[i].ExternalUserID == eventTyped.ExternalUserID && links[i].IDPConfigID == eventTyped.IDPConfigID { @@ -392,7 +377,7 @@ func OrgUserIDPLinks(ctx context.Context, filter preparation.FilterToQueryReduce } } - case *user_repo.UserIDPLinkCascadeRemovedEvent: + case *user.UserIDPLinkCascadeRemovedEvent: for i := range links { if links[i].ExternalUserID == eventTyped.ExternalUserID && links[i].IDPConfigID == eventTyped.IDPConfigID { @@ -495,14 +480,14 @@ func OrgUsers(ctx context.Context, filter preparation.FilterToQueryReducer, orgI ResourceOwner(orgID). OrderAsc(). AddQuery(). - AggregateTypes(user_repo.AggregateType). + AggregateTypes(user.AggregateType). EventTypes( - user_repo.HumanAddedType, - user_repo.MachineAddedEventType, - user_repo.HumanRegisteredType, - user_repo.UserDomainClaimedType, - user_repo.UserUserNameChangedType, - user_repo.UserRemovedType, + user.HumanAddedType, + user.MachineAddedEventType, + user.HumanRegisteredType, + user.UserDomainClaimedType, + user.UserUserNameChangedType, + user.UserRemovedType, ).Builder()) if err != nil { return nil, err @@ -511,25 +496,25 @@ func OrgUsers(ctx context.Context, filter preparation.FilterToQueryReducer, orgI users := make([]userIDName, 0) for _, event := range events { switch eventTyped := event.(type) { - case *user_repo.HumanAddedEvent: + case *user.HumanAddedEvent: users = append(users, userIDName{eventTyped.UserName, eventTyped.Aggregate().ID}) - case *user_repo.MachineAddedEvent: + case *user.MachineAddedEvent: users = append(users, userIDName{eventTyped.UserName, eventTyped.Aggregate().ID}) - case *user_repo.HumanRegisteredEvent: + case *user.HumanRegisteredEvent: users = append(users, userIDName{eventTyped.UserName, eventTyped.Aggregate().ID}) - case *user_repo.DomainClaimedEvent: + case *user.DomainClaimedEvent: for i := range users { if users[i].id == eventTyped.Aggregate().ID { users[i].name = eventTyped.UserName } } - case *user_repo.UsernameChangedEvent: + case *user.UsernameChangedEvent: for i := range users { if users[i].id == eventTyped.Aggregate().ID { users[i].name = eventTyped.UserName } } - case *user_repo.UserRemovedEvent: + case *user.UserRemovedEvent: for i := range users { if users[i].id == eventTyped.Aggregate().ID { users[i] = users[len(users)-1] diff --git a/internal/command/user_human.go b/internal/command/user_human.go index 688beaa3ff..0bb075a345 100644 --- a/internal/command/user_human.go +++ b/internal/command/user_human.go @@ -27,7 +27,7 @@ func (c *Commands) getHuman(ctx context.Context, userID, resourceowner string) ( } type AddHuman struct { - // ID is optional + // ID is optional, if empty it will be generated ID string // Username is required Username string @@ -114,11 +114,10 @@ func (c *Commands) AddHuman(ctx context.Context, resourceOwner string, human *Ad if resourceOwner == "" { return errors.ThrowInvalidArgument(nil, "COMMA-5Ky74", "Errors.Internal") } - agg := user.NewAggregate(human.ID, resourceOwner) cmds, err := preparation.PrepareCommands(ctx, c.eventstore.Filter, c.AddHumanCommand( - agg, human, + resourceOwner, c.userPasswordAlg, c.userEncryption, allowInitMail, @@ -146,16 +145,17 @@ type humanCreationCommand interface { AddPasswordData(secret *crypto.CryptoValue, changeRequired bool) } -func (c *Commands) AddHumanCommand(a *user.Aggregate, human *AddHuman, passwordAlg crypto.HashAlgorithm, codeAlg crypto.EncryptionAlgorithm, allowInitMail bool) preparation.Validation { +func (c *Commands) AddHumanCommand(human *AddHuman, orgID string, passwordAlg crypto.HashAlgorithm, codeAlg crypto.EncryptionAlgorithm, allowInitMail bool) preparation.Validation { return func() (_ preparation.CreateCommands, err error) { if err := human.Validate(); err != nil { return nil, err } return func(ctx context.Context, filter preparation.FilterToQueryReducer) ([]eventstore.Command, error) { - if err := c.addHumanCommandCheckID(ctx, filter, a, human); err != nil { + if err := c.addHumanCommandCheckID(ctx, filter, human, orgID); err != nil { return nil, err } + a := user.NewAggregate(human.ID, orgID) domainPolicy, err := domainPolicyWriteModel(ctx, filter, a.ResourceOwner) if err != nil { @@ -274,22 +274,20 @@ func (c *Commands) addHumanCommandPhone(ctx context.Context, filter preparation. return append(cmds, user.NewHumanPhoneCodeAddedEvent(ctx, &a.Aggregate, phoneCode.Crypted, phoneCode.Expiry)), nil } -func (c *Commands) addHumanCommandCheckID(ctx context.Context, filter preparation.FilterToQueryReducer, a *user.Aggregate, human *AddHuman) (err error) { - if human.ID != "" { - existingHuman, err := humanWriteModelByID(ctx, filter, human.ID, a.ResourceOwner) +func (c *Commands) addHumanCommandCheckID(ctx context.Context, filter preparation.FilterToQueryReducer, human *AddHuman, orgID string) (err error) { + if human.ID == "" { + human.ID, err = c.idGenerator.Next() if err != nil { return err } - if isUserStateExists(existingHuman.UserState) { - return errors.ThrowPreconditionFailed(nil, "COMMAND-k2unb", "Errors.User.AlreadyExisting") - } - return nil } - human.ID, err = c.idGenerator.Next() + existingHuman, err := humanWriteModelByID(ctx, filter, human.ID, orgID) if err != nil { return err } - a.ID = human.ID + if isUserStateExists(existingHuman.UserState) { + return errors.ThrowPreconditionFailed(nil, "COMMAND-k2unb", "Errors.User.AlreadyExisting") + } return nil } diff --git a/internal/command/user_human_test.go b/internal/command/user_human_test.go index 7e408ed9b3..bfbe48ad56 100644 --- a/internal/command/user_human_test.go +++ b/internal/command/user_human_test.go @@ -145,6 +145,7 @@ func TestCommandSide_AddHuman(t *testing.T) { t, expectFilter(), expectFilter(), + expectFilter(), ), }, args: args{ @@ -173,6 +174,7 @@ func TestCommandSide_AddHuman(t *testing.T) { idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "user1"), eventstore: eventstoreExpect( t, + expectFilter(), expectFilter( eventFromEventPusher( org.NewDomainPolicyAddedEvent(context.Background(), @@ -214,6 +216,7 @@ func TestCommandSide_AddHuman(t *testing.T) { fields: fields{ eventstore: eventstoreExpect( t, + expectFilter(), expectFilter( eventFromEventPusher( org.NewDomainPolicyAddedEvent(context.Background(), @@ -303,6 +306,7 @@ func TestCommandSide_AddHuman(t *testing.T) { fields: fields{ eventstore: eventstoreExpect( t, + expectFilter(), expectFilter( eventFromEventPusher( org.NewDomainPolicyAddedEvent(context.Background(), @@ -393,6 +397,7 @@ func TestCommandSide_AddHuman(t *testing.T) { fields: fields{ eventstore: eventstoreExpect( t, + expectFilter(), expectFilter( eventFromEventPusher( org.NewDomainPolicyAddedEvent(context.Background(), @@ -472,6 +477,7 @@ func TestCommandSide_AddHuman(t *testing.T) { fields: fields{ eventstore: eventstoreExpect( t, + expectFilter(), expectFilter( eventFromEventPusher( org.NewDomainPolicyAddedEvent(context.Background(), @@ -552,6 +558,7 @@ func TestCommandSide_AddHuman(t *testing.T) { fields: fields{ eventstore: eventstoreExpect( t, + expectFilter(), expectFilter( eventFromEventPusher( org.NewDomainPolicyAddedEvent(context.Background(), @@ -621,6 +628,7 @@ func TestCommandSide_AddHuman(t *testing.T) { fields: fields{ eventstore: eventstoreExpect( t, + expectFilter(), expectFilter( eventFromEventPusher( org.NewDomainPolicyAddedEvent(context.Background(), @@ -690,6 +698,7 @@ func TestCommandSide_AddHuman(t *testing.T) { fields: fields{ eventstore: eventstoreExpect( t, + expectFilter(), expectFilter( eventFromEventPusher( org.NewDomainPolicyAddedEvent(context.Background(), @@ -759,6 +768,7 @@ func TestCommandSide_AddHuman(t *testing.T) { fields: fields{ eventstore: eventstoreExpect( t, + expectFilter(), expectFilter( eventFromEventPusher( org.NewDomainPolicyAddedEvent(context.Background(), @@ -811,6 +821,7 @@ func TestCommandSide_AddHuman(t *testing.T) { fields: fields{ eventstore: eventstoreExpect( t, + expectFilter(), expectFilter( eventFromEventPusher( org.NewDomainPolicyAddedEvent(context.Background(), @@ -909,6 +920,7 @@ func TestCommandSide_AddHuman(t *testing.T) { fields: fields{ eventstore: eventstoreExpect( t, + expectFilter(), expectFilter( eventFromEventPusher( org.NewDomainPolicyAddedEvent(context.Background(), @@ -1007,6 +1019,7 @@ func TestCommandSide_AddHuman(t *testing.T) { fields: fields{ eventstore: eventstoreExpect( t, + expectFilter(), expectFilter( eventFromEventPusher( org.NewDomainPolicyAddedEvent(context.Background(), @@ -1094,6 +1107,7 @@ func TestCommandSide_AddHuman(t *testing.T) { fields: fields{ eventstore: eventstoreExpect( t, + expectFilter(), expectFilter( eventFromEventPusher( org.NewDomainPolicyAddedEvent(context.Background(), @@ -3781,8 +3795,8 @@ func TestAddHumanCommand(t *testing.T) { idGenerator id.Generator } type args struct { - a *user.Aggregate human *AddHuman + orgID string passwordAlg crypto.HashAlgorithm filter preparation.FilterToQueryReducer codeAlg crypto.EncryptionAlgorithm @@ -3798,12 +3812,12 @@ func TestAddHumanCommand(t *testing.T) { { name: "invalid email", args: args{ - a: agg, human: &AddHuman{ Email: Email{ Address: "invalid", }, }, + orgID: "ro", }, want: Want{ ValidationErr: caos_errs.ThrowInvalidArgument(nil, "EMAIL-599BI", "Errors.User.Email.Invalid"), @@ -3812,7 +3826,6 @@ func TestAddHumanCommand(t *testing.T) { { name: "invalid first name", args: args{ - a: agg, human: &AddHuman{ Username: "username", PreferredLanguage: language.English, @@ -3820,6 +3833,7 @@ func TestAddHumanCommand(t *testing.T) { Address: "support@zitadel.com", }, }, + orgID: "ro", }, want: Want{ ValidationErr: caos_errs.ThrowInvalidArgument(nil, "USER-UCej2", "Errors.User.Profile.FirstNameEmpty"), @@ -3828,13 +3842,13 @@ func TestAddHumanCommand(t *testing.T) { { name: "invalid last name", args: args{ - a: agg, human: &AddHuman{ Username: "username", PreferredLanguage: language.English, FirstName: "hurst", Email: Email{Address: "support@zitadel.com"}, }, + orgID: "ro", }, want: Want{ ValidationErr: caos_errs.ThrowInvalidArgument(nil, "USER-4hB7d", "Errors.User.Profile.LastNameEmpty"), @@ -3846,7 +3860,6 @@ func TestAddHumanCommand(t *testing.T) { idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "id"), }, args: args{ - a: agg, human: &AddHuman{ Email: Email{Address: "support@zitadel.com"}, PreferredLanguage: language.English, @@ -3855,23 +3868,28 @@ func TestAddHumanCommand(t *testing.T) { Password: "short", Username: "username", }, + orgID: "ro", filter: NewMultiFilter().Append( func(ctx context.Context, queryFactory *eventstore.SearchQueryBuilder) ([]eventstore.Event, error) { - return []eventstore.Event{ - org.NewDomainPolicyAddedEvent( - context.Background(), - &org.NewAggregate("id").Aggregate, - true, - true, - true, - ), - }, nil + return []eventstore.Event{}, nil }). + Append( + func(ctx context.Context, queryFactory *eventstore.SearchQueryBuilder) ([]eventstore.Event, error) { + return []eventstore.Event{ + org.NewDomainPolicyAddedEvent( + ctx, + &org.NewAggregate("id").Aggregate, + true, + true, + true, + ), + }, nil + }). Append( func(ctx context.Context, queryFactory *eventstore.SearchQueryBuilder) ([]eventstore.Event, error) { return []eventstore.Event{ org.NewPasswordComplexityPolicyAddedEvent( - context.Background(), + ctx, &org.NewAggregate("id").Aggregate, 8, true, @@ -3893,7 +3911,6 @@ func TestAddHumanCommand(t *testing.T) { idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "id"), }, args: args{ - a: agg, human: &AddHuman{ Email: Email{Address: "support@zitadel.com", Verified: true}, PreferredLanguage: language.English, @@ -3902,25 +3919,30 @@ func TestAddHumanCommand(t *testing.T) { Password: "password", Username: "username", }, + orgID: "ro", passwordAlg: crypto.CreateMockHashAlg(gomock.NewController(t)), codeAlg: crypto.CreateMockEncryptionAlg(gomock.NewController(t)), filter: NewMultiFilter().Append( func(ctx context.Context, queryFactory *eventstore.SearchQueryBuilder) ([]eventstore.Event, error) { - return []eventstore.Event{ - org.NewDomainPolicyAddedEvent( - context.Background(), - &org.NewAggregate("id").Aggregate, - true, - true, - true, - ), - }, nil + return []eventstore.Event{}, nil }). + Append( + func(ctx context.Context, queryFactory *eventstore.SearchQueryBuilder) ([]eventstore.Event, error) { + return []eventstore.Event{ + org.NewDomainPolicyAddedEvent( + ctx, + &org.NewAggregate("id").Aggregate, + true, + true, + true, + ), + }, nil + }). Append( func(ctx context.Context, queryFactory *eventstore.SearchQueryBuilder) ([]eventstore.Event, error) { return []eventstore.Event{ org.NewPasswordComplexityPolicyAddedEvent( - context.Background(), + ctx, &org.NewAggregate("id").Aggregate, 2, false, @@ -3966,7 +3988,7 @@ func TestAddHumanCommand(t *testing.T) { c := &Commands{ idGenerator: tt.fields.idGenerator, } - AssertValidation(t, context.Background(), c.AddHumanCommand(tt.args.a, tt.args.human, tt.args.passwordAlg, tt.args.codeAlg, tt.args.allowInitMail), tt.args.filter, tt.want) + AssertValidation(t, context.Background(), c.AddHumanCommand(tt.args.human, tt.args.orgID, tt.args.passwordAlg, tt.args.codeAlg, tt.args.allowInitMail), tt.args.filter, tt.want) }) } }