Create an Auth Plugin to pass to users

We pass the service catalog and token data through to consumers via
headers, with the auth plugin work we can create clients that are
authenticated with the incoming token and service catalog.

DocImpact: A new environment option is passed out of auth_token
middleware called `token_auth` which is an authentication plugin that
can be used with a session in a service.

Implements: blueprint create-auth-plugin
Change-Id: I549a421fc73f00126f6ac5f4b5911de2e32de825
This commit is contained in:
Jamie Lennox 2014-07-16 10:31:21 +10:00
parent e3a3dff00f
commit d26c81df44
3 changed files with 65 additions and 5 deletions

View File

@ -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),
}

View File

@ -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
@ -453,6 +459,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."""
@ -559,8 +597,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._app(env, start_response)
@ -696,7 +736,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
@ -706,7 +746,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:

View File

@ -29,6 +29,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
@ -1256,6 +1257,25 @@ class CommonAuthTokenMiddlewareTest(object):
# again.
self.assertThat(2, 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):