mirror of
https://github.com/astral-sh/uv.git
synced 2025-08-04 10:58:28 +00:00
Add in-URL credentials to store prior to creating requests (#2446)
## Summary The authentication middleware extracts in-URL credentials from URLs that pass through it; however, by the time a request reaches the store, the credentials will have already been removed, and relocated to the header. So we were never propagating in-URL credentials. This PR adds an explicit pass wherein we pass in-URL credentials to the store prior to doing any work. Closes https://github.com/astral-sh/uv/issues/2444. ## Test Plan `cargo run pip install` against an authenticated AWS registry.
This commit is contained in:
parent
d29645ce75
commit
f1aec3e779
9 changed files with 53 additions and 22 deletions
|
@ -53,11 +53,13 @@ impl File {
|
||||||
size: file.size,
|
size: file.size,
|
||||||
upload_time_utc_ms: file.upload_time.map(|dt| dt.timestamp_millis()),
|
upload_time_utc_ms: file.upload_time.map(|dt| dt.timestamp_millis()),
|
||||||
url: if file.url.contains("://") {
|
url: if file.url.contains("://") {
|
||||||
|
// Copy over any credentials from the global store.
|
||||||
let url = Url::parse(&file.url)
|
let url = Url::parse(&file.url)
|
||||||
.map_err(|err| FileConversionError::Url(file.url.clone(), err))?;
|
.map_err(|err| FileConversionError::Url(file.url.clone(), err))?;
|
||||||
let url = GLOBAL_AUTH_STORE.with_url_encoded_auth(url);
|
let url = GLOBAL_AUTH_STORE.with_url_encoded_auth(url);
|
||||||
FileLocation::AbsoluteUrl(url.to_string())
|
FileLocation::AbsoluteUrl(url.to_string())
|
||||||
} else {
|
} else {
|
||||||
|
// It's assumed that the base URL already contains any necessary credentials.
|
||||||
FileLocation::RelativeUrl(base.to_string(), file.url)
|
FileLocation::RelativeUrl(base.to_string(), file.url)
|
||||||
},
|
},
|
||||||
yanked: file.yanked,
|
yanked: file.yanked,
|
||||||
|
|
|
@ -26,6 +26,16 @@ pub enum IndexUrl {
|
||||||
Url(VerbatimUrl),
|
Url(VerbatimUrl),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl IndexUrl {
|
||||||
|
/// Return the raw URL for the index.
|
||||||
|
pub(crate) fn url(&self) -> &Url {
|
||||||
|
match self {
|
||||||
|
Self::Pypi(url) => url.raw(),
|
||||||
|
Self::Url(url) => url.raw(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Display for IndexUrl {
|
impl Display for IndexUrl {
|
||||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||||
match self {
|
match self {
|
||||||
|
@ -268,6 +278,16 @@ impl<'a> IndexLocations {
|
||||||
no_index: self.no_index,
|
no_index: self.no_index,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Return an iterator over all [`Url`] entries.
|
||||||
|
pub fn urls(&'a self) -> impl Iterator<Item = &'a Url> + 'a {
|
||||||
|
self.indexes()
|
||||||
|
.map(IndexUrl::url)
|
||||||
|
.chain(self.flat_index.iter().filter_map(|index| match index {
|
||||||
|
FlatIndexLocation::Path(_) => None,
|
||||||
|
FlatIndexLocation::Url(url) => Some(url),
|
||||||
|
}))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The index URLs to use for fetching packages.
|
/// The index URLs to use for fetching packages.
|
||||||
|
|
|
@ -57,21 +57,6 @@ pub struct BaseUrl(
|
||||||
);
|
);
|
||||||
|
|
||||||
impl BaseUrl {
|
impl BaseUrl {
|
||||||
/// Parse the given URL. If it's relative, join it to the current [`BaseUrl`]. Allows for
|
|
||||||
/// parsing URLs that may be absolute or relative, with a known base URL.
|
|
||||||
pub fn join_relative(&self, url: &str) -> Result<Url, url::ParseError> {
|
|
||||||
match Url::parse(url) {
|
|
||||||
Ok(url) => Ok(url),
|
|
||||||
Err(err) => {
|
|
||||||
if err == url::ParseError::RelativeUrlWithoutBase {
|
|
||||||
self.0.join(url)
|
|
||||||
} else {
|
|
||||||
Err(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Return the underlying [`Url`].
|
/// Return the underlying [`Url`].
|
||||||
pub fn as_url(&self) -> &Url {
|
pub fn as_url(&self) -> &Url {
|
||||||
&self.0
|
&self.0
|
||||||
|
|
|
@ -44,12 +44,11 @@ impl Middleware for AuthMiddleware {
|
||||||
next: Next<'_>,
|
next: Next<'_>,
|
||||||
) -> reqwest_middleware::Result<Response> {
|
) -> reqwest_middleware::Result<Response> {
|
||||||
let url = req.url().clone();
|
let url = req.url().clone();
|
||||||
|
|
||||||
// If the request already has an authorization header, we don't need to do anything.
|
// If the request already has an authorization header, we don't need to do anything.
|
||||||
// This gives in-URL credentials precedence over the netrc file.
|
// This gives in-URL credentials precedence over the netrc file.
|
||||||
if req.headers().contains_key(reqwest::header::AUTHORIZATION) {
|
if req.headers().contains_key(reqwest::header::AUTHORIZATION) {
|
||||||
if !url.username().is_empty() {
|
debug!("Request already has an authorization header: {url}");
|
||||||
GLOBAL_AUTH_STORE.save_from_url(&url);
|
|
||||||
}
|
|
||||||
return next.run(req, _extensions).await;
|
return next.run(req, _extensions).await;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -57,6 +56,7 @@ impl Middleware for AuthMiddleware {
|
||||||
if let Some(stored_auth) = GLOBAL_AUTH_STORE.get(&url) {
|
if let Some(stored_auth) = GLOBAL_AUTH_STORE.get(&url) {
|
||||||
// If we've already seen this URL, we can use the stored credentials
|
// If we've already seen this URL, we can use the stored credentials
|
||||||
if let Some(auth) = stored_auth {
|
if let Some(auth) = stored_auth {
|
||||||
|
debug!("Adding authentication to already-seen URL: {url}");
|
||||||
match auth {
|
match auth {
|
||||||
Credential::Basic(_) => {
|
Credential::Basic(_) => {
|
||||||
req.headers_mut().insert(
|
req.headers_mut().insert(
|
||||||
|
@ -67,6 +67,8 @@ impl Middleware for AuthMiddleware {
|
||||||
// Url must already have auth if before middleware runs - see `AuthenticationStore::with_url_encoded_auth`
|
// Url must already have auth if before middleware runs - see `AuthenticationStore::with_url_encoded_auth`
|
||||||
Credential::UrlEncoded(_) => (),
|
Credential::UrlEncoded(_) => (),
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
debug!("No credentials found for already-seen URL: {url}");
|
||||||
}
|
}
|
||||||
} else if let Some(auth) = self.nrc.as_ref().and_then(|nrc| {
|
} else if let Some(auth) = self.nrc.as_ref().and_then(|nrc| {
|
||||||
// If we find a matching entry in the netrc file, we can use it
|
// If we find a matching entry in the netrc file, we can use it
|
||||||
|
@ -100,6 +102,7 @@ impl Middleware for AuthMiddleware {
|
||||||
|
|
||||||
// If we still don't have any credentials, we save the URL so we don't have to check netrc or keyring again
|
// If we still don't have any credentials, we save the URL so we don't have to check netrc or keyring again
|
||||||
if !req.headers().contains_key(reqwest::header::AUTHORIZATION) {
|
if !req.headers().contains_key(reqwest::header::AUTHORIZATION) {
|
||||||
|
debug!("No credentials found for: {url}");
|
||||||
GLOBAL_AUTH_STORE.set(&url, None);
|
GLOBAL_AUTH_STORE.set(&url, None);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -102,6 +102,7 @@ impl AuthenticationStore {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Store in-URL credentials for future use.
|
||||||
pub fn save_from_url(&self, url: &Url) {
|
pub fn save_from_url(&self, url: &Url) {
|
||||||
let netloc = NetLoc::from(url);
|
let netloc = NetLoc::from(url);
|
||||||
let mut credentials = self.credentials.lock().unwrap();
|
let mut credentials = self.credentials.lock().unwrap();
|
||||||
|
|
|
@ -18,7 +18,7 @@ use tracing::debug;
|
||||||
use distribution_types::{IndexLocations, LocalEditable, Verbatim};
|
use distribution_types::{IndexLocations, LocalEditable, Verbatim};
|
||||||
use platform_tags::Tags;
|
use platform_tags::Tags;
|
||||||
use requirements_txt::EditableRequirement;
|
use requirements_txt::EditableRequirement;
|
||||||
use uv_auth::KeyringProvider;
|
use uv_auth::{KeyringProvider, GLOBAL_AUTH_STORE};
|
||||||
use uv_cache::Cache;
|
use uv_cache::Cache;
|
||||||
use uv_client::{Connectivity, FlatIndex, FlatIndexClient, RegistryClientBuilder};
|
use uv_client::{Connectivity, FlatIndex, FlatIndexClient, RegistryClientBuilder};
|
||||||
use uv_dispatch::BuildDispatch;
|
use uv_dispatch::BuildDispatch;
|
||||||
|
@ -187,6 +187,11 @@ pub(crate) async fn pip_compile(
|
||||||
let index_locations =
|
let index_locations =
|
||||||
index_locations.combine(index_url, extra_index_urls, find_links, no_index);
|
index_locations.combine(index_url, extra_index_urls, find_links, no_index);
|
||||||
|
|
||||||
|
// Add all authenticated sources to the store.
|
||||||
|
for url in index_locations.urls() {
|
||||||
|
GLOBAL_AUTH_STORE.save_from_url(url);
|
||||||
|
}
|
||||||
|
|
||||||
// Initialize the registry client.
|
// Initialize the registry client.
|
||||||
let client = RegistryClientBuilder::new(cache.clone())
|
let client = RegistryClientBuilder::new(cache.clone())
|
||||||
.native_tls(native_tls)
|
.native_tls(native_tls)
|
||||||
|
|
|
@ -20,7 +20,7 @@ use pep508_rs::{MarkerEnvironment, Requirement};
|
||||||
use platform_tags::Tags;
|
use platform_tags::Tags;
|
||||||
use pypi_types::Yanked;
|
use pypi_types::Yanked;
|
||||||
use requirements_txt::EditableRequirement;
|
use requirements_txt::EditableRequirement;
|
||||||
use uv_auth::KeyringProvider;
|
use uv_auth::{KeyringProvider, GLOBAL_AUTH_STORE};
|
||||||
use uv_cache::Cache;
|
use uv_cache::Cache;
|
||||||
use uv_client::{Connectivity, FlatIndex, FlatIndexClient, RegistryClient, RegistryClientBuilder};
|
use uv_client::{Connectivity, FlatIndex, FlatIndexClient, RegistryClient, RegistryClientBuilder};
|
||||||
use uv_dispatch::BuildDispatch;
|
use uv_dispatch::BuildDispatch;
|
||||||
|
@ -181,6 +181,11 @@ pub(crate) async fn pip_install(
|
||||||
let index_locations =
|
let index_locations =
|
||||||
index_locations.combine(index_url, extra_index_urls, find_links, no_index);
|
index_locations.combine(index_url, extra_index_urls, find_links, no_index);
|
||||||
|
|
||||||
|
// Add all authenticated sources to the store.
|
||||||
|
for url in index_locations.urls() {
|
||||||
|
GLOBAL_AUTH_STORE.save_from_url(url);
|
||||||
|
}
|
||||||
|
|
||||||
// Initialize the registry client.
|
// Initialize the registry client.
|
||||||
let client = RegistryClientBuilder::new(cache.clone())
|
let client = RegistryClientBuilder::new(cache.clone())
|
||||||
.native_tls(native_tls)
|
.native_tls(native_tls)
|
||||||
|
|
|
@ -10,7 +10,7 @@ use install_wheel_rs::linker::LinkMode;
|
||||||
use platform_tags::Tags;
|
use platform_tags::Tags;
|
||||||
use pypi_types::Yanked;
|
use pypi_types::Yanked;
|
||||||
use requirements_txt::EditableRequirement;
|
use requirements_txt::EditableRequirement;
|
||||||
use uv_auth::KeyringProvider;
|
use uv_auth::{KeyringProvider, GLOBAL_AUTH_STORE};
|
||||||
use uv_cache::{ArchiveTarget, ArchiveTimestamp, Cache};
|
use uv_cache::{ArchiveTarget, ArchiveTimestamp, Cache};
|
||||||
use uv_client::{Connectivity, FlatIndex, FlatIndexClient, RegistryClient, RegistryClientBuilder};
|
use uv_client::{Connectivity, FlatIndex, FlatIndexClient, RegistryClient, RegistryClientBuilder};
|
||||||
use uv_dispatch::BuildDispatch;
|
use uv_dispatch::BuildDispatch;
|
||||||
|
@ -115,6 +115,11 @@ pub(crate) async fn pip_sync(
|
||||||
let index_locations =
|
let index_locations =
|
||||||
index_locations.combine(index_url, extra_index_urls, find_links, no_index);
|
index_locations.combine(index_url, extra_index_urls, find_links, no_index);
|
||||||
|
|
||||||
|
// Add all authenticated sources to the store.
|
||||||
|
for url in index_locations.urls() {
|
||||||
|
GLOBAL_AUTH_STORE.save_from_url(url);
|
||||||
|
}
|
||||||
|
|
||||||
// Initialize the registry client.
|
// Initialize the registry client.
|
||||||
let client = RegistryClientBuilder::new(cache.clone())
|
let client = RegistryClientBuilder::new(cache.clone())
|
||||||
.native_tls(native_tls)
|
.native_tls(native_tls)
|
||||||
|
|
|
@ -13,7 +13,7 @@ use thiserror::Error;
|
||||||
|
|
||||||
use distribution_types::{DistributionMetadata, IndexLocations, Name};
|
use distribution_types::{DistributionMetadata, IndexLocations, Name};
|
||||||
use pep508_rs::Requirement;
|
use pep508_rs::Requirement;
|
||||||
use uv_auth::KeyringProvider;
|
use uv_auth::{KeyringProvider, GLOBAL_AUTH_STORE};
|
||||||
use uv_cache::Cache;
|
use uv_cache::Cache;
|
||||||
use uv_client::{Connectivity, FlatIndex, FlatIndexClient, RegistryClientBuilder};
|
use uv_client::{Connectivity, FlatIndex, FlatIndexClient, RegistryClientBuilder};
|
||||||
use uv_dispatch::BuildDispatch;
|
use uv_dispatch::BuildDispatch;
|
||||||
|
@ -140,6 +140,11 @@ async fn venv_impl(
|
||||||
// Extract the interpreter.
|
// Extract the interpreter.
|
||||||
let interpreter = venv.interpreter();
|
let interpreter = venv.interpreter();
|
||||||
|
|
||||||
|
// Add all authenticated sources to the store.
|
||||||
|
for url in index_locations.urls() {
|
||||||
|
GLOBAL_AUTH_STORE.save_from_url(url);
|
||||||
|
}
|
||||||
|
|
||||||
// Instantiate a client.
|
// Instantiate a client.
|
||||||
let client = RegistryClientBuilder::new(cache.clone())
|
let client = RegistryClientBuilder::new(cache.clone())
|
||||||
.native_tls(native_tls)
|
.native_tls(native_tls)
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue