Skip to content

Commit

Permalink
Added security vallidation feature
Browse files Browse the repository at this point in the history
now the validator will check for HTTP and APIKey authentication on operations.

Signed-off-by: Dave Shanley <[email protected]>
  • Loading branch information
daveshanley committed Dec 2, 2023
1 parent bde416a commit fbd40b0
Show file tree
Hide file tree
Showing 10 changed files with 543 additions and 6 deletions.
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ module github.com/pb33f/libopenapi-validator
go 1.21

require (
github.com/pb33f/libopenapi v0.13.16
github.com/pb33f/libopenapi v0.13.17
github.com/santhosh-tekuri/jsonschema/v5 v5.3.1
github.com/stretchr/testify v1.8.4
github.com/vmware-labs/yaml-jsonpath v0.3.2
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,8 @@ github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1y
github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY=
github.com/onsi/gomega v1.19.0 h1:4ieX6qQjPP/BfC3mpsAtIGGlxTWPeA3Inl/7DtXw1tw=
github.com/onsi/gomega v1.19.0/go.mod h1:LY+I3pBVzYsTBU1AnDwOSxaYi9WoWiqgwooUqq9yPro=
github.com/pb33f/libopenapi v0.13.16 h1:w1A2zJidQEISPHibDkYRCUhnxRg7R45ZW8hwD2b+wLo=
github.com/pb33f/libopenapi v0.13.16/go.mod h1:Lv2eEtsAtbRFlF8hjH82L8SIGoUNgemMVoKoB6A9THk=
github.com/pb33f/libopenapi v0.13.17 h1:FbY5Nx3xmALZf7TtdITXRSyeNZhB7U3COYbkAisX2eY=
github.com/pb33f/libopenapi v0.13.17/go.mod h1:Lv2eEtsAtbRFlF8hjH82L8SIGoUNgemMVoKoB6A9THk=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/santhosh-tekuri/jsonschema/v5 v5.3.1 h1:lZUw3E0/J3roVtGQ+SCrUrg3ON6NgVqpn3+iol9aGu4=
Expand Down
41 changes: 41 additions & 0 deletions helpers/parameter_utilities.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ package helpers

import (
"fmt"
"github.com/pb33f/libopenapi/datamodel/high/base"
v3 "github.com/pb33f/libopenapi/datamodel/high/v3"
"net/http"
"strconv"
Expand Down Expand Up @@ -61,6 +62,46 @@ func ExtractParamsForOperation(request *http.Request, item *v3.PathItem) []*v3.P
return params
}

// ExtractSecurityForOperation will extract the security requirements for the operation based on the request method.
func ExtractSecurityForOperation(request *http.Request, item *v3.PathItem) []*base.SecurityRequirement {
var schemes []*base.SecurityRequirement
switch request.Method {
case http.MethodGet:
if item.Get != nil {
schemes = append(schemes, item.Get.Security...)
}
case http.MethodPost:
if item.Post != nil {
schemes = append(schemes, item.Post.Security...)
}
case http.MethodPut:
if item.Put != nil {
schemes = append(schemes, item.Put.Security...)
}
case http.MethodDelete:
if item.Delete != nil {
schemes = append(schemes, item.Delete.Security...)
}
case http.MethodOptions:
if item.Options != nil {
schemes = append(schemes, item.Options.Security...)
}
case http.MethodHead:
if item.Head != nil {
schemes = append(schemes, item.Head.Security...)
}
case http.MethodPatch:
if item.Patch != nil {
schemes = append(schemes, item.Patch.Security...)
}
case http.MethodTrace:
if item.Trace != nil {
schemes = append(schemes, item.Trace.Security...)
}
}
return schemes
}

func cast(v string) any {

if v == "true" || v == "false" {
Expand Down
4 changes: 4 additions & 0 deletions parameters/parameters.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,10 @@ type ParameterValidator interface {
// ValidatePathParams validates the path parameters contained within *http.Request. It returns a boolean stating true
// if validation passed (false for failed), and a slice of errors if validation failed.
ValidatePathParams(request *http.Request) (bool, []*errors.ValidationError)

// ValidateSecurity validates the security requirements for the operation. It returns a boolean stating true
// if validation passed (false for failed), and a slice of errors if validation failed.
ValidateSecurity(request *http.Request) (bool, []*errors.ValidationError)
}

func (v *paramValidator) SetPathItem(path *v3.PathItem, pathValue string) {
Expand Down
132 changes: 132 additions & 0 deletions parameters/validate_security.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
// Copyright 2023 Princess B33f Heavy Industries / Dave Shanley
// SPDX-License-Identifier: MIT

package parameters

import (
"fmt"
"github.com/pb33f/libopenapi-validator/errors"
"github.com/pb33f/libopenapi-validator/helpers"
"github.com/pb33f/libopenapi-validator/paths"
v3 "github.com/pb33f/libopenapi/datamodel/high/v3"
"net/http"
"strings"
)

func (v *paramValidator) ValidateSecurity(request *http.Request) (bool, []*errors.ValidationError) {

// find path
var pathItem *v3.PathItem
var errs []*errors.ValidationError
if v.pathItem == nil {
pathItem, errs, _ = paths.FindPath(request, v.document)
if pathItem == nil || errs != nil {
v.errors = errs
return false, errs
}
} else {
pathItem = v.pathItem
}

// extract security for the operation
security := helpers.ExtractSecurityForOperation(request, pathItem)

if security == nil {
return true, nil
}

for _, sec := range security {
for secName, _ := range sec.Requirements {

// look up security from components
secScheme := v.document.Components.SecuritySchemes[secName]
if secScheme != nil {

switch strings.ToLower(secScheme.Type) {
case "http":
switch strings.ToLower(secScheme.Scheme) {
case "basic", "bearer", "digest":
// check for an authorization header
if request.Header.Get("Authorization") == "" {
return false, []*errors.ValidationError{
{
Message: fmt.Sprintf("Authorization header for '%s' scheme", secScheme.Scheme),
Reason: "Authorization header was not found",
ValidationType: "security",
ValidationSubType: secScheme.Scheme,
SpecLine: sec.GoLow().Requirements.ValueNode.Line,
SpecCol: sec.GoLow().Requirements.ValueNode.Column,
HowToFix: "Add an 'Authorization' header to this request",
},
}
}
}

case "apikey":
// check if the api key is in the request
if secScheme.In == "header" {
if request.Header.Get(secScheme.Name) == "" {
return false, []*errors.ValidationError{
{
Message: fmt.Sprintf("API Key %s not found in header", secScheme.Name),
Reason: "API Key not found in http header for security scheme 'apiKey' with type 'header'",
ValidationType: "security",
ValidationSubType: "apiKey",
SpecLine: sec.GoLow().Requirements.ValueNode.Line,
SpecCol: sec.GoLow().Requirements.ValueNode.Column,
HowToFix: fmt.Sprintf("Add the API Key via '%s' as a header of the request", secScheme.Name),
},
}
}
}
if secScheme.In == "query" {
if request.URL.Query().Get(secScheme.Name) == "" {
copyUrl := *request.URL
fixed := &copyUrl
q := fixed.Query()
q.Add(secScheme.Name, "your-api-key")
fixed.RawQuery = q.Encode()

return false, []*errors.ValidationError{
{
Message: fmt.Sprintf("API Key %s not found in query", secScheme.Name),
Reason: "API Key not found in URL query for security scheme 'apiKey' with type 'query'",
ValidationType: "security",
ValidationSubType: "apiKey",
SpecLine: sec.GoLow().Requirements.ValueNode.Line,
SpecCol: sec.GoLow().Requirements.ValueNode.Column,
HowToFix: fmt.Sprintf("Add an API Key via '%s' to the query string "+
"of the URL, for example '%s'", secScheme.Name, fixed.String()),
},
}
}
}
if secScheme.In == "cookie" {
cookies := request.Cookies()
cookieFound := false
for _, cookie := range cookies {
if cookie.Name == secScheme.Name {
cookieFound = true
break
}
}
if !cookieFound {
return false, []*errors.ValidationError{
{
Message: fmt.Sprintf("API Key %s not found in cookies", secScheme.Name),
Reason: "API Key not found in http request cookies for security scheme 'apiKey' with type 'cookie'",
ValidationType: "security",
ValidationSubType: "apiKey",
SpecLine: sec.GoLow().Requirements.ValueNode.Line,
SpecCol: sec.GoLow().Requirements.ValueNode.Column,
HowToFix: fmt.Sprintf("Submit an API Key '%s' as a cookie with the request", secScheme.Name),
},
}
}
}
}
}
}
}
return true, nil
}
Loading

0 comments on commit fbd40b0

Please sign in to comment.