/* * SPDX-License-Identifier: Apache-2.0 * * The OpenSearch Contributors require contributions made to * this file be licensed under the Apache-2.0 license or a * compatible open source license. */ /* * Licensed to Elasticsearch under one or more contributor * license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright * ownership. Elasticsearch 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. */ /* * Modifications Copyright OpenSearch Contributors. See * GitHub history for details. */ package org.opensearch.rest; import org.opensearch.client.node.NodeClient; import org.opensearch.core.common.bytes.BytesReference; import org.opensearch.common.settings.ClusterSettings; import org.opensearch.common.settings.Settings; import org.opensearch.common.util.concurrent.ThreadContext; import org.opensearch.core.rest.RestStatus; import org.opensearch.identity.IdentityService; import org.opensearch.core.indices.breaker.CircuitBreakerService; import org.opensearch.indices.breaker.HierarchyCircuitBreakerService; import org.opensearch.test.OpenSearchTestCase; import org.opensearch.test.rest.FakeRestChannel; import org.opensearch.test.rest.FakeRestRequest; import org.opensearch.usage.UsageService; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.stream.Collectors; import static org.hamcrest.CoreMatchers.notNullValue; import static org.hamcrest.Matchers.containsInAnyOrder; import static org.hamcrest.Matchers.is; public class RestHttpResponseHeadersTests extends OpenSearchTestCase { /** * For requests to a valid REST endpoint using an unsupported HTTP method, * verify that a 405 HTTP response code is returned, and that the response * 'Allow' header includes a list of valid HTTP methods for the endpoint * (see * HTTP/1.1 - * 10.4.6 - 405 Method Not Allowed). */ public void testUnsupportedMethodResponseHttpHeader() throws Exception { /* * Generate a random set of candidate valid HTTP methods to register * with the test RestController endpoint. Enums are returned in the * order they are declared, so the first step is to shuffle the HTTP * method list, passing in the RandomizedContext's Random instance, * before picking out a candidate sublist. */ List validHttpMethodArray = new ArrayList(Arrays.asList(RestRequest.Method.values())); validHttpMethodArray.remove(RestRequest.Method.OPTIONS); Collections.shuffle(validHttpMethodArray, random()); /* * The upper bound of the potential sublist is one less than the size of * the array, so we are guaranteed at least one invalid method to test. */ validHttpMethodArray = validHttpMethodArray.subList(0, randomIntBetween(1, validHttpMethodArray.size() - 1)); assert (validHttpMethodArray.size() > 0); assert (validHttpMethodArray.size() < RestRequest.Method.values().length); /* * Generate an inverse list of one or more candidate invalid HTTP * methods, so we have a candidate method to fire at the test endpoint. */ List invalidHttpMethodArray = new ArrayList(Arrays.asList(RestRequest.Method.values())); invalidHttpMethodArray.removeAll(validHttpMethodArray); // Remove OPTIONS, or else we'll get a 200 instead of 405 invalidHttpMethodArray.remove(RestRequest.Method.OPTIONS); assert (invalidHttpMethodArray.size() > 0); // Initialize test candidate RestController CircuitBreakerService circuitBreakerService = new HierarchyCircuitBreakerService( Settings.EMPTY, Collections.emptyList(), new ClusterSettings(Settings.EMPTY, ClusterSettings.BUILT_IN_CLUSTER_SETTINGS) ); final Settings settings = Settings.EMPTY; UsageService usageService = new UsageService(); final IdentityService identityService = new IdentityService(settings, List.of()); RestController restController = new RestController( Collections.emptySet(), null, null, circuitBreakerService, usageService, identityService ); // A basic RestHandler handles requests to the endpoint RestHandler restHandler = new RestHandler() { @Override public void handleRequest(RestRequest request, RestChannel channel, NodeClient client) throws Exception { channel.sendResponse(new TestResponse()); } }; // Register valid test handlers with test RestController for (RestRequest.Method method : validHttpMethodArray) { restController.registerHandler(method, "/", restHandler); } // Generate a test request with an invalid HTTP method FakeRestRequest.Builder fakeRestRequestBuilder = new FakeRestRequest.Builder(xContentRegistry()); fakeRestRequestBuilder.withMethod(invalidHttpMethodArray.get(0)); RestRequest restRequest = fakeRestRequestBuilder.build(); // Send the request and verify the response status code FakeRestChannel restChannel = new FakeRestChannel(restRequest, false, 1); restController.dispatchRequest(restRequest, restChannel, new ThreadContext(Settings.EMPTY)); assertThat(restChannel.capturedResponse().status().getStatus(), is(405)); /* * Verify the response allow header contains the valid methods for the * test endpoint */ assertThat(restChannel.capturedResponse().getHeaders().get("Allow"), notNullValue()); String responseAllowHeader = restChannel.capturedResponse().getHeaders().get("Allow").get(0); List responseAllowHeaderArray = Arrays.asList(responseAllowHeader.split(",")); assertThat(responseAllowHeaderArray.size(), is(validHttpMethodArray.size())); assertThat(responseAllowHeaderArray, containsInAnyOrder(getMethodNameStringArray(validHttpMethodArray).toArray())); } private static class TestResponse extends RestResponse { @Override public String contentType() { return null; } @Override public BytesReference content() { return null; } @Override public RestStatus status() { return RestStatus.OK; } } /** * Convert an RestRequest.Method array to a String array, so it can be * compared with the expected 'Allow' header String array. */ private List getMethodNameStringArray(List methodArray) { return methodArray.stream().map(method -> method.toString()).collect(Collectors.toList()); } }