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

lmccay pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/knox.git


The following commit(s) were added to refs/heads/master by this push:
     new ccfb00c3c KNOX-3129 - Add expiry to APIKEY and CLIENTID API Responses 
(#1022)
ccfb00c3c is described below

commit ccfb00c3ce337b801ffa111c2a16c3381d82e591
Author: lmccay <[email protected]>
AuthorDate: Mon Apr 14 18:48:58 2025 -0400

    KNOX-3129 - Add expiry to APIKEY and CLIENTID API Responses (#1022)
---
 .../gateway/service/knoxtoken/APIKeyResource.java  |  1 +
 .../knoxtoken/ClientCredentialsResource.java       |  1 +
 .../gateway/service/knoxtoken/OAuthResource.java   | 43 +++++--------------
 .../knoxtoken/PasscodeTokenResourceBase.java       | 50 ++++++++++++++++++++--
 .../knoxtoken/TokenServiceResourceTest.java        | 32 +++++++++++++-
 5 files changed, 90 insertions(+), 37 deletions(-)

diff --git 
a/gateway-service-knoxtoken/src/main/java/org/apache/knox/gateway/service/knoxtoken/APIKeyResource.java
 
b/gateway-service-knoxtoken/src/main/java/org/apache/knox/gateway/service/knoxtoken/APIKeyResource.java
index b29750890..eb8312013 100644
--- 
a/gateway-service-knoxtoken/src/main/java/org/apache/knox/gateway/service/knoxtoken/APIKeyResource.java
+++ 
b/gateway-service-knoxtoken/src/main/java/org/apache/knox/gateway/service/knoxtoken/APIKeyResource.java
@@ -76,6 +76,7 @@ public class APIKeyResource extends PasscodeTokenResourceBase 
{
             final HashMap<String, Object> map = new HashMap<>();
             map.put(KEY_ID, tokenId);
             map.put(API_KEY, passcode);
+            addExpiryIfNotNever(map);
             String jsonResponse = JsonUtils.renderAsJsonString(map);
             return resp.responseBuilder.entity(jsonResponse).build();
         }
diff --git 
a/gateway-service-knoxtoken/src/main/java/org/apache/knox/gateway/service/knoxtoken/ClientCredentialsResource.java
 
b/gateway-service-knoxtoken/src/main/java/org/apache/knox/gateway/service/knoxtoken/ClientCredentialsResource.java
index 63bfd7f4d..9b7e98d57 100644
--- 
a/gateway-service-knoxtoken/src/main/java/org/apache/knox/gateway/service/knoxtoken/ClientCredentialsResource.java
+++ 
b/gateway-service-knoxtoken/src/main/java/org/apache/knox/gateway/service/knoxtoken/ClientCredentialsResource.java
@@ -75,6 +75,7 @@ public class ClientCredentialsResource extends 
PasscodeTokenResourceBase {
             final HashMap<String, Object> map = new HashMap<>();
             map.put(CLIENT_ID, tokenId);
             map.put(CLIENT_SECRET, passcode);
+            addExpiryIfNotNever(map);
             String jsonResponse = JsonUtils.renderAsJsonString(map);
             return resp.responseBuilder.entity(jsonResponse).build();
         }
diff --git 
a/gateway-service-knoxtoken/src/main/java/org/apache/knox/gateway/service/knoxtoken/OAuthResource.java
 
b/gateway-service-knoxtoken/src/main/java/org/apache/knox/gateway/service/knoxtoken/OAuthResource.java
index 4064092b5..e2f7239a4 100644
--- 
a/gateway-service-knoxtoken/src/main/java/org/apache/knox/gateway/service/knoxtoken/OAuthResource.java
+++ 
b/gateway-service-knoxtoken/src/main/java/org/apache/knox/gateway/service/knoxtoken/OAuthResource.java
@@ -17,18 +17,16 @@
  */
 package org.apache.knox.gateway.service.knoxtoken;
 
-import org.apache.knox.gateway.i18n.messages.MessagesFactory;
 import org.apache.knox.gateway.util.JsonUtils;
 
 import javax.inject.Singleton;
+import javax.servlet.ServletContext;
+import javax.servlet.ServletException;
 import javax.ws.rs.GET;
 import javax.ws.rs.POST;
 import javax.ws.rs.Path;
 import javax.ws.rs.Produces;
 import javax.ws.rs.core.Response;
-
-import java.time.Duration;
-import java.time.format.DateTimeParseException;
 import java.util.HashMap;
 
 import static javax.ws.rs.core.MediaType.APPLICATION_JSON;
@@ -36,8 +34,7 @@ import static javax.ws.rs.core.MediaType.APPLICATION_XML;
 
 @Singleton
 @Path(OAuthResource.RESOURCE_PATH)
-public class OAuthResource extends TokenResource {
-    private static TokenServiceMessages log = 
MessagesFactory.get(TokenServiceMessages.class);
+public class OAuthResource extends PasscodeTokenResourceBase {
     static final String RESOURCE_PATH = 
"/{serviceName:.*}/v1/{oauthSegment:(oauth|token)}{path:(/tokens)?}";
     public static final String ISSUED_TOKEN_TYPE = "issued_token_type";
     public static final String REFRESH_TOKEN = "refresh_token";
@@ -57,6 +54,13 @@ public class OAuthResource extends TokenResource {
         return super.doPost();
     }
 
+    @Override
+    protected ServletContext wrapContextForDefaultParams(ServletContext 
context) throws ServletException {
+        ServletContext wrapperContext = new ServletContextWrapper(context);
+        setupTokenStateService(wrapperContext);
+        return wrapperContext;
+    }
+
     @Override
     public Response getAuthenticationToken() {
 
@@ -101,31 +105,4 @@ public class OAuthResource extends TokenResource {
             return resp.responseBuilder.build();
         }
     }
-
-    private long getTokenLifetimeInSeconds() {
-        long secs = tokenTTL/1000;
-
-        String lifetimeStr = request.getParameter(LIFESPAN);
-        if (lifetimeStr == null || lifetimeStr.isEmpty()) {
-            if (tokenTTL == -1) {
-                return -1;
-            }
-        }
-        else {
-            try {
-                long lifetime = Duration.parse(lifetimeStr).toMillis()/1000;
-                if (tokenTTL == -1) {
-                    // if TTL is set to -1 the topology owner grants unlimited 
lifetime therefore no additional check is needed on lifespan
-                    secs = lifetime;
-                } else if (lifetime <= tokenTTL/1000) {
-                    //this is expected due to security reasons: the configured 
TTL acts as an upper limit regardless of the supplied lifespan
-                    secs = lifetime;
-                }
-            }
-            catch (DateTimeParseException e) {
-                log.invalidLifetimeValue(lifetimeStr);
-            }
-        }
-        return secs;
-    }
 }
diff --git 
a/gateway-service-knoxtoken/src/main/java/org/apache/knox/gateway/service/knoxtoken/PasscodeTokenResourceBase.java
 
b/gateway-service-knoxtoken/src/main/java/org/apache/knox/gateway/service/knoxtoken/PasscodeTokenResourceBase.java
index 6804fbf24..63b43661c 100644
--- 
a/gateway-service-knoxtoken/src/main/java/org/apache/knox/gateway/service/knoxtoken/PasscodeTokenResourceBase.java
+++ 
b/gateway-service-knoxtoken/src/main/java/org/apache/knox/gateway/service/knoxtoken/PasscodeTokenResourceBase.java
@@ -22,6 +22,7 @@ import com.nimbusds.jose.JWSAlgorithm;
 import com.nimbusds.jose.jwk.OctetSequenceKey;
 import com.nimbusds.jose.jwk.gen.OctetSequenceKeyGenerator;
 import org.apache.knox.gateway.config.GatewayConfig;
+import org.apache.knox.gateway.i18n.messages.MessagesFactory;
 import org.apache.knox.gateway.services.GatewayServices;
 import org.apache.knox.gateway.services.ServiceType;
 import org.apache.knox.gateway.services.security.AliasService;
@@ -32,15 +33,32 @@ import 
org.apache.knox.gateway.services.security.token.impl.TokenMAC;
 import javax.servlet.ServletContext;
 import javax.servlet.ServletException;
 import javax.ws.rs.core.Response;
+import java.time.Duration;
+import java.time.format.DateTimeParseException;
+import java.util.Map;
 import java.util.UUID;
 
 public class PasscodeTokenResourceBase extends TokenResource {
     protected GatewayServices services;
+    private static TokenServiceMessages log = 
MessagesFactory.get(TokenServiceMessages.class);
+
+    protected void addExpiryIfNotNever(Map<String, Object> map) {
+        long expiresIn = getTokenLifetimeInSeconds();
+        if (expiresIn != -1) {
+            map.put(EXPIRES_IN, expiresIn);
+        }
+    }
+
     @Override
     protected ServletContext wrapContextForDefaultParams(ServletContext 
context) throws ServletException {
         ServletContext wrapperContext = new ServletContextWrapper(context);
-        
wrapperContext.setInitParameter(TokenStateService.CONFIG_SERVER_MANAGED, 
"true");
         wrapperContext.setInitParameter(TokenResource.TOKEN_TTL_PARAM, "-1");
+        setupTokenStateService(wrapperContext);
+        return wrapperContext;
+    }
+
+    protected void setupTokenStateService(ServletContext wrapperContext) 
throws ServletException {
+        
wrapperContext.setInitParameter(TokenStateService.CONFIG_SERVER_MANAGED, 
"true");
         services = (GatewayServices) 
wrapperContext.getAttribute(GatewayServices.GATEWAY_SERVICES_ATTRIBUTE);
         tokenStateService = 
services.getService(ServiceType.TOKEN_STATE_SERVICE);
         final GatewayConfig gatewayConfig = (GatewayConfig) 
wrapperContext.getAttribute(GatewayConfig.GATEWAY_CONFIG_ATTRIBUTE);
@@ -55,10 +73,9 @@ public class PasscodeTokenResourceBase extends TokenResource 
{
         if (hashkey == null) {
             generateAndStoreHMACKeyAlias();
         }
-        return wrapperContext;
     }
 
-    private void generateAndStoreHMACKeyAlias() {
+    protected void generateAndStoreHMACKeyAlias() {
         final int keyLength = 
Integer.parseInt(JWSAlgorithm.HS256.getName().substring(2));
         String jwkAsText = null;
         try {
@@ -87,4 +104,31 @@ public class PasscodeTokenResourceBase extends 
TokenResource {
 
         return response;
     }
+
+    protected long getTokenLifetimeInSeconds() {
+        long secs = tokenTTL/1000;
+
+        String lifetimeStr = request.getParameter(LIFESPAN);
+        if (lifetimeStr == null || lifetimeStr.isEmpty()) {
+            if (tokenTTL == -1) {
+                return -1;
+            }
+        }
+        else {
+            try {
+                long lifetime = Duration.parse(lifetimeStr).toMillis()/1000;
+                if (tokenTTL == -1) {
+                    // if TTL is set to -1 the topology owner grants unlimited 
lifetime therefore no additional check is needed on lifespan
+                    secs = lifetime;
+                } else if (lifetime <= tokenTTL/1000) {
+                    //this is expected due to security reasons: the configured 
TTL acts as an upper limit regardless of the supplied lifespan
+                    secs = lifetime;
+                }
+            }
+            catch (DateTimeParseException e) {
+                log.invalidLifetimeValue(lifetimeStr);
+            }
+        }
+        return secs;
+    }
 }
diff --git 
a/gateway-service-knoxtoken/src/test/java/org/apache/knox/gateway/service/knoxtoken/TokenServiceResourceTest.java
 
b/gateway-service-knoxtoken/src/test/java/org/apache/knox/gateway/service/knoxtoken/TokenServiceResourceTest.java
index 9613b28b9..2214d365d 100644
--- 
a/gateway-service-knoxtoken/src/test/java/org/apache/knox/gateway/service/knoxtoken/TokenServiceResourceTest.java
+++ 
b/gateway-service-knoxtoken/src/test/java/org/apache/knox/gateway/service/knoxtoken/TokenServiceResourceTest.java
@@ -1321,11 +1321,18 @@ public class TokenServiceResourceTest {
 
   @Test
   public void testClientCredentialsResponse() throws Exception {
+    tryClientCredentialsResource(null);
+    tryClientCredentialsResource("30000");
+  }
+
+  public void tryClientCredentialsResource(String expiryInMs) throws Exception 
{
     Map<String, String> contextExpectations = new HashMap<>();
     try {
       tss = new PersistentTestTokenStateService();
+      if (expiryInMs != null) {
+        contextExpectations.put("knox.token.ttl", expiryInMs);
+      }
       configureCommonExpectations(contextExpectations, Boolean.TRUE);
-
       ClientCredentialsResource ccr = new ClientCredentialsResource();
       ccr.request = request;
       ccr.context = context;
@@ -1338,6 +1345,13 @@ public class TokenServiceResourceTest {
       assertNotNull(clientId);
       String clientSecret = getTagValue(response.getEntity().toString(), 
ClientCredentialsResource.CLIENT_SECRET);
       assertNotNull(clientSecret);
+      String expiresIn = getTagValue(response.getEntity().toString(), 
APIKeyResource.EXPIRES_IN);
+      if (expiryInMs == null) {
+        assertNull(expiresIn);
+      } else {
+        assertNotNull(expiresIn);
+        assertEquals(30, Integer.parseInt(expiresIn));
+      }
     } finally {
       tss = new TestTokenStateService();
     }
@@ -1421,9 +1435,18 @@ public class TokenServiceResourceTest {
 
   @Test
   public void testAPIKeyResponse() throws Exception {
+    tryAPIKeyResponse(null);
+    tryAPIKeyResponse("30000");
+  }
+
+  public void tryAPIKeyResponse(String expiryInMs) throws Exception {
     Map<String, String> contextExpectations = new HashMap<>();
     try {
       tss = new PersistentTestTokenStateService();
+      // let the default built into
+      if (expiryInMs != null) {
+        contextExpectations.put("knox.token.ttl", expiryInMs);
+      }
       configureCommonExpectations(contextExpectations, Boolean.TRUE);
 
       APIKeyResource ccr = new APIKeyResource();
@@ -1438,6 +1461,13 @@ public class TokenServiceResourceTest {
       assertNotNull(keyId);
       String apikey = getTagValue(response.getEntity().toString(), 
APIKeyResource.API_KEY);
       assertNotNull(apikey);
+      String expiresIn = getTagValue(response.getEntity().toString(), 
APIKeyResource.EXPIRES_IN);
+      if (expiryInMs == null) {
+        assertNull(expiresIn);
+      } else {
+        assertNotNull(expiresIn);
+        assertEquals(30, Integer.parseInt(expiresIn));
+      }
     } finally {
       tss = new TestTokenStateService();
     }

Reply via email to