Repository: knox Updated Branches: refs/heads/master 4978951cc -> 3557612d5
KNOX-1047 - Add some tests for the Knox Token Service Project: http://git-wip-us.apache.org/repos/asf/knox/repo Commit: http://git-wip-us.apache.org/repos/asf/knox/commit/3557612d Tree: http://git-wip-us.apache.org/repos/asf/knox/tree/3557612d Diff: http://git-wip-us.apache.org/repos/asf/knox/diff/3557612d Branch: refs/heads/master Commit: 3557612d5a5d904c3dfa61ba03cccfbbd365b296 Parents: 4978951 Author: Colm O hEigeartaigh <cohei...@apache.org> Authored: Tue Sep 19 12:17:59 2017 +0100 Committer: Colm O hEigeartaigh <cohei...@apache.org> Committed: Tue Sep 19 12:17:59 2017 +0100 ---------------------------------------------------------------------- .../federation/AbstractJWTFilterTest.java | 14 +- .../federation/JWTFederationFilterTest.java | 1 - .../federation/SSOCookieProviderTest.java | 1 - gateway-service-knoxtoken/pom.xml | 29 ++- .../service/knoxtoken/TokenResource.java | 31 +-- .../knoxtoken/TokenServiceResourceTest.java | 256 ++++++++++++++++++- 6 files changed, 293 insertions(+), 39 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/knox/blob/3557612d/gateway-provider-security-jwt/src/test/java/org/apache/hadoop/gateway/provider/federation/AbstractJWTFilterTest.java ---------------------------------------------------------------------- diff --git a/gateway-provider-security-jwt/src/test/java/org/apache/hadoop/gateway/provider/federation/AbstractJWTFilterTest.java b/gateway-provider-security-jwt/src/test/java/org/apache/hadoop/gateway/provider/federation/AbstractJWTFilterTest.java index 6f221a9..d477f1f 100644 --- a/gateway-provider-security-jwt/src/test/java/org/apache/hadoop/gateway/provider/federation/AbstractJWTFilterTest.java +++ b/gateway-provider-security-jwt/src/test/java/org/apache/hadoop/gateway/provider/federation/AbstractJWTFilterTest.java @@ -60,7 +60,7 @@ import org.apache.hadoop.gateway.services.security.token.impl.JWTToken; import org.easymock.EasyMock; import org.junit.After; import org.junit.Assert; -import org.junit.Before; +import org.junit.BeforeClass; import org.junit.Test; import com.nimbusds.jose.*; @@ -74,16 +74,16 @@ public abstract class AbstractJWTFilterTest { private static final String dnTemplate = "CN={0},OU=Test,O=Hadoop,L=Test,ST=Test,C=US"; protected AbstractJWTFilter handler = null; - protected RSAPublicKey publicKey = null; - protected RSAPrivateKey privateKey = null; - protected String pem = null; + protected static RSAPublicKey publicKey = null; + protected static RSAPrivateKey privateKey = null; + protected static String pem = null; protected abstract void setTokenOnRequest(HttpServletRequest request, SignedJWT jwt); protected abstract void setGarbledTokenOnRequest(HttpServletRequest request, SignedJWT jwt); protected abstract String getAudienceProperty(); protected abstract String getVerificationPemProperty(); - private String buildDistinguishedName(String hostname) { + private static String buildDistinguishedName(String hostname) { MessageFormat headerFormatter = new MessageFormat(dnTemplate); String[] paramArray = new String[1]; paramArray[0] = hostname; @@ -91,8 +91,8 @@ public abstract class AbstractJWTFilterTest { return dn; } - @Before - public void setup() throws Exception, NoSuchAlgorithmException { + @BeforeClass + public static void generateKeys() throws Exception, NoSuchAlgorithmException { KeyPairGenerator kpg = KeyPairGenerator.getInstance("RSA"); kpg.initialize(2048); KeyPair KPair = kpg.generateKeyPair(); http://git-wip-us.apache.org/repos/asf/knox/blob/3557612d/gateway-provider-security-jwt/src/test/java/org/apache/hadoop/gateway/provider/federation/JWTFederationFilterTest.java ---------------------------------------------------------------------- diff --git a/gateway-provider-security-jwt/src/test/java/org/apache/hadoop/gateway/provider/federation/JWTFederationFilterTest.java b/gateway-provider-security-jwt/src/test/java/org/apache/hadoop/gateway/provider/federation/JWTFederationFilterTest.java index d19d999..99a3780 100644 --- a/gateway-provider-security-jwt/src/test/java/org/apache/hadoop/gateway/provider/federation/JWTFederationFilterTest.java +++ b/gateway-provider-security-jwt/src/test/java/org/apache/hadoop/gateway/provider/federation/JWTFederationFilterTest.java @@ -32,7 +32,6 @@ public class JWTFederationFilterTest extends AbstractJWTFilterTest { @Before public void setup() throws Exception, NoSuchAlgorithmException { - super.setup(); handler = new TestJWTFederationFilter(); ((TestJWTFederationFilter) handler).setTokenService(new TestJWTokenAuthority(publicKey)); } http://git-wip-us.apache.org/repos/asf/knox/blob/3557612d/gateway-provider-security-jwt/src/test/java/org/apache/hadoop/gateway/provider/federation/SSOCookieProviderTest.java ---------------------------------------------------------------------- diff --git a/gateway-provider-security-jwt/src/test/java/org/apache/hadoop/gateway/provider/federation/SSOCookieProviderTest.java b/gateway-provider-security-jwt/src/test/java/org/apache/hadoop/gateway/provider/federation/SSOCookieProviderTest.java index 85f7d59..768755b 100644 --- a/gateway-provider-security-jwt/src/test/java/org/apache/hadoop/gateway/provider/federation/SSOCookieProviderTest.java +++ b/gateway-provider-security-jwt/src/test/java/org/apache/hadoop/gateway/provider/federation/SSOCookieProviderTest.java @@ -45,7 +45,6 @@ public class SSOCookieProviderTest extends AbstractJWTFilterTest { @Before public void setup() throws Exception, NoSuchAlgorithmException { - super.setup(); handler = new TestSSOCookieFederationProvider(); ((TestSSOCookieFederationProvider) handler).setTokenService(new TestJWTokenAuthority(publicKey)); } http://git-wip-us.apache.org/repos/asf/knox/blob/3557612d/gateway-service-knoxtoken/pom.xml ---------------------------------------------------------------------- diff --git a/gateway-service-knoxtoken/pom.xml b/gateway-service-knoxtoken/pom.xml index 1dafa20..e27b7b0 100644 --- a/gateway-service-knoxtoken/pom.xml +++ b/gateway-service-knoxtoken/pom.xml @@ -56,19 +56,20 @@ <groupId>${gateway-group}</groupId> <artifactId>gateway-provider-jersey</artifactId> </dependency> - <dependency> - <groupId>junit</groupId> - <artifactId>junit</artifactId> - <scope>test</scope> - </dependency> - <dependency> - <groupId>org.apache.knox</groupId> - <artifactId>gateway-test-utils</artifactId> - <scope>test</scope> - </dependency> <dependency> - <groupId>org.easymock</groupId> - <artifactId>easymock</artifactId> - <scope>test</scope> - </dependency> </dependencies> + <groupId>junit</groupId> + <artifactId>junit</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.apache.knox</groupId> + <artifactId>gateway-test-utils</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.easymock</groupId> + <artifactId>easymock</artifactId> + <scope>test</scope> + </dependency> + </dependencies> </project> http://git-wip-us.apache.org/repos/asf/knox/blob/3557612d/gateway-service-knoxtoken/src/main/java/org/apache/hadoop/gateway/service/knoxtoken/TokenResource.java ---------------------------------------------------------------------- diff --git a/gateway-service-knoxtoken/src/main/java/org/apache/hadoop/gateway/service/knoxtoken/TokenResource.java b/gateway-service-knoxtoken/src/main/java/org/apache/hadoop/gateway/service/knoxtoken/TokenResource.java index 4fda69f..43dd526 100644 --- a/gateway-service-knoxtoken/src/main/java/org/apache/hadoop/gateway/service/knoxtoken/TokenResource.java +++ b/gateway-service-knoxtoken/src/main/java/org/apache/hadoop/gateway/service/knoxtoken/TokenResource.java @@ -22,6 +22,8 @@ import java.security.Principal; import java.util.ArrayList; import java.util.Map; import java.util.HashMap; +import java.util.List; + import javax.annotation.PostConstruct; import javax.servlet.ServletContext; import javax.servlet.http.HttpServletRequest; @@ -55,15 +57,15 @@ public class TokenResource { static final String RESOURCE_PATH = "knoxtoken/api/v1/token"; private static TokenServiceMessages log = MessagesFactory.get( TokenServiceMessages.class ); private long tokenTTL = 30000l; - private String[] targetAudiences = null; + private List<String> targetAudiences = new ArrayList<>(); private String tokenTargetUrl = null; private Map<String,Object> tokenClientDataMap = null; @Context - private HttpServletRequest request; + HttpServletRequest request; @Context - private HttpServletResponse response; + HttpServletResponse response; @Context ServletContext context; @@ -73,7 +75,10 @@ public class TokenResource { String audiences = context.getInitParameter(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(TOKEN_TTL_PARAM); @@ -85,7 +90,7 @@ public class TokenResource { log.invalidTokenTTLEncountered(ttl); } } - + tokenTargetUrl = context.getInitParameter(TOKEN_TARGET_URL); String clientData = context.getInitParameter(TOKEN_CLIENT_DATA); @@ -115,22 +120,18 @@ public class TokenResource { JWTokenAuthority ts = services.getService(GatewayServices.TOKEN_SERVICE); Principal p = ((HttpServletRequest)request).getUserPrincipal(); long expires = getExpiry(); - + try { JWT token = null; - if (targetAudiences == null || targetAudiences.length == 0) { - token = ts.issueToken(p, "RS256", getExpiry()); + if (targetAudiences.isEmpty()) { + token = ts.issueToken(p, "RS256", expires); } 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", expires); + token = ts.issueToken(p, targetAudiences, "RS256", expires); } if (token != null) { String accessToken = token.toString(); - + HashMap<String, Object> map = new HashMap<>(); map.put(ACCESS_TOKEN, accessToken); map.put(TOKEN_TYPE, BEARER); @@ -141,7 +142,7 @@ public class TokenResource { if (tokenClientDataMap != null) { map.putAll(tokenClientDataMap); } - + String jsonResponse = JsonUtils.renderAsJsonString(map); response.getWriter().write(jsonResponse); http://git-wip-us.apache.org/repos/asf/knox/blob/3557612d/gateway-service-knoxtoken/src/test/java/org/apache/hadoop/gateway/service/knoxtoken/TokenServiceResourceTest.java ---------------------------------------------------------------------- diff --git a/gateway-service-knoxtoken/src/test/java/org/apache/hadoop/gateway/service/knoxtoken/TokenServiceResourceTest.java b/gateway-service-knoxtoken/src/test/java/org/apache/hadoop/gateway/service/knoxtoken/TokenServiceResourceTest.java index 2b4fea1..9faa073 100644 --- a/gateway-service-knoxtoken/src/test/java/org/apache/hadoop/gateway/service/knoxtoken/TokenServiceResourceTest.java +++ b/gateway-service-knoxtoken/src/test/java/org/apache/hadoop/gateway/service/knoxtoken/TokenServiceResourceTest.java @@ -18,17 +18,63 @@ package org.apache.hadoop.gateway.service.knoxtoken; import org.apache.hadoop.gateway.service.knoxtoken.TokenResource; +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.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; + import java.util.Map; + +import javax.security.auth.Subject; +import javax.servlet.ServletContext; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.ws.rs.core.Response; + +import static org.junit.Assert.*; + +import java.io.PrintWriter; +import java.io.StringWriter; +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; /** - * + * Some tests for the token service */ public class TokenServiceResourceTest { + 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 testTokenService() throws Exception { Assert.assertTrue(true); @@ -50,4 +96,212 @@ public class TokenServiceResourceTest { tr.addClientDataToMap("".split(","), clientDataMap); Assert.assertTrue(clientDataMap.size() == 0); } + + @Test + public void testGetToken() throws Exception { + TokenResource tr = new TokenResource(); + + ServletContext context = EasyMock.createNiceMock(ServletContext.class); + //tr.context = context; + // tr.init(); + + HttpServletRequest request = EasyMock.createNiceMock(HttpServletRequest.class); + 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); + + StringWriter writer = new StringWriter(); + PrintWriter printWriter = new PrintWriter(writer); + HttpServletResponse response = EasyMock.createNiceMock(HttpServletResponse.class); + EasyMock.expect(response.getWriter()).andReturn(printWriter); + + EasyMock.replay(principal, services, context, request, response); + + tr.request = request; + tr.response = response; + + // Issue a token + Response retResponse = tr.doGet(); + + assertEquals(200, retResponse.getStatus()); + + // Parse the response + String retString = writer.toString(); + String accessToken = getTagValue(retString, "access_token"); + assertNotNull(accessToken); + String expiry = getTagValue(retString, "expires_in"); + assertNotNull(expiry); + + // Verify the token + JWTToken parsedToken = new JWTToken(accessToken); + assertEquals("alice", parsedToken.getSubject()); + assertTrue(authority.verifyToken(parsedToken)); + } + + @Test + public void testAudiences() throws Exception { + + ServletContext context = EasyMock.createNiceMock(ServletContext.class); + EasyMock.expect(context.getInitParameter("knox.token.audiences")).andReturn("recipient1,recipient2"); + EasyMock.expect(context.getInitParameter("knox.token.ttl")).andReturn(null); + EasyMock.expect(context.getInitParameter("knox.token.target.url")).andReturn(null); + EasyMock.expect(context.getInitParameter("knox.token.client.data")).andReturn(null); + + HttpServletRequest request = EasyMock.createNiceMock(HttpServletRequest.class); + 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); + + StringWriter writer = new StringWriter(); + PrintWriter printWriter = new PrintWriter(writer); + HttpServletResponse response = EasyMock.createNiceMock(HttpServletResponse.class); + EasyMock.expect(response.getWriter()).andReturn(printWriter); + + EasyMock.replay(principal, services, context, request, response); + + TokenResource tr = new TokenResource(); + tr.request = request; + tr.response = response; + tr.context = context; + tr.init(); + + // Issue a token + Response retResponse = tr.doGet(); + + assertEquals(200, retResponse.getStatus()); + + // Parse the response + String retString = writer.toString(); + String accessToken = getTagValue(retString, "access_token"); + assertNotNull(accessToken); + String expiry = getTagValue(retString, "expires_in"); + assertNotNull(expiry); + + // Verify the token + JWTToken parsedToken = new JWTToken(accessToken); + 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")); + } + + private String getTagValue(String token, String tagName) { + String searchString = tagName + "\":"; + String value = token.substring(token.indexOf(searchString) + searchString.length()); + if (value.startsWith("\"")) { + value = value.substring(1); + } + if (value.contains("\"")) { + return value.substring(0, value.indexOf("\"")); + } else if (value.contains(",")) { + return value.substring(0, value.indexOf(",")); + } else { + return value.substring(0, value.length() - 1); + } + } + + 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 { + ArrayList<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); + } + + } + + }