This is an automated email from the ASF dual-hosted git repository. smolnar82 pushed a commit to branch knox_idf in repository https://gitbox.apache.org/repos/asf/knox.git
commit 598885ee53c99db242d7b6809be5a6c8670de658 Author: Sandor Molnar <[email protected]> AuthorDate: Wed Jun 10 11:26:30 2026 +0200 KNOX-3343: Fix NPE when cacheKeyHeader is not found in the request (#1254) --- .../test_remoteauth_extauthz_additional_path.py | 6 +-- .../knox/gateway/filter/RemoteAuthFilter.java | 2 +- .../knox/gateway/filter/RemoteAuthFilterTest.java | 51 +++++++++++++++++----- 3 files changed, 44 insertions(+), 15 deletions(-) diff --git a/.github/workflows/tests/test_remoteauth_extauthz_additional_path.py b/.github/workflows/tests/test_remoteauth_extauthz_additional_path.py index 73a6f578a..069dbc166 100644 --- a/.github/workflows/tests/test_remoteauth_extauthz_additional_path.py +++ b/.github/workflows/tests/test_remoteauth_extauthz_additional_path.py @@ -49,10 +49,10 @@ class TestRemoteAuthExtAuthzAdditionalPath(unittest.TestCase): ) self.assertEqual(response.status_code, 401) - def test_extauthz_missing_credentials_server_error(self): - # No Authorization header: RemoteAuth hits an error path (500), not 401. + def test_extauthz_missing_credentials(self): + # No Authorization header: 401 (no longer hit the NPE because of missing cache key from the header). response = knox_get(self.extauthz_url) - self.assertEqual(response.status_code, 500) + self.assertEqual(response.status_code, 401) if __name__ == "__main__": diff --git a/gateway-provider-security-authc-remote/src/main/java/org/apache/knox/gateway/filter/RemoteAuthFilter.java b/gateway-provider-security-authc-remote/src/main/java/org/apache/knox/gateway/filter/RemoteAuthFilter.java index 63ab7b189..3b9249525 100755 --- a/gateway-provider-security-authc-remote/src/main/java/org/apache/knox/gateway/filter/RemoteAuthFilter.java +++ b/gateway-provider-security-authc-remote/src/main/java/org/apache/knox/gateway/filter/RemoteAuthFilter.java @@ -361,7 +361,7 @@ public class RemoteAuthFilter implements Filter { // Add method to hash cache key private String hashCacheKey(String key) { - return String.valueOf(key.hashCode()); + return key == null ? "" : String.valueOf(key.hashCode()); } // Change to package-private for testing diff --git a/gateway-provider-security-authc-remote/src/test/java/org/apache/knox/gateway/filter/RemoteAuthFilterTest.java b/gateway-provider-security-authc-remote/src/test/java/org/apache/knox/gateway/filter/RemoteAuthFilterTest.java index 593948609..b426c0fdd 100644 --- a/gateway-provider-security-authc-remote/src/test/java/org/apache/knox/gateway/filter/RemoteAuthFilterTest.java +++ b/gateway-provider-security-authc-remote/src/test/java/org/apache/knox/gateway/filter/RemoteAuthFilterTest.java @@ -99,7 +99,7 @@ public class RemoteAuthFilterTest { gatewayServicesMock = EasyMock.createNiceMock(GatewayServices.class); } - private void setUp(String trustStorePath, String trustStorePass, String trustStoreType) throws Exception { + private void setUp(String trustStorePath, String trustStorePass, String trustStoreType, String cacheKeyHeader) throws Exception { // Reset ALL mocks EasyMock.reset(requestMock, responseMock, filterConfigMock, gatewayServicesMock, servletContextMock, keystoreServiceMock, aliasServiceMock); @@ -150,7 +150,7 @@ public class RemoteAuthFilterTest { .andReturn("https://example.com/auth") .anyTimes(); EasyMock.expect(filterConfigMock.getInitParameter(RemoteAuthFilter.CONFIG_INCLUDE_HEADERS)).andReturn("Authorization").anyTimes(); - EasyMock.expect(filterConfigMock.getInitParameter(RemoteAuthFilter.DEFAULT_CACHE_KEY_HEADER)).andReturn("Authorization").anyTimes(); + EasyMock.expect(filterConfigMock.getInitParameter(RemoteAuthFilter.CONFIG_CACHE_KEY_HEADER)).andReturn(cacheKeyHeader).anyTimes(); EasyMock.expect(filterConfigMock.getInitParameter(RemoteAuthFilter.CONFIG_EXPIRE_AFTER)).andReturn("5").anyTimes(); EasyMock.expect(filterConfigMock.getInitParameter(RemoteAuthFilter.CONFIG_USER_HEADER)).andReturn(X_AUTHENTICATED_USER).anyTimes(); EasyMock.expect(filterConfigMock.getInitParameter(RemoteAuthFilter.CONFIG_GROUP_HEADER)) @@ -177,12 +177,20 @@ public class RemoteAuthFilterTest { // Default setup method for backward compatibility private void setUp() throws Exception { - setUp(null, null, null); + setUp("Authorization"); + } + + private void setUp(String cacheKeyHeader) throws Exception { + setUp(null, null, null, cacheKeyHeader); } private void setupURLConnection(String url) { + setupURLConnection(url, null, null); + } + + private void setupURLConnection(String url, String user, String group) { try { - filter.httpURLConnection = new MockHttpURLConnection(new URL(url)); + filter.httpURLConnection = new MockHttpURLConnection(new URL(url), user, group); } catch (MalformedURLException e) { throw new RuntimeException(e); } @@ -254,7 +262,16 @@ public class RemoteAuthFilterTest { @Test public void testCacheBehavior() throws Exception { - setUp(); + testCacheBehavior(true); + } + + @Test + public void testCacheBehaviorNoCachedSubject() throws Exception { + testCacheBehavior(false); + } + + private void testCacheBehavior(boolean useCache) throws Exception { + setUp(useCache ? "Authorization" : "HeaderNeverSet"); String principalName = "lmccayiv"; String groupNames = "admin2,scientists"; @@ -262,9 +279,14 @@ public class RemoteAuthFilterTest { subject.getPrincipals().add(new PrimaryPrincipal(principalName)); Arrays.stream(groupNames.split(",")).forEach(groupName -> subject.getPrincipals() .add(new GroupPrincipal(groupName))); - filter.setCachedSubject(BEARER_VALID_TOKEN, subject); - + if (useCache) { + filter.setCachedSubject(BEARER_VALID_TOKEN, subject); + } else { + EasyMock.expect(requestMock.getHeader("HeaderNeverSet")).andReturn(null).anyTimes(); + } + // either cached or uncached, we try to get the value of the "Authorization" header at one point EasyMock.expect(requestMock.getHeader("Authorization")).andReturn(BEARER_VALID_TOKEN).anyTimes(); + EasyMock.expect(responseMock.getStatus()).andReturn(200).anyTimes(); responseMock.sendError(EasyMock.eq(HttpServletResponse.SC_UNAUTHORIZED), EasyMock.anyString()); EasyMock.expectLastCall().andThrow(new AssertionError("Authentication should be successful, but was not.")).anyTimes(); @@ -272,7 +294,7 @@ public class RemoteAuthFilterTest { EasyMock.replay(requestMock, responseMock); try { - setupURLConnection(URL_SUCCESS); + setupURLConnection(URL_SUCCESS, principalName, groupNames); filter.doFilter(requestMock, responseMock, chainMock); assertEquals(responseMock.getStatus(), HttpServletResponse.SC_OK); @@ -418,7 +440,7 @@ public class RemoteAuthFilterTest { EasyMock.replay(servletContextMock, gatewayServicesMock, keystoreServiceMock, aliasServiceMock); // Setup with valid trust store configuration - this will now trigger truststore loading - setUp("/path/to/truststore.jks", null, "JKS"); // null password to test alias resolution + setUp("/path/to/truststore.jks", null, "JKS", "Authorization"); // null password to test alias resolution // Regular request expectations EasyMock.expect(requestMock.getServletContext()) @@ -455,14 +477,21 @@ public class RemoteAuthFilterTest { private final Map<String, List<String>> headers; public MockHttpURLConnection(URL url) { + this(url, null, null); + } + + public MockHttpURLConnection(URL url, String user, String group) { super(url); this.url = url; this.responseCode = getCode(); this.headers = new HashMap<>(); + setupHeaders(url, user, group); + } + private void setupHeaders(URL url, String user, String group) { if (url.toString().equals(URL_SUCCESS)) { - addHeader(X_AUTHENTICATED_USER, "lmccay"); - addHeader(X_AUTHENTICATED_GROUP, "admin,engineers"); + addHeader(X_AUTHENTICATED_USER, user == null ? "lmccay" : user); + addHeader(X_AUTHENTICATED_GROUP, group == null ? "admin,engineers" : group); } }
