Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ Reports any errors as a `{openapi.error}` [placeholder](https://caddyserver.com/
| `validate_servers` | Enable server validation. Accepts `true`, `false` or just the directive which enables validation. Default is `true`. |
| `log_error` | Toggles error logging. Default is `false` |
| `check` | Enable validation of the request parameters; include one or more of the following directives in the body:`req_params`, `req_body` and `resp_body`. `resp_body` only validates `application/json` payload. Note that validating the request body will implicitly set `req_params` |
| `skip_missing_spec` | Toggles skipping missing or malformed OpenAPI spec. In this case, the request/response will not be validated. Default is `false` |

Errors are reported in the following [placeholders](https://caddyserver.com/docs/caddyfile/concepts#placeholders). You can use them in other [directives](https://caddyserver.com/docs/caddyfile/directives) like [`respond`](https://caddyserver.com/docs/caddyfile/directives/respond)

Expand Down
17 changes: 17 additions & 0 deletions handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,23 @@ func (oapi OpenAPI) ServeHTTP(w http.ResponseWriter, req *http.Request, next cad
replacer.Set(OPENAPI_STATUS_CODE, "")
replacer.Set(OPENAPI_RESPONSE_ERROR, "")

// if oas is nil means that we skipped openapi spec parsing errors and we can't check this request
if nil == oapi.oas {

err := fmt.Errorf("OpenApi spec is missing or malformed")
replacer.Set(OPENAPI_ERROR, err.Error())
replacer.Set(OPENAPI_STATUS_CODE, 404)
if oapi.LogError {
oapi.err(fmt.Sprintf(">> %s %s %s: %s", getIP(req), req.Method, req.RequestURI, err))
}

if !oapi.FallThrough {
return err
} else {
return next.ServeHTTP(w, req)
}
}

route, pathParams, err := oapi.router.FindRoute(req)

if nil != err {
Expand Down
73 changes: 49 additions & 24 deletions openapi.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,21 +21,22 @@ import (
)

const (
MODULE_ID = "http.handlers.openapi"
X_POLICY = "x-policy"
OPENAPI_ERROR = "openapi.error"
OPENAPI_STATUS_CODE = "openapi.status_code"
OPENAPI_RESPONSE_ERROR = "openapi.response_error"
TOKEN_OPENAPI = "openapi"
TOKEN_POLICY_BUNDLE = "policy_bundle"
TOKEN_SPEC = "spec"
TOKEN_FALL_THROUGH = "fall_through"
TOKEN_LOG_ERROR = "log_error"
TOKEN_VALIDATE_SERVERS = "validate_servers"
TOKEN_CHECK = "check"
VALUE_REQ_PARAMS = "req_params"
VALUE_REQ_BODY = "req_body"
VALUE_RESP_BODY = "resp_body"
MODULE_ID = "http.handlers.openapi"
X_POLICY = "x-policy"
OPENAPI_ERROR = "openapi.error"
OPENAPI_STATUS_CODE = "openapi.status_code"
OPENAPI_RESPONSE_ERROR = "openapi.response_error"
TOKEN_OPENAPI = "openapi"
TOKEN_POLICY_BUNDLE = "policy_bundle"
TOKEN_SPEC = "spec"
TOKEN_FALL_THROUGH = "fall_through"
TOKEN_LOG_ERROR = "log_error"
TOKEN_VALIDATE_SERVERS = "validate_servers"
TOKEN_CHECK = "check"
TOKEN_SKIP_MISSING_SPEC = "skip_missing_spec"
VALUE_REQ_PARAMS = "req_params"
VALUE_REQ_BODY = "req_body"
VALUE_RESP_BODY = "resp_body"
)

// This middleware validates request against an OpenAPI V3 specification. No conforming request can be rejected
Expand All @@ -57,6 +58,9 @@ type OpenAPI struct {
// Enable server validation
ValidateServers bool `json:"valid_servers,omitempty"`

// Should the request proceed if spec is missing. Default is `false`
SkipMissingSpec bool `json:"skip_missing_spec,omitempty"`

oas *openapi3.T
router routers.Router

Expand Down Expand Up @@ -109,19 +113,34 @@ func (oapi *OpenAPI) Provision(ctx caddy.Context) error {

oapi.log(fmt.Sprintf("Using OpenAPI spec: %s", oapi.Spec))

if strings.HasPrefix("http", oapi.Spec) {
var u *url.URL
if u, err = url.Parse(oapi.Spec); nil != err {
parse_spec := func() error {
if strings.HasPrefix("http", oapi.Spec) {
var u *url.URL
if u, err = url.Parse(oapi.Spec); nil != err {
return err
}
if oas, err = openapi3.NewLoader().LoadFromURI(u); nil != err {
return err
}
} else if _, err = os.Stat(oapi.Spec); !(nil == err || os.IsExist(err)) {
return err
}
if oas, err = openapi3.NewLoader().LoadFromURI(u); nil != err {

} else if oas, err = openapi3.NewLoader().LoadFromFile(oapi.Spec); nil != err {
return err
}
} else if _, err = os.Stat(oapi.Spec); !(nil == err || os.IsExist(err)) {
return err

} else if oas, err = openapi3.NewLoader().LoadFromFile(oapi.Spec); nil != err {
return err
return nil
}

parse_err := parse_spec()

if nil != parse_err {
if !oapi.SkipMissingSpec {
return parse_err
} else {
oapi.log("OpenAPI spec missing or malformed, skipping...")
return nil
}
}

if oapi.ValidateServers {
Expand Down Expand Up @@ -229,6 +248,12 @@ func (oapi *OpenAPI) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
return err
}

case TOKEN_SKIP_MISSING_SPEC:
if d.NextArg() {
return d.ArgErr()
}
oapi.SkipMissingSpec = true

default:
return d.Errf("unrecognized subdirective: '%s'", token)
}
Expand Down