fix: set userAgentID in password change event if available (#7319)

This commit is contained in:
Livio Spring 2024-01-30 15:36:34 +01:00 committed by GitHub
parent c7d7464b3b
commit c20204d84d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 257 additions and 26 deletions

View File

@ -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
}

View File

@ -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:

View File

@ -3,6 +3,7 @@ package login
import (
"net/http"
http_mw "github.com/zitadel/zitadel/internal/api/http/middleware"
"github.com/zitadel/zitadel/internal/domain"
)
@ -24,7 +25,8 @@ func (l *Login) handleChangePassword(w http.ResponseWriter, r *http.Request) {
l.renderError(w, r, authReq, err)
return
}
_, err = l.command.ChangePassword(setContext(r.Context(), authReq.UserOrgID), authReq.UserOrgID, authReq.UserID, data.OldPassword, data.NewPassword)
userAgentID, _ := http_mw.UserAgentIDFromCtx(r.Context())
_, err = l.command.ChangePassword(setContext(r.Context(), authReq.UserOrgID), authReq.UserOrgID, authReq.UserID, data.OldPassword, data.NewPassword, userAgentID)
if err != nil {
l.renderChangePassword(w, r, authReq, err)
return

View File

@ -4,6 +4,7 @@ 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"
)
@ -71,7 +72,8 @@ func (l *Login) checkPWCode(w http.ResponseWriter, r *http.Request, authReq *dom
if authReq != nil {
userOrg = authReq.UserOrgID
}
_, err := l.command.SetPasswordWithVerifyCode(setContext(r.Context(), userOrg), userOrg, data.UserID, data.Code, data.Password)
userAgentID, _ := http_mw.UserAgentIDFromCtx(r.Context())
_, err := l.command.SetPasswordWithVerifyCode(setContext(r.Context(), userOrg), userOrg, data.UserID, data.Code, data.Password, userAgentID)
if err != nil {
l.renderInitPassword(w, r, authReq, data.UserID, "", err)
return

View File

@ -5,6 +5,7 @@ import (
"net/http"
"strconv"
http_mw "github.com/zitadel/zitadel/internal/api/http/middleware"
"github.com/zitadel/zitadel/internal/domain"
"github.com/zitadel/zitadel/internal/zerrors"
)
@ -85,7 +86,8 @@ func (l *Login) checkUserInitCode(w http.ResponseWriter, r *http.Request, authRe
l.renderInitUser(w, r, authReq, data.UserID, data.LoginName, "", data.PasswordSet, err)
return
}
err = l.command.HumanVerifyInitCode(setContext(r.Context(), userOrgID), data.UserID, userOrgID, data.Code, data.Password, initCodeGenerator)
userAgentID, _ := http_mw.UserAgentIDFromCtx(r.Context())
err = l.command.HumanVerifyInitCode(setContext(r.Context(), userOrgID), data.UserID, userOrgID, data.Code, data.Password, userAgentID, initCodeGenerator)
if err != nil {
l.renderInitUser(w, r, authReq, data.UserID, data.LoginName, "", data.PasswordSet, err)
return

View File

@ -50,7 +50,7 @@ func (c *Commands) ResendInitialMail(ctx context.Context, userID string, email d
return writeModelToObjectDetails(&existingCode.WriteModel), nil
}
func (c *Commands) HumanVerifyInitCode(ctx context.Context, userID, resourceOwner, code, password string, initCodeGenerator crypto.Generator) error {
func (c *Commands) HumanVerifyInitCode(ctx context.Context, userID, resourceOwner, code, password, userAgentID string, initCodeGenerator crypto.Generator) error {
if userID == "" {
return zerrors.ThrowInvalidArgument(nil, "COMMAND-mkM9f", "Errors.User.UserIDMissing")
}
@ -80,7 +80,7 @@ func (c *Commands) HumanVerifyInitCode(ctx context.Context, userID, resourceOwne
commands = append(commands, user.NewHumanEmailVerifiedEvent(ctx, userAgg))
}
if password != "" {
passwordCommand, err := c.setPasswordCommand(ctx, userAgg, domain.UserStateActive, password, false, false)
passwordCommand, err := c.setPasswordCommand(ctx, userAgg, domain.UserStateActive, password, userAgentID, false, false)
if err != nil {
return err
}

View File

@ -300,6 +300,7 @@ func TestCommandSide_VerifyInitCode(t *testing.T) {
code string
resourceOwner string
password string
userAgentID string
secretGenerator crypto.Generator
}
type res struct {
@ -578,6 +579,83 @@ func TestCommandSide_VerifyInitCode(t *testing.T) {
},
},
},
{
name: "valid code with password and userAgentID, ok",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(
eventFromEventPusher(
user.NewHumanAddedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
"username",
"firstname",
"lastname",
"nickname",
"displayname",
language.German,
domain.GenderUnspecified,
"email@test.ch",
true,
),
),
eventFromEventPusher(
user.NewHumanEmailVerifiedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate)),
eventFromEventPusherWithCreationDateNow(
user.NewHumanInitialCodeAddedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
&crypto.CryptoValue{
CryptoType: crypto.TypeEncryption,
Algorithm: "enc",
KeyID: "id",
Crypted: []byte("a"),
},
time.Hour*1,
),
),
),
expectFilter(
eventFromEventPusher(
org.NewPasswordComplexityPolicyAddedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
1,
false,
false,
false,
false,
),
),
),
expectPush(
user.NewHumanInitializedCheckSucceededEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
),
user.NewHumanPasswordChangedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
"$plain$x$password",
false,
"userAgent1",
),
),
),
userPasswordHasher: mockPasswordHasher("x"),
},
args: args{
ctx: context.Background(),
userID: "user1",
code: "a",
resourceOwner: "org1",
password: "password",
userAgentID: "userAgent1",
secretGenerator: GetMockSecretGenerator(t),
},
res: res{
want: &domain.ObjectDetails{
ResourceOwner: "org1",
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
@ -585,7 +663,7 @@ func TestCommandSide_VerifyInitCode(t *testing.T) {
eventstore: tt.fields.eventstore,
userPasswordHasher: tt.fields.userPasswordHasher,
}
err := r.HumanVerifyInitCode(tt.args.ctx, tt.args.userID, tt.args.resourceOwner, tt.args.code, tt.args.password, tt.args.secretGenerator)
err := r.HumanVerifyInitCode(tt.args.ctx, tt.args.userID, tt.args.resourceOwner, tt.args.code, tt.args.password, tt.args.userAgentID, tt.args.secretGenerator)
if tt.res.err == nil {
assert.NoError(t, err)
}

View File

@ -31,10 +31,10 @@ func (c *Commands) SetPassword(ctx context.Context, orgID, userID, password stri
if err = c.checkPermission(ctx, domain.PermissionUserWrite, wm.ResourceOwner, userID); err != nil {
return nil, err
}
return c.setPassword(ctx, wm, password, oneTime)
return c.setPassword(ctx, wm, password, "", oneTime)
}
func (c *Commands) SetPasswordWithVerifyCode(ctx context.Context, orgID, userID, code, password string) (objectDetails *domain.ObjectDetails, err error) {
func (c *Commands) SetPasswordWithVerifyCode(ctx context.Context, orgID, userID, code, password, userAgentID string) (objectDetails *domain.ObjectDetails, err error) {
ctx, span := tracing.NewSpan(ctx)
defer func() { span.EndWithError(err) }()
@ -58,13 +58,13 @@ func (c *Commands) SetPasswordWithVerifyCode(ctx context.Context, orgID, userID,
return nil, err
}
return c.setPassword(ctx, wm, password, false)
return c.setPassword(ctx, wm, password, userAgentID, false)
}
// 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) {
func (c *Commands) setEncodedPassword(ctx context.Context, wm *HumanPasswordWriteModel, password, userAgentID 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)
command, err := c.setPasswordCommand(ctx, &agg.Aggregate, wm.UserState, password, userAgentID, changeRequired, true)
if err != nil {
return nil, err
}
@ -76,9 +76,9 @@ func (c *Commands) setEncodedPassword(ctx context.Context, wm *HumanPasswordWrit
}
// 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) {
func (c *Commands) setPassword(ctx context.Context, wm *HumanPasswordWriteModel, password, userAgentID 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)
command, err := c.setPasswordCommand(ctx, &agg.Aggregate, wm.UserState, password, userAgentID, changeRequired, false)
if err != nil {
return nil, err
}
@ -89,7 +89,7 @@ func (c *Commands) setPassword(ctx context.Context, wm *HumanPasswordWriteModel,
return writeModelToObjectDetails(&wm.WriteModel), nil
}
func (c *Commands) setPasswordCommand(ctx context.Context, agg *eventstore.Aggregate, userState domain.UserState, password string, changeRequired, encoded bool) (_ eventstore.Command, err error) {
func (c *Commands) setPasswordCommand(ctx context.Context, agg *eventstore.Aggregate, userState domain.UserState, password, userAgentID string, changeRequired, encoded bool) (_ eventstore.Command, err error) {
if err = c.canUpdatePassword(ctx, password, agg.ResourceOwner, userState); err != nil {
return nil, err
}
@ -101,13 +101,13 @@ func (c *Commands) setPasswordCommand(ctx context.Context, agg *eventstore.Aggre
if err = convertPasswapErr(err); err != nil {
return nil, err
}
return user.NewHumanPasswordChangedEvent(ctx, agg, encodedPassword, changeRequired, ""), nil
return user.NewHumanPasswordChangedEvent(ctx, agg, encodedPassword, changeRequired, userAgentID), nil
}
return user.NewHumanPasswordChangedEvent(ctx, agg, password, changeRequired, ""), nil
return user.NewHumanPasswordChangedEvent(ctx, agg, password, changeRequired, userAgentID), nil
}
// ChangePassword change password of existing user
func (c *Commands) ChangePassword(ctx context.Context, orgID, userID, oldPassword, newPassword string) (objectDetails *domain.ObjectDetails, err error) {
func (c *Commands) ChangePassword(ctx context.Context, orgID, userID, oldPassword, newPassword, userAgentID string) (objectDetails *domain.ObjectDetails, err error) {
ctx, span := tracing.NewSpan(ctx)
defer func() { span.EndWithError(err) }()
@ -126,7 +126,7 @@ func (c *Commands) ChangePassword(ctx context.Context, orgID, userID, oldPasswor
if err != nil {
return nil, err
}
return c.setEncodedPassword(ctx, wm, newPasswordHash, false)
return c.setEncodedPassword(ctx, wm, newPasswordHash, userAgentID, false)
}
// verifyAndUpdatePassword verify if the old password is correct with the encoded hash and

View File

@ -278,6 +278,7 @@ func TestCommandSide_SetPasswordWithVerifyCode(t *testing.T) {
code string
resourceOwner string
password string
userAgentID string
}
type res struct {
want *domain.ObjectDetails
@ -496,6 +497,83 @@ func TestCommandSide_SetPasswordWithVerifyCode(t *testing.T) {
},
},
},
{
name: "set password with userAgentID, ok",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(
eventFromEventPusher(
user.NewHumanAddedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
"username",
"firstname",
"lastname",
"nickname",
"displayname",
language.German,
domain.GenderUnspecified,
"email@test.ch",
true,
),
),
eventFromEventPusher(
user.NewHumanEmailVerifiedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
),
),
eventFromEventPusherWithCreationDateNow(
user.NewHumanPasswordCodeAddedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
&crypto.CryptoValue{
CryptoType: crypto.TypeEncryption,
Algorithm: "enc",
KeyID: "id",
Crypted: []byte("a"),
},
time.Hour*1,
domain.NotificationTypeEmail,
),
),
),
expectFilter(
eventFromEventPusher(
org.NewPasswordComplexityPolicyAddedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
1,
false,
false,
false,
false,
),
),
),
expectPush(
user.NewHumanPasswordChangedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
"$plain$x$password",
false,
"userAgent1",
),
),
),
userPasswordHasher: mockPasswordHasher("x"),
userEncryption: crypto.CreateMockEncryptionAlg(gomock.NewController(t)),
},
args: args{
ctx: context.Background(),
userID: "user1",
resourceOwner: "org1",
password: "password",
code: "a",
userAgentID: "userAgent1",
},
res: res{
want: &domain.ObjectDetails{
ResourceOwner: "org1",
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
@ -504,7 +582,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)
got, err := r.SetPasswordWithVerifyCode(tt.args.ctx, tt.args.resourceOwner, tt.args.userID, tt.args.code, tt.args.password, tt.args.userAgentID)
if tt.res.err == nil {
assert.NoError(t, err)
}
@ -528,6 +606,7 @@ func TestCommandSide_ChangePassword(t *testing.T) {
resourceOwner string
oldPassword string
newPassword string
userAgentID string
}
type res struct {
want *domain.ObjectDetails
@ -745,6 +824,74 @@ func TestCommandSide_ChangePassword(t *testing.T) {
},
},
},
{
name: "change password with userAgentID, ok",
fields: fields{
userPasswordHasher: mockPasswordHasher("x"),
},
args: args{
ctx: context.Background(),
userID: "user1",
resourceOwner: "org1",
oldPassword: "password",
newPassword: "password1",
userAgentID: "userAgent1",
},
expect: []expect{
expectFilter(
eventFromEventPusher(
user.NewHumanAddedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
"username",
"firstname",
"lastname",
"nickname",
"displayname",
language.German,
domain.GenderUnspecified,
"email@test.ch",
true,
),
),
eventFromEventPusher(
user.NewHumanEmailVerifiedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
),
),
eventFromEventPusher(
user.NewHumanPasswordChangedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
"$plain$x$password",
false,
"")),
),
expectFilter(
eventFromEventPusher(
org.NewPasswordComplexityPolicyAddedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
1,
false,
false,
false,
false,
),
),
),
expectPush(
user.NewHumanPasswordChangedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
"$plain$x$password1",
false,
"userAgent1",
),
),
},
res: res{
want: &domain.ObjectDetails{
ResourceOwner: "org1",
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
@ -752,7 +899,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)
got, err := r.ChangePassword(tt.args.ctx, tt.args.resourceOwner, tt.args.userID, tt.args.oldPassword, tt.args.newPassword, tt.args.userAgentID)
if tt.res.err == nil {
assert.NoError(t, err)
}

View File

@ -394,7 +394,7 @@ func (c *Commands) changeUserPassword(ctx context.Context, cmds []eventstore.Com
// password already hashed in request
if password.EncodedPasswordHash != nil {
cmd, err := c.setPasswordCommand(ctx, &wm.Aggregate().Aggregate, wm.UserState, *password.EncodedPasswordHash, password.ChangeRequired, true)
cmd, err := c.setPasswordCommand(ctx, &wm.Aggregate().Aggregate, wm.UserState, *password.EncodedPasswordHash, "", password.ChangeRequired, true)
if cmd != nil {
return append(cmds, cmd), err
}
@ -402,7 +402,7 @@ func (c *Commands) changeUserPassword(ctx context.Context, cmds []eventstore.Com
}
// password already hashed in verify
if encodedPassword != "" {
cmd, err := c.setPasswordCommand(ctx, &wm.Aggregate().Aggregate, wm.UserState, encodedPassword, password.ChangeRequired, true)
cmd, err := c.setPasswordCommand(ctx, &wm.Aggregate().Aggregate, wm.UserState, encodedPassword, "", password.ChangeRequired, true)
if cmd != nil {
return append(cmds, cmd), err
}
@ -410,7 +410,7 @@ func (c *Commands) changeUserPassword(ctx context.Context, cmds []eventstore.Com
}
// password still to be hashed
if password.Password != nil {
cmd, err := c.setPasswordCommand(ctx, &wm.Aggregate().Aggregate, wm.UserState, *password.Password, password.ChangeRequired, false)
cmd, err := c.setPasswordCommand(ctx, &wm.Aggregate().Aggregate, wm.UserState, *password.Password, "", password.ChangeRequired, false)
if cmd != nil {
return append(cmds, cmd), err
}