diff options
author | Lance Bragstad <lbragstad@gmail.com> | 2016-09-09 22:10:02 +0000 |
---|---|---|
committer | Lance Bragstad <lbragstad@gmail.com> | 2016-09-12 16:16:29 +0000 |
commit | 301b6a7bc770830485937f0b9927a26e2e5ec8c8 (patch) | |
tree | 537ca831d3461acb1340a8b3e16114697cbb17a5 | |
parent | d1a761293a4adcc30d6d17d1d38e427f6ed02246 (diff) | |
download | keystone-301b6a7bc770830485937f0b9927a26e2e5ec8c8.tar.gz |
Consistently round down timestamps
This is one of the ways we can prevent race conditions with backends that round
datetime objects or strings before persisting them.
Change-Id: Iaee0ec8c7acd512b9d93096ce8306a2952061c7a
Closes-Bug: 1622010
-rw-r--r-- | keystone/common/utils.py | 2 | ||||
-rw-r--r-- | keystone/models/revoke_model.py | 2 | ||||
-rw-r--r-- | keystone/tests/unit/test_v3_auth.py | 102 | ||||
-rw-r--r-- | keystone/tests/unit/test_v3_os_revoke.py | 6 | ||||
-rw-r--r-- | keystone/token/provider.py | 3 |
5 files changed, 64 insertions, 51 deletions
diff --git a/keystone/common/utils.py b/keystone/common/utils.py index 9ef659f27..31ab5b4bc 100644 --- a/keystone/common/utils.py +++ b/keystone/common/utils.py @@ -527,7 +527,7 @@ def isotime(at=None, subsecond=False): # parse correctly most of the time. if not at: - at = timeutils.utcnow() + at = timeutils.utcnow().replace(microsecond=0) st = at.strftime(_ISO8601_TIME_FORMAT if not subsecond else _ISO8601_TIME_FORMAT_SUBSECOND) diff --git a/keystone/models/revoke_model.py b/keystone/models/revoke_model.py index 9ee5a6c9d..4efe0b36d 100644 --- a/keystone/models/revoke_model.py +++ b/keystone/models/revoke_model.py @@ -94,7 +94,7 @@ class RevokeEvent(object): self.expires_at = self.expires_at.replace(microsecond=0) if self.revoked_at is None: - self.revoked_at = timeutils.utcnow() + self.revoked_at = timeutils.utcnow().replace(microsecond=0) if self.issued_before is None: self.issued_before = self.revoked_at diff --git a/keystone/tests/unit/test_v3_auth.py b/keystone/tests/unit/test_v3_auth.py index a39ac6c79..8c1858d68 100644 --- a/keystone/tests/unit/test_v3_auth.py +++ b/keystone/tests/unit/test_v3_auth.py @@ -19,6 +19,7 @@ import operator import re import uuid +import freezegun from keystoneclient.common import cms import mock from oslo_log import versionutils @@ -3161,51 +3162,62 @@ class TestTokenRevokeById(test_v3.RestfulTestCase): def test_removing_role_assignment_does_not_affect_other_users(self): """Revoking a role from one user should not affect other users.""" - # This group grant is not needed for the test - self.delete( - '/projects/%(project_id)s/groups/%(group_id)s/roles/%(role_id)s' % - {'project_id': self.projectA['id'], - 'group_id': self.group1['id'], - 'role_id': self.role1['id']}) - - user1_token = self.get_requested_token( - self.build_authentication_request( - user_id=self.user1['id'], - password=self.user1['password'], - project_id=self.projectA['id'])) - - user3_token = self.get_requested_token( - self.build_authentication_request( - user_id=self.user3['id'], - password=self.user3['password'], - project_id=self.projectA['id'])) - - # delete relationships between user1 and projectA from setUp - self.delete( - '/projects/%(project_id)s/users/%(user_id)s/roles/%(role_id)s' % { - 'project_id': self.projectA['id'], - 'user_id': self.user1['id'], - 'role_id': self.role1['id']}) - # authorization for the first user should now fail - self.head('/auth/tokens', - headers={'X-Subject-Token': user1_token}, - expected_status=http_client.NOT_FOUND) - self.v3_create_token( - self.build_authentication_request( - user_id=self.user1['id'], - password=self.user1['password'], - project_id=self.projectA['id']), - expected_status=http_client.UNAUTHORIZED) - - # authorization for the second user should still succeed - self.head('/auth/tokens', - headers={'X-Subject-Token': user3_token}, - expected_status=http_client.OK) - self.v3_create_token( - self.build_authentication_request( - user_id=self.user3['id'], - password=self.user3['password'], - project_id=self.projectA['id'])) + time = datetime.datetime.utcnow() + with freezegun.freeze_time(time) as frozen_datetime: + # This group grant is not needed for the test + self.delete( + '/projects/%(p_id)s/groups/%(g_id)s/roles/%(r_id)s' % + {'p_id': self.projectA['id'], + 'g_id': self.group1['id'], + 'r_id': self.role1['id']}) + + # NOTE(lbragstad): Here we advance the clock one second to pass + # into the threshold of a new second because we just presisted a + # revocation event for removing a role from a group on a project. + # One thing to note about that revocation event is that it has no + # context about the group, so even though user3 might not be in + # group1, they could have their token revoked because the + # revocation event is very general. + frozen_datetime.tick(delta=datetime.timedelta(seconds=1)) + + user1_token = self.get_requested_token( + self.build_authentication_request( + user_id=self.user1['id'], + password=self.user1['password'], + project_id=self.projectA['id'])) + + user3_token = self.get_requested_token( + self.build_authentication_request( + user_id=self.user3['id'], + password=self.user3['password'], + project_id=self.projectA['id'])) + + # delete relationships between user1 and projectA from setUp + self.delete( + '/projects/%(p_id)s/users/%(u_id)s/roles/%(r_id)s' % { + 'p_id': self.projectA['id'], + 'u_id': self.user1['id'], + 'r_id': self.role1['id']}) + # authorization for the first user should now fail + self.head('/auth/tokens', + headers={'X-Subject-Token': user1_token}, + expected_status=http_client.NOT_FOUND) + self.v3_create_token( + self.build_authentication_request( + user_id=self.user1['id'], + password=self.user1['password'], + project_id=self.projectA['id']), + expected_status=http_client.UNAUTHORIZED) + + # authorization for the second user should still succeed + self.head('/auth/tokens', + headers={'X-Subject-Token': user3_token}, + expected_status=http_client.OK) + self.v3_create_token( + self.build_authentication_request( + user_id=self.user3['id'], + password=self.user3['password'], + project_id=self.projectA['id'])) def test_deleting_project_deletes_grants(self): # This is to make it a little bit more pretty with PEP8 diff --git a/keystone/tests/unit/test_v3_os_revoke.py b/keystone/tests/unit/test_v3_os_revoke.py index ff2c8f741..962ba6a61 100644 --- a/keystone/tests/unit/test_v3_os_revoke.py +++ b/keystone/tests/unit/test_v3_os_revoke.py @@ -78,7 +78,7 @@ class OSRevokeTests(test_v3.RestfulTestCase, test_v3.JsonHomeTestMixin): audit_id = uuid.uuid4().hex sample = self._blank_event() sample['audit_id'] = six.text_type(audit_id) - before_time = timeutils.utcnow() + before_time = timeutils.utcnow().replace(microsecond=0) self.revoke_api.revoke_by_audit_id(audit_id) resp = self.get('/OS-REVOKE/events') events = resp.json_body['events'] @@ -89,7 +89,7 @@ class OSRevokeTests(test_v3.RestfulTestCase, test_v3.JsonHomeTestMixin): project_id = uuid.uuid4().hex sample = dict() sample['project_id'] = six.text_type(project_id) - before_time = timeutils.utcnow() + before_time = timeutils.utcnow().replace(microsecond=0) self.revoke_api.revoke( revoke_model.RevokeEvent(project_id=project_id)) @@ -102,7 +102,7 @@ class OSRevokeTests(test_v3.RestfulTestCase, test_v3.JsonHomeTestMixin): domain_id = uuid.uuid4().hex sample = dict() sample['domain_id'] = six.text_type(domain_id) - before_time = timeutils.utcnow() + before_time = timeutils.utcnow().replace(microsecond=0) self.revoke_api.revoke( revoke_model.RevokeEvent(domain_id=domain_id)) diff --git a/keystone/token/provider.py b/keystone/token/provider.py index 8e3d06731..3d3b38be1 100644 --- a/keystone/token/provider.py +++ b/keystone/token/provider.py @@ -101,7 +101,8 @@ def default_expire_time(): """ expire_delta = datetime.timedelta(seconds=CONF.token.expiration) - return timeutils.utcnow() + expire_delta + expires_at = timeutils.utcnow() + expire_delta + return expires_at.replace(microsecond=0) def audit_info(parent_audit_id): |