Skip to content

Commit

Permalink
fix: correctly deal with premarshalled json objects (ory#667)
Browse files Browse the repository at this point in the history
  • Loading branch information
CaptainStandby authored Feb 17, 2023
1 parent 91297cd commit 1cb5662
Show file tree
Hide file tree
Showing 3 changed files with 186 additions and 11 deletions.
13 changes: 2 additions & 11 deletions decoderx/http.go
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ func HTTPFormDecoder() HTTPDecoderOption {
}
}

// HTTPJSONDecoder configures the HTTP decoder to only accept form-data
// HTTPJSONDecoder configures the HTTP decoder to only accept JSON data
// (application/json).
func HTTPJSONDecoder() HTTPDecoderOption {
return func(o *httpDecoderOptions) {
Expand Down Expand Up @@ -335,20 +335,11 @@ func (t *HTTP) decodeJSONForm(r *http.Request, destination interface{}, o *httpD
}

values := url.Values{}
var notJSONForm bool
parsed.ForEach(func(k, v gjson.Result) bool {
if v.IsArray() || v.IsObject() {
notJSONForm = true
return false
}
values.Set(k.String(), v.String())
return true
})

if notJSONForm {
return t.decodeJSON(r, destination, o, true)
}

if o.queryAndBody {
_ = r.ParseForm()
for k := range r.Form {
Expand Down Expand Up @@ -518,7 +509,7 @@ func (t *HTTP) decodeURLValues(values url.Values, paths []jsonschemax.Path, o *h
continue
}

raw, err = sjson.SetBytes(raw, path.Name, v)
raw, err = sjson.SetRawBytes(raw, path.Name, []byte(v))
case []map[string]interface{}:
raw, err = sjson.SetBytes(raw, path.Name, values[key])
}
Expand Down
162 changes: 162 additions & 0 deletions decoderx/http_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,168 @@ func TestHTTPFormDecoder(t *testing.T) {
options: []HTTPDecoderOption{HTTPJSONSchemaCompiler("stub/schema.json", nil)},
expectedError: `missing properties: "foo"`,
},
{
d: "should fail for invalid JSON data with unrestricted object",
request: newRequest(t, "POST", "/", bytes.NewBufferString(`{"dynamic_object":{"stuff":{"blub":[42,3.14152,"fu":"bar"},"consent":true}}`), httpContentTypeJSON),
options: []HTTPDecoderOption{
HTTPJSONSchemaCompiler("stub/dynamic-object.json", nil),
HTTPJSONDecoder()},
expectedError: "The request was malformed or contained invalid parameters",
},
{
d: "should fail validation for wrong JSON type with unrestricted object",
request: newRequest(t, "POST", "/", bytes.NewBufferString(`{"dynamic_object":[42,3.14152]}`), httpContentTypeJSON),
options: []HTTPDecoderOption{
HTTPJSONSchemaCompiler("stub/dynamic-object.json", nil),
HTTPJSONDecoder()},
expectedError: "expected object, but got array",
},
{
d: "should accept JSON data with unrestricted object",
request: newRequest(t, "POST", "/", bytes.NewBufferString(`{"dynamic_object":{"stuff":{"blub":[42,3.14152],"fu":"bar"},"consent":true}}`), httpContentTypeJSON),
options: []HTTPDecoderOption{
HTTPJSONSchemaCompiler("stub/dynamic-object.json", nil),
HTTPJSONDecoder()},
expected: `{
"dynamic_object": {
"stuff": {
"blub": [42, 3.14152],
"fu": "bar"
},
"consent": true
}
}`,
},
{
d: "should accept JSON data with unrestricted object and mixed object syntax and query parameter",
request: newRequest(t, "POST", "/?name.last=Horstmann", bytes.NewBufferString(`{"dynamic_object":{"stuff":{"blub":[42,3.14152],"fu":"bar"},"consent":true},"name.first":"Horst"}`), httpContentTypeJSON),
options: []HTTPDecoderOption{
HTTPJSONSchemaCompiler("stub/dynamic-object.json", nil),
HTTPJSONDecoder(),
HTTPDecoderJSONFollowsFormFormat(),
HTTPDecoderUseQueryAndBody()},
expected: `{
"dynamic_object": {
"stuff": {
"blub": [42, 3.14152],
"fu": "bar"
},
"consent": true
},
"name": {
"first": "Horst",
"last": "Horstmann"
}
}`,
},
{
d: "should accept JSON data with unrestricted object and mixed object syntax",
request: newRequest(t, "POST", "/", bytes.NewBufferString(`{"dynamic_object":{"stuff":{"blub":[42,3.14152],"fu":"bar"},"consent":true},"name.first":"Horst","name.last":"Horstmann"}`), httpContentTypeJSON),
options: []HTTPDecoderOption{
HTTPJSONSchemaCompiler("stub/dynamic-object.json", nil),
HTTPJSONDecoder(),
HTTPDecoderJSONFollowsFormFormat()},
expected: `{
"dynamic_object": {
"stuff": {
"blub": [42, 3.14152],
"fu": "bar"
},
"consent": true
},
"name": {
"first": "Horst",
"last": "Horstmann"
}
}`,
},
{
d: "should fail form data with invalid premarshalled JSON object",
request: newRequest(t, "POST", "/", bytes.NewBufferString(url.Values{
"dynamic_object": {`{"stuff":{"blub":[42, 3.14152,"fu":"bar"},"consent":true}`},
}.Encode()), httpContentTypeURLEncodedForm),
options: []HTTPDecoderOption{
HTTPJSONSchemaCompiler("stub/dynamic-object.json", nil),
HTTPFormDecoder()},
expectedError: "The request was malformed or contained invalid parameters",
},
{
d: "should fail validation for form data with wrong premarshalled JSON type",
request: newRequest(t, "POST", "/", bytes.NewBufferString(url.Values{
"dynamic_object": {`[42, 3.14152]`},
}.Encode()), httpContentTypeURLEncodedForm),
options: []HTTPDecoderOption{
HTTPJSONSchemaCompiler("stub/dynamic-object.json", nil),
HTTPFormDecoder()},
expectedError: "expected object, but got array",
},
{
d: "should accept form data with premarshalled JSON object",
request: newRequest(t, "POST", "/", bytes.NewBufferString(url.Values{
"dynamic_object": {`{"stuff":{"blub":[42, 3.14152],"fu":"bar"},"consent":true}`},
}.Encode()), httpContentTypeURLEncodedForm),
options: []HTTPDecoderOption{
HTTPJSONSchemaCompiler("stub/dynamic-object.json", nil),
HTTPFormDecoder()},
expected: `{
"dynamic_object": {
"stuff": {
"blub": [42, 3.14152],
"fu": "bar"
},
"consent": true
},
"name": {}
}`,
},
{
d: "should accept form data with premarshalled JSON object and mixed object syntax and query parameter",
request: newRequest(t, "POST", "/?name.last=Horstmann", bytes.NewBufferString(url.Values{
"dynamic_object": {`{"stuff":{"blub":[42, 3.14152],"fu":"bar"},"consent":true}`},
"name.first": {"Horst"},
}.Encode()), httpContentTypeURLEncodedForm),
options: []HTTPDecoderOption{
HTTPJSONSchemaCompiler("stub/dynamic-object.json", nil),
HTTPFormDecoder(),
HTTPDecoderUseQueryAndBody()},
expected: `{
"dynamic_object": {
"stuff": {
"blub": [42, 3.14152],
"fu": "bar"
},
"consent": true
},
"name": {
"first": "Horst",
"last": "Horstmann"
}
}`,
},
{
d: "should accept form data with premarshalled JSON object and mixed object syntax",
request: newRequest(t, "POST", "/", bytes.NewBufferString(url.Values{
"dynamic_object": {`{"stuff":{"blub":[42, 3.14152],"fu":"bar"},"consent":true}`},
"name.first": {"Horst"},
"name.last": {"Horstmann"},
}.Encode()), httpContentTypeURLEncodedForm),
options: []HTTPDecoderOption{
HTTPJSONSchemaCompiler("stub/dynamic-object.json", nil),
HTTPFormDecoder()},
expected: `{
"dynamic_object": {
"stuff": {
"blub": [42, 3.14152],
"fu": "bar"
},
"consent": true
},
"name": {
"first": "Horst",
"last": "Horstmann"
}
}`,
},
{
d: "should pass form request and type assert data",
request: newRequest(t, "POST", "/", bytes.NewBufferString(url.Values{
Expand Down
22 changes: 22 additions & 0 deletions decoderx/stub/dynamic-object.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
{
"$id": "https://example.com/config.schema.json",
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"properties": {
"name": {
"type": "object",
"properties": {
"first": {
"type": "string"
},
"last": {
"type": "string"
}
}
},
"dynamic_object": {
"type": "object",
"additionalProperties": true
}
}
}

0 comments on commit 1cb5662

Please sign in to comment.