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
This commit is contained in:
16
cinder/api/middleware/__init__.py
Normal file
16
cinder/api/middleware/__init__.py
Normal file
@@ -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.
|
||||
@@ -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
|
||||
75
cinder/api/middleware/fault.py
Normal file
75
cinder/api/middleware/fault.py
Normal file
@@ -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)
|
||||
@@ -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 "":
|
||||
|
||||
@@ -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
|
||||
0
cinder/tests/api/middleware/__init__.py
Normal file
0
cinder/tests/api/middleware/__init__.py
Normal file
@@ -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'
|
||||
@@ -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):
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
@@ -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):
|
||||
|
||||
Reference in New Issue
Block a user