diff --git a/doc/source/conf.py b/doc/source/conf.py index 19c81d6d..069382be 100644 --- a/doc/source/conf.py +++ b/doc/source/conf.py @@ -231,6 +231,7 @@ latex_documents = [ # If false, no module index is generated. #latex_use_modindex = True +keystoneclient = 'http://docs.openstack.org/developer/python-keystoneclient/' -# Example configuration for intersphinx: refer to the Python standard library. -#intersphinx_mapping = {'python': ('http://docs.python.org/', None)} +intersphinx_mapping = {'keystoneclient': (keystoneclient, None), + } diff --git a/keystonemiddleware/auth_token.py b/keystonemiddleware/auth_token.py index c4463452..328172e3 100644 --- a/keystonemiddleware/auth_token.py +++ b/keystonemiddleware/auth_token.py @@ -143,6 +143,11 @@ keystone.token_info Keystone token validation call, as well as basic information about the tenant and user. +keystone.token_auth + A keystoneclient auth plugin that may be used with a + :py:class:`keystoneclient.session.Session`. This plugin will load the + authentication data provided to auth_token middleware. + """ import contextlib @@ -154,6 +159,7 @@ import tempfile import time from keystoneclient import access +from keystoneclient.auth.identity import base as base_identity from keystoneclient.auth.identity import v2 from keystoneclient.auth import token_endpoint from keystoneclient.common import cms @@ -479,6 +485,38 @@ class _MiniResp(object): self.headers.append(('Content-type', 'text/plain')) +class _UserAuthPlugin(base_identity.BaseIdentityPlugin): + """The incoming authentication credentials. + + A plugin that represents the incoming user credentials. This can be + consumed by applications. + + This object is not expected to be constructed directly by users. It is + created and passed by auth_token middleware and then can be used as the + authentication plugin when communicating via a session. + """ + + def __init__(self, user_token, auth_ref): + # FIXME(jamielennox): set reauthenticate=False here when keystoneclient + # 0.11 is released to prevent trying to refetch authentication. + super(_UserAuthPlugin, self).__init__() + self._user_token = user_token + self._stored_auth_ref = auth_ref + + def get_token(self, session, **kwargs): + # NOTE(jamielennox): This is needed partially because the AccessInfo + # factory is so bad that we don't always get the correct token data. + # Override and always return the token that was provided in the req. + return self._user_token + + def get_auth_ref(self, session, **kwargs): + # NOTE(jamielennox): We can't go out and fetch this auth_ref, we've + # got it already so always return it. In the event it tries to + # re-authenticate it will get the same old auth_ref which is not + # perfect, but the best we can do for now. + return self._stored_auth_ref + + class AuthProtocol(object): """Auth Middleware that handles authenticating client calls.""" @@ -600,8 +638,10 @@ class AuthProtocol(object): self._remove_auth_headers(env) user_token = self._get_user_token_from_header(env) token_info = self._validate_user_token(user_token, env) + auth_ref = access.AccessInfo.factory(body=token_info) env['keystone.token_info'] = token_info - user_headers = self._build_user_headers(token_info) + env['keystone.token_auth'] = _UserAuthPlugin(user_token, auth_ref) + user_headers = self._build_user_headers(auth_ref, token_info) self._add_headers(env, user_headers) return self._call_app(env, start_response) @@ -755,7 +795,7 @@ class AuthProtocol(object): self._LOG.warn('Authorization failed for token') raise InvalidUserToken('Token authorization failed') - def _build_user_headers(self, token_info): + def _build_user_headers(self, auth_ref, token_info): """Convert token object into headers. Build headers that represent authenticated user - see main @@ -765,7 +805,6 @@ class AuthProtocol(object): :raise InvalidUserToken when unable to parse token object """ - auth_ref = access.AccessInfo.factory(body=token_info) roles = ','.join(auth_ref.role_names) if _token_is_v2(token_info) and not auth_ref.project_id: diff --git a/keystonemiddleware/tests/test_auth_token_middleware.py b/keystonemiddleware/tests/test_auth_token_middleware.py index 29f199ed..bd3bf8bc 100644 --- a/keystonemiddleware/tests/test_auth_token_middleware.py +++ b/keystonemiddleware/tests/test_auth_token_middleware.py @@ -30,6 +30,7 @@ from keystoneclient import access from keystoneclient.common import cms from keystoneclient import exceptions from keystoneclient import fixture +from keystoneclient import session import mock import testresources import testtools @@ -1296,6 +1297,25 @@ class CommonAuthTokenMiddlewareTest(object): # Assert that the token wasn't cached again. self.assertThat(1, matchers.Equals(cache.set.call_count)) + def test_auth_plugin(self): + httpretty.register_uri(httpretty.GET, + self.examples.SERVICE_URL, + body=VERSION_LIST_v3, + status_code=300) + + req = webob.Request.blank('/') + req.headers['X-Auth-Token'] = self.token_dict['uuid_token_default'] + body = self.middleware(req.environ, self.start_fake_response) + self.assertEqual(200, self.response_status) + self.assertEqual([FakeApp.SUCCESS], body) + + token_auth = req.environ['keystone.token_auth'] + endpoint_filter = {'service_type': self.examples.SERVICE_TYPE, + 'version': 3} + + url = token_auth.get_endpoint(session.Session(), **endpoint_filter) + self.assertEqual('%s/v3' % BASE_URI, url) + class V2CertDownloadMiddlewareTest(BaseAuthTokenMiddlewareTest, testresources.ResourcedTestCase):