http://git-wip-us.apache.org/repos/asf/knox/blob/58780d37/gateway-server/src/test/java/org/apache/hadoop/gateway/services/token/impl/DefaultTokenAuthorityServiceTest.java ---------------------------------------------------------------------- diff --cc gateway-server/src/test/java/org/apache/hadoop/gateway/services/token/impl/DefaultTokenAuthorityServiceTest.java index eba98a4,48616c0..da55422 --- a/gateway-server/src/test/java/org/apache/hadoop/gateway/services/token/impl/DefaultTokenAuthorityServiceTest.java +++ b/gateway-server/src/test/java/org/apache/hadoop/gateway/services/token/impl/DefaultTokenAuthorityServiceTest.java @@@ -21,13 -21,14 +21,15 @@@ import java.io.File import java.security.Principal; import java.util.HashMap; -import org.apache.hadoop.gateway.config.GatewayConfig; -import org.apache.hadoop.gateway.services.security.AliasService; -import org.apache.hadoop.gateway.services.security.KeystoreService; -import org.apache.hadoop.gateway.services.security.MasterService; -import org.apache.hadoop.gateway.services.security.impl.DefaultKeystoreService; -import org.apache.hadoop.gateway.services.security.token.JWTokenAuthority; -import org.apache.hadoop.gateway.services.security.token.TokenServiceException; -import org.apache.hadoop.gateway.services.security.token.impl.JWT; +import org.apache.knox.gateway.config.GatewayConfig; +import org.apache.knox.gateway.services.security.AliasService; +import org.apache.knox.gateway.services.security.KeystoreService; +import org.apache.knox.gateway.services.security.MasterService; +import org.apache.knox.gateway.services.security.impl.DefaultKeystoreService; +import org.apache.knox.gateway.services.security.token.JWTokenAuthority; +import org.apache.knox.gateway.services.security.token.impl.JWT; ++import org.apache.knox.gateway.services.security.token.TokenServiceException; ++ import org.easymock.EasyMock; import org.junit.Test;
http://git-wip-us.apache.org/repos/asf/knox/blob/58780d37/gateway-server/src/test/java/org/apache/knox/gateway/topology/simple/SimpleDescriptorFactoryTest.java ---------------------------------------------------------------------- diff --cc gateway-server/src/test/java/org/apache/knox/gateway/topology/simple/SimpleDescriptorFactoryTest.java index efee1d8,0000000..41a7c10 mode 100644,000000..100644 --- a/gateway-server/src/test/java/org/apache/knox/gateway/topology/simple/SimpleDescriptorFactoryTest.java +++ b/gateway-server/src/test/java/org/apache/knox/gateway/topology/simple/SimpleDescriptorFactoryTest.java @@@ -1,218 -1,0 +1,422 @@@ +/** + * 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.topology.simple; + +import java.io.File; +import java.io.FileWriter; +import java.io.Writer; +import java.util.*; + +import org.junit.Test; +import static org.junit.Assert.*; + + +public class SimpleDescriptorFactoryTest { + + + @Test + public void testParseJSONSimpleDescriptor() throws Exception { + + final String discoveryType = "AMBARI"; + final String discoveryAddress = "http://c6401.ambari.apache.org:8080"; + final String discoveryUser = "admin"; + final String providerConfig = "ambari-cluster-policy.xml"; + final String clusterName = "myCluster"; + + final Map<String, List<String>> services = new HashMap<>(); + services.put("NODEMANAGER", null); + services.put("JOBTRACKER", null); + services.put("RESOURCEMANAGER", null); + services.put("HIVE", Arrays.asList("http://c6401.ambari.apache.org", "http://c6402.ambari.apache.org", "http://c6403.ambari.apache.org")); + services.put("AMBARIUI", Arrays.asList("http://c6401.ambari.apache.org:8080")); + + String fileName = "test-topology.json"; + File testJSON = null; + try { + testJSON = writeJSON(fileName, discoveryType, discoveryAddress, discoveryUser, providerConfig, clusterName, services); + SimpleDescriptor sd = SimpleDescriptorFactory.parse(testJSON.getAbsolutePath()); + validateSimpleDescriptor(sd, discoveryType, discoveryAddress, providerConfig, clusterName, services); + } catch (Exception e) { + e.printStackTrace(); + } finally { + if (testJSON != null) { + try { + testJSON.delete(); + } catch (Exception e) { + // Ignore + } + } + } + } + + @Test ++ public void testParseJSONSimpleDescriptorWithServiceParams() throws Exception { ++ ++ final String discoveryType = "AMBARI"; ++ final String discoveryAddress = "http://c6401.ambari.apache.org:8080"; ++ final String discoveryUser = "admin"; ++ final String providerConfig = "ambari-cluster-policy.xml"; ++ final String clusterName = "myCluster"; ++ ++ final Map<String, List<String>> services = new HashMap<>(); ++ services.put("NODEMANAGER", null); ++ services.put("JOBTRACKER", null); ++ services.put("RESOURCEMANAGER", null); ++ services.put("HIVE", Arrays.asList("http://c6401.ambari.apache.org", "http://c6402.ambari.apache.org", "http://c6403.ambari.apache.org")); ++ services.put("AMBARIUI", Collections.singletonList("http://c6401.ambari.apache.org:8080")); ++ services.put("KNOXSSO", null); ++ services.put("KNOXTOKEN", null); ++ services.put("CustomRole", Collections.singletonList("http://c6402.ambari.apache.org:1234")); ++ ++ final Map<String, Map<String, String>> serviceParams = new HashMap<>(); ++ Map<String, String> knoxSSOParams = new HashMap<>(); ++ knoxSSOParams.put("knoxsso.cookie.secure.only", "true"); ++ knoxSSOParams.put("knoxsso.token.ttl", "100000"); ++ serviceParams.put("KNOXSSO", knoxSSOParams); ++ ++ Map<String, String> knoxTokenParams = new HashMap<>(); ++ knoxTokenParams.put("knox.token.ttl", "36000000"); ++ knoxTokenParams.put("knox.token.audiences", "tokenbased"); ++ knoxTokenParams.put("knox.token.target.url", "https://localhost:8443/gateway/tokenbased"); ++ serviceParams.put("KNOXTOKEN", knoxTokenParams); ++ ++ Map<String, String> customRoleParams = new HashMap<>(); ++ customRoleParams.put("custom.param.1", "value1"); ++ customRoleParams.put("custom.param.2", "value2"); ++ serviceParams.put("CustomRole", customRoleParams); ++ ++ String fileName = "test-topology.json"; ++ File testJSON = null; ++ try { ++ testJSON = writeJSON(fileName, ++ discoveryType, ++ discoveryAddress, ++ discoveryUser, ++ providerConfig, ++ clusterName, ++ services, ++ serviceParams); ++ SimpleDescriptor sd = SimpleDescriptorFactory.parse(testJSON.getAbsolutePath()); ++ validateSimpleDescriptor(sd, discoveryType, discoveryAddress, providerConfig, clusterName, services, serviceParams); ++ } catch (Exception e) { ++ e.printStackTrace(); ++ } finally { ++ if (testJSON != null) { ++ try { ++ testJSON.delete(); ++ } catch (Exception e) { ++ // Ignore ++ } ++ } ++ } ++ } ++ ++ @Test + public void testParseYAMLSimpleDescriptor() throws Exception { + + final String discoveryType = "AMBARI"; + final String discoveryAddress = "http://c6401.ambari.apache.org:8080"; + final String discoveryUser = "joeblow"; + final String providerConfig = "ambari-cluster-policy.xml"; + final String clusterName = "myCluster"; + + final Map<String, List<String>> services = new HashMap<>(); + services.put("NODEMANAGER", null); + services.put("JOBTRACKER", null); + services.put("RESOURCEMANAGER", null); + services.put("HIVE", Arrays.asList("http://c6401.ambari.apache.org", "http://c6402.ambari.apache.org", "http://c6403.ambari.apache.org")); + services.put("AMBARIUI", Arrays.asList("http://c6401.ambari.apache.org:8080")); + + String fileName = "test-topology.yml"; + File testYAML = null; + try { + testYAML = writeYAML(fileName, discoveryType, discoveryAddress, discoveryUser, providerConfig, clusterName, services); + SimpleDescriptor sd = SimpleDescriptorFactory.parse(testYAML.getAbsolutePath()); + validateSimpleDescriptor(sd, discoveryType, discoveryAddress, providerConfig, clusterName, services); + } catch (Exception e) { + e.printStackTrace(); + } finally { + if (testYAML != null) { + try { + testYAML.delete(); + } catch (Exception e) { + // Ignore + } + } + } + } + + - private void validateSimpleDescriptor(SimpleDescriptor sd, - String discoveryType, - String discoveryAddress, - String providerConfig, - String clusterName, ++ @Test ++ public void testParseYAMLSimpleDescriptorWithServiceParams() throws Exception { ++ ++ final String discoveryType = "AMBARI"; ++ final String discoveryAddress = "http://c6401.ambari.apache.org:8080"; ++ final String discoveryUser = "joeblow"; ++ final String providerConfig = "ambari-cluster-policy.xml"; ++ final String clusterName = "myCluster"; ++ ++ final Map<String, List<String>> services = new HashMap<>(); ++ services.put("NODEMANAGER", null); ++ services.put("JOBTRACKER", null); ++ services.put("RESOURCEMANAGER", null); ++ services.put("HIVE", Arrays.asList("http://c6401.ambari.apache.org", "http://c6402.ambari.apache.org", "http://c6403.ambari.apache.org")); ++ services.put("AMBARIUI", Arrays.asList("http://c6401.ambari.apache.org:8080")); ++ services.put("KNOXSSO", null); ++ services.put("KNOXTOKEN", null); ++ services.put("CustomRole", Collections.singletonList("http://c6402.ambari.apache.org:1234")); ++ ++ final Map<String, Map<String, String>> serviceParams = new HashMap<>(); ++ Map<String, String> knoxSSOParams = new HashMap<>(); ++ knoxSSOParams.put("knoxsso.cookie.secure.only", "true"); ++ knoxSSOParams.put("knoxsso.token.ttl", "100000"); ++ serviceParams.put("KNOXSSO", knoxSSOParams); ++ ++ Map<String, String> knoxTokenParams = new HashMap<>(); ++ knoxTokenParams.put("knox.token.ttl", "36000000"); ++ knoxTokenParams.put("knox.token.audiences", "tokenbased"); ++ knoxTokenParams.put("knox.token.target.url", "https://localhost:8443/gateway/tokenbased"); ++ serviceParams.put("KNOXTOKEN", knoxTokenParams); ++ ++ Map<String, String> customRoleParams = new HashMap<>(); ++ customRoleParams.put("custom.param.1", "value1"); ++ customRoleParams.put("custom.param.2", "value2"); ++ serviceParams.put("CustomRole", customRoleParams); ++ ++ String fileName = "test-topology.yml"; ++ File testYAML = null; ++ try { ++ testYAML = writeYAML(fileName, discoveryType, discoveryAddress, discoveryUser, providerConfig, clusterName, services, serviceParams); ++ SimpleDescriptor sd = SimpleDescriptorFactory.parse(testYAML.getAbsolutePath()); ++ validateSimpleDescriptor(sd, discoveryType, discoveryAddress, providerConfig, clusterName, services, serviceParams); ++ } catch (Exception e) { ++ e.printStackTrace(); ++ } finally { ++ if (testYAML != null) { ++ try { ++ testYAML.delete(); ++ } catch (Exception e) { ++ // Ignore ++ } ++ } ++ } ++ } ++ ++ ++ private void validateSimpleDescriptor(SimpleDescriptor sd, ++ String discoveryType, ++ String discoveryAddress, ++ String providerConfig, ++ String clusterName, + Map<String, List<String>> expectedServices) { ++ validateSimpleDescriptor(sd, discoveryType, discoveryAddress, providerConfig, clusterName, expectedServices, null); ++ } ++ ++ ++ private void validateSimpleDescriptor(SimpleDescriptor sd, ++ String discoveryType, ++ String discoveryAddress, ++ String providerConfig, ++ String clusterName, ++ Map<String, List<String>> expectedServices, ++ Map<String, Map<String, String>> expectedServiceParameters) { + assertNotNull(sd); + assertEquals(discoveryType, sd.getDiscoveryType()); + assertEquals(discoveryAddress, sd.getDiscoveryAddress()); + assertEquals(providerConfig, sd.getProviderConfig()); + assertEquals(clusterName, sd.getClusterName()); + + List<SimpleDescriptor.Service> actualServices = sd.getServices(); + + assertEquals(expectedServices.size(), actualServices.size()); + + for (SimpleDescriptor.Service actualService : actualServices) { + assertTrue(expectedServices.containsKey(actualService.getName())); + assertEquals(expectedServices.get(actualService.getName()), actualService.getURLs()); ++ ++ // Validate service parameters ++ if (expectedServiceParameters != null) { ++ if (expectedServiceParameters.containsKey(actualService.getName())) { ++ Map<String, String> expectedParams = expectedServiceParameters.get(actualService.getName()); ++ ++ Map<String, String> actualServiceParams = actualService.getParams(); ++ assertNotNull(actualServiceParams); ++ ++ // Validate the size of the service parameter set ++ assertEquals(expectedParams.size(), actualServiceParams.size()); ++ ++ // Validate the parameter contents ++ for (String paramName : actualServiceParams.keySet()) { ++ assertTrue(expectedParams.containsKey(paramName)); ++ assertEquals(expectedParams.get(paramName), actualServiceParams.get(paramName)); ++ } ++ } ++ } + } + } + + + private File writeJSON(String path, String content) throws Exception { + File f = new File(path); + + Writer fw = new FileWriter(f); + fw.write(content); + fw.flush(); + fw.close(); + + return f; + } + + + private File writeJSON(String path, + String discoveryType, + String discoveryAddress, + String discoveryUser, + String providerConfig, + String clusterName, + Map<String, List<String>> services) throws Exception { ++ return writeJSON(path, discoveryType, discoveryAddress, discoveryUser, providerConfig, clusterName, services, null); ++ } ++ ++ private File writeJSON(String path, ++ String discoveryType, ++ String discoveryAddress, ++ String discoveryUser, ++ String providerConfig, ++ String clusterName, ++ Map<String, List<String>> services, ++ Map<String, Map<String, String>> serviceParams) throws Exception { + File f = new File(path); + + Writer fw = new FileWriter(f); + fw.write("{" + "\n"); + fw.write("\"discovery-type\":\"" + discoveryType + "\",\n"); + fw.write("\"discovery-address\":\"" + discoveryAddress + "\",\n"); + fw.write("\"discovery-user\":\"" + discoveryUser + "\",\n"); + fw.write("\"provider-config-ref\":\"" + providerConfig + "\",\n"); + fw.write("\"cluster\":\"" + clusterName + "\",\n"); + fw.write("\"services\":[\n"); + + int i = 0; + for (String name : services.keySet()) { + fw.write("{\"name\":\"" + name + "\""); ++ ++ // Service params ++ if (serviceParams != null && !serviceParams.isEmpty()) { ++ Map<String, String> params = serviceParams.get(name); ++ if (params != null && !params.isEmpty()) { ++ fw.write(",\n\"params\":{\n"); ++ Iterator<String> paramNames = params.keySet().iterator(); ++ while (paramNames.hasNext()) { ++ String paramName = paramNames.next(); ++ String paramValue = params.get(paramName); ++ fw.write("\"" + paramName + "\":\"" + paramValue + "\""); ++ fw.write(paramNames.hasNext() ? ",\n" : ""); ++ } ++ fw.write("\n}"); ++ } ++ } ++ ++ // Service URLs + List<String> urls = services.get(name); + if (urls != null) { - fw.write(", \"urls\":["); ++ fw.write(",\n\"urls\":["); + Iterator<String> urlIter = urls.iterator(); + while (urlIter.hasNext()) { + fw.write("\"" + urlIter.next() + "\""); + if (urlIter.hasNext()) { + fw.write(", "); + } + } - fw.write("]"); ++ fw.write("]\n"); + } ++ + fw.write("}"); + if (i++ < services.size() - 1) { + fw.write(","); + } + fw.write("\n"); + } + fw.write("]\n"); + fw.write("}\n"); + fw.flush(); + fw.close(); + + return f; + } + - private File writeYAML(String path, - String discoveryType, - String discoveryAddress, - String discoveryUser, - String providerConfig, - String clusterName, ++ ++ private File writeYAML(String path, ++ String discoveryType, ++ String discoveryAddress, ++ String discoveryUser, ++ String providerConfig, ++ String clusterName, + Map<String, List<String>> services) throws Exception { ++ return writeYAML(path, discoveryType, discoveryAddress, discoveryUser, providerConfig, clusterName, services, null); ++ } ++ ++ ++ private File writeYAML(String path, ++ String discoveryType, ++ String discoveryAddress, ++ String discoveryUser, ++ String providerConfig, ++ String clusterName, ++ Map<String, List<String>> services, ++ Map<String, Map<String, String>> serviceParams) throws Exception { + File f = new File(path); + + Writer fw = new FileWriter(f); + fw.write("---" + "\n"); + fw.write("discovery-type: " + discoveryType + "\n"); + fw.write("discovery-address: " + discoveryAddress + "\n"); + fw.write("discovery-user: " + discoveryUser + "\n"); + fw.write("provider-config-ref: " + providerConfig + "\n"); + fw.write("cluster: " + clusterName+ "\n"); + fw.write("services:\n"); + for (String name : services.keySet()) { + fw.write(" - name: " + name + "\n"); ++ ++ // Service params ++ if (serviceParams != null && !serviceParams.isEmpty()) { ++ if (serviceParams.containsKey(name)) { ++ Map<String, String> params = serviceParams.get(name); ++ fw.write(" params:\n"); ++ for (String paramName : params.keySet()) { ++ fw.write(" " + paramName + ": " + params.get(paramName) + "\n"); ++ } ++ } ++ } ++ ++ // Service URLs + List<String> urls = services.get(name); + if (urls != null) { + fw.write(" urls:\n"); + for (String url : urls) { + fw.write(" - " + url + "\n"); + } + } + } + fw.flush(); + fw.close(); + + return f; + } + + +} http://git-wip-us.apache.org/repos/asf/knox/blob/58780d37/gateway-server/src/test/java/org/apache/knox/gateway/topology/simple/SimpleDescriptorHandlerTest.java ---------------------------------------------------------------------- diff --cc gateway-server/src/test/java/org/apache/knox/gateway/topology/simple/SimpleDescriptorHandlerTest.java index b5558fd,0000000..a0c977a mode 100644,000000..100644 --- a/gateway-server/src/test/java/org/apache/knox/gateway/topology/simple/SimpleDescriptorHandlerTest.java +++ b/gateway-server/src/test/java/org/apache/knox/gateway/topology/simple/SimpleDescriptorHandlerTest.java @@@ -1,392 -1,0 +1,447 @@@ +/** + * 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.topology.simple; + +import org.apache.knox.gateway.topology.validation.TopologyValidator; +import org.apache.knox.gateway.util.XmlUtils; +import java.io.ByteArrayInputStream; +import java.io.File; ++import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Properties; + +import javax.xml.xpath.XPath; +import javax.xml.xpath.XPathConstants; +import javax.xml.xpath.XPathFactory; + +import org.apache.commons.io.FileUtils; +import org.easymock.EasyMock; +import org.junit.Test; +import org.w3c.dom.Document; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; +import org.xml.sax.SAXException; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + + +public class SimpleDescriptorHandlerTest { + + private static final String TEST_PROVIDER_CONFIG = + " <gateway>\n" + + " <provider>\n" + + " <role>authentication</role>\n" + + " <name>ShiroProvider</name>\n" + + " <enabled>true</enabled>\n" + + " <param>\n" + + " <!-- \n" + + " session timeout in minutes, this is really idle timeout,\n" + + " defaults to 30mins, if the property value is not defined,, \n" + + " current client authentication would expire if client idles contiuosly for more than this value\n" + + " -->\n" + + " <name>sessionTimeout</name>\n" + + " <value>30</value>\n" + + " </param>\n" + + " <param>\n" + + " <name>main.ldapRealm</name>\n" + + " <value>org.apache.knox.gateway.shirorealm.KnoxLdapRealm</value>\n" + + " </param>\n" + + " <param>\n" + + " <name>main.ldapContextFactory</name>\n" + + " <value>org.apache.knox.gateway.shirorealm.KnoxLdapContextFactory</value>\n" + + " </param>\n" + + " <param>\n" + + " <name>main.ldapRealm.contextFactory</name>\n" + + " <value>$ldapContextFactory</value>\n" + + " </param>\n" + + " <param>\n" + + " <name>main.ldapRealm.userDnTemplate</name>\n" + + " <value>uid={0},ou=people,dc=hadoop,dc=apache,dc=org</value>\n" + + " </param>\n" + + " <param>\n" + + " <name>main.ldapRealm.contextFactory.url</name>\n" + + " <value>ldap://localhost:33389</value>\n" + + " </param>\n" + + " <param>\n" + + " <name>main.ldapRealm.contextFactory.authenticationMechanism</name>\n" + + " <value>simple</value>\n" + + " </param>\n" + + " <param>\n" + + " <name>urls./**</name>\n" + + " <value>authcBasic</value>\n" + + " </param>\n" + + " </provider>\n" + + "\n" + + " <provider>\n" + + " <role>identity-assertion</role>\n" + + " <name>Default</name>\n" + + " <enabled>true</enabled>\n" + + " </provider>\n" + + "\n" + + " <!--\n" + + " Defines rules for mapping host names internal to a Hadoop cluster to externally accessible host names.\n" + + " For example, a hadoop service running in AWS may return a response that includes URLs containing the\n" + + " some AWS internal host name. If the client needs to make a subsequent request to the host identified\n" + + " in those URLs they need to be mapped to external host names that the client Knox can use to connect.\n" + + "\n" + + " If the external hostname and internal host names are same turn of this provider by setting the value of\n" + + " enabled parameter as false.\n" + + "\n" + + " The name parameter specifies the external host names in a comma separated list.\n" + + " The value parameter specifies corresponding internal host names in a comma separated list.\n" + + "\n" + + " Note that when you are using Sandbox, the external hostname needs to be localhost, as seen in out\n" + + " of box sandbox.xml. This is because Sandbox uses port mapping to allow clients to connect to the\n" + + " Hadoop services using localhost. In real clusters, external host names would almost never be localhost.\n" + + " -->\n" + + " <provider>\n" + + " <role>hostmap</role>\n" + + " <name>static</name>\n" + + " <enabled>true</enabled>\n" + + " <param><name>localhost</name><value>sandbox,sandbox.hortonworks.com</value></param>\n" + + " </provider>\n" + + " </gateway>\n"; + + + /** + * KNOX-1006 + * - * N.B. This test depends on the DummyServiceDiscovery extension being configured: - * org.apache.knox.gateway.topology.discovery.test.extension.DummyServiceDiscovery ++ * N.B. This test depends on the PropertiesFileServiceDiscovery extension being configured: ++ * org.apache.knox.gateway.topology.discovery.test.extension.PropertiesFileServiceDiscovery + */ + @Test + public void testSimpleDescriptorHandler() throws Exception { + - final String type = "DUMMY"; - final String address = "http://c6401.ambari.apache.org:8080"; ++ final String type = "PROPERTIES_FILE"; + final String clusterName = "dummy"; ++ ++ // Create a properties file to be the source of service discovery details for this test ++ final File discoveryConfig = File.createTempFile(getClass().getName() + "_discovery-config", ".properties"); ++ ++ final String address = discoveryConfig.getAbsolutePath(); ++ ++ final Properties DISCOVERY_PROPERTIES = new Properties(); ++ DISCOVERY_PROPERTIES.setProperty(clusterName + ".name", clusterName); ++ DISCOVERY_PROPERTIES.setProperty(clusterName + ".NAMENODE", "hdfs://namenodehost:8020"); ++ DISCOVERY_PROPERTIES.setProperty(clusterName + ".JOBTRACKER", "rpc://jobtrackerhostname:8050"); ++ DISCOVERY_PROPERTIES.setProperty(clusterName + ".WEBHDFS", "http://webhdfshost:1234"); ++ DISCOVERY_PROPERTIES.setProperty(clusterName + ".WEBHCAT", "http://webhcathost:50111/templeton"); ++ DISCOVERY_PROPERTIES.setProperty(clusterName + ".OOZIE", "http://ooziehost:11000/oozie"); ++ DISCOVERY_PROPERTIES.setProperty(clusterName + ".WEBHBASE", "http://webhbasehost:1234"); ++ DISCOVERY_PROPERTIES.setProperty(clusterName + ".HIVE", "http://hivehostname:10001/clipath"); ++ DISCOVERY_PROPERTIES.setProperty(clusterName + ".RESOURCEMANAGER", "http://remanhost:8088/ws"); ++ ++ try { ++ DISCOVERY_PROPERTIES.store(new FileOutputStream(discoveryConfig), null); ++ } catch (FileNotFoundException e) { ++ fail(e.getMessage()); ++ } ++ + final Map<String, List<String>> serviceURLs = new HashMap<>(); + serviceURLs.put("NAMENODE", null); + serviceURLs.put("JOBTRACKER", null); + serviceURLs.put("WEBHDFS", null); + serviceURLs.put("WEBHCAT", null); + serviceURLs.put("OOZIE", null); + serviceURLs.put("WEBHBASE", null); + serviceURLs.put("HIVE", null); + serviceURLs.put("RESOURCEMANAGER", null); + serviceURLs.put("AMBARIUI", Collections.singletonList("http://c6401.ambari.apache.org:8080")); ++ serviceURLs.put("KNOXSSO", null); + + // Write the externalized provider config to a temp file - File providerConfig = writeProviderConfig("ambari-cluster-policy.xml", TEST_PROVIDER_CONFIG); ++ File providerConfig = new File(System.getProperty("java.io.tmpdir"), "ambari-cluster-policy.xml"); ++ FileUtils.write(providerConfig, TEST_PROVIDER_CONFIG); + + File topologyFile = null; + try { - File destDir = (new File(".")).getCanonicalFile(); ++ File destDir = new File(System.getProperty("java.io.tmpdir")).getCanonicalFile(); ++ ++ Map<String, Map<String, String>> serviceParameters = new HashMap<>(); ++ Map<String, String> knoxssoParams = new HashMap<>(); ++ knoxssoParams.put("knoxsso.cookie.secure.only", "true"); ++ knoxssoParams.put("knoxsso.token.ttl", "100000"); ++ serviceParameters.put("KNOXSSO", knoxssoParams); + + // Mock out the simple descriptor + SimpleDescriptor testDescriptor = EasyMock.createNiceMock(SimpleDescriptor.class); + EasyMock.expect(testDescriptor.getName()).andReturn("mysimpledescriptor").anyTimes(); + EasyMock.expect(testDescriptor.getDiscoveryAddress()).andReturn(address).anyTimes(); + EasyMock.expect(testDescriptor.getDiscoveryType()).andReturn(type).anyTimes(); + EasyMock.expect(testDescriptor.getDiscoveryUser()).andReturn(null).anyTimes(); + EasyMock.expect(testDescriptor.getProviderConfig()).andReturn(providerConfig.getAbsolutePath()).anyTimes(); + EasyMock.expect(testDescriptor.getClusterName()).andReturn(clusterName).anyTimes(); + List<SimpleDescriptor.Service> serviceMocks = new ArrayList<>(); + for (String serviceName : serviceURLs.keySet()) { + SimpleDescriptor.Service svc = EasyMock.createNiceMock(SimpleDescriptor.Service.class); + EasyMock.expect(svc.getName()).andReturn(serviceName).anyTimes(); + EasyMock.expect(svc.getURLs()).andReturn(serviceURLs.get(serviceName)).anyTimes(); ++ EasyMock.expect(svc.getParams()).andReturn(serviceParameters.get(serviceName)).anyTimes(); + EasyMock.replay(svc); + serviceMocks.add(svc); + } + EasyMock.expect(testDescriptor.getServices()).andReturn(serviceMocks).anyTimes(); + EasyMock.replay(testDescriptor); + + // Invoke the simple descriptor handler + Map<String, File> files = + SimpleDescriptorHandler.handle(testDescriptor, + providerConfig.getParentFile(), // simple desc co-located with provider config + destDir); + topologyFile = files.get("topology"); + + // Validate the resulting topology descriptor + assertTrue(topologyFile.exists()); + + // Validate the topology descriptor's correctness + TopologyValidator validator = new TopologyValidator( topologyFile.getAbsolutePath() ); + if( !validator.validateTopology() ){ + throw new SAXException( validator.getErrorString() ); + } + + XPathFactory xPathfactory = XPathFactory.newInstance(); + XPath xpath = xPathfactory.newXPath(); + + // Parse the topology descriptor + Document topologyXml = XmlUtils.readXml(topologyFile); + + // Validate the provider configuration + Document extProviderConf = XmlUtils.readXml(new ByteArrayInputStream(TEST_PROVIDER_CONFIG.getBytes())); + Node gatewayNode = (Node) xpath.compile("/topology/gateway").evaluate(topologyXml, XPathConstants.NODE); + assertTrue("Resulting provider config should be identical to the referenced content.", + extProviderConf.getDocumentElement().isEqualNode(gatewayNode)); + + // Validate the service declarations + Map<String, List<String>> topologyServiceURLs = new HashMap<>(); + NodeList serviceNodes = + (NodeList) xpath.compile("/topology/service").evaluate(topologyXml, XPathConstants.NODESET); + for (int serviceNodeIndex=0; serviceNodeIndex < serviceNodes.getLength(); serviceNodeIndex++) { + Node serviceNode = serviceNodes.item(serviceNodeIndex); ++ ++ // Validate the role + Node roleNode = (Node) xpath.compile("role/text()").evaluate(serviceNode, XPathConstants.NODE); + assertNotNull(roleNode); + String role = roleNode.getNodeValue(); ++ ++ // Validate the URLs + NodeList urlNodes = (NodeList) xpath.compile("url/text()").evaluate(serviceNode, XPathConstants.NODESET); + for(int urlNodeIndex = 0 ; urlNodeIndex < urlNodes.getLength(); urlNodeIndex++) { + Node urlNode = urlNodes.item(urlNodeIndex); + assertNotNull(urlNode); + String url = urlNode.getNodeValue(); - assertNotNull("Every declared service should have a URL.", url); - if (!topologyServiceURLs.containsKey(role)) { - topologyServiceURLs.put(role, new ArrayList<String>()); ++ ++ // If the service should have a URL (some don't require it) ++ if (serviceURLs.containsKey(role)) { ++ assertNotNull("Declared service should have a URL.", url); ++ if (!topologyServiceURLs.containsKey(role)) { ++ topologyServiceURLs.put(role, new ArrayList<>()); ++ } ++ topologyServiceURLs.get(role).add(url); // Add it for validation later + } - topologyServiceURLs.get(role).add(url); + } ++ ++ // If params were declared in the descriptor, then validate them in the resulting topology file ++ Map<String, String> params = serviceParameters.get(role); ++ if (params != null) { ++ NodeList paramNodes = (NodeList) xpath.compile("param").evaluate(serviceNode, XPathConstants.NODESET); ++ for (int paramNodeIndex = 0; paramNodeIndex < paramNodes.getLength(); paramNodeIndex++) { ++ Node paramNode = paramNodes.item(paramNodeIndex); ++ String paramName = (String) xpath.compile("name/text()").evaluate(paramNode, XPathConstants.STRING); ++ String paramValue = (String) xpath.compile("value/text()").evaluate(paramNode, XPathConstants.STRING); ++ assertTrue(params.keySet().contains(paramName)); ++ assertEquals(params.get(paramName), paramValue); ++ } ++ } ++ + } - assertEquals("Unexpected number of service declarations.", serviceURLs.size(), topologyServiceURLs.size()); ++ assertEquals("Unexpected number of service declarations.", (serviceURLs.size() - 1), topologyServiceURLs.size()); + + } catch (Exception e) { + e.printStackTrace(); + fail(e.getMessage()); + } finally { + providerConfig.delete(); ++ discoveryConfig.delete(); + if (topologyFile != null) { + topologyFile.delete(); + } + } + } + + + /** + * KNOX-1006 + * + * Verify the behavior of the SimpleDescriptorHandler when service discovery fails to produce a valid URL for + * a service. + * + * N.B. This test depends on the PropertiesFileServiceDiscovery extension being configured: + * org.apache.hadoop.gateway.topology.discovery.test.extension.PropertiesFileServiceDiscovery + */ + @Test + public void testInvalidServiceURLFromDiscovery() throws Exception { + final String CLUSTER_NAME = "myproperties"; + + // Configure the PropertiesFile Service Discovery implementation for this test + final String DEFAULT_VALID_SERVICE_URL = "http://localhost:9999/thiswillwork"; + Properties serviceDiscoverySourceProps = new Properties(); + serviceDiscoverySourceProps.setProperty(CLUSTER_NAME + ".NAMENODE", + DEFAULT_VALID_SERVICE_URL.replace("http", "hdfs")); + serviceDiscoverySourceProps.setProperty(CLUSTER_NAME + ".JOBTRACKER", + DEFAULT_VALID_SERVICE_URL.replace("http", "rpc")); + serviceDiscoverySourceProps.setProperty(CLUSTER_NAME + ".WEBHDFS", DEFAULT_VALID_SERVICE_URL); + serviceDiscoverySourceProps.setProperty(CLUSTER_NAME + ".WEBHCAT", DEFAULT_VALID_SERVICE_URL); + serviceDiscoverySourceProps.setProperty(CLUSTER_NAME + ".OOZIE", DEFAULT_VALID_SERVICE_URL); + serviceDiscoverySourceProps.setProperty(CLUSTER_NAME + ".WEBHBASE", DEFAULT_VALID_SERVICE_URL); + serviceDiscoverySourceProps.setProperty(CLUSTER_NAME + ".HIVE", "{SCHEME}://localhost:10000/"); + serviceDiscoverySourceProps.setProperty(CLUSTER_NAME + ".RESOURCEMANAGER", DEFAULT_VALID_SERVICE_URL); + serviceDiscoverySourceProps.setProperty(CLUSTER_NAME + ".AMBARIUI", DEFAULT_VALID_SERVICE_URL); + File serviceDiscoverySource = File.createTempFile("service-discovery", ".properties"); + serviceDiscoverySourceProps.store(new FileOutputStream(serviceDiscoverySource), + "Test Service Discovery Source"); + + // Prepare a mock SimpleDescriptor + final String type = "PROPERTIES_FILE"; + final String address = serviceDiscoverySource.getAbsolutePath(); + final Map<String, List<String>> serviceURLs = new HashMap<>(); + serviceURLs.put("NAMENODE", null); + serviceURLs.put("JOBTRACKER", null); + serviceURLs.put("WEBHDFS", null); + serviceURLs.put("WEBHCAT", null); + serviceURLs.put("OOZIE", null); + serviceURLs.put("WEBHBASE", null); + serviceURLs.put("HIVE", null); + serviceURLs.put("RESOURCEMANAGER", null); + serviceURLs.put("AMBARIUI", Collections.singletonList("http://c6401.ambari.apache.org:8080")); + + // Write the externalized provider config to a temp file + File providerConfig = writeProviderConfig("ambari-cluster-policy.xml", TEST_PROVIDER_CONFIG); + + File topologyFile = null; + try { + File destDir = (new File(".")).getCanonicalFile(); + + // Mock out the simple descriptor + SimpleDescriptor testDescriptor = EasyMock.createNiceMock(SimpleDescriptor.class); + EasyMock.expect(testDescriptor.getName()).andReturn("mysimpledescriptor").anyTimes(); + EasyMock.expect(testDescriptor.getDiscoveryAddress()).andReturn(address).anyTimes(); + EasyMock.expect(testDescriptor.getDiscoveryType()).andReturn(type).anyTimes(); + EasyMock.expect(testDescriptor.getDiscoveryUser()).andReturn(null).anyTimes(); + EasyMock.expect(testDescriptor.getProviderConfig()).andReturn(providerConfig.getAbsolutePath()).anyTimes(); + EasyMock.expect(testDescriptor.getClusterName()).andReturn(CLUSTER_NAME).anyTimes(); + List<SimpleDescriptor.Service> serviceMocks = new ArrayList<>(); + for (String serviceName : serviceURLs.keySet()) { + SimpleDescriptor.Service svc = EasyMock.createNiceMock(SimpleDescriptor.Service.class); + EasyMock.expect(svc.getName()).andReturn(serviceName).anyTimes(); + EasyMock.expect(svc.getURLs()).andReturn(serviceURLs.get(serviceName)).anyTimes(); + EasyMock.replay(svc); + serviceMocks.add(svc); + } + EasyMock.expect(testDescriptor.getServices()).andReturn(serviceMocks).anyTimes(); + EasyMock.replay(testDescriptor); + + // Invoke the simple descriptor handler + Map<String, File> files = + SimpleDescriptorHandler.handle(testDescriptor, + providerConfig.getParentFile(), // simple desc co-located with provider config + destDir); + + topologyFile = files.get("topology"); + + // Validate the resulting topology descriptor + assertTrue(topologyFile.exists()); + + // Validate the topology descriptor's correctness + TopologyValidator validator = new TopologyValidator( topologyFile.getAbsolutePath() ); + if( !validator.validateTopology() ){ + throw new SAXException( validator.getErrorString() ); + } + + XPathFactory xPathfactory = XPathFactory.newInstance(); + XPath xpath = xPathfactory.newXPath(); + + // Parse the topology descriptor + Document topologyXml = XmlUtils.readXml(topologyFile); + + // Validate the provider configuration + Document extProviderConf = XmlUtils.readXml(new ByteArrayInputStream(TEST_PROVIDER_CONFIG.getBytes())); + Node gatewayNode = (Node) xpath.compile("/topology/gateway").evaluate(topologyXml, XPathConstants.NODE); + assertTrue("Resulting provider config should be identical to the referenced content.", + extProviderConf.getDocumentElement().isEqualNode(gatewayNode)); + + // Validate the service declarations + List<String> topologyServices = new ArrayList<>(); + Map<String, List<String>> topologyServiceURLs = new HashMap<>(); + NodeList serviceNodes = + (NodeList) xpath.compile("/topology/service").evaluate(topologyXml, XPathConstants.NODESET); + for (int serviceNodeIndex=0; serviceNodeIndex < serviceNodes.getLength(); serviceNodeIndex++) { + Node serviceNode = serviceNodes.item(serviceNodeIndex); + Node roleNode = (Node) xpath.compile("role/text()").evaluate(serviceNode, XPathConstants.NODE); + assertNotNull(roleNode); + String role = roleNode.getNodeValue(); + topologyServices.add(role); + NodeList urlNodes = (NodeList) xpath.compile("url/text()").evaluate(serviceNode, XPathConstants.NODESET); + for(int urlNodeIndex = 0 ; urlNodeIndex < urlNodes.getLength(); urlNodeIndex++) { + Node urlNode = urlNodes.item(urlNodeIndex); + assertNotNull(urlNode); + String url = urlNode.getNodeValue(); + assertNotNull("Every declared service should have a URL.", url); + if (!topologyServiceURLs.containsKey(role)) { - topologyServiceURLs.put(role, new ArrayList<String>()); ++ topologyServiceURLs.put(role, new ArrayList<>()); + } + topologyServiceURLs.get(role).add(url); + } + } + + // There should not be a service element for HIVE, since it had no valid URLs + assertEquals("Unexpected number of service declarations.", serviceURLs.size() - 1, topologyServices.size()); + assertFalse("The HIVE service should have been omitted from the generated topology.", topologyServices.contains("HIVE")); + + assertEquals("Unexpected number of service URLs.", serviceURLs.size() - 1, topologyServiceURLs.size()); + + } catch (Exception e) { + e.printStackTrace(); + fail(e.getMessage()); + } finally { + serviceDiscoverySource.delete(); + providerConfig.delete(); + if (topologyFile != null) { + topologyFile.delete(); + } + } + } + + + private File writeProviderConfig(String path, String content) throws IOException { + File f = new File(path); + FileUtils.write(f, content); + return f; + } + +} http://git-wip-us.apache.org/repos/asf/knox/blob/58780d37/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 a97cee2,0000000..a103dac 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,334 @@@ +/** + * 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_SIG_ALG = "knoxsso.token.sigalg"; + 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}/.*$"; ++ private static final long TOKEN_TTL_DEFAULT = 30000L; + 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 long tokenTTL = TOKEN_TTL_DEFAULT; + private String whitelist = null; + private String domainSuffix = null; + private List<String> targetAudiences = new ArrayList<>(); + private boolean enableSession = false; ++ private String signatureAlgorithm = "RS256"; + + @Context + HttpServletRequest request; + + @Context + 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) { + String[] auds = audiences.split(","); + for (int i = 0; i < auds.length; i++) { + targetAudiences.add(auds[i].trim()); + } + } + + String ttl = context.getInitParameter(SSO_COOKIE_TOKEN_TTL_PARAM); + if (ttl != null) { + try { + tokenTTL = Long.parseLong(ttl); ++ if (tokenTTL < -1 || (tokenTTL + System.currentTimeMillis() < 0)) { ++ log.invalidTokenTTLEncountered(ttl); ++ tokenTTL = TOKEN_TTL_DEFAULT; ++ } + } + catch (NumberFormatException nfe) { + log.invalidTokenTTLEncountered(ttl); + } + } + + String enableSession = context.getInitParameter(SSO_ENABLE_SESSION_PARAM); + this.enableSession = ("true".equals(enableSession)); ++ ++ String sigAlg = context.getInitParameter(SSO_COOKIE_TOKEN_SIG_ALG); ++ if (sigAlg != null) { ++ signatureAlgorithm = sigAlg; ++ } + } + + @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.isEmpty()) { - token = ts.issueToken(p, "RS256", getExpiry()); ++ token = ts.issueToken(p, signatureAlgorithm, getExpiry()); + } else { - token = ts.issueToken(p, targetAudiences, "RS256", getExpiry()); ++ token = ts.issueToken(p, targetAudiences, signatureAlgorithm, 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; + } +}