This is an automated email from the ASF dual-hosted git repository. krisden 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 b3923f2 KNOX-2070 - SSOCookieFederationFilter NPE (#170) b3923f2 is described below commit b3923f29ddfee3b10ed99097480aefacb3878f04 Author: Kevin Risden <risd...@users.noreply.github.com> AuthorDate: Tue Oct 29 12:33:39 2019 -0400 KNOX-2070 - SSOCookieFederationFilter NPE (#170) Signed-off-by: Kevin Risden <kris...@apache.org> --- gateway-provider-security-jwt/pom.xml | 5 + .../jwt/filter/SSOCookieFederationFilter.java | 28 +- .../provider/federation/AbstractJWTFilterTest.java | 292 +++++++++++++++++++-- .../provider/federation/SSOCookieProviderTest.java | 12 + 4 files changed, 305 insertions(+), 32 deletions(-) diff --git a/gateway-provider-security-jwt/pom.xml b/gateway-provider-security-jwt/pom.xml index c9ba969..7fa0ba5 100644 --- a/gateway-provider-security-jwt/pom.xml +++ b/gateway-provider-security-jwt/pom.xml @@ -76,5 +76,10 @@ <artifactId>gateway-test-utils</artifactId> <scope>test</scope> </dependency> + <dependency> + <groupId>org.slf4j</groupId> + <artifactId>slf4j-api</artifactId> + <scope>test</scope> + </dependency> </dependencies> </project> diff --git a/gateway-provider-security-jwt/src/main/java/org/apache/knox/gateway/provider/federation/jwt/filter/SSOCookieFederationFilter.java b/gateway-provider-security-jwt/src/main/java/org/apache/knox/gateway/provider/federation/jwt/filter/SSOCookieFederationFilter.java index d5235aa..cbdbbd1 100644 --- a/gateway-provider-security-jwt/src/main/java/org/apache/knox/gateway/provider/federation/jwt/filter/SSOCookieFederationFilter.java +++ b/gateway-provider-security-jwt/src/main/java/org/apache/knox/gateway/provider/federation/jwt/filter/SSOCookieFederationFilter.java @@ -39,8 +39,11 @@ import java.nio.charset.StandardCharsets; import java.text.ParseException; public class SSOCookieFederationFilter extends AbstractJWTFilter { + public static final String XHR_HEADER = "X-Requested-With"; + public static final String XHR_VALUE = "XMLHttpRequest"; + private static final String GATEWAY_PATH = "gateway.path"; -public static final String SSO_COOKIE_NAME = "sso.cookie.name"; + public static final String SSO_COOKIE_NAME = "sso.cookie.name"; public static final String SSO_EXPECTED_AUDIENCES = "sso.expected.audiences"; public static final String SSO_AUTHENTICATION_PROVIDER_URL = "sso.authentication.provider.url"; public static final String SSO_VERIFICATION_PEM = "sso.token.verification.pem"; @@ -50,13 +53,11 @@ public static final String SSO_COOKIE_NAME = "sso.cookie.name"; private static final String ORIGINAL_URL_QUERY_PARAM = "originalUrl="; private static final String DEFAULT_SSO_COOKIE_NAME = "hadoop-jwt"; - private static final String XHR_HEADER = "X-Requested-With"; - private static final String XHR_VALUE = "XMLHttpRequest"; private static JWTMessages log = MessagesFactory.get( JWTMessages.class ); private String cookieName; private String authenticationProviderUrl; -private String gatewayPath; + private String gatewayPath; @Override public void init( FilterConfig filterConfig ) throws ServletException { @@ -130,19 +131,20 @@ private String gatewayPath; } @Override - protected void handleValidationError(HttpServletRequest request, HttpServletResponse response, int status, - String error) throws IOException { - String loginURL = constructLoginURL(request); - + protected void handleValidationError(HttpServletRequest request, HttpServletResponse response, + int status, String error) throws IOException { /* We don't need redirect if this is a XHR request */ - if (request.getHeader(XHR_HEADER) != null && request.getHeader(XHR_HEADER) - .equalsIgnoreCase(XHR_VALUE)) { - final byte[] data = error.getBytes(StandardCharsets.UTF_8); + if (request.getHeader(XHR_HEADER) != null && + request.getHeader(XHR_HEADER).equalsIgnoreCase(XHR_VALUE)) { response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); response.setContentType(MimeTypes.Type.TEXT_PLAIN.toString()); - response.setContentLength(data.length); - response.getOutputStream().write(data); + if(error != null && !error.isEmpty()) { + final byte[] data = error.getBytes(StandardCharsets.UTF_8); + response.setContentLength(data.length); + response.getOutputStream().write(data); + } } else { + String loginURL = constructLoginURL(request); response.sendRedirect(loginURL); } diff --git a/gateway-provider-security-jwt/src/test/java/org/apache/knox/gateway/provider/federation/AbstractJWTFilterTest.java b/gateway-provider-security-jwt/src/test/java/org/apache/knox/gateway/provider/federation/AbstractJWTFilterTest.java index 562671e..e46d2b9 100644 --- a/gateway-provider-security-jwt/src/test/java/org/apache/knox/gateway/provider/federation/AbstractJWTFilterTest.java +++ b/gateway-provider-security-jwt/src/test/java/org/apache/knox/gateway/provider/federation/AbstractJWTFilterTest.java @@ -43,8 +43,10 @@ import javax.servlet.FilterChain; import javax.servlet.FilterConfig; import javax.servlet.ServletContext; import javax.servlet.ServletException; +import javax.servlet.ServletOutputStream; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; +import javax.servlet.WriteListener; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.net.InetAddress; @@ -127,7 +129,21 @@ public abstract class AbstractJWTFilterTest { HttpServletResponse response = EasyMock.createNiceMock(HttpServletResponse.class); EasyMock.expect(response.encodeRedirectURL(SERVICE_URL)).andReturn( SERVICE_URL); - EasyMock.replay(request); + EasyMock.expect(response.getOutputStream()).andAnswer(() -> new ServletOutputStream() { + @Override + public void write(int b) { + } + + @Override + public void setWriteListener(WriteListener arg0) { + } + + @Override + public boolean isReady() { + return false; + } + }).anyTimes(); + EasyMock.replay(request, response); TestFilterChain chain = new TestFilterChain(); handler.doFilter(request, response, chain); @@ -159,7 +175,21 @@ public abstract class AbstractJWTFilterTest { HttpServletResponse response = EasyMock.createNiceMock(HttpServletResponse.class); EasyMock.expect(response.encodeRedirectURL(SERVICE_URL)).andReturn( SERVICE_URL); - EasyMock.replay(request); + EasyMock.expect(response.getOutputStream()).andAnswer(() -> new ServletOutputStream() { + @Override + public void write(int b) { + } + + @Override + public void setWriteListener(WriteListener arg0) { + } + + @Override + public boolean isReady() { + return false; + } + }).anyTimes(); + EasyMock.replay(request, response); TestFilterChain chain = new TestFilterChain(); handler.doFilter(request, response, chain); @@ -193,7 +223,21 @@ public abstract class AbstractJWTFilterTest { HttpServletResponse response = EasyMock.createNiceMock(HttpServletResponse.class); EasyMock.expect(response.encodeRedirectURL(SERVICE_URL)).andReturn( SERVICE_URL); - EasyMock.replay(request); + EasyMock.expect(response.getOutputStream()).andAnswer(() -> new ServletOutputStream() { + @Override + public void write(int b) { + } + + @Override + public void setWriteListener(WriteListener arg0) { + } + + @Override + public boolean isReady() { + return false; + } + }).anyTimes(); + EasyMock.replay(request, response); TestFilterChain chain = new TestFilterChain(); handler.doFilter(request, response, chain); @@ -223,7 +267,21 @@ public abstract class AbstractJWTFilterTest { HttpServletResponse response = EasyMock.createNiceMock(HttpServletResponse.class); EasyMock.expect(response.encodeRedirectURL(SERVICE_URL)).andReturn( SERVICE_URL); - EasyMock.replay(request); + EasyMock.expect(response.getOutputStream()).andAnswer(() -> new ServletOutputStream() { + @Override + public void write(int b) { + } + + @Override + public void setWriteListener(WriteListener arg0) { + } + + @Override + public boolean isReady() { + return false; + } + }).anyTimes(); + EasyMock.replay(request, response); TestFilterChain chain = new TestFilterChain(); handler.doFilter(request, response, chain); @@ -255,7 +313,21 @@ public abstract class AbstractJWTFilterTest { HttpServletResponse response = EasyMock.createNiceMock(HttpServletResponse.class); EasyMock.expect(response.encodeRedirectURL(SERVICE_URL)).andReturn( SERVICE_URL); - EasyMock.replay(request); + EasyMock.expect(response.getOutputStream()).andAnswer(() -> new ServletOutputStream() { + @Override + public void write(int b) { + } + + @Override + public void setWriteListener(WriteListener arg0) { + } + + @Override + public boolean isReady() { + return false; + } + }).anyTimes(); + EasyMock.replay(request, response); TestFilterChain chain = new TestFilterChain(); handler.doFilter(request, response, chain); @@ -284,7 +356,21 @@ public abstract class AbstractJWTFilterTest { HttpServletResponse response = EasyMock.createNiceMock(HttpServletResponse.class); EasyMock.expect(response.encodeRedirectURL(SERVICE_URL)).andReturn( SERVICE_URL); - EasyMock.replay(request); + EasyMock.expect(response.getOutputStream()).andAnswer(() -> new ServletOutputStream() { + @Override + public void write(int b) { + } + + @Override + public void setWriteListener(WriteListener arg0) { + } + + @Override + public boolean isReady() { + return false; + } + }).anyTimes(); + EasyMock.replay(request, response); TestFilterChain chain = new TestFilterChain(); handler.doFilter(request, response, chain); @@ -316,7 +402,21 @@ public abstract class AbstractJWTFilterTest { HttpServletResponse response = EasyMock.createNiceMock(HttpServletResponse.class); EasyMock.expect(response.encodeRedirectURL(SERVICE_URL)).andReturn( SERVICE_URL); - EasyMock.replay(request); + EasyMock.expect(response.getOutputStream()).andAnswer(() -> new ServletOutputStream() { + @Override + public void write(int b) { + } + + @Override + public void setWriteListener(WriteListener arg0) { + } + + @Override + public boolean isReady() { + return false; + } + }).anyTimes(); + EasyMock.replay(request, response); TestFilterChain chain = new TestFilterChain(); handler.doFilter(request, response, chain); @@ -351,7 +451,21 @@ public abstract class AbstractJWTFilterTest { HttpServletResponse response = EasyMock.createNiceMock(HttpServletResponse.class); EasyMock.expect(response.encodeRedirectURL(SERVICE_URL)).andReturn( SERVICE_URL); - EasyMock.replay(request); + EasyMock.expect(response.getOutputStream()).andAnswer(() -> new ServletOutputStream() { + @Override + public void write(int b) { + } + + @Override + public void setWriteListener(WriteListener arg0) { + } + + @Override + public boolean isReady() { + return false; + } + }).anyTimes(); + EasyMock.replay(request, response); TestFilterChain chain = new TestFilterChain(); handler.doFilter(request, response, chain); @@ -371,7 +485,7 @@ public abstract class AbstractJWTFilterTest { handler.init(new TestFilterConfig(props)); SignedJWT jwt = getJWT(AbstractJWTFilter.JWT_DEFAULT_ISSUER, "alice", - new Date(new Date().getTime() - 1000), privateKey); + new Date(new Date().getTime() - 1000), privateKey); HttpServletRequest request = EasyMock.createNiceMock(HttpServletRequest.class); setTokenOnRequest(request, jwt); @@ -382,7 +496,21 @@ public abstract class AbstractJWTFilterTest { HttpServletResponse response = EasyMock.createNiceMock(HttpServletResponse.class); EasyMock.expect(response.encodeRedirectURL(SERVICE_URL)).andReturn( SERVICE_URL); - EasyMock.replay(request); + EasyMock.expect(response.getOutputStream()).andAnswer(() -> new ServletOutputStream() { + @Override + public void write(int b) { + } + + @Override + public void setWriteListener(WriteListener arg0) { + } + + @Override + public boolean isReady() { + return false; + } + }).anyTimes(); + EasyMock.replay(request, response); TestFilterChain chain = new TestFilterChain(); handler.doFilter(request, response, chain); @@ -410,7 +538,21 @@ public abstract class AbstractJWTFilterTest { HttpServletResponse response = EasyMock.createNiceMock(HttpServletResponse.class); EasyMock.expect(response.encodeRedirectURL(SERVICE_URL)).andReturn( SERVICE_URL).anyTimes(); - EasyMock.replay(request); + EasyMock.expect(response.getOutputStream()).andAnswer(() -> new ServletOutputStream() { + @Override + public void write(int b) { + } + + @Override + public void setWriteListener(WriteListener arg0) { + } + + @Override + public boolean isReady() { + return false; + } + }).anyTimes(); + EasyMock.replay(request, response); TestFilterChain chain = new TestFilterChain(); handler.doFilter(request, response, chain); @@ -441,7 +583,21 @@ public abstract class AbstractJWTFilterTest { HttpServletResponse response = EasyMock.createNiceMock(HttpServletResponse.class); EasyMock.expect(response.encodeRedirectURL(SERVICE_URL)).andReturn( SERVICE_URL).anyTimes(); - EasyMock.replay(request); + EasyMock.expect(response.getOutputStream()).andAnswer(() -> new ServletOutputStream() { + @Override + public void write(int b) { + } + + @Override + public void setWriteListener(WriteListener arg0) { + } + + @Override + public boolean isReady() { + return false; + } + }).anyTimes(); + EasyMock.replay(request, response); TestFilterChain chain = new TestFilterChain(); handler.doFilter(request, response, chain); @@ -476,7 +632,21 @@ public abstract class AbstractJWTFilterTest { HttpServletResponse response = EasyMock.createNiceMock(HttpServletResponse.class); EasyMock.expect(response.encodeRedirectURL(SERVICE_URL)).andReturn( SERVICE_URL).anyTimes(); - EasyMock.replay(request); + EasyMock.expect(response.getOutputStream()).andAnswer(() -> new ServletOutputStream() { + @Override + public void write(int b) { + } + + @Override + public void setWriteListener(WriteListener arg0) { + } + + @Override + public boolean isReady() { + return false; + } + }).anyTimes(); + EasyMock.replay(request, response); TestFilterChain chain = new TestFilterChain(); handler.doFilter(request, response, chain); @@ -517,7 +687,21 @@ public abstract class AbstractJWTFilterTest { EasyMock.expect(request.getQueryString()).andReturn(null); HttpServletResponse response = EasyMock.createNiceMock(HttpServletResponse.class); EasyMock.expect(response.encodeRedirectURL(SERVICE_URL)).andReturn(SERVICE_URL); - EasyMock.replay(request); + EasyMock.expect(response.getOutputStream()).andAnswer(() -> new ServletOutputStream() { + @Override + public void write(int b) { + } + + @Override + public void setWriteListener(WriteListener arg0) { + } + + @Override + public boolean isReady() { + return false; + } + }).anyTimes(); + EasyMock.replay(request, response); TestFilterChain chain = new TestFilterChain(); handler.doFilter(request, response, chain); @@ -545,7 +729,21 @@ public abstract class AbstractJWTFilterTest { HttpServletResponse response = EasyMock.createNiceMock(HttpServletResponse.class); EasyMock.expect(response.encodeRedirectURL(SERVICE_URL)).andReturn( SERVICE_URL); - EasyMock.replay(request); + EasyMock.expect(response.getOutputStream()).andAnswer(() -> new ServletOutputStream() { + @Override + public void write(int b) { + } + + @Override + public void setWriteListener(WriteListener arg0) { + } + + @Override + public boolean isReady() { + return false; + } + }).anyTimes(); + EasyMock.replay(request, response); TestFilterChain chain = new TestFilterChain(); handler.doFilter(request, response, chain); @@ -574,7 +772,21 @@ public abstract class AbstractJWTFilterTest { HttpServletResponse response = EasyMock.createNiceMock(HttpServletResponse.class); EasyMock.expect(response.encodeRedirectURL(SERVICE_URL)).andReturn( SERVICE_URL); - EasyMock.replay(request); + EasyMock.expect(response.getOutputStream()).andAnswer(() -> new ServletOutputStream() { + @Override + public void write(int b) { + } + + @Override + public void setWriteListener(WriteListener arg0) { + } + + @Override + public boolean isReady() { + return false; + } + }).anyTimes(); + EasyMock.replay(request, response); TestFilterChain chain = new TestFilterChain(); handler.doFilter(request, response, chain); @@ -606,7 +818,21 @@ public abstract class AbstractJWTFilterTest { HttpServletResponse response = EasyMock.createNiceMock(HttpServletResponse.class); EasyMock.expect(response.encodeRedirectURL(SERVICE_URL)).andReturn( SERVICE_URL); - EasyMock.replay(request); + EasyMock.expect(response.getOutputStream()).andAnswer(() -> new ServletOutputStream() { + @Override + public void write(int b) { + } + + @Override + public void setWriteListener(WriteListener arg0) { + } + + @Override + public boolean isReady() { + return false; + } + }).anyTimes(); + EasyMock.replay(request, response); TestFilterChain chain = new TestFilterChain(); handler.doFilter(request, response, chain); @@ -637,7 +863,21 @@ public abstract class AbstractJWTFilterTest { HttpServletResponse response = EasyMock.createNiceMock(HttpServletResponse.class); EasyMock.expect(response.encodeRedirectURL(SERVICE_URL)).andReturn( SERVICE_URL); - EasyMock.replay(request); + EasyMock.expect(response.getOutputStream()).andAnswer(() -> new ServletOutputStream() { + @Override + public void write(int b) { + } + + @Override + public void setWriteListener(WriteListener arg0) { + } + + @Override + public boolean isReady() { + return false; + } + }).anyTimes(); + EasyMock.replay(request, response); TestFilterChain chain = new TestFilterChain(); handler.doFilter(request, response, chain); @@ -668,7 +908,21 @@ public abstract class AbstractJWTFilterTest { HttpServletResponse response = EasyMock.createNiceMock(HttpServletResponse.class); EasyMock.expect(response.encodeRedirectURL(SERVICE_URL)).andReturn( SERVICE_URL); - EasyMock.replay(request); + EasyMock.expect(response.getOutputStream()).andAnswer(() -> new ServletOutputStream() { + @Override + public void write(int b) { + } + + @Override + public void setWriteListener(WriteListener arg0) { + } + + @Override + public boolean isReady() { + return false; + } + }).anyTimes(); + EasyMock.replay(request, response); TestFilterChain chain = new TestFilterChain(); handler.doFilter(request, response, chain); diff --git a/gateway-provider-security-jwt/src/test/java/org/apache/knox/gateway/provider/federation/SSOCookieProviderTest.java b/gateway-provider-security-jwt/src/test/java/org/apache/knox/gateway/provider/federation/SSOCookieProviderTest.java index 7fc28a6..4555461 100644 --- a/gateway-provider-security-jwt/src/test/java/org/apache/knox/gateway/provider/federation/SSOCookieProviderTest.java +++ b/gateway-provider-security-jwt/src/test/java/org/apache/knox/gateway/provider/federation/SSOCookieProviderTest.java @@ -17,12 +17,15 @@ */ package org.apache.knox.gateway.provider.federation; +import static org.apache.knox.gateway.provider.federation.jwt.filter.SSOCookieFederationFilter.XHR_HEADER; +import static org.apache.knox.gateway.provider.federation.jwt.filter.SSOCookieFederationFilter.XHR_VALUE; import static org.junit.Assert.fail; import java.security.Principal; import java.util.Properties; import java.util.Date; import java.util.Set; +import java.util.concurrent.ThreadLocalRandom; import javax.servlet.ServletException; import javax.servlet.http.Cookie; @@ -39,8 +42,12 @@ import org.junit.Before; import org.junit.Test; import com.nimbusds.jwt.SignedJWT; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; public class SSOCookieProviderTest extends AbstractJWTFilterTest { + private static Logger LOGGER = LoggerFactory.getLogger(SSOCookieProviderTest.class); + private static final String SERVICE_URL = "https://localhost:8888/resource"; @Before @@ -53,6 +60,11 @@ public class SSOCookieProviderTest extends AbstractJWTFilterTest { protected void setTokenOnRequest(HttpServletRequest request, SignedJWT jwt) { Cookie cookie = new Cookie("hadoop-jwt", jwt.serialize()); EasyMock.expect(request.getCookies()).andReturn(new Cookie[] { cookie }); + + if(ThreadLocalRandom.current().nextBoolean()) { + LOGGER.info("Using XHR header for request"); + EasyMock.expect(request.getHeader(XHR_HEADER)).andReturn(XHR_VALUE).anyTimes(); + } } @Override