diff --git a/etc/solum/config-generator.conf b/etc/solum/config-generator.conf index 92e8b213d..9b930af97 100644 --- a/etc/solum/config-generator.conf +++ b/etc/solum/config-generator.conf @@ -23,3 +23,4 @@ namespace = solum.deployer.handlers.heat namespace = keystonemiddleware.auth_token namespace = solum.worker.config namespace = oslo.messaging +namespace = oslo.middleware.cors diff --git a/requirements.txt b/requirements.txt index 308850fc6..781ea12f0 100644 --- a/requirements.txt +++ b/requirements.txt @@ -9,6 +9,7 @@ netaddr>=0.7.6 oslo.config>=3.2.0 # Apache-2.0 oslo.db>=0.2.0 # Apache-2.0 oslo.messaging>=1.3.0 +oslo.middleware>=3.0.0 # Apache-2.0 oslo.serialization>=1.10.0 # Apache-2.0 pbr>=0.11,<2.0 pecan>=0.5.0 diff --git a/solum/api/app.py b/solum/api/app.py index 6949cd368..878a2b711 100644 --- a/solum/api/app.py +++ b/solum/api/app.py @@ -15,6 +15,7 @@ # under the License. from oslo_config import cfg +import oslo_middleware.cors as cors_middleware import pecan from solum.api import auth @@ -71,4 +72,18 @@ def setup_app(config=None): logging=getattr(config, 'logging', {}), **app_conf ) - return auth.install(app, CONF) + + app = auth.install(app, CONF) + + # Create a CORS wrapper, and attach solum-specific defaults that must be + # supported on all CORS responses. + app = cors_middleware.CORS(app, CONF) + app.set_latent( + allow_headers=['X-Auth-Token', 'X-Openstack-Request-Id', + 'X-Subject-Token'], + allow_methods=['GET', 'PUT', 'POST', 'DELETE', 'PATCH'], + expose_headers=['X-Auth-Token', 'X-Openstack-Request-Id', + 'X-Subject-Token'] + ) + + return app diff --git a/solum/tests/api/test_middleware.py b/solum/tests/api/test_middleware.py new file mode 100644 index 000000000..586deaad6 --- /dev/null +++ b/solum/tests/api/test_middleware.py @@ -0,0 +1,100 @@ +# -*- encoding: utf-8 -*- +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +""" +Tests to assert that various incorporated middleware works as expected. +""" + +from oslo_config import cfg +import oslo_middleware.cors as cors_middleware +import webob + +from solum.api import app as api_app +from solum.openstack.common.fixture import config +from solum.tests import base + + +class TestCORSMiddleware(base.BaseTestCase): + '''Provide a basic smoke test to ensure CORS middleware is active. + + The tests below provide minimal confirmation that the CORS middleware + is active, and may be configured. For comprehensive tests, please consult + the test suite in oslo_middleware. + ''' + + def setUp(self): + super(TestCORSMiddleware, self).setUp() + + # Config fixture + self.CONF = self.useFixture(config.Config()) + # Disable keystone + self.CONF.config(enable_authentication=False) + # Make sure the options are registered before overriding them. + self.CONF.register_opts(cors_middleware.CORS_OPTS, 'cors') + # Set our test data overrides. + self.CONF.config(allowed_origin="http://valid.example.com", + group='cors') + + # Initialize the conf object. Make sure we pass empty args=[], else the + # testr CLI arguments will be picked up. + cfg.CONF(project='solum', args=[]) + + # Create the application. + self.app = api_app.setup_app() + + def test_valid_cors_options_request(self): + request = webob.Request.blank('/') + request.method = 'OPTIONS' + request.headers['Origin'] = 'http://valid.example.com' + request.headers['Access-Control-Request-Method'] = 'GET' + response = request.get_response(self.app) + + # Assert response status. + self.assertEqual(200, response.status_code) + self.assertIn('Access-Control-Allow-Origin', response.headers) + self.assertEqual('http://valid.example.com', + response.headers['Access-Control-Allow-Origin']) + + def test_invalid_cors_options_request(self): + request = webob.Request.blank('/') + request.method = 'OPTIONS' + request.headers['Origin'] = 'http://invalid.example.com' + request.headers['Access-Control-Request-Method'] = 'GET' + response = request.get_response(self.app) + + # Assert response status. + self.assertEqual(200, response.status_code) + self.assertNotIn('Access-Control-Allow-Origin', response.headers) + + def test_valid_cors_get_request(self): + request = webob.Request.blank('/') + request.method = 'GET' + request.headers['Origin'] = 'http://valid.example.com' + response = request.get_response(self.app) + + # Assert response status. + self.assertEqual(200, response.status_code) + self.assertIn('Access-Control-Allow-Origin', response.headers) + self.assertEqual('http://valid.example.com', + response.headers['Access-Control-Allow-Origin']) + + def test_invalid_cors_get_request(self): + request = webob.Request.blank('/') + request.method = 'GET' + request.headers['Origin'] = 'http://invalid.example.com' + response = request.get_response(self.app) + + # Assert response status. + self.assertEqual(200, response.status_code) + self.assertNotIn('Access-Control-Allow-Origin', response.headers)