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:
Mike Perez 2012-11-04 01:20:47 -08:00
parent 30f8d3f8d6
commit c15d5eae48
13 changed files with 153 additions and 138 deletions

View 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.

View File

@ -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

View 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)

View File

@ -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 "":

View File

@ -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

View File

View 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'

View File

@ -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):

View File

@ -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

View File

@ -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):

View File

@ -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