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 import tests
from keystone.tests import default_fixtures
from keystone.token import provider
CONF = config.CONF
@ -2645,7 +2646,8 @@ class TokenTests(object):
self.token_api.delete_token, token_id)
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()
data = {'id': token_id, 'a': 'b',
'user': {'id': user_id}}
@ -2655,6 +2657,15 @@ class TokenTests(object):
data['tenant'] = None
if trust_id is not None:
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)
return new_token['id']
@ -2907,6 +2918,39 @@ class TokenTests(object):
for t in self.token_api.list_revoked_tokens():
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):
def _create_test_data(self):

View File

@ -70,6 +70,7 @@ class KvsToken(tests.TestCase, test_backend.TokenTests):
identity.CONF.identity.driver = (
'keystone.identity.backends.kvs.Identity')
self.load_backends()
self.load_fixtures(default_fixtures)
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 timeutils
from keystone import tests
from keystone.tests import default_fixtures
from keystone.tests import test_backend
from keystone.tests import test_utils
from keystone import token
@ -115,6 +116,7 @@ class MemcacheToken(tests.TestCase, test_backend.TokenTests):
def setUp(self):
super(MemcacheToken, self).setUp()
self.load_backends()
self.load_fixtures(default_fixtures)
fake_client = MemcacheClient()
self.token_man = token.Manager()
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):
now = timeutils.utcnow()
for token, token_ref in self.db.items():
if not token.startswith('revoked-token-'):
continue
if self.is_expired(now, token_ref):
self.db.delete(token)

View File

@ -83,12 +83,33 @@ class Token(token.Driver):
expires_ts = utils.unixtime(data_copy['expires'])
kwargs['time'] = expires_ts
self.client.set(ptk, data_copy, **kwargs)
if 'id' in data['user']:
user_id = data['user']['id']
user_key = self._prefix_user_id(user_id)
# Append the new token_id to the token-index-list stored in the
# user-key within memcache.
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)
def _convert_user_index_from_json(self, token_list, user_key):