Skip to content

Commit 2ac6346

Browse files
CynanXMark Piercefenollp
authored
feat: support rejecting when request body present but not required by specification (#1101)
Co-authored-by: Mark Pierce <[email protected]> Co-authored-by: Pierre Fenoll <[email protected]>
1 parent 6321ee8 commit 2ac6346

File tree

4 files changed

+133
-2
lines changed

4 files changed

+133
-2
lines changed

.github/docs/openapi3filter.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -238,6 +238,9 @@ type Options struct {
238238
// Set RegexCompiler to override the regex implementation
239239
RegexCompiler openapi3.RegexCompilerFunc
240240

241+
// Set RejectWhenRequestBodyNotSpecified so ValidateRequest fails when request body is present but not defined in the specification
242+
RejectWhenRequestBodyNotSpecified bool
243+
241244
// A document with security schemes defined will not pass validation
242245
// unless an AuthenticationFunc is defined.
243246
// See NoopAuthenticationFunc

openapi3filter/options.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,9 @@ type Options struct {
2828
// Set RegexCompiler to override the regex implementation
2929
RegexCompiler openapi3.RegexCompilerFunc
3030

31+
// Set RejectWhenRequestBodyNotSpecified so ValidateRequest fails when request body is present but not defined in the specification
32+
RejectWhenRequestBodyNotSpecified bool
33+
3134
// A document with security schemes defined will not pass validation
3235
// unless an AuthenticationFunc is defined.
3336
// See NoopAuthenticationFunc
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
package openapi3filter
2+
3+
import (
4+
"net/http"
5+
"strings"
6+
"testing"
7+
8+
"github.com/getkin/kin-openapi/openapi3filter"
9+
"github.com/stretchr/testify/require"
10+
11+
"github.com/getkin/kin-openapi/openapi3"
12+
"github.com/getkin/kin-openapi/routers/gorillamux"
13+
)
14+
15+
func TestIssue1100(t *testing.T) {
16+
spec := `
17+
openapi: 3.0.3
18+
info:
19+
version: 1.0.0
20+
title: sample api
21+
description: api service paths to test the issue
22+
paths:
23+
/api/path:
24+
post:
25+
summary: path
26+
tags:
27+
- api
28+
responses:
29+
'200':
30+
description: Ok
31+
`[1:]
32+
33+
loader := openapi3.NewLoader()
34+
35+
doc, err := loader.LoadFromData([]byte(spec))
36+
require.NoError(t, err)
37+
38+
err = doc.Validate(loader.Context)
39+
require.NoError(t, err)
40+
41+
router, err := gorillamux.NewRouter(doc)
42+
require.NoError(t, err)
43+
44+
for _, testcase := range []struct {
45+
name string
46+
endpoint string
47+
ct string
48+
data string
49+
rejectBody bool
50+
shouldFail bool
51+
}{
52+
{
53+
name: "json success",
54+
endpoint: "/api/path",
55+
ct: "application/json",
56+
data: ``,
57+
rejectBody: false,
58+
shouldFail: false,
59+
},
60+
{
61+
name: "json failure",
62+
endpoint: "/api/path",
63+
ct: "application/json",
64+
data: `{"data":"some+unexpected+data"}`,
65+
rejectBody: false,
66+
shouldFail: false,
67+
},
68+
{
69+
name: "json success",
70+
endpoint: "/api/path",
71+
ct: "application/json",
72+
data: ``,
73+
rejectBody: true,
74+
shouldFail: false,
75+
},
76+
{
77+
name: "json failure",
78+
endpoint: "/api/path",
79+
ct: "application/json",
80+
data: `{"data":"some+unexpected+data"}`,
81+
rejectBody: true,
82+
shouldFail: true,
83+
},
84+
} {
85+
t.Run(
86+
testcase.name, func(t *testing.T) {
87+
data := strings.NewReader(testcase.data)
88+
req, err := http.NewRequest("POST", testcase.endpoint, data)
89+
require.NoError(t, err)
90+
req.Header.Add("Content-Type", testcase.ct)
91+
92+
route, pathParams, err := router.FindRoute(req)
93+
require.NoError(t, err)
94+
95+
validationInput := &openapi3filter.RequestValidationInput{
96+
Request: req,
97+
PathParams: pathParams,
98+
Route: route,
99+
Options: &openapi3filter.Options{RejectWhenRequestBodyNotSpecified: testcase.rejectBody},
100+
}
101+
err = openapi3filter.ValidateRequest(loader.Context, validationInput)
102+
if testcase.shouldFail {
103+
require.Error(t, err, "This test case should fail "+testcase.data)
104+
} else {
105+
require.NoError(t, err, "This test case should pass "+testcase.data)
106+
}
107+
},
108+
)
109+
}
110+
}

openapi3filter/validate_request.go

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -90,8 +90,23 @@ func ValidateRequest(ctx context.Context, input *RequestValidationInput) error {
9090

9191
// RequestBody
9292
requestBody := operation.RequestBody
93-
if requestBody != nil && !options.ExcludeRequestBody {
94-
if err := ValidateRequestBody(ctx, input, requestBody.Value); err != nil {
93+
if !options.ExcludeRequestBody {
94+
// Validate specification request body if present
95+
if requestBody != nil {
96+
if err := ValidateRequestBody(ctx, input, requestBody.Value); err != nil {
97+
if !options.MultiError {
98+
return err
99+
}
100+
me = append(me, err)
101+
}
102+
}
103+
104+
// Reject if specification request body if not present (not wanted) but is present in the HTTP request
105+
if options.RejectWhenRequestBodyNotSpecified && input.Request.ContentLength > 0 {
106+
err := &RequestError{
107+
Input: input,
108+
Err: fmt.Errorf("request body not allowed for this request"),
109+
}
95110
if !options.MultiError {
96111
return err
97112
}

0 commit comments

Comments
 (0)