diff options
Diffstat (limited to 'aiohttp_cors')
-rw-r--r-- | aiohttp_cors/__about__.py | 6 | ||||
-rw-r--r-- | aiohttp_cors/__init__.py | 3 | ||||
-rw-r--r-- | aiohttp_cors/_log.py | 22 | ||||
-rw-r--r-- | aiohttp_cors/abc.py | 9 | ||||
-rw-r--r-- | aiohttp_cors/cors_config.py | 200 | ||||
-rw-r--r-- | aiohttp_cors/mixin.py | 47 | ||||
-rw-r--r-- | aiohttp_cors/preflight_handler.py | 130 | ||||
-rw-r--r-- | aiohttp_cors/urldispatcher_router_adapter.py | 209 |
8 files changed, 263 insertions, 363 deletions
diff --git a/aiohttp_cors/__about__.py b/aiohttp_cors/__about__.py index eb70c30..51c4841 100644 --- a/aiohttp_cors/__about__.py +++ b/aiohttp_cors/__about__.py @@ -19,10 +19,10 @@ This module must be stand-alone executable. """ __title__ = "aiohttp-cors" -__version__ = "0.5.3" -__author__ = "Vladimir Rutsky" +__version__ = "0.7.0" +__author__ = "Vladimir Rutsky and aio-libs team" __email__ = "vladimir@rutsky.org" __summary__ = "CORS support for aiohttp" __uri__ = "https://github.com/aio-libs/aiohttp-cors" __license__ = "Apache License, Version 2.0" -__copyright__ = "2015, 2016, 2017 {}".format(__author__) +__copyright__ = "2015-2018 {}".format(__author__) diff --git a/aiohttp_cors/__init__.py b/aiohttp_cors/__init__.py index 49474c8..cbcc5ef 100644 --- a/aiohttp_cors/__init__.py +++ b/aiohttp_cors/__init__.py @@ -25,11 +25,12 @@ from .__about__ import ( ) from .resource_options import ResourceOptions from .cors_config import CorsConfig +from .mixin import CorsViewMixin, custom_cors __all__ = ( "__title__", "__version__", "__author__", "__email__", "__summary__", "__uri__", "__license__", "__copyright__", - "setup", "CorsConfig", "ResourceOptions", + "setup", "CorsConfig", "ResourceOptions", "CorsViewMixin", "custom_cors" ) diff --git a/aiohttp_cors/_log.py b/aiohttp_cors/_log.py deleted file mode 100644 index 5b216cb..0000000 --- a/aiohttp_cors/_log.py +++ /dev/null @@ -1,22 +0,0 @@ -# Copyright 2015 Vladimir Rutsky <vladimir@rutsky.org> -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -"""aiohttp_cors logger""" - -import logging - -__all__ = ("logger",) - -# pylint: disable=invalid-name -logger = logging.getLogger("aiohttp_cors") diff --git a/aiohttp_cors/abc.py b/aiohttp_cors/abc.py index cdddd36..5204a83 100644 --- a/aiohttp_cors/abc.py +++ b/aiohttp_cors/abc.py @@ -15,7 +15,6 @@ """Abstract base classes. """ -import asyncio from abc import ABCMeta, abstractmethod from aiohttp import web @@ -51,7 +50,10 @@ class AbstractRouterAdapter(metaclass=ABCMeta): """ @abstractmethod - def add_preflight_handler(self, routing_entity, handler): + def add_preflight_handler(self, + routing_entity, + handler, + webview: bool=False): """Add OPTIONS handler for all routes defined by `routing_entity`. Does nothing if CORS handler already handles routing entity. @@ -79,9 +81,8 @@ class AbstractRouterAdapter(metaclass=ABCMeta): entity. """ - @asyncio.coroutine @abstractmethod - def get_preflight_request_config( + async def get_preflight_request_config( self, preflight_request: web.Request, origin: str, diff --git a/aiohttp_cors/cors_config.py b/aiohttp_cors/cors_config.py index d8017c9..2e1aeea 100644 --- a/aiohttp_cors/cors_config.py +++ b/aiohttp_cors/cors_config.py @@ -15,22 +15,21 @@ """CORS configuration container class definition. """ -import asyncio import collections +import warnings from typing import Mapping, Union, Any from aiohttp import hdrs, web -from .urldispatcher_router_adapter import OldRoutesUrlDispatcherRouterAdapter from .urldispatcher_router_adapter import ResourcesUrlDispatcherRouterAdapter from .abc import AbstractRouterAdapter from .resource_options import ResourceOptions +from .preflight_handler import _PreflightHandler __all__ = ( "CorsConfig", ) - # Positive response to Access-Control-Allow-Credentials _TRUE = "true" # CORS simple response headers: @@ -103,7 +102,7 @@ def _parse_config_options( _ConfigType = Mapping[str, Union[ResourceOptions, Mapping[str, Any]]] -class _CorsConfigImpl: +class _CorsConfigImpl(_PreflightHandler): def __init__(self, app: web.Application, @@ -139,10 +138,9 @@ class _CorsConfigImpl: return routing_entity - @asyncio.coroutine - def _on_response_prepare(self, - request: web.Request, - response: web.StreamResponse): + async def _on_response_prepare(self, + request: web.Request, + response: web.StreamResponse): """Non-preflight CORS request response processor. If request is done on CORS-enabled route, process request parameters @@ -196,127 +194,11 @@ class _CorsConfigImpl: # Set allowed credentials. response.headers[hdrs.ACCESS_CONTROL_ALLOW_CREDENTIALS] = _TRUE - @staticmethod - def _parse_request_method(request: web.Request): - """Parse Access-Control-Request-Method header of the preflight request - """ - method = request.headers.get(hdrs.ACCESS_CONTROL_REQUEST_METHOD) - if method is None: - raise web.HTTPForbidden( - text="CORS preflight request failed: " - "'Access-Control-Request-Method' header is not specified") - - # FIXME: validate method string (ABNF: method = token), if parsing - # fails, raise HTTPForbidden. - - return method - - @staticmethod - def _parse_request_headers(request: web.Request): - """Parse Access-Control-Request-Headers header or the preflight request - - Returns set of headers in upper case. - """ - headers = request.headers.get(hdrs.ACCESS_CONTROL_REQUEST_HEADERS) - if headers is None: - return frozenset() - - # FIXME: validate each header string, if parsing fails, raise - # HTTPForbidden. - # FIXME: check, that headers split and stripped correctly (according - # to ABNF). - headers = (h.strip(" \t").upper() for h in headers.split(",")) - # pylint: disable=bad-builtin - return frozenset(filter(None, headers)) - - @asyncio.coroutine - def _preflight_handler(self, request: web.Request): - """CORS preflight request handler""" - - # Handle according to part 6.2 of the CORS specification. - - origin = request.headers.get(hdrs.ORIGIN) - if origin is None: - # Terminate CORS according to CORS 6.2.1. - raise web.HTTPForbidden( - text="CORS preflight request failed: " - "origin header is not specified in the request") - - # CORS 6.2.3. Doing it out of order is not an error. - request_method = self._parse_request_method(request) - - # CORS 6.2.5. Doing it out of order is not an error. - - try: - config = \ - yield from self._router_adapter.get_preflight_request_config( - request, origin, request_method) - except KeyError: - raise web.HTTPForbidden( - text="CORS preflight request failed: " - "request method {!r} is not allowed " - "for {!r} origin".format(request_method, origin)) - - if not config: - # No allowed origins for the route. - # Terminate CORS according to CORS 6.2.1. - raise web.HTTPForbidden( - text="CORS preflight request failed: " - "no origins are allowed") - - options = config.get(origin, config.get("*")) - if options is None: - # No configuration for the origin - deny. - # Terminate CORS according to CORS 6.2.2. - raise web.HTTPForbidden( - text="CORS preflight request failed: " - "origin '{}' is not allowed".format(origin)) - - # CORS 6.2.4 - request_headers = self._parse_request_headers(request) - - # CORS 6.2.6 - if options.allow_headers == "*": - pass - else: - disallowed_headers = request_headers - options.allow_headers - if disallowed_headers: - raise web.HTTPForbidden( - text="CORS preflight request failed: " - "headers are not allowed: {}".format( - ", ".join(disallowed_headers))) - - # Ok, CORS actual request with specified in the preflight request - # parameters is allowed. - # Set appropriate headers and return 200 response. - - response = web.Response() - - # CORS 6.2.7 - response.headers[hdrs.ACCESS_CONTROL_ALLOW_ORIGIN] = origin - if options.allow_credentials: - # Set allowed credentials. - response.headers[hdrs.ACCESS_CONTROL_ALLOW_CREDENTIALS] = _TRUE - - # CORS 6.2.8 - if options.max_age is not None: - response.headers[hdrs.ACCESS_CONTROL_MAX_AGE] = \ - str(options.max_age) - - # CORS 6.2.9 - # TODO: more optimal for client preflight request cache would be to - # respond with ALL allowed methods. - response.headers[hdrs.ACCESS_CONTROL_ALLOW_METHODS] = request_method - - # CORS 6.2.10 - if request_headers: - # Note: case of the headers in the request is changed, but this - # shouldn't be a problem, since the headers should be compared in - # the case-insensitive way. - response.headers[hdrs.ACCESS_CONTROL_ALLOW_HEADERS] = \ - ",".join(request_headers) - - return response + async def _get_config(self, request, origin, request_method): + config = \ + await self._router_adapter.get_preflight_request_config( + request, origin, request_method) + return config class CorsConfig: @@ -341,7 +223,7 @@ class CorsConfig: Router adapter. Required if application uses non-default router. """ - defaults = _parse_config_options(defaults) + self.defaults = _parse_config_options(defaults) self._cors_impl = None @@ -350,27 +232,16 @@ class CorsConfig: self._old_routes_cors_impl = None - if router_adapter is not None: - self._cors_impl = _CorsConfigImpl(app, router_adapter) - - elif isinstance(app.router, web.UrlDispatcher): - self._resources_router_adapter = \ - ResourcesUrlDispatcherRouterAdapter(app.router, defaults) - self._resources_cors_impl = _CorsConfigImpl( - app, - self._resources_router_adapter) - self._old_routes_cors_impl = _CorsConfigImpl( - app, - OldRoutesUrlDispatcherRouterAdapter(app.router, defaults)) - else: - raise RuntimeError( - "Router adapter is not specified. " - "Routers other than aiohttp.web.UrlDispatcher requires" - "custom router adapter.") + if router_adapter is None: + router_adapter = \ + ResourcesUrlDispatcherRouterAdapter(app.router, self.defaults) + + self._cors_impl = _CorsConfigImpl(app, router_adapter) def add(self, routing_entity, - config: _ConfigType = None): + config: _ConfigType = None, + webview: bool=False): """Enable CORS for specific route or resource. If route is passed CORS is enabled for route's resource. @@ -382,30 +253,11 @@ class CorsConfig: :return: `routing_entity`. """ - if self._cors_impl is not None: - # Custom router adapter. - return self._cors_impl.add(routing_entity, config) + if webview: + warnings.warn('webview argument is deprecated, ' + 'views are handled authomatically without ' + 'extra settings', + DeprecationWarning, + stacklevel=2) - else: - # UrlDispatcher. - - if isinstance(routing_entity, (web.Resource, web.StaticResource)): - # New Resource - use new router adapter. - return self._resources_cors_impl.add(routing_entity, config) - - elif isinstance(routing_entity, web.AbstractRoute): - if self._resources_router_adapter.is_cors_for_resource( - routing_entity.resource): - # Route which resource has CORS configuration in - # new-style router adapter. - return self._resources_cors_impl.add( - routing_entity, config) - else: - # Route which resource has no CORS configuration, i.e. - # old-style route. - return self._old_routes_cors_impl.add( - routing_entity, config) - - else: - raise ValueError( - "Unknown resource/route type: {!r}".format(routing_entity)) + return self._cors_impl.add(routing_entity, config) diff --git a/aiohttp_cors/mixin.py b/aiohttp_cors/mixin.py new file mode 100644 index 0000000..f5b4506 --- /dev/null +++ b/aiohttp_cors/mixin.py @@ -0,0 +1,47 @@ +import collections + +from .preflight_handler import _PreflightHandler + + +def custom_cors(config): + def wrapper(function): + name = "{}_cors_config".format(function.__name__) + setattr(function, name, config) + return function + return wrapper + + +class CorsViewMixin(_PreflightHandler): + cors_config = None + + @classmethod + def get_request_config(cls, request, request_method): + try: + from . import APP_CONFIG_KEY + cors = request.app[APP_CONFIG_KEY] + except KeyError: + raise ValueError("aiohttp-cors is not configured.") + + method = getattr(cls, request_method.lower(), None) + + if not method: + raise KeyError() + + config_property_key = "{}_cors_config".format(request_method.lower()) + + custom_config = getattr(method, config_property_key, None) + if not custom_config: + custom_config = {} + + class_config = cls.cors_config + if not class_config: + class_config = {} + + return collections.ChainMap(custom_config, class_config, cors.defaults) + + async def _get_config(self, request, origin, request_method): + return self.get_request_config(request, request_method) + + async def options(self): + response = await self._preflight_handler(self.request) + return response diff --git a/aiohttp_cors/preflight_handler.py b/aiohttp_cors/preflight_handler.py new file mode 100644 index 0000000..35e15e1 --- /dev/null +++ b/aiohttp_cors/preflight_handler.py @@ -0,0 +1,130 @@ +from aiohttp import hdrs, web + +# Positive response to Access-Control-Allow-Credentials +_TRUE = "true" + + +class _PreflightHandler: + + @staticmethod + def _parse_request_method(request: web.Request): + """Parse Access-Control-Request-Method header of the preflight request + """ + method = request.headers.get(hdrs.ACCESS_CONTROL_REQUEST_METHOD) + if method is None: + raise web.HTTPForbidden( + text="CORS preflight request failed: " + "'Access-Control-Request-Method' header is not specified") + + # FIXME: validate method string (ABNF: method = token), if parsing + # fails, raise HTTPForbidden. + + return method + + @staticmethod + def _parse_request_headers(request: web.Request): + """Parse Access-Control-Request-Headers header or the preflight request + + Returns set of headers in upper case. + """ + headers = request.headers.get(hdrs.ACCESS_CONTROL_REQUEST_HEADERS) + if headers is None: + return frozenset() + + # FIXME: validate each header string, if parsing fails, raise + # HTTPForbidden. + # FIXME: check, that headers split and stripped correctly (according + # to ABNF). + headers = (h.strip(" \t").upper() for h in headers.split(",")) + # pylint: disable=bad-builtin + return frozenset(filter(None, headers)) + + async def _get_config(self, request, origin, request_method): + raise NotImplementedError() + + async def _preflight_handler(self, request: web.Request): + """CORS preflight request handler""" + + # Handle according to part 6.2 of the CORS specification. + + origin = request.headers.get(hdrs.ORIGIN) + if origin is None: + # Terminate CORS according to CORS 6.2.1. + raise web.HTTPForbidden( + text="CORS preflight request failed: " + "origin header is not specified in the request") + + # CORS 6.2.3. Doing it out of order is not an error. + request_method = self._parse_request_method(request) + + # CORS 6.2.5. Doing it out of order is not an error. + + try: + config = \ + await self._get_config(request, origin, request_method) + except KeyError: + raise web.HTTPForbidden( + text="CORS preflight request failed: " + "request method {!r} is not allowed " + "for {!r} origin".format(request_method, origin)) + + if not config: + # No allowed origins for the route. + # Terminate CORS according to CORS 6.2.1. + raise web.HTTPForbidden( + text="CORS preflight request failed: " + "no origins are allowed") + + options = config.get(origin, config.get("*")) + if options is None: + # No configuration for the origin - deny. + # Terminate CORS according to CORS 6.2.2. + raise web.HTTPForbidden( + text="CORS preflight request failed: " + "origin '{}' is not allowed".format(origin)) + + # CORS 6.2.4 + request_headers = self._parse_request_headers(request) + + # CORS 6.2.6 + if options.allow_headers == "*": + pass + else: + disallowed_headers = request_headers - options.allow_headers + if disallowed_headers: + raise web.HTTPForbidden( + text="CORS preflight request failed: " + "headers are not allowed: {}".format( + ", ".join(disallowed_headers))) + + # Ok, CORS actual request with specified in the preflight request + # parameters is allowed. + # Set appropriate headers and return 200 response. + + response = web.Response() + + # CORS 6.2.7 + response.headers[hdrs.ACCESS_CONTROL_ALLOW_ORIGIN] = origin + if options.allow_credentials: + # Set allowed credentials. + response.headers[hdrs.ACCESS_CONTROL_ALLOW_CREDENTIALS] = _TRUE + + # CORS 6.2.8 + if options.max_age is not None: + response.headers[hdrs.ACCESS_CONTROL_MAX_AGE] = \ + str(options.max_age) + + # CORS 6.2.9 + # TODO: more optimal for client preflight request cache would be to + # respond with ALL allowed methods. + response.headers[hdrs.ACCESS_CONTROL_ALLOW_METHODS] = request_method + + # CORS 6.2.10 + if request_headers: + # Note: case of the headers in the request is changed, but this + # shouldn't be a problem, since the headers should be compared in + # the case-insensitive way. + response.headers[hdrs.ACCESS_CONTROL_ALLOW_HEADERS] = \ + ",".join(request_headers) + + return response diff --git a/aiohttp_cors/urldispatcher_router_adapter.py b/aiohttp_cors/urldispatcher_router_adapter.py index 92bfbb3..1a65e99 100644 --- a/aiohttp_cors/urldispatcher_router_adapter.py +++ b/aiohttp_cors/urldispatcher_router_adapter.py @@ -14,9 +14,7 @@ """AbstractRouterAdapter for aiohttp.web.UrlDispatcher. """ -import asyncio import collections -import re from typing import Union @@ -24,6 +22,7 @@ from aiohttp import web from aiohttp import hdrs from .abc import AbstractRouterAdapter +from .mixin import CorsViewMixin # There several usage patterns of routes which should be handled @@ -89,6 +88,22 @@ class _ResourceConfig: self.method_config = {} +def _is_web_view(entity, strict=True): + webview = False + if isinstance(entity, web.AbstractRoute): + handler = entity.handler + if isinstance(handler, type) and issubclass(handler, web.View): + webview = True + if not issubclass(handler, CorsViewMixin): + if strict: + raise ValueError("web view should be derived from " + "aiohttp_cors.WebViewMixig for working " + "with the library") + else: + return False + return webview + + class ResourcesUrlDispatcherRouterAdapter(AbstractRouterAdapter): """Adapter for `UrlDispatcher` for Resources-based routing only. @@ -138,6 +153,22 @@ class ResourcesUrlDispatcherRouterAdapter(AbstractRouterAdapter): if resource in self._resources_with_preflight_handlers: # Preflight handler already added for this resource. return + for route_obj in resource: + if route_obj.method == hdrs.METH_OPTIONS: + if route_obj.handler is handler: + return # already added + else: + raise ValueError( + "{!r} already has OPTIONS handler {!r}" + .format(resource, route_obj.handler)) + elif route_obj.method == hdrs.METH_ANY: + if _is_web_view(route_obj): + self._preflight_routes.add(route_obj) + self._resources_with_preflight_handlers.add(resource) + return + else: + raise ValueError("{!r} already has a '*' handler " + "for all methods".format(resource)) preflight_route = resource.add_route(hdrs.METH_OPTIONS, handler) self._preflight_routes.add(preflight_route) @@ -160,13 +191,8 @@ class ResourcesUrlDispatcherRouterAdapter(AbstractRouterAdapter): elif isinstance(routing_entity, web.ResourceRoute): route = routing_entity - # Preflight handler for Route's Resource already must be - # configured. if not self.is_cors_for_resource(route.resource): - raise ValueError( - "Can't setup CORS for {!r} request, " - "CORS must be enabled for route's resource first.".format( - route)) + self.add_preflight_handler(route.resource, handler) else: raise ValueError( @@ -187,8 +213,10 @@ class ResourcesUrlDispatcherRouterAdapter(AbstractRouterAdapter): def is_preflight_request(self, request: web.Request) -> bool: """Is `request` is a CORS preflight request.""" - - return self._request_route(request) in self._preflight_routes + route = self._request_route(request) + if _is_web_view(route, strict=False): + return request.method == 'OPTIONS' + return route in self._preflight_routes def is_cors_enabled_on_request(self, request: web.Request) -> bool: """Is `request` is a request for CORS-enabled resource.""" @@ -219,6 +247,9 @@ class ResourcesUrlDispatcherRouterAdapter(AbstractRouterAdapter): # Add resource's route configuration or fail if it's already added. if route.resource not in self._resource_config: + self.set_config_for_routing_entity(route.resource, config) + + if route.resource not in self._resource_config: raise ValueError( "Can't setup CORS for {!r} request, " "CORS must be enabled for route's resource first.".format( @@ -239,8 +270,7 @@ class ResourcesUrlDispatcherRouterAdapter(AbstractRouterAdapter): "Resource or ResourceRoute expected, got {!r}".format( routing_entity)) - @asyncio.coroutine - def get_preflight_request_config( + async def get_preflight_request_config( self, preflight_request: web.Request, origin: str, @@ -279,155 +309,16 @@ class ResourcesUrlDispatcherRouterAdapter(AbstractRouterAdapter): resource_config = self._resource_config[resource] # Take Route config (if any) with defaults from Resource CORS # configuration and global defaults. + route = request.match_info.route + if _is_web_view(route, strict=False): + method_config = request.match_info.handler.get_request_config( + request, request.method) + else: + method_config = resource_config.method_config.get(request.method, + {}) defaulted_config = collections.ChainMap( - resource_config.method_config.get(request.method, {}), + method_config, resource_config.default_config, self._default_config) return defaulted_config - - -class OldRoutesUrlDispatcherRouterAdapter(AbstractRouterAdapter): - """Adapter for `UrlDispatcher` for old-style routing only. - - In all use cases when Resource is not explicitly used, - Resource will automatically allocated for old route. - In this case all routes will have it's own resource, and to find - related routes (routes that shares same path) we need to iterate over - all routes with enabled CORS and check is they handle specific path. - - This whole class should go away when user will migrate to proper - Resource/Route usage scheme. - """ - - def __init__(self, - router: web.UrlDispatcher, - defaults): - """ - :param defaults: - Default CORS configuration. - """ - self._router = router - - # Default configuration for all routes. - self._default_config = defaults - - # Mapping from route to config. - self._route_config = collections.OrderedDict() - - self._preflight_routes = set() - - def add_preflight_handler( - self, - route: web.AbstractRoute, - handler): - """Add OPTIONS handler for same paths that `route` handles.""" - - assert isinstance(route, web.AbstractRoute) - - if isinstance(route, web.ResourceRoute): - # New-style route (which Resource is not used explicitly, - # otherwise it would be handled by other adapter). - preflight_route = route.resource.add_route( - hdrs.METH_OPTIONS, handler) - - elif isinstance(route, web.Route): - # Old-style route. - - if isinstance(route, web.StaticRoute): - # TODO: Use custom matches that uses `str.startswith()` - # if regexp performance is not enough. - pattern = re.compile("^" + re.escape(route._prefix)) - preflight_route = web.DynamicRoute( - hdrs.METH_OPTIONS, handler, None, pattern, "") - self._router.register_route(preflight_route) - - elif isinstance(route, web.PlainRoute): - # May occur only if user manually creates PlainRoute. - preflight_route = self._router.add_route( - hdrs.METH_OPTIONS, route._path, handler) - - elif isinstance(route, web.DynamicRoute): - # May occur only if user manually creates DynamicRoute. - preflight_route = web.DynamicRoute( - hdrs.METH_OPTIONS, handler, None, - route._pattern, route._formatter) - self._router.register_route(preflight_route) - - else: - raise RuntimeError( - "Unhandled deprecated route type {!r}".format(route)) - - else: - raise RuntimeError("Unhandled route type {!r}".format(route)) - - self._preflight_routes.add(preflight_route) - - def _request_route(self, request: web.Request) -> web.ResourceRoute: - match_info = request.match_info - assert isinstance(match_info, web.UrlMappingMatchInfo) - return match_info.route - - def is_preflight_request(self, request: web.Request) -> bool: - """Is `request` is a CORS preflight request.""" - - return self._request_route(request) in self._preflight_routes - - def is_cors_enabled_on_request(self, request: web.Request) -> bool: - """Is `request` is a request for CORS-enabled resource.""" - - return self._request_route(request) in self._route_config - - def set_config_for_routing_entity( - self, - route: web.AbstractRoute, - config): - """Record CORS configuration for route.""" - - assert isinstance(route, web.AbstractRoute) - - if any(options.allow_methods is not None - for options in config.values()): - raise ValueError( - "'allow_methods' parameter is not supported on old-style " - "routes. You specified {!r} for {!r}. " - "Use Resources to configure CORS.".format( - config, route)) - - if route in self._route_config: - raise ValueError( - "CORS is already configured for {!r} route.".format( - route)) - - self._route_config[route] = config - - @asyncio.coroutine - def get_preflight_request_config( - self, - preflight_request: web.Request, - origin: str, - requested_method: str): - assert self.is_preflight_request(preflight_request) - - request = preflight_request.clone(method=requested_method) - for route, config in self._route_config.items(): - match_info, allowed_methods = yield from route.resource.resolve( - request) - if match_info is not None: - return collections.ChainMap(config, self._default_config) - else: - raise KeyError - - def get_non_preflight_request_config(self, request: web.Request): - """Get stored CORS configuration for routing entity that handles - specified request.""" - - assert self.is_cors_enabled_on_request(request) - - route = self._request_route(request) - route_config = self._route_config[route] - - defaulted_config = collections.ChainMap( - route_config, self._default_config) - - return defaulted_config |