Ensure tokens are added to both Trustor and Trustee indexes

Tokens are now added to both the Trustor and Trustee user-token-index
so that bulk token revocations (e.g. password change) of the trustee
will work as expected. This is a backport of the basic code that was
used in the Icehouse-vintage Dogpile Token KVS backend that resolves
this issue by merging the handling of memcache and KVS backends into
the same logic.

Change-Id: I3e19e4a8fc1e11cef6db51d364e80061e97befa7
Closes-Bug: #1260080
This commit is contained in:
Morgan Fainberg 2014-02-21 13:33:25 -08:00
parent c22f2edeb7
commit b6f0e26da0
5 changed files with 77 additions and 7 deletions

View File

@ -25,6 +25,7 @@ from keystone import exception
from keystone.openstack.common import timeutils from keystone.openstack.common import timeutils
from keystone import tests from keystone import tests
from keystone.tests import default_fixtures from keystone.tests import default_fixtures
from keystone.token import provider
CONF = config.CONF CONF = config.CONF
@ -2645,7 +2646,8 @@ class TokenTests(object):
self.token_api.delete_token, token_id) self.token_api.delete_token, token_id)
def create_token_sample_data(self, tenant_id=None, trust_id=None, def create_token_sample_data(self, tenant_id=None, trust_id=None,
user_id="testuserid"): user_id='testuserid',
trustee_user_id='testuserid2'):
token_id = self._create_token_id() token_id = self._create_token_id()
data = {'id': token_id, 'a': 'b', data = {'id': token_id, 'a': 'b',
'user': {'id': user_id}} 'user': {'id': user_id}}
@ -2655,6 +2657,15 @@ class TokenTests(object):
data['tenant'] = None data['tenant'] = None
if trust_id is not None: if trust_id is not None:
data['trust_id'] = trust_id data['trust_id'] = trust_id
data.setdefault('access', {}).setdefault('trust', {})
# Testuserid2 is used here since a trustee will be different in
# the cases of impersonation and therefore should not match the
# token's user_id.
data['access']['trust']['trustee_user_id'] = trustee_user_id
data['token_version'] = provider.V2
# Issue token stores a copy of all token data at token['token_data'].
# This emulates that assumption as part of the test.
data['token_data'] = copy.deepcopy(data)
new_token = self.token_api.create_token(token_id, data) new_token = self.token_api.create_token(token_id, data)
return new_token['id'] return new_token['id']
@ -2907,6 +2918,39 @@ class TokenTests(object):
for t in self.token_api.list_revoked_tokens(): for t in self.token_api.list_revoked_tokens():
self.assertIn('expires', t) self.assertIn('expires', t)
def test_token_in_trustee_and_trustor_token_list(self):
self.opt_in_group('trust',
enabled=True)
trustor = self.user_foo
trustee = self.user_two
trust_id = uuid.uuid4().hex
trust_info = {'trustor_user_id': trustor['id'],
'trustee_user_id': trustee['id'],
'project_id': self.tenant_bar['id'],
'expires_at': timeutils.
parse_isotime('2031-02-18T18:10:00Z'),
'impersonation': True}
self.trust_api.create_trust(trust_id, trust_info,
roles=[{'id': 'member'},
{'id': 'other'},
{'id': 'browser'}])
token_id = self.create_token_sample_data(
tenant_id=self.tenant_bar['id'],
trust_id=trust_id,
user_id=trustor['id'],
trustee_user_id=trustee['id'])
# Ensure the token id exists in both the trustor and trustee token
# lists
self.assertIn(token_id,
self.token_api.list_tokens(self.user_two['id'],
trust_id=trust_id))
self.assertIn(token_id,
self.token_api.list_tokens(self.user_foo['id'],
trust_id=trust_id))
class TokenCacheInvalidation(object): class TokenCacheInvalidation(object):
def _create_test_data(self): def _create_test_data(self):

View File

