Skip to content

Commit c4fd34f

Browse files
Use Credentials abstraction in uv-publish (#12682)
## Summary I noticed that we aren't using these here -- we have a separate username and password situation.
1 parent 1ff7265 commit c4fd34f

File tree

5 files changed

+129
-120
lines changed

5 files changed

+129
-120
lines changed

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/uv-auth/src/credentials.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ impl From<Option<String>> for Username {
7070
impl Credentials {
7171
/// Create a set of HTTP Basic Authentication credentials.
7272
#[allow(dead_code)]
73-
pub(crate) fn basic(username: Option<String>, password: Option<String>) -> Self {
73+
pub fn basic(username: Option<String>, password: Option<String>) -> Self {
7474
Self::Basic {
7575
username: Username::new(username),
7676
password,
@@ -79,7 +79,7 @@ impl Credentials {
7979

8080
/// Create a set of Bearer Authentication credentials.
8181
#[allow(dead_code)]
82-
pub(crate) fn bearer(token: Vec<u8>) -> Self {
82+
pub fn bearer(token: Vec<u8>) -> Self {
8383
Self::Bearer { token }
8484
}
8585

@@ -245,7 +245,7 @@ impl Credentials {
245245
/// Create an HTTP Basic Authentication header for the credentials.
246246
///
247247
/// Panics if the username or password cannot be base64 encoded.
248-
pub(crate) fn to_header_value(&self) -> HeaderValue {
248+
pub fn to_header_value(&self) -> HeaderValue {
249249
match self {
250250
Self::Basic { .. } => {
251251
// See: <https://github.com/seanmonstar/reqwest/blob/2c11ef000b151c2eebeed2c18a7b81042220c6b0/src/util.rs#L3>
@@ -291,7 +291,7 @@ impl Credentials {
291291
///
292292
/// Any existing credentials will be overridden.
293293
#[must_use]
294-
pub(crate) fn authenticate(&self, mut request: Request) -> Request {
294+
pub fn authenticate(&self, mut request: Request) -> Request {
295295
request
296296
.headers_mut()
297297
.insert(reqwest::header::AUTHORIZATION, Self::to_header_value(self));

crates/uv-publish/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ license.workspace = true
1313
doctest = false
1414

1515
[dependencies]
16+
uv-auth = { workspace = true }
1617
uv-cache = { workspace = true }
1718
uv-client = { workspace = true }
1819
uv-configuration = { workspace = true }

crates/uv-publish/src/lib.rs

Lines changed: 91 additions & 86 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
mod trusted_publishing;
22

3-
use crate::trusted_publishing::TrustedPublishingError;
4-
use base64::prelude::BASE64_STANDARD;
5-
use base64::Engine;
3+
use std::path::{Path, PathBuf};
4+
use std::sync::Arc;
5+
use std::time::{Duration, SystemTime};
6+
use std::{env, fmt, io};
7+
68
use fs_err::tokio::File;
79
use futures::TryStreamExt;
810
use glob::{glob, GlobError, PatternError};
@@ -15,17 +17,15 @@ use reqwest_retry::policies::ExponentialBackoff;
1517
use reqwest_retry::{RetryPolicy, Retryable, RetryableStrategy};
1618
use rustc_hash::FxHashSet;
1719
use serde::Deserialize;
18-
use std::path::{Path, PathBuf};
19-
use std::sync::Arc;
20-
use std::time::{Duration, SystemTime};
21-
use std::{env, fmt, io};
2220
use thiserror::Error;
2321
use tokio::io::{AsyncReadExt, BufReader};
2422
use tokio::sync::Semaphore;
2523
use tokio_util::io::ReaderStream;
2624
use tracing::{debug, enabled, trace, warn, Level};
2725
use trusted_publishing::TrustedPublishingToken;
2826
use url::Url;
27+
28+
use uv_auth::Credentials;
2929
use uv_cache::{Cache, Refresh};
3030
use uv_client::{
3131
BaseClient, MetadataFormat, OwnedArchive, RegistryClientBuilder, UvRetryableStrategy,
@@ -41,6 +41,8 @@ use uv_pypi_types::{HashAlgorithm, HashDigest, Metadata23, MetadataError};
4141
use uv_static::EnvVars;
4242
use uv_warnings::{warn_user, warn_user_once};
4343

44+
use crate::trusted_publishing::TrustedPublishingError;
45+
4446
#[derive(Error, Debug)]
4547
pub enum PublishError {
4648
#[error("The publish path is not a valid glob pattern: `{0}`")]
@@ -370,8 +372,7 @@ pub async fn upload(
370372
filename: &DistFilename,
371373
registry: &Url,
372374
client: &BaseClient,
373-
username: Option<&str>,
374-
password: Option<&str>,
375+
credentials: &Credentials,
375376
check_url_client: Option<&CheckUrlClient<'_>>,
376377
download_concurrency: &Semaphore,
377378
reporter: Arc<impl Reporter>,
@@ -391,8 +392,7 @@ pub async fn upload(
391392
filename,
392393
registry,
393394
client,
394-
username,
395-
password,
395+
credentials,
396396
&form_metadata,
397397
reporter.clone(),
398398
)
@@ -744,8 +744,7 @@ async fn build_request(
744744
filename: &DistFilename,
745745
registry: &Url,
746746
client: &BaseClient,
747-
username: Option<&str>,
748-
password: Option<&str>,
747+
credentials: &Credentials,
749748
form_metadata: &[(&'static str, String)],
750749
reporter: Arc<impl Reporter>,
751750
) -> Result<(RequestBuilder, usize), PublishPrepareError> {
@@ -767,17 +766,15 @@ async fn build_request(
767766
let part = Part::stream_with_length(file_reader, file_size).file_name(raw_filename.to_string());
768767
form = form.part("content", part);
769768

770-
let url = if let Some(username) = username {
771-
if password.is_none() {
772-
// Attach the username to the URL so the authentication middleware can find the matching
773-
// password.
774-
let mut url = registry.clone();
775-
let _ = url.set_username(username);
776-
url
777-
} else {
778-
// We set the authorization header below.
779-
registry.clone()
780-
}
769+
// If we have a username but no password, attach the username to the URL so the authentication
770+
// middleware can find the matching password.
771+
let url = if let Some(username) = credentials
772+
.username()
773+
.filter(|_| credentials.password().is_none())
774+
{
775+
let mut url = registry.clone();
776+
let _ = url.set_username(username);
777+
url
781778
} else {
782779
registry.clone()
783780
};
@@ -793,11 +790,20 @@ async fn build_request(
793790
reqwest::header::ACCEPT,
794791
"application/json;q=0.9, text/plain;q=0.8, text/html;q=0.7",
795792
);
796-
if let (Some(username), Some(password)) = (username, password) {
797-
debug!("Using username/password basic auth");
798-
let credentials = BASE64_STANDARD.encode(format!("{username}:{password}"));
799-
request = request.header(AUTHORIZATION, format!("Basic {credentials}"));
793+
794+
match credentials {
795+
Credentials::Basic { password, .. } => {
796+
if password.is_some() {
797+
debug!("Using HTTP Basic authentication");
798+
request = request.header(AUTHORIZATION, credentials.to_header_value());
799+
}
800+
}
801+
Credentials::Bearer { .. } => {
802+
debug!("Using Bearer token authentication");
803+
request = request.header(AUTHORIZATION, credentials.to_header_value());
804+
}
800805
}
806+
801807
Ok((request, idx))
802808
}
803809

@@ -875,6 +881,7 @@ mod tests {
875881
use std::path::PathBuf;
876882
use std::sync::Arc;
877883
use url::Url;
884+
use uv_auth::Credentials;
878885
use uv_client::BaseClientBuilder;
879886
use uv_distribution_filename::DistFilename;
880887

@@ -958,8 +965,7 @@ mod tests {
958965
&filename,
959966
&Url::parse("https://example.org/upload").unwrap(),
960967
&BaseClientBuilder::new().build(),
961-
Some("ferris"),
962-
Some("F3RR!S"),
968+
&Credentials::basic(Some("ferris".to_string()), Some("F3RR!S".to_string())),
963969
&form_metadata,
964970
Arc::new(DummyReporter),
965971
)
@@ -969,35 +975,35 @@ mod tests {
969975
insta::with_settings!({
970976
filters => [("boundary=[0-9a-f-]+", "boundary=[...]")],
971977
}, {
972-
assert_debug_snapshot!(&request, @r###"
973-
RequestBuilder {
974-
inner: RequestBuilder {
975-
method: POST,
976-
url: Url {
977-
scheme: "https",
978-
cannot_be_a_base: false,
979-
username: "",
980-
password: None,
981-
host: Some(
982-
Domain(
983-
"example.org",
978+
assert_debug_snapshot!(&request, @r#"
979+
RequestBuilder {
980+
inner: RequestBuilder {
981+
method: POST,
982+
url: Url {
983+
scheme: "https",
984+
cannot_be_a_base: false,
985+
username: "",
986+
password: None,
987+
host: Some(
988+
Domain(
989+
"example.org",
990+
),
984991
),
985-
),
986-
port: None,
987-
path: "/upload",
988-
query: None,
989-
fragment: None,
992+
port: None,
993+
path: "/upload",
994+
query: None,
995+
fragment: None,
996+
},
997+
headers: {
998+
"content-type": "multipart/form-data; boundary=[...]",
999+
"content-length": "6803",
1000+
"accept": "application/json;q=0.9, text/plain;q=0.8, text/html;q=0.7",
1001+
"authorization": Sensitive,
1002+
},
9901003
},
991-
headers: {
992-
"content-type": "multipart/form-data; boundary=[...]",
993-
"content-length": "6803",
994-
"accept": "application/json;q=0.9, text/plain;q=0.8, text/html;q=0.7",
995-
"authorization": "Basic ZmVycmlzOkYzUlIhUw==",
996-
},
997-
},
998-
..
999-
}
1000-
"###);
1004+
..
1005+
}
1006+
"#);
10011007
});
10021008
}
10031009

@@ -1109,8 +1115,7 @@ mod tests {
11091115
&filename,
11101116
&Url::parse("https://example.org/upload").unwrap(),
11111117
&BaseClientBuilder::new().build(),
1112-
Some("ferris"),
1113-
Some("F3RR!S"),
1118+
&Credentials::basic(Some("ferris".to_string()), Some("F3RR!S".to_string())),
11141119
&form_metadata,
11151120
Arc::new(DummyReporter),
11161121
)
@@ -1120,35 +1125,35 @@ mod tests {
11201125
insta::with_settings!({
11211126
filters => [("boundary=[0-9a-f-]+", "boundary=[...]")],
11221127
}, {
1123-
assert_debug_snapshot!(&request, @r###"
1124-
RequestBuilder {
1125-
inner: RequestBuilder {
1126-
method: POST,
1127-
url: Url {
1128-
scheme: "https",
1129-
cannot_be_a_base: false,
1130-
username: "",
1131-
password: None,
1132-
host: Some(
1133-
Domain(
1134-
"example.org",
1128+
assert_debug_snapshot!(&request, @r#"
1129+
RequestBuilder {
1130+
inner: RequestBuilder {
1131+
method: POST,
1132+
url: Url {
1133+
scheme: "https",
1134+
cannot_be_a_base: false,
1135+
username: "",
1136+
password: None,
1137+
host: Some(
1138+
Domain(
1139+
"example.org",
1140+
),
11351141
),
1136-
),
1137-
port: None,
1138-
path: "/upload",
1139-
query: None,
1140-
fragment: None,
1142+
port: None,
1143+
path: "/upload",
1144+
query: None,
1145+
fragment: None,
1146+
},
1147+
headers: {
1148+
"content-type": "multipart/form-data; boundary=[...]",
1149+
"content-length": "19330",
1150+
"accept": "application/json;q=0.9, text/plain;q=0.8, text/html;q=0.7",
1151+
"authorization": Sensitive,
1152+
},
11411153
},
1142-
headers: {
1143-
"content-type": "multipart/form-data; boundary=[...]",
1144-
"content-length": "19330",
1145-
"accept": "application/json;q=0.9, text/plain;q=0.8, text/html;q=0.7",
1146-
"authorization": "Basic ZmVycmlzOkYzUlIhUw==",
1147-
},
1148-
},
1149-
..
1150-
}
1151-
"###);
1154+
..
1155+
}
1156+
"#);
11521157
});
11531158
}
11541159
}

0 commit comments

Comments
 (0)