Fix revocation event handling with MySQL
When MySQL is used to store revocation events, events are returned
from the database with the timestamps truncated to the second. This
causes a revocation event for a token (which has the issued_at
timestamp to the microsecond) to not match the revocation event and
therefore the token is not considered to be revoked.
The fix is to have the revocation events and token timestamps both
always be truncated to the second. This will cause all tokens for a
user that are issued within a second to be revoked when any of those
tokens are revoked, which shouldn't be a problem.
Conflicts:
keystone/tests/test_v3_os_revoke.py
Change-Id: Ibd82b4ce910206dfd504c396614ae2ebed025e9b
Closes-Bug: #1347961
(cherry picked from commit 7aee6304f6
)
This commit is contained in:
parent
c900a6ef25
commit
6cbf835542
|
@ -69,6 +69,11 @@ class RevokeEvent(object):
|
|||
# This is revoking all tokens for a domain.
|
||||
self.domain_scope_id = None
|
||||
|
||||
if self.expires_at is not None:
|
||||
# Trim off the expiration time because MySQL timestamps are only
|
||||
# accurate to the second.
|
||||
self.expires_at = self.expires_at.replace(microsecond=0)
|
||||
|
||||
if self.revoked_at is None:
|
||||
self.revoked_at = timeutils.utcnow()
|
||||
if self.issued_before is None:
|
||||
|
@ -90,8 +95,7 @@ class RevokeEvent(object):
|
|||
if self.consumer_id is not None:
|
||||
event['OS-OAUTH1:access_token_id'] = self.access_token_id
|
||||
if self.expires_at is not None:
|
||||
event['expires_at'] = timeutils.isotime(self.expires_at,
|
||||
subsecond=True)
|
||||
event['expires_at'] = timeutils.isotime(self.expires_at)
|
||||
if self.issued_before is not None:
|
||||
event['issued_before'] = timeutils.isotime(self.issued_before,
|
||||
subsecond=True)
|
||||
|
@ -242,9 +246,15 @@ class RevokeTree(object):
|
|||
|
||||
def build_token_values_v2(access, default_domain_id):
|
||||
token_data = access['token']
|
||||
|
||||
token_expires_at = timeutils.parse_isotime(token_data['expires'])
|
||||
|
||||
# Trim off the microseconds because the revocation event only has
|
||||
# expirations accurate to the second.
|
||||
token_expires_at = token_expires_at.replace(microsecond=0)
|
||||
|
||||
token_values = {
|
||||
'expires_at': timeutils.normalize_time(
|
||||
timeutils.parse_isotime(token_data['expires'])),
|
||||
'expires_at': timeutils.normalize_time(token_expires_at),
|
||||
'issued_at': timeutils.normalize_time(
|
||||
timeutils.parse_isotime(token_data['issued_at']))}
|
||||
|
||||
|
@ -282,9 +292,15 @@ def build_token_values_v2(access, default_domain_id):
|
|||
|
||||
|
||||
def build_token_values(token_data):
|
||||
|
||||
token_expires_at = timeutils.parse_isotime(token_data['expires_at'])
|
||||
|
||||
# Trim off the microseconds because the revocation event only has
|
||||
# expirations accurate to the second.
|
||||
token_expires_at = token_expires_at.replace(microsecond=0)
|
||||
|
||||
token_values = {
|
||||
'expires_at': timeutils.normalize_time(
|
||||
timeutils.parse_isotime(token_data['expires_at'])),
|
||||
'expires_at': timeutils.normalize_time(token_expires_at),
|
||||
'issued_at': timeutils.normalize_time(
|
||||
timeutils.parse_isotime(token_data['issued_at']))}
|
||||
|
||||
|
|
|
@ -350,7 +350,7 @@ class RevokeTreeTests(tests.TestCase):
|
|||
event = self._revoke_by_expiration(user_id, future_time)
|
||||
token_data_1 = _sample_blank_token()
|
||||
token_data_1['user_id'] = user_id
|
||||
token_data_1['expires_at'] = future_time
|
||||
token_data_1['expires_at'] = future_time.replace(microsecond=0)
|
||||
self._assertTokenRevoked(token_data_1)
|
||||
|
||||
token_data_2 = _sample_blank_token()
|
||||
|
@ -375,7 +375,7 @@ class RevokeTreeTests(tests.TestCase):
|
|||
token_data = _sample_blank_token()
|
||||
token_data['user_id'] = user_id
|
||||
token_data['project_id'] = project_id
|
||||
token_data['expires_at'] = future_time
|
||||
token_data['expires_at'] = future_time.replace(microsecond=0)
|
||||
|
||||
self._revoke_by_expiration(user_id, future_time, project_id=project_id)
|
||||
self._assertTokenRevoked(token_data)
|
||||
|
@ -392,7 +392,7 @@ class RevokeTreeTests(tests.TestCase):
|
|||
token_data = _sample_blank_token()
|
||||
token_data['user_id'] = user_id
|
||||
token_data['assignment_domain_id'] = domain_id
|
||||
token_data['expires_at'] = future_time
|
||||
token_data['expires_at'] = future_time.replace(microsecond=0)
|
||||
|
||||
self._revoke_by_expiration(user_id, future_time, domain_id=domain_id)
|
||||
self._assertTokenRevoked(token_data)
|
||||
|
|
|
@ -1441,6 +1441,11 @@ class TestTokenRevokeApi(TestTokenRevokeById):
|
|||
def assertUserAndExpiryInList(self, events, user_id, expires_at):
|
||||
found = False
|
||||
for e in events:
|
||||
|
||||
# Timestamps in the event list are accurate to second.
|
||||
expires_at = timeutils.parse_isotime(expires_at)
|
||||
expires_at = timeutils.isotime(expires_at)
|
||||
|
||||
if e['user_id'] == user_id and e['expires_at'] == expires_at:
|
||||
found = True
|
||||
self.assertTrue(found,
|
||||
|
@ -1464,14 +1469,9 @@ class TestTokenRevokeApi(TestTokenRevokeById):
|
|||
response.json_body['token']
|
||||
headers3 = {'X-Subject-Token': response.headers['X-Subject-Token']}
|
||||
|
||||
scoped_token = self.get_scoped_token()
|
||||
headers_unrevoked = {'X-Subject-Token': scoped_token}
|
||||
|
||||
self.head('/auth/tokens', headers=headers, expected_status=200)
|
||||
self.head('/auth/tokens', headers=headers2, expected_status=200)
|
||||
self.head('/auth/tokens', headers=headers3, expected_status=200)
|
||||
self.head('/auth/tokens', headers=headers_unrevoked,
|
||||
expected_status=200)
|
||||
|
||||
self.delete('/auth/tokens', headers=headers, expected_status=204)
|
||||
# NOTE(ayoung): not deleting token3, as it should be deleted
|
||||
|
@ -1488,8 +1488,6 @@ class TestTokenRevokeApi(TestTokenRevokeById):
|
|||
self.head('/auth/tokens', headers=headers, expected_status=404)
|
||||
self.head('/auth/tokens', headers=headers2, expected_status=200)
|
||||
self.head('/auth/tokens', headers=headers3, expected_status=200)
|
||||
self.head('/auth/tokens', headers=headers_unrevoked,
|
||||
expected_status=200)
|
||||
|
||||
def test_list_with_filter(self):
|
||||
|
||||
|
|
|
@ -62,8 +62,7 @@ class OSRevokeTests(test_v3.RestfulTestCase):
|
|||
expires_at = token.default_expire_time()
|
||||
sample = self._blank_event()
|
||||
sample['user_id'] = unicode(user_id)
|
||||
sample['expires_at'] = unicode(timeutils.isotime(expires_at,
|
||||
subsecond=True))
|
||||
sample['expires_at'] = unicode(timeutils.isotime(expires_at))
|
||||
before_time = timeutils.utcnow()
|
||||
self.revoke_api.revoke_by_expiration(user_id, expires_at)
|
||||
resp = self.get('/OS-REVOKE/events')
|
||||
|
|
Loading…
Reference in New Issue