Fixed #20743 -- Added support for keyfile/certfile in SMTP connections.

Thanks jwmayfield, serg.partizan, and Wojciech Banaś for work on the patch.
This commit is contained in:
Andi Albrecht 2014-05-18 09:56:55 +02:00 committed by Tim Graham
parent 61f56e239f
commit 00535e8e6b
7 changed files with 87 additions and 4 deletions

View File

@ -194,6 +194,8 @@ EMAIL_HOST_USER = ''
EMAIL_HOST_PASSWORD = '' EMAIL_HOST_PASSWORD = ''
EMAIL_USE_TLS = False EMAIL_USE_TLS = False
EMAIL_USE_SSL = False EMAIL_USE_SSL = False
EMAIL_SSL_CERTFILE = None
EMAIL_SSL_KEYFILE = None
# List of strings representing installed apps. # List of strings representing installed apps.
INSTALLED_APPS = () INSTALLED_APPS = ()

View File

@ -15,6 +15,7 @@ class EmailBackend(BaseEmailBackend):
""" """
def __init__(self, host=None, port=None, username=None, password=None, def __init__(self, host=None, port=None, username=None, password=None,
use_tls=None, fail_silently=False, use_ssl=None, timeout=None, use_tls=None, fail_silently=False, use_ssl=None, timeout=None,
ssl_keyfile=None, ssl_certfile=None,
**kwargs): **kwargs):
super(EmailBackend, self).__init__(fail_silently=fail_silently) super(EmailBackend, self).__init__(fail_silently=fail_silently)
self.host = host or settings.EMAIL_HOST self.host = host or settings.EMAIL_HOST
@ -24,6 +25,8 @@ class EmailBackend(BaseEmailBackend):
self.use_tls = settings.EMAIL_USE_TLS if use_tls is None else use_tls self.use_tls = settings.EMAIL_USE_TLS if use_tls is None else use_tls
self.use_ssl = settings.EMAIL_USE_SSL if use_ssl is None else use_ssl self.use_ssl = settings.EMAIL_USE_SSL if use_ssl is None else use_ssl
self.timeout = timeout self.timeout = timeout
self.ssl_keyfile = settings.EMAIL_SSL_KEYFILE if ssl_keyfile is None else ssl_keyfile
self.ssl_certfile = settings.EMAIL_SSL_CERTFILE if ssl_certfile is None else ssl_certfile
if self.use_ssl and self.use_tls: if self.use_ssl and self.use_tls:
raise ValueError( raise ValueError(
"EMAIL_USE_TLS/EMAIL_USE_SSL are mutually exclusive, so only set " "EMAIL_USE_TLS/EMAIL_USE_SSL are mutually exclusive, so only set "
@ -46,6 +49,11 @@ class EmailBackend(BaseEmailBackend):
connection_params = {'local_hostname': DNS_NAME.get_fqdn()} connection_params = {'local_hostname': DNS_NAME.get_fqdn()}
if self.timeout is not None: if self.timeout is not None:
connection_params['timeout'] = self.timeout connection_params['timeout'] = self.timeout
if self.use_ssl:
connection_params.update({
'keyfile': self.ssl_keyfile,
'certfile': self.ssl_certfile,
})
try: try:
self.connection = connection_class(self.host, self.port, **connection_params) self.connection = connection_class(self.host, self.port, **connection_params)
@ -53,7 +61,7 @@ class EmailBackend(BaseEmailBackend):
# non-secure connections. # non-secure connections.
if not self.use_ssl and self.use_tls: if not self.use_ssl and self.use_tls:
self.connection.ehlo() self.connection.ehlo()
self.connection.starttls() self.connection.starttls(keyfile=self.ssl_keyfile, certfile=self.ssl_certfile)
self.connection.ehlo() self.connection.ehlo()
if self.username and self.password: if self.username and self.password:
self.connection.login(self.username, self.password) self.connection.login(self.username, self.password)

View File

@ -1228,6 +1228,38 @@ see the explicit TLS setting :setting:`EMAIL_USE_TLS`.
Note that :setting:`EMAIL_USE_TLS`/:setting:`EMAIL_USE_SSL` are mutually Note that :setting:`EMAIL_USE_TLS`/:setting:`EMAIL_USE_SSL` are mutually
exclusive, so only set one of those settings to ``True``. exclusive, so only set one of those settings to ``True``.
.. setting:: EMAIL_SSL_CERTFILE
EMAIL_SSL_CERTFILE
------------------
.. versionadded:: 1.8
Default: ``None``
If :setting:`EMAIL_USE_SSL` or :setting:`EMAIL_USE_TLS` is ``True``, you can
optionally specify the path to a PEM-formatted certificate chain file to use
for the SSL connection.
.. setting:: EMAIL_SSL_KEYFILE
EMAIL_SSL_KEYFILE
-----------------
.. versionadded:: 1.8
Default: ``None``
If :setting:`EMAIL_USE_SSL` or :setting:`EMAIL_USE_TLS` is ``True``, you can
optionally specify the path to a PEM-formatted private key file to use for the
SSL connection.
Note that setting :setting:`EMAIL_SSL_CERTFILE` and :setting:`EMAIL_SSL_KEYFILE`
doesn't result in any certificate checking. They're passed to the underlying SSL
connection. Please refer to the documentation of Python's
:func:`python:ssl.wrap_socket` function for details on how the certificate chain
file and private key file are handled.
.. setting:: FILE_CHARSET .. setting:: FILE_CHARSET
FILE_CHARSET FILE_CHARSET
@ -2926,6 +2958,8 @@ Email
* :setting:`EMAIL_HOST_PASSWORD` * :setting:`EMAIL_HOST_PASSWORD`
* :setting:`EMAIL_HOST_USER` * :setting:`EMAIL_HOST_USER`
* :setting:`EMAIL_PORT` * :setting:`EMAIL_PORT`
* :setting:`EMAIL_SSL_CERTFILE`
* :setting:`EMAIL_SSL_KEYFILE`
* :setting:`EMAIL_SUBJECT_PREFIX` * :setting:`EMAIL_SUBJECT_PREFIX`
* :setting:`EMAIL_USE_TLS` * :setting:`EMAIL_USE_TLS`
* :setting:`MANAGERS` * :setting:`MANAGERS`

View File

@ -140,6 +140,10 @@ Email
* :ref:`Email backends <topic-email-backends>` now support the context manager * :ref:`Email backends <topic-email-backends>` now support the context manager
protocol for opening and closing connections. protocol for opening and closing connections.
* The SMTP email backend now supports ``keyfile`` and ``certfile``
authentication with the :setting:`EMAIL_SSL_CERTFILE` and
:setting:`EMAIL_SSL_KEYFILE` settings.
File Storage File Storage
^^^^^^^^^^^^ ^^^^^^^^^^^^

View File

@ -426,6 +426,7 @@ Palau
params params
parens parens
pdf pdf
PEM
perl perl
permalink permalink
pessimization pessimization

View File

@ -445,13 +445,14 @@ can :ref:`write your own email backend <topic-custom-email-backend>`.
SMTP backend SMTP backend
~~~~~~~~~~~~ ~~~~~~~~~~~~
.. class:: backends.smtp.EmailBackend([host=None, port=None, username=None, password=None, use_tls=None, fail_silently=False, use_ssl=None, timeout=None, **kwargs]) .. class:: backends.smtp.EmailBackend([host=None, port=None, username=None, password=None, use_tls=None, fail_silently=False, use_ssl=None, timeout=None, ssl_keyfile=None, ssl_certfile=None, **kwargs])
This is the default backend. Email will be sent through a SMTP server. This is the default backend. Email will be sent through a SMTP server.
The server address and authentication credentials are set in the The server address and authentication credentials are set in the
:setting:`EMAIL_HOST`, :setting:`EMAIL_PORT`, :setting:`EMAIL_HOST_USER`, :setting:`EMAIL_HOST`, :setting:`EMAIL_PORT`, :setting:`EMAIL_HOST_USER`,
:setting:`EMAIL_HOST_PASSWORD`, :setting:`EMAIL_USE_TLS` and :setting:`EMAIL_HOST_PASSWORD`, :setting:`EMAIL_USE_TLS`,
:setting:`EMAIL_USE_SSL` settings in your settings file. :setting:`EMAIL_USE_SSL`, :setting:`EMAIL_SSL_CERTFILE` and
:setting:`EMAIL_SSL_KEYFILE` settings in your settings file.
The SMTP backend is the default configuration inherited by Django. If you The SMTP backend is the default configuration inherited by Django. If you
want to specify it explicitly, put the following in your settings:: want to specify it explicitly, put the following in your settings::
@ -481,6 +482,11 @@ SMTP backend
If unspecified, the default ``timeout`` will be the one provided by If unspecified, the default ``timeout`` will be the one provided by
:func:`socket.getdefaulttimeout()`, which defaults to ``None`` (no timeout). :func:`socket.getdefaulttimeout()`, which defaults to ``None`` (no timeout).
.. versionchanged:: 1.8
The ``ssl_keyfile`` and ``ssl_certfile`` parameters and
corresponding settings were added.
.. _topic-email-console-backend: .. _topic-email-console-backend:
Console backend Console backend

View File

@ -969,6 +969,34 @@ class SMTPBackendTests(BaseEmailBackendTests, SimpleTestCase):
backend = smtp.EmailBackend() backend = smtp.EmailBackend()
self.assertFalse(backend.use_ssl) self.assertFalse(backend.use_ssl)
@override_settings(EMAIL_SSL_CERTFILE='foo')
def test_email_ssl_certfile_use_settings(self):
backend = smtp.EmailBackend()
self.assertEqual(backend.ssl_certfile, 'foo')
@override_settings(EMAIL_SSL_CERTFILE='foo')
def test_email_ssl_certfile_override_settings(self):
backend = smtp.EmailBackend(ssl_certfile='bar')
self.assertEqual(backend.ssl_certfile, 'bar')
def test_email_ssl_certfile_default_disabled(self):
backend = smtp.EmailBackend()
self.assertEqual(backend.ssl_certfile, None)
@override_settings(EMAIL_SSL_KEYFILE='foo')
def test_email_ssl_keyfile_use_settings(self):
backend = smtp.EmailBackend()
self.assertEqual(backend.ssl_keyfile, 'foo')
@override_settings(EMAIL_SSL_KEYFILE='foo')
def test_email_ssl_keyfile_override_settings(self):
backend = smtp.EmailBackend(ssl_keyfile='bar')
self.assertEqual(backend.ssl_keyfile, 'bar')
def test_email_ssl_keyfile_default_disabled(self):
backend = smtp.EmailBackend()
self.assertEqual(backend.ssl_keyfile, None)
@override_settings(EMAIL_USE_TLS=True) @override_settings(EMAIL_USE_TLS=True)
def test_email_tls_attempts_starttls(self): def test_email_tls_attempts_starttls(self):
backend = smtp.EmailBackend() backend = smtp.EmailBackend()