Skip to content
Open
Show file tree
Hide file tree
Changes from all 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 :file:`~/.netrc` (:file:`~/_netrc` on Windows) location when the :envvar:`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
18 changes: 18 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,23 @@ 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."""
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")

home_env_var = "USERPROFILE" if platform.system() == "Windows" else "HOME"
monkeypatch.setenv(home_env_var, str(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