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 fb707882a KNOX-3312 - Client Credentials Flow with HTTP Basic needs 
Unwrapped S… (#1219)
fb707882a is described below

commit fb707882a29260a19a17f0cce6168e0ec3e318e8
Author: lmccay <[email protected]>
AuthorDate: Wed May 6 09:58:50 2026 -0400

    KNOX-3312 - Client Credentials Flow with HTTP Basic needs Unwrapped S… 
(#1219)
    
    KNOX-3312 - Client Credentials Flow with HTTP Basic needs Unwrapped Servlet 
Request
---
 .../federation/jwt/filter/JWTFederationFilter.java |  15 +-
 .../federation/OAuthFlowsFederationFilterTest.java | 159 +++++++++++++++------
 2 files changed, 127 insertions(+), 47 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 31b3df411..3979f0b02 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
@@ -361,12 +361,15 @@ public class JWTFederationFilter extends 
AbstractJWTFilter {
       String passcode = values[1].isEmpty() ? null : values[1];
       if (TOKEN.equalsIgnoreCase(username) || 
PASSCODE.equalsIgnoreCase(username)) {
           parsed = Pair.of(TOKEN.equalsIgnoreCase(username) ? TokenType.JWT : 
TokenType.Passcode, passcode);
-      } else if (request != null && 
CLIENT_CREDENTIALS.equals(request.getParameter(GRANT_TYPE))) {
-          // Allow client_credentials flow where client_id/client_secret are 
provided via HTTP Basic
-          if (passcode != null) {
-            validateClientID(username, passcode);
-            parsed = Pair.of(TokenType.Passcode, passcode);
-          }
+      } else if (request != null) {
+          HttpServletRequest unwrappedRequest = 
ServletRequestUtils.unwrapHttpServletRequest(request);
+          if 
(CLIENT_CREDENTIALS.equals(unwrappedRequest.getParameter(GRANT_TYPE))) {
+            // Allow client_credentials flow where client_id/client_secret are 
provided via HTTP Basic
+            if (passcode != null) {
+              validateClientID(username, passcode);
+              parsed = Pair.of(TokenType.Passcode, passcode);
+            }
+            }
       }
 
       return parsed;
diff --git 
a/gateway-provider-security-jwt/src/test/java/org/apache/knox/gateway/provider/federation/OAuthFlowsFederationFilterTest.java
 
b/gateway-provider-security-jwt/src/test/java/org/apache/knox/gateway/provider/federation/OAuthFlowsFederationFilterTest.java
index 6768dbb9b..515d173f3 100644
--- 
a/gateway-provider-security-jwt/src/test/java/org/apache/knox/gateway/provider/federation/OAuthFlowsFederationFilterTest.java
+++ 
b/gateway-provider-security-jwt/src/test/java/org/apache/knox/gateway/provider/federation/OAuthFlowsFederationFilterTest.java
@@ -47,9 +47,56 @@ import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
 
+import javax.servlet.http.HttpServletRequestWrapper;
 
 public class OAuthFlowsFederationFilterTest extends 
TokenIDAsHTTPBasicCredsFederationFilterTest {
 
+    /**
+     * Test wrapper that hides access to parameters and request body to 
simulate
+     * real-world servlet request wrappers that may be present in the filter 
chain.
+     * This ensures the filter properly unwraps requests to access OAuth 
parameters.
+     */
+    private static class TestServletRequestWrapper extends 
HttpServletRequestWrapper {
+        private final HttpServletRequest wrappedRequest;
+
+        TestServletRequestWrapper(HttpServletRequest request) {
+            super(request);
+            this.wrappedRequest = request;
+        }
+
+        @Override
+        public String getParameter(String name) {
+            // Intentionally hide parameters to simulate wrapped request 
behavior
+            return null;
+        }
+
+        @Override
+        public String getContentType() {
+            return wrappedRequest.getContentType();
+        }
+
+        @Override
+        public String getHeader(String name) {
+            return wrappedRequest.getHeader(name);
+        }
+
+        @Override
+        public String getQueryString() {
+            return wrappedRequest.getQueryString();
+        }
+
+        @Override
+        public StringBuffer getRequestURL() {
+            return wrappedRequest.getRequestURL();
+        }
+
+        // The underlying request is still accessible via getRequest()
+        @Override
+        public HttpServletRequest getRequest() {
+            return wrappedRequest;
+        }
+    }
+
     @Override
     protected void setTokenOnRequest(HttpServletRequest request, String 
authUsername, String authPassword) {
         
EasyMock.expect((Object)request.getHeader("Authorization")).andReturn("");
@@ -109,13 +156,17 @@ public class OAuthFlowsFederationFilterTest extends 
TokenIDAsHTTPBasicCredsFeder
     public void testGetWireTokenUsingClientCredentialsFlowWithoutClientId() 
throws Exception {
         final String passcode = 
"WTJ4cFpXNTBMV2xrTFRFeU16UTE6OlkyeHBaVzUwTFhObFkzSmxkQzB4TWpNME5RPT0=";
 
-        final HttpServletRequest request = 
EasyMock.createNiceMock(HttpServletRequest.class);
-        ensureClientCredentials(request, null, passcode);
-        
EasyMock.expect(request.getHeader("Authorization")).andReturn(null).anyTimes();
+        final HttpServletRequest mockRequest = 
EasyMock.createNiceMock(HttpServletRequest.class);
+        ensureClientCredentials(mockRequest, null, passcode);
+        
EasyMock.expect(mockRequest.getHeader("Authorization")).andReturn(null).anyTimes();
         final HttpServletResponse response = 
EasyMock.createNiceMock(HttpServletResponse.class);
         response.sendError(HttpServletResponse.SC_UNAUTHORIZED, 
JWTFederationFilter.MISMATCHING_CLIENT_ID_AND_CLIENT_SECRET);
         EasyMock.expectLastCall().atLeastOnce();
-        EasyMock.replay(request, response);
+        EasyMock.replay(mockRequest, response);
+
+        // Wrap the request to simulate real-world scenario where wrappers 
hide parameter access
+        final HttpServletRequest request = new 
TestServletRequestWrapper(mockRequest);
+
         handler.init(new TestFilterConfig(getProperties()));
         SecurityException securityException = null;
         try {
@@ -133,14 +184,18 @@ public class OAuthFlowsFederationFilterTest extends 
TokenIDAsHTTPBasicCredsFeder
       final String clientId = "client-id-12345";
       final String passcode = 
"WTJ4cFpXNTBMV2xrTFRFeU16UTE6OlkyeHBaVzUwTFhObFkzSmxkQzB4TWpNME5RPT0=";
 
-      final HttpServletRequest request = 
EasyMock.createNiceMock(HttpServletRequest.class);
-      ensureClientCredentials(request, clientId, passcode);
-      
EasyMock.expect(request.getHeader("Authorization")).andReturn(null).anyTimes();
-      EasyMock.replay(request);
+      final HttpServletRequest mockRequest = 
EasyMock.createNiceMock(HttpServletRequest.class);
+      ensureClientCredentials(mockRequest, clientId, passcode);
+      
EasyMock.expect(mockRequest.getHeader("Authorization")).andReturn(null).anyTimes();
+      EasyMock.replay(mockRequest);
+
+      // Wrap the request to simulate real-world scenario where wrappers hide 
parameter access
+      final HttpServletRequest request = new 
TestServletRequestWrapper(mockRequest);
+
       handler.init(new TestFilterConfig(getProperties()));
       final Pair<TokenType, String> wireToken = ((TestJWTFederationFilter) 
handler).getWireToken(request);
 
-      EasyMock.verify(request);
+      EasyMock.verify(mockRequest);
 
       assertNotNull(wireToken);
       assertEquals(TokenType.Passcode, wireToken.getLeft());
@@ -163,14 +218,17 @@ public class OAuthFlowsFederationFilterTest extends 
TokenIDAsHTTPBasicCredsFeder
          "sub": "system:serviceaccount:poc:webhdfs-sa"
         }
         */
-        final HttpServletRequest request = 
EasyMock.createNiceMock(HttpServletRequest.class);
-        ensureJWTClientCredentials(request, true);
-        EasyMock.replay(request);
+        final HttpServletRequest mockRequest = 
EasyMock.createNiceMock(HttpServletRequest.class);
+        ensureJWTClientCredentials(mockRequest, true);
+        EasyMock.replay(mockRequest);
+
+        // Wrap the request to simulate real-world scenario where wrappers 
hide parameter access
+        final HttpServletRequest request = new 
TestServletRequestWrapper(mockRequest);
 
         handler.init(new TestFilterConfig(getProperties()));
         final Pair<TokenType, String> wireToken = ((TestJWTFederationFilter) 
handler).getWireToken(request);
 
-        EasyMock.verify(request);
+        EasyMock.verify(mockRequest);
 
         assertNotNull(wireToken);
         assertEquals(TokenType.JWT, wireToken.getLeft());
@@ -185,15 +243,18 @@ public class OAuthFlowsFederationFilterTest extends 
TokenIDAsHTTPBasicCredsFeder
         final String basicCredentials = Base64.getEncoder()
                 .encodeToString((clientId + ":" + 
passcode).getBytes(StandardCharsets.UTF_8));
 
-        final HttpServletRequest request = 
EasyMock.createNiceMock(HttpServletRequest.class);
-        EasyMock.expect(request.getHeader("Authorization")).andReturn("Basic " 
+ basicCredentials).anyTimes();
-        
EasyMock.expect(request.getParameter(GRANT_TYPE)).andReturn(CLIENT_CREDENTIALS).anyTimes();
-        EasyMock.replay(request);
+        final HttpServletRequest mockRequest = 
EasyMock.createNiceMock(HttpServletRequest.class);
+        
EasyMock.expect(mockRequest.getHeader("Authorization")).andReturn("Basic " + 
basicCredentials).anyTimes();
+        
EasyMock.expect(mockRequest.getParameter(GRANT_TYPE)).andReturn(CLIENT_CREDENTIALS).anyTimes();
+        EasyMock.replay(mockRequest);
+
+        // Wrap the request to simulate real-world scenario where wrappers 
hide parameter access
+        final HttpServletRequest request = new 
TestServletRequestWrapper(mockRequest);
 
         handler.init(new TestFilterConfig(getProperties()));
         final Pair<TokenType, String> wireToken = ((TestJWTFederationFilter) 
handler).getWireToken(request);
 
-        EasyMock.verify(request);
+        EasyMock.verify(mockRequest);
 
         assertNotNull(wireToken);
         assertEquals(TokenType.Passcode, wireToken.getLeft());
@@ -210,9 +271,12 @@ public class OAuthFlowsFederationFilterTest extends 
TokenIDAsHTTPBasicCredsFeder
         final Pair<TokenStateService, TokenMetadata> tokenServices = 
createMockTokenStateService(tokenId, passcodeToken);
         final FilterConfig filterConfig = initFilter(tokenServices.getLeft());
 
-        final HttpServletRequest request = 
createMockRequestForClientCredentials(tokenId);
+        final HttpServletRequest mockRequest = 
createMockRequestForClientCredentials(tokenId);
         final HttpServletResponse response = 
EasyMock.createNiceMock(HttpServletResponse.class);
-        EasyMock.replay(tokenServices.getLeft(), tokenServices.getRight(), 
request, response);
+        EasyMock.replay(tokenServices.getLeft(), tokenServices.getRight(), 
mockRequest, response);
+
+        // Wrap the request to simulate real-world scenario where wrappers 
hide parameter access
+        final HttpServletRequest request = new 
TestServletRequestWrapper(mockRequest);
 
         SignatureVerificationCache.getInstance(topologyName, 
filterConfig).recordSignatureVerification(passcode);
 
@@ -234,13 +298,16 @@ public class OAuthFlowsFederationFilterTest extends 
TokenIDAsHTTPBasicCredsFeder
         final Pair<TokenStateService, TokenMetadata> tokenServices = 
createMockTokenStateService(tokenId, passcodeToken);
         final FilterConfig filterConfig = initFilter(tokenServices.getLeft());
 
-        final HttpServletRequest request = 
createMockRequestForClientCredentials(tokenId + "invalidating_string");
+        final HttpServletRequest mockRequest = 
createMockRequestForClientCredentials(tokenId + "invalidating_string");
 
         final HttpServletResponse response = 
EasyMock.createNiceMock(HttpServletResponse.class);
         response.sendError(HttpServletResponse.SC_UNAUTHORIZED,
                 JWTFederationFilter.MISMATCHING_CLIENT_ID_AND_CLIENT_SECRET);
         EasyMock.expectLastCall().once();
-        EasyMock.replay(tokenServices.getLeft(), tokenServices.getRight(), 
request, response);
+        EasyMock.replay(tokenServices.getLeft(), tokenServices.getRight(), 
mockRequest, response);
+
+        // Wrap the request to simulate real-world scenario where wrappers 
hide parameter access
+        final HttpServletRequest request = new 
TestServletRequestWrapper(mockRequest);
 
         SignatureVerificationCache.getInstance(topologyName, 
filterConfig).recordSignatureVerification(passcode);
 
@@ -267,13 +334,17 @@ public class OAuthFlowsFederationFilterTest extends 
TokenIDAsHTTPBasicCredsFeder
     public void testInvalidClientSecret() throws Exception {
         final String passcode = "sUpers3cret!";
 
-        final HttpServletRequest request = 
EasyMock.createNiceMock(HttpServletRequest.class);
-        ensureClientCredentials(request, "client_123", passcode);
-        
EasyMock.expect(request.getHeader("Authorization")).andReturn(null).anyTimes();
+        final HttpServletRequest mockRequest = 
EasyMock.createNiceMock(HttpServletRequest.class);
+        ensureClientCredentials(mockRequest, "client_123", passcode);
+        
EasyMock.expect(mockRequest.getHeader("Authorization")).andReturn(null).anyTimes();
         final HttpServletResponse response = 
EasyMock.createNiceMock(HttpServletResponse.class);
         response.sendError(HttpServletResponse.SC_UNAUTHORIZED, 
JWTFederationFilter.INVALID_CLIENT_SECRET);
         EasyMock.expectLastCall().atLeastOnce();
-        EasyMock.replay(request, response);
+        EasyMock.replay(mockRequest, response);
+
+        // Wrap the request to simulate real-world scenario where wrappers 
hide parameter access
+        final HttpServletRequest request = new 
TestServletRequestWrapper(mockRequest);
+
         handler.init(new TestFilterConfig(getProperties()));
         SecurityException securityException = null;
         try {
@@ -283,7 +354,7 @@ public class OAuthFlowsFederationFilterTest extends 
TokenIDAsHTTPBasicCredsFeder
         }
         Assert.assertNotNull(securityException);
         Assert.assertEquals(JWTFederationFilter.INVALID_CLIENT_SECRET, 
securityException.getMessage());
-        EasyMock.verify(request, response);
+        EasyMock.verify(mockRequest, response);
     }
 
     @Override
@@ -373,17 +444,20 @@ public class OAuthFlowsFederationFilterTest extends 
TokenIDAsHTTPBasicCredsFeder
     }
 
     private void testGetWireTokenWithGrant(String grantType, String 
tokenParam, String tokenValue) throws Exception {
-          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(GRANT_TYPE)).andReturn(grantType).anyTimes();
-          
EasyMock.expect(request.getParameter(tokenParam)).andReturn(tokenValue).anyTimes();
-          EasyMock.replay(request);
+          final HttpServletRequest mockRequest = 
EasyMock.createNiceMock(HttpServletRequest.class);
+          
EasyMock.expect(mockRequest.getHeader("Authorization")).andReturn(null).anyTimes();
+          
EasyMock.expect(mockRequest.getQueryString()).andReturn(null).anyTimes();
+          
EasyMock.expect(mockRequest.getParameter(GRANT_TYPE)).andReturn(grantType).anyTimes();
+          
EasyMock.expect(mockRequest.getParameter(tokenParam)).andReturn(tokenValue).anyTimes();
+          EasyMock.replay(mockRequest);
+
+          // Wrap the request to simulate real-world scenario where wrappers 
hide parameter access
+          final HttpServletRequest request = new 
TestServletRequestWrapper(mockRequest);
 
           handler.init(new TestFilterConfig(getProperties()));
           final Pair<TokenType, String> wireToken = ((TestJWTFederationFilter) 
handler).getWireToken(request);
 
-          EasyMock.verify(request);
+          EasyMock.verify(mockRequest);
 
           assertNotNull(wireToken);
           assertEquals(TokenType.Passcode, wireToken.getLeft());
@@ -397,15 +471,18 @@ public class OAuthFlowsFederationFilterTest extends 
TokenIDAsHTTPBasicCredsFeder
 
         final FilterConfig filterConfig = initFilter(tokenStateService);
 
-        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(GRANT_TYPE)).andReturn(grantType).anyTimes();
-        
EasyMock.expect(request.getParameter(tokenParam)).andReturn(passcodeToken).anyTimes();
-        EasyMock.expect(request.getQueryString()).andReturn(null).anyTimes();
+        final HttpServletRequest mockRequest = 
EasyMock.createNiceMock(HttpServletRequest.class);
+        EasyMock.expect(mockRequest.getRequestURL()).andReturn(new 
StringBuffer(SERVICE_URL)).anyTimes();
+        
EasyMock.expect(mockRequest.getHeader("Authorization")).andReturn(null).anyTimes();
+        
EasyMock.expect(mockRequest.getParameter(GRANT_TYPE)).andReturn(grantType).anyTimes();
+        
EasyMock.expect(mockRequest.getParameter(tokenParam)).andReturn(passcodeToken).anyTimes();
+        
EasyMock.expect(mockRequest.getQueryString()).andReturn(null).anyTimes();
 
         final HttpServletResponse response = 
EasyMock.createNiceMock(HttpServletResponse.class);
-        EasyMock.replay(tokenStateService, tokenMetadata, request, response);
+        EasyMock.replay(tokenStateService, tokenMetadata, mockRequest, 
response);
+
+        // Wrap the request to simulate real-world scenario where wrappers 
hide parameter access
+        final HttpServletRequest request = new 
TestServletRequestWrapper(mockRequest);
 
         SignatureVerificationCache.getInstance("jwt-topology", 
filterConfig).recordSignatureVerification(passcode);
 

Reply via email to