mirror of
https://github.com/paperless-ngx/paperless-ngx.git
synced 2025-12-31 13:58:04 -06:00
Enhancement: mail message preprocessor for gpg encrypted mails (#7456)
--------- Co-authored-by: shamoon <4887959+shamoon@users.noreply.github.com>
This commit is contained in:
103
src/paperless_mail/preprocessor.py
Normal file
103
src/paperless_mail/preprocessor.py
Normal file
@@ -0,0 +1,103 @@
|
||||
import abc
|
||||
import os
|
||||
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 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"
|
||||
|
||||
NAME = "MailMessageDecryptor"
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.renew_logging_group()
|
||||
self._gpg = GPG(gnupghome=settings.EMAIL_GNUPG_HOME)
|
||||
|
||||
@staticmethod
|
||||
def able_to_run() -> bool:
|
||||
if not settings.EMAIL_ENABLE_GPG_DECRYPTOR:
|
||||
return False
|
||||
if settings.EMAIL_GNUPG_HOME is None:
|
||||
return True
|
||||
return os.path.isdir(settings.EMAIL_GNUPG_HOME)
|
||||
|
||||
def run(self, message: MailMessage) -> MailMessage:
|
||||
if not hasattr(message, "obj"):
|
||||
self.log.debug("Message does not have 'obj' attribute")
|
||||
return message
|
||||
if message.obj.get_content_type() != "multipart/encrypted":
|
||||
self.log.debug("Message not encrypted. Keep unchanged")
|
||||
return message
|
||||
|
||||
self.log.debug("Message is encrypted.")
|
||||
email_message = self._to_email_message(message)
|
||||
decrypted_raw_message = self._gpg.decrypt(email_message.as_string())
|
||||
|
||||
if not decrypted_raw_message.ok:
|
||||
self.log.debug(
|
||||
f"Message decryption failed with status message "
|
||||
f"{decrypted_raw_message.status}",
|
||||
)
|
||||
raise Exception(
|
||||
f"Decryption failed: {decrypted_raw_message.status}, {decrypted_raw_message.stderr}",
|
||||
)
|
||||
self.log.debug("Message decrypted successfully.")
|
||||
|
||||
decrypted_message = self._build_decrypted_message(
|
||||
decrypted_raw_message,
|
||||
email_message,
|
||||
)
|
||||
|
||||
return MailMessage(
|
||||
[(f"UID {message.uid}".encode(), decrypted_message.as_bytes())],
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def _to_email_message(message: MailMessage) -> Message:
|
||||
email_message = message_from_bytes(
|
||||
message.obj.as_bytes(),
|
||||
policy=policy.default,
|
||||
)
|
||||
return email_message
|
||||
|
||||
@staticmethod
|
||||
def _build_decrypted_message(decrypted_raw_message, email_message):
|
||||
decrypted_message = message_from_bytes(
|
||||
decrypted_raw_message.data,
|
||||
policy=policy.default,
|
||||
)
|
||||
for header, value in email_message.items():
|
||||
if not decrypted_message.get(header):
|
||||
decrypted_message.add_header(header, value)
|
||||
return decrypted_message
|
||||
Reference in New Issue
Block a user