Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Global cors #1276

Merged
merged 2 commits into from
Oct 22, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ Next Release (TBD)
* Fix error for ``chalice logs`` when a Lambda function
has not been invoked
(`#1252 <https://github.com/aws/chalice/issues/1252>`__)
* Add global CORS configuration
(`#70 <https://github.com/aws/chalice/pull/70>`__)


1.12.0
Expand Down
5 changes: 3 additions & 2 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -825,7 +825,8 @@ Tutorial: CORS Support

You can specify whether a view supports CORS by adding the
``cors=True`` parameter to your ``@app.route()`` call. By
default this value is false:
default this value is ``False``. Global CORS can be set by
setting ``app.api.cors = True``.

.. code-block:: python

Expand All @@ -834,7 +835,7 @@ default this value is false:
return {}


Settings ``cors=True`` has similar behavior to enabling CORS
Setting ``cors=True`` has similar behavior to enabling CORS
using the AWS Console. This includes:

* Injecting the ``Access-Control-Allow-Origin: *`` header to your
Expand Down
7 changes: 5 additions & 2 deletions chalice/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -519,6 +519,7 @@ class APIGateway(object):

def __init__(self):
self.binary_types = self.default_binary_types
self.cors = False

@property
def default_binary_types(self):
Expand Down Expand Up @@ -731,6 +732,7 @@ def __init__(self):
self.builtin_auth_handlers = []
self.event_sources = []
self.pure_lambda_functions = []
self.api = APIGateway()

def _do_register_handler(self, handler_type, name, user_handler,
wrapped_handler, kwargs, options=None):
Expand Down Expand Up @@ -888,8 +890,10 @@ def _register_route(self, name, user_handler, kwargs, **unused):
'api_key_required': actual_kwargs.pop('api_key_required', None),
'content_types': actual_kwargs.pop('content_types',
['application/json']),
'cors': actual_kwargs.pop('cors', False),
'cors': actual_kwargs.pop('cors', self.api.cors),
}
if route_kwargs['cors'] is None:
route_kwargs['cors'] = self.api.cors
if not isinstance(route_kwargs['content_types'], list):
raise ValueError(
'In view function "%s", the content_types '
Expand Down Expand Up @@ -920,7 +924,6 @@ class Chalice(_HandlerRegistration, DecoratorAPI):
def __init__(self, app_name, debug=False, configure_logs=True, env=None):
super(Chalice, self).__init__()
self.app_name = app_name
self.api = APIGateway()
self.websocket_api = WebsocketAPI()
self.current_request = None
self.lambda_context = None
Expand Down
23 changes: 17 additions & 6 deletions docs/source/api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -101,13 +101,13 @@ Chalice
the method required a valid API key.

:param cors: Specify if CORS is supported for this view. This can either
by a boolean value or an instance of :class:`CORSConfig`. Setting this
value is set to ``True`` gives similar behavior to enabling CORS in the
AWS Console. This includes injecting the
by a boolean value, ``None``, or an instance of :class:`CORSConfig`.
Setting this value is set to ``True`` gives similar behavior to enabling
CORS in the AWS Console. This includes injecting the
``Access-Control-Allow-Origin`` header to have a value of ``*`` as well
as adding an ``OPTIONS`` method to support preflighting requests. If
you would like more control over how CORS is configured, you can
provide an instance of :class:`CORSConfig`.
you would like more control over how CORS is configured, you can provide
an instance of :class:`CORSConfig`.

.. method:: authorizer(name, \*\*options)

Expand Down Expand Up @@ -615,6 +615,17 @@ APIGateway
There is a single instance of this class attached to each
:class:`Chalice` object under the ``api`` attribute.

.. attribute:: cors

Global cors configuration. If a route-level cors configuration is not
provided, or is ``None`` then this configuration will be used. By
default it is set to ``False``. This can either be ``True``, ``False``,
or an instance of the ``CORSConfig`` class. This makes it easy to enable
CORS for your entire application by setting ``app.api.cors = True``.

.. versionadded:: 1.12.1


.. attribute:: default_binary_types

The value of ``default_binary_types`` are the ``Content-Types`` that are
Expand Down Expand Up @@ -772,7 +783,7 @@ CORS

.. class:: CORSConfig(allow_origin='*', allow_headers=None, expose_headers=None, max_age=None, allow_credentials=None)

CORS configuration to attach to a route.
CORS configuration to attach to a route, or globally on ``app.api.cors``.

.. code-block:: python

Expand Down
43 changes: 43 additions & 0 deletions tests/unit/test_app.py
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,29 @@ def image():
return demo


@fixture
def sample_app_with_default_cors():
demo = app.Chalice('demo-app')
demo.api.cors = True

@demo.route('/on', methods=['POST'],
content_types=['image/gif'])
def on():
return {'image': True}

@demo.route('/off', methods=['POST'], cors=False,
content_types=['image/gif'])
def off():
return {'image': True}

@demo.route('/default', methods=['POST'], cors=None,
content_types=['image/gif'])
def default():
return {'image': True}

return demo


@fixture
def sample_websocket_app():
demo = app.Chalice('app-name')
Expand Down Expand Up @@ -429,6 +452,26 @@ def test_error_contains_cors_headers(sample_app_with_cors, create_event):
assert 'Access-Control-Allow-Origin' in raw_response['headers']


class TestDefaultCORS(object):
def test_cors_enabled(self, sample_app_with_default_cors, create_event):
event = create_event('/on', 'POST', {'not': 'image'})
raw_response = sample_app_with_default_cors(event, context=None)
assert raw_response['statusCode'] == 415
assert 'Access-Control-Allow-Origin' in raw_response['headers']

def test_cors_none(self, sample_app_with_default_cors, create_event):
event = create_event('/default', 'POST', {'not': 'image'})
raw_response = sample_app_with_default_cors(event, context=None)
assert raw_response['statusCode'] == 415
assert 'Access-Control-Allow-Origin' in raw_response['headers']

def test_cors_disabled(self, sample_app_with_default_cors, create_event):
event = create_event('/off', 'POST', {'not': 'image'})
raw_response = sample_app_with_default_cors(event, context=None)
assert raw_response['statusCode'] == 415
assert 'Access-Control-Allow-Origin' not in raw_response['headers']


def test_no_view_function_found(sample_app, create_event):
bad_path = create_event('/noexist', 'GET', {})
with pytest.raises(app.ChaliceError):
Expand Down