File: //lib/python3/dist-packages/josepy/util.py
"""JOSE utilities."""
try:
    from collections.abc import Hashable, Mapping  # pylint: disable=import-error
except ImportError:
    from collections import Hashable, Mapping
import OpenSSL
import six
from cryptography.hazmat.primitives.asymmetric import rsa
class abstractclassmethod(classmethod):
    # pylint: disable=invalid-name,too-few-public-methods
    """Descriptor for an abstract classmethod.
    It augments the :mod:`abc` framework with an abstract
    classmethod. This is implemented as :class:`abc.abstractclassmethod`
    in the standard Python library starting with version 3.2.
    This particular implementation, allegedly based on Python 3.3 source
    code, is stolen from
    http://stackoverflow.com/questions/11217878/python-2-7-combine-abc-abstractmethod-and-classmethod.
    """
    __isabstractmethod__ = True
    def __init__(self, target):
        target.__isabstractmethod__ = True
        super(abstractclassmethod, self).__init__(target)
class ComparableX509(object):  # pylint: disable=too-few-public-methods
    """Wrapper for OpenSSL.crypto.X509** objects that supports __eq__.
    :ivar wrapped: Wrapped certificate or certificate request.
    :type wrapped: `OpenSSL.crypto.X509` or `OpenSSL.crypto.X509Req`.
    """
    def __init__(self, wrapped):
        assert isinstance(wrapped, OpenSSL.crypto.X509) or isinstance(
            wrapped, OpenSSL.crypto.X509Req)
        self.wrapped = wrapped
    def __getattr__(self, name):
        return getattr(self.wrapped, name)
    def _dump(self, filetype=OpenSSL.crypto.FILETYPE_ASN1):
        """Dumps the object into a buffer with the specified encoding.
        :param int filetype: The desired encoding. Should be one of
            `OpenSSL.crypto.FILETYPE_ASN1`,
            `OpenSSL.crypto.FILETYPE_PEM`, or
            `OpenSSL.crypto.FILETYPE_TEXT`.
        :returns: Encoded X509 object.
        :rtype: str
        """
        if isinstance(self.wrapped, OpenSSL.crypto.X509):
            func = OpenSSL.crypto.dump_certificate
        else:  # assert in __init__ makes sure this is X509Req
            func = OpenSSL.crypto.dump_certificate_request
        return func(filetype, self.wrapped)
    def __eq__(self, other):
        if not isinstance(other, self.__class__):
            return NotImplemented
        # pylint: disable=protected-access
        return self._dump() == other._dump()
    def __hash__(self):
        return hash((self.__class__, self._dump()))
    def __ne__(self, other):
        return not self == other
    def __repr__(self):
        return '<{0}({1!r})>'.format(self.__class__.__name__, self.wrapped)
class ComparableKey(object):  # pylint: disable=too-few-public-methods
    """Comparable wrapper for ``cryptography`` keys.
    See https://github.com/pyca/cryptography/issues/2122.
    """
    __hash__ = NotImplemented
    def __init__(self, wrapped):
        self._wrapped = wrapped
    def __getattr__(self, name):
        return getattr(self._wrapped, name)
    def __eq__(self, other):
        # pylint: disable=protected-access
        if (not isinstance(other, self.__class__) or
                self._wrapped.__class__ is not other._wrapped.__class__):
            return NotImplemented
        elif hasattr(self._wrapped, 'private_numbers'):
            return self.private_numbers() == other.private_numbers()
        elif hasattr(self._wrapped, 'public_numbers'):
            return self.public_numbers() == other.public_numbers()
        else:
            return NotImplemented
    def __ne__(self, other):
        return not self == other
    def __repr__(self):
        return '<{0}({1!r})>'.format(self.__class__.__name__, self._wrapped)
    def public_key(self):
        """Get wrapped public key."""
        return self.__class__(self._wrapped.public_key())
class ComparableRSAKey(ComparableKey):  # pylint: disable=too-few-public-methods
    """Wrapper for ``cryptography`` RSA keys.
    Wraps around:
    - :class:`~cryptography.hazmat.primitives.asymmetric.rsa.RSAPrivateKey`
    - :class:`~cryptography.hazmat.primitives.asymmetric.rsa.RSAPublicKey`
    """
    def __hash__(self):
        # public_numbers() hasn't got stable hash!
        # https://github.com/pyca/cryptography/issues/2143
        if isinstance(self._wrapped, rsa.RSAPrivateKeyWithSerialization):
            priv = self.private_numbers()
            pub = priv.public_numbers
            return hash((self.__class__, priv.p, priv.q, priv.dmp1,
                         priv.dmq1, priv.iqmp, pub.n, pub.e))
        elif isinstance(self._wrapped, rsa.RSAPublicKeyWithSerialization):
            pub = self.public_numbers()
            return hash((self.__class__, pub.n, pub.e))
class ImmutableMap(Mapping, Hashable):  # type: ignore
    # pylint: disable=too-few-public-methods
    """Immutable key to value mapping with attribute access."""
    __slots__ = ()
    """Must be overridden in subclasses."""
    def __init__(self, **kwargs):
        if set(kwargs) != set(self.__slots__):
            raise TypeError(
                '__init__() takes exactly the following arguments: {0} '
                '({1} given)'.format(', '.join(self.__slots__),
                                     ', '.join(kwargs) if kwargs else 'none'))
        for slot in self.__slots__:
            object.__setattr__(self, slot, kwargs.pop(slot))
    def update(self, **kwargs):
        """Return updated map."""
        items = dict(self)
        items.update(kwargs)
        return type(self)(**items)  # pylint: disable=star-args
    def __getitem__(self, key):
        try:
            return getattr(self, key)
        except AttributeError:
            raise KeyError(key)
    def __iter__(self):
        return iter(self.__slots__)
    def __len__(self):
        return len(self.__slots__)
    def __hash__(self):
        return hash(tuple(getattr(self, slot) for slot in self.__slots__))
    def __setattr__(self, name, value):
        raise AttributeError("can't set attribute")
    def __repr__(self):
        return '{0}({1})'.format(self.__class__.__name__, ', '.join(
            '{0}={1!r}'.format(key, value)
            for key, value in six.iteritems(self)))
class frozendict(Mapping, Hashable):  # type: ignore
    # pylint: disable=invalid-name,too-few-public-methods
    """Frozen dictionary."""
    __slots__ = ('_items', '_keys')
    def __init__(self, *args, **kwargs):
        if kwargs and not args:
            items = dict(kwargs)
        elif len(args) == 1 and isinstance(args[0], Mapping):
            items = args[0]
        else:
            raise TypeError()
        # TODO: support generators/iterators
        object.__setattr__(self, '_items', items)
        object.__setattr__(self, '_keys', tuple(sorted(six.iterkeys(items))))
    def __getitem__(self, key):
        return self._items[key]
    def __iter__(self):
        return iter(self._keys)
    def __len__(self):
        return len(self._items)
    def _sorted_items(self):
        return tuple((key, self[key]) for key in self._keys)
    def __hash__(self):
        return hash(self._sorted_items())
    def __getattr__(self, name):
        try:
            return self._items[name]
        except KeyError:
            raise AttributeError(name)
    def __setattr__(self, name, value):
        raise AttributeError("can't set attribute")
    def __repr__(self):
        return 'frozendict({0})'.format(', '.join('{0}={1!r}'.format(
            key, value) for key, value in self._sorted_items()))