Fail with specific error message when no password on auth always (#12313)

This addresses a small part of #12280, namely when you have
`authenticate` set to `always`, it will output a distinct error message
for the case where you have a username but are missing a password.
This commit is contained in:
John Mumm 2025-03-19 17:43:45 +01:00 committed by GitHub
parent f7d9b0e2fa
commit 615cd6e045
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 55 additions and 7 deletions

View file

@ -196,6 +196,7 @@ impl Middleware for AuthMiddleware {
extensions,
next,
&url,
auth_policy,
)
.await;
}
@ -212,7 +213,9 @@ impl Middleware for AuthMiddleware {
// If it's fully authenticated, finish the request
if credentials.password().is_some() {
trace!("Request for {url} is fully authenticated");
return self.complete_request(None, request, extensions, next).await;
return self
.complete_request(None, request, extensions, next, auth_policy)
.await;
}
// If we just found a username, we'll make the request then look for password elsewhere
@ -286,7 +289,7 @@ impl Middleware for AuthMiddleware {
trace!("Retrying request for {url} with credentials from cache {credentials:?}");
retry_request = credentials.authenticate(retry_request);
return self
.complete_request(None, retry_request, extensions, next)
.complete_request(None, retry_request, extensions, next, auth_policy)
.await;
}
}
@ -300,7 +303,13 @@ impl Middleware for AuthMiddleware {
retry_request = credentials.authenticate(retry_request);
trace!("Retrying request for {url} with {credentials:?}");
return self
.complete_request(Some(credentials), retry_request, extensions, next)
.complete_request(
Some(credentials),
retry_request,
extensions,
next,
auth_policy,
)
.await;
}
@ -309,7 +318,7 @@ impl Middleware for AuthMiddleware {
trace!("Retrying request for {url} with username from cache {credentials:?}");
retry_request = credentials.authenticate(retry_request);
return self
.complete_request(None, retry_request, extensions, next)
.complete_request(None, retry_request, extensions, next, auth_policy)
.await;
}
}
@ -334,13 +343,16 @@ impl AuthMiddleware {
request: Request,
extensions: &mut Extensions,
next: Next<'_>,
auth_policy: AuthPolicy,
) -> reqwest_middleware::Result<Response> {
let Some(credentials) = credentials else {
// Nothing to insert into the cache if we don't have credentials
return next.run(request, extensions).await;
};
let url = request.url().clone();
if matches!(auth_policy, AuthPolicy::Always) && credentials.password().is_none() {
return Err(Error::Middleware(format_err!("Missing password for {url}")));
}
let result = next.run(request, extensions).await;
// Update the cache with new credentials on a successful request
@ -363,6 +375,7 @@ impl AuthMiddleware {
extensions: &mut Extensions,
next: Next<'_>,
url: &str,
auth_policy: AuthPolicy,
) -> reqwest_middleware::Result<Response> {
let credentials = Arc::new(credentials);
@ -370,7 +383,7 @@ impl AuthMiddleware {
if credentials.password().is_some() {
trace!("Request for {url} is already fully authenticated");
return self
.complete_request(Some(credentials), request, extensions, next)
.complete_request(Some(credentials), request, extensions, next, auth_policy)
.await;
}
@ -402,7 +415,7 @@ impl AuthMiddleware {
};
return self
.complete_request(credentials, request, extensions, next)
.complete_request(credentials, request, extensions, next, auth_policy)
.await;
}

View file

@ -10081,6 +10081,41 @@ fn add_auth_policy_always_without_credentials() -> Result<()> {
Ok(())
}
/// In authentication "always", authenticated requests with a username but
/// no discoverable password will fail.
#[test]
fn add_auth_policy_always_with_username_no_password() -> Result<()> {
let context = TestContext::new("3.12");
let pyproject_toml = context.temp_dir.child("pyproject.toml");
pyproject_toml.write_str(indoc! { r#"
[project]
name = "foo"
version = "1.0.0"
requires-python = ">=3.11, <4"
dependencies = []
[[tool.uv.index]]
name = "my-index"
url = "https://public@pypi.org/simple"
authenticate = "always"
default = true
"#
})?;
uv_snapshot!(context.add().arg("anyio"), @r"
success: false
exit_code: 2
----- stdout -----
----- stderr -----
error: Failed to fetch: `https://pypi.org/simple/anyio/`
Caused by: Missing password for https://pypi.org/simple/anyio/
"
);
Ok(())
}
/// In authentication "never", even if the correct credentials are supplied
/// in the URL, no authenticated requests will be allowed.
#[test]