From 2e7b4f2027a1147ca28301e4f88adf8274b39a1f Mon Sep 17 00:00:00 2001 From: DUVAL Thomas Date: Thu, 9 Jun 2016 09:11:50 +0200 Subject: Update Keystone core to Mitaka. Change-Id: Ia10d6add16f4a9d25d1f42d420661c46332e69db --- .../token/providers/fernet/token_formatters.py | 543 ++++++++++----------- 1 file changed, 254 insertions(+), 289 deletions(-) (limited to 'keystone-moon/keystone/token/providers/fernet/token_formatters.py') diff --git a/keystone-moon/keystone/token/providers/fernet/token_formatters.py b/keystone-moon/keystone/token/providers/fernet/token_formatters.py index dbfee6dd..dfdd06e8 100644 --- a/keystone-moon/keystone/token/providers/fernet/token_formatters.py +++ b/keystone-moon/keystone/token/providers/fernet/token_formatters.py @@ -20,7 +20,6 @@ import msgpack from oslo_config import cfg from oslo_log import log from oslo_utils import timeutils -import six from six.moves import map from six.moves import urllib @@ -66,14 +65,22 @@ class TokenFormatter(object): return fernet.MultiFernet(fernet_instances) def pack(self, payload): - """Pack a payload for transport as a token.""" + """Pack a payload for transport as a token. + + :type payload: six.binary_type + :rtype: six.text_type + + """ # base64 padding (if any) is not URL-safe - return self.crypto.encrypt(payload).rstrip('=') + return self.crypto.encrypt(payload).rstrip(b'=').decode('utf-8') def unpack(self, token): - """Unpack a token, and validate the payload.""" - token = six.binary_type(token) + """Unpack a token, and validate the payload. + :type token: six.text_type + :rtype: six.binary_type + + """ # TODO(lbragstad): Restore padding on token before decoding it. # Initially in Kilo, Fernet tokens were returned to the user with # padding appended to the token. Later in Liberty this padding was @@ -89,16 +96,17 @@ class TokenFormatter(object): token = TokenFormatter.restore_padding(token) try: - return self.crypto.decrypt(token) + return self.crypto.decrypt(token.encode('utf-8')) except fernet.InvalidToken: raise exception.ValidationError( - _('This is not a recognized Fernet token')) + _('This is not a recognized Fernet token %s') % token) @classmethod def restore_padding(cls, token): """Restore padding based on token size. :param token: token to restore padding on + :type token: six.text_type :returns: token with correct padding """ @@ -106,21 +114,22 @@ class TokenFormatter(object): mod_returned = len(token) % 4 if mod_returned: missing_padding = 4 - mod_returned - token += b'=' * missing_padding + token += '=' * missing_padding return token @classmethod def creation_time(cls, fernet_token): - """Returns the creation time of a valid Fernet token.""" - # tokens may be transmitted as Unicode, but they're just ASCII - # (pypi/cryptography will refuse to operate on Unicode input) - fernet_token = six.binary_type(fernet_token) + """Returns the creation time of a valid Fernet token. - # Restore padding on token before decoding it + :type fernet_token: six.text_type + + """ fernet_token = TokenFormatter.restore_padding(fernet_token) + # fernet_token is six.text_type - # fernet tokens are base64 encoded, so we need to unpack them first - token_bytes = base64.urlsafe_b64decode(fernet_token) + # Fernet tokens are base64 encoded, so we need to unpack them first + # urlsafe_b64decode() requires six.binary_type + token_bytes = base64.urlsafe_b64decode(fernet_token.encode('utf-8')) # slice into the byte array to get just the timestamp timestamp_bytes = token_bytes[TIMESTAMP_START:TIMESTAMP_END] @@ -136,66 +145,20 @@ class TokenFormatter(object): def create_token(self, user_id, expires_at, audit_ids, methods=None, domain_id=None, project_id=None, trust_id=None, - federated_info=None): + federated_info=None, access_token_id=None): """Given a set of payload attributes, generate a Fernet token.""" - if trust_id: - version = TrustScopedPayload.version - payload = TrustScopedPayload.assemble( - user_id, - methods, - project_id, - 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 = FederatedUnscopedPayload.version - payload = FederatedUnscopedPayload.assemble( - user_id, - methods, - expires_at, - audit_ids, - federated_info) - elif project_id: - version = ProjectScopedPayload.version - payload = ProjectScopedPayload.assemble( - user_id, - methods, - project_id, - expires_at, - audit_ids) - elif domain_id: - version = DomainScopedPayload.version - payload = DomainScopedPayload.assemble( - user_id, - methods, - domain_id, - expires_at, - audit_ids) - else: - version = UnscopedPayload.version - payload = UnscopedPayload.assemble( - user_id, - methods, - expires_at, - audit_ids) + for payload_class in PAYLOAD_CLASSES: + if payload_class.create_arguments_apply( + project_id=project_id, domain_id=domain_id, + trust_id=trust_id, federated_info=federated_info, + access_token_id=access_token_id): + break + + version = payload_class.version + payload = payload_class.assemble( + user_id, methods, project_id, domain_id, expires_at, audit_ids, + trust_id, federated_info, access_token_id + ) versioned_payload = (version,) + payload serialized_payload = msgpack.packb(versioned_payload) @@ -215,44 +178,21 @@ class TokenFormatter(object): return token def validate_token(self, token): - """Validates a Fernet token and returns the payload attributes.""" - # Convert v2 unicode token to a string - if not isinstance(token, six.binary_type): - token = token.encode('ascii') + """Validates a Fernet token and returns the payload attributes. + :type token: six.text_type + + """ serialized_payload = self.unpack(token) versioned_payload = msgpack.unpackb(serialized_payload) version, payload = versioned_payload[0], versioned_payload[1:] - # depending on the formatter, these may or may not be defined - domain_id = None - project_id = None - trust_id = None - federated_info = None - - if version == UnscopedPayload.version: - (user_id, methods, expires_at, audit_ids) = ( - UnscopedPayload.disassemble(payload)) - elif version == DomainScopedPayload.version: - (user_id, methods, domain_id, expires_at, audit_ids) = ( - DomainScopedPayload.disassemble(payload)) - elif version == ProjectScopedPayload.version: - (user_id, methods, project_id, expires_at, audit_ids) = ( - ProjectScopedPayload.disassemble(payload)) - elif version == TrustScopedPayload.version: - (user_id, methods, project_id, expires_at, audit_ids, trust_id) = ( - TrustScopedPayload.disassemble(payload)) - elif version == FederatedUnscopedPayload.version: - (user_id, methods, expires_at, audit_ids, federated_info) = ( - FederatedUnscopedPayload.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) + for payload_class in PAYLOAD_CLASSES: + if version == payload_class.version: + (user_id, methods, project_id, domain_id, expires_at, + audit_ids, trust_id, federated_info, access_token_id) = ( + payload_class.disassemble(payload)) + break else: # If the token_format is not recognized, raise ValidationError. raise exception.ValidationError(_( @@ -267,7 +207,7 @@ class TokenFormatter(object): expires_at = ks_utils.isotime(at=expires_at, subsecond=True) return (user_id, methods, audit_ids, domain_id, project_id, trust_id, - federated_info, created_at, expires_at) + federated_info, access_token_id, created_at, expires_at) class BasePayload(object): @@ -275,10 +215,32 @@ class BasePayload(object): version = None @classmethod - def assemble(cls, *args): + def create_arguments_apply(cls, **kwargs): + """Check the arguments to see if they apply to this payload variant. + + :returns: True if the arguments indicate that this payload class is + needed for the token otherwise returns False. + :rtype: bool + + """ + raise NotImplementedError() + + @classmethod + def assemble(cls, user_id, methods, project_id, domain_id, expires_at, + audit_ids, trust_id, federated_info, access_token_id): """Assemble the payload of a token. - :param args: whatever data should go into the payload + :param user_id: identifier of the user in the token request + :param methods: list of authentication methods used + :param project_id: ID of the project to scope to + :param domain_id: ID of the domain to scope to + :param expires_at: datetime of the token's expiration + :param audit_ids: list of the token's audit IDs + :param trust_id: ID of the trust in effect + :param federated_info: dictionary containing group IDs, the identity + provider ID, protocol ID, and federated domain + ID + :param access_token_id: ID of the secret in OAuth1 authentication :returns: the payload of a token """ @@ -288,6 +250,17 @@ class BasePayload(object): def disassemble(cls, payload): """Disassemble an unscoped payload into the component data. + The tuple consists of:: + + (user_id, methods, project_id, domain_id, expires_at_str, + audit_ids, trust_id, federated_info, access_token_id) + + * ``methods`` are the auth methods. + * federated_info is a dict contains the group IDs, the identity + provider ID, the protocol ID, and the federated domain ID + + Fields will be set to None if they didn't apply to this payload type. + :param payload: this variant of payload :returns: a tuple of the payloads component data @@ -302,9 +275,6 @@ class BasePayload(object): :returns: a byte representation of the uuid """ - # TODO(lbragstad): Wrap this in an exception. Not sure what the case - # would be where we couldn't handle what we've been given but incase - # the integrity of the token has been compromised. uuid_obj = uuid.UUID(uuid_string) return uuid_obj.bytes @@ -316,18 +286,15 @@ class BasePayload(object): :returns: uuid hex formatted string """ - # TODO(lbragstad): Wrap this in an exception. Not sure what the case - # would be where we couldn't handle what we've been given but incase - # the integrity of the token has been compromised. uuid_obj = uuid.UUID(bytes=uuid_byte_string) return uuid_obj.hex @classmethod - def _convert_time_string_to_int(cls, time_string): - """Convert a time formatted string to a timestamp integer. + def _convert_time_string_to_float(cls, time_string): + """Convert a time formatted string to a float. :param time_string: time formatted string - :returns: an integer timestamp + :returns: a timestamp as a float """ time_object = timeutils.parse_isotime(time_string) @@ -335,14 +302,14 @@ class BasePayload(object): datetime.datetime.utcfromtimestamp(0)).total_seconds() @classmethod - def _convert_int_to_time_string(cls, time_int): - """Convert a timestamp integer to a string. + def _convert_float_to_time_string(cls, time_float): + """Convert a floating point timestamp to a string. - :param time_int: integer representing timestamp + :param time_float: integer representing timestamp :returns: a time formatted strings """ - time_object = datetime.datetime.utcfromtimestamp(time_int) + time_object = datetime.datetime.utcfromtimestamp(time_float) return ks_utils.isotime(time_object, subsecond=True) @classmethod @@ -361,74 +328,51 @@ class BasePayload(object): # federation) return (False, value) - @classmethod - def attempt_convert_uuid_bytes_to_hex(cls, value): - """Attempt to convert value to hex or return value. - - :param value: value to attempt to convert to hex - :returns: uuid value in hex or value - - """ - try: - return cls.convert_uuid_bytes_to_hex(value) - except ValueError: - return value - class UnscopedPayload(BasePayload): version = 0 @classmethod - def assemble(cls, user_id, methods, expires_at, audit_ids): - """Assemble the payload of an unscoped token. - - :param user_id: identifier of the user in the token request - :param methods: list of authentication methods used - :param expires_at: datetime of the token's expiration - :param audit_ids: list of the token's audit IDs - :returns: the payload of an unscoped token + def create_arguments_apply(cls, **kwargs): + return True - """ + @classmethod + def assemble(cls, user_id, methods, project_id, domain_id, expires_at, + audit_ids, trust_id, federated_info, access_token_id): b_user_id = cls.attempt_convert_uuid_hex_to_bytes(user_id) methods = auth_plugins.convert_method_list_to_integer(methods) - expires_at_int = cls._convert_time_string_to_int(expires_at) + expires_at_int = cls._convert_time_string_to_float(expires_at) b_audit_ids = list(map(provider.random_urlsafe_str_to_bytes, audit_ids)) return (b_user_id, methods, expires_at_int, b_audit_ids) @classmethod def disassemble(cls, payload): - """Disassemble an unscoped payload into the component data. - - :param payload: the payload of an unscoped token - :return: a tuple containing the user_id, auth methods, expires_at, and - audit_ids - - """ (is_stored_as_bytes, user_id) = payload[0] if is_stored_as_bytes: - user_id = cls.attempt_convert_uuid_bytes_to_hex(user_id) + user_id = cls.convert_uuid_bytes_to_hex(user_id) methods = auth_plugins.convert_integer_to_method_list(payload[1]) - expires_at_str = cls._convert_int_to_time_string(payload[2]) + expires_at_str = cls._convert_float_to_time_string(payload[2]) audit_ids = list(map(provider.base64_encode, payload[3])) - return (user_id, methods, expires_at_str, audit_ids) + project_id = None + domain_id = None + trust_id = None + federated_info = None + access_token_id = None + return (user_id, methods, project_id, domain_id, expires_at_str, + audit_ids, trust_id, federated_info, access_token_id) class DomainScopedPayload(BasePayload): version = 1 @classmethod - def assemble(cls, user_id, methods, domain_id, expires_at, audit_ids): - """Assemble the payload of a domain-scoped token. + def create_arguments_apply(cls, **kwargs): + return kwargs['domain_id'] - :param user_id: ID of the user in the token request - :param methods: list of authentication methods used - :param domain_id: ID of the domain to scope to - :param expires_at: datetime of the token's expiration - :param audit_ids: list of the token's audit IDs - :returns: the payload of a domain-scoped token - - """ + @classmethod + def assemble(cls, user_id, methods, project_id, domain_id, expires_at, + audit_ids, trust_id, federated_info, access_token_id): b_user_id = cls.attempt_convert_uuid_hex_to_bytes(user_id) methods = auth_plugins.convert_method_list_to_integer(methods) try: @@ -439,23 +383,16 @@ class DomainScopedPayload(BasePayload): b_domain_id = domain_id else: raise - expires_at_int = cls._convert_time_string_to_int(expires_at) + expires_at_int = cls._convert_time_string_to_float(expires_at) b_audit_ids = list(map(provider.random_urlsafe_str_to_bytes, audit_ids)) return (b_user_id, methods, b_domain_id, expires_at_int, b_audit_ids) @classmethod def disassemble(cls, payload): - """Disassemble a payload into the component data. - - :param payload: the payload of a token - :return: a tuple containing the user_id, auth methods, domain_id, - expires_at_str, and audit_ids - - """ (is_stored_as_bytes, user_id) = payload[0] if is_stored_as_bytes: - user_id = cls.attempt_convert_uuid_bytes_to_hex(user_id) + user_id = cls.convert_uuid_bytes_to_hex(user_id) methods = auth_plugins.convert_integer_to_method_list(payload[1]) try: domain_id = cls.convert_uuid_bytes_to_hex(payload[2]) @@ -465,79 +402,68 @@ class DomainScopedPayload(BasePayload): domain_id = payload[2] else: raise - expires_at_str = cls._convert_int_to_time_string(payload[3]) + expires_at_str = cls._convert_float_to_time_string(payload[3]) audit_ids = list(map(provider.base64_encode, payload[4])) - - return (user_id, methods, domain_id, expires_at_str, audit_ids) + project_id = None + trust_id = None + federated_info = None + access_token_id = None + return (user_id, methods, project_id, domain_id, expires_at_str, + audit_ids, trust_id, federated_info, access_token_id) class ProjectScopedPayload(BasePayload): version = 2 @classmethod - def assemble(cls, user_id, methods, project_id, expires_at, audit_ids): - """Assemble the payload of a project-scoped token. + def create_arguments_apply(cls, **kwargs): + return kwargs['project_id'] - :param user_id: ID of the user in the token request - :param methods: list of authentication methods used - :param project_id: ID of the project to scope to - :param expires_at: datetime of the token's expiration - :param audit_ids: list of the token's audit IDs - :returns: the payload of a project-scoped token - - """ + @classmethod + def assemble(cls, user_id, methods, project_id, domain_id, expires_at, + audit_ids, trust_id, federated_info, access_token_id): b_user_id = cls.attempt_convert_uuid_hex_to_bytes(user_id) methods = auth_plugins.convert_method_list_to_integer(methods) b_project_id = cls.attempt_convert_uuid_hex_to_bytes(project_id) - expires_at_int = cls._convert_time_string_to_int(expires_at) + expires_at_int = cls._convert_time_string_to_float(expires_at) b_audit_ids = list(map(provider.random_urlsafe_str_to_bytes, audit_ids)) return (b_user_id, methods, b_project_id, expires_at_int, b_audit_ids) @classmethod def disassemble(cls, payload): - """Disassemble a payload into the component data. - - :param payload: the payload of a token - :return: a tuple containing the user_id, auth methods, project_id, - expires_at_str, and audit_ids - - """ (is_stored_as_bytes, user_id) = payload[0] if is_stored_as_bytes: - user_id = cls.attempt_convert_uuid_bytes_to_hex(user_id) + user_id = cls.convert_uuid_bytes_to_hex(user_id) methods = auth_plugins.convert_integer_to_method_list(payload[1]) (is_stored_as_bytes, project_id) = payload[2] if is_stored_as_bytes: - project_id = cls.attempt_convert_uuid_bytes_to_hex(project_id) - expires_at_str = cls._convert_int_to_time_string(payload[3]) + project_id = cls.convert_uuid_bytes_to_hex(project_id) + expires_at_str = cls._convert_float_to_time_string(payload[3]) audit_ids = list(map(provider.base64_encode, payload[4])) - - return (user_id, methods, project_id, expires_at_str, audit_ids) + domain_id = None + trust_id = None + federated_info = None + access_token_id = None + return (user_id, methods, project_id, domain_id, expires_at_str, + audit_ids, trust_id, federated_info, access_token_id) class TrustScopedPayload(BasePayload): version = 3 @classmethod - def assemble(cls, user_id, methods, project_id, expires_at, audit_ids, - trust_id): - """Assemble the payload of a trust-scoped token. - - :param user_id: ID of the user in the token request - :param methods: list of authentication methods used - :param project_id: ID of the project to scope to - :param expires_at: datetime of the token's expiration - :param audit_ids: list of the token's audit IDs - :param trust_id: ID of the trust in effect - :returns: the payload of a trust-scoped token + def create_arguments_apply(cls, **kwargs): + return kwargs['trust_id'] - """ + @classmethod + def assemble(cls, user_id, methods, project_id, domain_id, expires_at, + audit_ids, trust_id, federated_info, access_token_id): b_user_id = cls.attempt_convert_uuid_hex_to_bytes(user_id) methods = auth_plugins.convert_method_list_to_integer(methods) b_project_id = cls.attempt_convert_uuid_hex_to_bytes(project_id) b_trust_id = cls.convert_uuid_hex_to_bytes(trust_id) - expires_at_int = cls._convert_time_string_to_int(expires_at) + expires_at_int = cls._convert_time_string_to_float(expires_at) b_audit_ids = list(map(provider.random_urlsafe_str_to_bytes, audit_ids)) @@ -546,31 +472,30 @@ class TrustScopedPayload(BasePayload): @classmethod def disassemble(cls, payload): - """Validate a trust-based payload. - - :param token_string: a string representing the token - :returns: a tuple containing the user_id, auth methods, project_id, - expires_at_str, audit_ids, and trust_id - - """ (is_stored_as_bytes, user_id) = payload[0] if is_stored_as_bytes: - user_id = cls.attempt_convert_uuid_bytes_to_hex(user_id) + user_id = cls.convert_uuid_bytes_to_hex(user_id) methods = auth_plugins.convert_integer_to_method_list(payload[1]) (is_stored_as_bytes, project_id) = payload[2] if is_stored_as_bytes: - project_id = cls.attempt_convert_uuid_bytes_to_hex(project_id) - expires_at_str = cls._convert_int_to_time_string(payload[3]) + project_id = cls.convert_uuid_bytes_to_hex(project_id) + expires_at_str = cls._convert_float_to_time_string(payload[3]) audit_ids = list(map(provider.base64_encode, payload[4])) trust_id = cls.convert_uuid_bytes_to_hex(payload[5]) - - return (user_id, methods, project_id, expires_at_str, audit_ids, - trust_id) + domain_id = None + federated_info = None + access_token_id = None + return (user_id, methods, project_id, domain_id, expires_at_str, + audit_ids, trust_id, federated_info, access_token_id) class FederatedUnscopedPayload(BasePayload): version = 4 + @classmethod + def create_arguments_apply(cls, **kwargs): + return kwargs['federated_info'] + @classmethod def pack_group_id(cls, group_dict): return cls.attempt_convert_uuid_hex_to_bytes(group_dict['id']) @@ -579,24 +504,12 @@ class FederatedUnscopedPayload(BasePayload): def unpack_group_id(cls, group_id_in_bytes): (is_stored_as_bytes, group_id) = group_id_in_bytes if is_stored_as_bytes: - group_id = cls.attempt_convert_uuid_bytes_to_hex(group_id) + group_id = cls.convert_uuid_bytes_to_hex(group_id) return {'id': group_id} @classmethod - def assemble(cls, user_id, methods, expires_at, audit_ids, federated_info): - """Assemble the payload of a federated token. - - :param user_id: ID of the user in the token request - :param methods: list of authentication methods used - :param expires_at: datetime of the token's expiration - :param audit_ids: list of the token's audit IDs - :param federated_info: dictionary containing group IDs, the identity - provider ID, protocol ID, and federated domain - ID - :returns: the payload of a federated token - - """ - + def assemble(cls, user_id, methods, project_id, domain_id, expires_at, + audit_ids, trust_id, federated_info, access_token_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(cls.pack_group_id, @@ -604,7 +517,7 @@ class FederatedUnscopedPayload(BasePayload): 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) + expires_at_int = cls._convert_time_string_to_float(expires_at) b_audit_ids = list(map(provider.random_urlsafe_str_to_bytes, audit_ids)) @@ -613,59 +526,43 @@ class FederatedUnscopedPayload(BasePayload): @classmethod def disassemble(cls, payload): - """Validate a federated payload. - - :param token_string: a string representing the token - :return: a tuple containing the user_id, auth methods, audit_ids, and a - dictionary containing federated information such as the group - IDs, the identity provider ID, the protocol ID, and the - federated domain ID - - """ - (is_stored_as_bytes, user_id) = payload[0] if is_stored_as_bytes: - user_id = cls.attempt_convert_uuid_bytes_to_hex(user_id) + user_id = cls.convert_uuid_bytes_to_hex(user_id) methods = auth_plugins.convert_integer_to_method_list(payload[1]) group_ids = list(map(cls.unpack_group_id, payload[2])) (is_stored_as_bytes, idp_id) = payload[3] if is_stored_as_bytes: - idp_id = cls.attempt_convert_uuid_bytes_to_hex(idp_id) + idp_id = cls.convert_uuid_bytes_to_hex(idp_id) protocol_id = payload[4] - expires_at_str = cls._convert_int_to_time_string(payload[5]) + expires_at_str = cls._convert_float_to_time_string(payload[5]) audit_ids = list(map(provider.base64_encode, payload[6])) 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) + project_id = None + domain_id = None + trust_id = None + access_token_id = None + return (user_id, methods, project_id, domain_id, expires_at_str, + audit_ids, trust_id, federated_info, access_token_id) class FederatedScopedPayload(FederatedUnscopedPayload): 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 - - """ + def assemble(cls, user_id, methods, project_id, domain_id, expires_at, + audit_ids, trust_id, federated_info, access_token_id): 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_scope_id = cls.attempt_convert_uuid_hex_to_bytes( + project_id or domain_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) + expires_at_int = cls._convert_time_string_to_float(expires_at) b_audit_ids = list(map(provider.random_urlsafe_str_to_bytes, audit_ids)) @@ -674,39 +571,107 @@ class FederatedScopedPayload(FederatedUnscopedPayload): @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 - - """ (is_stored_as_bytes, user_id) = payload[0] if is_stored_as_bytes: - user_id = cls.attempt_convert_uuid_bytes_to_hex(user_id) + user_id = cls.convert_uuid_bytes_to_hex(user_id) methods = auth_plugins.convert_integer_to_method_list(payload[1]) (is_stored_as_bytes, scope_id) = payload[2] if is_stored_as_bytes: - scope_id = cls.attempt_convert_uuid_bytes_to_hex(scope_id) + scope_id = cls.convert_uuid_bytes_to_hex(scope_id) + project_id = ( + scope_id + if cls.version == FederatedProjectScopedPayload.version else None) + domain_id = ( + scope_id + if cls.version == FederatedDomainScopedPayload.version else None) group_ids = list(map(cls.unpack_group_id, payload[3])) (is_stored_as_bytes, idp_id) = payload[4] if is_stored_as_bytes: - idp_id = cls.attempt_convert_uuid_bytes_to_hex(idp_id) + idp_id = cls.convert_uuid_bytes_to_hex(idp_id) protocol_id = payload[5] - expires_at_str = cls._convert_int_to_time_string(payload[6]) + expires_at_str = cls._convert_float_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) + trust_id = None + access_token_id = None + return (user_id, methods, project_id, domain_id, expires_at_str, + audit_ids, trust_id, federated_info, access_token_id) class FederatedProjectScopedPayload(FederatedScopedPayload): version = 5 + @classmethod + def create_arguments_apply(cls, **kwargs): + return kwargs['project_id'] and kwargs['federated_info'] + class FederatedDomainScopedPayload(FederatedScopedPayload): version = 6 + + @classmethod + def create_arguments_apply(cls, **kwargs): + return kwargs['domain_id'] and kwargs['federated_info'] + + +class OauthScopedPayload(BasePayload): + version = 7 + + @classmethod + def create_arguments_apply(cls, **kwargs): + return kwargs['access_token_id'] + + @classmethod + def assemble(cls, user_id, methods, project_id, domain_id, expires_at, + audit_ids, trust_id, federated_info, access_token_id): + b_user_id = cls.attempt_convert_uuid_hex_to_bytes(user_id) + methods = auth_plugins.convert_method_list_to_integer(methods) + b_project_id = cls.attempt_convert_uuid_hex_to_bytes(project_id) + expires_at_int = cls._convert_time_string_to_float(expires_at) + b_audit_ids = list(map(provider.random_urlsafe_str_to_bytes, + audit_ids)) + b_access_token_id = cls.attempt_convert_uuid_hex_to_bytes( + access_token_id) + return (b_user_id, methods, b_project_id, b_access_token_id, + expires_at_int, b_audit_ids) + + @classmethod + def disassemble(cls, payload): + (is_stored_as_bytes, user_id) = payload[0] + if is_stored_as_bytes: + user_id = cls.convert_uuid_bytes_to_hex(user_id) + methods = auth_plugins.convert_integer_to_method_list(payload[1]) + (is_stored_as_bytes, project_id) = payload[2] + if is_stored_as_bytes: + project_id = cls.convert_uuid_bytes_to_hex(project_id) + (is_stored_as_bytes, access_token_id) = payload[3] + if is_stored_as_bytes: + access_token_id = cls.convert_uuid_bytes_to_hex(access_token_id) + expires_at_str = cls._convert_float_to_time_string(payload[4]) + audit_ids = list(map(provider.base64_encode, payload[5])) + domain_id = None + trust_id = None + federated_info = None + + return (user_id, methods, project_id, domain_id, expires_at_str, + audit_ids, trust_id, federated_info, access_token_id) + + +# For now, the order of the classes in the following list is important. This +# is because the way they test that the payload applies to them in +# the create_arguments_apply method requires that the previous ones rejected +# the payload arguments. For example, UnscopedPayload must be last since it's +# the catch-all after all the other payloads have been checked. +# TODO(blk-u): Clean up the create_arguments_apply methods so that they don't +# depend on the previous classes then these can be in any order. +PAYLOAD_CLASSES = [ + OauthScopedPayload, + TrustScopedPayload, + FederatedProjectScopedPayload, + FederatedDomainScopedPayload, + FederatedUnscopedPayload, + ProjectScopedPayload, + DomainScopedPayload, + UnscopedPayload, +] -- cgit 1.2.3-korg