summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLance Bragstad <lbragstad@gmail.com>2016-09-09 22:10:02 +0000
committerLance Bragstad <lbragstad@gmail.com>2016-09-12 16:16:29 +0000
commit301b6a7bc770830485937f0b9927a26e2e5ec8c8 (patch)
tree537ca831d3461acb1340a8b3e16114697cbb17a5
parentd1a761293a4adcc30d6d17d1d38e427f6ed02246 (diff)
downloadkeystone-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.py2
-rw-r--r--keystone/models/revoke_model.py2
-rw-r--r--keystone/tests/unit/test_v3_auth.py102
-rw-r--r--keystone/tests/unit/test_v3_os_revoke.py6
-rw-r--r--keystone/token/provider.py3
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):