mirror of
https://github.com/pacnpal/django-anymail.git
synced 2025-12-20 03:41:05 -05:00
Fix fail_silently when session/client creation fails
Make sure backends actually fail silently when asked (rather than raising inaccurate errors suggesting coding problems). Fixes #308
This commit is contained in:
@@ -33,6 +33,10 @@ vNext
|
||||
Fixes
|
||||
~~~~~
|
||||
|
||||
* Fix misleading error messages when sending with ``fail_silently=True``
|
||||
and session creation fails (e.g., with Amazon SES backend and missing
|
||||
credentials). (Thanks to `@technolingo`_.)
|
||||
|
||||
* **Postmark:** Fix spurious AnymailInvalidAddress in ``message.cc`` when
|
||||
inbound message has no Cc recipients. (Thanks to `@Ecno92`_.)
|
||||
|
||||
@@ -1455,6 +1459,7 @@ Features
|
||||
.. _@slinkymanbyday: https://github.com/slinkymanbyday
|
||||
.. _@swrobel: https://github.com/swrobel
|
||||
.. _@tcourtqtm: https://github.com/tcourtqtm
|
||||
.. _@technolingo: https://github.com/technolingo
|
||||
.. _@Thorbenl: https://github.com/Thorbenl
|
||||
.. _@tiltec: https://github.com/tiltec
|
||||
.. _@tim-schilling: https://github.com/tim-schilling
|
||||
|
||||
@@ -59,7 +59,7 @@ class EmailBackend(AnymailBaseBackend):
|
||||
self.client = boto3.session.Session(**self.session_params).client(
|
||||
"ses", **self.client_params
|
||||
)
|
||||
except BOTO_BASE_ERRORS:
|
||||
except Exception:
|
||||
if not self.fail_silently:
|
||||
raise
|
||||
else:
|
||||
@@ -71,6 +71,22 @@ class EmailBackend(AnymailBaseBackend):
|
||||
# self.client.close() # boto3 doesn't support (or require) client shutdown
|
||||
self.client = None
|
||||
|
||||
def _send(self, message):
|
||||
if self.client:
|
||||
return super()._send(message)
|
||||
elif self.fail_silently:
|
||||
# (Probably missing boto3 credentials in open().)
|
||||
return False
|
||||
else:
|
||||
class_name = self.__class__.__name__
|
||||
raise RuntimeError(
|
||||
"boto3 Session has not been opened in {class_name}._send. "
|
||||
"(This is either an implementation error in {class_name}, "
|
||||
"or you are incorrectly calling _send directly.)".format(
|
||||
class_name=class_name
|
||||
)
|
||||
)
|
||||
|
||||
def build_message_payload(self, message, defaults):
|
||||
# The SES SendRawEmail and SendBulkTemplatedEmail calls have
|
||||
# very different signatures, so use a custom payload for each
|
||||
|
||||
@@ -63,7 +63,7 @@ class EmailBackend(AnymailBaseBackend):
|
||||
self.client = boto3.session.Session(**self.session_params).client(
|
||||
"sesv2", **self.client_params
|
||||
)
|
||||
except BOTO_BASE_ERRORS:
|
||||
except Exception:
|
||||
if not self.fail_silently:
|
||||
raise
|
||||
else:
|
||||
@@ -75,6 +75,22 @@ class EmailBackend(AnymailBaseBackend):
|
||||
self.client.close()
|
||||
self.client = None
|
||||
|
||||
def _send(self, message):
|
||||
if self.client:
|
||||
return super()._send(message)
|
||||
elif self.fail_silently:
|
||||
# (Probably missing boto3 credentials in open().)
|
||||
return False
|
||||
else:
|
||||
class_name = self.__class__.__name__
|
||||
raise RuntimeError(
|
||||
"boto3 Session has not been opened in {class_name}._send. "
|
||||
"(This is either an implementation error in {class_name}, "
|
||||
"or you are incorrectly calling _send directly.)".format(
|
||||
class_name=class_name
|
||||
)
|
||||
)
|
||||
|
||||
def build_message_payload(self, message, defaults):
|
||||
if getattr(message, "template_id", UNSET) is not UNSET:
|
||||
# For simplicity, use SESv2 SendBulkEmail for all templated messages
|
||||
|
||||
@@ -47,7 +47,12 @@ class AnymailRequestsBackend(AnymailBaseBackend):
|
||||
self.session = None
|
||||
|
||||
def _send(self, message):
|
||||
if self.session is None:
|
||||
if self.session:
|
||||
return super()._send(message)
|
||||
elif self.fail_silently:
|
||||
# create_session failed
|
||||
return False
|
||||
else:
|
||||
class_name = self.__class__.__name__
|
||||
raise RuntimeError(
|
||||
"Session has not been opened in {class_name}._send. "
|
||||
@@ -56,7 +61,6 @@ class AnymailRequestsBackend(AnymailBaseBackend):
|
||||
class_name=class_name
|
||||
)
|
||||
)
|
||||
return super()._send(message)
|
||||
|
||||
def create_session(self):
|
||||
"""Create the requests.Session object used by this instance of the backend.
|
||||
|
||||
@@ -393,6 +393,16 @@ class AmazonSESBackendStandardEmailTests(AmazonSESBackendMockAPITestCase):
|
||||
sent = self.message.send(fail_silently=True)
|
||||
self.assertEqual(sent, 0)
|
||||
|
||||
def test_session_failure_fail_silently(self):
|
||||
# Make sure fail_silently is respected if boto3.Session creation fails
|
||||
# (e.g., due to invalid or missing credentials)
|
||||
from botocore.exceptions import NoCredentialsError
|
||||
|
||||
self.mock_session.side_effect = NoCredentialsError()
|
||||
|
||||
sent = self.message.send(fail_silently=True)
|
||||
self.assertEqual(sent, 0)
|
||||
|
||||
def test_prevents_header_injection(self):
|
||||
# Since we build the raw MIME message, we're responsible for preventing header
|
||||
# injection. django.core.mail.EmailMessage.message() implements most of that
|
||||
|
||||
@@ -403,6 +403,16 @@ class AmazonSESBackendStandardEmailTests(AmazonSESBackendMockAPITestCase):
|
||||
sent = self.message.send(fail_silently=True)
|
||||
self.assertEqual(sent, 0)
|
||||
|
||||
def test_session_failure_fail_silently(self):
|
||||
# Make sure fail_silently is respected if boto3.Session creation fails
|
||||
# (e.g., due to invalid or missing credentials)
|
||||
from botocore.exceptions import NoCredentialsError
|
||||
|
||||
self.mock_session.side_effect = NoCredentialsError()
|
||||
|
||||
sent = self.message.send(fail_silently=True)
|
||||
self.assertEqual(sent, 0)
|
||||
|
||||
def test_prevents_header_injection(self):
|
||||
# Since we build the raw MIME message, we're responsible for preventing header
|
||||
# injection. django.core.mail.EmailMessage.message() implements most of that
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
from unittest import mock
|
||||
|
||||
from django.test import SimpleTestCase, override_settings, tag
|
||||
|
||||
from anymail.backends.base_requests import AnymailRequestsBackend, RequestsPayload
|
||||
@@ -69,6 +71,14 @@ class RequestsBackendBaseTestCase(RequestsBackendMockAPITestCase):
|
||||
timeout = self.get_api_call_arg("timeout")
|
||||
self.assertEqual(timeout, 5)
|
||||
|
||||
@mock.patch(f"{__name__}.MinimalRequestsBackend.create_session")
|
||||
def test_create_session_error_fail_silently(self, mock_create_session):
|
||||
# If create_session fails and fail_silently is True,
|
||||
# make sure _send doesn't raise a misleading error.
|
||||
mock_create_session.side_effect = ValueError("couldn't create session")
|
||||
sent = self.message.send(fail_silently=True)
|
||||
self.assertEqual(sent, 0)
|
||||
|
||||
|
||||
@tag("live")
|
||||
@override_settings(EMAIL_BACKEND="tests.test_base_backends.MinimalRequestsBackend")
|
||||
|
||||
Reference in New Issue
Block a user