diff --git a/.github/docs/openapi3filter.txt b/.github/docs/openapi3filter.txt index 42c6249bf..e20c50cf8 100644 --- a/.github/docs/openapi3filter.txt +++ b/.github/docs/openapi3filter.txt @@ -238,6 +238,9 @@ type Options struct { // Set RegexCompiler to override the regex implementation RegexCompiler openapi3.RegexCompilerFunc + // Set RejectWhenRequestBodyNotSpecified so ValidateRequest fails when request body is present but not defined in the specification + RejectWhenRequestBodyNotSpecified bool + // A document with security schemes defined will not pass validation // unless an AuthenticationFunc is defined. // See NoopAuthenticationFunc diff --git a/openapi3filter/options.go b/openapi3filter/options.go index e7fad8321..d4f4cfc14 100644 --- a/openapi3filter/options.go +++ b/openapi3filter/options.go @@ -28,6 +28,9 @@ type Options struct { // Set RegexCompiler to override the regex implementation RegexCompiler openapi3.RegexCompilerFunc + // Set RejectWhenRequestBodyNotSpecified so ValidateRequest fails when request body is present but not defined in the specification + RejectWhenRequestBodyNotSpecified bool + // A document with security schemes defined will not pass validation // unless an AuthenticationFunc is defined. // See NoopAuthenticationFunc diff --git a/openapi3filter/testdata/issue1100_test.go b/openapi3filter/testdata/issue1100_test.go new file mode 100644 index 000000000..b5147dfe8 --- /dev/null +++ b/openapi3filter/testdata/issue1100_test.go @@ -0,0 +1,110 @@ +package openapi3filter + +import ( + "net/http" + "strings" + "testing" + + "github.com/getkin/kin-openapi/openapi3filter" + "github.com/stretchr/testify/require" + + "github.com/getkin/kin-openapi/openapi3" + "github.com/getkin/kin-openapi/routers/gorillamux" +) + +func TestIssue1100(t *testing.T) { + spec := ` +openapi: 3.0.3 +info: + version: 1.0.0 + title: sample api + description: api service paths to test the issue +paths: + /api/path: + post: + summary: path + tags: + - api + responses: + '200': + description: Ok + `[1:] + + loader := openapi3.NewLoader() + + doc, err := loader.LoadFromData([]byte(spec)) + require.NoError(t, err) + + err = doc.Validate(loader.Context) + require.NoError(t, err) + + router, err := gorillamux.NewRouter(doc) + require.NoError(t, err) + + for _, testcase := range []struct { + name string + endpoint string + ct string + data string + rejectBody bool + shouldFail bool + }{ + { + name: "json success", + endpoint: "/api/path", + ct: "application/json", + data: ``, + rejectBody: false, + shouldFail: false, + }, + { + name: "json failure", + endpoint: "/api/path", + ct: "application/json", + data: `{"data":"some+unexpected+data"}`, + rejectBody: false, + shouldFail: false, + }, + { + name: "json success", + endpoint: "/api/path", + ct: "application/json", + data: ``, + rejectBody: true, + shouldFail: false, + }, + { + name: "json failure", + endpoint: "/api/path", + ct: "application/json", + data: `{"data":"some+unexpected+data"}`, + rejectBody: true, + shouldFail: true, + }, + } { + t.Run( + testcase.name, func(t *testing.T) { + data := strings.NewReader(testcase.data) + req, err := http.NewRequest("POST", testcase.endpoint, data) + require.NoError(t, err) + req.Header.Add("Content-Type", testcase.ct) + + route, pathParams, err := router.FindRoute(req) + require.NoError(t, err) + + validationInput := &openapi3filter.RequestValidationInput{ + Request: req, + PathParams: pathParams, + Route: route, + Options: &openapi3filter.Options{RejectWhenRequestBodyNotSpecified: testcase.rejectBody}, + } + err = openapi3filter.ValidateRequest(loader.Context, validationInput) + if testcase.shouldFail { + require.Error(t, err, "This test case should fail "+testcase.data) + } else { + require.NoError(t, err, "This test case should pass "+testcase.data) + } + }, + ) + } +} diff --git a/openapi3filter/validate_request.go b/openapi3filter/validate_request.go index 7676b13bd..ab5e64214 100644 --- a/openapi3filter/validate_request.go +++ b/openapi3filter/validate_request.go @@ -90,8 +90,23 @@ func ValidateRequest(ctx context.Context, input *RequestValidationInput) error { // RequestBody requestBody := operation.RequestBody - if requestBody != nil && !options.ExcludeRequestBody { - if err := ValidateRequestBody(ctx, input, requestBody.Value); err != nil { + if !options.ExcludeRequestBody { + // Validate specification request body if present + if requestBody != nil { + if err := ValidateRequestBody(ctx, input, requestBody.Value); err != nil { + if !options.MultiError { + return err + } + me = append(me, err) + } + } + + // Reject if specification request body if not present (not wanted) but is present in the HTTP request + if options.RejectWhenRequestBodyNotSpecified && input.Request.ContentLength > 0 { + err := &RequestError{ + Input: input, + Err: fmt.Errorf("request body not allowed for this request"), + } if !options.MultiError { return err }