CORS: Allowed to configure allow-credentials header to work via SSL

This adds support to return the "Access-Control-Allow-Credentials" header
if needed, so CORS will work flawlessly with authenticated applications.

Closes #6380
This commit is contained in:
Alexander Reelsen 2014-08-05 17:31:33 +02:00
parent 657b954528
commit 35e67c84fa
5 changed files with 30 additions and 7 deletions

View File

@ -57,6 +57,10 @@ be cached for. Defaults to `1728000` (20 days)
|`http.cors.allow-headers` |Which headers to allow. Defaults to |`http.cors.allow-headers` |Which headers to allow. Defaults to
`X-Requested-With, Content-Type, Content-Length`. `X-Requested-With, Content-Type, Content-Length`.
|`http.cors.allow-credentials` | Whether the `Access-Control-Allow-Credentials`
header should be returned. Note: This header is only returned, when the setting is
set to `true`. Defaults to `false`
|======================================================================= |=======================================================================

View File

@ -43,6 +43,7 @@ import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.regex.Pattern; import java.util.regex.Pattern;
import static org.elasticsearch.http.netty.NettyHttpServerTransport.*;
import static org.jboss.netty.handler.codec.http.HttpHeaders.Names.*; import static org.jboss.netty.handler.codec.http.HttpHeaders.Names.*;
/** /**
@ -97,20 +98,24 @@ public class NettyHttpChannel extends HttpChannel {
resp = new DefaultHttpResponse(HttpVersion.HTTP_1_1, status); resp = new DefaultHttpResponse(HttpVersion.HTTP_1_1, status);
} }
if (RestUtils.isBrowser(nettyRequest.headers().get(USER_AGENT))) { if (RestUtils.isBrowser(nettyRequest.headers().get(USER_AGENT))) {
if (transport.settings().getAsBoolean("http.cors.enabled", true)) { if (transport.settings().getAsBoolean(SETTING_CORS_ENABLED, true)) {
String originHeader = request.header(ORIGIN); String originHeader = request.header(ORIGIN);
if (!Strings.isNullOrEmpty(originHeader)) { if (!Strings.isNullOrEmpty(originHeader)) {
if (corsPattern == null) { if (corsPattern == null) {
resp.headers().add(ACCESS_CONTROL_ALLOW_ORIGIN, transport.settings().get("http.cors.allow-origin", "*")); resp.headers().add(ACCESS_CONTROL_ALLOW_ORIGIN, transport.settings().get(SETTING_CORS_ALLOW_ORIGIN, "*"));
} else { } else {
resp.headers().add(ACCESS_CONTROL_ALLOW_ORIGIN, corsPattern.matcher(originHeader).matches() ? originHeader : "null"); resp.headers().add(ACCESS_CONTROL_ALLOW_ORIGIN, corsPattern.matcher(originHeader).matches() ? originHeader : "null");
} }
} }
if (nettyRequest.getMethod() == HttpMethod.OPTIONS) { if (nettyRequest.getMethod() == HttpMethod.OPTIONS) {
// Allow Ajax requests based on the CORS "preflight" request // Allow Ajax requests based on the CORS "preflight" request
resp.headers().add(ACCESS_CONTROL_MAX_AGE, transport.settings().getAsInt("http.cors.max-age", 1728000)); resp.headers().add(ACCESS_CONTROL_MAX_AGE, transport.settings().getAsInt(SETTING_CORS_MAX_AGE, 1728000));
resp.headers().add(ACCESS_CONTROL_ALLOW_METHODS, transport.settings().get("http.cors.allow-methods", "OPTIONS, HEAD, GET, POST, PUT, DELETE")); resp.headers().add(ACCESS_CONTROL_ALLOW_METHODS, transport.settings().get(SETTING_CORS_ALLOW_METHODS, "OPTIONS, HEAD, GET, POST, PUT, DELETE"));
resp.headers().add(ACCESS_CONTROL_ALLOW_HEADERS, transport.settings().get("http.cors.allow-headers", "X-Requested-With, Content-Type, Content-Length")); resp.headers().add(ACCESS_CONTROL_ALLOW_HEADERS, transport.settings().get(SETTING_CORS_ALLOW_HEADERS, "X-Requested-With, Content-Type, Content-Length"));
}
if (transport.settings().getAsBoolean(SETTING_CORS_ALLOW_CREDENTIALS, false)) {
resp.headers().add(ACCESS_CONTROL_ALLOW_CREDENTIALS, "true");
} }
} }
} }

View File

@ -65,6 +65,13 @@ public class NettyHttpServerTransport extends AbstractLifecycleComponent<HttpSer
NettyUtils.setup(); NettyUtils.setup();
} }
public static final String SETTING_CORS_ENABLED = "http.cors.enabled";
public static final String SETTING_CORS_ALLOW_ORIGIN = "http.cors.allow-origin";
public static final String SETTING_CORS_MAX_AGE = "http.cors.max-age";
public static final String SETTING_CORS_ALLOW_METHODS = "http.cors.allow-methods";
public static final String SETTING_CORS_ALLOW_HEADERS = "http.cors.allow-headers";
public static final String SETTING_CORS_ALLOW_CREDENTIALS = "http.cors.allow-credentials";
private final NetworkService networkService; private final NetworkService networkService;
final BigArrays bigArrays; final BigArrays bigArrays;

View File

@ -38,6 +38,7 @@ public class CorsRegexDefaultTests extends ElasticsearchIntegrationTest {
assertThat(response.getStatusCode(), is(200)); assertThat(response.getStatusCode(), is(200));
assertThat(response.getHeaders(), hasKey("Access-Control-Allow-Origin")); assertThat(response.getHeaders(), hasKey("Access-Control-Allow-Origin"));
assertThat(response.getHeaders().get("Access-Control-Allow-Origin"), is("*")); assertThat(response.getHeaders().get("Access-Control-Allow-Origin"), is("*"));
assertThat(response.getHeaders(), not(hasKey("Access-Control-Allow-Credentials")));
} }
@Test @Test
@ -46,5 +47,6 @@ public class CorsRegexDefaultTests extends ElasticsearchIntegrationTest {
assertThat(response.getStatusCode(), is(200)); assertThat(response.getStatusCode(), is(200));
assertThat(response.getHeaders(), not(hasKey("Access-Control-Allow-Origin"))); assertThat(response.getHeaders(), not(hasKey("Access-Control-Allow-Origin")));
assertThat(response.getHeaders(), not(hasKey("Access-Control-Allow-Credentials")));
} }
} }

View File

@ -32,9 +32,12 @@ import org.junit.Test;
import java.net.InetSocketAddress; import java.net.InetSocketAddress;
import static org.elasticsearch.http.netty.NettyHttpServerTransport.SETTING_CORS_ALLOW_ORIGIN;
import static org.elasticsearch.http.netty.NettyHttpServerTransport.SETTING_CORS_ALLOW_CREDENTIALS;
import static org.elasticsearch.test.ElasticsearchIntegrationTest.ClusterScope; import static org.elasticsearch.test.ElasticsearchIntegrationTest.ClusterScope;
import static org.elasticsearch.test.ElasticsearchIntegrationTest.Scope; import static org.elasticsearch.test.ElasticsearchIntegrationTest.Scope;
import static org.hamcrest.Matchers.*; import static org.hamcrest.Matchers.*;
import static org.hamcrest.Matchers.is;
/** /**
* *
@ -47,8 +50,8 @@ public class CorsRegexTests extends ElasticsearchIntegrationTest {
@Override @Override
protected Settings nodeSettings(int nodeOrdinal) { protected Settings nodeSettings(int nodeOrdinal) {
return ImmutableSettings.settingsBuilder() return ImmutableSettings.settingsBuilder()
.put("http.cors.allow-origin", "/https?:\\/\\/localhost(:[0-9]+)?/") .put(SETTING_CORS_ALLOW_ORIGIN, "/https?:\\/\\/localhost(:[0-9]+)?/")
.put("network.host", "127.0.0.1") .put(SETTING_CORS_ALLOW_CREDENTIALS, "true")
.put(super.nodeSettings(nodeOrdinal)) .put(super.nodeSettings(nodeOrdinal))
.build(); .build();
} }
@ -62,6 +65,8 @@ public class CorsRegexTests extends ElasticsearchIntegrationTest {
corsValue = "https://localhost:9200"; corsValue = "https://localhost:9200";
response = httpClient().method("GET").path("/").addHeader("User-Agent", "Mozilla Bar").addHeader("Origin", corsValue).execute(); response = httpClient().method("GET").path("/").addHeader("User-Agent", "Mozilla Bar").addHeader("Origin", corsValue).execute();
assertResponseWithOriginheader(response, corsValue); assertResponseWithOriginheader(response, corsValue);
assertThat(response.getHeaders(), hasKey("Access-Control-Allow-Credentials"));
assertThat(response.getHeaders().get("Access-Control-Allow-Credentials"), is("true"));
} }
@Test @Test