Browse Source

Allow for customised content-type validation (#80906)

In order to support additional media types in request body a custom
validation has to be supported.
This commit moves validation from RestController to RestHandler
interface (default) and allows new RestHandler implementations to
provide its custom implementation

closes #80482
Przemyslaw Gomulka 3 years ago
parent
commit
c8d98cf532

+ 6 - 0
docs/changelog/80906.yaml

@@ -0,0 +1,6 @@
+pr: 80906
+summary: Allow for customised content-type validation
+area: Infra/REST API
+type: enhancement
+issues:
+ - 80482

+ 10 - 0
server/src/main/java/org/elasticsearch/rest/BaseRestHandler.java

@@ -103,6 +103,11 @@ public abstract class BaseRestHandler implements RestHandler {
         action.accept(channel);
     }
 
+    @Override
+    public boolean mediaTypesValid(RestRequest request) {
+        return request.getXContentType() != null;
+    }
+
     protected final String unrecognized(
         final RestRequest request,
         final Set<String> invalids,
@@ -241,5 +246,10 @@ public abstract class BaseRestHandler implements RestHandler {
         public boolean allowsUnsafeBuffers() {
             return delegate.allowsUnsafeBuffers();
         }
+
+        @Override
+        public boolean mediaTypesValid(RestRequest request) {
+            return delegate.mediaTypesValid(request);
+        }
     }
 }

+ 4 - 4
server/src/main/java/org/elasticsearch/rest/RestController.java

@@ -334,15 +334,15 @@ public class RestController implements HttpServerTransport.Dispatcher {
         throws Exception {
         final int contentLength = request.contentLength();
         if (contentLength > 0) {
-            final XContentType xContentType = request.getXContentType();
-            if (xContentType == null) {
+            if (handler.mediaTypesValid(request) == false) {
                 sendContentTypeErrorMessage(request.getAllHeaderValues("Content-Type"), channel);
                 return;
             }
+            final XContentType xContentType = request.getXContentType();
             // TODO consider refactoring to handler.supportsContentStream(xContentType). It is only used with JSON and SMILE
             if (handler.supportsContentStream()
-                && xContentType.canonical() != XContentType.JSON
-                && xContentType.canonical() != XContentType.SMILE) {
+                && XContentType.JSON != xContentType.canonical()
+                && XContentType.SMILE != xContentType.canonical()) {
                 channel.sendResponse(
                     BytesRestResponse.createSimpleErrorResponse(
                         channel,

+ 2 - 5
server/src/main/java/org/elasticsearch/rest/RestHandler.java

@@ -13,10 +13,7 @@ import org.elasticsearch.client.internal.node.NodeClient;
 import org.elasticsearch.core.Nullable;
 import org.elasticsearch.core.RestApiVersion;
 import org.elasticsearch.rest.RestRequest.Method;
-import org.elasticsearch.xcontent.MediaType;
-import org.elasticsearch.xcontent.MediaTypeRegistry;
 import org.elasticsearch.xcontent.XContent;
-import org.elasticsearch.xcontent.XContentType;
 
 import java.util.Collections;
 import java.util.List;
@@ -76,8 +73,8 @@ public interface RestHandler {
         return false;
     }
 
-    default MediaTypeRegistry<? extends MediaType> validAcceptMediaTypes() {
-        return XContentType.MEDIA_TYPE_REGISTRY;
+    default boolean mediaTypesValid(RestRequest request) {
+        return request.getXContentType() != null;
     }
 
     class Route {

+ 26 - 0
server/src/test/java/org/elasticsearch/rest/RestControllerTests.java

@@ -774,6 +774,32 @@ public class RestControllerTests extends ESTestCase {
         assertTrue(channel.getSendResponseCalled());
     }
 
+    public void testCustomMediaTypeValidation() {
+        RestController restController = new RestController(Collections.emptySet(), null, client, circuitBreakerService, usageService);
+
+        final String mediaType = "application/x-protobuf";
+        FakeRestRequest fakeRestRequest = requestWithContent(mediaType);
+        AssertingChannel channel = new AssertingChannel(fakeRestRequest, true, RestStatus.OK);
+
+        // register handler that handles custom media type validation
+        restController.registerHandler(new Route(GET, "/foo"), new RestHandler() {
+            @Override
+            public boolean mediaTypesValid(RestRequest request) {
+                return request.getXContentType() == null
+                    && request.getParsedContentType().mediaTypeWithoutParameters().equals("application/x-protobuf");
+            }
+
+            @Override
+            public void handleRequest(RestRequest request, RestChannel channel, NodeClient client) throws Exception {
+                channel.sendResponse(new BytesRestResponse(RestStatus.OK, BytesRestResponse.TEXT_CONTENT_TYPE, BytesArray.EMPTY));
+            }
+        });
+
+        assertFalse(channel.getSendResponseCalled());
+        restController.dispatchRequest(fakeRestRequest, channel, new ThreadContext(Settings.EMPTY));
+        assertTrue(channel.getSendResponseCalled());
+    }
+
     private static final class TestHttpServerTransport extends AbstractLifecycleComponent implements HttpServerTransport {
 
         TestHttpServerTransport() {}

+ 2 - 4
x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/rest/SecurityRestFilter.java

@@ -24,8 +24,6 @@ import org.elasticsearch.rest.RestRequest;
 import org.elasticsearch.rest.RestRequest.Method;
 import org.elasticsearch.rest.RestRequestFilter;
 import org.elasticsearch.rest.RestStatus;
-import org.elasticsearch.xcontent.MediaType;
-import org.elasticsearch.xcontent.MediaTypeRegistry;
 import org.elasticsearch.xpack.core.XPackSettings;
 import org.elasticsearch.xpack.security.authc.AuthenticationService;
 import org.elasticsearch.xpack.security.authc.support.SecondaryAuthenticator;
@@ -185,7 +183,7 @@ public class SecurityRestFilter implements RestHandler {
     }
 
     @Override
-    public MediaTypeRegistry<? extends MediaType> validAcceptMediaTypes() {
-        return restHandler.validAcceptMediaTypes();
+    public boolean mediaTypesValid(RestRequest request) {
+        return restHandler.mediaTypesValid(request);
     }
 }

+ 0 - 6
x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/plugin/RestSqlQueryAction.java

@@ -12,8 +12,6 @@ import org.elasticsearch.core.RestApiVersion;
 import org.elasticsearch.rest.BaseRestHandler;
 import org.elasticsearch.rest.RestRequest;
 import org.elasticsearch.rest.action.RestCancellableNodeClient;
-import org.elasticsearch.xcontent.MediaType;
-import org.elasticsearch.xcontent.MediaTypeRegistry;
 import org.elasticsearch.xcontent.XContentParser;
 import org.elasticsearch.xpack.sql.action.Protocol;
 import org.elasticsearch.xpack.sql.action.SqlQueryAction;
@@ -42,10 +40,6 @@ public class RestSqlQueryAction extends BaseRestHandler {
         );
     }
 
-    public MediaTypeRegistry<? extends MediaType> validAcceptMediaTypes() {
-        return SqlMediaTypeParser.MEDIA_TYPE_REGISTRY;
-    }
-
     @Override
     protected RestChannelConsumer prepareRequest(RestRequest request, NodeClient client) throws IOException {
         SqlQueryRequest sqlRequest;