This is an automated email from the ASF dual-hosted git repository.

adulceanu pushed a commit to branch trunk
in repository https://gitbox.apache.org/repos/asf/jackrabbit-oak.git


The following commit(s) were added to refs/heads/trunk by this push:
     new 3c97422858 Issues/oak 10781 (#1485)
3c97422858 is described below

commit 3c974228584433932873f1ad2279affe2ee14618
Author: Tushar <145645280+t-r...@users.noreply.github.com>
AuthorDate: Tue May 28 18:47:09 2024 +0530

    Issues/oak 10781 (#1485)
    
    * OAK-10675: add service principal support in oak-blob-cloud-azure
    
    * OAK-10675: use user delegation key signed sas for service principal and 
add license to new files
    
    * OAK-10675: some self review changes
    
    * OAK-10675: set null blob headers as empty string
    
    * OAK-10675: update pom to resolve package dependency
    
    * OAK-10675: add import packages to resolve bundle
    
    * OAK-10770: embed runtime dependencies of azure identity in 
oak-segment-azure
    
    * OAK-10770: remove unused imports, refactor dependencies
    
    * OAK-10770: add imports, refactor dependencies
    
    * OAK-10675: fix runtime dependency issues, code review
    
    * OAK-10781: add access token refresh mechanism
    
    * close executor
    
    * reduce token refresh delauy to 1 minute
    
    * retrigger build
---
 .../blobstorage/AzureBlobContainerProvider.java    | 70 +++++++++++++++++++++-
 1 file changed, 67 insertions(+), 3 deletions(-)

diff --git 
a/oak-blob-cloud-azure/src/main/java/org/apache/jackrabbit/oak/blob/cloud/azure/blobstorage/AzureBlobContainerProvider.java
 
b/oak-blob-cloud-azure/src/main/java/org/apache/jackrabbit/oak/blob/cloud/azure/blobstorage/AzureBlobContainerProvider.java
index c8600594cd..2f6d6b4f47 100644
--- 
a/oak-blob-cloud-azure/src/main/java/org/apache/jackrabbit/oak/blob/cloud/azure/blobstorage/AzureBlobContainerProvider.java
+++ 
b/oak-blob-cloud-azure/src/main/java/org/apache/jackrabbit/oak/blob/cloud/azure/blobstorage/AzureBlobContainerProvider.java
@@ -18,6 +18,7 @@
  */
 package org.apache.jackrabbit.oak.blob.cloud.azure.blobstorage;
 
+import com.azure.core.credential.AccessToken;
 import com.azure.core.credential.TokenRequestContext;
 import com.azure.identity.ClientSecretCredential;
 import com.azure.identity.ClientSecretCredentialBuilder;
@@ -34,18 +35,30 @@ import 
com.microsoft.azure.storage.blob.SharedAccessBlobPermissions;
 import com.microsoft.azure.storage.blob.SharedAccessBlobPolicy;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.jackrabbit.core.data.DataStoreException;
+import org.apache.jackrabbit.oak.commons.concurrent.ExecutorCloser;
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
+import java.io.Closeable;
+import java.io.IOException;
 import java.net.URISyntaxException;
 import java.security.InvalidKeyException;
 import java.time.Instant;
+import java.time.LocalDateTime;
+import java.time.OffsetDateTime;
+import java.time.format.DateTimeFormatter;
 import java.util.Date;
 import java.util.EnumSet;
 import java.util.Optional;
 import java.util.Properties;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
 
-public class AzureBlobContainerProvider {
+public class AzureBlobContainerProvider implements Closeable {
+    private static final Logger log = 
LoggerFactory.getLogger(AzureBlobContainerProvider.class);
     private static final String DEFAULT_ENDPOINT_SUFFIX = "core.windows.net";
     private static final String AZURE_DEFAULT_SCOPE = 
"https://storage.azure.com/.default";;
     private final String azureConnectionString;
@@ -57,6 +70,9 @@ public class AzureBlobContainerProvider {
     private final String tenantId;
     private final String clientId;
     private final String clientSecret;
+    private static final long TOKEN_REFRESHER_INITIAL_DELAY = 45L;
+    private static final long TOKEN_REFRESHER_DELAY = 1L;
+    private static final ScheduledExecutorService executorService = 
Executors.newSingleThreadScheduledExecutor();
 
     private AzureBlobContainerProvider(Builder builder) {
         this.azureConnectionString = builder.azureConnectionString;
@@ -194,8 +210,15 @@ public class AzureBlobContainerProvider {
                 .clientSecret(clientSecret)
                 .tenantId(tenantId)
                 .build();
-        String accessToken = clientSecretCredential.getTokenSync(new 
TokenRequestContext().addScopes(AZURE_DEFAULT_SCOPE)).getToken();
-        return new StorageCredentialsToken(accountName, accessToken);
+        AccessToken accessToken = clientSecretCredential.getTokenSync(new 
TokenRequestContext().addScopes(AZURE_DEFAULT_SCOPE));
+        if (accessToken == null || 
StringUtils.isBlank(accessToken.getToken())) {
+            log.error("Access token is null or empty");
+            throw new IllegalArgumentException("Could not connect to azure 
storage, access token is null or empty");
+        }
+        StorageCredentialsToken storageCredentialsToken = new 
StorageCredentialsToken(accountName, accessToken.getToken());
+        TokenRefresher tokenRefresher = new 
TokenRefresher(clientSecretCredential, accessToken, storageCredentialsToken);
+        executorService.scheduleWithFixedDelay(tokenRefresher, 
TOKEN_REFRESHER_INITIAL_DELAY, TOKEN_REFRESHER_DELAY, TimeUnit.MINUTES);
+        return storageCredentialsToken;
     }
 
     @NotNull
@@ -268,4 +291,45 @@ public class AzureBlobContainerProvider {
         return StringUtils.isBlank(azureConnectionString) &&
                 StringUtils.isNoneBlank(accountName, tenantId, clientId, 
clientSecret);
     }
+
+    private static class TokenRefresher implements Runnable {
+
+        private final ClientSecretCredential clientSecretCredential;
+        private AccessToken accessToken;
+        private final StorageCredentialsToken storageCredentialsToken;
+
+        public TokenRefresher(ClientSecretCredential clientSecretCredential,
+                              AccessToken accessToken,
+                              StorageCredentialsToken storageCredentialsToken) 
{
+            this.clientSecretCredential = clientSecretCredential;
+            this.accessToken = accessToken;
+            this.storageCredentialsToken = storageCredentialsToken;
+        }
+
+        @Override
+        public void run() {
+            try {
+                log.debug("Checking for azure access token expiry at: {}", 
LocalDateTime.now());
+                OffsetDateTime tokenExpiryThreshold = 
OffsetDateTime.now().plusMinutes(5);
+                if (accessToken.getExpiresAt() != null && 
accessToken.getExpiresAt().isBefore(tokenExpiryThreshold)) {
+                    log.info("Access token is about to expire (5 minutes or 
less) at: {}. New access token will be generated",
+                            
accessToken.getExpiresAt().format(DateTimeFormatter.ISO_LOCAL_DATE_TIME));
+                    AccessToken newToken = 
clientSecretCredential.getTokenSync(new 
TokenRequestContext().addScopes(AZURE_DEFAULT_SCOPE));
+                    if (newToken == null || 
StringUtils.isBlank(newToken.getToken())) {
+                        log.error("New access token is null or empty");
+                        return;
+                    }
+                    this.accessToken = newToken;
+                    
this.storageCredentialsToken.updateToken(this.accessToken.getToken());
+                }
+            } catch (Exception e) {
+                log.error("Error while acquiring new access token: ", e);
+            }
+        }
+    }
+
+    @Override
+    public void close() throws IOException {
+        new ExecutorCloser(executorService).close();
+    }
 }

Reply via email to