Skip to content

Commit

Permalink
feat: Add support for Python 3.10. (#309)
Browse files Browse the repository at this point in the history
* feat: Add support for Python 3.10.

Fix an issue with the use of SSL server context in cilents to call
into the server for lazy loading of init logic in server. This commit
adds a helper function that translates server context to client context
in the right places.

This should add full support for Python 3.10

* fix: Remove auto-added import by vscode :-|

* remove dependency on qa job to let CI run

* fix: some flake8 errors after rebase

* fix: one failing test and type checking

* ci: re-add dependnecy on QA now that it is fixed in master

* fix: ignore the Flake8 error for the insecure ssl option

* fix: another vscode added random import :-(

* empty commit to trigger ci

* fix: Add Python 3.10 support classifier to setup.cfg
  • Loading branch information
maxking committed Nov 3, 2022
1 parent 73c3a0c commit c78fc95
Show file tree
Hide file tree
Showing 5 changed files with 39 additions and 10 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/unit-testing-and-coverage.yml
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ jobs:
fail-fast: false
matrix:
os: [ "macos-10.15", "ubuntu-18.04", "ubuntu-20.04", "windows-latest" ]
python-version: [ "3.6", "3.7", "3.8", "3.9", "pypy3.6" ]
python-version: [ "3.6", "3.7", "3.8", "3.9", "3.10", "pypy3.6" ]
runs-on: ${{ matrix.os }}
timeout-minutes: 15 # Slowest so far is pypy3 on MacOS, taking almost 7m
steps:
Expand Down
24 changes: 22 additions & 2 deletions aiosmtpd/controller.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,24 @@ def get_localhost() -> Literal["::1", "127.0.0.1"]:
raise


def _server_to_client_ssl_ctx(server_ctx: ssl.SSLContext) -> ssl.SSLContext:
"""
Given an SSLContext object with TLS_SERVER_PROTOCOL return a client
context that can connect to the server.
"""
client_ctx = ssl.create_default_context(purpose=ssl.Purpose.SERVER_AUTH)
client_ctx.options = server_ctx.options
client_ctx.check_hostname = False
# We do not verify the ssl cert for the server here simply because this
# is a local connection to poke at the server for it to do its lazy
# initialization sequence. The only purpose of this client context
# is to make a connection to the *local* server created using the same
# code. That is also the reason why we disable cert verification below
# and the flake8 check for the same.
client_ctx.verify_mode = ssl.CERT_NONE # noqa: DUO122
return client_ctx


class _FakeServer(asyncio.StreamReaderProtocol):
"""
Returned by _factory_invoker() in lieu of an SMTP instance in case
Expand Down Expand Up @@ -425,7 +443,8 @@ def _trigger_server(self):
with ExitStack() as stk:
s = stk.enter_context(create_connection((hostname, self.port), 1.0))
if self.ssl_context:
s = stk.enter_context(self.ssl_context.wrap_socket(s))
client_ctx = _server_to_client_ssl_ctx(self.ssl_context)
s = stk.enter_context(client_ctx.wrap_socket(s))
s.recv(1024)


Expand Down Expand Up @@ -467,7 +486,8 @@ def _trigger_server(self):
s: makesock = stk.enter_context(makesock(AF_UNIX, SOCK_STREAM))
s.connect(self.unix_socket)
if self.ssl_context:
s = stk.enter_context(self.ssl_context.wrap_socket(s))
client_ctx = _server_to_client_ssl_ctx(self.ssl_context)
s = stk.enter_context(client_ctx.wrap_socket(s))
s.recv(1024)


Expand Down
6 changes: 5 additions & 1 deletion aiosmtpd/tests/test_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
UnixSocketUnthreadedController,
_FakeServer,
get_localhost,
_server_to_client_ssl_ctx,
)
from aiosmtpd.handlers import Sink
from aiosmtpd.smtp import SMTP as Server
Expand Down Expand Up @@ -101,7 +102,10 @@ def safe_socket_dir() -> Generator[Path, None, None]:
def assert_smtp_socket(controller: UnixSocketMixin) -> bool:
assert Path(controller.unix_socket).exists()
sockfile = controller.unix_socket
ssl_context = controller.ssl_context
if controller.ssl_context:
ssl_context = _server_to_client_ssl_ctx(controller.ssl_context)
else:
ssl_context = None
with ExitStack() as stk:
sock: socket.socket = stk.enter_context(
socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
Expand Down
16 changes: 10 additions & 6 deletions aiosmtpd/tests/test_smtp.py
Original file line number Diff line number Diff line change
Expand Up @@ -1946,12 +1946,16 @@ class TestAuthArgs:
def test_warn_authreqnotls(self, caplog):
with pytest.warns(UserWarning) as record:
_ = Server(Sink(), auth_required=True, auth_require_tls=False)
assert len(record) == 1
assert (
record[0].message.args[0]
== "Requiring AUTH while not requiring TLS can lead to "
"security vulnerabilities!"
)
for warning in record:
if warning.message.args and (
warning.message.args[0]
== "Requiring AUTH while not requiring TLS can lead to "
"security vulnerabilities!"
):
break
else:
pytest.xfail("Did not raise expected warning")

assert caplog.record_tuples[0] == (
"mail.log",
logging.WARNING,
Expand Down
1 change: 1 addition & 0 deletions setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ classifiers =
Programming Language :: Python :: 3.7
Programming Language :: Python :: 3.8
Programming Language :: Python :: 3.9
Programming Language :: Python :: 3.10
Programming Language :: Python :: Implementation :: CPython
Programming Language :: Python :: Implementation :: PyPy
Topic :: Communications :: Email :: Mail Transport Agents
Expand Down

0 comments on commit c78fc95

Please sign in to comment.