From 591249165815810c93aa75f3900e68424e345359 Mon Sep 17 00:00:00 2001 From: Peltoche Date: Sat, 29 Sep 2018 20:22:29 +0200 Subject: [PATCH] Add validation on request body parameters --- analyzer.go | 51 ++++++++++++++++++++++++++++++++++++++++++++--- analyzer_test.go | 49 +++++++++++++++++++++++++++++++++++++++++++++ transport.go | 31 +++++++++++++++++++++++++++- transport_test.go | 23 +++++++++++++++++++++ 4 files changed, 150 insertions(+), 4 deletions(-) diff --git a/analyzer.go b/analyzer.go index d25d07a..3ea4206 100644 --- a/analyzer.go +++ b/analyzer.go @@ -1,15 +1,19 @@ package oaichecker import ( + "encoding/json" "errors" - "fmt" "net/http" "github.com/go-openapi/analysis" + "github.com/go-openapi/spec" + "github.com/go-openapi/strfmt" + "github.com/go-openapi/validate" ) type Analyzer struct { analyzer *analysis.Spec + schema *spec.Schema } func NewAnalyzer(specs *Specs) *Analyzer { @@ -18,7 +22,8 @@ func NewAnalyzer(specs *Specs) *Analyzer { } return &Analyzer{ - analyzer: analysis.New(specs.document.OrigSpec()), + analyzer: specs.document.Analyzer, + schema: specs.document.Schema(), } } @@ -36,7 +41,47 @@ func (t *Analyzer) Analyze(req *http.Request) error { return errors.New("operation not defined inside the specs") } - fmt.Printf("operation: %#v\n", operation) + for _, param := range operation.Parameters { + var err error + + switch param.In { + case "body": + err = t.validateBodyParameter(req, ¶m) + } + if err != nil { + return err + } + } + + return nil +} + +func (t *Analyzer) validateBodyParameter(req *http.Request, param *spec.Parameter) error { + bodyReader, err := req.GetBody() + if err != nil { + return err + } + + input := map[string]interface{}{} + err = json.NewDecoder(bodyReader).Decode(&input) + if err != nil { + return err + } + + paramRef := param.ParamProps.Schema.Ref.String() + + var schema *spec.Schema + for _, def := range t.analyzer.AllDefinitions() { + if paramRef == def.Ref.String() { + schema = def.Schema + break + } + } + + err = validate.AgainstSchema(schema, input, strfmt.Default) + if err != nil { + return err + } return nil } diff --git a/analyzer_test.go b/analyzer_test.go index b9851ad..806f9aa 100644 --- a/analyzer_test.go +++ b/analyzer_test.go @@ -2,6 +2,7 @@ package oaichecker import ( "net/http" + "strings" "testing" "github.com/stretchr/testify/assert" @@ -40,3 +41,51 @@ func Test_Analyzer_Analyze_with_request_not_in_specs(t *testing.T) { assert.EqualError(t, err, "operation not defined inside the specs") } + +func Test_Analyzer_Analyze_with_body_parameters(t *testing.T) { + specs, err := NewSpecsFromFile("./dataset/petstore.json") + require.NoError(t, err) + + analyzer := NewAnalyzer(specs) + + req, err := http.NewRequest("POST", "/pet", strings.NewReader(`{ + "name": "foobar", + "photoUrls": ["tutu"] + }`)) + require.NoError(t, err) + + err = analyzer.Analyze(req) + + assert.NoError(t, err) +} + +func Test_Analyzer_Analyze_with_invalid_body_parameters(t *testing.T) { + specs, err := NewSpecsFromFile("./dataset/petstore.json") + require.NoError(t, err) + + analyzer := NewAnalyzer(specs) + + req, err := http.NewRequest("POST", "/pet", strings.NewReader(`{ + "name": "foobar" + }`)) + require.NoError(t, err) + + err = analyzer.Analyze(req) + + assert.EqualError(t, err, "validation failure list:\n"+ + ".photoUrls in body is required") +} + +func Test_Analyzer_Analyze_with_invalid_body_format(t *testing.T) { + specs, err := NewSpecsFromFile("./dataset/petstore.json") + require.NoError(t, err) + + analyzer := NewAnalyzer(specs) + + req, err := http.NewRequest("POST", "/pet", strings.NewReader(`not a json`)) + require.NoError(t, err) + + err = analyzer.Analyze(req) + + assert.EqualError(t, err, "invalid character 'o' in literal null (expecting 'u')") +} diff --git a/transport.go b/transport.go index 7a5205a..6c826a0 100644 --- a/transport.go +++ b/transport.go @@ -1,6 +1,11 @@ package oaichecker -import "net/http" +import ( + "bytes" + "io" + "io/ioutil" + "net/http" +) type Transport struct { Transport http.RoundTripper @@ -15,6 +20,30 @@ func NewTransport(specs *Specs) *Transport { } func (t *Transport) RoundTrip(req *http.Request) (*http.Response, error) { + var err error + + // GetBody is an optional func to return a new copy of Body + switch req.Body.(type) { + case nil: + req.GetBody = func() (io.ReadCloser, error) { + return http.NoBody, nil + } + default: + body, err := ioutil.ReadAll(req.Body) + if err != nil { + return nil, err + } + + req.GetBody = func() (io.ReadCloser, error) { + return ioutil.NopCloser(bytes.NewReader(body)), nil + } + } + + req.Body, err = req.GetBody() + if err != nil { + return nil, err + } + res, err := t.Transport.RoundTrip(req) if err != nil { return nil, err diff --git a/transport_test.go b/transport_test.go index 726a395..7d07101 100644 --- a/transport_test.go +++ b/transport_test.go @@ -6,6 +6,7 @@ import ( "io/ioutil" "net/http" "net/http/httptest" + "strings" "testing" "github.com/stretchr/testify/assert" @@ -101,3 +102,25 @@ func Test_Transport_with_an_analyzer_error(t *testing.T) { assert.Nil(t, res) assert.EqualError(t, err, fmt.Sprintf("Get %s/invalid-path: operation not defined inside the specs", ts.URL)) } + +func Test_Transport_with_a_body(t *testing.T) { + ts := newServer(func(w http.ResponseWriter, r *http.Request) { + w.Write([]byte("some-response")) + }) + defer ts.Close() + + specs, err := NewSpecsFromFile("./dataset/petstore.json") + require.NoError(t, err) + + client := http.Client{ + Transport: NewTransport(specs), + } + + res, err := client.Post(ts.URL+"/pet", "application/json", strings.NewReader(`{ + "name": "foobar", + "photoUrls": ["some-url"] + }`)) + + assert.NoError(t, err) + assert.Equal(t, "some-response", resBody(t, res)) +}