Skip to content

Commit

Permalink
fix: Ensure request body data relies only on immutable data structures.
Browse files Browse the repository at this point in the history
Fixes: #27
playpauseandstop committed Jan 27, 2020
1 parent 61e631f commit 695d516
Showing 4 changed files with 33 additions and 24 deletions.
3 changes: 2 additions & 1 deletion CHANGELOG.rst
Original file line number Diff line number Diff line change
@@ -4,8 +4,9 @@
2.0.0b3 (In Development)
------------------------

- feature: Provide human readable security & request parameter validation
- feature: Provide human readable security, request & response validation
errors
- feature: Support free form objects in request body
- feature: Allow to enable CORS / error middleware on setting up OpenAPI
support for ``aiohttp.web`` application
- feature: Provide ``BaseSettings`` and ``env_factory`` helpers to work with
34 changes: 17 additions & 17 deletions rororo/openapi/mappings.py
Original file line number Diff line number Diff line change
@@ -7,25 +7,9 @@
from ..annotations import T


def convert_dict_to_mapping_proxy(data: Any) -> Any:
"""Convert all dicts to mapping proxy types."""
if isinstance(data, list):
return [convert_dict_to_mapping_proxy(item) for item in data]

if isinstance(data, Mapping):
return types.MappingProxyType(
{
key: convert_dict_to_mapping_proxy(value)
for key, value in data.items()
}
)

return data


def enforce_dicts(data: Any) -> Any:
"""Deep convert openapi-core Models to dicts."""
if isinstance(data, list):
if isinstance(data, (list, tuple)):
return [enforce_dicts(item) for item in data]

if isinstance(data, Mapping):
@@ -37,6 +21,22 @@ def enforce_dicts(data: Any) -> Any:
return data


def enforce_immutable_data(data: Any) -> Any:
"""Deep convert all dicts to mapping proxies & lists to tuples.
This ensure that all request body data is immutable.
"""
if isinstance(data, list):
return tuple(enforce_immutable_data(item) for item in data)

if isinstance(data, Mapping):
return types.MappingProxyType(
{key: enforce_immutable_data(value) for key, value in data.items()}
)

return data


def merge_data(original_data: T, extra_data: Any) -> T:
if isinstance(original_data, dict) and isinstance(extra_data, Mapping):
for key, value in extra_data.items():
4 changes: 2 additions & 2 deletions rororo/openapi/validators.py
Original file line number Diff line number Diff line change
@@ -14,7 +14,7 @@
to_openapi_parameters,
)
from .exceptions import ValidationError
from .mappings import convert_dict_to_mapping_proxy, enforce_dicts, merge_data
from .mappings import enforce_dicts, enforce_immutable_data, merge_data


CUSTOM_FORMATTERS = {"email": str}
@@ -42,7 +42,7 @@ def _get_body(
raw_body = operation.request_body.get_value(request)

return (
convert_dict_to_mapping_proxy(
enforce_immutable_data(
merge_data(body_with_dicts, media_type.cast(raw_body))
),
errors,
16 changes: 12 additions & 4 deletions tests/test_openapi.py
Original file line number Diff line number Diff line change
@@ -100,8 +100,14 @@ async def retrieve_nested_object_from_request_body(
return web.json_response(
{**enforce_dicts(context.data), "uid": str(context.data["uid"])},
headers={
"X-Content-Data-Type": str(type(context.data)),
"X-UID-Data-Type": str(type(context.data["uid"])),
"X-Data-Type": str(type(context.data)),
"X-Data-Data-Data-Items-Type": str(
type(context.data["data"]["data_items"])
),
"X-Data-Data-Str-Items-Type": str(
type(context.data["data"]["str_items"])
),
"X-Data-UID-Type": str(type(context.data["uid"])),
},
)

@@ -311,8 +317,10 @@ async def test_request_body_nested_obejct(aiohttp_client, schema_path):
client = await aiohttp_client(app)
response = await client.post("/api/nested-object", json=TEST_NESTED_OBJECT)
assert response.status == 200
assert response.headers["X-Content-Data-Type"] == "<class 'mappingproxy'>"
assert response.headers["X-UID-Data-Type"] == "<class 'uuid.UUID'>"
assert response.headers["X-Data-Type"] == "<class 'mappingproxy'>"
assert response.headers["X-Data-Data-Data-Items-Type"] == "<class 'tuple'>"
assert response.headers["X-Data-Data-Str-Items-Type"] == "<class 'tuple'>"
assert response.headers["X-Data-UID-Type"] == "<class 'uuid.UUID'>"
assert await response.json() == TEST_NESTED_OBJECT


0 comments on commit 695d516

Please sign in to comment.