summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--doc/source/cors.rst35
-rw-r--r--doc/source/oslo_config.rst4
-rw-r--r--oslo/__init__.py13
-rw-r--r--oslo/middleware/__init__.py28
-rw-r--r--oslo/middleware/base.py13
-rw-r--r--oslo/middleware/catch_errors.py13
-rw-r--r--oslo/middleware/correlation_id.py13
-rw-r--r--oslo/middleware/debug.py13
-rw-r--r--oslo/middleware/request_id.py13
-rw-r--r--oslo/middleware/sizelimit.py13
-rw-r--r--oslo_middleware/base.py10
-rw-r--r--oslo_middleware/cors.py50
-rw-r--r--oslo_middleware/tests/test_cors.py92
-rw-r--r--requirements.txt4
-rw-r--r--setup.cfg5
-rw-r--r--tests/__init__.py0
-rw-r--r--tests/test_catch_errors.py47
-rw-r--r--tests/test_correlation_id.py53
-rw-r--r--tests/test_request_id.py37
-rw-r--r--tests/test_sizelimit.py108
-rw-r--r--tests/test_warning.py61
-rw-r--r--tox.ini14
22 files changed, 153 insertions, 486 deletions
diff --git a/doc/source/cors.rst b/doc/source/cors.rst
index 890f7fd..99e5dc0 100644
--- a/doc/source/cors.rst
+++ b/doc/source/cors.rst
@@ -49,18 +49,19 @@ In your application's config file, then include a default configuration block
something like this::
[cors]
- allowed_origin=https://website.example.com:443
+ 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
-This middleware permits you to define multiple `allowed_origin`'s. To express
-this in your configuration file, first begin with a `[cors]` group as above,
-into which you place your default configuration values. Then add as many
-additional configuration groups as necessary, naming them `[cors.something]`
-(each name must be unique). The purpose of the suffix to `cors.` is
-legibility, we recommend using a reasonable human-readable string::
+This middleware permits you to override the rules for multiple
+`allowed_origin`'s. To express this in your configuration file, first begin
+with a `[cors]` group as above, into which you place your default
+configuration values. Then add as many additional configuration groups as
+necessary, naming them `[cors.something]` (each name must be unique). The
+purpose of the suffix to `cors.` is legibility, we recommend using a
+reasonable human-readable string::
[cors.ironic_webclient]
# CORS Configuration for a hypothetical ironic webclient, which overrides
@@ -94,16 +95,32 @@ Configuration for pastedeploy
-----------------------------
If your application is using pastedeploy, the following configuration block
-will add CORS support. To add multiple domains, simply add another filter.::
+will add CORS support.::
[filter:cors]
paste.filter_factory = oslo_middleware.cors:filter_factory
- allowed_origin=https://website.example.com:443
+ 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
+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
+configuration, this may be done as follows.::
+
+ [filter:cors]
+ paste.filter_factory = oslo_middleware.cors:filter_factory
+ oslo_config_project = oslo_project_name
+
+ # Optional field, in case the program name is different from the project:
+ oslo_config_program = oslo_project_name-api
+
+ # This method also permits setting latent properties, for any origins set
+ # in oslo config.
+ latent_allow_headers=X-Auth-Token
+ latent_expose_headers=X-Auth-Token
+ latent_methods=GET,PUT,POST
Configuration Options
---------------------
diff --git a/doc/source/oslo_config.rst b/doc/source/oslo_config.rst
index 09c83c6..6e772c6 100644
--- a/doc/source/oslo_config.rst
+++ b/doc/source/oslo_config.rst
@@ -29,6 +29,10 @@ The paste filter (in /etc/my_app/api-paste.ini) will looks like::
# the application configuration, by setting this:
# oslo_config_project = my_app
+ # In some cases, you may need to specify the program name for the project
+ # as well.
+ # oslo_config_program = my_app-api
+
The oslo.config file of the application (eg: /etc/my_app/my_app.conf) will looks like::
[oslo_middleware]
diff --git a/oslo/__init__.py b/oslo/__init__.py
deleted file mode 100644
index dc130d6..0000000
--- a/oslo/__init__.py
+++ /dev/null
@@ -1,13 +0,0 @@
-# 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__('pkg_resources').declare_namespace(__name__)
diff --git a/oslo/middleware/__init__.py b/oslo/middleware/__init__.py
deleted file mode 100644
index 1407ce1..0000000
--- a/oslo/middleware/__init__.py
+++ /dev/null
@@ -1,28 +0,0 @@
-# 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 warnings
-
-from oslo_middleware import *
-
-
-def deprecated():
- new_name = __name__.replace('.', '_')
- warnings.warn(
- ('The oslo namespace package is deprecated. Please use %s instead.' %
- new_name),
- DeprecationWarning,
- stacklevel=3,
- )
-
-
-deprecated()
diff --git a/oslo/middleware/base.py b/oslo/middleware/base.py
deleted file mode 100644
index 53e25e9..0000000
--- a/oslo/middleware/base.py
+++ /dev/null
@@ -1,13 +0,0 @@
-# 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.
-
-from oslo_middleware.base import * # noqa
diff --git a/oslo/middleware/catch_errors.py b/oslo/middleware/catch_errors.py
deleted file mode 100644
index 81e4c6c..0000000
--- a/oslo/middleware/catch_errors.py
+++ /dev/null
@@ -1,13 +0,0 @@
-# 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.
-
-from oslo_middleware.catch_errors import * # noqa
diff --git a/oslo/middleware/correlation_id.py b/oslo/middleware/correlation_id.py
deleted file mode 100644
index fff548c..0000000
--- a/oslo/middleware/correlation_id.py
+++ /dev/null
@@ -1,13 +0,0 @@
-# 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.
-
-from oslo_middleware.correlation_id import * # noqa
diff --git a/oslo/middleware/debug.py b/oslo/middleware/debug.py
deleted file mode 100644
index 2907289..0000000
--- a/oslo/middleware/debug.py
+++ /dev/null
@@ -1,13 +0,0 @@
-# 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.
-
-from oslo_middleware.debug import * # noqa
diff --git a/oslo/middleware/request_id.py b/oslo/middleware/request_id.py
deleted file mode 100644
index 81e3164..0000000
--- a/oslo/middleware/request_id.py
+++ /dev/null
@@ -1,13 +0,0 @@
-# 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.
-
-from oslo_middleware.request_id import * # noqa
diff --git a/oslo/middleware/sizelimit.py b/oslo/middleware/sizelimit.py
deleted file mode 100644
index c04c1cd..0000000
--- a/oslo/middleware/sizelimit.py
+++ /dev/null
@@ -1,13 +0,0 @@
-# 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.
-
-from oslo_middleware.sizelimit import * # noqa
diff --git a/oslo_middleware/base.py b/oslo_middleware/base.py
index 86f2249..a98a058 100644
--- a/oslo_middleware/base.py
+++ b/oslo_middleware/base.py
@@ -67,8 +67,16 @@ class ConfigurableMiddleware(object):
default_config_files = [self.conf['oslo_config_file']]
else:
default_config_files = None
+
+ if 'oslo_config_program' in self.conf:
+ program = self.conf['oslo_config_program']
+ else:
+ program = None
+
self.oslo_conf = cfg.ConfigOpts()
- self.oslo_conf([], project=self.conf['oslo_config_project'],
+ self.oslo_conf([],
+ project=self.conf['oslo_config_project'],
+ prog=program,
default_config_files=default_config_files,
validate_default_values=True)
diff --git a/oslo_middleware/cors.py b/oslo_middleware/cors.py
index 65d7be0..ac86bc1 100644
--- a/oslo_middleware/cors.py
+++ b/oslo_middleware/cors.py
@@ -25,10 +25,10 @@ import webob.response
LOG = logging.getLogger(__name__)
CORS_OPTS = [
- cfg.StrOpt('allowed_origin',
- default=None,
- help='Indicate whether this resource may be shared with the '
- 'domain received in the requests "origin" header.'),
+ cfg.ListOpt('allowed_origin',
+ default=None,
+ help='Indicate whether this resource may be shared with the '
+ 'domain received in the requests "origin" header.'),
cfg.BoolOpt('allow_credentials',
default=True,
help='Indicate that the actual request can include user '
@@ -55,6 +55,7 @@ CORS_OPTS = [
class InvalidOriginError(Exception):
"""Exception raised when Origin is invalid."""
+
def __init__(self, origin):
self.origin = origin
super(InvalidOriginError, self).__init__(
@@ -87,6 +88,18 @@ class CORS(base.ConfigurableMiddleware):
self.allowed_origins = {}
self._init_conf()
+ def sanitize(csv_list):
+ try:
+ return [str.strip(x) for x in csv_list.split(',')]
+ except Exception:
+ return None
+
+ self.set_latent(
+ allow_headers=sanitize(self.conf.get('latent_allow_headers')),
+ expose_headers=sanitize(self.conf.get('latent_expose_headers')),
+ allow_methods=sanitize(self.conf.get('latent_allow_methods'))
+ )
+
@classmethod
def factory(cls, global_conf, **local_conf):
"""factory method for paste.deploy
@@ -99,7 +112,7 @@ class CORS(base.ConfigurableMiddleware):
allow_headers: List of HTTP headers to permit from the client.
"""
if ('allowed_origin' not in local_conf
- and 'oslo_config_project' not in local_conf):
+ and 'oslo_config_project' not in local_conf):
raise TypeError("allowed_origin or oslo_config_project "
"is required")
return super(CORS, cls).factory(global_conf, **local_conf)
@@ -167,19 +180,20 @@ class CORS(base.ConfigurableMiddleware):
:param allow_headers: List of HTTP headers to permit from the client.
:return:
'''
-
- if allowed_origin in self.allowed_origins:
- LOG.warn('Allowed origin [%s] already exists, skipping' % (
- allowed_origin,))
- return
-
- 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
- }
+ for origin in allowed_origin:
+
+ if origin in self.allowed_origins:
+ LOG.warn('Allowed origin [%s] already exists, skipping' % (
+ allowed_origin,))
+ continue
+
+ self.allowed_origins[origin] = {
+ 'allow_credentials': allow_credentials,
+ 'expose_headers': expose_headers,
+ 'max_age': max_age,
+ 'allow_methods': allow_methods,
+ 'allow_headers': allow_headers
+ }
def set_latent(self, allow_headers=None, allow_methods=None,
expose_headers=None):
diff --git a/oslo_middleware/tests/test_cors.py b/oslo_middleware/tests/test_cors.py
index d1c1885..e47c5d8 100644
--- a/oslo_middleware/tests/test_cors.py
+++ b/oslo_middleware/tests/test_cors.py
@@ -136,6 +136,18 @@ class CORSTestFilterFactory(test_base.BaseTestCase):
self.assertEqual(['GET'], config['allow_methods'])
self.assertEqual([], config['allow_headers'])
+ def test_filter_factory_multiorigin(self):
+ self.useFixture(fixture.Config()).conf([])
+
+ # Test a valid filter.
+ filter = cors.filter_factory(None,
+ allowed_origin='http://valid.example.com,'
+ 'http://other.example.com')
+ application = filter(test_application)
+
+ self.assertIn('http://valid.example.com', application.allowed_origins)
+ self.assertIn('http://other.example.com', application.allowed_origins)
+
def test_no_origin_fail(self):
'''Assert that a filter factory with no allowed_origin fails.'''
self.assertRaises(TypeError,
@@ -152,6 +164,29 @@ class CORSTestFilterFactory(test_base.BaseTestCase):
'''Assert that a filter factory with oslo_config_project succeed.'''
cors.filter_factory(global_conf=None, oslo_config_project='foobar')
+ def test_factory_latent_properties(self):
+ '''Assert latent properties in paste.ini config.
+
+ If latent_* properties are added to a paste.ini config, assert that
+ they are persisted in the middleware.
+ '''
+
+ # Spaces in config are deliberate to frobb the config parsing.
+ filter = cors.filter_factory(global_conf=None,
+ oslo_config_project='foobar',
+ latent_expose_headers=' X-Header-1 , X-2',
+ latent_allow_headers='X-Header-1 , X-2',
+ latent_allow_methods='GET,PUT, POST')
+ app = filter(test_application)
+
+ # Ensure that the properties are in latent configuration.
+ self.assertEqual(['X-Header-1', 'X-2'],
+ app._latent_configuration['expose_headers'])
+ self.assertEqual(['X-Header-1', 'X-2'],
+ app._latent_configuration['allow_headers'])
+ self.assertEqual(['GET', 'PUT', 'POST'],
+ app._latent_configuration['methods'])
+
class CORSRegularRequestTest(CORSTestBase):
"""CORS Specification Section 6.1
@@ -197,6 +232,10 @@ class CORSRegularRequestTest(CORSTestBase):
allowed_origin='http://all.example.com',
allow_methods='GET,PUT,POST,DELETE,HEAD')
+ config.load_raw_values(group='cors.duplicate',
+ allowed_origin='http://domain1.example.com,'
+ 'http://domain2.example.com')
+
# Now that the config is set up, create our application.
self.application = cors.CORS(test_application, cfg.CONF)
@@ -205,7 +244,7 @@ class CORSRegularRequestTest(CORSTestBase):
# Confirm global configuration
gc = cfg.CONF.cors
- self.assertEqual(gc.allowed_origin, 'http://valid.example.com')
+ 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)
@@ -214,7 +253,7 @@ class CORSRegularRequestTest(CORSTestBase):
# Confirm credentials overrides.
cc = cfg.CONF['cors.credentials']
- self.assertEqual(cc.allowed_origin, 'http://creds.example.com')
+ 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)
@@ -223,7 +262,7 @@ class CORSRegularRequestTest(CORSTestBase):
# Confirm exposed-headers overrides.
ec = cfg.CONF['cors.exposed-headers']
- self.assertEqual(ec.allowed_origin, 'http://headers.example.com')
+ 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)
@@ -232,7 +271,7 @@ class CORSRegularRequestTest(CORSTestBase):
# Confirm cached overrides.
chc = cfg.CONF['cors.cached']
- self.assertEqual(chc.allowed_origin, 'http://cached.example.com')
+ 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)
@@ -241,7 +280,7 @@ class CORSRegularRequestTest(CORSTestBase):
# Confirm get-only overrides.
goc = cfg.CONF['cors.get-only']
- self.assertEqual(goc.allowed_origin, 'http://get.example.com')
+ 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)
@@ -250,7 +289,7 @@ class CORSRegularRequestTest(CORSTestBase):
# Confirm all-methods overrides.
ac = cfg.CONF['cors.all-methods']
- self.assertEqual(ac.allowed_origin, 'http://all.example.com')
+ 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)
@@ -258,6 +297,16 @@ class CORSRegularRequestTest(CORSTestBase):
['GET', 'PUT', 'POST', 'DELETE', 'HEAD'])
self.assertEqual(ac.allow_headers, gc.allow_headers)
+ # Confirm duplicate domains.
+ ac = cfg.CONF['cors.duplicate']
+ self.assertEqual(ac.allowed_origin, ['http://domain1.example.com',
+ 'http://domain2.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, gc.allow_methods)
+ self.assertEqual(ac.allow_headers, gc.allow_headers)
+
def test_no_origin_header(self):
"""CORS Specification Section 6.1.1
@@ -329,6 +378,21 @@ class CORSRegularRequestTest(CORSTestBase):
allow_credentials=None,
expose_headers=None)
+ # Test valid header from list of duplicates.
+ for method in self.methods:
+ request = webob.Request.blank('/')
+ request.method = method
+ request.headers['Origin'] = 'http://domain2.example.com'
+ response = request.get_response(self.application)
+ self.assertCORSResponse(response,
+ status='200 OK',
+ allow_origin='http://domain2.example.com',
+ max_age=None,
+ allow_methods=None,
+ allow_headers=None,
+ allow_credentials=None,
+ expose_headers=None)
+
def test_supports_credentials(self):
"""CORS Specification Section 6.1.3
@@ -465,7 +529,7 @@ class CORSPreflightRequestTest(CORSTestBase):
# Confirm global configuration
gc = cfg.CONF.cors
- self.assertEqual(gc.allowed_origin, 'http://valid.example.com')
+ 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)
@@ -474,7 +538,7 @@ class CORSPreflightRequestTest(CORSTestBase):
# Confirm credentials overrides.
cc = cfg.CONF['cors.credentials']
- self.assertEqual(cc.allowed_origin, 'http://creds.example.com')
+ 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)
@@ -483,7 +547,7 @@ class CORSPreflightRequestTest(CORSTestBase):
# Confirm exposed-headers overrides.
ec = cfg.CONF['cors.exposed-headers']
- self.assertEqual(ec.allowed_origin, 'http://headers.example.com')
+ 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)
@@ -492,7 +556,7 @@ class CORSPreflightRequestTest(CORSTestBase):
# Confirm cached overrides.
chc = cfg.CONF['cors.cached']
- self.assertEqual(chc.allowed_origin, 'http://cached.example.com')
+ 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)
@@ -501,7 +565,7 @@ class CORSPreflightRequestTest(CORSTestBase):
# Confirm get-only overrides.
goc = cfg.CONF['cors.get-only']
- self.assertEqual(goc.allowed_origin, 'http://get.example.com')
+ 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)
@@ -510,7 +574,7 @@ class CORSPreflightRequestTest(CORSTestBase):
# Confirm all-methods overrides.
ac = cfg.CONF['cors.all-methods']
- self.assertEqual(ac.allowed_origin, 'http://all.example.com')
+ 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)
@@ -983,7 +1047,7 @@ class CORSTestWildcard(CORSTestBase):
# Confirm global configuration
gc = cfg.CONF.cors
- self.assertEqual(gc.allowed_origin, 'http://default.example.com')
+ 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)
@@ -993,7 +1057,7 @@ class CORSTestWildcard(CORSTestBase):
# Confirm all-methods overrides.
ac = cfg.CONF['cors.wildcard']
- self.assertEqual(ac.allowed_origin, '*')
+ 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)
diff --git a/requirements.txt b/requirements.txt
index 464eb56..dcaba68 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -4,12 +4,12 @@
pbr>=1.6
Babel>=1.3
-Jinja2>=2.6 # BSD License (3 clause)
+Jinja2>=2.8 # BSD License (3 clause)
ordereddict
oslo.config>=2.6.0 # Apache-2.0
oslo.context>=0.2.0 # Apache-2.0
oslo.i18n>=1.5.0 # Apache-2.0
-oslo.utils!=2.6.0,>=2.4.0 # Apache-2.0
+oslo.utils>=2.8.0 # Apache-2.0
six>=1.9.0
stevedore>=1.5.0 # Apache-2.0
WebOb>=1.2.3
diff --git a/setup.cfg b/setup.cfg
index c9e924a..f006941 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -15,17 +15,12 @@ classifier =
Programming Language :: Python
Programming Language :: Python :: 2
Programming Language :: Python :: 2.7
- Programming Language :: Python :: 2.6
Programming Language :: Python :: 3
Programming Language :: Python :: 3.4
[files]
packages =
- oslo
- oslo.middleware
oslo_middleware
-namespace_packages =
- oslo
[entry_points]
oslo.config.opts =
diff --git a/tests/__init__.py b/tests/__init__.py
deleted file mode 100644
index e69de29..0000000
--- a/tests/__init__.py
+++ /dev/null
diff --git a/tests/test_catch_errors.py b/tests/test_catch_errors.py
deleted file mode 100644
index 9e71f5b..0000000
--- a/tests/test_catch_errors.py
+++ /dev/null
@@ -1,47 +0,0 @@
-# Copyright (c) 2013 NEC Corporation
-# All Rights Reserved.
-#
-# 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 mock
-from oslotest import base as test_base
-import webob.dec
-import webob.exc
-
-from oslo.middleware import catch_errors
-
-
-class CatchErrorsTest(test_base.BaseTestCase):
-
- def _test_has_request_id(self, application, expected_code=None):
- app = catch_errors.CatchErrors(application)
- req = webob.Request.blank('/test')
- res = req.get_response(app)
- self.assertEqual(expected_code, res.status_int)
-
- def test_success_response(self):
- @webob.dec.wsgify
- def application(req):
- return 'Hello, World!!!'
-
- self._test_has_request_id(application, webob.exc.HTTPOk.code)
-
- def test_internal_server_error(self):
- @webob.dec.wsgify
- def application(req):
- raise Exception()
-
- with mock.patch.object(catch_errors.LOG, 'exception') as log_exc:
- self._test_has_request_id(application,
- webob.exc.HTTPInternalServerError.code)
- self.assertEqual(1, log_exc.call_count)
diff --git a/tests/test_correlation_id.py b/tests/test_correlation_id.py
deleted file mode 100644
index b927e1d..0000000
--- a/tests/test_correlation_id.py
+++ /dev/null
@@ -1,53 +0,0 @@
-# Copyright (c) 2013 Rackspace Hosting
-# All Rights Reserved.
-#
-# 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 uuid
-
-import mock
-from oslotest import base as test_base
-from oslotest import moxstubout
-
-from oslo.middleware import correlation_id
-
-
-class CorrelationIdTest(test_base.BaseTestCase):
-
- def setUp(self):
- super(CorrelationIdTest, self).setUp()
- self.stubs = self.useFixture(moxstubout.MoxStubout()).stubs
-
- def test_process_request(self):
- app = mock.Mock()
- req = mock.Mock()
- req.headers = {}
-
- mock_uuid4 = mock.Mock()
- mock_uuid4.return_value = "fake_uuid"
- self.stubs.Set(uuid, 'uuid4', mock_uuid4)
-
- middleware = correlation_id.CorrelationId(app)
- middleware(req)
-
- self.assertEqual(req.headers.get("X_CORRELATION_ID"), "fake_uuid")
-
- def test_process_request_should_not_regenerate_correlation_id(self):
- app = mock.Mock()
- req = mock.Mock()
- req.headers = {"X_CORRELATION_ID": "correlation_id"}
-
- middleware = correlation_id.CorrelationId(app)
- middleware(req)
-
- self.assertEqual(req.headers.get("X_CORRELATION_ID"), "correlation_id")
diff --git a/tests/test_request_id.py b/tests/test_request_id.py
deleted file mode 100644
index 549d7be..0000000
--- a/tests/test_request_id.py
+++ /dev/null
@@ -1,37 +0,0 @@
-# Copyright (c) 2013 NEC Corporation
-# All Rights Reserved.
-#
-# 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.
-
-
-from oslotest import base as test_base
-from testtools import matchers
-import webob
-import webob.dec
-
-from oslo.middleware import request_id
-
-
-class RequestIdTest(test_base.BaseTestCase):
- def test_generate_request_id(self):
- @webob.dec.wsgify
- def application(req):
- return req.environ[request_id.ENV_REQUEST_ID]
-
- app = request_id.RequestId(application)
- req = webob.Request.blank('/test')
- res = req.get_response(app)
- res_req_id = res.headers.get(request_id.HTTP_RESP_HEADER_REQUEST_ID)
- self.assertThat(res_req_id, matchers.StartsWith(b'req-'))
- # request-id in request environ is returned as response body
- self.assertEqual(res_req_id, res.body)
diff --git a/tests/test_sizelimit.py b/tests/test_sizelimit.py
deleted file mode 100644
index b985f76..0000000
--- a/tests/test_sizelimit.py
+++ /dev/null
@@ -1,108 +0,0 @@
-# Copyright (c) 2012 Red Hat, Inc.
-#
-# 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.
-
-from oslotest import base as test_base
-import six
-import webob
-
-from oslo.middleware import sizelimit
-from oslo_config import fixture as config
-
-
-class TestLimitingReader(test_base.BaseTestCase):
-
- def test_limiting_reader(self):
- BYTES = 1024
- bytes_read = 0
- data = six.StringIO("*" * BYTES)
- for chunk in sizelimit.LimitingReader(data, BYTES):
- bytes_read += len(chunk)
-
- self.assertEqual(bytes_read, BYTES)
-
- bytes_read = 0
- data = six.StringIO("*" * BYTES)
- reader = sizelimit.LimitingReader(data, BYTES)
- byte = reader.read(1)
- while len(byte) != 0:
- bytes_read += 1
- byte = reader.read(1)
-
- self.assertEqual(bytes_read, BYTES)
-
- def test_read_default_value(self):
- BYTES = 1024
- data_str = "*" * BYTES
- data = six.StringIO(data_str)
- reader = sizelimit.LimitingReader(data, BYTES)
- res = reader.read()
- self.assertEqual(data_str, res)
-
- def test_limiting_reader_fails(self):
- BYTES = 1024
-
- def _consume_all_iter():
- bytes_read = 0
- data = six.StringIO("*" * BYTES)
- for chunk in sizelimit.LimitingReader(data, BYTES - 1):
- bytes_read += len(chunk)
-
- self.assertRaises(webob.exc.HTTPRequestEntityTooLarge,
- _consume_all_iter)
-
- def _consume_all_read():
- bytes_read = 0
- data = six.StringIO("*" * BYTES)
- reader = sizelimit.LimitingReader(data, BYTES - 1)
- byte = reader.read(1)
- while len(byte) != 0:
- bytes_read += 1
- byte = reader.read(1)
-
- self.assertRaises(webob.exc.HTTPRequestEntityTooLarge,
- _consume_all_read)
-
-
-class TestRequestBodySizeLimiter(test_base.BaseTestCase):
-
- def setUp(self):
- super(TestRequestBodySizeLimiter, self).setUp()
- self.useFixture(config.Config())
-
- @webob.dec.wsgify()
- def fake_app(req):
- return webob.Response(req.body)
-
- self.middleware = sizelimit.RequestBodySizeLimiter(fake_app)
- self.MAX_REQUEST_BODY_SIZE = (
- self.middleware.oslo_conf.oslo_middleware.max_request_body_size)
- self.request = webob.Request.blank('/', method='POST')
-
- def test_content_length_acceptable(self):
- self.request.headers['Content-Length'] = self.MAX_REQUEST_BODY_SIZE
- self.request.body = b"0" * self.MAX_REQUEST_BODY_SIZE
- response = self.request.get_response(self.middleware)
- self.assertEqual(response.status_int, 200)
-
- def test_content_length_too_large(self):
- self.request.headers['Content-Length'] = self.MAX_REQUEST_BODY_SIZE + 1
- self.request.body = b"0" * (self.MAX_REQUEST_BODY_SIZE + 1)
- response = self.request.get_response(self.middleware)
- self.assertEqual(response.status_int, 413)
-
- def test_request_too_large_no_content_length(self):
- self.request.body = b"0" * (self.MAX_REQUEST_BODY_SIZE + 1)
- self.request.headers['Content-Length'] = None
- response = self.request.get_response(self.middleware)
- self.assertEqual(response.status_int, 413)
diff --git a/tests/test_warning.py b/tests/test_warning.py
deleted file mode 100644
index 8e7d96c..0000000
--- a/tests/test_warning.py
+++ /dev/null
@@ -1,61 +0,0 @@
-# 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 imp
-import os
-import warnings
-
-import mock
-from oslotest import base as test_base
-import six
-
-
-class DeprecationWarningTest(test_base.BaseTestCase):
-
- @mock.patch('warnings.warn')
- def test_warning(self, mock_warn):
- import oslo.middleware
- imp.reload(oslo.middleware)
- self.assertTrue(mock_warn.called)
- args = mock_warn.call_args
- self.assertIn('oslo_middleware', args[0][0])
- self.assertIn('deprecated', args[0][0])
- self.assertTrue(issubclass(args[0][1], DeprecationWarning))
-
- def test_real_warning(self):
- with warnings.catch_warnings(record=True) as warning_msgs:
- warnings.resetwarnings()
- warnings.simplefilter('always', DeprecationWarning)
- import oslo.middleware
-
- # Use a separate function to get the stack level correct
- # so we know the message points back to this file. This
- # corresponds to an import or reload, which isn't working
- # inside the test under Python 3.3. That may be due to a
- # difference in the import implementation not triggering
- # warnings properly when the module is reloaded, or
- # because the warnings module is mostly implemented in C
- # and something isn't cleanly resetting the global state
- # used to track whether a warning needs to be
- # emitted. Whatever the cause, we definitely see the
- # warnings.warn() being invoked on a reload (see the test
- # above) and warnings are reported on the console when we
- # run the tests. A simpler test script run outside of
- # testr does correctly report the warnings.
- def foo():
- oslo.middleware.deprecated()
-
- foo()
- self.assertEqual(1, len(warning_msgs))
- msg = warning_msgs[0]
- self.assertIn('oslo_middleware', six.text_type(msg.message))
- self.assertEqual('test_warning.py', os.path.basename(msg.filename))
diff --git a/tox.ini b/tox.ini
index 95922fc..1117f30 100644
--- a/tox.ini
+++ b/tox.ini
@@ -1,19 +1,9 @@
[tox]
minversion = 1.6
-envlist = py34,py26,py27,pypy,pep8
-# NOTE(dhellmann): We cannot set skipdist=True
-# for oslo libraries because of the namespace package.
-#skipsdist = True
+envlist = py34,py27,pypy,pep8
[testenv]
-# NOTE(dhellmann): We cannot set usedevelop=True
-# for oslo libraries because of the namespace package.
-#usedevelop = True
-install_command = pip install -U {opts} {packages}
-setenv =
- VIRTUAL_ENV={envdir}
-deps = -r{toxinidir}/requirements.txt
- -r{toxinidir}/test-requirements.txt
+deps = -r{toxinidir}/test-requirements.txt
commands = python setup.py testr --slowest --testr-args='{posargs}'
[testenv:pep8]