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;
+ }
+}