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

pzampino 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 0068c63b7 KNOX-3179:  Moved client id validation into client secret 
parse, Modify getParameter in UrlEncodedFormRequest (#1074)
0068c63b7 is described below

commit 0068c63b75fc688c80010deb6743a82b15a20fd2
Author: hanicz <han...@users.noreply.github.com>
AuthorDate: Tue Aug 12 16:20:48 2025 +0200

    KNOX-3179:  Moved client id validation into client secret parse, Modify 
getParameter in UrlEncodedFormRequest (#1074)
    
    * KNOX-3179: Moved client id validation into client secret parse, Modify 
getParameter in UrlEncodedFormRequest
    
    * KNOX-3179: New test for UrlEncodedFormRequest getParameter
    
    * KNOX-3179: Use existing mismatch message
---
 .../federation/jwt/filter/JWTFederationFilter.java | 53 ++++++++++------------
 ...lientIdAndClientSecretFederationFilterTest.java | 35 ++++++++++++--
 .../apache/knox/gateway/UrlEncodedFormRequest.java |  4 ++
 .../knox/gateway/UrlEncodedFormRequestTest.java    |  8 ++--
 4 files changed, 63 insertions(+), 37 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 f13b552a4..6e292370d 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
@@ -235,20 +235,16 @@ public class JWTFederationFilter extends 
AbstractJWTFilter {
         // The received token value must be a Base64 encoded value of 
Base64(tokenId)::Base64(rawPasscode)
         String tokenId = null;
         String passcode = null;
-        boolean prechecks = true;
         try {
           final String[] base64DecodedTokenIdAndPasscode = 
decodeBase64(tokenValue).split("::");
           tokenId = decodeBase64(base64DecodedTokenIdAndPasscode[0]);
           passcode = decodeBase64(base64DecodedTokenIdAndPasscode[1]);
-          // if this is a client credentials flow request then ensure the 
presented clientId is
-          // the actual owner of the client_secret
-          prechecks = validateClientCredentialsFlow((HttpServletRequest) 
request, (HttpServletResponse) response, tokenId);
         } catch (Exception e) {
           log.failedToParsePasscodeToken(e);
           handleValidationError((HttpServletRequest) request, 
(HttpServletResponse) response, HttpServletResponse.SC_UNAUTHORIZED,
               "Error while parsing the received passcode token");
         }
-        if (prechecks && validateToken((HttpServletRequest) request, 
(HttpServletResponse) response, chain, tokenId, passcode)) {
+        if (validateToken((HttpServletRequest) request, (HttpServletResponse) 
response, chain, tokenId, passcode)) {
           try {
             Subject subject = createSubjectFromTokenIdentifier(tokenId);
             continueWithEstablishedSecurityContext(subject, 
(HttpServletRequest) request, (HttpServletResponse) response, chain);
@@ -264,22 +260,19 @@ public class JWTFederationFilter extends 
AbstractJWTFilter {
     }
   }
 
-  private boolean validateClientCredentialsFlow(HttpServletRequest request, 
HttpServletResponse response, String tokenId)
-       throws IOException {
-    boolean validated = true;
-    final String grantType = request.getParameter(GRANT_TYPE);
-    if (grantType != null && !grantType.isEmpty()) {
-      final String clientID = request.getParameter(CLIENT_ID);
-      // if there is no client_id then this is not a client credentials flow
-      if (clientID != null && !tokenId.equals(clientID)) {
-        validated = false;
-        log.wrongPasscodeToken(tokenId);
-        handleValidationError(request, response,
-                HttpServletResponse.SC_UNAUTHORIZED,
-                MISMATCHING_CLIENT_ID_AND_CLIENT_SECRET);
-      }
+  private void validateClientID(HttpServletRequest request, String tokenValue) 
{
+    final String clientID = request.getParameter(CLIENT_ID);
+    String tokenId;
+    try {
+      final String[] base64DecodedTokenIdAndPasscode = 
decodeBase64(tokenValue).split("::");
+      tokenId = decodeBase64(base64DecodedTokenIdAndPasscode[0]);
+    } catch (Exception e) {
+      throw new SecurityException("Error while parsing the received client 
secret", e);
+    }
+    // if there is no client_id then this is not a client credentials flow
+    if (clientID != null && !tokenId.equals(clientID)) {
+     throw new SecurityException(MISMATCHING_CLIENT_ID_AND_CLIENT_SECRET);
     }
-    return validated;
   }
 
   private String decodeBase64(String toBeDecoded) {
@@ -344,15 +337,16 @@ public class JWTFederationFilter extends 
AbstractJWTFilter {
       return getClientCredentialsFromRequestBody(request);
     }
 
-    private Pair<TokenType, String> 
getClientCredentialsFromRequestBody(ServletRequest request) throws IOException {
-      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
-        final String clientSecret = request.getParameter( CLIENT_SECRET);
-        return Pair.of(TokenType.Passcode, clientSecret);
-      }
+    private Pair<TokenType, String> 
getClientCredentialsFromRequestBody(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
+          final String clientSecret = request.getParameter(CLIENT_SECRET);
+          validateClientID((HttpServletRequest) request, clientSecret);
+          return Pair.of(TokenType.Passcode, clientSecret);
+        }
       return null;
     }
 
@@ -450,5 +444,4 @@ public class JWTFederationFilter extends AbstractJWTFilter {
       super("None of the presented cookies are valid.");
     }
   }
-
 }
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/ClientIdAndClientSecretFederationFilterTest.java
index 004989c08..191f2c9a4 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/ClientIdAndClientSecretFederationFilterTest.java
@@ -58,10 +58,13 @@ public class ClientIdAndClientSecretFederationFilterTest 
extends TokenIDAsHTTPBa
 
     @Test
     public void testGetWireTokenUsingClientCredentialsFlow() throws Exception {
-      final String clientSecret = "sup3r5ecreT!";
+      final String clientId = "client-id-12345";
+      final String passcode = 
"WTJ4cFpXNTBMV2xrTFRFeU16UTE6OlkyeHBaVzUwTFhObFkzSmxkQzB4TWpNME5RPT0=";
+
       final HttpServletRequest request = 
EasyMock.createNiceMock(HttpServletRequest.class);
-      ensureClientCredentials(request, clientSecret);
+      ensureClientCredentials(request, passcode);
       
EasyMock.expect(request.getHeader("Authorization")).andReturn(null).anyTimes();
+      
EasyMock.expect(request.getParameter(JWTFederationFilter.CLIENT_ID)).andReturn(clientId).anyTimes();
       EasyMock.replay(request);
       handler.init(new TestFilterConfig(getProperties()));
       final Pair<TokenType, String> wireToken = ((TestJWTFederationFilter) 
handler).getWireToken(request);
@@ -70,7 +73,7 @@ public class ClientIdAndClientSecretFederationFilterTest 
extends TokenIDAsHTTPBa
 
       assertNotNull(wireToken);
       assertEquals(TokenType.Passcode, wireToken.getLeft());
-      assertEquals(clientSecret, wireToken.getRight());
+      assertEquals(passcode, wireToken.getRight());
     }
 
     @Test
@@ -117,7 +120,7 @@ public class ClientIdAndClientSecretFederationFilterTest 
extends TokenIDAsHTTPBa
         Assert.assertNotNull(chain.subject);
     }
 
-    @Test
+    @Test(expected = SecurityException.class)
     public void testFailedVerifyClientCredentialsFlow() throws Exception {
         final String topologyName = "jwt-topology";
         final String tokenId = "4e0c548b-6568-4061-a3dc-62908087650a";
@@ -173,6 +176,20 @@ public class ClientIdAndClientSecretFederationFilterTest 
extends TokenIDAsHTTPBa
       ((TestJWTFederationFilter) handler).getWireToken(request);
     }
 
+    @Test(expected = SecurityException.class)
+    public void testInvalidClientSecret() throws Exception {
+        final String passcode = "sUpers3cret!";
+
+        final HttpServletRequest request = 
EasyMock.createNiceMock(HttpServletRequest.class);
+        ensureClientCredentials(request, passcode);
+        
EasyMock.expect(request.getHeader("Authorization")).andReturn(null).anyTimes();
+        EasyMock.replay(request);
+        handler.init(new TestFilterConfig(getProperties()));
+        ((TestJWTFederationFilter) handler).getWireToken(request);
+
+        EasyMock.verify(request);
+    }
+
     @Override
     @Test
     public void testInvalidUsername() throws Exception {
@@ -192,4 +209,14 @@ public class ClientIdAndClientSecretFederationFilterTest 
extends TokenIDAsHTTPBa
         // set by the JWTProvider when determining that the request
         // is a client credentials flow.
     }
+
+    @Override
+    @Test
+    public void testInvalidPasscodeForJWT() throws Exception {
+    }
+
+    @Override
+    @Test
+    public void testUnableToParseJWT() throws Exception {
+    }
 }
diff --git 
a/gateway-server/src/main/java/org/apache/knox/gateway/UrlEncodedFormRequest.java
 
b/gateway-server/src/main/java/org/apache/knox/gateway/UrlEncodedFormRequest.java
index 2e2482aa1..88219d91c 100644
--- 
a/gateway-server/src/main/java/org/apache/knox/gateway/UrlEncodedFormRequest.java
+++ 
b/gateway-server/src/main/java/org/apache/knox/gateway/UrlEncodedFormRequest.java
@@ -26,6 +26,7 @@ import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletRequestWrapper;
 
 import org.apache.knox.gateway.i18n.messages.MessagesFactory;
+import 
org.apache.knox.gateway.provider.federation.jwt.filter.JWTFederationFilter;
 import org.eclipse.jetty.util.MultiMap;
 import org.eclipse.jetty.util.UrlEncoded;
 
@@ -77,6 +78,9 @@ public class UrlEncodedFormRequest extends 
HttpServletRequestWrapper {
 
   @Override
   public String getParameter(String name) {
+    if(JWTFederationFilter.GRANT_TYPE.equals(name) || 
JWTFederationFilter.CLIENT_ID.equals(name) || 
JWTFederationFilter.CLIENT_SECRET.equals(name)) {
+      return super.getParameter(name);
+    }
     return queryParams.getValue(name, 0);
   }
 
diff --git 
a/gateway-server/src/test/java/org/apache/knox/gateway/UrlEncodedFormRequestTest.java
 
b/gateway-server/src/test/java/org/apache/knox/gateway/UrlEncodedFormRequestTest.java
index 87a77a700..19f0b39b4 100644
--- 
a/gateway-server/src/test/java/org/apache/knox/gateway/UrlEncodedFormRequestTest.java
+++ 
b/gateway-server/src/test/java/org/apache/knox/gateway/UrlEncodedFormRequestTest.java
@@ -39,17 +39,19 @@ public class UrlEncodedFormRequestTest {
 
   @Test
   public void testParametersAreComingFromQueryStringOnly() throws Exception {
-    MockHttpServletRequest originalRequest = makeRequest("a=1&b=2", 
"query1=x&query2=y&query2=y2");
+    MockHttpServletRequest originalRequest = 
makeRequest("a=1&b=2&client_id=payload_client_id", 
"query1=x&query2=y&query2=y2&client_id=query_client_id");
     assertEquals("1", originalRequest.getParameter("a"));
     assertEquals("2", originalRequest.getParameter("b"));
+    assertEquals("payload_client_id", 
originalRequest.getParameter("client_id"));
     UrlEncodedFormRequest wrappedRequest = new 
UrlEncodedFormRequest(originalRequest);
     assertEquals("x", wrappedRequest.getParameter("query1"));
     assertEquals("y", wrappedRequest.getParameter("query2"));
+    assertEquals("payload_client_id", 
wrappedRequest.getParameter("client_id"));
     assertNull(wrappedRequest.getParameter("a"));
     assertNull(wrappedRequest.getParameter("b"));
     assertArrayEquals(new String[]{"x"}, 
wrappedRequest.getParameterValues("query1"));
     assertArrayEquals(new String[]{"y", "y2"}, 
wrappedRequest.getParameterValues("query2"));
-    assertEquals(Arrays.asList("query1", "query2"), 
Collections.list(wrappedRequest.getParameterNames()));
+    assertEquals(Arrays.asList("query1", "query2", "client_id"), 
Collections.list(wrappedRequest.getParameterNames()));
     assertArrayEquals(new String[]{"x"}, 
wrappedRequest.getParameterMap().get("query1"));
     assertArrayEquals(new String[]{"y", "y2"}, 
wrappedRequest.getParameterMap().get("query2"));
     assertNull(wrappedRequest.getParameterValues("unknown"));
@@ -81,4 +83,4 @@ public class UrlEncodedFormRequestTest {
             new ByteArrayInputStream(body.getBytes(StandardCharsets.UTF_8))));
     return request;
   }
-}
\ No newline at end of file
+}

Reply via email to