Fernet 'expires' value loses 'ms' after validation

When we create a Fernet token we get a token with an expiration
time which contains microseconds. When we validate a Fernet token,
the expiration time changes and comes back missing the microseconds.
We need to make the expiration time the same between creation and
validation time, having microseconds in both cases. This applies to
both v2 and v3 API.

Conflicts:
    keystone/tests/unit/test_v3_auth.py
    keystone/tests/unit/token/test_fernet_provider.py
    keystone/token/providers/fernet/core.py
    keystone/token/providers/fernet/token_formatters.py

NOTE: This backport differs from the patch to master to handle the
difference in oslo.timeutils versus the move to keystone's own
implementation of isotime in master, and to move the fix in
keystone/token/providers/fernet/core.py to avoid having to backport a
significant refactor Fernet's implementation.

Closes-Bug: #1459790
Change-Id: Ic6adbc2d69af03519a2c4ef66d558bcff9022049
(cherry picked from commit 9391f9307e)
This commit is contained in:
Roxana Gherle 2015-08-06 09:46:58 -07:00 committed by Dolph Mathews
parent 0ed33e3d25
commit aa3354e73b
4 changed files with 35 additions and 33 deletions

View File

@ -267,8 +267,8 @@ class TokenAPITests(object):
# just need to make sure the non fraction part agrees
self.assertIn(v2_token['access']['token']['expires'][:-1],
token_data['token']['expires_at'])
self.assertEqual(v2_token['access']['user']['roles'][0]['id'],
token_data['token']['roles'][0]['id'])
self.assertEqual(v2_token['access']['user']['roles'][0]['name'],
token_data['token']['roles'][0]['name'])
def test_v2_v3_unscoped_token_intermix(self):
body = {
@ -540,22 +540,6 @@ class TestFernetTokenAPIs(test_v3.RestfulTestCase, TokenAPITests):
super(TestFernetTokenAPIs, self).setUp()
self.doSetUp()
@test_utils.wip('Failing due to bug 1459790.')
def test_v3_v2_token_intermix(self):
super(TestFernetTokenAPIs, self).test_v3_v2_token_intermix()
@test_utils.wip('Failing due to bug 1459790.')
def test_v3_v2_unscoped_token_intermix(self):
super(TestFernetTokenAPIs, self).test_v3_v2_unscoped_token_intermix()
@test_utils.wip('Failing due to bug 1459790.')
def test_v2_v3_token_intermix(self):
super(TestFernetTokenAPIs, self).test_v2_v3_token_intermix()
@test_utils.wip('Failing due to bug 1459790.')
def test_rescoping_token(self):
super(TestFernetTokenAPIs, self).test_rescoping_token()
@test_utils.wip('Failing due to bug 1475762.')
def test_v3_v2_intermix_non_default_project_failed(self):
super(TestFernetTokenAPIs,

View File

@ -75,7 +75,7 @@ class TestPayloads(tests.TestCase):
def test_time_string_to_int_conversions(self):
payload_cls = token_formatters.BasePayload
expected_time_str = timeutils.isotime()
expected_time_str = timeutils.isotime(subsecond=True)
time_obj = timeutils.parse_isotime(expected_time_str)
expected_time_int = (
(timeutils.normalize_time(time_obj) -
@ -92,7 +92,7 @@ class TestPayloads(tests.TestCase):
def test_unscoped_payload(self):
exp_user_id = uuid.uuid4().hex
exp_methods = ['password']
exp_expires_at = timeutils.isotime(timeutils.utcnow())
exp_expires_at = timeutils.isotime(timeutils.utcnow(), subsecond=True)
exp_audit_ids = [provider.random_urlsafe_str()]
payload = token_formatters.UnscopedPayload.assemble(
@ -110,7 +110,7 @@ class TestPayloads(tests.TestCase):
exp_user_id = uuid.uuid4().hex
exp_methods = ['password']
exp_project_id = uuid.uuid4().hex
exp_expires_at = timeutils.isotime(timeutils.utcnow())
exp_expires_at = timeutils.isotime(timeutils.utcnow(), subsecond=True)
exp_audit_ids = [provider.random_urlsafe_str()]
payload = token_formatters.ProjectScopedPayload.assemble(
@ -130,7 +130,7 @@ class TestPayloads(tests.TestCase):
exp_user_id = uuid.uuid4().hex
exp_methods = ['password']
exp_domain_id = uuid.uuid4().hex
exp_expires_at = timeutils.isotime(timeutils.utcnow())
exp_expires_at = timeutils.isotime(timeutils.utcnow(), subsecond=True)
exp_audit_ids = [provider.random_urlsafe_str()]
payload = token_formatters.DomainScopedPayload.assemble(
@ -150,7 +150,7 @@ class TestPayloads(tests.TestCase):
exp_user_id = uuid.uuid4().hex
exp_methods = ['password']
exp_domain_id = CONF.identity.default_domain_id
exp_expires_at = timeutils.isotime(timeutils.utcnow())
exp_expires_at = timeutils.isotime(timeutils.utcnow(), subsecond=True)
exp_audit_ids = [provider.random_urlsafe_str()]
payload = token_formatters.DomainScopedPayload.assemble(
@ -170,7 +170,7 @@ class TestPayloads(tests.TestCase):
exp_user_id = uuid.uuid4().hex
exp_methods = ['password']
exp_project_id = uuid.uuid4().hex
exp_expires_at = timeutils.isotime(timeutils.utcnow())
exp_expires_at = timeutils.isotime(timeutils.utcnow(), subsecond=True)
exp_audit_ids = [provider.random_urlsafe_str()]
exp_trust_id = uuid.uuid4().hex
@ -191,7 +191,7 @@ class TestPayloads(tests.TestCase):
def test_unscoped_payload_with_non_uuid_user_id(self):
exp_user_id = 'someNonUuidUserId'
exp_methods = ['password']
exp_expires_at = timeutils.isotime(timeutils.utcnow())
exp_expires_at = timeutils.isotime(timeutils.utcnow(), subsecond=True)
exp_audit_ids = [provider.random_urlsafe_str()]
payload = token_formatters.UnscopedPayload.assemble(
@ -209,7 +209,7 @@ class TestPayloads(tests.TestCase):
exp_user_id = 'someNonUuidUserId'
exp_methods = ['password']
exp_project_id = uuid.uuid4().hex
exp_expires_at = timeutils.isotime(timeutils.utcnow())
exp_expires_at = timeutils.isotime(timeutils.utcnow(), subsecond=True)
exp_audit_ids = [provider.random_urlsafe_str()]
payload = token_formatters.ProjectScopedPayload.assemble(
@ -229,7 +229,7 @@ class TestPayloads(tests.TestCase):
exp_user_id = uuid.uuid4().hex
exp_methods = ['password']
exp_project_id = 'someNonUuidProjectId'
exp_expires_at = timeutils.isotime(timeutils.utcnow())
exp_expires_at = timeutils.isotime(timeutils.utcnow(), subsecond=True)
exp_audit_ids = [provider.random_urlsafe_str()]
payload = token_formatters.ProjectScopedPayload.assemble(
@ -249,7 +249,7 @@ class TestPayloads(tests.TestCase):
exp_user_id = 'someNonUuidUserId'
exp_methods = ['password']
exp_domain_id = uuid.uuid4().hex
exp_expires_at = timeutils.isotime(timeutils.utcnow())
exp_expires_at = timeutils.isotime(timeutils.utcnow(), subsecond=True)
exp_audit_ids = [provider.random_urlsafe_str()]
payload = token_formatters.DomainScopedPayload.assemble(
@ -269,7 +269,7 @@ class TestPayloads(tests.TestCase):
exp_user_id = 'someNonUuidUserId'
exp_methods = ['password']
exp_project_id = uuid.uuid4().hex
exp_expires_at = timeutils.isotime(timeutils.utcnow())
exp_expires_at = timeutils.isotime(timeutils.utcnow(), subsecond=True)
exp_audit_ids = [provider.random_urlsafe_str()]
exp_trust_id = uuid.uuid4().hex
@ -291,7 +291,7 @@ class TestPayloads(tests.TestCase):
exp_user_id = uuid.uuid4().hex
exp_methods = ['password']
exp_project_id = 'someNonUuidProjectId'
exp_expires_at = timeutils.isotime(timeutils.utcnow())
exp_expires_at = timeutils.isotime(timeutils.utcnow(), subsecond=True)
exp_audit_ids = [provider.random_urlsafe_str()]
exp_trust_id = uuid.uuid4().hex
@ -312,7 +312,7 @@ class TestPayloads(tests.TestCase):
def test_federated_payload_with_non_uuid_ids(self):
exp_user_id = 'someNonUuidUserId'
exp_methods = ['password']
exp_expires_at = timeutils.isotime(timeutils.utcnow())
exp_expires_at = timeutils.isotime(timeutils.utcnow(), subsecond=True)
exp_audit_ids = [provider.random_urlsafe_str()]
exp_federated_info = {'group_ids': [{'id': 'someNonUuidGroupId'}],
'idp_id': uuid.uuid4().hex,

View File

@ -12,6 +12,7 @@
from oslo_config import cfg
from oslo_log import log
from oslo_utils import timeutils
from keystone.common import dependency
from keystone.contrib import federation
@ -87,12 +88,28 @@ class Provider(common.BaseProvider):
audit_ids,
methods=method_names,
project_id=project_id)
self._build_issued_at_info(token_id, v3_token_data)
# Convert v3 to v2 token data and build v2 catalog
token_data = self.v2_token_data_helper.v3_to_v2_token(token_id,
v3_token_data)
return token_id, token_data
def _build_issued_at_info(self, token_id, token_data):
# NOTE(roxanaghe, lbragstad): We must use the creation time that
# Fernet builds into it's token. The Fernet spec details that the
# token creation time is built into the token, outside of the payload
# provided by Keystone. This is the reason why we don't pass the
# issued_at time in the payload. This also means that we shouldn't
# return a token reference with a creation time that we created
# when Fernet uses a different creation time. We should use the
# creation time provided by Fernet because it's the creation time
# that we have to rely on when we validate the token.
fernet_creation_datetime_obj = self.token_formatter.creation_time(
token_id)
token_data['token']['issued_at'] = timeutils.isotime(
at=fernet_creation_datetime_obj, subsecond=True)
def _build_federated_info(self, token_data):
"""Extract everything needed for federated tokens.
@ -195,6 +212,7 @@ class Provider(common.BaseProvider):
project_id=project_id,
trust_id=token_data['token'].get('OS-TRUST:trust', {}).get('id'),
federated_info=federated_dict)
self._build_issued_at_info(token, token_data)
return token, token_data
def validate_v2_token(self, token_ref):

View File

@ -274,8 +274,8 @@ class BasePayload(object):
:returns: a time formatted strings
"""
time_object = datetime.datetime.utcfromtimestamp(int(time_int))
return timeutils.isotime(time_object)
time_object = datetime.datetime.utcfromtimestamp(time_int)
return timeutils.isotime(time_object, subsecond=True)
@classmethod
def attempt_convert_uuid_hex_to_bytes(cls, value):