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 10ac9f6b2 KNOX-3272: Improved OAuth flow checks and return 401 in case 
of missing client ID or invalid client secret (#1170)
10ac9f6b2 is described below

commit 10ac9f6b2728ee221efc30792915f61c9ea9e43e
Author: Sandor Molnar <[email protected]>
AuthorDate: Fri Mar 6 17:01:32 2026 +0100

    KNOX-3272: Improved OAuth flow checks and return 401 in case of missing 
client ID or invalid client secret (#1170)
---
 .../federation/jwt/filter/JWTFederationFilter.java |  7 ++-
 .../federation/OAuthFlowsFederationFilterTest.java | 67 +++++++++++++++++-----
 ...okenIDAsHTTPBasicCredsFederationFilterTest.java | 10 +++-
 3 files changed, 64 insertions(+), 20 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 5795d9a0e..f89f65566 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
@@ -58,6 +58,7 @@ public class JWTFederationFilter extends AbstractJWTFilter {
   public static final String CLIENT_CREDENTIALS = "client_credentials";
   public static final String CLIENT_SECRET = "client_secret";
   public static final String CLIENT_ID = "client_id";
+  public static final String INVALID_CLIENT_SECRET = "Error while parsing the 
received client secret";
   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";
@@ -171,7 +172,7 @@ public class JWTFederationFilter extends AbstractJWTFilter {
     try {
       wireToken = getWireToken(request);
     } catch (SecurityException e) {
-      handleValidationError((HttpServletRequest) request, 
(HttpServletResponse) response, HttpServletResponse.SC_BAD_REQUEST, null);
+      handleValidationError((HttpServletRequest) request, 
(HttpServletResponse) response, HttpServletResponse.SC_UNAUTHORIZED, 
e.getMessage());
       throw e;
     }
 
@@ -230,10 +231,10 @@ public class JWTFederationFilter extends 
AbstractJWTFilter {
       final String[] base64DecodedTokenIdAndPasscode = 
decodeBase64(tokenValue).split("::");
       tokenId = decodeBase64(base64DecodedTokenIdAndPasscode[0]);
     } catch (Exception e) {
-      throw new SecurityException("Error while parsing the received client 
secret", e);
+      throw new SecurityException(INVALID_CLIENT_SECRET, e);
     }
     // if there is no client_id then this is not a client credentials flow
-    if (clientID != null && !tokenId.equals(clientID)) {
+    if (!tokenId.equals(clientID)) {
      throw new SecurityException(MISMATCHING_CLIENT_ID_AND_CLIENT_SECRET);
     }
   }
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 6b72b9d9d..1df3b7543 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
@@ -17,6 +17,7 @@
 package org.apache.knox.gateway.provider.federation;
 
 
+import com.nimbusds.jwt.SignedJWT;
 import org.apache.commons.lang3.tuple.Pair;
 import 
org.apache.knox.gateway.provider.federation.jwt.filter.JWTFederationFilter;
 import 
org.apache.knox.gateway.provider.federation.jwt.filter.JWTFederationFilter.TokenType;
@@ -32,6 +33,7 @@ import javax.servlet.FilterConfig;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 import java.nio.charset.StandardCharsets;
+import java.text.ParseException;
 import java.util.Base64;
 import java.util.Properties;
 
@@ -40,34 +42,63 @@ import static org.junit.Assert.assertNotNull;
 
 
 public class OAuthFlowsFederationFilterTest extends 
TokenIDAsHTTPBasicCredsFederationFilterTest {
+
     @Override
     protected void setTokenOnRequest(HttpServletRequest request, String 
authUsername, String authPassword) {
         
EasyMock.expect((Object)request.getHeader("Authorization")).andReturn("");
         
EasyMock.expect((Object)request.getContentType()).andReturn("application/x-www-form-urlencoded").anyTimes();
-        ensureClientCredentials(request, authPassword, false);
+        ensureClientCredentials(request, authUsername, authPassword, false);
+    }
+
+    @Override
+    protected String getAuthUserName(final String authUserName, final 
SignedJWT jwt) throws ParseException {
+        return super.getTokenId(jwt);
     }
 
-    private void ensureClientCredentials(final HttpServletRequest request, 
final String clientSecret) {
-        ensureClientCredentials(request, clientSecret, true);
+    private void ensureClientCredentials(final HttpServletRequest request, 
final String clientId, final String clientSecret) {
+        ensureClientCredentials(request, clientId, clientSecret, true);
     }
 
-    private void ensureClientCredentials(final HttpServletRequest request, 
final String clientSecret, final boolean excludeQueryString) {
+    private void ensureClientCredentials(final HttpServletRequest request, 
final String clientId, final String clientSecret, final boolean 
excludeQueryString) {
         
EasyMock.expect(request.getParameter(JWTFederationFilter.GRANT_TYPE)).andReturn(JWTFederationFilter.CLIENT_CREDENTIALS).anyTimes();
+        
EasyMock.expect(request.getParameter(JWTFederationFilter.CLIENT_ID)).andReturn(clientId).anyTimes();
         
EasyMock.expect(request.getParameter(JWTFederationFilter.CLIENT_SECRET)).andReturn(clientSecret).anyTimes();
         if (excludeQueryString) {
             
EasyMock.expect(request.getQueryString()).andReturn(null).anyTimes();
         }
     }
 
+    @Test
+    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 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);
+        handler.init(new TestFilterConfig(getProperties()));
+        SecurityException securityException = null;
+        try {
+            handler.doFilter(request, response, new TestFilterChain());
+        } catch (final SecurityException e) {
+            securityException = e;
+        }
+        assertNotNull(securityException);
+        
assertEquals(JWTFederationFilter.MISMATCHING_CLIENT_ID_AND_CLIENT_SECRET, 
securityException.getMessage());
+        EasyMock.verify(response);
+    }
+
     @Test
     public void testGetWireTokenUsingClientCredentialsFlow() throws Exception {
       final String clientId = "client-id-12345";
       final String passcode = 
"WTJ4cFpXNTBMV2xrTFRFeU16UTE6OlkyeHBaVzUwTFhObFkzSmxkQzB4TWpNME5RPT0=";
 
       final HttpServletRequest request = 
EasyMock.createNiceMock(HttpServletRequest.class);
-      ensureClientCredentials(request, passcode);
+      ensureClientCredentials(request, clientId, 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);
@@ -164,18 +195,27 @@ public class OAuthFlowsFederationFilterTest extends 
TokenIDAsHTTPBasicCredsFeder
       ((TestJWTFederationFilter) handler).getWireToken(request);
     }
 
-    @Test(expected = SecurityException.class)
+    @Test
     public void testInvalidClientSecret() throws Exception {
         final String passcode = "sUpers3cret!";
 
         final HttpServletRequest request = 
EasyMock.createNiceMock(HttpServletRequest.class);
-        ensureClientCredentials(request, passcode);
+        ensureClientCredentials(request, "client_123", passcode);
         
EasyMock.expect(request.getHeader("Authorization")).andReturn(null).anyTimes();
-        EasyMock.replay(request);
+        final HttpServletResponse response = 
EasyMock.createNiceMock(HttpServletResponse.class);
+        response.sendError(HttpServletResponse.SC_UNAUTHORIZED, 
JWTFederationFilter.INVALID_CLIENT_SECRET);
+        EasyMock.expectLastCall().atLeastOnce();
+        EasyMock.replay(request, response);
         handler.init(new TestFilterConfig(getProperties()));
-        ((TestJWTFederationFilter) handler).getWireToken(request);
-
-        EasyMock.verify(request);
+        SecurityException securityException = null;
+        try {
+            handler.doFilter(request, response, new TestFilterChain());
+        } catch (SecurityException e) {
+            securityException = e;
+        }
+        Assert.assertNotNull(securityException);
+        Assert.assertEquals(JWTFederationFilter.INVALID_CLIENT_SECRET, 
securityException.getMessage());
+        EasyMock.verify(request, response);
     }
 
     @Override
@@ -260,8 +300,7 @@ public class OAuthFlowsFederationFilterTest extends 
TokenIDAsHTTPBasicCredsFeder
         final HttpServletRequest request = 
EasyMock.createNiceMock(HttpServletRequest.class);
         final String clientCredentials = 
"TkdVd1l6VTBPR0l0TmpVMk9DMDBNRFl4TFdFelpHTXROakk1TURnd09EYzJOVEJoOjpNREV6T0dGaFpXUXRZMkV5WVMwME4yWXhMVGhsWkRndFpUQmpNemszTlRrMlpqazE=";
         EasyMock.expect(request.getRequestURL()).andReturn(new 
StringBuffer(SERVICE_URL)).anyTimes();
-        ensureClientCredentials(request, clientCredentials);
-        
EasyMock.expect(request.getParameter("client_id")).andReturn(clientId).anyTimes();
+        ensureClientCredentials(request, clientId, clientCredentials);
         return request;
     }
 
diff --git 
a/gateway-provider-security-jwt/src/test/java/org/apache/knox/gateway/provider/federation/TokenIDAsHTTPBasicCredsFederationFilterTest.java
 
b/gateway-provider-security-jwt/src/test/java/org/apache/knox/gateway/provider/federation/TokenIDAsHTTPBasicCredsFederationFilterTest.java
index 17f6fd53b..b92158f89 100644
--- 
a/gateway-provider-security-jwt/src/test/java/org/apache/knox/gateway/provider/federation/TokenIDAsHTTPBasicCredsFederationFilterTest.java
+++ 
b/gateway-provider-security-jwt/src/test/java/org/apache/knox/gateway/provider/federation/TokenIDAsHTTPBasicCredsFederationFilterTest.java
@@ -79,7 +79,7 @@ public class TokenIDAsHTTPBasicCredsFederationFilterTest 
extends JWTAsHTTPBasicC
         final String subject = (String) 
jwt.getJWTClaimsSet().getClaim(JWTToken.SUBJECT);
         final String passcode = (String) 
jwt.getJWTClaimsSet().getClaims().get(PASSCODE_CLAIM);
         addTokenState(jwt, issueTime, subject, passcode);
-        setTokenOnRequest(request, TestJWTFederationFilter.PASSCODE, 
generatePasscodeField(getTokenId(jwt), passcode));
+        setTokenOnRequest(request, 
getAuthUserName(TestJWTFederationFilter.PASSCODE, jwt), 
generatePasscodeField(getTokenId(jwt), passcode));
       } catch(ParseException e) {
         Assert.fail(e.getMessage());
       }
@@ -106,18 +106,22 @@ public class TokenIDAsHTTPBasicCredsFederationFilterTest 
extends JWTAsHTTPBasicC
         final String subject = (String) 
jwt.getJWTClaimsSet().getClaim(JWTToken.SUBJECT);
         final String passcode = (String) 
jwt.getJWTClaimsSet().getClaims().get(PASSCODE_CLAIM);
         addTokenState(jwt, issueTime, subject, passcode);
-        setTokenOnRequest(request, authUsername, 
generatePasscodeField(getTokenId(jwt), passcode));
+        setTokenOnRequest(request, getAuthUserName(authUsername, jwt), 
generatePasscodeField(getTokenId(jwt), passcode));
       } catch(ParseException e) {
         Assert.fail(e.getMessage());
       }
     }
 
+    protected String getAuthUserName(final String authUserName, final 
SignedJWT jwt) throws ParseException {
+        return authUserName;
+    }
+
     @Override
     protected void setGarbledTokenOnRequest(final HttpServletRequest request, 
final SignedJWT jwt) {
         setTokenOnRequest(request, TestJWTFederationFilter.PASSCODE, "junk" + 
getTokenId(jwt));
     }
 
-    private String getTokenId(final SignedJWT jwt) {
+    protected String getTokenId(final SignedJWT jwt) {
         String tokenId = null;
         try {
              tokenId = (String) 
jwt.getJWTClaimsSet().getClaim(JWTToken.KNOX_ID_CLAIM);

Reply via email to