From c15d5eae48c42d5b885b8a6557d091a75713015f Mon Sep 17 00:00:00 2001 From: Mike Perez Date: Sun, 4 Nov 2012 01:20:47 -0800 Subject: [PATCH] moving all middleware code in cinder.api.middleware This includes auth, sizelimit, and faultwrapper middleware. Test directory struture has been adjusted to match. progress on blueprint apiv2 Change-Id: I62eecb208553abfee8dc8d2679264884b4cec153 --- cinder/api/middleware/__init__.py | 16 ++++ cinder/api/{ => middleware}/auth.py | 49 ++++++++++-- cinder/api/middleware/fault.py | 75 +++++++++++++++++++ cinder/api/{ => middleware}/sizelimit.py | 0 cinder/api/openstack/__init__.py | 49 ------------ cinder/api/openstack/auth.py | 65 ---------------- cinder/tests/api/middleware/__init__.py | 0 .../tests/api/{ => middleware}/test_auth.py | 5 +- .../{openstack => middleware}/test_faults.py | 0 .../api/{ => middleware}/test_sizelimit.py | 5 +- cinder/tests/api/openstack/fakes.py | 13 ++-- cinder/tests/test_wsgi.py | 4 +- etc/cinder/api-paste.ini | 10 +-- 13 files changed, 153 insertions(+), 138 deletions(-) create mode 100644 cinder/api/middleware/__init__.py rename cinder/api/{ => middleware}/auth.py (65%) create mode 100644 cinder/api/middleware/fault.py rename cinder/api/{ => middleware}/sizelimit.py (100%) delete mode 100644 cinder/api/openstack/auth.py create mode 100644 cinder/tests/api/middleware/__init__.py rename cinder/tests/api/{ => middleware}/test_auth.py (93%) rename cinder/tests/api/{openstack => middleware}/test_faults.py (100%) rename cinder/tests/api/{ => middleware}/test_sizelimit.py (91%) diff --git a/cinder/api/middleware/__init__.py b/cinder/api/middleware/__init__.py new file mode 100644 index 000000000..d65c689a8 --- /dev/null +++ b/cinder/api/middleware/__init__.py @@ -0,0 +1,16 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2011 OpenStack LLC. +# 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. diff --git a/cinder/api/auth.py b/cinder/api/middleware/auth.py similarity index 65% rename from cinder/api/auth.py rename to cinder/api/middleware/auth.py index cea6c60c5..e806576d8 100644 --- a/cinder/api/auth.py +++ b/cinder/api/middleware/auth.py @@ -1,6 +1,7 @@ # vim: tabstop=4 shiftwidth=4 softtabstop=4 -# Copyright (c) 2011 OpenStack, LLC +# Copyright 2010 OpenStack LLC. +# 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 @@ -17,15 +18,17 @@ Common Auth Middleware. """ +import os import webob.dec import webob.exc +from cinder.api.openstack import wsgi from cinder import context from cinder import flags from cinder.openstack.common import cfg from cinder.openstack.common import log as logging -from cinder import wsgi +from cinder import wsgi as base_wsgi use_forwarded_for_opt = cfg.BoolOpt('use_forwarded_for', @@ -53,23 +56,23 @@ def pipeline_factory(loader, global_conf, **local_conf): return app -class InjectContext(wsgi.Middleware): +class InjectContext(base_wsgi.Middleware): """Add a 'cinder.context' to WSGI environ.""" def __init__(self, context, *args, **kwargs): self.context = context super(InjectContext, self).__init__(*args, **kwargs) - @webob.dec.wsgify(RequestClass=wsgi.Request) + @webob.dec.wsgify(RequestClass=base_wsgi.Request) def __call__(self, req): req.environ['cinder.context'] = self.context return self.application -class CinderKeystoneContext(wsgi.Middleware): +class CinderKeystoneContext(base_wsgi.Middleware): """Make a request context from keystone headers""" - @webob.dec.wsgify(RequestClass=wsgi.Request) + @webob.dec.wsgify(RequestClass=base_wsgi.Request) def __call__(self, req): user_id = req.headers.get('X_USER') user_id = req.headers.get('X_USER_ID', user_id) @@ -101,3 +104,37 @@ class CinderKeystoneContext(wsgi.Middleware): req.environ['cinder.context'] = ctx return self.application + + +class NoAuthMiddleware(base_wsgi.Middleware): + """Return a fake token if one isn't specified.""" + + @webob.dec.wsgify(RequestClass=wsgi.Request) + def __call__(self, req): + if 'X-Auth-Token' not in req.headers: + user_id = req.headers.get('X-Auth-User', 'admin') + project_id = req.headers.get('X-Auth-Project-Id', 'admin') + os_url = os.path.join(req.url, project_id) + res = webob.Response() + # NOTE(vish): This is expecting and returning Auth(1.1), whereas + # keystone uses 2.0 auth. We should probably allow + # 2.0 auth here as well. + res.headers['X-Auth-Token'] = '%s:%s' % (user_id, project_id) + res.headers['X-Server-Management-Url'] = os_url + res.content_type = 'text/plain' + res.status = '204' + return res + + token = req.headers['X-Auth-Token'] + user_id, _sep, project_id = token.partition(':') + project_id = project_id or user_id + remote_address = getattr(req, 'remote_address', '127.0.0.1') + if FLAGS.use_forwarded_for: + remote_address = req.headers.get('X-Forwarded-For', remote_address) + ctx = context.RequestContext(user_id, + project_id, + is_admin=True, + remote_address=remote_address) + + req.environ['cinder.context'] = ctx + return self.application diff --git a/cinder/api/middleware/fault.py b/cinder/api/middleware/fault.py new file mode 100644 index 000000000..15892b4e3 --- /dev/null +++ b/cinder/api/middleware/fault.py @@ -0,0 +1,75 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2010 United States Government as represented by the +# Administrator of the National Aeronautics and Space Administration. +# 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 webob.dec +import webob.exc + +from cinder.api.openstack import wsgi +from cinder.openstack.common import log as logging +from cinder import utils +from cinder import wsgi as base_wsgi + + +LOG = logging.getLogger(__name__) + + +class FaultWrapper(base_wsgi.Middleware): + """Calls down the middleware stack, making exceptions into faults.""" + + _status_to_type = {} + + @staticmethod + def status_to_type(status): + if not FaultWrapper._status_to_type: + for clazz in utils.walk_class_hierarchy(webob.exc.HTTPError): + FaultWrapper._status_to_type[clazz.code] = clazz + return FaultWrapper._status_to_type.get( + status, webob.exc.HTTPInternalServerError)() + + def _error(self, inner, req): + LOG.exception(_("Caught error: %s"), unicode(inner)) + + safe = getattr(inner, 'safe', False) + headers = getattr(inner, 'headers', None) + status = getattr(inner, 'code', 500) + if status is None: + status = 500 + + msg_dict = dict(url=req.url, status=status) + LOG.info(_("%(url)s returned with HTTP %(status)d") % msg_dict) + outer = self.status_to_type(status) + if headers: + outer.headers = headers + # NOTE(johannes): We leave the explanation empty here on + # purpose. It could possibly have sensitive information + # that should not be returned back to the user. See + # bugs 868360 and 874472 + # NOTE(eglynn): However, it would be over-conservative and + # inconsistent with the EC2 API to hide every exception, + # including those that are safe to expose, see bug 1021373 + if safe: + outer.explanation = '%s: %s' % (inner.__class__.__name__, + unicode(inner)) + return wsgi.Fault(outer) + + @webob.dec.wsgify(RequestClass=wsgi.Request) + def __call__(self, req): + try: + return req.get_response(self.application) + except Exception as ex: + return self._error(ex, req) diff --git a/cinder/api/sizelimit.py b/cinder/api/middleware/sizelimit.py similarity index 100% rename from cinder/api/sizelimit.py rename to cinder/api/middleware/sizelimit.py diff --git a/cinder/api/openstack/__init__.py b/cinder/api/openstack/__init__.py index 9f6079dd3..d68639860 100644 --- a/cinder/api/openstack/__init__.py +++ b/cinder/api/openstack/__init__.py @@ -21,8 +21,6 @@ WSGI middleware for OpenStack API controllers. """ import routes -import webob.dec -import webob.exc from cinder.api.openstack import wsgi from cinder.openstack.common import log as logging @@ -33,53 +31,6 @@ from cinder import wsgi as base_wsgi LOG = logging.getLogger(__name__) -class FaultWrapper(base_wsgi.Middleware): - """Calls down the middleware stack, making exceptions into faults.""" - - _status_to_type = {} - - @staticmethod - def status_to_type(status): - if not FaultWrapper._status_to_type: - for clazz in utils.walk_class_hierarchy(webob.exc.HTTPError): - FaultWrapper._status_to_type[clazz.code] = clazz - return FaultWrapper._status_to_type.get( - status, webob.exc.HTTPInternalServerError)() - - def _error(self, inner, req): - LOG.exception(_("Caught error: %s"), unicode(inner)) - - safe = getattr(inner, 'safe', False) - headers = getattr(inner, 'headers', None) - status = getattr(inner, 'code', 500) - if status is None: - status = 500 - - msg_dict = dict(url=req.url, status=status) - LOG.info(_("%(url)s returned with HTTP %(status)d") % msg_dict) - outer = self.status_to_type(status) - if headers: - outer.headers = headers - # NOTE(johannes): We leave the explanation empty here on - # purpose. It could possibly have sensitive information - # that should not be returned back to the user. See - # bugs 868360 and 874472 - # NOTE(eglynn): However, it would be over-conservative and - # inconsistent with the EC2 API to hide every exception, - # including those that are safe to expose, see bug 1021373 - if safe: - outer.explanation = '%s: %s' % (inner.__class__.__name__, - unicode(inner)) - return wsgi.Fault(outer) - - @webob.dec.wsgify(RequestClass=wsgi.Request) - def __call__(self, req): - try: - return req.get_response(self.application) - except Exception as ex: - return self._error(ex, req) - - class APIMapper(routes.Mapper): def routematch(self, url=None, environ=None): if url is "": diff --git a/cinder/api/openstack/auth.py b/cinder/api/openstack/auth.py deleted file mode 100644 index 68c98dad5..000000000 --- a/cinder/api/openstack/auth.py +++ /dev/null @@ -1,65 +0,0 @@ -# vim: tabstop=4 shiftwidth=4 softtabstop=4 - -# Copyright 2010 OpenStack LLC. -# 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 os - -import webob.dec -import webob.exc - -from cinder.api.openstack import wsgi -from cinder import context -from cinder import flags -from cinder.openstack.common import log as logging -from cinder import wsgi as base_wsgi - -LOG = logging.getLogger(__name__) -FLAGS = flags.FLAGS -flags.DECLARE('use_forwarded_for', 'cinder.api.auth') - - -class NoAuthMiddleware(base_wsgi.Middleware): - """Return a fake token if one isn't specified.""" - - @webob.dec.wsgify(RequestClass=wsgi.Request) - def __call__(self, req): - if 'X-Auth-Token' not in req.headers: - user_id = req.headers.get('X-Auth-User', 'admin') - project_id = req.headers.get('X-Auth-Project-Id', 'admin') - os_url = os.path.join(req.url, project_id) - res = webob.Response() - # NOTE(vish): This is expecting and returning Auth(1.1), whereas - # keystone uses 2.0 auth. We should probably allow - # 2.0 auth here as well. - res.headers['X-Auth-Token'] = '%s:%s' % (user_id, project_id) - res.headers['X-Server-Management-Url'] = os_url - res.content_type = 'text/plain' - res.status = '204' - return res - - token = req.headers['X-Auth-Token'] - user_id, _sep, project_id = token.partition(':') - project_id = project_id or user_id - remote_address = getattr(req, 'remote_address', '127.0.0.1') - if FLAGS.use_forwarded_for: - remote_address = req.headers.get('X-Forwarded-For', remote_address) - ctx = context.RequestContext(user_id, - project_id, - is_admin=True, - remote_address=remote_address) - - req.environ['cinder.context'] = ctx - return self.application diff --git a/cinder/tests/api/middleware/__init__.py b/cinder/tests/api/middleware/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/cinder/tests/api/test_auth.py b/cinder/tests/api/middleware/test_auth.py similarity index 93% rename from cinder/tests/api/test_auth.py rename to cinder/tests/api/middleware/test_auth.py index cfb8b7775..4fca13fe6 100644 --- a/cinder/tests/api/test_auth.py +++ b/cinder/tests/api/middleware/test_auth.py @@ -14,7 +14,7 @@ import webob -import cinder.api.auth +import cinder.api.middleware.auth from cinder import test @@ -29,7 +29,8 @@ class TestCinderKeystoneContextMiddleware(test.TestCase): return webob.Response() self.context = None - self.middleware = cinder.api.auth.CinderKeystoneContext(fake_app) + self.middleware = (cinder.api.middleware.auth + .CinderKeystoneContext(fake_app)) self.request = webob.Request.blank('/') self.request.headers['X_TENANT_ID'] = 'testtenantid' self.request.headers['X_AUTH_TOKEN'] = 'testauthtoken' diff --git a/cinder/tests/api/openstack/test_faults.py b/cinder/tests/api/middleware/test_faults.py similarity index 100% rename from cinder/tests/api/openstack/test_faults.py rename to cinder/tests/api/middleware/test_faults.py diff --git a/cinder/tests/api/test_sizelimit.py b/cinder/tests/api/middleware/test_sizelimit.py similarity index 91% rename from cinder/tests/api/test_sizelimit.py rename to cinder/tests/api/middleware/test_sizelimit.py index 280ee9c29..0a151dabf 100644 --- a/cinder/tests/api/test_sizelimit.py +++ b/cinder/tests/api/middleware/test_sizelimit.py @@ -14,7 +14,7 @@ import webob -import cinder.api.sizelimit +import cinder.api.middleware.sizelimit from cinder import flags from cinder import test @@ -31,7 +31,8 @@ class TestRequestBodySizeLimiter(test.TestCase): def fake_app(req): return webob.Response() - self.middleware = cinder.api.sizelimit.RequestBodySizeLimiter(fake_app) + self.middleware = (cinder.api.middleware.sizelimit + .RequestBodySizeLimiter(fake_app)) self.request = webob.Request.blank('/', method='POST') def test_content_length_acceptable(self): diff --git a/cinder/tests/api/openstack/fakes.py b/cinder/tests/api/openstack/fakes.py index cbc8613b3..87c2a61d3 100644 --- a/cinder/tests/api/openstack/fakes.py +++ b/cinder/tests/api/openstack/fakes.py @@ -23,9 +23,8 @@ import webob import webob.dec import webob.request -from cinder.api import auth as api_auth -from cinder.api import openstack as openstack_api -from cinder.api.openstack import auth +from cinder.api.middleware import auth +from cinder.api.middleware import fault from cinder.api.openstack import volume from cinder.api.openstack.volume import limits from cinder.api.openstack import wsgi as os_wsgi @@ -72,18 +71,18 @@ def wsgi_app(inner_app_v1=None, fake_auth=True, fake_auth_context=None, ctxt = fake_auth_context else: ctxt = context.RequestContext('fake', 'fake', auth_token=True) - api_v1 = openstack_api.FaultWrapper(api_auth.InjectContext(ctxt, + api_v1 = fault.FaultWrapper(auth.InjectContext(ctxt, inner_app_v1)) elif use_no_auth: - api_v1 = openstack_api.FaultWrapper(auth.NoAuthMiddleware( + api_v1 = fault.FaultWrapper(auth.NoAuthMiddleware( limits.RateLimitingMiddleware(inner_app_v1))) else: - api_v1 = openstack_api.FaultWrapper(auth.AuthMiddleware( + api_v1 = fault.FaultWrapper(auth.AuthMiddleware( limits.RateLimitingMiddleware(inner_app_v1))) mapper = urlmap.URLMap() mapper['/v1'] = api_v1 - mapper['/'] = openstack_api.FaultWrapper(versions.Versions()) + mapper['/'] = fault.FaultWrapper(versions.Versions()) return mapper diff --git a/cinder/tests/test_wsgi.py b/cinder/tests/test_wsgi.py index f9189afa7..08b933809 100644 --- a/cinder/tests/test_wsgi.py +++ b/cinder/tests/test_wsgi.py @@ -24,7 +24,7 @@ import tempfile import unittest import webob.dec -from cinder.api import openstack as openstack_api +from cinder.api.middleware import fault from cinder import exception from cinder import test import cinder.wsgi @@ -97,7 +97,7 @@ class TestWSGIServer(unittest.TestCase): class ExceptionTest(test.TestCase): def _wsgi_app(self, inner_app): - return openstack_api.FaultWrapper(inner_app) + return fault.FaultWrapper(inner_app) def _do_test_exception_safety_reflected_in_faults(self, expose): class ExceptionWithSafety(exception.CinderException): diff --git a/etc/cinder/api-paste.ini b/etc/cinder/api-paste.ini index 575f4bd6a..c7c5baf7e 100644 --- a/etc/cinder/api-paste.ini +++ b/etc/cinder/api-paste.ini @@ -8,19 +8,19 @@ use = call:cinder.api.urlmap:urlmap_factory /v1: openstack_volume_api_v1 [composite:openstack_volume_api_v1] -use = call:cinder.api.auth:pipeline_factory +use = call:cinder.api.middleware.auth:pipeline_factory noauth = faultwrap sizelimit noauth osapi_volume_app_v1 keystone = faultwrap sizelimit authtoken keystonecontext osapi_volume_app_v1 keystone_nolimit = faultwrap sizelimit authtoken keystonecontext osapi_volume_app_v1 [filter:faultwrap] -paste.filter_factory = cinder.api.openstack:FaultWrapper.factory +paste.filter_factory = cinder.api.middleware.fault:FaultWrapper.factory [filter:noauth] -paste.filter_factory = cinder.api.openstack.auth:NoAuthMiddleware.factory +paste.filter_factory = cinder.api.middleware.auth:NoAuthMiddleware.factory [filter:sizelimit] -paste.filter_factory = cinder.api.sizelimit:RequestBodySizeLimiter.factory +paste.filter_factory = cinder.api.middleware.sizelimit:RequestBodySizeLimiter.factory [app:osapi_volume_app_v1] paste.app_factory = cinder.api.openstack.volume:APIRouter.factory @@ -36,7 +36,7 @@ paste.app_factory = cinder.api.versions:Versions.factory ########## [filter:keystonecontext] -paste.filter_factory = cinder.api.auth:CinderKeystoneContext.factory +paste.filter_factory = cinder.api.middleware.auth:CinderKeystoneContext.factory [filter:authtoken] paste.filter_factory = keystone.middleware.auth_token:filter_factory -- 2.45.2