[REQ-E003] - Removing properties from an object with additionalProperties set to False used as request parameter

Rationale

If the object is defined with additionalProperties set to False then the object will not allow presence of properties not defined on the properties section of the object definition. Removing a definition of an existing property makes objects sent from a client, that is using “old” Swagger specs, to the server be considered invalid by the backend.

Mitigation

A possible mitigation could be to not remove the property from the object schema, mark it as deprecated (mostly for documentation purposes) and make sure that your business logic fills the field with a placeholder.

Example

Old Swagger Specs

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
swagger: '2.0'
info:
  title: Minimal Case of REQ-E003 Rule
  version: '1.0'
paths:
  /endpoint:
    post:
      parameters:
      - in: body
        name: body
        required: true
        schema:
          additionalProperties: false
          properties:
            property_1:
              type: string
            property_2:
              type: string
          type: object
      responses:
        default:
          description: ''

New Swagger Specs

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
swagger: '2.0'
info:
  title: Minimal Case of REQ-E003 Rule
  version: '1.0'
paths:
  /endpoint:
    post:
      parameters:
      - in: body
        name: body
        required: true
        schema:
          additionalProperties: false
          properties:
            property_1:
              type: string
          type: object
      responses:
        default:
          description: ''

Backward Incompatibility

The following snippet triggers the incompatibility error.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
# -*- coding: utf-8 -*-
from __future__ import absolute_import
from __future__ import print_function
from __future__ import unicode_literals

from os.path import abspath

from bravado.client import SwaggerClient
from jsonschema import ValidationError
from six.moves.urllib.parse import urljoin
from six.moves.urllib.request import pathname2url

old_client = SwaggerClient.from_url(
    spec_url=urljoin('file:', pathname2url(abspath('old.yaml'))),
)
new_client = SwaggerClient.from_url(
    spec_url=urljoin('file:', pathname2url(abspath('new.yaml'))),
)

object_to_send = {'property_1': 'v1', 'property_2': 'v2'}

print('Calling the post endpoint with the old client: Succeeded')
old_client.endpoint.post_endpoint(body=object_to_send)

print('Calling the post endpoint with the old client: Failed')
try:
    new_client.endpoint.post_endpoint(body=object_to_send)
    raise RuntimeError('An error was expected')
except ValidationError:
    pass

NOTE: The code is taking advantage of bravado