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