Skip to content

Commit

Permalink
pathItem as parameter to avoid race conditions
Browse files Browse the repository at this point in the history
Signed-off-by: Emilien Puget <emilien.puget@numspot.com>
  • Loading branch information
emilien-puget authored and daveshanley committed Jul 9, 2024
1 parent 8c66162 commit 696235e
Show file tree
Hide file tree
Showing 19 changed files with 562 additions and 268 deletions.
38 changes: 21 additions & 17 deletions parameters/cookie_parameters.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,32 +7,36 @@ import (
"fmt"
"github.com/pb33f/libopenapi-validator/errors"
"github.com/pb33f/libopenapi-validator/helpers"
"github.com/pb33f/libopenapi-validator/paths"
"github.com/pb33f/libopenapi/datamodel/high/base"
v3 "github.com/pb33f/libopenapi/datamodel/high/v3"
"net/http"
"strconv"
"strings"
"github.com/pb33f/libopenapi-validator/paths"
)

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

// find path
var pathItem *v3.PathItem
var foundPath string
var errs []*errors.ValidationError

if v.pathItem == nil {
pathItem, errs, foundPath = paths.FindPath(request, v.document)
if pathItem == nil || errs != nil {
v.errors = errs
return false, errs
}
} else {
pathItem = v.pathItem
foundPath = v.pathValue
pathItem, errs, foundPath := paths.FindPath(request, v.document)
if len(errs) > 0 {
return false, errs
}
return v.ValidateCookieParamsWithPathItem(request, pathItem, foundPath)
}

func (v *paramValidator) ValidateCookieParamsWithPathItem(request *http.Request, pathItem *v3.PathItem, pathValue string) (bool, []*errors.ValidationError) {
if pathItem == nil {
return false, []*errors.ValidationError{{
ValidationType: helpers.ParameterValidationPath,
ValidationSubType: "missing",
Message: fmt.Sprintf("%s Path '%s' not found", request.Method, request.URL.Path),
Reason: fmt.Sprintf("The %s request contains a path of '%s' "+
"however that path, or the %s method for that path does not exist in the specification",
request.Method, request.URL.Path, request.Method),
SpecLine: -1,
SpecCol: -1,
HowToFix: errors.HowToFixPath,
}}
}
// extract params for the operation
var params = helpers.ExtractParamsForOperation(request, pathItem)
var validationErrors []*errors.ValidationError
Expand Down Expand Up @@ -125,7 +129,7 @@ func (v *paramValidator) ValidateCookieParams(request *http.Request) (bool, []*e
}
}

errors.PopulateValidationErrors(validationErrors, request, foundPath)
errors.PopulateValidationErrors(validationErrors, request, pathValue)

if len(validationErrors) > 0 {
return false, validationErrors
Expand Down
34 changes: 32 additions & 2 deletions parameters/cookie_parameters_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -550,11 +550,41 @@ paths:

// preset the path
path, _, pv := paths.FindPath(request, &m.Model)
v.SetPathItem(path, pv)

valid, errors := v.ValidateCookieParams(request)
valid, errors := v.ValidateCookieParamsWithPathItem(request, path, pv)

assert.False(t, valid)
assert.Len(t, errors, 1)
assert.Equal(t, "Instead of '2500', use one of the allowed values: '1, 2, 99'", errors[0].HowToFix)
}

func TestNewValidator_PresetPath_notfound(t *testing.T) {

spec := `openapi: 3.1.0
paths:
/burgers/beef:
get:
parameters:
- name: PattyPreference
in: cookie
required: true
schema:
type: integer
enum: [1, 2, 99]`

doc, _ := libopenapi.NewDocument([]byte(spec))
m, _ := doc.BuildV3Model()
v := NewParameterValidator(&m.Model)

request, _ := http.NewRequest(http.MethodGet, "https://things.com/pizza/beef", nil)
request.AddCookie(&http.Cookie{Name: "PattyPreference", Value: "2500"}) // too many dude.

// preset the path
path, _, pv := paths.FindPath(request, &m.Model)

valid, errors := v.ValidateCookieParamsWithPathItem(request, path, pv)

assert.False(t, valid)
assert.Len(t, errors, 1)
assert.Equal(t, "GET Path '/pizza/beef' not found", errors[0].Message)
}
36 changes: 21 additions & 15 deletions parameters/header_parameters.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,27 +11,33 @@ import (

"github.com/pb33f/libopenapi-validator/errors"
"github.com/pb33f/libopenapi-validator/helpers"
"github.com/pb33f/libopenapi-validator/paths"
"github.com/pb33f/libopenapi/datamodel/high/base"
v3 "github.com/pb33f/libopenapi/datamodel/high/v3"
"github.com/pb33f/libopenapi-validator/paths"
)

