From 0c66e1eed9f2a933e7b37c8f4176286c1a879719 Mon Sep 17 00:00:00 2001 From: Mike Edmunds Date: Sun, 15 Dec 2019 14:23:03 -0800 Subject: [PATCH] Docs: document DEBUG_API_REQUESTS setting (And add a system check to warn about its use in production deployment.) --- anymail/apps.py | 3 ++- anymail/checks.py | 17 +++++++++++++++++ docs/help.rst | 8 ++++++++ docs/installation.rst | 22 ++++++++++++++++++++++ tests/test_checks.py | 15 ++++++++++++++- 5 files changed, 63 insertions(+), 2 deletions(-) diff --git a/anymail/apps.py b/anymail/apps.py index 72a7408..d0bdc14 100644 --- a/anymail/apps.py +++ b/anymail/apps.py @@ -1,7 +1,7 @@ from django.apps import AppConfig from django.core import checks -from .checks import check_deprecated_settings +from .checks import check_deprecated_settings, check_insecure_settings class AnymailBaseConfig(AppConfig): @@ -10,3 +10,4 @@ class AnymailBaseConfig(AppConfig): def ready(self): checks.register(check_deprecated_settings) + checks.register(check_insecure_settings) diff --git a/anymail/checks.py b/anymail/checks.py index da14efa..55ad219 100644 --- a/anymail/checks.py +++ b/anymail/checks.py @@ -1,6 +1,8 @@ from django.conf import settings from django.core import checks +from anymail.utils import get_anymail_setting + def check_deprecated_settings(app_configs, **kwargs): errors = [] @@ -24,3 +26,18 @@ def check_deprecated_settings(app_configs, **kwargs): )) return errors + + +def check_insecure_settings(app_configs, **kwargs): + errors = [] + + # anymail.W002: DEBUG_API_REQUESTS can leak private information + if get_anymail_setting("debug_api_requests", default=False) and not settings.DEBUG: + errors.append(checks.Warning( + "You have enabled the ANYMAIL setting DEBUG_API_REQUESTS, which can " + "leak API keys and other sensitive data into logs or the console.", + hint="You should not use DEBUG_API_REQUESTS in production deployment.", + id="anymail.W002", + )) + + return errors diff --git a/docs/help.rst b/docs/help.rst index 63deadd..1170eae 100644 --- a/docs/help.rst +++ b/docs/help.rst @@ -44,6 +44,14 @@ often help you pinpoint the problem... other than Anymail. And you can look through the :setting:`EMAIL_FILE_PATH` file contents afterward to see if you're generating the email you want. +**Examine the raw API communication** + + Sometimes you just want to see exactly what Anymail is telling your ESP to do + and how your ESP is responding. In a dev environment, enable the Anymail setting + :setting:`DEBUG_API_REQUESTS ` + to show the raw HTTP requests and responses from (most) ESP APIs. (This is not + recommended in production, as it can leak sensitive data into your logs.) + .. _contact: .. _support: diff --git a/docs/installation.rst b/docs/installation.rst index 10413e8..2067a22 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -299,3 +299,25 @@ For Requests-based Anymail backends, the timeout value used for all API calls to The default is 30 seconds. You can set to a single float, a 2-tuple of floats for separate connection and read timeouts, or `None` to disable timeouts (not recommended). See :ref:`requests:timeouts` in the Requests docs for more information. + + +.. setting:: ANYMAIL_DEBUG_API_REQUESTS + +.. rubric:: DEBUG_API_REQUESTS + +.. versionadded:: 4.3 + +When set to `True`, outputs the raw API communication with the ESP, to assist in +debugging. Each HTTP request and ESP response is dumped to :data:`sys.stdout` once +the response is received. + +.. caution:: + + Do not enable :setting:`!DEBUG_API_REQUESTS` in production deployments. The debug + output will include your API keys, email addresses, and other sensitive data + that you generally don't want to capture in server logs or reveal on the console. + +:setting:`!DEBUG_API_REQUESTS` only applies to sending email through Requests-based +Anymail backends. For other backends, there may be similar debugging facilities +available in the ESP's API wrapper package (e.g., ``boto3.set_stream_logger`` for +Amazon SES). diff --git a/tests/test_checks.py b/tests/test_checks.py index c6af934..c40e5a7 100644 --- a/tests/test_checks.py +++ b/tests/test_checks.py @@ -2,7 +2,7 @@ from django.core import checks from django.test import SimpleTestCase from django.test.utils import override_settings -from anymail.checks import check_deprecated_settings +from anymail.checks import check_deprecated_settings, check_insecure_settings from .utils import AnymailTestMixin @@ -25,3 +25,16 @@ class DeprecatedSettingsTests(SimpleTestCase, AnymailTestMixin): hint="You must update your settings.py.", id="anymail.E001", )]) + + +class InsecureSettingsTests(SimpleTestCase, AnymailTestMixin): + @override_settings(ANYMAIL={"DEBUG_API_REQUESTS": True}) + def test_debug_api_requests_deployed(self): + errors = check_insecure_settings(None) + self.assertEqual(len(errors), 1) + self.assertEqual(errors[0].id, "anymail.W002") + + @override_settings(ANYMAIL={"DEBUG_API_REQUESTS": True}, DEBUG=True) + def test_debug_api_requests_debug(self): + errors = check_insecure_settings(None) + self.assertEqual(len(errors), 0) # no warning in DEBUG (non-production) config