Skip to content

Commit 3dabc84

Browse files
authored
NIFI-14057 Return HTTP 405 for TRACE and OPTIONS on all paths for ListenHTTP (#9563)
- Added Filter to handle OPTIONS and TRACE methods for returning HTTP 405 Signed-off-by: David Handermann <[email protected]>
1 parent 116f2f7 commit 3dabc84

File tree

4 files changed

+75
-26
lines changed

4 files changed

+75
-26
lines changed

nifi-extension-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/nifi/processors/standard/ListenHTTP.java

+5
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
*/
1717
package org.apache.nifi.processors.standard;
1818

19+
import jakarta.servlet.DispatcherType;
1920
import jakarta.servlet.Servlet;
2021
import jakarta.servlet.http.HttpServletResponse;
2122
import jakarta.ws.rs.Path;
@@ -44,6 +45,7 @@
4445
import org.apache.nifi.processor.Relationship;
4546
import org.apache.nifi.processor.exception.ProcessException;
4647
import org.apache.nifi.processor.util.StandardValidators;
48+
import org.apache.nifi.processors.standard.filters.HttpMethodFilter;
4749
import org.apache.nifi.processors.standard.http.HttpProtocolStrategy;
4850
import org.apache.nifi.processors.standard.servlets.ContentAcknowledgmentServlet;
4951
import org.apache.nifi.processors.standard.servlets.HealthCheckServlet;
@@ -71,6 +73,7 @@
7173
import java.util.ArrayList;
7274
import java.util.Arrays;
7375
import java.util.Collection;
76+
import java.util.EnumSet;
7477
import java.util.HashSet;
7578
import java.util.List;
7679
import java.util.Map;
@@ -476,6 +479,8 @@ synchronized private void createHttpServerFromService(final ProcessContext conte
476479
}
477480
}
478481

482+
contextHandler.addFilter(HttpMethodFilter.class, "/*", EnumSet.allOf(DispatcherType.class));
483+
479484
contextHandler.setAttribute(CONTEXT_ATTRIBUTE_PROCESSOR, this);
480485
contextHandler.setAttribute(CONTEXT_ATTRIBUTE_LOGGER, getLogger());
481486
contextHandler.setAttribute(CONTEXT_ATTRIBUTE_SESSION_FACTORY_HOLDER, sessionFactoryReference);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one or more
3+
* contributor license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright ownership.
5+
* The ASF licenses this file to You under the Apache License, Version 2.0
6+
* (the "License"); you may not use this file except in compliance with
7+
* the License. You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
package org.apache.nifi.processors.standard.filters;
18+
19+
import jakarta.servlet.Filter;
20+
import jakarta.servlet.FilterChain;
21+
import jakarta.servlet.ServletException;
22+
import jakarta.servlet.ServletRequest;
23+
import jakarta.servlet.ServletResponse;
24+
import jakarta.servlet.http.HttpServletRequest;
25+
import jakarta.servlet.http.HttpServletResponse;
26+
27+
import java.io.IOException;
28+
29+
public class HttpMethodFilter implements Filter {
30+
31+
@Override
32+
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
33+
34+
HttpServletRequest request = (HttpServletRequest) servletRequest;
35+
HttpServletResponse response = (HttpServletResponse) servletResponse;
36+
37+
if (request.getMethod().equals("OPTIONS") || request.getMethod().equals("TRACE")) {
38+
response.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED, "Method Not Allowed");
39+
} else {
40+
filterChain.doFilter(servletRequest, servletResponse);
41+
}
42+
}
43+
}

nifi-extension-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/nifi/processors/standard/servlets/ListenHTTPServlet.java

-16
Original file line numberDiff line numberDiff line change
@@ -158,22 +158,6 @@ protected void doHead(final HttpServletRequest request, final HttpServletRespons
158158
}
159159
}
160160

