Skip to content

Commit

Permalink
Fix UUID validation (#219)
Browse files Browse the repository at this point in the history
Co-authored-by: Pavel Pristupa <pavel@Pavels-MacBook-Pro.local>
  • Loading branch information
pristupa and Pavel Pristupa authored Jun 22, 2022
1 parent 1086010 commit b7c9788
Show file tree
Hide file tree
Showing 9 changed files with 72 additions and 16 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [9.6.0] - 2022-06-22

Return 400 Bad Request instead of 500 if UUID request parameter is malformed

## [9.5.6] - 2022-06-16

Fix cyclic imports caused by DRF importing external renderer classes during import
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[tool.poetry]
name = "winter"
version = "9.5.6"
version = "9.6.0"
homepage = "https://github.com/WinterFramework/winter"
description = "Web Framework inspired by Spring Framework"
authors = ["Alexander Egorov <mofr@zond.org>"]
Expand Down
5 changes: 4 additions & 1 deletion tests/controllers/controller_with_query_parameters.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
from typing import Dict
from typing import List
from typing import Optional
from uuid import UUID

import winter

Expand All @@ -13,7 +14,7 @@
class ControllerWithQueryParameters:

@winter.map_query_parameter('string', to='mapped_string')
@winter.route_get('/{?date,boolean,optional_boolean,date_time,array,expanded_array*,string}')
@winter.route_get('/{?date,boolean,optional_boolean,date_time,array,expanded_array*,string,uid}')
def root(
self,
date: datetime.date,
Expand All @@ -22,6 +23,7 @@ def root(
array: List[int],
expanded_array: List[str],
mapped_string: str,
uid: UUID,
optional_boolean: Optional[bool] = None,
) -> Dict[str, Any]:
return {
Expand All @@ -32,4 +34,5 @@ def root(
'array': array,
'expanded_array': expanded_array,
'string': mapped_string,
'uid': str(uid),
}
59 changes: 53 additions & 6 deletions tests/routing/test_query_parameters.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
import datetime
from http import HTTPStatus
from typing import List
from typing import Optional
from uuid import UUID
from uuid import uuid4

import pytest
from dateutil import parser
Expand Down Expand Up @@ -64,6 +68,22 @@ def method(query_param: int): # pragma: no cover
assert str(exception.value) == expected_exception_message


def test_query_parameter_resolver_with_raises_parse_uuid_error():
@winter.route_get('{?query_param}')
def method(query_param: UUID): # pragma: no cover
pass

resolver = QueryParameterArgumentResolver()

argument = method.get_argument('query_param')
request = get_request('query_param=invalid_uuid')

with pytest.raises(decoder.JSONDecodeException) as exception:
resolver.resolve_argument(argument, request, {})

assert str(exception.value) == 'Cannot decode "invalid_uuid" to uuid'


@pytest.mark.parametrize(
('argument_name', 'expected_is_supported'), (
('query_param', True),
Expand Down Expand Up @@ -147,13 +167,13 @@ def method(x_param: int, y_param: int = 1): # pragma: no cover


@pytest.mark.parametrize(
('date', 'date_time', 'boolean', 'optional_boolean', 'array', 'string'), (
('2019-05-02', '2019-05-02 22:28:31', 'false', None, [10, 20], 'xyz'),
('2019-05-01', '2019-05-01 22:28:31', 'true', 'true', [10, 20], 'xyz'),
('2019-05-01', '2019-05-01 22:28:31', 'true', 'false', [10, 20], 'xyz'),
('date', 'date_time', 'boolean', 'optional_boolean', 'array', 'string', 'uid'), (
('2019-05-02', '2019-05-02 22:28:31', 'false', None, [10, 20], 'xyz', uuid4()),
('2019-05-01', '2019-05-01 22:28:31', 'true', 'true', [10, 20], 'xyz', uuid4()),
('2019-05-01', '2019-05-01 22:28:31', 'true', 'false', [10, 20], 'xyz', uuid4()),
),
)
def test_query_parameter(date, date_time, boolean, optional_boolean, array, string):
def test_query_parameter(date, date_time, boolean, optional_boolean, array, string, uid):
client = APIClient()
user = AuthorizedUser()
client.force_authenticate(user)
Expand All @@ -165,10 +185,11 @@ def test_query_parameter(date, date_time, boolean, optional_boolean, array, stri
'array': array,
'expanded_array': list(map(str, array)),
'string': string,
'uid': str(uid),
}
base_uri = URITemplate(
'/with-query-parameter/'
'{?date,date_time,boolean,optional_boolean,array,expanded_array*,string}',
'{?date,date_time,boolean,optional_boolean,array,expanded_array*,string,uid}',
)
query_params = {
'date': date,
Expand All @@ -177,6 +198,7 @@ def test_query_parameter(date, date_time, boolean, optional_boolean, array, stri
'array': ','.join(map(str, array)),
'expanded_array': array,
'string': string,
'uid': uid,
}

if optional_boolean is not None:
Expand All @@ -187,3 +209,28 @@ def test_query_parameter(date, date_time, boolean, optional_boolean, array, stri
# Act
http_response = client.get(base_uri)
assert http_response.data == expected_data


def test_invalid_uuid_query_parameter_triggers_400():
client = APIClient()
user = AuthorizedUser()
client.force_authenticate(user)
base_uri = URITemplate(
'/with-query-parameter/'
'{?date,date_time,boolean,optional_boolean,array,expanded_array*,string,uid}',
)
query_params = {
'date': datetime.datetime.now().date(),
'date_time': datetime.datetime.now(),
'boolean': 'true',
'array': '5',
'expanded_array': ['5'],
'string': '',
'uid': str(uuid4()) + 'a',
}

base_uri = base_uri.expand(**query_params)

# Act
http_response = client.get(base_uri)
assert http_response.status_code == HTTPStatus.BAD_REQUEST
2 changes: 1 addition & 1 deletion winter/core/json/decoder.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
_decoders = {}

Item = TypeVar('Item')
uuid_regexp = re.compile(r'[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}')
uuid_regexp = re.compile(r'^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$')


class _MissingException(Exception):
Expand Down
4 changes: 2 additions & 2 deletions winter/web/exceptions/__init__.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
from .exception_handler_generator import ExceptionHandlerGenerator
from .exception_mapper import ExceptionMapper
from .exceptions import RedirectException
from .exceptions import ThrottleException
from .handlers import ExceptionHandler
from .handlers import MethodExceptionsManager
from .handlers import exception_handlers_registry
from .problem import problem
from .problem_handling_info import ProblemHandlingInfo
from .redirect_exception import RedirectException
from .throttle_exception import ThrottleException
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,9 @@
@problem(status=HTTPStatus.TOO_MANY_REQUESTS, detail='Request was throttled')
class ThrottleException(Exception):
pass


class RedirectException(Exception):
def __init__(self, redirect_to: str):
super().__init__()
self.redirect_to = redirect_to
4 changes: 0 additions & 4 deletions winter/web/exceptions/redirect_exception.py

This file was deleted.

2 changes: 1 addition & 1 deletion winter_openapi/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,13 @@
from .annotations import global_exception
from .annotations import register_global_exception
from .enum_inspector import inspect_enum_class
from .swagger_auto_schema import SwaggerAutoSchema
from .method_arguments_inspector import MethodArgumentsInspector
from .method_arguments_inspector import get_method_arguments_inspectors
from .method_arguments_inspector import register_controller_method_inspector
from .page_position_argument_inspector import PagePositionArgumentsInspector
from .path_parameters_inspector import PathParametersInspector
from .query_parameters_inspector import QueryParametersInspector
from .swagger_auto_schema import SwaggerAutoSchema
from .type_inspection import InspectorNotFound
from .type_inspection import TypeInfo
from .type_inspection import inspect_type
Expand Down

0 comments on commit b7c9788

Please sign in to comment.