From 55b056fa9e5d776b00b666ca74fa56461f2b4dfc Mon Sep 17 00:00:00 2001 From: Michael Krotscheck Date: Mon, 30 Nov 2015 16:09:51 -0800 Subject: [PATCH] Added CORS support to Keystone This adds the CORS support middleware to Keystone, allowing a deployer to optionally configure rules under which a javascript client may break the single-origin policy and access the API directly. For keystone the paste.ini method of deploying the middleware was chosen, because it needs to be able to annotate error responses created by other middlewares. If one such middleware throws an error - such as an error from sizelimit - that error response must still have CORS headers in order to be readable by the user agent. This patch also includes tests to assert that the expected setup entry points are present. It is expected that this test will be refined in future patches. OpenStack CrossProject Spec: http://specs.openstack.org/openstack/openstack-specs/specs/cors-support.html Oslo_Middleware Docs: http://docs.openstack.org/developer/oslo.middleware/cors.html OpenStack Cloud Admin Guide: http://docs.openstack.org/admin-guide-cloud/cross_project_cors.html Change-Id: I151e0ee5da1bf3d9fbbe8f11bb06e687b284e5f6 --- etc/keystone-paste.ini | 17 ++++++--- keystone/tests/unit/test_entry_points.py | 48 ++++++++++++++++++++++++ 2 files changed, 60 insertions(+), 5 deletions(-) create mode 100644 keystone/tests/unit/test_entry_points.py diff --git a/etc/keystone-paste.ini b/etc/keystone-paste.ini index 6e2051432d..c2c835383f 100644 --- a/etc/keystone-paste.ini +++ b/etc/keystone-paste.ini @@ -18,6 +18,13 @@ use = egg:keystone#admin_token_auth [filter:json_body] use = egg:keystone#json_body +[filter:cors] +use = egg:oslo.middleware#cors +oslo_config_project = keystone +latent_allow_headers = X-Auth-Token, X-Openstack-Request-Id, X-Subject-Token +latent_expose_headers = X-Auth-Token, X-Openstack-Request-Id, X-Subject-Token +latent_allow_methods = GET, PUT, POST, DELETE, PATCH + [filter:ec2_extension] use = egg:keystone#ec2_extension @@ -45,17 +52,17 @@ use = egg:keystone#admin_service [pipeline:public_api] # The last item in this pipeline must be public_service or an equivalent # application. It cannot be a filter. -pipeline = sizelimit url_normalize request_id build_auth_context token_auth admin_token_auth json_body ec2_extension public_service +pipeline = cors sizelimit url_normalize request_id build_auth_context token_auth admin_token_auth json_body ec2_extension public_service [pipeline:admin_api] # The last item in this pipeline must be admin_service or an equivalent # application. It cannot be a filter. -pipeline = sizelimit url_normalize request_id build_auth_context token_auth admin_token_auth json_body ec2_extension s3_extension admin_service +pipeline = cors sizelimit url_normalize request_id build_auth_context token_auth admin_token_auth json_body ec2_extension s3_extension admin_service [pipeline:api_v3] # The last item in this pipeline must be service_v3 or an equivalent # application. It cannot be a filter. -pipeline = sizelimit url_normalize request_id build_auth_context token_auth admin_token_auth json_body ec2_extension_v3 s3_extension service_v3 +pipeline = cors sizelimit url_normalize request_id build_auth_context token_auth admin_token_auth json_body ec2_extension_v3 s3_extension service_v3 [app:public_version_service] use = egg:keystone#public_version_service @@ -64,10 +71,10 @@ use = egg:keystone#public_version_service use = egg:keystone#admin_version_service [pipeline:public_version_api] -pipeline = sizelimit url_normalize public_version_service +pipeline = cors sizelimit url_normalize public_version_service [pipeline:admin_version_api] -pipeline = sizelimit url_normalize admin_version_service +pipeline = cors sizelimit url_normalize admin_version_service [composite:main] use = egg:Paste#urlmap diff --git a/keystone/tests/unit/test_entry_points.py b/keystone/tests/unit/test_entry_points.py new file mode 100644 index 0000000000..e973e942b0 --- /dev/null +++ b/keystone/tests/unit/test_entry_points.py @@ -0,0 +1,48 @@ +# 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. + +import stevedore +from testtools import matchers + +from keystone.tests.unit import core as test + + +class TestPasteDeploymentEntryPoints(test.TestCase): + def test_entry_point_middleware(self): + """Assert that our list of expected middleware is present.""" + expected_names = [ + 'admin_token_auth', + 'build_auth_context', + 'crud_extension', + 'cors', + 'debug', + 'endpoint_filter_extension', + 'ec2_extension', + 'ec2_extension_v3', + 'federation_extension', + 'json_body', + 'oauth1_extension', + 'request_id', + 'revoke_extension', + 's3_extension', + 'simple_cert_extension', + 'sizelimit', + 'token_auth', + 'url_normalize', + 'user_crud_extension', + ] + + em = stevedore.ExtensionManager('paste.filter_factory') + + actual_names = [extension.name for extension in em] + + self.assertThat(actual_names, matchers.ContainsAll(expected_names))