Better, attempt removal later for ConsumableDocument

This commit is contained in:
shamoon
2025-12-30 12:30:34 -08:00
parent f7a6f79c8b
commit 880b3e6d15
3 changed files with 82 additions and 6 deletions

View File

@@ -793,11 +793,7 @@ def run_workflows(
logging_group, logging_group,
original_file, original_file,
) )
elif ( elif action.type == WorkflowAction.WorkflowActionType.PASSWORD_REMOVAL:
action.type == WorkflowAction.WorkflowActionType.PASSWORD_REMOVAL
and not use_overrides
):
# Password removal only makes sense on actual documents
execute_password_removal_action(action, document, logging_group) execute_password_removal_action(action, document, logging_group)
if not use_overrides: if not use_overrides:

View File

@@ -2,6 +2,7 @@ import datetime
import json import json
import shutil import shutil
import socket import socket
import tempfile
from datetime import timedelta from datetime import timedelta
from pathlib import Path from pathlib import Path
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
@@ -60,6 +61,7 @@ from documents.tests.utils import DirectoriesMixin
from documents.tests.utils import DummyProgressManager from documents.tests.utils import DummyProgressManager
from documents.tests.utils import FileSystemAssertsMixin from documents.tests.utils import FileSystemAssertsMixin
from documents.tests.utils import SampleDirMixin from documents.tests.utils import SampleDirMixin
from documents.workflows.actions import execute_password_removal_action
from paperless_mail.models import MailAccount from paperless_mail.models import MailAccount
from paperless_mail.models import MailRule from paperless_mail.models import MailRule
@@ -3641,6 +3643,68 @@ class TestWorkflows(
mock_remove_password.assert_not_called() mock_remove_password.assert_not_called()
@mock.patch("documents.bulk_edit.remove_password")
def test_password_removal_consumable_document_deferred(
self,
mock_remove_password,
):
action = WorkflowAction.objects.create(
type=WorkflowAction.WorkflowActionType.PASSWORD_REMOVAL,
passwords="first, second",
)
temp_dir = Path(tempfile.mkdtemp())
original_file = temp_dir / "file.pdf"
original_file.write_bytes(b"pdf content")
consumable = ConsumableDocument(
source=DocumentSource.ApiUpload,
original_file=original_file,
)
execute_password_removal_action(action, consumable, logging_group=None)
mock_remove_password.assert_not_called()
mock_remove_password.side_effect = [
ValueError("bad password"),
"OK",
]
doc = Document.objects.create(
checksum="pw-checksum-consumed",
title="Protected",
)
document_consumption_finished.send(
sender=self.__class__,
document=doc,
)
assert mock_remove_password.call_count == 2
mock_remove_password.assert_has_calls(
[
mock.call(
[doc.id],
password="first",
update_document=True,
user=doc.owner,
),
mock.call(
[doc.id],
password="second",
update_document=True,
user=doc.owner,
),
],
)
# ensure handler disconnected after first run
document_consumption_finished.send(
sender=self.__class__,
document=doc,
)
assert mock_remove_password.call_count == 2
class TestWebhookSend: class TestWebhookSend:
def test_send_webhook_data_or_json( def test_send_webhook_data_or_json(

View File

@@ -15,6 +15,7 @@ from documents.models import Document
from documents.models import DocumentType from documents.models import DocumentType
from documents.models import WorkflowAction from documents.models import WorkflowAction
from documents.models import WorkflowTrigger from documents.models import WorkflowTrigger
from documents.signals import document_consumption_finished
from documents.templating.workflows import parse_w_workflow_placeholders from documents.templating.workflows import parse_w_workflow_placeholders
from documents.workflows.webhooks import send_webhook from documents.workflows.webhooks import send_webhook
@@ -264,7 +265,7 @@ def execute_webhook_action(
def execute_password_removal_action( def execute_password_removal_action(
action: WorkflowAction, action: WorkflowAction,
document: Document, document: Document | ConsumableDocument,
logging_group, logging_group,
) -> None: ) -> None:
""" """
@@ -285,6 +286,21 @@ def execute_password_removal_action(
if password.strip() if password.strip()
] ]
if isinstance(document, ConsumableDocument):
# hook the consumption-finished signal to attempt password removal later
def handler(sender, **kwargs):
consumed_document: Document = kwargs.get("document")
if consumed_document is not None:
execute_password_removal_action(
action,
consumed_document,
logging_group,
)
document_consumption_finished.disconnect(handler)
document_consumption_finished.connect(handler, weak=False)
return
# import here to avoid circular dependency # import here to avoid circular dependency
from documents.bulk_edit import remove_password from documents.bulk_edit import remove_password