This is an automated email from the ASF dual-hosted git repository.
tustvold pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/arrow-rs-object-store.git
The following commit(s) were added to refs/heads/main by this push:
new 255b5f5 feat(gcp): support explicit bearer token config in parse flow
(#717)
255b5f5 is described below
commit 255b5f59e59440dabc910880afd5c71331b57ad1
Author: siddharthmittal13 <[email protected]>
AuthorDate: Tue Jun 2 15:48:03 2026 +0530
feat(gcp): support explicit bearer token config in parse flow (#717)
* feat(gcp): support explicit bearer token config in parse flow
* feat(gcp): resolved lint issue and addressed review comments.
---------
Co-authored-by: Siddharth Mittal <[email protected]>
---
src/gcp/builder.rs | 70 +++++++++++++++++++++++++++++++++++++++++++++++++++---
src/parse.rs | 18 ++++++++++++++
2 files changed, 85 insertions(+), 3 deletions(-)
diff --git a/src/gcp/builder.rs b/src/gcp/builder.rs
index 9db6ea5..82752b0 100644
--- a/src/gcp/builder.rs
+++ b/src/gcp/builder.rs
@@ -112,6 +112,8 @@ pub struct GoogleCloudStorageBuilder {
client_options: ClientOptions,
/// Credentials
credentials: Option<GcpCredentialProvider>,
+ /// Explicit bearer token, if configured
+ bearer_token: Option<String>,
/// Skip signing requests
skip_signature: ConfigValue<bool>,
/// Credentials for sign url
@@ -179,6 +181,15 @@ pub enum GoogleConfigKey {
/// - `application_credentials`
ApplicationCredentials,
+ /// Explicit OAuth bearer token
+ ///
+ /// This is treated as a static token and will not be refreshed
automatically.
+ ///
+ /// Supported keys:
+ /// - `google_bearer_token`
+ /// - `bearer_token`
+ BearerToken,
+
/// Skip signing request
///
/// Supported keys:
@@ -198,6 +209,7 @@ impl AsRef<str> for GoogleConfigKey {
Self::Bucket => "google_bucket",
Self::BaseUrl => "google_base_url",
Self::ApplicationCredentials => "google_application_credentials",
+ Self::BearerToken => "google_bearer_token",
Self::SkipSignature => "google_skip_signature",
Self::Client(key) => key.as_ref(),
}
@@ -219,6 +231,7 @@ impl FromStr for GoogleConfigKey {
"google_application_credentials" | "application_credentials" => {
Ok(Self::ApplicationCredentials)
}
+ "google_bearer_token" | "bearer_token" => Ok(Self::BearerToken),
"google_skip_signature" | "skip_signature" =>
Ok(Self::SkipSignature),
_ => match s.strip_prefix("google_").unwrap_or(s).parse() {
Ok(key) => Ok(Self::Client(key)),
@@ -240,6 +253,7 @@ impl Default for GoogleCloudStorageBuilder {
url: None,
base_url: None,
credentials: None,
+ bearer_token: None,
skip_signature: Default::default(),
signing_credentials: None,
http_connector: None,
@@ -322,6 +336,7 @@ impl GoogleCloudStorageBuilder {
GoogleConfigKey::ApplicationCredentials => {
self.application_credentials_path = Some(value.into())
}
+ GoogleConfigKey::BearerToken => self =
self.with_bearer_token(value),
GoogleConfigKey::SkipSignature => self.skip_signature.parse(value),
GoogleConfigKey::Client(key) => {
self.client_options = self.client_options.with_config(key,
value)
@@ -348,6 +363,7 @@ impl GoogleCloudStorageBuilder {
GoogleConfigKey::Bucket => self.bucket_name.clone(),
GoogleConfigKey::BaseUrl => self.base_url.clone(),
GoogleConfigKey::ApplicationCredentials =>
self.application_credentials_path.clone(),
+ GoogleConfigKey::BearerToken => self.bearer_token.clone(),
GoogleConfigKey::SkipSignature =>
Some(self.skip_signature.to_string()),
GoogleConfigKey::Client(key) =>
self.client_options.get_config_value(key),
}
@@ -445,6 +461,18 @@ impl GoogleCloudStorageBuilder {
self
}
+ /// Set an explicit OAuth bearer token.
+ ///
+ /// This is treated as a static token and will not be refreshed
automatically.
+ pub fn with_bearer_token(mut self, bearer_token: impl Into<String>) ->
Self {
+ let bearer_token = bearer_token.into();
+ self.credentials =
Some(Arc::new(StaticCredentialProvider::new(GcpCredential {
+ bearer: bearer_token.clone(),
+ })));
+ self.bearer_token = Some(bearer_token);
+ self
+ }
+
/// If enabled, [`GoogleCloudStorage`] will not fetch credentials and will
not sign requests.
///
/// This can be useful when interacting with public GCS buckets that deny
authorized requests.
@@ -456,6 +484,7 @@ impl GoogleCloudStorageBuilder {
/// Set the credential provider overriding any other options
pub fn with_credentials(mut self, credentials: GcpCredentialProvider) ->
Self {
self.credentials = Some(credentials);
+ self.bearer_token = None;
self
}
@@ -688,8 +717,8 @@ mod tests {
assert_eq!(builder.bucket_name.unwrap(), google_bucket_name.as_str());
}
- #[test]
- fn gcs_test_config_aliases() {
+ #[tokio::test]
+ async fn gcs_test_config_aliases() {
// Service account path
for alias in [
"google_service_account",
@@ -720,6 +749,17 @@ mod tests {
GoogleCloudStorageBuilder::new().with_config(alias.parse().unwrap(),
"fake_bucket");
assert_eq!("fake_bucket", builder.bucket_name.unwrap());
}
+
+ for alias in ["google_bearer_token", "bearer_token"] {
+ let gcs = GoogleCloudStorageBuilder::new()
+ .with_config(alias.parse().unwrap(), "test-token")
+ .with_bucket_name("fake_bucket")
+ .build()
+ .unwrap();
+
+ let credential = gcs.credentials().get_credential().await.unwrap();
+ assert_eq!(credential.bearer, "test-token");
+ }
}
#[tokio::test]
@@ -811,9 +851,11 @@ mod tests {
fn gcs_test_config_get_value() {
let google_service_account =
"object_store:fake_service_account".to_string();
let google_bucket_name = "object_store:fake_bucket".to_string();
+ let google_bearer_token = "test-token".to_string();
let builder = GoogleCloudStorageBuilder::new()
.with_config(GoogleConfigKey::ServiceAccount,
&google_service_account)
- .with_config(GoogleConfigKey::Bucket, &google_bucket_name);
+ .with_config(GoogleConfigKey::Bucket, &google_bucket_name)
+ .with_config(GoogleConfigKey::BearerToken, &google_bearer_token);
assert_eq!(
builder
@@ -825,6 +867,28 @@ mod tests {
builder.get_config_value(&GoogleConfigKey::Bucket).unwrap(),
google_bucket_name
);
+ assert_eq!(
+ builder
+ .get_config_value(&GoogleConfigKey::BearerToken)
+ .unwrap(),
+ google_bearer_token
+ );
+ }
+
+ #[test]
+ fn gcs_test_with_credentials_clears_bearer_config_value() {
+ let custom_creds =
Arc::new(StaticCredentialProvider::new(GcpCredential {
+ bearer: "custom-token".to_string(),
+ }));
+
+ let builder = GoogleCloudStorageBuilder::new()
+ .with_bearer_token("test-token")
+ .with_credentials(custom_creds);
+
+ assert_eq!(
+ builder.get_config_value(&GoogleConfigKey::BearerToken),
+ None
+ );
}
#[test]
diff --git a/src/parse.rs b/src/parse.rs
index b30fea7..d316b2f 100644
--- a/src/parse.rs
+++ b/src/parse.rs
@@ -240,6 +240,7 @@ where
#[cfg(test)]
mod tests {
use super::*;
+ use url::Url;
#[test]
fn test_parse() {
@@ -422,6 +423,23 @@ mod tests {
assert_eq!(path.as_ref(), "my file with spaces");
}
+ #[test]
+ #[cfg(feature = "gcp")]
+ fn test_url_gcs_bearer_token_opts() {
+ let url = Url::parse("gs://bucket/path").unwrap();
+
+ for alias in ["google_bearer_token", "bearer_token"] {
+ let opts = [
+ (alias, "test-token"),
+ ("google_proxy_url", "https://example.com"),
+ ];
+
+ let (store, path) = parse_url_opts(&url, opts).unwrap();
+ assert_eq!(path.as_ref(), "path");
+ assert_eq!(store.to_string(), "GoogleCloudStorage(bucket)");
+ }
+ }
+
#[tokio::test]
#[cfg(all(feature = "http", not(target_arch = "wasm32")))]
async fn test_url_http() {