summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJamie Lennox <jamielennox@redhat.com>2014-11-26 14:01:58 +1000
committerJamie Lennox <jamielennox@redhat.com>2015-02-10 15:14:52 +1100
commitaebc5df958974d34c49a2a62da7bb124eafca4bd (patch)
tree9e00c0af97f7e2116f6a2504aa4c983684be4dc3
parent1f670d13d2980202cb7a5b9092bc94019ef6d113 (diff)
downloadkeystonemiddleware-aebc5df958974d34c49a2a62da7bb124eafca4bd.tar.gz
Turn our auth plugin into a token interface
This auth plugin is already going to be distributed to everyone so that they can use it to talk to use the current authentication to talk to other services. Given that it knows everything that auth_token middleware knows it may as well be the base for generating policy contexts. Implements: bp auth-context-object Change-Id: I15e3517e854ef110987e392510c6de0f14645a59
-rw-r--r--keystonemiddleware/auth_token.py161
-rw-r--r--keystonemiddleware/tests/test_auth_token_middleware.py81
2 files changed, 233 insertions, 9 deletions
diff --git a/keystonemiddleware/auth_token.py b/keystonemiddleware/auth_token.py
index 6535002..d4880d3 100644
--- a/keystonemiddleware/auth_token.py
+++ b/keystonemiddleware/auth_token.py
@@ -667,6 +667,99 @@ class _AuthTokenPlugin(auth.BaseAuthPlugin):
_AuthTokenPlugin.register_conf_options(CONF, _AUTHTOKEN_GROUP)
+class _TokenData(object):
+ """An abstraction to show auth_token consumers some of the token contents.
+
+ This is a simplified and cleaned up keystoneclient.access.AccessInfo object
+ with which services relying on auth_token middleware can find details of
+ the current token.
+ """
+
+ def __init__(self, auth_ref):
+ self._stored_auth_ref = auth_ref
+
+ @property
+ def _is_v2(self):
+ return self._stored_auth_ref.version == 'v2.0'
+
+ @property
+ def auth_token(self):
+ """The token data used to authenticate requests.
+
+ :returns: token data.
+ :rtype: str
+ """
+ return self._stored_auth_ref.auth_token
+
+ @property
+ def user_id(self):
+ """The user id associated with the authentication request.
+
+ :rtype: str
+ """
+ return self._stored_auth_ref.user_id
+
+ @property
+ def user_domain_id(self):
+ """Returns the domain id of the user associated with the authentication
+ request.
+
+ :returns: str
+ """
+ # NOTE(jamielennox): v2 AccessInfo returns 'default' for domain_id
+ # because it can't know that value. We want to return None instead.
+ if self._is_v2:
+ return None
+
+ return self._stored_auth_ref.user_domain_id
+
+ @property
+ def project_id(self):
+ """The project ID associated with the authentication.
+
+ :rtype: str
+ """
+ return self._stored_auth_ref.project_id
+
+ @property
+ def project_domain_id(self):
+ """The domain id of the project associated with the authentication
+ request.
+
+ :rtype: str
+ """
+ # NOTE(jamielennox): v2 AccessInfo returns 'default' for domain_id
+ # because it can't know that value. We want to return None instead.
+ if self._is_v2:
+ return None
+
+ return self._stored_auth_ref.project_domain_id
+
+ @property
+ def trust_id(self):
+ """Returns the trust id associated with the authentication request..
+
+ :rtype: str
+ """
+ return self._stored_auth_ref.trust_id
+
+ @property
+ def role_ids(self):
+ """Role ids of the user associated with the authentication request.
+
+ :rtype: set(str)
+ """
+ return frozenset(self._stored_auth_ref.role_ids or [])
+
+ @property
+ def role_names(self):
+ """Role names of the user associated with the authentication request.
+
+ :rtype: set(str)
+ """
+ return frozenset(self._stored_auth_ref.role_names or [])
+
+
class _UserAuthPlugin(base_identity.BaseIdentityPlugin):
"""The incoming authentication credentials.
@@ -678,15 +771,56 @@ class _UserAuthPlugin(base_identity.BaseIdentityPlugin):
authentication plugin when communicating via a session.
"""
- def __init__(self, auth_ref):
+ def __init__(self, user_auth_ref, serv_auth_ref):
super(_UserAuthPlugin, self).__init__(reauthenticate=False)
- self._stored_auth_ref = auth_ref
+ self._user_auth_ref = user_auth_ref
+ self._serv_auth_ref = serv_auth_ref
+ self._user_data = None
+ self._serv_data = None
+
+ @property
+ def has_user_token(self):
+ """Did this authentication request contained a user auth token."""
+ return self._user_auth_ref is not None
+
+ @property
+ def user(self):
+ """Authentication information about the user token.
+
+ Will return None if a user token was not passed with this request.
+ """
+ if not self.has_user_token:
+ return None
+
+ if not self._user_data:
+ self._user_data = _TokenData(self._user_auth_ref)
+
+ return self._user_data
+
+ @property
+ def has_service_token(self):
+ """Did this authentication request contained a service token."""
+ return self._serv_auth_ref is not None
+
+ @property
+ def service(self):
+ """Authentication information about the service token.
+
+ Will return None if a user token was not passed with this request.
+ """
+ if not self.has_service_token:
+ return None
+
+ if not self._serv_data:
+ self._serv_data = _TokenData(self._serv_auth_ref)
+
+ return self._serv_data
def get_auth_ref(self, session, **kwargs):
# NOTE(jamielennox): We will always use the auth_ref that was
# calculated by the middleware. reauthenticate=False in __init__ should
# ensure that this function is only called on the first access.
- return self._stored_auth_ref
+ return self._user_auth_ref
class AuthProtocol(object):
@@ -776,16 +910,19 @@ class AuthProtocol(object):
self._remove_auth_headers(env)
try:
+ user_auth_ref = None
+ serv_auth_ref = None
try:
self._LOG.debug('Authenticating user token')
user_token = self._get_user_token_from_header(env)
- token_info = self._validate_token(user_token, env)
- auth_ref = access.AccessInfo.factory(body=token_info,
- auth_token=user_token)
- env['keystone.token_info'] = token_info
- env['keystone.token_auth'] = _UserAuthPlugin(auth_ref)
- user_headers = self._build_user_headers(auth_ref, token_info)
+ user_token_info = self._validate_token(user_token, env)
+ user_auth_ref = access.AccessInfo.factory(
+ body=user_token_info,
+ auth_token=user_token)
+ env['keystone.token_info'] = user_token_info
+ user_headers = self._build_user_headers(user_auth_ref,
+ user_token_info)
self._add_headers(env, user_headers)
except InvalidToken:
if self._delay_auth_decision:
@@ -804,6 +941,9 @@ class AuthProtocol(object):
if serv_token is not None:
serv_token_info = self._validate_token(
serv_token, env)
+ serv_auth_ref = access.AccessInfo.factory(
+ body=serv_token_info,
+ auth_token=serv_token)
serv_headers = self._build_service_headers(serv_token_info)
self._add_headers(env, serv_headers)
except InvalidToken:
@@ -813,6 +953,9 @@ class AuthProtocol(object):
_LI('Invalid service token - rejecting request'))
return self._reject_request(env, start_response)
+ env['keystone.token_auth'] = _UserAuthPlugin(user_auth_ref,
+ serv_auth_ref)
+
except ServiceError as e:
self._LOG.critical(_LC('Unable to obtain admin token: %s'), e)
return self._do_503_error(env, start_response)
diff --git a/keystonemiddleware/tests/test_auth_token_middleware.py b/keystonemiddleware/tests/test_auth_token_middleware.py
index 67a0456..aae45e8 100644
--- a/keystonemiddleware/tests/test_auth_token_middleware.py
+++ b/keystonemiddleware/tests/test_auth_token_middleware.py
@@ -1373,6 +1373,10 @@ class CommonAuthTokenMiddlewareTest(object):
url = token_auth.get_endpoint(session.Session(), **endpoint_filter)
self.assertEqual('%s/v3' % BASE_URI, url)
+ self.assertTrue(token_auth.has_user_token)
+ self.assertFalse(token_auth.has_service_token)
+ self.assertIsNone(token_auth.service)
+
class V2CertDownloadMiddlewareTest(BaseAuthTokenMiddlewareTest,
testresources.ResourcedTestCase):
@@ -1629,6 +1633,35 @@ class v2AuthTokenMiddlewareTest(BaseAuthTokenMiddlewareTest,
self.assertFalse(req.headers.get('X-Service-Catalog'))
self.assertEqual(body, [FakeApp.SUCCESS])
+ def test_user_plugin_token_properties(self):
+ req = webob.Request.blank('/')
+ req.headers['X-Service-Catalog'] = '[]'
+ token = self.examples.UUID_TOKEN_DEFAULT
+ token_data = self.examples.TOKEN_RESPONSES[token]
+ req.headers['X-Auth-Token'] = token
+ req.headers['X-Service-Token'] = token
+
+ body = self.middleware(req.environ, self.start_fake_response)
+ self.assertEqual(self.response_status, 200)
+ self.assertEqual([FakeApp.SUCCESS], body)
+
+ token_auth = req.environ['keystone.token_auth']
+
+ self.assertTrue(token_auth.has_user_token)
+ self.assertTrue(token_auth.has_service_token)
+
+ for t in [token_auth.user, token_auth.service]:
+ self.assertEqual(token_data.user_id, t.user_id)
+ self.assertEqual(token_data.tenant_id, t.project_id)
+
+ self.assertThat(t.role_names, matchers.HasLength(2))
+ self.assertIn('role1', t.role_names)
+ self.assertIn('role2', t.role_names)
+
+ self.assertIsNone(t.trust_id)
+ self.assertIsNone(t.user_domain_id)
+ self.assertIsNone(t.project_domain_id)
+
class CrossVersionAuthTokenMiddlewareTest(BaseAuthTokenMiddlewareTest,
testresources.ResourcedTestCase):
@@ -1842,6 +1875,35 @@ class v3AuthTokenMiddlewareTest(BaseAuthTokenMiddlewareTest,
self.assert_valid_request_200(
self.token_dict['signed_token_scoped_pkiz'])
+ def test_user_plugin_token_properties(self):
+ req = webob.Request.blank('/')
+ req.headers['X-Service-Catalog'] = '[]'
+ token = self.examples.v3_UUID_TOKEN_DEFAULT
+ token_data = self.examples.TOKEN_RESPONSES[token]
+ req.headers['X-Auth-Token'] = token
+ req.headers['X-Service-Token'] = token
+
+ body = self.middleware(req.environ, self.start_fake_response)
+ self.assertEqual(self.response_status, 200)
+ self.assertEqual([FakeApp.SUCCESS], body)
+
+ token_auth = req.environ['keystone.token_auth']
+
+ self.assertTrue(token_auth.has_user_token)
+ self.assertTrue(token_auth.has_service_token)
+
+ for t in [token_auth.user, token_auth.service]:
+ self.assertEqual(token_data.user_id, t.user_id)
+ self.assertEqual(token_data.project_id, t.project_id)
+ self.assertEqual(token_data.user_domain_id, t.user_domain_id)
+ self.assertEqual(token_data.project_domain_id, t.project_domain_id)
+
+ self.assertThat(t.role_names, matchers.HasLength(2))
+ self.assertIn('role1', t.role_names)
+ self.assertIn('role2', t.role_names)
+
+ self.assertIsNone(t.trust_id)
+
class TokenEncodingTest(testtools.TestCase):
def test_unquoted_token(self):
@@ -2180,6 +2242,25 @@ class DelayedAuthTests(BaseAuthTokenMiddlewareTest):
middleware = auth_token.AuthProtocol(fake_app, conf)
self.assertFalse(middleware._delay_auth_decision)
+ def test_auth_plugin_with_no_tokens(self):
+ body = uuid.uuid4().hex
+ auth_uri = 'http://local.test'
+ conf = {'delay_auth_decision': True, 'auth_uri': auth_uri}
+ self.fake_app = new_app('200 OK', body)
+ self.set_middleware(conf=conf)
+
+ req = webob.Request.blank('/')
+ resp = self.middleware(req.environ, self.start_fake_response)
+
+ self.assertEqual([six.b(body)], resp)
+
+ token_auth = req.environ['keystone.token_auth']
+
+ self.assertFalse(token_auth.has_user_token)
+ self.assertIsNone(token_auth.user)
+ self.assertFalse(token_auth.has_service_token)
+ self.assertIsNone(token_auth.service)
+
class CommonCompositeAuthTests(object):
"""Test Composite authentication.