func (v *paramValidator) ValidateHeaderParams(request *http.Request) (bool, []*errors.ValidationError) {
// find path
var pathItem *v3.PathItem
var specPath string
var errs []*errors.ValidationError
if v.pathItem == nil {
pathItem, errs, specPath = paths.FindPath(request, v.document)
if pathItem == nil || errs != nil {
v.errors = errs
return false, errs
}
} else {
pathItem = v.pathItem
specPath = v.pathValue
pathItem, errs, foundPath := paths.FindPath(request, v.document)
if len(errs) > 0 {
return false, errs
}
return v.ValidateHeaderParamsWithPathItem(request, pathItem, foundPath)
}

func (v *paramValidator) ValidateHeaderParamsWithPathItem(request *http.Request, pathItem *v3.PathItem, pathValue string) (bool, []*errors.ValidationError) {
if pathItem == nil {
return false, []*errors.ValidationError{{
ValidationType: helpers.ParameterValidationPath,
ValidationSubType: "missing",
Message: fmt.Sprintf("%s Path '%s' not found", request.Method, request.URL.Path),
Reason: fmt.Sprintf("The %s request contains a path of '%s' "+
"however that path, or the %s method for that path does not exist in the specification",
request.Method, request.URL.Path, request.Method),
SpecLine: -1,
SpecCol: -1,
HowToFix: errors.HowToFixPath,
}}
}
// extract params for the operation
params := helpers.ExtractParamsForOperation(request, pathItem)

Expand Down Expand Up @@ -145,7 +151,7 @@ func (v *paramValidator) ValidateHeaderParams(request *http.Request) (bool, []*e
}
}

errors.PopulateValidationErrors(validationErrors, request, specPath)
errors.PopulateValidationErrors(validationErrors, request, pathValue)

if len(validationErrors) > 0 {
return false, validationErrors
Expand Down
34 changes: 32 additions & 2 deletions parameters/header_parameters_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -624,12 +624,42 @@ paths:

// preset the path
path, _, pv := paths.FindPath(request, &m.Model)
v.SetPathItem(path, pv)

valid, errors := v.ValidateHeaderParams(request)
valid, errors := v.ValidateHeaderParamsWithPathItem(request, path, pv)

assert.False(t, valid)
assert.Len(t, errors, 1)
assert.Equal(t, "Instead of '1200', "+
"use one of the allowed values: '1, 2, 99'", errors[0].HowToFix)
}

func TestNewValidator_HeaderParamSetPath_notfound(t *testing.T) {

spec := `openapi: 3.1.0
paths:
/vending/drinks:
get:
parameters:
- name: coffeeCups
in: header
required: true
schema:
type: integer
enum: [1,2,99]`

doc, _ := libopenapi.NewDocument([]byte(spec))
m, _ := doc.BuildV3Model()
v := NewParameterValidator(&m.Model)

request, _ := http.NewRequest(http.MethodGet, "https://things.com/buying/drinks", nil)
request.Header.Set("coffeecups", "1200") // that's a lot of cups dude, we only have one dishwasher.

// preset the path
path, _, pv := paths.FindPath(request, &m.Model)

valid, errors := v.ValidateHeaderParamsWithPathItem(request, path, pv)

assert.False(t, valid)
assert.Len(t, errors, 1)
assert.Equal(t, "GET Path '/buying/drinks' not found", errors[0].Message)
}
36 changes: 22 additions & 14 deletions parameters/parameters.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,42 +14,53 @@ import (
//
// ValidateQueryParams will validate the query parameters for the request
// ValidateHeaderParams will validate the header parameters for the request
// ValidateCookieParams will validate the cookie parameters for the request
// ValidateCookieParamsWithPathItem will validate the cookie parameters for the request
// ValidatePathParams will validate the path parameters for the request
//
// Each method accepts an *http.Request and returns true if validation passed,
// false if validation failed and a slice of ValidationError pointers.
type ParameterValidator interface {

// SetPathItem will set the pathItem for the ParameterValidator, all validations will be performed against this pathItem
// otherwise if not set, each validation will perform a lookup for the pathItem based on the *http.Request
SetPathItem(path *v3.PathItem, pathValue string)

// ValidateQueryParams accepts an *http.Request and validates the query parameters against the OpenAPI specification.
// The method will locate the correct path, and operation, based on the verb. The parameters for the operation
// will be matched and validated against what has been supplied in the http.Request query string.
ValidateQueryParams(request *http.Request) (bool, []*errors.ValidationError)

// ValidateQueryParamsWithPathItem accepts an *http.Request and validates the query parameters against the OpenAPI specification.
// The method will locate the correct path, and operation, based on the verb. The parameters for the operation
// will be matched and validated against what has been supplied in the http.Request query string.
ValidateQueryParamsWithPathItem(request *http.Request, pathItem *v3.PathItem, pathValue string) (bool, []*errors.ValidationError)

// ValidateHeaderParams validates the header 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.
ValidateHeaderParams(request *http.Request) (bool, []*errors.ValidationError)

// ValidateHeaderParamsWithPathItem validates the header 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.
ValidateHeaderParamsWithPathItem(request *http.Request, pathItem *v3.PathItem, pathValue string) (bool, []*errors.ValidationError)

// ValidateCookieParams validates the cookie 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.
ValidateCookieParams(request *http.Request) (bool, []*errors.ValidationError)

// ValidateCookieParamsWithPathItem validates the cookie 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.
ValidateCookieParamsWithPathItem(request *http.Request, pathItem *v3.PathItem, pathValue string) (bool, []*errors.ValidationError)

// 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)

// ValidatePathParamsWithPathItem 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.
ValidatePathParamsWithPathItem(request *http.Request, pathItem *v3.PathItem, pathValue string) (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) {
v.pathItem = path
v.pathValue = pathValue
// ValidateSecurityWithPathItem 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.
ValidateSecurityWithPathItem(request *http.Request, pathItem *v3.PathItem, pathValue string) (bool, []*errors.ValidationError)
}

// NewParameterValidator will create a new ParameterValidator from an OpenAPI 3+ document
Expand All @@ -58,8 +69,5 @@ func NewParameterValidator(document *v3.Document) ParameterValidator {
}

type paramValidator struct {
document *v3.Document
pathItem *v3.PathItem
pathValue string
errors []*errors.ValidationError
document *v3.Document
}
37 changes: 21 additions & 16 deletions parameters/path_parameters.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,25 +17,30 @@ import (
)

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

// find path
var pathItem *v3.PathItem
var errs []*errors.ValidationError
var foundPath string
if v.pathItem == nil && v.pathValue == "" {
pathItem, errs, foundPath = paths.FindPath(request, v.document)
if pathItem == nil || errs != nil {
v.errors = errs
return false, errs
}
} else {
pathItem = v.pathItem
foundPath = v.pathValue
pathItem, errs, foundPath := paths.FindPath(request, v.document)
if len(errs) > 0 {
return false, errs
}
return v.ValidatePathParamsWithPathItem(request, pathItem, foundPath)
}

func (v *paramValidator) ValidatePathParamsWithPathItem(request *http.Request, pathItem *v3.PathItem, pathValue string) (bool, []*errors.ValidationError) {
if pathItem == nil {
return false, []*errors.ValidationError{{
ValidationType: helpers.ParameterValidationPath,
ValidationSubType: "missing",
Message: fmt.Sprintf("%s Path '%s' not found", request.Method, request.URL.Path),
Reason: fmt.Sprintf("The %s request contains a path of '%s' "+
"however that path, or the %s method for that path does not exist in the specification",
request.Method, request.URL.Path, request.Method),
SpecLine: -1,
SpecCol: -1,
HowToFix: errors.HowToFixPath,
}}
}
// split the path into segments
submittedSegments := strings.Split(paths.StripRequestPath(request, v.document), helpers.Slash)
pathSegments := strings.Split(foundPath, helpers.Slash)
pathSegments := strings.Split(pathValue, helpers.Slash)

// extract params for the operation
var params = helpers.ExtractParamsForOperation(request, pathItem)
Expand Down Expand Up @@ -283,7 +288,7 @@ func (v *paramValidator) ValidatePathParams(request *http.Request) (bool, []*err
}
}

errors.PopulateValidationErrors(validationErrors, request, foundPath)
errors.PopulateValidationErrors(validationErrors, request, pathValue)

if len(validationErrors) > 0 {
return false, validationErrors
Expand Down
36 changes: 34 additions & 2 deletions parameters/path_parameters_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1397,16 +1397,48 @@ paths:

// preset the path
path, _, pv := paths.FindPath(request, &m.Model)
v.SetPathItem(path, pv)

valid, errors := v.ValidatePathParams(request)
valid, errors := v.ValidatePathParamsWithPathItem(request, path, pv)

assert.False(t, valid)
assert.Len(t, errors, 1)
assert.Equal(t, "Path parameter 'burgerId' does not match allowed values", errors[0].Message)
assert.Equal(t, "Instead of '22334', use one of the allowed values: '1, 2, 99, 100'", errors[0].HowToFix)
}

func TestNewValidator_SetPathForPathParam_notfound(t *testing.T) {

spec := `openapi: 3.1.0
paths:
/burgers/{;burgerId}/locate:
parameters:
- name: burgerId
in: path
style: matrix
schema:
type: number
enum: [1,2,99,100]
get:
operationId: locateBurgers`

doc, _ := libopenapi.NewDocument([]byte(spec))

m, _ := doc.BuildV3Model()

v := NewParameterValidator(&m.Model)

request, _ := http.NewRequest(http.MethodGet, "https://things.com/pizza/;burgerId=22334/locate", nil)

// preset the path
path, _, pv := paths.FindPath(request, &m.Model)

valid, errors := v.ValidatePathParamsWithPathItem(request, path, pv)

assert.False(t, valid)
assert.Len(t, errors, 1)
assert.Equal(t, "GET Path '/pizza/;burgerId=22334/locate' not found", errors[0].Message)
}

func TestNewValidator_ServerPathPrefixInRequestPath(t *testing.T) {

spec := `openapi: 3.1.0
Expand Down
Loading

0 comments on commit 696235e

Please sign in to comment.