mirror of https://github.com/django/django.git
Fixed #17471 -- Added smtplib.SMTP_SSL connection option for SMTP backend
Thanks dj.facebook at gmail.com for the report and initial patch and Areski Belaid and senko for improvements.
This commit is contained in:
parent
684a606a4e
commit
59ebe39812
|
@ -184,6 +184,7 @@ EMAIL_PORT = 25
|
|||
EMAIL_HOST_USER = ''
|
||||
EMAIL_HOST_PASSWORD = ''
|
||||
EMAIL_USE_TLS = False
|
||||
EMAIL_USE_SSL = False
|
||||
|
||||
# List of strings representing installed apps.
|
||||
INSTALLED_APPS = ()
|
||||
|
|
|
@ -15,22 +15,18 @@ class EmailBackend(BaseEmailBackend):
|
|||
A wrapper that manages the SMTP network connection.
|
||||
"""
|
||||
def __init__(self, host=None, port=None, username=None, password=None,
|
||||
use_tls=None, fail_silently=False, **kwargs):
|
||||
use_tls=None, fail_silently=False, use_ssl=None, **kwargs):
|
||||
super(EmailBackend, self).__init__(fail_silently=fail_silently)
|
||||
self.host = host or settings.EMAIL_HOST
|
||||
self.port = port or settings.EMAIL_PORT
|
||||
if username is None:
|
||||
self.username = settings.EMAIL_HOST_USER
|
||||
else:
|
||||
self.username = username
|
||||
if password is None:
|
||||
self.password = settings.EMAIL_HOST_PASSWORD
|
||||
else:
|
||||
self.password = password
|
||||
if use_tls is None:
|
||||
self.use_tls = settings.EMAIL_USE_TLS
|
||||
else:
|
||||
self.use_tls = use_tls
|
||||
self.username = settings.EMAIL_HOST_USER if username is None else username
|
||||
self.password = settings.EMAIL_HOST_PASSWORD if password is None else password
|
||||
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
|
||||
if self.use_ssl and self.use_tls:
|
||||
raise ValueError(
|
||||
"EMAIL_USE_TLS/EMAIL_USE_SSL are mutually exclusive, so only set "
|
||||
"one of those settings to True.")
|
||||
self.connection = None
|
||||
self._lock = threading.RLock()
|
||||
|
||||
|
@ -45,12 +41,18 @@ class EmailBackend(BaseEmailBackend):
|
|||
try:
|
||||
# If local_hostname is not specified, socket.getfqdn() gets used.
|
||||
# For performance, we use the cached FQDN for local_hostname.
|
||||
self.connection = smtplib.SMTP(self.host, self.port,
|
||||
if self.use_ssl:
|
||||
self.connection = smtplib.SMTP_SSL(self.host, self.port,
|
||||
local_hostname=DNS_NAME.get_fqdn())
|
||||
if self.use_tls:
|
||||
self.connection.ehlo()
|
||||
self.connection.starttls()
|
||||
self.connection.ehlo()
|
||||
else:
|
||||
self.connection = smtplib.SMTP(self.host, self.port,
|
||||
local_hostname=DNS_NAME.get_fqdn())
|
||||
# TLS/SSL are mutually exclusive, so only attempt TLS over
|
||||
# non-secure connections.
|
||||
if self.use_tls:
|
||||
self.connection.ehlo()
|
||||
self.connection.starttls()
|
||||
self.connection.ehlo()
|
||||
if self.username and self.password:
|
||||
self.connection.login(self.username, self.password)
|
||||
return True
|
||||
|
|
|
@ -1055,6 +1055,26 @@ EMAIL_USE_TLS
|
|||
Default: ``False``
|
||||
|
||||
Whether to use a TLS (secure) connection when talking to the SMTP server.
|
||||
This is used for explicit TLS connections, generally on port 587. If you are
|
||||
experiencing hanging connections, see the implicit TLS setting
|
||||
:setting:`EMAIL_USE_SSL`.
|
||||
|
||||
.. setting:: EMAIL_USE_SSL
|
||||
|
||||
EMAIL_USE_SSL
|
||||
-------------
|
||||
|
||||
.. versionadded:: 1.7
|
||||
|
||||
Default: ``False``
|
||||
|
||||
Whether to use an implicit TLS (secure) connection when talking to the SMTP
|
||||
server. In most email documentation this type of TLS connection is referred
|
||||
to as SSL. It is generally used on port 465. If you are experiencing problems,
|
||||
see the explicit TLS setting :setting:`EMAIL_USE_TLS`.
|
||||
|
||||
Note that :setting:`EMAIL_USE_TLS`/:setting:`EMAIL_USE_SSL` are mutually
|
||||
exclusive, so only set one of those settings to ``True``.
|
||||
|
||||
.. setting:: FILE_CHARSET
|
||||
|
||||
|
|
|
@ -27,7 +27,8 @@ Mail is sent using the SMTP host and port specified in the
|
|||
:setting:`EMAIL_HOST` and :setting:`EMAIL_PORT` settings. The
|
||||
:setting:`EMAIL_HOST_USER` and :setting:`EMAIL_HOST_PASSWORD` settings, if
|
||||
set, are used to authenticate to the SMTP server, and the
|
||||
:setting:`EMAIL_USE_TLS` setting controls whether a secure connection is used.
|
||||
:setting:`EMAIL_USE_TLS` and :setting:`EMAIL_USE_SSL` settings control whether
|
||||
a secure connection is used.
|
||||
|
||||
.. note::
|
||||
|
||||
|
@ -408,8 +409,8 @@ SMTP backend
|
|||
This is the default backend. Email will be sent through a SMTP server.
|
||||
The server address and authentication credentials are set in the
|
||||
:setting:`EMAIL_HOST`, :setting:`EMAIL_PORT`, :setting:`EMAIL_HOST_USER`,
|
||||
:setting:`EMAIL_HOST_PASSWORD` and :setting:`EMAIL_USE_TLS` settings in your
|
||||
settings file.
|
||||
:setting:`EMAIL_HOST_PASSWORD`, :setting:`EMAIL_USE_TLS` and
|
||||
:setting:`EMAIL_USE_SSL` settings in your settings file.
|
||||
|
||||
The SMTP backend is the default configuration inherited by Django. If you
|
||||
want to specify it explicitly, put the following in your settings::
|
||||
|
|
|
@ -9,6 +9,8 @@ import smtpd
|
|||
import sys
|
||||
import tempfile
|
||||
import threading
|
||||
from smtplib import SMTPException
|
||||
from ssl import SSLError
|
||||
|
||||
from django.core import mail
|
||||
from django.core.mail import (EmailMessage, mail_admins, mail_managers,
|
||||
|
@ -621,11 +623,23 @@ class ConsoleBackendTests(BaseEmailBackendTests, TestCase):
|
|||
self.assertTrue(s.getvalue().startswith('Content-Type: text/plain; charset="utf-8"\nMIME-Version: 1.0\nContent-Transfer-Encoding: 7bit\nSubject: Subject\nFrom: from@example.com\nTo: to@example.com\nDate: '))
|
||||
|
||||
|
||||
class FakeSMTPChannel(smtpd.SMTPChannel):
|
||||
|
||||
def collect_incoming_data(self, data):
|
||||
try:
|
||||
super(FakeSMTPChannel, self).collect_incoming_data(data)
|
||||
except UnicodeDecodeError:
|
||||
# ignore decode error in SSL/TLS connection tests as we only care
|
||||
# whether the connection attempt was made
|
||||
pass
|
||||
|
||||
|
||||
class FakeSMTPServer(smtpd.SMTPServer, threading.Thread):
|
||||
"""
|
||||
Asyncore SMTP server wrapped into a thread. Based on DummyFTPServer from:
|
||||
http://svn.python.org/view/python/branches/py3k/Lib/test/test_ftplib.py?revision=86061&view=markup
|
||||
"""
|
||||
channel_class = FakeSMTPChannel
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
threading.Thread.__init__(self)
|
||||
|
@ -738,3 +752,44 @@ class SMTPBackendTests(BaseEmailBackendTests, TestCase):
|
|||
backend.close()
|
||||
except Exception as e:
|
||||
self.fail("close() unexpectedly raised an exception: %s" % e)
|
||||
|
||||
@override_settings(EMAIL_USE_TLS=True)
|
||||
def test_email_tls_use_settings(self):
|
||||
backend = smtp.EmailBackend()
|
||||
self.assertTrue(backend.use_tls)
|
||||
|
||||
@override_settings(EMAIL_USE_TLS=True)
|
||||
def test_email_tls_override_settings(self):
|
||||
backend = smtp.EmailBackend(use_tls=False)
|
||||
self.assertFalse(backend.use_tls)
|
||||
|
||||
def test_email_tls_default_disabled(self):
|
||||
backend = smtp.EmailBackend()
|
||||
self.assertFalse(backend.use_tls)
|
||||
|
||||
@override_settings(EMAIL_USE_SSL=True)
|
||||
def test_email_ssl_use_settings(self):
|
||||
backend = smtp.EmailBackend()
|
||||
self.assertTrue(backend.use_ssl)
|
||||
|
||||
@override_settings(EMAIL_USE_SSL=True)
|
||||
def test_email_ssl_override_settings(self):
|
||||
backend = smtp.EmailBackend(use_ssl=False)
|
||||
self.assertFalse(backend.use_ssl)
|
||||
|
||||
def test_email_ssl_default_disabled(self):
|
||||
backend = smtp.EmailBackend()
|
||||
self.assertFalse(backend.use_ssl)
|
||||
|
||||
@override_settings(EMAIL_USE_TLS=True)
|
||||
def test_email_tls_attempts_starttls(self):
|
||||
backend = smtp.EmailBackend()
|
||||
self.assertTrue(backend.use_tls)
|
||||
self.assertRaisesMessage(SMTPException,
|
||||
'STARTTLS extension not supported by server.', backend.open)
|
||||
|
||||
@override_settings(EMAIL_USE_SSL=True)
|
||||
def test_email_ssl_attempts_ssl_connection(self):
|
||||
backend = smtp.EmailBackend()
|
||||
self.assertTrue(backend.use_ssl)
|
||||
self.assertRaises(SSLError, backend.open)
|
||||
|
|
Loading…
Reference in New Issue