diff options
author | Roman Prykhodchenko <me@romcheg.me> | 2014-01-14 18:14:32 +0200 |
---|---|---|
committer | Roman Prykhodchenko <me@romcheg.me> | 2014-03-14 12:35:24 +0200 |
commit | de3de047d1163775cebda55ef04603e0a93777b2 (patch) | |
tree | 46c8185ad8d4967d7aac25f602dfec7dba1c121e | |
parent | 9e15b9c511d2c2d4be5e3319a1642381212eef1c (diff) | |
download | ironic-de3de047d1163775cebda55ef04603e0a93777b2.tar.gz |
Process public API list as regular expressions
In order to allow flexible patterns for public API specification
and to speed up the process of checking whether some request is
performed against the public API, it's reasonable to interpret the
list of public endpoints as regular expressions.
This patch changes the logic of checking whether a request requires
authorisation from searching the endpoint in the set to checking the
endpoint against a list of regular expressions.
Closes-bug: #1251880
Change-Id: I638ca0e20fa7e44fbeeae0d1e4c2f4188fb597a5
-rw-r--r-- | ironic/api/hooks.py | 5 | ||||
-rw-r--r-- | ironic/api/middleware/auth_token.py | 25 | ||||
-rw-r--r-- | ironic/common/exception.py | 4 | ||||
-rw-r--r-- | ironic/tests/api/test_acl.py | 13 |
4 files changed, 38 insertions, 9 deletions
diff --git a/ironic/api/hooks.py b/ironic/api/hooks.py index 519bba0c2..bc4c9f3d1 100644 --- a/ironic/api/hooks.py +++ b/ironic/api/hooks.py @@ -21,7 +21,6 @@ from pecan import hooks from webob import exc from ironic.common import context -from ironic.common import utils from ironic.conductor import rpcapi from ironic.db import api as dbapi from ironic.openstack.common import policy @@ -75,11 +74,9 @@ class ContextHook(hooks.PecanHook): auth_token = state.request.headers.get('X-Auth-Token') creds = {'roles': state.request.headers.get('X-Roles', '').split(',')} + is_public_api = state.request.environ.get('is_public_api', False) is_admin = policy.check('admin', state.request.headers, creds) - path = utils.safe_rstrip(state.request.path, '/') - is_public_api = path in self.public_api_routes - state.request.context = context.RequestContext( auth_token=auth_token, user=user_id, diff --git a/ironic/api/middleware/auth_token.py b/ironic/api/middleware/auth_token.py index d9c32b111..9cad079b3 100644 --- a/ironic/api/middleware/auth_token.py +++ b/ironic/api/middleware/auth_token.py @@ -12,9 +12,15 @@ # License for the specific language governing permissions and limitations # under the License. +import re + from keystoneclient.middleware import auth_token +from ironic.common import exception from ironic.common import utils +from ironic.openstack.common import log + +LOG = log.getLogger(__name__) class AuthTokenMiddleware(auth_token.AuthProtocol): @@ -25,14 +31,29 @@ class AuthTokenMiddleware(auth_token.AuthProtocol): """ def __init__(self, app, conf, public_api_routes=[]): - self.public_api_routes = set(public_api_routes) + route_pattern_tpl = '%s(\.json|\.xml)?$' + + try: + self.public_api_routes = [re.compile(route_pattern_tpl % route_tpl) + for route_tpl in public_api_routes] + except re.error as e: + msg = _('Cannot compile public API routes: %s') % e + + LOG.error(msg) + raise exception.ConfigInvalid(error_msg=msg) super(AuthTokenMiddleware, self).__init__(app, conf) def __call__(self, env, start_response): path = utils.safe_rstrip(env.get('PATH_INFO'), '/') - if path in self.public_api_routes: + # The information whether the API call is being performed against the + # public API is required for some other components. Saving it to the + # WSGI environment is reasonable thereby. + env['is_public_api'] = any(map(lambda pattern: re.match(pattern, path), + self.public_api_routes)) + + if env['is_public_api']: return self.app(env, start_response) return super(AuthTokenMiddleware, self).__call__(env, start_response) diff --git a/ironic/common/exception.py b/ironic/common/exception.py index 3a604127b..7fdadec73 100644 --- a/ironic/common/exception.py +++ b/ironic/common/exception.py @@ -349,3 +349,7 @@ class NoFreeConductorWorker(TemporaryFailure): class VendorPassthruException(IronicException): pass + + +class ConfigInvalid(IronicException): + message = _("Invalid configuration file. %(error_msg)s") diff --git a/ironic/tests/api/test_acl.py b/ironic/tests/api/test_acl.py index 57056fc4e..0dfb8a499 100644 --- a/ironic/tests/api/test_acl.py +++ b/ironic/tests/api/test_acl.py @@ -83,6 +83,13 @@ class TestACL(base.FunctionalTest): # expect_errors should be set to True: If expect_errors is set to False # the response gets converted to JSON and we cannot read the response # code so easy. - response = self.get_json('/', expect_errors=True) - - self.assertEqual(200, response.status_int) + for route in ('/', '/v1'): + response = self.get_json(route, + path_prefix='', expect_errors=True) + self.assertEqual(200, response.status_int) + + def test_public_api_with_path_extensions(self): + for route in ('/v1/', '/v1.json', '/v1.xml'): + response = self.get_json(route, + path_prefix='', expect_errors=True) + self.assertEqual(200, response.status_int) |