Skip to content

Commit

Permalink
openapi block with properties instead of properties only; request/res…
Browse files Browse the repository at this point in the history
…ponse are always validated if openapi block is present; request is rejected if invalid and ignore_request_violations is not true; response is rejected if invalid and ignore_response_violations is not true (#21)
  • Loading branch information
Johannes Koch authored and Marcel Ludwig committed Dec 8, 2020
1 parent a5a73d6 commit 5285121
Show file tree
Hide file tree
Showing 4 changed files with 43 additions and 31 deletions.
16 changes: 3 additions & 13 deletions config/backend.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,7 @@ type Backend struct {
RequestBodyLimit string `hcl:"request_body_limit,optional"`
TTFBTimeout string `hcl:"ttfb_timeout,optional"`
Timeout string `hcl:"timeout,optional"`
OpenAPIFile string `hcl:"openapi_file,optional"`
ValidateReq bool `hcl:"validate_request,optional"`
ValidateRes bool `hcl:"validate_response,optional"`
OpenAPI *OpenAPI `hcl:"openapi,block"`
}

func (b Backend) Body() hcl.Body {
Expand Down Expand Up @@ -82,16 +80,8 @@ func (b *Backend) Merge(other *Backend) (*Backend, []hcl.Body) {
result.Timeout = other.Timeout
}

if other.OpenAPIFile != "" {
result.OpenAPIFile = other.OpenAPIFile
}

if other.ValidateReq {
result.ValidateReq = other.ValidateReq
}

if other.ValidateRes {
result.ValidateRes = other.ValidateRes
if other.OpenAPI != nil {
result.OpenAPI = other.OpenAPI
}

return &result, bodies
Expand Down
7 changes: 7 additions & 0 deletions config/openapi.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package config

type OpenAPI struct {
File string `hcl:"file"`
IgnoreRequestViolations bool `hcl:"ignore_request_violations,optional"`
IgnoreResponseViolations bool `hcl:"ignore_response_violations,optional"`
}
44 changes: 31 additions & 13 deletions handler/proxy.go
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,23 @@ func (c *CORSOptions) AllowsOrigin(origin string) bool {
return false
}

type OpenAPIOptions struct {
File string
IgnoreRequestViolations bool
IgnoreResponseViolations bool
}

func NewOpenAPIOptions(openapi *config.OpenAPI) *OpenAPIOptions {
if openapi == nil {
return nil
}
return &OpenAPIOptions{
File: openapi.File,
IgnoreRequestViolations: openapi.IgnoreRequestViolations,
IgnoreResponseViolations: openapi.IgnoreResponseViolations,
}
}

func NewProxy(options *ProxyOptions, log *logrus.Entry, srvOpts *server.Options, evalCtx *hcl.EvalContext) (http.Handler, error) {
logConf := *logging.DefaultConfig
logConf.TypeFieldKey = "couper_backend"
Expand Down Expand Up @@ -172,12 +189,12 @@ func (p *Proxy) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
}

func (p *Proxy) prepareRequestValidation(outreq *http.Request) (context.Context, *openapi3filter.Route, *openapi3filter.RequestValidationInput, error) {
if p.options.ValidateReq || p.options.ValidateRes {
if p.options.OpenAPI != nil {
dir, err := os.Getwd()
if err != nil {
return nil, nil, nil, err
}
router := openapi3filter.NewRouter().WithSwaggerFromFile(dir + "/" + p.options.OpenAPIFile)
router := openapi3filter.NewRouter().WithSwaggerFromFile(dir + "/" + p.options.OpenAPI.File)
validationCtx := context.Background()
route, pathParams, _ := router.FindRoute(outreq.Method, outreq.URL)

Expand All @@ -192,7 +209,7 @@ func (p *Proxy) prepareRequestValidation(outreq *http.Request) (context.Context,
}

func (p *Proxy) prepareResponseValidation(requestValidationInput *openapi3filter.RequestValidationInput, res *http.Response) (*openapi3filter.ResponseValidationInput, []byte, error) {
if p.options.ValidateRes {
if p.options.OpenAPI != nil {
responseValidationInput := &openapi3filter.ResponseValidationInput{
RequestValidationInput: requestValidationInput,
Status: res.StatusCode,
Expand Down Expand Up @@ -269,12 +286,14 @@ func (p *Proxy) roundtrip(rw http.ResponseWriter, req *http.Request) {
couperErr.DefaultJSON.ServeError(couperErr.UpstreamRequestValidationFailed).ServeHTTP(rw, req)
return
}
if p.options.ValidateReq {
if requestValidationInput != nil {
if err := openapi3filter.ValidateRequest(validationCtx, requestValidationInput); err != nil {
// TODO: use error template from parent endpoint>api>server
p.log.WithField("upstream request validation", err).Error()
couperErr.DefaultJSON.ServeError(couperErr.UpstreamRequestValidationFailed).ServeHTTP(rw, req)
return
if !p.options.OpenAPI.IgnoreRequestViolations {
couperErr.DefaultJSON.ServeError(couperErr.UpstreamRequestValidationFailed).ServeHTTP(rw, req)
return
}
}
}

Expand Down Expand Up @@ -309,21 +328,20 @@ func (p *Proxy) roundtrip(rw http.ResponseWriter, req *http.Request) {

responseValidationInput, body, err := p.prepareResponseValidation(requestValidationInput, res)
if err != nil {
// this only happens if response body buffering fails
// TODO: use error template from parent endpoint>api>server
p.log.WithField("upstream response validation", err).Error()
couperErr.DefaultJSON.ServeError(couperErr.UpstreamResponseBufferingFailed).ServeHTTP(rw, req)
return
}
if responseValidationInput != nil {
if route != nil {
if err := openapi3filter.ValidateResponse(validationCtx, responseValidationInput); err != nil {
// TODO: use error template from parent endpoint>api>server
p.log.WithField("upstream response validation", err).Error()
if responseValidationInput != nil && route != nil {
if err := openapi3filter.ValidateResponse(validationCtx, responseValidationInput); err != nil {
// TODO: use error template from parent endpoint>api>server
p.log.WithField("upstream response validation", err).Error()
if !p.options.OpenAPI.IgnoreResponseViolations {
couperErr.DefaultJSON.ServeError(couperErr.UpstreamResponseValidationFailed).ServeHTTP(rw, req)
return
}
} else {
p.log.Info("response validation enabled, but no route found")
}
}

Expand Down
7 changes: 2 additions & 5 deletions handler/proxy_options.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,8 @@ type ProxyOptions struct {
ConnectTimeout, Timeout, TTFBTimeout time.Duration
Context []hcl.Body
BackendName string
OpenAPIFile string
ValidateReq, ValidateRes bool
CORS *CORSOptions
OpenAPI *OpenAPIOptions
RequestBodyLimit int64
}

Expand Down Expand Up @@ -49,12 +48,10 @@ func NewProxyOptions(conf *config.Backend, corsOpts *CORSOptions, remainCtx []hc
CORS: cors,
ConnectTimeout: connectD,
Context: remainCtx,
OpenAPI: NewOpenAPIOptions(conf.OpenAPI),
RequestBodyLimit: bodyLimit,
TTFBTimeout: ttfbD,
Timeout: totalD,
OpenAPIFile: conf.OpenAPIFile,
ValidateReq: conf.ValidateReq,
ValidateRes: conf.ValidateRes,
}, nil
}

Expand Down

0 comments on commit 5285121

Please sign in to comment.