Files
zitadel/internal/command/user_v2_machine.go
Livio Spring fa83c39510 fix: correct user self management on metadata and delete (#10666)
# Which Problems Are Solved

This PR fixes the self-management of users for metadata and own removal
and improves the corresponding permission checks.
While looking into the problems, I also noticed that there's a bug in
the metadata mapping when using `api.metadata.push` in actions v1 and
that re-adding a previously existing key after its removal was not
possible.

# How the Problems Are Solved

- Added a parameter `allowSelfManagement` to checkPermissionOnUser to
not require a permission if a user is changing its own data.
- Updated use of `NewPermissionCheckUserWrite` including prevention of
self-management for metadata.
- Pass permission check to the command side (for metadata functions) to
allow it implicitly for login v1 and actions v1.
- Use of json.Marshal for the metadata mapping (as with
`AppendMetadata`)
- Check the metadata state when comparing the value.

# Additional Changes

- added a variadic `roles` parameter to the `CreateOrgMembership`
integration test helper function to allow defining specific roles.

# Additional Context

- noted internally while testing v4.1.x
- requires backport to v4.x
- closes https://github.com/zitadel/zitadel/issues/10470
- relates to https://github.com/zitadel/zitadel/pull/10426

(cherry picked from commit 5329d50509)
2025-09-30 07:09:03 +02:00

95 lines
2.6 KiB
Go

package command
import (
"context"
"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 ChangeMachine struct {
ID string
ResourceOwner string
Username *string
Name *string
Description *string
// Details are set after a successful execution of the command
Details *domain.ObjectDetails
}
func (h *ChangeMachine) Changed() bool {
if h.Username != nil {
return true
}
if h.Name != nil {
return true
}
if h.Description != nil {
return true
}
return false
}
func (c *Commands) ChangeUserMachine(ctx context.Context, machine *ChangeMachine) (err error) {
existingMachine, err := c.UserMachineWriteModel(
ctx,
machine.ID,
machine.ResourceOwner,
false,
)
if err != nil {
return err
}
if machine.Changed() {
if err := c.checkPermissionUpdateUser(ctx, existingMachine.ResourceOwner, existingMachine.AggregateID, true); err != nil {
return err
}
}
cmds := make([]eventstore.Command, 0)
if machine.Username != nil {
cmds, err = c.changeUsername(ctx, cmds, existingMachine, *machine.Username)
if err != nil {
return err
}
}
var machineChanges []user.MachineChanges
if machine.Name != nil && *machine.Name != existingMachine.Name {
machineChanges = append(machineChanges, user.ChangeName(*machine.Name))
}
if machine.Description != nil && *machine.Description != existingMachine.Description {
machineChanges = append(machineChanges, user.ChangeDescription(*machine.Description))
}
if len(machineChanges) > 0 {
cmds = append(cmds, user.NewMachineChangedEvent(ctx, &existingMachine.Aggregate().Aggregate, machineChanges))
}
if len(cmds) == 0 {
machine.Details = writeModelToObjectDetails(&existingMachine.WriteModel)
return nil
}
err = c.pushAppendAndReduce(ctx, existingMachine, cmds...)
if err != nil {
return err
}
machine.Details = writeModelToObjectDetails(&existingMachine.WriteModel)
return nil
}
func (c *Commands) UserMachineWriteModel(ctx context.Context, userID, resourceOwner string, metadataWM bool) (writeModel *UserV2WriteModel, err error) {
ctx, span := tracing.NewSpan(ctx)
defer func() { span.EndWithError(err) }()
writeModel = NewUserMachineWriteModel(userID, resourceOwner, metadataWM)
err = c.eventstore.FilterToQueryReducer(ctx, writeModel)
if err != nil {
return nil, err
}
if !isUserStateExists(writeModel.UserState) {
return nil, zerrors.ThrowNotFound(nil, "COMMAND-ugjs0upun6", "Errors.User.NotFound")
}
return writeModel, nil
}