Commit 0872816d authored by Antoine Pitrou's avatar Antoine Pitrou

Issue #8808: The IMAP4_SSL constructor now allows passing an SSLContext

parameter to control parameters of the secure channel.  Patch by Sijin
Joseph.
parent 45fd0c99
...@@ -64,14 +64,21 @@ Three exceptions are defined as attributes of the :class:`IMAP4` class: ...@@ -64,14 +64,21 @@ Three exceptions are defined as attributes of the :class:`IMAP4` class:
There's also a subclass for secure connections: There's also a subclass for secure connections:
.. class:: IMAP4_SSL(host='', port=IMAP4_SSL_PORT, keyfile=None, certfile=None) .. class:: IMAP4_SSL(host='', port=IMAP4_SSL_PORT, keyfile=None, certfile=None, ssl_context=None)
This is a subclass derived from :class:`IMAP4` that connects over an SSL This is a subclass derived from :class:`IMAP4` that connects over an SSL
encrypted socket (to use this class you need a socket module that was compiled encrypted socket (to use this class you need a socket module that was compiled
with SSL support). If *host* is not specified, ``''`` (the local host) is used. with SSL support). If *host* is not specified, ``''`` (the local host) is used.
If *port* is omitted, the standard IMAP4-over-SSL port (993) is used. *keyfile* If *port* is omitted, the standard IMAP4-over-SSL port (993) is used. *keyfile*
and *certfile* are also optional - they can contain a PEM formatted private key and *certfile* are also optional - they can contain a PEM formatted private key
and certificate chain file for the SSL connection. and certificate chain file for the SSL connection. *ssl_context* parameter is a
:class:`ssl.SSLContext` object which allows bundling SSL configuration
options, certificates and private keys into a single (potentially long-lived)
structure. Note that the *keyfile*/*certfile* parameters are mutually exclusive with *ssl_context*,
a :class:`ValueError` is thrown if *keyfile*/*certfile* is provided along with *ssl_context*.
.. versionchanged:: 3.3
*ssl_context* parameter added.
The second subclass allows for connections created by a child process: The second subclass allows for connections created by a child process:
......
...@@ -1177,25 +1177,40 @@ if HAVE_SSL: ...@@ -1177,25 +1177,40 @@ if HAVE_SSL:
"""IMAP4 client class over SSL connection """IMAP4 client class over SSL connection
Instantiate with: IMAP4_SSL([host[, port[, keyfile[, certfile]]]]) Instantiate with: IMAP4_SSL([host[, port[, keyfile[, certfile[, ssl_context]]]]])
host - host's name (default: localhost); host - host's name (default: localhost);
port - port number (default: standard IMAP4 SSL port). port - port number (default: standard IMAP4 SSL port);
keyfile - PEM formatted file that contains your private key (default: None); keyfile - PEM formatted file that contains your private key (default: None);
certfile - PEM formatted certificate chain file (default: None); certfile - PEM formatted certificate chain file (default: None);
ssl_context - a SSLContext object that contains your certificate chain
and private key (default: None)
Note: if ssl_context is provided, then parameters keyfile or
certfile should not be set otherwise ValueError is thrown.
for more documentation see the docstring of the parent class IMAP4. for more documentation see the docstring of the parent class IMAP4.
""" """
def __init__(self, host = '', port = IMAP4_SSL_PORT, keyfile = None, certfile = None): def __init__(self, host='', port=IMAP4_SSL_PORT, keyfile=None, certfile=None, ssl_context=None):
if ssl_context is not None and keyfile is not None:
raise ValueError("ssl_context and keyfile arguments are mutually "
"exclusive")
if ssl_context is not None and certfile is not None:
raise ValueError("ssl_context and certfile arguments are mutually "
"exclusive")
self.keyfile = keyfile self.keyfile = keyfile
self.certfile = certfile self.certfile = certfile
self.ssl_context = ssl_context
IMAP4.__init__(self, host, port) IMAP4.__init__(self, host, port)
def _create_socket(self): def _create_socket(self):
sock = IMAP4._create_socket(self) sock = IMAP4._create_socket(self)
return ssl.wrap_socket(sock, self.keyfile, self.certfile) if self.ssl_context:
return self.ssl_context.wrap_socket(sock)
else:
return ssl.wrap_socket(sock, self.keyfile, self.certfile)
def open(self, host='', port=IMAP4_SSL_PORT): def open(self, host='', port=IMAP4_SSL_PORT):
"""Setup connection to remote server on "host:port". """Setup connection to remote server on "host:port".
......
...@@ -258,11 +258,58 @@ class RemoteIMAP_SSLTest(RemoteIMAPTest): ...@@ -258,11 +258,58 @@ class RemoteIMAP_SSLTest(RemoteIMAPTest):
port = 993 port = 993
imap_class = IMAP4_SSL imap_class = IMAP4_SSL
def setUp(self):
pass
def tearDown(self):
pass
def create_ssl_context(self):
ssl_context = ssl.SSLContext(ssl.PROTOCOL_SSLv23)
ssl_context.load_cert_chain(CERTFILE)
return ssl_context
def check_logincapa(self, server):
try:
for cap in server.capabilities:
self.assertIsInstance(cap, str)
self.assertFalse('LOGINDISABLED' in server.capabilities)
self.assertTrue('AUTH=PLAIN' in server.capabilities)
rs = server.login(self.username, self.password)
self.assertEqual(rs[0], 'OK')
finally:
server.logout()
def test_logincapa(self): def test_logincapa(self):
for cap in self.server.capabilities: with transient_internet(self.host):
self.assertIsInstance(cap, str) _server = self.imap_class(self.host, self.port)
self.assertFalse('LOGINDISABLED' in self.server.capabilities) self.check_logincapa(_server)
self.assertTrue('AUTH=PLAIN' in self.server.capabilities)
def test_logincapa_with_client_certfile(self):
with transient_internet(self.host):
_server = self.imap_class(self.host, self.port, certfile=CERTFILE)
self.check_logincapa(_server)
def test_logincapa_with_client_ssl_context(self):
with transient_internet(self.host):
_server = self.imap_class(self.host, self.port, ssl_context=self.create_ssl_context())
self.check_logincapa(_server)
def test_logout(self):
with transient_internet(self.host):
_server = self.imap_class(self.host, self.port)
rs = _server.logout()
self.assertEqual(rs[0], 'BYE')
def test_ssl_context_certfile_exclusive(self):
with transient_internet(self.host):
self.assertRaises(ValueError, self.imap_class, self.host, self.port,
certfile=CERTFILE, ssl_context=self.create_ssl_context())
def test_ssl_context_keyfile_exclusive(self):
with transient_internet(self.host):
self.assertRaises(ValueError, self.imap_class, self.host, self.port,
keyfile=CERTFILE, ssl_context=self.create_ssl_context())
def test_main(): def test_main():
......
...@@ -140,6 +140,10 @@ Core and Builtins ...@@ -140,6 +140,10 @@ Core and Builtins
Library Library
------- -------
- Issue #8808: The IMAP4_SSL constructor now allows passing an SSLContext
parameter to control parameters of the secure channel. Patch by Sijin
Joseph.
- ntpath.samefile failed to notice that "a.txt" and "A.TXT" refer to the same - ntpath.samefile failed to notice that "a.txt" and "A.TXT" refer to the same
file on Windows XP. As noticed in issue #10684. file on Windows XP. As noticed in issue #10684.
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment