mirror of
https://github.com/zitadel/zitadel.git
synced 2025-08-12 08:47:32 +00:00
feat: patch user scim v2 endpoint (#9219)
# 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
This commit is contained in:
105
internal/api/scim/resources/patch/patch_replace.go
Normal file
105
internal/api/scim/resources/patch/patch_replace.go
Normal file
@@ -0,0 +1,105 @@
|
||||
package patch
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"reflect"
|
||||
|
||||
"github.com/zitadel/logging"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/api/scim/resources/filter"
|
||||
"github.com/zitadel/zitadel/internal/api/scim/serrors"
|
||||
"github.com/zitadel/zitadel/internal/zerrors"
|
||||
)
|
||||
|
||||
func applyReplacePatch(patcher ResourcePatcher, op *Operation, value interface{}) error {
|
||||
if op.Path == nil {
|
||||
return flattenAndApplyPatchOperations(patcher, op, value)
|
||||
}
|
||||
|
||||
result, err := patcher.FilterEvaluator().Evaluate(reflect.ValueOf(value), op.Path)
|
||||
if err != nil {
|
||||
return serrors.ThrowInvalidPath(zerrors.ThrowInvalidArgument(err, "SCIM-i2o3", "Failed to evaluate path"))
|
||||
}
|
||||
|
||||
switch filterResult := result.(type) {
|
||||
case *filter.SimpleValueEvaluationResult:
|
||||
return applyReplacePatchSimple(patcher, filterResult, op.Value, op.valueIsArray)
|
||||
case *filter.FilteredValuesEvaluationResult:
|
||||
return applyReplacePatchFiltered(patcher, filterResult, op.Value)
|
||||
}
|
||||
|
||||
logging.Errorf("SCIM replace patch: unsupported filter type %T", result)
|
||||
return serrors.ThrowInvalidPath(zerrors.ThrowInvalidArgument(err, "SCIM-optu9", "Invalid patch path"))
|
||||
}
|
||||
|
||||
func applyReplacePatchSimple(patcher ResourcePatcher, evaluationResult *filter.SimpleValueEvaluationResult, newValueRaw json.RawMessage, valueIsArray bool) error {
|
||||
// patch value is an array
|
||||
// or it is a scalar or an object but the target is a slice
|
||||
// unmarshal it as a slice and set it on the target, clearing all existing entries
|
||||
if valueIsArray || evaluationResult.Value.Kind() == reflect.Slice {
|
||||
return applyReplacePatchSimpleSlice(patcher, evaluationResult, newValueRaw, valueIsArray)
|
||||
}
|
||||
|
||||
// patch value and target is a scalar or object
|
||||
if err := unmarshalPatchValue(newValueRaw, evaluationResult.Value); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return patcher.Replaced(evaluationResult.PathSegments)
|
||||
}
|
||||
|
||||
func applyReplacePatchSimpleSlice(patcher ResourcePatcher, evaluationResult *filter.SimpleValueEvaluationResult, newValueRaw json.RawMessage, valueIsArray bool) error {
|
||||
if evaluationResult.Value.Kind() != reflect.Slice {
|
||||
return zerrors.ThrowInvalidArgument(nil, "SCIM-E345X", "Cannot apply array patch value to single value attribute")
|
||||
}
|
||||
|
||||
values, err := unmarshalPatchValuesSlice(evaluationResult.Value.Type().Elem(), newValueRaw, valueIsArray)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
evaluationResult.Value.Set(values)
|
||||
modifiedIndexes := make(map[int]bool, values.Len())
|
||||
for i := 0; i < values.Len(); i++ {
|
||||
modifiedIndexes[i] = true
|
||||
}
|
||||
|
||||
if err = ensureSinglePrimaryAdded(values, values, modifiedIndexes); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return patcher.Replaced(evaluationResult.PathSegments)
|
||||
}
|
||||
|
||||
func applyReplacePatchFiltered(patcher ResourcePatcher, result *filter.FilteredValuesEvaluationResult, newValueRaw json.RawMessage) error {
|
||||
if len(result.Matches) == 0 {
|
||||
return serrors.ThrowNoTarget(zerrors.ThrowInvalidArgument(nil, "SCIM-4513", "Path evaluation resulted in no matches"))
|
||||
}
|
||||
|
||||
for _, match := range result.Matches {
|
||||
if err := unmarshalPatchValue(newValueRaw, match.Value); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if err := ensureSinglePrimaryBasedOnMatches(result.Source, result.Matches); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return patcher.Replaced(result.PathSegments)
|
||||
}
|
||||
|
||||
func ensureSinglePrimaryBasedOnMatches(slice reflect.Value, matches []*filter.FilteredValuesEvaluationResultMatch) error {
|
||||
if len(matches) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
modifiedElements := make([]reflect.Value, 0, len(matches))
|
||||
modifiedIndexes := make(map[int]bool, len(matches))
|
||||
for _, match := range matches {
|
||||
modifiedElements = append(modifiedElements, match.Element)
|
||||
modifiedIndexes[match.SourceIndex] = true
|
||||
}
|
||||
|
||||
return ensureSinglePrimary(slice, modifiedElements, modifiedIndexes)
|
||||
}
|
Reference in New Issue
Block a user