Commit fc42e34d authored by Viacheslav Sukhovieiev's avatar Viacheslav Sukhovieiev
Browse files

feat: add handler decorator logic

parent 0225f1f8
Pipeline #35934 passed with stages
in 2 minutes and 28 seconds
......@@ -15,6 +15,7 @@
"""CORS support for aiohttp.
"""
import functools
from typing import Mapping, Union, Any
from aiohttp import web
......@@ -65,3 +66,39 @@ def setup(app: web.Application, *,
cors = CorsConfig(app, defaults=defaults)
app[APP_CONFIG_KEY] = cors
return cors
def get_base_config() -> dict:
"""
returns base CORS config
{
"*": ResourceOptions(
allow_credentials=True,
expose_headers="*",
allow_headers="*",
allow_methods="*",
)
}
"""
return {
"*": ResourceOptions(
allow_credentials=True,
expose_headers="*",
allow_headers="*",
allow_methods="*",
)
}
def custom_handler(config=None, defaults=False):
if not config and defaults is True:
config = get_base_config()
def wrapper(func):
@functools.wraps(func)
def handler(*args, **kwargs):
return func(*args, **kwargs)
handler.__apply_cors = True
handler.__cors_config = config
return handler
return wrapper
......@@ -261,3 +261,9 @@ class CorsConfig:
stacklevel=2)
return self._cors_impl.add(routing_entity, config)
def apply(self, app: web.Application):
for route in list(app.router.routes()):
if hasattr(route.handler, "__apply_cors"):
config = getattr(route.handler, "__cors_config", None)
self.add(route, config)
......@@ -279,9 +279,10 @@ class ResourcesUrlDispatcherRouterAdapter(AbstractRouterAdapter):
resource = self._request_resource(preflight_request)
resource_config = self._resource_config[resource]
defaulted_config = collections.ChainMap(
resource_config.default_config,
self._default_config)
if resource_config.default_config:
defaulted_config = resource_config.default_config
else:
defaulted_config = self._default_config
options = defaulted_config.get(origin, defaulted_config.get("*"))
if options is not None and options.is_method_allowed(requested_method):
......
......@@ -22,7 +22,7 @@ import pytest
from aiohttp import web
from aiohttp import hdrs
from prozorro_sale.cors import setup as _setup, ResourceOptions, CorsViewMixin
from prozorro_sale.cors import setup as _setup, ResourceOptions, CorsViewMixin, custom_handler
TEST_BODY = "Hello, world"
......@@ -40,6 +40,15 @@ async def handler(request: web.Request) -> web.StreamResponse:
return response
def decorated_handler(config=None, defaults=False):
@custom_handler(config, defaults)
async def handler(request: web.Request) -> web.StreamResponse:
response = web.Response(text=TEST_BODY)
response.headers[SERVER_CUSTOM_HEADER_NAME] = SERVER_CUSTOM_HEADER_VALUE
return response
return handler
class WebViewHandler(web.View, CorsViewMixin):
async def get(self) -> web.StreamResponse:
......@@ -912,6 +921,180 @@ async def test_static_route(aiohttp_client):
assert data == ''
@pytest.mark.parametrize('config, defaults, status, message', [
pytest.param(
{
"*": ResourceOptions(
allow_credentials=True,
expose_headers="*",
allow_headers="*",
)
},
False,
200,
'',
id='with config without defaults'
),
pytest.param(
None,
True,
200,
'',
id='without config with defaults'
),
pytest.param(
None,
False,
403,
'CORS preflight request failed: no origins are allowed',
id='without config and without defaults'
)
])
async def test_custom_handler(aiohttp_client, config, defaults, status, message):
app = web.Application()
app.router.add_route("PUT", "/{name}", decorated_handler(config, defaults))
cors = _setup(app)
cors.apply(app)
client = await aiohttp_client(app)
resp = await client.options(
"/user",
headers={
hdrs.ORIGIN: "http://example.org",
hdrs.ACCESS_CONTROL_REQUEST_METHOD: "PUT"
}
)
assert resp.status == status
data = await resp.text()
assert data == message
async def test_cors_apply_only_on_custom_handler(aiohttp_client):
app = web.Application()
config = {
"*": ResourceOptions(
allow_credentials=True,
expose_headers="*",
allow_headers="*",
)
}
app.router.add_route("PUT", "/{name}/decorated", decorated_handler(config))
app.router.add_route("PUT", "/{name}/non_decorated", handler)
cors = _setup(app)
cors.apply(app)
client = await aiohttp_client(app)
resp = await client.options(
"/user/non_decorated",
headers={
hdrs.ORIGIN: "http://example.org",
hdrs.ACCESS_CONTROL_REQUEST_METHOD: "PUT"
}
)
assert resp.status == 405
data = await resp.text()
assert data == "405: Method Not Allowed"
resp = await client.options(
"/user/decorated",
headers={
hdrs.ORIGIN: "http://example.org",
hdrs.ACCESS_CONTROL_REQUEST_METHOD: "PUT"
}
)
assert resp.status == 200
data = await resp.text()
assert data == ""
async def test_custom_handler_with_defaults(aiohttp_client):
app = web.Application()
config = {
"*": ResourceOptions(
allow_credentials=True,
expose_headers="*",
allow_headers="*",
)
}
app.router.add_route("PUT", "/{name}", decorated_handler())
cors = _setup(app, defaults=config)
cors.apply(app)
client = await aiohttp_client(app)
resp = await client.options(
"/user",
headers={
hdrs.ORIGIN: "http://example.org",
hdrs.ACCESS_CONTROL_REQUEST_METHOD: "PUT"
}
)
assert resp.status == 200
data = await resp.text()
assert data == ""
async def test_custom_handler_config_rewrite_defaults(aiohttp_client):
app = web.Application()
options = ResourceOptions(
allow_credentials=True,
expose_headers="*",
allow_headers="*",
)
default_origin_url = "http://example.org"
handler_origin_url = "http://example2.org"
config = {default_origin_url: options}
handler_config = {handler_origin_url: options}
app.router.add_route("PUT", "/{name}", decorated_handler(handler_config))
cors = _setup(app, defaults=config)
cors.apply(app)
client = await aiohttp_client(app)
resp = await client.options(
"/user",
headers={
hdrs.ORIGIN: default_origin_url,
hdrs.ACCESS_CONTROL_REQUEST_METHOD: "PUT"
}
)
assert resp.status == 403
data = await resp.text()
assert data == f"CORS preflight request failed: origin '{default_origin_url}' is not allowed"
resp = await client.options(
"/user",
headers={
hdrs.ORIGIN: handler_origin_url,
hdrs.ACCESS_CONTROL_REQUEST_METHOD: "PUT"
}
)
assert resp.status == 200
data = await resp.text()
assert data == ""
# TODO: test requesting resources with not configured CORS.
# TODO: test wildcard origin in default config.
# TODO: test different combinations of ResourceOptions options.
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment