oapi-codegen icon indicating copy to clipboard operation
oapi-codegen copied to clipboard

OapiRequestValidator middleware not working

Open tina-junold opened this issue 3 years ago • 4 comments

Hi,

I'm using Gin with OapiRequestValidator, but it seems that the middleware does not handle invalid request data. Did i miss something?

A tiny example:

import(
	"net/http"
	middleware "github.com/deepmap/oapi-codegen/pkg/gin-middleware"
	// [...]
	"some.repo/test/example-app/internal/api"
	"some.repo/test/example-app/internal/handler"
	"github.com/gin-gonic/gin"
)

func main()
	g := gin.New()
	swagger, _ := api.GetSwagger()
        g.Use(middleware.OapiRequestValidator(swagger))
	todo := handler.NewTodo()
	api.RegisterHandlers(g, todo)

	srv := &http.Server{
		Addr:    os.Getenv(":3000"),
		Handler: g,
	}
        if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
           panic(err)
	}
)

tina-junold avatar Apr 29 '22 16:04 tina-junold

That looks correct to me. Could you construct a tiny, runnable example which demonstrates the problem?

deepmap-marcinr avatar May 16 '22 23:05 deepmap-marcinr

That looks correct to me. Could you construct a tiny, runnable example which demonstrates the problem?

I have one:

OpenAPI

openapi: 3.0.3

info:
  title: Local Storage API
  version: v2

servers:
  - url: /v2/local_storage

tags:
  - name: Mount methods

  - name: Mount
    description: |-
      <SchemaDefinition schemaRef="#/components/schemas/Mount" />

x-tagGroups:
  - name: Mount
    tags:
      - Mount methods

  - name: Schemas
    tags:
      - Mount

paths:
  /mount:
    get:
      summary: Get all mounted volumes
      description: |-
        Get all volumes currently mounted on the system. Volumes can be filtered by corresponding query parameters.
      operationId: getMounts
      tags:
        - Mount methods
      parameters:
        - name: id
          in: query
          description: |-
            Filter the results by id
          schema:
            type: string
            example: "0"
        - name: mount_point
          in: query
          description: |-
            Filter the results by mount point
          schema:
            type: string
            example: "/"
        - name: type
          in: query
          description: |-
            Filter the results by type
          schema:
            type: string
            example: "ext4"
        - name: source
          in: query
          description: |-
            Filter the results by source
          schema:
            type: string
            example: "/dev/sda1"
      responses:
        '200':
          $ref: '#/components/responses/GetMountsResponseOK'

    post:
      summary: Mount a volume
      description: |-
        (TODO)
      operationId: mount
      tags:
        - Mount methods
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/Mount'
      responses:
        '200':
          $ref: '#/components/responses/AddMountResponseOK'
        '400':
          $ref: '#/components/responses/ResponseBadRequest'
        '403':
          $ref: '#/components/responses/ResponseForbidden'
        '409':
          $ref: '#/components/responses/ResponseConflict'

    put:
      summary: Update a mount volume
      description: |-
        Updating a mount volume is equivalent to unmounting the volume and mounting it again with the new parameters.
      operationId: updateMount
      tags:
        - Mount methods
      parameters:
        - name: mount_point
          in: query
          required: true
          description: |-
            Filter the results by mount point
          schema:
            type: string
            example: "/"
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/Mount'
      responses:
        '200':
          $ref: '#/components/responses/UpdateMountResponseOK'
        '400':
          $ref: '#/components/responses/ResponseBadRequest'
        '403':
          $ref: '#/components/responses/ResponseForbidden'
        '404':
          $ref: '#/components/responses/ResponseNotFound'
        '409':
          $ref: '#/components/responses/ResponseConflict'

    delete:
      summary: Umount volume
      description: |-
        (TODO)
      operationId: Umount
      tags:
        - Mount methods
      parameters:
        - name: mount_point
          in: query
          required: true
          description: |-
            Filter the results by mount point
          schema:
            type: string
            example: "/DATA"
        - name: persist
          in: query
          description: |-
            `true` if the mount should be removed from `/etc/fstab`, `false` otherwise
          schema:
            type: boolean
            default: true
      responses:
        '200':
          $ref: '#/components/responses/UmountResponseOK'
        '400':
          $ref: '#/components/responses/ResponseBadRequest'
        '403':
          $ref: '#/components/responses/ResponseForbidden'
        '404':
          $ref: '#/components/responses/ResponseNotFound'
        '409':
          $ref: '#/components/responses/ResponseConflict'

