Skip to content

Commit 25f3057

Browse files
Deprecate TCPConnector ssl (aio-libs#13829)
1 parent a921cfd commit 25f3057

9 files changed

Lines changed: 105 additions & 50 deletions

‎CHANGES/13829.deprecation.rst‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
Deprecated the ``ssl`` parameter of :class:`~aiohttp.TCPConnector`, scheduled for
2+
removal in 5.0 -- by :user:`Dreamsorcerer`.
3+
4+
Pass ``ssl`` to :class:`~aiohttp.ClientSession` for a session-wide default, or to
5+
:meth:`~aiohttp.ClientSession.get` and the other request methods per request.

‎aiohttp/connector.py‎

Lines changed: 22 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -954,11 +954,11 @@ def _make_ssl_context(verified: bool) -> SSLContext:
954954
class TCPConnector(BaseConnector):
955955
"""TCP connector.
956956
957-
verify_ssl - Set to True to check ssl certifications.
958-
fingerprint - Pass the binary sha256
959-
digest of the expected certificate in DER format to verify
960-
that the certificate the server presents matches. See also
961-
https://en.wikipedia.org/wiki/HTTP_Public_Key_Pinning
957+
ssl - DEPRECATED. Will be removed in aiohttp 5.0.
958+
SSL validation mode: ``True`` for the default checks, ``False`` to
959+
skip certificate validation, a Fingerprint for certificate pinning
960+
or an ssl.SSLContext for custom validation. Pass ``ssl`` to
961+
ClientSession or to the individual request instead.
962962
resolver - Enable DNS lookups and use this
963963
resolver
964964
use_dns_cache - Use memory cache for DNS lookups.
@@ -999,7 +999,7 @@ def __init__(
999999
ttl_dns_cache: int | None = 10,
10001000
dns_cache_max_size: int = 1000,
10011001
family: socket.AddressFamily = socket.AddressFamily.AF_UNSPEC,
1002-
ssl: bool | Fingerprint | SSLContext = True,
1002+
ssl: bool | Fingerprint | SSLContext | _SENTINEL = sentinel,
10031003
local_addr: tuple[str, int] | None = None,
10041004
resolver: AbstractResolver | None = None,
10051005
keepalive_timeout: None | float | _SENTINEL = sentinel,
@@ -1022,12 +1022,23 @@ def __init__(
10221022
timeout_ceil_threshold=timeout_ceil_threshold,
10231023
)
10241024

1025-
if not isinstance(ssl, SSL_ALLOWED_TYPES):
1026-
raise TypeError(
1027-
"ssl should be SSLContext, Fingerprint, or bool, "
1028-
f"got {ssl!r} instead."
1025+
self._ssl: bool | Fingerprint | SSLContext
1026+
if ssl is sentinel:
1027+
self._ssl = True
1028+
else:
1029+
if not isinstance(ssl, SSL_ALLOWED_TYPES):
1030+
raise TypeError(
1031+
"ssl should be SSLContext, Fingerprint, or bool, "
1032+
f"got {ssl!r} instead."
1033+
)
1034+
warnings.warn(
1035+
"The ssl parameter is deprecated since 4.0 and scheduled for "
1036+
"removal in 5.0, pass ssl to ClientSession() or to the "
1037+
"individual request instead",
1038+
DeprecationWarning,
1039+
stacklevel=2,
10291040
)
1030-
self._ssl = ssl
1041+
self._ssl = ssl
10311042

10321043
self._resolver: AbstractResolver
10331044
if resolver is None:

‎docs/client_advanced.rst‎

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -618,7 +618,7 @@ checks can be relaxed by setting *ssl* to ``False``::
618618
If you need to setup custom ssl parameters (use own certification
619619
files for example) you can create a :class:`ssl.SSLContext` instance and
620620
pass it into the :meth:`ClientSession.request` methods or set it for the
621-
entire session with ``ClientSession(connector=TCPConnector(ssl=ssl_context))``.
621+
entire session with ``ClientSession(ssl=ssl_context)``.
622622

623623
There are explicit errors when ssl verification fails
624624

@@ -660,7 +660,7 @@ installed or Python is unable to find them, resulting in a error like
660660
One way to work around this problem is to use the `certifi` package::
661661

662662
ssl_context = ssl.create_default_context(cafile=certifi.where())
663-
async with ClientSession(connector=TCPConnector(ssl=ssl_context)) as sess:
663+
async with ClientSession(ssl=ssl_context) as sess:
664664
...
665665

666666
Example: Use self-signed certificate
@@ -706,9 +706,9 @@ DER with e.g::
706706
Tip: to convert from a hexadecimal digest to a binary byte-string,
707707
you can use :func:`binascii.unhexlify`.
708708

709-
*ssl* parameter could be passed
710-
to :class:`TCPConnector` as default, the value from
711-
:meth:`ClientSession.get` and others override default.
709+
*ssl* parameter could be passed to :class:`ClientSession` as the
710+
session-wide default, the value from :meth:`ClientSession.get` and
711+
others override it.
712712

713713
.. _aiohttp-client-proxy-support:
714714

‎docs/client_reference.rst‎

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1231,8 +1231,8 @@ is controlled by *force_close* constructor's parameter).
12311231
Constructor accepts all parameters suitable for
12321232
:class:`BaseConnector` plus several TCP-specific ones:
12331233

1234-
:param ssl: SSL validation mode. ``True`` for default SSL check
1235-
(:func:`ssl.create_default_context` is used),
1234+
:param ssl: **(DEPRECATED)** SSL validation mode. ``True`` for default
1235+
SSL check (:func:`ssl.create_default_context` is used),
12361236
``False`` for skip SSL certificate validation,
12371237
:class:`aiohttp.Fingerprint` for fingerprint
12381238
validation, :class:`ssl.SSLContext` for custom SSL
@@ -1243,6 +1243,12 @@ is controlled by *force_close* constructor's parameter).
12431243

12441244
.. versionadded:: 3.0
12451245

1246+
.. deprecated:: 4.0
1247+
1248+
Scheduled for removal in 5.0. Pass *ssl* to
1249+
:class:`ClientSession` for a session-wide default, or to
1250+
:meth:`ClientSession.get` and others per request.
1251+
12461252
:param bool verify_ssl: perform SSL certificate validation for
12471253
*HTTPS* requests (enabled by default). May be disabled to
12481254
skip validation for sites with invalid certificates.

‎examples/fake_server.py‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -101,9 +101,9 @@ async def main() -> None:
101101
fake_facebook = FakeFacebook()
102102
info = await fake_facebook.start()
103103
resolver = FakeResolver(info)
104-
connector = TCPConnector(resolver=resolver, ssl=False)
104+
connector = TCPConnector(resolver=resolver)
105105

106-
async with ClientSession(connector=connector) as session:
106+
async with ClientSession(connector=connector, ssl=False) as session:
107107
async with session.get(
108108
"https://graph.facebook.com/v2.7/me", params={"access_token": token}
109109
) as resp:

‎tests/test_client_functional.py‎

Lines changed: 21 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -706,15 +706,13 @@ async def test_ssl_client(
706706
aiohttp_client: AiohttpClient,
707707
client_ssl_ctx: ssl.SSLContext,
708708
) -> None:
709-
connector = aiohttp.TCPConnector(ssl=client_ssl_ctx)
710-
711709
async def handler(request: web.Request) -> web.Response:
712710
return web.Response(text="Test message")
713711

714712
app = web.Application()
715713
app.router.add_route("GET", "/", handler)
716714
server = await aiohttp_server(app, ssl=ssl_ctx)
717-
client = await aiohttp_client(server, connector=connector) # type: ignore[var-annotated]
715+
client = await aiohttp_client(server, ssl=client_ssl_ctx) # type: ignore[var-annotated]
718716

719717
async with client.get("/") as resp:
720718
assert resp.status == 200
@@ -743,8 +741,8 @@ async def handler(request: web.Request) -> web.Response:
743741
server = await aiohttp_server(app, ssl=server_ctx)
744742
url = server.make_url("/")
745743

746-
connector = aiohttp.TCPConnector(ssl=client_ctx, limit=1, limit_per_host=1)
747-
async with aiohttp.ClientSession(connector=connector) as session:
744+
connector = aiohttp.TCPConnector(limit=1, limit_per_host=1)
745+
async with aiohttp.ClientSession(connector=connector, ssl=client_ctx) as session:
748746
async with session.get(url, server_hostname="first.example") as resp:
749747
assert resp.status == 200
750748
await resp.read()
@@ -767,7 +765,7 @@ async def test_ssl_client_shutdown_timeout(
767765
with pytest.warns(
768766
DeprecationWarning, match="ssl_shutdown_timeout parameter is deprecated"
769767
):
770-
connector = aiohttp.TCPConnector(ssl=client_ssl_ctx, ssl_shutdown_timeout=0.1)
768+
connector = aiohttp.TCPConnector(ssl_shutdown_timeout=0.1)
771769

772770
async def streaming_handler(request: web.Request) -> NoReturn:
773771
# Create a streaming response that continuously sends data
@@ -784,7 +782,9 @@ async def streaming_handler(request: web.Request) -> NoReturn:
784782
app = web.Application()
785783
app.router.add_route("GET", "/stream", streaming_handler)
786784
server = await aiohttp_server(app, ssl=ssl_ctx)
787-
client = await aiohttp_client(server, connector=connector) # type: ignore[var-annotated]
785+
client = await aiohttp_client( # type: ignore[var-annotated]
786+
server, connector=connector, ssl=client_ssl_ctx
787+
)
788788

789789
# Verify the connector has the correct timeout
790790
assert connector._ssl_shutdown_timeout == 0.1
@@ -836,8 +836,7 @@ async def handler(request: web.Request) -> web.Response:
836836
ssl_ctx.set_alpn_protocols(("http/1.1",))
837837
server = await aiohttp_server(app, ssl=ssl_ctx)
838838

839-
connector = aiohttp.TCPConnector(ssl=False)
840-
client = await aiohttp_client(server, connector=connector) # type: ignore[var-annotated]
839+
client = await aiohttp_client(server, ssl=False) # type: ignore[var-annotated]
841840
async with client.get("/") as resp:
842841
assert resp.status == 200
843842
txt = await resp.text()
@@ -855,11 +854,13 @@ async def test_tcp_connector_fingerprint_ok(
855854
async def handler(request: web.Request) -> web.Response:
856855
return web.Response(text="Test message")
857856

858-
connector = aiohttp.TCPConnector(ssl=tls_fingerprint)
857+
connector = aiohttp.TCPConnector()
859858
app = web.Application()
860859
app.router.add_route("GET", "/", handler)
861860
server = await aiohttp_server(app, ssl=ssl_ctx)
862-
client = await aiohttp_client(server, connector=connector) # type: ignore[var-annotated]
861+
client = await aiohttp_client( # type: ignore[var-annotated]
862+
server, connector=connector, ssl=tls_fingerprint
863+
)
863864

864865
async with client.get("/") as resp:
865866
assert resp.status == 200
@@ -882,12 +883,12 @@ async def handler(request: web.Request) -> NoReturn:
882883

883884
bad_fingerprint = b"\x00" * len(tls_certificate_fingerprint_sha256)
884885

885-
connector = aiohttp.TCPConnector(ssl=Fingerprint(bad_fingerprint))
886-
887886
app = web.Application()
888887
app.router.add_route("GET", "/", handler)
889888
server = await aiohttp_server(app, ssl=ssl_ctx)
890-
client = await aiohttp_client(server, connector=connector) # type: ignore[var-annotated]
889+
client = await aiohttp_client( # type: ignore[var-annotated]
890+
server, ssl=Fingerprint(bad_fingerprint)
891+
)
891892

892893
with pytest.raises(ServerFingerprintMismatch) as cm:
893894
await client.get("/")
@@ -3487,10 +3488,10 @@ async def resolve(
34873488
async def close(self) -> None:
34883489
"""Dummy"""
34893490

3490-
connector = aiohttp.TCPConnector(resolver=FakeResolver(), ssl=False)
3491+
connector = aiohttp.TCPConnector(resolver=FakeResolver())
34913492

34923493
async with (
3493-
aiohttp.ClientSession(connector=connector) as client,
3494+
aiohttp.ClientSession(connector=connector, ssl=False) as client,
34943495
client.get(
34953496
url_from,
34963497
headers={"Authorization": aiohttp.encode_basic_auth("user", "pass")},
@@ -3597,9 +3598,9 @@ async def resolve(
35973598
async def close(self) -> None:
35983599
"""Dummy"""
35993600

3600-
connector = aiohttp.TCPConnector(resolver=FakeResolver(), ssl=False)
3601+
connector = aiohttp.TCPConnector(resolver=FakeResolver())
36013602

3602-
async with aiohttp.ClientSession(connector=connector) as client:
3603+
async with aiohttp.ClientSession(connector=connector, ssl=False) as client:
36033604
async with client.get(
36043605
url_from,
36053606
headers={
@@ -3722,10 +3723,11 @@ async def resolve(
37223723
async def close(self) -> None:
37233724
"""Dummy"""
37243725

3725-
connector = aiohttp.TCPConnector(resolver=FakeResolver(), ssl=False)
3726+
connector = aiohttp.TCPConnector(resolver=FakeResolver())
37263727

37273728
async with aiohttp.ClientSession(
37283729
connector=connector,
3730+
ssl=False,
37293731
headers={"Authorization": "Basic dXNlcjpwYXNz"},
37303732
) as client:
37313733
async with client.get(url_from) as resp:

‎tests/test_connector.py‎

Lines changed: 40 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2702,12 +2702,45 @@ async def test_invalid_ssl_param() -> None:
27022702

27032703
async def test_tcp_connector_ctor_fingerprint_valid() -> None:
27042704
valid = aiohttp.Fingerprint(hashlib.sha256(b"foo").digest())
2705-
conn = aiohttp.TCPConnector(ssl=valid)
2705+
with pytest.warns(DeprecationWarning, match="ssl parameter is deprecated"):
2706+
conn = aiohttp.TCPConnector(ssl=valid)
27062707
assert conn._ssl is valid
27072708

27082709
await conn.close()
27092710

27102711

2712+
async def test_tcp_connector_ssl_deprecated() -> None:
2713+
with pytest.warns(
2714+
DeprecationWarning,
2715+
match="ssl parameter is deprecated since 4.0 and scheduled for removal in 5.0",
2716+
):
2717+
conn = aiohttp.TCPConnector(ssl=False)
2718+
assert conn._ssl is False
2719+
2720+
await conn.close()
2721+
2722+
2723+
async def test_tcp_connector_ssl_default_not_deprecated() -> None:
2724+
with warnings.catch_warnings():
2725+
warnings.simplefilter("error")
2726+
conn = aiohttp.TCPConnector()
2727+
assert conn._ssl is True
2728+
2729+
await conn.close()
2730+
2731+
2732+
async def test_tcp_connector_fingerprint_from_deprecated_ssl_param() -> None:
2733+
"""The deprecated connector-level ssl is still used when the request has none."""
2734+
fingerprint = aiohttp.Fingerprint(hashlib.sha256(b"foo").digest())
2735+
with pytest.warns(DeprecationWarning, match="ssl parameter is deprecated"):
2736+
conn = aiohttp.TCPConnector(ssl=fingerprint)
2737+
req = mock.Mock()
2738+
req.ssl = True
2739+
assert conn._get_fingerprint(req) is fingerprint
2740+
2741+
await conn.close()
2742+
2743+
27112744
async def test_insecure_fingerprint_md5() -> None:
27122745
with pytest.raises(ValueError):
27132746
aiohttp.TCPConnector(ssl=aiohttp.Fingerprint(hashlib.md5(b"foo").digest()))
@@ -2787,7 +2820,8 @@ async def test___get_ssl_context2() -> None:
27872820

27882821
async def test___get_ssl_context3() -> None:
27892822
ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
2790-
conn = aiohttp.TCPConnector(ssl=ctx)
2823+
with pytest.warns(DeprecationWarning, match="ssl parameter is deprecated"):
2824+
conn = aiohttp.TCPConnector(ssl=ctx)
27912825
req = mock.Mock()
27922826
req.is_ssl.return_value = True
27932827
req.ssl = True
@@ -2798,7 +2832,8 @@ async def test___get_ssl_context3() -> None:
27982832

27992833
async def test___get_ssl_context4() -> None:
28002834
ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
2801-
conn = aiohttp.TCPConnector(ssl=ctx)
2835+
with pytest.warns(DeprecationWarning, match="ssl parameter is deprecated"):
2836+
conn = aiohttp.TCPConnector(ssl=ctx)
28022837
req = mock.Mock()
28032838
req.is_ssl.return_value = True
28042839
req.ssl = False
@@ -2809,7 +2844,8 @@ async def test___get_ssl_context4() -> None:
28092844

28102845
async def test___get_ssl_context5() -> None:
28112846
ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
2812-
conn = aiohttp.TCPConnector(ssl=ctx)
2847+
with pytest.warns(DeprecationWarning, match="ssl parameter is deprecated"):
2848+
conn = aiohttp.TCPConnector(ssl=ctx)
28132849
req = mock.Mock()
28142850
req.is_ssl.return_value = True
28152851
req.ssl = aiohttp.Fingerprint(hashlib.sha256(b"1").digest())

‎tests/test_proxy_functional.py‎

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -58,10 +58,7 @@ async def get_request(
5858
trust_env: bool = False,
5959
**kwargs: Any,
6060
) -> ClientResponse:
61-
connector = aiohttp.TCPConnector(ssl=False)
62-
async with aiohttp.ClientSession(
63-
connector=connector, trust_env=trust_env
64-
) as client:
61+
async with aiohttp.ClientSession(ssl=False, trust_env=trust_env) as client:
6562
async with client.request(method, url, **kwargs) as resp:
6663
return resp
6764

‎tests/test_web_sendfile_functional.py‎

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -742,8 +742,7 @@ async def test_static_file_ssl(
742742
app = web.Application()
743743
app.router.add_static("/static", dirname)
744744
server = await aiohttp_server(app, ssl=ssl_ctx)
745-
conn = aiohttp.TCPConnector(ssl=client_ssl_ctx)
746-
client = await aiohttp_client(server, connector=conn) # type: ignore[var-annotated]
745+
client = await aiohttp_client(server, ssl=client_ssl_ctx) # type: ignore[var-annotated]
747746

748747
resp = await client.get("/static/" + filename)
749748
assert 200 == resp.status
@@ -755,7 +754,6 @@ async def test_static_file_ssl(
755754

756755
resp.release()
757756
await client.close()
758-
await conn.close()
759757

760758

761759
async def test_static_file_directory_traversal_attack(

0 commit comments

Comments
 (0)