Skip to content

Commit fbd40b0

Browse files
committed
Added security vallidation feature
now the validator will check for HTTP and APIKey authentication on operations. Signed-off-by: Dave Shanley <[email protected]>
1 parent bde416a commit fbd40b0

File tree

10 files changed

+543
-6
lines changed

10 files changed

+543
-6
lines changed

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ module github.com/pb33f/libopenapi-validator
33
go 1.21
44

55
require (
6-
github.com/pb33f/libopenapi v0.13.16
6+
github.com/pb33f/libopenapi v0.13.17
77
github.com/santhosh-tekuri/jsonschema/v5 v5.3.1
88
github.com/stretchr/testify v1.8.4
99
github.com/vmware-labs/yaml-jsonpath v0.3.2

go.sum

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,8 +47,8 @@ github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1y
4747
github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY=
4848
github.com/onsi/gomega v1.19.0 h1:4ieX6qQjPP/BfC3mpsAtIGGlxTWPeA3Inl/7DtXw1tw=
4949
github.com/onsi/gomega v1.19.0/go.mod h1:LY+I3pBVzYsTBU1AnDwOSxaYi9WoWiqgwooUqq9yPro=
50-
github.com/pb33f/libopenapi v0.13.16 h1:w1A2zJidQEISPHibDkYRCUhnxRg7R45ZW8hwD2b+wLo=
51-
github.com/pb33f/libopenapi v0.13.16/go.mod h1:Lv2eEtsAtbRFlF8hjH82L8SIGoUNgemMVoKoB6A9THk=
50+
github.com/pb33f/libopenapi v0.13.17 h1:FbY5Nx3xmALZf7TtdITXRSyeNZhB7U3COYbkAisX2eY=
51+
github.com/pb33f/libopenapi v0.13.17/go.mod h1:Lv2eEtsAtbRFlF8hjH82L8SIGoUNgemMVoKoB6A9THk=
5252
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
5353
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
5454
github.com/santhosh-tekuri/jsonschema/v5 v5.3.1 h1:lZUw3E0/J3roVtGQ+SCrUrg3ON6NgVqpn3+iol9aGu4=

helpers/parameter_utilities.go

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ package helpers
55

66
import (
77
"fmt"
8+
"github.com/pb33f/libopenapi/datamodel/high/base"
89
v3 "github.com/pb33f/libopenapi/datamodel/high/v3"
910
"net/http"
1011
"strconv"
@@ -61,6 +62,46 @@ func ExtractParamsForOperation(request *http.Request, item *v3.PathItem) []*v3.P
6162
return params
6263
}
6364

65+
// ExtractSecurityForOperation will extract the security requirements for the operation based on the request method.
66+
func ExtractSecurityForOperation(request *http.Request, item *v3.PathItem) []*base.SecurityRequirement {
67+
var schemes []*base.SecurityRequirement
68+
switch request.Method {
69+
case http.MethodGet:
70+
if item.Get != nil {
71+
schemes = append(schemes, item.Get.Security...)
72+
}
73+
case http.MethodPost:
74+
if item.Post != nil {
75+
schemes = append(schemes, item.Post.Security...)
76+
}
77+
case http.MethodPut:
78+
if item.Put != nil {
79+
schemes = append(schemes, item.Put.Security...)
80+
}
81+
case http.MethodDelete:
82+
if item.Delete != nil {
83+
schemes = append(schemes, item.Delete.Security...)
84+
}
85+
case http.MethodOptions:
86+
if item.Options != nil {
87+
schemes = append(schemes, item.Options.Security...)
88+
}
89+
case http.MethodHead:
90+
if item.Head != nil {
91+
schemes = append(schemes, item.Head.Security...)
92+
}
93+
case http.MethodPatch:
94+
if item.Patch != nil {
95+
schemes = append(schemes, item.Patch.Security...)
96+
}
97+
case http.MethodTrace:
98+
if item.Trace != nil {
99+
schemes = append(schemes, item.Trace.Security...)
100+
}
101+
}
102+
return schemes
103+
}
104+
64105
func cast(v string) any {
65106

66107
if v == "true" || v == "false" {

parameters/parameters.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,10 @@ type ParameterValidator interface {
4141
// ValidatePathParams validates the path parameters contained within *http.Request. It returns a boolean stating true
4242
// if validation passed (false for failed), and a slice of errors if validation failed.
4343
ValidatePathParams(request *http.Request) (bool, []*errors.ValidationError)
44+
45+
// ValidateSecurity validates the security requirements for the operation. It returns a boolean stating true
46+
// if validation passed (false for failed), and a slice of errors if validation failed.
47+
ValidateSecurity(request *http.Request) (bool, []*errors.ValidationError)
4448
}
4549

4650
func (v *paramValidator) SetPathItem(path *v3.PathItem, pathValue string) {

parameters/validate_security.go

Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
// Copyright 2023 Princess B33f Heavy Industries / Dave Shanley
2+
// SPDX-License-Identifier: MIT
3+
4+
package parameters
5+
6+
import (
7+
"fmt"
8+
"github.com/pb33f/libopenapi-validator/errors"
9+
"github.com/pb33f/libopenapi-validator/helpers"
10+
"github.com/pb33f/libopenapi-validator/paths"
11+
v3 "github.com/pb33f/libopenapi/datamodel/high/v3"
12+
"net/http"
13+
"strings"
14+
)
15+
16+
func (v *paramValidator) ValidateSecurity(request *http.Request) (bool, []*errors.ValidationError) {
17+
18+
// find path
19+
var pathItem *v3.PathItem
20+
var errs []*errors.ValidationError
21+
if v.pathItem == nil {
22+
pathItem, errs, _ = paths.FindPath(request, v.document)
23+
if pathItem == nil || errs != nil {
24+
v.errors = errs
25+
return false, errs
26+
}
27+
} else {
28+
pathItem = v.pathItem
29+
}
30+
31+
// extract security for the operation
32+
security := helpers.ExtractSecurityForOperation(request, pathItem)
33+
34+
if security == nil {
35+
return true, nil
36+
}
37+
38+
for _, sec := range security {
39+
for secName, _ := range sec.Requirements {
40+
41+
// look up security from components
42+
secScheme := v.document.Components.SecuritySchemes[secName]
43+
if secScheme != nil {
44+
45+
switch strings.ToLower(secScheme.Type) {
46+
case "http":
47+
switch strings.ToLower(secScheme.Scheme) {
48+
case "basic", "bearer", "digest":
49+
// check for an authorization header
50+
if request.Header.Get("Authorization") == "" {
51+
return false, []*errors.ValidationError{
52+
{
53+
Message: fmt.Sprintf("Authorization header for '%s' scheme", secScheme.Scheme),
54+
Reason: "Authorization header was not found",
55+
ValidationType: "security",
56+
ValidationSubType: secScheme.Scheme,
57+
SpecLine: sec.GoLow().Requirements.ValueNode.Line,
58+
SpecCol: sec.GoLow().Requirements.ValueNode.Column,
59+
HowToFix: "Add an 'Authorization' header to this request",
60+
},
61+
}
62+
}
63+
}
64+
65+
case "apikey":
66+
// check if the api key is in the request
67+
if secScheme.In == "header" {
68+
if request.Header.Get(secScheme.Name) == "" {
69+
return false, []*errors.ValidationError{
70+
{
71+
Message: fmt.Sprintf("API Key %s not found in header", secScheme.Name),
72+
Reason: "API Key not found in http header for security scheme 'apiKey' with type 'header'",
73+
ValidationType: "security",
74+
ValidationSubType: "apiKey",
75+
SpecLine: sec.GoLow().Requirements.ValueNode.Line,
76+
SpecCol: sec.GoLow().Requirements.ValueNode.Column,
77+
HowToFix: fmt.Sprintf("Add the API Key via '%s' as a header of the request", secScheme.Name),
78+
},
79+
}
80+
}
81+
}
82+
if secScheme.In == "query" {
83+
if request.URL.Query().Get(secScheme.Name) == "" {
84+
copyUrl := *request.URL
85+
fixed := &copyUrl
86+
q := fixed.Query()
87+
q.Add(secScheme.Name, "your-api-key")
88+
fixed.RawQuery = q.Encode()
89+
90+
return false, []*errors.ValidationError{
91+
{
92+
Message: fmt.Sprintf("API Key %s not found in query", secScheme.Name),
93+
Reason: "API Key not found in URL query for security scheme 'apiKey' with type 'query'",
94+
ValidationType: "security",
95+
ValidationSubType: "apiKey",
96+
SpecLine: sec.GoLow().Requirements.ValueNode.Line,
97+
SpecCol: sec.GoLow().Requirements.ValueNode.Column,
98+
HowToFix: fmt.Sprintf("Add an API Key via '%s' to the query string "+
99+
"of the URL, for example '%s'", secScheme.Name, fixed.String()),
100+
},
101+
}
102+
}
103+
}
104+
if secScheme.In == "cookie" {
105+
cookies := request.Cookies()
106+
cookieFound := false
107+
for _, cookie := range cookies {
108+
if cookie.Name == secScheme.Name {
109+
cookieFound = true
110+
break
111+
}
112+
}
113+
if !cookieFound {
114+
return false, []*errors.ValidationError{
115+
{
116+
Message: fmt.Sprintf("API Key %s not found in cookies", secScheme.Name),
117+
Reason: "API Key not found in http request cookies for security scheme 'apiKey' with type 'cookie'",
118+
ValidationType: "security",
119+
ValidationSubType: "apiKey",
120+
SpecLine: sec.GoLow().Requirements.ValueNode.Line,
121+
SpecCol: sec.GoLow().Requirements.ValueNode.Column,
122+
HowToFix: fmt.Sprintf("Submit an API Key '%s' as a cookie with the request", secScheme.Name),
123+
},
124+
}
125+
}
126+
}
127+
}
128+
}
129+
}
130+
}
131+
return true, nil
132+
}

0 commit comments

Comments
 (0)