Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

24 changes: 23 additions & 1 deletion crates/uv-auth/src/cache.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ use url::Url;
use uv_once_map::OnceMap;
use uv_redacted::DisplaySafeUrl;

use crate::Realm;
use crate::credentials::{Authentication, Username};
use crate::{Credentials, Realm};

type FxOnceMap<K, V> = OnceMap<K, V, BuildHasherDefault<FxHasher>>;

Expand All @@ -33,6 +33,7 @@ impl Display for FetchUrl {
}
}

#[derive(Debug)] // All internal types are redacted.
pub struct CredentialsCache {
/// A cache per realm and username
realms: RwLock<FxHashMap<(Realm, Username), Arc<Authentication>>>,
Expand All @@ -58,6 +59,27 @@ impl CredentialsCache {
}
}

/// 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_from_url(&self, url: &DisplaySafeUrl) -> bool {
if let Some(credentials) = Credentials::from_url(url) {
trace!("Caching credentials for {url}");
self.insert(url, Arc::new(Authentication::from(credentials)));
true
} else {
false
}
}

/// 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(&self, url: &DisplaySafeUrl, credentials: Credentials) {
trace!("Caching credentials for {url}");
self.insert(url, Arc::new(Authentication::from(credentials)));
}

/// Return the credentials that should be used for a realm and username, if any.
pub(crate) fn get_realm(
&self,
Expand Down
38 changes: 1 addition & 37 deletions crates/uv-auth/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,5 @@
use std::sync::{Arc, LazyLock};

use tracing::trace;

use uv_redacted::DisplaySafeUrl;

use crate::credentials::Authentication;
pub use access_token::AccessToken;
use cache::CredentialsCache;
pub use cache::CredentialsCache;
pub use credentials::{Credentials, Username};
pub use index::{AuthPolicy, Index, Indexes};
pub use keyring::KeyringProvider;
Expand All @@ -29,32 +22,3 @@ mod pyx;
mod realm;
mod service;
mod store;

// TODO(zanieb): Consider passing a cache explicitly throughout

/// Global authentication cache for a uv invocation
///
/// This is used to share credentials across uv clients.
pub(crate) static CREDENTIALS_CACHE: LazyLock<CredentialsCache> =
LazyLock::new(CredentialsCache::default);

/// 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_from_url(url: &DisplaySafeUrl) -> bool {
if let Some(credentials) = Credentials::from_url(url) {
trace!("Caching credentials for {url}");
CREDENTIALS_CACHE.insert(url, Arc::new(Authentication::from(credentials)));
true
} else {
false
}
}

