Skip to content

Commit

Permalink
Add support for multiple possible security schemes without requiring …
Browse files Browse the repository at this point in the history
…they're all true
  • Loading branch information
AidanWelch authored and daveshanley committed Oct 16, 2024
1 parent 0a73ef5 commit c4e3ad1
Show file tree
Hide file tree
Showing 2 changed files with 140 additions and 12 deletions.
31 changes: 21 additions & 10 deletions parameters/validate_security.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@ import (

"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"
"github.com/pb33f/libopenapi/orderedmap"
"github.com/pb33f/libopenapi-validator/paths"
)

func (v *paramValidator) ValidateSecurity(request *http.Request) (bool, []*errors.ValidationError) {
Expand Down Expand Up @@ -44,7 +44,13 @@ func (v *paramValidator) ValidateSecurityWithPathItem(request *http.Request, pat
return true, nil
}

allErrors := []*errors.ValidationError{}

for _, sec := range security {
if sec.ContainsEmptyRequirement {
return true, nil
}

for pair := orderedmap.First(sec.Requirements); pair != nil; pair = pair.Next() {
secName := pair.Key()

Expand Down Expand Up @@ -85,8 +91,9 @@ func (v *paramValidator) ValidateSecurityWithPathItem(request *http.Request, pat
}

errors.PopulateValidationErrors(validationErrors, request, pathValue)

return false, validationErrors
allErrors = append(allErrors, validationErrors...)
} else {
return true, nil
}
}

Expand All @@ -107,8 +114,9 @@ func (v *paramValidator) ValidateSecurityWithPathItem(request *http.Request, pat
}

errors.PopulateValidationErrors(validationErrors, request, pathValue)

return false, validationErrors
allErrors = append(allErrors, validationErrors...)
} else {
return true, nil
}
}
if secScheme.In == "query" {
Expand All @@ -133,8 +141,9 @@ func (v *paramValidator) ValidateSecurityWithPathItem(request *http.Request, pat
}

errors.PopulateValidationErrors(validationErrors, request, pathValue)

return false, validationErrors
allErrors = append(allErrors, validationErrors...)
} else {
return true, nil
}
}
if secScheme.In == "cookie" {
Expand All @@ -160,12 +169,14 @@ func (v *paramValidator) ValidateSecurityWithPathItem(request *http.Request, pat
}

errors.PopulateValidationErrors(validationErrors, request, pathValue)

return false, validationErrors
allErrors = append(allErrors, validationErrors...)
} else {
return true, nil
}
}
}
}
}
return true, nil

return false, allErrors
}
121 changes: 119 additions & 2 deletions parameters/validate_security_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,12 @@
package parameters

import (
"net/http"
"testing"

"github.com/pb33f/libopenapi"
"github.com/pb33f/libopenapi-validator/paths"
"github.com/stretchr/testify/assert"
"net/http"
"testing"
)

func TestParamValidator_ValidateSecurity_APIKeyHeader_NotFound(t *testing.T) {
Expand Down Expand Up @@ -398,3 +399,119 @@ paths:
assert.Len(t, errors, 1)
assert.Equal(t, "POST Path '/beef' not found", errors[0].Message)
}

func TestParamValidator_ValidateSecurity_MultipleSecurity(t *testing.T) {

spec := `openapi: 3.1.0
paths:
/products:
post:
security:
- ApiKeyAuthQuery:
- write:products
- ApiKeyAuthHeader:
- write:products
components:
securitySchemes:
ApiKeyAuthQuery:
type: apiKey
in: query
name: X-API-Key
ApiKeyAuthHeader:
type: apiKey
in: header
name: X-API-Key
`

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

m, _ := doc.BuildV3Model()

v := NewParameterValidator(&m.Model)

request, _ := http.NewRequest(http.MethodPost, "https://things.com/products", nil)
request.Header.Add("X-API-Key", "1234")

valid, errors := v.ValidateSecurity(request)
assert.True(t, valid)
assert.Equal(t, 0, len(errors))
}

func TestParamValidator_ValidateSecurity_MultipleSecurity_EmptyOption(t *testing.T) {

spec := `openapi: 3.1.0
paths:
/products:
post:
security:
- ApiKeyAuth:
- write:products
- {}
components:
securitySchemes:
ApiKeyAuth:
type: apiKey
in: header
name: X-API-Key
`

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

m, _ := doc.BuildV3Model()

v := NewParameterValidator(&m.Model)

request, _ := http.NewRequest(http.MethodPost, "https://things.com/products", nil)

valid, errors := v.ValidateSecurity(request)
assert.True(t, valid)
assert.Equal(t, 0, len(errors))
}

func TestParamValidator_ValidateSecurity_MultipleSecurity_NotFound(t *testing.T) {

spec := `openapi: 3.1.0
paths:
/products:
post:
security:
- ApiKeyAuthQuery:
- write:products
- ApiKeyAuthHeader:
- write:products
components:
securitySchemes:
ApiKeyAuthQuery:
type: apiKey
in: query
name: X-API-Key
ApiKeyAuthHeader:
type: apiKey
in: header
name: X-API-Key
`

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

m, _ := doc.BuildV3Model()

v := NewParameterValidator(&m.Model)

request, _ := http.NewRequest(http.MethodPost, "https://things.com/products", nil)

valid, errors := v.ValidateSecurity(request)
assert.False(t, valid)
assert.Equal(t, 2, len(errors))

assert.Equal(t, "API Key X-API-Key not found in query", errors[0].Message)
assert.Equal(t, "Add an API Key via 'X-API-Key' to the query string of the URL, "+
"for example 'https://things.com/products?X-API-Key=your-api-key'", errors[0].HowToFix)
assert.Equal(t, request.Method, errors[0].RequestMethod)
assert.Equal(t, request.URL.Path, errors[0].RequestPath)
assert.Equal(t, "/products", errors[0].SpecPath)

assert.Equal(t, "API Key X-API-Key not found in header", errors[1].Message)
assert.Equal(t, request.Method, errors[1].RequestMethod)
assert.Equal(t, request.URL.Path, errors[1].RequestPath)
assert.Equal(t, "/products", errors[1].SpecPath)
}

0 comments on commit c4e3ad1

Please sign in to comment.