Skip to content

Commit b230fb6

Browse files
jtfmummGankra
authored andcommitted
Use index URL instead of package URL for keyring credential lookups (#12651)
Some registries (like Azure Artifact) can require you to authenticate separately for every package URL if you do not authenticate for the /simple endpoint. These changes make the auth middleware aware of index URL endpoints and attempts to fetch keyring credentials for such an index URL when making a request to any URL it's a prefix of. The current uv behavior is to cache credentials either at the request URL or realm level. But with these changes, we also need to cache credentials at the index level. Note that when uv does not detect an index URL for a request URL, it will continue to apply the old behavior. Addresses part of #4056 Closes #4583 Closes #11236 Closes #11391 Closes #11507
1 parent 7a3ebc8 commit b230fb6

File tree

26 files changed

+572
-204
lines changed

26 files changed

+572
-204
lines changed

crates/uv-auth/src/cache.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,8 @@ type FxOnceMap<K, V> = OnceMap<K, V, BuildHasherDefault<FxHasher>>;
1717
pub struct CredentialsCache {
1818
/// A cache per realm and username
1919
realms: RwLock<FxHashMap<(Realm, Username), Arc<Credentials>>>,
20-
/// A cache tracking the result of fetches from external services
21-
pub(crate) fetches: FxOnceMap<(Realm, Username), Option<Arc<Credentials>>>,
20+
/// A cache tracking the result of realm or index URL fetches from external services
21+
pub(crate) fetches: FxOnceMap<(String, Username), Option<Arc<Credentials>>>,
2222
/// A cache per URL, uses a trie for efficient prefix queries.
2323
urls: RwLock<UrlTrie>,
2424
}

crates/uv-auth/src/policy.rs renamed to crates/uv-auth/src/index.rs

Lines changed: 46 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use std::fmt::{self, Display, Formatter};
22

3-
use rustc_hash::FxHashMap;
3+
use rustc_hash::FxHashSet;
44
use url::Url;
55

66
/// When to use authentication.
@@ -47,39 +47,68 @@ impl Display for AuthPolicy {
4747
}
4848
}
4949
}
50+
51+
// TODO(john): We are not using `uv_distribution_types::Index` directly
52+
// here because it would cause circular crate dependencies. However, this
53+
// could potentially make sense for a future refactor.
54+
#[derive(Debug, Clone, Hash, Eq, PartialEq)]
55+
pub struct Index {
56+
pub url: Url,
57+
/// The root endpoint where authentication is applied.
58+
/// For PEP 503 endpoints, this excludes `/simple`.
59+
pub root_url: Url,
60+
pub auth_policy: AuthPolicy,
61+
}
62+
5063
#[derive(Debug, Default, Clone, Eq, PartialEq)]
51-
pub struct UrlAuthPolicies(FxHashMap<Url, AuthPolicy>);
64+
pub struct Indexes(FxHashSet<Index>);
5265

53-
impl UrlAuthPolicies {
66+
impl Indexes {
5467
pub fn new() -> Self {
55-
Self(FxHashMap::default())
68+
Self(FxHashSet::default())
5669
}
5770

58-
/// Create a new [`UrlAuthPolicies`] from a list of URL and [`AuthPolicy`]
59-
/// tuples.
60-
pub fn from_tuples(tuples: impl IntoIterator<Item = (Url, AuthPolicy)>) -> Self {
61-
let mut auth_policies = Self::new();
62-
for (url, auth_policy) in tuples {
63-
auth_policies.add_policy(url, auth_policy);
71+
/// Create a new [`AuthIndexUrls`] from an iterator of [`AuthIndexUrl`]s.
72+
pub fn from_indexes(urls: impl IntoIterator<Item = Index>) -> Self {
73+
let mut index_urls = Self::new();
74+
for url in urls {
75+
index_urls.0.insert(url);
6476
}
65-
auth_policies
77+
index_urls
6678
}
6779

68-
/// An [`AuthPolicy`] for a URL.
69-
pub fn add_policy(&mut self, url: Url, auth_policy: AuthPolicy) {
70-
self.0.insert(url, auth_policy);
80+
/// Get the index URL prefix for a URL if one exists.
81+
pub fn index_url_for(&self, url: &Url) -> Option<&Url> {
82+
// TODO(john): There are probably not many URLs to iterate through,
83+
// but we could use a trie instead of a HashSet here for more
84+
// efficient search.
85+
self.0
86+
.iter()
87+
.find(|index| is_url_prefix(&index.root_url, url))
88+
.map(|index| &index.url)
7189
}
7290

7391
/// Get the [`AuthPolicy`] for a URL.
7492
pub fn policy_for(&self, url: &Url) -> AuthPolicy {
7593
// TODO(john): There are probably not many URLs to iterate through,
7694
// but we could use a trie instead of a HashMap here for more
7795
// efficient search.
78-
for (auth_url, auth_policy) in &self.0 {
79-
if url.as_str().starts_with(auth_url.as_str()) {
80-
return *auth_policy;
96+
for index in &self.0 {
97+
if is_url_prefix(&index.root_url, url) {
98+
return index.auth_policy;
8199
}
82100
}
83101
AuthPolicy::Auto
84102
}
85103
}
104+
105+
fn is_url_prefix(base: &Url, url: &Url) -> bool {
106+
if base.scheme() != url.scheme()
107+
|| base.host_str() != url.host_str()
108+
|| base.port_or_known_default() != url.port_or_known_default()
109+
{
110+
return false;
111+
}
112+
113+
url.path().starts_with(base.path())
114+
}

crates/uv-auth/src/lib.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,16 +5,16 @@ use url::Url;
55

66
use cache::CredentialsCache;
77
pub use credentials::Credentials;
8+
pub use index::{AuthPolicy, Index, Indexes};
89
pub use keyring::KeyringProvider;
910
pub use middleware::AuthMiddleware;
10-
pub use policy::{AuthPolicy, UrlAuthPolicies};
1111
use realm::Realm;
1212

1313
mod cache;
1414
mod credentials;
15+
mod index;
1516
mod keyring;
1617
mod middleware;
17-
mod policy;
1818
mod realm;
1919

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

0 commit comments

Comments
 (0)