PDFCLOUD-5556 Add client methods for digital signatures in PDF#21
PDFCLOUD-5556 Add client methods for digital signatures in PDF#21datalogics-cgreen wants to merge 3 commits intopdfrest:mainfrom
Conversation
0de94ba to
c5cfde8
Compare
datalogics-kam
left a comment
There was a problem hiding this comment.
Requesting changes
- Proper use of Pydantic dumping
- Bool | none code smell
- Mismatch between payload optionality vs typing of the public interface
- Validation should be done in Pydantic models, not client.py.
| def _serialize_signature_configuration( | ||
| value: _PdfSignatureConfigurationModel, | ||
| ) -> str: | ||
| payload = value.model_dump(mode="json", exclude_none=True) |
There was a problem hiding this comment.
Use model_dump_json instead of model_dump followed by json.dumps.
| x: str | int | float | ||
| y: str | int | float |
| x: str | int | float | ||
| y: str | int | float |
There was a problem hiding this comment.
Use float as a type here.
| class PdfSignatureLocation(TypedDict): | ||
| bottom_left: Required[PdfSignaturePoint] | ||
| top_right: Required[PdfSignaturePoint] | ||
| page: Required[str | int] |
There was a problem hiding this comment.
I'm wondering...can this still be typed...maybe a Pydantic annotation won't help on the int but probably that str is a Literal really.
| if "pfx" in credential_map and "certificate" in credential_map: | ||
| msg = "Provide either PFX credentials or certificate/private_key, not both." | ||
| raise PdfRestConfigurationError(msg) | ||
| if "pfx" not in credential_map and "certificate" not in credential_map: | ||
| msg = "Either PFX credentials (pfx + passphrase) or certificate/private_key credentials are required." | ||
| raise PdfRestConfigurationError(msg) | ||
| if "pfx" in credential_map: | ||
| payload["pfx_credential"] = credential_map["pfx"] | ||
| try: | ||
| payload["pfx_passphrase"] = credential_map["passphrase"] | ||
| except KeyError as exc: | ||
| msg = "PFX credentials require both 'pfx' and 'passphrase' files." | ||
| raise PdfRestConfigurationError(msg) from exc | ||
| elif "certificate" in credential_map: | ||
| payload["certificate"] = credential_map["certificate"] | ||
| try: | ||
| payload["private_key"] = credential_map["private_key"] | ||
| except KeyError as exc: | ||
| msg = "Certificate credentials require both 'certificate' and 'private_key' files." | ||
| raise PdfRestConfigurationError(msg) from exc |
There was a problem hiding this comment.
All this should be on a Payload validator.
| if "pfx" in credential_map and "certificate" in credential_map: | |
| msg = "Provide either PFX credentials or certificate/private_key, not both." | |
| raise PdfRestConfigurationError(msg) | |
| if "pfx" not in credential_map and "certificate" not in credential_map: | |
| msg = "Either PFX credentials (pfx + passphrase) or certificate/private_key credentials are required." | |
| raise PdfRestConfigurationError(msg) | |
| if "pfx" in credential_map: | |
| payload["pfx_credential"] = credential_map["pfx"] | |
| try: | |
| payload["pfx_passphrase"] = credential_map["passphrase"] | |
| except KeyError as exc: | |
| msg = "PFX credentials require both 'pfx' and 'passphrase' files." | |
| raise PdfRestConfigurationError(msg) from exc | |
| elif "certificate" in credential_map: | |
| payload["certificate"] = credential_map["certificate"] | |
| try: | |
| payload["private_key"] = credential_map["private_key"] | |
| except KeyError as exc: | |
| msg = "Certificate credentials require both 'certificate' and 'private_key' files." | |
| raise PdfRestConfigurationError(msg) from exc | |
| # Move to payload validator |
| if "pfx" in credential_map and "certificate" in credential_map: | ||
| msg = "Provide either PFX credentials or certificate/private_key, not both." | ||
| raise PdfRestConfigurationError(msg) | ||
| if "pfx" not in credential_map and "certificate" not in credential_map: | ||
| msg = "Either PFX credentials (pfx + passphrase) or certificate/private_key credentials are required." | ||
| raise PdfRestConfigurationError(msg) | ||
| if "pfx" in credential_map: | ||
| payload["pfx_credential"] = credential_map["pfx"] | ||
| try: | ||
| payload["pfx_passphrase"] = credential_map["passphrase"] | ||
| except KeyError as exc: | ||
| msg = "PFX credentials require both 'pfx' and 'passphrase' files." | ||
| raise PdfRestConfigurationError(msg) from exc | ||
| elif "certificate" in credential_map: | ||
| payload["certificate"] = credential_map["certificate"] | ||
| try: | ||
| payload["private_key"] = credential_map["private_key"] | ||
| except KeyError as exc: | ||
| msg = "Certificate credentials require both 'certificate' and 'private_key' files." | ||
| raise PdfRestConfigurationError(msg) from exc |
There was a problem hiding this comment.
All this should be done by validating the payload, which can have optional fields, and check them either before or after the whole class is validated (I think before is for data mods, after is better for validity)
Not least because it duplicates 21 lines of complicated code.
| if "pfx" in credential_map and "certificate" in credential_map: | |
| msg = "Provide either PFX credentials or certificate/private_key, not both." | |
| raise PdfRestConfigurationError(msg) | |
| if "pfx" not in credential_map and "certificate" not in credential_map: | |
| msg = "Either PFX credentials (pfx + passphrase) or certificate/private_key credentials are required." | |
| raise PdfRestConfigurationError(msg) | |
| if "pfx" in credential_map: | |
| payload["pfx_credential"] = credential_map["pfx"] | |
| try: | |
| payload["pfx_passphrase"] = credential_map["passphrase"] | |
| except KeyError as exc: | |
| msg = "PFX credentials require both 'pfx' and 'passphrase' files." | |
| raise PdfRestConfigurationError(msg) from exc | |
| elif "certificate" in credential_map: | |
| payload["certificate"] = credential_map["certificate"] | |
| try: | |
| payload["private_key"] = credential_map["private_key"] | |
| except KeyError as exc: | |
| msg = "Certificate credentials require both 'certificate' and 'private_key' files." | |
| raise PdfRestConfigurationError(msg) from exc | |
| # Move to payload validator |
|
|
||
| class _PdfSignatureDisplayModel(BaseModel): | ||
| include_distinguished_name: bool | None = None | ||
| include_datetime: bool | None = None |
There was a problem hiding this comment.
Are these bools really optional? Optional bool can be confusing...and the TypedDict doesn't have optional values. This is the part that validates, and makes sure that at runtime, the TypedDict got passed the correct values.
type checking -> IDE time
validation -> runtime
| contact: str | None = None | ||
| location: str | None = None | ||
| name: str | None = None | ||
| reason: str | None = None |
There was a problem hiding this comment.
According to the types on PdfSignatureDisplay, these are not optional. They should validate how they're typed on the public interface.
There was a problem hiding this comment.
These are meant to be optional. If they are not wanted, then they should be omitted from the request.
|
|
||
| class _PdfSignatureConfigurationModel(BaseModel): | ||
| type: Literal["new"] | ||
| name: str | None = None |
There was a problem hiding this comment.
But the public interface says this isn't optional...
There was a problem hiding this comment.
It's required.
| timeout=timeout, | ||
| ) | ||
|
|
||
| def sign_pdf( |
There was a problem hiding this comment.
Since I was on the topic of "things we split into separate functions for clarity", pdfAssistant has
pdfassistant_chatbot/functions/signed_pdf.py
255:async def digitally_sign_pdf_with_pfx(
362:async def digitally_sign_pdf_with_non_pfx(
...should that idea be surfaced here? Just trying to keep the interface grounded.
There was a problem hiding this comment.
This is what I have now done with Watermark PDF and what I'm currently working on for /pdf.
Assisted-by: Codex
Assisted-by: Codex
c5cfde8 to
503c84c
Compare
|
Replaced by #26 with some additional fixes. |
Adds client support for the Sign PDF endpoint, including new payload models and public types in the pdfrest package.
Adds comprehensive Sign PDF tests (unit + live) and supporting test fixtures/resources for signing credentials and assets.