diff --git a/internal/api/grpc/resources/user/v3alpha/password.go b/internal/api/grpc/resources/user/v3alpha/password.go index 2528851ded..fc93de17bf 100644 --- a/internal/api/grpc/resources/user/v3alpha/password.go +++ b/internal/api/grpc/resources/user/v3alpha/password.go @@ -24,14 +24,38 @@ func (s *Server) SetPassword(ctx context.Context, req *user.SetPasswordRequest) } func setPasswordRequestToSetSchemaUserPassword(req *user.SetPasswordRequest) *command.SetSchemaUserPassword { + pw, verification := setPasswordToSetSchemaUserPassword(req.GetNewPassword()) return &command.SetSchemaUserPassword{ - ResourceOwner: organizationToUpdateResourceOwner(req.Organization), - UserID: req.GetId(), - Password: req.GetNewPassword().GetPassword(), - EncodedPasswordHash: req.GetNewPassword().GetHash(), - ChangeRequired: req.GetNewPassword().GetChangeRequired(), - VerificationCode: req.GetNewPassword().GetVerificationCode(), - CurrentPassword: req.GetNewPassword().GetCurrentPassword(), + ResourceOwner: organizationToUpdateResourceOwner(req.Organization), + UserID: req.GetId(), + Password: pw, + Verification: verification, + } +} + +func setPasswordToSetSchemaUserPassword(req *user.SetPassword) (*command.SchemaUserPassword, *command.SchemaUserPasswordVerification) { + return setPasswordToSchemaUserPassword(req.GetPassword(), req.GetHash(), req.GetChangeRequired()), + setPasswordToSchemaUserPasswordVerification(req.GetCurrentPassword(), req.GetVerificationCode()) +} + +func setPasswordToSchemaUserPassword(pw string, hash string, changeRequired bool) *command.SchemaUserPassword { + if pw == "" && hash == "" { + return nil + } + return &command.SchemaUserPassword{ + Password: pw, + EncodedPasswordHash: hash, + ChangeRequired: changeRequired, + } +} + +func setPasswordToSchemaUserPasswordVerification(pw string, code string) *command.SchemaUserPasswordVerification { + if pw == "" && code == "" { + return nil + } + return &command.SchemaUserPasswordVerification{ + CurrentPassword: pw, + Code: code, } } diff --git a/internal/api/grpc/resources/user/v3alpha/pat.go b/internal/api/grpc/resources/user/v3alpha/pat.go index 36eb890be2..d12ba635f9 100644 --- a/internal/api/grpc/resources/user/v3alpha/pat.go +++ b/internal/api/grpc/resources/user/v3alpha/pat.go @@ -25,21 +25,32 @@ func (s *Server) AddPersonalAccessToken(ctx context.Context, req *user.AddPerson return &user.AddPersonalAccessTokenResponse{ Details: resource_object.DomainToDetailsPb(details, object.OwnerType_OWNER_TYPE_ORG, details.ResourceOwner), PersonalAccessTokenId: details.ID, - PersonalAccessToken: pat.Token, + PersonalAccessToken: pat.PAT.Token, }, nil } func addPersonalAccessTokenRequestToAddPAT(req *user.AddPersonalAccessTokenRequest) *command.AddPAT { - expDate := time.Time{} - if req.GetPersonalAccessToken().GetExpirationDate() != nil { - expDate = req.GetPersonalAccessToken().GetExpirationDate().AsTime() + if req == nil { + return nil } - return &command.AddPAT{ - ResourceOwner: organizationToUpdateResourceOwner(req.Organization), - UserID: req.GetId(), + ResourceOwner: organizationToUpdateResourceOwner(req.Organization), + UserID: req.GetId(), + PAT: setPersonalAccessTokenToAddPAT(req.GetPersonalAccessToken()), + } +} + +func setPersonalAccessTokenToAddPAT(set *user.SetPersonalAccessToken) *command.PAT { + if set == nil { + return nil + } + expDate := time.Time{} + if set.GetExpirationDate() != nil { + expDate = set.GetExpirationDate().AsTime() + } + return &command.PAT{ ExpirationDate: expDate, - Scope: []string{oidc.ScopeOpenID, oidc.ScopeProfile, z_oidc.ScopeUserMetaData, z_oidc.ScopeResourceOwner}, + Scopes: []string{oidc.ScopeOpenID, oidc.ScopeProfile, z_oidc.ScopeUserMetaData, z_oidc.ScopeResourceOwner}, } } diff --git a/internal/api/grpc/resources/user/v3alpha/publickey.go b/internal/api/grpc/resources/user/v3alpha/publickey.go index bf07f298a3..66f517405d 100644 --- a/internal/api/grpc/resources/user/v3alpha/publickey.go +++ b/internal/api/grpc/resources/user/v3alpha/publickey.go @@ -22,19 +22,31 @@ func (s *Server) AddPublicKey(ctx context.Context, req *user.AddPublicKeyRequest return &user.AddPublicKeyResponse{ Details: resource_object.DomainToDetailsPb(details, object.OwnerType_OWNER_TYPE_ORG, details.ResourceOwner), PublicKeyId: details.ID, - PrivateKey: pk.PrivateKey, + PrivateKey: pk.GetPrivateKey(), }, nil } func addPublicKeyRequestToAddPublicKey(req *user.AddPublicKeyRequest) *command.AddPublicKey { - expDate := time.Time{} - if req.GetPublicKey().GetExpirationDate() != nil { - expDate = req.GetPublicKey().GetExpirationDate().AsTime() + if req == nil { + return nil } return &command.AddPublicKey{ - ResourceOwner: organizationToUpdateResourceOwner(req.Organization), - UserID: req.GetId(), - PublicKey: req.GetPublicKey().GetPublicKey().GetPublicKey(), + ResourceOwner: organizationToUpdateResourceOwner(req.Organization), + UserID: req.GetId(), + PublicKey: setPublicKeyToAddPublicKey(req.GetPublicKey()), + } +} + +func setPublicKeyToAddPublicKey(req *user.SetPublicKey) *command.PublicKey { + if req == nil { + return nil + } + expDate := time.Time{} + if req.GetExpirationDate() != nil { + expDate = req.GetExpirationDate().AsTime() + } + return &command.PublicKey{ + PublicKey: req.GetPublicKey().GetPublicKey(), ExpirationDate: expDate, } } diff --git a/internal/api/grpc/resources/user/v3alpha/user.go b/internal/api/grpc/resources/user/v3alpha/user.go index 971644de7d..c9ad312256 100644 --- a/internal/api/grpc/resources/user/v3alpha/user.go +++ b/internal/api/grpc/resources/user/v3alpha/user.go @@ -30,20 +30,54 @@ func (s *Server) CreateUser(ctx context.Context, req *user.CreateUserRequest) (_ }, nil } +type authenticators struct { + Usernames []*command.Username + Password *command.SchemaUserPassword + PublicKeys []*command.PublicKey + PATs []*command.PAT +} + +func setAuthenticatorsToAuthenticators(set *user.SetAuthenticators) *authenticators { + if set == nil { + return nil + } + auths := &authenticators{} + for _, u := range set.GetUsernames() { + auths.Usernames = append(auths.Usernames, setUsernameToAddUsername(u)) + } + if set.GetPassword() != nil { + auths.Password = setPasswordToSchemaUserPassword(set.GetPassword().GetPassword(), set.GetPassword().GetHash(), set.GetPassword().GetChangeRequired()) + } + for _, p := range set.GetPublicKey() { + auths.PublicKeys = append(auths.PublicKeys, setPublicKeyToAddPublicKey(p)) + } + for _, p := range set.GetPersonalAccessToken() { + auths.PATs = append(auths.PATs, setPersonalAccessTokenToAddPAT(p)) + } + return auths +} + func createUserRequestToCreateSchemaUser(ctx context.Context, req *user.CreateUserRequest) (*command.CreateSchemaUser, error) { data, err := req.GetUser().GetData().MarshalJSON() if err != nil { return nil, err } - return &command.CreateSchemaUser{ + user := &command.CreateSchemaUser{ ResourceOwner: organizationToCreateResourceOwner(ctx, req.Organization), SchemaID: req.GetUser().GetSchemaId(), ID: req.GetUser().GetUserId(), Data: data, Email: setEmailToEmail(req.GetUser().GetContact().GetEmail()), Phone: setPhoneToPhone(req.GetUser().GetContact().GetPhone()), - }, nil + } + if auths := setAuthenticatorsToAuthenticators(req.GetUser().Authenticators); auths != nil { + user.Usernames = auths.Usernames + user.Password = auths.Password + user.PublicKeys = auths.PublicKeys + user.PATs = auths.PATs + } + return user, nil } func organizationToCreateResourceOwner(ctx context.Context, org *object.Organization) string { diff --git a/internal/api/grpc/resources/user/v3alpha/username.go b/internal/api/grpc/resources/user/v3alpha/username.go index 0e4bc7257a..43fe374bd7 100644 --- a/internal/api/grpc/resources/user/v3alpha/username.go +++ b/internal/api/grpc/resources/user/v3alpha/username.go @@ -24,11 +24,23 @@ func (s *Server) AddUsername(ctx context.Context, req *user.AddUsernameRequest) } func addUsernameRequestToAddUsername(req *user.AddUsernameRequest) *command.AddUsername { + if req == nil { + return nil + } return &command.AddUsername{ ResourceOwner: organizationToUpdateResourceOwner(req.Organization), UserID: req.GetId(), - Username: req.GetUsername().GetUsername(), - IsOrgSpecific: req.GetUsername().GetIsOrganizationSpecific(), + Username: setUsernameToAddUsername(req.GetUsername()), + } +} + +func setUsernameToAddUsername(req *user.SetUsername) *command.Username { + if req == nil { + return nil + } + return &command.Username{ + Username: req.GetUsername(), + IsOrgSpecific: req.GetIsOrganizationSpecific(), } } diff --git a/internal/command/user_v3.go b/internal/command/user_v3.go index 7a8d6f8e9c..05720c98b1 100644 --- a/internal/command/user_v3.go +++ b/internal/command/user_v3.go @@ -19,6 +19,11 @@ type CreateSchemaUser struct { ReturnCodeEmail *string Phone *Phone ReturnCodePhone *string + + Usernames []*Username + Password *SchemaUserPassword + PublicKeys []*PublicKey + PATs []*PAT } func (s *CreateSchemaUser) Valid() (err error) { @@ -84,6 +89,34 @@ func (c *Commands) CreateSchemaUser(ctx context.Context, user *CreateSchemaUser) if codePhone != "" { user.ReturnCodePhone = &codePhone } + for i := range user.Usernames { + _, usernameEvents, err := c.addUsername(ctx, writeModel.ResourceOwner, writeModel.AggregateID, user.Usernames[i]) + if err != nil { + return nil, err + } + events = append(events, usernameEvents...) + } + if user.Password != nil { + _, pwEvents, err := c.setSchemaUserPassword(ctx, writeModel.ResourceOwner, writeModel.AggregateID, nil, user.Password) + if err != nil { + return nil, err + } + events = append(events, pwEvents...) + } + for i := range user.PublicKeys { + _, pkEvents, err := c.addPublicKey(ctx, writeModel.ResourceOwner, writeModel.AggregateID, user.PublicKeys[i]) + if err != nil { + return nil, err + } + events = append(events, pkEvents...) + } + for i := range user.PATs { + _, patEvents, err := c.addPAT(ctx, writeModel.ResourceOwner, writeModel.AggregateID, user.PATs[i]) + if err != nil { + return nil, err + } + events = append(events, patEvents...) + } return c.pushAppendAndReduceDetails(ctx, writeModel, events...) } diff --git a/internal/command/user_v3_password.go b/internal/command/user_v3_password.go index 4652b275cf..2360212a38 100644 --- a/internal/command/user_v3_password.go +++ b/internal/command/user_v3_password.go @@ -6,6 +6,7 @@ import ( "github.com/zitadel/zitadel/internal/crypto" "github.com/zitadel/zitadel/internal/domain" + "github.com/zitadel/zitadel/internal/eventstore" "github.com/zitadel/zitadel/internal/telemetry/tracing" "github.com/zitadel/zitadel/internal/zerrors" ) @@ -14,19 +15,22 @@ type SetSchemaUserPassword struct { ResourceOwner string UserID string + Verification *SchemaUserPasswordVerification + Password *SchemaUserPassword +} + +type SchemaUserPasswordVerification struct { + CurrentPassword string + Code string +} + +type SchemaUserPassword struct { Password string EncodedPasswordHash string ChangeRequired bool - - CurrentPassword string - VerificationCode string } -func (p *SetSchemaUserPassword) Validate(hasher *crypto.Hasher) (err error) { - if p.UserID == "" { - return zerrors.ThrowInvalidArgument(nil, "COMMAND-aS3Vz5t6BS", "Errors.IDMissing") - } - +func (p *SchemaUserPassword) Validate(hasher *crypto.Hasher) (err error) { if p.EncodedPasswordHash != "" { if !hasher.EncodingSupported(p.EncodedPasswordHash) { return zerrors.ThrowInvalidArgument(nil, "COMMAND-oz74onzvqr", "Errors.User.Password.NotSupported") @@ -35,56 +39,75 @@ func (p *SetSchemaUserPassword) Validate(hasher *crypto.Hasher) (err error) { if p.Password == "" && p.EncodedPasswordHash == "" { return zerrors.ThrowInvalidArgument(nil, "COMMAND-3klek4sbns", "Errors.User.Password.Empty") } - return nil } -func (c *Commands) SetSchemaUserPassword(ctx context.Context, user *SetSchemaUserPassword) (*domain.ObjectDetails, error) { - if err := user.Validate(c.userPasswordHasher); err != nil { +func (c *Commands) SetSchemaUserPassword(ctx context.Context, set *SetSchemaUserPassword) (*domain.ObjectDetails, error) { + if set.UserID == "" { + return nil, zerrors.ThrowInvalidArgument(nil, "COMMAND-aS3Vz5t6BS", "Errors.IDMissing") + } + if err := set.Password.Validate(c.userPasswordHasher); err != nil { return nil, err } - - schemaUser := &schemaUserPassword{ - Create: true, - ResourceOwner: user.ResourceOwner, - UserID: user.UserID, - VerificationCode: user.VerificationCode, - CurrentPassword: user.CurrentPassword, - Password: user.Password, - EncodedPasswordHash: user.EncodedPasswordHash, - } - - writeModel, err := c.getSchemaUserPasswordWithVerification(ctx, schemaUser) + schemauser, err := existingSchemaUser(ctx, c, set.ResourceOwner, set.UserID) if err != nil { return nil, err } + set.ResourceOwner = schemauser.ResourceOwner - // If password is provided, let's check if is compliant with the policy. - // If only a encodedPassword is passed, we can skip this. - if user.Password != "" { - if err = c.checkPasswordComplexity(ctx, user.Password, writeModel.ResourceOwner); err != nil { - return nil, err - } + _, err = existingSchema(ctx, c, "", schemauser.SchemaID) + if err != nil { + return nil, err } + // TODO check for possible authenticators - encodedPassword := schemaUser.EncodedPasswordHash - if encodedPassword == "" && user.Password != "" { - encodedPassword, err = c.userPasswordHasher.Hash(user.Password) - if err = convertPasswapErr(err); err != nil { - return nil, err - } - } - - events, err := writeModel.NewCreate(ctx, - encodedPassword, - user.ChangeRequired, - ) + writeModel, events, err := c.setSchemaUserPassword(ctx, set.ResourceOwner, set.UserID, set.Verification, set.Password) if err != nil { return nil, err } return c.pushAppendAndReduceDetails(ctx, writeModel, events...) } +func (c *Commands) setSchemaUserPassword(ctx context.Context, resourceOwner, userID string, verification *SchemaUserPasswordVerification, set *SchemaUserPassword) (*PasswordV3WriteModel, []eventstore.Command, error) { + if set == nil { + return nil, nil, nil + } + schemaUser := &schemaUserPassword{ + Set: true, + ResourceOwner: resourceOwner, + UserID: userID, + Verification: verification, + NewPassword: set, + } + writeModel, err := c.getSchemaUserPasswordWithVerification(ctx, schemaUser) + if err != nil { + return nil, nil, err + } + + // If password is provided, let's check if is compliant with the policy. + // If only a encodedPassword is passed, we can skip this. + if set.Password != "" { + if err = c.checkPasswordComplexity(ctx, set.Password, writeModel.ResourceOwner); err != nil { + return nil, nil, err + } + } + encodedPassword := schemaUser.NewPassword.EncodedPasswordHash + if encodedPassword == "" && set.Password != "" { + encodedPassword, err = c.userPasswordHasher.Hash(set.Password) + if err = convertPasswapErr(err); err != nil { + return nil, nil, err + } + } + events, err := writeModel.NewCreate(ctx, + encodedPassword, + set.ChangeRequired, + ) + if err != nil { + return nil, nil, err + } + return writeModel, events, nil +} + type RequestSchemaUserPasswordReset struct { ResourceOwner string UserID string @@ -132,13 +155,11 @@ func (c *Commands) DeleteSchemaUserPassword(ctx context.Context, resourceOwner, } type schemaUserPassword struct { - Create bool - ResourceOwner string - UserID string - VerificationCode string - CurrentPassword string - Password string - EncodedPasswordHash string + Set bool + ResourceOwner string + UserID string + Verification *SchemaUserPasswordVerification + NewPassword *SchemaUserPassword } func (c *Commands) getSchemaUserPasswordWM(ctx context.Context, resourceOwner, id string) (*PasswordV3WriteModel, error) { @@ -165,23 +186,21 @@ func (c *Commands) getSchemaUserPasswordWithVerification(ctx context.Context, us if err != nil { 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 err := writeModel.Exists(); !user.Set && err != nil { + return nil, err } // if no verification is set, the user must have the permission to change the password verification := c.setSchemaUserPasswordWithPermission(writeModel.UserID, writeModel.ResourceOwner) - // otherwise check the password code... - if user.VerificationCode != "" { - verification = c.setSchemaUserPasswordWithVerifyCode(writeModel.CodeCreationDate, writeModel.CodeExpiry, writeModel.Code, user.VerificationCode) - } - // ...or old password - if user.CurrentPassword != "" { - verification = c.checkSchemaUserCurrentPassword(user.Password, user.EncodedPasswordHash, user.CurrentPassword, writeModel.EncodedHash) + if user.Verification != nil { + // otherwise check the password code... + if user.Verification.Code != "" { + verification = c.setSchemaUserPasswordWithVerifyCode(writeModel.CodeCreationDate, writeModel.CodeExpiry, writeModel.Code, user.Verification.Code) + } + // ...or old password + if user.Verification.CurrentPassword != "" { + verification = c.checkSchemaUserCurrentPassword(user.NewPassword.Password, user.NewPassword.EncodedPasswordHash, user.Verification.CurrentPassword, writeModel.EncodedHash) + } } if verification != nil { @@ -191,7 +210,7 @@ func (c *Commands) getSchemaUserPasswordWithVerification(ctx context.Context, us } // use the new hash from the verification in case there is one (e.g. existing pw check) if newEncodedPassword != "" { - user.EncodedPasswordHash = newEncodedPassword + user.NewPassword.EncodedPasswordHash = newEncodedPassword } } return writeModel, nil diff --git a/internal/command/user_v3_password_test.go b/internal/command/user_v3_password_test.go index bfa6d9bbf1..8ad7b3bfbd 100644 --- a/internal/command/user_v3_password_test.go +++ b/internal/command/user_v3_password_test.go @@ -94,7 +94,8 @@ func TestCommands_SetSchemaUserPassword(t *testing.T) { args{ ctx: authz.NewMockContext("instanceID", "", ""), user: &SetSchemaUserPassword{ - UserID: "user1", + UserID: "user1", + Password: &SchemaUserPassword{}, }, }, res{ @@ -108,7 +109,6 @@ func TestCommands_SetSchemaUserPassword(t *testing.T) { fields{ eventstore: expectEventstore( expectFilter(), - expectFilter(), ), checkPermission: newMockPermissionCheckAllowed(), }, @@ -116,7 +116,7 @@ func TestCommands_SetSchemaUserPassword(t *testing.T) { ctx: authz.NewMockContext("instanceID", "", ""), user: &SetSchemaUserPassword{ UserID: "notexisting", - Password: "password", + Password: &SchemaUserPassword{Password: "password"}, }, }, res{ @@ -129,8 +129,9 @@ func TestCommands_SetSchemaUserPassword(t *testing.T) { "no permission, error", fields{ eventstore: expectEventstore( - expectFilter(), filterSchemaUserExisting(), + filterSchemaExisting(), + expectFilter(), ), checkPermission: newMockPermissionCheckNotAllowed(), }, @@ -138,7 +139,7 @@ func TestCommands_SetSchemaUserPassword(t *testing.T) { ctx: authz.NewMockContext("instanceID", "", ""), user: &SetSchemaUserPassword{ UserID: "user1", - Password: "password", + Password: &SchemaUserPassword{Password: "password"}, }, }, res{ @@ -151,8 +152,9 @@ func TestCommands_SetSchemaUserPassword(t *testing.T) { "password added, ok", fields{ eventstore: expectEventstore( - expectFilter(), filterSchemaUserExisting(), + filterSchemaExisting(), + expectFilter(), filterPasswordComplexityPolicyExisting(), expectPush( authenticator.NewPasswordCreatedEvent( @@ -170,9 +172,11 @@ func TestCommands_SetSchemaUserPassword(t *testing.T) { args{ ctx: authz.NewMockContext("instanceID", "", ""), user: &SetSchemaUserPassword{ - UserID: "user1", - Password: "password", - ChangeRequired: false, + UserID: "user1", + Password: &SchemaUserPassword{ + Password: "password", + ChangeRequired: false, + }, }, }, res{ @@ -185,7 +189,9 @@ func TestCommands_SetSchemaUserPassword(t *testing.T) { "password set, complexity failed", fields{ eventstore: expectEventstore( - filterSchemaUserPasswordExisting(), + filterSchemaUserExisting(), + filterSchemaExisting(), + expectFilter(), filterPasswordComplexityPolicyExisting(), expectPush( authenticator.NewPasswordCreatedEvent( @@ -203,9 +209,11 @@ func TestCommands_SetSchemaUserPassword(t *testing.T) { args{ ctx: authz.NewMockContext("instanceID", "", ""), user: &SetSchemaUserPassword{ - UserID: "user1", - Password: "password", - ChangeRequired: false, + UserID: "user1", + Password: &SchemaUserPassword{ + Password: "password", + ChangeRequired: false, + }, }, }, res{ @@ -218,7 +226,9 @@ func TestCommands_SetSchemaUserPassword(t *testing.T) { "password set, ok", fields{ eventstore: expectEventstore( - filterSchemaUserPasswordExisting(), + filterSchemaUserExisting(), + filterSchemaExisting(), + expectFilter(), filterPasswordComplexityPolicyExisting(), expectPush( authenticator.NewPasswordCreatedEvent( @@ -236,9 +246,11 @@ func TestCommands_SetSchemaUserPassword(t *testing.T) { args{ ctx: authz.NewMockContext("instanceID", "", ""), user: &SetSchemaUserPassword{ - UserID: "user1", - Password: "password", - ChangeRequired: false, + UserID: "user1", + Password: &SchemaUserPassword{ + Password: "password", + ChangeRequired: false, + }, }, }, res{ @@ -251,7 +263,9 @@ func TestCommands_SetSchemaUserPassword(t *testing.T) { "password set, changeRequired, ok", fields{ eventstore: expectEventstore( - filterSchemaUserPasswordExisting(), + filterSchemaUserExisting(), + filterSchemaExisting(), + expectFilter(), filterPasswordComplexityPolicyExisting(), expectPush( authenticator.NewPasswordCreatedEvent( @@ -269,9 +283,11 @@ func TestCommands_SetSchemaUserPassword(t *testing.T) { args{ ctx: authz.NewMockContext("instanceID", "", ""), user: &SetSchemaUserPassword{ - UserID: "user1", - Password: "password", - ChangeRequired: true, + UserID: "user1", + Password: &SchemaUserPassword{ + Password: "password", + ChangeRequired: true, + }, }, }, res{ @@ -284,7 +300,9 @@ func TestCommands_SetSchemaUserPassword(t *testing.T) { "password set, encoded, ok", fields{ eventstore: expectEventstore( - filterSchemaUserPasswordExisting(), + filterSchemaUserExisting(), + filterSchemaExisting(), + expectFilter(), filterPasswordComplexityPolicyExisting(), expectPush( authenticator.NewPasswordCreatedEvent( @@ -302,10 +320,12 @@ func TestCommands_SetSchemaUserPassword(t *testing.T) { args{ ctx: authz.NewMockContext("instanceID", "", ""), user: &SetSchemaUserPassword{ - UserID: "user1", - Password: "passwordnotused", - EncodedPasswordHash: "$plain$x$password2", - ChangeRequired: false, + UserID: "user1", + Password: &SchemaUserPassword{ + Password: "passwordnotused", + EncodedPasswordHash: "$plain$x$password2", + ChangeRequired: false, + }, }, }, res{ @@ -318,6 +338,8 @@ func TestCommands_SetSchemaUserPassword(t *testing.T) { "password set, current password, ok", fields{ eventstore: expectEventstore( + filterSchemaUserExisting(), + filterSchemaExisting(), filterSchemaUserPasswordExisting(), filterPasswordComplexityPolicyExisting(), expectPush( @@ -335,10 +357,14 @@ func TestCommands_SetSchemaUserPassword(t *testing.T) { args{ ctx: authz.NewMockContext("instanceID", "", ""), user: &SetSchemaUserPassword{ - UserID: "user1", - Password: "password2", - CurrentPassword: "password", - ChangeRequired: false, + UserID: "user1", + Password: &SchemaUserPassword{ + Password: "password2", + ChangeRequired: false, + }, + Verification: &SchemaUserPasswordVerification{ + CurrentPassword: "password", + }, }, }, res{ @@ -351,6 +377,8 @@ func TestCommands_SetSchemaUserPassword(t *testing.T) { "password set, current password, ok", fields{ eventstore: expectEventstore( + filterSchemaUserExisting(), + filterSchemaExisting(), filterSchemaUserPasswordExisting(), filterPasswordComplexityPolicyExisting(), expectPush( @@ -368,10 +396,14 @@ func TestCommands_SetSchemaUserPassword(t *testing.T) { args{ ctx: authz.NewMockContext("instanceID", "", ""), user: &SetSchemaUserPassword{ - UserID: "user1", - Password: "password2", - CurrentPassword: "password", - ChangeRequired: false, + UserID: "user1", + Password: &SchemaUserPassword{ + Password: "password2", + ChangeRequired: false, + }, + Verification: &SchemaUserPasswordVerification{ + CurrentPassword: "password", + }, }, }, res{ @@ -383,6 +415,8 @@ func TestCommands_SetSchemaUserPassword(t *testing.T) { "password set, current password, failed", fields{ eventstore: expectEventstore( + filterSchemaUserExisting(), + filterSchemaExisting(), filterSchemaUserPasswordExisting(), ), userPasswordHasher: mockPasswordHasher("x"), @@ -390,10 +424,14 @@ func TestCommands_SetSchemaUserPassword(t *testing.T) { args{ ctx: authz.NewMockContext("instanceID", "", ""), user: &SetSchemaUserPassword{ - UserID: "user1", - Password: "password2", - CurrentPassword: "notreally", - ChangeRequired: false, + UserID: "user1", + Password: &SchemaUserPassword{ + Password: "password2", + ChangeRequired: false, + }, + Verification: &SchemaUserPasswordVerification{ + CurrentPassword: "notreally", + }, }, }, res{ @@ -406,6 +444,8 @@ func TestCommands_SetSchemaUserPassword(t *testing.T) { "password set, code, ok", fields{ eventstore: expectEventstore( + filterSchemaUserExisting(), + filterSchemaExisting(), expectFilter( eventFromEventPusher( authenticator.NewPasswordCreatedEvent( @@ -449,10 +489,14 @@ func TestCommands_SetSchemaUserPassword(t *testing.T) { args{ ctx: authz.NewMockContext("instanceID", "", ""), user: &SetSchemaUserPassword{ - UserID: "user1", - Password: "password2", - VerificationCode: "code", - ChangeRequired: false, + UserID: "user1", + Password: &SchemaUserPassword{ + Password: "password2", + ChangeRequired: false, + }, + Verification: &SchemaUserPasswordVerification{ + Code: "code", + }, }, }, res{ @@ -465,6 +509,8 @@ func TestCommands_SetSchemaUserPassword(t *testing.T) { "password set, code, failed", fields{ eventstore: expectEventstore( + filterSchemaUserExisting(), + filterSchemaExisting(), expectFilter( eventFromEventPusher( authenticator.NewPasswordCreatedEvent( @@ -498,10 +544,14 @@ func TestCommands_SetSchemaUserPassword(t *testing.T) { args{ ctx: authz.NewMockContext("instanceID", "", ""), user: &SetSchemaUserPassword{ - UserID: "user1", - Password: "password2", - VerificationCode: "notreally", - ChangeRequired: false, + UserID: "user1", + Password: &SchemaUserPassword{ + Password: "password2", + ChangeRequired: false, + }, + Verification: &SchemaUserPasswordVerification{ + Code: "notreally", + }, }, }, res{ @@ -514,6 +564,8 @@ func TestCommands_SetSchemaUserPassword(t *testing.T) { "password set, code, no code", fields{ eventstore: expectEventstore( + filterSchemaUserExisting(), + filterSchemaExisting(), filterSchemaUserPasswordExisting(), ), userPasswordHasher: mockPasswordHasher("x"), @@ -522,10 +574,14 @@ func TestCommands_SetSchemaUserPassword(t *testing.T) { args{ ctx: authz.NewMockContext("instanceID", "", ""), user: &SetSchemaUserPassword{ - UserID: "user1", - Password: "password2", - VerificationCode: "notreally", - ChangeRequired: false, + UserID: "user1", + Password: &SchemaUserPassword{ + Password: "password2", + ChangeRequired: false, + }, + Verification: &SchemaUserPasswordVerification{ + Code: "notreally", + }, }, }, res{ @@ -618,7 +674,7 @@ func TestCommands_RequestSchemaUserPasswordReset(t *testing.T) { "no permission, error", fields{ eventstore: expectEventstore( - expectFilter(), + filterSchemaUserPasswordExisting(), ), checkPermission: newMockPermissionCheckNotAllowed(), }, diff --git a/internal/command/user_v3_pat.go b/internal/command/user_v3_pat.go index 344130879e..e568d5bb99 100644 --- a/internal/command/user_v3_pat.go +++ b/internal/command/user_v3_pat.go @@ -7,6 +7,7 @@ import ( "github.com/zitadel/zitadel/internal/crypto" "github.com/zitadel/zitadel/internal/domain" + "github.com/zitadel/zitadel/internal/eventstore" "github.com/zitadel/zitadel/internal/zerrors" ) @@ -18,27 +19,24 @@ type AddPAT struct { ResourceOwner string UserID string + PAT *PAT +} + +type PAT struct { ExpirationDate time.Time - Scope []string + Scopes []string Token string } -func (wm *AddPAT) GetExpirationDate() time.Time { - return wm.ExpirationDate -} - -func (wm *AddPAT) SetExpirationDate(date time.Time) { - wm.ExpirationDate = date -} - -func (c *Commands) AddPAT(ctx context.Context, pat *AddPAT) (*domain.ObjectDetails, error) { - if pat.UserID == "" { +func (c *Commands) AddPAT(ctx context.Context, add *AddPAT) (*domain.ObjectDetails, error) { + if add.UserID == "" { return nil, zerrors.ThrowInvalidArgument(nil, "COMMAND-14sGR7lTaj", "Errors.IDMissing") } - schemauser, err := existingSchemaUser(ctx, c, pat.ResourceOwner, pat.UserID) + schemauser, err := existingSchemaUser(ctx, c, add.ResourceOwner, add.UserID) if err != nil { return nil, err } + add.ResourceOwner = schemauser.ResourceOwner _, err = existingSchema(ctx, c, "", schemauser.SchemaID) if err != nil { @@ -46,26 +44,36 @@ func (c *Commands) AddPAT(ctx context.Context, pat *AddPAT) (*domain.ObjectDetai } // TODO check for possible authenticators - id, err := c.idGenerator.Next() - if err != nil { - return nil, err - } - writeModel, err := c.getSchemaPATWM(ctx, schemauser.ResourceOwner, schemauser.AggregateID, id) - if err != nil { - return nil, err - } - - events, err := writeModel.NewCreate(ctx, pat.ExpirationDate, pat.Scope) - if err != nil { - return nil, err - } - pat.Token, err = createSchemaUserPAT(c.keyAlgorithm, writeModel.AggregateID, pat.UserID) + writeModel, events, err := c.addPAT(ctx, add.ResourceOwner, add.UserID, add.PAT) if err != nil { return nil, err } return c.pushAppendAndReduceDetails(ctx, writeModel, events...) } +func (c *Commands) addPAT(ctx context.Context, resourceOwner, userID string, add *PAT) (*PATV3WriteModel, []eventstore.Command, error) { + if add == nil { + return nil, nil, nil + } + id, err := c.idGenerator.Next() + if err != nil { + return nil, nil, err + } + writeModel, err := c.getSchemaPATWM(ctx, resourceOwner, userID, id) + if err != nil { + return nil, nil, err + } + events, err := writeModel.NewCreate(ctx, add.ExpirationDate, add.Scopes) + if err != nil { + return nil, nil, err + } + add.Token, err = createSchemaUserPAT(c.keyAlgorithm, writeModel.AggregateID, writeModel.UserID) + if err != nil { + return nil, nil, err + } + return writeModel, events, nil +} + func (c *Commands) DeletePAT(ctx context.Context, resourceOwner, userID, id string) (*domain.ObjectDetails, error) { if userID == "" { return nil, zerrors.ThrowInvalidArgument(nil, "COMMAND-hzqeAXW1qP", "Errors.IDMissing") diff --git a/internal/command/user_v3_pat_test.go b/internal/command/user_v3_pat_test.go index 2ab2ee1897..639681ae95 100644 --- a/internal/command/user_v3_pat_test.go +++ b/internal/command/user_v3_pat_test.go @@ -62,8 +62,10 @@ func TestCommands_AddPAT(t *testing.T) { checkPermission: newMockPermissionCheckAllowed(), }, args{ - ctx: authz.NewMockContext("instanceID", "", ""), - user: &AddPAT{}, + ctx: authz.NewMockContext("instanceID", "", ""), + user: &AddPAT{ + PAT: &PAT{}, + }, }, res{ err: func(err error) bool { @@ -83,6 +85,7 @@ func TestCommands_AddPAT(t *testing.T) { ctx: authz.NewMockContext("instanceID", "", ""), user: &AddPAT{ UserID: "notexisting", + PAT: &PAT{}, }, }, res{ @@ -106,6 +109,7 @@ func TestCommands_AddPAT(t *testing.T) { ctx: authz.NewMockContext("instanceID", "", ""), user: &AddPAT{ UserID: "user1", + PAT: &PAT{}, }, }, res{ @@ -127,6 +131,7 @@ func TestCommands_AddPAT(t *testing.T) { ctx: authz.NewMockContext("instanceID", "", ""), user: &AddPAT{ UserID: "user1", + PAT: &PAT{}, }, }, res{ @@ -160,7 +165,9 @@ func TestCommands_AddPAT(t *testing.T) { ctx: authz.NewMockContext("instanceID", "", ""), user: &AddPAT{ UserID: "user1", - Scope: []string{"first", "second", "third"}, + PAT: &PAT{ + Scopes: []string{"first", "second", "third"}, + }, }, }, res{ @@ -194,9 +201,11 @@ func TestCommands_AddPAT(t *testing.T) { args{ ctx: authz.NewMockContext("instanceID", "", ""), user: &AddPAT{ - UserID: "user1", - ExpirationDate: time.Date(2024, time.January, 1, 1, 1, 1, 1, time.UTC), - Scope: []string{"first", "second", "third"}, + UserID: "user1", + PAT: &PAT{ + ExpirationDate: time.Date(2024, time.January, 1, 1, 1, 1, 1, time.UTC), + Scopes: []string{"first", "second", "third"}, + }, }, }, res{ @@ -224,7 +233,7 @@ func TestCommands_AddPAT(t *testing.T) { } if tt.res.err == nil { assertObjectDetails(t, tt.res.details, details) - assert.Equal(t, tt.res.token, tt.args.user.Token) + assert.Equal(t, tt.res.token, tt.args.user.PAT.Token) } }) } diff --git a/internal/command/user_v3_publickey.go b/internal/command/user_v3_publickey.go index 97fba932c0..11b3154b47 100644 --- a/internal/command/user_v3_publickey.go +++ b/internal/command/user_v3_publickey.go @@ -5,6 +5,7 @@ import ( "time" "github.com/zitadel/zitadel/internal/domain" + "github.com/zitadel/zitadel/internal/eventstore" "github.com/zitadel/zitadel/internal/zerrors" ) @@ -12,35 +13,47 @@ type AddPublicKey struct { ResourceOwner string UserID string + PublicKey *PublicKey +} + +func (wm *AddPublicKey) GetPrivateKey() []byte { + if wm.PublicKey == nil { + return nil + } + return wm.PublicKey.PrivateKey +} + +type PublicKey struct { ExpirationDate time.Time PublicKey []byte PrivateKey []byte } -func (wm *AddPublicKey) GetExpirationDate() time.Time { +func (wm *PublicKey) GetExpirationDate() time.Time { return wm.ExpirationDate } -func (wm *AddPublicKey) SetExpirationDate(date time.Time) { +func (wm *PublicKey) SetExpirationDate(date time.Time) { wm.ExpirationDate = date } -func (wm *AddPublicKey) SetPublicKey(data []byte) { +func (wm *PublicKey) SetPublicKey(data []byte) { wm.PublicKey = data } -func (wm *AddPublicKey) SetPrivateKey(data []byte) { +func (wm *PublicKey) SetPrivateKey(data []byte) { wm.PrivateKey = data } -func (c *Commands) AddPublicKey(ctx context.Context, pk *AddPublicKey) (*domain.ObjectDetails, error) { - if pk.UserID == "" { +func (c *Commands) AddPublicKey(ctx context.Context, add *AddPublicKey) (*domain.ObjectDetails, error) { + if add.UserID == "" { return nil, zerrors.ThrowInvalidArgument(nil, "COMMAND-14sGR7lTaj", "Errors.IDMissing") } - schemauser, err := existingSchemaUser(ctx, c, pk.ResourceOwner, pk.UserID) + schemauser, err := existingSchemaUser(ctx, c, add.ResourceOwner, add.UserID) if err != nil { return nil, err } + add.ResourceOwner = schemauser.ResourceOwner _, err = existingSchema(ctx, c, "", schemauser.SchemaID) if err != nil { @@ -48,28 +61,37 @@ func (c *Commands) AddPublicKey(ctx context.Context, pk *AddPublicKey) (*domain. } // TODO check for possible authenticators - id, err := c.idGenerator.Next() - if err != nil { - return nil, err - } - writeModel, err := c.getSchemaPublicKeyWM(ctx, schemauser.ResourceOwner, schemauser.AggregateID, id) - if err != nil { - return nil, err - } - - if len(pk.PublicKey) == 0 { - if err := domain.SetNewAuthNKeyPair(pk, c.machineKeySize); err != nil { - return nil, err - } - } - - events, err := writeModel.NewCreate(ctx, pk.ExpirationDate, pk.PublicKey) + writeModel, events, err := c.addPublicKey(ctx, add.ResourceOwner, add.UserID, add.PublicKey) if err != nil { return nil, err } return c.pushAppendAndReduceDetails(ctx, writeModel, events...) } +func (c *Commands) addPublicKey(ctx context.Context, resourceOwner, userID string, add *PublicKey) (*PublicKeyV3WriteModel, []eventstore.Command, error) { + if add == nil { + return nil, nil, nil + } + id, err := c.idGenerator.Next() + if err != nil { + return nil, nil, err + } + writeModel, err := c.getSchemaPublicKeyWM(ctx, resourceOwner, userID, id) + if err != nil { + return nil, nil, err + } + if len(add.PublicKey) == 0 { + if err := domain.SetNewAuthNKeyPair(add, c.machineKeySize); err != nil { + return nil, nil, err + } + } + events, err := writeModel.NewCreate(ctx, add.ExpirationDate, add.PublicKey) + if err != nil { + return nil, nil, err + } + return writeModel, events, nil +} + func (c *Commands) DeletePublicKey(ctx context.Context, resourceOwner, userID, id string) (*domain.ObjectDetails, error) { if userID == "" { return nil, zerrors.ThrowInvalidArgument(nil, "COMMAND-hzqeAXW1qP", "Errors.IDMissing") diff --git a/internal/command/user_v3_publickey_test.go b/internal/command/user_v3_publickey_test.go index 992625b238..cd4cf1dd3d 100644 --- a/internal/command/user_v3_publickey_test.go +++ b/internal/command/user_v3_publickey_test.go @@ -101,8 +101,10 @@ func TestCommands_AddPublicKey(t *testing.T) { args{ ctx: authz.NewMockContext("instanceID", "", ""), user: &AddPublicKey{ - UserID: "user1", - PublicKey: []byte("something"), + UserID: "user1", + PublicKey: &PublicKey{ + PublicKey: []byte("something"), + }, }, }, res{ @@ -155,8 +157,10 @@ func TestCommands_AddPublicKey(t *testing.T) { args{ ctx: authz.NewMockContext("instanceID", "", ""), user: &AddPublicKey{ - UserID: "user1", - PublicKey: []byte("something"), + UserID: "user1", + PublicKey: &PublicKey{ + PublicKey: []byte("something"), + }, }, }, res{ @@ -188,9 +192,11 @@ func TestCommands_AddPublicKey(t *testing.T) { args{ ctx: authz.NewMockContext("instanceID", "", ""), user: &AddPublicKey{ - UserID: "user1", - PublicKey: []byte("something"), - ExpirationDate: time.Date(2024, time.January, 1, 1, 1, 1, 1, time.UTC), + UserID: "user1", + PublicKey: &PublicKey{ + PublicKey: []byte("something"), + ExpirationDate: time.Date(2024, time.January, 1, 1, 1, 1, 1, time.UTC), + }, }, }, res{ diff --git a/internal/command/user_v3_test.go b/internal/command/user_v3_test.go index be4329a46d..7de6e698b3 100644 --- a/internal/command/user_v3_test.go +++ b/internal/command/user_v3_test.go @@ -16,6 +16,7 @@ import ( "github.com/zitadel/zitadel/internal/eventstore" "github.com/zitadel/zitadel/internal/id" "github.com/zitadel/zitadel/internal/id/mock" + "github.com/zitadel/zitadel/internal/repository/user/authenticator" "github.com/zitadel/zitadel/internal/repository/user/schema" "github.com/zitadel/zitadel/internal/repository/user/schemauser" "github.com/zitadel/zitadel/internal/zerrors" @@ -23,10 +24,12 @@ import ( func TestCommands_CreateSchemaUser(t *testing.T) { type fields struct { - eventstore func(t *testing.T) *eventstore.Eventstore - idGenerator id.Generator - checkPermission domain.PermissionCheck - newCode encrypedCodeFunc + eventstore func(t *testing.T) *eventstore.Eventstore + idGenerator id.Generator + checkPermission domain.PermissionCheck + newCode encrypedCodeFunc + userPasswordHasher *crypto.Hasher + tokenAlg crypto.EncryptionAlgorithm } type args struct { ctx context.Context @@ -698,15 +701,100 @@ func TestCommands_CreateSchemaUser(t *testing.T) { }, }, }, + { + "user created, full authenticators", + fields{ + eventstore: expectEventstore( + expectFilter(), + filterSchemaExisting(), + expectFilter(), + expectFilter(), + filterPasswordComplexityPolicyExisting(), + expectFilter(), + expectFilter(), + expectPush( + schemauser.NewCreatedEvent( + context.Background(), + &schemauser.NewAggregate("id1", "org1").Aggregate, + "type", + 1, + json.RawMessage(`{ + "name": "user" + }`), + ), + authenticator.NewUsernameCreatedEvent( + context.Background(), + &authenticator.NewAggregate("username1", "org1").Aggregate, + "id1", + true, + "username1", + ), + authenticator.NewPasswordCreatedEvent( + context.Background(), + &authenticator.NewAggregate("id1", "org1").Aggregate, + "id1", + "$plain$x$password", + false, + ), + authenticator.NewPublicKeyCreatedEvent( + context.Background(), + &authenticator.NewAggregate("pk1", "org1").Aggregate, + "id1", + time.Date(2024, time.January, 1, 1, 1, 1, 1, time.UTC), + []byte("something"), + ), + authenticator.NewPATCreatedEvent( + context.Background(), + &authenticator.NewAggregate("pat1", "org1").Aggregate, + "id1", + time.Date(2024, time.January, 1, 1, 1, 1, 1, time.UTC), + []string{"first", "second", "third"}, + ), + ), + ), + idGenerator: mock.NewIDGeneratorExpectIDs(t, "id1", "username1", "pk1", "pat1"), + checkPermission: newMockPermissionCheckAllowed(), + userPasswordHasher: mockPasswordHasher("x"), + tokenAlg: crypto.CreateMockEncryptionAlg(gomock.NewController(t)), + }, + args{ + ctx: authz.NewMockContext("instanceID", "", ""), + user: &CreateSchemaUser{ + ResourceOwner: "org1", + SchemaID: "type", + Data: json.RawMessage(`{ + "name": "user" + }`), + Usernames: []*Username{ + {Username: "username1", IsOrgSpecific: true}, + }, + Password: &SchemaUserPassword{Password: "password"}, + PublicKeys: []*PublicKey{ + {PublicKey: []byte("something"), ExpirationDate: time.Date(2024, time.January, 1, 1, 1, 1, 1, time.UTC)}, + }, + PATs: []*PAT{ + {Scopes: []string{"first", "second", "third"}, ExpirationDate: time.Date(2024, time.January, 1, 1, 1, 1, 1, time.UTC)}, + }, + }, + }, + res{ + details: &domain.ObjectDetails{ + ResourceOwner: "org1", + ID: "id1", + }, + }, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { c := &Commands{ - eventstore: tt.fields.eventstore(t), - idGenerator: tt.fields.idGenerator, - checkPermission: tt.fields.checkPermission, - newEncryptedCode: tt.fields.newCode, - userEncryption: crypto.CreateMockEncryptionAlg(gomock.NewController(t)), + eventstore: tt.fields.eventstore(t), + idGenerator: tt.fields.idGenerator, + checkPermission: tt.fields.checkPermission, + newEncryptedCode: tt.fields.newCode, + userEncryption: crypto.CreateMockEncryptionAlg(gomock.NewController(t)), + userPasswordHasher: tt.fields.userPasswordHasher, + keyAlgorithm: tt.fields.tokenAlg, } details, err := c.CreateSchemaUser(tt.args.ctx, tt.args.user) if tt.res.err == nil { diff --git a/internal/command/user_v3_username.go b/internal/command/user_v3_username.go index 768fb06467..09cb20357b 100644 --- a/internal/command/user_v3_username.go +++ b/internal/command/user_v3_username.go @@ -4,6 +4,7 @@ import ( "context" "github.com/zitadel/zitadel/internal/domain" + "github.com/zitadel/zitadel/internal/eventstore" "github.com/zitadel/zitadel/internal/zerrors" ) @@ -11,18 +12,23 @@ type AddUsername struct { ResourceOwner string UserID string + Username *Username +} + +type Username struct { Username string IsOrgSpecific bool } -func (c *Commands) AddUsername(ctx context.Context, username *AddUsername) (*domain.ObjectDetails, error) { - if username.UserID == "" { +func (c *Commands) AddUsername(ctx context.Context, add *AddUsername) (*domain.ObjectDetails, error) { + if add.UserID == "" { return nil, zerrors.ThrowInvalidArgument(nil, "COMMAND-aS3Vz5t6BS", "Errors.IDMissing") } - schemauser, err := existingSchemaUser(ctx, c, username.ResourceOwner, username.UserID) + schemauser, err := existingSchemaUser(ctx, c, add.ResourceOwner, add.UserID) if err != nil { return nil, err } + add.ResourceOwner = schemauser.ResourceOwner _, err = existingSchema(ctx, c, "", schemauser.SchemaID) if err != nil { @@ -30,21 +36,32 @@ func (c *Commands) AddUsername(ctx context.Context, username *AddUsername) (*dom } // TODO check for possible authenticators - id, err := c.idGenerator.Next() - if err != nil { - return nil, err - } - writeModel, err := c.getSchemaUsernameWM(ctx, schemauser.ResourceOwner, schemauser.AggregateID, id) - if err != nil { - return nil, err - } - events, err := writeModel.NewCreate(ctx, username.IsOrgSpecific, username.Username) + writeModel, events, err := c.addUsername(ctx, add.ResourceOwner, add.UserID, add.Username) if err != nil { return nil, err } return c.pushAppendAndReduceDetails(ctx, writeModel, events...) } +func (c *Commands) addUsername(ctx context.Context, resourceOwner, userID string, add *Username) (*UsernameV3WriteModel, []eventstore.Command, error) { + if resourceOwner == "" || userID == "" || add == nil { + return nil, nil, nil + } + id, err := c.idGenerator.Next() + if err != nil { + return nil, nil, err + } + writeModel, err := c.getSchemaUsernameWM(ctx, resourceOwner, userID, id) + if err != nil { + return nil, nil, err + } + events, err := writeModel.NewCreate(ctx, add.IsOrgSpecific, add.Username) + if err != nil { + return nil, nil, err + } + return writeModel, events, nil +} + func (c *Commands) DeleteUsername(ctx context.Context, resourceOwner, userID, id string) (*domain.ObjectDetails, error) { if userID == "" { return nil, zerrors.ThrowInvalidArgument(nil, "COMMAND-J6ybG5WZiy", "Errors.IDMissing") diff --git a/internal/command/user_v3_username_test.go b/internal/command/user_v3_username_test.go index 7ebe4fce7a..dc135664a9 100644 --- a/internal/command/user_v3_username_test.go +++ b/internal/command/user_v3_username_test.go @@ -142,6 +142,9 @@ func TestCommands_AddUsername(t *testing.T) { ctx: authz.NewMockContext("instanceID", "", ""), user: &AddUsername{ UserID: "user1", + Username: &Username{ + Username: "user1", + }, }, }, res{ @@ -163,6 +166,9 @@ func TestCommands_AddUsername(t *testing.T) { ctx: authz.NewMockContext("instanceID", "", ""), user: &AddUsername{ UserID: "user1", + Username: &Username{ + Username: "user1", + }, }, }, res{ @@ -194,9 +200,11 @@ func TestCommands_AddUsername(t *testing.T) { args{ ctx: authz.NewMockContext("instanceID", "", ""), user: &AddUsername{ - UserID: "user1", - Username: "username", - IsOrgSpecific: false, + UserID: "user1", + Username: &Username{ + Username: "username", + IsOrgSpecific: false, + }, }, }, res{ @@ -228,9 +236,11 @@ func TestCommands_AddUsername(t *testing.T) { args{ ctx: authz.NewMockContext("instanceID", "", ""), user: &AddUsername{ - UserID: "user1", - Username: "username", - IsOrgSpecific: true, + UserID: "user1", + Username: &Username{ + Username: "username", + IsOrgSpecific: true, + }, }, }, res{