import abc from dataclasses import dataclass from typing import Dict, Type from pyhanko.pdf_utils import misc # TODO Consider maybe allowing a credential exporter that just dumps # the file encryption key, and would be compatible with any of the "built-in" # security handlers. @dataclass(frozen=True) class SerialisedCredential: """ A credential in serialised form. """ credential_type: str """ The registered type name of the credential (see :meth:`.SerialisableCredential.register`). """ data: bytes """ The credential data, as a byte string. """ class SerialisableCredential(abc.ABC): """ Class representing a credential that can be serialised. """ __registered_subclasses: Dict[str, Type['SerialisableCredential']] = dict() @classmethod def get_name(cls) -> str: """ Get the type name of the credential, which will be embedded into serialised values and used on deserialisation. """ raise NotImplementedError @staticmethod def register(cls: Type['SerialisableCredential']): """ Register a subclass into the credential serialisation registry, using the name returned by :meth:`get_name`. Can be used as a class decorator. :param cls: The subclass. :return: The subclass. """ SerialisableCredential.__registered_subclasses[cls.get_name()] = cls return cls def _ser_value(self) -> bytes: """ Serialise a value to raw binary data. To be overridden by subclasses. :return: A byte string :raises misc.PdfWriteError: If a serialisation error occurs. """ raise NotImplementedError @classmethod def _deser_value(cls, data: bytes): """ Deserialise a value from raw binary data. :param data: The data to deserialise. :return: The deserialised value (an instance of a subclass of :class:`.SerialisableCredential`) :raises misc.PdfReadError: If a deserialisation error occurs. """ raise NotImplementedError @staticmethod def deserialise( ser_value: SerialisedCredential, ) -> 'SerialisableCredential': """ Deserialise a :class:`.SerialisedCredential` value by looking up the proper subclass of :class:`.SerialisableCredential` and invoking its deserialisation method. :param ser_value: The value to deserialise. :return: The deserialised credential. :raises misc.PdfReadError: If a deserialisation error occurs. """ cred_type = ser_value.credential_type try: cls = SerialisableCredential.__registered_subclasses[cred_type] except KeyError: raise misc.PdfReadError( f"Failed to deserialise credential: " f"credential type '{cred_type}' not known." ) return cls._deser_value(ser_value.data) def serialise(self) -> SerialisedCredential: """ Serialise a value to an annotated :class:`.SerialisedCredential` value. :return: A :class:`.SerialisedCredential` value. :raises misc.PdfWriteError: If a serialisation error occurs. """ return SerialisedCredential( credential_type=self.__class__.get_name(), data=self._ser_value() )