diff --git a/parameters/validate_security.go b/parameters/validate_security.go index d2e6e9b..a886200 100644 --- a/parameters/validate_security.go +++ b/parameters/validate_security.go @@ -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) { @@ -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() @@ -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 } } @@ -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" { @@ -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" { @@ -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 } diff --git a/parameters/validate_security_test.go b/parameters/validate_security_test.go index 2d68164..121ad58 100644 --- a/parameters/validate_security_test.go +++ b/parameters/validate_security_test.go @@ -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) { @@ -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) +}