connexion icon indicating copy to clipboard operation
connexion copied to clipboard

Infinite loop on validation with recursive references and unresolved refs

Open gaetano-guerriero opened this issue 3 years ago • 1 comments

Description

Having an OpenAPI 3.0 spec with these two conditions:

  • includes a remote $ref deeply nested inside a locally resolved $ref
  • includes a schema with a circular references

Causes infinite loop when trying to validate the request body using jsonschema.

Expected behaviour

The infinite loop does not happen, validation is done and response is served.

Actual behaviour

Code enters an infinite loop and server never responds

Steps to reproduce

import connexion


def view(*data):
    return {}

app = connexion.FlaskApp(__name__, specification_dir='openapi/')
app.add_api('my_api.yaml')
app.run(port=8080)

my_api.yaml

openapi: 3.0.0
info:
  title: Sample API
  description: Optional
  version: '0.1'

servers:
  - url: http://api.example.com
    description: Optional server description, e.g. Main (production) server

paths:
  /:
    post:
      operationId: 'main.view'
      summary: S
      description: D
      responses:
        '200':
          description: A JSON array of user names
          content:
            application/json:
              schema: 
                type: object
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                foo:
                  $ref: '#/components/schemas/Foo'
                bar:
                  $ref: '#/components/schemas/Bar'

components:
  schemas:
    Foo:
      type: array
      items:
        $ref: 'file:///tmp/connextest/openapi/referred_schema.json#/components/schemas/FooElement'
    Bar:
      type: object
      properties:
        child:
          $ref: '#/components/schemas/Bar'
    FooElement:
      type: string

/tmp/referred_schema.json

{
    "components": {
        "schemas": {
            "FooElement": {
                "type": "string"
            }
        }
    }
}

To generate infinite loop:

$ python main.py &
$ echo '{"foo": [1], "bar": {}}' | http POST :8080/ 'Content-Type: application/json'

Additional info:

  • In the example above components.schemas.Bar.properties.child is the recursive reference.

  • components.schemas.Foo.items is the unsolved $ref.

  • components.schemas.Foo.items is unresolved due to resolve_refs implementation (missing recursive call in 'try' path if '$ref' is missing)

  • components.schemas.Bar.properties.child is resolved with a circular reference to components.schemas.Bar

  • jsonschema receives the schema with the circular reference, and crashes when trying to solve the unsolved $ref

I see two possible roads to fix the issue:

  • fix the recursion in resolve_refs also for resolved nodes not directly including a $ref, but maybe with a nested $ref
  • avoid resolving refs altogether in components.schemas. jsonschema is perfectly able to solve the refs while validating, why connexion needs to solve everything in advance ?

Output of the commands:

$ python --version
Python 3.10.7
$ pip show connexion | grep "^Version\:"
Version: 2.14.1

gaetano-guerriero avatar Oct 25 '22 12:10 gaetano-guerriero

Can reproduce.

RobbeSneyders avatar Nov 01 '23 16:11 RobbeSneyders