From 1dfa650ab415ef9dc25f6cb19b2e5f79083ae3fc Mon Sep 17 00:00:00 2001 From: Zanie Blue Date: Thu, 30 Jan 2025 10:22:21 -0600 Subject: [PATCH] Propagate credentials for `/simple` to `/...` 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 --- crates/uv-auth/src/lib.rs | 4 ++-- crates/uv-distribution-types/src/index.rs | 18 ++++++++++++++++- crates/uv/src/commands/build_frontend.rs | 8 ++++++-- crates/uv/src/commands/pip/compile.rs | 7 ++++++- crates/uv/src/commands/pip/install.rs | 7 ++++++- crates/uv/src/commands/pip/sync.rs | 7 ++++++- crates/uv/src/commands/project/add.rs | 6 +++++- crates/uv/src/commands/project/lock.rs | 12 ++++++++++-- crates/uv/src/commands/project/mod.rs | 24 +++++++++++++++++++---- crates/uv/src/commands/project/sync.rs | 14 ++++++++++--- crates/uv/src/commands/venv.rs | 13 ++++++++++-- crates/uv/tests/it/lock.rs | 7 +++---- 12 files changed, 103 insertions(+), 24 deletions(-) diff --git a/crates/uv-auth/src/lib.rs b/crates/uv-auth/src/lib.rs index 16f644418..a01c5a528 100644 --- a/crates/uv-auth/src/lib.rs +++ b/crates/uv-auth/src/lib.rs @@ -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. /// /// Returns `true` if the store was updated. -pub fn store_credentials(url: &Url, credentials: Credentials) { +pub fn store_credentials(url: &Url, credentials: Arc) { trace!("Caching credentials for {url}"); - CREDENTIALS_CACHE.insert(url, Arc::new(credentials)); + CREDENTIALS_CACHE.insert(url, credentials); } diff --git a/crates/uv-distribution-types/src/index.rs b/crates/uv-distribution-types/src/index.rs index 3fd439827..e8465ddae 100644 --- a/crates/uv-distribution-types/src/index.rs +++ b/crates/uv-distribution-types/src/index.rs @@ -150,11 +150,27 @@ impl Index { self.url } - /// Return the raw [`URL`] of the index. + /// Return the raw [`Url`] of the index. pub fn raw_url(&self) -> &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 { + 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. pub fn credentials(&self) -> Option { // If the index is named, and credentials are provided via the environment, prefer those. diff --git a/crates/uv/src/commands/build_frontend.rs b/crates/uv/src/commands/build_frontend.rs index 7901fe56b..6fafb8861 100644 --- a/crates/uv/src/commands/build_frontend.rs +++ b/crates/uv/src/commands/build_frontend.rs @@ -3,6 +3,7 @@ use std::fmt::Write as _; use std::io::Write as _; use std::path::{Path, PathBuf}; use std::str::FromStr; +use std::sync::Arc; use std::{fmt, io}; use anyhow::{Context, Result}; @@ -16,7 +17,6 @@ use crate::commands::reporters::PythonDownloadReporter; use crate::commands::ExitStatus; use crate::printer::Printer; use crate::settings::{ResolverSettings, ResolverSettingsRef}; -use uv_auth::store_credentials; use uv_build_backend::check_direct_build; use uv_cache::{Cache, CacheBucket}; use uv_client::{BaseClientBuilder, Connectivity, FlatIndexClient, RegistryClientBuilder}; @@ -496,7 +496,11 @@ async fn build_package( // Add all authenticated sources to the cache. for index in index_locations.allowed_indexes() { 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()); + } } } diff --git a/crates/uv/src/commands/pip/compile.rs b/crates/uv/src/commands/pip/compile.rs index c4c302ed1..c2a84df5e 100644 --- a/crates/uv/src/commands/pip/compile.rs +++ b/crates/uv/src/commands/pip/compile.rs @@ -1,6 +1,7 @@ use std::collections::BTreeSet; use std::env; use std::path::Path; +use std::sync::Arc; use anyhow::{anyhow, Result}; use itertools::Itertools; @@ -292,7 +293,11 @@ pub(crate) async fn pip_compile( // Add all authenticated sources to the cache. for index in index_locations.allowed_indexes() { 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()); + } } } diff --git a/crates/uv/src/commands/pip/install.rs b/crates/uv/src/commands/pip/install.rs index f508e4828..ee8113c96 100644 --- a/crates/uv/src/commands/pip/install.rs +++ b/crates/uv/src/commands/pip/install.rs @@ -1,5 +1,6 @@ use std::collections::BTreeSet; use std::fmt::Write; +use std::sync::Arc; use itertools::Itertools; use owo_colors::OwoColorize; @@ -311,7 +312,11 @@ pub(crate) async fn pip_install( // Add all authenticated sources to the cache. for index in index_locations.allowed_indexes() { 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()); + } } } diff --git a/crates/uv/src/commands/pip/sync.rs b/crates/uv/src/commands/pip/sync.rs index 91871f794..c6b6fa43b 100644 --- a/crates/uv/src/commands/pip/sync.rs +++ b/crates/uv/src/commands/pip/sync.rs @@ -1,5 +1,6 @@ use std::collections::BTreeSet; use std::fmt::Write; +use std::sync::Arc; use anyhow::Result; use owo_colors::OwoColorize; @@ -251,7 +252,11 @@ pub(crate) async fn pip_sync( // Add all authenticated sources to the cache. for index in index_locations.allowed_indexes() { 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()); + } } } diff --git a/crates/uv/src/commands/project/add.rs b/crates/uv/src/commands/project/add.rs index 4926d72fc..ea232aba8 100644 --- a/crates/uv/src/commands/project/add.rs +++ b/crates/uv/src/commands/project/add.rs @@ -286,7 +286,11 @@ pub(crate) async fn add( // Add all authenticated sources to the cache. for index in settings.index_locations.allowed_indexes() { 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()); + } } } diff --git a/crates/uv/src/commands/project/lock.rs b/crates/uv/src/commands/project/lock.rs index cacbcf330..9bc287ba7 100644 --- a/crates/uv/src/commands/project/lock.rs +++ b/crates/uv/src/commands/project/lock.rs @@ -466,13 +466,21 @@ async fn do_lock( // Add all authenticated sources to the cache. for index in index_locations.allowed_indexes() { 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() { 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()); + } } } diff --git a/crates/uv/src/commands/project/mod.rs b/crates/uv/src/commands/project/mod.rs index 7ceabe536..a5f38d9b9 100644 --- a/crates/uv/src/commands/project/mod.rs +++ b/crates/uv/src/commands/project/mod.rs @@ -1078,7 +1078,11 @@ pub(crate) async fn resolve_names( // Add all authenticated sources to the cache. for index in index_locations.allowed_indexes() { 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. for index in index_locations.allowed_indexes() { 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. for index in index_locations.allowed_indexes() { 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. for index in index_locations.allowed_indexes() { 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()); + } } } diff --git a/crates/uv/src/commands/project/sync.rs b/crates/uv/src/commands/project/sync.rs index 609c6bf87..1e8b8aacd 100644 --- a/crates/uv/src/commands/project/sync.rs +++ b/crates/uv/src/commands/project/sync.rs @@ -1,9 +1,9 @@ use std::path::Path; +use std::sync::Arc; use anyhow::{Context, Result}; use itertools::Itertools; -use uv_auth::store_credentials; use uv_cache::Cache; use uv_client::{Connectivity, FlatIndexClient, RegistryClientBuilder}; use uv_configuration::{ @@ -353,7 +353,11 @@ pub(super) async fn do_sync( // Add all authenticated sources to the cache. for index in index_locations.allowed_indexes() { 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. for index in target.indexes() { 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()); + } } } diff --git a/crates/uv/src/commands/venv.rs b/crates/uv/src/commands/venv.rs index 0fe661223..6c971c8c2 100644 --- a/crates/uv/src/commands/venv.rs +++ b/crates/uv/src/commands/venv.rs @@ -1,6 +1,7 @@ use std::fmt::Write; use std::path::{Path, PathBuf}; use std::str::FromStr; +use std::sync::Arc; use std::vec; use anstream::eprint; @@ -233,7 +234,11 @@ async fn venv_impl( // Add all authenticated sources to the cache. for index in index_locations.allowed_indexes() { 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. for index in index_locations.allowed_indexes() { 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()); + } } } diff --git a/crates/uv/tests/it/lock.rs b/crates/uv/tests/it/lock.rs index dfa6f7226..6907d39cb 100644 --- a/crates/uv/tests/it/lock.rs +++ b/crates/uv/tests/it/lock.rs @@ -8347,15 +8347,14 @@ fn lock_multiple_indexes_same_realm_different_credentials() -> Result<()> { .env(EnvVars::index_password("INTERNAL_PROXY_HERON"), "heron") .env(EnvVars::index_username("INTERNAL_PROXY_EAGLE"), "public") .env(EnvVars::index_password("INTERNAL_PROXY_EAGLE"), "eagle"), @r###" - success: false - exit_code: 2 + success: true + exit_code: 0 ----- stdout ----- ----- stderr ----- warning: Missing version constraint (e.g., a lower bound) for `iniconfig` 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` - 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) + Resolved 5 packages in [TIME] "###); Ok(())