From 5f72313003fe8b0142657892ebd94bfee2a053d4 Mon Sep 17 00:00:00 2001 From: exceptionfactory Date: Thu, 26 Dec 2024 22:17:21 -0600 Subject: [PATCH] NIFI-14115 Set Standard HTTP Headers for Framework Responses This closes #9598 - Added HeaderWriterHandler implementing Jetty Handler methods for setting standard HTTP response headers - Removed HeaderWriterFilter approach for writing standard HTTP headers - Refactored Jetty Server instantiation to StandardServerProvider for streamlined configuration and testing - Simplified Server start test method to avoid timing issues - Removed unnecessary Timeout annotation Signed-off-by: Joseph Witt --- .../apache/nifi/web/server/JettyServer.java | 109 +++----------- .../nifi/web/server/ServerProvider.java | 33 +++++ .../web/server/StandardServerProvider.java | 138 ++++++++++++++++++ .../filter/StandardRequestFilterProvider.java | 27 ---- .../server/handler/HeaderWriterHandler.java | 64 ++++++++ .../server/StandardServerProviderTest.java | 131 +++++++++++++++++ .../RestApiRequestFilterProviderTest.java | 2 - .../StandardRequestFilterProviderTest.java | 4 +- 8 files changed, 388 insertions(+), 120 deletions(-) create mode 100644 nifi-framework-bundle/nifi-framework/nifi-web/nifi-jetty/src/main/java/org/apache/nifi/web/server/ServerProvider.java create mode 100644 nifi-framework-bundle/nifi-framework/nifi-web/nifi-jetty/src/main/java/org/apache/nifi/web/server/StandardServerProvider.java create mode 100644 nifi-framework-bundle/nifi-framework/nifi-web/nifi-jetty/src/main/java/org/apache/nifi/web/server/handler/HeaderWriterHandler.java create mode 100644 nifi-framework-bundle/nifi-framework/nifi-web/nifi-jetty/src/test/java/org/apache/nifi/web/server/StandardServerProviderTest.java diff --git a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-jetty/src/main/java/org/apache/nifi/web/server/JettyServer.java b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-jetty/src/main/java/org/apache/nifi/web/server/JettyServer.java index 27f17894b6e4..21e21e434a94 100644 --- a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-jetty/src/main/java/org/apache/nifi/web/server/JettyServer.java +++ b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-jetty/src/main/java/org/apache/nifi/web/server/JettyServer.java @@ -22,11 +22,7 @@ import java.io.IOException; import java.io.InputStreamReader; import java.io.OutputStreamWriter; -import java.io.UncheckedIOException; import java.lang.reflect.InvocationTargetException; -import java.net.InetAddress; -import java.net.NetworkInterface; -import java.net.SocketException; import java.net.URI; import java.net.URISyntaxException; import java.nio.file.Files; @@ -36,7 +32,6 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; -import java.util.Collections; import java.util.EnumSet; import java.util.HashMap; import java.util.HashSet; @@ -103,14 +98,11 @@ import org.apache.nifi.web.ContentAccess; import org.apache.nifi.web.NiFiWebConfigurationContext; import org.apache.nifi.web.UiExtensionType; -import org.apache.nifi.web.server.connector.FrameworkServerConnectorFactory; import org.apache.nifi.web.server.filter.FilterParameter; import org.apache.nifi.web.server.filter.LogoutCompleteRedirectFilter; import org.apache.nifi.web.server.filter.RequestFilterProvider; import org.apache.nifi.web.server.filter.RestApiRequestFilterProvider; import org.apache.nifi.web.server.filter.StandardRequestFilterProvider; -import org.apache.nifi.web.server.log.RequestLogProvider; -import org.apache.nifi.web.server.log.StandardRequestLogProvider; import org.eclipse.jetty.deploy.App; import org.eclipse.jetty.deploy.AppProvider; import org.eclipse.jetty.deploy.DeploymentManager; @@ -121,7 +113,6 @@ import org.eclipse.jetty.rewrite.handler.RedirectPatternRule; import org.eclipse.jetty.rewrite.handler.RewriteHandler; import org.eclipse.jetty.server.Handler; -import org.eclipse.jetty.server.RequestLog; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.ServerConnector; import org.eclipse.jetty.server.SslConnectionFactory; @@ -130,7 +121,6 @@ import org.eclipse.jetty.ee10.servlet.ErrorPageErrorHandler; import org.eclipse.jetty.ee10.servlet.FilterHolder; import org.eclipse.jetty.ee10.servlet.ServletHolder; -import org.eclipse.jetty.util.thread.QueuedThreadPool; import org.eclipse.jetty.ee10.webapp.WebAppClassLoader; import org.eclipse.jetty.ee10.webapp.WebAppContext; import org.slf4j.Logger; @@ -222,42 +212,29 @@ public JettyServer() { public void init() { clearWorkingDirectory(); - final QueuedThreadPool threadPool = new QueuedThreadPool(props.getWebThreads()); - threadPool.setName("NiFi Web Server"); - this.server = new Server(threadPool); - final FrameworkSslContextProvider sslContextProvider = new FrameworkSslContextProvider(props); - this.sslContext = sslContextProvider.loadSslContext().orElse(null); - - configureConnectors(server); - - final ContextHandlerCollection handlerCollection = new ContextHandlerCollection(); - final Handler standardHandler = getStandardHandler(handlerCollection); - server.setHandler(standardHandler); - - final RewriteHandler defaultRewriteHandler = new RewriteHandler(); - final RedirectPatternRule redirectDefault = new RedirectPatternRule("/*", "/nifi"); - defaultRewriteHandler.addRule(redirectDefault); - server.setDefaultHandler(defaultRewriteHandler); - - deploymentManager.setContexts(handlerCollection); - server.addBean(deploymentManager); - - final String requestLogFormat = props.getProperty(NiFiProperties.WEB_REQUEST_LOG_FORMAT); - final RequestLogProvider requestLogProvider = new StandardRequestLogProvider(requestLogFormat); - final RequestLog requestLog = requestLogProvider.getRequestLog(); - server.setRequestLog(requestLog); - } - private Handler getStandardHandler(final ContextHandlerCollection handlerCollection) { - // Only restrict the host header if running in HTTPS mode - if (props.isHTTPSConfigured()) { - final HostHeaderHandler hostHeaderHandler = new HostHeaderHandler(props); - handlerCollection.addHandler(hostHeaderHandler); - } + try { + final FrameworkSslContextProvider sslContextProvider = new FrameworkSslContextProvider(props); + sslContext = sslContextProvider.loadSslContext().orElse(null); - final Handler warHandlers = loadInitialWars(bundles); - handlerCollection.addHandler(warHandlers); - return handlerCollection; + final ServerProvider serverProvider = new StandardServerProvider(sslContext); + server = serverProvider.getServer(props); + + final Handler serverHandler = server.getHandler(); + if (serverHandler instanceof Handler.Collection serverHandlerCollection) { + final ContextHandlerCollection contextHandlerCollection = new ContextHandlerCollection(); + final Handler warHandlers = loadInitialWars(bundles); + contextHandlerCollection.addHandler(warHandlers); + deploymentManager.setContexts(contextHandlerCollection); + server.addBean(deploymentManager); + + serverHandlerCollection.addHandler(contextHandlerCollection); + } else { + throw new IllegalStateException("Server Handler not Handler.Collection: Server Provider configuration failed"); + } + } catch (final Throwable e) { + startUpFailure(e); + } } private void clearWorkingDirectory() { @@ -771,50 +748,6 @@ private File getWebApiDocsDir() { return webApiDocsDir; } - private void configureConnectors(final Server server) { - try { - final FrameworkServerConnectorFactory serverConnectorFactory = new FrameworkServerConnectorFactory(server, props); - if (props.isHTTPSConfigured()) { - serverConnectorFactory.setSslContext(sslContext); - } - - final Map interfaces = props.isHTTPSConfigured() ? props.getHttpsNetworkInterfaces() : props.getHttpNetworkInterfaces(); - final Set interfaceNames = interfaces.values().stream().filter(StringUtils::isNotBlank).collect(Collectors.toSet()); - // Add Server Connectors based on configured Network Interface Names - if (interfaceNames.isEmpty()) { - final ServerConnector serverConnector = serverConnectorFactory.getServerConnector(); - final String host = props.isHTTPSConfigured() ? props.getProperty(NiFiProperties.WEB_HTTPS_HOST) : props.getProperty(NiFiProperties.WEB_HTTP_HOST); - if (StringUtils.isNotBlank(host)) { - serverConnector.setHost(host); - } - server.addConnector(serverConnector); - } else { - interfaceNames.stream() - // Map interface name properties to Network Interfaces - .map(interfaceName -> { - try { - return NetworkInterface.getByName(interfaceName); - } catch (final SocketException e) { - throw new UncheckedIOException(String.format("Network Interface [%s] not found", interfaceName), e); - } - }) - // Map Network Interfaces to host addresses - .filter(Objects::nonNull) - .flatMap(networkInterface -> Collections.list(networkInterface.getInetAddresses()).stream()) - .map(InetAddress::getHostAddress) - // Map host addresses to Server Connectors - .map(host -> { - final ServerConnector serverConnector = serverConnectorFactory.getServerConnector(); - serverConnector.setHost(host); - return serverConnector; - }) - .forEach(server::addConnector); - } - } catch (final Throwable e) { - startUpFailure(e); - } - } - protected List getApplicationUrls() { return Arrays.stream(server.getConnectors()) .map(connector -> (ServerConnector) connector) diff --git a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-jetty/src/main/java/org/apache/nifi/web/server/ServerProvider.java b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-jetty/src/main/java/org/apache/nifi/web/server/ServerProvider.java new file mode 100644 index 000000000000..3c8cf1e01f93 --- /dev/null +++ b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-jetty/src/main/java/org/apache/nifi/web/server/ServerProvider.java @@ -0,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.nifi.web.server; + +import org.apache.nifi.util.NiFiProperties; +import org.eclipse.jetty.server.Server; + +/** + * Abstraction for configuring Server instances based on application properties + */ +interface ServerProvider { + /** + * Get Server configured using application properties + * + * @param properties Application properties + * @return Server + */ + Server getServer(NiFiProperties properties); +} diff --git a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-jetty/src/main/java/org/apache/nifi/web/server/StandardServerProvider.java b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-jetty/src/main/java/org/apache/nifi/web/server/StandardServerProvider.java new file mode 100644 index 000000000000..5fb29c001a9a --- /dev/null +++ b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-jetty/src/main/java/org/apache/nifi/web/server/StandardServerProvider.java @@ -0,0 +1,138 @@ +/* + * 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.nifi.web.server; + +import org.apache.commons.lang3.StringUtils; +import org.apache.nifi.util.NiFiProperties; +import org.apache.nifi.web.server.connector.FrameworkServerConnectorFactory; +import org.apache.nifi.web.server.handler.HeaderWriterHandler; +import org.apache.nifi.web.server.log.RequestLogProvider; +import org.apache.nifi.web.server.log.StandardRequestLogProvider; +import org.eclipse.jetty.rewrite.handler.RedirectPatternRule; +import org.eclipse.jetty.rewrite.handler.RewriteHandler; +import org.eclipse.jetty.server.Handler; +import org.eclipse.jetty.server.RequestLog; +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.server.ServerConnector; +import org.eclipse.jetty.util.thread.QueuedThreadPool; + +import javax.net.ssl.SSLContext; +import java.io.UncheckedIOException; +import java.net.InetAddress; +import java.net.NetworkInterface; +import java.net.SocketException; +import java.util.Collections; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.stream.Collectors; + +/** + * Standard implementation of Server Provider with default Handlers + */ +class StandardServerProvider implements ServerProvider { + private static final String ALL_PATHS_PATTERN = "/*"; + + private static final String FRONTEND_CONTEXT_PATH = "/nifi"; + + private final SSLContext sslContext; + + StandardServerProvider(final SSLContext sslContext) { + this.sslContext = sslContext; + } + + @Override + public Server getServer(final NiFiProperties properties) { + Objects.requireNonNull(properties, "Properties required"); + + final QueuedThreadPool threadPool = new QueuedThreadPool(properties.getWebThreads()); + threadPool.setName("NiFi Web Server"); + final Server server = new Server(threadPool); + addConnectors(server, properties, sslContext); + + final Handler standardHandler = getStandardHandler(properties); + server.setHandler(standardHandler); + + final RewriteHandler defaultRewriteHandler = new RewriteHandler(); + final RedirectPatternRule redirectDefault = new RedirectPatternRule(ALL_PATHS_PATTERN, FRONTEND_CONTEXT_PATH); + defaultRewriteHandler.addRule(redirectDefault); + server.setDefaultHandler(defaultRewriteHandler); + + final String requestLogFormat = properties.getProperty(NiFiProperties.WEB_REQUEST_LOG_FORMAT); + final RequestLogProvider requestLogProvider = new StandardRequestLogProvider(requestLogFormat); + final RequestLog requestLog = requestLogProvider.getRequestLog(); + server.setRequestLog(requestLog); + + return server; + } + + private void addConnectors(final Server server, final NiFiProperties properties, final SSLContext sslContext) { + final FrameworkServerConnectorFactory serverConnectorFactory = new FrameworkServerConnectorFactory(server, properties); + if (properties.isHTTPSConfigured()) { + serverConnectorFactory.setSslContext(sslContext); + } + + final Map interfaces = properties.isHTTPSConfigured() ? properties.getHttpsNetworkInterfaces() : properties.getHttpNetworkInterfaces(); + final Set interfaceNames = interfaces.values().stream().filter(StringUtils::isNotBlank).collect(Collectors.toSet()); + // Add Server Connectors based on configured Network Interface Names + if (interfaceNames.isEmpty()) { + final ServerConnector serverConnector = serverConnectorFactory.getServerConnector(); + final String host = properties.isHTTPSConfigured() ? properties.getProperty(NiFiProperties.WEB_HTTPS_HOST) : properties.getProperty(NiFiProperties.WEB_HTTP_HOST); + if (StringUtils.isNotBlank(host)) { + serverConnector.setHost(host); + } + server.addConnector(serverConnector); + } else { + interfaceNames.stream() + // Map interface name properties to Network Interfaces + .map(interfaceName -> { + try { + return NetworkInterface.getByName(interfaceName); + } catch (final SocketException e) { + throw new UncheckedIOException(String.format("Network Interface [%s] not found", interfaceName), e); + } + }) + // Map Network Interfaces to host addresses + .filter(Objects::nonNull) + .flatMap(networkInterface -> Collections.list(networkInterface.getInetAddresses()).stream()) + .map(InetAddress::getHostAddress) + // Map host addresses to Server Connectors + .map(host -> { + final ServerConnector serverConnector = serverConnectorFactory.getServerConnector(); + serverConnector.setHost(host); + return serverConnector; + }) + .forEach(server::addConnector); + } + } + + private Handler getStandardHandler(final NiFiProperties properties) { + // Standard Handler supporting an ordered sequence of Handlers invoked until completion + final Handler.Collection standardHandler = new Handler.Sequence(); + + // Set Handler for standard response headers + standardHandler.addHandler(new HeaderWriterHandler()); + + // Validate Host Header when running with HTTPS enabled + if (properties.isHTTPSConfigured()) { + final HostHeaderHandler hostHeaderHandler = new HostHeaderHandler(properties); + standardHandler.addHandler(hostHeaderHandler); + } + + return standardHandler; + } +} diff --git a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-jetty/src/main/java/org/apache/nifi/web/server/filter/StandardRequestFilterProvider.java b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-jetty/src/main/java/org/apache/nifi/web/server/filter/StandardRequestFilterProvider.java index 471d2b31941a..364773734ce7 100644 --- a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-jetty/src/main/java/org/apache/nifi/web/server/filter/StandardRequestFilterProvider.java +++ b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-jetty/src/main/java/org/apache/nifi/web/server/filter/StandardRequestFilterProvider.java @@ -24,17 +24,9 @@ import org.apache.nifi.web.server.log.RequestAuthenticationFilter; import org.eclipse.jetty.ee10.servlet.FilterHolder; import org.eclipse.jetty.ee10.servlets.DoSFilter; -import org.springframework.security.web.header.HeaderWriter; -import org.springframework.security.web.header.HeaderWriterFilter; -import org.springframework.security.web.header.writers.ContentSecurityPolicyHeaderWriter; -import org.springframework.security.web.header.writers.HstsHeaderWriter; -import org.springframework.security.web.header.writers.XContentTypeOptionsHeaderWriter; -import org.springframework.security.web.header.writers.XXssProtectionHeaderWriter; -import org.springframework.security.web.header.writers.frameoptions.XFrameOptionsHeaderWriter; import jakarta.servlet.Filter; import java.util.ArrayList; -import java.util.Arrays; import java.util.List; import java.util.Objects; import java.util.concurrent.TimeUnit; @@ -45,8 +37,6 @@ public class StandardRequestFilterProvider implements RequestFilterProvider { private static final int MAX_CONTENT_SIZE_DISABLED = 0; - private static final String STANDARD_CONTENT_POLICY = "frame-ancestors 'self'"; - /** * Get Filters using provided NiFi Properties * @@ -63,8 +53,6 @@ public List getFilters(final NiFiProperties properties) { filters.add(getFilterHolder(RequestAuthenticationFilter.class)); } - filters.add(getHeaderWriterFilter()); - final int maxContentSize = getMaxContentSize(properties); if (maxContentSize > MAX_CONTENT_SIZE_DISABLED) { final FilterHolder contentLengthFilter = getContentLengthFilter(maxContentSize); @@ -93,21 +81,6 @@ protected FilterHolder getDenialOfServiceFilter(final NiFiProperties properties, return filter; } - private FilterHolder getHeaderWriterFilter() { - final List headerWriters = Arrays.asList( - new ContentSecurityPolicyHeaderWriter(STANDARD_CONTENT_POLICY), - new HstsHeaderWriter(), - new XContentTypeOptionsHeaderWriter(), - new XFrameOptionsHeaderWriter(XFrameOptionsHeaderWriter.XFrameOptionsMode.SAMEORIGIN), - new XXssProtectionHeaderWriter() - ); - - final HeaderWriterFilter headerWriterFilter = new HeaderWriterFilter(headerWriters); - final FilterHolder filterHolder = new FilterHolder(headerWriterFilter); - filterHolder.setName(HeaderWriterFilter.class.getSimpleName()); - return filterHolder; - } - private FilterHolder getFilterHolder(final Class filterClass) { final FilterHolder filter = new FilterHolder(filterClass); filter.setName(filterClass.getSimpleName()); diff --git a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-jetty/src/main/java/org/apache/nifi/web/server/handler/HeaderWriterHandler.java b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-jetty/src/main/java/org/apache/nifi/web/server/handler/HeaderWriterHandler.java new file mode 100644 index 000000000000..e2db859c548a --- /dev/null +++ b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-jetty/src/main/java/org/apache/nifi/web/server/handler/HeaderWriterHandler.java @@ -0,0 +1,64 @@ +/* + * 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.nifi.web.server.handler; + +import org.eclipse.jetty.http.HttpFields; +import org.eclipse.jetty.server.Request; +import org.eclipse.jetty.server.Handler; + +import org.eclipse.jetty.server.Response; +import org.eclipse.jetty.util.Callback; + +/** + * HTTP Response Header Writer Handler applies standard headers to HTTP responses + */ +public class HeaderWriterHandler extends Handler.Wrapper { + private static final String CONTENT_SECURITY_POLICY_HEADER = "Content-Security-Policy"; + private static final String CONTENT_SECURITY_POLICY = "frame-ancestors 'self'"; + + private static final String FRAME_OPTIONS_HEADER = "X-Frame-Options"; + private static final String FRAME_OPTIONS = "SAMEORIGIN"; + + private static final String STRICT_TRANSPORT_SECURITY_HEADER = "Strict-Transport-Security"; + private static final String STRICT_TRANSPORT_SECURITY = "max-age=31540000"; + + private static final String XSS_PROTECTION_HEADER = "X-XSS-Protection"; + private static final String XSS_PROTECTION = "1; mode=block"; + + /** + * Handle requests and set HTTP response headers + * + * @param request Jetty Request + * @param response Jetty Response + * @param callback Jetty Callback + * @return Handled status + * @throws Exception Thrown on failures from subsequent handlers + */ + @Override + public boolean handle(final Request request, final Response response, final Callback callback) throws Exception { + final HttpFields.Mutable responseHeaders = response.getHeaders(); + responseHeaders.put(CONTENT_SECURITY_POLICY_HEADER, CONTENT_SECURITY_POLICY); + responseHeaders.put(FRAME_OPTIONS_HEADER, FRAME_OPTIONS); + responseHeaders.put(XSS_PROTECTION_HEADER, XSS_PROTECTION); + + if (request.isSecure()) { + responseHeaders.put(STRICT_TRANSPORT_SECURITY_HEADER, STRICT_TRANSPORT_SECURITY); + } + + return super.handle(request, response, callback); + } +} diff --git a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-jetty/src/test/java/org/apache/nifi/web/server/StandardServerProviderTest.java b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-jetty/src/test/java/org/apache/nifi/web/server/StandardServerProviderTest.java new file mode 100644 index 000000000000..093fb0ffae06 --- /dev/null +++ b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-jetty/src/test/java/org/apache/nifi/web/server/StandardServerProviderTest.java @@ -0,0 +1,131 @@ +/* + * 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.nifi.web.server; + +import org.apache.nifi.jetty.configuration.connector.ApplicationLayerProtocol; +import org.apache.nifi.util.NiFiProperties; +import org.apache.nifi.web.server.handler.HeaderWriterHandler; +import org.eclipse.jetty.rewrite.handler.RewriteHandler; +import org.eclipse.jetty.server.Connector; +import org.eclipse.jetty.server.Handler; +import org.eclipse.jetty.server.RequestLog; +import org.eclipse.jetty.server.Server; +import org.junit.jupiter.api.Test; + +import javax.net.ssl.SSLContext; +import java.security.NoSuchAlgorithmException; +import java.util.List; +import java.util.Properties; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +class StandardServerProviderTest { + + private static final String RANDOM_PORT = "0"; + + private static final String SSL_PROTOCOL = "ssl"; + + @Test + void testGetServer() { + final Properties applicationProperties = new Properties(); + applicationProperties.setProperty(NiFiProperties.WEB_HTTP_PORT, RANDOM_PORT); + final NiFiProperties properties = NiFiProperties.createBasicNiFiProperties(null, applicationProperties); + + final StandardServerProvider provider = new StandardServerProvider(null); + + final Server server = provider.getServer(properties); + + assertStandardConfigurationFound(server); + assertHttpConnectorFound(server); + } + + @Test + void testGetServerHttps() throws NoSuchAlgorithmException { + final Properties applicationProperties = new Properties(); + applicationProperties.setProperty(NiFiProperties.WEB_HTTPS_PORT, RANDOM_PORT); + final NiFiProperties properties = NiFiProperties.createBasicNiFiProperties(null, applicationProperties); + + final SSLContext sslContext = SSLContext.getDefault(); + final StandardServerProvider provider = new StandardServerProvider(sslContext); + + final Server server = provider.getServer(properties); + + assertStandardConfigurationFound(server); + assertHttpsConnectorFound(server); + } + + @Test + void testGetServerStart() throws Exception { + final Properties applicationProperties = new Properties(); + applicationProperties.setProperty(NiFiProperties.WEB_HTTP_PORT, RANDOM_PORT); + final NiFiProperties properties = NiFiProperties.createBasicNiFiProperties(null, applicationProperties); + + final StandardServerProvider provider = new StandardServerProvider(null); + + final Server server = provider.getServer(properties); + + assertStandardConfigurationFound(server); + assertHttpConnectorFound(server); + + try { + server.start(); + + assertFalse(server.isFailed()); + } finally { + server.stop(); + } + } + + void assertHttpConnectorFound(final Server server) { + final Connector[] connectors = server.getConnectors(); + assertNotNull(connectors); + final Connector connector = connectors[0]; + final List protocols = connector.getProtocols(); + assertEquals(ApplicationLayerProtocol.HTTP_1_1.getProtocol(), protocols.getFirst()); + } + + void assertHttpsConnectorFound(final Server server) { + final Connector[] connectors = server.getConnectors(); + assertNotNull(connectors); + final Connector connector = connectors[0]; + final List protocols = connector.getProtocols(); + assertEquals(SSL_PROTOCOL, protocols.getFirst()); + } + + void assertStandardConfigurationFound(final Server server) { + assertNotNull(server); + assertHandlersFound(server); + + final RequestLog requestLog = server.getRequestLog(); + assertNotNull(requestLog); + } + + void assertHandlersFound(final Server server) { + final Handler serverHandler = server.getHandler(); + assertInstanceOf(Handler.Collection.class, serverHandler); + + Handler defaultHandler = server.getDefaultHandler(); + assertInstanceOf(RewriteHandler.class, defaultHandler); + + final Handler.Collection handlerCollection = (Handler.Collection) serverHandler; + final HeaderWriterHandler headerWriterHandler = handlerCollection.getDescendant(HeaderWriterHandler.class); + assertNotNull(headerWriterHandler); + } +} diff --git a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-jetty/src/test/java/org/apache/nifi/web/server/filter/RestApiRequestFilterProviderTest.java b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-jetty/src/test/java/org/apache/nifi/web/server/filter/RestApiRequestFilterProviderTest.java index bf7059532a3c..cc05b119f5e2 100644 --- a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-jetty/src/test/java/org/apache/nifi/web/server/filter/RestApiRequestFilterProviderTest.java +++ b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-jetty/src/test/java/org/apache/nifi/web/server/filter/RestApiRequestFilterProviderTest.java @@ -21,7 +21,6 @@ import org.eclipse.jetty.ee10.servlets.DoSFilter; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import org.springframework.security.web.header.HeaderWriterFilter; import jakarta.servlet.Filter; import java.util.Collections; @@ -56,7 +55,6 @@ public void testGetFilters() { assertNotNull(filters); assertFalse(filters.isEmpty()); - assertFilterClassFound(filters, HeaderWriterFilter.class); assertFilterClassFound(filters, DataTransferExcludedDoSFilter.class); } diff --git a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-jetty/src/test/java/org/apache/nifi/web/server/filter/StandardRequestFilterProviderTest.java b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-jetty/src/test/java/org/apache/nifi/web/server/filter/StandardRequestFilterProviderTest.java index a0dc2b8e9032..51380af793ae 100644 --- a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-jetty/src/test/java/org/apache/nifi/web/server/filter/StandardRequestFilterProviderTest.java +++ b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-jetty/src/test/java/org/apache/nifi/web/server/filter/StandardRequestFilterProviderTest.java @@ -22,7 +22,6 @@ import org.eclipse.jetty.ee10.servlet.FilterHolder; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import org.springframework.security.web.header.HeaderWriterFilter; import jakarta.servlet.Filter; import java.util.Collections; @@ -81,7 +80,7 @@ public void testGetFiltersHttpsEnabled() { assertFilterClassFound(filters, RequestAuthenticationFilter.class); - final FilterHolder firstFilterHolder = filters.get(0); + final FilterHolder firstFilterHolder = filters.getFirst(); final Class firstFilterClass = firstFilterHolder.getHeldClass(); assertEquals(RequestAuthenticationFilter.class, firstFilterClass); } @@ -90,7 +89,6 @@ private void assertStandardFiltersFound(final List filters) { assertNotNull(filters); assertFalse(filters.isEmpty()); - assertFilterClassFound(filters, HeaderWriterFilter.class); assertFilterClassFound(filters, DataTransferExcludedDoSFilter.class); }