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

smolnar 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 71a72e5e0 KNOX-3259 - Implement OAuth Refresh Token and Exchange Token 
Grant Types (#1155)
71a72e5e0 is described below

commit 71a72e5e0697fa6331876de8e341caeb93b84cc4
Author: lmccay <[email protected]>
AuthorDate: Tue Feb 24 03:03:29 2026 -0500

    KNOX-3259 - Implement OAuth Refresh Token and Exchange Token Grant Types 
(#1155)
---
 .../federation/jwt/filter/JWTFederationFilter.java |  39 +++++--
 ...st.java => OAuthFlowsFederationFilterTest.java} | 126 ++++++++++++++++++++-
 2 files changed, 157 insertions(+), 8 deletions(-)

diff --git 
a/gateway-provider-security-jwt/src/main/java/org/apache/knox/gateway/provider/federation/jwt/filter/JWTFederationFilter.java
 
b/gateway-provider-security-jwt/src/main/java/org/apache/knox/gateway/provider/federation/jwt/filter/JWTFederationFilter.java
index e3b0e9f2f..174928265 100644
--- 
a/gateway-provider-security-jwt/src/main/java/org/apache/knox/gateway/provider/federation/jwt/filter/JWTFederationFilter.java
+++ 
b/gateway-provider-security-jwt/src/main/java/org/apache/knox/gateway/provider/federation/jwt/filter/JWTFederationFilter.java
@@ -65,6 +65,10 @@ public class JWTFederationFilter extends AbstractJWTFilter {
   public static final String CLIENT_SECRET = "client_secret";
   public static final String CLIENT_ID = "client_id";
   public static final String MISMATCHING_CLIENT_ID_AND_CLIENT_SECRET = "Client 
credentials flow with mismatching client_id and client_secret";
+  public static final String REFRESH_TOKEN = "refresh_token";
+  public static final String REFRESH_TOKEN_PARAM = "refresh_token";
+  public static final String TOKEN_EXCHANGE = "token_exchange";
+  public static final String SUBJECT_TOKEN = "subject_token";
 
   public enum TokenType {
     JWT, Passcode;
@@ -308,7 +312,7 @@ public class JWTFederationFilter extends AbstractJWTFilter {
       }
 
       if (parsed == null) {
-        parsed = parseFromClientCredentialsFlow(request);
+        parsed = parseFromGrantTypeFlow(request);
       }
 
       if (parsed == null) {
@@ -321,7 +325,7 @@ public class JWTFederationFilter extends AbstractJWTFilter {
       return parsed;
     }
 
-    private Pair<TokenType, String> 
parseFromClientCredentialsFlow(ServletRequest request) throws IOException {
+    private Pair<TokenType, String> parseFromGrantTypeFlow(ServletRequest 
request) throws IOException {
       /*
         POST /{tenant}/oauth2/v2.0/token HTTP/1.1
         Host: login.microsoftonline.com:443
@@ -338,18 +342,39 @@ public class JWTFederationFilter extends 
AbstractJWTFilter {
       if (clientSecretPresentAsQueryString) {
         throw new SecurityException("client_secret must not be sent as a query 
parameter");
       }
-      return getClientCredentialsFromRequestBody(request);
+      return getTokenFromRequestBody(request);
     }
 
-    private Pair<TokenType, String> 
getClientCredentialsFromRequestBody(ServletRequest request) {
+    private Pair<TokenType, String> getTokenFromRequestBody(ServletRequest 
request) {
         final String grantType = request.getParameter(GRANT_TYPE);
         if (CLIENT_CREDENTIALS.equals(grantType)) {
-          // this is indeed a client credentials flow client_id and
-          // client_secret are expected now the client_id will be in
-          // the token as the token_id so we will get that later
+          // client credentials flow: client_id and client_secret are expected
+          // the client_id will be in the token as the token_id
           final String clientSecret = request.getParameter(CLIENT_SECRET);
           validateClientID((HttpServletRequest) request, clientSecret);
           return Pair.of(TokenType.Passcode, clientSecret);
+        } else if (REFRESH_TOKEN.equals(grantType)) {
+          // refresh_token flow: the refresh_token parameter contains the 
actual token
+          final String refreshToken = 
request.getParameter(REFRESH_TOKEN_PARAM);
+          if (refreshToken != null) {
+            // determine if it's a JWT or passcode token
+            if (isJWT(refreshToken)) {
+              return Pair.of(TokenType.JWT, refreshToken);
+            } else {
+              return Pair.of(TokenType.Passcode, refreshToken);
+            }
+          }
+        } else if (TOKEN_EXCHANGE.equals(grantType)) {
+          // token_exchange flow: the subject_token parameter contains the 
token to be exchanged
+          final String subjectToken = request.getParameter(SUBJECT_TOKEN);
+          if (subjectToken != null) {
+            // determine if it's a JWT or passcode token
+            if (isJWT(subjectToken)) {
+              return Pair.of(TokenType.JWT, subjectToken);
+            } else {
+              return Pair.of(TokenType.Passcode, subjectToken);
+            }
+          }
         }
       return null;
     }
diff --git 
a/gateway-provider-security-jwt/src/test/java/org/apache/knox/gateway/provider/federation/ClientIdAndClientSecretFederationFilterTest.java
 
b/gateway-provider-security-jwt/src/test/java/org/apache/knox/gateway/provider/federation/OAuthFlowsFederationFilterTest.java
similarity index 62%
rename from 
gateway-provider-security-jwt/src/test/java/org/apache/knox/gateway/provider/federation/ClientIdAndClientSecretFederationFilterTest.java
rename to 
gateway-provider-security-jwt/src/test/java/org/apache/knox/gateway/provider/federation/OAuthFlowsFederationFilterTest.java
index 710604a7e..064be121a 100644
--- 
a/gateway-provider-security-jwt/src/test/java/org/apache/knox/gateway/provider/federation/ClientIdAndClientSecretFederationFilterTest.java
+++ 
b/gateway-provider-security-jwt/src/test/java/org/apache/knox/gateway/provider/federation/OAuthFlowsFederationFilterTest.java
@@ -38,7 +38,7 @@ import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
 
 
-public class ClientIdAndClientSecretFederationFilterTest extends 
TokenIDAsHTTPBasicCredsFederationFilterTest {
+public class OAuthFlowsFederationFilterTest extends 
TokenIDAsHTTPBasicCredsFederationFilterTest {
     @Override
     protected void setTokenOnRequest(HttpServletRequest request, String 
authUsername, String authPassword) {
         
EasyMock.expect((Object)request.getHeader("Authorization")).andReturn("");
@@ -243,4 +243,128 @@ public class ClientIdAndClientSecretFederationFilterTest 
extends TokenIDAsHTTPBa
     @Test
     public void testUnableToParseJWT() throws Exception {
     }
+
+    @Test
+    public void testGetWireTokenUsingRefreshTokenFlow() throws Exception {
+      final String refreshToken = 
"WTJ4cFpXNTBMV2xrTFRFeU16UTE6OlkyeHBaVzUwTFhObFkzSmxkQzB4TWpNME5RPT0=";
+
+      final HttpServletRequest request = 
EasyMock.createNiceMock(HttpServletRequest.class);
+      
EasyMock.expect(request.getHeader("Authorization")).andReturn(null).anyTimes();
+      EasyMock.expect(request.getQueryString()).andReturn(null).anyTimes();
+      
EasyMock.expect(request.getParameter(JWTFederationFilter.GRANT_TYPE)).andReturn(JWTFederationFilter.REFRESH_TOKEN).anyTimes();
+      
EasyMock.expect(request.getParameter(JWTFederationFilter.REFRESH_TOKEN_PARAM)).andReturn(refreshToken).anyTimes();
+      EasyMock.replay(request);
+
+      handler.init(new TestFilterConfig(getProperties()));
+      final Pair<TokenType, String> wireToken = ((TestJWTFederationFilter) 
handler).getWireToken(request);
+
+      EasyMock.verify(request);
+
+      assertNotNull(wireToken);
+      assertEquals(TokenType.Passcode, wireToken.getLeft());
+      assertEquals(refreshToken, wireToken.getRight());
+    }
+
+    @Test
+    public void testGetWireTokenUsingTokenExchangeFlow() throws Exception {
+      final String subjectToken = 
"WTJ4cFpXNTBMV2xrTFRFeU16UTE2OlkyeHBaVzUwTFhObFkzSmxkQzB4TWpNME5RPT0=";
+
+      final HttpServletRequest request = 
EasyMock.createNiceMock(HttpServletRequest.class);
+      
EasyMock.expect(request.getHeader("Authorization")).andReturn(null).anyTimes();
+      EasyMock.expect(request.getQueryString()).andReturn(null).anyTimes();
+      
EasyMock.expect(request.getParameter(JWTFederationFilter.GRANT_TYPE)).andReturn(JWTFederationFilter.TOKEN_EXCHANGE).anyTimes();
+      
EasyMock.expect(request.getParameter(JWTFederationFilter.SUBJECT_TOKEN)).andReturn(subjectToken).anyTimes();
+      EasyMock.replay(request);
+
+      handler.init(new TestFilterConfig(getProperties()));
+      final Pair<TokenType, String> wireToken = ((TestJWTFederationFilter) 
handler).getWireToken(request);
+
+      EasyMock.verify(request);
+
+      assertNotNull(wireToken);
+      assertEquals(TokenType.Passcode, wireToken.getLeft());
+      assertEquals(subjectToken, wireToken.getRight());
+    }
+
+    @Test
+    public void testVerifyRefreshTokenFlow() throws Exception {
+        final String topologyName = "jwt-topology";
+        final String tokenId = "4e0c548b-6568-4061-a3dc-62908087650b";
+        final String passcode = "0138aaed-ca2a-47f1-8ed8-e0c397596f96";
+        String passcodeToken = 
"TkdVd1l6VTBPR0l0TmpVMk9DMDBNRFl4TFdFelpHTXROakk1TURnd09EYzJOVEJpOjpNREV6T0dGaFpXUXRZMkV5WVMwME4yWXhMVGhsWkRndFpUQmpNemszTlRrMlpqazI=";
+
+        final TokenStateService tokenStateService = 
EasyMock.createNiceMock(TokenStateService.class);
+        
EasyMock.expect(tokenStateService.getTokenExpiration(tokenId)).andReturn(Long.MAX_VALUE).anyTimes();
+
+        final TokenMetadata tokenMetadata = 
EasyMock.createNiceMock(TokenMetadata.class);
+        EasyMock.expect(tokenMetadata.isEnabled()).andReturn(true).anyTimes();
+        
EasyMock.expect(tokenMetadata.getPasscode()).andReturn(passcodeToken).anyTimes();
+        
EasyMock.expect(tokenStateService.getTokenMetadata(EasyMock.anyString())).andReturn(tokenMetadata).anyTimes();
+
+        final Properties filterConfigProps = getProperties();
+        filterConfigProps.put(TokenStateService.CONFIG_SERVER_MANAGED, 
Boolean.toString(true));
+        filterConfigProps.put(TestFilterConfig.TOPOLOGY_NAME_PROP, 
topologyName);
+        final FilterConfig filterConfig = new 
TestFilterConfig(filterConfigProps, tokenStateService);
+        handler.init(filterConfig);
+
+        final HttpServletRequest request = 
EasyMock.createNiceMock(HttpServletRequest.class);
+        EasyMock.expect(request.getRequestURL()).andReturn(new 
StringBuffer(SERVICE_URL)).anyTimes();
+        
EasyMock.expect(request.getHeader("Authorization")).andReturn(null).anyTimes();
+        
EasyMock.expect(request.getParameter(JWTFederationFilter.GRANT_TYPE)).andReturn(JWTFederationFilter.REFRESH_TOKEN).anyTimes();
+        
EasyMock.expect(request.getParameter(JWTFederationFilter.REFRESH_TOKEN_PARAM)).andReturn(passcodeToken).anyTimes();
+        EasyMock.expect(request.getQueryString()).andReturn(null).anyTimes();
+
+        final HttpServletResponse response = 
EasyMock.createNiceMock(HttpServletResponse.class);
+        EasyMock.replay(tokenStateService, tokenMetadata, request, response);
+
+        SignatureVerificationCache.getInstance(topologyName, 
filterConfig).recordSignatureVerification(passcode);
+
+        final TestFilterChain chain = new TestFilterChain();
+        handler.doFilter(request, response, chain);
+
+        EasyMock.verify(response);
+        Assert.assertTrue(chain.doFilterCalled);
+        Assert.assertNotNull(chain.subject);
+    }
+
+    @Test
+    public void testVerifyTokenExchangeFlow() throws Exception {
+        final String topologyName = "jwt-topology";
+        final String tokenId = "4e0c548b-6568-4061-a3dc-62908087650c";
+        final String passcode = "0138aaed-ca2a-47f1-8ed8-e0c397596f97";
+        String passcodeToken = 
"TkdVd1l6VTBPR0l0TmpVMk9DMDBNRFl4TFdFelpHTXROakk1TURnd09EYzJOVEJqOjpNREV6T0dGaFpXUXRZMkV5WVMwME4yWXhMVGhsWkRndFpUQmpNemszTlRrMlpqazM=";
+
+        final TokenStateService tokenStateService = 
EasyMock.createNiceMock(TokenStateService.class);
+        
EasyMock.expect(tokenStateService.getTokenExpiration(tokenId)).andReturn(Long.MAX_VALUE).anyTimes();
+
+        final TokenMetadata tokenMetadata = 
EasyMock.createNiceMock(TokenMetadata.class);
+        EasyMock.expect(tokenMetadata.isEnabled()).andReturn(true).anyTimes();
+        
EasyMock.expect(tokenMetadata.getPasscode()).andReturn(passcodeToken).anyTimes();
+        
EasyMock.expect(tokenStateService.getTokenMetadata(EasyMock.anyString())).andReturn(tokenMetadata).anyTimes();
+
+        final Properties filterConfigProps = getProperties();
+        filterConfigProps.put(TokenStateService.CONFIG_SERVER_MANAGED, 
Boolean.toString(true));
+        filterConfigProps.put(TestFilterConfig.TOPOLOGY_NAME_PROP, 
topologyName);
+        final FilterConfig filterConfig = new 
TestFilterConfig(filterConfigProps, tokenStateService);
+        handler.init(filterConfig);
+
+        final HttpServletRequest request = 
EasyMock.createNiceMock(HttpServletRequest.class);
+        EasyMock.expect(request.getRequestURL()).andReturn(new 
StringBuffer(SERVICE_URL)).anyTimes();
+        
EasyMock.expect(request.getHeader("Authorization")).andReturn(null).anyTimes();
+        
EasyMock.expect(request.getParameter(JWTFederationFilter.GRANT_TYPE)).andReturn(JWTFederationFilter.TOKEN_EXCHANGE).anyTimes();
+        
EasyMock.expect(request.getParameter(JWTFederationFilter.SUBJECT_TOKEN)).andReturn(passcodeToken).anyTimes();
+        EasyMock.expect(request.getQueryString()).andReturn(null).anyTimes();
+
+        final HttpServletResponse response = 
EasyMock.createNiceMock(HttpServletResponse.class);
+        EasyMock.replay(tokenStateService, tokenMetadata, request, response);
+
+        SignatureVerificationCache.getInstance(topologyName, 
filterConfig).recordSignatureVerification(passcode);
+
+        final TestFilterChain chain = new TestFilterChain();
+        handler.doFilter(request, response, chain);
+
+        EasyMock.verify(response);
+        Assert.assertTrue(chain.doFilterCalled);
+        Assert.assertNotNull(chain.subject);
+    }
 }

Reply via email to