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

exceptionfactory pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/nifi.git


The following commit(s) were added to refs/heads/main by this push:
     new f71ccfd313 NIFI-14650 Toolkit CLI handles OIDC Access Token expiration 
(#10009)
f71ccfd313 is described below

commit f71ccfd313ea414f492e6ba0286bc81c8acb927b
Author: Pierre Villard <[email protected]>
AuthorDate: Tue Jun 17 16:25:47 2025 +0200

    NIFI-14650 Toolkit CLI handles OIDC Access Token expiration (#10009)
    
    Signed-off-by: David Handermann <[email protected]>
---
 .../OIDCClientCredentialsRequestConfig.java        |  45 +++++++--
 .../client/impl/request/util/AccessToken.java      | 108 +++++++++++++++++++++
 2 files changed, 145 insertions(+), 8 deletions(-)

diff --git 
a/nifi-toolkit/nifi-toolkit-client/src/main/java/org/apache/nifi/toolkit/client/impl/request/OIDCClientCredentialsRequestConfig.java
 
b/nifi-toolkit/nifi-toolkit-client/src/main/java/org/apache/nifi/toolkit/client/impl/request/OIDCClientCredentialsRequestConfig.java
index 7ce7eb0409..18f879ba8c 100644
--- 
a/nifi-toolkit/nifi-toolkit-client/src/main/java/org/apache/nifi/toolkit/client/impl/request/OIDCClientCredentialsRequestConfig.java
+++ 
b/nifi-toolkit/nifi-toolkit-client/src/main/java/org/apache/nifi/toolkit/client/impl/request/OIDCClientCredentialsRequestConfig.java
@@ -16,17 +16,21 @@
  */
 package org.apache.nifi.toolkit.client.impl.request;
 
+import com.fasterxml.jackson.databind.DeserializationFeature;
 import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.PropertyNamingStrategies;
 import jakarta.ws.rs.client.ClientBuilder;
 import jakarta.ws.rs.client.Entity;
 import jakarta.ws.rs.client.WebTarget;
 import jakarta.ws.rs.core.Form;
 import org.apache.nifi.toolkit.client.NiFiClientConfig;
 import org.apache.nifi.toolkit.client.RequestConfig;
+import org.apache.nifi.toolkit.client.impl.request.util.AccessToken;
 
 import javax.net.ssl.HostnameVerifier;
 import javax.net.ssl.SSLContext;
 
+import java.time.Instant;
 import java.util.HashMap;
 import java.util.Map;
 import java.util.Objects;
@@ -39,7 +43,15 @@ public class OIDCClientCredentialsRequestConfig implements 
RequestConfig {
     public static final String AUTHORIZATION_HEADER = "Authorization";
     public static final String BEARER = "Bearer";
 
-    private final String token;
+    public static final ObjectMapper OBJECT_MAPPER = new ObjectMapper()
+            .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, 
false)
+            .setPropertyNamingStrategy(PropertyNamingStrategies.SNAKE_CASE);
+
+    private AccessToken token;
+    private final NiFiClientConfig niFiClientConfig;
+    private final String oidcTokenUrl;
+    private final String oidcClientId;
+    private final String oidcClientSecret;
 
     public OIDCClientCredentialsRequestConfig(NiFiClientConfig 
niFiClientConfig, final String oidcTokenUrl, final String oidcClientId, final 
String oidcClientSecret) {
         Objects.requireNonNull(oidcTokenUrl);
@@ -47,6 +59,24 @@ public class OIDCClientCredentialsRequestConfig implements 
RequestConfig {
         Objects.requireNonNull(oidcClientSecret);
         Objects.requireNonNull(niFiClientConfig);
 
+        this.niFiClientConfig = niFiClientConfig;
+        this.oidcTokenUrl = oidcTokenUrl;
+        this.oidcClientId = oidcClientId;
+        this.oidcClientSecret = oidcClientSecret;
+        setNewToken();
+    }
+
+    @Override
+    public Map<String, String> getHeaders() {
+        if (isRefreshRequired()) {
+            setNewToken();
+        }
+        final Map<String, String> headers = new HashMap<>();
+        headers.put(AUTHORIZATION_HEADER, BEARER + " " + 
token.getAccessToken());
+        return headers;
+    }
+
+    private void setNewToken() {
         final SSLContext sslContext = niFiClientConfig.getSslContext();
         final HostnameVerifier hostnameVerifier = 
niFiClientConfig.getHostnameVerifier();
 
@@ -66,18 +96,17 @@ public class OIDCClientCredentialsRequestConfig implements 
RequestConfig {
         final WebTarget target = clientBuilder.build().target(oidcTokenUrl);
         final String response = target.request().post(Entity.form(form), 
String.class);
 
-        ObjectMapper mapper = new ObjectMapper();
         try {
-            this.token = 
mapper.readTree(response).get("access_token").textValue();
+            this.token = OBJECT_MAPPER.readValue(response, AccessToken.class);
         } catch (Exception e) {
             throw new RuntimeException(e);
         }
     }
 
-    @Override
-    public Map<String, String> getHeaders() {
-        final Map<String, String> headers = new HashMap<>();
-        headers.put(AUTHORIZATION_HEADER, BEARER + " " + token);
-        return headers;
+    private boolean isRefreshRequired() {
+        final Instant expirationRefreshTime = token.getFetchTime()
+                .plusSeconds(token.getExpiresIn())
+                .minusSeconds(60); // Refresh 60 seconds before expiration
+        return Instant.now().isAfter(expirationRefreshTime);
     }
 }
diff --git 
a/nifi-toolkit/nifi-toolkit-client/src/main/java/org/apache/nifi/toolkit/client/impl/request/util/AccessToken.java
 
b/nifi-toolkit/nifi-toolkit-client/src/main/java/org/apache/nifi/toolkit/client/impl/request/util/AccessToken.java
new file mode 100644
index 0000000000..9537c97e60
--- /dev/null
+++ 
b/nifi-toolkit/nifi-toolkit-client/src/main/java/org/apache/nifi/toolkit/client/impl/request/util/AccessToken.java
@@ -0,0 +1,108 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.nifi.toolkit.client.impl.request.util;
+
+import java.time.Instant;
+import java.util.HashMap;
+import java.util.Map;
+
+public class AccessToken {
+    private String accessToken;
+    private String refreshToken;
+    private String tokenType;
+    private long expiresIn;
+    private String scope;
+
+    private final Instant fetchTime;
+
+    private final Map<String, Object> additionalParameters = new HashMap<>();
+
+    public AccessToken() {
+        this.fetchTime = Instant.now();
+    }
+
+    public AccessToken(String accessToken, String refreshToken, String 
tokenType, long expiresIn, String scope) {
+        this();
+        this.accessToken = accessToken;
+        this.refreshToken = refreshToken;
+        this.tokenType = tokenType;
+        this.expiresIn = expiresIn;
+        this.scope = scope;
+    }
+
+    public String getAccessToken() {
+        return accessToken;
+    }
+
+    public void setAccessToken(String accessToken) {
+        this.accessToken = accessToken;
+    }
+
+    public String getRefreshToken() {
+        return refreshToken;
+    }
+
+    public void setRefreshToken(String refreshToken) {
+        this.refreshToken = refreshToken;
+    }
+
+    public String getTokenType() {
+        return tokenType;
+    }
+
+    public void setTokenType(String tokenType) {
+        this.tokenType = tokenType;
+    }
+
+    public long getExpiresIn() {
+        return expiresIn;
+    }
+
+    public void setExpiresIn(long expiresIn) {
+        this.expiresIn = expiresIn;
+    }
+
+    public String getScope() {
+        return scope;
+    }
+
+    public void setScope(String scope) {
+        this.scope = scope;
+    }
+
+    public Instant getFetchTime() {
+        return fetchTime;
+    }
+
+    public boolean isExpired() {
+        final Instant expirationTime = fetchTime.plusSeconds(expiresIn);
+        return now().isAfter(expirationTime);
+    }
+
+    Instant now() {
+        return Instant.now();
+    }
+
+    public void setAdditionalParameter(final String key, final Object value) {
+        additionalParameters.put(key, value);
+    }
+
+    public Map<String, Object> getAdditionalParameters() {
+        return additionalParameters;
+    }
+}

Reply via email to