mirror of
https://github.com/zitadel/zitadel.git
synced 2025-03-01 14:27:24 +00:00

# Which Problems Are Solved * Adds support for the patch user SCIM v2 endpoint # How the Problems Are Solved * Adds support for the patch user SCIM v2 endpoint under `PATCH /scim/v2/{orgID}/Users/{id}` # Additional Context Part of #8140
112 lines
3.1 KiB
Go
112 lines
3.1 KiB
Go
package resources
|
|
|
|
import (
|
|
"context"
|
|
"strings"
|
|
|
|
"github.com/zitadel/zitadel/internal/api/scim/metadata"
|
|
"github.com/zitadel/zitadel/internal/api/scim/resources/filter"
|
|
"github.com/zitadel/zitadel/internal/api/scim/resources/patch"
|
|
"github.com/zitadel/zitadel/internal/command"
|
|
"github.com/zitadel/zitadel/internal/domain"
|
|
)
|
|
|
|
type userPatcher struct {
|
|
ctx context.Context
|
|
user *ScimUser
|
|
metadataChanges map[metadata.Key]*domain.Metadata
|
|
metadataKeysToRemove map[metadata.Key]bool
|
|
handler *UsersHandler
|
|
}
|
|
|
|
func (h *UsersHandler) applyPatchesToChangeHuman(ctx context.Context, user *ScimUser, operations patch.OperationCollection) (*command.ChangeHuman, error) {
|
|
patcher := &userPatcher{
|
|
ctx: ctx,
|
|
user: user,
|
|
metadataChanges: make(map[metadata.Key]*domain.Metadata),
|
|
metadataKeysToRemove: make(map[metadata.Key]bool),
|
|
handler: h,
|
|
}
|
|
|
|
if err := operations.Apply(patcher, user); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// we rely on the change detection of the write model to only execute commands that really change data
|
|
changeCommand, err := h.mapToChangeHuman(ctx, user)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
patcher.applyMetadataChangesToCommand(changeCommand)
|
|
return changeCommand, nil
|
|
}
|
|
|
|
func (p *userPatcher) FilterEvaluator() *filter.Evaluator {
|
|
return p.handler.filterEvaluator
|
|
}
|
|
|
|
func (p *userPatcher) Added(attributePath []string) error {
|
|
return p.updateMetadata(attributePath)
|
|
}
|
|
|
|
func (p *userPatcher) Replaced(attributePath []string) error {
|
|
return p.updateMetadata(attributePath)
|
|
}
|
|
|
|
func (p *userPatcher) Removed(attributePath []string) error {
|
|
return p.updateMetadata(attributePath)
|
|
}
|
|
|
|
func (p *userPatcher) applyMetadataChangesToCommand(command *command.ChangeHuman) {
|
|
command.MetadataKeysToRemove = make([]string, 0, len(p.metadataKeysToRemove))
|
|
for key := range p.metadataKeysToRemove {
|
|
command.MetadataKeysToRemove = append(command.MetadataKeysToRemove, string(key))
|
|
}
|
|
|
|
command.Metadata = make([]*domain.Metadata, 0, len(p.metadataChanges))
|
|
for _, update := range p.metadataChanges {
|
|
command.Metadata = append(command.Metadata, update)
|
|
}
|
|
}
|
|
|
|
func (p *userPatcher) updateMetadata(attributePath []string) error {
|
|
if len(attributePath) == 0 {
|
|
return nil
|
|
}
|
|
|
|
// try full path first (e.g. name.middleName)
|
|
// try root only if full path did not match (e.g. for entitlements.value only entitlements is mapped)
|
|
var ok bool
|
|
var keys []metadata.Key
|
|
if len(attributePath) > 1 {
|
|
keys, ok = metadata.AttributePathToMetadataKeys[strings.Join(attributePath, ".")]
|
|
}
|
|
|
|
if !ok {
|
|
keys, ok = metadata.AttributePathToMetadataKeys[attributePath[0]]
|
|
if !ok {
|
|
return nil
|
|
}
|
|
}
|
|
|
|
for _, key := range keys {
|
|
value, err := getValueForMetadataKey(p.user, key)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if len(value) > 0 {
|
|
delete(p.metadataKeysToRemove, key)
|
|
p.metadataChanges[key] = &domain.Metadata{
|
|
Key: string(metadata.ScopeKey(p.ctx, key)),
|
|
Value: value,
|
|
}
|
|
} else {
|
|
p.metadataKeysToRemove[key] = true
|
|
delete(p.metadataChanges, key)
|
|
}
|
|
}
|
|
return nil
|
|
}
|