Fernet payloads for federated scoped tokens.

Federated project and domain scoped tokens need to be able to keep
``OS-FEDERATION`` info within their payloads.

Change-Id: I00bbff8066e3842e5a1db4f63ceb7dd9aec083a3
Closes-Bug: #1471289
This commit is contained in:
Marek Denis 2015-07-15 18:18:36 +02:00
parent 02f8576ddf
commit c5b194300a
3 changed files with 200 additions and 20 deletions

View File

@ -334,6 +334,58 @@ class TestPayloads(tests.TestCase):
self.assertEqual(exp_federated_info['protocol_id'],
federated_info['protocol_id'])
def test_federated_project_scoped_payload(self):
exp_user_id = 'someNonUuidUserId'
exp_methods = ['token']
exp_project_id = uuid.uuid4().hex
exp_expires_at = utils.isotime(timeutils.utcnow(), subsecond=True)
exp_audit_ids = [provider.random_urlsafe_str()]
exp_federated_info = {'group_ids': [{'id': 'someNonUuidGroupId'}],
'idp_id': uuid.uuid4().hex,
'protocol_id': uuid.uuid4().hex}
payload = token_formatters.FederatedProjectScopedPayload.assemble(
exp_user_id, exp_methods, exp_project_id, exp_expires_at,
exp_audit_ids, exp_federated_info)
(user_id, methods, project_id, expires_at, audit_ids,
federated_info) = (
token_formatters.FederatedProjectScopedPayload.disassemble(
payload))
self.assertEqual(exp_user_id, user_id)
self.assertEqual(exp_methods, methods)
self.assertEqual(exp_project_id, project_id)
self.assertEqual(exp_expires_at, expires_at)
self.assertEqual(exp_audit_ids, audit_ids)
self.assertDictEqual(exp_federated_info, federated_info)
def test_federated_domain_scoped_payload(self):
exp_user_id = 'someNonUuidUserId'
exp_methods = ['token']
exp_domain_id = uuid.uuid4().hex
exp_expires_at = utils.isotime(timeutils.utcnow(), subsecond=True)
exp_audit_ids = [provider.random_urlsafe_str()]
exp_federated_info = {'group_ids': [{'id': 'someNonUuidGroupId'}],
'idp_id': uuid.uuid4().hex,
'protocol_id': uuid.uuid4().hex}
payload = token_formatters.FederatedDomainScopedPayload.assemble(
exp_user_id, exp_methods, exp_domain_id, exp_expires_at,
exp_audit_ids, exp_federated_info)
(user_id, methods, domain_id, expires_at, audit_ids,
federated_info) = (
token_formatters.FederatedDomainScopedPayload.disassemble(
payload))
self.assertEqual(exp_user_id, user_id)
self.assertEqual(exp_methods, methods)
self.assertEqual(exp_domain_id, domain_id)
self.assertEqual(exp_expires_at, expires_at)
self.assertEqual(exp_audit_ids, audit_ids)
self.assertDictEqual(exp_federated_info, federated_info)
class TestFernetKeyRotation(tests.TestCase):
def setUp(self):

View File

