Merge branch 'master' into KNOX-998-Package_Restructuring # Conflicts: # gateway-demo-ldap/src/main/java/org/apache/knox/gateway/security/ldap/BaseDirectoryService.java # gateway-demo-ldap/src/main/java/org/apache/knox/gateway/security/ldap/BaseDirectoryServiceFactory.java # gateway-demo-ldap/src/main/java/org/apache/knox/gateway/security/ldap/SimpleDirectoryServiceFactory.java # gateway-server/src/main/java/org/apache/knox/gateway/GatewayFilter.java # gateway-server/src/test/java/org/apache/knox/gateway/GatewayFilterTest.java # gateway-server/src/test/java/org/apache/knox/gateway/services/topology/DefaultTopologyServiceTest.java # gateway-service-admin/src/main/java/org/apache/knox/gateway/service/admin/TopologiesResource.java # gateway-test/src/test/java/org/apache/knox/gateway/GatewayAdminTopologyFuncTest.java
Project: http://git-wip-us.apache.org/repos/asf/knox/repo Commit: http://git-wip-us.apache.org/repos/asf/knox/commit/c754cc06 Tree: http://git-wip-us.apache.org/repos/asf/knox/tree/c754cc06 Diff: http://git-wip-us.apache.org/repos/asf/knox/diff/c754cc06 Branch: refs/heads/master Commit: c754cc06ac33c7cfff28c47ec562d888241c2641 Parents: 9577842 11ec78a Author: Sandeep More <[email protected]> Authored: Wed Nov 1 17:10:14 2017 -0400 Committer: Sandeep More <[email protected]> Committed: Wed Nov 1 17:10:14 2017 -0400 ---------------------------------------------------------------------- gateway-demo-ldap/pom.xml | 36 +- .../security/ldap/BaseDirectoryService.java | 2323 ------------------ .../ldap/BaseDirectoryServiceFactory.java | 290 --- .../security/ldap/SimpleDirectoryService.java | 6 +- .../ldap/SimpleDirectoryServiceFactory.java | 34 - .../ldap/SimpleLdapDirectoryServer.java | 38 +- .../ambari/AmbariServiceDiscovery.java | 3 +- .../filter/RegexIdentityAssertionFilter.java | 4 +- .../regex/filter/RegexTemplate.java | 12 +- .../regex/filter/RegexTemplateTest.java | 23 +- .../webappsec/filter/StrictTranportFilter.java | 137 ++ .../webappsec/deploy/WebAppSecContributor.java | 11 + .../webappsec/StrictTranportFilterTest.java | 164 ++ .../home/conf/topologies/manager.xml | 1 + gateway-release/home/templates/sandbox-apps.xml | 1 + .../org/apache/knox/gateway/GatewayFilter.java | 65 +- .../apache/knox/gateway/GatewayMessages.java | 34 +- .../gateway/config/impl/GatewayConfigImpl.java | 3 +- .../topology/impl/DefaultTopologyService.java | 221 +- .../builder/BeanPropertyTopologyBuilder.java | 11 + .../xml/KnoxFormatXmlTopologyRules.java | 2 + .../src/main/resources/conf/topology-v1.xsd | 1 + .../apache/knox/gateway/GatewayFilterTest.java | 49 + .../topology/DefaultTopologyServiceTest.java | 404 ++- .../topology/file/provider-config-one.xml | 74 + .../topology/file/simple-descriptor-five.json | 14 + .../topology/file/simple-descriptor-six.json | 18 + .../service/admin/HrefListingMarshaller.java | 75 + .../service/admin/TopologiesResource.java | 393 ++- .../service/admin/beans/BeanConverter.java | 2 + .../gateway/service/admin/beans/Topology.java | 11 + .../services/ambariui/2.2.1/rewrite.xml | 104 + .../services/ambariui/2.2.1/service.xml | 92 + .../knox/gateway/i18n/GatewaySpiMessages.java | 10 +- .../services/topology/TopologyService.java | 33 +- .../apache/knox/gateway/topology/Topology.java | 9 + .../gateway/topology/topology_binding-xml.xml | 5 +- gateway-test-release/pom.xml | 11 - gateway-test/pom.xml | 14 - .../gateway/GatewayAdminTopologyFuncTest.java | 586 +++++ pom.xml | 8 +- 41 files changed, 2495 insertions(+), 2837 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/knox/blob/c754cc06/gateway-demo-ldap/pom.xml ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/knox/blob/c754cc06/gateway-demo-ldap/src/main/java/org/apache/knox/gateway/security/ldap/BaseDirectoryService.java ---------------------------------------------------------------------- diff --cc gateway-demo-ldap/src/main/java/org/apache/knox/gateway/security/ldap/BaseDirectoryService.java index 53add76,0000000..e69de29 mode 100644,000000..100644 --- a/gateway-demo-ldap/src/main/java/org/apache/knox/gateway/security/ldap/BaseDirectoryService.java +++ b/gateway-demo-ldap/src/main/java/org/apache/knox/gateway/security/ldap/BaseDirectoryService.java http://git-wip-us.apache.org/repos/asf/knox/blob/c754cc06/gateway-demo-ldap/src/main/java/org/apache/knox/gateway/security/ldap/BaseDirectoryServiceFactory.java ---------------------------------------------------------------------- diff --cc gateway-demo-ldap/src/main/java/org/apache/knox/gateway/security/ldap/BaseDirectoryServiceFactory.java index aed78bf,0000000..e69de29 mode 100644,000000..100644 --- a/gateway-demo-ldap/src/main/java/org/apache/knox/gateway/security/ldap/BaseDirectoryServiceFactory.java +++ b/gateway-demo-ldap/src/main/java/org/apache/knox/gateway/security/ldap/BaseDirectoryServiceFactory.java http://git-wip-us.apache.org/repos/asf/knox/blob/c754cc06/gateway-demo-ldap/src/main/java/org/apache/knox/gateway/security/ldap/SimpleDirectoryService.java ---------------------------------------------------------------------- diff --cc gateway-demo-ldap/src/main/java/org/apache/knox/gateway/security/ldap/SimpleDirectoryService.java index 69cdb3c,0000000..4e843a5 mode 100644,000000..100644 --- a/gateway-demo-ldap/src/main/java/org/apache/knox/gateway/security/ldap/SimpleDirectoryService.java +++ b/gateway-demo-ldap/src/main/java/org/apache/knox/gateway/security/ldap/SimpleDirectoryService.java @@@ -1,29 -1,0 +1,33 @@@ +/** + * 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.security.ldap; + - public class SimpleDirectoryService extends BaseDirectoryService { ++import org.apache.directory.server.core.DefaultDirectoryService; ++ ++public class SimpleDirectoryService extends DefaultDirectoryService { + + public SimpleDirectoryService() throws Exception { ++ super(); + } + ++ @Override + protected void showSecurityWarnings() throws Exception { + // NoOp - This prevents confusing warnings from being output. + } + +} http://git-wip-us.apache.org/repos/asf/knox/blob/c754cc06/gateway-demo-ldap/src/main/java/org/apache/knox/gateway/security/ldap/SimpleDirectoryServiceFactory.java ---------------------------------------------------------------------- diff --cc gateway-demo-ldap/src/main/java/org/apache/knox/gateway/security/ldap/SimpleDirectoryServiceFactory.java index a25355b,0000000..e69de29 mode 100644,000000..100644 --- a/gateway-demo-ldap/src/main/java/org/apache/knox/gateway/security/ldap/SimpleDirectoryServiceFactory.java +++ b/gateway-demo-ldap/src/main/java/org/apache/knox/gateway/security/ldap/SimpleDirectoryServiceFactory.java http://git-wip-us.apache.org/repos/asf/knox/blob/c754cc06/gateway-demo-ldap/src/main/java/org/apache/knox/gateway/security/ldap/SimpleLdapDirectoryServer.java ---------------------------------------------------------------------- diff --cc gateway-demo-ldap/src/main/java/org/apache/knox/gateway/security/ldap/SimpleLdapDirectoryServer.java index 9f59e9b,0000000..4809f19 mode 100644,000000..100644 --- a/gateway-demo-ldap/src/main/java/org/apache/knox/gateway/security/ldap/SimpleLdapDirectoryServer.java +++ b/gateway-demo-ldap/src/main/java/org/apache/knox/gateway/security/ldap/SimpleLdapDirectoryServer.java @@@ -1,124 -1,0 +1,160 @@@ +/** + * 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.security.ldap; + +import org.apache.commons.io.FileUtils; +import org.apache.directory.api.ldap.model.entry.DefaultModification; +import org.apache.directory.api.ldap.model.entry.ModificationOperation; +import org.apache.directory.api.ldap.model.exception.LdapException; +import org.apache.directory.api.ldap.model.name.Dn; +import org.apache.directory.server.core.api.CoreSession; +import org.apache.directory.server.core.api.DirectoryService; +import org.apache.directory.server.core.api.partition.Partition; ++import org.apache.directory.server.core.factory.DefaultDirectoryServiceFactory; +import org.apache.directory.server.core.factory.DirectoryServiceFactory; ++import org.apache.directory.server.core.factory.JdbmPartitionFactory; ++import org.apache.directory.server.core.factory.PartitionFactory; +import org.apache.directory.server.ldap.LdapServer; +import org.apache.directory.server.protocol.shared.store.LdifFileLoader; +import org.apache.directory.server.protocol.shared.transport.TcpTransport; +import org.apache.directory.server.protocol.shared.transport.Transport; +import org.apache.log4j.PropertyConfigurator; ++import org.slf4j.Logger; ++import org.slf4j.LoggerFactory; + +import java.io.File; +import java.io.FileNotFoundException; +import java.net.ServerSocket; +import java.util.UUID; + +public class SimpleLdapDirectoryServer { + ++ private static final Logger LOG = LoggerFactory.getLogger(SimpleLdapDirectoryServer.class); ++ + private DirectoryServiceFactory factory; + + private DirectoryService service; + + private LdapServer server; + + public SimpleLdapDirectoryServer( String rootDn, File usersLdif, Transport... transports ) throws Exception { + if( !usersLdif.exists() ) { + throw new FileNotFoundException( usersLdif.getAbsolutePath() ); + } + - factory = new SimpleDirectoryServiceFactory(); ++ DirectoryService directoryService = null; ++ try { ++ // creating the instance here so that ++ // we we can set some properties like accesscontrol, anon access ++ // before starting up the service ++ directoryService = new SimpleDirectoryService(); ++ ++ // no need to register a shutdown hook during tests because this ++ // starts a lot of threads and slows down test execution ++ directoryService.setShutdownHookEnabled( false ); ++ } catch ( Exception e ) { ++ throw new RuntimeException( e ); ++ } ++ ++ PartitionFactory partitionFactory = null; ++ try { ++ String typeName = System.getProperty( "apacheds.partition.factory" ); ++ ++ if ( typeName != null ) { ++ Class<? extends PartitionFactory> type = ( Class<? extends PartitionFactory> ) Class.forName( typeName ); ++ partitionFactory = type.newInstance(); ++ } else { ++ partitionFactory = new JdbmPartitionFactory(); ++ } ++ } catch ( Exception e ) { ++ LOG.error( "Error instantiating custom partiton factory", e ); ++ throw new RuntimeException( e ); ++ } ++ ++ factory = new DefaultDirectoryServiceFactory( directoryService, partitionFactory ); + factory.init( UUID.randomUUID().toString() ); + service = factory.getDirectoryService(); + + enabledPosixSchema( service ); + + Partition partition = factory.getPartitionFactory().createPartition( + service.getSchemaManager(), service.getDnFactory(), "users", rootDn, 500, + service.getInstanceLayout().getInstanceDirectory() ); + service.addPartition( partition ); + + CoreSession session = service.getAdminSession(); + LdifFileLoader lfl = new LdifFileLoader( session, usersLdif, null ); + lfl.execute(); + + server = new LdapServer(); + server.setTransports( transports ); + server.setDirectoryService( service ); + } + + private static void enabledPosixSchema( DirectoryService service ) throws LdapException { + service.getSchemaManager().getLoadedSchema( "nis" ).enable(); + service.getAdminSession().modify( + new Dn( "cn=nis,ou=schema" ), + new DefaultModification( ModificationOperation.REPLACE_ATTRIBUTE, "m-disabled", "FALSE" ) ); + } + + public void start() throws Exception { + service.startup(); + server.start(); + } + + public void stop( boolean clean ) throws Exception { + server.stop(); + service.shutdown(); + if( clean ) { + FileUtils.deleteDirectory( service.getInstanceLayout().getInstanceDirectory() ); + } + } + + public static void main( String[] args ) throws Exception { + PropertyConfigurator.configure( System.getProperty( "log4j.configuration" ) ); + + SimpleLdapDirectoryServer ldap; + + File file; + if ( args.length < 1 ) { + file = new File( "conf/users.ldif" ); + } else { + File dir = new File( args[0] ); + if( !dir.exists() || !dir.isDirectory() ) { + throw new FileNotFoundException( dir.getAbsolutePath() ); + } + file = new File( dir, "users.ldif" ); + } + + if( !file.exists() || !file.canRead() ) { + throw new FileNotFoundException( file.getAbsolutePath() ); + } + + int port = 33389; + + // Make sure the port is free. + ServerSocket socket = new ServerSocket( port ); + socket.close(); + + TcpTransport transport = new TcpTransport( port ); + ldap = new SimpleLdapDirectoryServer( "dc=hadoop,dc=apache,dc=org", file, transport ); + ldap.start(); + } + +} http://git-wip-us.apache.org/repos/asf/knox/blob/c754cc06/gateway-discovery-ambari/src/main/java/org/apache/knox/gateway/topology/discovery/ambari/AmbariServiceDiscovery.java ---------------------------------------------------------------------- diff --cc gateway-discovery-ambari/src/main/java/org/apache/knox/gateway/topology/discovery/ambari/AmbariServiceDiscovery.java index 70af903,0000000..dbc783d mode 100644,000000..100644 --- a/gateway-discovery-ambari/src/main/java/org/apache/knox/gateway/topology/discovery/ambari/AmbariServiceDiscovery.java +++ b/gateway-discovery-ambari/src/main/java/org/apache/knox/gateway/topology/discovery/ambari/AmbariServiceDiscovery.java @@@ -1,305 -1,0 +1,306 @@@ +/** + * 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.discovery.ambari; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Properties; + +import net.minidev.json.JSONArray; +import net.minidev.json.JSONObject; +import net.minidev.json.JSONValue; +import org.apache.knox.gateway.config.ConfigurationException; +import org.apache.knox.gateway.i18n.messages.MessagesFactory; +import org.apache.knox.gateway.services.security.AliasService; +import org.apache.knox.gateway.services.security.AliasServiceException; +import org.apache.knox.gateway.topology.discovery.GatewayService; +import org.apache.knox.gateway.topology.discovery.ServiceDiscovery; +import org.apache.knox.gateway.topology.discovery.ServiceDiscoveryConfig; +import org.apache.http.HttpEntity; +import org.apache.http.HttpStatus; +import org.apache.http.client.methods.CloseableHttpResponse; +import org.apache.http.client.methods.HttpGet; +import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.message.BasicHeader; +import org.apache.http.util.EntityUtils; + + +class AmbariServiceDiscovery implements ServiceDiscovery { + + static final String TYPE = "AMBARI"; + + static final String AMBARI_CLUSTERS_URI = "/api/v1/clusters"; + + static final String AMBARI_HOSTROLES_URI = + AMBARI_CLUSTERS_URI + "/%s/services?fields=components/host_components/HostRoles"; + + static final String AMBARI_SERVICECONFIGS_URI = + AMBARI_CLUSTERS_URI + "/%s/configurations/service_config_versions?is_current=true"; + + private static final String COMPONENT_CONFIG_MAPPING_FILE = + "ambari-service-discovery-component-config-mapping.properties"; + + private static final AmbariServiceDiscoveryMessages log = MessagesFactory.get(AmbariServiceDiscoveryMessages.class); + + // Map of component names to service configuration types + private static Map<String, String> componentServiceConfigs = new HashMap<>(); + static { + try { + Properties configMapping = new Properties(); + configMapping.load(AmbariServiceDiscovery.class.getClassLoader().getResourceAsStream(COMPONENT_CONFIG_MAPPING_FILE)); + for (String componentName : configMapping.stringPropertyNames()) { + componentServiceConfigs.put(componentName, configMapping.getProperty(componentName)); + } + } catch (Exception e) { + log.failedToLoadServiceDiscoveryConfiguration(COMPONENT_CONFIG_MAPPING_FILE, e); + } + } + + private static final String DEFAULT_USER_ALIAS = "ambari.discovery.user"; + private static final String DEFAULT_PWD_ALIAS = "ambari.discovery.password"; + + @GatewayService + private AliasService aliasService; + + private CloseableHttpClient httpClient = null; + + + AmbariServiceDiscovery() { + httpClient = org.apache.http.impl.client.HttpClients.createDefault(); + } + + + @Override + public String getType() { + return TYPE; + } + + + @Override + public Map<String, Cluster> discover(ServiceDiscoveryConfig config) { + Map<String, Cluster> clusters = new HashMap<String, Cluster>(); + + String discoveryAddress = config.getAddress(); + + // Invoke Ambari REST API to discover the available clusters + String clustersDiscoveryURL = String.format("%s" + AMBARI_CLUSTERS_URI, discoveryAddress); + + JSONObject json = invokeREST(clustersDiscoveryURL, config.getUser(), config.getPasswordAlias()); + + // Parse the cluster names from the response, and perform the cluster discovery + JSONArray clusterItems = (JSONArray) json.get("items"); + for (Object clusterItem : clusterItems) { + String clusterName = (String) ((JSONObject)((JSONObject) clusterItem).get("Clusters")).get("cluster_name"); + try { + Cluster c = discover(config, clusterName); + clusters.put(clusterName, c); + } catch (Exception e) { + log.clusterDiscoveryError(clusterName, e); + } + } + + return clusters; + } + + + @Override + public Cluster discover(ServiceDiscoveryConfig config, String clusterName) { + AmbariCluster cluster = new AmbariCluster(clusterName); + + Map<String, String> serviceComponents = new HashMap<>(); + + String discoveryAddress = config.getAddress(); + String discoveryUser = config.getUser(); + String discoveryPwdAlias = config.getPasswordAlias(); + + Map<String, List<String>> componentHostNames = new HashMap<>(); + String hostRolesURL = String.format("%s" + AMBARI_HOSTROLES_URI, discoveryAddress, clusterName); + JSONObject hostRolesJSON = invokeREST(hostRolesURL, discoveryUser, discoveryPwdAlias); + if (hostRolesJSON != null) { + // Process the host roles JSON + JSONArray items = (JSONArray) hostRolesJSON.get("items"); + for (Object obj : items) { + JSONArray components = (JSONArray) ((JSONObject) obj).get("components"); + for (Object component : components) { + JSONArray hostComponents = (JSONArray) ((JSONObject) component).get("host_components"); + for (Object hostComponent : hostComponents) { + JSONObject hostRoles = (JSONObject) ((JSONObject) hostComponent).get("HostRoles"); + String serviceName = (String) hostRoles.get("service_name"); + String componentName = (String) hostRoles.get("component_name"); + + serviceComponents.put(componentName, serviceName); + + // Assuming public host name is more applicable than host_name + String hostName = (String) hostRoles.get("public_host_name"); + if (hostName == null) { + // Some (even slightly) older versions of Ambari/HDP do not return public_host_name, + // so fall back to host_name in those cases. + hostName = (String) hostRoles.get("host_name"); + } + + if (hostName != null) { + log.discoveredServiceHost(serviceName, hostName); + if (!componentHostNames.containsKey(componentName)) { + componentHostNames.put(componentName, new ArrayList<String>()); + } + componentHostNames.get(componentName).add(hostName); + } + } + } + } + } + + Map<String, Map<String, AmbariCluster.ServiceConfiguration>> serviceConfigurations = + new HashMap<String, Map<String, AmbariCluster.ServiceConfiguration>>(); + String serviceConfigsURL = String.format("%s" + AMBARI_SERVICECONFIGS_URI, discoveryAddress, clusterName); + JSONObject serviceConfigsJSON = invokeREST(serviceConfigsURL, discoveryUser, discoveryPwdAlias); + if (serviceConfigsJSON != null) { + // Process the service configurations + JSONArray serviceConfigs = (JSONArray) serviceConfigsJSON.get("items"); + for (Object serviceConfig : serviceConfigs) { + String serviceName = (String) ((JSONObject) serviceConfig).get("service_name"); + JSONArray configurations = (JSONArray) ((JSONObject) serviceConfig).get("configurations"); + for (Object configuration : configurations) { + String configType = (String) ((JSONObject) configuration).get("type"); + String configVersion = String.valueOf(((JSONObject) configuration).get("version")); + + Map<String, String> configProps = new HashMap<String, String>(); + JSONObject configProperties = (JSONObject) ((JSONObject) configuration).get("properties"); + for (String propertyName : configProperties.keySet()) { + configProps.put(propertyName, String.valueOf(((JSONObject) configProperties).get(propertyName))); + } + if (!serviceConfigurations.containsKey(serviceName)) { + serviceConfigurations.put(serviceName, new HashMap<String, AmbariCluster.ServiceConfiguration>()); + } + serviceConfigurations.get(serviceName).put(configType, new AmbariCluster.ServiceConfiguration(configType, configVersion, configProps)); + cluster.addServiceConfiguration(serviceName, configType, new AmbariCluster.ServiceConfiguration(configType, configVersion, configProps)); + } + } + } + + // Construct the AmbariCluster model + for (String componentName : serviceComponents.keySet()) { + String serviceName = serviceComponents.get(componentName); + List<String> hostNames = componentHostNames.get(componentName); + + Map<String, AmbariCluster.ServiceConfiguration> configs = serviceConfigurations.get(serviceName); + String configType = componentServiceConfigs.get(componentName); + if (configType != null) { + AmbariCluster.ServiceConfiguration svcConfig = configs.get(configType); + AmbariComponent c = new AmbariComponent(componentName, + svcConfig.getVersion(), + clusterName, + serviceName, + hostNames, + svcConfig.getProperties()); + cluster.addComponent(c); + } + } + + return cluster; + } + + + protected JSONObject invokeREST(String url, String username, String passwordAlias) { + JSONObject result = null; + + CloseableHttpResponse response = null; + try { + HttpGet request = new HttpGet(url); + + // If no configured username, then use default username alias + String password = null; + if (username == null) { + if (aliasService != null) { + try { + char[] defaultUser = aliasService.getPasswordFromAliasForGateway(DEFAULT_USER_ALIAS); + if (defaultUser != null) { + username = new String(defaultUser); + } + } catch (AliasServiceException e) { + log.aliasServiceUserError(DEFAULT_USER_ALIAS, e.getLocalizedMessage()); + } + } + + // If username is still null + if (username == null) { + log.aliasServiceUserNotFound(); + throw new ConfigurationException("No username is configured for Ambari service discovery."); + } + } + + if (aliasService != null) { - // If not password alias is configured, then try the default alias ++ // If no password alias is configured, then try the default alias + if (passwordAlias == null) { + passwordAlias = DEFAULT_PWD_ALIAS; + } ++ + try { + char[] pwd = aliasService.getPasswordFromAliasForGateway(passwordAlias); + if (pwd != null) { + password = new String(pwd); + } + + } catch (AliasServiceException e) { + log.aliasServicePasswordError(passwordAlias, e.getLocalizedMessage()); + } + } + + // If the password could not be determined + if (password == null) { + log.aliasServicePasswordNotFound(); + throw new ConfigurationException("No password is configured for Ambari service discovery."); + } + + // Add an auth header if credentials are available + String encodedCreds = + org.apache.commons.codec.binary.Base64.encodeBase64String((username + ":" + password).getBytes()); + request.addHeader(new BasicHeader("Authorization", "Basic " + encodedCreds)); + + response = httpClient.execute(request); + + if (HttpStatus.SC_OK == response.getStatusLine().getStatusCode()) { + HttpEntity entity = response.getEntity(); + if (entity != null) { + result = (JSONObject) JSONValue.parse((EntityUtils.toString(entity))); + log.debugJSON(result.toJSONString()); + } else { + log.noJSON(url); + } + } else { + log.unexpectedRestResponseStatusCode(url, response.getStatusLine().getStatusCode()); + } + + } catch (IOException e) { + log.restInvocationError(url, e); + } finally { + if(response != null) { + try { + response.close(); + } catch (IOException e) { + // Ignore + } + } + } + return result; + } + + +} http://git-wip-us.apache.org/repos/asf/knox/blob/c754cc06/gateway-provider-identity-assertion-regex/src/main/java/org/apache/knox/gateway/identityasserter/regex/filter/RegexIdentityAssertionFilter.java ---------------------------------------------------------------------- diff --cc gateway-provider-identity-assertion-regex/src/main/java/org/apache/knox/gateway/identityasserter/regex/filter/RegexIdentityAssertionFilter.java index 4cc86ae,0000000..3c9cf11 mode 100644,000000..100644 --- a/gateway-provider-identity-assertion-regex/src/main/java/org/apache/knox/gateway/identityasserter/regex/filter/RegexIdentityAssertionFilter.java +++ b/gateway-provider-identity-assertion-regex/src/main/java/org/apache/knox/gateway/identityasserter/regex/filter/RegexIdentityAssertionFilter.java @@@ -1,88 -1,0 +1,90 @@@ +/** + * 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.identityasserter.regex.filter; + +import javax.security.auth.Subject; +import javax.servlet.FilterConfig; +import javax.servlet.ServletException; + +import org.apache.knox.gateway.identityasserter.common.filter.CommonIdentityAssertionFilter; +import org.apache.knox.gateway.security.principal.PrincipalMappingException; + +import java.util.Map; +import java.util.StringTokenizer; +import java.util.TreeMap; ++import java.lang.Boolean; + +public class RegexIdentityAssertionFilter extends + CommonIdentityAssertionFilter { + + private String input = null; + private String output = null; + private Map<String,String> dict; + RegexTemplate template; + + @Override + public void init(FilterConfig filterConfig) throws ServletException { + super.init(filterConfig); + try { + input = filterConfig.getInitParameter( "input" ); + if( input == null ) { + input = ""; + } + output = filterConfig.getInitParameter( "output" ); + if( output == null ) { + output = ""; + } + dict = loadDictionary( filterConfig.getInitParameter( "lookup" ) ); - template = new RegexTemplate( input, output, dict ); ++ boolean useOriginalOnLookupFailure = Boolean.parseBoolean(filterConfig.getInitParameter("use.original.on.lookup.failure")); ++ template = new RegexTemplate( input, output, dict, useOriginalOnLookupFailure); + } catch ( PrincipalMappingException e ) { + throw new ServletException( e ); + } + } + + public String[] mapGroupPrincipals(String mappedPrincipalName, Subject subject) { + // Returning null will allow existing Subject group principals to remain the same + return null; + } + + public String mapUserPrincipal(String principalName) { + return template.apply( principalName ); + } + + private Map<String, String> loadDictionary( String config ) throws PrincipalMappingException { + Map<String,String> dict = new TreeMap<>(String.CASE_INSENSITIVE_ORDER); + if( config != null && !config.isEmpty() ) { + try { + StringTokenizer t = new StringTokenizer( config, ";" ); + while( t.hasMoreTokens() ) { + String nvp = t.nextToken(); + String[] a = nvp.split( "=" ); + dict.put( a[0].trim(), a[1].trim() ); + } + return dict; + } catch( Exception e ) { + dict.clear(); + throw new PrincipalMappingException( + "Unable to load lookup dictionary from provided configuration: " + config + + ". No principal mapping will be provided.", e ); + } + } + return dict; + } + +} http://git-wip-us.apache.org/repos/asf/knox/blob/c754cc06/gateway-provider-identity-assertion-regex/src/main/java/org/apache/knox/gateway/identityasserter/regex/filter/RegexTemplate.java ---------------------------------------------------------------------- diff --cc gateway-provider-identity-assertion-regex/src/main/java/org/apache/knox/gateway/identityasserter/regex/filter/RegexTemplate.java index e8f108e,0000000..659d3df mode 100644,000000..100644 --- a/gateway-provider-identity-assertion-regex/src/main/java/org/apache/knox/gateway/identityasserter/regex/filter/RegexTemplate.java +++ b/gateway-provider-identity-assertion-regex/src/main/java/org/apache/knox/gateway/identityasserter/regex/filter/RegexTemplate.java @@@ -1,75 -1,0 +1,79 @@@ +/** + * 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 + * <p/> + * http://www.apache.org/licenses/LICENSE-2.0 + * <p/> + * 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.identityasserter.regex.filter; + +import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class RegexTemplate { + + private static Pattern directPattern = Pattern.compile( "\\{(\\[?\\d+?\\]?)\\}" ); + private static Pattern indirectPattern = Pattern.compile( "\\[(\\d+?)\\]" ); + + Pattern inputPattern; + String outputTemplate; + Map<String,String> lookupTable; ++ boolean useOriginalOnLookupFailure; + + public RegexTemplate( String regex, String template ) { - this( regex, template, null ); ++ this( regex, template, null, false ); + } + - public RegexTemplate( String regex, String template, Map<String,String> map ) { ++ public RegexTemplate( String regex, String template, Map<String,String> map, boolean useOriginalOnLookupFailure ) { + this.inputPattern = Pattern.compile( regex ); + this.outputTemplate = template; + this.lookupTable = map; ++ this.useOriginalOnLookupFailure = useOriginalOnLookupFailure; + } + + public String apply( String input ) { + String output = outputTemplate; + Matcher inputMatcher = inputPattern.matcher( input ); + if( inputMatcher.find() ) { + output = expandTemplate( inputMatcher, output ); + } + return output; + } + + private String expandTemplate( Matcher inputMatcher, String output ) { + Matcher directMatcher = directPattern.matcher( output ); + while( directMatcher.find() ) { ++ String lookupKey = null; + String lookupValue = null; + String lookupStr = directMatcher.group( 1 ); + Matcher indirectMatcher = indirectPattern.matcher( lookupStr ); + if( indirectMatcher.find() ) { + lookupStr = indirectMatcher.group( 1 ); + int lookupIndex = Integer.parseInt( lookupStr ); + if( lookupTable != null ) { - String lookupKey = inputMatcher.group( lookupIndex ); ++ lookupKey = inputMatcher.group( lookupIndex ); + lookupValue = lookupTable.get( lookupKey ); + } + } else { + int lookupIndex = Integer.parseInt( lookupStr ); + lookupValue = inputMatcher.group( lookupIndex ); + } - output = directMatcher.replaceFirst( lookupValue == null ? "" : lookupValue ); ++ String replaceWith = this.useOriginalOnLookupFailure ? lookupKey : "" ; ++ output = directMatcher.replaceFirst( lookupValue == null ? replaceWith : lookupValue ); + directMatcher = directPattern.matcher( output ); + } + return output; + } + +} http://git-wip-us.apache.org/repos/asf/knox/blob/c754cc06/gateway-provider-identity-assertion-regex/src/test/java/org/apache/knox/gateway/identityasserter/regex/filter/RegexTemplateTest.java ---------------------------------------------------------------------- diff --cc gateway-provider-identity-assertion-regex/src/test/java/org/apache/knox/gateway/identityasserter/regex/filter/RegexTemplateTest.java index 3c3b06f,0000000..49630be mode 100644,000000..100644 --- a/gateway-provider-identity-assertion-regex/src/test/java/org/apache/knox/gateway/identityasserter/regex/filter/RegexTemplateTest.java +++ b/gateway-provider-identity-assertion-regex/src/test/java/org/apache/knox/gateway/identityasserter/regex/filter/RegexTemplateTest.java @@@ -1,72 -1,0 +1,93 @@@ +/** + * 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 + * <p/> + * http://www.apache.org/licenses/LICENSE-2.0 + * <p/> + * 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.identityasserter.regex.filter; + +import org.junit.Test; + +import java.util.Map; +import java.util.TreeMap; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.core.Is.is; + +public class RegexTemplateTest { + + @Test + public void testExtractUsernameFromEmailAddress() { + + RegexTemplate template; + String actual; + + template = new RegexTemplate( "(.*)@.*", "prefix_{1}_suffix" ); + actual = template.apply( "[email protected]" ); + assertThat( actual, is( "prefix_member_suffix" ) ); + + template = new RegexTemplate( "(.*)@.*", "prefix_{0}_suffix" ); + actual = template.apply( "[email protected]" ); + assertThat( actual, is( "[email protected]_suffix" ) ); + + template = new RegexTemplate( "(.*)@.*", "prefix_{1}_{a}_suffix" ); + actual = template.apply( "[email protected]" ); + assertThat( actual, is( "prefix_member_{a}_suffix" ) ); + + } + + @Test + public void testExtractUsernameFromEmailAddressAndMapDomain() { + + RegexTemplate template; + Map<String,String> map = new TreeMap<>(String.CASE_INSENSITIVE_ORDER); + map.put( "us", "USA" ); + map.put( "ca", "CANADA" ); + + String actual; + - template = new RegexTemplate( "(.*)@(.*?)\\..*", "prefix_{1}:{[2]}_suffix", map ); ++ template = new RegexTemplate( "(.*)@(.*?)\\..*", "prefix_{1}:{[2]}_suffix", map, false ); + actual = template.apply( "[email protected]" ); + assertThat( actual, is( "prefix_member:USA_suffix" ) ); + + actual = template.apply( "[email protected]" ); + assertThat( actual, is( "prefix_member:CANADA_suffix" ) ); + + actual = template.apply( "[email protected]" ); + assertThat( actual, is( "prefix_member:_suffix" ) ); + + } + ++ @Test ++ public void testLookupFailure() { ++ ++ RegexTemplate template; ++ Map<String,String> map = new TreeMap<>(String.CASE_INSENSITIVE_ORDER); ++ map.put( "us", "USA" ); ++ map.put( "ca", "CANADA" ); ++ ++ String actual; ++ ++ template = new RegexTemplate( "(.*)@(.*?)\\..*", "prefix_{1}:{[2]}_suffix", map, true ); ++ actual = template.apply( "[email protected]" ); ++ assertThat( actual, is( "prefix_member:USA_suffix" ) ); ++ ++ actual = template.apply( "[email protected]" ); ++ assertThat( actual, is( "prefix_member:CANADA_suffix" ) ); ++ ++ actual = template.apply( "[email protected]" ); ++ assertThat( actual, is( "prefix_member:nj_suffix" ) ); ++ ++ } +} http://git-wip-us.apache.org/repos/asf/knox/blob/c754cc06/gateway-provider-security-webappsec/src/main/java/org/apache/knox/gateway/webappsec/deploy/WebAppSecContributor.java ---------------------------------------------------------------------- diff --cc gateway-provider-security-webappsec/src/main/java/org/apache/knox/gateway/webappsec/deploy/WebAppSecContributor.java index a182b37,0000000..17fb8c2 mode 100644,000000..100644 --- a/gateway-provider-security-webappsec/src/main/java/org/apache/knox/gateway/webappsec/deploy/WebAppSecContributor.java +++ b/gateway-provider-security-webappsec/src/main/java/org/apache/knox/gateway/webappsec/deploy/WebAppSecContributor.java @@@ -1,107 -1,0 +1,118 @@@ +/** + * 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.webappsec.deploy; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; + +import org.apache.knox.gateway.deploy.DeploymentContext; +import org.apache.knox.gateway.deploy.ProviderDeploymentContributorBase; +import org.apache.knox.gateway.descriptor.FilterParamDescriptor; +import org.apache.knox.gateway.descriptor.ResourceDescriptor; +import org.apache.knox.gateway.topology.Provider; +import org.apache.knox.gateway.topology.Service; + +public class WebAppSecContributor extends + ProviderDeploymentContributorBase { + private static final String ROLE = "webappsec"; + private static final String NAME = "WebAppSec"; + private static final String CSRF_SUFFIX = "_CSRF"; + private static final String CSRF_FILTER_CLASSNAME = "org.apache.knox.gateway.webappsec.filter.CSRFPreventionFilter"; + private static final String CSRF_ENABLED = "csrf.enabled"; + private static final String CORS_SUFFIX = "_CORS"; + private static final String CORS_FILTER_CLASSNAME = "com.thetransactioncompany.cors.CORSFilter"; + private static final String CORS_ENABLED = "cors.enabled"; + private static final String XFRAME_OPTIONS_SUFFIX = "_XFRAMEOPTIONS"; + private static final String XFRAME_OPTIONS_FILTER_CLASSNAME = "org.apache.knox.gateway.webappsec.filter.XFrameOptionsFilter"; + private static final String XFRAME_OPTIONS_ENABLED = "xframe.options.enabled"; ++ private static final String STRICT_TRANSPORT_SUFFIX = "_STRICTTRANSPORT"; ++ private static final String STRICT_TRANSPORT_FILTER_CLASSNAME = "org.apache.hadoop.gateway.webappsec.filter.StrictTranportFilter"; ++ private static final String STRICT_TRANSPORT_ENABLED = "strict.transport.enabled"; + + + @Override + public String getRole() { + return ROLE; + } + + @Override + public String getName() { + return NAME; + } + + @Override + public void initializeContribution(DeploymentContext context) { + super.initializeContribution(context); + } + + @Override + public void contributeFilter(DeploymentContext context, Provider provider, Service service, + ResourceDescriptor resource, List<FilterParamDescriptor> params) { + + Provider webappsec = context.getTopology().getProvider(ROLE, NAME); + if (webappsec != null && webappsec.isEnabled()) { + Map<String,String> map = provider.getParams(); + if (params == null) { + params = new ArrayList<FilterParamDescriptor>(); + } + + Map<String, String> providerParams = provider.getParams(); + // CORS support + String corsEnabled = map.get(CORS_ENABLED); + if ( corsEnabled != null && "true".equals(corsEnabled)) { + provisionConfig(resource, providerParams, params, "cors."); + resource.addFilter().name( getName() + CORS_SUFFIX ).role( getRole() ).impl( CORS_FILTER_CLASSNAME ).params( params ); + } + + // CRSF + params = new ArrayList<FilterParamDescriptor>(); + String csrfEnabled = map.get(CSRF_ENABLED); + if ( csrfEnabled != null && "true".equals(csrfEnabled)) { + provisionConfig(resource, providerParams, params, "csrf."); + resource.addFilter().name( getName() + CSRF_SUFFIX ).role( getRole() ).impl( CSRF_FILTER_CLASSNAME ).params( params ); + } + + // X-Frame-Options - clickjacking protection + params = new ArrayList<FilterParamDescriptor>(); + String xframeOptionsEnabled = map.get(XFRAME_OPTIONS_ENABLED); + if ( xframeOptionsEnabled != null && "true".equals(xframeOptionsEnabled)) { + provisionConfig(resource, providerParams, params, "xframe."); + resource.addFilter().name( getName() + XFRAME_OPTIONS_SUFFIX ).role( getRole() ).impl( XFRAME_OPTIONS_FILTER_CLASSNAME ).params( params ); + } ++ ++ // HTTP Strict-Transport-Security ++ params = new ArrayList<FilterParamDescriptor>(); ++ String strictTranportEnabled = map.get(STRICT_TRANSPORT_ENABLED); ++ if ( strictTranportEnabled != null && "true".equals(strictTranportEnabled)) { ++ provisionConfig(resource, providerParams, params, "strict."); ++ resource.addFilter().name( getName() + STRICT_TRANSPORT_SUFFIX).role( getRole() ).impl(STRICT_TRANSPORT_FILTER_CLASSNAME).params( params ); ++ } + } + } + + private void provisionConfig(ResourceDescriptor resource, Map<String,String> providerParams, + List<FilterParamDescriptor> params, String prefix) { + for(Entry<String, String> entry : providerParams.entrySet()) { + if (entry.getKey().startsWith(prefix)) { + params.add( resource.createFilterParam().name( entry.getKey().toLowerCase() ).value( entry.getValue() ) ); + } + } + } +} http://git-wip-us.apache.org/repos/asf/knox/blob/c754cc06/gateway-release/home/conf/topologies/manager.xml ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/knox/blob/c754cc06/gateway-server/src/main/java/org/apache/knox/gateway/GatewayFilter.java ---------------------------------------------------------------------- diff --cc gateway-server/src/main/java/org/apache/knox/gateway/GatewayFilter.java index 5d7c5db,0000000..8dd29bf mode 100644,000000..100644 --- a/gateway-server/src/main/java/org/apache/knox/gateway/GatewayFilter.java +++ b/gateway-server/src/main/java/org/apache/knox/gateway/GatewayFilter.java @@@ -1,390 -1,0 +1,453 @@@ +/** + * 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; + +import org.apache.knox.gateway.audit.api.Action; +import org.apache.knox.gateway.audit.api.ActionOutcome; +import org.apache.knox.gateway.audit.api.AuditContext; +import org.apache.knox.gateway.audit.api.AuditService; +import org.apache.knox.gateway.audit.api.AuditServiceFactory; +import org.apache.knox.gateway.audit.api.Auditor; +import org.apache.knox.gateway.audit.api.CorrelationContext; +import org.apache.knox.gateway.audit.api.CorrelationServiceFactory; +import org.apache.knox.gateway.audit.api.ResourceType; +import org.apache.knox.gateway.audit.log4j.audit.AuditConstants; +import org.apache.knox.gateway.config.GatewayConfig; +import org.apache.knox.gateway.filter.AbstractGatewayFilter; +import org.apache.knox.gateway.i18n.messages.MessagesFactory; +import org.apache.knox.gateway.i18n.resources.ResourcesFactory; ++import org.apache.knox.gateway.topology.Topology; +import org.apache.knox.gateway.util.urltemplate.Matcher; +import org.apache.knox.gateway.util.urltemplate.Parser; +import org.apache.knox.gateway.util.urltemplate.Template; + +import javax.servlet.Filter; +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.HttpServletRequestWrapper; +import javax.servlet.http.HttpServletResponse; + +import java.io.IOException; +import java.net.URISyntaxException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Enumeration; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.UUID; + +/** + * + */ +public class GatewayFilter implements Filter { + + private static final FilterChain EMPTY_CHAIN = new FilterChain() { + public void doFilter( ServletRequest servletRequest, ServletResponse servletResponse ) throws IOException, ServletException { + } + }; + + private static final GatewayMessages LOG = MessagesFactory.get( GatewayMessages.class ); + private static final GatewayResources RES = ResourcesFactory.get( GatewayResources.class ); + private static AuditService auditService = AuditServiceFactory.getAuditService(); + private static Auditor auditor = auditService.getAuditor( + AuditConstants.DEFAULT_AUDITOR_NAME, AuditConstants.KNOX_SERVICE_NAME, + AuditConstants.KNOX_COMPONENT_NAME ); + + private Set<Holder> holders; + private Matcher<Chain> chains; + private FilterConfig config; + + public GatewayFilter() { + holders = new HashSet<>(); + chains = new Matcher<Chain>(); + } + + @Override + public void init( FilterConfig filterConfig ) throws ServletException { + this.config = filterConfig; + } + + @Override + public void doFilter( ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain ) throws IOException, ServletException { + doFilter( servletRequest, servletResponse ); + if( filterChain != null ) { + filterChain.doFilter( servletRequest, servletResponse ); + } + } + + @SuppressWarnings("unchecked") + public void doFilter( ServletRequest servletRequest, ServletResponse servletResponse ) throws IOException, ServletException { + HttpServletRequest httpRequest = (HttpServletRequest)servletRequest; + HttpServletResponse httpResponse = (HttpServletResponse)servletResponse; + + //TODO: The resulting pathInfo + query needs to be added to the servlet context somehow so that filters don't need to rebuild it. This is done in HttpClientDispatch right now for example. + String servlet = httpRequest.getServletPath(); + String path = httpRequest.getPathInfo(); + String query = httpRequest.getQueryString(); + String requestPath = ( servlet == null ? "" : servlet ) + ( path == null ? "" : path ); + String requestPathWithQuery = requestPath + ( query == null ? "" : "?" + query ); + + Template pathWithQueryTemplate; + try { + pathWithQueryTemplate = Parser.parseLiteral( requestPathWithQuery ); + } catch( URISyntaxException e ) { + throw new ServletException( e ); + } + String contextWithPathAndQuery = httpRequest.getContextPath() + requestPathWithQuery; + LOG.receivedRequest( httpRequest.getMethod(), requestPath ); + + servletRequest.setAttribute( + AbstractGatewayFilter.SOURCE_REQUEST_URL_ATTRIBUTE_NAME, pathWithQueryTemplate ); + servletRequest.setAttribute( + AbstractGatewayFilter.SOURCE_REQUEST_CONTEXT_URL_ATTRIBUTE_NAME, contextWithPathAndQuery ); + + Matcher<Chain>.Match match = chains.match( pathWithQueryTemplate ); - ++ ++ // if there was no match then look for a default service for the topology ++ if (match == null) { ++ Topology topology = (Topology) servletRequest.getServletContext().getAttribute("org.apache.hadoop.gateway.topology"); ++ if (topology != null) { ++ String defaultServicePath = topology.getDefaultServicePath(); ++ if (defaultServicePath != null) { ++ try { ++ String newPathWithQuery = defaultServicePath + "/" + pathWithQueryTemplate; ++ match = chains.match(Parser.parseLiteral(newPathWithQuery)); ++ String origUrl = ((HttpServletRequest) servletRequest).getRequestURL().toString(); ++ String url = origUrl; ++ if (path.equals("/")) { ++ url += defaultServicePath; ++ } ++ else { ++ int index = origUrl.indexOf(path); ++ url = origUrl.substring(0, index) + "/" + defaultServicePath + path; ++ } ++ String contextPath = defaultServicePath; ++ servletRequest = new ForwardedRequest((HttpServletRequest) servletRequest, ++ contextPath, ++ url); ++ } catch (URISyntaxException e) { ++ throw new ServletException( e ); ++ } ++ } ++ } ++ } ++ + assignCorrelationRequestId(); + // Populate Audit/correlation parameters + AuditContext auditContext = auditService.getContext(); + auditContext.setTargetServiceName( match == null ? null : match.getValue().getResourceRole() ); + auditContext.setRemoteIp( getRemoteAddress(servletRequest) ); + auditContext.setRemoteHostname( servletRequest.getRemoteHost() ); + auditor.audit( + Action.ACCESS, contextWithPathAndQuery, ResourceType.URI, + ActionOutcome.UNAVAILABLE, RES.requestMethod(((HttpServletRequest)servletRequest).getMethod())); + + if( match != null ) { + Chain chain = match.getValue(); + servletRequest.setAttribute( AbstractGatewayFilter.TARGET_SERVICE_ROLE, chain.getResourceRole() ); + try { + chain.doFilter( servletRequest, servletResponse ); + } catch( IOException e ) { + LOG.failedToExecuteFilter( e ); + auditor.audit( Action.ACCESS, contextWithPathAndQuery, ResourceType.URI, ActionOutcome.FAILURE ); + throw e; + } catch( ServletException e ) { + LOG.failedToExecuteFilter( e ); + auditor.audit( Action.ACCESS, contextWithPathAndQuery, ResourceType.URI, ActionOutcome.FAILURE ); + throw e; + } catch( RuntimeException e ) { + LOG.failedToExecuteFilter( e ); + auditor.audit( Action.ACCESS, contextWithPathAndQuery, ResourceType.URI, ActionOutcome.FAILURE ); + throw e; + } catch( ThreadDeath e ) { + LOG.failedToExecuteFilter( e ); + auditor.audit( Action.ACCESS, contextWithPathAndQuery, ResourceType.URI, ActionOutcome.FAILURE ); + throw e; + } catch( Throwable e ) { + LOG.failedToExecuteFilter( e ); + auditor.audit( Action.ACCESS, contextWithPathAndQuery, ResourceType.URI, ActionOutcome.FAILURE ); + throw new ServletException( e ); + } + } else { + LOG.failedToMatchPath( requestPath ); + httpResponse.setStatus( HttpServletResponse.SC_NOT_FOUND ); + } + //KAM[ Don't do this or the Jetty default servlet will overwrite any response setup by the filter. + // filterChain.doFilter( servletRequest, servletResponse ); + //] + } + + private String getRemoteAddress(ServletRequest servletRequest) { + GatewayConfig gatewayConfig = + (GatewayConfig) servletRequest.getServletContext(). + getAttribute(GatewayConfig.GATEWAY_CONFIG_ATTRIBUTE); + + String addrHeaderName = gatewayConfig.getHeaderNameForRemoteAddress(); + String addr = ((HttpServletRequest)servletRequest).getHeader(addrHeaderName); + if (addr == null || addr.trim().isEmpty()) { + addr = servletRequest.getRemoteAddr(); + } + return addr; + } + + @Override + public void destroy() { + for( Holder holder : holders ) { + holder.destroy(); + } + } + + private void addHolder( Holder holder ) { + holders.add( holder ); + Chain chain = chains.get( holder.template ); + if( chain == null ) { + chain = new Chain(); + chain.setResourceRole( holder.getResourceRole() ); + chains.add( holder.template, chain ); + } + chain.chain.add( holder ); + } + + public void addFilter( String path, String name, Filter filter, Map<String,String> params, String resourceRole ) throws URISyntaxException { + Holder holder = new Holder( path, name, filter, params, resourceRole ); + addHolder( holder ); + } + +// public void addFilter( String path, String name, Class<RegexDirFilter> clazz, Map<String,String> params ) throws URISyntaxException { +// Holder holder = new Holder( path, name, clazz, params ); +// addHolder( holder ); +// } + + public void addFilter( String path, String name, String clazz, Map<String,String> params, String resourceRole ) throws URISyntaxException { + Holder holder = new Holder( path, name, clazz, params, resourceRole ); + addHolder( holder ); + } + + // Now creating the correlation context only if required since it may be created upstream in the CorrelationHandler. + private void assignCorrelationRequestId() { + CorrelationContext correlationContext = CorrelationServiceFactory.getCorrelationService().getContext(); + if( correlationContext == null ) { + correlationContext = CorrelationServiceFactory.getCorrelationService().createContext(); + } + String requestId = correlationContext.getRequestId(); + if( requestId == null ) { + correlationContext.setRequestId( UUID.randomUUID().toString() ); + } + } + + private class Chain implements FilterChain { + + private List<Holder> chain; + private String resourceRole; + + private Chain() { + this.chain = new ArrayList<Holder>(); + } + + private Chain( List<Holder> chain ) { + this.chain = chain; + } + + public void doFilter( ServletRequest servletRequest, ServletResponse servletResponse ) throws IOException, ServletException { + if( chain != null && !chain.isEmpty() ) { + final Filter filter = chain.get( 0 ); + final FilterChain chain = subChain(); + filter.doFilter( servletRequest, servletResponse, chain ); + } + } + + private FilterChain subChain() { + if( chain != null && chain.size() > 1 ) { + return new Chain( chain.subList( 1, chain.size() ) ); + } else { + return EMPTY_CHAIN; + } + } + + private String getResourceRole() { + return resourceRole; + } + + private void setResourceRole( String resourceRole ) { + this.resourceRole = resourceRole; + } + + } + + private class Holder implements Filter, FilterConfig { +// private String path; + private Template template; + private String name; + private Map<String,String> params; + private Filter instance; + private Class<? extends Filter> clazz; + private String type; + private String resourceRole; + + private Holder( String path, String name, Filter filter, Map<String,String> params, String resourceRole ) throws URISyntaxException { +// this.path = path; + this.template = Parser.parseTemplate( path ); + this.name = name; + this.params = params; + this.instance = filter; + this.clazz = filter.getClass(); + this.type = clazz.getCanonicalName(); + this.resourceRole = resourceRole; + } + +// private Holder( String path, String name, Class<RegexDirFilter> clazz, Map<String,String> params ) throws URISyntaxException { +// this.path = path; +// this.template = Parser.parse( path ); +// this.name = name; +// this.params = params; +// this.instance = null; +// this.clazz = clazz; +// this.type = clazz.getCanonicalName(); +// } + + private Holder( String path, String name, String clazz, Map<String,String> params, String resourceRole ) throws URISyntaxException { +// this.path = path; + this.template = Parser.parseTemplate( path ); + this.name = name; + this.params = params; + this.instance = null; + this.clazz = null; + this.type = clazz; + this.resourceRole = resourceRole; + } + + @Override + public String getFilterName() { + return name; + } + + @Override + public ServletContext getServletContext() { + return GatewayFilter.this.config.getServletContext(); + } + + @Override + public String getInitParameter( String name ) { + String value = null; + if( params != null ) { + value = params.get( name ); + } + return value; + } + + @Override + public Enumeration<String> getInitParameterNames() { + Enumeration<String> names = null; + if( params != null ) { + names = Collections.enumeration( params.keySet() ); + } + return names; + } + + @Override + public void init( FilterConfig filterConfig ) throws ServletException { + getInstance().init( filterConfig ); + } + + @Override + public void doFilter( ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain ) throws IOException, ServletException { + final Filter filter = getInstance(); + filter.doFilter( servletRequest, servletResponse, filterChain ); + } + + @Override + public void destroy() { + if( instance != null ) { + instance.destroy(); + instance = null; + } + } + + @SuppressWarnings("unchecked") + private Class<? extends Filter> getClazz() throws ClassNotFoundException { + if( clazz == null ) { + ClassLoader loader = Thread.currentThread().getContextClassLoader(); + if( loader == null ) { + loader = this.getClass().getClassLoader(); + } + clazz = (Class)loader.loadClass( type ); + } + return clazz; + } + + private Filter getInstance() throws ServletException { + if( instance == null ) { + try { + if( clazz == null ) { + clazz = getClazz(); + } + instance = clazz.newInstance(); + instance.init( this ); + } catch( Exception e ) { + throw new ServletException( e ); + } + } + return instance; + } + + private String getResourceRole() { + return resourceRole; + } + + } + ++ /** ++ * A request wrapper class that wraps a request and adds the context path if ++ * needed. ++ */ ++ static class ForwardedRequest extends HttpServletRequestWrapper { ++ ++ private String newURL; ++ private String contextpath; ++ ++ public ForwardedRequest(final HttpServletRequest request, ++ final String contextpath, final String newURL) { ++ super(request); ++ this.newURL = newURL; ++ this.contextpath = contextpath; ++ } ++ ++ @Override ++ public StringBuffer getRequestURL() { ++ return new StringBuffer(newURL); ++ } ++ ++ @Override ++ public String getRequestURI() { ++ return newURL; ++ } ++ ++ @Override ++ public String getContextPath() { ++ return super.getContextPath() + "/" + this.contextpath; ++ } ++ ++ } +}
