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 a1e271d Fix GCP signing token (#338)
a1e271d is described below
commit a1e271d2575d9767341b246b0e231ecec9243092
Author: jackm-mimica <[email protected]>
AuthorDate: Tue May 6 18:41:09 2025 +0100
Fix GCP signing token (#338)
* Fix GCP signing token
* refresh token replaced by access token
* Added parsing email from id_token
* falls back to the tokeninfo endpoint on error
---
src/gcp/credential.rs | 88 +++++++++++++++++++++++++++++++++++++++------------
1 file changed, 68 insertions(+), 20 deletions(-)
diff --git a/src/gcp/credential.rs b/src/gcp/credential.rs
index 261230b..1d224fd 100644
--- a/src/gcp/credential.rs
+++ b/src/gcp/credential.rs
@@ -223,6 +223,7 @@ struct TokenClaims<'a> {
struct TokenResponse {
access_token: String,
expires_in: u64,
+ id_token: Option<String>,
}
/// Self-signed JWT (JSON Web Token).
@@ -604,6 +605,37 @@ struct EmailResponse {
email: String,
}
+#[derive(Debug, Deserialize)]
+struct IdTokenClaims {
+ email: String,
+}
+
+async fn get_token_response(
+ client_id: &str,
+ client_secret: &str,
+ refresh_token: &str,
+ client: &HttpClient,
+ retry: &RetryConfig,
+) -> Result<TokenResponse> {
+ client
+ .post(DEFAULT_TOKEN_GCP_URI)
+ .form([
+ ("grant_type", "refresh_token"),
+ ("client_id", client_id),
+ ("client_secret", client_secret),
+ ("refresh_token", refresh_token),
+ ])
+ .retryable(retry)
+ .idempotent(true)
+ .send()
+ .await
+ .map_err(|source| Error::TokenRequest { source })?
+ .into_body()
+ .json::<TokenResponse>()
+ .await
+ .map_err(|source| Error::TokenResponseBody { source })
+}
+
impl AuthorizedUserSigningCredentials {
pub(crate) fn from(credential: AuthorizedUserCredentials) ->
crate::Result<Self> {
Ok(Self { credential })
@@ -614,16 +646,42 @@ impl AuthorizedUserSigningCredentials {
client: &HttpClient,
retry: &RetryConfig,
) -> crate::Result<String> {
+ let response = get_token_response(
+ &self.credential.client_id,
+ &self.credential.client_secret,
+ &self.credential.refresh_token,
+ client,
+ retry,
+ )
+ .await?;
+
+ // Extract email from id_token if available
+ if let Some(id_token) = response.id_token {
+ // Split the JWT string by dots to get the payload section
+ let parts: Vec<&str> = id_token.split('.').collect();
+ if parts.len() == 3 {
+ // Decode the base64-encoded payload (middle part)
+ if let Ok(payload) = BASE64_URL_SAFE_NO_PAD.decode(parts[1]) {
+ // Parse the payload as JSON and extract the email
+ if let Ok(claims) =
serde_json::from_slice::<IdTokenClaims>(&payload) {
+ return Ok(claims.email);
+ }
+ }
+ // If any of the parsing steps fail, fallback to other method
+ }
+ }
+
+ // Fallback to the original method if id_token is not available or
invalid
let response = client
.get("https://oauth2.googleapis.com/tokeninfo")
- .query(&[("access_token", &self.credential.refresh_token)])
+ .query(&[("access_token", response.access_token)])
.send_retry(retry)
.await
.map_err(|source| Error::TokenRequest { source })?
.into_body()
.json::<EmailResponse>()
.await
- .map_err(|source| Error::TokenResponseBody { source })?;
+ .map_err(|source: HttpError| Error::TokenResponseBody { source })?;
Ok(response.email)
}
@@ -639,7 +697,6 @@ impl TokenProvider for AuthorizedUserSigningCredentials {
retry: &RetryConfig,
) -> crate::Result<TemporaryToken<Arc<GcpSigningCredential>>> {
let email = self.client_email(client, retry).await?;
-
Ok(TemporaryToken {
token: Arc::new(GcpSigningCredential {
email,
@@ -659,23 +716,14 @@ impl TokenProvider for AuthorizedUserCredentials {
client: &HttpClient,
retry: &RetryConfig,
) -> crate::Result<TemporaryToken<Arc<GcpCredential>>> {
- let response = client
- .post(DEFAULT_TOKEN_GCP_URI)
- .form([
- ("grant_type", "refresh_token"),
- ("client_id", &self.client_id),
- ("client_secret", &self.client_secret),
- ("refresh_token", &self.refresh_token),
- ])
- .retryable(retry)
- .idempotent(true)
- .send()
- .await
- .map_err(|source| Error::TokenRequest { source })?
- .into_body()
- .json::<TokenResponse>()
- .await
- .map_err(|source| Error::TokenResponseBody { source })?;
+ let response = get_token_response(
+ &self.client_id,
+ &self.client_secret,
+ &self.refresh_token,
+ client,
+ retry,
+ )
+ .await?;
Ok(TemporaryToken {
token: Arc::new(GcpCredential {