Skip to content

Commit

Permalink
Merge branch 'brew-1944-cors-in-config'
Browse files Browse the repository at this point in the history
  • Loading branch information
amercader committed Nov 4, 2014
2 parents cf68e74 + 0d97844 commit 852aaaa
Show file tree
Hide file tree
Showing 6 changed files with 212 additions and 29 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.rst
Expand Up @@ -29,6 +29,12 @@ API changes and deprecations
Note that logic.get_action() and toolkit.get_action() are *not* deprecated,
core code and plugin code should still use ``get_action()``.

* Cross-Origin Resource Sharing (CORS) support is no longer enabled by
default. Previously, Access-Control-Allow-* response headers were added for
all requests, with Access-Control-Allow-Origin set to the wildcard value
``*``. To re-enable CORS, use the new ``ckan.cors`` settings detailed in the
Config File Options documentation (:doc:`/maintaining/configuration`)

Template changes
----------------

Expand Down
9 changes: 9 additions & 0 deletions ckan/config/deployment.ini_tmpl
Expand Up @@ -78,6 +78,15 @@ ckan.site_id = default
#ckan.simple_search = 1


## CORS Settings

# If cors.origin_allow_all is true, all origins are allowed.
# If false, the cors.origin_whitelist is used.
# ckan.cors.origin_allow_all = true
# cors.origin_whitelist is a space separated list of allowed domains.
# ckan.cors.origin_whitelist = http://example1.com http://example2.com


## Plugins Settings

# Note: Add ``datastore`` to enable the CKAN DataStore
Expand Down
30 changes: 24 additions & 6 deletions ckan/lib/base.py
Expand Up @@ -369,17 +369,35 @@ def __call__(self, environ, start_response):
return res

def __after__(self, action, **params):
self._set_cors()
# Do we have CORS settings in config?
if config.get('ckan.cors.origin_allow_all') \
and request.headers.get('Origin'):
self._set_cors()
r_time = time.time() - c.__timer
url = request.environ['CKAN_CURRENT_URL'].split('?')[0]
log.info(' %s render time %.3f seconds' % (url, r_time))

def _set_cors(self):
response.headers['Access-Control-Allow-Origin'] = "*"
response.headers['Access-Control-Allow-Methods'] = \
"POST, PUT, GET, DELETE, OPTIONS"
response.headers['Access-Control-Allow-Headers'] = \
"X-CKAN-API-KEY, Authorization, Content-Type"
'''
Set up Access Control Allow headers if either origin_allow_all is
True, or the request Origin is in the origin_whitelist.
'''
cors_origin_allowed = None
if asbool(config.get('ckan.cors.origin_allow_all')):
cors_origin_allowed = "*"
elif config.get('ckan.cors.origin_whitelist') and \
request.headers.get('Origin') \
in config['ckan.cors.origin_whitelist'].split(" "):
# set var to the origin to allow it.
cors_origin_allowed = request.headers.get('Origin')

if cors_origin_allowed is not None:
response.headers['Access-Control-Allow-Origin'] = \
cors_origin_allowed
response.headers['Access-Control-Allow-Methods'] = \
"POST, PUT, GET, DELETE, OPTIONS"
response.headers['Access-Control-Allow-Headers'] = \
"X-CKAN-API-KEY, Authorization, Content-Type"

