Propagate credentials for `<index>/simple` to `<index>/...` endpoints (#11074)

Closes https://github.com/astral-sh/uv/issues/11017
Closes https://github.com/astral-sh/uv/issues/8565

Sort of an minimal implementation of
https://github.com/astral-sh/uv/issues/4583
This commit is contained in:
Zanie Blue 2025-01-30 10:22:21 -06:00 committed by GitHub
parent d281f49103
commit 1dfa650ab4
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 103 additions and 24 deletions

View File

@ -39,7 +39,7 @@ pub fn store_credentials_from_url(url: &Url) -> bool {
/// Populate the global authentication store with credentials on a URL, if there are any. /// Populate the global authentication store with credentials on a URL, if there are any.
/// ///
/// Returns `true` if the store was updated. /// Returns `true` if the store was updated.
pub fn store_credentials(url: &Url, credentials: Credentials) { pub fn store_credentials(url: &Url, credentials: Arc<Credentials>) {
trace!("Caching credentials for {url}"); trace!("Caching credentials for {url}");
CREDENTIALS_CACHE.insert(url, Arc::new(credentials)); CREDENTIALS_CACHE.insert(url, credentials);
} }

View File

@ -150,11 +150,27 @@ impl Index {
self.url self.url
} }
/// Return the raw [`URL`] of the index. /// Return the raw [`Url`] of the index.
pub fn raw_url(&self) -> &Url { pub fn raw_url(&self) -> &Url {
self.url.url() self.url.url()
} }
/// Return the root [`Url`] of the index, if applicable.
///
/// For indexes with a `/simple` endpoint, this is simply the URL with the final segment
/// removed. This is useful, e.g., for credential propagation to other endpoints on the index.
pub fn root_url(&self) -> Option<Url> {
let segments = self.raw_url().path_segments()?;
let last = segments.last()?;
if !last.eq_ignore_ascii_case("simple") {
return None;
}
let mut url = self.raw_url().clone();
url.path_segments_mut().ok()?.pop();
Some(url)
}
/// Retrieve the credentials for the index, either from the environment, or from the URL itself. /// Retrieve the credentials for the index, either from the environment, or from the URL itself.
pub fn credentials(&self) -> Option<Credentials> { pub fn credentials(&self) -> Option<Credentials> {
// If the index is named, and credentials are provided via the environment, prefer those. // If the index is named, and credentials are provided via the environment, prefer those.

View File

@ -3,6 +3,7 @@ use std::fmt::Write as _;
use std::io::Write as _; use std::io::Write as _;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use std::str::FromStr; use std::str::FromStr;
use std::sync::Arc;
use std::{fmt, io}; use std::{fmt, io};
use anyhow::{Context, Result}; use anyhow::{Context, Result};
@ -16,7 +17,6 @@ use crate::commands::reporters::PythonDownloadReporter;
use crate::commands::ExitStatus; use crate::commands::ExitStatus;
use crate::printer::Printer; use crate::printer::Printer;
use crate::settings::{ResolverSettings, ResolverSettingsRef}; use crate::settings::{ResolverSettings, ResolverSettingsRef};
use uv_auth::store_credentials;
use uv_build_backend::check_direct_build; use uv_build_backend::check_direct_build;
use uv_cache::{Cache, CacheBucket}; use uv_cache::{Cache, CacheBucket};
use uv_client::{BaseClientBuilder, Connectivity, FlatIndexClient, RegistryClientBuilder}; use uv_client::{BaseClientBuilder, Connectivity, FlatIndexClient, RegistryClientBuilder};
@ -496,7 +496,11 @@ async fn build_package(
// Add all authenticated sources to the cache. // Add all authenticated sources to the cache.
for index in index_locations.allowed_indexes() { for index in index_locations.allowed_indexes() {
if let Some(credentials) = index.credentials() { if let Some(credentials) = index.credentials() {
store_credentials(index.raw_url(), credentials); let credentials = Arc::new(credentials);
uv_auth::store_credentials(index.raw_url(), credentials.clone());
if let Some(root_url) = index.root_url() {
uv_auth::store_credentials(&root_url, credentials.clone());
}
} }
} }

View File

@ -1,6 +1,7 @@
use std::collections::BTreeSet; use std::collections::BTreeSet;
use std::env; use std::env;
use std::path::Path; use std::path::Path;
use std::sync::Arc;
use anyhow::{anyhow, Result}; use anyhow::{anyhow, Result};
use itertools::Itertools; use itertools::Itertools;
@ -292,7 +293,11 @@ pub(crate) async fn pip_compile(
// Add all authenticated sources to the cache. // Add all authenticated sources to the cache.
for index in index_locations.allowed_indexes() { for index in index_locations.allowed_indexes() {
if let Some(credentials) = index.credentials() { if let Some(credentials) = index.credentials() {
uv_auth::store_credentials(index.raw_url(), credentials); let credentials = Arc::new(credentials);
uv_auth::store_credentials(index.raw_url(), credentials.clone());
if let Some(root_url) = index.root_url() {
uv_auth::store_credentials(&root_url, credentials.clone());
}
} }
} }

View File

@ -1,5 +1,6 @@
use std::collections::BTreeSet; use std::collections::BTreeSet;
use std::fmt::Write; use std::fmt::Write;
use std::sync::Arc;
use itertools::Itertools; use itertools::Itertools;
use owo_colors::OwoColorize; use owo_colors::OwoColorize;
@ -311,7 +312,11 @@ pub(crate) async fn pip_install(
// Add all authenticated sources to the cache. // Add all authenticated sources to the cache.
for index in index_locations.allowed_indexes() { for index in index_locations.allowed_indexes() {
if let Some(credentials) = index.credentials() { if let Some(credentials) = index.credentials() {
uv_auth::store_credentials(index.raw_url(), credentials); let credentials = Arc::new(credentials);
uv_auth::store_credentials(index.raw_url(), credentials.clone());
if let Some(root_url) = index.root_url() {
uv_auth::store_credentials(&root_url, credentials.clone());
}
} }
} }

View File

@ -1,5 +1,6 @@
use std::collections::BTreeSet; use std::collections::BTreeSet;
use std::fmt::Write; use std::fmt::Write;
use std::sync::Arc;
use anyhow::Result; use anyhow::Result;
use owo_colors::OwoColorize; use owo_colors::OwoColorize;
@ -251,7 +252,11 @@ pub(crate) async fn pip_sync(
// Add all authenticated sources to the cache. // Add all authenticated sources to the cache.
for index in index_locations.allowed_indexes() { for index in index_locations.allowed_indexes() {
if let Some(credentials) = index.credentials() { if let Some(credentials) = index.credentials() {
uv_auth::store_credentials(index.raw_url(), credentials); let credentials = Arc::new(credentials);
uv_auth::store_credentials(index.raw_url(), credentials.clone());
if let Some(root_url) = index.root_url() {
uv_auth::store_credentials(&root_url, credentials.clone());
}
} }
} }

View File

@ -286,7 +286,11 @@ pub(crate) async fn add(
// Add all authenticated sources to the cache. // Add all authenticated sources to the cache.
for index in settings.index_locations.allowed_indexes() { for index in settings.index_locations.allowed_indexes() {
if let Some(credentials) = index.credentials() { if let Some(credentials) = index.credentials() {
uv_auth::store_credentials(index.raw_url(), credentials); let credentials = Arc::new(credentials);
uv_auth::store_credentials(index.raw_url(), credentials.clone());
if let Some(root_url) = index.root_url() {
uv_auth::store_credentials(&root_url, credentials.clone());
}
} }
} }

View File

@ -466,13 +466,21 @@ async fn do_lock(
// Add all authenticated sources to the cache. // Add all authenticated sources to the cache.
for index in index_locations.allowed_indexes() { for index in index_locations.allowed_indexes() {
if let Some(credentials) = index.credentials() { if let Some(credentials) = index.credentials() {
uv_auth::store_credentials(index.raw_url(), credentials); let credentials = Arc::new(credentials);
uv_auth::store_credentials(index.raw_url(), credentials.clone());
if let Some(root_url) = index.root_url() {
uv_auth::store_credentials(&root_url, credentials.clone());
}
} }
} }
for index in target.indexes() { for index in target.indexes() {
if let Some(credentials) = index.credentials() { if let Some(credentials) = index.credentials() {
uv_auth::store_credentials(index.raw_url(), credentials); let credentials = Arc::new(credentials);
uv_auth::store_credentials(index.raw_url(), credentials.clone());
if let Some(root_url) = index.root_url() {
uv_auth::store_credentials(&root_url, credentials.clone());
}
} }
} }

View File

@ -1078,7 +1078,11 @@ pub(crate) async fn resolve_names(
// Add all authenticated sources to the cache. // Add all authenticated sources to the cache.
for index in index_locations.allowed_indexes() { for index in index_locations.allowed_indexes() {
if let Some(credentials) = index.credentials() { if let Some(credentials) = index.credentials() {
uv_auth::store_credentials(index.raw_url(), credentials); let credentials = Arc::new(credentials);
uv_auth::store_credentials(index.raw_url(), credentials.clone());
if let Some(root_url) = index.root_url() {
uv_auth::store_credentials(&root_url, credentials.clone());
}
} }
} }
@ -1228,7 +1232,11 @@ pub(crate) async fn resolve_environment(
// Add all authenticated sources to the cache. // Add all authenticated sources to the cache.
for index in index_locations.allowed_indexes() { for index in index_locations.allowed_indexes() {
if let Some(credentials) = index.credentials() { if let Some(credentials) = index.credentials() {
uv_auth::store_credentials(index.raw_url(), credentials); let credentials = Arc::new(credentials);
uv_auth::store_credentials(index.raw_url(), credentials.clone());
if let Some(root_url) = index.root_url() {
uv_auth::store_credentials(&root_url, credentials.clone());
}
} }
} }
@ -1395,7 +1403,11 @@ pub(crate) async fn sync_environment(
// Add all authenticated sources to the cache. // Add all authenticated sources to the cache.
for index in index_locations.allowed_indexes() { for index in index_locations.allowed_indexes() {
if let Some(credentials) = index.credentials() { if let Some(credentials) = index.credentials() {
uv_auth::store_credentials(index.raw_url(), credentials); let credentials = Arc::new(credentials);
uv_auth::store_credentials(index.raw_url(), credentials.clone());
if let Some(root_url) = index.root_url() {
uv_auth::store_credentials(&root_url, credentials.clone());
}
} }
} }
@ -1590,7 +1602,11 @@ pub(crate) async fn update_environment(
// Add all authenticated sources to the cache. // Add all authenticated sources to the cache.
for index in index_locations.allowed_indexes() { for index in index_locations.allowed_indexes() {
if let Some(credentials) = index.credentials() { if let Some(credentials) = index.credentials() {
uv_auth::store_credentials(index.raw_url(), credentials); let credentials = Arc::new(credentials);
uv_auth::store_credentials(index.raw_url(), credentials.clone());
if let Some(root_url) = index.root_url() {
uv_auth::store_credentials(&root_url, credentials.clone());
}
} }
} }

View File

@ -1,9 +1,9 @@
use std::path::Path; use std::path::Path;
use std::sync::Arc;
use anyhow::{Context, Result}; use anyhow::{Context, Result};
use itertools::Itertools; use itertools::Itertools;
use uv_auth::store_credentials;
use uv_cache::Cache; use uv_cache::Cache;
use uv_client::{Connectivity, FlatIndexClient, RegistryClientBuilder}; use uv_client::{Connectivity, FlatIndexClient, RegistryClientBuilder};
use uv_configuration::{ use uv_configuration::{
@ -353,7 +353,11 @@ pub(super) async fn do_sync(
// Add all authenticated sources to the cache. // Add all authenticated sources to the cache.
for index in index_locations.allowed_indexes() { for index in index_locations.allowed_indexes() {
if let Some(credentials) = index.credentials() { if let Some(credentials) = index.credentials() {
store_credentials(index.raw_url(), credentials); let credentials = Arc::new(credentials);
uv_auth::store_credentials(index.raw_url(), credentials.clone());
if let Some(root_url) = index.root_url() {
uv_auth::store_credentials(&root_url, credentials.clone());
}
} }
} }
@ -520,7 +524,11 @@ fn store_credentials_from_target(target: InstallTarget<'_>) {
// Iterate over any idnexes in the target. // Iterate over any idnexes in the target.
for index in target.indexes() { for index in target.indexes() {
if let Some(credentials) = index.credentials() { if let Some(credentials) = index.credentials() {
store_credentials(index.raw_url(), credentials); let credentials = Arc::new(credentials);
uv_auth::store_credentials(index.raw_url(), credentials.clone());
if let Some(root_url) = index.root_url() {
uv_auth::store_credentials(&root_url, credentials.clone());
}
} }
} }

View File

@ -1,6 +1,7 @@
use std::fmt::Write; use std::fmt::Write;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use std::str::FromStr; use std::str::FromStr;
use std::sync::Arc;
use std::vec; use std::vec;
use anstream::eprint; use anstream::eprint;
@ -233,7 +234,11 @@ async fn venv_impl(
// Add all authenticated sources to the cache. // Add all authenticated sources to the cache.
for index in index_locations.allowed_indexes() { for index in index_locations.allowed_indexes() {
if let Some(credentials) = index.credentials() { if let Some(credentials) = index.credentials() {
uv_auth::store_credentials(index.raw_url(), credentials); let credentials = Arc::new(credentials);
uv_auth::store_credentials(index.raw_url(), credentials.clone());
if let Some(root_url) = index.root_url() {
uv_auth::store_credentials(&root_url, credentials.clone());
}
} }
} }
@ -280,7 +285,11 @@ async fn venv_impl(
// Add all authenticated sources to the cache. // Add all authenticated sources to the cache.
for index in index_locations.allowed_indexes() { for index in index_locations.allowed_indexes() {
if let Some(credentials) = index.credentials() { if let Some(credentials) = index.credentials() {
uv_auth::store_credentials(index.raw_url(), credentials); let credentials = Arc::new(credentials);
uv_auth::store_credentials(index.raw_url(), credentials.clone());
if let Some(root_url) = index.root_url() {
uv_auth::store_credentials(&root_url, credentials.clone());
}
} }
} }

View File

@ -8347,15 +8347,14 @@ fn lock_multiple_indexes_same_realm_different_credentials() -> Result<()> {
.env(EnvVars::index_password("INTERNAL_PROXY_HERON"), "heron") .env(EnvVars::index_password("INTERNAL_PROXY_HERON"), "heron")
.env(EnvVars::index_username("INTERNAL_PROXY_EAGLE"), "public") .env(EnvVars::index_username("INTERNAL_PROXY_EAGLE"), "public")
.env(EnvVars::index_password("INTERNAL_PROXY_EAGLE"), "eagle"), @r###" .env(EnvVars::index_password("INTERNAL_PROXY_EAGLE"), "eagle"), @r###"
success: false success: true
exit_code: 2 exit_code: 0
----- stdout ----- ----- stdout -----
----- stderr ----- ----- stderr -----
warning: Missing version constraint (e.g., a lower bound) for `iniconfig` warning: Missing version constraint (e.g., a lower bound) for `iniconfig`
warning: Missing version constraint (e.g., a lower bound) for `anyio` warning: Missing version constraint (e.g., a lower bound) for `anyio`
error: Failed to fetch: `https://pypi-proxy.fly.dev/basic-auth-heron/files/packages/ef/a6/62565a6e1cf69e10f5727360368e451d4b7f58beeac6173dc9db836a5b46/iniconfig-2.0.0-py3-none-any.whl.metadata` Resolved 5 packages in [TIME]
Caused by: HTTP status client error (401 Unauthorized) for url (https://pypi-proxy.fly.dev/basic-auth-heron/files/packages/ef/a6/62565a6e1cf69e10f5727360368e451d4b7f58beeac6173dc9db836a5b46/iniconfig-2.0.0-py3-none-any.whl.metadata)
"###); "###);
Ok(()) Ok(())