Skip to content

Commit

Permalink
Make redirect URL auth take precedence over input auth in client
Browse files Browse the repository at this point in the history
  • Loading branch information
Pierre-Louis Peeters committed Oct 11, 2024
1 parent 3369cd9 commit f21bc20
Show file tree
Hide file tree
Showing 4 changed files with 65 additions and 1 deletion.
1 change: 1 addition & 0 deletions CHANGES/9436.bugfix.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Authentication provided by a redirect now takes precedence over provided ``auth`` when making requests with the client -- by :user:`PLPeeters`.
4 changes: 3 additions & 1 deletion aiohttp/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -566,7 +566,9 @@ async def _request(
"credentials encoded in URL"
)

if auth is None:
# Override the auth with the one from the URL only if we
# have no auth, or if we got an auth from a redirect URL
if auth is None or (history and auth_from_url is not None):
auth = auth_from_url

if auth is None and (
Expand Down
7 changes: 7 additions & 0 deletions docs/client_advanced.rst
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,13 @@ argument. An instance of :class:`BasicAuth` can be passed in like this::
async with ClientSession(auth=auth) as session:
...

Note that if the request is redirected and the redirect URL contains
credentials, those credentials will supersede any previously set credentials.
In other words, if ``http://user@example.com`` redirects to
``http://other_user@example.com``, the second request will be authenticated
as ``other_user``. Providing both the ``auth`` parameter and authentication in
the *initial* URL will result in a :exc:`ValueError`.

For other authentication flows, the ``Authorization`` header can be set
directly::

Expand Down
54 changes: 54 additions & 0 deletions tests/test_client_functional.py
Original file line number Diff line number Diff line change
Expand Up @@ -2980,6 +2980,60 @@ async def close(self) -> None:
assert resp.status == 200


async def test_creds_redirect_url_creds_precedence(
create_server_for_url_and_handler: Callable[[URL, Handler], Awaitable[TestServer]],
) -> None:
url_from = URL("http://example.com")
url_to = URL("http://user@example.com")

async def srv(request: web.Request) -> web.Response:
assert request.host == url_from.host

if request.headers.get(hdrs.AUTHORIZATION) == "Basic dXNlcjpwYXNz":
raise web.HTTPMovedPermanently(url_to)

return web.Response()

server = await create_server_for_url_and_handler(url_from, srv)

etc_hosts = {
(url_from.host, 80): server,
}

class FakeResolver(AbstractResolver):
async def resolve(
self,
host: str,
port: int = 0,
family: socket.AddressFamily = socket.AF_INET,
) -> List[ResolveResult]:
server = etc_hosts[(host, port)]
assert server.port is not None

return [
{
"hostname": host,
"host": server.host,
"port": server.port,
"family": socket.AF_INET,
"proto": 0,
"flags": socket.AI_NUMERICHOST,
}
]

async def close(self) -> None:
"""Dummy"""

connector = aiohttp.TCPConnector(resolver=FakeResolver(), ssl=False)

async with aiohttp.ClientSession(connector=connector) as client:
resp = await client.get(url_from, auth=aiohttp.BasicAuth("user", "pass"))
assert len(resp.history) == 1
assert str(resp.url) == "http://example.com"
assert resp.request_info.headers.get("authorization") == "Basic dXNlcjo="
assert resp.status == 200


@pytest.fixture
def create_server_for_url_and_handler(
aiohttp_server: AiohttpServer, tls_certificate_authority: trustme.CA
Expand Down

0 comments on commit f21bc20

Please sign in to comment.