124 lines
3.5 KiB
Python
124 lines
3.5 KiB
Python
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()
|
|
)
|