KNOX-1052 - Add some tests for the Knox SSO Service
Project: http://git-wip-us.apache.org/repos/asf/knox/repo Commit: http://git-wip-us.apache.org/repos/asf/knox/commit/2666894b Tree: http://git-wip-us.apache.org/repos/asf/knox/tree/2666894b Diff: http://git-wip-us.apache.org/repos/asf/knox/diff/2666894b Branch: refs/heads/KNOX-998-Package_Restructuring Commit: 2666894bc84281ed78890110ab15b009fa5f2830 Parents: a5a8825 Author: Colm O hEigeartaigh <cohei...@apache.org> Authored: Wed Sep 20 11:09:54 2017 +0100 Committer: Colm O hEigeartaigh <cohei...@apache.org> Committed: Wed Sep 20 11:09:54 2017 +0100 ---------------------------------------------------------------------- gateway-service-knoxsso/pom.xml | 11 +- .../gateway/service/knoxsso/WebSSOResource.java | 20 +- .../service/knoxsso/WebSSOResourceTest.java | 304 ++++++++++++++++++- 3 files changed, 308 insertions(+), 27 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/knox/blob/2666894b/gateway-service-knoxsso/pom.xml ---------------------------------------------------------------------- diff --git a/gateway-service-knoxsso/pom.xml b/gateway-service-knoxsso/pom.xml index f5018a2..e6b6ca5 100644 --- a/gateway-service-knoxsso/pom.xml +++ b/gateway-service-knoxsso/pom.xml @@ -59,9 +59,10 @@ <artifactId>gateway-test-utils</artifactId> <scope>test</scope> </dependency> - <dependency> - <groupId>org.easymock</groupId> - <artifactId>easymock</artifactId> - <scope>test</scope> - </dependency> </dependencies> + <dependency> + <groupId>org.easymock</groupId> + <artifactId>easymock</artifactId> + <scope>test</scope> + </dependency> + </dependencies> </project> http://git-wip-us.apache.org/repos/asf/knox/blob/2666894b/gateway-service-knoxsso/src/main/java/org/apache/hadoop/gateway/service/knoxsso/WebSSOResource.java ---------------------------------------------------------------------- diff --git a/gateway-service-knoxsso/src/main/java/org/apache/hadoop/gateway/service/knoxsso/WebSSOResource.java b/gateway-service-knoxsso/src/main/java/org/apache/hadoop/gateway/service/knoxsso/WebSSOResource.java index 7cc5378..0d9e6dd 100644 --- a/gateway-service-knoxsso/src/main/java/org/apache/hadoop/gateway/service/knoxsso/WebSSOResource.java +++ b/gateway-service-knoxsso/src/main/java/org/apache/hadoop/gateway/service/knoxsso/WebSSOResource.java @@ -23,6 +23,7 @@ import java.net.URISyntaxException; import java.security.Principal; import java.util.ArrayList; import java.util.Arrays; +import java.util.List; import java.util.Map; import java.util.Map.Entry; @@ -74,14 +75,14 @@ public class WebSSOResource { private long tokenTTL = 30000l; private String whitelist = null; private String domainSuffix = null; - private String[] targetAudiences = null; + private List<String> targetAudiences = new ArrayList<>(); private boolean enableSession = false; @Context - private HttpServletRequest request; + HttpServletRequest request; @Context - private HttpServletResponse response; + HttpServletResponse response; @Context ServletContext context; @@ -124,7 +125,10 @@ public class WebSSOResource { String audiences = context.getInitParameter(SSO_COOKIE_TOKEN_AUDIENCES_PARAM); if (audiences != null) { - targetAudiences = audiences.split(","); + String[] auds = audiences.split(","); + for (int i = 0; i < auds.length; i++) { + targetAudiences.add(auds[i]); + } } String ttl = context.getInitParameter(SSO_COOKIE_TOKEN_TTL_PARAM); @@ -180,14 +184,10 @@ public class WebSSOResource { try { JWT token = null; - if (targetAudiences == null || targetAudiences.length == 0) { + if (targetAudiences.isEmpty()) { token = ts.issueToken(p, "RS256", getExpiry()); } else { - ArrayList<String> aud = new ArrayList<String>(); - for (int i = 0; i < targetAudiences.length; i++) { - aud.add(targetAudiences[i]); - } - token = ts.issueToken(p, aud, "RS256", getExpiry()); + token = ts.issueToken(p, targetAudiences, "RS256", getExpiry()); } // Coverity CID 1327959 http://git-wip-us.apache.org/repos/asf/knox/blob/2666894b/gateway-service-knoxsso/src/test/java/org/apache/hadoop/gateway/service/knoxsso/WebSSOResourceTest.java ---------------------------------------------------------------------- diff --git a/gateway-service-knoxsso/src/test/java/org/apache/hadoop/gateway/service/knoxsso/WebSSOResourceTest.java b/gateway-service-knoxsso/src/test/java/org/apache/hadoop/gateway/service/knoxsso/WebSSOResourceTest.java index 73910dd..c953c91 100644 --- a/gateway-service-knoxsso/src/test/java/org/apache/hadoop/gateway/service/knoxsso/WebSSOResourceTest.java +++ b/gateway-service-knoxsso/src/test/java/org/apache/hadoop/gateway/service/knoxsso/WebSSOResourceTest.java @@ -17,15 +17,65 @@ */ package org.apache.hadoop.gateway.service.knoxsso; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +import java.security.KeyPair; +import java.security.KeyPairGenerator; +import java.security.NoSuchAlgorithmException; +import java.security.Principal; +import java.security.interfaces.RSAPrivateKey; +import java.security.interfaces.RSAPublicKey; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import javax.security.auth.Subject; +import javax.servlet.ServletContext; +import javax.servlet.ServletOutputStream; +import javax.servlet.http.Cookie; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpServletResponseWrapper; + +import org.apache.hadoop.gateway.services.GatewayServices; +import org.apache.hadoop.gateway.services.security.token.JWTokenAuthority; +import org.apache.hadoop.gateway.services.security.token.TokenServiceException; +import org.apache.hadoop.gateway.services.security.token.impl.JWT; +import org.apache.hadoop.gateway.services.security.token.impl.JWTToken; import org.apache.hadoop.gateway.util.RegExUtils; +import org.easymock.EasyMock; import org.junit.Assert; +import org.junit.BeforeClass; import org.junit.Test; +import com.nimbusds.jose.JWSSigner; +import com.nimbusds.jose.JWSVerifier; +import com.nimbusds.jose.crypto.RSASSASigner; +import com.nimbusds.jose.crypto.RSASSAVerifier; + /** - * + * Some tests for the Knox SSO service. */ public class WebSSOResourceTest { + protected static RSAPublicKey publicKey; + protected static RSAPrivateKey privateKey; + + @BeforeClass + public static void setup() throws Exception, NoSuchAlgorithmException { + KeyPairGenerator kpg = KeyPairGenerator.getInstance("RSA"); + kpg.initialize(1024); + KeyPair KPair = kpg.generateKeyPair(); + + publicKey = (RSAPublicKey) KPair.getPublic(); + privateKey = (RSAPrivateKey) KPair.getPrivate(); + } + @Test public void testWhitelistMatching() throws Exception { String whitelist = "^https?://.*example.com:8080/.*$;" + @@ -35,37 +85,267 @@ public class WebSSOResourceTest { "^https?://localhost:\\d{0,9}/.*$;^/.*$"; // match on explicit hostname/domain and port - Assert.assertTrue("Failed to match whitelist", RegExUtils.checkWhitelist(whitelist, + Assert.assertTrue("Failed to match whitelist", RegExUtils.checkWhitelist(whitelist, "http://host.example.com:8080/")); // match on non-required port - Assert.assertTrue("Failed to match whitelist", RegExUtils.checkWhitelist(whitelist, + Assert.assertTrue("Failed to match whitelist", RegExUtils.checkWhitelist(whitelist, "http://host.example.com/")); // match on required but any port - Assert.assertTrue("Failed to match whitelist", RegExUtils.checkWhitelist(whitelist, + Assert.assertTrue("Failed to match whitelist", RegExUtils.checkWhitelist(whitelist, "http://host.example2.com:1234/")); // fail on missing port - Assert.assertFalse("Matched whitelist inappropriately", RegExUtils.checkWhitelist(whitelist, + Assert.assertFalse("Matched whitelist inappropriately", RegExUtils.checkWhitelist(whitelist, "http://host.example2.com/")); // fail on invalid port - Assert.assertFalse("Matched whitelist inappropriately", RegExUtils.checkWhitelist(whitelist, + Assert.assertFalse("Matched whitelist inappropriately", RegExUtils.checkWhitelist(whitelist, "http://host.example.com:8081/")); // fail on alphanumeric port - Assert.assertFalse("Matched whitelist inappropriately", RegExUtils.checkWhitelist(whitelist, + Assert.assertFalse("Matched whitelist inappropriately", RegExUtils.checkWhitelist(whitelist, "http://host.example.com:A080/")); // fail on invalid hostname/domain - Assert.assertFalse("Matched whitelist inappropriately", RegExUtils.checkWhitelist(whitelist, + Assert.assertFalse("Matched whitelist inappropriately", RegExUtils.checkWhitelist(whitelist, "http://host.example.net:8080/")); // fail on required port - Assert.assertFalse("Matched whitelist inappropriately", RegExUtils.checkWhitelist(whitelist, + Assert.assertFalse("Matched whitelist inappropriately", RegExUtils.checkWhitelist(whitelist, "http://host.example2.com/")); // fail on required https - Assert.assertFalse("Matched whitelist inappropriately", RegExUtils.checkWhitelist(whitelist, + Assert.assertFalse("Matched whitelist inappropriately", RegExUtils.checkWhitelist(whitelist, "http://host.example3.com/")); // match on localhost and port - Assert.assertTrue("Failed to match whitelist", RegExUtils.checkWhitelist(whitelist, + Assert.assertTrue("Failed to match whitelist", RegExUtils.checkWhitelist(whitelist, "http://localhost:8080/")); // match on local/relative path - Assert.assertTrue("Failed to match whitelist", RegExUtils.checkWhitelist(whitelist, + Assert.assertTrue("Failed to match whitelist", RegExUtils.checkWhitelist(whitelist, "/local/resource/")); } + + @Test + public void testGetToken() throws Exception { + + ServletContext context = EasyMock.createNiceMock(ServletContext.class); + EasyMock.expect(context.getInitParameter("knoxsso.cookie.name")).andReturn(null); + EasyMock.expect(context.getInitParameter("knoxsso.cookie.secure.only")).andReturn(null); + EasyMock.expect(context.getInitParameter("knoxsso.cookie.max.age")).andReturn(null); + EasyMock.expect(context.getInitParameter("knoxsso.cookie.domain.suffix")).andReturn(null); + EasyMock.expect(context.getInitParameter("knoxsso.redirect.whitelist.regex")).andReturn(null); + EasyMock.expect(context.getInitParameter("knoxsso.token.audiences")).andReturn(null); + EasyMock.expect(context.getInitParameter("knoxsso.token.ttl")).andReturn(null); + EasyMock.expect(context.getInitParameter("knoxsso.enable.session")).andReturn(null); + + HttpServletRequest request = EasyMock.createNiceMock(HttpServletRequest.class); + EasyMock.expect(request.getParameter("originalUrl")).andReturn("http://localhost:9080/service"); + EasyMock.expect(request.getParameterMap()).andReturn(Collections.<String,String[]>emptyMap()); + EasyMock.expect(request.getServletContext()).andReturn(context).anyTimes(); + + Principal principal = EasyMock.createNiceMock(Principal.class); + EasyMock.expect(principal.getName()).andReturn("alice").anyTimes(); + EasyMock.expect(request.getUserPrincipal()).andReturn(principal).anyTimes(); + + GatewayServices services = EasyMock.createNiceMock(GatewayServices.class); + EasyMock.expect(context.getAttribute(GatewayServices.GATEWAY_SERVICES_ATTRIBUTE)).andReturn(services); + + JWTokenAuthority authority = new TestJWTokenAuthority(publicKey, privateKey); + EasyMock.expect(services.getService(GatewayServices.TOKEN_SERVICE)).andReturn(authority); + + HttpServletResponse response = EasyMock.createNiceMock(HttpServletResponse.class); + ServletOutputStream outputStream = EasyMock.createNiceMock(ServletOutputStream.class); + CookieResponseWrapper responseWrapper = new CookieResponseWrapper(response, outputStream); + + EasyMock.replay(principal, services, context, request); + + WebSSOResource webSSOResponse = new WebSSOResource(); + webSSOResponse.request = request; + webSSOResponse.response = responseWrapper; + webSSOResponse.context = context; + webSSOResponse.init(); + + // Issue a token + webSSOResponse.doGet(); + + // Check the cookie + Cookie cookie = responseWrapper.getCookie("hadoop-jwt"); + assertNotNull(cookie); + + JWTToken parsedToken = new JWTToken(cookie.getValue()); + assertEquals("alice", parsedToken.getSubject()); + assertTrue(authority.verifyToken(parsedToken)); + } + + @Test + public void testAudiences() throws Exception { + + ServletContext context = EasyMock.createNiceMock(ServletContext.class); + EasyMock.expect(context.getInitParameter("knoxsso.cookie.name")).andReturn(null); + EasyMock.expect(context.getInitParameter("knoxsso.cookie.secure.only")).andReturn(null); + EasyMock.expect(context.getInitParameter("knoxsso.cookie.max.age")).andReturn(null); + EasyMock.expect(context.getInitParameter("knoxsso.cookie.domain.suffix")).andReturn(null); + EasyMock.expect(context.getInitParameter("knoxsso.redirect.whitelist.regex")).andReturn(null); + EasyMock.expect(context.getInitParameter("knoxsso.token.audiences")).andReturn("recipient1,recipient2"); + EasyMock.expect(context.getInitParameter("knoxsso.token.ttl")).andReturn(null); + EasyMock.expect(context.getInitParameter("knoxsso.enable.session")).andReturn(null); + + HttpServletRequest request = EasyMock.createNiceMock(HttpServletRequest.class); + EasyMock.expect(request.getParameter("originalUrl")).andReturn("http://localhost:9080/service"); + EasyMock.expect(request.getParameterMap()).andReturn(Collections.<String,String[]>emptyMap()); + EasyMock.expect(request.getServletContext()).andReturn(context).anyTimes(); + + Principal principal = EasyMock.createNiceMock(Principal.class); + EasyMock.expect(principal.getName()).andReturn("alice").anyTimes(); + EasyMock.expect(request.getUserPrincipal()).andReturn(principal).anyTimes(); + + GatewayServices services = EasyMock.createNiceMock(GatewayServices.class); + EasyMock.expect(context.getAttribute(GatewayServices.GATEWAY_SERVICES_ATTRIBUTE)).andReturn(services); + + JWTokenAuthority authority = new TestJWTokenAuthority(publicKey, privateKey); + EasyMock.expect(services.getService(GatewayServices.TOKEN_SERVICE)).andReturn(authority); + + HttpServletResponse response = EasyMock.createNiceMock(HttpServletResponse.class); + ServletOutputStream outputStream = EasyMock.createNiceMock(ServletOutputStream.class); + CookieResponseWrapper responseWrapper = new CookieResponseWrapper(response, outputStream); + + EasyMock.replay(principal, services, context, request); + + WebSSOResource webSSOResponse = new WebSSOResource(); + webSSOResponse.request = request; + webSSOResponse.response = responseWrapper; + webSSOResponse.context = context; + webSSOResponse.init(); + + // Issue a token + webSSOResponse.doGet(); + + // Check the cookie + Cookie cookie = responseWrapper.getCookie("hadoop-jwt"); + assertNotNull(cookie); + + JWTToken parsedToken = new JWTToken(cookie.getValue()); + assertEquals("alice", parsedToken.getSubject()); + assertTrue(authority.verifyToken(parsedToken)); + + // Verify the audiences + List<String> audiences = Arrays.asList(parsedToken.getAudienceClaims()); + assertEquals(2, audiences.size()); + assertTrue(audiences.contains("recipient1")); + assertTrue(audiences.contains("recipient2")); + } + + /** + * A wrapper for HttpServletResponseWrapper to store the cookies + */ + private static class CookieResponseWrapper extends HttpServletResponseWrapper { + + private ServletOutputStream outputStream; + private Map<String, Cookie> cookies = new HashMap<>(); + + public CookieResponseWrapper(HttpServletResponse response) { + super(response); + } + + public CookieResponseWrapper(HttpServletResponse response, ServletOutputStream outputStream) { + super(response); + this.outputStream = outputStream; + } + + @Override + public ServletOutputStream getOutputStream() { + return outputStream; + } + + @Override + public void addCookie(Cookie cookie) { + super.addCookie(cookie); + cookies.put(cookie.getName(), cookie); + } + + public Cookie getCookie(String name) { + return cookies.get(name); + } + + } + + private static class TestJWTokenAuthority implements JWTokenAuthority { + + private RSAPublicKey publicKey; + private RSAPrivateKey privateKey; + + public TestJWTokenAuthority(RSAPublicKey publicKey, RSAPrivateKey privateKey) { + this.publicKey = publicKey; + this.privateKey = privateKey; + } + + @Override + public JWTToken issueToken(Subject subject, String algorithm) + throws TokenServiceException { + Principal p = (Principal) subject.getPrincipals().toArray()[0]; + return issueToken(p, algorithm); + } + + @Override + public JWTToken issueToken(Principal p, String algorithm) + throws TokenServiceException { + return issueToken(p, null, algorithm); + } + + @Override + public JWTToken issueToken(Principal p, String audience, String algorithm) + throws TokenServiceException { + return issueToken(p, audience, algorithm, -1); + } + + @Override + public boolean verifyToken(JWTToken token) throws TokenServiceException { + JWSVerifier verifier = new RSASSAVerifier(publicKey); + return token.verify(verifier); + } + + @Override + public JWTToken issueToken(Principal p, String audience, String algorithm, + long expires) throws TokenServiceException { + List<String> audiences = null; + if (audience != null) { + audiences = new ArrayList<String>(); + audiences.add(audience); + } + return issueToken(p, audiences, algorithm, expires); + } + + @Override + public JWTToken issueToken(Principal p, List<String> audiences, String algorithm, + long expires) throws TokenServiceException { + String[] claimArray = new String[4]; + claimArray[0] = "KNOXSSO"; + claimArray[1] = p.getName(); + claimArray[2] = null; + if (expires == -1) { + claimArray[3] = null; + } else { + claimArray[3] = String.valueOf(expires); + } + + JWTToken token = null; + if ("RS256".equals(algorithm)) { + token = new JWTToken("RS256", claimArray, audiences); + JWSSigner signer = new RSASSASigner(privateKey); + token.sign(signer); + } else { + throw new TokenServiceException("Cannot issue token - Unsupported algorithm"); + } + + return token; + } + + @Override + public JWT issueToken(Principal p, String algorithm, long expiry) + throws TokenServiceException { + return issueToken(p, Collections.<String>emptyList(), algorithm, expiry); + } + + @Override + public boolean verifyToken(JWTToken token, RSAPublicKey publicKey) throws TokenServiceException { + JWSVerifier verifier = new RSASSAVerifier(publicKey); + return token.verify(verifier); + } + + } + }