@ -70,6 +70,7 @@ class KvsToken(tests.TestCase, test_backend.TokenTests):
identity.CONF.identity.driver = ( identity.CONF.identity.driver = (
'keystone.identity.backends.kvs.Identity') 'keystone.identity.backends.kvs.Identity')
self.load_backends() self.load_backends()
self.load_fixtures(default_fixtures)
class KvsTrust(tests.TestCase, test_backend.TrustTests): class KvsTrust(tests.TestCase, test_backend.TrustTests):

View File

@ -26,6 +26,7 @@ from keystone import exception
from keystone.openstack.common import jsonutils from keystone.openstack.common import jsonutils
from keystone.openstack.common import timeutils from keystone.openstack.common import timeutils
from keystone import tests from keystone import tests
from keystone.tests import default_fixtures
from keystone.tests import test_backend from keystone.tests import test_backend
from keystone.tests import test_utils from keystone.tests import test_utils
from keystone import token from keystone import token
@ -115,6 +116,7 @@ class MemcacheToken(tests.TestCase, test_backend.TokenTests):
def setUp(self): def setUp(self):
super(MemcacheToken, self).setUp() super(MemcacheToken, self).setUp()
self.load_backends() self.load_backends()
self.load_fixtures(default_fixtures)
fake_client = MemcacheClient() fake_client = MemcacheClient()
self.token_man = token.Manager() self.token_man = token.Manager()
self.token_man.driver = token_memcache.Token(client=fake_client) self.token_man.driver = token_memcache.Token(client=fake_client)

View File

@ -150,5 +150,7 @@ class Token(kvs.Base, token.Driver):
def flush_expired_tokens(self): def flush_expired_tokens(self):
now = timeutils.utcnow() now = timeutils.utcnow()
for token, token_ref in self.db.items(): for token, token_ref in self.db.items():
if not token.startswith('revoked-token-'):
continue
if self.is_expired(now, token_ref): if self.is_expired(now, token_ref):
self.db.delete(token) self.db.delete(token)

View File

@ -83,12 +83,33 @@ class Token(token.Driver):
expires_ts = utils.unixtime(data_copy['expires']) expires_ts = utils.unixtime(data_copy['expires'])
kwargs['time'] = expires_ts kwargs['time'] = expires_ts
self.client.set(ptk, data_copy, **kwargs) self.client.set(ptk, data_copy, **kwargs)
if 'id' in data['user']: user_id = data['user']['id']
user_id = data['user']['id'] user_key = self._prefix_user_id(user_id)
user_key = self._prefix_user_id(user_id) # Append the new token_id to the token-index-list stored in the
# Append the new token_id to the token-index-list stored in the # user-key within memcache.
# user-key within memcache. self._update_user_list_with_cas(user_key, token_id, data_copy)
self._update_user_list_with_cas(user_key, token_id, data_copy) if CONF.trust.enabled and data.get('trust_id'):
# NOTE(morganfainberg): If trusts are enabled and this is a trust
# scoped token, we add the token to the trustee list as well. This
# allows password changes of the trustee to also expire the token.
# There is no harm in placing the token in multiple lists, as
# _list_tokens is smart enough to handle almost any case of
# valid/invalid/expired for a given token.
token_data = data_copy['token_data']
if data_copy['token_version'] == token.provider.V2:
trustee_user_id = token_data['access']['trust'][
'trustee_user_id']
elif data_copy['token_version'] == token.provider.V3:
trustee_user_id = token_data['OS-TRUST:trust'][
'trustee_user_id']
else:
raise token.provider.UnsupportedTokenVersionException(
_('Unknown token version %s') %
data_copy.get('token_version'))
trustee_key = self._prefix_user_id(trustee_user_id)
self._update_user_list_with_cas(trustee_key, token_id, data_copy)
return copy.deepcopy(data_copy) return copy.deepcopy(data_copy)
def _convert_user_index_from_json(self, token_list, user_key): def _convert_user_index_from_json(self, token_list, user_key):