summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--doc/source/cors.rst16
-rw-r--r--oslo_middleware/cors.py9
-rw-r--r--oslo_middleware/tests/test_cors.py290
3 files changed, 258 insertions, 57 deletions
diff --git a/doc/source/cors.rst b/doc/source/cors.rst
index 00ed574..b47180c 100644
--- a/doc/source/cors.rst
+++ b/doc/source/cors.rst
@@ -6,6 +6,14 @@ This middleware provides a comprehensive, configurable implementation of the
CORS_ (Cross Origin Resource Sharing) specification as oslo-supported python
wsgi middleware.
+.. note::
+
+ While this middleware supports the use of the `*` wildcard origin in the
+ specification, this feature is not recommended for security reasons. It
+ is provided to simplify basic use of CORS, practically meaning "I don't
+ care how this is used." In an intranet setting, this could lead to leakage
+ of data beyond the intranet and therefore should be avoided.
+
Quickstart
----------
First, include the middleware in your application::
@@ -46,10 +54,10 @@ legibility, we recommend using a reasonable human-readable string::
# CORS Configuration for horizon, which uses global options.
allowed_origin=https://horizon.example.com:443
- [cors.dashboard]
- # CORS Configuration for a hypothetical dashboard, which only permits
- # HTTP GET requests.
- allowed_origin=https://dashboard.example.com:443
+ [cors.wildcard]
+ # CORS Configuration for the CORS specified domain wildcard, which only
+ # permits HTTP GET requests.
+ allowed_origin=*
allow_methods=GET
diff --git a/oslo_middleware/cors.py b/oslo_middleware/cors.py
index 8eb88d3..3e0021b 100644
--- a/oslo_middleware/cors.py
+++ b/oslo_middleware/cors.py
@@ -154,9 +154,12 @@ class CORS(base.Middleware):
# Is this origin registered? (Section 6.2.2)
origin = request.headers['Origin']
if origin not in self.allowed_origins:
- LOG.debug('CORS request from origin \'%s\' not permitted.'
- % (origin,))
- return
+ if '*' in self.allowed_origins:
+ origin = '*'
+ else:
+ LOG.debug('CORS request from origin \'%s\' not permitted.'
+ % (origin,))
+ return
cors_config = self.allowed_origins[origin]
# If there's no request method, exit. (Section 6.2.3)
diff --git a/oslo_middleware/tests/test_cors.py b/oslo_middleware/tests/test_cors.py
index 458800e..34001e0 100644
--- a/oslo_middleware/tests/test_cors.py
+++ b/oslo_middleware/tests/test_cors.py
@@ -26,46 +26,6 @@ class CORSTestBase(test_base.BaseTestCase):
Sets up applications and helper methods.
"""
- def setUp(self):
- super(CORSTestBase, self).setUp()
-
- @webob.dec.wsgify
- def application(req):
- return 'Hello, World!!!'
-
- # Set up the config fixture.
- config = self.useFixture(fixture.Config(cfg.CONF))
-
- config.load_raw_values(group='cors',
- allowed_origin='http://valid.example.com',
- allow_credentials='False',
- max_age='',
- expose_headers='',
- allow_methods='GET',
- allow_headers='')
-
- config.load_raw_values(group='cors.credentials',
- allowed_origin='http://creds.example.com',
- allow_credentials='True')
-
- config.load_raw_values(group='cors.exposed-headers',
- allowed_origin='http://headers.example.com',
- expose_headers='X-Header-1,X-Header-2',
- allow_headers='X-Header-1,X-Header-2')
-
- config.load_raw_values(group='cors.cached',
- allowed_origin='http://cached.example.com',
- max_age='3600')
-
- config.load_raw_values(group='cors.get-only',
- allowed_origin='http://get.example.com',
- allow_methods='GET')
- config.load_raw_values(group='cors.all-methods',
- allowed_origin='http://all.example.com',
- allow_methods='GET,PUT,POST,DELETE,HEAD')
-
- # Now that the config is set up, create our application.
- self.application = cors.CORS(application, cfg.CONF)
def assertCORSResponse(self, response,
status='200 OK',
@@ -128,6 +88,58 @@ class CORSTestBase(test_base.BaseTestCase):
else:
self.assertNotIn(header, response.headers)
+
+class CORSRegularRequestTest(CORSTestBase):
+ """CORS Specification Section 6.1
+
+ http://www.w3.org/TR/cors/#resource-requests
+ """
+
+ # List of HTTP methods (other than OPTIONS) to test with.
+ methods = ['POST', 'PUT', 'DELETE', 'GET', 'TRACE', 'HEAD']
+
+ def setUp(self):
+ """Setup the tests."""
+ super(CORSRegularRequestTest, self).setUp()
+
+ @webob.dec.wsgify
+ def application(req):
+ return 'Hello, World!!!'
+
+ # Set up the config fixture.
+ config = self.useFixture(fixture.Config(cfg.CONF))
+
+ config.load_raw_values(group='cors',
+ allowed_origin='http://valid.example.com',
+ allow_credentials='False',
+ max_age='',
+ expose_headers='',
+ allow_methods='GET',
+ allow_headers='')
+
+ config.load_raw_values(group='cors.credentials',
+ allowed_origin='http://creds.example.com',
+ allow_credentials='True')
+
+ config.load_raw_values(group='cors.exposed-headers',
+ allowed_origin='http://headers.example.com',
+ expose_headers='X-Header-1,X-Header-2',
+ allow_headers='X-Header-1,X-Header-2')
+
+ config.load_raw_values(group='cors.cached',
+ allowed_origin='http://cached.example.com',
+ max_age='3600')
+
+ config.load_raw_values(group='cors.get-only',
+ allowed_origin='http://get.example.com',
+ allow_methods='GET')
+ config.load_raw_values(group='cors.all-methods',
+ allowed_origin='http://all.example.com',
+ allow_methods='GET,PUT,POST,DELETE,HEAD')
+
+ # Now that the config is set up, create our application.
+ self.application = cors.CORS(application, cfg.CONF)
+
def test_config_overrides(self):
"""Assert that the configuration options are properly registered."""
@@ -186,16 +198,6 @@ class CORSTestBase(test_base.BaseTestCase):
['GET', 'PUT', 'POST', 'DELETE', 'HEAD'])
self.assertEqual(ac.allow_headers, gc.allow_headers)
-
-class CORSRegularRequestTest(CORSTestBase):
- """CORS Specification Section 6.1
-
- http://www.w3.org/TR/cors/#resource-requests
- """
-
- # List of HTTP methods (other than OPTIONS) to test with.
- methods = ['POST', 'PUT', 'DELETE', 'GET', 'TRACE', 'HEAD']
-
def test_no_origin_header(self):
"""CORS Specification Section 6.1.1
@@ -339,6 +341,105 @@ class CORSPreflightRequestTest(CORSTestBase):
http://www.w3.org/TR/cors/#resource-preflight-requests
"""
+ def setUp(self):
+ super(CORSPreflightRequestTest, self).setUp()
+
+ @webob.dec.wsgify
+ def application(req):
+ return 'Hello, World!!!'
+
+ # Set up the config fixture.
+ config = self.useFixture(fixture.Config(cfg.CONF))
+
+ config.load_raw_values(group='cors',
+ allowed_origin='http://valid.example.com',
+ allow_credentials='False',
+ max_age='',
+ expose_headers='',
+ allow_methods='GET',
+ allow_headers='')
+
+ config.load_raw_values(group='cors.credentials',
+ allowed_origin='http://creds.example.com',
+ allow_credentials='True')
+
+ config.load_raw_values(group='cors.exposed-headers',
+ allowed_origin='http://headers.example.com',
+ expose_headers='X-Header-1,X-Header-2',
+ allow_headers='X-Header-1,X-Header-2')
+
+ config.load_raw_values(group='cors.cached',
+ allowed_origin='http://cached.example.com',
+ max_age='3600')
+
+ config.load_raw_values(group='cors.get-only',
+ allowed_origin='http://get.example.com',
+ allow_methods='GET')
+ config.load_raw_values(group='cors.all-methods',
+ allowed_origin='http://all.example.com',
+ allow_methods='GET,PUT,POST,DELETE,HEAD')
+
+ # Now that the config is set up, create our application.
+ self.application = cors.CORS(application, cfg.CONF)
+
+ def test_config_overrides(self):
+ """Assert that the configuration options are properly registered."""
+
+ # Confirm global configuration
+ gc = cfg.CONF.cors
+ self.assertEqual(gc.allowed_origin, 'http://valid.example.com')
+ self.assertEqual(gc.allow_credentials, False)
+ self.assertEqual(gc.expose_headers, [])
+ self.assertEqual(gc.max_age, None)
+ self.assertEqual(gc.allow_methods, ['GET'])
+ self.assertEqual(gc.allow_headers, [])
+
+ # Confirm credentials overrides.
+ cc = cfg.CONF['cors.credentials']
+ self.assertEqual(cc.allowed_origin, 'http://creds.example.com')
+ self.assertEqual(cc.allow_credentials, True)
+ self.assertEqual(cc.expose_headers, gc.expose_headers)
+ self.assertEqual(cc.max_age, gc.max_age)
+ self.assertEqual(cc.allow_methods, gc.allow_methods)
+ self.assertEqual(cc.allow_headers, gc.allow_headers)
+
+ # Confirm exposed-headers overrides.
+ ec = cfg.CONF['cors.exposed-headers']
+ self.assertEqual(ec.allowed_origin, 'http://headers.example.com')
+ self.assertEqual(ec.allow_credentials, gc.allow_credentials)
+ self.assertEqual(ec.expose_headers, ['X-Header-1', 'X-Header-2'])
+ self.assertEqual(ec.max_age, gc.max_age)
+ self.assertEqual(ec.allow_methods, gc.allow_methods)
+ self.assertEqual(ec.allow_headers, ['X-Header-1', 'X-Header-2'])
+
+ # Confirm cached overrides.
+ chc = cfg.CONF['cors.cached']
+ self.assertEqual(chc.allowed_origin, 'http://cached.example.com')
+ self.assertEqual(chc.allow_credentials, gc.allow_credentials)
+ self.assertEqual(chc.expose_headers, gc.expose_headers)
+ self.assertEqual(chc.max_age, 3600)
+ self.assertEqual(chc.allow_methods, gc.allow_methods)
+ self.assertEqual(chc.allow_headers, gc.allow_headers)
+
+ # Confirm get-only overrides.
+ goc = cfg.CONF['cors.get-only']
+ self.assertEqual(goc.allowed_origin, 'http://get.example.com')
+ self.assertEqual(goc.allow_credentials, gc.allow_credentials)
+ self.assertEqual(goc.expose_headers, gc.expose_headers)
+ self.assertEqual(goc.max_age, gc.max_age)
+ self.assertEqual(goc.allow_methods, ['GET'])
+ self.assertEqual(goc.allow_headers, gc.allow_headers)
+
+ # Confirm all-methods overrides.
+ ac = cfg.CONF['cors.all-methods']
+ self.assertEqual(ac.allowed_origin, 'http://all.example.com')
+ self.assertEqual(ac.allow_credentials, gc.allow_credentials)
+ self.assertEqual(ac.expose_headers, gc.expose_headers)
+ self.assertEqual(ac.max_age, gc.max_age)
+ self.assertEqual(ac.allow_methods,
+ ['GET', 'PUT', 'POST', 'DELETE', 'HEAD'])
+ self.assertEqual(ac.allow_headers, gc.allow_headers)
+
def test_no_origin_header(self):
"""CORS Specification Section 6.2.1
@@ -699,3 +800,92 @@ class CORSPreflightRequestTest(CORSTestBase):
allow_headers=requested_headers,
allow_credentials=None,
expose_headers=None)
+
+
+class CORSTestWildcard(CORSTestBase):
+ """Test the CORS wildcard specification."""
+
+ def setUp(self):
+ super(CORSTestWildcard, self).setUp()
+
+ @webob.dec.wsgify
+ def application(req):
+ return 'Hello, World!!!'
+
+ # Set up the config fixture.
+ config = self.useFixture(fixture.Config(cfg.CONF))
+
+ config.load_raw_values(group='cors',
+ allowed_origin='http://default.example.com',
+ allow_credentials='True',
+ max_age='',
+ expose_headers='',
+ allow_methods='GET,PUT,POST,DELETE,HEAD',
+ allow_headers='')
+
+ config.load_raw_values(group='cors.wildcard',
+ allowed_origin='*',
+ allow_methods='GET')
+
+ # Now that the config is set up, create our application.
+ self.application = cors.CORS(application, cfg.CONF)
+
+ def test_config_overrides(self):
+ """Assert that the configuration options are properly registered."""
+
+ # Confirm global configuration
+ gc = cfg.CONF.cors
+ self.assertEqual(gc.allowed_origin, 'http://default.example.com')
+ self.assertEqual(gc.allow_credentials, True)
+ self.assertEqual(gc.expose_headers, [])
+ self.assertEqual(gc.max_age, None)
+ self.assertEqual(gc.allow_methods, ['GET', 'PUT', 'POST', 'DELETE',
+ 'HEAD'])
+ self.assertEqual(gc.allow_headers, [])
+
+ # Confirm all-methods overrides.
+ ac = cfg.CONF['cors.wildcard']
+ self.assertEqual(ac.allowed_origin, '*')
+ self.assertEqual(gc.allow_credentials, True)
+ self.assertEqual(ac.expose_headers, gc.expose_headers)
+ self.assertEqual(ac.max_age, gc.max_age)
+ self.assertEqual(ac.allow_methods, ['GET'])
+ self.assertEqual(ac.allow_headers, gc.allow_headers)
+
+ def test_wildcard_domain(self):
+ """CORS Specification, Wildcards
+
+ If the configuration file specifies CORS settings for the wildcard '*'
+ domain, it should return those for all origin domains except for the
+ overrides.
+ """
+
+ # Test valid domain
+ request = webob.Request({})
+ request.method = "OPTIONS"
+ request.headers['Origin'] = 'http://default.example.com'
+ request.headers['Access-Control-Request-Method'] = 'GET'
+ response = request.get_response(self.application)
+ self.assertCORSResponse(response,
+ status='200 OK',
+ allow_origin='http://default.example.com',
+ max_age=None,
+ allow_methods='GET',
+ allow_headers='',
+ allow_credentials='true',
+ expose_headers=None)
+
+ # Test invalid domain
+ request = webob.Request({})
+ request.method = "OPTIONS"
+ request.headers['Origin'] = 'http://invalid.example.com'
+ request.headers['Access-Control-Request-Method'] = 'GET'
+ response = request.get_response(self.application)
+ self.assertCORSResponse(response,
+ status='200 OK',
+ allow_origin='*',
+ max_age=None,
+ allow_methods='GET',
+ allow_headers='',
+ allow_credentials='true',
+ expose_headers=None)