diff options
-rw-r--r-- | doc/source/cors.rst | 8 | ||||
-rw-r--r-- | oslo_middleware/cors.py | 44 | ||||
-rw-r--r-- | oslo_middleware/tests/test_cors.py | 88 |
3 files changed, 131 insertions, 9 deletions
diff --git a/doc/source/cors.rst b/doc/source/cors.rst index 99e5dc0..2788096 100644 --- a/doc/source/cors.rst +++ b/doc/source/cors.rst @@ -52,8 +52,8 @@ something like this:: allowed_origin=https://website.example.com:443,https://website2.example.com:443 max_age=3600 allow_methods=GET,POST,PUT,DELETE - allow_headers=Content-Type,Cache-Control,Content-Language,Expires,Last-Modified,Pragma,X-Custom-Header - expose_headers=Content-Type,Cache-Control,Content-Language,Expires,Last-Modified,Pragma,X-Custom-Header + allow_headers=X-Custom-Header + expose_headers=X-Custom-Header This middleware permits you to override the rules for multiple `allowed_origin`'s. To express this in your configuration file, first begin @@ -102,8 +102,8 @@ will add CORS support.:: allowed_origin=https://website.example.com:443,https://website2.example.com:443 max_age=3600 allow_methods=GET,POST,PUT,DELETE - allow_headers=Content-Type,Cache-Control,Content-Language,Expires,Last-Modified,Pragma,X-Custom-Header - expose_headers=Content-Type,Cache-Control,Content-Language,Expires,Last-Modified,Pragma,X-Custom-Header + allow_headers=X-Custom-Header + expose_headers=X-Custom-Header If your application is using pastedeploy, but would also like to use the existing configuration from oslo_config in order to simplify the points of diff --git a/oslo_middleware/cors.py b/oslo_middleware/cors.py index 2e53741..3d7b49b 100644 --- a/oslo_middleware/cors.py +++ b/oslo_middleware/cors.py @@ -37,25 +37,59 @@ CORS_OPTS = [ help='Indicate that the actual request can include user ' 'credentials'), cfg.ListOpt('expose_headers', - default=['Content-Type', 'Cache-Control', 'Content-Language', - 'Expires', 'Last-Modified', 'Pragma'], + default=[], help='Indicate which headers are safe to expose to the API. ' 'Defaults to HTTP Simple Headers.'), cfg.IntOpt('max_age', default=3600, help='Maximum cache age of CORS preflight requests.'), cfg.ListOpt('allow_methods', - default=['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'], + default=['OPTIONS', 'GET', 'HEAD', 'POST', 'PUT', 'DELETE', + 'TRACE', 'PATCH'], # RFC 2616, RFC 5789 help='Indicate which methods can be used during the actual ' 'request.'), cfg.ListOpt('allow_headers', - default=['Content-Type', 'Cache-Control', 'Content-Language', - 'Expires', 'Last-Modified', 'Pragma'], + default=[], help='Indicate which header field names may be used during ' 'the actual request.') ] +def set_defaults(**kwargs): + """Override the default values for configuration options. + + This method permits a project to override the default CORS option values. + For example, it may wish to offer a set of sane default headers which + allow it to function with only minimal additional configuration. + + :param allow_credentials: Whether to permit credentials. + :type allow_credentials: bool + :param expose_headers: A list of headers to expose. + :type expose_headers: List of Strings + :param max_age: Maximum cache duration in seconds. + :type max_age: Int + :param allow_methods: List of HTTP methods to permit. + :type allow_methods: List of Strings + :param allow_headers: List of HTTP headers to permit from the client. + :type allow_headers: List of Strings + """ + # Since 'None' is a valid config override, we have to use kwargs. Else + # there's no good way for a user to override only one option, because all + # the others would be overridden to 'None'. + + valid_params = set(k.name for k in CORS_OPTS + if k.name != 'allowed_origin') + passed_params = set(k for k in kwargs) + + wrong_params = passed_params - valid_params + if wrong_params: + raise AttributeError('Parameter(s) [%s] invalid, please only use [%s]' + % (wrong_params, valid_params)) + + # Set global defaults. + cfg.set_defaults(CORS_OPTS, **kwargs) + + class InvalidOriginError(Exception): """Exception raised when Origin is invalid.""" diff --git a/oslo_middleware/tests/test_cors.py b/oslo_middleware/tests/test_cors.py index e07a85b..5e0822f 100644 --- a/oslo_middleware/tests/test_cors.py +++ b/oslo_middleware/tests/test_cors.py @@ -130,6 +130,94 @@ class CORSTestBase(test_base.BaseTestCase): self.assertNotIn(header, response.headers) +class CORSTestDefaultOverrides(CORSTestBase): + def setUp(self): + super(CORSTestDefaultOverrides, self).setUp() + + fixture = self.config_fixture # Line length accommodation + + fixture.load_raw_values(group='cors', + allowed_origin='http://valid.example.com') + + fixture.load_raw_values(group='cors.override_creds', + allowed_origin='http://creds.example.com', + allow_credentials='True') + + fixture.load_raw_values(group='cors.override_headers', + allowed_origin='http://headers.example.com', + expose_headers='X-Header-1,X-Header-2', + allow_headers='X-Header-1,X-Header-2') + + self.override_opts = { + 'expose_headers': ['X-Header-1'], + 'allow_headers': ['X-Header-2'], + 'allow_methods': ['GET', 'DELETE'], + 'allow_credentials': False, + 'max_age': 10 + } + + def test_config_defaults(self): + """Assert that using set_defaults overrides the appropriate values.""" + + cors.set_defaults(**self.override_opts) + + for opt in cors.CORS_OPTS: + if opt.dest in self.override_opts: + self.assertEqual(opt.default, self.override_opts[opt.dest]) + + def test_invalid_default_option(self): + """Assert that using set_defaults only permits valid options.""" + + self.assertRaises(AttributeError, + cors.set_defaults, + allowed_origin='test') + + def test_cascading_override(self): + """Assert that using set_defaults overrides cors.* config values.""" + + # set defaults + cors.set_defaults(**self.override_opts) + + # Now that the config is set up, create our application. + self.application = cors.CORS(test_application, self.config) + + # Check the global configuration for expected values: + gc = self.config.cors + self.assertEqual(gc.allowed_origin, ['http://valid.example.com']) + self.assertEqual(gc.allow_credentials, + self.override_opts['allow_credentials']) + self.assertEqual(gc.expose_headers, + self.override_opts['expose_headers']) + self.assertEqual(gc.max_age, 10) + self.assertEqual(gc.allow_methods, + self.override_opts['allow_methods']) + self.assertEqual(gc.allow_headers, + self.override_opts['allow_headers']) + + # Check the child configuration for expected values: + cc = self.config['cors.override_creds'] + self.assertEqual(cc.allowed_origin, ['http://creds.example.com']) + self.assertTrue(cc.allow_credentials) + self.assertEqual(cc.expose_headers, + self.override_opts['expose_headers']) + self.assertEqual(cc.max_age, 10) + self.assertEqual(cc.allow_methods, + self.override_opts['allow_methods']) + self.assertEqual(cc.allow_headers, + self.override_opts['allow_headers']) + + # Check the other child configuration for expected values: + ec = self.config['cors.override_headers'] + self.assertEqual(ec.allowed_origin, ['http://headers.example.com']) + self.assertEqual(ec.allow_credentials, + self.override_opts['allow_credentials']) + self.assertEqual(ec.expose_headers, ['X-Header-1', 'X-Header-2']) + self.assertEqual(ec.max_age, 10) + self.assertEqual(ec.allow_methods, + self.override_opts['allow_methods']) + self.assertEqual(ec.allow_headers, ['X-Header-1', 'X-Header-2']) + + class CORSTestFilterFactory(CORSTestBase): """Test the CORS filter_factory method.""" |