diff options
author | Jenkins <jenkins@review.openstack.org> | 2015-02-10 20:13:25 +0000 |
---|---|---|
committer | Gerrit Code Review <review@openstack.org> | 2015-02-10 20:13:25 +0000 |
commit | b562b04ee5db309268716e0e1b8270f30bdf1a76 (patch) | |
tree | 56264c010cd491a1e56a80616dbdca74544c148b | |
parent | fb306836b411546db385274996a8cdaf6154aaf7 (diff) | |
parent | aebc5df958974d34c49a2a62da7bb124eafca4bd (diff) | |
download | keystonemiddleware-b562b04ee5db309268716e0e1b8270f30bdf1a76.tar.gz |
Merge "Turn our auth plugin into a token interface"
-rw-r--r-- | keystonemiddleware/auth_token.py | 161 | ||||
-rw-r--r-- | keystonemiddleware/tests/test_auth_token_middleware.py | 81 |
2 files changed, 233 insertions, 9 deletions
diff --git a/keystonemiddleware/auth_token.py b/keystonemiddleware/auth_token.py index 03dc5dd..ea3b80e 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): @@ -788,16 +922,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: @@ -816,6 +953,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: @@ -825,6 +965,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 0927abe..fce882a 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. |