Pecan: Allow unauthenticated version listing
The current API allows listings of the neturon server version without authentication. Because pecan was treating this like a normal controller, it was requiring keystone authentication. This adjusts the version listing to behave as a wrapper so it can be placed outside of the keystone authentication wrapper to allow anonymous queries. Closes-Bug: #1556038 Change-Id: I9f5aa3bea0e11c5e179fc286f9fa350b3930364f
This commit is contained in:
parent
e433c2870a
commit
21825d6cbe
|
@ -25,7 +25,7 @@ class Versions(object):
|
|||
|
||||
@classmethod
|
||||
def factory(cls, global_config, **local_config):
|
||||
return cls()
|
||||
return cls(app=None)
|
||||
|
||||
@webob.dec.wsgify(RequestClass=wsgi.Request)
|
||||
def __call__(self, req):
|
||||
|
@ -38,6 +38,8 @@ class Versions(object):
|
|||
]
|
||||
|
||||
if req.path != '/':
|
||||
if self.app:
|
||||
return req.get_response(self.app)
|
||||
language = req.best_match_language()
|
||||
msg = _('Unknown API version specified')
|
||||
msg = oslo_i18n.translate(msg, language)
|
||||
|
@ -57,3 +59,6 @@ class Versions(object):
|
|||
response.body = wsgi.encode_body(body)
|
||||
|
||||
return response
|
||||
|
||||
def __init__(self, app):
|
||||
self.app = app
|
||||
|
|
|
@ -19,6 +19,7 @@ from oslo_middleware import cors
|
|||
from oslo_middleware import request_id
|
||||
import pecan
|
||||
|
||||
from neutron.api import versions
|
||||
from neutron.common import exceptions as n_exc
|
||||
from neutron.pecan_wsgi import hooks
|
||||
from neutron.pecan_wsgi import startup
|
||||
|
@ -75,6 +76,9 @@ def _wrap_app(app):
|
|||
raise n_exc.InvalidConfigurationOption(
|
||||
opt_name='auth_strategy', opt_value=cfg.CONF.auth_strategy)
|
||||
|
||||
# version can be unauthenticated so it goes outside of auth
|
||||
app = versions.Versions(app)
|
||||
|
||||
# This should be the last middleware in the list (which results in
|
||||
# it being the first in the middleware chain). This is to ensure
|
||||
# that any errors thrown by other middleware, such as an auth
|
||||
|
|
|
@ -41,10 +41,12 @@ class RootController(object):
|
|||
|
||||
@utils.expose(generic=True)
|
||||
def index(self):
|
||||
builder = versions_view.get_view_builder(pecan.request)
|
||||
versions = [builder.build(version) for version in _get_version_info()]
|
||||
return dict(versions=versions)
|
||||
# NOTE(kevinbenton): The pecan framework does not handle
|
||||
# any requests to the root because they are intercepted
|
||||
# by the 'version' returning wrapper.
|
||||
pass
|
||||
|
||||
@utils.when(index, method='GET')
|
||||
@utils.when(index, method='HEAD')
|
||||
@utils.when(index, method='POST')
|
||||
@utils.when(index, method='PATCH')
|
||||
|
|
|
@ -62,10 +62,10 @@ class TestRootController(test_functional.PecanFunctionalTest):
|
|||
manager.NeutronManager.set_controller_for_resource(
|
||||
_SERVICE_PLUGIN_COLLECTION, FakeServicePluginController())
|
||||
|
||||
def _test_method_returns_405(self, method):
|
||||
def _test_method_returns_code(self, method, code=200):
|
||||
api_method = getattr(self.app, method)
|
||||
response = api_method(self.base_url, expect_errors=True)
|
||||
self.assertEqual(response.status_int, 405)
|
||||
self.assertEqual(response.status_int, code)
|
||||
|
||||
def test_get(self):
|
||||
response = self.app.get(self.base_url)
|
||||
|
@ -77,20 +77,12 @@ class TestRootController(test_functional.PecanFunctionalTest):
|
|||
self.assertIn(attr, versions[0])
|
||||
self.assertEqual(value, versions[0][attr])
|
||||
|
||||
def test_post(self):
|
||||
self._test_method_returns_405('post')
|
||||
|
||||
def test_put(self):
|
||||
self._test_method_returns_405('put')
|
||||
|
||||
def test_patch(self):
|
||||
self._test_method_returns_405('patch')
|
||||
|
||||
def test_delete(self):
|
||||
self._test_method_returns_405('delete')
|
||||
|
||||
def test_head(self):
|
||||
self._test_method_returns_405('head')
|
||||
def test_methods(self):
|
||||
self._test_method_returns_code('post')
|
||||
self._test_method_returns_code('patch')
|
||||
self._test_method_returns_code('delete')
|
||||
self._test_method_returns_code('head')
|
||||
self._test_method_returns_code('put')
|
||||
|
||||
|
||||
class TestV2Controller(TestRootController):
|
||||
|
@ -116,6 +108,14 @@ class TestV2Controller(TestRootController):
|
|||
expect_errors=True)
|
||||
self.assertEqual(response.status_int, 404)
|
||||
|
||||
def test_methods(self):
|
||||
self._test_method_returns_code('post', 405)
|
||||
self._test_method_returns_code('put', 405)
|
||||
self._test_method_returns_code('patch', 405)
|
||||
self._test_method_returns_code('delete', 405)
|
||||
self._test_method_returns_code('head', 405)
|
||||
self._test_method_returns_code('delete', 405)
|
||||
|
||||
|
||||
class TestExtensionsController(TestRootController):
|
||||
"""Test extension listing and detail reporting."""
|
||||
|
@ -142,6 +142,14 @@ class TestExtensionsController(TestRootController):
|
|||
json_body = jsonutils.loads(response.body)
|
||||
self.assertEqual(test_alias, json_body['extension']['alias'])
|
||||
|
||||
def test_methods(self):
|
||||
self._test_method_returns_code('post', 405)
|
||||
self._test_method_returns_code('put', 405)
|
||||
self._test_method_returns_code('patch', 405)
|
||||
self._test_method_returns_code('delete', 405)
|
||||
self._test_method_returns_code('head', 405)
|
||||
self._test_method_returns_code('delete', 405)
|
||||
|
||||
|
||||
class TestQuotasController(test_functional.PecanFunctionalTest):
|
||||
"""Test quota management API controller."""
|
||||
|
@ -229,6 +237,7 @@ class TestResourceController(TestRootController):
|
|||
"""Test generic controller"""
|
||||
# TODO(salv-orlando): This test case must not explicitly test the 'port'
|
||||
# resource. Also it should validate correct plugin/resource association
|
||||
base_url = '/v2.0'
|
||||
|
||||
def setUp(self):
|
||||
super(TestResourceController, self).setUp()
|
||||
|
@ -279,6 +288,14 @@ class TestResourceController(TestRootController):
|
|||
def test_plugin_initialized(self):
|
||||
self.assertIsNotNone(manager.NeutronManager._instance)
|
||||
|
||||
def test_methods(self):
|
||||
self._test_method_returns_code('post', 405)
|
||||
self._test_method_returns_code('put', 405)
|
||||
self._test_method_returns_code('patch', 405)
|
||||
self._test_method_returns_code('delete', 405)
|
||||
self._test_method_returns_code('head', 405)
|
||||
self._test_method_returns_code('delete', 405)
|
||||
|
||||
|
||||
class TestRequestProcessing(TestResourceController):
|
||||
|
||||
|
|
|
@ -73,7 +73,7 @@ class TestErrors(PecanFunctionalTest):
|
|||
class TestRequestID(PecanFunctionalTest):
|
||||
|
||||
def test_request_id(self):
|
||||
response = self.app.get('/')
|
||||
response = self.app.get('/v2.0/')
|
||||
self.assertIn('x-openstack-request-id', response.headers)
|
||||
self.assertTrue(
|
||||
response.headers['x-openstack-request-id'].startswith('req-'))
|
||||
|
@ -88,7 +88,7 @@ class TestKeystoneAuth(PecanFunctionalTest):
|
|||
pass
|
||||
|
||||
def test_auth_enforced(self):
|
||||
response = self.app.get('/', expect_errors=True)
|
||||
response = self.app.get('/v2.0/', expect_errors=True)
|
||||
self.assertEqual(response.status_int, 401)
|
||||
|
||||
|
||||
|
|
Loading…
Reference in New Issue