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 8bb878a  feat(azure): expose `split_sas` for custom SAS credential 
providers (#721)
8bb878a is described below

commit 8bb878a07b1a0e07521c1374e8302e9a21bb0f62
Author: Aleksandar Tomic <[email protected]>
AuthorDate: Tue Jun 2 11:30:59 2026 +0200

    feat(azure): expose `split_sas` for custom SAS credential providers (#721)
    
    `split_sas` parses an Azure Shared Access Signature string into the
    `Vec<(String, String)>` query pairs expected by
    `AzureCredential::SASToken`. It is what `MicrosoftAzureBuilder` runs
    internally when a raw SAS string is supplied via `AzureConfigKey::SasKey`.
    
    Users implementing their own `CredentialProvider<Credential =
    AzureCredential>` that fetches SAS tokens at request time (e.g. tokens
    refreshed from a secret manager or coordination service) currently have
    to reimplement this parser to feed pairs into
    `AzureCredential::SASToken`. Reimplementations are easy to get subtly
    wrong: in particular, using `form_urlencoded` decodes `+` as space,
    which corrupts base64-encoded SAS signatures.
    
    Make `split_sas` public, change its return type to the public
    `crate::Result<Vec<(String, String)>>` (the inner `Error` variants
    already convert into `crate::Error` via the existing `From` impl, so
    the two existing internal call sites are unchanged), document it, and
    re-export it from `object_store::azure`. Add tests for the empty/`?`
    edge cases and the missing-`=` error path.
---
 src/azure/builder.rs | 24 +++++++++++++++++++++++-
 src/azure/mod.rs     |  2 +-
 2 files changed, 24 insertions(+), 2 deletions(-)

diff --git a/src/azure/builder.rs b/src/azure/builder.rs
index afd7a0a..1f57fac 100644
--- a/src/azure/builder.rs
+++ b/src/azure/builder.rs
@@ -1074,7 +1074,8 @@ fn url_from_env(env_name: &str, default_url: &str) -> 
Result<Url> {
     Ok(url)
 }
 
-fn split_sas(sas: &str) -> Result<Vec<(String, String)>, Error> {
+/// Parse a SAS token string into the query pairs expected by 
[`AzureCredential::SASToken`].
+pub fn split_sas(sas: &str) -> Result<Vec<(String, String)>> {
     let sas = percent_decode_str(sas)
         .decode_utf8()
         .map_err(|source| Error::DecodeSasKey { source })?;
@@ -1273,6 +1274,27 @@ mod tests {
         assert_eq!(expected, pairs);
     }
 
+    #[test]
+    fn azure_test_split_sas_trims_leading_question_mark_and_skips_empties() {
+        let pairs = split_sas("?&sv=2021-10-04& &sp=r").unwrap();
+        assert_eq!(
+            pairs,
+            vec![
+                ("sv".to_string(), "2021-10-04".to_string()),
+                ("sp".to_string(), "r".to_string()),
+            ],
+        );
+    }
+
+    #[test]
+    fn azure_test_split_sas_rejects_missing_equals() {
+        let err = split_sas("sv=2021-10-04&bogus").unwrap_err();
+        assert!(
+            err.to_string().contains("Missing component"),
+            "unexpected error: {err}",
+        );
+    }
+
     #[test]
     fn azure_test_client_opts() {
         let key = "AZURE_PROXY_URL";
diff --git a/src/azure/mod.rs b/src/azure/mod.rs
index e6b9b9c..1429bec 100644
--- a/src/azure/mod.rs
+++ b/src/azure/mod.rs
@@ -53,7 +53,7 @@ pub type AzureCredentialProvider = Arc<dyn 
CredentialProvider<Credential = Azure
 use crate::azure::client::AzureClient;
 use crate::client::parts::Parts;
 use crate::list::{PaginatedListOptions, PaginatedListResult, 
PaginatedListStore};
-pub use builder::{AzureConfigKey, MicrosoftAzureBuilder};
+pub use builder::{AzureConfigKey, MicrosoftAzureBuilder, split_sas};
 pub use credential::AzureCredential;
 
 const STORE: &str = "MicrosoftAzure";

Reply via email to