Add settings for email decryption preprocessor

This commit is contained in:
Daniel Bankmann 2024-08-18 00:29:58 +02:00 committed by shamoon
parent 243daf7469
commit c134ce9025
4 changed files with 72 additions and 17 deletions

View File

@ -1171,6 +1171,15 @@ if DEBUG: # pragma: no cover
EMAIL_BACKEND = "django.core.mail.backends.filebased.EmailBackend" EMAIL_BACKEND = "django.core.mail.backends.filebased.EmailBackend"
EMAIL_FILE_PATH = BASE_DIR / "sent_emails" 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 # Soft Delete

View File

@ -10,7 +10,6 @@ from datetime import timedelta
from fnmatch import fnmatch from fnmatch import fnmatch
from pathlib import Path from pathlib import Path
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
from typing import Callable
from typing import Optional from typing import Optional
from typing import Union 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 MailRule
from paperless_mail.models import ProcessedMail from paperless_mail.models import ProcessedMail
from paperless_mail.preprocessor import MailMessageDecryptor from paperless_mail.preprocessor import MailMessageDecryptor
from paperless_mail.preprocessor import MailMessagePreprocessor
# Apple Mail sets multiple IMAP KEYWORD and the general "\Flagged" FLAG # Apple Mail sets multiple IMAP KEYWORD and the general "\Flagged" FLAG
# imaplib => conn.fetch(b"<message_id>", "FLAGS") # imaplib => conn.fetch(b"<message_id>", "FLAGS")
@ -428,12 +428,22 @@ class MailAccountHandler(LoggingMixin):
logging_name = "paperless_mail" 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__() super().__init__()
self._message_preprocessors.append(MailMessageDecryptor(*gpgArgs, **gpgkwArgs))
self.renew_logging_group() 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]: def _correspondent_from_name(self, name: str) -> Optional[Correspondent]:
try: try:
@ -542,7 +552,7 @@ class MailAccountHandler(LoggingMixin):
def _preprocess_message(self, message: MailMessage): def _preprocess_message(self, message: MailMessage):
for preprocessor in self._message_preprocessors: for preprocessor in self._message_preprocessors:
message = preprocessor(message) message = preprocessor.run(message)
return message return message
def _handle_mail_rule( def _handle_mail_rule(

View File

@ -1,22 +1,53 @@
import abc
from email import message_from_bytes from email import message_from_bytes
from email import policy from email import policy
from email.message import Message from email.message import Message
from django.conf import settings
from gnupg import GPG from gnupg import GPG
from imap_tools import MailMessage from imap_tools import MailMessage
from documents.loggers import LoggingMixin 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" logging_name = "paperless_mail_message_decryptor"
def __init__(self, *args, **kwargs): NAME = "MailMessageDecryptor"
def __init__(self):
super().__init__() super().__init__()
self.renew_logging_group() 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"): if not hasattr(message, "obj"):
self.log.debug("Message does not have 'obj' attribute") self.log.debug("Message does not have 'obj' attribute")
return message return message

View File

@ -16,6 +16,7 @@ import pytest
from django.core.management import call_command from django.core.management import call_command
from django.db import DatabaseError from django.db import DatabaseError
from django.test import TestCase from django.test import TestCase
from django.test import override_settings
from imap_tools import NOT from imap_tools import NOT
from imap_tools import EmailAddress from imap_tools import EmailAddress
from imap_tools import FolderInfo from imap_tools import FolderInfo
@ -273,10 +274,11 @@ class TestMail(
self.reset_bogus_mailbox() self.reset_bogus_mailbox()
self.messageEncryptor = MessageEncryptor() self.messageEncryptor = MessageEncryptor()
with override_settings(
self.mail_account_handler = MailAccountHandler( EMAIL_GNUPG_HOME=self.messageEncryptor.gpg_home,
gnupghome=self.messageEncryptor.gpg_home, EMAIL_ENABLE_GPG_DECRYPTOR=True,
) ):
self.mail_account_handler = MailAccountHandler()
super().setUp() super().setUp()
@ -1368,10 +1370,13 @@ class TestMail(
self.assertEqual(encrypted_message.attachments[0].filename, "encrypted.asc") self.assertEqual(encrypted_message.attachments[0].filename, "encrypted.asc")
self.assertEqual(encrypted_message.text, "") self.assertEqual(encrypted_message.text, "")
message_decryptor = MailMessageDecryptor( with override_settings(
gnupghome=self.messageEncryptor.gpg_home, EMAIL_ENABLE_GPG_DECRYPTOR=True,
) EMAIL_GNUPG_HOME=self.messageEncryptor.gpg_home,
decrypted_message = message_decryptor(encrypted_message) ):
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(len(decrypted_message.attachments), 2)
self.assertEqual(decrypted_message.attachments[0].filename, "f1.pdf") self.assertEqual(decrypted_message.attachments[0].filename, "f1.pdf")