mirror of
https://github.com/honeymoose/OpenSearch.git
synced 2025-02-23 05:15:04 +00:00
Move CORS Config into :server package (#43779)
This commit moves the config that stores Cors options into the server package. Currently both nio and netty modules must have a copy of this config. Moving it into server allows one copy and the tests to be in a common location.
This commit is contained in:
parent
0a352486e8
commit
6b1a769638
@ -34,7 +34,6 @@ import io.netty.channel.socket.nio.NioServerSocketChannel;
|
||||
import io.netty.handler.codec.ByteToMessageDecoder;
|
||||
import io.netty.handler.codec.http.HttpContentCompressor;
|
||||
import io.netty.handler.codec.http.HttpContentDecompressor;
|
||||
import io.netty.handler.codec.http.HttpMethod;
|
||||
import io.netty.handler.codec.http.HttpObjectAggregator;
|
||||
import io.netty.handler.codec.http.HttpRequestDecoder;
|
||||
import io.netty.handler.codec.http.HttpResponseEncoder;
|
||||
@ -44,12 +43,10 @@ import io.netty.util.AttributeKey;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.elasticsearch.ExceptionsHelper;
|
||||
import org.elasticsearch.common.Strings;
|
||||
import org.elasticsearch.common.network.NetworkService;
|
||||
import org.elasticsearch.common.settings.Setting;
|
||||
import org.elasticsearch.common.settings.Setting.Property;
|
||||
import org.elasticsearch.common.settings.Settings;
|
||||
import org.elasticsearch.common.settings.SettingsException;
|
||||
import org.elasticsearch.common.unit.ByteSizeUnit;
|
||||
import org.elasticsearch.common.unit.ByteSizeValue;
|
||||
import org.elasticsearch.common.util.BigArrays;
|
||||
@ -60,26 +57,14 @@ import org.elasticsearch.http.HttpChannel;
|
||||
import org.elasticsearch.http.HttpHandlingSettings;
|
||||
import org.elasticsearch.http.HttpReadTimeoutException;
|
||||
import org.elasticsearch.http.HttpServerChannel;
|
||||
import org.elasticsearch.http.netty4.cors.Netty4CorsConfig;
|
||||
import org.elasticsearch.http.netty4.cors.Netty4CorsConfigBuilder;
|
||||
import org.elasticsearch.http.netty4.cors.Netty4CorsHandler;
|
||||
import org.elasticsearch.rest.RestUtils;
|
||||
import org.elasticsearch.threadpool.ThreadPool;
|
||||
import org.elasticsearch.transport.netty4.Netty4Utils;
|
||||
|
||||
import java.net.InetSocketAddress;
|
||||
import java.util.Arrays;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.regex.PatternSyntaxException;
|
||||
|
||||
import static org.elasticsearch.common.util.concurrent.EsExecutors.daemonThreadFactory;
|
||||
import static org.elasticsearch.http.HttpTransportSettings.SETTING_CORS_ALLOW_CREDENTIALS;
|
||||
import static org.elasticsearch.http.HttpTransportSettings.SETTING_CORS_ALLOW_HEADERS;
|
||||
import static org.elasticsearch.http.HttpTransportSettings.SETTING_CORS_ALLOW_METHODS;
|
||||
import static org.elasticsearch.http.HttpTransportSettings.SETTING_CORS_ALLOW_ORIGIN;
|
||||
import static org.elasticsearch.http.HttpTransportSettings.SETTING_CORS_ENABLED;
|
||||
import static org.elasticsearch.http.HttpTransportSettings.SETTING_CORS_MAX_AGE;
|
||||
import static org.elasticsearch.http.HttpTransportSettings.SETTING_HTTP_MAX_CHUNK_SIZE;
|
||||
import static org.elasticsearch.http.HttpTransportSettings.SETTING_HTTP_MAX_CONTENT_LENGTH;
|
||||
import static org.elasticsearch.http.HttpTransportSettings.SETTING_HTTP_MAX_HEADER_SIZE;
|
||||
@ -91,7 +76,6 @@ import static org.elasticsearch.http.HttpTransportSettings.SETTING_HTTP_TCP_RECE
|
||||
import static org.elasticsearch.http.HttpTransportSettings.SETTING_HTTP_TCP_REUSE_ADDRESS;
|
||||
import static org.elasticsearch.http.HttpTransportSettings.SETTING_HTTP_TCP_SEND_BUFFER_SIZE;
|
||||
import static org.elasticsearch.http.HttpTransportSettings.SETTING_PIPELINING_MAX_EVENTS;
|
||||
import static org.elasticsearch.http.netty4.cors.Netty4CorsHandler.ANY_ORIGIN;
|
||||
|
||||
public class Netty4HttpServerTransport extends AbstractHttpServerTransport {
|
||||
private static final Logger logger = LogManager.getLogger(Netty4HttpServerTransport.class);
|
||||
@ -156,8 +140,6 @@ public class Netty4HttpServerTransport extends AbstractHttpServerTransport {
|
||||
|
||||
protected volatile ServerBootstrap serverBootstrap;
|
||||
|
||||
private final Netty4CorsConfig corsConfig;
|
||||
|
||||
public Netty4HttpServerTransport(Settings settings, NetworkService networkService, BigArrays bigArrays, ThreadPool threadPool,
|
||||
NamedXContentRegistry xContentRegistry, Dispatcher dispatcher) {
|
||||
super(settings, networkService, bigArrays, threadPool, xContentRegistry, dispatcher);
|
||||
@ -176,8 +158,6 @@ public class Netty4HttpServerTransport extends AbstractHttpServerTransport {
|
||||
ByteSizeValue receivePredictor = SETTING_HTTP_NETTY_RECEIVE_PREDICTOR_SIZE.get(settings);
|
||||
recvByteBufAllocator = new FixedRecvByteBufAllocator(receivePredictor.bytesAsInt());
|
||||
|
||||
this.corsConfig = buildCorsConfig(settings);
|
||||
|
||||
logger.debug("using max_chunk_size[{}], max_header_size[{}], max_initial_line_length[{}], max_content_length[{}], " +
|
||||
"receive_predictor[{}], max_composite_buffer_components[{}], pipelining_max_events[{}]",
|
||||
maxChunkSize, maxHeaderSize, maxInitialLineLength, maxContentLength, receivePredictor, maxCompositeBufferComponents,
|
||||
@ -230,43 +210,6 @@ public class Netty4HttpServerTransport extends AbstractHttpServerTransport {
|
||||
}
|
||||
}
|
||||
|
||||
// package private for testing
|
||||
static Netty4CorsConfig buildCorsConfig(Settings settings) {
|
||||
if (SETTING_CORS_ENABLED.get(settings) == false) {
|
||||
return Netty4CorsConfigBuilder.forOrigins().disable().build();
|
||||
}
|
||||
String origin = SETTING_CORS_ALLOW_ORIGIN.get(settings);
|
||||
final Netty4CorsConfigBuilder builder;
|
||||
if (Strings.isNullOrEmpty(origin)) {
|
||||
builder = Netty4CorsConfigBuilder.forOrigins();
|
||||
} else if (origin.equals(ANY_ORIGIN)) {
|
||||
builder = Netty4CorsConfigBuilder.forAnyOrigin();
|
||||
} else {
|
||||
try {
|
||||
Pattern p = RestUtils.checkCorsSettingForRegex(origin);
|
||||
if (p == null) {
|
||||
builder = Netty4CorsConfigBuilder.forOrigins(RestUtils.corsSettingAsArray(origin));
|
||||
} else {
|
||||
builder = Netty4CorsConfigBuilder.forPattern(p);
|
||||
}
|
||||
} catch (PatternSyntaxException e) {
|
||||
throw new SettingsException("Bad regex in [" + SETTING_CORS_ALLOW_ORIGIN.getKey() + "]: [" + origin + "]", e);
|
||||
}
|
||||
}
|
||||
if (SETTING_CORS_ALLOW_CREDENTIALS.get(settings)) {
|
||||
builder.allowCredentials();
|
||||
}
|
||||
String[] strMethods = Strings.tokenizeToStringArray(SETTING_CORS_ALLOW_METHODS.get(settings), ",");
|
||||
HttpMethod[] methods = Arrays.stream(strMethods)
|
||||
.map(HttpMethod::valueOf)
|
||||
.toArray(HttpMethod[]::new);
|
||||
return builder.allowedRequestMethods(methods)
|
||||
.maxAge(SETTING_CORS_MAX_AGE.get(settings))
|
||||
.allowedRequestHeaders(Strings.tokenizeToStringArray(SETTING_CORS_ALLOW_HEADERS.get(settings), ","))
|
||||
.shortCircuit()
|
||||
.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected HttpServerChannel bind(InetSocketAddress socketAddress) throws Exception {
|
||||
ChannelFuture future = serverBootstrap.bind(socketAddress).sync();
|
||||
|
@ -1,221 +0,0 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
package org.elasticsearch.http.netty4.cors;
|
||||
|
||||
import io.netty.handler.codec.http.DefaultHttpHeaders;
|
||||
import io.netty.handler.codec.http.EmptyHttpHeaders;
|
||||
import io.netty.handler.codec.http.HttpHeaders;
|
||||
import io.netty.handler.codec.http.HttpMethod;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* Configuration for Cross-Origin Resource Sharing (CORS).
|
||||
*
|
||||
* This class was lifted from the Netty project:
|
||||
* https://github.com/netty/netty
|
||||
*/
|
||||
public final class Netty4CorsConfig {
|
||||
|
||||
private final Optional<Set<String>> origins;
|
||||
private final Optional<Pattern> pattern;
|
||||
private final boolean anyOrigin;
|
||||
private final boolean enabled;
|
||||
private final boolean allowCredentials;
|
||||
private final long maxAge;
|
||||
private final Set<HttpMethod> allowedRequestMethods;
|
||||
private final Set<String> allowedRequestHeaders;
|
||||
private final Map<CharSequence, Callable<?>> preflightHeaders;
|
||||
private final boolean shortCircuit;
|
||||
|
||||
Netty4CorsConfig(final Netty4CorsConfigBuilder builder) {
|
||||
origins = builder.origins.map(s -> new LinkedHashSet<>(s));
|
||||
pattern = builder.pattern;
|
||||
anyOrigin = builder.anyOrigin;
|
||||
enabled = builder.enabled;
|
||||
allowCredentials = builder.allowCredentials;
|
||||
maxAge = builder.maxAge;
|
||||
allowedRequestMethods = builder.requestMethods;
|
||||
allowedRequestHeaders = builder.requestHeaders;
|
||||
preflightHeaders = builder.preflightHeaders;
|
||||
shortCircuit = builder.shortCircuit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if support for CORS is enabled.
|
||||
*
|
||||
* @return {@code true} if support for CORS is enabled, false otherwise.
|
||||
*/
|
||||
public boolean isCorsSupportEnabled() {
|
||||
return enabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether a wildcard origin, '*', is supported. This also means that null origins are
|
||||
* supported.
|
||||
*
|
||||
* @return {@code boolean} true if any origin is allowed.
|
||||
*/
|
||||
public boolean isAnyOriginSupported() {
|
||||
return anyOrigin;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the set of allowed origins.
|
||||
*
|
||||
* @return {@code Set} the allowed origins.
|
||||
*/
|
||||
public Optional<Set<String>> origins() {
|
||||
return origins;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the input origin is allowed by this configuration.
|
||||
*
|
||||
* @return {@code true} if the origin is allowed, otherwise {@code false}
|
||||
*/
|
||||
public boolean isOriginAllowed(final String origin) {
|
||||
if (origins.isPresent()) {
|
||||
return origins.get().contains(origin);
|
||||
} else if (pattern.isPresent()) {
|
||||
return pattern.get().matcher(origin).matches();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if credentials are supported for CORS requests.
|
||||
*
|
||||
* By default credentials are not included in CORS requests but if isCredentialsAllowed returns
|
||||
* true credentials will be added to CORS requests. Setting this value to true will set the
|
||||
* CORS 'Access-Control-Allow-Credentials' response header to true.
|
||||
*
|
||||
* Please note that credentials support needs to be enabled on the client side as well.
|
||||
* The client needs to opt-in to send credentials by calling:
|
||||
* <pre>
|
||||
* xhr.withCredentials = true;
|
||||
* </pre>
|
||||
* The default value for 'withCredentials' is false in which case no credentials are sent.
|
||||
* Setting this to true will included credentials in cross origin requests.
|
||||
*
|
||||
* @return {@code true} if credentials are supported.
|
||||
*/
|
||||
public boolean isCredentialsAllowed() {
|
||||
return allowCredentials;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the maxAge setting.
|
||||
*
|
||||
* When making a preflight request the client has to perform two request with can be inefficient.
|
||||
* This setting will set the CORS 'Access-Control-Max-Age' response header and enables the
|
||||
* caching of the preflight response for the specified time. During this time no preflight
|
||||
* request will be made.
|
||||
*
|
||||
* @return {@code long} the time in seconds that a preflight request may be cached.
|
||||
*/
|
||||
public long maxAge() {
|
||||
return maxAge;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the allowed set of Request Methods. The Http methods that should be returned in the
|
||||
* CORS 'Access-Control-Request-Method' response header.
|
||||
*
|
||||
* @return {@code Set} of {@link HttpMethod}s that represent the allowed Request Methods.
|
||||
*/
|
||||
public Set<HttpMethod> allowedRequestMethods() {
|
||||
return Collections.unmodifiableSet(allowedRequestMethods);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the allowed set of Request Headers.
|
||||
*
|
||||
* The header names returned from this method will be used to set the CORS
|
||||
* 'Access-Control-Allow-Headers' response header.
|
||||
*
|
||||
* @return {@code Set<String>} of strings that represent the allowed Request Headers.
|
||||
*/
|
||||
public Set<String> allowedRequestHeaders() {
|
||||
return Collections.unmodifiableSet(allowedRequestHeaders);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns HTTP response headers that should be added to a CORS preflight response.
|
||||
*
|
||||
* @return {@link HttpHeaders} the HTTP response headers to be added.
|
||||
*/
|
||||
public HttpHeaders preflightResponseHeaders() {
|
||||
if (preflightHeaders.isEmpty()) {
|
||||
return EmptyHttpHeaders.INSTANCE;
|
||||
}
|
||||
final HttpHeaders preflightHeaders = new DefaultHttpHeaders();
|
||||
for (Map.Entry<CharSequence, Callable<?>> entry : this.preflightHeaders.entrySet()) {
|
||||
final Object value = getValue(entry.getValue());
|
||||
if (value instanceof Iterable) {
|
||||
preflightHeaders.add(entry.getKey().toString(), (Iterable<?>) value);
|
||||
} else {
|
||||
preflightHeaders.add(entry.getKey().toString(), value);
|
||||
}
|
||||
}
|
||||
return preflightHeaders;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether a CORS request should be rejected if it's invalid before being
|
||||
* further processing.
|
||||
*
|
||||
* CORS headers are set after a request is processed. This may not always be desired
|
||||
* and this setting will check that the Origin is valid and if it is not valid no
|
||||
* further processing will take place, and a error will be returned to the calling client.
|
||||
*
|
||||
* @return {@code true} if a CORS request should short-circuit upon receiving an invalid Origin header.
|
||||
*/
|
||||
public boolean isShortCircuit() {
|
||||
return shortCircuit;
|
||||
}
|
||||
|
||||
private static <T> T getValue(final Callable<T> callable) {
|
||||
try {
|
||||
return callable.call();
|
||||
} catch (final Exception e) {
|
||||
throw new IllegalStateException("Could not generate value for callable [" + callable + ']', e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "CorsConfig[enabled=" + enabled +
|
||||
", origins=" + origins +
|
||||
", anyOrigin=" + anyOrigin +
|
||||
", isCredentialsAllowed=" + allowCredentials +
|
||||
", maxAge=" + maxAge +
|
||||
", allowedRequestMethods=" + allowedRequestMethods +
|
||||
", allowedRequestHeaders=" + allowedRequestHeaders +
|
||||
", preflightHeaders=" + preflightHeaders + ']';
|
||||
}
|
||||
|
||||
}
|
@ -1,265 +0,0 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
package org.elasticsearch.http.netty4.cors;
|
||||
|
||||
import io.netty.handler.codec.http.HttpMethod;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* Builder used to configure and build a {@link Netty4CorsConfig} instance.
|
||||
*
|
||||
* This class was lifted from the Netty project:
|
||||
* https://github.com/netty/netty
|
||||
*/
|
||||
public final class Netty4CorsConfigBuilder {
|
||||
|
||||
/**
|
||||
* Creates a Builder instance with it's origin set to '*'.
|
||||
*
|
||||
* @return Builder to support method chaining.
|
||||
*/
|
||||
public static Netty4CorsConfigBuilder forAnyOrigin() {
|
||||
return new Netty4CorsConfigBuilder();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a {@link Netty4CorsConfigBuilder} instance with the specified pattern origin.
|
||||
*
|
||||
* @param pattern the regular expression pattern to match incoming origins on.
|
||||
* @return {@link Netty4CorsConfigBuilder} with the configured origin pattern.
|
||||
*/
|
||||
public static Netty4CorsConfigBuilder forPattern(final Pattern pattern) {
|
||||
if (pattern == null) {
|
||||
throw new IllegalArgumentException("CORS pattern cannot be null");
|
||||
}
|
||||
return new Netty4CorsConfigBuilder(pattern);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@link Netty4CorsConfigBuilder} instance with the specified origins.
|
||||
*
|
||||
* @return {@link Netty4CorsConfigBuilder} to support method chaining.
|
||||
*/
|
||||
public static Netty4CorsConfigBuilder forOrigins(final String... origins) {
|
||||
return new Netty4CorsConfigBuilder(origins);
|
||||
}
|
||||
|
||||
Optional<Set<String>> origins;
|
||||
Optional<Pattern> pattern;
|
||||
final boolean anyOrigin;
|
||||
boolean enabled = true;
|
||||
boolean allowCredentials;
|
||||
long maxAge;
|
||||
final Set<HttpMethod> requestMethods = new HashSet<>();
|
||||
final Set<String> requestHeaders = new HashSet<>();
|
||||
final Map<CharSequence, Callable<?>> preflightHeaders = new HashMap<>();
|
||||
boolean shortCircuit;
|
||||
|
||||
/**
|
||||
* Creates a new Builder instance with the origin passed in.
|
||||
*
|
||||
* @param origins the origin to be used for this builder.
|
||||
*/
|
||||
Netty4CorsConfigBuilder(final String... origins) {
|
||||
this.origins = Optional.of(new LinkedHashSet<>(Arrays.asList(origins)));
|
||||
pattern = Optional.empty();
|
||||
anyOrigin = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new Builder instance allowing any origin, "*" which is the
|
||||
* wildcard origin.
|
||||
*
|
||||
*/
|
||||
Netty4CorsConfigBuilder() {
|
||||
anyOrigin = true;
|
||||
origins = Optional.empty();
|
||||
pattern = Optional.empty();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new Builder instance allowing any origin that matches the pattern.
|
||||
*
|
||||
* @param pattern the pattern to match against for incoming origins.
|
||||
*/
|
||||
Netty4CorsConfigBuilder(final Pattern pattern) {
|
||||
this.pattern = Optional.of(pattern);
|
||||
origins = Optional.empty();
|
||||
anyOrigin = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Disables CORS support.
|
||||
*
|
||||
* @return {@link Netty4CorsConfigBuilder} to support method chaining.
|
||||
*/
|
||||
public Netty4CorsConfigBuilder disable() {
|
||||
enabled = false;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* By default cookies are not included in CORS requests, but this method will enable cookies to
|
||||
* be added to CORS requests. Calling this method will set the CORS 'Access-Control-Allow-Credentials'
|
||||
* response header to true.
|
||||
*
|
||||
* Please note, that cookie support needs to be enabled on the client side as well.
|
||||
* The client needs to opt-in to send cookies by calling:
|
||||
* <pre>
|
||||
* xhr.withCredentials = true;
|
||||
* </pre>
|
||||
* The default value for 'withCredentials' is false in which case no cookies are sent.
|
||||
* Setting this to true will included cookies in cross origin requests.
|
||||
*
|
||||
* @return {@link Netty4CorsConfigBuilder} to support method chaining.
|
||||
*/
|
||||
public Netty4CorsConfigBuilder allowCredentials() {
|
||||
allowCredentials = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* When making a preflight request the client has to perform two request with can be inefficient.
|
||||
* This setting will set the CORS 'Access-Control-Max-Age' response header and enables the
|
||||
* caching of the preflight response for the specified time. During this time no preflight
|
||||
* request will be made.
|
||||
*
|
||||
* @param max the maximum time, in seconds, that the preflight response may be cached.
|
||||
* @return {@link Netty4CorsConfigBuilder} to support method chaining.
|
||||
*/
|
||||
public Netty4CorsConfigBuilder maxAge(final long max) {
|
||||
maxAge = max;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Specifies the allowed set of HTTP Request Methods that should be returned in the
|
||||
* CORS 'Access-Control-Request-Method' response header.
|
||||
*
|
||||
* @param methods the {@link HttpMethod}s that should be allowed.
|
||||
* @return {@link Netty4CorsConfigBuilder} to support method chaining.
|
||||
*/
|
||||
public Netty4CorsConfigBuilder allowedRequestMethods(final HttpMethod... methods) {
|
||||
requestMethods.addAll(Arrays.asList(methods));
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Specifies the if headers that should be returned in the CORS 'Access-Control-Allow-Headers'
|
||||
* response header.
|
||||
*
|
||||
* If a client specifies headers on the request, for example by calling:
|
||||
* <pre>
|
||||
* xhr.setRequestHeader('My-Custom-Header', "SomeValue");
|
||||
* </pre>
|
||||
* the server will receive the above header name in the 'Access-Control-Request-Headers' of the
|
||||
* preflight request. The server will then decide if it allows this header to be sent for the
|
||||
* real request (remember that a preflight is not the real request but a request asking the server
|
||||
* if it allow a request).
|
||||
*
|
||||
* @param headers the headers to be added to the preflight 'Access-Control-Allow-Headers' response header.
|
||||
* @return {@link Netty4CorsConfigBuilder} to support method chaining.
|
||||
*/
|
||||
public Netty4CorsConfigBuilder allowedRequestHeaders(final String... headers) {
|
||||
requestHeaders.addAll(Arrays.asList(headers));
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Specifies that a CORS request should be rejected if it's invalid before being
|
||||
* further processing.
|
||||
*
|
||||
* CORS headers are set after a request is processed. This may not always be desired
|
||||
* and this setting will check that the Origin is valid and if it is not valid no
|
||||
* further processing will take place, and a error will be returned to the calling client.
|
||||
*
|
||||
* @return {@link Netty4CorsConfigBuilder} to support method chaining.
|
||||
*/
|
||||
public Netty4CorsConfigBuilder shortCircuit() {
|
||||
shortCircuit = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds a {@link Netty4CorsConfig} with settings specified by previous method calls.
|
||||
*
|
||||
* @return {@link Netty4CorsConfig} the configured CorsConfig instance.
|
||||
*/
|
||||
public Netty4CorsConfig build() {
|
||||
if (preflightHeaders.isEmpty()) {
|
||||
preflightHeaders.put("date", DateValueGenerator.INSTANCE);
|
||||
preflightHeaders.put("content-length", new ConstantValueGenerator("0"));
|
||||
}
|
||||
return new Netty4CorsConfig(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* This class is used for preflight HTTP response values that do not need to be
|
||||
* generated, but instead the value is "static" in that the same value will be returned
|
||||
* for each call.
|
||||
*/
|
||||
private static final class ConstantValueGenerator implements Callable<Object> {
|
||||
|
||||
private final Object value;
|
||||
|
||||
/**
|
||||
* Sole constructor.
|
||||
*
|
||||
* @param value the value that will be returned when the call method is invoked.
|
||||
*/
|
||||
private ConstantValueGenerator(final Object value) {
|
||||
if (value == null) {
|
||||
throw new IllegalArgumentException("value must not be null");
|
||||
}
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object call() {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This callable is used for the DATE preflight HTTP response HTTP header.
|
||||
* It's value must be generated when the response is generated, hence will be
|
||||
* different for every call.
|
||||
*/
|
||||
private static final class DateValueGenerator implements Callable<Date> {
|
||||
|
||||
static final DateValueGenerator INSTANCE = new DateValueGenerator();
|
||||
|
||||
@Override
|
||||
public Date call() throws Exception {
|
||||
return new Date();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -32,31 +32,32 @@ import io.netty.handler.codec.http.HttpRequest;
|
||||
import io.netty.handler.codec.http.HttpResponse;
|
||||
import io.netty.handler.codec.http.HttpResponseStatus;
|
||||
import org.elasticsearch.common.Strings;
|
||||
import org.elasticsearch.http.CorsHandler;
|
||||
import org.elasticsearch.http.netty4.Netty4HttpResponse;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* Handles <a href="http://www.w3.org/TR/cors/">Cross Origin Resource Sharing</a> (CORS) requests.
|
||||
* <p>
|
||||
* This handler can be configured using a {@link Netty4CorsConfig}, please
|
||||
* This handler can be configured using a {@link CorsHandler.Config}, please
|
||||
* refer to this class for details about the configuration options available.
|
||||
*
|
||||
* This code was borrowed from Netty 4 and refactored to work for Elasticsearch's Netty 3 setup.
|
||||
*/
|
||||
public class Netty4CorsHandler extends ChannelDuplexHandler {
|
||||
|
||||
public static final String ANY_ORIGIN = "*";
|
||||
private static Pattern SCHEME_PATTERN = Pattern.compile("^https?://");
|
||||
|
||||
private final Netty4CorsConfig config;
|
||||
private final CorsHandler.Config config;
|
||||
private FullHttpRequest request;
|
||||
|
||||
/**
|
||||
* Creates a new instance with the specified {@link Netty4CorsConfig}.
|
||||
* Creates a new instance with the specified {@link CorsHandler.Config}.
|
||||
*/
|
||||
public Netty4CorsHandler(final Netty4CorsConfig config) {
|
||||
public Netty4CorsHandler(final CorsHandler.Config config) {
|
||||
if (config == null) {
|
||||
throw new NullPointerException();
|
||||
}
|
||||
@ -76,7 +77,7 @@ public class Netty4CorsHandler extends ChannelDuplexHandler {
|
||||
releaseRequest();
|
||||
}
|
||||
}
|
||||
if (config.isShortCircuit() && !validateOrigin()) {
|
||||
if (!validateOrigin()) {
|
||||
try {
|
||||
forbidden(ctx, request);
|
||||
return;
|
||||
@ -96,7 +97,7 @@ public class Netty4CorsHandler extends ChannelDuplexHandler {
|
||||
ctx.write(response, promise);
|
||||
}
|
||||
|
||||
public static void setCorsResponseHeaders(HttpRequest request, HttpResponse resp, Netty4CorsConfig config) {
|
||||
public static void setCorsResponseHeaders(HttpRequest request, HttpResponse resp, CorsHandler.Config config) {
|
||||
if (!config.isCorsSupportEnabled()) {
|
||||
return;
|
||||
}
|
||||
@ -161,7 +162,8 @@ public class Netty4CorsHandler extends ChannelDuplexHandler {
|
||||
* @param response the HttpResponse to which the preflight response headers should be added.
|
||||
*/
|
||||
private void setPreflightHeaders(final HttpResponse response) {
|
||||
response.headers().add(config.preflightResponseHeaders());
|
||||
response.headers().add("date", new Date());
|
||||
response.headers().add("content-length", "0");
|
||||
}
|
||||
|
||||
private boolean setOrigin(final HttpResponse response) {
|
||||
|
@ -29,6 +29,7 @@ import io.netty.handler.codec.http.HttpResponse;
|
||||
import io.netty.handler.codec.http.HttpVersion;
|
||||
import org.elasticsearch.common.bytes.BytesArray;
|
||||
import org.elasticsearch.common.settings.Settings;
|
||||
import org.elasticsearch.http.CorsHandler;
|
||||
import org.elasticsearch.http.HttpTransportSettings;
|
||||
import org.elasticsearch.http.netty4.cors.Netty4CorsHandler;
|
||||
import org.elasticsearch.rest.RestStatus;
|
||||
@ -140,7 +141,7 @@ public class Netty4CorsTests extends ESTestCase {
|
||||
}
|
||||
httpRequest.headers().add(HttpHeaderNames.HOST, host);
|
||||
EmbeddedChannel embeddedChannel = new EmbeddedChannel();
|
||||
embeddedChannel.pipeline().addLast(new Netty4CorsHandler(Netty4HttpServerTransport.buildCorsConfig(settings)));
|
||||
embeddedChannel.pipeline().addLast(new Netty4CorsHandler(CorsHandler.fromSettings(settings)));
|
||||
Netty4HttpRequest nettyRequest = new Netty4HttpRequest(httpRequest, 0);
|
||||
embeddedChannel.writeOutbound(nettyRequest.createResponse(RestStatus.OK, new BytesArray("content")));
|
||||
return embeddedChannel.readOutbound();
|
||||
|
@ -39,12 +39,10 @@ import io.netty.handler.codec.http.HttpResponseStatus;
|
||||
import io.netty.handler.codec.http.HttpUtil;
|
||||
import io.netty.handler.codec.http.HttpVersion;
|
||||
import org.elasticsearch.ElasticsearchException;
|
||||
import org.elasticsearch.common.Strings;
|
||||
import org.elasticsearch.common.bytes.BytesArray;
|
||||
import org.elasticsearch.common.network.NetworkService;
|
||||
import org.elasticsearch.common.settings.Setting;
|
||||
import org.elasticsearch.common.settings.Settings;
|
||||
import org.elasticsearch.common.settings.SettingsException;
|
||||
import org.elasticsearch.common.transport.TransportAddress;
|
||||
import org.elasticsearch.common.unit.ByteSizeValue;
|
||||
import org.elasticsearch.common.unit.TimeValue;
|
||||
@ -55,7 +53,6 @@ import org.elasticsearch.http.BindHttpException;
|
||||
import org.elasticsearch.http.HttpServerTransport;
|
||||
import org.elasticsearch.http.HttpTransportSettings;
|
||||
import org.elasticsearch.http.NullDispatcher;
|
||||
import org.elasticsearch.http.netty4.cors.Netty4CorsConfig;
|
||||
import org.elasticsearch.indices.breaker.NoneCircuitBreakerService;
|
||||
import org.elasticsearch.rest.BytesRestResponse;
|
||||
import org.elasticsearch.rest.RestChannel;
|
||||
@ -69,23 +66,11 @@ import org.junit.Before;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.util.regex.PatternSyntaxException;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static org.elasticsearch.common.Strings.collectionToDelimitedString;
|
||||
import static org.elasticsearch.http.HttpTransportSettings.SETTING_CORS_ALLOW_CREDENTIALS;
|
||||
import static org.elasticsearch.http.HttpTransportSettings.SETTING_CORS_ALLOW_HEADERS;
|
||||
import static org.elasticsearch.http.HttpTransportSettings.SETTING_CORS_ALLOW_METHODS;
|
||||
import static org.elasticsearch.http.HttpTransportSettings.SETTING_CORS_ALLOW_ORIGIN;
|
||||
import static org.elasticsearch.http.HttpTransportSettings.SETTING_CORS_ENABLED;
|
||||
import static org.elasticsearch.http.HttpTransportSettings.SETTING_CORS_MAX_AGE;
|
||||
import static org.elasticsearch.rest.RestStatus.BAD_REQUEST;
|
||||
import static org.elasticsearch.rest.RestStatus.OK;
|
||||
import static org.hamcrest.Matchers.containsString;
|
||||
@ -119,48 +104,6 @@ public class Netty4HttpServerTransportTests extends ESTestCase {
|
||||
bigArrays = null;
|
||||
}
|
||||
|
||||
public void testCorsConfig() {
|
||||
final Set<String> methods = new HashSet<>(Arrays.asList("get", "options", "post"));
|
||||
final Set<String> headers = new HashSet<>(Arrays.asList("Content-Type", "Content-Length"));
|
||||
final String prefix = randomBoolean() ? " " : ""; // sometimes have a leading whitespace between comma delimited elements
|
||||
final Settings settings = Settings.builder()
|
||||
.put(SETTING_CORS_ENABLED.getKey(), true)
|
||||
.put(SETTING_CORS_ALLOW_ORIGIN.getKey(), "*")
|
||||
.put(SETTING_CORS_ALLOW_METHODS.getKey(), collectionToDelimitedString(methods, ",", prefix, ""))
|
||||
.put(SETTING_CORS_ALLOW_HEADERS.getKey(), collectionToDelimitedString(headers, ",", prefix, ""))
|
||||
.put(SETTING_CORS_ALLOW_CREDENTIALS.getKey(), true)
|
||||
.build();
|
||||
final Netty4CorsConfig corsConfig = Netty4HttpServerTransport.buildCorsConfig(settings);
|
||||
assertTrue(corsConfig.isAnyOriginSupported());
|
||||
assertEquals(headers, corsConfig.allowedRequestHeaders());
|
||||
assertEquals(methods, corsConfig.allowedRequestMethods().stream().map(HttpMethod::name).collect(Collectors.toSet()));
|
||||
}
|
||||
|
||||
public void testCorsConfigWithDefaults() {
|
||||
final Set<String> methods = Strings.commaDelimitedListToSet(SETTING_CORS_ALLOW_METHODS.getDefault(Settings.EMPTY));
|
||||
final Set<String> headers = Strings.commaDelimitedListToSet(SETTING_CORS_ALLOW_HEADERS.getDefault(Settings.EMPTY));
|
||||
final long maxAge = SETTING_CORS_MAX_AGE.getDefault(Settings.EMPTY);
|
||||
final Settings settings = Settings.builder().put(SETTING_CORS_ENABLED.getKey(), true).build();
|
||||
final Netty4CorsConfig corsConfig = Netty4HttpServerTransport.buildCorsConfig(settings);
|
||||
assertFalse(corsConfig.isAnyOriginSupported());
|
||||
assertEquals(Collections.emptySet(), corsConfig.origins().get());
|
||||
assertEquals(headers, corsConfig.allowedRequestHeaders());
|
||||
assertEquals(methods, corsConfig.allowedRequestMethods().stream().map(HttpMethod::name).collect(Collectors.toSet()));
|
||||
assertEquals(maxAge, corsConfig.maxAge());
|
||||
assertFalse(corsConfig.isCredentialsAllowed());
|
||||
}
|
||||
|
||||
public void testCorsConfigWithBadRegex() {
|
||||
final Settings settings = Settings.builder()
|
||||
.put(SETTING_CORS_ENABLED.getKey(), true)
|
||||
.put(SETTING_CORS_ALLOW_ORIGIN.getKey(), "/[*/")
|
||||
.put(SETTING_CORS_ALLOW_CREDENTIALS.getKey(), true)
|
||||
.build();
|
||||
SettingsException e = expectThrows(SettingsException.class, () -> Netty4HttpServerTransport.buildCorsConfig(settings));
|
||||
assertThat(e.getMessage(), containsString("Bad regex in [http.cors.allow-origin]: [/[*/]"));
|
||||
assertThat(e.getCause(), instanceOf(PatternSyntaxException.class));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that {@link Netty4HttpServerTransport} supports the "Expect: 100-continue" HTTP header
|
||||
* @throws InterruptedException if the client communication with the server is interrupted
|
||||
|
@ -31,10 +31,10 @@ import io.netty.handler.codec.http.HttpRequestDecoder;
|
||||
import io.netty.handler.codec.http.HttpResponseEncoder;
|
||||
import org.elasticsearch.ExceptionsHelper;
|
||||
import org.elasticsearch.common.unit.TimeValue;
|
||||
import org.elasticsearch.http.CorsHandler;
|
||||
import org.elasticsearch.http.HttpHandlingSettings;
|
||||
import org.elasticsearch.http.HttpPipelinedRequest;
|
||||
import org.elasticsearch.http.HttpReadTimeoutException;
|
||||
import org.elasticsearch.http.nio.cors.NioCorsConfig;
|
||||
import org.elasticsearch.http.nio.cors.NioCorsHandler;
|
||||
import org.elasticsearch.nio.FlushOperation;
|
||||
import org.elasticsearch.nio.InboundChannelBuffer;
|
||||
@ -63,7 +63,7 @@ public class HttpReadWriteHandler implements NioChannelHandler {
|
||||
private int inFlightRequests = 0;
|
||||
|
||||
public HttpReadWriteHandler(NioHttpChannel nioHttpChannel, NioHttpServerTransport transport, HttpHandlingSettings settings,
|
||||
NioCorsConfig corsConfig, TaskScheduler taskScheduler, LongSupplier nanoClock) {
|
||||
CorsHandler.Config corsConfig, TaskScheduler taskScheduler, LongSupplier nanoClock) {
|
||||
this.nioHttpChannel = nioHttpChannel;
|
||||
this.transport = transport;
|
||||
this.taskScheduler = taskScheduler;
|
||||
|
@ -19,14 +19,11 @@
|
||||
|
||||
package org.elasticsearch.http.nio;
|
||||
|
||||
import io.netty.handler.codec.http.HttpMethod;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.elasticsearch.ElasticsearchException;
|
||||
import org.elasticsearch.common.Strings;
|
||||
import org.elasticsearch.common.network.NetworkService;
|
||||
import org.elasticsearch.common.settings.Settings;
|
||||
import org.elasticsearch.common.settings.SettingsException;
|
||||
import org.elasticsearch.common.unit.ByteSizeValue;
|
||||
import org.elasticsearch.common.util.BigArrays;
|
||||
import org.elasticsearch.common.util.PageCacheRecycler;
|
||||
@ -34,8 +31,6 @@ import org.elasticsearch.common.xcontent.NamedXContentRegistry;
|
||||
import org.elasticsearch.http.AbstractHttpServerTransport;
|
||||
import org.elasticsearch.http.HttpChannel;
|
||||
import org.elasticsearch.http.HttpServerChannel;
|
||||
import org.elasticsearch.http.nio.cors.NioCorsConfig;
|
||||
import org.elasticsearch.http.nio.cors.NioCorsConfigBuilder;
|
||||
import org.elasticsearch.nio.BytesChannelContext;
|
||||
import org.elasticsearch.nio.ChannelFactory;
|
||||
import org.elasticsearch.nio.InboundChannelBuffer;
|
||||
@ -44,7 +39,6 @@ import org.elasticsearch.nio.NioSelector;
|
||||
import org.elasticsearch.nio.NioSocketChannel;
|
||||
import org.elasticsearch.nio.ServerChannelContext;
|
||||
import org.elasticsearch.nio.SocketChannelContext;
|
||||
import org.elasticsearch.rest.RestUtils;
|
||||
import org.elasticsearch.threadpool.ThreadPool;
|
||||
import org.elasticsearch.transport.nio.NioGroupFactory;
|
||||
import org.elasticsearch.transport.nio.PageAllocator;
|
||||
@ -53,17 +47,8 @@ import java.io.IOException;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.nio.channels.ServerSocketChannel;
|
||||
import java.nio.channels.SocketChannel;
|
||||
import java.util.Arrays;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.regex.PatternSyntaxException;
|
||||
|
||||
import static org.elasticsearch.http.HttpTransportSettings.SETTING_CORS_ALLOW_CREDENTIALS;
|
||||
import static org.elasticsearch.http.HttpTransportSettings.SETTING_CORS_ALLOW_HEADERS;
|
||||
import static org.elasticsearch.http.HttpTransportSettings.SETTING_CORS_ALLOW_METHODS;
|
||||
import static org.elasticsearch.http.HttpTransportSettings.SETTING_CORS_ALLOW_ORIGIN;
|
||||
import static org.elasticsearch.http.HttpTransportSettings.SETTING_CORS_ENABLED;
|
||||
import static org.elasticsearch.http.HttpTransportSettings.SETTING_CORS_MAX_AGE;
|
||||
import static org.elasticsearch.http.HttpTransportSettings.SETTING_HTTP_MAX_CHUNK_SIZE;
|
||||
import static org.elasticsearch.http.HttpTransportSettings.SETTING_HTTP_MAX_HEADER_SIZE;
|
||||
import static org.elasticsearch.http.HttpTransportSettings.SETTING_HTTP_MAX_INITIAL_LINE_LENGTH;
|
||||
@ -73,12 +58,10 @@ import static org.elasticsearch.http.HttpTransportSettings.SETTING_HTTP_TCP_RECE
|
||||
import static org.elasticsearch.http.HttpTransportSettings.SETTING_HTTP_TCP_REUSE_ADDRESS;
|
||||
import static org.elasticsearch.http.HttpTransportSettings.SETTING_HTTP_TCP_SEND_BUFFER_SIZE;
|
||||
import static org.elasticsearch.http.HttpTransportSettings.SETTING_PIPELINING_MAX_EVENTS;
|
||||
import static org.elasticsearch.http.nio.cors.NioCorsHandler.ANY_ORIGIN;
|
||||
|
||||
public class NioHttpServerTransport extends AbstractHttpServerTransport {
|
||||
private static final Logger logger = LogManager.getLogger(NioHttpServerTransport.class);
|
||||
|
||||
protected final NioCorsConfig corsConfig;
|
||||
protected final PageAllocator pageAllocator;
|
||||
private final NioGroupFactory nioGroupFactory;
|
||||
|
||||
@ -102,7 +85,6 @@ public class NioHttpServerTransport extends AbstractHttpServerTransport {
|
||||
ByteSizeValue maxHeaderSize = SETTING_HTTP_MAX_HEADER_SIZE.get(settings);
|
||||
ByteSizeValue maxInitialLineLength = SETTING_HTTP_MAX_INITIAL_LINE_LENGTH.get(settings);
|
||||
int pipeliningMaxEvents = SETTING_PIPELINING_MAX_EVENTS.get(settings);
|
||||
this.corsConfig = buildCorsConfig(settings);
|
||||
|
||||
this.tcpNoDelay = SETTING_HTTP_TCP_NO_DELAY.get(settings);
|
||||
this.tcpKeepAlive = SETTING_HTTP_TCP_KEEP_ALIVE.get(settings);
|
||||
@ -155,42 +137,6 @@ public class NioHttpServerTransport extends AbstractHttpServerTransport {
|
||||
return new HttpChannelFactory();
|
||||
}
|
||||
|
||||
static NioCorsConfig buildCorsConfig(Settings settings) {
|
||||
if (SETTING_CORS_ENABLED.get(settings) == false) {
|
||||
return NioCorsConfigBuilder.forOrigins().disable().build();
|
||||
}
|
||||
String origin = SETTING_CORS_ALLOW_ORIGIN.get(settings);
|
||||
final NioCorsConfigBuilder builder;
|
||||
if (Strings.isNullOrEmpty(origin)) {
|
||||
builder = NioCorsConfigBuilder.forOrigins();
|
||||
} else if (origin.equals(ANY_ORIGIN)) {
|
||||
builder = NioCorsConfigBuilder.forAnyOrigin();
|
||||
} else {
|
||||
try {
|
||||
Pattern p = RestUtils.checkCorsSettingForRegex(origin);
|
||||
if (p == null) {
|
||||
builder = NioCorsConfigBuilder.forOrigins(RestUtils.corsSettingAsArray(origin));
|
||||
} else {
|
||||
builder = NioCorsConfigBuilder.forPattern(p);
|
||||
}
|
||||
} catch (PatternSyntaxException e) {
|
||||
throw new SettingsException("Bad regex in [" + SETTING_CORS_ALLOW_ORIGIN.getKey() + "]: [" + origin + "]", e);
|
||||
}
|
||||
}
|
||||
if (SETTING_CORS_ALLOW_CREDENTIALS.get(settings)) {
|
||||
builder.allowCredentials();
|
||||
}
|
||||
String[] strMethods = Strings.tokenizeToStringArray(SETTING_CORS_ALLOW_METHODS.get(settings), ",");
|
||||
HttpMethod[] methods = Arrays.stream(strMethods)
|
||||
.map(HttpMethod::valueOf)
|
||||
.toArray(HttpMethod[]::new);
|
||||
return builder.allowedRequestMethods(methods)
|
||||
.maxAge(SETTING_CORS_MAX_AGE.get(settings))
|
||||
.allowedRequestHeaders(Strings.tokenizeToStringArray(SETTING_CORS_ALLOW_HEADERS.get(settings), ","))
|
||||
.shortCircuit()
|
||||
.build();
|
||||
}
|
||||
|
||||
protected void acceptChannel(NioSocketChannel socketChannel) {
|
||||
super.serverAcceptedChannel((HttpChannel) socketChannel);
|
||||
}
|
||||
|
@ -1,221 +0,0 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
package org.elasticsearch.http.nio.cors;
|
||||
|
||||
import io.netty.handler.codec.http.DefaultHttpHeaders;
|
||||
import io.netty.handler.codec.http.EmptyHttpHeaders;
|
||||
import io.netty.handler.codec.http.HttpHeaders;
|
||||
import io.netty.handler.codec.http.HttpMethod;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* Configuration for Cross-Origin Resource Sharing (CORS).
|
||||
*
|
||||
* This class was lifted from the Netty project:
|
||||
* https://github.com/netty/netty
|
||||
*/
|
||||
public final class NioCorsConfig {
|
||||
|
||||
private final Optional<Set<String>> origins;
|
||||
private final Optional<Pattern> pattern;
|
||||
private final boolean anyOrigin;
|
||||
private final boolean enabled;
|
||||
private final boolean allowCredentials;
|
||||
private final long maxAge;
|
||||
private final Set<HttpMethod> allowedRequestMethods;
|
||||
private final Set<String> allowedRequestHeaders;
|
||||
private final Map<CharSequence, Callable<?>> preflightHeaders;
|
||||
private final boolean shortCircuit;
|
||||
|
||||
NioCorsConfig(final NioCorsConfigBuilder builder) {
|
||||
origins = builder.origins.map(s -> new LinkedHashSet<>(s));
|
||||
pattern = builder.pattern;
|
||||
anyOrigin = builder.anyOrigin;
|
||||
enabled = builder.enabled;
|
||||
allowCredentials = builder.allowCredentials;
|
||||
maxAge = builder.maxAge;
|
||||
allowedRequestMethods = builder.requestMethods;
|
||||
allowedRequestHeaders = builder.requestHeaders;
|
||||
preflightHeaders = builder.preflightHeaders;
|
||||
shortCircuit = builder.shortCircuit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if support for CORS is enabled.
|
||||
*
|
||||
* @return {@code true} if support for CORS is enabled, false otherwise.
|
||||
*/
|
||||
public boolean isCorsSupportEnabled() {
|
||||
return enabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether a wildcard origin, '*', is supported. This also means that null origins are
|
||||
* supported.
|
||||
*
|
||||
* @return {@code boolean} true if any origin is allowed.
|
||||
*/
|
||||
public boolean isAnyOriginSupported() {
|
||||
return anyOrigin;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the set of allowed origins.
|
||||
*
|
||||
* @return {@code Set} the allowed origins.
|
||||
*/
|
||||
public Optional<Set<String>> origins() {
|
||||
return origins;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the input origin is allowed by this configuration.
|
||||
*
|
||||
* @return {@code true} if the origin is allowed, otherwise {@code false}
|
||||
*/
|
||||
public boolean isOriginAllowed(final String origin) {
|
||||
if (origins.isPresent()) {
|
||||
return origins.get().contains(origin);
|
||||
} else if (pattern.isPresent()) {
|
||||
return pattern.get().matcher(origin).matches();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if credentials are supported for CORS requests.
|
||||
*
|
||||
* By default credentials are not included in CORS requests but if isCredentialsAllowed returns
|
||||
* true credentials will be added to CORS requests. Setting this value to true will set the
|
||||
* CORS 'Access-Control-Allow-Credentials' response header to true.
|
||||
*
|
||||
* Please note that credentials support needs to be enabled on the client side as well.
|
||||
* The client needs to opt-in to send credentials by calling:
|
||||
* <pre>
|
||||
* xhr.withCredentials = true;
|
||||
* </pre>
|
||||
* The default value for 'withCredentials' is false in which case no credentials are sent.
|
||||
* Setting this to true will included cookies in cross origin requests.
|
||||
*
|
||||
* @return {@code true} if credentials are supported.
|
||||
*/
|
||||
public boolean isCredentialsAllowed() {
|
||||
return allowCredentials;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the maxAge setting.
|
||||
*
|
||||
* When making a preflight request the client has to perform two request with can be inefficient.
|
||||
* This setting will set the CORS 'Access-Control-Max-Age' response header and enables the
|
||||
* caching of the preflight response for the specified time. During this time no preflight
|
||||
* request will be made.
|
||||
*
|
||||
* @return {@code long} the time in seconds that a preflight request may be cached.
|
||||
*/
|
||||
public long maxAge() {
|
||||
return maxAge;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the allowed set of Request Methods. The Http methods that should be returned in the
|
||||
* CORS 'Access-Control-Request-Method' response header.
|
||||
*
|
||||
* @return {@code Set} of {@link HttpMethod}s that represent the allowed Request Methods.
|
||||
*/
|
||||
public Set<HttpMethod> allowedRequestMethods() {
|
||||
return Collections.unmodifiableSet(allowedRequestMethods);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the allowed set of Request Headers.
|
||||
*
|
||||
* The header names returned from this method will be used to set the CORS
|
||||
* 'Access-Control-Allow-Headers' response header.
|
||||
*
|
||||
* @return {@code Set<String>} of strings that represent the allowed Request Headers.
|
||||
*/
|
||||
public Set<String> allowedRequestHeaders() {
|
||||
return Collections.unmodifiableSet(allowedRequestHeaders);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns HTTP response headers that should be added to a CORS preflight response.
|
||||
*
|
||||
* @return {@link HttpHeaders} the HTTP response headers to be added.
|
||||
*/
|
||||
public HttpHeaders preflightResponseHeaders() {
|
||||
if (preflightHeaders.isEmpty()) {
|
||||
return EmptyHttpHeaders.INSTANCE;
|
||||
}
|
||||
final HttpHeaders preflightHeaders = new DefaultHttpHeaders();
|
||||
for (Map.Entry<CharSequence, Callable<?>> entry : this.preflightHeaders.entrySet()) {
|
||||
final Object value = getValue(entry.getValue());
|
||||
if (value instanceof Iterable) {
|
||||
preflightHeaders.add(entry.getKey().toString(), (Iterable<?>) value);
|
||||
} else {
|
||||
preflightHeaders.add(entry.getKey().toString(), value);
|
||||
}
|
||||
}
|
||||
return preflightHeaders;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether a CORS request should be rejected if it's invalid before being
|
||||
* further processing.
|
||||
*
|
||||
* CORS headers are set after a request is processed. This may not always be desired
|
||||
* and this setting will check that the Origin is valid and if it is not valid no
|
||||
* further processing will take place, and a error will be returned to the calling client.
|
||||
*
|
||||
* @return {@code true} if a CORS request should short-circuit upon receiving an invalid Origin header.
|
||||
*/
|
||||
public boolean isShortCircuit() {
|
||||
return shortCircuit;
|
||||
}
|
||||
|
||||
private static <T> T getValue(final Callable<T> callable) {
|
||||
try {
|
||||
return callable.call();
|
||||
} catch (final Exception e) {
|
||||
throw new IllegalStateException("Could not generate value for callable [" + callable + ']', e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "CorsConfig[enabled=" + enabled +
|
||||
", origins=" + origins +
|
||||
", anyOrigin=" + anyOrigin +
|
||||
", isCredentialsAllowed=" + allowCredentials +
|
||||
", maxAge=" + maxAge +
|
||||
", allowedRequestMethods=" + allowedRequestMethods +
|
||||
", allowedRequestHeaders=" + allowedRequestHeaders +
|
||||
", preflightHeaders=" + preflightHeaders + ']';
|
||||
}
|
||||
|
||||
}
|
@ -1,265 +0,0 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
package org.elasticsearch.http.nio.cors;
|
||||
|
||||
import io.netty.handler.codec.http.HttpMethod;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* Builder used to configure and build a {@link NioCorsConfig} instance.
|
||||
*
|
||||
* This class was lifted from the Netty project:
|
||||
* https://github.com/netty/netty
|
||||
*/
|
||||
public final class NioCorsConfigBuilder {
|
||||
|
||||
/**
|
||||
* Creates a Builder instance with it's origin set to '*'.
|
||||
*
|
||||
* @return Builder to support method chaining.
|
||||
*/
|
||||
public static NioCorsConfigBuilder forAnyOrigin() {
|
||||
return new NioCorsConfigBuilder();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a {@link NioCorsConfigBuilder} instance with the specified pattern origin.
|
||||
*
|
||||
* @param pattern the regular expression pattern to match incoming origins on.
|
||||
* @return {@link NioCorsConfigBuilder} with the configured origin pattern.
|
||||
*/
|
||||
public static NioCorsConfigBuilder forPattern(final Pattern pattern) {
|
||||
if (pattern == null) {
|
||||
throw new IllegalArgumentException("CORS pattern cannot be null");
|
||||
}
|
||||
return new NioCorsConfigBuilder(pattern);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@link NioCorsConfigBuilder} instance with the specified origins.
|
||||
*
|
||||
* @return {@link NioCorsConfigBuilder} to support method chaining.
|
||||
*/
|
||||
public static NioCorsConfigBuilder forOrigins(final String... origins) {
|
||||
return new NioCorsConfigBuilder(origins);
|
||||
}
|
||||
|
||||
Optional<Set<String>> origins;
|
||||
Optional<Pattern> pattern;
|
||||
final boolean anyOrigin;
|
||||
boolean enabled = true;
|
||||
boolean allowCredentials;
|
||||
long maxAge;
|
||||
final Set<HttpMethod> requestMethods = new HashSet<>();
|
||||
final Set<String> requestHeaders = new HashSet<>();
|
||||
final Map<CharSequence, Callable<?>> preflightHeaders = new HashMap<>();
|
||||
boolean shortCircuit;
|
||||
|
||||
/**
|
||||
* Creates a new Builder instance with the origin passed in.
|
||||
*
|
||||
* @param origins the origin to be used for this builder.
|
||||
*/
|
||||
NioCorsConfigBuilder(final String... origins) {
|
||||
this.origins = Optional.of(new LinkedHashSet<>(Arrays.asList(origins)));
|
||||
pattern = Optional.empty();
|
||||
anyOrigin = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new Builder instance allowing any origin, "*" which is the
|
||||
* wildcard origin.
|
||||
*
|
||||
*/
|
||||
NioCorsConfigBuilder() {
|
||||
anyOrigin = true;
|
||||
origins = Optional.empty();
|
||||
pattern = Optional.empty();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new Builder instance allowing any origin that matches the pattern.
|
||||
*
|
||||
* @param pattern the pattern to match against for incoming origins.
|
||||
*/
|
||||
NioCorsConfigBuilder(final Pattern pattern) {
|
||||
this.pattern = Optional.of(pattern);
|
||||
origins = Optional.empty();
|
||||
anyOrigin = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Disables CORS support.
|
||||
*
|
||||
* @return {@link NioCorsConfigBuilder} to support method chaining.
|
||||
*/
|
||||
public NioCorsConfigBuilder disable() {
|
||||
enabled = false;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* By default cookies are not included in CORS requests, but this method will enable cookies to
|
||||
* be added to CORS requests. Calling this method will set the CORS 'Access-Control-Allow-Credentials'
|
||||
* response header to true.
|
||||
*
|
||||
* Please note, that cookie support needs to be enabled on the client side as well.
|
||||
* The client needs to opt-in to send cookies by calling:
|
||||
* <pre>
|
||||
* xhr.withCredentials = true;
|
||||
* </pre>
|
||||
* The default value for 'withCredentials' is false in which case no cookies are sent.
|
||||
* Setting this to true will included cookies in cross origin requests.
|
||||
*
|
||||
* @return {@link NioCorsConfigBuilder} to support method chaining.
|
||||
*/
|
||||
public NioCorsConfigBuilder allowCredentials() {
|
||||
allowCredentials = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* When making a preflight request the client has to perform two request with can be inefficient.
|
||||
* This setting will set the CORS 'Access-Control-Max-Age' response header and enables the
|
||||
* caching of the preflight response for the specified time. During this time no preflight
|
||||
* request will be made.
|
||||
*
|
||||
* @param max the maximum time, in seconds, that the preflight response may be cached.
|
||||
* @return {@link NioCorsConfigBuilder} to support method chaining.
|
||||
*/
|
||||
public NioCorsConfigBuilder maxAge(final long max) {
|
||||
maxAge = max;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Specifies the allowed set of HTTP Request Methods that should be returned in the
|
||||
* CORS 'Access-Control-Request-Method' response header.
|
||||
*
|
||||
* @param methods the {@link HttpMethod}s that should be allowed.
|
||||
* @return {@link NioCorsConfigBuilder} to support method chaining.
|
||||
*/
|
||||
public NioCorsConfigBuilder allowedRequestMethods(final HttpMethod... methods) {
|
||||
requestMethods.addAll(Arrays.asList(methods));
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Specifies the if headers that should be returned in the CORS 'Access-Control-Allow-Headers'
|
||||
* response header.
|
||||
*
|
||||
* If a client specifies headers on the request, for example by calling:
|
||||
* <pre>
|
||||
* xhr.setRequestHeader('My-Custom-Header', "SomeValue");
|
||||
* </pre>
|
||||
* the server will receive the above header name in the 'Access-Control-Request-Headers' of the
|
||||
* preflight request. The server will then decide if it allows this header to be sent for the
|
||||
* real request (remember that a preflight is not the real request but a request asking the server
|
||||
* if it allow a request).
|
||||
*
|
||||
* @param headers the headers to be added to the preflight 'Access-Control-Allow-Headers' response header.
|
||||
* @return {@link NioCorsConfigBuilder} to support method chaining.
|
||||
*/
|
||||
public NioCorsConfigBuilder allowedRequestHeaders(final String... headers) {
|
||||
requestHeaders.addAll(Arrays.asList(headers));
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Specifies that a CORS request should be rejected if it's invalid before being
|
||||
* further processing.
|
||||
*
|
||||
* CORS headers are set after a request is processed. This may not always be desired
|
||||
* and this setting will check that the Origin is valid and if it is not valid no
|
||||
* further processing will take place, and a error will be returned to the calling client.
|
||||
*
|
||||
* @return {@link NioCorsConfigBuilder} to support method chaining.
|
||||
*/
|
||||
public NioCorsConfigBuilder shortCircuit() {
|
||||
shortCircuit = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds a {@link NioCorsConfig} with settings specified by previous method calls.
|
||||
*
|
||||
* @return {@link NioCorsConfig} the configured CorsConfig instance.
|
||||
*/
|
||||
public NioCorsConfig build() {
|
||||
if (preflightHeaders.isEmpty()) {
|
||||
preflightHeaders.put("date", DateValueGenerator.INSTANCE);
|
||||
preflightHeaders.put("content-length", new ConstantValueGenerator("0"));
|
||||
}
|
||||
return new NioCorsConfig(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* This class is used for preflight HTTP response values that do not need to be
|
||||
* generated, but instead the value is "static" in that the same value will be returned
|
||||
* for each call.
|
||||
*/
|
||||
private static final class ConstantValueGenerator implements Callable<Object> {
|
||||
|
||||
private final Object value;
|
||||
|
||||
/**
|
||||
* Sole constructor.
|
||||
*
|
||||
* @param value the value that will be returned when the call method is invoked.
|
||||
*/
|
||||
private ConstantValueGenerator(final Object value) {
|
||||
if (value == null) {
|
||||
throw new IllegalArgumentException("value must not be null");
|
||||
}
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object call() {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This callable is used for the DATE preflight HTTP response HTTP header.
|
||||
* It's value must be generated when the response is generated, hence will be
|
||||
* different for every call.
|
||||
*/
|
||||
private static final class DateValueGenerator implements Callable<Date> {
|
||||
|
||||
static final DateValueGenerator INSTANCE = new DateValueGenerator();
|
||||
|
||||
@Override
|
||||
public Date call() throws Exception {
|
||||
return new Date();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -32,15 +32,17 @@ import io.netty.handler.codec.http.HttpRequest;
|
||||
import io.netty.handler.codec.http.HttpResponse;
|
||||
import io.netty.handler.codec.http.HttpResponseStatus;
|
||||
import org.elasticsearch.common.Strings;
|
||||
import org.elasticsearch.http.CorsHandler;
|
||||
import org.elasticsearch.http.nio.NioHttpResponse;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* Handles <a href="http://www.w3.org/TR/cors/">Cross Origin Resource Sharing</a> (CORS) requests.
|
||||
* <p>
|
||||
* This handler can be configured using a {@link NioCorsConfig}, please
|
||||
* This handler can be configured using a {@link CorsHandler.Config}, please
|
||||
* refer to this class for details about the configuration options available.
|
||||
*
|
||||
* This code was borrowed from Netty 4 and refactored to work for Elasticsearch's Netty 3 setup.
|
||||
@ -50,13 +52,13 @@ public class NioCorsHandler extends ChannelDuplexHandler {
|
||||
public static final String ANY_ORIGIN = "*";
|
||||
private static Pattern SCHEME_PATTERN = Pattern.compile("^https?://");
|
||||
|
||||
private final NioCorsConfig config;
|
||||
private final CorsHandler.Config config;
|
||||
private FullHttpRequest request;
|
||||
|
||||
/**
|
||||
* Creates a new instance with the specified {@link NioCorsConfig}.
|
||||
* Creates a new instance with the specified {@link CorsHandler.Config}.
|
||||
*/
|
||||
public NioCorsHandler(final NioCorsConfig config) {
|
||||
public NioCorsHandler(final CorsHandler.Config config) {
|
||||
if (config == null) {
|
||||
throw new NullPointerException();
|
||||
}
|
||||
@ -76,7 +78,7 @@ public class NioCorsHandler extends ChannelDuplexHandler {
|
||||
releaseRequest();
|
||||
}
|
||||
}
|
||||
if (config.isShortCircuit() && !validateOrigin()) {
|
||||
if (!validateOrigin()) {
|
||||
try {
|
||||
forbidden(ctx, request);
|
||||
return;
|
||||
@ -96,7 +98,7 @@ public class NioCorsHandler extends ChannelDuplexHandler {
|
||||
ctx.write(response, promise);
|
||||
}
|
||||
|
||||
public static void setCorsResponseHeaders(HttpRequest request, HttpResponse resp, NioCorsConfig config) {
|
||||
public static void setCorsResponseHeaders(HttpRequest request, HttpResponse resp, CorsHandler.Config config) {
|
||||
if (!config.isCorsSupportEnabled()) {
|
||||
return;
|
||||
}
|
||||
@ -161,7 +163,8 @@ public class NioCorsHandler extends ChannelDuplexHandler {
|
||||
* @param response the HttpResponse to which the preflight response headers should be added.
|
||||
*/
|
||||
private void setPreflightHeaders(final HttpResponse response) {
|
||||
response.headers().add(config.preflightResponseHeaders());
|
||||
response.headers().add("date", new Date());
|
||||
response.headers().add("content-length", "0");
|
||||
}
|
||||
|
||||
private boolean setOrigin(final HttpResponse response) {
|
||||
|
@ -37,14 +37,13 @@ import org.elasticsearch.common.bytes.BytesArray;
|
||||
import org.elasticsearch.common.settings.Settings;
|
||||
import org.elasticsearch.common.unit.ByteSizeValue;
|
||||
import org.elasticsearch.common.unit.TimeValue;
|
||||
import org.elasticsearch.http.CorsHandler;
|
||||
import org.elasticsearch.http.HttpChannel;
|
||||
import org.elasticsearch.http.HttpHandlingSettings;
|
||||
import org.elasticsearch.http.HttpReadTimeoutException;
|
||||
import org.elasticsearch.http.HttpRequest;
|
||||
import org.elasticsearch.http.HttpResponse;
|
||||
import org.elasticsearch.http.HttpTransportSettings;
|
||||
import org.elasticsearch.http.nio.cors.NioCorsConfig;
|
||||
import org.elasticsearch.http.nio.cors.NioCorsConfigBuilder;
|
||||
import org.elasticsearch.http.nio.cors.NioCorsHandler;
|
||||
import org.elasticsearch.nio.FlushOperation;
|
||||
import org.elasticsearch.nio.InboundChannelBuffer;
|
||||
@ -98,7 +97,7 @@ public class HttpReadWriteHandlerTests extends ESTestCase {
|
||||
channel = mock(NioHttpChannel.class);
|
||||
taskScheduler = mock(TaskScheduler.class);
|
||||
|
||||
NioCorsConfig corsConfig = NioCorsConfigBuilder.forAnyOrigin().build();
|
||||
CorsHandler.Config corsConfig = CorsHandler.disabled();
|
||||
handler = new HttpReadWriteHandler(channel, transport, httpHandlingSettings, corsConfig, taskScheduler, System::nanoTime);
|
||||
handler.channelActive();
|
||||
}
|
||||
@ -329,7 +328,7 @@ public class HttpReadWriteHandlerTests extends ESTestCase {
|
||||
Settings settings = Settings.builder().put(SETTING_HTTP_READ_TIMEOUT.getKey(), timeValue).build();
|
||||
HttpHandlingSettings httpHandlingSettings = HttpHandlingSettings.fromSettings(settings);
|
||||
|
||||
NioCorsConfig corsConfig = NioCorsConfigBuilder.forAnyOrigin().build();
|
||||
CorsHandler.Config corsConfig = CorsHandler.disabled();
|
||||
TaskScheduler taskScheduler = new TaskScheduler();
|
||||
|
||||
Iterator<Integer> timeValues = Arrays.asList(0, 2, 4, 6, 8).iterator();
|
||||
@ -378,7 +377,7 @@ public class HttpReadWriteHandlerTests extends ESTestCase {
|
||||
|
||||
private FullHttpResponse executeCorsRequest(final Settings settings, final String originValue, final String host) throws IOException {
|
||||
HttpHandlingSettings httpSettings = HttpHandlingSettings.fromSettings(settings);
|
||||
NioCorsConfig corsConfig = NioHttpServerTransport.buildCorsConfig(settings);
|
||||
CorsHandler.Config corsConfig = CorsHandler.fromSettings(settings);
|
||||
HttpReadWriteHandler handler = new HttpReadWriteHandler(channel, transport, httpSettings, corsConfig, taskScheduler,
|
||||
System::nanoTime);
|
||||
handler.channelActive();
|
||||
|
@ -32,12 +32,10 @@ import io.netty.handler.codec.http.HttpResponseStatus;
|
||||
import io.netty.handler.codec.http.HttpUtil;
|
||||
import io.netty.handler.codec.http.HttpVersion;
|
||||
import org.elasticsearch.ElasticsearchException;
|
||||
import org.elasticsearch.common.Strings;
|
||||
import org.elasticsearch.common.bytes.BytesArray;
|
||||
import org.elasticsearch.common.network.NetworkService;
|
||||
import org.elasticsearch.common.settings.Setting;
|
||||
import org.elasticsearch.common.settings.Settings;
|
||||
import org.elasticsearch.common.settings.SettingsException;
|
||||
import org.elasticsearch.common.transport.TransportAddress;
|
||||
import org.elasticsearch.common.unit.ByteSizeValue;
|
||||
import org.elasticsearch.common.unit.TimeValue;
|
||||
@ -48,7 +46,6 @@ import org.elasticsearch.http.BindHttpException;
|
||||
import org.elasticsearch.http.HttpServerTransport;
|
||||
import org.elasticsearch.http.HttpTransportSettings;
|
||||
import org.elasticsearch.http.NullDispatcher;
|
||||
import org.elasticsearch.http.nio.cors.NioCorsConfig;
|
||||
import org.elasticsearch.indices.breaker.NoneCircuitBreakerService;
|
||||
import org.elasticsearch.nio.NioSocketChannel;
|
||||
import org.elasticsearch.rest.BytesRestResponse;
|
||||
@ -64,22 +61,11 @@ import org.junit.Before;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.util.regex.PatternSyntaxException;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static org.elasticsearch.http.HttpTransportSettings.SETTING_CORS_ALLOW_CREDENTIALS;
|
||||
import static org.elasticsearch.http.HttpTransportSettings.SETTING_CORS_ALLOW_HEADERS;
|
||||
import static org.elasticsearch.http.HttpTransportSettings.SETTING_CORS_ALLOW_METHODS;
|
||||
import static org.elasticsearch.http.HttpTransportSettings.SETTING_CORS_ALLOW_ORIGIN;
|
||||
import static org.elasticsearch.http.HttpTransportSettings.SETTING_CORS_ENABLED;
|
||||
import static org.elasticsearch.http.HttpTransportSettings.SETTING_CORS_MAX_AGE;
|
||||
import static org.elasticsearch.rest.RestStatus.BAD_REQUEST;
|
||||
import static org.elasticsearch.rest.RestStatus.OK;
|
||||
import static org.hamcrest.Matchers.containsString;
|
||||
@ -115,48 +101,6 @@ public class NioHttpServerTransportTests extends ESTestCase {
|
||||
bigArrays = null;
|
||||
}
|
||||
|
||||
public void testCorsConfig() {
|
||||
final Set<String> methods = new HashSet<>(Arrays.asList("get", "options", "post"));
|
||||
final Set<String> headers = new HashSet<>(Arrays.asList("Content-Type", "Content-Length"));
|
||||
final String prefix = randomBoolean() ? " " : ""; // sometimes have a leading whitespace between comma delimited elements
|
||||
final Settings settings = Settings.builder()
|
||||
.put(SETTING_CORS_ENABLED.getKey(), true)
|
||||
.put(SETTING_CORS_ALLOW_ORIGIN.getKey(), "*")
|
||||
.put(SETTING_CORS_ALLOW_METHODS.getKey(), Strings.collectionToDelimitedString(methods, ",", prefix, ""))
|
||||
.put(SETTING_CORS_ALLOW_HEADERS.getKey(), Strings.collectionToDelimitedString(headers, ",", prefix, ""))
|
||||
.put(SETTING_CORS_ALLOW_CREDENTIALS.getKey(), true)
|
||||
.build();
|
||||
final NioCorsConfig corsConfig = NioHttpServerTransport.buildCorsConfig(settings);
|
||||
assertTrue(corsConfig.isAnyOriginSupported());
|
||||
assertEquals(headers, corsConfig.allowedRequestHeaders());
|
||||
assertEquals(methods, corsConfig.allowedRequestMethods().stream().map(HttpMethod::name).collect(Collectors.toSet()));
|
||||
}
|
||||
|
||||
public void testCorsConfigWithDefaults() {
|
||||
final Set<String> methods = Strings.commaDelimitedListToSet(SETTING_CORS_ALLOW_METHODS.getDefault(Settings.EMPTY));
|
||||
final Set<String> headers = Strings.commaDelimitedListToSet(SETTING_CORS_ALLOW_HEADERS.getDefault(Settings.EMPTY));
|
||||
final long maxAge = SETTING_CORS_MAX_AGE.getDefault(Settings.EMPTY);
|
||||
final Settings settings = Settings.builder().put(SETTING_CORS_ENABLED.getKey(), true).build();
|
||||
final NioCorsConfig corsConfig = NioHttpServerTransport.buildCorsConfig(settings);
|
||||
assertFalse(corsConfig.isAnyOriginSupported());
|
||||
assertEquals(Collections.emptySet(), corsConfig.origins().get());
|
||||
assertEquals(headers, corsConfig.allowedRequestHeaders());
|
||||
assertEquals(methods, corsConfig.allowedRequestMethods().stream().map(HttpMethod::name).collect(Collectors.toSet()));
|
||||
assertEquals(maxAge, corsConfig.maxAge());
|
||||
assertFalse(corsConfig.isCredentialsAllowed());
|
||||
}
|
||||
|
||||
public void testCorsConfigWithBadRegex() {
|
||||
final Settings settings = Settings.builder()
|
||||
.put(SETTING_CORS_ENABLED.getKey(), true)
|
||||
.put(SETTING_CORS_ALLOW_ORIGIN.getKey(), "/[*/")
|
||||
.put(SETTING_CORS_ALLOW_CREDENTIALS.getKey(), true)
|
||||
.build();
|
||||
SettingsException e = expectThrows(SettingsException.class, () -> NioHttpServerTransport.buildCorsConfig(settings));
|
||||
assertThat(e.getMessage(), containsString("Bad regex in [http.cors.allow-origin]: [/[*/]"));
|
||||
assertThat(e.getCause(), instanceOf(PatternSyntaxException.class));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that {@link NioHttpServerTransport} supports the "Expect: 100-continue" HTTP header
|
||||
* @throws InterruptedException if the client communication with the server is interrupted
|
||||
|
@ -73,6 +73,7 @@ public abstract class AbstractHttpServerTransport extends AbstractLifecycleCompo
|
||||
protected final BigArrays bigArrays;
|
||||
protected final ThreadPool threadPool;
|
||||
protected final Dispatcher dispatcher;
|
||||
protected final CorsHandler.Config corsConfig;
|
||||
private final NamedXContentRegistry xContentRegistry;
|
||||
|
||||
protected final PortsRange port;
|
||||
@ -94,6 +95,7 @@ public abstract class AbstractHttpServerTransport extends AbstractLifecycleCompo
|
||||
this.xContentRegistry = xContentRegistry;
|
||||
this.dispatcher = dispatcher;
|
||||
this.handlingSettings = HttpHandlingSettings.fromSettings(settings);
|
||||
this.corsConfig = CorsHandler.fromSettings(settings);
|
||||
|
||||
// we can't make the network.bind_host a fallback since we already fall back to http.host hence the extra conditional here
|
||||
List<String> httpBindHost = SETTING_HTTP_BIND_HOST.get(settings);
|
||||
|
258
server/src/main/java/org/elasticsearch/http/CorsHandler.java
Normal file
258
server/src/main/java/org/elasticsearch/http/CorsHandler.java
Normal file
@ -0,0 +1,258 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
/*
|
||||
* Copyright 2013 The Netty Project
|
||||
*
|
||||
* The Netty Project 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.elasticsearch.http;
|
||||
|
||||
import org.elasticsearch.common.Strings;
|
||||
import org.elasticsearch.common.settings.Settings;
|
||||
import org.elasticsearch.common.settings.SettingsException;
|
||||
import org.elasticsearch.rest.RestRequest;
|
||||
import org.elasticsearch.rest.RestUtils;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Locale;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.regex.PatternSyntaxException;
|
||||
|
||||
import static org.elasticsearch.http.HttpTransportSettings.SETTING_CORS_ALLOW_CREDENTIALS;
|
||||
import static org.elasticsearch.http.HttpTransportSettings.SETTING_CORS_ALLOW_HEADERS;
|
||||
import static org.elasticsearch.http.HttpTransportSettings.SETTING_CORS_ALLOW_METHODS;
|
||||
import static org.elasticsearch.http.HttpTransportSettings.SETTING_CORS_ALLOW_ORIGIN;
|
||||
import static org.elasticsearch.http.HttpTransportSettings.SETTING_CORS_ENABLED;
|
||||
import static org.elasticsearch.http.HttpTransportSettings.SETTING_CORS_MAX_AGE;
|
||||
|
||||
/**
|
||||
* This file is forked from the https://netty.io project. In particular it combines the following three
|
||||
* files: io.netty.handler.codec.http.cors.CorsHandler, io.netty.handler.codec.http.cors.CorsConfig, and
|
||||
* io.netty.handler.codec.http.cors.CorsConfigBuilder.
|
||||
*
|
||||
* It modifies the original netty code to operation on Elasticsearch http request/response abstractions.
|
||||
* Additionally, it removes CORS features that are not used by Elasticsearch.
|
||||
*/
|
||||
public class CorsHandler {
|
||||
|
||||
public static final String ANY_ORIGIN = "*";
|
||||
|
||||
private CorsHandler() {
|
||||
}
|
||||
|
||||
public static class Config {
|
||||
|
||||
private final boolean enabled;
|
||||
private final Optional<Set<String>> origins;
|
||||
private final Optional<Pattern> pattern;
|
||||
private final boolean anyOrigin;
|
||||
private final boolean credentialsAllowed;
|
||||
private final Set<RestRequest.Method> allowedRequestMethods;
|
||||
private final Set<String> allowedRequestHeaders;
|
||||
private final long maxAge;
|
||||
|
||||
public Config(Builder builder) {
|
||||
this.enabled = builder.enabled;
|
||||
origins = builder.origins.map(HashSet::new);
|
||||
pattern = builder.pattern;
|
||||
anyOrigin = builder.anyOrigin;
|
||||
this.credentialsAllowed = builder.allowCredentials;
|
||||
this.allowedRequestMethods = Collections.unmodifiableSet(builder.requestMethods);
|
||||
this.allowedRequestHeaders = Collections.unmodifiableSet(builder.requestHeaders);
|
||||
this.maxAge = builder.maxAge;
|
||||
}
|
||||
|
||||
public boolean isCorsSupportEnabled() {
|
||||
return enabled;
|
||||
}
|
||||
|
||||
public boolean isAnyOriginSupported() {
|
||||
return anyOrigin;
|
||||
}
|
||||
|
||||
public boolean isOriginAllowed(String origin) {
|
||||
if (origins.isPresent()) {
|
||||
return origins.get().contains(origin);
|
||||
} else if (pattern.isPresent()) {
|
||||
return pattern.get().matcher(origin).matches();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean isCredentialsAllowed() {
|
||||
return credentialsAllowed;
|
||||
}
|
||||
|
||||
public Set<RestRequest.Method> allowedRequestMethods() {
|
||||
return allowedRequestMethods;
|
||||
}
|
||||
|
||||
public Set<String> allowedRequestHeaders() {
|
||||
return allowedRequestHeaders;
|
||||
}
|
||||
|
||||
public long maxAge() {
|
||||
return maxAge;
|
||||
}
|
||||
|
||||
public Optional<Set<String>> origins() {
|
||||
return origins;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Config{" +
|
||||
"enabled=" + enabled +
|
||||
", origins=" + origins +
|
||||
", pattern=" + pattern +
|
||||
", anyOrigin=" + anyOrigin +
|
||||
", credentialsAllowed=" + credentialsAllowed +
|
||||
", allowedRequestMethods=" + allowedRequestMethods +
|
||||
", allowedRequestHeaders=" + allowedRequestHeaders +
|
||||
", maxAge=" + maxAge +
|
||||
'}';
|
||||
}
|
||||
|
||||
private static class Builder {
|
||||
|
||||
private boolean enabled = true;
|
||||
private Optional<Set<String>> origins;
|
||||
private Optional<Pattern> pattern;
|
||||
private final boolean anyOrigin;
|
||||
private boolean allowCredentials = false;
|
||||
long maxAge;
|
||||
private final Set<RestRequest.Method> requestMethods = new HashSet<>();
|
||||
private final Set<String> requestHeaders = new HashSet<>();
|
||||
|
||||
private Builder() {
|
||||
anyOrigin = true;
|
||||
origins = Optional.empty();
|
||||
pattern = Optional.empty();
|
||||
}
|
||||
|
||||
private Builder(final String... origins) {
|
||||
this.origins = Optional.of(new LinkedHashSet<>(Arrays.asList(origins)));
|
||||
pattern = Optional.empty();
|
||||
anyOrigin = false;
|
||||
}
|
||||
|
||||
private Builder(final Pattern pattern) {
|
||||
this.pattern = Optional.of(pattern);
|
||||
origins = Optional.empty();
|
||||
anyOrigin = false;
|
||||
}
|
||||
|
||||
static Builder forOrigins(final String... origins) {
|
||||
return new Builder(origins);
|
||||
}
|
||||
|
||||
static Builder forAnyOrigin() {
|
||||
return new Builder();
|
||||
}
|
||||
|
||||
static Builder forPattern(Pattern pattern) {
|
||||
return new Builder(pattern);
|
||||
}
|
||||
|
||||
Builder allowCredentials() {
|
||||
this.allowCredentials = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
public Builder allowedRequestMethods(RestRequest.Method[] methods) {
|
||||
requestMethods.addAll(Arrays.asList(methods));
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder maxAge(int maxAge) {
|
||||
this.maxAge = maxAge;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder allowedRequestHeaders(String[] headers) {
|
||||
requestHeaders.addAll(Arrays.asList(headers));
|
||||
return this;
|
||||
}
|
||||
|
||||
public Config build() {
|
||||
return new Config(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static Config disabled() {
|
||||
Config.Builder builder = new Config.Builder();
|
||||
builder.enabled = false;
|
||||
return new Config(builder);
|
||||
}
|
||||
|
||||
public static Config fromSettings(Settings settings) {
|
||||
if (SETTING_CORS_ENABLED.get(settings) == false) {
|
||||
return disabled();
|
||||
}
|
||||
String origin = SETTING_CORS_ALLOW_ORIGIN.get(settings);
|
||||
final CorsHandler.Config.Builder builder;
|
||||
if (Strings.isNullOrEmpty(origin)) {
|
||||
builder = CorsHandler.Config.Builder.forOrigins();
|
||||
} else if (origin.equals(CorsHandler.ANY_ORIGIN)) {
|
||||
builder = CorsHandler.Config.Builder.forAnyOrigin();
|
||||
} else {
|
||||
try {
|
||||
Pattern p = RestUtils.checkCorsSettingForRegex(origin);
|
||||
if (p == null) {
|
||||
builder = CorsHandler.Config.Builder.forOrigins(RestUtils.corsSettingAsArray(origin));
|
||||
} else {
|
||||
builder = CorsHandler.Config.Builder.forPattern(p);
|
||||
}
|
||||
} catch (PatternSyntaxException e) {
|
||||
throw new SettingsException("Bad regex in [" + SETTING_CORS_ALLOW_ORIGIN.getKey() + "]: [" + origin + "]", e);
|
||||
}
|
||||
}
|
||||
if (SETTING_CORS_ALLOW_CREDENTIALS.get(settings)) {
|
||||
builder.allowCredentials();
|
||||
}
|
||||
String[] strMethods = Strings.tokenizeToStringArray(SETTING_CORS_ALLOW_METHODS.get(settings), ",");
|
||||
RestRequest.Method[] methods = Arrays.stream(strMethods)
|
||||
.map(s -> s.toUpperCase(Locale.ENGLISH))
|
||||
.map(RestRequest.Method::valueOf)
|
||||
.toArray(RestRequest.Method[]::new);
|
||||
Config config = builder.allowedRequestMethods(methods)
|
||||
.maxAge(SETTING_CORS_MAX_AGE.get(settings))
|
||||
.allowedRequestHeaders(Strings.tokenizeToStringArray(SETTING_CORS_ALLOW_HEADERS.get(settings), ","))
|
||||
.build();
|
||||
return config;
|
||||
}
|
||||
}
|
@ -0,0 +1,90 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
package org.elasticsearch.http;
|
||||
|
||||
import org.elasticsearch.common.Strings;
|
||||
import org.elasticsearch.common.settings.Settings;
|
||||
import org.elasticsearch.common.settings.SettingsException;
|
||||
import org.elasticsearch.rest.RestRequest;
|
||||
import org.elasticsearch.test.ESTestCase;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.Locale;
|
||||
import java.util.Set;
|
||||
import java.util.regex.PatternSyntaxException;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static org.elasticsearch.common.Strings.collectionToDelimitedString;
|
||||
import static org.elasticsearch.http.HttpTransportSettings.SETTING_CORS_ALLOW_CREDENTIALS;
|
||||
import static org.elasticsearch.http.HttpTransportSettings.SETTING_CORS_ALLOW_HEADERS;
|
||||
import static org.elasticsearch.http.HttpTransportSettings.SETTING_CORS_ALLOW_METHODS;
|
||||
import static org.elasticsearch.http.HttpTransportSettings.SETTING_CORS_ALLOW_ORIGIN;
|
||||
import static org.elasticsearch.http.HttpTransportSettings.SETTING_CORS_ENABLED;
|
||||
import static org.elasticsearch.http.HttpTransportSettings.SETTING_CORS_MAX_AGE;
|
||||
import static org.hamcrest.Matchers.containsString;
|
||||
import static org.hamcrest.Matchers.instanceOf;
|
||||
|
||||
public class CorsHandlerTests extends ESTestCase {
|
||||
|
||||
public void testCorsConfigWithBadRegex() {
|
||||
final Settings settings = Settings.builder()
|
||||
.put(SETTING_CORS_ENABLED.getKey(), true)
|
||||
.put(SETTING_CORS_ALLOW_ORIGIN.getKey(), "/[*/")
|
||||
.put(SETTING_CORS_ALLOW_CREDENTIALS.getKey(), true)
|
||||
.build();
|
||||
SettingsException e = expectThrows(SettingsException.class, () -> CorsHandler.fromSettings(settings));
|
||||
assertThat(e.getMessage(), containsString("Bad regex in [http.cors.allow-origin]: [/[*/]"));
|
||||
assertThat(e.getCause(), instanceOf(PatternSyntaxException.class));
|
||||
}
|
||||
|
||||
public void testCorsConfig() {
|
||||
final Set<String> methods = new HashSet<>(Arrays.asList("get", "options", "post"));
|
||||
final Set<String> headers = new HashSet<>(Arrays.asList("Content-Type", "Content-Length"));
|
||||
final String prefix = randomBoolean() ? " " : ""; // sometimes have a leading whitespace between comma delimited elements
|
||||
final Settings settings = Settings.builder()
|
||||
.put(SETTING_CORS_ENABLED.getKey(), true)
|
||||
.put(SETTING_CORS_ALLOW_ORIGIN.getKey(), "*")
|
||||
.put(SETTING_CORS_ALLOW_METHODS.getKey(), collectionToDelimitedString(methods, ",", prefix, ""))
|
||||
.put(SETTING_CORS_ALLOW_HEADERS.getKey(), collectionToDelimitedString(headers, ",", prefix, ""))
|
||||
.put(SETTING_CORS_ALLOW_CREDENTIALS.getKey(), true)
|
||||
.build();
|
||||
final CorsHandler.Config corsConfig = CorsHandler.fromSettings(settings);
|
||||
assertTrue(corsConfig.isAnyOriginSupported());
|
||||
assertEquals(headers, corsConfig.allowedRequestHeaders());
|
||||
assertEquals(methods.stream().map(s -> s.toUpperCase(Locale.ENGLISH)).collect(Collectors.toSet()),
|
||||
corsConfig.allowedRequestMethods().stream().map(RestRequest.Method::name).collect(Collectors.toSet()));
|
||||
}
|
||||
|
||||
public void testCorsConfigWithDefaults() {
|
||||
final Set<String> methods = Strings.commaDelimitedListToSet(SETTING_CORS_ALLOW_METHODS.getDefault(Settings.EMPTY));
|
||||
final Set<String> headers = Strings.commaDelimitedListToSet(SETTING_CORS_ALLOW_HEADERS.getDefault(Settings.EMPTY));
|
||||
final long maxAge = SETTING_CORS_MAX_AGE.getDefault(Settings.EMPTY);
|
||||
final Settings settings = Settings.builder().put(SETTING_CORS_ENABLED.getKey(), true).build();
|
||||
final CorsHandler.Config corsConfig = CorsHandler.fromSettings(settings);
|
||||
assertFalse(corsConfig.isAnyOriginSupported());
|
||||
assertEquals(Collections.emptySet(), corsConfig.origins().get());
|
||||
assertEquals(headers, corsConfig.allowedRequestHeaders());
|
||||
assertEquals(methods, corsConfig.allowedRequestMethods().stream().map(RestRequest.Method::name).collect(Collectors.toSet()));
|
||||
assertEquals(maxAge, corsConfig.maxAge());
|
||||
assertFalse(corsConfig.isCredentialsAllowed());
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user