Skip to content

Commit

Permalink
Merge pull request #562 from Cornices/add-option-force-cors
Browse files Browse the repository at this point in the history
Add option to force CORS headers in responses
  • Loading branch information
leplatrem authored Sep 29, 2021
2 parents b89a452 + 3436867 commit 271e27b
Show file tree
Hide file tree
Showing 4 changed files with 57 additions and 0 deletions.
4 changes: 4 additions & 0 deletions CHANGES.txt
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@ CHANGELOG

- Drop support of Marshmallow < 3

**New features**

- Add ``cornice.always_cors`` setting to force CORS headers in responses when services origins is ``*``.

**Bug fixes**

- Correctly determine service with pyramid route to ensure filters are applied and apply_cors_post_request is called
Expand Down
13 changes: 13 additions & 0 deletions cornice/cors.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
import fnmatch
import functools

from pyramid.settings import asbool


CORS_PARAMETERS = ('cors_headers', 'cors_enabled', 'cors_origins',
'cors_credentials', 'cors_max_age',
Expand Down Expand Up @@ -91,6 +93,17 @@ def ensure_origin(service, request, response=None, **kwargs):
method = _get_method(request)

origin = request.headers.get('Origin')

if not origin:
always_cors = asbool(
request.registry.settings.get("cornice.always_cors")
)
# With this setting, if the service origins has "*", then
# always return CORS headers.
origins = getattr(service, "cors_origins", [])
if always_cors and "*" in origins:
origin = "*"

if origin:
if not any([fnmatch.fnmatchcase(origin, o)
for o in service.cors_origins_for(method)]):
Expand Down
6 changes: 6 additions & 0 deletions docs/source/services.rst
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,12 @@ and appropriate headers validation.
cors_origins=('*',),
cors_max_age=3600)
.. note::

When ``*`` is among CORS origins and the setting ``cornice.always_cors`` is set to ``true``,
then CORS response headers are always returned.

There are also a number of parameters that are related to the support of
CORS (Cross Origin Resource Sharing). You can read the CORS specification
at http://www.w3.org/TR/cors/ and see :class:`the exhaustive list of options in Cornice <cornice.service.Service>`.
Expand Down
34 changes: 34 additions & 0 deletions tests/test_cors.py
Original file line number Diff line number Diff line change
Expand Up @@ -367,3 +367,37 @@ def test_preflight_does_not_need_authentication(self):
self.app.options('/spam', status=200,
headers={'Origin': 'notmyidea.org',
'Access-Control-Request-Method': 'POST'})


class TestAlwaysCORS(TestCase):

def setUp(self):
self.config = testing.setUp()
self.config.include('cornice')
self.config.add_settings({ "cornice.always_cors": True })
self.config.add_route('noservice', '/noservice')
self.config.scan('tests.test_cors')
self.app = TestApp(CatchErrors(self.config.make_wsgi_app()))

def tearDown(self):
testing.tearDown()

def test_response_returns_CORS_headers_without_origin(self):
resp = self.app.post('/bacon/response', status=200)
self.assertIn('Access-Control-Allow-Origin', resp.headers)

def test_response_does_not_return_CORS_headers_if_no_origin(self):
resp = self.app.put('/squirel')
self.assertNotIn('Access-Control-Allow-Origin', resp.headers)

def test_preflight_checks_origin_when_not_star(self):
self.app.options('/squirel',
headers={'Origin': 'notmyidea.org',
'Access-Control-Request-Method': 'PUT'},
status=400)
self.app.put('/squirel',
headers={'Origin': 'notmyidea.org'},
status=400)

def test_checks_origin_when_not_star(self):
self.app.put('/squirel', headers={'Origin': 'not foobar'}, status=400)

0 comments on commit 271e27b

Please sign in to comment.