Skip to content

Commit

Permalink
Ensure tokens are added to both Trustor and Trustee indexes
Browse files Browse the repository at this point in the history
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
  • Loading branch information
Morgan Fainberg committed Feb 21, 2014
1 parent c22f2ed commit b6f0e26
Show file tree
Hide file tree
Showing 5 changed files with 77 additions and 7 deletions.
46 changes: 45 additions & 1 deletion keystone/tests/test_backend.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
from keystone.openstack.common import timeutils
from keystone import tests
from keystone.tests import default_fixtures
from keystone.token import provider


CONF = config.CONF
Expand Down Expand Up @@ -2645,7 +2646,8 @@ def test_token_crud(self):
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}}
Expand All @@ -2655,6 +2657,15 @@ def create_token_sample_data(self, tenant_id=None, trust_id=None,
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']

Expand Down Expand Up @@ -2907,6 +2918,39 @@ def test_predictable_revoked_uuid_token_id(self):
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):
Expand Down
1 change: 1 addition & 0 deletions keystone/tests/test_backend_kvs.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ def setUp(self):
identity.CONF.identity.driver = (
'keystone.identity.backends.kvs.Identity')
self.load_backends()
self.load_fixtures(default_fixtures)


class KvsTrust(tests.TestCase, test_backend.TrustTests):
Expand Down
2 changes: 2 additions & 0 deletions keystone/tests/test_backend_memcache.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
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
Expand Down Expand Up @@ -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)
Expand Down
2 changes: 2 additions & 0 deletions keystone/token/backends/kvs.py
Original file line number Diff line number Diff line change
Expand Up @@ -150,5 +150,7 @@ def list_revoked_tokens(self):
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)
33 changes: 27 additions & 6 deletions keystone/token/backends/memcache.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,12 +83,33 @@ def create_token(self, token_id, data):
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)
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):
Expand Down

0 comments on commit b6f0e26

Please sign in to comment.