pb33f / libopenapi-validator

OpenAPI validation extension for libopenapi, validate http requests and responses as well as schemas
https://pb33f.io/libopenapi/validation/
Other
55 stars 19 forks source link

A malformed JSON body allows missing required properties to validate... #17

Closed jasonquigleyhpecom closed 1 year ago

jasonquigleyhpecom commented 1 year ago

Hello Dave,

when I pass a malformed JSON body, I can trick the validation to pass a document with missing required properties:

Example code:

package main

import (
    "bytes"
    "flag"
    "fmt"
    "net/http"
    "os"

    "github.com/pb33f/libopenapi"
    "github.com/pb33f/libopenapi-validator/requests"
    "github.com/pb33f/libopenapi/datamodel"
)

func main() {
    var path, payload, specFile string
    flag.StringVar(&path, "path",
        "/burgers/create-burger",
        "request path",
    )
    flag.StringVar(&payload, "payload",
        "burger-with-garbage.json",
        "payload file",
    )
    flag.StringVar(&specFile, "spec",
        "burgers-inline.yaml",
        "spec file",
    )
    flag.Parse()

    fmt.Println("Spec file is:    " + specFile)
    fmt.Println("Path is:         " + path)
    fmt.Println("Payload file is: " + payload)

    // Load an OpenAPI Spec file
    specBytes, err := os.ReadFile(specFile)
    if err != nil {
        panic(err)
    }

    // Load the payload file
    payloadBytes, err := os.ReadFile(payload)
    if err != nil {
        panic(err)
    }

    // Enable file references
    config := datamodel.DocumentConfiguration{
        AllowFileReferences: true,
    }

    // Create a new OpenAPI document using libopenapi
    document, docErrs := libopenapi.NewDocumentWithConfiguration(specBytes, &config)
    if docErrs != nil {
        panic(docErrs)
    }

    // Build a model
    document.SetConfiguration(&datamodel.DocumentConfiguration{AllowFileReferences: true})
    docModel, errors := document.BuildV3Model()
    if len(errors) > 0 {
        for i := range errors {
            fmt.Printf("error: %e\n", errors[i])
        }
        panic(fmt.Sprintf("cannot create v3 model from document: %d errors reported", len(errors)))
    }

    request, err := http.NewRequest(http.MethodPost, "http://localhost"+path, bytes.NewReader(payloadBytes))
    if err != nil {
        panic(err)
    }
    request.Header.Set("Content-Type", "application/json")

    valdtr := requests.NewRequestBodyValidator(&docModel.Model)
    valid, valdtrErrors := valdtr.ValidateRequestBody(request)
    if !valid {
        for _, err := range valdtrErrors {
            for _, reason := range err.SchemaValidationErrors {
                fmt.Printf("-------> %+v\n", reason)
            }
        }
    }

Schema file:

openapi: 3.1.0
info:
  title: Burgers
  license:
    name: License Agreement
    url: https://www.example.com/licensing.html
  version: latest
  description: |
    More burgers!
    A unified API for consuming burgers 
  contact:
    name: Ronald Macdonald
    email: burgers@example.com

servers:
  - url: https://api.example.com
    description: Development environment

externalDocs:
  description: Find out more about burgers
  url: https://www.example.com

security:
  - Bearer: []

paths:
  /burgers/create-burger:
    post:
      operationId: createBurger
      requestBody:
        content:
          application/json:
            schema:
              type: object
              required:
              - name
              - patties
              - vegetarian
              properties:
                name:
                  type: string
                patties:
                  type: integer
                vegetarian:
                  type: boolean
              unevaluatedProperties: false
            examples:
              pbjBurger:
                summary: A horrible, nutty, sticky mess.
                value:
                  name: Peanut And Jelly
                  patties: 3
                  vegetarian: true
        responses:
          '201':
            description: Burger created
            headers:
              Location:
                description: URL for the created burger
                schema:
                  type: string
                  format: uri
                example: burgers/0e7f516c-0829-4135-83d6-09ce844ddd9d

components:
  securitySchemes:
    Bearer:
      description: Uses a token for authorization
      type: http
      scheme: bearer

Malformed payload:

{
    "name": "Garbage burger",
    "patties": 3,
}

Result:

Spec file is:    burgers-inline.yaml
Path is:         /burgers/create-burger
Payload file is: burger-malformed.json

Correct payload:

{
    "name": "Garbage burger",
    "patties": 3
}

Result:

Spec file is:    burgers-inline.yaml
Path is:         /burgers/create-burger
Payload file is: burger-missing.json
-------> Reason: missing properties: 'vegetarian', Location: /required
daveshanley commented 1 year ago

Hmm.. I will look into this for you.

daveshanley commented 1 year ago

Fixed in v0.0.15. Thank you for making libopenapi better.