diff --git a/src/paperless/settings.py b/src/paperless/settings.py index 000904aef..27c609e8d 100644 --- a/src/paperless/settings.py +++ b/src/paperless/settings.py @@ -1171,6 +1171,15 @@ if DEBUG: # pragma: no cover EMAIL_BACKEND = "django.core.mail.backends.filebased.EmailBackend" EMAIL_FILE_PATH = BASE_DIR / "sent_emails" +############################################################################### +# Email Preprocessors # +############################################################################### + +EMAIL_GNUPG_HOME: Final[Optional[str]] = os.getenv("PAPERLESS_EMAIL_GNUPG_HOME") +EMAIL_ENABLE_GPG_DECRYPTOR: Final[bool] = __get_boolean( + "PAPERLESS_ENABLE_GPG_DECRYPTOR", +) + ############################################################################### # Soft Delete diff --git a/src/paperless_mail/mail.py b/src/paperless_mail/mail.py index 93efdb844..84d916962 100644 --- a/src/paperless_mail/mail.py +++ b/src/paperless_mail/mail.py @@ -10,7 +10,6 @@ from datetime import timedelta from fnmatch import fnmatch from pathlib import Path from typing import TYPE_CHECKING -from typing import Callable from typing import Optional from typing import Union @@ -45,6 +44,7 @@ from paperless_mail.models import MailAccount from paperless_mail.models import MailRule from paperless_mail.models import ProcessedMail from paperless_mail.preprocessor import MailMessageDecryptor +from paperless_mail.preprocessor import MailMessagePreprocessor # Apple Mail sets multiple IMAP KEYWORD and the general "\Flagged" FLAG # imaplib => conn.fetch(b"", "FLAGS") @@ -428,12 +428,22 @@ class MailAccountHandler(LoggingMixin): logging_name = "paperless_mail" - _message_preprocessors: list[Callable[[MailMessage], MailMessage]] = [] + _message_preprocessor_types: list[type[MailMessagePreprocessor]] = [ + MailMessageDecryptor, + ] - def __init__(self, *gpgArgs, **gpgkwArgs) -> None: + def __init__(self) -> None: super().__init__() - self._message_preprocessors.append(MailMessageDecryptor(*gpgArgs, **gpgkwArgs)) self.renew_logging_group() + self._init_preprocessors() + + def _init_preprocessors(self): + self._message_preprocessors: list[MailMessagePreprocessor] = [] + for preprocessor_type in self._message_preprocessor_types: + if preprocessor_type.able_to_run(): + self._message_preprocessors.append(preprocessor_type()) + else: + self.log.debug(f"Skipping mail preprocessor {preprocessor_type.NAME}") def _correspondent_from_name(self, name: str) -> Optional[Correspondent]: try: @@ -542,7 +552,7 @@ class MailAccountHandler(LoggingMixin): def _preprocess_message(self, message: MailMessage): for preprocessor in self._message_preprocessors: - message = preprocessor(message) + message = preprocessor.run(message) return message def _handle_mail_rule( diff --git a/src/paperless_mail/preprocessor.py b/src/paperless_mail/preprocessor.py index 145a0f400..8a11697c0 100644 --- a/src/paperless_mail/preprocessor.py +++ b/src/paperless_mail/preprocessor.py @@ -1,22 +1,53 @@ +import abc from email import message_from_bytes from email import policy from email.message import Message +from django.conf import settings from gnupg import GPG from imap_tools import MailMessage from documents.loggers import LoggingMixin -class MailMessageDecryptor(LoggingMixin): +class MailMessagePreprocessor(abc.ABC): + """ + Defines the interface for preprocessors that alter messages before they are handled in MailAccountHandler + """ + + NAME: str = "MailMessagePreprocessor" + + @staticmethod + @abc.abstractmethod + def able_to_run() -> bool: + """ + Return True if the conditions are met for the preprocessor to run, False otherwise + + If False, run(message) will not be called + """ + + @abc.abstractmethod + def run(self, message: MailMessage) -> MailMessage: + """ + Performs the actual preprocessing task + """ + + +class MailMessageDecryptor(MailMessagePreprocessor, LoggingMixin): logging_name = "paperless_mail_message_decryptor" - def __init__(self, *args, **kwargs): + NAME = "MailMessageDecryptor" + + def __init__(self): super().__init__() self.renew_logging_group() - self._gpg = GPG(*args, **kwargs) + self._gpg = GPG(gnupghome=settings.EMAIL_GNUPG_HOME) - def __call__(self, message: MailMessage) -> MailMessage: + @staticmethod + def able_to_run() -> bool: + return settings.EMAIL_ENABLE_GPG_DECRYPTOR + + def run(self, message: MailMessage) -> MailMessage: if not hasattr(message, "obj"): self.log.debug("Message does not have 'obj' attribute") return message diff --git a/src/paperless_mail/tests/test_mail.py b/src/paperless_mail/tests/test_mail.py index 018bc8fc0..ced18eac5 100644 --- a/src/paperless_mail/tests/test_mail.py +++ b/src/paperless_mail/tests/test_mail.py @@ -16,6 +16,7 @@ import pytest from django.core.management import call_command from django.db import DatabaseError from django.test import TestCase +from django.test import override_settings from imap_tools import NOT from imap_tools import EmailAddress from imap_tools import FolderInfo @@ -273,10 +274,11 @@ class TestMail( self.reset_bogus_mailbox() self.messageEncryptor = MessageEncryptor() - - self.mail_account_handler = MailAccountHandler( - gnupghome=self.messageEncryptor.gpg_home, - ) + with override_settings( + EMAIL_GNUPG_HOME=self.messageEncryptor.gpg_home, + EMAIL_ENABLE_GPG_DECRYPTOR=True, + ): + self.mail_account_handler = MailAccountHandler() super().setUp() @@ -1368,10 +1370,13 @@ class TestMail( self.assertEqual(encrypted_message.attachments[0].filename, "encrypted.asc") self.assertEqual(encrypted_message.text, "") - message_decryptor = MailMessageDecryptor( - gnupghome=self.messageEncryptor.gpg_home, - ) - decrypted_message = message_decryptor(encrypted_message) + with override_settings( + EMAIL_ENABLE_GPG_DECRYPTOR=True, + EMAIL_GNUPG_HOME=self.messageEncryptor.gpg_home, + ): + message_decryptor = MailMessageDecryptor() + self.assertTrue(message_decryptor.able_to_run()) + decrypted_message = message_decryptor.run(encrypted_message) self.assertEqual(len(decrypted_message.attachments), 2) self.assertEqual(decrypted_message.attachments[0].filename, "f1.pdf")