diff --git a/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/nifi/processors/standard/HandleHttpRequest.java b/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/nifi/processors/standard/HandleHttpRequest.java index f532ca2290..a1493f1a2e 100644 --- a/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/nifi/processors/standard/HandleHttpRequest.java +++ b/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/nifi/processors/standard/HandleHttpRequest.java @@ -91,10 +91,11 @@ import com.sun.jersey.api.client.ClientResponse.Status; @WritesAttribute(attribute = "http.method", description = "The HTTP Method that was used for the request, such as GET or POST"), @WritesAttribute(attribute = HTTPUtils.HTTP_LOCAL_NAME, description = "IP address/hostname of the server"), @WritesAttribute(attribute = HTTPUtils.HTTP_PORT, description = "Listening port of the server"), - @WritesAttribute(attribute = "http.query.string", description = "The query string portion of hte Request URL"), + @WritesAttribute(attribute = "http.query.string", description = "The query string portion of the Request URL"), @WritesAttribute(attribute = HTTPUtils.HTTP_REMOTE_HOST, description = "The hostname of the requestor"), @WritesAttribute(attribute = "http.remote.addr", description = "The hostname:port combination of the requestor"), @WritesAttribute(attribute = "http.remote.user", description = "The username of the requestor"), + @WritesAttribute(attribute = "http.protocol", description = "The protocol used to communicate"), @WritesAttribute(attribute = HTTPUtils.HTTP_REQUEST_URI, description = "The full Request URL"), @WritesAttribute(attribute = "http.auth.type", description = "The type of HTTP Authorization used"), @WritesAttribute(attribute = "http.principal.name", description = "The name of the authenticated user making the request"), @@ -106,7 +107,7 @@ import com.sun.jersey.api.client.ClientResponse.Status; + "attribute, prefixed with \"http.headers.\" For example, if the request contains an HTTP Header named \"x-my-header\", then the value " + "will be added to an attribute named \"http.headers.x-my-header\"")}) @SeeAlso(value = {HandleHttpResponse.class}, - classNames = {"org.apache.nifi.http.StandardHttpContextMap", "org.apache.nifi.ssl.StandardSSLContextService"}) + classNames = {"org.apache.nifi.http.StandardHttpContextMap", "org.apache.nifi.ssl.RestrictedStandardSSLContextService"}) public class HandleHttpRequest extends AbstractProcessor { private static final Pattern URL_QUERY_PARAM_DELIMITER = Pattern.compile("&"); @@ -448,6 +449,8 @@ public class HandleHttpRequest extends AbstractProcessor { sslFactory.setNeedClientAuth(needClientAuth); sslFactory.setWantClientAuth(wantClientAuth); + sslFactory.setProtocol(sslService.getSslAlgorithm()); + if (sslService.isKeyStoreConfigured()) { sslFactory.setKeyStorePath(sslService.getKeyStoreFile()); sslFactory.setKeyStorePassword(sslService.getKeyStorePassword()); @@ -528,6 +531,7 @@ public class HandleHttpRequest extends AbstractProcessor { putAttribute(attributes, HTTPUtils.HTTP_REMOTE_HOST, request.getRemoteHost()); putAttribute(attributes, "http.remote.addr", request.getRemoteAddr()); putAttribute(attributes, "http.remote.user", request.getRemoteUser()); + putAttribute(attributes, "http.protocol", request.getProtocol()); putAttribute(attributes, HTTPUtils.HTTP_REQUEST_URI, request.getRequestURI()); putAttribute(attributes, "http.request.url", request.getRequestURL().toString()); putAttribute(attributes, "http.auth.type", request.getAuthType()); diff --git a/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/test/java/org/apache/nifi/processors/standard/TestHandleHttpRequest.java b/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/test/java/org/apache/nifi/processors/standard/TestHandleHttpRequest.java index c6bb33740c..0315ba2f22 100644 --- a/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/test/java/org/apache/nifi/processors/standard/TestHandleHttpRequest.java +++ b/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/test/java/org/apache/nifi/processors/standard/TestHandleHttpRequest.java @@ -22,9 +22,13 @@ import java.io.IOException; import java.net.HttpURLConnection; import java.net.MalformedURLException; import java.net.URL; +import java.util.HashMap; +import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; +import javax.net.ssl.HttpsURLConnection; +import javax.net.ssl.SSLContext; import javax.servlet.AsyncContext; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; @@ -32,6 +36,9 @@ import javax.servlet.http.HttpServletResponse; import org.apache.nifi.controller.AbstractControllerService; import org.apache.nifi.http.HttpContextMap; import org.apache.nifi.reporting.InitializationException; +import org.apache.nifi.ssl.SSLContextService; +import org.apache.nifi.ssl.StandardRestrictedSSLContextService; +import org.apache.nifi.ssl.StandardSSLContextService; import org.apache.nifi.stream.io.NullOutputStream; import org.apache.nifi.stream.io.StreamUtils; import org.apache.nifi.util.MockFlowFile; @@ -42,6 +49,36 @@ import org.junit.Test; public class TestHandleHttpRequest { + private static Map getTruststoreProperties() { + final Map props = new HashMap<>(); + props.put(StandardSSLContextService.TRUSTSTORE.getName(), "src/test/resources/localhost-ts.jks"); + props.put(StandardSSLContextService.TRUSTSTORE_PASSWORD.getName(), "localtest"); + props.put(StandardSSLContextService.TRUSTSTORE_TYPE.getName(), "JKS"); + return props; + } + + private static Map getKeystoreProperties() { + final Map properties = new HashMap<>(); + properties.put(StandardSSLContextService.KEYSTORE.getName(), "src/test/resources/localhost-ks.jks"); + properties.put(StandardSSLContextService.KEYSTORE_PASSWORD.getName(), "localtest"); + properties.put(StandardSSLContextService.KEYSTORE_TYPE.getName(), "JKS"); + return properties; + } + + private static SSLContext useSSLContextService(final TestRunner controller, final Map sslProperties) { + final SSLContextService service = new StandardRestrictedSSLContextService(); + try { + controller.addControllerService("ssl-service", service, sslProperties); + controller.enableControllerService(service); + } catch (InitializationException ex) { + ex.printStackTrace(); + Assert.fail("Could not create SSL Context Service"); + } + + controller.setProperty(HandleHttpRequest.SSL_CONTEXT, "ssl-service"); + return service.createSSLContext(SSLContextService.ClientAuth.WANT); + } + @Test(timeout=10000) public void testRequestAddedToService() throws InitializationException, MalformedURLException, IOException, InterruptedException { final TestRunner runner = TestRunners.newTestRunner(HandleHttpRequest.class); @@ -165,6 +202,73 @@ public class TestHandleHttpRequest { } } + @Test + public void testSecure() throws InitializationException { + final TestRunner runner = TestRunners.newTestRunner(HandleHttpRequest.class); + runner.setProperty(HandleHttpRequest.PORT, "0"); + + final MockHttpContextMap contextMap = new MockHttpContextMap(); + runner.addControllerService("http-context-map", contextMap); + runner.enableControllerService(contextMap); + runner.setProperty(HandleHttpRequest.HTTP_CONTEXT_MAP, "http-context-map"); + + final Map sslProperties = getKeystoreProperties(); + sslProperties.putAll(getTruststoreProperties()); + sslProperties.put(StandardSSLContextService.SSL_ALGORITHM.getName(), "TLSv1.2"); + final SSLContext sslContext = useSSLContextService(runner, sslProperties); + + // trigger processor to stop but not shutdown. + runner.run(1, false); + try { + final Thread httpThread = new Thread(new Runnable() { + @Override + public void run() { + try { + final int port = ((HandleHttpRequest) runner.getProcessor()).getPort(); + final HttpsURLConnection connection = (HttpsURLConnection) new URL("https://localhost:" + + port + "/my/path?query=true&value1=value1&value2=&value3&value4=apple=orange").openConnection(); + + connection.setSSLSocketFactory(sslContext.getSocketFactory()); + connection.setDoOutput(false); + connection.setRequestMethod("GET"); + connection.setRequestProperty("header1", "value1"); + connection.setRequestProperty("header2", ""); + connection.setRequestProperty("header3", "apple=orange"); + connection.setConnectTimeout(3000); + connection.setReadTimeout(3000); + + StreamUtils.copy(connection.getInputStream(), new NullOutputStream()); + } catch (final Throwable t) { + t.printStackTrace(); + Assert.fail(t.toString()); + } + } + }); + httpThread.start(); + + while ( runner.getFlowFilesForRelationship(HandleHttpRequest.REL_SUCCESS).isEmpty() ) { + // process the request. + runner.run(1, false, false); + } + + runner.assertAllFlowFilesTransferred(HandleHttpRequest.REL_SUCCESS, 1); + assertEquals(1, contextMap.size()); + + final MockFlowFile mff = runner.getFlowFilesForRelationship(HandleHttpRequest.REL_SUCCESS).get(0); + mff.assertAttributeEquals("http.query.param.query", "true"); + mff.assertAttributeEquals("http.query.param.value1", "value1"); + mff.assertAttributeEquals("http.query.param.value2", ""); + mff.assertAttributeEquals("http.query.param.value3", ""); + mff.assertAttributeEquals("http.query.param.value4", "apple=orange"); + mff.assertAttributeEquals("http.headers.header1", "value1"); + mff.assertAttributeEquals("http.headers.header3", "apple=orange"); + mff.assertAttributeEquals("http.protocol", "HTTP/1.1"); + } finally { + // shut down the server + runner.run(1, true); + } + } + private static class MockHttpContextMap extends AbstractControllerService implements HttpContextMap { private boolean registerSuccessfully = true;