/// 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: &DisplaySafeUrl, credentials: Credentials) {
trace!("Caching credentials for {url}");
CREDENTIALS_CACHE.insert(url, Arc::new(Authentication::from(credentials)));
}
35 changes: 21 additions & 14 deletions crates/uv-auth/src/middleware.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ use crate::credentials::Authentication;
use crate::providers::{HuggingFaceProvider, S3EndpointProvider};
use crate::pyx::{DEFAULT_TOLERANCE_SECS, PyxTokenStore};
use crate::{
AccessToken, CREDENTIALS_CACHE, CredentialsCache, KeyringProvider,
AccessToken, CredentialsCache, KeyringProvider,
cache::FetchUrl,
credentials::{Credentials, Username},
index::{AuthPolicy, Indexes},
Expand Down Expand Up @@ -126,7 +126,8 @@ pub struct AuthMiddleware {
netrc: NetrcMode,
text_store: TextStoreMode,
keyring: Option<KeyringProvider>,
cache: Option<CredentialsCache>,
/// Global authentication cache for a uv invocation to share credentials across uv clients.
cache: Arc<CredentialsCache>,
/// Auth policies for specific URLs.
indexes: Indexes,
/// Set all endpoints as needing authentication. We never try to send an
Expand All @@ -141,13 +142,20 @@ pub struct AuthMiddleware {
preview: Preview,
}

impl Default for AuthMiddleware {
fn default() -> Self {
Self::new()
}
}

impl AuthMiddleware {
pub fn new() -> Self {
Self {
netrc: NetrcMode::default(),
text_store: TextStoreMode::default(),
keyring: None,
cache: None,
// TODO(konsti): There shouldn't be a credential cache without that in the initializer.
cache: Arc::new(CredentialsCache::default()),
indexes: Indexes::new(),
only_authenticated: false,
base_client: None,
Expand Down Expand Up @@ -200,7 +208,14 @@ impl AuthMiddleware {
/// Configure the [`CredentialsCache`] to use.
#[must_use]
pub fn with_cache(mut self, cache: CredentialsCache) -> Self {
self.cache = Some(cache);
self.cache = Arc::new(cache);
self
}

/// Configure the [`CredentialsCache`] to use from an existing [`Arc`].
#[must_use]
pub fn with_cache_arc(mut self, cache: Arc<CredentialsCache>) -> Self {
self.cache = cache;
self
}

Expand Down Expand Up @@ -233,17 +248,9 @@ impl AuthMiddleware {
self
}

/// Get the configured authentication store.
///
/// If not set, the global store is used.
/// Global authentication cache for a uv invocation to share credentials across uv clients.
fn cache(&self) -> &CredentialsCache {
self.cache.as_ref().unwrap_or(&CREDENTIALS_CACHE)
}
}

impl Default for AuthMiddleware {
fn default() -> Self {
Self::new()
&self.cache
}
}

Expand Down
1 change: 1 addition & 0 deletions crates/uv-build-frontend/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ doctest = false
workspace = true

[dependencies]
uv-auth = { workspace = true }
uv-cache-key = { workspace = true }
uv-configuration = { workspace = true }
uv-distribution = { workspace = true }
Expand Down
9 changes: 8 additions & 1 deletion crates/uv-build-frontend/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ use tokio::io::AsyncBufReadExt;
use tokio::process::Command;
use tokio::sync::{Mutex, Semaphore};
use tracing::{Instrument, debug, info_span, instrument, warn};

use uv_auth::CredentialsCache;
use uv_cache_key::cache_digest;
use uv_configuration::{BuildKind, BuildOutput, SourceStrategy};
use uv_distribution::BuildRequires;
Expand Down Expand Up @@ -292,6 +292,7 @@ impl SourceBuild {
mut environment_variables: FxHashMap<OsString, OsString>,
level: BuildOutput,
concurrent_builds: usize,
credentials_cache: &CredentialsCache,
preview: Preview,
) -> Result<Self, Error> {
let temp_dir = build_context.cache().venv_dir()?;
Expand All @@ -312,6 +313,7 @@ impl SourceBuild {
source_strategy,
workspace_cache,
&default_backend,
credentials_cache,
)
.await
.map_err(|err| *err)?;
Expand Down Expand Up @@ -455,6 +457,7 @@ impl SourceBuild {
&environment_variables,
&modified_path,
&temp_dir,
credentials_cache,
)
.await?;
}
Expand Down Expand Up @@ -561,6 +564,7 @@ impl SourceBuild {
source_strategy: SourceStrategy,
workspace_cache: &WorkspaceCache,
default_backend: &Pep517Backend,
credentials_cache: &CredentialsCache,
) -> Result<(Pep517Backend, Option<Project>), Box<Error>> {
match fs::read_to_string(source_tree.join("pyproject.toml")) {
Ok(toml) => {
Expand Down Expand Up @@ -589,6 +593,7 @@ impl SourceBuild {
locations,
source_strategy,
workspace_cache,
credentials_cache,
)
.await
.map_err(Error::Lowering)?;
Expand Down Expand Up @@ -961,6 +966,7 @@ async fn create_pep517_build_environment(
environment_variables: &FxHashMap<OsString, OsString>,
modified_path: &OsString,
temp_dir: &TempDir,
credentials_cache: &CredentialsCache,
) -> Result<(), Error> {
// Write the hook output to a file so that we can read it back reliably.
let outfile = temp_dir
Expand Down Expand Up @@ -1055,6 +1061,7 @@ async fn create_pep517_build_environment(
locations,
source_strategy,
workspace_cache,
credentials_cache,
)
.await
.map_err(Error::Lowering)?;
Expand Down
33 changes: 29 additions & 4 deletions crates/uv-client/src/base_client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ use tracing::{debug, trace};
use url::ParseError;
use url::Url;

use uv_auth::{AuthMiddleware, Credentials, Indexes, PyxTokenStore};
use uv_auth::{AuthMiddleware, Credentials, CredentialsCache, Indexes, PyxTokenStore};
use uv_configuration::{KeyringProviderType, TrustedHost};
use uv_fs::Simplified;
use uv_pep508::MarkerEnvironment;
Expand Down Expand Up @@ -78,6 +78,8 @@ pub struct BaseClientBuilder<'a> {
markers: Option<&'a MarkerEnvironment>,
platform: Option<&'a Platform>,
auth_integration: AuthIntegration,
/// Global authentication cache for a uv invocation to share credentials across uv clients.
credentials_cache: Arc<CredentialsCache>,
indexes: Indexes,
timeout: Duration,
extra_middleware: Option<ExtraMiddleware>,
Expand Down Expand Up @@ -136,6 +138,7 @@ impl Default for BaseClientBuilder<'_> {
markers: None,
platform: None,
auth_integration: AuthIntegration::default(),
credentials_cache: Arc::new(CredentialsCache::default()),
indexes: Indexes::new(),
timeout: Duration::from_secs(30),
extra_middleware: None,
Expand All @@ -147,7 +150,7 @@ impl Default for BaseClientBuilder<'_> {
}
}

impl BaseClientBuilder<'_> {
impl<'a> BaseClientBuilder<'a> {
pub fn new(
connectivity: Connectivity,
native_tls: bool,
Expand All @@ -166,9 +169,7 @@ impl BaseClientBuilder<'_> {
..Self::default()
}
}
}

impl<'a> BaseClientBuilder<'a> {
/// Use a custom reqwest client instead of creating a new one.
///
/// This allows you to provide your own reqwest client with custom configuration.
Expand Down Expand Up @@ -276,6 +277,20 @@ impl<'a> BaseClientBuilder<'a> {
self
}

pub fn credentials_cache(&self) -> &CredentialsCache {
&self.credentials_cache
}

/// See [`CredentialsCache::store_credentials_from_url`].
pub fn store_credentials_from_url(&self, url: &DisplaySafeUrl) -> bool {
self.credentials_cache.store_credentials_from_url(url)
}

/// See [`CredentialsCache::store_credentials`].
pub fn store_credentials(&self, url: &DisplaySafeUrl, credentials: Credentials) {
self.credentials_cache.store_credentials(url, credentials);
}

pub fn is_native_tls(&self) -> bool {
self.native_tls
}
Expand Down Expand Up @@ -324,6 +339,7 @@ impl<'a> BaseClientBuilder<'a> {
dangerous_client,
raw_dangerous_client,
timeout,
credentials_cache: self.credentials_cache.clone(),
}
}

Expand All @@ -350,6 +366,7 @@ impl<'a> BaseClientBuilder<'a> {
raw_client: existing.raw_client.clone(),
raw_dangerous_client: existing.raw_dangerous_client.clone(),
timeout: existing.timeout,
credentials_cache: existing.credentials_cache.clone(),
}
}

Expand Down Expand Up @@ -554,6 +571,7 @@ impl<'a> BaseClientBuilder<'a> {
match self.auth_integration {
AuthIntegration::Default => {
let mut auth_middleware = AuthMiddleware::new()
.with_cache_arc(self.credentials_cache.clone())
.with_base_client(base_client)
.with_indexes(self.indexes.clone())
.with_keyring(self.keyring.to_provider())
Expand All @@ -565,6 +583,7 @@ impl<'a> BaseClientBuilder<'a> {
}
AuthIntegration::OnlyAuthenticated => {
let mut auth_middleware = AuthMiddleware::new()
.with_cache_arc(self.credentials_cache.clone())
.with_base_client(base_client)
.with_indexes(self.indexes.clone())
.with_keyring(self.keyring.to_provider())
Expand Down Expand Up @@ -608,6 +627,8 @@ pub struct BaseClient {
allow_insecure_host: Vec<TrustedHost>,
/// The number of retries to attempt on transient errors.
retries: u32,
/// Global authentication cache for a uv invocation to share credentials across uv clients.
credentials_cache: Arc<CredentialsCache>,
}

#[derive(Debug, Clone, Copy)]
Expand Down Expand Up @@ -659,6 +680,10 @@ impl BaseClient {
}
builder.build_with_max_retries(self.retries)
}

pub fn credentials_cache(&self) -> &CredentialsCache {
&self.credentials_cache
}
}

/// Wrapper around [`ClientWithMiddleware`] that manages redirects.
Expand Down
Loading
Loading