open-api icon indicating copy to clipboard operation
open-api copied to clipboard

[openapi-request-validator] Validating multipart/form-data requests

Open Envek opened this issue 3 years ago • 1 comments

This is related, but different from #710. I'm not sure that there is a bug, maybe I just need use these packages differently as I'm using them at quite low level (with manual parsing of request body and etc).

The problem

In OpenAPI 3 multipart/form-data requests should be declared as objects in requestBody according to Describing Request Body / File Uploads docs:

openapi: 3.0.3
info:
  title: Sample Application API
  version: v1
paths:
  /fileAttachments:
    post:
      requestBody:
        content:
          multipart/form-data:
            schema:
              type: object
              properties:
                someId:
                  type: string
                  nullable: false
                status:
                  type: string
                  nullable: true
                file:
                  type: string
                  format: binary
                  nullable: false
      responses:
        204:
          description: Success

I'm trying to validate such a request on AWS Lambda. Due to platform validations, I'm getting raw body, so I parse it with lambda-multipart-parser and validate with openapi-request-validator with code like this:

import OpenAPIRequestValidator } from "openapi-request-validator"
import { parse as parseMultipart } from "lambda-multipart-parser"

const { files, ...plainFields } = await parseMultipart(event)
const fileFields  = Object.fromEntries(files.map(file => [file.fieldname, file.content.toString()]))
data   = { ...plainFields, ...fileFields }

(new OpenAPIRequestValidator({…})).validateRequest({
      headers: headers,
      body: data,
      params: event.pathParameters,
      query: event.queryStringParameters,
    })

Which gives me following object as a body:

{
  someId: "deadbeef",
  status: "whatever",
  file: '�PNG\r\n…',
}

Expected result

openapi-request-validator validates request and doesn't pass requests with file or someId missing

Actual result

openapi-request-validator passes requests with missing non-nullable fields

Workaround

Then I went to openapi-request-validator source and found that it expects for multipart requests that all input fields should be declared as input formdata parameters instead of request body (see this test case)

So I tried to change schema in a way that openapi-request-validator should understand: replaced request body with parameters

apiSchema.yml with formData parameters
openapi: 3.0.3
info:
  title: Sample Application API
  version: v1
paths:
  /fileAttachments:
    post:
      parameters:
        - name: someId
          in: formData
          required: true
          schema:
            type: string
        - name: status
          in: formData
          required: false
          schema:
            type: string
        - name: file
          in: formData
          required: true
          schema:
            type: string
            format: binary
      responses:
        204:
          description: Success

And success! Now it is being validated as expected.

But Swagger Editor tells me that this schema is now invalid:

Structural error at paths./fileAttachments.post.parameters.0.in
should be equal to one of the allowed values
allowedValues: path, query, header, cookie

(and 2 more errors)

To complete this workaround I had to preprocess schema, converting multipart bodies into formData parameters on schema load with code like this.

Some hacky TypeScript code
import yaml from "js-yaml"
import { readFileSync } from "fs"
import $RefParser from "@apidevtools/json-schema-ref-parser"
import { OpenAPIV3 } from "openapi-types"

type OpenApiHttpMethods = "get" | "post" | "put" | "delete" | "options" | "head" | "patch" | "trace"

const apiSpec = <OpenAPIV3.Document>yaml.safeLoad(readFileSync("./apiSchema.yaml").toString())

async function preprocessOpenapiSpec(apiSpec: OpenAPIV3.Document): Promise<OpenAPIV3.Document> {
  const schema = <OpenAPIV3.Document>await $RefParser.dereference(apiSpec)

  // Replace multipart request body with formData parameters for validation
  for (const methods of Object.values(schema.paths)) {
    for (const method of ["get", "post", "put", "delete", "options", "head", "patch", "trace"]) {
      const methodOptions = methods[<OpenApiHttpMethods>method]
      if (!methodOptions) continue

      const reqBody = <OpenAPIV3.RequestBodyObject | undefined>methodOptions?.requestBody
      if (reqBody?.content && "multipart/form-data" in reqBody.content) {
        const props = (<OpenAPIV3.SchemaObject>reqBody.content["multipart/form-data"].schema)?.properties
        if (!props) continue

        methodOptions.parameters ||= []

        for (const [name, subschema] of Object.entries(props)) {
          const required = ("nullable" in subschema) ? !subschema.nullable : false
          methodOptions.parameters.push({ name, in: "formData", required, schema: subschema })
        }

        delete methodOptions?.requestBody
      }
    }
  }

  return schema
}

Question

Is there a bug in openapi-request-validator? If so, can you point me where to start fixing it, so I can prepare a pull request (I'm not sure where to start, honestly)

Or should I somehow preprocess schema or body using some other package from this suite?


Originally posted by @Envek in https://github.com/kogosoftwarellc/open-api/issues/710#issuecomment-788128653

Envek avatar Mar 04 '21 14:03 Envek

+1 I am having a problem with this as well. Any updates?

anthonysette avatar Jun 21 '21 16:06 anthonysette