Skip to content

Commit

Permalink
feat: add glob deny paths to ApplyJSONPatch (#832)
Browse files Browse the repository at this point in the history
Co-authored-by: Jonas Hungershausen <[email protected]>
  • Loading branch information
hperl and jonas-jonas authored Jan 7, 2025
1 parent 54e81b4 commit 07fa661
Show file tree
Hide file tree
Showing 2 changed files with 17 additions and 4 deletions.
14 changes: 10 additions & 4 deletions jsonx/patch.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"strings"

jsonpatch "github.com/evanphx/json-patch/v5"
"github.com/gobwas/glob"

"github.com/ory/x/pointerx"
)
Expand Down Expand Up @@ -42,15 +43,20 @@ func isElementAccess(path string) bool {
return false
}

// ApplyJSONPatch applies a JSON patch to an object. It returns an error if the
// patch is invalid or if the patch includes paths that are denied. denyPaths is
// a list of path globs (interpreted with [glob.Compile] that are not allowed to
// be patched.
func ApplyJSONPatch(p json.RawMessage, object interface{}, denyPaths ...string) error {
patch, err := jsonpatch.DecodePatch(p)
if err != nil {
return err
}

denySet := make(map[string]struct{})
for _, path := range denyPaths {
denySet[path] = struct{}{}
denyPattern := fmt.Sprintf("{%s}", strings.Join(denyPaths, ","))
matcher, err := glob.Compile(denyPattern, '/')
if err != nil {
return err
}

for _, op := range patch {
Expand All @@ -62,7 +68,7 @@ func ApplyJSONPatch(p json.RawMessage, object interface{}, denyPaths ...string)
if err != nil {
return fmt.Errorf("error parsing patch operations: %v", err)
}
if _, ok := denySet[path]; ok {
if matcher.Match(path) {
return fmt.Errorf("patch includes denied path: %s", path)
}

Expand Down
7 changes: 7 additions & 0 deletions jsonx/patch_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,13 @@ func TestApplyJSONPatch(t *testing.T) {
require.Error(t, ApplyJSONPatch(rawPatch, &obj, "/Field1"))
require.Equal(t, object, obj)
})
t.Run("case=patch denied sub-path", func(t *testing.T) {
rawPatch := []byte(`[{"op": "replace", "path": "/Field3/Field1", "value": true}]`)
obj := deepcopy.Copy(object).(TestType)
err := ApplyJSONPatch(rawPatch, &obj, "/Field3/**", "/Field1/*/Unknown")
require.Error(t, err)
require.Equal(t, object, obj)
})
t.Run("case=patch allowed path", func(t *testing.T) {
rawPatch := []byte(`[{"op": "add", "path": "/Field2/-", "value": "bar"}]`)
expected := deepcopy.Copy(object).(TestType)
Expand Down

0 comments on commit 07fa661

Please sign in to comment.