mirror of
https://github.com/LukeHagar/connexion.git
synced 2025-12-09 20:37:46 +00:00
Do not sanitize body keys in OpenAPI 3 (#1008)
* Remove the unused "query_sanitazion" fixture * Test whether no sanitization is performed in the request body * Do not perform sanitization on request body keys in OpenAPI v3 The deserialized JSON form of the request body needs to be passed to the client applications * without further modification * so that they can work directly with objects that have been received over the network. The only names for which sanitization makes sense are the ones which are used as Python identifiers. Keys of the top-level JSON object within the request payload are never used by Connexion as Python identifiers. Also, no such sanitization of keys within request body is performed in OpenAPI v2. Closes issue #835.
This commit is contained in:
committed by
Henning Jacobs
parent
c4c7e677f0
commit
738f47ed50
@@ -244,12 +244,12 @@ class OpenAPIOperation(AbstractOperation):
|
|||||||
return {}
|
return {}
|
||||||
|
|
||||||
def _get_body_argument(self, body, arguments, has_kwargs, sanitize):
|
def _get_body_argument(self, body, arguments, has_kwargs, sanitize):
|
||||||
x_body_name = self.body_schema.get('x-body-name', 'body')
|
x_body_name = sanitize(self.body_schema.get('x-body-name', 'body'))
|
||||||
if is_nullable(self.body_schema) and is_null(body):
|
if is_nullable(self.body_schema) and is_null(body):
|
||||||
return {x_body_name: None}
|
return {x_body_name: None}
|
||||||
|
|
||||||
default_body = self.body_schema.get('default', {})
|
default_body = self.body_schema.get('default', {})
|
||||||
body_props = {sanitize(k): {"schema": v} for k, v
|
body_props = {k: {"schema": v} for k, v
|
||||||
in self.body_schema.get("properties", {}).items()}
|
in self.body_schema.get("properties", {}).items()}
|
||||||
|
|
||||||
# by OpenAPI specification `additionalProperties` defaults to `true`
|
# by OpenAPI specification `additionalProperties` defaults to `true`
|
||||||
@@ -269,25 +269,27 @@ class OpenAPIOperation(AbstractOperation):
|
|||||||
|
|
||||||
res = {}
|
res = {}
|
||||||
if body_props or additional_props:
|
if body_props or additional_props:
|
||||||
res = self._sanitize_body_argument(body_arg, body_props, additional_props, sanitize)
|
res = self._get_typed_body_values(body_arg, body_props, additional_props)
|
||||||
|
|
||||||
if x_body_name in arguments or has_kwargs:
|
if x_body_name in arguments or has_kwargs:
|
||||||
return {x_body_name: res}
|
return {x_body_name: res}
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
def _sanitize_body_argument(self, body_arg, body_props, additional_props, sanitize):
|
def _get_typed_body_values(self, body_arg, body_props, additional_props):
|
||||||
"""
|
"""
|
||||||
|
Return a copy of the provided body_arg dictionary
|
||||||
|
whose values will have the appropriate types
|
||||||
|
as defined in the provided schemas.
|
||||||
|
|
||||||
:type body_arg: type dict
|
:type body_arg: type dict
|
||||||
:type body_props: dict
|
:type body_props: dict
|
||||||
:type additional_props: dict|bool
|
:type additional_props: dict|bool
|
||||||
:type sanitize: types.FunctionType
|
|
||||||
:rtype: dict
|
:rtype: dict
|
||||||
"""
|
"""
|
||||||
additional_props_defn = {"schema": additional_props} if isinstance(additional_props, dict) else None
|
additional_props_defn = {"schema": additional_props} if isinstance(additional_props, dict) else None
|
||||||
res = {}
|
res = {}
|
||||||
|
|
||||||
for key, value in body_arg.items():
|
for key, value in body_arg.items():
|
||||||
key = sanitize(key)
|
|
||||||
try:
|
try:
|
||||||
prop_defn = body_props[key]
|
prop_defn = body_props[key]
|
||||||
res[key] = self._get_val_from_param(value, prop_defn)
|
res[key] = self._get_val_from_param(value, prop_defn)
|
||||||
|
|||||||
@@ -403,6 +403,22 @@ def test_param_sanitization(simple_app):
|
|||||||
assert resp.status_code == 200
|
assert resp.status_code == 200
|
||||||
assert json.loads(resp.data.decode('utf-8', 'replace')) == body
|
assert json.loads(resp.data.decode('utf-8', 'replace')) == body
|
||||||
|
|
||||||
|
def test_no_sanitization_in_request_body(simple_app):
|
||||||
|
app_client = simple_app.app.test_client()
|
||||||
|
data = {
|
||||||
|
'name': 'John',
|
||||||
|
'$surname': 'Doe',
|
||||||
|
'1337': True,
|
||||||
|
'!#/bin/sh': False,
|
||||||
|
'(1/0)': 'division by zero',
|
||||||
|
's/$/EOL/': 'regular expression',
|
||||||
|
'@8am': 'time',
|
||||||
|
}
|
||||||
|
response = app_client.post('/v1.0/forward', json=data)
|
||||||
|
|
||||||
|
assert response.status_code == 200
|
||||||
|
assert response.json == data
|
||||||
|
|
||||||
def test_parameters_snake_case(snake_case_app):
|
def test_parameters_snake_case(snake_case_app):
|
||||||
app_client = snake_case_app.app.test_client()
|
app_client = snake_case_app.app.test_client()
|
||||||
headers = {'Content-type': 'application/json'}
|
headers = {'Content-type': 'application/json'}
|
||||||
|
|||||||
@@ -179,11 +179,6 @@ def bad_operations_app(request):
|
|||||||
resolver_error=501)
|
resolver_error=501)
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(scope="session", params=SPECS)
|
|
||||||
def query_sanitazion(request):
|
|
||||||
return build_app_from_fixture('query_sanitazion', request.param)
|
|
||||||
|
|
||||||
|
|
||||||
if sys.version_info < (3, 5, 3) and sys.version_info[0] == 3:
|
if sys.version_info < (3, 5, 3) and sys.version_info[0] == 3:
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def aiohttp_client(test_client):
|
def aiohttp_client(test_client):
|
||||||
|
|||||||
@@ -131,6 +131,11 @@ def schema(new_stack):
|
|||||||
return new_stack
|
return new_stack
|
||||||
|
|
||||||
|
|
||||||
|
def forward(body):
|
||||||
|
"""Return a response with the same payload as in the request body."""
|
||||||
|
return body
|
||||||
|
|
||||||
|
|
||||||
def schema_response_object(valid):
|
def schema_response_object(valid):
|
||||||
if valid == "invalid_requirements":
|
if valid == "invalid_requirements":
|
||||||
return {"docker_version": 1.0}
|
return {"docker_version": 1.0}
|
||||||
|
|||||||
31
tests/fixtures/query_sanitazion/openapi.yaml
vendored
31
tests/fixtures/query_sanitazion/openapi.yaml
vendored
@@ -1,31 +0,0 @@
|
|||||||
openapi: 3.0.0
|
|
||||||
info:
|
|
||||||
title: '{{title}}'
|
|
||||||
version: '1.0'
|
|
||||||
paths:
|
|
||||||
/greeting:
|
|
||||||
post:
|
|
||||||
summary: Generate greeting
|
|
||||||
description: Generates a greeting message.
|
|
||||||
operationId: fakeapi.hello.post_greeting
|
|
||||||
responses:
|
|
||||||
'200':
|
|
||||||
description: greeting response
|
|
||||||
content:
|
|
||||||
'application/json':
|
|
||||||
schema:
|
|
||||||
type: object
|
|
||||||
requestBody:
|
|
||||||
content:
|
|
||||||
application/x-www-form-urlencoded:
|
|
||||||
schema:
|
|
||||||
x-body-name: name
|
|
||||||
type: object
|
|
||||||
properties:
|
|
||||||
name:
|
|
||||||
description: Name of the person to greet.
|
|
||||||
type: string
|
|
||||||
required:
|
|
||||||
- name
|
|
||||||
servers:
|
|
||||||
- url: /v1.0
|
|
||||||
22
tests/fixtures/query_sanitazion/swagger.yaml
vendored
22
tests/fixtures/query_sanitazion/swagger.yaml
vendored
@@ -1,22 +0,0 @@
|
|||||||
swagger: "2.0"
|
|
||||||
info:
|
|
||||||
title: "{{title}}"
|
|
||||||
version: "1.0"
|
|
||||||
basePath: /v1.0
|
|
||||||
paths:
|
|
||||||
/greeting:
|
|
||||||
post:
|
|
||||||
summary: Generate greeting
|
|
||||||
description: Generates a greeting message.
|
|
||||||
operationId: fakeapi.hello.post_greeting
|
|
||||||
responses:
|
|
||||||
'200':
|
|
||||||
description: greeting response
|
|
||||||
schema:
|
|
||||||
type: object
|
|
||||||
parameters:
|
|
||||||
- name: name
|
|
||||||
in: formData
|
|
||||||
description: Name of the person to greet.
|
|
||||||
required: true
|
|
||||||
type: string
|
|
||||||
20
tests/fixtures/simple/openapi.yaml
vendored
20
tests/fixtures/simple/openapi.yaml
vendored
@@ -1074,8 +1074,22 @@ paths:
|
|||||||
trace:
|
trace:
|
||||||
operationId: fakeapi.hello.trace_add_operation_on_http_methods_only
|
operationId: fakeapi.hello.trace_add_operation_on_http_methods_only
|
||||||
responses: {}
|
responses: {}
|
||||||
|
/forward:
|
||||||
|
post:
|
||||||
|
operationId: fakeapi.hello.forward
|
||||||
|
requestBody:
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
type: object
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: >
|
||||||
|
The response containing the same data as were present in request body.
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
type: object
|
||||||
|
|
||||||
servers:
|
servers:
|
||||||
- url: http://localhost:{port}/{basePath}
|
- url: http://localhost:{port}/{basePath}
|
||||||
@@ -1125,4 +1139,4 @@ components:
|
|||||||
description: Name of the person to greet.
|
description: Name of the person to greet.
|
||||||
required: false
|
required: false
|
||||||
schema:
|
schema:
|
||||||
type: string
|
type: string
|
||||||
|
|||||||
20
tests/fixtures/simple/swagger.yaml
vendored
20
tests/fixtures/simple/swagger.yaml
vendored
@@ -964,6 +964,26 @@ paths:
|
|||||||
items:
|
items:
|
||||||
type: integer
|
type: integer
|
||||||
|
|
||||||
|
/forward:
|
||||||
|
post:
|
||||||
|
operationId: fakeapi.hello.forward
|
||||||
|
consumes:
|
||||||
|
- application/json
|
||||||
|
produces:
|
||||||
|
- application/json
|
||||||
|
parameters:
|
||||||
|
- name: body
|
||||||
|
in: body
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
type: object
|
||||||
|
responses:
|
||||||
|
200:
|
||||||
|
description: >
|
||||||
|
The response containing the same data as were present in request body.
|
||||||
|
schema:
|
||||||
|
type: object
|
||||||
|
|
||||||
definitions:
|
definitions:
|
||||||
new_stack:
|
new_stack:
|
||||||
type: object
|
type: object
|
||||||
|
|||||||
Reference in New Issue
Block a user