Store native credentials for realms with the https scheme stripped (#15879)

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

Unfortunately, this is how we perform lookups. We could also change the
lookup to include the scheme. I expect all of this to change in the
future anyway, as I want to redesign the storage model for native
credentials.
This commit is contained in:
Zanie Blue 2025-09-15 13:47:33 -05:00 committed by GitHub
parent 31f46cd6a6
commit 89a59749c0
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 74 additions and 40 deletions

View File

@ -87,9 +87,26 @@ impl KeyringProvider {
// Ensure we strip credentials from the URL before storing
let url = url.without_credentials();
// If there's no path, we'll perform a host-level login
let target = if let Some(host) = url.host_str().filter(|_| !url.path().is_empty()) {
let mut target = String::new();
if url.scheme() != "https" {
target.push_str(url.scheme());
target.push_str("://");
}
target.push_str(host);
if let Some(port) = url.port() {
target.push(':');
target.push_str(&port.to_string());
}
target
} else {
url.to_string()
};
match &self.backend {
KeyringProviderBackend::Native => {
self.store_native(url.as_str(), username, password).await?;
self.store_native(&target, username, password).await?;
Ok(true)
}
KeyringProviderBackend::Subprocess => {
@ -122,9 +139,26 @@ impl KeyringProvider {
// Ensure we strip credentials from the URL before storing
let url = url.without_credentials();
// If there's no path, we'll perform a host-level login
let target = if let Some(host) = url.host_str().filter(|_| !url.path().is_empty()) {
let mut target = String::new();
if url.scheme() != "https" {
target.push_str(url.scheme());
target.push_str("://");
}
target.push_str(host);
if let Some(port) = url.port() {
target.push(':');
target.push_str(&port.to_string());
}
target
} else {
url.to_string()
};
match &self.backend {
KeyringProviderBackend::Native => {
self.remove_native(url.as_str(), username).await?;
self.remove_native(&target, username).await?;
Ok(())
}
KeyringProviderBackend::Subprocess => {

View File

@ -67,16 +67,17 @@ fn add_package_native_auth_realm() -> Result<()> {
// storied in the system keyring.
uv_snapshot!(context.add().arg("anyio").arg("--default-index").arg("https://public@pypi-proxy.fly.dev/basic-auth/simple")
.env(EnvVars::UV_PREVIEW_FEATURES, "native-auth"), @r"
success: false
exit_code: 1
success: true
exit_code: 0
----- stdout -----
----- stderr -----
× No solution found when resolving dependencies:
Because anyio was not found in the package registry and your project depends on anyio, we can conclude that your project's requirements are unsatisfiable.
hint: An index URL (https://pypi-proxy.fly.dev/basic-auth/simple) could not be queried due to a lack of valid authentication credentials (401 Unauthorized).
help: If you want to add the package regardless of the failed resolution, provide the `--frozen` flag to skip locking and syncing.
Resolved 4 packages in [TIME]
Prepared 3 packages in [TIME]
Installed 3 packages in [TIME]
+ anyio==4.3.0
+ idna==3.6
+ sniffio==1.3.1
"
);
@ -287,12 +288,12 @@ fn token_native_auth() -> Result<()> {
.arg("--username")
.arg("public")
.env(EnvVars::UV_PREVIEW_FEATURES, "native-auth"), @r"
success: false
exit_code: 2
success: true
exit_code: 0
----- stdout -----
heron
----- stderr -----
error: Failed to fetch credentials for public@https://pypi-proxy.fly.dev/basic-auth/simple
");
// Without the username
@ -342,12 +343,12 @@ fn token_native_auth() -> Result<()> {
uv_snapshot!(context.auth_token()
.arg("https://pypi-proxy.fly.dev/basic-auth/simple")
.env(EnvVars::UV_PREVIEW_FEATURES, "native-auth"), @r"
success: false
exit_code: 2
success: true
exit_code: 0
----- stdout -----
heron
----- stderr -----
error: Failed to fetch credentials for https://pypi-proxy.fly.dev/basic-auth/simple
");
context
@ -361,12 +362,12 @@ fn token_native_auth() -> Result<()> {
uv_snapshot!(context.auth_token()
.arg("https://public@pypi-proxy.fly.dev/basic-auth/simple")
.env(EnvVars::UV_PREVIEW_FEATURES, "native-auth"), @r"
success: false
exit_code: 2
success: true
exit_code: 0
----- stdout -----
heron
----- stderr -----
error: Failed to fetch credentials for public@https://pypi-proxy.fly.dev/basic-auth/simple
");
// Conflict between --username and URL username is rejected
@ -411,12 +412,12 @@ fn token_native_auth_realm() -> Result<()> {
uv_snapshot!(context.auth_token()
.arg("pypi-proxy.fly.dev")
.env(EnvVars::UV_PREVIEW_FEATURES, "native-auth"), @r"
success: false
exit_code: 2
success: true
exit_code: 0
----- stdout -----
heron
----- stderr -----
error: Failed to fetch credentials for https://pypi-proxy.fly.dev/
");
// Without persisted credentials (with a username in the request)
@ -470,36 +471,36 @@ fn token_native_auth_realm() -> Result<()> {
.arg("--username")
.arg("public")
.env(EnvVars::UV_PREVIEW_FEATURES, "native-auth"), @r"
success: false
exit_code: 2
success: true
exit_code: 0
----- stdout -----
heron
----- stderr -----
error: Failed to fetch credentials for public@https://pypi-proxy.fly.dev/basic-auth/simple
");
// Without the username
uv_snapshot!(context.auth_token()
.arg("pypi-proxy.fly.dev")
.env(EnvVars::UV_PREVIEW_FEATURES, "native-auth"), @r"
success: false
exit_code: 2
success: true
exit_code: 0
----- stdout -----
heron
----- stderr -----
error: Failed to fetch credentials for https://pypi-proxy.fly.dev/
");
// Without the username
uv_snapshot!(context.auth_token()
.arg("https://pypi-proxy.fly.dev/basic-auth/simple")
.env(EnvVars::UV_PREVIEW_FEATURES, "native-auth"), @r"
success: false
exit_code: 2
success: true
exit_code: 0
----- stdout -----
heron
----- stderr -----
error: Failed to fetch credentials for https://pypi-proxy.fly.dev/basic-auth/simple
");
// With a mismatched username
@ -569,12 +570,12 @@ fn token_native_auth_realm() -> Result<()> {
uv_snapshot!(context.auth_token()
.arg("https://public@pypi-proxy.fly.dev/basic-auth/simple")
.env(EnvVars::UV_PREVIEW_FEATURES, "native-auth"), @r"
success: false
exit_code: 2
success: true
exit_code: 0
----- stdout -----
heron
----- stderr -----
error: Failed to fetch credentials for public@https://pypi-proxy.fly.dev/basic-auth/simple
");
Ok(())
@ -1849,12 +1850,12 @@ fn native_auth_prefix_match() -> Result<()> {
.arg("--username")
.arg("testuser")
.env(EnvVars::UV_PREVIEW_FEATURES, "native-auth"), @r"
success: false
exit_code: 2
success: true
exit_code: 0
----- stdout -----
testpass
----- stderr -----
error: Failed to fetch credentials for testuser@https://example.com/api/v1
"
);
@ -1893,18 +1894,17 @@ fn native_auth_host_fallback() -> Result<()> {
);
// Should fallback to host-level matching
// TODO(zanieb): This is not working as intended
uv_snapshot!(context.auth_token()
.arg("https://example.com/any/path")
.arg("--username")
.arg("testuser")
.env(EnvVars::UV_PREVIEW_FEATURES, "native-auth"), @r"
success: false
exit_code: 2
success: true
exit_code: 0
----- stdout -----
hostpass
----- stderr -----
error: Failed to fetch credentials for testuser@https://example.com/any/path
"
);