Skip to content

Commit 696235e

Browse files
emilien-pugetdaveshanley
authored andcommitted
pathItem as parameter to avoid race conditions
Signed-off-by: Emilien Puget <[email protected]>
1 parent 8c66162 commit 696235e

19 files changed

+562
-268
lines changed

parameters/cookie_parameters.go

+21-17
Original file line numberDiff line numberDiff line change
@@ -7,32 +7,36 @@ import (
77
"fmt"
88
"github.com/pb33f/libopenapi-validator/errors"
99
"github.com/pb33f/libopenapi-validator/helpers"
10-
"github.com/pb33f/libopenapi-validator/paths"
1110
"github.com/pb33f/libopenapi/datamodel/high/base"
1211
v3 "github.com/pb33f/libopenapi/datamodel/high/v3"
1312
"net/http"
1413
"strconv"
1514
"strings"
15+
"github.com/pb33f/libopenapi-validator/paths"
1616
)
1717

1818
func (v *paramValidator) ValidateCookieParams(request *http.Request) (bool, []*errors.ValidationError) {
19-
20-
// find path
21-
var pathItem *v3.PathItem
22-
var foundPath string
23-
var errs []*errors.ValidationError
24-
25-
if v.pathItem == nil {
26-
pathItem, errs, foundPath = paths.FindPath(request, v.document)
27-
if pathItem == nil || errs != nil {
28-
v.errors = errs
29-
return false, errs
30-
}
31-
} else {
32-
pathItem = v.pathItem
33-
foundPath = v.pathValue
19+
pathItem, errs, foundPath := paths.FindPath(request, v.document)
20+
if len(errs) > 0 {
21+
return false, errs
3422
}
23+
return v.ValidateCookieParamsWithPathItem(request, pathItem, foundPath)
24+
}
3525

26+
func (v *paramValidator) ValidateCookieParamsWithPathItem(request *http.Request, pathItem *v3.PathItem, pathValue string) (bool, []*errors.ValidationError) {
27+
if pathItem == nil {
28+
return false, []*errors.ValidationError{{
29+
ValidationType: helpers.ParameterValidationPath,
30+
ValidationSubType: "missing",
31+
Message: fmt.Sprintf("%s Path '%s' not found", request.Method, request.URL.Path),
32+
Reason: fmt.Sprintf("The %s request contains a path of '%s' "+
33+
"however that path, or the %s method for that path does not exist in the specification",
34+
request.Method, request.URL.Path, request.Method),
35+
SpecLine: -1,
36+
SpecCol: -1,
37+
HowToFix: errors.HowToFixPath,
38+
}}
39+
}
3640
// extract params for the operation
3741
var params = helpers.ExtractParamsForOperation(request, pathItem)
3842
var validationErrors []*errors.ValidationError
@@ -125,7 +129,7 @@ func (v *paramValidator) ValidateCookieParams(request *http.Request) (bool, []*e
125129
}
126130
}
127131

128-
errors.PopulateValidationErrors(validationErrors, request, foundPath)
132+
errors.PopulateValidationErrors(validationErrors, request, pathValue)
129133

130134
if len(validationErrors) > 0 {
131135
return false, validationErrors

parameters/cookie_parameters_test.go

+32-2
Original file line numberDiff line numberDiff line change
@@ -550,11 +550,41 @@ paths:
550550

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

555-
valid, errors := v.ValidateCookieParams(request)
554+
valid, errors := v.ValidateCookieParamsWithPathItem(request, path, pv)
556555

557556
assert.False(t, valid)
558557
assert.Len(t, errors, 1)
559558
assert.Equal(t, "Instead of '2500', use one of the allowed values: '1, 2, 99'", errors[0].HowToFix)
560559
}
560+
561+
func TestNewValidator_PresetPath_notfound(t *testing.T) {
562+
563+
spec := `openapi: 3.1.0
564+
paths:
565+
/burgers/beef:
566+
get:
567+
parameters:
568+
- name: PattyPreference
569+
in: cookie
570+
required: true
571+
schema:
572+
type: integer
573+
enum: [1, 2, 99]`
574+
575+
doc, _ := libopenapi.NewDocument([]byte(spec))
576+
m, _ := doc.BuildV3Model()
577+
v := NewParameterValidator(&m.Model)
578+
579+
request, _ := http.NewRequest(http.MethodGet, "https://things.com/pizza/beef", nil)
580+
request.AddCookie(&http.Cookie{Name: "PattyPreference", Value: "2500"}) // too many dude.
581+
582+
// preset the path
583+
path, _, pv := paths.FindPath(request, &m.Model)
584+
585+
valid, errors := v.ValidateCookieParamsWithPathItem(request, path, pv)
586+
587+
assert.False(t, valid)
588+
assert.Len(t, errors, 1)
589+
assert.Equal(t, "GET Path '/pizza/beef' not found", errors[0].Message)
590+
}

parameters/header_parameters.go

+21-15
Original file line numberDiff line numberDiff line change
@@ -11,27 +11,33 @@ import (
1111

1212
"github.com/pb33f/libopenapi-validator/errors"
1313
"github.com/pb33f/libopenapi-validator/helpers"
14-
"github.com/pb33f/libopenapi-validator/paths"
1514
"github.com/pb33f/libopenapi/datamodel/high/base"
1615
v3 "github.com/pb33f/libopenapi/datamodel/high/v3"
16+
"github.com/pb33f/libopenapi-validator/paths"
1717
)
1818

1919
func (v *paramValidator) ValidateHeaderParams(request *http.Request) (bool, []*errors.ValidationError) {
20-
// find path
21-
var pathItem *v3.PathItem
22-
var specPath string
23-
var errs []*errors.ValidationError
24-
if v.pathItem == nil {
25-
pathItem, errs, specPath = paths.FindPath(request, v.document)
26-
if pathItem == nil || errs != nil {
27-
v.errors = errs
28-
return false, errs
29-
}
30-
} else {
31-
pathItem = v.pathItem
32-
specPath = v.pathValue
20+
pathItem, errs, foundPath := paths.FindPath(request, v.document)
21+
if len(errs) > 0 {
22+
return false, errs
3323
}
24+
return v.ValidateHeaderParamsWithPathItem(request, pathItem, foundPath)
25+
}
3426

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

@@ -145,7 +151,7 @@ func (v *paramValidator) ValidateHeaderParams(request *http.Request) (bool, []*e
145151
}
146152
}
147153

148-
errors.PopulateValidationErrors(validationErrors, request, specPath)
154+
errors.PopulateValidationErrors(validationErrors, request, pathValue)
149155

150156
if len(validationErrors) > 0 {
151157
return false, validationErrors

parameters/header_parameters_test.go

+32-2
Original file line numberDiff line numberDiff line change
@@ -624,12 +624,42 @@ paths:
624624

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

629-
valid, errors := v.ValidateHeaderParams(request)
628+
valid, errors := v.ValidateHeaderParamsWithPathItem(request, path, pv)
630629

631630
assert.False(t, valid)
632631
assert.Len(t, errors, 1)
633632
assert.Equal(t, "Instead of '1200', "+
634633
"use one of the allowed values: '1, 2, 99'", errors[0].HowToFix)
635634
}
635+
636+
func TestNewValidator_HeaderParamSetPath_notfound(t *testing.T) {
637+
638+
spec := `openapi: 3.1.0
639+
paths:
640+
/vending/drinks:
641+
get:
642+
parameters:
643+
- name: coffeeCups
644+
in: header
645+
required: true
646+
schema:
647+
type: integer
648+
enum: [1,2,99]`
649+
650+
doc, _ := libopenapi.NewDocument([]byte(spec))
651+
m, _ := doc.BuildV3Model()
652+
v := NewParameterValidator(&m.Model)
653+
654+
request, _ := http.NewRequest(http.MethodGet, "https://things.com/buying/drinks", nil)
655+
request.Header.Set("coffeecups", "1200") // that's a lot of cups dude, we only have one dishwasher.
656+
657+
// preset the path
658+
path, _, pv := paths.FindPath(request, &m.Model)
659+
660+
valid, errors := v.ValidateHeaderParamsWithPathItem(request, path, pv)
661+
662+
assert.False(t, valid)
663+
assert.Len(t, errors, 1)
664+
assert.Equal(t, "GET Path '/buying/drinks' not found", errors[0].Message)
665+
}

parameters/parameters.go

+22-14
Original file line numberDiff line numberDiff line change
@@ -14,42 +14,53 @@ import (
1414
//
1515
// ValidateQueryParams will validate the query parameters for the request
1616
// ValidateHeaderParams will validate the header parameters for the request
17-
// ValidateCookieParams will validate the cookie parameters for the request
17+
// ValidateCookieParamsWithPathItem will validate the cookie parameters for the request
1818
// ValidatePathParams will validate the path parameters for the request
1919
//
2020
// Each method accepts an *http.Request and returns true if validation passed,
2121
// false if validation failed and a slice of ValidationError pointers.
2222
type ParameterValidator interface {
23-
24-
// SetPathItem will set the pathItem for the ParameterValidator, all validations will be performed against this pathItem
25-
// otherwise if not set, each validation will perform a lookup for the pathItem based on the *http.Request
26-
SetPathItem(path *v3.PathItem, pathValue string)
27-
2823
// ValidateQueryParams accepts an *http.Request and validates the query parameters against the OpenAPI specification.
2924
// The method will locate the correct path, and operation, based on the verb. The parameters for the operation
3025
// will be matched and validated against what has been supplied in the http.Request query string.
3126
ValidateQueryParams(request *http.Request) (bool, []*errors.ValidationError)
3227

28+
// ValidateQueryParamsWithPathItem accepts an *http.Request and validates the query parameters against the OpenAPI specification.
29+
// The method will locate the correct path, and operation, based on the verb. The parameters for the operation
30+
// will be matched and validated against what has been supplied in the http.Request query string.
31+
ValidateQueryParamsWithPathItem(request *http.Request, pathItem *v3.PathItem, pathValue string) (bool, []*errors.ValidationError)
32+
3333
// ValidateHeaderParams validates the header parameters contained within *http.Request. It returns a boolean
3434
// stating true if validation passed (false for failed), and a slice of errors if validation failed.
3535
ValidateHeaderParams(request *http.Request) (bool, []*errors.ValidationError)
3636

37+
// ValidateHeaderParamsWithPathItem validates the header parameters contained within *http.Request. It returns a boolean
38+
// stating true if validation passed (false for failed), and a slice of errors if validation failed.
39+
ValidateHeaderParamsWithPathItem(request *http.Request, pathItem *v3.PathItem, pathValue string) (bool, []*errors.ValidationError)
40+
3741
// ValidateCookieParams validates the cookie parameters contained within *http.Request.
3842
// It returns a boolean stating true if validation passed (false for failed), and a slice of errors if validation failed.
3943
ValidateCookieParams(request *http.Request) (bool, []*errors.ValidationError)
4044

45+
// ValidateCookieParamsWithPathItem validates the cookie parameters contained within *http.Request.
46+
// It returns a boolean stating true if validation passed (false for failed), and a slice of errors if validation failed.
47+
ValidateCookieParamsWithPathItem(request *http.Request, pathItem *v3.PathItem, pathValue string) (bool, []*errors.ValidationError)
48+
4149
// ValidatePathParams validates the path parameters contained within *http.Request. It returns a boolean stating true
4250
// if validation passed (false for failed), and a slice of errors if validation failed.
4351
ValidatePathParams(request *http.Request) (bool, []*errors.ValidationError)
4452

53+
// ValidatePathParamsWithPathItem validates the path parameters contained within *http.Request. It returns a boolean stating true
54+
// if validation passed (false for failed), and a slice of errors if validation failed.
55+
ValidatePathParamsWithPathItem(request *http.Request, pathItem *v3.PathItem, pathValue string) (bool, []*errors.ValidationError)
56+
4557
// ValidateSecurity validates the security requirements for the operation. It returns a boolean stating true
4658
// if validation passed (false for failed), and a slice of errors if validation failed.
4759
ValidateSecurity(request *http.Request) (bool, []*errors.ValidationError)
48-
}
4960

50-
func (v *paramValidator) SetPathItem(path *v3.PathItem, pathValue string) {
51-
v.pathItem = path
52-
v.pathValue = pathValue
61+
// ValidateSecurityWithPathItem validates the security requirements for the operation. It returns a boolean stating true
62+
// if validation passed (false for failed), and a slice of errors if validation failed.
63+
ValidateSecurityWithPathItem(request *http.Request, pathItem *v3.PathItem, pathValue string) (bool, []*errors.ValidationError)
5364
}
5465

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

6071
type paramValidator struct {
61-
document *v3.Document
62-
pathItem *v3.PathItem
63-
pathValue string
64-
errors []*errors.ValidationError
72+
document *v3.Document
6573
}

parameters/path_parameters.go

+21-16
Original file line numberDiff line numberDiff line change
@@ -17,25 +17,30 @@ import (
1717
)
1818

1919
func (v *paramValidator) ValidatePathParams(request *http.Request) (bool, []*errors.ValidationError) {
20-
21-
// find path
22-
var pathItem *v3.PathItem
23-
var errs []*errors.ValidationError
24-
var foundPath string
25-
if v.pathItem == nil && v.pathValue == "" {
26-
pathItem, errs, foundPath = paths.FindPath(request, v.document)
27-
if pathItem == nil || errs != nil {
28-
v.errors = errs
29-
return false, errs
30-
}
31-
} else {
32-
pathItem = v.pathItem
33-
foundPath = v.pathValue
20+
pathItem, errs, foundPath := paths.FindPath(request, v.document)
21+
if len(errs) > 0 {
22+
return false, errs
3423
}
24+
return v.ValidatePathParamsWithPathItem(request, pathItem, foundPath)
25+
}
3526

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

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

286-
errors.PopulateValidationErrors(validationErrors, request, foundPath)
291+
errors.PopulateValidationErrors(validationErrors, request, pathValue)
287292

288293
if len(validationErrors) > 0 {
289294
return false, validationErrors

parameters/path_parameters_test.go

+34-2
Original file line numberDiff line numberDiff line change
@@ -1397,16 +1397,48 @@ paths:
13971397

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

1402-
valid, errors := v.ValidatePathParams(request)
1401+
valid, errors := v.ValidatePathParamsWithPathItem(request, path, pv)
14031402

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

1409+
func TestNewValidator_SetPathForPathParam_notfound(t *testing.T) {
1410+
1411+
spec := `openapi: 3.1.0
1412+
paths:
1413+
/burgers/{;burgerId}/locate:
1414+
parameters:
1415+
- name: burgerId
1416+
in: path
1417+
style: matrix
1418+
schema:
1419+
type: number
1420+
enum: [1,2,99,100]
1421+
get:
1422+
operationId: locateBurgers`
1423+
1424+
doc, _ := libopenapi.NewDocument([]byte(spec))
1425+
1426+
m, _ := doc.BuildV3Model()
1427+
1428+
v := NewParameterValidator(&m.Model)
1429+
1430+
request, _ := http.NewRequest(http.MethodGet, "https://things.com/pizza/;burgerId=22334/locate", nil)
1431+
1432+
// preset the path
1433+
path, _, pv := paths.FindPath(request, &m.Model)
1434+
1435+
valid, errors := v.ValidatePathParamsWithPathItem(request, path, pv)
1436+
1437+
assert.False(t, valid)
1438+
assert.Len(t, errors, 1)
1439+
assert.Equal(t, "GET Path '/pizza/;burgerId=22334/locate' not found", errors[0].Message)
1440+
}
1441+
14101442
func TestNewValidator_ServerPathPrefixInRequestPath(t *testing.T) {
14111443

14121444
spec := `openapi: 3.1.0

0 commit comments

Comments
 (0)