Use the root index URL when retrieving credentials from the native store (#15873)

Part of https://github.com/astral-sh/uv/issues/15818

We use the root when we store the credentials, so we need to use the
root when we retrieve them!
This commit is contained in:
Zanie Blue 2025-09-15 13:47:24 -05:00 committed by GitHub
parent daff98988b
commit 31f46cd6a6
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 43 additions and 34 deletions

View File

@ -95,9 +95,9 @@ impl Indexes {
index_urls index_urls
} }
/// Get the index URL prefix for a URL if one exists. /// Get the index for a URL if one exists.
pub fn index_url_for(&self, url: &Url) -> Option<&DisplaySafeUrl> { pub fn index_for(&self, url: &Url) -> Option<&Index> {
self.find_prefix_index(url).map(|index| &index.url) self.find_prefix_index(url)
} }
/// Get the [`AuthPolicy`] for a URL. /// Get the [`AuthPolicy`] for a URL.

View File

@ -22,7 +22,7 @@ use crate::{
realm::Realm, realm::Realm,
}; };
use crate::{TextCredentialStore, TomlCredentialError}; use crate::{Index, TextCredentialStore, TomlCredentialError};
/// Strategy for loading netrc files. /// Strategy for loading netrc files.
enum NetrcMode { enum NetrcMode {
@ -297,7 +297,7 @@ impl Middleware for AuthMiddleware {
// In the middleware, existing credentials are already moved from the URL // In the middleware, existing credentials are already moved from the URL
// to the headers so for display purposes we restore some information // to the headers so for display purposes we restore some information
let url = tracing_url(&request, request_credentials.as_ref()); let url = tracing_url(&request, request_credentials.as_ref());
let maybe_index_url = self.indexes.index_url_for(request.url()); let index = self.indexes.index_for(request.url());
let auth_policy = self.indexes.auth_policy_for(request.url()); let auth_policy = self.indexes.auth_policy_for(request.url());
trace!("Handling request for {url} with authentication policy {auth_policy}"); trace!("Handling request for {url} with authentication policy {auth_policy}");
@ -312,7 +312,7 @@ impl Middleware for AuthMiddleware {
extensions, extensions,
next, next,
&url, &url,
maybe_index_url, index,
auth_policy, auth_policy,
) )
.await; .await;
@ -406,8 +406,8 @@ impl Middleware for AuthMiddleware {
.as_ref() .as_ref()
.map(|credentials| credentials.to_username()) .map(|credentials| credentials.to_username())
.unwrap_or(Username::none()); .unwrap_or(Username::none());
let credentials = if let Some(index_url) = maybe_index_url { let credentials = if let Some(index) = index {
self.cache().get_url(index_url, &username).or_else(|| { self.cache().get_url(&index.url, &username).or_else(|| {
self.cache() self.cache()
.get_realm(Realm::from(&**retry_request_url), username) .get_realm(Realm::from(&**retry_request_url), username)
}) })
@ -435,7 +435,7 @@ impl Middleware for AuthMiddleware {
.fetch_credentials( .fetch_credentials(
credentials.as_deref(), credentials.as_deref(),
retry_request_url, retry_request_url,
maybe_index_url, index,
auth_policy, auth_policy,
) )
.await .await
@ -529,7 +529,7 @@ impl AuthMiddleware {
extensions: &mut Extensions, extensions: &mut Extensions,
next: Next<'_>, next: Next<'_>,
url: &DisplaySafeUrl, url: &DisplaySafeUrl,
index_url: Option<&DisplaySafeUrl>, index: Option<&Index>,
auth_policy: AuthPolicy, auth_policy: AuthPolicy,
) -> reqwest_middleware::Result<Response> { ) -> reqwest_middleware::Result<Response> {
let credentials = Arc::new(credentials); let credentials = Arc::new(credentials);
@ -545,11 +545,15 @@ impl AuthMiddleware {
trace!("Request for {url} is missing a password, looking for credentials"); trace!("Request for {url} is missing a password, looking for credentials");
// There's just a username, try to find a password. // There's just a username, try to find a password.
// If we have an index URL, check the cache for that URL. Otherwise, // If we have an index, check the cache for that URL. Otherwise,
// check for the realm. // check for the realm.
let maybe_cached_credentials = if let Some(index_url) = index_url { let maybe_cached_credentials = if let Some(index) = index {
self.cache() self.cache()
.get_url(index_url, credentials.as_username().as_ref()) .get_url(&index.url, credentials.as_username().as_ref())
.or_else(|| {
self.cache()
.get_url(&index.root_url, credentials.as_username().as_ref())
})
} else { } else {
self.cache() self.cache()
.get_realm(Realm::from(request.url()), credentials.to_username()) .get_realm(Realm::from(request.url()), credentials.to_username())
@ -574,14 +578,14 @@ impl AuthMiddleware {
.fetch_credentials( .fetch_credentials(
Some(&credentials), Some(&credentials),
DisplaySafeUrl::ref_cast(request.url()), DisplaySafeUrl::ref_cast(request.url()),
index_url, index,
auth_policy, auth_policy,
) )
.await .await
{ {
request = credentials.authenticate(request); request = credentials.authenticate(request);
Some(credentials) Some(credentials)
} else if index_url.is_some() { } else if index.is_some() {
// If this is a known index, we fall back to checking for the realm. // If this is a known index, we fall back to checking for the realm.
if let Some(credentials) = self if let Some(credentials) = self
.cache() .cache()
@ -608,7 +612,7 @@ impl AuthMiddleware {
&self, &self,
credentials: Option<&Credentials>, credentials: Option<&Credentials>,
url: &DisplaySafeUrl, url: &DisplaySafeUrl,
maybe_index_url: Option<&DisplaySafeUrl>, index: Option<&Index>,
auth_policy: AuthPolicy, auth_policy: AuthPolicy,
) -> Option<Arc<Credentials>> { ) -> Option<Arc<Credentials>> {
let username = Username::from( let username = Username::from(
@ -617,8 +621,8 @@ impl AuthMiddleware {
// Fetches can be expensive, so we will only run them _once_ per realm or index URL and username combination // Fetches can be expensive, so we will only run them _once_ per realm or index URL and username combination
// All other requests for the same realm or index URL will wait until the first one completes // All other requests for the same realm or index URL will wait until the first one completes
let key = if let Some(index_url) = maybe_index_url { let key = if let Some(index) = index {
(FetchUrl::Index(index_url.clone()), username) (FetchUrl::Index(index.url.clone()), username)
} else { } else {
(FetchUrl::Realm(Realm::from(&**url)), username) (FetchUrl::Realm(Realm::from(&**url)), username)
}; };
@ -719,13 +723,16 @@ impl AuthMiddleware {
} else { } else {
String::new() String::new()
}; };
if let Some(index_url) = maybe_index_url { if let Some(index) = index {
debug!("Checking native store for credentials for index URL {}{}", display_username, index_url); // N.B. The native store performs an exact look up right now, so we use the root
native_store.fetch(DisplaySafeUrl::ref_cast(index_url), username).await // URL of the index instead of relying on prefix-matching.
debug!("Checking native store for credentials for index URL {}{}", display_username, index.root_url);
native_store.fetch(&index.root_url, username).await
} else { } else {
debug!("Checking native store for credentials for URL {}{}", display_username, url); debug!("Checking native store for credentials for URL {}{}", display_username, url);
native_store.fetch(url, username).await native_store.fetch(url, username).await
} }
// TODO(zanieb): We should have a realm fallback here too
} else { } else {
None None
} }
@ -742,19 +749,20 @@ impl AuthMiddleware {
// URLs; instead, we fetch if there's a username or if the user has requested to // URLs; instead, we fetch if there's a username or if the user has requested to
// always authenticate. // always authenticate.
if let Some(username) = credentials.and_then(|credentials| credentials.username()) { if let Some(username) = credentials.and_then(|credentials| credentials.username()) {
if let Some(index_url) = maybe_index_url { if let Some(index) = index {
debug!("Checking keyring for credentials for index URL {}@{}", username, index_url); debug!("Checking keyring for credentials for index URL {}@{}", username, index.url);
keyring.fetch(DisplaySafeUrl::ref_cast(index_url), Some(username)).await keyring.fetch(DisplaySafeUrl::ref_cast(&index.url), Some(username)).await
} else { } else {
debug!("Checking keyring for credentials for full URL {}@{}", username, url); debug!("Checking keyring for credentials for full URL {}@{}", username, url);
keyring.fetch(url, Some(username)).await keyring.fetch(url, Some(username)).await
} }
} else if matches!(auth_policy, AuthPolicy::Always) { } else if matches!(auth_policy, AuthPolicy::Always) {
if let Some(index_url) = maybe_index_url { if let Some(index) = index {
debug!( debug!(
"Checking keyring for credentials for index URL {index_url} without username due to `authenticate = always`" "Checking keyring for credentials for index URL {} without username due to `authenticate = always`",
index.url
); );
keyring.fetch(DisplaySafeUrl::ref_cast(index_url), None).await keyring.fetch(DisplaySafeUrl::ref_cast(&index.url), None).await
} else { } else {
None None
} }

View File

@ -176,16 +176,17 @@ fn add_package_native_auth() -> Result<()> {
// credentials storied in the system keyring. // credentials storied in the system keyring.
uv_snapshot!(context.add().arg("anyio").arg("--default-index").arg("https://public@pypi-proxy.fly.dev/basic-auth/simple") uv_snapshot!(context.add().arg("anyio").arg("--default-index").arg("https://public@pypi-proxy.fly.dev/basic-auth/simple")
.env(EnvVars::UV_PREVIEW_FEATURES, "native-auth"), @r" .env(EnvVars::UV_PREVIEW_FEATURES, "native-auth"), @r"
success: false success: true
exit_code: 1 exit_code: 0
----- stdout ----- ----- stdout -----
----- stderr ----- ----- stderr -----
× No solution found when resolving dependencies: Resolved 4 packages in [TIME]
Because anyio was not found in the package registry and your project depends on anyio, we can conclude that your project's requirements are unsatisfiable. Prepared 3 packages in [TIME]
Installed 3 packages in [TIME]
hint: An index URL (https://pypi-proxy.fly.dev/basic-auth/simple) could not be queried due to a lack of valid authentication credentials (401 Unauthorized). + anyio==4.3.0
help: If you want to add the package regardless of the failed resolution, provide the `--frozen` flag to skip locking and syncing. + idna==3.6
+ sniffio==1.3.1
" "
); );