diff options
author | Michael Krotscheck <krotscheck@gmail.com> | 2015-06-12 14:31:14 -0700 |
---|---|---|
committer | Michael Krotscheck <krotscheck@gmail.com> | 2015-06-30 09:01:48 -0700 |
commit | 655de16a871bb0b4768f18a88e9f573fea18ed41 (patch) | |
tree | 21b37e890b0780f517702428f7e9314b95c7d3ab /oslo_middleware | |
parent | 0caa7a9e4509ef337925c9586a8b8617cd847158 (diff) | |
download | oslo-middleware-655de16a871bb0b4768f18a88e9f573fea18ed41.tar.gz |
Support PasteDeploy
This patch removes the oslo_config specific code out of the
middleware's class, and instead provides a factory method by which
this middleware may be constructed. It then also provides a similar
factory method that supports pastedeploy's filter_factory protocol.
Change-Id: I68d9c5439707a624aa24f3dbe7bbfd616b382a6d
Diffstat (limited to 'oslo_middleware')
-rw-r--r-- | oslo_middleware/cors.py | 103 | ||||
-rw-r--r-- | oslo_middleware/tests/test_cors.py | 36 |
2 files changed, 116 insertions, 23 deletions
diff --git a/oslo_middleware/cors.py b/oslo_middleware/cors.py index a0d9322..f178d0c 100644 --- a/oslo_middleware/cors.py +++ b/oslo_middleware/cors.py @@ -54,6 +54,38 @@ CORS_OPTS = [ ] +def filter_factory(global_conf, + allowed_origin, + allow_credentials=True, + expose_headers=None, + max_age=None, + allow_methods=None, + allow_headers=None): + '''Factory to support paste.deploy + + :param global_conf: The paste.ini global configuration object (not used). + :param allowed_origin: Protocol, host, and port for the allowed origin. + :param allow_credentials: Whether to permit credentials. + :param expose_headers: A list of headers to expose. + :param max_age: Maximum cache duration. + :param allow_methods: List of HTTP methods to permit. + :param allow_headers: List of HTTP headers to permit from the client. + :return: + ''' + + def filter(app): + cors_app = CORS(app) + cors_app.add_origin(allowed_origin=allowed_origin, + allow_credentials=allow_credentials, + expose_headers=expose_headers, + max_age=max_age, + allow_methods=allow_methods, + allow_headers=allow_headers) + return cors_app + + return filter + + class CORS(base.Middleware): """CORS Middleware. @@ -72,13 +104,21 @@ class CORS(base.Middleware): 'Pragma' ] - def __init__(self, application, conf): + def __init__(self, application, conf=None): super(CORS, self).__init__(application) + # Begin constructing our configuration hash. + self.allowed_origins = {} + + # Sanity check. Do we have an oslo.config? If so, load it. Else, assume + # that we'll use add_config. + if conf: + self._init_from_oslo(conf) + + def _init_from_oslo(self, conf): + '''Initialize this middleware from an oslo.config instance.''' + # First, check the configuration and register global options. - if not conf or not isinstance(conf, cfg.ConfigOpts): - raise ValueError("This middleware requires a configuration of" - " type oslo_config.ConfigOpts.") conf.register_opts(CORS_OPTS, 'cors') # Clone our original CORS_OPTS, and set the defaults to whatever is @@ -93,13 +133,10 @@ class CORS(base.Middleware): allow_methods=conf.cors.allow_methods, allow_headers=conf.cors.allow_headers) - # Begin constructing our configuration hash. - self.allowed_origins = {} - # If the default configuration contains an allowed_origin, don't # forget to register that. if conf.cors.allowed_origin: - self.allowed_origins[conf.cors.allowed_origin] = conf.cors + self.add_origin(**conf.cors) # Iterate through all the loaded config sections, looking for ones # prefixed with 'cors.' @@ -107,15 +144,34 @@ class CORS(base.Middleware): if section.startswith('cors.'): # Register with the preconstructed defaults conf.register_opts(subgroup_opts, section) + self.add_origin(**conf[section]) + + def add_origin(self, allowed_origin, allow_credentials=True, + expose_headers=None, max_age=None, allow_methods=None, + allow_headers=None): + '''Add another origin to this filter. + + :param allowed_origin: Protocol, host, and port for the allowed origin. + :param allow_credentials: Whether to permit credentials. + :param expose_headers: A list of headers to expose. + :param max_age: Maximum cache duration. + :param allow_methods: List of HTTP methods to permit. + :param allow_headers: List of HTTP headers to permit from the client. + :return: + ''' - # Make sure that allowed_origin is available. Otherwise skip. - allowed_origin = conf[section].allowed_origin - if not allowed_origin: - LOG.warn('Config section [%s] does not contain' - ' \'allowed_origin\', skipping.' % (section,)) - continue + if allowed_origin in self.allowed_origins: + LOG.warn('Allowed origin [%s] already exists, skipping' % ( + allowed_origin,)) + return - self.allowed_origins[allowed_origin] = conf[section] + self.allowed_origins[allowed_origin] = { + 'allow_credentials': allow_credentials, + 'expose_headers': expose_headers, + 'max_age': max_age, + 'allow_methods': allow_methods, + 'allow_headers': allow_headers + } def process_response(self, response, request=None): '''Check for CORS headers, and decorate if necessary. @@ -198,14 +254,15 @@ class CORS(base.Middleware): return response # Compare request method to permitted methods (Section 6.2.5) - if request_method not in cors_config.allow_methods: + if request_method not in cors_config['allow_methods']: return response # Compare request headers to permitted headers, case-insensitively. # (Section 6.2.6) for requested_header in request_headers: upper_header = requested_header.upper() - permitted_headers = cors_config.allow_headers + self.simple_headers + permitted_headers = (cors_config['allow_headers'] + + self.simple_headers) if upper_header not in (header.upper() for header in permitted_headers): return response @@ -215,13 +272,13 @@ class CORS(base.Middleware): response.headers['Access-Control-Allow-Origin'] = origin # Does this CORS configuration permit credentials? (Section 6.2.7) - if cors_config.allow_credentials: + if cors_config['allow_credentials']: response.headers['Access-Control-Allow-Credentials'] = 'true' # Attach Access-Control-Max-Age if appropriate. (Section 6.2.8) - if 'max_age' in cors_config and cors_config.max_age: + if 'max_age' in cors_config and cors_config['max_age']: response.headers['Access-Control-Max-Age'] = \ - str(cors_config.max_age) + str(cors_config['max_age']) # Attach Access-Control-Allow-Methods. (Section 6.2.9) response.headers['Access-Control-Allow-Methods'] = request_method @@ -257,10 +314,10 @@ class CORS(base.Middleware): response.headers['Access-Control-Allow-Origin'] = origin # Does this CORS configuration permit credentials? (Section 6.1.3) - if cors_config.allow_credentials: + if cors_config['allow_credentials']: response.headers['Access-Control-Allow-Credentials'] = 'true' # Attach the exposed headers and exit. (Section 6.1.4) - if cors_config.expose_headers: + if cors_config['expose_headers']: response.headers['Access-Control-Expose-Headers'] = \ - ','.join(cors_config.expose_headers) + ','.join(cors_config['expose_headers']) diff --git a/oslo_middleware/tests/test_cors.py b/oslo_middleware/tests/test_cors.py index 229bb80..d078d14 100644 --- a/oslo_middleware/tests/test_cors.py +++ b/oslo_middleware/tests/test_cors.py @@ -111,6 +111,42 @@ class CORSTestBase(test_base.BaseTestCase): self.assertNotIn(header, response.headers) +class CORSTestFilterFactory(test_base.BaseTestCase): + """Test the CORS filter_factory method.""" + + def test_filter_factory(self): + # Test a valid filter. + filter = cors.filter_factory(None, + allowed_origin='http://valid.example.com', + allow_credentials='False', + max_age='', + expose_headers='', + allow_methods='GET', + allow_headers='') + application = filter(test_application) + + self.assertIn('http://valid.example.com', application.allowed_origins) + + config = application.allowed_origins['http://valid.example.com'] + self.assertEqual('False', config['allow_credentials']) + self.assertEqual('', config['max_age']) + self.assertEqual('', config['expose_headers']) + self.assertEqual('GET', config['allow_methods']) + self.assertEqual('', config['allow_headers']) + + def test_no_origin_fail(self): + '''Assert that a filter factory with no allowed_origin fails.''' + self.assertRaises(TypeError, + cors.filter_factory, + global_conf=None, + # allowed_origin=None, # Expected value. + allow_credentials='False', + max_age='', + expose_headers='', + allow_methods='GET', + allow_headers='') + + class CORSRegularRequestTest(CORSTestBase): """CORS Specification Section 6.1 |