Skip to content

Commit

Permalink
[#1944] Allow CORS settings in config.
Browse files Browse the repository at this point in the history
New config settings:
ckan.cors.origin_allow_all = <true|false>
ckan.cors.origin_whitelist = <space separated list of allowable domain origins>

Access-Control-Allow headers will only be set in the response if the
request contains an Origin header, the origin_allow_all setting is
present, and either:

a) the origin_allow_all setting is true, or
b) the request origin is listed in the origin_whitelist setting
  • Loading branch information
brew committed Oct 27, 2014
1 parent 2f512bf commit 162d371
Show file tree
Hide file tree
Showing 3 changed files with 169 additions and 29 deletions.
30 changes: 24 additions & 6 deletions ckan/lib/base.py
Expand Up @@ -375,17 +375,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.

0 comments on commit 162d371

Please sign in to comment.