Skip to content

Commit a8ce140

Browse files
committed
More review feedback
1 parent d236659 commit a8ce140

File tree

2 files changed

+68
-49
lines changed

2 files changed

+68
-49
lines changed

crates/uv/src/commands/auth/helper.rs

Lines changed: 26 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ use std::io::Read;
44

55
use anyhow::{Context, Result, bail};
66
use serde::{Deserialize, Serialize};
7-
use url::Url;
7+
use tracing::debug;
88

99
use uv_auth::{AuthBackend, Credentials, PyxTokenStore};
1010
use uv_client::BaseClientBuilder;
@@ -16,7 +16,7 @@ use crate::{commands::ExitStatus, printer::Printer, settings::NetworkSettings};
1616
/// Request format for the Bazel credential helper protocol.
1717
#[derive(Debug, Deserialize)]
1818
struct BazelCredentialRequest {
19-
uri: String,
19+
uri: DisplaySafeUrl,
2020
}
2121

2222
impl BazelCredentialRequest {
@@ -42,21 +42,16 @@ struct BazelCredentialResponse {
4242

4343
impl TryFrom<Credentials> for BazelCredentialResponse {
4444
fn try_from(creds: Credentials) -> Result<Self> {
45-
let mut headers = HashMap::new();
46-
// Only include the Authorization header if credentials are authenticated
47-
// (i.e., not just a username without password)
48-
if creds.is_authenticated() {
49-
let header_value = creds.to_header_value();
50-
51-
// Convert HeaderValue to String
52-
let header_str = header_value
53-
.to_str()
54-
.context("Failed to convert header value to string")?
55-
.to_string();
56-
57-
headers.insert("Authorization".to_string(), vec![header_str]);
58-
}
59-
Ok(Self { headers })
45+
let header_str = creds
46+
.to_header_value()
47+
.to_str()
48+
// TODO: this is infallible in practice
49+
.context("Failed to convert header value to string")?
50+
.to_owned();
51+
52+
Ok(Self {
53+
headers: HashMap::from([("Authorization".to_owned(), vec![header_str])]),
54+
})
6055
}
6156

6257
type Error = anyhow::Error;
@@ -71,16 +66,22 @@ async fn credentials_for_url(
7166

7267
// Use only the username from the URL, if present - discarding the password
7368
let url_credentials = Credentials::from_url(url);
74-
let url_username = url_credentials.as_ref().and_then(|c| c.username());
75-
let username = url_username.map(ToString::to_string);
69+
let username = url_credentials.as_ref().and_then(|c| c.username());
70+
if url_credentials
71+
.as_ref()
72+
.map(|c| c.password().is_some())
73+
.unwrap_or(false)
74+
{
75+
debug!("URL '{url}' contain a password; ignoring");
76+
}
7677

7778
if pyx_store.is_known_domain(url) {
7879
if username.is_some() {
7980
bail!(
8081
"Cannot specify a username for URLs under {}",
8182
url.host()
8283
.map(|host| host.to_string())
83-
.unwrap_or("this host".to_owned())
84+
.unwrap_or(url.to_string())
8485
);
8586
}
8687
let client = BaseClientBuilder::new(
@@ -97,15 +98,13 @@ async fn credentials_for_url(
9798
.access_token(client.for_host(pyx_store.api()).raw_client(), 0)
9899
.await
99100
.context("Authentication failure")?;
100-
let token = maybe_token.ok_or_else(|| anyhow::anyhow!("No access token found"))?;
101+
let token = maybe_token.context("No access token found")?;
101102
return Ok(Some(Credentials::bearer(token.into_bytes())));
102103
}
103104
let backend = AuthBackend::from_settings(preview)?;
104105
let credentials = match &backend {
105-
AuthBackend::System(provider) => provider.fetch(url, username.as_deref()).await,
106-
AuthBackend::TextStore(store, _lock) => {
107-
store.get_credentials(url, username.as_deref()).cloned()
108-
}
106+
AuthBackend::System(provider) => provider.fetch(url, username).await,
107+
AuthBackend::TextStore(store, _lock) => store.get_credentials(url, username).cloned(),
109108
};
110109
Ok(credentials)
111110
}
@@ -131,17 +130,14 @@ pub(crate) async fn helper(
131130

132131
// TODO: make this logic generic over the protocol by providing `request.uri` from a
133132
// trait - that should help with adding new protocols
134-
let url = Url::parse(&request.uri).context("Invalid URI in credential request")?;
135-
let safe_url = DisplaySafeUrl::from_url(url);
136-
137-
let credentials = credentials_for_url(&safe_url, preview, network_settings).await?;
133+
let credentials = credentials_for_url(&request.uri, preview, network_settings).await?;
138134

139135
let response = serde_json::to_string(
140136
&credentials
141137
.map(BazelCredentialResponse::try_from)
142138
.unwrap_or_else(|| Ok(BazelCredentialResponse::default()))?,
143139
)
144140
.context("Failed to serialize response as JSON")?;
145-
writeln!(printer.stdout(), "{response}")?;
141+
writeln!(printer.stdout_important(), "{response}")?;
146142
Ok(ExitStatus::Success)
147143
}

crates/uv/tests/it/auth.rs

Lines changed: 42 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1929,10 +1929,7 @@ fn native_auth_host_fallback() -> Result<()> {
19291929
/// Test credential helper with basic auth credentials
19301930
#[test]
19311931
fn helper_basic_auth() {
1932-
let context = TestContext::new("3.12").with_filter((
1933-
r#""Basic [a-zA-Z0-9+/=]+""#.to_owned(),
1934-
r#""Basic [REDACTED]""#.to_owned(),
1935-
));
1932+
let context = TestContext::new("3.12");
19361933

19371934
// Store credentials
19381935
uv_snapshot!(context.filters(), context.auth_login()
@@ -1953,7 +1950,7 @@ fn helper_basic_auth() {
19531950
success: true
19541951
exit_code: 0
19551952
----- stdout -----
1956-
{"headers":{"Authorization":["Basic [REDACTED]"]}}
1953+
{"headers":{"Authorization":["Basic dGVzdHVzZXI6dGVzdHBhc3M="]}}
19571954
19581955
----- stderr -----
19591956
"#
@@ -1963,10 +1960,7 @@ fn helper_basic_auth() {
19631960
/// Test credential helper with token credentials
19641961
#[test]
19651962
fn helper_token() {
1966-
let context = TestContext::new("3.12").with_filter((
1967-
r#""Basic [a-zA-Z0-9+/=]+""#.to_owned(),
1968-
r#""Basic [REDACTED]""#.to_owned(),
1969-
));
1963+
let context = TestContext::new("3.12");
19701964

19711965
// Store token
19721966
uv_snapshot!(context.filters(), context.auth_login()
@@ -1987,7 +1981,7 @@ fn helper_token() {
19871981
success: true
19881982
exit_code: 0
19891983
----- stdout -----
1990-
{"headers":{"Authorization":["Basic [REDACTED]"]}}
1984+
{"headers":{"Authorization":["Basic X190b2tlbl9fOm15dG9rZW4xMjM="]}}
19911985
19921986
----- stderr -----
19931987
"#
@@ -2037,25 +2031,22 @@ fn helper_invalid_uri() {
20372031

20382032
uv_snapshot!(context.filters(), context.auth_helper(),
20392033
input=r#"{"uri":"not a url"}"#,
2040-
@r"
2034+
@r#"
20412035
success: false
20422036
exit_code: 2
20432037
----- stdout -----
20442038
20452039
----- stderr -----
2046-
error: Invalid URI in credential request
2047-
Caused by: relative URL without a base
2048-
"
2040+
error: Failed to parse credential request as JSON
2041+
Caused by: relative URL without a base: "not a url" at line 1 column 18
2042+
"#
20492043
);
20502044
}
20512045

20522046
/// Test credential helper with username in URI
20532047
#[test]
20542048
fn helper_username_in_uri() {
2055-
let context = TestContext::new("3.12").with_filter((
2056-
r#""Basic [a-zA-Z0-9+/=]+""#.to_owned(),
2057-
r#""Basic [REDACTED]""#.to_owned(),
2058-
));
2049+
let context = TestContext::new("3.12");
20592050

20602051
// Store credentials with specific username
20612052
uv_snapshot!(context.filters(), context.auth_login()
@@ -2077,7 +2068,39 @@ fn helper_username_in_uri() {
20772068
success: true
20782069
exit_code: 0
20792070
----- stdout -----
2080-
{"headers":{"Authorization":["Basic [REDACTED]"]}}
2071+
{"headers":{"Authorization":["Basic c3BlY2lmaWN1c2VyOnNwZWNpZmljcGFzcw=="]}}
2072+
2073+
----- stderr -----
2074+
"#
2075+
);
2076+
}
2077+
2078+
/// Test credential helper with unknown username in URI
2079+
#[test]
2080+
fn helper_unknown_username_in_uri() {
2081+
let context = TestContext::new("3.12");
2082+
2083+
// Store credentials with specific username
2084+
uv_snapshot!(context.filters(), context.auth_login()
2085+
.arg("https://test.example.com")
2086+
.arg("--username").arg("specificuser")
2087+
.arg("--password").arg("specificpass"), @r###"
2088+
success: true
2089+
exit_code: 0
2090+
----- stdout -----
2091+
2092+
----- stderr -----
2093+
Stored credentials for specificuser@https://test.example.com/
2094+
"###);
2095+
2096+
// Test with username in URI
2097+
uv_snapshot!(context.filters(), context.auth_helper(),
2098+
input=r#"{"uri":"https://[email protected]/path"}"#,
2099+
@r#"
2100+
success: true
2101+
exit_code: 0
2102+
----- stdout -----
2103+
{"headers":{}}
20812104
20822105
----- stderr -----
20832106
"#

0 commit comments

Comments
 (0)