diff --git a/froide/foirequest/api_views/attachment.py b/froide/foirequest/api_views/attachment.py index f0610650e..192198016 100644 --- a/froide/foirequest/api_views/attachment.py +++ b/froide/foirequest/api_views/attachment.py @@ -14,7 +14,7 @@ from froide.campaign.models import Campaign from froide.document.api_views import DocumentSerializer from froide.foirequest.models.message import FoiMessage -from froide.foirequest.utils import find_attachment_name +from froide.foirequest.utils import create_decrypted_attachment, find_attachment_name from froide.helper.auth import is_crew from froide.helper.storage import make_unique_filename from froide.helper.text_utils import slugify @@ -28,11 +28,15 @@ from ..models import FoiAttachment, FoiEvent from ..permissions import WriteFoiRequestPermission from ..serializers import ( + DecryptAttachmentSerializer, FoiAttachmentSerializer, FoiAttachmentTusSerializer, ImageAttachmentConverterSerializer, ) -from ..tasks import convert_images_to_pdf_api_task, move_upload_to_attachment +from ..tasks import ( + convert_images_to_pdf_api_task, + move_upload_to_attachment, +) User = get_user_model() @@ -337,3 +341,34 @@ def convert_to_pdf(self, request, pk=None): ) else: return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) + + @extend_schema( + responses={status.HTTP_201_CREATED: FoiAttachmentSerializer}, + operation_id="decrypt_pdf", + ) + @action( + detail=True, + methods=["post"], + url_path="decrypt-pdf", + serializer_class=DecryptAttachmentSerializer, + ) + def decrypt_pdf(self, request, pk=None): + attachment = self.get_object() + if not attachment.is_pdf: + return Response( + {"detail": _("Attachment is not a PDF.")}, + status=status.HTTP_400_BAD_REQUEST, + ) + serializer = self.get_serializer(data=request.data) + if not serializer.is_valid(): + return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) + + data = serializer.validated_data + decrypted_att = create_decrypted_attachment( + attachment, data["password"], approved=data["approved"] + ) + + return Response( + FoiAttachmentSerializer(decrypted_att, context={"request": request}).data, + status=status.HTTP_201_CREATED, + ) diff --git a/froide/foirequest/serializers.py b/froide/foirequest/serializers.py index c6f80029b..9c2727b6a 100644 --- a/froide/foirequest/serializers.py +++ b/froide/foirequest/serializers.py @@ -534,6 +534,11 @@ class ImageAttachmentConverterSerializer(serializers.Serializer): message = FoiMessageRelatedField() +class DecryptAttachmentSerializer(serializers.Serializer): + password = serializers.CharField(required=True) + approved = serializers.BooleanField(default=False, required=False) + + def optimize_message_queryset(request, qs): atts = get_read_foiattachment_queryset( request, queryset=FoiAttachment.objects.filter(belongs_to__in=qs) diff --git a/froide/foirequest/tasks.py b/froide/foirequest/tasks.py index b85c777b4..96e5c5473 100644 --- a/froide/foirequest/tasks.py +++ b/froide/foirequest/tasks.py @@ -3,6 +3,7 @@ from collections.abc import Collection from datetime import timedelta from functools import partial +from typing import Optional from django.conf import settings from django.core.files.base import ContentFile @@ -15,7 +16,7 @@ from froide.celery import app as celery_app from froide.foirequest.models.event import FoiEvent -from froide.foirequest.utils import find_attachment_name +from froide.foirequest.utils import make_decrypted_attachment from froide.proof.models import Proof from froide.publicbody.models import PublicBody from froide.upload.models import Upload @@ -546,7 +547,9 @@ def warn_on_unprocessed_mail(): @celery_app.task( name="froide.foirequest.tasks.decrypt_pdf_attachment_task", time_limit=600 ) -def decrypt_pdf_attachment_task(att_id, password): +def decrypt_pdf_attachment_task( + att_id: int, decrypted_id: Optional[int] = None, password: str = "" +): from filingcabinet.pdf_utils import decrypt_pdf from filingcabinet.utils import get_local_file @@ -561,24 +564,35 @@ def decrypt_pdf_attachment_task(att_id, password): with get_local_file(att.file.path) as file_path: output_bytes = decrypt_pdf(file_path, password) + if decrypted_id is not None: + try: + decrypted_att = FoiAttachment.objects.get(pk=decrypted_id) + except FoiAttachment.DoesNotExist: + return + else: + decrypted_att = make_decrypted_attachment( + att, + att_kwargs={ + "approved": False, + }, + ) + if output_bytes is None: + # Decryption failed, let's delete the decrypted attachment if already saved + if decrypted_att.pk is not None: + decrypted_att.delete() + return - name, ext = os.path.splitext(att.name) - name = _("{name}_decrypted{ext}").format(name=name, ext=".pdf") - name = find_attachment_name(name, att.belongs_to) - - decrypted_att = FoiAttachment.objects.create( - name=name, - belongs_to=att.belongs_to, - approved=False, - filetype="application/pdf", - is_converted=True, - can_approve=att.can_approve, - ) new_file = ContentFile(output_bytes) decrypted_att.size = new_file.size + decrypted_att.pending = False decrypted_att.file.save(att.name, new_file, save=True) - att.converted = decrypted_att - att.save() + if att.converted != decrypted_att: + if att.converted is not None: + # Previously converted version is no longer needed + att.converted.remove_file_and_delete() + + att.converted = decrypted_att + att.save() diff --git a/froide/foirequest/templates/foirequest/redact.html b/froide/foirequest/templates/foirequest/redact.html index 972b15ee5..25b9374c5 100644 --- a/froide/foirequest/templates/foirequest/redact.html +++ b/froide/foirequest/templates/foirequest/redact.html @@ -22,9 +22,7 @@