"Fossies" - the Fresh Open Source Software Archive

Member "keystone-18.0.0/keystone/token/token_formatters.py" (14 Oct 2020, 31779 Bytes) of package /linux/misc/openstack/keystone-18.0.0.tar.gz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) Python source code syntax highlighting (style: standard) with prefixed line numbers. Alternatively you can here view or download the uninterpreted source code file. For more information about "token_formatters.py" see the Fossies "Dox" file reference documentation and the latest Fossies "Diffs" side-by-side code changes report: 17.0.0_vs_18.0.0.

    1 # Licensed under the Apache License, Version 2.0 (the "License"); you may
    2 # not use this file except in compliance with the License. You may obtain
    3 # a copy of the License at
    4 #
    5 #      http://www.apache.org/licenses/LICENSE-2.0
    6 #
    7 # Unless required by applicable law or agreed to in writing, software
    8 # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
    9 # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
   10 # License for the specific language governing permissions and limitations
   11 # under the License.
   12 
   13 import base64
   14 import datetime
   15 import struct
   16 import uuid
   17 
   18 from cryptography import fernet
   19 import msgpack
   20 from oslo_log import log
   21 from oslo_utils import timeutils
   22 
   23 from keystone.auth import plugins as auth_plugins
   24 from keystone.common import fernet_utils as utils
   25 from keystone.common import utils as ks_utils
   26 import keystone.conf
   27 from keystone import exception
   28 from keystone.i18n import _
   29 
   30 
   31 CONF = keystone.conf.CONF
   32 LOG = log.getLogger(__name__)
   33 
   34 # Fernet byte indexes as computed by pypi/keyless_fernet and defined in
   35 # https://github.com/fernet/spec
   36 TIMESTAMP_START = 1
   37 TIMESTAMP_END = 9
   38 
   39 
   40 class TokenFormatter(object):
   41     """Packs and unpacks payloads into tokens for transport."""
   42 
   43     @property
   44     def crypto(self):
   45         """Return a cryptography instance.
   46 
   47         You can extend this class with a custom crypto @property to provide
   48         your own token encoding / decoding. For example, using a different
   49         cryptography library (e.g. ``python-keyczar``) or to meet arbitrary
   50         security requirements.
   51 
   52         This @property just needs to return an object that implements
   53         ``encrypt(plaintext)`` and ``decrypt(ciphertext)``.
   54 
   55         """
   56         fernet_utils = utils.FernetUtils(
   57             CONF.fernet_tokens.key_repository,
   58             CONF.fernet_tokens.max_active_keys,
   59             'fernet_tokens'
   60         )
   61         keys = fernet_utils.load_keys()
   62 
   63         if not keys:
   64             raise exception.KeysNotFound()
   65 
   66         fernet_instances = [fernet.Fernet(key) for key in keys]
   67         return fernet.MultiFernet(fernet_instances)
   68 
   69     def pack(self, payload):
   70         """Pack a payload for transport as a token.
   71 
   72         :type payload: bytes
   73         :rtype: str
   74 
   75         """
   76         # base64 padding (if any) is not URL-safe
   77         return self.crypto.encrypt(payload).rstrip(b'=').decode('utf-8')
   78 
   79     def unpack(self, token):
   80         """Unpack a token, and validate the payload.
   81 
   82         :type token: str
   83         :rtype: bytes
   84 
   85         """
   86         token = TokenFormatter.restore_padding(token)
   87 
   88         try:
   89             return self.crypto.decrypt(token.encode('utf-8'))
   90         except fernet.InvalidToken:
   91             raise exception.ValidationError(
   92                 _('Could not recognize Fernet token'))
   93 
   94     @classmethod
   95     def restore_padding(cls, token):
   96         """Restore padding based on token size.
   97 
   98         :param token: token to restore padding on
   99         :type token: str
  100         :returns: token with correct padding
  101 
  102         """
  103         # Re-inflate the padding
  104         mod_returned = len(token) % 4
  105         if mod_returned:
  106             missing_padding = 4 - mod_returned
  107             token += '=' * missing_padding
  108         return token
  109 
  110     @classmethod
  111     def creation_time(cls, fernet_token):
  112         """Return the creation time of a valid Fernet token.
  113 
  114         :type fernet_token: str
  115 
  116         """
  117         fernet_token = TokenFormatter.restore_padding(fernet_token)
  118         # fernet_token is str
  119 
  120         # Fernet tokens are base64 encoded, so we need to unpack them first
  121         # urlsafe_b64decode() requires bytes
  122         token_bytes = base64.urlsafe_b64decode(fernet_token.encode('utf-8'))
  123 
  124         # slice into the byte array to get just the timestamp
  125         timestamp_bytes = token_bytes[TIMESTAMP_START:TIMESTAMP_END]
  126 
  127         # convert those bytes to an integer
  128         # (it's a 64-bit "unsigned long long int" in C)
  129         timestamp_int = struct.unpack(">Q", timestamp_bytes)[0]
  130 
  131         # and with an integer, it's trivial to produce a datetime object
  132         issued_at = datetime.datetime.utcfromtimestamp(timestamp_int)
  133 
  134         return issued_at
  135 
  136     def create_token(self, user_id, expires_at, audit_ids, payload_class,
  137                      methods=None, system=None, domain_id=None,
  138                      project_id=None, trust_id=None, federated_group_ids=None,
  139                      identity_provider_id=None, protocol_id=None,
  140                      access_token_id=None, app_cred_id=None):
  141         """Given a set of payload attributes, generate a Fernet token."""
  142         version = payload_class.version
  143         payload = payload_class.assemble(
  144             user_id, methods, system, project_id, domain_id, expires_at,
  145             audit_ids, trust_id, federated_group_ids, identity_provider_id,
  146             protocol_id, access_token_id, app_cred_id
  147         )
  148 
  149         versioned_payload = (version,) + payload
  150         serialized_payload = msgpack.packb(versioned_payload)
  151         token = self.pack(serialized_payload)
  152 
  153         # NOTE(lbragstad): We should warn against Fernet tokens that are over
  154         # 255 characters in length. This is mostly due to persisting the tokens
  155         # in a backend store of some kind that might have a limit of 255
  156         # characters. Even though Keystone isn't storing a Fernet token
  157         # anywhere, we can't say it isn't being stored somewhere else with
  158         # those kind of backend constraints.
  159         if len(token) > 255:
  160             LOG.info('Fernet token created with length of %d '
  161                      'characters, which exceeds 255 characters',
  162                      len(token))
  163 
  164         return token
  165 
  166     def validate_token(self, token):
  167         """Validate a Fernet token and returns the payload attributes.
  168 
  169         :type token: str
  170 
  171         """
  172         serialized_payload = self.unpack(token)
  173         # TODO(melwitt): msgpack changed their data format in version 1.0, so
  174         # in order to support a rolling upgrade, we must pass raw=True to
  175         # support the old format. The try-except may be removed once the
  176         # N-1 release no longer supports msgpack < 1.0.
  177         try:
  178             versioned_payload = msgpack.unpackb(serialized_payload)
  179         except UnicodeDecodeError:
  180             versioned_payload = msgpack.unpackb(serialized_payload, raw=True)
  181 
  182         version, payload = versioned_payload[0], versioned_payload[1:]
  183 
  184         for payload_class in _PAYLOAD_CLASSES:
  185             if version == payload_class.version:
  186                 (user_id, methods, system, project_id, domain_id,
  187                  expires_at, audit_ids, trust_id, federated_group_ids,
  188                  identity_provider_id, protocol_id, access_token_id,
  189                  app_cred_id) = payload_class.disassemble(payload)
  190                 break
  191         else:
  192             # If the token_format is not recognized, raise ValidationError.
  193             raise exception.ValidationError(_(
  194                 'This is not a recognized Fernet payload version: %s') %
  195                 version)
  196 
  197         # FIXME(lbragstad): Without this, certain token validation tests fail
  198         # when running with python 3. Once we get further along in this
  199         # refactor, we should be better about handling string encoding/types at
  200         # the edges of the application.
  201         if isinstance(system, bytes):
  202             system = system.decode('utf-8')
  203 
  204         # rather than appearing in the payload, the creation time is encoded
  205         # into the token format itself
  206         issued_at = TokenFormatter.creation_time(token)
  207         issued_at = ks_utils.isotime(at=issued_at, subsecond=True)
  208         expires_at = timeutils.parse_isotime(expires_at)
  209         expires_at = ks_utils.isotime(at=expires_at, subsecond=True)
  210 
  211         return (user_id, methods, audit_ids, system, domain_id, project_id,
  212                 trust_id, federated_group_ids, identity_provider_id,
  213                 protocol_id, access_token_id, app_cred_id, issued_at,
  214                 expires_at)
  215 
  216 
  217 class BasePayload(object):
  218     # each payload variant should have a unique version
  219     version = None
  220 
  221     @classmethod
  222     def assemble(cls, user_id, methods, system, project_id, domain_id,
  223                  expires_at, audit_ids, trust_id, federated_group_ids,
  224                  identity_provider_id, protocol_id, access_token_id,
  225                  app_cred_id):
  226         """Assemble the payload of a token.
  227 
  228         :param user_id: identifier of the user in the token request
  229         :param methods: list of authentication methods used
  230         :param system: a string including system scope information
  231         :param project_id: ID of the project to scope to
  232         :param domain_id: ID of the domain to scope to
  233         :param expires_at: datetime of the token's expiration
  234         :param audit_ids: list of the token's audit IDs
  235         :param trust_id: ID of the trust in effect
  236         :param federated_group_ids: list of group IDs from SAML assertion
  237         :param identity_provider_id: ID of the user's identity provider
  238         :param protocol_id: federated protocol used for authentication
  239         :param access_token_id: ID of the secret in OAuth1 authentication
  240         :param app_cred_id: ID of the application credential in effect
  241         :returns: the payload of a token
  242 
  243         """
  244         raise NotImplementedError()
  245 
  246     @classmethod
  247     def disassemble(cls, payload):
  248         """Disassemble an unscoped payload into the component data.
  249 
  250         The tuple consists of::
  251 
  252             (user_id, methods, system, project_id, domain_id,
  253              expires_at_str, audit_ids, trust_id, federated_group_ids,
  254              identity_provider_id, protocol_id,` access_token_id, app_cred_id)
  255 
  256         * ``methods`` are the auth methods.
  257 
  258         Fields will be set to None if they didn't apply to this payload type.
  259 
  260         :param payload: this variant of payload
  261         :returns: a tuple of the payloads component data
  262 
  263         """
  264         raise NotImplementedError()
  265 
  266     @classmethod
  267     def convert_uuid_hex_to_bytes(cls, uuid_string):
  268         """Compress UUID formatted strings to bytes.
  269 
  270         :param uuid_string: uuid string to compress to bytes
  271         :returns: a byte representation of the uuid
  272 
  273         """
  274         uuid_obj = uuid.UUID(uuid_string)
  275         return uuid_obj.bytes
  276 
  277     @classmethod
  278     def convert_uuid_bytes_to_hex(cls, uuid_byte_string):
  279         """Generate uuid.hex format based on byte string.
  280 
  281         :param uuid_byte_string: uuid string to generate from
  282         :returns: uuid hex formatted string
  283 
  284         """
  285         uuid_obj = uuid.UUID(bytes=uuid_byte_string)
  286         return uuid_obj.hex
  287 
  288     @classmethod
  289     def _convert_time_string_to_float(cls, time_string):
  290         """Convert a time formatted string to a float.
  291 
  292         :param time_string: time formatted string
  293         :returns: a timestamp as a float
  294 
  295         """
  296         time_object = timeutils.parse_isotime(time_string)
  297         return (timeutils.normalize_time(time_object) -
  298                 datetime.datetime.utcfromtimestamp(0)).total_seconds()
  299 
  300     @classmethod
  301     def _convert_float_to_time_string(cls, time_float):
  302         """Convert a floating point timestamp to a string.
  303 
  304         :param time_float: integer representing timestamp
  305         :returns: a time formatted strings
  306 
  307         """
  308         time_object = datetime.datetime.utcfromtimestamp(time_float)
  309         return ks_utils.isotime(time_object, subsecond=True)
  310 
  311     @classmethod
  312     def attempt_convert_uuid_hex_to_bytes(cls, value):
  313         """Attempt to convert value to bytes or return value.
  314 
  315         :param value: value to attempt to convert to bytes
  316         :returns: tuple containing boolean indicating whether user_id was
  317                   stored as bytes and uuid value as bytes or the original value
  318 
  319         """
  320         try:
  321             return (True, cls.convert_uuid_hex_to_bytes(value))
  322         except (ValueError, TypeError):
  323             # ValueError: this might not be a UUID, depending on the
  324             # situation (i.e. federation)
  325             # TypeError: the provided value may be binary encoded
  326             # in which case just return the value (i.e. Python 3)
  327             return (False, value)
  328 
  329     @classmethod
  330     def base64_encode(cls, s):
  331         """Encode a URL-safe string.
  332 
  333         :type s: str
  334         :rtype: str
  335 
  336         """
  337         # urlsafe_b64encode() returns bytes so need to convert to
  338         # str, might as well do it before stripping.
  339         return base64.urlsafe_b64encode(s).decode('utf-8').rstrip('=')
  340 
  341     @classmethod
  342     def random_urlsafe_str_to_bytes(cls, s):
  343         """Convert string from :func:`random_urlsafe_str()` to bytes.
  344 
  345         :type s: str
  346         :rtype: bytes
  347 
  348         """
  349         # urlsafe_b64decode() requires str, unicode isn't accepted.
  350         s = str(s)
  351 
  352         # restore the padding (==) at the end of the string
  353         return base64.urlsafe_b64decode(s + '==')
  354 
  355     @classmethod
  356     def _convert_or_decode(cls, is_stored_as_bytes, value):
  357         """Convert a value to text type, translating uuid -> hex if required.
  358 
  359         :param is_stored_as_bytes: whether value is already bytes
  360         :type is_stored_as_bytes: boolean
  361         :param value: value to attempt to convert to bytes
  362         :type value: str or bytes
  363         :rtype: str
  364         """
  365         if is_stored_as_bytes:
  366             return cls.convert_uuid_bytes_to_hex(value)
  367         elif isinstance(value, bytes):
  368             return value.decode('utf-8')
  369         return value
  370 
  371 
  372 class UnscopedPayload(BasePayload):
  373     version = 0
  374 
  375     @classmethod
  376     def assemble(cls, user_id, methods, system, project_id, domain_id,
  377                  expires_at, audit_ids, trust_id, federated_group_ids,
  378                  identity_provider_id, protocol_id, access_token_id,
  379                  app_cred_id):
  380         b_user_id = cls.attempt_convert_uuid_hex_to_bytes(user_id)
  381         methods = auth_plugins.convert_method_list_to_integer(methods)
  382         expires_at_int = cls._convert_time_string_to_float(expires_at)
  383         b_audit_ids = list(map(cls.random_urlsafe_str_to_bytes,
  384                            audit_ids))
  385         return (b_user_id, methods, expires_at_int, b_audit_ids)
  386 
  387     @classmethod
  388     def disassemble(cls, payload):
  389         (is_stored_as_bytes, user_id) = payload[0]
  390         user_id = cls._convert_or_decode(is_stored_as_bytes, user_id)
  391         methods = auth_plugins.convert_integer_to_method_list(payload[1])
  392         expires_at_str = cls._convert_float_to_time_string(payload[2])
  393         audit_ids = list(map(cls.base64_encode, payload[3]))
  394         system = None
  395         project_id = None
  396         domain_id = None
  397         trust_id = None
  398         federated_group_ids = None
  399         identity_provider_id = None
  400         protocol_id = None
  401         access_token_id = None
  402         app_cred_id = None
  403         return (user_id, methods, system, project_id, domain_id,
  404                 expires_at_str, audit_ids, trust_id, federated_group_ids,
  405                 identity_provider_id, protocol_id, access_token_id,
  406                 app_cred_id)
  407 
  408 
  409 class DomainScopedPayload(BasePayload):
  410     version = 1
  411 
  412     @classmethod
  413     def assemble(cls, user_id, methods, system, project_id, domain_id,
  414                  expires_at, audit_ids, trust_id, federated_group_ids,
  415                  identity_provider_id, protocol_id, access_token_id,
  416                  app_cred_id):
  417         b_user_id = cls.attempt_convert_uuid_hex_to_bytes(user_id)
  418         methods = auth_plugins.convert_method_list_to_integer(methods)
  419         try:
  420             b_domain_id = cls.convert_uuid_hex_to_bytes(domain_id)
  421         except ValueError:
  422             # the default domain ID is configurable, and probably isn't a UUID
  423             if domain_id == CONF.identity.default_domain_id:
  424                 b_domain_id = domain_id
  425             else:
  426                 raise
  427         expires_at_int = cls._convert_time_string_to_float(expires_at)
  428         b_audit_ids = list(map(cls.random_urlsafe_str_to_bytes,
  429                            audit_ids))
  430         return (b_user_id, methods, b_domain_id, expires_at_int, b_audit_ids)
  431 
  432     @classmethod
  433     def disassemble(cls, payload):
  434         (is_stored_as_bytes, user_id) = payload[0]
  435         user_id = cls._convert_or_decode(is_stored_as_bytes, user_id)
  436         methods = auth_plugins.convert_integer_to_method_list(payload[1])
  437         try:
  438             domain_id = cls.convert_uuid_bytes_to_hex(payload[2])
  439         except ValueError:
  440             # the default domain ID is configurable, and probably isn't a UUID
  441             if isinstance(payload[2], bytes):
  442                 payload[2] = payload[2].decode('utf-8')
  443             if payload[2] == CONF.identity.default_domain_id:
  444                 domain_id = payload[2]
  445             else:
  446                 raise
  447         expires_at_str = cls._convert_float_to_time_string(payload[3])
  448         audit_ids = list(map(cls.base64_encode, payload[4]))
  449         system = None
  450         project_id = None
  451         trust_id = None
  452         federated_group_ids = None
  453         identity_provider_id = None
  454         protocol_id = None
  455         access_token_id = None
  456         app_cred_id = None
  457         return (user_id, methods, system, project_id, domain_id,
  458                 expires_at_str, audit_ids, trust_id, federated_group_ids,
  459                 identity_provider_id, protocol_id, access_token_id,
  460                 app_cred_id)
  461 
  462 
  463 class ProjectScopedPayload(BasePayload):
  464     version = 2
  465 
  466     @classmethod
  467     def assemble(cls, user_id, methods, system, project_id, domain_id,
  468                  expires_at, audit_ids, trust_id, federated_group_ids,
  469                  identity_provider_id, protocol_id, access_token_id,
  470                  app_cred_id):
  471         b_user_id = cls.attempt_convert_uuid_hex_to_bytes(user_id)
  472         methods = auth_plugins.convert_method_list_to_integer(methods)
  473         b_project_id = cls.attempt_convert_uuid_hex_to_bytes(project_id)
  474         expires_at_int = cls._convert_time_string_to_float(expires_at)
  475         b_audit_ids = list(map(cls.random_urlsafe_str_to_bytes,
  476                            audit_ids))
  477         return (b_user_id, methods, b_project_id, expires_at_int, b_audit_ids)
  478 
  479     @classmethod
  480     def disassemble(cls, payload):
  481         (is_stored_as_bytes, user_id) = payload[0]
  482         user_id = cls._convert_or_decode(is_stored_as_bytes, user_id)
  483         methods = auth_plugins.convert_integer_to_method_list(payload[1])
  484         (is_stored_as_bytes, project_id) = payload[2]
  485         project_id = cls._convert_or_decode(is_stored_as_bytes, project_id)
  486         expires_at_str = cls._convert_float_to_time_string(payload[3])
  487         audit_ids = list(map(cls.base64_encode, payload[4]))
  488         system = None
  489         domain_id = None
  490         trust_id = None
  491         federated_group_ids = None
  492         identity_provider_id = None
  493         protocol_id = None
  494         access_token_id = None
  495         app_cred_id = None
  496         return (user_id, methods, system, project_id, domain_id,
  497                 expires_at_str, audit_ids, trust_id, federated_group_ids,
  498                 identity_provider_id, protocol_id, access_token_id,
  499                 app_cred_id)
  500 
  501 
  502 class TrustScopedPayload(BasePayload):
  503     version = 3
  504 
  505     @classmethod
  506     def assemble(cls, user_id, methods, system, project_id, domain_id,
  507                  expires_at, audit_ids, trust_id, federated_group_ids,
  508                  identity_provider_id, protocol_id, access_token_id,
  509                  app_cred_id):
  510         b_user_id = cls.attempt_convert_uuid_hex_to_bytes(user_id)
  511         methods = auth_plugins.convert_method_list_to_integer(methods)
  512         b_project_id = cls.attempt_convert_uuid_hex_to_bytes(project_id)
  513         b_trust_id = cls.convert_uuid_hex_to_bytes(trust_id)
  514         expires_at_int = cls._convert_time_string_to_float(expires_at)
  515         b_audit_ids = list(map(cls.random_urlsafe_str_to_bytes,
  516                            audit_ids))
  517 
  518         return (b_user_id, methods, b_project_id, expires_at_int, b_audit_ids,
  519                 b_trust_id)
  520 
  521     @classmethod
  522     def disassemble(cls, payload):
  523         (is_stored_as_bytes, user_id) = payload[0]
  524         user_id = cls._convert_or_decode(is_stored_as_bytes, user_id)
  525         methods = auth_plugins.convert_integer_to_method_list(payload[1])
  526         (is_stored_as_bytes, project_id) = payload[2]
  527         project_id = cls._convert_or_decode(is_stored_as_bytes, project_id)
  528         expires_at_str = cls._convert_float_to_time_string(payload[3])
  529         audit_ids = list(map(cls.base64_encode, payload[4]))
  530         trust_id = cls.convert_uuid_bytes_to_hex(payload[5])
  531         system = None
  532         domain_id = None
  533         federated_group_ids = None
  534         identity_provider_id = None
  535         protocol_id = None
  536         access_token_id = None
  537         app_cred_id = None
  538         return (user_id, methods, system, project_id, domain_id,
  539                 expires_at_str, audit_ids, trust_id, federated_group_ids,
  540                 identity_provider_id, protocol_id, access_token_id,
  541                 app_cred_id)
  542 
  543 
  544 class FederatedUnscopedPayload(BasePayload):
  545     version = 4
  546 
  547     @classmethod
  548     def pack_group_id(cls, group_dict):
  549         return cls.attempt_convert_uuid_hex_to_bytes(group_dict['id'])
  550 
  551     @classmethod
  552     def unpack_group_id(cls, group_id_in_bytes):
  553         (is_stored_as_bytes, group_id) = group_id_in_bytes
  554         group_id = cls._convert_or_decode(is_stored_as_bytes, group_id)
  555         return {'id': group_id}
  556 
  557     @classmethod
  558     def assemble(cls, user_id, methods, system, project_id, domain_id,
  559                  expires_at, audit_ids, trust_id, federated_group_ids,
  560                  identity_provider_id, protocol_id, access_token_id,
  561                  app_cred_id):
  562         b_user_id = cls.attempt_convert_uuid_hex_to_bytes(user_id)
  563         methods = auth_plugins.convert_method_list_to_integer(methods)
  564         b_group_ids = list(map(cls.pack_group_id, federated_group_ids))
  565         b_idp_id = cls.attempt_convert_uuid_hex_to_bytes(identity_provider_id)
  566         expires_at_int = cls._convert_time_string_to_float(expires_at)
  567         b_audit_ids = list(map(cls.random_urlsafe_str_to_bytes,
  568                                audit_ids))
  569 
  570         return (b_user_id, methods, b_group_ids, b_idp_id, protocol_id,
  571                 expires_at_int, b_audit_ids)
  572 
  573     @classmethod
  574     def disassemble(cls, payload):
  575         (is_stored_as_bytes, user_id) = payload[0]
  576         user_id = cls._convert_or_decode(is_stored_as_bytes, user_id)
  577         methods = auth_plugins.convert_integer_to_method_list(payload[1])
  578         group_ids = list(map(cls.unpack_group_id, payload[2]))
  579         (is_stored_as_bytes, idp_id) = payload[3]
  580         idp_id = cls._convert_or_decode(is_stored_as_bytes, idp_id)
  581         protocol_id = payload[4]
  582         if isinstance(protocol_id, bytes):
  583             protocol_id = protocol_id.decode('utf-8')
  584         expires_at_str = cls._convert_float_to_time_string(payload[5])
  585         audit_ids = list(map(cls.base64_encode, payload[6]))
  586         system = None
  587         project_id = None
  588         domain_id = None
  589         trust_id = None
  590         access_token_id = None
  591         app_cred_id = None
  592         return (user_id, methods, system, project_id, domain_id,
  593                 expires_at_str, audit_ids, trust_id, group_ids, idp_id,
  594                 protocol_id, access_token_id, app_cred_id)
  595 
  596 
  597 class FederatedScopedPayload(FederatedUnscopedPayload):
  598     version = None
  599 
  600     @classmethod
  601     def assemble(cls, user_id, methods, system, project_id, domain_id,
  602                  expires_at, audit_ids, trust_id, federated_group_ids,
  603                  identity_provider_id, protocol_id, access_token_id,
  604                  app_cred_id):
  605         b_user_id = cls.attempt_convert_uuid_hex_to_bytes(user_id)
  606         methods = auth_plugins.convert_method_list_to_integer(methods)
  607         b_scope_id = cls.attempt_convert_uuid_hex_to_bytes(
  608             project_id or domain_id)
  609         b_group_ids = list(map(cls.pack_group_id, federated_group_ids))
  610         b_idp_id = cls.attempt_convert_uuid_hex_to_bytes(identity_provider_id)
  611         expires_at_int = cls._convert_time_string_to_float(expires_at)
  612         b_audit_ids = list(map(cls.random_urlsafe_str_to_bytes,
  613                                audit_ids))
  614 
  615         return (b_user_id, methods, b_scope_id, b_group_ids, b_idp_id,
  616                 protocol_id, expires_at_int, b_audit_ids)
  617 
  618     @classmethod
  619     def disassemble(cls, payload):
  620         (is_stored_as_bytes, user_id) = payload[0]
  621         user_id = cls._convert_or_decode(is_stored_as_bytes, user_id)
  622         methods = auth_plugins.convert_integer_to_method_list(payload[1])
  623         (is_stored_as_bytes, scope_id) = payload[2]
  624         scope_id = cls._convert_or_decode(is_stored_as_bytes, scope_id)
  625         project_id = (
  626             scope_id
  627             if cls.version == FederatedProjectScopedPayload.version else None)
  628         domain_id = (
  629             scope_id
  630             if cls.version == FederatedDomainScopedPayload.version else None)
  631         group_ids = list(map(cls.unpack_group_id, payload[3]))
  632         (is_stored_as_bytes, idp_id) = payload[4]
  633         idp_id = cls._convert_or_decode(is_stored_as_bytes, idp_id)
  634         protocol_id = payload[5]
  635         if isinstance(protocol_id, bytes):
  636             protocol_id = protocol_id.decode('utf-8')
  637         expires_at_str = cls._convert_float_to_time_string(payload[6])
  638         audit_ids = list(map(cls.base64_encode, payload[7]))
  639         system = None
  640         trust_id = None
  641         access_token_id = None
  642         app_cred_id = None
  643         return (user_id, methods, system, project_id, domain_id,
  644                 expires_at_str, audit_ids, trust_id, group_ids, idp_id,
  645                 protocol_id, access_token_id, app_cred_id)
  646 
  647 
  648 class FederatedProjectScopedPayload(FederatedScopedPayload):
  649     version = 5
  650 
  651 
  652 class FederatedDomainScopedPayload(FederatedScopedPayload):
  653     version = 6
  654 
  655 
  656 class OauthScopedPayload(BasePayload):
  657     version = 7
  658 
  659     @classmethod
  660     def assemble(cls, user_id, methods, system, project_id, domain_id,
  661                  expires_at, audit_ids, trust_id, federated_group_ids,
  662                  identity_provider_id, protocol_id, access_token_id,
  663                  app_cred_id):
  664         b_user_id = cls.attempt_convert_uuid_hex_to_bytes(user_id)
  665         methods = auth_plugins.convert_method_list_to_integer(methods)
  666         b_project_id = cls.attempt_convert_uuid_hex_to_bytes(project_id)
  667         expires_at_int = cls._convert_time_string_to_float(expires_at)
  668         b_audit_ids = list(map(cls.random_urlsafe_str_to_bytes,
  669                            audit_ids))
  670         b_access_token_id = cls.attempt_convert_uuid_hex_to_bytes(
  671             access_token_id)
  672         return (b_user_id, methods, b_project_id, b_access_token_id,
  673                 expires_at_int, b_audit_ids)
  674 
  675     @classmethod
  676     def disassemble(cls, payload):
  677         (is_stored_as_bytes, user_id) = payload[0]
  678         user_id = cls._convert_or_decode(is_stored_as_bytes, user_id)
  679         methods = auth_plugins.convert_integer_to_method_list(payload[1])
  680         (is_stored_as_bytes, project_id) = payload[2]
  681         project_id = cls._convert_or_decode(is_stored_as_bytes, project_id)
  682         (is_stored_as_bytes, access_token_id) = payload[3]
  683         access_token_id = cls._convert_or_decode(is_stored_as_bytes,
  684                                                  access_token_id)
  685         expires_at_str = cls._convert_float_to_time_string(payload[4])
  686         audit_ids = list(map(cls.base64_encode, payload[5]))
  687         system = None
  688         domain_id = None
  689         trust_id = None
  690         federated_group_ids = None
  691         identity_provider_id = None
  692         protocol_id = None
  693         app_cred_id = None
  694 
  695         return (user_id, methods, system, project_id, domain_id,
  696                 expires_at_str, audit_ids, trust_id, federated_group_ids,
  697                 identity_provider_id, protocol_id, access_token_id,
  698                 app_cred_id)
  699 
  700 
  701 class SystemScopedPayload(BasePayload):
  702     version = 8
  703 
  704     @classmethod
  705     def assemble(cls, user_id, methods, system, project_id, domain_id,
  706                  expires_at, audit_ids, trust_id, federated_group_ids,
  707                  identity_provider_id, protocol_id, access_token_id,
  708                  app_cred_id):
  709         b_user_id = cls.attempt_convert_uuid_hex_to_bytes(user_id)
  710         methods = auth_plugins.convert_method_list_to_integer(methods)
  711         expires_at_int = cls._convert_time_string_to_float(expires_at)
  712         b_audit_ids = list(map(cls.random_urlsafe_str_to_bytes,
  713                            audit_ids))
  714         return (b_user_id, methods, system, expires_at_int, b_audit_ids)
  715 
  716     @classmethod
  717     def disassemble(cls, payload):
  718         (is_stored_as_bytes, user_id) = payload[0]
  719         user_id = cls._convert_or_decode(is_stored_as_bytes, user_id)
  720         methods = auth_plugins.convert_integer_to_method_list(payload[1])
  721         system = payload[2]
  722         expires_at_str = cls._convert_float_to_time_string(payload[3])
  723         audit_ids = list(map(cls.base64_encode, payload[4]))
  724         project_id = None
  725         domain_id = None
  726         trust_id = None
  727         federated_group_ids = None
  728         identity_provider_id = None
  729         protocol_id = None
  730         access_token_id = None
  731         app_cred_id = None
  732         return (user_id, methods, system, project_id, domain_id,
  733                 expires_at_str, audit_ids, trust_id, federated_group_ids,
  734                 identity_provider_id, protocol_id, access_token_id,
  735                 app_cred_id)
  736 
  737 
  738 class ApplicationCredentialScopedPayload(BasePayload):
  739     version = 9
  740 
  741     @classmethod
  742     def assemble(cls, user_id, methods, system, project_id, domain_id,
  743                  expires_at, audit_ids, trust_id, federated_group_ids,
  744                  identity_provider_id, protocol_id, access_token_id,
  745                  app_cred_id):
  746         b_user_id = cls.attempt_convert_uuid_hex_to_bytes(user_id)
  747         methods = auth_plugins.convert_method_list_to_integer(methods)
  748         b_project_id = cls.attempt_convert_uuid_hex_to_bytes(project_id)
  749         expires_at_int = cls._convert_time_string_to_float(expires_at)
  750         b_audit_ids = list(map(cls.random_urlsafe_str_to_bytes,
  751                            audit_ids))
  752         b_app_cred_id = cls.attempt_convert_uuid_hex_to_bytes(app_cred_id)
  753         return (b_user_id, methods, b_project_id, expires_at_int, b_audit_ids,
  754                 b_app_cred_id)
  755 
  756     @classmethod
  757     def disassemble(cls, payload):
  758         (is_stored_as_bytes, user_id) = payload[0]
  759         user_id = cls._convert_or_decode(is_stored_as_bytes, user_id)
  760         methods = auth_plugins.convert_integer_to_method_list(payload[1])
  761         (is_stored_as_bytes, project_id) = payload[2]
  762         project_id = cls._convert_or_decode(is_stored_as_bytes, project_id)
  763         expires_at_str = cls._convert_float_to_time_string(payload[3])
  764         audit_ids = list(map(cls.base64_encode, payload[4]))
  765         system = None
  766         domain_id = None
  767         trust_id = None
  768         federated_group_ids = None
  769         identity_provider_id = None
  770         protocol_id = None
  771         access_token_id = None
  772         (is_stored_as_bytes, app_cred_id) = payload[5]
  773         app_cred_id = cls._convert_or_decode(is_stored_as_bytes, app_cred_id)
  774         return (user_id, methods, system, project_id, domain_id,
  775                 expires_at_str, audit_ids, trust_id, federated_group_ids,
  776                 identity_provider_id, protocol_id, access_token_id,
  777                 app_cred_id)
  778 
  779 
  780 _PAYLOAD_CLASSES = [
  781     UnscopedPayload,
  782     DomainScopedPayload,
  783     ProjectScopedPayload,
  784     TrustScopedPayload,
  785     FederatedUnscopedPayload,
  786     FederatedProjectScopedPayload,
  787     FederatedDomainScopedPayload,
  788     OauthScopedPayload,
  789     SystemScopedPayload,
  790     ApplicationCredentialScopedPayload,
  791 ]