Add only_authenticated option to the client (#7545)

This commit is contained in:
konsti 2024-09-21 16:09:14 +02:00 committed by GitHub
parent 0d81bfbc67
commit d9a5f5ca1c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 77 additions and 35 deletions

View file

@ -8,7 +8,7 @@ use crate::{
realm::Realm,
CredentialsCache, KeyringProvider, CREDENTIALS_CACHE,
};
use anyhow::anyhow;
use anyhow::{anyhow, format_err};
use netrc::Netrc;
use reqwest::{Request, Response};
use reqwest_middleware::{Error, Middleware, Next};
@ -22,6 +22,9 @@ pub struct AuthMiddleware {
netrc: Option<Netrc>,
keyring: Option<KeyringProvider>,
cache: Option<CredentialsCache>,
/// We know that the endpoint needs authentication, so we don't try to send an unauthenticated
/// request, avoiding cloning an uncloneable request.
only_authenticated: bool,
}
impl AuthMiddleware {
@ -30,6 +33,7 @@ impl AuthMiddleware {
netrc: Netrc::new().ok(),
keyring: None,
cache: None,
only_authenticated: false,
}
}
@ -56,6 +60,14 @@ impl AuthMiddleware {
self
}
/// We know that the endpoint needs authentication, so we don't try to send an unauthenticated
/// request, avoiding cloning an uncloneable request.
#[must_use]
pub fn with_only_authenticated(mut self, only_authenticated: bool) -> Self {
self.only_authenticated = only_authenticated;
self
}
/// Get the configured authentication store.
///
/// If not set, the global store is used.
@ -198,14 +210,21 @@ impl Middleware for AuthMiddleware {
.as_ref()
.is_some_and(|credentials| credentials.username().is_some());
let (mut retry_request, response) = if self.only_authenticated {
// For endpoints where we require the user to provide credentials, we don't try the
// unauthenticated request first.
trace!("Checking for credentials for {url}");
(request, None)
} else {
// Otherwise, attempt an anonymous request
trace!("Attempting unauthenticated request for {url}");
// <https://github.com/TrueLayer/reqwest-middleware/blob/abdf1844c37092d323683c2396b7eefda1418d3c/reqwest-retry/src/middleware.rs#L141-L149>
// Clone the request so we can retry it on authentication failure
let mut retry_request = request.try_clone().ok_or_else(|| {
let retry_request = request.try_clone().ok_or_else(|| {
Error::Middleware(anyhow!(
"Request object is not cloneable. Are you passing a streaming body?".to_string()
"Request object is not cloneable. Are you passing a streaming body?"
.to_string()
))
})?;
@ -225,6 +244,9 @@ impl Middleware for AuthMiddleware {
response.status()
);
(retry_request, Some(response))
};
// Check in the cache first
let credentials = self.cache().get_realm(
Realm::from(retry_request.url()),
@ -265,7 +287,13 @@ impl Middleware for AuthMiddleware {
}
}
if let Some(response) = response {
Ok(response)
} else {
Err(Error::Middleware(format_err!(
"Missing credentials for {url}"
)))
}
}
}

View file

@ -36,6 +36,7 @@ pub struct BaseClientBuilder<'a> {
client: Option<Client>,
markers: Option<&'a MarkerEnvironment>,
platform: Option<&'a Platform>,
only_authenticated: bool,
}
impl Default for BaseClientBuilder<'_> {
@ -55,6 +56,7 @@ impl BaseClientBuilder<'_> {
client: None,
markers: None,
platform: None,
only_authenticated: false,
}
}
}
@ -108,6 +110,12 @@ impl<'a> BaseClientBuilder<'a> {
self
}
#[must_use]
pub fn only_authenticated(mut self, only_authenticated: bool) -> Self {
self.only_authenticated = only_authenticated;
self
}
pub fn is_offline(&self) -> bool {
matches!(self.connectivity, Connectivity::Offline)
}
@ -230,8 +238,10 @@ impl<'a> BaseClientBuilder<'a> {
fn apply_middleware(&self, client: Client) -> ClientWithMiddleware {
match self.connectivity {
Connectivity::Online => {
let client = reqwest_middleware::ClientBuilder::new(client);
let mut client = reqwest_middleware::ClientBuilder::new(client);
// Avoid uncloneable errors with a streaming body during publish.
if self.retries > 0 {
// Initialize the retry strategy.
let retry_policy =
ExponentialBackoff::builder().build_with_max_retries(self.retries);
@ -239,11 +249,15 @@ impl<'a> BaseClientBuilder<'a> {
retry_policy,
UvRetryableStrategy,
);
let client = client.with(retry_strategy);
client = client.with(retry_strategy);
}
// Initialize the authentication middleware to set headers.
let client =
client.with(AuthMiddleware::new().with_keyring(self.keyring.to_provider()));
client = client.with(
AuthMiddleware::new()
.with_keyring(self.keyring.to_provider())
.with_only_authenticated(self.only_authenticated),
);
client.build()
}