Skip to content

Commit

Permalink
openapi3filter: fix crash when given arrays of objects as query param…
Browse files Browse the repository at this point in the history
…eters (#664)
  • Loading branch information
orensolo committed Nov 24, 2022
1 parent cd217fe commit 8a66010
Show file tree
Hide file tree
Showing 2 changed files with 206 additions and 1 deletion.
123 changes: 123 additions & 0 deletions openapi3filter/issue625_test.go
@@ -0,0 +1,123 @@
package openapi3filter_test

import (
"net/http"
"strings"
"testing"

"github.com/stretchr/testify/require"

"github.com/getkin/kin-openapi/openapi3"
"github.com/getkin/kin-openapi/openapi3filter"
"github.com/getkin/kin-openapi/routers/gorillamux"
)

func TestIssue625(t *testing.T) {

anyOfArraySpec := `
openapi: 3.0.0
info:
version: 1.0.0
title: Sample API
paths:
/items:
get:
description: Returns a list of stuff
parameters:
- description: test object
explode: false
in: query
name: test
required: false
schema:
type: array
items:
anyOf:
- type: integer
- type: boolean
responses:
'200':
description: Successful response
`[1:]

oneOfArraySpec := strings.ReplaceAll(anyOfArraySpec, "anyOf", "oneOf")

allOfArraySpec := strings.ReplaceAll(strings.ReplaceAll(anyOfArraySpec, "anyOf", "allOf"),
"type: boolean", "type: number")

tests := []struct {
name string
spec string
req string
errStr string
}{
{
name: "success anyof object array",
spec: anyOfArraySpec,
req: "/items?test=3,7",
},
{
name: "failed anyof object array",
spec: anyOfArraySpec,
req: "/items?test=s1,s2",
errStr: `parameter "test" in query has an error: path 0: value s1: an invalid boolean: invalid syntax`,
},

{
name: "success allof object array",
spec: allOfArraySpec,
req: `/items?test=1,3`,
},
{
name: "failed allof object array",
spec: allOfArraySpec,
req: `/items?test=1.2,3.1`,
errStr: `parameter "test" in query has an error: Value must be an integer`,
},
{
name: "success oneof object array",
spec: oneOfArraySpec,
req: `/items?test=true,3`,
},
{
name: "faled oneof object array",
spec: oneOfArraySpec,
req: `/items?test="val1","val2"`,
errStr: `parameter "test" in query has an error: item 0: decoding oneOf failed: 0 schemas matched`,
},
}

for _, testcase := range tests {
t.Run(testcase.name, func(t *testing.T) {
loader := openapi3.NewLoader()
ctx := loader.Context

doc, err := loader.LoadFromData([]byte(testcase.spec))
require.NoError(t, err)

err = doc.Validate(ctx)
require.NoError(t, err)

router, err := gorillamux.NewRouter(doc)
require.NoError(t, err)
httpReq, err := http.NewRequest(http.MethodGet, testcase.req, nil)
require.NoError(t, err)

route, pathParams, err := router.FindRoute(httpReq)
require.NoError(t, err)

requestValidationInput := &openapi3filter.RequestValidationInput{
Request: httpReq,
PathParams: pathParams,
Route: route,
}
err = openapi3filter.ValidateRequest(ctx, requestValidationInput)
if testcase.errStr == "" {
require.NoError(t, err)
} else {
require.Contains(t, err.Error(), testcase.errStr)
}
},
)
}
}
84 changes: 83 additions & 1 deletion openapi3filter/req_resp_decoder.go
Expand Up @@ -526,10 +526,92 @@ func (d *urlValuesDecoder) DecodeArray(param string, sm *openapi3.SerializationM
}
values = strings.Split(values[0], delim)
}
val, err := parseArray(values, schema)
val, err := d.parseArray(values, sm, schema)
return val, ok, err
}

// parseArray returns an array that contains items from a raw array.
// Every item is parsed as a primitive value.
// The function returns an error when an error happened while parse array's items.
func (d *urlValuesDecoder) parseArray(raw []string, sm *openapi3.SerializationMethod, schemaRef *openapi3.SchemaRef) ([]interface{}, error) {
var value []interface{}

for i, v := range raw {
item, err := d.parseValue(v, schemaRef.Value.Items)
if err != nil {
if v, ok := err.(*ParseError); ok {
return nil, &ParseError{path: []interface{}{i}, Cause: v}
}
return nil, fmt.Errorf("item %d: %w", i, err)
}

// If the items are nil, then the array is nil. There shouldn't be case where some values are actual primitive
// values and some are nil values.
if item == nil {
return nil, nil
}
value = append(value, item)
}
return value, nil
}

func (d *urlValuesDecoder) parseValue(v string, schema *openapi3.SchemaRef) (interface{}, error) {
if len(schema.Value.AllOf) > 0 {
var value interface{}
var err error
for _, sr := range schema.Value.AllOf {
value, err = d.parseValue(v, sr)
if value == nil || err != nil {
break
}
}
return value, err
}

if len(schema.Value.AnyOf) > 0 {
var value interface{}
var err error
for _, sr := range schema.Value.AnyOf {
value, err = d.parseValue(v, sr)
if err == nil {
return value, nil
}
}

return nil, err
}

if len(schema.Value.OneOf) > 0 {
isMatched := 0
var value interface{}
var err error
for _, sr := range schema.Value.OneOf {
result, err := d.parseValue(v, sr)
if err == nil {
value = result
isMatched++
}
}
if isMatched == 1 {
return value, nil
} else if isMatched > 1 {
return nil, fmt.Errorf("decoding oneOf failed: %d schemas matched", isMatched)
} else if isMatched == 0 {
return nil, fmt.Errorf("decoding oneOf failed: %d schemas matched", isMatched)
}

return nil, err
}

if schema.Value.Not != nil {
// TODO(decode not): handle decoding "not" JSON Schema
return nil, errors.New("not implemented: decoding 'not'")
}

return parsePrimitive(v, schema)

}

func (d *urlValuesDecoder) DecodeObject(param string, sm *openapi3.SerializationMethod, schema *openapi3.SchemaRef) (map[string]interface{}, bool, error) {
var propsFn func(url.Values) (map[string]string, error)
switch sm.Style {
Expand Down

0 comments on commit 8a66010

Please sign in to comment.