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() {

Reply via email to