def _get_user_for_apikey(self):
apikey_header_name = config.get(APIKEY_HEADER_NAME_KEY,
Expand Down
145 changes: 145 additions & 0 deletions ckan/new_tests/lib/test_base.py
@@ -0,0 +1,145 @@
from nose import tools as nose_tools

from ckan.new_tests import helpers


class TestCORS(helpers.FunctionalTestBase):

def test_options(self):
app = self._get_test_app()
response = app.options(url='/', status=200)
assert len(str(response.body)) == 0, 'OPTIONS must return no content'

def test_cors_config_no_cors(self):
'''
No ckan.cors settings in config, so no Access-Control-Allow headers in
response.
'''
app = self._get_test_app()
response = app.get('/')
response_headers = dict(response.headers)

assert 'Access-Control-Allow-Origin' not in response_headers
assert 'Access-Control-Allow-Methods' not in response_headers
assert 'Access-Control-Allow-Headers' not in response_headers

def test_cors_config_no_cors_with_origin(self):
'''
No ckan.cors settings in config, so no Access-Control-Allow headers in
response, even with origin header in request.
'''
app = self._get_test_app()
request_headers = {'Origin': 'http://thirdpartyrequests.org'}
response = app.get('/', headers=request_headers)
response_headers = dict(response.headers)

assert 'Access-Control-Allow-Origin' not in response_headers
assert 'Access-Control-Allow-Methods' not in response_headers
assert 'Access-Control-Allow-Headers' not in response_headers

@helpers.change_config('ckan.cors.origin_allow_all', 'true')
def test_cors_config_origin_allow_all_true_no_origin(self):
'''
With origin_allow_all set to true, but no origin in the request
header, no Access-Control-Allow headers should be in the response.
'''
app = self._get_test_app()
response = app.get('/')
response_headers = dict(response.headers)

assert 'Access-Control-Allow-Origin' not in response_headers
assert 'Access-Control-Allow-Methods' not in response_headers
assert 'Access-Control-Allow-Headers' not in response_headers

@helpers.change_config('ckan.cors.origin_allow_all', 'true')
@helpers.change_config('ckan.site_url', 'http://test.ckan.org')
def test_cors_config_origin_allow_all_true_with_origin(self):
'''
With origin_allow_all set to true, and an origin in the request
header, the appropriate Access-Control-Allow headers should be in the
response.
'''
app = self._get_test_app()
request_headers = {'Origin': 'http://thirdpartyrequests.org'}
response = app.get('/', headers=request_headers)
response_headers = dict(response.headers)

assert 'Access-Control-Allow-Origin' in response_headers
nose_tools.assert_equal(response_headers['Access-Control-Allow-Origin'], '*')
nose_tools.assert_equal(response_headers['Access-Control-Allow-Methods'], "POST, PUT, GET, DELETE, OPTIONS")
nose_tools.assert_equal(response_headers['Access-Control-Allow-Headers'], "X-CKAN-API-KEY, Authorization, Content-Type")

@helpers.change_config('ckan.cors.origin_allow_all', 'false')
@helpers.change_config('ckan.site_url', 'http://test.ckan.org')
def test_cors_config_origin_allow_all_false_with_origin_without_whitelist(self):
'''
With origin_allow_all set to false, with an origin in the request
header, but no whitelist defined, there should be no Access-Control-
Allow headers in the response.
'''
app = self._get_test_app()
request_headers = {'Origin': 'http://thirdpartyrequests.org'}
response = app.get('/', headers=request_headers)
response_headers = dict(response.headers)

assert 'Access-Control-Allow-Origin' not in response_headers
assert 'Access-Control-Allow-Methods' not in response_headers
assert 'Access-Control-Allow-Headers' not in response_headers

@helpers.change_config('ckan.cors.origin_allow_all', 'false')
@helpers.change_config('ckan.cors.origin_whitelist', 'http://thirdpartyrequests.org')
@helpers.change_config('ckan.site_url', 'http://test.ckan.org')
def test_cors_config_origin_allow_all_false_with_whitelisted_origin(self):
'''
With origin_allow_all set to false, with an origin in the request
header, and a whitelist defined (containing the origin), the
appropriate Access-Control-Allow headers should be in the response.
'''
app = self._get_test_app()
request_headers = {'Origin': 'http://thirdpartyrequests.org'}
response = app.get('/', headers=request_headers)
response_headers = dict(response.headers)

assert 'Access-Control-Allow-Origin' in response_headers
nose_tools.assert_equal(response_headers['Access-Control-Allow-Origin'], 'http://thirdpartyrequests.org')
nose_tools.assert_equal(response_headers['Access-Control-Allow-Methods'], "POST, PUT, GET, DELETE, OPTIONS")
nose_tools.assert_equal(response_headers['Access-Control-Allow-Headers'], "X-CKAN-API-KEY, Authorization, Content-Type")

@helpers.change_config('ckan.cors.origin_allow_all', 'false')
@helpers.change_config('ckan.cors.origin_whitelist', 'http://google.com http://thirdpartyrequests.org http://yahoo.co.uk')
@helpers.change_config('ckan.site_url', 'http://test.ckan.org')
def test_cors_config_origin_allow_all_false_with_multiple_whitelisted_origins(self):
'''
With origin_allow_all set to false, with an origin in the request
header, and a whitelist defining multiple allowed origins (containing
the origin), the appropriate Access-Control-Allow headers should be in
the response.
'''
app = self._get_test_app()
request_headers = {'Origin': 'http://thirdpartyrequests.org'}
response = app.get('/', headers=request_headers)
response_headers = dict(response.headers)

assert 'Access-Control-Allow-Origin' in response_headers
nose_tools.assert_equal(response_headers['Access-Control-Allow-Origin'], 'http://thirdpartyrequests.org')
nose_tools.assert_equal(response_headers['Access-Control-Allow-Methods'], "POST, PUT, GET, DELETE, OPTIONS")
nose_tools.assert_equal(response_headers['Access-Control-Allow-Headers'], "X-CKAN-API-KEY, Authorization, Content-Type")

@helpers.change_config('ckan.cors.origin_allow_all', 'false')
@helpers.change_config('ckan.cors.origin_whitelist', 'http://google.com http://yahoo.co.uk')
@helpers.change_config('ckan.site_url', 'http://test.ckan.org')
def test_cors_config_origin_allow_all_false_with_whitelist_not_containing_origin(self):
'''
With origin_allow_all set to false, with an origin in the request
header, and a whitelist defining multiple allowed origins (but not
containing the requesting origin), there should be no Access-Control-
Allow headers in the response.
'''
app = self._get_test_app()
request_headers = {'Origin': 'http://thirdpartyrequests.org'}
response = app.get('/', headers=request_headers)
response_headers = dict(response.headers)

assert 'Access-Control-Allow-Origin' not in response_headers
assert 'Access-Control-Allow-Methods' not in response_headers
assert 'Access-Control-Allow-Headers' not in response_headers
23 changes: 0 additions & 23 deletions ckan/tests/functional/test_cors.py

This file was deleted.

28 changes: 28 additions & 0 deletions doc/maintaining/configuration.rst
Expand Up @@ -515,6 +515,34 @@ Default value: ``None``
List of the extra resource fields that would be used when searching.


CORS Settings
-------------

Cross-Origin Resource Sharing (CORS) can be enabled and controlled with the following settings:

.. _ckan.cors.origin_allow_all:

ckan.cors.origin_allow_all
^^^^^^^^^^^^^^^^^^^^^^^^^^

Example::

ckan.cors.origin_allow_all = True

This setting must be present to enable CORS. If True, all origins will be allowed (the response header Access-Control-Allow-Origin is set to '*'). If False, only origins from the ``ckan.cors.origin_whitelist`` setting will be allowed.

.. _ckan.cors.origin_whitelist:

ckan.cors.origin_whitelist
^^^^^^^^^^^^^^^^^^^^^^^^^^

Example::

ckan.cors.origin_whitelist = http://www.myremotedomain1.com http://myremotedomain1.com

A space separated list of allowable origins. This setting is used when ``ckan.cors.origin_allow_all = False``.


Plugins Settings
----------------

Expand Down

0 comments on commit 852aaaa

Please sign in to comment.