components:

  responses:

    GetMountsResponseOK:
      description: OK
      content:
        application/json:
          schema:
            allOf:
              - $ref: '#/components/schemas/BaseResponse'
              - type: object
                properties:
                  data:
                    type: array
                    items:
                      $ref: '#/components/schemas/Mount'

    AddMountResponseOK:
      description: OK
      content:
        application/json:
          schema:
            allOf:
              - $ref: '#/components/schemas/BaseResponse'
              - type: object
                properties:
                  data:
                    $ref: '#/components/schemas/Mount'

    UpdateMountResponseOK:
      description: OK
      content:
        application/json:
          schema:
            allOf:
              - $ref: '#/components/schemas/BaseResponse'
              - type: object
                properties:
                  data:
                    $ref: '#/components/schemas/Mount'

    UmountResponseOK:
      description: OK
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/BaseResponse'

    ResponseBadRequest:
      description: Bad Request
      content:
        application/json:
          schema:
            allOf:
              - $ref: '#/components/schemas/BaseResponse'
          example:
            message: "Bad Request"

    ResponseNotFound:
      description: Not Found
      content:
        application/json:
          schema:
            allOf:
              - $ref: '#/components/schemas/BaseResponse'
          example:
            message: "Not Found"

    ResponseForbidden:
      description: Forbidden
      content:
        application/json:
          schema:
            allOf:
              - $ref: '#/components/schemas/BaseResponse'
          example:
            message: "Forbidden"

    ResponseConflict:
      description: Conflict
      content:
        application/json:
          schema:
            allOf:
              - $ref: '#/components/schemas/BaseResponse'
          example:
            message: "Conflict"

  schemas:

    BaseResponse:
      properties:
        message:
          readOnly: true
          description: message returned by server side if there is any
          type: string
          example: ""

    Mount:
      type: object
      minProperties: 1
      properties:
        id:
          type: integer
          readOnly: true
        mount_point:
          type: string
          nullable: false
          example: "/DATA"
        fstype:
          type: string
          nullable: false
          example: "fuse.mergerfs"
        source:
          type: string
          nullable: false
          example: "/mnt/a:/mnt/b"
        options:
          type: string
          example: "defaults,allow_other,use_ino,category.create=mfs,moveonenospc=true,minfreespace=1M"
        persist:
          type: boolean
          description: |-
            `true` if the mount should be persisted in `/etc/fstab`, `false` otherwise
          default: false
        extended:
          type: object
          description: |-
            Extended properties of the mount
          additionalProperties:
            type: string
          example:
            "mergerfs.srcmounts": "/mnt/a:/mnt/b"

HTTP Request:

curl --location --request PUT 'http://localhost:8080/v2/local_storage/mount?mount_point=/media' \
--header 'Content-Type: application/json' \
--data-raw '{
    "Mountpoint": "/media",
    "FSType": "mergerfs",
    "Source": "/mnt/sdb:/mnt/sdc",
    "Options": "defaults,allow_other,use_ino,category.create=mfs,moveonenospc=true,minfreespace=1M",
  "Persist": true
}'

Note in the request JSON, all fields are camelCase, where OpenAPI yaml expects snake_case:

{
    "Mountpoint": "/media",
    "FSType": "mergerfs",
    "Source": "/mnt/sdb:/mnt/sdc",
    "Options": "defaults,allow_other,use_ino,category.create=mfs,moveonenospc=true,minfreespace=1M",
    "Persist": true
}

So the actual request the server side sees is

{
    "mount_point": null,
    "fstype": null,
    "source": null,
    "options": null,
    "Persist": true
}

The validator should fail, because in the YAML, the mandatory fields are marked with nullable: false but it's in fact not honored:

        mount_point:
          type: string
          nullable: false
          example: "/DATA"
        fstype:
          type: string
          nullable: false
          example: "fuse.mergerfs"
        source:
          type: string
          nullable: false
          example: "/mnt/a:/mnt/b"

tigerinus avatar Sep 16 '22 20:09 tigerinus

We simply wrap the openapi3 filter from https://github.com/getkin/kin-openapi to do request validation.

The bug lies in there, perhaps you could come up with a reproduction case for their project, and file an Issue there?

deepmap-marcinr avatar Sep 23 '22 19:09 deepmap-marcinr

Here https://github.com/getkin/kin-openapi/issues/598

tigerinus avatar Sep 23 '22 23:09 tigerinus