You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@knox.apache.org by mo...@apache.org on 2017/09/20 14:36:36 UTC
[12/12] knox git commit: Merge remote-tracking branch 'origin/master'
into KNOX-998-Package_Restructuring
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 <mo...@apache.org>
Authored: Wed Sep 20 09:52:47 2017 -0400
Committer: Sandeep More <mo...@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;
+ }
+}