161-
private void notAllowed(final HttpServletRequest request, final HttpServletResponse response) throws IOException {
162-
response.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED, "Method Not Allowed");
163-
}
164-
165-
@Override
166-
protected void doTrace(final HttpServletRequest request, final HttpServletResponse response) throws ServletException, IOException {
167-
notAllowed(request, response);
168-
logger.debug("Denying TRACE request; method not allowed.");
169-
}
170-
171-
@Override
172-
protected void doOptions(final HttpServletRequest request, final HttpServletResponse response) throws ServletException, IOException {
173-
notAllowed(request, response);
174-
logger.debug("Denying OPTIONS request; method not allowed.");
175-
}
176-
177161
@Override
178162
protected void doPost(final HttpServletRequest request, final HttpServletResponse response) throws ServletException, IOException {
179163

nifi-extension-bundles/nifi-standard-bundle/nifi-standard-processors/src/test/java/org/apache/nifi/processors/standard/TestListenHTTP.java

+27-10
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@
5858
import javax.security.auth.x500.X500Principal;
5959

6060
import jakarta.servlet.http.HttpServletResponse;
61+
6162
import java.io.ByteArrayOutputStream;
6263
import java.io.File;
6364
import java.io.IOException;
@@ -345,14 +346,14 @@ public void testSecureServerSupportsCurrentTlsProtocolVersion() throws Exception
345346
public void testSecureServerTrustStoreConfiguredClientAuthenticationRequired() throws Exception {
346347
configureProcessorSslContextService(ListenHTTP.ClientAuthentication.REQUIRED);
347348
final int port = startSecureServer();
348-
assertThrows(IOException.class, () -> sendMessage(null, true, port, false, HTTP_POST));
349+
assertThrows(IOException.class, () -> sendMessage(null, true, port, HTTP_BASE_PATH, false, HTTP_POST));
349350
}
350351

351352
@Test
352353
public void testSecureServerTrustStoreNotConfiguredClientAuthenticationNotRequired() throws Exception {
353354
configureProcessorSslContextService(ListenHTTP.ClientAuthentication.AUTO);
354355
final int port = startSecureServer();
355-
final int responseCode = sendMessage(null, true, port, true, HTTP_POST);
356+
final int responseCode = sendMessage(null, true, port, HTTP_BASE_PATH, true, HTTP_POST);
356357
assertEquals(HttpServletResponse.SC_NO_CONTENT, responseCode);
357358
}
358359

@@ -464,7 +465,7 @@ public void testPostContentEncodingGzipAccepted() throws IOException {
464465

465466
final OkHttpClient okHttpClient = getOkHttpClient(false, false);
466467
final Request.Builder requestBuilder = new Request.Builder();
467-
final String url = buildUrl(false, port);
468+
final String url = buildUrl(false, port, HTTP_BASE_PATH);
468469
requestBuilder.url(url);
469470

470471
final String message = String.class.getSimpleName();
@@ -496,6 +497,14 @@ public void testOptionsNotAllowed() throws Exception {
496497
startWebServerAndSendMessages(messages, HttpServletResponse.SC_METHOD_NOT_ALLOWED, false, false, HTTP_OPTIONS);
497498
}
498499

500+
@Test
501+
public void testOptionsNotAllowedOnNonBasePath() throws Exception {
502+
final int port = startWebServer();
503+
final int statusCode = sendMessage("payload 1", false, port, "randomPath", false, HTTP_OPTIONS);
504+
505+
assertEquals(HttpServletResponse.SC_METHOD_NOT_ALLOWED, statusCode, "HTTP Status Code not matched");
506+
}
507+
499508
@Test
500509
public void testTraceNotAllowed() throws Exception {
501510
final List<String> messages = new ArrayList<>();
@@ -504,6 +513,14 @@ public void testTraceNotAllowed() throws Exception {
504513
startWebServerAndSendMessages(messages, HttpServletResponse.SC_METHOD_NOT_ALLOWED, false, false, HTTP_TRACE);
505514
}
506515

516+
@Test
517+
public void testTraceNotAllowedOnNonBasePath() throws Exception {
518+
final int port = startWebServer();
519+
final int statusCode = sendMessage("payload 1", false, port, "randomPath", false, HTTP_TRACE);
520+
521+
assertEquals(HttpServletResponse.SC_METHOD_NOT_ALLOWED, statusCode, "HTTP Status Code not matched");
522+
}
523+
507524
private MockRecordParser setupRecordReaderTest() throws InitializationException {
508525
final MockRecordParser parser = new MockRecordParser();
509526
final MockRecordWriter writer = new MockRecordWriter();
@@ -527,10 +544,10 @@ private int startSecureServer() {
527544
return startWebServer();
528545
}
529546

530-
private int sendMessage(final String message, final boolean secure, final int port, boolean clientAuthRequired, final String httpMethod) throws IOException {
547+
private int sendMessage(final String message, final boolean secure, final int port, final String basePath, boolean clientAuthRequired, final String httpMethod) throws IOException {
531548
final byte[] bytes = message == null ? new byte[]{} : message.getBytes(StandardCharsets.UTF_8);
532549
final RequestBody requestBody = RequestBody.create(bytes, APPLICATION_OCTET_STREAM);
533-
final String url = buildUrl(secure, port);
550+
final String url = buildUrl(secure, port, basePath);
534551
final Request.Builder requestBuilder = new Request.Builder();
535552
final Request request = requestBuilder.method(httpMethod, requestBody)
536553
.url(url)
@@ -557,8 +574,8 @@ private OkHttpClient getOkHttpClient(final boolean secure, final boolean clientA
557574
return builder.build();
558575
}
559576

560-
private String buildUrl(final boolean secure, final int port) {
561-
return String.format("%s://localhost:%s/%s", secure ? "https" : "http", port, HTTP_BASE_PATH);
577+
private String buildUrl(final boolean secure, final int port, String basePath) {
578+
return String.format("%s://localhost:%s/%s", secure ? "https" : "http", port, basePath);
562579
}
563580

564581
private void testPOSTRequestsReceived(int returnCode, boolean secure, boolean twoWaySsl) throws Exception {
@@ -623,7 +640,7 @@ private void startWebServerAndSendMessages(final List<String> messages, final in
623640
final int port = startWebServer();
624641

625642
for (final String message : messages) {
626-
final int statusCode = sendMessage(message, secure, port, clientAuthRequired, httpMethod);
643+
final int statusCode = sendMessage(message, secure, port, HTTP_BASE_PATH, clientAuthRequired, httpMethod);
627644
assertEquals(expectedStatusCode, statusCode, "HTTP Status Code not matched");
628645
}
629646
}
@@ -669,7 +686,7 @@ public void testMultipartFormDataRequest() throws IOException {
669686
.build();
670687

671688
final Request request = new Request.Builder()
672-
.url(buildUrl(isSecure, port))
689+
.url(buildUrl(isSecure, port, HTTP_BASE_PATH))
673690
.post(multipartBody)
674691
.build();
675692

@@ -741,7 +758,7 @@ public void testLargeHTTPRequestHeader() throws Exception {
741758

742759
final int port = startWebServer();
743760
OkHttpClient client = getOkHttpClient(false, false);
744-
final String url = buildUrl(false, port);
761+
final String url = buildUrl(false, port, HTTP_BASE_PATH);
745762
Request request = new Request.Builder()
746763
.url(url)
747764
.addHeader("Large-Header", largeHeaderValue)

0 commit comments

Comments
 (0)