diff --git a/functionaltests/run_tests.sh b/functionaltests/run_tests.sh index 49b31e08..d719cad2 100755 --- a/functionaltests/run_tests.sh +++ b/functionaltests/run_tests.sh @@ -15,7 +15,7 @@ # How many seconds to wait for the API to be responding before giving up API_RESPONDING_TIMEOUT=20 -if ! timeout ${API_RESPONDING_TIMEOUT} sh -c "while ! curl -s http://127.0.0.1:8989/v2/ 2>/dev/null | grep -q 'Authentication required' ; do sleep 1; done"; then +if ! timeout ${API_RESPONDING_TIMEOUT} sh -c "until curl --output /dev/null --silent --head --fail http://localhost:8989; do sleep 1; done"; then echo "Mistral API failed to respond within ${API_RESPONDING_TIMEOUT} seconds" exit 1 fi diff --git a/mistral/api/access_control.py b/mistral/api/access_control.py index 6a990cbc..a0108594 100644 --- a/mistral/api/access_control.py +++ b/mistral/api/access_control.py @@ -25,7 +25,12 @@ _ENFORCER = None def setup(app): if cfg.CONF.pecan.auth_enable: - return auth_token.AuthProtocol(app, dict(cfg.CONF.keystone_authtoken)) + conf = dict(cfg.CONF.keystone_authtoken) + + # Change auth decisions of requests to the app itself. + conf.update({'delay_auth_decision': True}) + + return auth_token.AuthProtocol(app, conf) else: return app diff --git a/mistral/api/app.py b/mistral/api/app.py index bc88a3ca..181f0b6d 100644 --- a/mistral/api/app.py +++ b/mistral/api/app.py @@ -51,7 +51,7 @@ def setup_app(config=None): app = pecan.make_app( app_conf.pop('root'), - hooks=lambda: [ctx.ContextHook()], + hooks=lambda: [ctx.ContextHook(), ctx.AuthHook()], logging=getattr(config, 'logging', {}), **app_conf ) diff --git a/mistral/context.py b/mistral/context.py index 57b6001b..455e0b34 100644 --- a/mistral/context.py +++ b/mistral/context.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # # Copyright 2013 - Mirantis, Inc. # @@ -19,6 +18,7 @@ from keystoneclient.v3 import client as keystone_client from oslo_config import cfg import oslo_messaging as messaging from oslo_serialization import jsonutils +import pecan from pecan import hooks from mistral import exceptions as exc @@ -28,6 +28,7 @@ from mistral import utils CONF = cfg.CONF _CTX_THREAD_LOCAL_NAME = "MISTRAL_APP_CTX_THREAD_LOCAL" +ALLOWED_WITHOUT_AUTH = ['/', '/v2/'] class BaseContext(object): @@ -157,7 +158,6 @@ class JsonPayloadSerializer(messaging.NoOpSerializer): class RpcContextSerializer(messaging.Serializer): - def __init__(self, base=None): self._base = base or messaging.NoOpSerializer() @@ -183,6 +183,33 @@ class RpcContextSerializer(messaging.Serializer): return ctx +class AuthHook(hooks.PecanHook): + def before(self, state): + if state.request.path in ALLOWED_WITHOUT_AUTH: + return + + if CONF.pecan.auth_enable: + # Note(nmakhotkin): Since we have deferred authentication, + # need to check for auth manually (check for corresponding + # headers according to keystonemiddleware docs. + identity_status = state.request.headers.get('X-Identity-Status') + service_identity_status = state.request.headers.get( + 'X-Service-Identity-Status' + ) + + if (identity_status == 'Confirmed' + or service_identity_status == 'Confirmed'): + return + + if state.request.headers.get('X-Auth-Token'): + msg = ("Auth token is invalid: %s" + % state.request.headers['X-Auth-Token']) + else: + msg = 'Authentication required' + + pecan.abort(status_code=401, detail=msg) + + class ContextHook(hooks.PecanHook): def before(self, state): set_ctx(context_from_headers(state.request.headers)) diff --git a/mistral/tests/unit/api/v2/test_root.py b/mistral/tests/unit/api/v2/test_root.py index a6875fc2..1ab683a4 100644 --- a/mistral/tests/unit/api/v2/test_root.py +++ b/mistral/tests/unit/api/v2/test_root.py @@ -16,6 +16,7 @@ from oslo_serialization import jsonutils from mistral.tests.unit.api import base +from mistral.tests.unit.api import test_auth class TestRootController(base.FunctionalTest): @@ -32,3 +33,43 @@ class TestRootController(base.FunctionalTest): data[0]['link'], {'href': 'http://localhost/v2', 'target': 'v2'} ) + + def test_v2_root(self): + resp = self.app.get('/v2/', headers={'Accept': 'application/json'}) + + self.assertEqual(resp.status_int, 200) + + data = jsonutils.loads(resp.body.decode()) + + self.assertEqual( + 'http://localhost/v2', + data['uri'] + ) + + +class TestRootControllerWithAuth(test_auth.TestKeystoneMiddleware): + def test_index(self): + resp = self.app.get('/', headers={'Accept': 'application/json'}) + + self.assertEqual(resp.status_int, 200) + + data = jsonutils.loads(resp.body.decode()) + + self.assertEqual(data[0]['id'], 'v2.0') + self.assertEqual(data[0]['status'], 'CURRENT') + self.assertEqual( + data[0]['link'], + {'href': 'http://localhost/v2', 'target': 'v2'} + ) + + def test_v2_root(self): + resp = self.app.get('/v2/', headers={'Accept': 'application/json'}) + + self.assertEqual(resp.status_int, 200) + + data = jsonutils.loads(resp.body.decode()) + + self.assertEqual( + 'http://localhost/v2', + data['uri'] + )