import email
import email.contentmanager
+import shutil
+import subprocess
import tempfile
from email.message import Message
from email.mime.application import MIMEApplication
)
self.gpg.gen_key(input_data)
+ def cleanup(self) -> None:
+ """
+ Kill the gpg-agent process and clean up the temporary GPG home directory.
+
+ This uses gpgconf to properly terminate the agent, which is the officially
+ recommended cleanup method from the GnuPG project. python-gnupg does not
+ provide built-in cleanup methods as it's only a wrapper around the gpg CLI.
+ """
+ # Kill the gpg-agent using the official GnuPG cleanup tool
+ try:
+ subprocess.run(
+ ["gpgconf", "--kill", "gpg-agent"],
+ env={"GNUPGHOME": self.gpg_home},
+ check=False,
+ capture_output=True,
+ timeout=5,
+ )
+ except (FileNotFoundError, subprocess.TimeoutExpired):
+ # gpgconf not found or hung - agent will timeout eventually
+ pass
+
+ # Clean up the temporary directory
+ shutil.rmtree(self.gpg_home, ignore_errors=True)
+
@staticmethod
def get_email_body_without_headers(email_message: Message) -> bytes:
"""
class TestMailMessageGpgDecryptor(TestMail):
+ @classmethod
+ def setUpClass(cls):
+ """Create GPG encryptor once for all tests in this class."""
+ super().setUpClass()
+ cls.messageEncryptor = MessageEncryptor()
+
+ @classmethod
+ def tearDownClass(cls):
+ """Clean up GPG resources after all tests complete."""
+ if hasattr(cls, "messageEncryptor"):
+ cls.messageEncryptor.cleanup()
+ super().tearDownClass()
+
def setUp(self):
- self.messageEncryptor = MessageEncryptor()
with override_settings(
EMAIL_GNUPG_HOME=self.messageEncryptor.gpg_home,
EMAIL_ENABLE_GPG_DECRYPTOR=True,
def test_decrypt_fails(self):
encrypted_message, _ = self.create_encrypted_unencrypted_message_pair()
+ # This test creates its own empty GPG home to test decryption failure
empty_gpg_home = tempfile.mkdtemp()
- with override_settings(
- EMAIL_ENABLE_GPG_DECRYPTOR=True,
- EMAIL_GNUPG_HOME=empty_gpg_home,
- ):
- message_decryptor = MailMessageDecryptor()
- self.assertRaises(Exception, message_decryptor.run, encrypted_message)
+ try:
+ with override_settings(
+ EMAIL_ENABLE_GPG_DECRYPTOR=True,
+ EMAIL_GNUPG_HOME=empty_gpg_home,
+ ):
+ message_decryptor = MailMessageDecryptor()
+ self.assertRaises(Exception, message_decryptor.run, encrypted_message)
+ finally:
+ # Clean up the temporary GPG home used only by this test
+ try:
+ subprocess.run(
+ ["gpgconf", "--kill", "gpg-agent"],
+ env={"GNUPGHOME": empty_gpg_home},
+ check=False,
+ capture_output=True,
+ timeout=5,
+ )
+ except (FileNotFoundError, subprocess.TimeoutExpired):
+ pass
+ shutil.rmtree(empty_gpg_home, ignore_errors=True)
def test_decrypt_encrypted_mail(self):
"""