Merge remote-tracking branch 'origin/master' into KNOX-998-Package_Restructuring
# Conflicts: # gateway-service-knoxsso/src/test/java/org/apache/knox/gateway/service/knoxsso/WebSSOResourceTest.java # gateway-service-knoxtoken/src/test/java/org/apache/knox/gateway/service/knoxtoken/TokenServiceResourceTest.java Project: http://git-wip-us.apache.org/repos/asf/knox/repo Commit: http://git-wip-us.apache.org/repos/asf/knox/commit/416ee7c1 Tree: http://git-wip-us.apache.org/repos/asf/knox/tree/416ee7c1 Diff: http://git-wip-us.apache.org/repos/asf/knox/diff/416ee7c1 Branch: refs/heads/KNOX-998-Package_Restructuring Commit: 416ee7c15076ddcaf25cd6a908e6cf1b39683673 Parents: f4a4355 2666894 Author: Sandeep More <m...@apache.org> Authored: Wed Sep 20 09:52:47 2017 -0400 Committer: Sandeep More <m...@apache.org> Committed: Wed Sep 20 09:52:47 2017 -0400 ---------------------------------------------------------------------- .../federation/AbstractJWTFilterTest.java | 14 +- .../federation/JWTFederationFilterTest.java | 1 - .../federation/SSOCookieProviderTest.java | 1 - gateway-service-knoxsso/pom.xml | 11 +- .../gateway/service/knoxsso/WebSSOResource.java | 20 +- .../service/knoxsso/WebSSOResourceTest.java | 303 +++++++++++++++- gateway-service-knoxtoken/pom.xml | 29 +- .../service/knoxtoken/TokenResource.java | 31 +- .../knoxtoken/TokenServiceResourceTest.java | 257 +++++++++++++- .../gateway/AmbariServiceDefinitionTest.java | 14 +- .../knox/gateway/GatewayAdminFuncTest.java | 2 +- .../gateway/GatewayAdminTopologyFuncTest.java | 62 ++-- .../apache/knox/gateway/GatewayAppFuncTest.java | 70 ++-- .../knox/gateway/GatewayBasicFuncTest.java | 349 +++++++++---------- .../knox/gateway/GatewayDeployFuncTest.java | 2 +- .../knox/gateway/GatewayHealthFuncTest.java | 6 +- .../GatewayLdapDynamicGroupFuncTest.java | 6 +- .../knox/gateway/GatewayLdapGroupFuncTest.java | 4 +- .../gateway/GatewayLdapPosixGroupFuncTest.java | 13 +- .../gateway/GatewayLocalServiceFuncTest.java | 2 +- .../knox/gateway/GatewayMultiFuncTest.java | 26 +- .../GatewayPortMappingDisableFeatureTest.java | 4 +- .../gateway/GatewayPortMappingFailTest.java | 2 +- .../gateway/GatewayPortMappingFuncTest.java | 4 +- .../knox/gateway/GatewaySampleFuncTest.java | 2 +- .../apache/knox/gateway/Knox242FuncTest.java | 6 +- .../apache/knox/gateway/WebHdfsHaFuncTest.java | 30 +- 27 files changed, 895 insertions(+), 376 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/knox/blob/416ee7c1/gateway-provider-security-jwt/src/test/java/org/apache/knox/gateway/provider/federation/AbstractJWTFilterTest.java ---------------------------------------------------------------------- diff --cc gateway-provider-security-jwt/src/test/java/org/apache/knox/gateway/provider/federation/AbstractJWTFilterTest.java index 10efeb5,0000000..ea56486 mode 100644,000000..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 @@@ -1,636 -1,0 +1,636 @@@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.knox.gateway.provider.federation; + +import static org.junit.Assert.fail; + +import java.io.IOException; +import java.net.InetAddress; +import java.security.AccessController; +import java.security.KeyPair; +import java.security.KeyPairGenerator; +import java.security.NoSuchAlgorithmException; +import java.security.Principal; +import java.security.PublicKey; +import java.security.cert.Certificate; +import java.security.interfaces.RSAPrivateKey; +import java.security.interfaces.RSAPublicKey; +import java.text.MessageFormat; +import java.util.Enumeration; +import java.util.List; +import java.util.ArrayList; +import java.util.Properties; +import java.util.Date; +import java.util.Set; + +import javax.security.auth.Subject; +import javax.servlet.FilterChain; +import javax.servlet.FilterConfig; +import javax.servlet.ServletContext; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.apache.commons.codec.binary.Base64; +import org.apache.knox.gateway.provider.federation.jwt.filter.AbstractJWTFilter; +import org.apache.knox.gateway.provider.federation.jwt.filter.SSOCookieFederationFilter; +import org.apache.knox.gateway.security.PrimaryPrincipal; +import org.apache.knox.gateway.services.security.impl.X509CertificateUtil; +import org.apache.knox.gateway.services.security.token.JWTokenAuthority; +import org.apache.knox.gateway.services.security.token.TokenServiceException; +import org.apache.knox.gateway.services.security.token.impl.JWT; +import org.apache.knox.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.*; +import com.nimbusds.jwt.JWTClaimsSet; +import com.nimbusds.jwt.SignedJWT; +import com.nimbusds.jose.crypto.RSASSASigner; +import com.nimbusds.jose.crypto.RSASSAVerifier; + +public abstract class AbstractJWTFilterTest { + private static final String SERVICE_URL = "https://localhost:8888/resource"; + 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; + String dn = headerFormatter.format(paramArray); + 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(); + String dn = buildDistinguishedName(InetAddress.getLocalHost().getHostName()); + Certificate cert = X509CertificateUtil.generateCertificate(dn, KPair, 365, "SHA1withRSA"); + byte[] data = cert.getEncoded(); + Base64 encoder = new Base64( 76, "\n".getBytes( "ASCII" ) ); + pem = new String(encoder.encodeToString( data ).getBytes( "ASCII" )).trim(); + + publicKey = (RSAPublicKey) KPair.getPublic(); + privateKey = (RSAPrivateKey) KPair.getPrivate(); + } + + @After + public void teardown() throws Exception { + handler.destroy(); + } + + @Test + public void testValidJWT() throws Exception { + try { + Properties props = getProperties(); + handler.init(new TestFilterConfig(props)); + + SignedJWT jwt = getJWT("alice", new Date(new Date().getTime() + 5000), privateKey, props); + + HttpServletRequest request = EasyMock.createNiceMock(HttpServletRequest.class); + setTokenOnRequest(request, jwt); + + EasyMock.expect(request.getRequestURL()).andReturn( + new StringBuffer(SERVICE_URL)).anyTimes(); + EasyMock.expect(request.getQueryString()).andReturn(null); + HttpServletResponse response = EasyMock.createNiceMock(HttpServletResponse.class); + EasyMock.expect(response.encodeRedirectURL(SERVICE_URL)).andReturn( + SERVICE_URL); + EasyMock.replay(request); + + TestFilterChain chain = new TestFilterChain(); + handler.doFilter(request, response, chain); + Assert.assertTrue("doFilterCalled should not be false.", chain.doFilterCalled ); + Set<PrimaryPrincipal> principals = chain.subject.getPrincipals(PrimaryPrincipal.class); + Assert.assertTrue("No PrimaryPrincipal", !principals.isEmpty()); + Assert.assertEquals("Not the expected principal", "alice", ((Principal)principals.toArray()[0]).getName()); + } catch (ServletException se) { + fail("Should NOT have thrown a ServletException."); + } + } + + @Test + public void testValidAudienceJWT() throws Exception { + try { + Properties props = getProperties(); + props.put(getAudienceProperty(), "bar"); + handler.init(new TestFilterConfig(props)); + + SignedJWT jwt = getJWT("alice", new Date(new Date().getTime() + 5000), privateKey, props); + + HttpServletRequest request = EasyMock.createNiceMock(HttpServletRequest.class); + setTokenOnRequest(request, jwt); + + EasyMock.expect(request.getRequestURL()).andReturn( + new StringBuffer(SERVICE_URL)).anyTimes(); + EasyMock.expect(request.getQueryString()).andReturn(null); + HttpServletResponse response = EasyMock.createNiceMock(HttpServletResponse.class); + EasyMock.expect(response.encodeRedirectURL(SERVICE_URL)).andReturn( + SERVICE_URL); + EasyMock.replay(request); + + TestFilterChain chain = new TestFilterChain(); + handler.doFilter(request, response, chain); + Assert.assertTrue("doFilterCalled should not be false.", chain.doFilterCalled ); + Set<PrimaryPrincipal> principals = chain.subject.getPrincipals(PrimaryPrincipal.class); + Assert.assertTrue("No PrimaryPrincipal", !principals.isEmpty()); + Assert.assertEquals("Not the expected principal", "alice", ((Principal)principals.toArray()[0]).getName()); + } catch (ServletException se) { + fail("Should NOT have thrown a ServletException."); + } + } + + @Test + public void testInvalidAudienceJWT() throws Exception { + try { + Properties props = getProperties(); + props.put(getAudienceProperty(), "foo"); + props.put("sso.authentication.provider.url", "https://localhost:8443/gateway/knoxsso/api/v1/websso"); + + handler.init(new TestFilterConfig(props)); + + SignedJWT jwt = getJWT("alice", new Date(new Date().getTime() + 5000), privateKey, props); + + HttpServletRequest request = EasyMock.createNiceMock(HttpServletRequest.class); + setTokenOnRequest(request, jwt); + + EasyMock.expect(request.getRequestURL()).andReturn( + new StringBuffer(SERVICE_URL)).anyTimes(); + EasyMock.expect(request.getQueryString()).andReturn(null); + HttpServletResponse response = EasyMock.createNiceMock(HttpServletResponse.class); + EasyMock.expect(response.encodeRedirectURL(SERVICE_URL)).andReturn( + SERVICE_URL); + EasyMock.replay(request); + + TestFilterChain chain = new TestFilterChain(); + handler.doFilter(request, response, chain); + Assert.assertTrue("doFilterCalled should not be true.", !chain.doFilterCalled); + Assert.assertTrue("No Subject should be returned.", chain.subject == null); + } catch (ServletException se) { + fail("Should NOT have thrown a ServletException."); + } + } + + @Test + public void testValidVerificationPEM() throws Exception { + try { + Properties props = getProperties(); + +// System.out.println("+" + pem + "+"); + + props.put(getAudienceProperty(), "bar"); + props.put("sso.authentication.provider.url", "https://localhost:8443/gateway/knoxsso/api/v1/websso"); + props.put(getVerificationPemProperty(), pem); + handler.init(new TestFilterConfig(props)); + + SignedJWT jwt = getJWT("alice", new Date(new Date().getTime() + 50000), privateKey, props); + + HttpServletRequest request = EasyMock.createNiceMock(HttpServletRequest.class); + setTokenOnRequest(request, jwt); + + EasyMock.expect(request.getRequestURL()).andReturn( + new StringBuffer(SERVICE_URL)).anyTimes(); + EasyMock.expect(request.getQueryString()).andReturn(null); + HttpServletResponse response = EasyMock.createNiceMock(HttpServletResponse.class); + EasyMock.expect(response.encodeRedirectURL(SERVICE_URL)).andReturn( + SERVICE_URL); + EasyMock.replay(request); + + TestFilterChain chain = new TestFilterChain(); + handler.doFilter(request, response, chain); + Assert.assertTrue("doFilterCalled should not be false.", chain.doFilterCalled ); + Set<PrimaryPrincipal> principals = chain.subject.getPrincipals(PrimaryPrincipal.class); + Assert.assertTrue("No PrimaryPrincipal", !principals.isEmpty()); + Assert.assertEquals("Not the expected principal", "alice", ((Principal)principals.toArray()[0]).getName()); + } catch (ServletException se) { + fail("Should NOT have thrown a ServletException."); + } + } + + @Test + public void testExpiredJWT() throws Exception { + try { + Properties props = getProperties(); + handler.init(new TestFilterConfig(props)); + + SignedJWT jwt = getJWT("alice", new Date(new Date().getTime() - 1000), privateKey, props); + + HttpServletRequest request = EasyMock.createNiceMock(HttpServletRequest.class); + setTokenOnRequest(request, jwt); + + EasyMock.expect(request.getRequestURL()).andReturn( + new StringBuffer(SERVICE_URL)).anyTimes(); + EasyMock.expect(request.getQueryString()).andReturn(null); + HttpServletResponse response = EasyMock.createNiceMock(HttpServletResponse.class); + EasyMock.expect(response.encodeRedirectURL(SERVICE_URL)).andReturn( + SERVICE_URL); + EasyMock.replay(request); + + TestFilterChain chain = new TestFilterChain(); + handler.doFilter(request, response, chain); + Assert.assertTrue("doFilterCalled should not be false.", !chain.doFilterCalled); + Assert.assertTrue("No Subject should be returned.", chain.subject == null); + } catch (ServletException se) { + fail("Should NOT have thrown a ServletException."); + } + } + + @Test + public void testValidJWTNoExpiration() throws Exception { + try { + Properties props = getProperties(); + handler.init(new TestFilterConfig(props)); + + SignedJWT jwt = getJWT("alice", null, privateKey, props); + + HttpServletRequest request = EasyMock.createNiceMock(HttpServletRequest.class); + setTokenOnRequest(request, jwt); + + EasyMock.expect(request.getRequestURL()).andReturn( + new StringBuffer(SERVICE_URL)).anyTimes(); + EasyMock.expect(request.getQueryString()).andReturn(null); + HttpServletResponse response = EasyMock.createNiceMock(HttpServletResponse.class); + EasyMock.expect(response.encodeRedirectURL(SERVICE_URL)).andReturn( + SERVICE_URL).anyTimes(); + EasyMock.replay(request); + + TestFilterChain chain = new TestFilterChain(); + handler.doFilter(request, response, chain); + Assert.assertTrue("doFilterCalled should not be false.", chain.doFilterCalled ); + Set<PrimaryPrincipal> principals = chain.subject.getPrincipals(PrimaryPrincipal.class); + Assert.assertTrue("No PrimaryPrincipal", !principals.isEmpty()); + Assert.assertEquals("Not the expected principal", "alice", ((Principal)principals.toArray()[0]).getName()); + } catch (ServletException se) { + fail("Should NOT have thrown a ServletException."); + } + } + + @Test + public void testUnableToParseJWT() throws Exception { + try { + Properties props = getProperties(); + handler.init(new TestFilterConfig(props)); + + SignedJWT jwt = getJWT("bob", new Date(new Date().getTime() + 5000), privateKey, props); + + HttpServletRequest request = EasyMock.createNiceMock(HttpServletRequest.class); + setGarbledTokenOnRequest(request, jwt); + + EasyMock.expect(request.getRequestURL()).andReturn( + new StringBuffer(SERVICE_URL)).anyTimes(); + EasyMock.expect(request.getQueryString()).andReturn(null); + HttpServletResponse response = EasyMock.createNiceMock(HttpServletResponse.class); + EasyMock.expect(response.encodeRedirectURL(SERVICE_URL)).andReturn( + SERVICE_URL).anyTimes(); + EasyMock.replay(request); + + TestFilterChain chain = new TestFilterChain(); + handler.doFilter(request, response, chain); + Assert.assertTrue("doFilterCalled should not be true.", !chain.doFilterCalled); + Assert.assertTrue("No Subject should be returned.", chain.subject == null); + } catch (ServletException se) { + fail("Should NOT have thrown a ServletException."); + } + } + + @Test + public void testFailedSignatureValidationJWT() throws Exception { + try { + // Create a private key to sign the token + KeyPairGenerator kpg = KeyPairGenerator.getInstance("RSA"); + kpg.initialize(1024); + + KeyPair kp = kpg.genKeyPair(); + + Properties props = getProperties(); + handler.init(new TestFilterConfig(props)); + + SignedJWT jwt = getJWT("bob", new Date(new Date().getTime() + 5000), + (RSAPrivateKey)kp.getPrivate(), props); + + HttpServletRequest request = EasyMock.createNiceMock(HttpServletRequest.class); + setTokenOnRequest(request, jwt); + + EasyMock.expect(request.getRequestURL()).andReturn( + new StringBuffer(SERVICE_URL)).anyTimes(); + EasyMock.expect(request.getQueryString()).andReturn(null); + HttpServletResponse response = EasyMock.createNiceMock(HttpServletResponse.class); + EasyMock.expect(response.encodeRedirectURL(SERVICE_URL)).andReturn( + SERVICE_URL).anyTimes(); + EasyMock.replay(request); + + TestFilterChain chain = new TestFilterChain(); + handler.doFilter(request, response, chain); + Assert.assertTrue("doFilterCalled should not be true.", !chain.doFilterCalled); + Assert.assertTrue("No Subject should be returned.", chain.subject == null); + } catch (ServletException se) { + fail("Should NOT have thrown a ServletException."); + } + } + + @Test + public void testInvalidVerificationPEM() throws Exception { + try { + Properties props = getProperties(); + + KeyPairGenerator kpg = KeyPairGenerator.getInstance("RSA"); + kpg.initialize(1024); + + KeyPair KPair = kpg.generateKeyPair(); + String dn = buildDistinguishedName(InetAddress.getLocalHost().getHostName()); + Certificate cert = X509CertificateUtil.generateCertificate(dn, KPair, 365, "SHA1withRSA"); + byte[] data = cert.getEncoded(); + Base64 encoder = new Base64( 76, "\n".getBytes( "ASCII" ) ); + String failingPem = new String(encoder.encodeToString( data ).getBytes( "ASCII" )).trim(); + + props.put(getAudienceProperty(), "bar"); + props.put(getVerificationPemProperty(), failingPem); + handler.init(new TestFilterConfig(props)); + + SignedJWT jwt = getJWT("alice", new Date(new Date().getTime() + 50000), privateKey, props); + + HttpServletRequest request = EasyMock.createNiceMock(HttpServletRequest.class); + setTokenOnRequest(request, jwt); + + EasyMock.expect(request.getRequestURL()).andReturn( + new StringBuffer(SERVICE_URL)).anyTimes(); + EasyMock.expect(request.getQueryString()).andReturn(null); + HttpServletResponse response = EasyMock.createNiceMock(HttpServletResponse.class); + EasyMock.expect(response.encodeRedirectURL(SERVICE_URL)).andReturn(SERVICE_URL); + EasyMock.replay(request); + + TestFilterChain chain = new TestFilterChain(); + handler.doFilter(request, response, chain); + Assert.assertTrue("doFilterCalled should not be true.", chain.doFilterCalled == false); + Assert.assertTrue("No Subject should be returned.", chain.subject == null); + } catch (ServletException se) { + fail("Should NOT have thrown a ServletException."); + } + } + + @Test + public void testInvalidIssuer() throws Exception { + try { + Properties props = getProperties(); + handler.init(new TestFilterConfig(props)); + + SignedJWT jwt = getJWT("new-issuer", "alice", new Date(new Date().getTime() + 5000), privateKey); + + HttpServletRequest request = EasyMock.createNiceMock(HttpServletRequest.class); + setTokenOnRequest(request, jwt); + + EasyMock.expect(request.getRequestURL()).andReturn( + new StringBuffer(SERVICE_URL)).anyTimes(); + EasyMock.expect(request.getQueryString()).andReturn(null); + HttpServletResponse response = EasyMock.createNiceMock(HttpServletResponse.class); + EasyMock.expect(response.encodeRedirectURL(SERVICE_URL)).andReturn( + SERVICE_URL); + EasyMock.replay(request); + + TestFilterChain chain = new TestFilterChain(); + handler.doFilter(request, response, chain); + Assert.assertTrue("doFilterCalled should not be true.", !chain.doFilterCalled); + Assert.assertTrue("No Subject should be returned.", chain.subject == null); + } catch (ServletException se) { + fail("Should NOT have thrown a ServletException."); + } + } + + @Test + public void testValidIssuerViaConfig() throws Exception { + try { + Properties props = getProperties(); + props.setProperty(AbstractJWTFilter.JWT_EXPECTED_ISSUER, "new-issuer"); + handler.init(new TestFilterConfig(props)); + + SignedJWT jwt = getJWT("new-issuer", "alice", new Date(new Date().getTime() + 5000), privateKey); + + HttpServletRequest request = EasyMock.createNiceMock(HttpServletRequest.class); + setTokenOnRequest(request, jwt); + + EasyMock.expect(request.getRequestURL()).andReturn( + new StringBuffer(SERVICE_URL)).anyTimes(); + EasyMock.expect(request.getQueryString()).andReturn(null); + HttpServletResponse response = EasyMock.createNiceMock(HttpServletResponse.class); + EasyMock.expect(response.encodeRedirectURL(SERVICE_URL)).andReturn( + SERVICE_URL); + EasyMock.replay(request); + + TestFilterChain chain = new TestFilterChain(); + handler.doFilter(request, response, chain); + Assert.assertTrue("doFilterCalled should not be false.", chain.doFilterCalled); + Set<PrimaryPrincipal> principals = chain.subject.getPrincipals(PrimaryPrincipal.class); + Assert.assertTrue("No PrimaryPrincipal", principals.size() > 0); + Assert.assertEquals("Not the expected principal", "alice", ((Principal)principals.toArray()[0]).getName()); + } catch (ServletException se) { + fail("Should NOT have thrown a ServletException."); + } + } + + protected Properties getProperties() { + Properties props = new Properties(); + props.setProperty( + SSOCookieFederationFilter.SSO_AUTHENTICATION_PROVIDER_URL, + "https://localhost:8443/authserver"); + return props; + } + + protected SignedJWT getJWT(String sub, Date expires, RSAPrivateKey privateKey, + Properties props) throws Exception { + return getJWT(AbstractJWTFilter.JWT_DEFAULT_ISSUER, sub, expires, privateKey); + } + + protected SignedJWT getJWT(String issuer, String sub, Date expires, RSAPrivateKey privateKey) + throws Exception { + List<String> aud = new ArrayList<String>(); + aud.add("bar"); + + JWTClaimsSet claims = new JWTClaimsSet.Builder() + .issuer(issuer) + .subject(sub) + .audience(aud) + .expirationTime(expires) + .claim("scope", "openid") + .build(); + + JWSHeader header = new JWSHeader.Builder(JWSAlgorithm.RS256).build(); + + SignedJWT signedJWT = new SignedJWT(header, claims); + JWSSigner signer = new RSASSASigner(privateKey); + + signedJWT.sign(signer); + + return signedJWT; + } + + protected static class TestFilterConfig implements FilterConfig { + Properties props = null; + + public TestFilterConfig(Properties props) { + this.props = props; + } + + @Override + public String getFilterName() { + return null; + } + + /* (non-Javadoc) + * @see javax.servlet.FilterConfig#getServletContext() + */ + @Override + public ServletContext getServletContext() { +// JWTokenAuthority authority = EasyMock.createNiceMock(JWTokenAuthority.class); +// GatewayServices services = EasyMock.createNiceMock(GatewayServices.class); +// EasyMock.expect(services.getService("TokenService").andReturn(authority)); +// ServletContext context = EasyMock.createNiceMock(ServletContext.class); +// EasyMock.expect(context.getAttribute(GatewayServices.GATEWAY_SERVICES_ATTRIBUTE).andReturn(new DefaultGatewayServices())); + return null; + } + + /* (non-Javadoc) + * @see javax.servlet.FilterConfig#getInitParameter(java.lang.String) + */ + @Override + public String getInitParameter(String name) { + return props.getProperty(name, null); + } + + /* (non-Javadoc) + * @see javax.servlet.FilterConfig#getInitParameterNames() + */ + @Override + public Enumeration<String> getInitParameterNames() { + return null; + } + + } + + protected static class TestJWTokenAuthority implements JWTokenAuthority { + + private PublicKey verifyingKey; + + public TestJWTokenAuthority(PublicKey verifyingKey) { + this.verifyingKey = verifyingKey; + } + + /* (non-Javadoc) + * @see JWTokenAuthority#issueToken(javax.security.auth.Subject, java.lang.String) + */ + @Override + public JWTToken issueToken(Subject subject, String algorithm) + throws TokenServiceException { + // TODO Auto-generated method stub + return null; + } + + /* (non-Javadoc) + * @see JWTokenAuthority#issueToken(java.security.Principal, java.lang.String) + */ + @Override + public JWTToken issueToken(Principal p, String algorithm) + throws TokenServiceException { + // TODO Auto-generated method stub + return null; + } + + /* (non-Javadoc) + * @see JWTokenAuthority#issueToken(java.security.Principal, java.lang.String, java.lang.String) + */ + @Override + public JWTToken issueToken(Principal p, String audience, String algorithm) + throws TokenServiceException { + return null; + } + + /* (non-Javadoc) + * @see JWTokenAuthority#verifyToken(JWTToken) + */ + @Override + public boolean verifyToken(JWTToken token) throws TokenServiceException { + JWSVerifier verifier = new RSASSAVerifier((RSAPublicKey) verifyingKey); + return token.verify(verifier); + } + + /* (non-Javadoc) + * @see JWTokenAuthority#issueToken(java.security.Principal, java.lang.String, java.lang.String, long) + */ + @Override + public JWTToken issueToken(Principal p, String audience, String algorithm, + long expires) throws TokenServiceException { + return null; + } + + @Override + public JWTToken issueToken(Principal p, List<String> audiences, String algorithm, + long expires) throws TokenServiceException { + return null; + } + + /* (non-Javadoc) + * @see JWTokenAuthority#issueToken(java.security.Principal, java.lang.String, long) + */ + @Override + public JWT issueToken(Principal p, String audience, long l) + throws TokenServiceException { + // TODO Auto-generated method stub + return null; + } + + @Override + public boolean verifyToken(JWTToken token, RSAPublicKey publicKey) throws TokenServiceException { + JWSVerifier verifier = new RSASSAVerifier(publicKey); + return token.verify(verifier); + } + + } + + protected static class TestFilterChain implements FilterChain { + boolean doFilterCalled = false; + Subject subject = null; + + /* (non-Javadoc) + * @see javax.servlet.FilterChain#doFilter(javax.servlet.ServletRequest, javax.servlet.ServletResponse) + */ + @Override + public void doFilter(ServletRequest request, ServletResponse response) + throws IOException, ServletException { + doFilterCalled = true; + + subject = Subject.getSubject( AccessController.getContext() ); + } + + } +} http://git-wip-us.apache.org/repos/asf/knox/blob/416ee7c1/gateway-provider-security-jwt/src/test/java/org/apache/knox/gateway/provider/federation/JWTFederationFilterTest.java ---------------------------------------------------------------------- diff --cc gateway-provider-security-jwt/src/test/java/org/apache/knox/gateway/provider/federation/JWTFederationFilterTest.java index c35d013,0000000..bfb5e91 mode 100644,000000..100644 --- a/gateway-provider-security-jwt/src/test/java/org/apache/knox/gateway/provider/federation/JWTFederationFilterTest.java +++ b/gateway-provider-security-jwt/src/test/java/org/apache/knox/gateway/provider/federation/JWTFederationFilterTest.java @@@ -1,67 -1,0 +1,66 @@@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.knox.gateway.provider.federation; + +import java.security.NoSuchAlgorithmException; + +import javax.servlet.http.HttpServletRequest; + +import org.apache.knox.gateway.provider.federation.jwt.filter.JWTFederationFilter; +import org.apache.knox.gateway.services.security.token.JWTokenAuthority; +import org.easymock.EasyMock; +import org.junit.Before; + +import com.nimbusds.jwt.SignedJWT; + +public class JWTFederationFilterTest extends AbstractJWTFilterTest { + + @Before + public void setup() throws Exception, NoSuchAlgorithmException { - super.setup(); + handler = new TestJWTFederationFilter(); + ((TestJWTFederationFilter) handler).setTokenService(new TestJWTokenAuthority(publicKey)); + } + + protected void setTokenOnRequest(HttpServletRequest request, SignedJWT jwt) { + String token = "Bearer " + jwt.serialize(); + EasyMock.expect(request.getHeader("Authorization")).andReturn(token); + } + + protected void setGarbledTokenOnRequest(HttpServletRequest request, SignedJWT jwt) { + String token = "Bearer " + "ljm" + jwt.serialize(); + EasyMock.expect(request.getHeader("Authorization")).andReturn(token); + } + + protected String getAudienceProperty() { + return TestJWTFederationFilter.KNOX_TOKEN_AUDIENCES; + } + + private static class TestJWTFederationFilter extends JWTFederationFilter { + + public void setTokenService(JWTokenAuthority ts) { + authority = ts; + } + + } + + @Override + protected String getVerificationPemProperty() { + return TestJWTFederationFilter.TOKEN_VERIFICATION_PEM; + }; + +} http://git-wip-us.apache.org/repos/asf/knox/blob/416ee7c1/gateway-provider-security-jwt/src/test/java/org/apache/knox/gateway/provider/federation/SSOCookieProviderTest.java ---------------------------------------------------------------------- diff --cc gateway-provider-security-jwt/src/test/java/org/apache/knox/gateway/provider/federation/SSOCookieProviderTest.java index d217799,0000000..babbee2 mode 100644,000000..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 @@@ -1,162 -1,0 +1,161 @@@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.knox.gateway.provider.federation; + +import static org.junit.Assert.fail; + +import java.security.NoSuchAlgorithmException; +import java.security.Principal; +import java.util.Properties; +import java.util.Date; +import java.util.Set; + +import javax.servlet.ServletException; +import javax.servlet.http.Cookie; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.apache.knox.gateway.provider.federation.jwt.filter.SSOCookieFederationFilter; +import org.apache.knox.gateway.security.PrimaryPrincipal; +import org.apache.knox.gateway.services.security.token.JWTokenAuthority; +import org.easymock.EasyMock; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; + +import com.nimbusds.jwt.SignedJWT; + +public class SSOCookieProviderTest extends AbstractJWTFilterTest { + private static final String SERVICE_URL = "https://localhost:8888/resource"; + + @Before + public void setup() throws Exception, NoSuchAlgorithmException { - super.setup(); + handler = new TestSSOCookieFederationProvider(); + ((TestSSOCookieFederationProvider) handler).setTokenService(new TestJWTokenAuthority(publicKey)); + } + + protected void setTokenOnRequest(HttpServletRequest request, SignedJWT jwt) { + Cookie cookie = new Cookie("hadoop-jwt", jwt.serialize()); + EasyMock.expect(request.getCookies()).andReturn(new Cookie[] { cookie }); + } + + protected void setGarbledTokenOnRequest(HttpServletRequest request, SignedJWT jwt) { + Cookie cookie = new Cookie("hadoop-jwt", "ljm" + jwt.serialize()); + EasyMock.expect(request.getCookies()).andReturn(new Cookie[] { cookie }); + } + + protected String getAudienceProperty() { + return TestSSOCookieFederationProvider.SSO_EXPECTED_AUDIENCES; + } + + @Test + public void testCustomCookieNameJWT() throws Exception { + try { + Properties props = getProperties(); + props.put("sso.cookie.name", "jowt"); + handler.init(new TestFilterConfig(props)); + + SignedJWT jwt = getJWT("alice", new Date(new Date().getTime() + 5000), + privateKey, props); + + Cookie cookie = new Cookie("jowt", jwt.serialize()); + HttpServletRequest request = EasyMock.createNiceMock(HttpServletRequest.class); + EasyMock.expect(request.getCookies()).andReturn(new Cookie[] { cookie }); + EasyMock.expect(request.getRequestURL()).andReturn( + new StringBuffer(SERVICE_URL)).anyTimes(); + EasyMock.expect(request.getQueryString()).andReturn(null); + HttpServletResponse response = EasyMock.createNiceMock(HttpServletResponse.class); + EasyMock.expect(response.encodeRedirectURL(SERVICE_URL)).andReturn( + SERVICE_URL); + EasyMock.replay(request); + + TestFilterChain chain = new TestFilterChain(); + handler.doFilter(request, response, chain); + Assert.assertTrue("doFilterCalled should not be false.", chain.doFilterCalled ); + Set<PrimaryPrincipal> principals = chain.subject.getPrincipals(PrimaryPrincipal.class); + Assert.assertTrue("No PrimaryPrincipal returned.", !principals.isEmpty()); + Assert.assertEquals("Not the expected principal", "alice", ((Principal)principals.toArray()[0]).getName()); + } catch (ServletException se) { + fail("Should NOT have thrown a ServletException."); + } + } + + @Test + public void testNoProviderURLJWT() throws Exception { + try { + Properties props = getProperties(); + props.remove("sso.authentication.provider.url"); + handler.init(new TestFilterConfig(props)); + + fail("Servlet exception should have been thrown."); + + } catch (ServletException se) { + // expected - let's ensure it mentions the missing authentication provider URL + se.getMessage().contains("authentication provider URL is missing"); + } + } + + @Test + public void testOrigURLWithQueryString() throws Exception { + Properties props = getProperties(); + handler.init(new TestFilterConfig(props)); + + HttpServletRequest request = EasyMock.createNiceMock(HttpServletRequest.class); + EasyMock.expect(request.getRequestURL()).andReturn( + new StringBuffer(SERVICE_URL)).anyTimes(); + EasyMock.expect(request.getQueryString()).andReturn("name=value"); + EasyMock.replay(request); + + String loginURL = ((TestSSOCookieFederationProvider)handler).testConstructLoginURL(request); + Assert.assertNotNull("loginURL should not be null.", loginURL); + Assert.assertEquals("https://localhost:8443/authserver?originalUrl=" + SERVICE_URL + "?name=value", loginURL); + } + + @Test + public void testOrigURLNoQueryString() throws Exception { + Properties props = getProperties(); + handler.init(new TestFilterConfig(props)); + + HttpServletRequest request = EasyMock.createNiceMock(HttpServletRequest.class); + EasyMock.expect(request.getRequestURL()).andReturn( + new StringBuffer(SERVICE_URL)).anyTimes(); + EasyMock.expect(request.getQueryString()).andReturn(null); + EasyMock.replay(request); + + String loginURL = ((TestSSOCookieFederationProvider)handler).testConstructLoginURL(request); + Assert.assertNotNull("LoginURL should not be null.", loginURL); + Assert.assertEquals("https://localhost:8443/authserver?originalUrl=" + SERVICE_URL, loginURL); + } + + + @Override + protected String getVerificationPemProperty() { + return SSOCookieFederationFilter.SSO_VERIFICATION_PEM; + }; + + private static class TestSSOCookieFederationProvider extends SSOCookieFederationFilter { + public String testConstructLoginURL(HttpServletRequest req) { + return constructLoginURL(req); + } + + public void setTokenService(JWTokenAuthority ts) { + authority = ts; + } + }; + +} http://git-wip-us.apache.org/repos/asf/knox/blob/416ee7c1/gateway-service-knoxsso/src/main/java/org/apache/knox/gateway/service/knoxsso/WebSSOResource.java ---------------------------------------------------------------------- diff --cc gateway-service-knoxsso/src/main/java/org/apache/knox/gateway/service/knoxsso/WebSSOResource.java index a6bb3f7,0000000..8a9d028 mode 100644,000000..100644 --- a/gateway-service-knoxsso/src/main/java/org/apache/knox/gateway/service/knoxsso/WebSSOResource.java +++ b/gateway-service-knoxsso/src/main/java/org/apache/knox/gateway/service/knoxsso/WebSSOResource.java @@@ -1,322 -1,0 +1,322 @@@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.knox.gateway.service.knoxsso; + +import java.io.IOException; +import java.net.URI; +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; + +import javax.annotation.PostConstruct; +import javax.servlet.ServletContext; +import javax.servlet.http.Cookie; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpSession; +import javax.ws.rs.GET; +import javax.ws.rs.POST; +import javax.ws.rs.Path; +import javax.ws.rs.Produces; +import javax.ws.rs.core.Context; +import javax.ws.rs.core.Response; +import javax.ws.rs.WebApplicationException; + +import org.apache.knox.gateway.i18n.messages.MessagesFactory; +import org.apache.knox.gateway.services.GatewayServices; +import org.apache.knox.gateway.services.security.token.JWTokenAuthority; +import org.apache.knox.gateway.services.security.token.TokenServiceException; +import org.apache.knox.gateway.services.security.token.impl.JWT; +import org.apache.knox.gateway.util.RegExUtils; +import org.apache.knox.gateway.util.Urls; + +import static javax.ws.rs.core.MediaType.APPLICATION_JSON; +import static javax.ws.rs.core.MediaType.APPLICATION_XML; + +@Path( WebSSOResource.RESOURCE_PATH ) +public class WebSSOResource { + private static final String SSO_COOKIE_NAME = "knoxsso.cookie.name"; + private static final String SSO_COOKIE_SECURE_ONLY_INIT_PARAM = "knoxsso.cookie.secure.only"; + private static final String SSO_COOKIE_MAX_AGE_INIT_PARAM = "knoxsso.cookie.max.age"; + private static final String SSO_COOKIE_DOMAIN_SUFFIX_PARAM = "knoxsso.cookie.domain.suffix"; + private static final String SSO_COOKIE_TOKEN_TTL_PARAM = "knoxsso.token.ttl"; + private static final String SSO_COOKIE_TOKEN_AUDIENCES_PARAM = "knoxsso.token.audiences"; + private static final String SSO_COOKIE_TOKEN_WHITELIST_PARAM = "knoxsso.redirect.whitelist.regex"; + private static final String SSO_ENABLE_SESSION_PARAM = "knoxsso.enable.session"; + private static final String ORIGINAL_URL_REQUEST_PARAM = "originalUrl"; + private static final String ORIGINAL_URL_COOKIE_NAME = "original-url"; + private static final String DEFAULT_SSO_COOKIE_NAME = "hadoop-jwt"; + // default for the whitelist - open up for development - relative paths and localhost only + private static final String DEFAULT_WHITELIST = "^/.*$;^https?://(localhost|127.0.0.1|0:0:0:0:0:0:0:1|::1):\\d{0,9}/.*$"; + static final String RESOURCE_PATH = "/api/v1/websso"; + private static KnoxSSOMessages log = MessagesFactory.get( KnoxSSOMessages.class ); + private String cookieName = null; + private boolean secureOnly = true; + private int maxAge = -1; + 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; + + @PostConstruct + public void init() { + + // configured cookieName + cookieName = context.getInitParameter(SSO_COOKIE_NAME); + if (cookieName == null) { + cookieName = DEFAULT_SSO_COOKIE_NAME; + } + + String secure = context.getInitParameter(SSO_COOKIE_SECURE_ONLY_INIT_PARAM); + if (secure != null) { + secureOnly = ("false".equals(secure) ? false : true); + if (!secureOnly) { + log.cookieSecureOnly(secureOnly); + } + } + + String age = context.getInitParameter(SSO_COOKIE_MAX_AGE_INIT_PARAM); + if (age != null) { + try { + log.setMaxAge(age); + maxAge = Integer.parseInt(age); + } + catch (NumberFormatException nfe) { + log.invalidMaxAgeEncountered(age); + } + } + + domainSuffix = context.getInitParameter(SSO_COOKIE_DOMAIN_SUFFIX_PARAM); + + whitelist = context.getInitParameter(SSO_COOKIE_TOKEN_WHITELIST_PARAM); + if (whitelist == null) { + // default to local/relative targets + whitelist = DEFAULT_WHITELIST; + } + + 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); + if (ttl != null) { + try { + tokenTTL = Long.parseLong(ttl); + } + catch (NumberFormatException nfe) { + log.invalidTokenTTLEncountered(ttl); + } + } + + String enableSession = context.getInitParameter(SSO_ENABLE_SESSION_PARAM); + this.enableSession = ("true".equals(enableSession)); + } + + @GET + @Produces({APPLICATION_JSON, APPLICATION_XML}) + public Response doGet() { + return getAuthenticationToken(HttpServletResponse.SC_TEMPORARY_REDIRECT); + } + + @POST + @Produces({APPLICATION_JSON, APPLICATION_XML}) + public Response doPost() { + return getAuthenticationToken(HttpServletResponse.SC_SEE_OTHER); + } + + private Response getAuthenticationToken(int statusCode) { + GatewayServices services = (GatewayServices) request.getServletContext() + .getAttribute(GatewayServices.GATEWAY_SERVICES_ATTRIBUTE); + boolean removeOriginalUrlCookie = true; + String original = getCookieValue((HttpServletRequest) request, ORIGINAL_URL_COOKIE_NAME); + if (original == null) { + // in the case where there are no SAML redirects done before here + // we need to get it from the request parameters + removeOriginalUrlCookie = false; + original = getOriginalUrlFromQueryParams(); + if (original.isEmpty()) { + log.originalURLNotFound(); + throw new WebApplicationException("Original URL not found in the request.", Response.Status.BAD_REQUEST); + } + boolean validRedirect = RegExUtils.checkWhitelist(whitelist, original); + if (!validRedirect) { + log.whiteListMatchFail(original, whitelist); + throw new WebApplicationException("Original URL not valid according to the configured whitelist.", + Response.Status.BAD_REQUEST); + } + } + + JWTokenAuthority ts = services.getService(GatewayServices.TOKEN_SERVICE); + Principal p = ((HttpServletRequest)request).getUserPrincipal(); + + 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 + if( token != null ) { + addJWTHadoopCookie( original, token ); + } + + if (removeOriginalUrlCookie) { + removeOriginalUrlCookie(response); + } + + log.aboutToRedirectToOriginal(original); + response.setStatus(statusCode); + response.setHeader("Location", original); + try { + response.getOutputStream().close(); + } catch (IOException e) { + log.unableToCloseOutputStream(e.getMessage(), Arrays.toString(e.getStackTrace())); + } + } + catch (TokenServiceException e) { + log.unableToIssueToken(e); + } + URI location = null; + try { + location = new URI(original); + } + catch(URISyntaxException urise) { + // todo log return error response + } + + if (!enableSession) { + // invalidate the session to avoid autologin + // Coverity CID 1352857 + HttpSession session = request.getSession(false); + if( session != null ) { + session.invalidate(); + } + } + + return Response.seeOther(location).entity("{ \"redirectTo\" : " + original + " }").build(); + } + + private String getOriginalUrlFromQueryParams() { + String original = request.getParameter(ORIGINAL_URL_REQUEST_PARAM); + StringBuffer buf = new StringBuffer(original); + + // Add any other query params. + // Probably not ideal but will not break existing integrations by requiring + // some encoding. + Map<String, String[]> params = request.getParameterMap(); + for (Entry<String, String[]> entry : params.entrySet()) { + if (!ORIGINAL_URL_REQUEST_PARAM.equals(entry.getKey()) + && !original.contains(entry.getKey() + "=")) { + buf.append("&").append(entry.getKey()); + String[] values = entry.getValue(); + if (values.length > 0 && values[0] != null) { + buf.append("="); + } + for (int i = 0; i < values.length; i++) { + if (values[0] != null) { + buf.append(values[i]); + if (i < values.length-1) { + buf.append("&").append(entry.getKey()).append("="); + } + } + } + } + } + + return buf.toString(); + } + + private long getExpiry() { + long expiry = 0l; + if (tokenTTL == -1) { + expiry = -1; + } + else { + expiry = System.currentTimeMillis() + tokenTTL; + } + return expiry; + } + + private void addJWTHadoopCookie(String original, JWT token) { + log.addingJWTCookie(token.toString()); + Cookie c = new Cookie(cookieName, token.toString()); + c.setPath("/"); + try { + String domain = Urls.getDomainName(original, domainSuffix); + if (domain != null) { + c.setDomain(domain); + } + c.setHttpOnly(true); + if (secureOnly) { + c.setSecure(true); + } + if (maxAge != -1) { + c.setMaxAge(maxAge); + } + response.addCookie(c); + log.addedJWTCookie(); + } + catch(Exception e) { + log.unableAddCookieToResponse(e.getMessage(), Arrays.toString(e.getStackTrace())); + throw new WebApplicationException("Unable to add JWT cookie to response."); + } + } + + private void removeOriginalUrlCookie(HttpServletResponse response) { + Cookie c = new Cookie(ORIGINAL_URL_COOKIE_NAME, null); + c.setMaxAge(0); + c.setPath(RESOURCE_PATH); + response.addCookie(c); + } + + private String getCookieValue(HttpServletRequest request, String name) { + Cookie[] cookies = request.getCookies(); + String value = null; + if (cookies != null) { + for(Cookie cookie : cookies){ + if(name.equals(cookie.getName())){ + value = cookie.getValue(); + } + } + } + if (value == null) { + log.cookieNotFound(name); + } + return value; + } +} http://git-wip-us.apache.org/repos/asf/knox/blob/416ee7c1/gateway-service-knoxsso/src/test/java/org/apache/knox/gateway/service/knoxsso/WebSSOResourceTest.java ---------------------------------------------------------------------- diff --cc gateway-service-knoxsso/src/test/java/org/apache/knox/gateway/service/knoxsso/WebSSOResourceTest.java index 5b195e4,0000000..864440c mode 100644,000000..100644 --- a/gateway-service-knoxsso/src/test/java/org/apache/knox/gateway/service/knoxsso/WebSSOResourceTest.java +++ b/gateway-service-knoxsso/src/test/java/org/apache/knox/gateway/service/knoxsso/WebSSOResourceTest.java @@@ -1,71 -1,0 +1,352 @@@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.knox.gateway.service.knoxsso; + +import org.apache.knox.gateway.util.RegExUtils; ++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.knox.gateway.services.GatewayServices; ++import org.apache.knox.gateway.services.security.token.JWTokenAuthority; ++import org.apache.knox.gateway.services.security.token.TokenServiceException; ++import org.apache.knox.gateway.services.security.token.impl.JWT; ++import org.apache.knox.gateway.services.security.token.impl.JWTToken; ++import org.apache.knox.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/.*$;" + + "^https?://.*example.com/.*$;" + + "^https?://.*example2.com:\\d{0,9}/.*$;" + + "^https://.*example3.com:\\d{0,9}/.*$;" + + "^https?://localhost:\\d{0,9}/.*$;^/.*$"; + + // match on explicit hostname/domain and port + 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); ++ } ++ ++ } ++ +} http://git-wip-us.apache.org/repos/asf/knox/blob/416ee7c1/gateway-service-knoxtoken/src/main/java/org/apache/knox/gateway/service/knoxtoken/TokenResource.java ---------------------------------------------------------------------- diff --cc gateway-service-knoxtoken/src/main/java/org/apache/knox/gateway/service/knoxtoken/TokenResource.java index 9118d50,0000000..2c77bdf mode 100644,000000..100644 --- a/gateway-service-knoxtoken/src/main/java/org/apache/knox/gateway/service/knoxtoken/TokenResource.java +++ b/gateway-service-knoxtoken/src/main/java/org/apache/knox/gateway/service/knoxtoken/TokenResource.java @@@ -1,182 -1,0 +1,183 @@@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.knox.gateway.service.knoxtoken; + +import java.io.IOException; +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; +import javax.servlet.http.HttpServletResponse; +import javax.ws.rs.GET; +import javax.ws.rs.POST; +import javax.ws.rs.Path; +import javax.ws.rs.Produces; +import javax.ws.rs.core.Context; +import javax.ws.rs.core.Response; +import org.apache.knox.gateway.i18n.messages.MessagesFactory; +import org.apache.knox.gateway.services.GatewayServices; +import org.apache.knox.gateway.services.security.token.JWTokenAuthority; +import org.apache.knox.gateway.services.security.token.TokenServiceException; +import org.apache.knox.gateway.services.security.token.impl.JWT; +import org.apache.knox.gateway.util.JsonUtils; + +import static javax.ws.rs.core.MediaType.APPLICATION_JSON; +import static javax.ws.rs.core.MediaType.APPLICATION_XML; + +@Path( TokenResource.RESOURCE_PATH ) +public class TokenResource { + private static final String EXPIRES_IN = "expires_in"; + private static final String TOKEN_TYPE = "token_type"; + private static final String ACCESS_TOKEN = "access_token"; + private static final String TARGET_URL = "target_url"; + private static final String BEARER = "Bearer "; + private static final String TOKEN_TTL_PARAM = "knox.token.ttl"; + private static final String TOKEN_AUDIENCES_PARAM = "knox.token.audiences"; + private static final String TOKEN_TARGET_URL = "knox.token.target.url"; + private static final String TOKEN_CLIENT_DATA = "knox.token.client.data"; + 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; + + @PostConstruct + public void init() { + + 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); + if (ttl != null) { + try { + tokenTTL = Long.parseLong(ttl); + } + catch (NumberFormatException nfe) { + log.invalidTokenTTLEncountered(ttl); + } + } - ++ + tokenTargetUrl = context.getInitParameter(TOKEN_TARGET_URL); + + String clientData = context.getInitParameter(TOKEN_CLIENT_DATA); + if (clientData != null) { + tokenClientDataMap = new HashMap<>(); + String[] tokenClientData = clientData.split(","); + addClientDataToMap(tokenClientData, tokenClientDataMap); + } + } + + @GET + @Produces({APPLICATION_JSON, APPLICATION_XML}) + public Response doGet() { + return getAuthenticationToken(); + } + + @POST + @Produces({APPLICATION_JSON, APPLICATION_XML}) + public Response doPost() { + return getAuthenticationToken(); + } + + private Response getAuthenticationToken() { + GatewayServices services = (GatewayServices) request.getServletContext() + .getAttribute(GatewayServices.GATEWAY_SERVICES_ATTRIBUTE); + + 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); + map.put(EXPIRES_IN, expires); + if (tokenTargetUrl != null) { + map.put(TARGET_URL, tokenTargetUrl); + } + if (tokenClientDataMap != null) { + map.putAll(tokenClientDataMap); + } - ++ + String jsonResponse = JsonUtils.renderAsJsonString(map); + + response.getWriter().write(jsonResponse); + return Response.ok().build(); + } + else { + return Response.serverError().build(); + } + } + catch (TokenServiceException | IOException e) { + log.unableToIssueToken(e); + } + return Response.ok().entity("{ \"Unable to acquire token.\" }").build(); + } + + void addClientDataToMap(String[] tokenClientData, + Map<String,Object> map) { + String[] kv = null; + for (int i = 0; i < tokenClientData.length; i++) { + kv = tokenClientData[i].split("="); + if (kv.length == 2) { + map.put(kv[0], kv[1]); + } + } + } + + private long getExpiry() { + long expiry = 0l; + if (tokenTTL == -1) { + expiry = -1; + } + else { + expiry = System.currentTimeMillis() + tokenTTL; + } + return expiry; + } +}