Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

pathItem as parameter to avoid race conditions #87

Merged
merged 1 commit into from
Jul 9, 2024
Merged
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
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
Loading