mirror of
https://github.com/zitadel/zitadel.git
synced 2025-08-12 11:07:32 +00:00
feat: user service v2 create, update and remove (#6996)
* feat: user service v2 remove user * feat: user service v2 add user human * feat: user service v2 change user human * feat: user service v2 change user human unit tests * feat: user service v2 reactivate, deactivate, lock, unlock user * feat: user service v2 integration tests * fix: merge back origin/main * lint: linter corrections * fix: move permission check for isVerfied and password change * fix: add deprecated notices and other review comments * fix: consistent naming in proto * fix: errors package renaming * fix: remove / delete user renaming in integration test * fix: machine user status changes through user v2 api * fix: linting changes * fix: linting changes * fix: changes from review * fix: changes from review * fix: changes from review * fix: changes from review * fix: changes from review --------- Co-authored-by: Tim Möhlmann <tim+github@zitadel.com> Co-authored-by: Livio Spring <livio.a@gmail.com>
This commit is contained in:
@@ -11,7 +11,7 @@ import (
|
||||
|
||||
func (s *Server) UpdateMyPassword(ctx context.Context, req *auth_pb.UpdateMyPasswordRequest) (*auth_pb.UpdateMyPasswordResponse, error) {
|
||||
ctxData := authz.GetCtxData(ctx)
|
||||
objectDetails, err := s.command.ChangePassword(ctx, ctxData.ResourceOwner, ctxData.UserID, req.OldPassword, req.NewPassword, "")
|
||||
objectDetails, err := s.command.ChangePassword(ctx, ctxData.ResourceOwner, ctxData.UserID, req.OldPassword, req.NewPassword)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@@ -53,9 +53,9 @@ func (s *Server) SetPassword(ctx context.Context, req *user.SetPasswordRequest)
|
||||
|
||||
switch v := req.GetVerification().(type) {
|
||||
case *user.SetPasswordRequest_CurrentPassword:
|
||||
details, err = s.command.ChangePassword(ctx, resourceOwner, req.GetUserId(), v.CurrentPassword, req.GetNewPassword().GetPassword(), "")
|
||||
details, err = s.command.ChangePassword(ctx, resourceOwner, req.GetUserId(), v.CurrentPassword, req.GetNewPassword().GetPassword())
|
||||
case *user.SetPasswordRequest_VerificationCode:
|
||||
details, err = s.command.SetPasswordWithVerifyCode(ctx, resourceOwner, req.GetUserId(), v.VerificationCode, req.GetNewPassword().GetPassword(), "")
|
||||
details, err = s.command.SetPasswordWithVerifyCode(ctx, resourceOwner, req.GetUserId(), v.VerificationCode, req.GetNewPassword().GetPassword())
|
||||
case nil:
|
||||
details, err = s.command.SetPassword(ctx, resourceOwner, req.GetUserId(), req.GetNewPassword().GetPassword(), req.GetNewPassword().GetChangeRequired())
|
||||
default:
|
||||
|
@@ -28,7 +28,7 @@ func (s *Server) AddHumanUser(ctx context.Context, req *user.AddHumanUserRequest
|
||||
return nil, err
|
||||
}
|
||||
orgID := authz.GetCtxData(ctx).OrgID
|
||||
if err = s.command.AddHuman(ctx, orgID, human, false); err != nil {
|
||||
if err = s.command.AddUserHuman(ctx, orgID, human, false, s.userCodeAlg); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &user.AddHumanUserResponse{
|
||||
@@ -113,6 +113,172 @@ func genderToDomain(gender user.Gender) domain.Gender {
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Server) UpdateHumanUser(ctx context.Context, req *user.UpdateHumanUserRequest) (_ *user.UpdateHumanUserResponse, err error) {
|
||||
human, err := UpdateUserRequestToChangeHuman(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = s.command.ChangeUserHuman(ctx, human, s.userCodeAlg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &user.UpdateHumanUserResponse{
|
||||
Details: object.DomainToDetailsPb(human.Details),
|
||||
EmailCode: human.EmailCode,
|
||||
PhoneCode: human.PhoneCode,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *Server) LockUser(ctx context.Context, req *user.LockUserRequest) (_ *user.LockUserResponse, err error) {
|
||||
details, err := s.command.LockUserV2(ctx, req.UserId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &user.LockUserResponse{
|
||||
Details: object.DomainToDetailsPb(details),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *Server) UnlockUser(ctx context.Context, req *user.UnlockUserRequest) (_ *user.UnlockUserResponse, err error) {
|
||||
details, err := s.command.UnlockUserV2(ctx, req.UserId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &user.UnlockUserResponse{
|
||||
Details: object.DomainToDetailsPb(details),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *Server) DeactivateUser(ctx context.Context, req *user.DeactivateUserRequest) (_ *user.DeactivateUserResponse, err error) {
|
||||
details, err := s.command.DeactivateUserV2(ctx, req.UserId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &user.DeactivateUserResponse{
|
||||
Details: object.DomainToDetailsPb(details),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *Server) ReactivateUser(ctx context.Context, req *user.ReactivateUserRequest) (_ *user.ReactivateUserResponse, err error) {
|
||||
details, err := s.command.ReactivateUserV2(ctx, req.UserId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &user.ReactivateUserResponse{
|
||||
Details: object.DomainToDetailsPb(details),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func ifNotNilPtr[v, p any](value *v, conv func(v) p) *p {
|
||||
var pNil *p
|
||||
if value == nil {
|
||||
return pNil
|
||||
}
|
||||
pVal := conv(*value)
|
||||
return &pVal
|
||||
}
|
||||
|
||||
func UpdateUserRequestToChangeHuman(req *user.UpdateHumanUserRequest) (*command.ChangeHuman, error) {
|
||||
email, err := SetHumanEmailToEmail(req.Email, req.GetUserId())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &command.ChangeHuman{
|
||||
ID: req.GetUserId(),
|
||||
Username: req.Username,
|
||||
Profile: SetHumanProfileToProfile(req.Profile),
|
||||
Email: email,
|
||||
Phone: SetHumanPhoneToPhone(req.Phone),
|
||||
Password: SetHumanPasswordToPassword(req.Password),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func SetHumanProfileToProfile(profile *user.SetHumanProfile) *command.Profile {
|
||||
if profile == nil {
|
||||
return nil
|
||||
}
|
||||
var firstName *string
|
||||
if profile.GivenName != "" {
|
||||
firstName = &profile.GivenName
|
||||
}
|
||||
var lastName *string
|
||||
if profile.FamilyName != "" {
|
||||
lastName = &profile.FamilyName
|
||||
}
|
||||
return &command.Profile{
|
||||
FirstName: firstName,
|
||||
LastName: lastName,
|
||||
NickName: profile.NickName,
|
||||
DisplayName: profile.DisplayName,
|
||||
PreferredLanguage: ifNotNilPtr(profile.PreferredLanguage, language.Make),
|
||||
Gender: ifNotNilPtr(profile.Gender, genderToDomain),
|
||||
}
|
||||
}
|
||||
|
||||
func SetHumanEmailToEmail(email *user.SetHumanEmail, userID string) (*command.Email, error) {
|
||||
if email == nil {
|
||||
return nil, nil
|
||||
}
|
||||
var urlTemplate string
|
||||
if email.GetSendCode() != nil && email.GetSendCode().UrlTemplate != nil {
|
||||
urlTemplate = *email.GetSendCode().UrlTemplate
|
||||
if err := domain.RenderConfirmURLTemplate(io.Discard, urlTemplate, userID, "code", "orgID"); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return &command.Email{
|
||||
Address: domain.EmailAddress(email.Email),
|
||||
Verified: email.GetIsVerified(),
|
||||
ReturnCode: email.GetReturnCode() != nil,
|
||||
URLTemplate: urlTemplate,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func SetHumanPhoneToPhone(phone *user.SetHumanPhone) *command.Phone {
|
||||
if phone == nil {
|
||||
return nil
|
||||
}
|
||||
return &command.Phone{
|
||||
Number: domain.PhoneNumber(phone.GetPhone()),
|
||||
Verified: phone.GetIsVerified(),
|
||||
ReturnCode: phone.GetReturnCode() != nil,
|
||||
}
|
||||
}
|
||||
|
||||
func SetHumanPasswordToPassword(password *user.SetPassword) *command.Password {
|
||||
if password == nil {
|
||||
return nil
|
||||
}
|
||||
var changeRequired bool
|
||||
var passwordStr *string
|
||||
if password.GetPassword() != nil {
|
||||
passwordStr = &password.GetPassword().Password
|
||||
changeRequired = password.GetPassword().GetChangeRequired()
|
||||
}
|
||||
var hash *string
|
||||
if password.GetHashedPassword() != nil {
|
||||
hash = &password.GetHashedPassword().Hash
|
||||
changeRequired = password.GetHashedPassword().GetChangeRequired()
|
||||
}
|
||||
var code *string
|
||||
if password.GetVerificationCode() != "" {
|
||||
codeT := password.GetVerificationCode()
|
||||
code = &codeT
|
||||
}
|
||||
var oldPassword *string
|
||||
if password.GetCurrentPassword() != "" {
|
||||
oldPasswordT := password.GetCurrentPassword()
|
||||
oldPassword = &oldPasswordT
|
||||
}
|
||||
return &command.Password{
|
||||
PasswordCode: code,
|
||||
OldPassword: oldPassword,
|
||||
Password: passwordStr,
|
||||
EncodedPasswordHash: hash,
|
||||
ChangeRequired: changeRequired,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Server) AddIDPLink(ctx context.Context, req *user.AddIDPLinkRequest) (_ *user.AddIDPLinkResponse, err error) {
|
||||
orgID := authz.GetCtxData(ctx).OrgID
|
||||
details, err := s.command.AddUserIDPLink(ctx, req.UserId, orgID, &command.AddLink{
|
||||
@@ -128,6 +294,92 @@ func (s *Server) AddIDPLink(ctx context.Context, req *user.AddIDPLinkRequest) (_
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *Server) DeleteUser(ctx context.Context, req *user.DeleteUserRequest) (_ *user.DeleteUserResponse, err error) {
|
||||
memberships, grants, err := s.removeUserDependencies(ctx, req.GetUserId())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
details, err := s.command.RemoveUserV2(ctx, req.UserId, memberships, grants...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &user.DeleteUserResponse{
|
||||
Details: object.DomainToDetailsPb(details),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *Server) removeUserDependencies(ctx context.Context, userID string) ([]*command.CascadingMembership, []string, error) {
|
||||
userGrantUserQuery, err := query.NewUserGrantUserIDSearchQuery(userID)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
grants, err := s.query.UserGrants(ctx, &query.UserGrantsQueries{
|
||||
Queries: []query.SearchQuery{userGrantUserQuery},
|
||||
}, true, true)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
membershipsUserQuery, err := query.NewMembershipUserIDQuery(userID)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
memberships, err := s.query.Memberships(ctx, &query.MembershipSearchQuery{
|
||||
Queries: []query.SearchQuery{membershipsUserQuery},
|
||||
}, false)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
return cascadingMemberships(memberships.Memberships), userGrantsToIDs(grants.UserGrants), nil
|
||||
}
|
||||
|
||||
func cascadingMemberships(memberships []*query.Membership) []*command.CascadingMembership {
|
||||
cascades := make([]*command.CascadingMembership, len(memberships))
|
||||
for i, membership := range memberships {
|
||||
cascades[i] = &command.CascadingMembership{
|
||||
UserID: membership.UserID,
|
||||
ResourceOwner: membership.ResourceOwner,
|
||||
IAM: cascadingIAMMembership(membership.IAM),
|
||||
Org: cascadingOrgMembership(membership.Org),
|
||||
Project: cascadingProjectMembership(membership.Project),
|
||||
ProjectGrant: cascadingProjectGrantMembership(membership.ProjectGrant),
|
||||
}
|
||||
}
|
||||
return cascades
|
||||
}
|
||||
|
||||
func cascadingIAMMembership(membership *query.IAMMembership) *command.CascadingIAMMembership {
|
||||
if membership == nil {
|
||||
return nil
|
||||
}
|
||||
return &command.CascadingIAMMembership{IAMID: membership.IAMID}
|
||||
}
|
||||
func cascadingOrgMembership(membership *query.OrgMembership) *command.CascadingOrgMembership {
|
||||
if membership == nil {
|
||||
return nil
|
||||
}
|
||||
return &command.CascadingOrgMembership{OrgID: membership.OrgID}
|
||||
}
|
||||
func cascadingProjectMembership(membership *query.ProjectMembership) *command.CascadingProjectMembership {
|
||||
if membership == nil {
|
||||
return nil
|
||||
}
|
||||
return &command.CascadingProjectMembership{ProjectID: membership.ProjectID}
|
||||
}
|
||||
func cascadingProjectGrantMembership(membership *query.ProjectGrantMembership) *command.CascadingProjectGrantMembership {
|
||||
if membership == nil {
|
||||
return nil
|
||||
}
|
||||
return &command.CascadingProjectGrantMembership{ProjectID: membership.ProjectID, GrantID: membership.GrantID}
|
||||
}
|
||||
|
||||
func userGrantsToIDs(userGrants []*query.UserGrant) []string {
|
||||
converted := make([]string, len(userGrants))
|
||||
for i, grant := range userGrants {
|
||||
converted[i] = grant.ID
|
||||
}
|
||||
return converted
|
||||
}
|
||||
|
||||
func (s *Server) StartIdentityProviderIntent(ctx context.Context, req *user.StartIdentityProviderIntentRequest) (_ *user.StartIdentityProviderIntentResponse, err error) {
|
||||
switch t := req.GetContent().(type) {
|
||||
case *user.StartIdentityProviderIntentRequest_Urls:
|
||||
|
@@ -540,6 +540,896 @@ func TestServer_AddHumanUser(t *testing.T) {
|
||||
if tt.want.GetEmailCode() != "" {
|
||||
assert.NotEmpty(t, got.GetEmailCode())
|
||||
}
|
||||
if tt.want.GetPhoneCode() != "" {
|
||||
assert.NotEmpty(t, got.GetPhoneCode())
|
||||
}
|
||||
integration.AssertDetails(t, tt.want, got)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestServer_UpdateHumanUser(t *testing.T) {
|
||||
type args struct {
|
||||
ctx context.Context
|
||||
req *user.UpdateHumanUserRequest
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
prepare func(request *user.UpdateHumanUserRequest) error
|
||||
args args
|
||||
want *user.UpdateHumanUserResponse
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "not exisiting",
|
||||
prepare: func(request *user.UpdateHumanUserRequest) error {
|
||||
request.UserId = "notexisiting"
|
||||
return nil
|
||||
},
|
||||
args: args{
|
||||
CTX,
|
||||
&user.UpdateHumanUserRequest{
|
||||
Username: gu.Ptr("changed"),
|
||||
},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "change username, ok",
|
||||
prepare: func(request *user.UpdateHumanUserRequest) error {
|
||||
userID := Tester.CreateHumanUser(CTX).GetUserId()
|
||||
request.UserId = userID
|
||||
return nil
|
||||
},
|
||||
args: args{
|
||||
CTX,
|
||||
&user.UpdateHumanUserRequest{
|
||||
Username: gu.Ptr(fmt.Sprint(time.Now().UnixNano() + 1)),
|
||||
},
|
||||
},
|
||||
want: &user.UpdateHumanUserResponse{
|
||||
Details: &object.Details{
|
||||
ChangeDate: timestamppb.Now(),
|
||||
ResourceOwner: Tester.Organisation.ID,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "change profile, ok",
|
||||
prepare: func(request *user.UpdateHumanUserRequest) error {
|
||||
userID := Tester.CreateHumanUser(CTX).GetUserId()
|
||||
request.UserId = userID
|
||||
return nil
|
||||
},
|
||||
args: args{
|
||||
CTX,
|
||||
&user.UpdateHumanUserRequest{
|
||||
Profile: &user.SetHumanProfile{
|
||||
GivenName: "Donald",
|
||||
FamilyName: "Duck",
|
||||
NickName: gu.Ptr("Dukkie"),
|
||||
DisplayName: gu.Ptr("Donald Duck"),
|
||||
PreferredLanguage: gu.Ptr("en"),
|
||||
Gender: user.Gender_GENDER_DIVERSE.Enum(),
|
||||
},
|
||||
},
|
||||
},
|
||||
want: &user.UpdateHumanUserResponse{
|
||||
Details: &object.Details{
|
||||
ChangeDate: timestamppb.Now(),
|
||||
ResourceOwner: Tester.Organisation.ID,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "change email, ok",
|
||||
prepare: func(request *user.UpdateHumanUserRequest) error {
|
||||
userID := Tester.CreateHumanUser(CTX).GetUserId()
|
||||
request.UserId = userID
|
||||
return nil
|
||||
},
|
||||
args: args{
|
||||
CTX,
|
||||
&user.UpdateHumanUserRequest{
|
||||
Email: &user.SetHumanEmail{
|
||||
Email: "changed@test.com",
|
||||
Verification: &user.SetHumanEmail_IsVerified{IsVerified: true},
|
||||
},
|
||||
},
|
||||
},
|
||||
want: &user.UpdateHumanUserResponse{
|
||||
Details: &object.Details{
|
||||
ChangeDate: timestamppb.Now(),
|
||||
ResourceOwner: Tester.Organisation.ID,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "change email, code, ok",
|
||||
prepare: func(request *user.UpdateHumanUserRequest) error {
|
||||
userID := Tester.CreateHumanUser(CTX).GetUserId()
|
||||
request.UserId = userID
|
||||
return nil
|
||||
},
|
||||
args: args{
|
||||
CTX,
|
||||
&user.UpdateHumanUserRequest{
|
||||
Email: &user.SetHumanEmail{
|
||||
Email: "changed@test.com",
|
||||
Verification: &user.SetHumanEmail_ReturnCode{},
|
||||
},
|
||||
},
|
||||
},
|
||||
want: &user.UpdateHumanUserResponse{
|
||||
Details: &object.Details{
|
||||
ChangeDate: timestamppb.Now(),
|
||||
ResourceOwner: Tester.Organisation.ID,
|
||||
},
|
||||
EmailCode: gu.Ptr("something"),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "change phone, ok",
|
||||
prepare: func(request *user.UpdateHumanUserRequest) error {
|
||||
userID := Tester.CreateHumanUser(CTX).GetUserId()
|
||||
request.UserId = userID
|
||||
return nil
|
||||
},
|
||||
args: args{
|
||||
CTX,
|
||||
&user.UpdateHumanUserRequest{
|
||||
Phone: &user.SetHumanPhone{
|
||||
Phone: "+41791234567",
|
||||
Verification: &user.SetHumanPhone_IsVerified{IsVerified: true},
|
||||
},
|
||||
},
|
||||
},
|
||||
want: &user.UpdateHumanUserResponse{
|
||||
Details: &object.Details{
|
||||
ChangeDate: timestamppb.Now(),
|
||||
ResourceOwner: Tester.Organisation.ID,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "change phone, code, ok",
|
||||
prepare: func(request *user.UpdateHumanUserRequest) error {
|
||||
userID := Tester.CreateHumanUser(CTX).GetUserId()
|
||||
request.UserId = userID
|
||||
return nil
|
||||
},
|
||||
args: args{
|
||||
CTX,
|
||||
&user.UpdateHumanUserRequest{
|
||||
Phone: &user.SetHumanPhone{
|
||||
Phone: "+41791234568",
|
||||
Verification: &user.SetHumanPhone_ReturnCode{},
|
||||
},
|
||||
},
|
||||
},
|
||||
want: &user.UpdateHumanUserResponse{
|
||||
Details: &object.Details{
|
||||
ChangeDate: timestamppb.Now(),
|
||||
ResourceOwner: Tester.Organisation.ID,
|
||||
},
|
||||
PhoneCode: gu.Ptr("something"),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "change password, code, ok",
|
||||
prepare: func(request *user.UpdateHumanUserRequest) error {
|
||||
userID := Tester.CreateHumanUser(CTX).GetUserId()
|
||||
request.UserId = userID
|
||||
resp, err := Client.PasswordReset(CTX, &user.PasswordResetRequest{
|
||||
UserId: userID,
|
||||
Medium: &user.PasswordResetRequest_ReturnCode{
|
||||
ReturnCode: &user.ReturnPasswordResetCode{},
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
request.Password.Verification = &user.SetPassword_VerificationCode{
|
||||
VerificationCode: resp.GetVerificationCode(),
|
||||
}
|
||||
return nil
|
||||
},
|
||||
args: args{
|
||||
CTX,
|
||||
&user.UpdateHumanUserRequest{
|
||||
Password: &user.SetPassword{
|
||||
PasswordType: &user.SetPassword_Password{
|
||||
Password: &user.Password{
|
||||
Password: "Password1!",
|
||||
ChangeRequired: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
want: &user.UpdateHumanUserResponse{
|
||||
Details: &object.Details{
|
||||
ChangeDate: timestamppb.Now(),
|
||||
ResourceOwner: Tester.Organisation.ID,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "change hashed password, code, ok",
|
||||
prepare: func(request *user.UpdateHumanUserRequest) error {
|
||||
userID := Tester.CreateHumanUser(CTX).GetUserId()
|
||||
request.UserId = userID
|
||||
resp, err := Client.PasswordReset(CTX, &user.PasswordResetRequest{
|
||||
UserId: userID,
|
||||
Medium: &user.PasswordResetRequest_ReturnCode{
|
||||
ReturnCode: &user.ReturnPasswordResetCode{},
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
request.Password.Verification = &user.SetPassword_VerificationCode{
|
||||
VerificationCode: resp.GetVerificationCode(),
|
||||
}
|
||||
return nil
|
||||
},
|
||||
args: args{
|
||||
CTX,
|
||||
&user.UpdateHumanUserRequest{
|
||||
Password: &user.SetPassword{
|
||||
PasswordType: &user.SetPassword_HashedPassword{
|
||||
HashedPassword: &user.HashedPassword{
|
||||
Hash: "$2y$12$hXUrnqdq1RIIYZ2HPytIIe5lXdIvbhqrTvdPsSF7o.jFh817Z6lwm",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
want: &user.UpdateHumanUserResponse{
|
||||
Details: &object.Details{
|
||||
ChangeDate: timestamppb.Now(),
|
||||
ResourceOwner: Tester.Organisation.ID,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "change hashed password, code, not supported",
|
||||
prepare: func(request *user.UpdateHumanUserRequest) error {
|
||||
userID := Tester.CreateHumanUser(CTX).GetUserId()
|
||||
request.UserId = userID
|
||||
resp, err := Client.PasswordReset(CTX, &user.PasswordResetRequest{
|
||||
UserId: userID,
|
||||
Medium: &user.PasswordResetRequest_ReturnCode{
|
||||
ReturnCode: &user.ReturnPasswordResetCode{},
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
request.Password = &user.SetPassword{
|
||||
Verification: &user.SetPassword_VerificationCode{
|
||||
VerificationCode: resp.GetVerificationCode(),
|
||||
},
|
||||
}
|
||||
return nil
|
||||
},
|
||||
args: args{
|
||||
CTX,
|
||||
&user.UpdateHumanUserRequest{
|
||||
Password: &user.SetPassword{
|
||||
PasswordType: &user.SetPassword_HashedPassword{
|
||||
HashedPassword: &user.HashedPassword{
|
||||
Hash: "$scrypt$ln=16,r=8,p=1$cmFuZG9tc2FsdGlzaGFyZA$Rh+NnJNo1I6nRwaNqbDm6kmADswD1+7FTKZ7Ln9D8nQ",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "change password, old password, ok",
|
||||
prepare: func(request *user.UpdateHumanUserRequest) error {
|
||||
userID := Tester.CreateHumanUser(CTX).GetUserId()
|
||||
request.UserId = userID
|
||||
|
||||
resp, err := Client.PasswordReset(CTX, &user.PasswordResetRequest{
|
||||
UserId: userID,
|
||||
Medium: &user.PasswordResetRequest_ReturnCode{
|
||||
ReturnCode: &user.ReturnPasswordResetCode{},
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
pw := "Password1."
|
||||
_, err = Client.SetPassword(CTX, &user.SetPasswordRequest{
|
||||
UserId: userID,
|
||||
NewPassword: &user.Password{
|
||||
Password: pw,
|
||||
ChangeRequired: true,
|
||||
},
|
||||
Verification: &user.SetPasswordRequest_VerificationCode{
|
||||
VerificationCode: resp.GetVerificationCode(),
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
request.Password.Verification = &user.SetPassword_CurrentPassword{
|
||||
CurrentPassword: pw,
|
||||
}
|
||||
return nil
|
||||
},
|
||||
args: args{
|
||||
CTX,
|
||||
&user.UpdateHumanUserRequest{
|
||||
Password: &user.SetPassword{
|
||||
PasswordType: &user.SetPassword_Password{
|
||||
Password: &user.Password{
|
||||
Password: "Password1!",
|
||||
ChangeRequired: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
want: &user.UpdateHumanUserResponse{
|
||||
Details: &object.Details{
|
||||
ChangeDate: timestamppb.Now(),
|
||||
ResourceOwner: Tester.Organisation.ID,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
err := tt.prepare(tt.args.req)
|
||||
require.NoError(t, err)
|
||||
|
||||
got, err := Client.UpdateHumanUser(tt.args.ctx, tt.args.req)
|
||||
if tt.wantErr {
|
||||
require.Error(t, err)
|
||||
} else {
|
||||
require.NoError(t, err)
|
||||
}
|
||||
if tt.want.GetEmailCode() != "" {
|
||||
assert.NotEmpty(t, got.GetEmailCode())
|
||||
}
|
||||
if tt.want.GetPhoneCode() != "" {
|
||||
assert.NotEmpty(t, got.GetPhoneCode())
|
||||
}
|
||||
integration.AssertDetails(t, tt.want, got)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestServer_LockUser(t *testing.T) {
|
||||
type args struct {
|
||||
ctx context.Context
|
||||
req *user.LockUserRequest
|
||||
prepare func(request *user.LockUserRequest) error
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want *user.LockUserResponse
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "lock, not existing",
|
||||
args: args{
|
||||
CTX,
|
||||
&user.LockUserRequest{
|
||||
UserId: "notexisting",
|
||||
},
|
||||
func(request *user.LockUserRequest) error { return nil },
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "lock, ok",
|
||||
args: args{
|
||||
CTX,
|
||||
&user.LockUserRequest{},
|
||||
func(request *user.LockUserRequest) error {
|
||||
resp := Tester.CreateHumanUser(CTX)
|
||||
request.UserId = resp.GetUserId()
|
||||
return nil
|
||||
},
|
||||
},
|
||||
want: &user.LockUserResponse{
|
||||
Details: &object.Details{
|
||||
ChangeDate: timestamppb.Now(),
|
||||
ResourceOwner: Tester.Organisation.ID,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "lock machine, ok",
|
||||
args: args{
|
||||
CTX,
|
||||
&user.LockUserRequest{},
|
||||
func(request *user.LockUserRequest) error {
|
||||
resp := Tester.CreateMachineUser(CTX)
|
||||
request.UserId = resp.GetUserId()
|
||||
return nil
|
||||
},
|
||||
},
|
||||
want: &user.LockUserResponse{
|
||||
Details: &object.Details{
|
||||
ChangeDate: timestamppb.Now(),
|
||||
ResourceOwner: Tester.Organisation.ID,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "lock, already locked",
|
||||
args: args{
|
||||
CTX,
|
||||
&user.LockUserRequest{},
|
||||
func(request *user.LockUserRequest) error {
|
||||
resp := Tester.CreateHumanUser(CTX)
|
||||
request.UserId = resp.GetUserId()
|
||||
_, err := Client.LockUser(CTX, &user.LockUserRequest{
|
||||
UserId: resp.GetUserId(),
|
||||
})
|
||||
return err
|
||||
},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "lock machine, already locked",
|
||||
args: args{
|
||||
CTX,
|
||||
&user.LockUserRequest{},
|
||||
func(request *user.LockUserRequest) error {
|
||||
resp := Tester.CreateMachineUser(CTX)
|
||||
request.UserId = resp.GetUserId()
|
||||
_, err := Client.LockUser(CTX, &user.LockUserRequest{
|
||||
UserId: resp.GetUserId(),
|
||||
})
|
||||
return err
|
||||
},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
err := tt.args.prepare(tt.args.req)
|
||||
require.NoError(t, err)
|
||||
|
||||
got, err := Client.LockUser(tt.args.ctx, tt.args.req)
|
||||
if tt.wantErr {
|
||||
require.Error(t, err)
|
||||
} else {
|
||||
require.NoError(t, err)
|
||||
}
|
||||
integration.AssertDetails(t, tt.want, got)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestServer_UnLockUser(t *testing.T) {
|
||||
type args struct {
|
||||
ctx context.Context
|
||||
req *user.UnlockUserRequest
|
||||
prepare func(request *user.UnlockUserRequest) error
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want *user.UnlockUserResponse
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "unlock, not existing",
|
||||
args: args{
|
||||
CTX,
|
||||
&user.UnlockUserRequest{
|
||||
UserId: "notexisting",
|
||||
},
|
||||
func(request *user.UnlockUserRequest) error { return nil },
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "unlock, not locked",
|
||||
args: args{
|
||||
ctx: CTX,
|
||||
req: &user.UnlockUserRequest{},
|
||||
prepare: func(request *user.UnlockUserRequest) error {
|
||||
resp := Tester.CreateHumanUser(CTX)
|
||||
request.UserId = resp.GetUserId()
|
||||
return nil
|
||||
},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "unlock machine, not locked",
|
||||
args: args{
|
||||
ctx: CTX,
|
||||
req: &user.UnlockUserRequest{},
|
||||
prepare: func(request *user.UnlockUserRequest) error {
|
||||
resp := Tester.CreateMachineUser(CTX)
|
||||
request.UserId = resp.GetUserId()
|
||||
return nil
|
||||
},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "unlock, ok",
|
||||
args: args{
|
||||
ctx: CTX,
|
||||
req: &user.UnlockUserRequest{},
|
||||
prepare: func(request *user.UnlockUserRequest) error {
|
||||
resp := Tester.CreateHumanUser(CTX)
|
||||
request.UserId = resp.GetUserId()
|
||||
_, err := Client.LockUser(CTX, &user.LockUserRequest{
|
||||
UserId: resp.GetUserId(),
|
||||
})
|
||||
return err
|
||||
},
|
||||
},
|
||||
want: &user.UnlockUserResponse{
|
||||
Details: &object.Details{
|
||||
ChangeDate: timestamppb.Now(),
|
||||
ResourceOwner: Tester.Organisation.ID,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "unlock machine, ok",
|
||||
args: args{
|
||||
ctx: CTX,
|
||||
req: &user.UnlockUserRequest{},
|
||||
prepare: func(request *user.UnlockUserRequest) error {
|
||||
resp := Tester.CreateMachineUser(CTX)
|
||||
request.UserId = resp.GetUserId()
|
||||
_, err := Client.LockUser(CTX, &user.LockUserRequest{
|
||||
UserId: resp.GetUserId(),
|
||||
})
|
||||
return err
|
||||
},
|
||||
},
|
||||
want: &user.UnlockUserResponse{
|
||||
Details: &object.Details{
|
||||
ChangeDate: timestamppb.Now(),
|
||||
ResourceOwner: Tester.Organisation.ID,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
err := tt.args.prepare(tt.args.req)
|
||||
require.NoError(t, err)
|
||||
|
||||
got, err := Client.UnlockUser(tt.args.ctx, tt.args.req)
|
||||
if tt.wantErr {
|
||||
require.Error(t, err)
|
||||
} else {
|
||||
require.NoError(t, err)
|
||||
}
|
||||
integration.AssertDetails(t, tt.want, got)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestServer_DeactivateUser(t *testing.T) {
|
||||
type args struct {
|
||||
ctx context.Context
|
||||
req *user.DeactivateUserRequest
|
||||
prepare func(request *user.DeactivateUserRequest) error
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want *user.DeactivateUserResponse
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "deactivate, not existing",
|
||||
args: args{
|
||||
CTX,
|
||||
&user.DeactivateUserRequest{
|
||||
UserId: "notexisting",
|
||||
},
|
||||
func(request *user.DeactivateUserRequest) error { return nil },
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "deactivate, ok",
|
||||
args: args{
|
||||
CTX,
|
||||
&user.DeactivateUserRequest{},
|
||||
func(request *user.DeactivateUserRequest) error {
|
||||
resp := Tester.CreateHumanUser(CTX)
|
||||
request.UserId = resp.GetUserId()
|
||||
return nil
|
||||
},
|
||||
},
|
||||
want: &user.DeactivateUserResponse{
|
||||
Details: &object.Details{
|
||||
ChangeDate: timestamppb.Now(),
|
||||
ResourceOwner: Tester.Organisation.ID,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "deactivate machine, ok",
|
||||
args: args{
|
||||
CTX,
|
||||
&user.DeactivateUserRequest{},
|
||||
func(request *user.DeactivateUserRequest) error {
|
||||
resp := Tester.CreateMachineUser(CTX)
|
||||
request.UserId = resp.GetUserId()
|
||||
return nil
|
||||
},
|
||||
},
|
||||
want: &user.DeactivateUserResponse{
|
||||
Details: &object.Details{
|
||||
ChangeDate: timestamppb.Now(),
|
||||
ResourceOwner: Tester.Organisation.ID,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "deactivate, already deactivated",
|
||||
args: args{
|
||||
CTX,
|
||||
&user.DeactivateUserRequest{},
|
||||
func(request *user.DeactivateUserRequest) error {
|
||||
resp := Tester.CreateHumanUser(CTX)
|
||||
request.UserId = resp.GetUserId()
|
||||
_, err := Client.DeactivateUser(CTX, &user.DeactivateUserRequest{
|
||||
UserId: resp.GetUserId(),
|
||||
})
|
||||
return err
|
||||
},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "deactivate machine, already deactivated",
|
||||
args: args{
|
||||
CTX,
|
||||
&user.DeactivateUserRequest{},
|
||||
func(request *user.DeactivateUserRequest) error {
|
||||
resp := Tester.CreateMachineUser(CTX)
|
||||
request.UserId = resp.GetUserId()
|
||||
_, err := Client.DeactivateUser(CTX, &user.DeactivateUserRequest{
|
||||
UserId: resp.GetUserId(),
|
||||
})
|
||||
return err
|
||||
},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
err := tt.args.prepare(tt.args.req)
|
||||
require.NoError(t, err)
|
||||
|
||||
got, err := Client.DeactivateUser(tt.args.ctx, tt.args.req)
|
||||
if tt.wantErr {
|
||||
require.Error(t, err)
|
||||
} else {
|
||||
require.NoError(t, err)
|
||||
}
|
||||
integration.AssertDetails(t, tt.want, got)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestServer_ReactivateUser(t *testing.T) {
|
||||
type args struct {
|
||||
ctx context.Context
|
||||
req *user.ReactivateUserRequest
|
||||
prepare func(request *user.ReactivateUserRequest) error
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want *user.ReactivateUserResponse
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "reactivate, not existing",
|
||||
args: args{
|
||||
CTX,
|
||||
&user.ReactivateUserRequest{
|
||||
UserId: "notexisting",
|
||||
},
|
||||
func(request *user.ReactivateUserRequest) error { return nil },
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "reactivate, not deactivated",
|
||||
args: args{
|
||||
ctx: CTX,
|
||||
req: &user.ReactivateUserRequest{},
|
||||
prepare: func(request *user.ReactivateUserRequest) error {
|
||||
resp := Tester.CreateHumanUser(CTX)
|
||||
request.UserId = resp.GetUserId()
|
||||
return nil
|
||||
},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "reactivate machine, not deactivated",
|
||||
args: args{
|
||||
ctx: CTX,
|
||||
req: &user.ReactivateUserRequest{},
|
||||
prepare: func(request *user.ReactivateUserRequest) error {
|
||||
resp := Tester.CreateMachineUser(CTX)
|
||||
request.UserId = resp.GetUserId()
|
||||
return nil
|
||||
},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "reactivate, ok",
|
||||
args: args{
|
||||
ctx: CTX,
|
||||
req: &user.ReactivateUserRequest{},
|
||||
prepare: func(request *user.ReactivateUserRequest) error {
|
||||
resp := Tester.CreateHumanUser(CTX)
|
||||
request.UserId = resp.GetUserId()
|
||||
_, err := Client.DeactivateUser(CTX, &user.DeactivateUserRequest{
|
||||
UserId: resp.GetUserId(),
|
||||
})
|
||||
return err
|
||||
},
|
||||
},
|
||||
want: &user.ReactivateUserResponse{
|
||||
Details: &object.Details{
|
||||
ChangeDate: timestamppb.Now(),
|
||||
ResourceOwner: Tester.Organisation.ID,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "reactivate machine, ok",
|
||||
args: args{
|
||||
ctx: CTX,
|
||||
req: &user.ReactivateUserRequest{},
|
||||
prepare: func(request *user.ReactivateUserRequest) error {
|
||||
resp := Tester.CreateMachineUser(CTX)
|
||||
request.UserId = resp.GetUserId()
|
||||
_, err := Client.DeactivateUser(CTX, &user.DeactivateUserRequest{
|
||||
UserId: resp.GetUserId(),
|
||||
})
|
||||
return err
|
||||
},
|
||||
},
|
||||
want: &user.ReactivateUserResponse{
|
||||
Details: &object.Details{
|
||||
ChangeDate: timestamppb.Now(),
|
||||
ResourceOwner: Tester.Organisation.ID,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
err := tt.args.prepare(tt.args.req)
|
||||
require.NoError(t, err)
|
||||
|
||||
got, err := Client.ReactivateUser(tt.args.ctx, tt.args.req)
|
||||
if tt.wantErr {
|
||||
require.Error(t, err)
|
||||
} else {
|
||||
require.NoError(t, err)
|
||||
}
|
||||
integration.AssertDetails(t, tt.want, got)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestServer_DeleteUser(t *testing.T) {
|
||||
projectResp, err := Tester.CreateProject(CTX)
|
||||
require.NoError(t, err)
|
||||
type args struct {
|
||||
ctx context.Context
|
||||
req *user.DeleteUserRequest
|
||||
prepare func(request *user.DeleteUserRequest) error
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want *user.DeleteUserResponse
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "remove, not existing",
|
||||
args: args{
|
||||
CTX,
|
||||
&user.DeleteUserRequest{
|
||||
UserId: "notexisting",
|
||||
},
|
||||
func(request *user.DeleteUserRequest) error { return nil },
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "remove human, ok",
|
||||
args: args{
|
||||
ctx: CTX,
|
||||
req: &user.DeleteUserRequest{},
|
||||
prepare: func(request *user.DeleteUserRequest) error {
|
||||
resp := Tester.CreateHumanUser(CTX)
|
||||
request.UserId = resp.GetUserId()
|
||||
return err
|
||||
},
|
||||
},
|
||||
want: &user.DeleteUserResponse{
|
||||
Details: &object.Details{
|
||||
ChangeDate: timestamppb.Now(),
|
||||
ResourceOwner: Tester.Organisation.ID,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "remove machine, ok",
|
||||
args: args{
|
||||
ctx: CTX,
|
||||
req: &user.DeleteUserRequest{},
|
||||
prepare: func(request *user.DeleteUserRequest) error {
|
||||
resp := Tester.CreateMachineUser(CTX)
|
||||
request.UserId = resp.GetUserId()
|
||||
return err
|
||||
},
|
||||
},
|
||||
want: &user.DeleteUserResponse{
|
||||
Details: &object.Details{
|
||||
ChangeDate: timestamppb.Now(),
|
||||
ResourceOwner: Tester.Organisation.ID,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "remove dependencies, ok",
|
||||
args: args{
|
||||
ctx: CTX,
|
||||
req: &user.DeleteUserRequest{},
|
||||
prepare: func(request *user.DeleteUserRequest) error {
|
||||
resp := Tester.CreateHumanUser(CTX)
|
||||
request.UserId = resp.GetUserId()
|
||||
Tester.CreateProjectUserGrant(t, CTX, projectResp.GetId(), request.UserId)
|
||||
Tester.CreateProjectMembership(t, CTX, projectResp.GetId(), request.UserId)
|
||||
Tester.CreateOrgMembership(t, CTX, request.UserId)
|
||||
return err
|
||||
},
|
||||
},
|
||||
want: &user.DeleteUserResponse{
|
||||
Details: &object.Details{
|
||||
ChangeDate: timestamppb.Now(),
|
||||
ResourceOwner: Tester.Organisation.ID,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
err := tt.args.prepare(tt.args.req)
|
||||
require.NoError(t, err)
|
||||
|
||||
got, err := Client.DeleteUser(tt.args.ctx, tt.args.req)
|
||||
if tt.wantErr {
|
||||
require.Error(t, err)
|
||||
} else {
|
||||
require.NoError(t, err)
|
||||
}
|
||||
integration.AssertDetails(t, tt.want, got)
|
||||
})
|
||||
}
|
||||
|
@@ -4,8 +4,6 @@ import (
|
||||
"net/http"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/domain"
|
||||
|
||||
http_mw "github.com/zitadel/zitadel/internal/api/http/middleware"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -26,8 +24,7 @@ func (l *Login) handleChangePassword(w http.ResponseWriter, r *http.Request) {
|
||||
l.renderError(w, r, authReq, err)
|
||||
return
|
||||
}
|
||||
userAgentID, _ := http_mw.UserAgentIDFromCtx(r.Context())
|
||||
_, err = l.command.ChangePassword(setContext(r.Context(), authReq.UserOrgID), authReq.UserOrgID, authReq.UserID, data.OldPassword, data.NewPassword, userAgentID)
|
||||
_, err = l.command.ChangePassword(setContext(r.Context(), authReq.UserOrgID), authReq.UserOrgID, authReq.UserID, data.OldPassword, data.NewPassword)
|
||||
if err != nil {
|
||||
l.renderChangePassword(w, r, authReq, err)
|
||||
return
|
||||
|
@@ -4,7 +4,6 @@ import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
http_mw "github.com/zitadel/zitadel/internal/api/http/middleware"
|
||||
"github.com/zitadel/zitadel/internal/domain"
|
||||
"github.com/zitadel/zitadel/internal/zerrors"
|
||||
)
|
||||
@@ -72,8 +71,7 @@ func (l *Login) checkPWCode(w http.ResponseWriter, r *http.Request, authReq *dom
|
||||
if authReq != nil {
|
||||
userOrg = authReq.UserOrgID
|
||||
}
|
||||
userAgentID, _ := http_mw.UserAgentIDFromCtx(r.Context())
|
||||
_, err := l.command.SetPasswordWithVerifyCode(setContext(r.Context(), userOrg), userOrg, data.UserID, data.Code, data.Password, userAgentID)
|
||||
_, err := l.command.SetPasswordWithVerifyCode(setContext(r.Context(), userOrg), userOrg, data.UserID, data.Code, data.Password)
|
||||
if err != nil {
|
||||
l.renderInitPassword(w, r, authReq, data.UserID, "", err)
|
||||
return
|
||||
|
@@ -44,7 +44,7 @@ func (c *Commands) ChangeDefaultDomainPolicy(ctx context.Context, userLoginMustB
|
||||
}
|
||||
|
||||
func (c *Commands) getDefaultDomainPolicy(ctx context.Context) (*domain.DomainPolicy, error) {
|
||||
policyWriteModel, err := c.defaultDomainPolicyWriteModelByID(ctx)
|
||||
policyWriteModel, err := c.instanceDomainPolicyWriteModel(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -56,7 +56,7 @@ func (c *Commands) getDefaultDomainPolicy(ctx context.Context) (*domain.DomainPo
|
||||
return policy, nil
|
||||
}
|
||||
|
||||
func (c *Commands) defaultDomainPolicyWriteModelByID(ctx context.Context) (policy *InstanceDomainPolicyWriteModel, err error) {
|
||||
func (c *Commands) instanceDomainPolicyWriteModel(ctx context.Context) (policy *InstanceDomainPolicyWriteModel, err error) {
|
||||
ctx, span := tracing.NewSpan(ctx)
|
||||
defer func() { span.EndWithError(err) }()
|
||||
|
||||
|
@@ -453,7 +453,7 @@ func (c *Commands) prepareRemoveOrg(a *org.Aggregate) preparation.Validation {
|
||||
return nil, zerrors.ThrowNotFound(nil, "COMMA-aps2n", "Errors.Org.NotFound")
|
||||
}
|
||||
|
||||
domainPolicy, err := c.getOrgDomainPolicy(ctx, a.ID)
|
||||
domainPolicy, err := c.domainPolicyWriteModel(ctx, a.ID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@@ -59,18 +59,19 @@ func (c *Commands) RemoveOrgDomainPolicy(ctx context.Context, orgID string) (*do
|
||||
return pushedEventsToObjectDetails(pushedEvents), nil
|
||||
}
|
||||
|
||||
// Deprecated: Use commands.domainPolicyWriteModel directly, to remove the domain.DomainPolicy struct
|
||||
func (c *Commands) getOrgDomainPolicy(ctx context.Context, orgID string) (*domain.DomainPolicy, error) {
|
||||
policy, err := c.orgDomainPolicyWriteModelByID(ctx, orgID)
|
||||
policy, err := c.orgDomainPolicyWriteModel(ctx, orgID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if policy.State == domain.PolicyStateActive {
|
||||
if policy.State.Exists() {
|
||||
return orgWriteModelToDomainPolicy(policy), nil
|
||||
}
|
||||
return c.getDefaultDomainPolicy(ctx)
|
||||
}
|
||||
|
||||
func (c *Commands) orgDomainPolicyWriteModelByID(ctx context.Context, orgID string) (policy *OrgDomainPolicyWriteModel, err error) {
|
||||
func (c *Commands) orgDomainPolicyWriteModel(ctx context.Context, orgID string) (policy *OrgDomainPolicyWriteModel, err error) {
|
||||
ctx, span := tracing.NewSpan(ctx)
|
||||
defer func() { span.EndWithError(err) }()
|
||||
|
||||
|
@@ -26,7 +26,7 @@ type UniqueConstraintReadModel struct {
|
||||
}
|
||||
|
||||
type commandProvider interface {
|
||||
getOrgDomainPolicy(ctx context.Context, orgID string) (*domain.DomainPolicy, error)
|
||||
domainPolicyWriteModel(ctx context.Context, orgID string) (*PolicyDomainWriteModel, error)
|
||||
}
|
||||
|
||||
func NewUniqueConstraintReadModel(ctx context.Context, provider commandProvider) *UniqueConstraintReadModel {
|
||||
@@ -114,21 +114,21 @@ func (rm *UniqueConstraintReadModel) Reduce() error {
|
||||
case *project.RoleRemovedEvent:
|
||||
rm.removeUniqueConstraint(e.Aggregate().ID, e.Key, project.UniqueRoleType)
|
||||
case *user.HumanAddedEvent:
|
||||
policy, err := rm.commandProvider.getOrgDomainPolicy(rm.ctx, e.Aggregate().ResourceOwner)
|
||||
policy, err := rm.commandProvider.domainPolicyWriteModel(rm.ctx, e.Aggregate().ResourceOwner)
|
||||
if err != nil {
|
||||
logging.Log("COMMAND-0k9Gs").WithError(err).Error("could not read policy for human added event unique constraint")
|
||||
continue
|
||||
}
|
||||
rm.addUniqueConstraint(e.Aggregate().ID, e.Aggregate().ID, user.NewAddUsernameUniqueConstraint(e.UserName, e.Aggregate().ResourceOwner, policy.UserLoginMustBeDomain))
|
||||
case *user.HumanRegisteredEvent:
|
||||
policy, err := rm.commandProvider.getOrgDomainPolicy(rm.ctx, e.Aggregate().ResourceOwner)
|
||||
policy, err := rm.commandProvider.domainPolicyWriteModel(rm.ctx, e.Aggregate().ResourceOwner)
|
||||
if err != nil {
|
||||
logging.Log("COMMAND-m9fod").WithError(err).Error("could not read policy for human registered event unique constraint")
|
||||
continue
|
||||
}
|
||||
rm.addUniqueConstraint(e.Aggregate().ID, e.Aggregate().ID, user.NewAddUsernameUniqueConstraint(e.UserName, e.Aggregate().ResourceOwner, policy.UserLoginMustBeDomain))
|
||||
case *user.MachineAddedEvent:
|
||||
policy, err := rm.commandProvider.getOrgDomainPolicy(rm.ctx, e.Aggregate().ResourceOwner)
|
||||
policy, err := rm.commandProvider.domainPolicyWriteModel(rm.ctx, e.Aggregate().ResourceOwner)
|
||||
if err != nil {
|
||||
logging.Log("COMMAND-2n8vs").WithError(err).Error("could not read policy for machine added event unique constraint")
|
||||
continue
|
||||
@@ -138,14 +138,14 @@ func (rm *UniqueConstraintReadModel) Reduce() error {
|
||||
rm.removeUniqueConstraint(e.Aggregate().ID, e.Aggregate().ID, user.UniqueUsername)
|
||||
rm.listRemoveUniqueConstraint(e.Aggregate().ID, user.UniqueUserIDPLinkType)
|
||||
case *user.UsernameChangedEvent:
|
||||
policy, err := rm.commandProvider.getOrgDomainPolicy(rm.ctx, e.Aggregate().ResourceOwner)
|
||||
policy, err := rm.commandProvider.domainPolicyWriteModel(rm.ctx, e.Aggregate().ResourceOwner)
|
||||
if err != nil {
|
||||
logging.Log("COMMAND-5n8gk").WithError(err).Error("could not read policy for username changed event unique constraint")
|
||||
continue
|
||||
}
|
||||
rm.changeUniqueConstraint(e.Aggregate().ID, e.Aggregate().ID, user.NewAddUsernameUniqueConstraint(e.UserName, e.Aggregate().ResourceOwner, policy.UserLoginMustBeDomain))
|
||||
case *user.DomainClaimedEvent:
|
||||
policy, err := rm.commandProvider.getOrgDomainPolicy(rm.ctx, e.Aggregate().ResourceOwner)
|
||||
policy, err := rm.commandProvider.domainPolicyWriteModel(rm.ctx, e.Aggregate().ResourceOwner)
|
||||
if err != nil {
|
||||
logging.Log("COMMAND-xb8uf").WithError(err).Error("could not read policy for domain claimed event unique constraint")
|
||||
continue
|
||||
|
@@ -38,7 +38,7 @@ func (c *Commands) ChangeUsername(ctx context.Context, orgID, userID, userName s
|
||||
return nil, zerrors.ThrowPreconditionFailed(nil, "COMMAND-6m9gs", "Errors.User.UsernameNotChanged")
|
||||
}
|
||||
|
||||
domainPolicy, err := c.getOrgDomainPolicy(ctx, orgID)
|
||||
domainPolicy, err := c.domainPolicyWriteModel(ctx, orgID)
|
||||
if err != nil {
|
||||
return nil, zerrors.ThrowPreconditionFailed(err, "COMMAND-38fnu", "Errors.Org.DomainPolicy.NotExisting")
|
||||
}
|
||||
@@ -196,7 +196,7 @@ func (c *Commands) RemoveUser(ctx context.Context, userID, resourceOwner string,
|
||||
return nil, zerrors.ThrowNotFound(nil, "COMMAND-m9od", "Errors.User.NotFound")
|
||||
}
|
||||
|
||||
domainPolicy, err := c.getOrgDomainPolicy(ctx, existingUser.ResourceOwner)
|
||||
domainPolicy, err := c.domainPolicyWriteModel(ctx, existingUser.ResourceOwner)
|
||||
if err != nil {
|
||||
return nil, zerrors.ThrowPreconditionFailed(err, "COMMAND-3M9fs", "Errors.Org.DomainPolicy.NotExisting")
|
||||
}
|
||||
@@ -330,7 +330,7 @@ func (c *Commands) userDomainClaimed(ctx context.Context, userID string) (events
|
||||
changedUserGrant := NewUserWriteModel(userID, existingUser.ResourceOwner)
|
||||
userAgg := UserAggregateFromWriteModel(&changedUserGrant.WriteModel)
|
||||
|
||||
domainPolicy, err := c.getOrgDomainPolicy(ctx, existingUser.ResourceOwner)
|
||||
domainPolicy, err := c.domainPolicyWriteModel(ctx, existingUser.ResourceOwner)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
@@ -7,6 +7,7 @@ import (
|
||||
"github.com/zitadel/zitadel/internal/zerrors"
|
||||
)
|
||||
|
||||
// Deprecated: User commands.domainPolicyWriteModel directly, to remove use of eventstore.Filter function
|
||||
func domainPolicyWriteModel(ctx context.Context, filter preparation.FilterToQueryReducer, orgID string) (*PolicyDomainWriteModel, error) {
|
||||
wm, err := orgDomainPolicy(ctx, filter, orgID)
|
||||
if err != nil {
|
||||
@@ -25,6 +26,25 @@ func domainPolicyWriteModel(ctx context.Context, filter preparation.FilterToQuer
|
||||
return nil, zerrors.ThrowInternal(nil, "USER-Ggk9n", "Errors.Internal")
|
||||
}
|
||||
|
||||
func (c *Commands) domainPolicyWriteModel(ctx context.Context, orgID string) (*PolicyDomainWriteModel, error) {
|
||||
wm, err := c.orgDomainPolicyWriteModel(ctx, orgID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if wm != nil && wm.State.Exists() {
|
||||
return &wm.PolicyDomainWriteModel, err
|
||||
}
|
||||
instanceWriteModel, err := c.instanceDomainPolicyWriteModel(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if instanceWriteModel != nil && instanceWriteModel.State.Exists() {
|
||||
return &instanceWriteModel.PolicyDomainWriteModel, err
|
||||
}
|
||||
return nil, zerrors.ThrowInternal(nil, "USER-Ggk9n", "Errors.Internal")
|
||||
}
|
||||
|
||||
// Deprecated: Use commands.orgDomainPolicyWriteModel directly, to remove use of eventstore.Filter function
|
||||
func orgDomainPolicy(ctx context.Context, filter preparation.FilterToQueryReducer, orgID string) (*OrgDomainPolicyWriteModel, error) {
|
||||
policy := NewOrgDomainPolicyWriteModel(orgID)
|
||||
events, err := filter(ctx, policy.Query())
|
||||
@@ -39,6 +59,7 @@ func orgDomainPolicy(ctx context.Context, filter preparation.FilterToQueryReduce
|
||||
return policy, err
|
||||
}
|
||||
|
||||
// Deprecated: Use commands.instanceDomainPolicyWriteModel directly, to remove use of eventstore.Filter function
|
||||
func instanceDomainPolicy(ctx context.Context, filter preparation.FilterToQueryReducer) (*InstanceDomainPolicyWriteModel, error) {
|
||||
policy := NewInstanceDomainPolicyWriteModel(ctx)
|
||||
events, err := filter(ctx, policy.Query())
|
||||
|
@@ -127,6 +127,7 @@ func (m *AddMetadataEntry) Valid() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Deprecated: use commands.AddUserHuman
|
||||
func (c *Commands) AddHuman(ctx context.Context, resourceOwner string, human *AddHuman, allowInitMail bool) (err error) {
|
||||
if resourceOwner == "" {
|
||||
return zerrors.ThrowInvalidArgument(nil, "COMMA-5Ky74", "Errors.Internal")
|
||||
@@ -180,7 +181,7 @@ func (c *Commands) AddHumanCommand(human *AddHuman, orgID string, hasher *crypto
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err = userValidateDomain(ctx, a, human.Username, domainPolicy.UserLoginMustBeDomain, filter); err != nil {
|
||||
if err = c.userValidateDomain(ctx, a.ResourceOwner, human.Username, domainPolicy.UserLoginMustBeDomain); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -310,6 +311,7 @@ func (c *Commands) addHumanCommandPhone(ctx context.Context, filter preparation.
|
||||
return append(cmds, user.NewHumanPhoneCodeAddedEventV2(ctx, &a.Aggregate, phoneCode.Crypted, phoneCode.Expiry, human.Phone.ReturnCode)), nil
|
||||
}
|
||||
|
||||
// Deprecated: use commands.NewUserHumanWriteModel, to remove deprecated eventstore.Filter
|
||||
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()
|
||||
@@ -347,7 +349,7 @@ func addHumanCommandPassword(ctx context.Context, filter preparation.FilterToQue
|
||||
return nil
|
||||
}
|
||||
|
||||
func userValidateDomain(ctx context.Context, a *user.Aggregate, username string, mustBeDomain bool, filter preparation.FilterToQueryReducer) error {
|
||||
func (c *Commands) userValidateDomain(ctx context.Context, resourceOwner string, username string, mustBeDomain bool) error {
|
||||
if mustBeDomain {
|
||||
return nil
|
||||
}
|
||||
@@ -357,17 +359,12 @@ func userValidateDomain(ctx context.Context, a *user.Aggregate, username string,
|
||||
return nil
|
||||
}
|
||||
|
||||
domainCheck := NewOrgDomainVerifiedWriteModel(username[index+1:])
|
||||
events, err := filter(ctx, domainCheck.Query())
|
||||
domainCheck, err := c.orgDomainVerifiedWriteModel(ctx, username[index+1:])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
domainCheck.AppendEvents(events...)
|
||||
if err = domainCheck.Reduce(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if domainCheck.Verified && domainCheck.ResourceOwner != a.ResourceOwner {
|
||||
if domainCheck.Verified && domainCheck.ResourceOwner != resourceOwner {
|
||||
return zerrors.ThrowInvalidArgument(nil, "COMMAND-SFd21", "Errors.User.DomainNotAllowedAsUsername")
|
||||
}
|
||||
|
||||
@@ -411,6 +408,7 @@ func (h *AddHuman) shouldAddInitCode() bool {
|
||||
h.Password == ""
|
||||
}
|
||||
|
||||
// Deprecated: use commands.AddUserHuman
|
||||
func (c *Commands) ImportHuman(ctx context.Context, orgID string, human *domain.Human, passwordless bool, links []*domain.UserIDPLink, initCodeGenerator, emailCodeGenerator, phoneCodeGenerator, passwordlessCodeGenerator crypto.Generator) (_ *domain.Human, passwordlessCode *domain.PasswordlessInitCode, err error) {
|
||||
if orgID == "" {
|
||||
return nil, nil, zerrors.ThrowInvalidArgument(nil, "COMMAND-5N8fs", "Errors.ResourceOwnerMissing")
|
||||
@@ -459,6 +457,7 @@ func (c *Commands) ImportHuman(ctx context.Context, orgID string, human *domain.
|
||||
return writeModelToHuman(addedHuman), passwordlessCode, nil
|
||||
}
|
||||
|
||||
// Deprecated: use commands.AddUserHuman
|
||||
func (c *Commands) RegisterHuman(ctx context.Context, orgID string, human *domain.Human, link *domain.UserIDPLink, orgMemberRoles []string, initCodeGenerator, emailCodeGenerator, phoneCodeGenerator crypto.Generator) (*domain.Human, error) {
|
||||
if orgID == "" {
|
||||
return nil, zerrors.ThrowInvalidArgument(nil, "COMMAND-GEdf2", "Errors.ResourceOwnerMissing")
|
||||
|
@@ -80,9 +80,7 @@ func (c *Commands) HumanVerifyInitCode(ctx context.Context, userID, resourceOwne
|
||||
commands = append(commands, user.NewHumanEmailVerifiedEvent(ctx, userAgg))
|
||||
}
|
||||
if password != "" {
|
||||
passwordWriteModel := NewHumanPasswordWriteModel(userID, existingCode.ResourceOwner)
|
||||
passwordWriteModel.UserState = domain.UserStateActive
|
||||
passwordCommand, err := c.setPasswordCommand(ctx, passwordWriteModel, password, false)
|
||||
passwordCommand, err := c.setPasswordCommand(ctx, userAgg, domain.UserStateActive, password, false, false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@@ -78,7 +78,7 @@ func (c *Commands) createHumanTOTP(ctx context.Context, userID, resourceOwner st
|
||||
logging.WithError(err).WithField("traceID", tracing.TraceIDFromCtx(ctx)).Debug("unable to get org for loginname")
|
||||
return nil, zerrors.ThrowPreconditionFailed(err, "COMMAND-55M9f", "Errors.Org.NotFound")
|
||||
}
|
||||
orgPolicy, err := c.getOrgDomainPolicy(ctx, org.AggregateID)
|
||||
orgPolicy, err := c.domainPolicyWriteModel(ctx, org.AggregateID)
|
||||
if err != nil {
|
||||
logging.WithError(err).WithField("traceID", tracing.TraceIDFromCtx(ctx)).Debug("unable to get org policy for loginname")
|
||||
return nil, zerrors.ThrowPreconditionFailed(err, "COMMAND-8ugTs", "Errors.Org.DomainPolicy.NotFound")
|
||||
|
@@ -34,7 +34,7 @@ func (c *Commands) SetPassword(ctx context.Context, orgID, userID, password stri
|
||||
return c.setPassword(ctx, wm, password, oneTime)
|
||||
}
|
||||
|
||||
func (c *Commands) SetPasswordWithVerifyCode(ctx context.Context, orgID, userID, code, password, userAgentID string) (objectDetails *domain.ObjectDetails, err error) {
|
||||
func (c *Commands) SetPasswordWithVerifyCode(ctx context.Context, orgID, userID, code, password string) (objectDetails *domain.ObjectDetails, err error) {
|
||||
ctx, span := tracing.NewSpan(ctx)
|
||||
defer func() { span.EndWithError(err) }()
|
||||
|
||||
@@ -61,8 +61,10 @@ func (c *Commands) SetPasswordWithVerifyCode(ctx context.Context, orgID, userID,
|
||||
return c.setPassword(ctx, wm, password, false)
|
||||
}
|
||||
|
||||
func (c *Commands) setPassword(ctx context.Context, wm *HumanPasswordWriteModel, password string, changeRequired bool) (objectDetails *domain.ObjectDetails, err error) {
|
||||
command, err := c.setPasswordCommand(ctx, wm, password, changeRequired)
|
||||
// setEncodedPassword add change event from already encoded password to HumanPasswordWriteModel and return the necessary object details for response
|
||||
func (c *Commands) setEncodedPassword(ctx context.Context, wm *HumanPasswordWriteModel, password string, changeRequired bool) (objectDetails *domain.ObjectDetails, err error) {
|
||||
agg := user.NewAggregate(wm.AggregateID, wm.ResourceOwner)
|
||||
command, err := c.setPasswordCommand(ctx, &agg.Aggregate, wm.UserState, password, changeRequired, true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -73,20 +75,39 @@ func (c *Commands) setPassword(ctx context.Context, wm *HumanPasswordWriteModel,
|
||||
return writeModelToObjectDetails(&wm.WriteModel), nil
|
||||
}
|
||||
|
||||
func (c *Commands) setPasswordCommand(ctx context.Context, wm *HumanPasswordWriteModel, password string, changeRequired bool) (_ eventstore.Command, err error) {
|
||||
if err = c.canUpdatePassword(ctx, password, wm); err != nil {
|
||||
// setPassword add change event to HumanPasswordWriteModel and return the necessary object details for response
|
||||
func (c *Commands) setPassword(ctx context.Context, wm *HumanPasswordWriteModel, password string, changeRequired bool) (objectDetails *domain.ObjectDetails, err error) {
|
||||
agg := user.NewAggregate(wm.AggregateID, wm.ResourceOwner)
|
||||
command, err := c.setPasswordCommand(ctx, &agg.Aggregate, wm.UserState, password, changeRequired, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ctx, span := tracing.NewNamedSpan(ctx, "passwap.Hash")
|
||||
encoded, err := c.userPasswordHasher.Hash(password)
|
||||
span.EndWithError(err)
|
||||
if err = convertPasswapErr(err); err != nil {
|
||||
err = c.pushAppendAndReduce(ctx, wm, command)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return user.NewHumanPasswordChangedEvent(ctx, UserAggregateFromWriteModel(&wm.WriteModel), encoded, changeRequired, ""), nil
|
||||
return writeModelToObjectDetails(&wm.WriteModel), nil
|
||||
}
|
||||
|
||||
func (c *Commands) ChangePassword(ctx context.Context, orgID, userID, oldPassword, newPassword, userAgentID string) (objectDetails *domain.ObjectDetails, err error) {
|
||||
func (c *Commands) setPasswordCommand(ctx context.Context, agg *eventstore.Aggregate, userState domain.UserState, password string, changeRequired, encoded bool) (_ eventstore.Command, err error) {
|
||||
if err = c.canUpdatePassword(ctx, password, agg.ResourceOwner, userState); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if !encoded {
|
||||
ctx, span := tracing.NewNamedSpan(ctx, "passwap.Hash")
|
||||
encodedPassword, err := c.userPasswordHasher.Hash(password)
|
||||
span.EndWithError(err)
|
||||
if err = convertPasswapErr(err); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return user.NewHumanPasswordChangedEvent(ctx, agg, encodedPassword, changeRequired, ""), nil
|
||||
}
|
||||
return user.NewHumanPasswordChangedEvent(ctx, agg, password, changeRequired, ""), nil
|
||||
}
|
||||
|
||||
// ChangePassword change password of existing user
|
||||
func (c *Commands) ChangePassword(ctx context.Context, orgID, userID, oldPassword, newPassword string) (objectDetails *domain.ObjectDetails, err error) {
|
||||
ctx, span := tracing.NewSpan(ctx)
|
||||
defer func() { span.EndWithError(err) }()
|
||||
|
||||
@@ -100,38 +121,39 @@ func (c *Commands) ChangePassword(ctx context.Context, orgID, userID, oldPasswor
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if wm.EncodedHash == "" {
|
||||
return nil, zerrors.ThrowPreconditionFailed(nil, "COMMAND-Fds3s", "Errors.User.Password.Empty")
|
||||
}
|
||||
if err = c.canUpdatePassword(ctx, newPassword, wm); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ctx, spanPasswap := tracing.NewNamedSpan(ctx, "passwap.VerifyAndUpdate")
|
||||
updated, err := c.userPasswordHasher.VerifyAndUpdate(wm.EncodedHash, oldPassword, newPassword)
|
||||
spanPasswap.EndWithError(err)
|
||||
if err = convertPasswapErr(err); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = c.pushAppendAndReduce(ctx, wm,
|
||||
user.NewHumanPasswordChangedEvent(ctx, UserAggregateFromWriteModel(&wm.WriteModel), updated, false, userAgentID))
|
||||
newPasswordHash, err := c.verifyAndUpdatePassword(ctx, wm.EncodedHash, oldPassword, newPassword)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return writeModelToObjectDetails(&wm.WriteModel), nil
|
||||
return c.setEncodedPassword(ctx, wm, newPasswordHash, false)
|
||||
}
|
||||
|
||||
func (c *Commands) canUpdatePassword(ctx context.Context, newPassword string, wm *HumanPasswordWriteModel) (err error) {
|
||||
// verifyAndUpdatePassword verify if the old password is correct with the encoded hash and
|
||||
// returns the hash of the new password if so
|
||||
func (c *Commands) verifyAndUpdatePassword(ctx context.Context, encodedHash, oldPassword, newPassword string) (string, error) {
|
||||
if encodedHash == "" {
|
||||
return "", zerrors.ThrowPreconditionFailed(nil, "COMMAND-Fds3s", "Errors.User.Password.NotSet")
|
||||
}
|
||||
|
||||
_, spanPasswap := tracing.NewNamedSpan(ctx, "passwap.Verify")
|
||||
updated, err := c.userPasswordHasher.VerifyAndUpdate(encodedHash, oldPassword, newPassword)
|
||||
spanPasswap.EndWithError(err)
|
||||
return updated, convertPasswapErr(err)
|
||||
}
|
||||
|
||||
// canUpdatePassword checks uf the given password can be used to be the password of a user
|
||||
func (c *Commands) canUpdatePassword(ctx context.Context, newPassword string, resourceOwner string, state domain.UserState) (err error) {
|
||||
ctx, span := tracing.NewSpan(ctx)
|
||||
defer func() { span.EndWithError(err) }()
|
||||
|
||||
if wm.UserState == domain.UserStateUnspecified || wm.UserState == domain.UserStateDeleted {
|
||||
if !isUserStateExists(state) {
|
||||
return zerrors.ThrowNotFound(nil, "COMMAND-G8dh3", "Errors.User.Password.NotFound")
|
||||
}
|
||||
if wm.UserState == domain.UserStateInitial {
|
||||
if state == domain.UserStateInitial {
|
||||
return zerrors.ThrowPreconditionFailed(nil, "COMMAND-M9dse", "Errors.User.NotInitialised")
|
||||
}
|
||||
policy, err := c.getOrgPasswordComplexityPolicy(ctx, wm.ResourceOwner)
|
||||
policy, err := c.getOrgPasswordComplexityPolicy(ctx, resourceOwner)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -142,6 +164,7 @@ func (c *Commands) canUpdatePassword(ctx context.Context, newPassword string, wm
|
||||
return nil
|
||||
}
|
||||
|
||||
// RequestSetPassword generate and send out new code to change password for a specific user
|
||||
func (c *Commands) RequestSetPassword(ctx context.Context, userID, resourceOwner string, notifyType domain.NotificationType, passwordVerificationCode crypto.Generator) (objectDetails *domain.ObjectDetails, err error) {
|
||||
if userID == "" {
|
||||
return nil, zerrors.ThrowInvalidArgument(nil, "COMMAND-M00oL", "Errors.User.UserIDMissing")
|
||||
@@ -151,7 +174,7 @@ func (c *Commands) RequestSetPassword(ctx context.Context, userID, resourceOwner
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if existingHuman.UserState == domain.UserStateUnspecified || existingHuman.UserState == domain.UserStateDeleted {
|
||||
if !isUserStateExists(existingHuman.UserState) {
|
||||
return nil, zerrors.ThrowPreconditionFailed(nil, "COMMAND-Hj9ds", "Errors.User.NotFound")
|
||||
}
|
||||
if existingHuman.UserState == domain.UserStateInitial {
|
||||
@@ -173,6 +196,7 @@ func (c *Commands) RequestSetPassword(ctx context.Context, userID, resourceOwner
|
||||
return writeModelToObjectDetails(&existingHuman.WriteModel), nil
|
||||
}
|
||||
|
||||
// PasswordCodeSent notification send with code to change password
|
||||
func (c *Commands) PasswordCodeSent(ctx context.Context, orgID, userID string) (err error) {
|
||||
if userID == "" {
|
||||
return zerrors.ThrowInvalidArgument(nil, "COMMAND-meEfe", "Errors.User.UserIDMissing")
|
||||
@@ -190,6 +214,7 @@ func (c *Commands) PasswordCodeSent(ctx context.Context, orgID, userID string) (
|
||||
return err
|
||||
}
|
||||
|
||||
// PasswordChangeSent notification sent that user changed his password
|
||||
func (c *Commands) PasswordChangeSent(ctx context.Context, orgID, userID string) (err error) {
|
||||
if userID == "" {
|
||||
return zerrors.ThrowInvalidArgument(nil, "COMMAND-pqlm2n", "Errors.User.UserIDMissing")
|
||||
@@ -207,6 +232,7 @@ func (c *Commands) PasswordChangeSent(ctx context.Context, orgID, userID string)
|
||||
return err
|
||||
}
|
||||
|
||||
// HumanCheckPassword check password for user with additional informations from authRequest
|
||||
func (c *Commands) HumanCheckPassword(ctx context.Context, orgID, userID, password string, authRequest *domain.AuthRequest, lockoutPolicy *domain.LockoutPolicy) (err error) {
|
||||
ctx, span := tracing.NewSpan(ctx)
|
||||
defer func() { span.EndWithError(err) }()
|
||||
@@ -230,13 +256,13 @@ func (c *Commands) HumanCheckPassword(ctx context.Context, orgID, userID, passwo
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if wm.UserState == domain.UserStateUnspecified || wm.UserState == domain.UserStateDeleted {
|
||||
|
||||
if !isUserStateExists(wm.UserState) {
|
||||
return zerrors.ThrowPreconditionFailed(nil, "COMMAND-3n77z", "Errors.User.NotFound")
|
||||
}
|
||||
if wm.UserState == domain.UserStateLocked {
|
||||
return zerrors.ThrowPreconditionFailed(nil, "COMMAND-JLK35", "Errors.User.Locked")
|
||||
}
|
||||
|
||||
if wm.EncodedHash == "" {
|
||||
return zerrors.ThrowPreconditionFailed(nil, "COMMAND-3nJ4t", "Errors.User.Password.NotSet")
|
||||
}
|
||||
|
@@ -278,7 +278,6 @@ func TestCommandSide_SetPasswordWithVerifyCode(t *testing.T) {
|
||||
code string
|
||||
resourceOwner string
|
||||
password string
|
||||
agentID string
|
||||
}
|
||||
type res struct {
|
||||
want *domain.ObjectDetails
|
||||
@@ -505,7 +504,7 @@ func TestCommandSide_SetPasswordWithVerifyCode(t *testing.T) {
|
||||
userPasswordHasher: tt.fields.userPasswordHasher,
|
||||
userEncryption: tt.fields.userEncryption,
|
||||
}
|
||||
got, err := r.SetPasswordWithVerifyCode(tt.args.ctx, tt.args.resourceOwner, tt.args.userID, tt.args.code, tt.args.password, tt.args.agentID)
|
||||
got, err := r.SetPasswordWithVerifyCode(tt.args.ctx, tt.args.resourceOwner, tt.args.userID, tt.args.code, tt.args.password)
|
||||
if tt.res.err == nil {
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
@@ -529,7 +528,6 @@ func TestCommandSide_ChangePassword(t *testing.T) {
|
||||
resourceOwner string
|
||||
oldPassword string
|
||||
newPassword string
|
||||
agentID string
|
||||
}
|
||||
type res struct {
|
||||
want *domain.ObjectDetails
|
||||
@@ -675,18 +673,6 @@ func TestCommandSide_ChangePassword(t *testing.T) {
|
||||
false,
|
||||
"")),
|
||||
),
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
org.NewPasswordComplexityPolicyAddedEvent(context.Background(),
|
||||
&user.NewAggregate("user1", "org1").Aggregate,
|
||||
1,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
),
|
||||
),
|
||||
),
|
||||
},
|
||||
res: res{
|
||||
err: zerrors.IsErrorInvalidArgument,
|
||||
@@ -766,7 +752,7 @@ func TestCommandSide_ChangePassword(t *testing.T) {
|
||||
eventstore: eventstoreExpect(t, tt.expect...),
|
||||
userPasswordHasher: tt.fields.userPasswordHasher,
|
||||
}
|
||||
got, err := r.ChangePassword(tt.args.ctx, tt.args.resourceOwner, tt.args.userID, tt.args.oldPassword, tt.args.newPassword, tt.args.agentID)
|
||||
got, err := r.ChangePassword(tt.args.ctx, tt.args.resourceOwner, tt.args.userID, tt.args.oldPassword, tt.args.newPassword)
|
||||
if tt.res.err == nil {
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
@@ -4266,6 +4266,17 @@ func TestCommandSide_HumanSignOut(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func newAddMachineEvent(userLoginMustBeDomain bool, accessTokenType domain.OIDCTokenType) *user.MachineAddedEvent {
|
||||
return user.NewMachineAddedEvent(context.Background(),
|
||||
&user.NewAggregate("user1", "org1").Aggregate,
|
||||
"username",
|
||||
"name",
|
||||
"description",
|
||||
userLoginMustBeDomain,
|
||||
accessTokenType,
|
||||
)
|
||||
}
|
||||
|
||||
func newAddHumanEvent(password string, changeRequired, userLoginMustBeDomain bool, phone string, preferredLanguage language.Tag) *user.HumanAddedEvent {
|
||||
event := user.NewHumanAddedEvent(context.Background(),
|
||||
&user.NewAggregate("user1", "org1").Aggregate,
|
||||
|
@@ -149,7 +149,7 @@ func (c *Commands) addHumanWebAuthN(ctx context.Context, userID, resourceowner,
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
orgPolicy, err := c.getOrgDomainPolicy(ctx, org.AggregateID)
|
||||
orgPolicy, err := c.domainPolicyWriteModel(ctx, org.AggregateID)
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
|
213
internal/command/user_v2.go
Normal file
213
internal/command/user_v2.go
Normal file
@@ -0,0 +1,213 @@
|
||||
package command
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/zitadel/logging"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/api/authz"
|
||||
"github.com/zitadel/zitadel/internal/domain"
|
||||
"github.com/zitadel/zitadel/internal/eventstore"
|
||||
"github.com/zitadel/zitadel/internal/repository/user"
|
||||
"github.com/zitadel/zitadel/internal/telemetry/tracing"
|
||||
"github.com/zitadel/zitadel/internal/zerrors"
|
||||
)
|
||||
|
||||
func (c *Commands) LockUserV2(ctx context.Context, userID string) (*domain.ObjectDetails, error) {
|
||||
if userID == "" {
|
||||
return nil, zerrors.ThrowInvalidArgument(nil, "COMMAND-agz3eczifm", "Errors.User.UserIDMissing")
|
||||
}
|
||||
|
||||
existingHuman, err := c.userStateWriteModel(ctx, userID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !isUserStateExists(existingHuman.UserState) {
|
||||
return nil, zerrors.ThrowNotFound(nil, "COMMAND-450yxuqrh1", "Errors.User.NotFound")
|
||||
}
|
||||
if !hasUserState(existingHuman.UserState, domain.UserStateActive, domain.UserStateInitial) {
|
||||
return nil, zerrors.ThrowPreconditionFailed(nil, "COMMAND-lgws8wtsqf", "Errors.User.ShouldBeActiveOrInitial")
|
||||
}
|
||||
|
||||
if err := c.checkPermissionUpdateUser(ctx, existingHuman.ResourceOwner, existingHuman.AggregateID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := c.pushAppendAndReduce(ctx, existingHuman, user.NewUserLockedEvent(ctx, &existingHuman.Aggregate().Aggregate)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return writeModelToObjectDetails(&existingHuman.WriteModel), nil
|
||||
}
|
||||
|
||||
func (c *Commands) UnlockUserV2(ctx context.Context, userID string) (*domain.ObjectDetails, error) {
|
||||
if userID == "" {
|
||||
return nil, zerrors.ThrowInvalidArgument(nil, "COMMAND-a9ld4xckax", "Errors.User.UserIDMissing")
|
||||
}
|
||||
|
||||
existingHuman, err := c.userStateWriteModel(ctx, userID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !isUserStateExists(existingHuman.UserState) {
|
||||
return nil, zerrors.ThrowNotFound(nil, "COMMAND-x377t913pw", "Errors.User.NotFound")
|
||||
}
|
||||
if !hasUserState(existingHuman.UserState, domain.UserStateLocked) {
|
||||
return nil, zerrors.ThrowPreconditionFailed(nil, "COMMAND-olb9vb0oca", "Errors.User.NotLocked")
|
||||
}
|
||||
if err := c.checkPermissionUpdateUser(ctx, existingHuman.ResourceOwner, existingHuman.AggregateID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := c.pushAppendAndReduce(ctx, existingHuman, user.NewUserUnlockedEvent(ctx, &existingHuman.Aggregate().Aggregate)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return writeModelToObjectDetails(&existingHuman.WriteModel), nil
|
||||
}
|
||||
|
||||
func (c *Commands) DeactivateUserV2(ctx context.Context, userID string) (*domain.ObjectDetails, error) {
|
||||
if userID == "" {
|
||||
return nil, zerrors.ThrowInvalidArgument(nil, "COMMAND-78iiirat8y", "Errors.User.UserIDMissing")
|
||||
}
|
||||
|
||||
existingHuman, err := c.userStateWriteModel(ctx, userID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !isUserStateExists(existingHuman.UserState) {
|
||||
return nil, zerrors.ThrowNotFound(nil, "COMMAND-5gp2p62iin", "Errors.User.NotFound")
|
||||
}
|
||||
if isUserStateInitial(existingHuman.UserState) {
|
||||
return nil, zerrors.ThrowPreconditionFailed(nil, "COMMAND-gvx4kct9r2", "Errors.User.CantDeactivateInitial")
|
||||
}
|
||||
if isUserStateInactive(existingHuman.UserState) {
|
||||
return nil, zerrors.ThrowPreconditionFailed(nil, "COMMAND-5gunjw0cd7", "Errors.User.AlreadyInactive")
|
||||
}
|
||||
if err := c.checkPermissionUpdateUser(ctx, existingHuman.ResourceOwner, existingHuman.AggregateID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := c.pushAppendAndReduce(ctx, existingHuman, user.NewUserDeactivatedEvent(ctx, &existingHuman.Aggregate().Aggregate)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return writeModelToObjectDetails(&existingHuman.WriteModel), nil
|
||||
}
|
||||
|
||||
func (c *Commands) ReactivateUserV2(ctx context.Context, userID string) (*domain.ObjectDetails, error) {
|
||||
if userID == "" {
|
||||
return nil, zerrors.ThrowInvalidArgument(nil, "COMMAND-0nx1ie38fw", "Errors.User.UserIDMissing")
|
||||
}
|
||||
|
||||
existingHuman, err := c.userStateWriteModel(ctx, userID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !isUserStateExists(existingHuman.UserState) {
|
||||
return nil, zerrors.ThrowNotFound(nil, "COMMAND-9hy5kzbuk6", "Errors.User.NotFound")
|
||||
}
|
||||
if !isUserStateInactive(existingHuman.UserState) {
|
||||
return nil, zerrors.ThrowPreconditionFailed(nil, "COMMAND-s5qqcz97hf", "Errors.User.NotInactive")
|
||||
}
|
||||
if err := c.checkPermissionUpdateUser(ctx, existingHuman.ResourceOwner, existingHuman.AggregateID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := c.pushAppendAndReduce(ctx, existingHuman, user.NewUserReactivatedEvent(ctx, &existingHuman.Aggregate().Aggregate)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return writeModelToObjectDetails(&existingHuman.WriteModel), nil
|
||||
}
|
||||
|
||||
func (c *Commands) checkPermissionUpdateUser(ctx context.Context, resourceOwner, userID string) error {
|
||||
if userID != "" && userID == authz.GetCtxData(ctx).UserID {
|
||||
return nil
|
||||
}
|
||||
if err := c.checkPermission(ctx, domain.PermissionUserWrite, resourceOwner, userID); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Commands) checkPermissionDeleteUser(ctx context.Context, resourceOwner, userID string) error {
|
||||
if userID != "" && userID == authz.GetCtxData(ctx).UserID {
|
||||
return nil
|
||||
}
|
||||
if err := c.checkPermission(ctx, domain.PermissionUserDelete, resourceOwner, userID); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Commands) userStateWriteModel(ctx context.Context, userID string) (writeModel *UserV2WriteModel, err error) {
|
||||
ctx, span := tracing.NewSpan(ctx)
|
||||
defer func() { span.EndWithError(err) }()
|
||||
|
||||
writeModel = NewUserStateWriteModel(userID, "")
|
||||
err = c.eventstore.FilterToQueryReducer(ctx, writeModel)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return writeModel, nil
|
||||
}
|
||||
|
||||
func (c *Commands) RemoveUserV2(ctx context.Context, userID string, cascadingUserMemberships []*CascadingMembership, cascadingGrantIDs ...string) (*domain.ObjectDetails, error) {
|
||||
if userID == "" {
|
||||
return nil, zerrors.ThrowInvalidArgument(nil, "COMMAND-vaipl7s13l", "Errors.User.UserIDMissing")
|
||||
}
|
||||
|
||||
existingUser, err := c.userRemoveWriteModel(ctx, userID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !isUserStateExists(existingUser.UserState) {
|
||||
return nil, zerrors.ThrowNotFound(nil, "COMMAND-bd4ir1mblj", "Errors.User.NotFound")
|
||||
}
|
||||
if err := c.checkPermissionDeleteUser(ctx, existingUser.ResourceOwner, existingUser.AggregateID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
domainPolicy, err := c.domainPolicyWriteModel(ctx, existingUser.ResourceOwner)
|
||||
if err != nil {
|
||||
return nil, zerrors.ThrowPreconditionFailed(err, "COMMAND-l40ykb3xh2", "Errors.Org.DomainPolicy.NotExisting")
|
||||
}
|
||||
var events []eventstore.Command
|
||||
events = append(events, user.NewUserRemovedEvent(ctx, &existingUser.Aggregate().Aggregate, existingUser.UserName, existingUser.IDPLinks, domainPolicy.UserLoginMustBeDomain))
|
||||
|
||||
for _, grantID := range cascadingGrantIDs {
|
||||
removeEvent, _, err := c.removeUserGrant(ctx, grantID, "", true)
|
||||
if err != nil {
|
||||
logging.WithFields("usergrantid", grantID).WithError(err).Warn("could not cascade remove role on user grant")
|
||||
continue
|
||||
}
|
||||
events = append(events, removeEvent)
|
||||
}
|
||||
|
||||
if len(cascadingUserMemberships) > 0 {
|
||||
membershipEvents, err := c.removeUserMemberships(ctx, cascadingUserMemberships)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
events = append(events, membershipEvents...)
|
||||
}
|
||||
|
||||
pushedEvents, err := c.eventstore.Push(ctx, events...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = AppendAndReduce(existingUser, pushedEvents...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return writeModelToObjectDetails(&existingUser.WriteModel), nil
|
||||
}
|
||||
|
||||
func (c *Commands) userRemoveWriteModel(ctx context.Context, userID string) (writeModel *UserV2WriteModel, err error) {
|
||||
ctx, span := tracing.NewSpan(ctx)
|
||||
defer func() { span.EndWithError(err) }()
|
||||
|
||||
writeModel = NewUserRemoveWriteModel(userID, "")
|
||||
err = c.eventstore.FilterToQueryReducer(ctx, writeModel)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return writeModel, nil
|
||||
}
|
@@ -67,6 +67,14 @@ func (c *Commands) changeUserEmailWithCode(ctx context.Context, userID, resource
|
||||
// When the plain text code is returned, no notification e-mail will be send to the user.
|
||||
// urlTmpl allows changing the target URL that is used by the e-mail and should be a validated Go template, if used.
|
||||
func (c *Commands) changeUserEmailWithGenerator(ctx context.Context, userID, resourceOwner, email string, gen crypto.Generator, returnCode bool, urlTmpl string) (*domain.Email, error) {
|
||||
cmd, err := c.changeUserEmailWithGeneratorEvents(ctx, userID, resourceOwner, email, gen, returnCode, urlTmpl)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return cmd.Push(ctx)
|
||||
}
|
||||
|
||||
func (c *Commands) changeUserEmailWithGeneratorEvents(ctx context.Context, userID, resourceOwner, email string, gen crypto.Generator, returnCode bool, urlTmpl string) (*UserEmailEvents, error) {
|
||||
cmd, err := c.NewUserEmailEvents(ctx, userID, resourceOwner)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -82,7 +90,7 @@ func (c *Commands) changeUserEmailWithGenerator(ctx context.Context, userID, res
|
||||
if err = cmd.AddGeneratedCode(ctx, gen, urlTmpl, returnCode); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return cmd.Push(ctx)
|
||||
return cmd, nil
|
||||
}
|
||||
|
||||
func (c *Commands) VerifyUserEmail(ctx context.Context, userID, resourceOwner, code string, alg crypto.EncryptionAlgorithm) (*domain.ObjectDetails, error) {
|
||||
@@ -167,18 +175,30 @@ func (c *UserEmailEvents) SetVerified(ctx context.Context) {
|
||||
// AddGeneratedCode generates a new encrypted code and sets it to the email address.
|
||||
// When returnCode a plain text of the code will be returned from Push.
|
||||
func (c *UserEmailEvents) AddGeneratedCode(ctx context.Context, gen crypto.Generator, urlTmpl string, returnCode bool) error {
|
||||
value, plain, err := crypto.NewCode(gen)
|
||||
cmd, code, err := generateCodeCommand(ctx, c.aggregate, gen, urlTmpl, returnCode)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
c.events = append(c.events, user.NewHumanEmailCodeAddedEventV2(ctx, c.aggregate, value, gen.Expiry(), urlTmpl, returnCode))
|
||||
c.events = append(c.events, cmd)
|
||||
if returnCode {
|
||||
c.plainCode = &plain
|
||||
c.plainCode = &code
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func generateCodeCommand(ctx context.Context, agg *eventstore.Aggregate, gen crypto.Generator, urlTmpl string, returnCode bool) (eventstore.Command, string, error) {
|
||||
value, plain, err := crypto.NewCode(gen)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
|
||||
cmd := user.NewHumanEmailCodeAddedEventV2(ctx, agg, value, gen.Expiry(), urlTmpl, returnCode)
|
||||
if returnCode {
|
||||
return cmd, plain, nil
|
||||
}
|
||||
return cmd, "", nil
|
||||
}
|
||||
|
||||
func (c *UserEmailEvents) VerifyCode(ctx context.Context, code string, gen crypto.Generator) error {
|
||||
if code == "" {
|
||||
return zerrors.ThrowInvalidArgument(nil, "COMMAND-Fia4a", "Errors.User.Code.Empty")
|
||||
|
457
internal/command/user_v2_human.go
Normal file
457
internal/command/user_v2_human.go
Normal file
@@ -0,0 +1,457 @@
|
||||
package command
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"golang.org/x/text/language"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/crypto"
|
||||
"github.com/zitadel/zitadel/internal/domain"
|
||||
"github.com/zitadel/zitadel/internal/eventstore"
|
||||
"github.com/zitadel/zitadel/internal/repository/user"
|
||||
"github.com/zitadel/zitadel/internal/telemetry/tracing"
|
||||
"github.com/zitadel/zitadel/internal/zerrors"
|
||||
)
|
||||
|
||||
type ChangeHuman struct {
|
||||
ID string
|
||||
Username *string
|
||||
Profile *Profile
|
||||
Email *Email
|
||||
Phone *Phone
|
||||
|
||||
Password *Password
|
||||
|
||||
// Details are set after a successful execution of the command
|
||||
Details *domain.ObjectDetails
|
||||
|
||||
// EmailCode is set by the command
|
||||
EmailCode *string
|
||||
|
||||
// PhoneCode is set by the command
|
||||
PhoneCode *string
|
||||
}
|
||||
|
||||
type Profile struct {
|
||||
FirstName *string
|
||||
LastName *string
|
||||
NickName *string
|
||||
DisplayName *string
|
||||
PreferredLanguage *language.Tag
|
||||
Gender *domain.Gender
|
||||
}
|
||||
|
||||
type Password struct {
|
||||
// Either you have to have permission, a password code or the old password to change
|
||||
PasswordCode *string
|
||||
OldPassword *string
|
||||
Password *string
|
||||
EncodedPasswordHash *string
|
||||
|
||||
ChangeRequired bool
|
||||
}
|
||||
|
||||
func (h *ChangeHuman) Validate(hasher *crypto.PasswordHasher) (err error) {
|
||||
if h.Email != nil && h.Email.Address != "" {
|
||||
if err := h.Email.Validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if h.Phone != nil && h.Phone.Number != "" {
|
||||
if h.Phone.Number, err = h.Phone.Number.Normalize(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if h.Password != nil {
|
||||
if err := h.Password.Validate(hasher); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *Password) Validate(hasher *crypto.PasswordHasher) error {
|
||||
if p.EncodedPasswordHash != nil {
|
||||
if !hasher.EncodingSupported(*p.EncodedPasswordHash) {
|
||||
return zerrors.ThrowInvalidArgument(nil, "USER-oz74onzvqr", "Errors.User.Password.NotSupported")
|
||||
}
|
||||
}
|
||||
if p.Password == nil && p.EncodedPasswordHash == nil {
|
||||
return zerrors.ThrowInvalidArgument(nil, "COMMAND-3klek4sbns", "Errors.User.Password.Empty")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *ChangeHuman) Changed() bool {
|
||||
if h.Username != nil {
|
||||
return true
|
||||
}
|
||||
if h.Profile != nil {
|
||||
return true
|
||||
}
|
||||
if h.Email != nil {
|
||||
return true
|
||||
}
|
||||
if h.Phone != nil {
|
||||
return true
|
||||
}
|
||||
if h.Password != nil {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (c *Commands) AddUserHuman(ctx context.Context, resourceOwner string, human *AddHuman, allowInitMail bool, alg crypto.EncryptionAlgorithm) (err error) {
|
||||
if resourceOwner == "" {
|
||||
return zerrors.ThrowInvalidArgument(nil, "COMMA-095xh8fll1", "Errors.Internal")
|
||||
}
|
||||
|
||||
if err := human.Validate(c.userPasswordHasher); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if human.ID == "" {
|
||||
human.ID, err = c.idGenerator.Next()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// only check if user is already existing
|
||||
existingHuman, err := c.userExistsWriteModel(
|
||||
ctx,
|
||||
human.ID,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if isUserStateExists(existingHuman.UserState) {
|
||||
return zerrors.ThrowPreconditionFailed(nil, "COMMAND-7yiox1isql", "Errors.User.AlreadyExisting")
|
||||
}
|
||||
// check for permission to create user on resourceOwner
|
||||
if err := c.checkPermission(ctx, domain.PermissionUserWrite, resourceOwner, human.ID); err != nil {
|
||||
return err
|
||||
}
|
||||
// add resourceowner for the events with the aggregate
|
||||
existingHuman.ResourceOwner = resourceOwner
|
||||
|
||||
domainPolicy, err := c.domainPolicyWriteModel(ctx, resourceOwner)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = c.userValidateDomain(ctx, resourceOwner, human.Username, domainPolicy.UserLoginMustBeDomain); err != nil {
|
||||
return err
|
||||
}
|
||||
var createCmd humanCreationCommand
|
||||
if human.Register {
|
||||
createCmd = user.NewHumanRegisteredEvent(
|
||||
ctx,
|
||||
&existingHuman.Aggregate().Aggregate,
|
||||
human.Username,
|
||||
human.FirstName,
|
||||
human.LastName,
|
||||
human.NickName,
|
||||
human.DisplayName,
|
||||
human.PreferredLanguage,
|
||||
human.Gender,
|
||||
human.Email.Address,
|
||||
domainPolicy.UserLoginMustBeDomain,
|
||||
)
|
||||
} else {
|
||||
createCmd = user.NewHumanAddedEvent(
|
||||
ctx,
|
||||
&existingHuman.Aggregate().Aggregate,
|
||||
human.Username,
|
||||
human.FirstName,
|
||||
human.LastName,
|
||||
human.NickName,
|
||||
human.DisplayName,
|
||||
human.PreferredLanguage,
|
||||
human.Gender,
|
||||
human.Email.Address,
|
||||
domainPolicy.UserLoginMustBeDomain,
|
||||
)
|
||||
}
|
||||
|
||||
if human.Phone.Number != "" {
|
||||
createCmd.AddPhoneData(human.Phone.Number)
|
||||
}
|
||||
|
||||
// separated to change when old user logic is not used anymore
|
||||
filter := c.eventstore.Filter //nolint:staticcheck
|
||||
if err := addHumanCommandPassword(ctx, filter, createCmd, human, c.userPasswordHasher); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cmds := make([]eventstore.Command, 0, 3)
|
||||
cmds = append(cmds, createCmd)
|
||||
|
||||
cmds, err = c.addHumanCommandEmail(ctx, filter, cmds, existingHuman.Aggregate(), human, alg, allowInitMail)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cmds, err = c.addHumanCommandPhone(ctx, filter, cmds, existingHuman.Aggregate(), human, alg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, metadataEntry := range human.Metadata {
|
||||
cmds = append(cmds, user.NewMetadataSetEvent(
|
||||
ctx,
|
||||
&existingHuman.Aggregate().Aggregate,
|
||||
metadataEntry.Key,
|
||||
metadataEntry.Value,
|
||||
))
|
||||
}
|
||||
for _, link := range human.Links {
|
||||
cmd, err := addLink(ctx, filter, existingHuman.Aggregate(), link)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cmds = append(cmds, cmd)
|
||||
}
|
||||
|
||||
if len(cmds) == 0 {
|
||||
human.Details = writeModelToObjectDetails(&existingHuman.WriteModel)
|
||||
return nil
|
||||
}
|
||||
|
||||
err = c.pushAppendAndReduce(ctx, existingHuman, cmds...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
human.Details = writeModelToObjectDetails(&existingHuman.WriteModel)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Commands) ChangeUserHuman(ctx context.Context, human *ChangeHuman, alg crypto.EncryptionAlgorithm) (err error) {
|
||||
if err := human.Validate(c.userPasswordHasher); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
existingHuman, err := c.userHumanWriteModel(
|
||||
ctx,
|
||||
human.ID,
|
||||
human.Profile != nil,
|
||||
human.Email != nil,
|
||||
human.Phone != nil,
|
||||
human.Password != nil,
|
||||
false, // avatar not updateable
|
||||
false, // IDPLinks not updateable
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !isUserStateExists(existingHuman.UserState) {
|
||||
return zerrors.ThrowNotFound(nil, "COMMAND-ugjs0upun6", "Errors.User.NotFound")
|
||||
}
|
||||
|
||||
if human.Changed() {
|
||||
if err := c.checkPermissionUpdateUser(ctx, existingHuman.ResourceOwner, existingHuman.AggregateID); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
cmds := make([]eventstore.Command, 0)
|
||||
if human.Username != nil {
|
||||
cmds, err = c.changeUsername(ctx, cmds, existingHuman, *human.Username)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if human.Profile != nil {
|
||||
cmds, err = changeUserProfile(ctx, cmds, existingHuman, human.Profile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if human.Email != nil {
|
||||
cmds, human.EmailCode, err = c.changeUserEmail(ctx, cmds, existingHuman, human.Email, alg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if human.Phone != nil {
|
||||
cmds, human.PhoneCode, err = c.changeUserPhone(ctx, cmds, existingHuman, human.Phone, alg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if human.Password != nil {
|
||||
cmds, err = c.changeUserPassword(ctx, cmds, existingHuman, human.Password, alg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if len(cmds) == 0 {
|
||||
human.Details = writeModelToObjectDetails(&existingHuman.WriteModel)
|
||||
return nil
|
||||
}
|
||||
err = c.pushAppendAndReduce(ctx, existingHuman, cmds...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
human.Details = writeModelToObjectDetails(&existingHuman.WriteModel)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Commands) changeUserEmail(ctx context.Context, cmds []eventstore.Command, wm *UserV2WriteModel, email *Email, alg crypto.EncryptionAlgorithm) (_ []eventstore.Command, code *string, err error) {
|
||||
ctx, span := tracing.NewSpan(ctx)
|
||||
defer func() { span.End() }()
|
||||
|
||||
if email.Address != "" && email.Address != wm.Email {
|
||||
cmds = append(cmds, user.NewHumanEmailChangedEvent(ctx, &wm.Aggregate().Aggregate, email.Address))
|
||||
|
||||
if email.Verified {
|
||||
return append(cmds, user.NewHumanEmailVerifiedEvent(ctx, &wm.Aggregate().Aggregate)), code, nil
|
||||
} else {
|
||||
cryptoCode, err := c.newEmailCode(ctx, c.eventstore.Filter, alg) //nolint:staticcheck
|
||||
if err != nil {
|
||||
return cmds, code, err
|
||||
}
|
||||
cmds = append(cmds, user.NewHumanEmailCodeAddedEventV2(ctx, &wm.Aggregate().Aggregate, cryptoCode.Crypted, cryptoCode.Expiry, email.URLTemplate, email.ReturnCode))
|
||||
if email.ReturnCode {
|
||||
code = &cryptoCode.Plain
|
||||
}
|
||||
return cmds, code, nil
|
||||
}
|
||||
}
|
||||
// only create separate event of verified if email was not changed
|
||||
if email.Verified && wm.IsEmailVerified != email.Verified {
|
||||
return append(cmds, user.NewHumanEmailVerifiedEvent(ctx, &wm.Aggregate().Aggregate)), nil, nil
|
||||
}
|
||||
return cmds, code, nil
|
||||
}
|
||||
|
||||
func (c *Commands) changeUserPhone(ctx context.Context, cmds []eventstore.Command, wm *UserV2WriteModel, phone *Phone, alg crypto.EncryptionAlgorithm) (_ []eventstore.Command, code *string, err error) {
|
||||
ctx, span := tracing.NewSpan(ctx)
|
||||
defer func() { span.End() }()
|
||||
|
||||
if phone.Number != "" && phone.Number != wm.Phone {
|
||||
cmds = append(cmds, user.NewHumanPhoneChangedEvent(ctx, &wm.Aggregate().Aggregate, phone.Number))
|
||||
|
||||
if phone.Verified {
|
||||
return append(cmds, user.NewHumanPhoneVerifiedEvent(ctx, &wm.Aggregate().Aggregate)), code, nil
|
||||
} else {
|
||||
cryptoCode, err := c.newPhoneCode(ctx, c.eventstore.Filter, alg) //nolint:staticcheck
|
||||
if err != nil {
|
||||
return cmds, code, err
|
||||
}
|
||||
cmds = append(cmds, user.NewHumanPhoneCodeAddedEventV2(ctx, &wm.Aggregate().Aggregate, cryptoCode.Crypted, cryptoCode.Expiry, phone.ReturnCode))
|
||||
if phone.ReturnCode {
|
||||
code = &cryptoCode.Plain
|
||||
}
|
||||
return cmds, code, nil
|
||||
}
|
||||
}
|
||||
// only create separate event of verified if email was not changed
|
||||
if phone.Verified && wm.IsPhoneVerified != phone.Verified {
|
||||
return append(cmds, user.NewHumanPhoneVerifiedEvent(ctx, &wm.Aggregate().Aggregate)), code, nil
|
||||
}
|
||||
return cmds, code, nil
|
||||
}
|
||||
|
||||
func changeUserProfile(ctx context.Context, cmds []eventstore.Command, wm *UserV2WriteModel, profile *Profile) ([]eventstore.Command, error) {
|
||||
ctx, span := tracing.NewSpan(ctx)
|
||||
defer func() { span.End() }()
|
||||
|
||||
cmd, err := wm.NewProfileChangedEvent(ctx, profile.FirstName, profile.LastName, profile.NickName, profile.DisplayName, profile.PreferredLanguage, profile.Gender)
|
||||
if cmd != nil {
|
||||
return append(cmds, cmd), err
|
||||
}
|
||||
return cmds, err
|
||||
}
|
||||
|
||||
func (c *Commands) changeUserPassword(ctx context.Context, cmds []eventstore.Command, wm *UserV2WriteModel, password *Password, alg crypto.EncryptionAlgorithm) ([]eventstore.Command, error) {
|
||||
ctx, span := tracing.NewSpan(ctx)
|
||||
defer func() { span.End() }()
|
||||
|
||||
// Either have a code to set the password
|
||||
if password.PasswordCode != nil {
|
||||
if err := crypto.VerifyCodeWithAlgorithm(wm.PasswordCodeCreationDate, wm.PasswordCodeExpiry, wm.PasswordCode, *password.PasswordCode, alg); err != nil {
|
||||
return cmds, err
|
||||
}
|
||||
}
|
||||
var encodedPassword string
|
||||
// or have the old password to change it
|
||||
if password.OldPassword != nil {
|
||||
// newly encode old password if no new and already encoded password is set
|
||||
pw := *password.OldPassword
|
||||
if password.Password != nil {
|
||||
pw = *password.Password
|
||||
}
|
||||
alreadyEncodedPassword, err := c.verifyAndUpdatePassword(ctx, wm.PasswordEncodedHash, *password.OldPassword, pw)
|
||||
if err != nil {
|
||||
return cmds, err
|
||||
}
|
||||
encodedPassword = alreadyEncodedPassword
|
||||
}
|
||||
|
||||
// password already hashed in request
|
||||
if password.EncodedPasswordHash != nil {
|
||||
cmd, err := c.setPasswordCommand(ctx, &wm.Aggregate().Aggregate, wm.UserState, *password.EncodedPasswordHash, password.ChangeRequired, true)
|
||||
if cmd != nil {
|
||||
return append(cmds, cmd), err
|
||||
}
|
||||
return cmds, err
|
||||
}
|
||||
// password already hashed in verify
|
||||
if encodedPassword != "" {
|
||||
cmd, err := c.setPasswordCommand(ctx, &wm.Aggregate().Aggregate, wm.UserState, encodedPassword, password.ChangeRequired, true)
|
||||
if cmd != nil {
|
||||
return append(cmds, cmd), err
|
||||
}
|
||||
return cmds, err
|
||||
}
|
||||
// password still to be hashed
|
||||
if password.Password != nil {
|
||||
cmd, err := c.setPasswordCommand(ctx, &wm.Aggregate().Aggregate, wm.UserState, *password.Password, password.ChangeRequired, false)
|
||||
if cmd != nil {
|
||||
return append(cmds, cmd), err
|
||||
}
|
||||
return cmds, err
|
||||
}
|
||||
// no password changes necessary
|
||||
return cmds, nil
|
||||
}
|
||||
|
||||
func (c *Commands) userExistsWriteModel(ctx context.Context, userID string) (writeModel *UserV2WriteModel, err error) {
|
||||
ctx, span := tracing.NewSpan(ctx)
|
||||
defer func() { span.EndWithError(err) }()
|
||||
|
||||
writeModel = NewUserExistsWriteModel(userID, "")
|
||||
err = c.eventstore.FilterToQueryReducer(ctx, writeModel)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return writeModel, nil
|
||||
}
|
||||
|
||||
func (c *Commands) userHumanWriteModel(ctx context.Context, userID string, profileWM, emailWM, phoneWM, passwordWM, avatarWM, idpLinksWM bool) (writeModel *UserV2WriteModel, err error) {
|
||||
ctx, span := tracing.NewSpan(ctx)
|
||||
defer func() { span.EndWithError(err) }()
|
||||
|
||||
writeModel = NewUserHumanWriteModel(userID, "", profileWM, emailWM, phoneWM, passwordWM, avatarWM, idpLinksWM)
|
||||
err = c.eventstore.FilterToQueryReducer(ctx, writeModel)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return writeModel, nil
|
||||
}
|
||||
|
||||
func (c *Commands) orgDomainVerifiedWriteModel(ctx context.Context, domain string) (writeModel *OrgDomainVerifiedWriteModel, err error) {
|
||||
ctx, span := tracing.NewSpan(ctx)
|
||||
defer func() { span.EndWithError(err) }()
|
||||
|
||||
writeModel = NewOrgDomainVerifiedWriteModel(domain)
|
||||
err = c.eventstore.FilterToQueryReducer(ctx, writeModel)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return writeModel, nil
|
||||
}
|
2568
internal/command/user_v2_human_test.go
Normal file
2568
internal/command/user_v2_human_test.go
Normal file
File diff suppressed because it is too large
Load Diff
558
internal/command/user_v2_model.go
Normal file
558
internal/command/user_v2_model.go
Normal file
@@ -0,0 +1,558 @@
|
||||
package command
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"golang.org/x/text/language"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/crypto"
|
||||
"github.com/zitadel/zitadel/internal/domain"
|
||||
"github.com/zitadel/zitadel/internal/eventstore"
|
||||
"github.com/zitadel/zitadel/internal/repository/user"
|
||||
)
|
||||
|
||||
type UserV2WriteModel struct {
|
||||
eventstore.WriteModel
|
||||
|
||||
UserName string
|
||||
|
||||
MachineWriteModel bool
|
||||
Name string
|
||||
Description string
|
||||
AccessTokenType domain.OIDCTokenType
|
||||
|
||||
MachineSecretWriteModel bool
|
||||
ClientSecret *crypto.CryptoValue
|
||||
|
||||
ProfileWriteModel bool
|
||||
FirstName string
|
||||
LastName string
|
||||
NickName string
|
||||
DisplayName string
|
||||
PreferredLanguage language.Tag
|
||||
Gender domain.Gender
|
||||
|
||||
AvatarWriteModel bool
|
||||
Avatar string
|
||||
|
||||
HumanWriteModel bool
|
||||
InitCode *crypto.CryptoValue
|
||||
InitCodeCreationDate time.Time
|
||||
InitCodeExpiry time.Duration
|
||||
InitCheckFailedCount uint64
|
||||
|
||||
PasswordWriteModel bool
|
||||
PasswordEncodedHash string
|
||||
PasswordChangeRequired bool
|
||||
PasswordCode *crypto.CryptoValue
|
||||
PasswordCodeCreationDate time.Time
|
||||
PasswordCodeExpiry time.Duration
|
||||
PasswordCheckFailedCount uint64
|
||||
|
||||
EmailWriteModel bool
|
||||
Email domain.EmailAddress
|
||||
IsEmailVerified bool
|
||||
EmailCode *crypto.CryptoValue
|
||||
EmailCodeCreationDate time.Time
|
||||
EmailCodeExpiry time.Duration
|
||||
EmailCheckFailedCount uint64
|
||||
|
||||
PhoneWriteModel bool
|
||||
Phone domain.PhoneNumber
|
||||
IsPhoneVerified bool
|
||||
PhoneCode *crypto.CryptoValue
|
||||
PhoneCodeCreationDate time.Time
|
||||
PhoneCodeExpiry time.Duration
|
||||
PhoneCheckFailedCount uint64
|
||||
|
||||
StateWriteModel bool
|
||||
UserState domain.UserState
|
||||
|
||||
IDPLinkWriteModel bool
|
||||
IDPLinks []*domain.UserIDPLink
|
||||
}
|
||||
|
||||
func NewUserExistsWriteModel(userID, resourceOwner string) *UserV2WriteModel {
|
||||
return newUserV2WriteModel(userID, resourceOwner, WithHuman(), WithMachine())
|
||||
}
|
||||
|
||||
func NewUserStateWriteModel(userID, resourceOwner string) *UserV2WriteModel {
|
||||
return newUserV2WriteModel(userID, resourceOwner, WithHuman(), WithMachine(), WithState())
|
||||
}
|
||||
|
||||
func NewUserRemoveWriteModel(userID, resourceOwner string) *UserV2WriteModel {
|
||||
return newUserV2WriteModel(userID, resourceOwner, WithHuman(), WithMachine(), WithState(), WithIDPLinks())
|
||||
}
|
||||
|
||||
func NewUserHumanWriteModel(userID, resourceOwner string, profileWM, emailWM, phoneWM, passwordWM, avatarWM, idpLinks bool) *UserV2WriteModel {
|
||||
opts := []UserV2WMOption{WithHuman(), WithState()}
|
||||
if profileWM {
|
||||
opts = append(opts, WithProfile())
|
||||
}
|
||||
if emailWM {
|
||||
opts = append(opts, WithEmail())
|
||||
}
|
||||
if phoneWM {
|
||||
opts = append(opts, WithPhone())
|
||||
}
|
||||
if passwordWM {
|
||||
opts = append(opts, WithPassword())
|
||||
}
|
||||
if avatarWM {
|
||||
opts = append(opts, WithAvatar())
|
||||
}
|
||||
if idpLinks {
|
||||
opts = append(opts, WithIDPLinks())
|
||||
}
|
||||
return newUserV2WriteModel(userID, resourceOwner, opts...)
|
||||
}
|
||||
|
||||
func newUserV2WriteModel(userID, resourceOwner string, opts ...UserV2WMOption) *UserV2WriteModel {
|
||||
wm := &UserV2WriteModel{
|
||||
WriteModel: eventstore.WriteModel{
|
||||
AggregateID: userID,
|
||||
ResourceOwner: resourceOwner,
|
||||
},
|
||||
}
|
||||
|
||||
for _, optFunc := range opts {
|
||||
optFunc(wm)
|
||||
}
|
||||
return wm
|
||||
}
|
||||
|
||||
type UserV2WMOption func(o *UserV2WriteModel)
|
||||
|
||||
func WithHuman() UserV2WMOption {
|
||||
return func(o *UserV2WriteModel) {
|
||||
o.HumanWriteModel = true
|
||||
}
|
||||
}
|
||||
func WithMachine() UserV2WMOption {
|
||||
return func(o *UserV2WriteModel) {
|
||||
o.MachineWriteModel = true
|
||||
}
|
||||
}
|
||||
func WithProfile() UserV2WMOption {
|
||||
return func(o *UserV2WriteModel) {
|
||||
o.ProfileWriteModel = true
|
||||
}
|
||||
}
|
||||
func WithEmail() UserV2WMOption {
|
||||
return func(o *UserV2WriteModel) {
|
||||
o.EmailWriteModel = true
|
||||
}
|
||||
}
|
||||
func WithPhone() UserV2WMOption {
|
||||
return func(o *UserV2WriteModel) {
|
||||
o.PhoneWriteModel = true
|
||||
}
|
||||
}
|
||||
func WithPassword() UserV2WMOption {
|
||||
return func(o *UserV2WriteModel) {
|
||||
o.PasswordWriteModel = true
|
||||
}
|
||||
}
|
||||
func WithState() UserV2WMOption {
|
||||
return func(o *UserV2WriteModel) {
|
||||
o.StateWriteModel = true
|
||||
}
|
||||
}
|
||||
func WithAvatar() UserV2WMOption {
|
||||
return func(o *UserV2WriteModel) {
|
||||
o.AvatarWriteModel = true
|
||||
}
|
||||
}
|
||||
func WithIDPLinks() UserV2WMOption {
|
||||
return func(o *UserV2WriteModel) {
|
||||
o.IDPLinkWriteModel = true
|
||||
}
|
||||
}
|
||||
|
||||
func (wm *UserV2WriteModel) Reduce() error {
|
||||
for _, event := range wm.Events {
|
||||
switch e := event.(type) {
|
||||
case *user.HumanAddedEvent:
|
||||
wm.reduceHumanAddedEvent(e)
|
||||
case *user.HumanRegisteredEvent:
|
||||
wm.reduceHumanRegisteredEvent(e)
|
||||
|
||||
case *user.HumanInitialCodeAddedEvent:
|
||||
wm.UserState = domain.UserStateInitial
|
||||
wm.SetInitCode(e.Code, e.Expiry, e.CreationDate())
|
||||
case *user.HumanInitializedCheckSucceededEvent:
|
||||
wm.UserState = domain.UserStateActive
|
||||
wm.EmptyInitCode()
|
||||
case *user.HumanInitializedCheckFailedEvent:
|
||||
wm.InitCheckFailedCount += 1
|
||||
|
||||
case *user.UsernameChangedEvent:
|
||||
wm.UserName = e.UserName
|
||||
case *user.HumanProfileChangedEvent:
|
||||
wm.reduceHumanProfileChangedEvent(e)
|
||||
|
||||
case *user.MachineChangedEvent:
|
||||
if e.Name != nil {
|
||||
wm.Name = *e.Name
|
||||
}
|
||||
if e.Description != nil {
|
||||
wm.Description = *e.Description
|
||||
}
|
||||
if e.AccessTokenType != nil {
|
||||
wm.AccessTokenType = *e.AccessTokenType
|
||||
}
|
||||
|
||||
case *user.MachineAddedEvent:
|
||||
wm.UserName = e.UserName
|
||||
wm.Name = e.Name
|
||||
wm.Description = e.Description
|
||||
wm.AccessTokenType = e.AccessTokenType
|
||||
wm.UserState = domain.UserStateActive
|
||||
|
||||
case *user.HumanEmailChangedEvent:
|
||||
wm.Email = e.EmailAddress
|
||||
wm.IsEmailVerified = false
|
||||
wm.EmptyEmailCode()
|
||||
case *user.HumanEmailCodeAddedEvent:
|
||||
wm.IsEmailVerified = false
|
||||
wm.SetEMailCode(e.Code, e.Expiry, e.CreationDate())
|
||||
case *user.HumanEmailVerifiedEvent:
|
||||
wm.IsEmailVerified = true
|
||||
wm.EmptyEmailCode()
|
||||
case *user.HumanEmailVerificationFailedEvent:
|
||||
wm.EmailCheckFailedCount += 1
|
||||
|
||||
case *user.HumanPhoneChangedEvent:
|
||||
wm.IsPhoneVerified = false
|
||||
wm.Phone = e.PhoneNumber
|
||||
wm.EmptyPhoneCode()
|
||||
case *user.HumanPhoneCodeAddedEvent:
|
||||
wm.IsPhoneVerified = false
|
||||
wm.SetPhoneCode(e.Code, e.Expiry, e.CreationDate())
|
||||
case *user.HumanPhoneVerifiedEvent:
|
||||
wm.IsPhoneVerified = true
|
||||
wm.EmptyPhoneCode()
|
||||
case *user.HumanPhoneVerificationFailedEvent:
|
||||
wm.PhoneCheckFailedCount += 1
|
||||
case *user.HumanPhoneRemovedEvent:
|
||||
wm.EmptyPhoneCode()
|
||||
wm.Phone = ""
|
||||
wm.IsPhoneVerified = false
|
||||
|
||||
case *user.HumanAvatarAddedEvent:
|
||||
wm.Avatar = e.StoreKey
|
||||
case *user.HumanAvatarRemovedEvent:
|
||||
wm.Avatar = ""
|
||||
|
||||
case *user.UserLockedEvent:
|
||||
wm.UserState = domain.UserStateLocked
|
||||
case *user.UserUnlockedEvent:
|
||||
wm.PasswordCheckFailedCount = 0
|
||||
wm.UserState = domain.UserStateActive
|
||||
|
||||
case *user.UserDeactivatedEvent:
|
||||
wm.UserState = domain.UserStateInactive
|
||||
case *user.UserReactivatedEvent:
|
||||
wm.UserState = domain.UserStateActive
|
||||
|
||||
case *user.UserRemovedEvent:
|
||||
wm.UserState = domain.UserStateDeleted
|
||||
|
||||
case *user.HumanPasswordHashUpdatedEvent:
|
||||
wm.PasswordEncodedHash = e.EncodedHash
|
||||
case *user.HumanPasswordCheckFailedEvent:
|
||||
wm.PasswordCheckFailedCount += 1
|
||||
case *user.HumanPasswordCheckSucceededEvent:
|
||||
wm.PasswordCheckFailedCount = 0
|
||||
case *user.HumanPasswordChangedEvent:
|
||||
wm.PasswordEncodedHash = user.SecretOrEncodedHash(e.Secret, e.EncodedHash)
|
||||
wm.PasswordChangeRequired = e.ChangeRequired
|
||||
wm.EmptyPasswordCode()
|
||||
case *user.HumanPasswordCodeAddedEvent:
|
||||
wm.SetPasswordCode(e.Code, e.Expiry, e.CreationDate())
|
||||
case *user.UserIDPLinkAddedEvent:
|
||||
wm.AddIDPLink(e.IDPConfigID, e.DisplayName, e.ExternalUserID)
|
||||
case *user.UserIDPLinkRemovedEvent:
|
||||
wm.RemoveIDPLink(e.IDPConfigID, e.ExternalUserID)
|
||||
case *user.UserIDPLinkCascadeRemovedEvent:
|
||||
wm.RemoveIDPLink(e.IDPConfigID, e.ExternalUserID)
|
||||
}
|
||||
}
|
||||
return wm.WriteModel.Reduce()
|
||||
}
|
||||
|
||||
func (wm *UserV2WriteModel) AddIDPLink(configID, displayName, externalUserID string) {
|
||||
wm.IDPLinks = append(wm.IDPLinks, &domain.UserIDPLink{IDPConfigID: configID, DisplayName: displayName, ExternalUserID: externalUserID})
|
||||
}
|
||||
|
||||
func (wm *UserV2WriteModel) RemoveIDPLink(configID, externalUserID string) {
|
||||
idx, _ := wm.IDPLinkByID(configID, externalUserID)
|
||||
if idx < 0 {
|
||||
return
|
||||
}
|
||||
copy(wm.IDPLinks[idx:], wm.IDPLinks[idx+1:])
|
||||
wm.IDPLinks[len(wm.IDPLinks)-1] = nil
|
||||
wm.IDPLinks = wm.IDPLinks[:len(wm.IDPLinks)-1]
|
||||
}
|
||||
|
||||
func (wm *UserV2WriteModel) EmptyInitCode() {
|
||||
wm.InitCode = nil
|
||||
wm.InitCodeExpiry = 0
|
||||
wm.InitCodeCreationDate = time.Time{}
|
||||
wm.InitCheckFailedCount = 0
|
||||
}
|
||||
func (wm *UserV2WriteModel) SetInitCode(code *crypto.CryptoValue, expiry time.Duration, creationDate time.Time) {
|
||||
wm.InitCode = code
|
||||
wm.InitCodeExpiry = expiry
|
||||
wm.InitCodeCreationDate = creationDate
|
||||
wm.InitCheckFailedCount = 0
|
||||
}
|
||||
func (wm *UserV2WriteModel) EmptyEmailCode() {
|
||||
wm.EmailCode = nil
|
||||
wm.EmailCodeExpiry = 0
|
||||
wm.EmailCodeCreationDate = time.Time{}
|
||||
wm.EmailCheckFailedCount = 0
|
||||
}
|
||||
func (wm *UserV2WriteModel) SetEMailCode(code *crypto.CryptoValue, expiry time.Duration, creationDate time.Time) {
|
||||
wm.EmailCode = code
|
||||
wm.EmailCodeExpiry = expiry
|
||||
wm.EmailCodeCreationDate = creationDate
|
||||
wm.EmailCheckFailedCount = 0
|
||||
}
|
||||
func (wm *UserV2WriteModel) EmptyPhoneCode() {
|
||||
wm.PhoneCode = nil
|
||||
wm.PhoneCodeExpiry = 0
|
||||
wm.PhoneCodeCreationDate = time.Time{}
|
||||
wm.PhoneCheckFailedCount = 0
|
||||
}
|
||||
func (wm *UserV2WriteModel) SetPhoneCode(code *crypto.CryptoValue, expiry time.Duration, creationDate time.Time) {
|
||||
wm.PhoneCode = code
|
||||
wm.PhoneCodeExpiry = expiry
|
||||
wm.PhoneCodeCreationDate = creationDate
|
||||
wm.PhoneCheckFailedCount = 0
|
||||
}
|
||||
func (wm *UserV2WriteModel) EmptyPasswordCode() {
|
||||
wm.PasswordCode = nil
|
||||
wm.PasswordCodeExpiry = 0
|
||||
wm.PasswordCodeCreationDate = time.Time{}
|
||||
}
|
||||
func (wm *UserV2WriteModel) SetPasswordCode(code *crypto.CryptoValue, expiry time.Duration, creationDate time.Time) {
|
||||
wm.PasswordCode = code
|
||||
wm.PasswordCodeExpiry = expiry
|
||||
wm.PasswordCodeCreationDate = creationDate
|
||||
}
|
||||
|
||||
func (wm *UserV2WriteModel) Query() *eventstore.SearchQueryBuilder {
|
||||
// remove events are always processed
|
||||
// and username is based for machine and human
|
||||
eventTypes := []eventstore.EventType{
|
||||
user.UserRemovedType,
|
||||
user.UserUserNameChangedType,
|
||||
}
|
||||
|
||||
if wm.HumanWriteModel {
|
||||
eventTypes = append(eventTypes,
|
||||
user.UserV1AddedType,
|
||||
user.HumanAddedType,
|
||||
user.UserV1RegisteredType,
|
||||
user.HumanRegisteredType,
|
||||
)
|
||||
}
|
||||
|
||||
if wm.MachineWriteModel {
|
||||
eventTypes = append(eventTypes,
|
||||
user.MachineChangedEventType,
|
||||
user.MachineAddedEventType,
|
||||
)
|
||||
}
|
||||
|
||||
if wm.EmailWriteModel {
|
||||
eventTypes = append(eventTypes,
|
||||
user.UserV1EmailChangedType,
|
||||
user.HumanEmailChangedType,
|
||||
user.UserV1EmailCodeAddedType,
|
||||
user.HumanEmailCodeAddedType,
|
||||
|
||||
user.UserV1EmailVerifiedType,
|
||||
user.HumanEmailVerifiedType,
|
||||
user.HumanEmailVerificationFailedType,
|
||||
user.UserV1EmailVerificationFailedType,
|
||||
)
|
||||
}
|
||||
if wm.PhoneWriteModel {
|
||||
eventTypes = append(eventTypes,
|
||||
user.UserV1PhoneChangedType,
|
||||
user.HumanPhoneChangedType,
|
||||
user.UserV1PhoneCodeAddedType,
|
||||
user.HumanPhoneCodeAddedType,
|
||||
|
||||
user.UserV1PhoneVerifiedType,
|
||||
user.HumanPhoneVerifiedType,
|
||||
user.HumanPhoneVerificationFailedType,
|
||||
user.UserV1PhoneVerificationFailedType,
|
||||
|
||||
user.UserV1PhoneRemovedType,
|
||||
user.HumanPhoneRemovedType,
|
||||
)
|
||||
}
|
||||
if wm.ProfileWriteModel {
|
||||
eventTypes = append(eventTypes,
|
||||
user.UserV1ProfileChangedType,
|
||||
user.HumanProfileChangedType,
|
||||
)
|
||||
}
|
||||
if wm.StateWriteModel {
|
||||
eventTypes = append(eventTypes,
|
||||
user.UserV1InitialCodeAddedType,
|
||||
user.HumanInitialCodeAddedType,
|
||||
|
||||
user.UserV1InitializedCheckSucceededType,
|
||||
user.HumanInitializedCheckSucceededType,
|
||||
user.HumanInitializedCheckFailedType,
|
||||
user.UserV1InitializedCheckFailedType,
|
||||
|
||||
user.UserLockedType,
|
||||
user.UserUnlockedType,
|
||||
user.UserDeactivatedType,
|
||||
user.UserReactivatedType,
|
||||
)
|
||||
}
|
||||
if wm.AvatarWriteModel {
|
||||
eventTypes = append(eventTypes,
|
||||
user.HumanAvatarAddedType,
|
||||
user.HumanAvatarRemovedType,
|
||||
)
|
||||
}
|
||||
if wm.PasswordWriteModel {
|
||||
eventTypes = append(eventTypes,
|
||||
user.HumanPasswordHashUpdatedType,
|
||||
|
||||
user.HumanPasswordChangedType,
|
||||
user.UserV1PasswordChangedType,
|
||||
user.HumanPasswordCodeAddedType,
|
||||
user.UserV1PasswordCodeAddedType,
|
||||
|
||||
user.HumanPasswordCheckFailedType,
|
||||
user.UserV1PasswordCheckFailedType,
|
||||
user.HumanPasswordCheckSucceededType,
|
||||
user.UserV1PasswordCheckSucceededType,
|
||||
)
|
||||
}
|
||||
if wm.IDPLinkWriteModel {
|
||||
eventTypes = append(eventTypes,
|
||||
user.UserIDPLinkAddedType,
|
||||
user.UserIDPLinkRemovedType,
|
||||
user.UserIDPLinkCascadeRemovedType,
|
||||
)
|
||||
}
|
||||
|
||||
query := eventstore.NewSearchQueryBuilder(eventstore.ColumnsEvent).
|
||||
AddQuery().
|
||||
AggregateTypes(user.AggregateType).
|
||||
AggregateIDs(wm.AggregateID).
|
||||
EventTypes(eventTypes...).
|
||||
Builder()
|
||||
if wm.ResourceOwner != "" {
|
||||
query.ResourceOwner(wm.ResourceOwner)
|
||||
}
|
||||
return query
|
||||
}
|
||||
|
||||
func (wm *UserV2WriteModel) 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.UserState = domain.UserStateActive
|
||||
wm.PasswordEncodedHash = user.SecretOrEncodedHash(e.Secret, e.EncodedHash)
|
||||
wm.PasswordChangeRequired = e.ChangeRequired
|
||||
}
|
||||
|
||||
func (wm *UserV2WriteModel) 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.UserState = domain.UserStateActive
|
||||
wm.PasswordEncodedHash = user.SecretOrEncodedHash(e.Secret, e.EncodedHash)
|
||||
wm.PasswordChangeRequired = e.ChangeRequired
|
||||
}
|
||||
|
||||
func (wm *UserV2WriteModel) 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 *UserV2WriteModel) Aggregate() *user.Aggregate {
|
||||
return user.NewAggregate(wm.AggregateID, wm.ResourceOwner)
|
||||
}
|
||||
|
||||
func (wm *UserV2WriteModel) NewProfileChangedEvent(
|
||||
ctx context.Context,
|
||||
firstName,
|
||||
lastName,
|
||||
nickName,
|
||||
displayName *string,
|
||||
preferredLanguage *language.Tag,
|
||||
gender *domain.Gender,
|
||||
) (*user.HumanProfileChangedEvent, error) {
|
||||
changes := make([]user.ProfileChanges, 0)
|
||||
if firstName != nil && wm.FirstName != *firstName {
|
||||
changes = append(changes, user.ChangeFirstName(*firstName))
|
||||
}
|
||||
if lastName != nil && wm.LastName != *lastName {
|
||||
changes = append(changes, user.ChangeLastName(*lastName))
|
||||
}
|
||||
if nickName != nil && wm.NickName != *nickName {
|
||||
changes = append(changes, user.ChangeNickName(*nickName))
|
||||
}
|
||||
if displayName != nil && wm.DisplayName != *displayName {
|
||||
changes = append(changes, user.ChangeDisplayName(*displayName))
|
||||
}
|
||||
if preferredLanguage != nil && wm.PreferredLanguage != *preferredLanguage {
|
||||
changes = append(changes, user.ChangePreferredLanguage(*preferredLanguage))
|
||||
}
|
||||
if gender != nil && wm.Gender != *gender {
|
||||
changes = append(changes, user.ChangeGender(*gender))
|
||||
}
|
||||
if len(changes) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
return user.NewHumanProfileChangedEvent(ctx, &wm.Aggregate().Aggregate, changes)
|
||||
}
|
||||
|
||||
func (wm *UserV2WriteModel) IDPLinkByID(idpID, externalUserID string) (idx int, idp *domain.UserIDPLink) {
|
||||
for idx, idp = range wm.IDPLinks {
|
||||
if idp.IDPConfigID == idpID && idp.ExternalUserID == externalUserID {
|
||||
return idx, idp
|
||||
}
|
||||
}
|
||||
return -1, nil
|
||||
}
|
2386
internal/command/user_v2_model_test.go
Normal file
2386
internal/command/user_v2_model_test.go
Normal file
File diff suppressed because it is too large
Load Diff
1413
internal/command/user_v2_test.go
Normal file
1413
internal/command/user_v2_test.go
Normal file
File diff suppressed because it is too large
Load Diff
37
internal/command/user_v2_username.go
Normal file
37
internal/command/user_v2_username.go
Normal file
@@ -0,0 +1,37 @@
|
||||
package command
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/eventstore"
|
||||
"github.com/zitadel/zitadel/internal/repository/user"
|
||||
"github.com/zitadel/zitadel/internal/zerrors"
|
||||
)
|
||||
|
||||
func (c *Commands) changeUsername(ctx context.Context, cmds []eventstore.Command, wm *UserV2WriteModel, userName string) ([]eventstore.Command, error) {
|
||||
if wm.UserName == userName {
|
||||
return cmds, nil
|
||||
}
|
||||
orgID := wm.ResourceOwner
|
||||
|
||||
domainPolicy, err := c.domainPolicyWriteModel(ctx, orgID)
|
||||
if err != nil {
|
||||
return cmds, zerrors.ThrowPreconditionFailed(err, "COMMAND-79pv6e1q62", "Errors.Org.DomainPolicy.NotExisting")
|
||||
}
|
||||
if !domainPolicy.UserLoginMustBeDomain {
|
||||
index := strings.LastIndex(userName, "@")
|
||||
if index > 1 {
|
||||
domainCheck := NewOrgDomainVerifiedWriteModel(userName[index+1:])
|
||||
if err := c.eventstore.FilterToQueryReducer(ctx, domainCheck); err != nil {
|
||||
return cmds, err
|
||||
}
|
||||
if domainCheck.Verified && domainCheck.ResourceOwner != orgID {
|
||||
return cmds, zerrors.ThrowInvalidArgument(nil, "COMMAND-Di2ei", "Errors.User.DomainNotAllowedAsUsername")
|
||||
}
|
||||
}
|
||||
}
|
||||
return append(cmds,
|
||||
user.NewUsernameChangedEvent(ctx, &wm.Aggregate().Aggregate, wm.UserName, userName, domainPolicy.UserLoginMustBeDomain),
|
||||
), nil
|
||||
}
|
@@ -29,6 +29,7 @@ type PermissionCheck func(ctx context.Context, permission, orgID, resourceID str
|
||||
const (
|
||||
PermissionUserWrite = "user.write"
|
||||
PermissionUserRead = "user.read"
|
||||
PermissionUserDelete = "user.delete"
|
||||
PermissionSessionWrite = "session.write"
|
||||
PermissionSessionDelete = "session.delete"
|
||||
)
|
||||
|
@@ -19,6 +19,7 @@ import (
|
||||
|
||||
"github.com/zitadel/zitadel/internal/api/authz"
|
||||
"github.com/zitadel/zitadel/internal/command"
|
||||
"github.com/zitadel/zitadel/internal/domain"
|
||||
"github.com/zitadel/zitadel/internal/idp/providers/ldap"
|
||||
openid "github.com/zitadel/zitadel/internal/idp/providers/oidc"
|
||||
"github.com/zitadel/zitadel/internal/idp/providers/saml"
|
||||
@@ -31,6 +32,7 @@ import (
|
||||
organisation "github.com/zitadel/zitadel/pkg/grpc/org/v2beta"
|
||||
session "github.com/zitadel/zitadel/pkg/grpc/session/v2beta"
|
||||
"github.com/zitadel/zitadel/pkg/grpc/system"
|
||||
user_pb "github.com/zitadel/zitadel/pkg/grpc/user"
|
||||
user "github.com/zitadel/zitadel/pkg/grpc/user/v2beta"
|
||||
)
|
||||
|
||||
@@ -134,6 +136,17 @@ func (s *Tester) CreateHumanUser(ctx context.Context) *user.AddHumanUserResponse
|
||||
return resp
|
||||
}
|
||||
|
||||
func (s *Tester) CreateMachineUser(ctx context.Context) *mgmt.AddMachineUserResponse {
|
||||
resp, err := s.Client.Mgmt.AddMachineUser(ctx, &mgmt.AddMachineUserRequest{
|
||||
UserName: fmt.Sprintf("%d@mouse.com", time.Now().UnixNano()),
|
||||
Name: "Mickey",
|
||||
Description: "Mickey Mouse",
|
||||
AccessTokenType: user_pb.AccessTokenType_ACCESS_TOKEN_TYPE_BEARER,
|
||||
})
|
||||
logging.OnError(err).Fatal("create human user")
|
||||
return resp
|
||||
}
|
||||
|
||||
func (s *Tester) CreateUserIDPlink(ctx context.Context, userID, externalID, idpID, username string) *user.AddIDPLinkResponse {
|
||||
resp, err := s.Client.UserV2.AddIDPLink(
|
||||
ctx,
|
||||
@@ -406,3 +419,29 @@ func (s *Tester) CreatePasswordSession(t *testing.T, ctx context.Context, userID
|
||||
return createResp.GetSessionId(), createResp.GetSessionToken(),
|
||||
createResp.GetDetails().GetChangeDate().AsTime(), createResp.GetDetails().GetChangeDate().AsTime()
|
||||
}
|
||||
|
||||
func (s *Tester) CreateProjectUserGrant(t *testing.T, ctx context.Context, projectID, userID string) string {
|
||||
resp, err := s.Client.Mgmt.AddUserGrant(ctx, &mgmt.AddUserGrantRequest{
|
||||
UserId: userID,
|
||||
ProjectId: projectID,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
return resp.GetUserGrantId()
|
||||
}
|
||||
|
||||
func (s *Tester) CreateOrgMembership(t *testing.T, ctx context.Context, userID string) {
|
||||
_, err := s.Client.Mgmt.AddOrgMember(ctx, &mgmt.AddOrgMemberRequest{
|
||||
UserId: userID,
|
||||
Roles: []string{domain.RoleOrgOwner},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
func (s *Tester) CreateProjectMembership(t *testing.T, ctx context.Context, projectID, userID string) {
|
||||
_, err := s.Client.Mgmt.AddProjectMember(ctx, &mgmt.AddProjectMemberRequest{
|
||||
ProjectId: projectID,
|
||||
UserId: userID,
|
||||
Roles: []string{domain.RoleProjectOwner},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
Reference in New Issue
Block a user