Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGES/11713.bugfix.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Fixed loading netrc credentials from the default ``~/.netrc`` location when the ``NETRC`` environment variable is not set -- by :user:`bdraco`.
1 change: 1 addition & 0 deletions CHANGES/11714.bugfix.rst
9 changes: 1 addition & 8 deletions aiohttp/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -590,14 +590,7 @@ async def _request(
auth = self._default_auth

# Try netrc if auth is still None and trust_env is enabled.
# Only check if NETRC environment variable is set to avoid
# creating an expensive executor job unnecessarily.
if (
auth is None
and self._trust_env
and url.host is not None
and os.environ.get("NETRC")
):
if auth is None and self._trust_env and url.host is not None:
auth = await self._loop.run_in_executor(
None, self._get_netrc_auth, url.host
)
Expand Down
19 changes: 19 additions & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import asyncio
import base64
import os
import platform
import socket
import ssl
import sys
Expand Down Expand Up @@ -316,6 +317,24 @@ def netrc_other_host(monkeypatch: pytest.MonkeyPatch, tmp_path: Path) -> Path:
return netrc_file


@pytest.fixture
def netrc_home_directory(monkeypatch: pytest.MonkeyPatch, tmp_path: Path) -> Path:
"""Create a netrc file in a mocked home directory without setting NETRC env var."""
# Create a fake home directory with .netrc or _netrc (Windows)
home_dir = tmp_path / "home"
home_dir.mkdir()
netrc_filename = "_netrc" if platform.system() == "Windows" else ".netrc"
netrc_file = home_dir / netrc_filename
netrc_file.write_text("default login netrc_user password netrc_pass\n")

# Mock Path.home() to return our fake home directory
monkeypatch.setattr("pathlib.Path.home", lambda: home_dir)
# Ensure NETRC env var is not set
monkeypatch.delenv("NETRC", raising=False)

return netrc_file


@pytest.fixture
def start_connection() -> Iterator[mock.Mock]:
with mock.patch(
Expand Down
18 changes: 16 additions & 2 deletions tests/test_client_functional.py
Original file line number Diff line number Diff line change
Expand Up @@ -3775,12 +3775,12 @@ async def test_netrc_auth_from_env( # type: ignore[misc]


@pytest.mark.usefixtures("no_netrc")
async def test_netrc_auth_skipped_without_env_var( # type: ignore[misc]
async def test_netrc_auth_skipped_without_netrc_file( # type: ignore[misc]
headers_echo_client: Callable[
..., Awaitable[TestClient[web.Request, web.Application]]
],
) -> None:
"""Test that netrc authentication is skipped when NETRC env var is not set."""
"""Test that netrc authentication is skipped when no netrc file exists."""
client = await headers_echo_client(trust_env=True)
async with client.get("/") as r:
assert r.status == 200
Expand All @@ -3789,6 +3789,20 @@ async def test_netrc_auth_skipped_without_env_var( # type: ignore[misc]
assert "Authorization" not in content["headers"]


@pytest.mark.usefixtures("netrc_home_directory")
async def test_netrc_auth_from_home_directory( # type: ignore[misc]
headers_echo_client: Callable[
..., Awaitable[TestClient[web.Request, web.Application]]
],
) -> None:
"""Test that netrc authentication works from default ~/.netrc without NETRC env var."""
client = await headers_echo_client(trust_env=True)
async with client.get("/") as r:
assert r.status == 200
content = await r.json()
assert content["headers"]["Authorization"] == "Basic bmV0cmNfdXNlcjpuZXRyY19wYXNz"


@pytest.mark.usefixtures("netrc_default_contents")
async def test_netrc_auth_overridden_by_explicit_auth( # type: ignore[misc]
headers_echo_client: Callable[
Expand Down
15 changes: 13 additions & 2 deletions tests/test_client_session.py
Original file line number Diff line number Diff line change
Expand Up @@ -1368,8 +1368,8 @@ async def test_netrc_auth_skipped_without_trust_env(auth_server: TestServer) ->


@pytest.mark.usefixtures("no_netrc")
async def test_netrc_auth_skipped_without_netrc_env(auth_server: TestServer) -> None:
"""Test that netrc authentication is skipped when NETRC env var is not set."""
async def test_netrc_auth_skipped_without_netrc_file(auth_server: TestServer) -> None:
"""Test that netrc authentication is skipped when no netrc file exists."""
async with (
ClientSession(trust_env=True) as session,
session.get(auth_server.make_url("/")) as resp,
Expand All @@ -1378,6 +1378,17 @@ async def test_netrc_auth_skipped_without_netrc_env(auth_server: TestServer) ->
assert text == "no_auth"


@pytest.mark.usefixtures("netrc_home_directory")
async def test_netrc_auth_from_home_directory(auth_server: TestServer) -> None:
"""Test that netrc authentication works from default ~/.netrc location without NETRC env var."""
async with (
ClientSession(trust_env=True) as session,
session.get(auth_server.make_url("/")) as resp,
):
text = await resp.text()
assert text == "auth:Basic bmV0cmNfdXNlcjpuZXRyY19wYXNz"


@pytest.mark.usefixtures("netrc_default_contents")
async def test_netrc_auth_overridden_by_explicit_auth(auth_server: TestServer) -> None:
"""Test that explicit auth parameter overrides netrc authentication."""
Expand Down
Loading