@ -127,20 +127,22 @@ class Provider(common.BaseProvider):
which unpacks the values and builds the Fernet token.
"""
group_ids = token_data['token'].get('user', {}).get(
federation_constants.FEDERATION, {}).get('groups')
idp_id = token_data['token'].get('user', {}).get(
federation_constants.FEDERATION, {}).get(
'identity_provider', {}).get('id')
protocol_id = token_data['token'].get('user', {}).get(
federation_constants.FEDERATION, {}).get('protocol', {}).get('id')
if not group_ids:
group_ids = list()
if group_ids:
federated_dict = dict(group_ids=group_ids, idp_id=idp_id,
protocol_id=protocol_id)
return federated_dict
return None
# If we don't have an identity provider ID and a protocol ID, it's safe
# to assume we aren't dealing with a federated token.
if not (idp_id and protocol_id):
return None
group_ids = token_data['token'].get('user', {}).get(
federation_constants.FEDERATION, {}).get('groups')
return {'group_ids': group_ids,
'idp_id': idp_id,
'protocol_id': protocol_id}
def _rebuild_federated_info(self, federated_dict, user_id):
"""Format federated information into the token reference.
@ -158,12 +160,36 @@ class Provider(common.BaseProvider):
federated_info = dict(groups=g_ids,
identity_provider=dict(id=idp_id),
protocol=dict(id=protocol_id))
token_dict = {'user': {
federation_constants.FEDERATION: federated_info}}
token_dict['user']['id'] = user_id
token_dict['user']['name'] = user_id
token_dict = {
'user': {
federation_constants.FEDERATION: federated_info,
'id': user_id,
'name': user_id
}
}
return token_dict
def _rebuild_federated_token_roles(self, token_dict, federated_dict,
user_id, project_id, domain_id):
"""Populate roles based on (groups, project/domain) pair.
We must populate roles from (groups, project/domain) as ephemeral users
don't exist in the backend. Upon success, a ``roles`` key will be added
to ``token_dict``.
:param token_dict: dictionary with data used for building token
:param federated_dict: federated information such as identity provider
protocol and set of group IDs
:param user_id: user ID
:param project_id: project ID the token is being scoped to
:param domain_id: domain ID the token is being scoped to
"""
group_ids = [x['id'] for x in federated_dict['group_ids']]
self.v3_token_data_helper.populate_roles_for_groups(
token_dict, group_ids, project_id, domain_id, user_id)
def validate_v2_token(self, token_ref):
"""Validate a V2 formatted token.
@ -220,6 +246,10 @@ class Provider(common.BaseProvider):
trust_ref = None
if federated_info:
token_dict = self._rebuild_federated_info(federated_info, user_id)
if project_id or domain_id:
self._rebuild_federated_token_roles(token_dict, federated_info,
user_id, project_id,
domain_id)
if trust_id:
trust_ref = self.trust_api.get_trust(trust_id)

View File

@ -117,6 +117,24 @@ class TokenFormatter(object):
expires_at,
audit_ids,
trust_id)
elif project_id and federated_info:
version = FederatedProjectScopedPayload.version
payload = FederatedProjectScopedPayload.assemble(
user_id,
methods,
project_id,
expires_at,
audit_ids,
federated_info)
elif domain_id and federated_info:
version = FederatedDomainScopedPayload.version
payload = FederatedDomainScopedPayload.assemble(
user_id,
methods,
domain_id,
expires_at,
audit_ids,
federated_info)
elif federated_info:
version = FederatedPayload.version
payload = FederatedPayload.assemble(
@ -197,6 +215,14 @@ class TokenFormatter(object):
elif version == FederatedPayload.version:
(user_id, methods, expires_at, audit_ids, federated_info) = (
FederatedPayload.disassemble(payload))
elif version == FederatedProjectScopedPayload.version:
(user_id, methods, project_id, expires_at, audit_ids,
federated_info) = FederatedProjectScopedPayload.disassemble(
payload)
elif version == FederatedDomainScopedPayload.version:
(user_id, methods, domain_id, expires_at, audit_ids,
federated_info) = FederatedDomainScopedPayload.disassemble(
payload)
else:
# If the token_format is not recognized, raise ValidationError.
raise exception.ValidationError(_(
@ -502,6 +528,15 @@ class TrustScopedPayload(BasePayload):
class FederatedPayload(BasePayload):
version = 4
@classmethod
def pack_group_id(cls, group_dict):
return cls.attempt_convert_uuid_hex_to_bytes(group_dict['id'])
@classmethod
def unpack_group_id(cls, group_id_in_bytes):
group_id = cls.attempt_convert_uuid_bytes_to_hex(group_id_in_bytes)
return {'id': group_id}
@classmethod
def assemble(cls, user_id, methods, expires_at, audit_ids, federated_info):
"""Assemble the payload of a federated token.
@ -516,12 +551,11 @@ class FederatedPayload(BasePayload):
:returns: the payload of a federated token
"""
def pack_group_ids(group_dict):
return cls.attempt_convert_uuid_hex_to_bytes(group_dict['id'])
b_user_id = cls.attempt_convert_uuid_hex_to_bytes(user_id)
methods = auth_plugins.convert_method_list_to_integer(methods)
b_group_ids = list(map(pack_group_ids, federated_info['group_ids']))
b_group_ids = list(map(cls.pack_group_id,
federated_info['group_ids']))
b_idp_id = cls.attempt_convert_uuid_hex_to_bytes(
federated_info['idp_id'])
protocol_id = federated_info['protocol_id']
@ -543,13 +577,10 @@ class FederatedPayload(BasePayload):
federated domain ID
"""
def unpack_group_ids(group_id_in_bytes):
group_id = cls.attempt_convert_uuid_bytes_to_hex(group_id_in_bytes)
return {'id': group_id}
user_id = cls.attempt_convert_uuid_bytes_to_hex(payload[0])
methods = auth_plugins.convert_integer_to_method_list(payload[1])
group_ids = list(map(unpack_group_ids, payload[2]))
group_ids = list(map(cls.unpack_group_id, payload[2]))
idp_id = cls.attempt_convert_uuid_bytes_to_hex(payload[3])
protocol_id = payload[4]
expires_at_str = cls._convert_int_to_time_string(payload[5])
@ -557,3 +588,70 @@ class FederatedPayload(BasePayload):
federated_info = dict(group_ids=group_ids, idp_id=idp_id,
protocol_id=protocol_id)
return (user_id, methods, expires_at_str, audit_ids, federated_info)
class FederatedScopedPayload(FederatedPayload):
version = None
@classmethod
def assemble(cls, user_id, methods, scope_id, expires_at, audit_ids,
federated_info):
"""Assemble the project-scoped payload of a federated token.
:param user_id: ID of the user in the token request
:param methods: list of authentication methods used
:param scope_id: ID of the project or domain ID to scope to
:param expires_at: datetime of the token's expiration
:param audit_ids: list of the token's audit IDs
:param federated_info: dictionary containing the identity provider ID,
protocol ID, federated domain ID and group IDs
:returns: the payload of a federated token
"""
b_user_id = cls.attempt_convert_uuid_hex_to_bytes(user_id)
methods = auth_plugins.convert_method_list_to_integer(methods)
b_scope_id = cls.attempt_convert_uuid_hex_to_bytes(scope_id)
b_group_ids = list(map(cls.pack_group_id,
federated_info['group_ids']))
b_idp_id = cls.attempt_convert_uuid_hex_to_bytes(
federated_info['idp_id'])
protocol_id = federated_info['protocol_id']
expires_at_int = cls._convert_time_string_to_int(expires_at)
b_audit_ids = list(map(provider.random_urlsafe_str_to_bytes,
audit_ids))
return (b_user_id, methods, b_scope_id, b_group_ids, b_idp_id,
protocol_id, expires_at_int, b_audit_ids)
@classmethod
def disassemble(cls, payload):
"""Validate a project-scoped federated payload.
:param token_string: a string representing the token
:returns: a tuple containing the user_id, auth methods, scope_id,
expiration time (as str), audit_ids, and a dictionary
containing federated information such as the the identity
provider ID, the protocol ID, the federated domain ID and
group IDs
"""
user_id = cls.attempt_convert_uuid_bytes_to_hex(payload[0])
methods = auth_plugins.convert_integer_to_method_list(payload[1])
scope_id = cls.attempt_convert_uuid_bytes_to_hex(payload[2])
group_ids = list(map(cls.unpack_group_id, payload[3]))
idp_id = cls.attempt_convert_uuid_bytes_to_hex(payload[4])
protocol_id = payload[5]
expires_at_str = cls._convert_int_to_time_string(payload[6])
audit_ids = list(map(provider.base64_encode, payload[7]))
federated_info = dict(idp_id=idp_id, protocol_id=protocol_id,
group_ids=group_ids)
return (user_id, methods, scope_id, expires_at_str, audit_ids,
federated_info)
class FederatedProjectScopedPayload(FederatedScopedPayload):
version = 5
class FederatedDomainScopedPayload(FederatedScopedPayload):
version = 6