Allow CORS to use modifying REST API
This supports integrations with other web applications by making it possible for another website to send mutation XHRs to Gerrit. Change-Id: Ifb40f7a5ab2fa53d484af2ccbc2162275b2b02e1
This commit is contained in:
@@ -20,8 +20,11 @@ import static com.google.common.net.HttpHeaders.ACCESS_CONTROL_ALLOW_CREDENTIALS
|
||||
import static com.google.common.net.HttpHeaders.ACCESS_CONTROL_ALLOW_HEADERS;
|
||||
import static com.google.common.net.HttpHeaders.ACCESS_CONTROL_ALLOW_METHODS;
|
||||
import static com.google.common.net.HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN;
|
||||
import static com.google.common.net.HttpHeaders.ACCESS_CONTROL_MAX_AGE;
|
||||
import static com.google.common.net.HttpHeaders.ACCESS_CONTROL_REQUEST_HEADERS;
|
||||
import static com.google.common.net.HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD;
|
||||
import static com.google.common.net.HttpHeaders.AUTHORIZATION;
|
||||
import static com.google.common.net.HttpHeaders.CONTENT_TYPE;
|
||||
import static com.google.common.net.HttpHeaders.ORIGIN;
|
||||
import static com.google.common.net.HttpHeaders.VARY;
|
||||
import static java.math.RoundingMode.CEILING;
|
||||
@@ -53,7 +56,6 @@ import com.google.common.collect.Iterables;
|
||||
import com.google.common.collect.ListMultimap;
|
||||
import com.google.common.collect.Lists;
|
||||
import com.google.common.collect.MultimapBuilder;
|
||||
import com.google.common.collect.Streams;
|
||||
import com.google.common.io.BaseEncoding;
|
||||
import com.google.common.io.CountingOutputStream;
|
||||
import com.google.common.math.IntMath;
|
||||
@@ -139,11 +141,13 @@ import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.TreeMap;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.Stream;
|
||||
import java.util.zip.GZIPOutputStream;
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServlet;
|
||||
@@ -169,8 +173,13 @@ public class RestApiServlet extends HttpServlet {
|
||||
// TODO: Remove when HttpServletResponse.SC_UNPROCESSABLE_ENTITY is available
|
||||
private static final int SC_UNPROCESSABLE_ENTITY = 422;
|
||||
private static final String X_REQUESTED_WITH = "X-Requested-With";
|
||||
private static final String X_GERRIT_AUTH = "X-Gerrit-Auth";
|
||||
private static final ImmutableSet<String> ALLOWED_CORS_METHODS =
|
||||
ImmutableSet.of("GET", "HEAD", "POST", "PUT", "DELETE");
|
||||
private static final ImmutableSet<String> ALLOWED_CORS_REQUEST_HEADERS =
|
||||
ImmutableSet.of(X_REQUESTED_WITH);
|
||||
Stream.of(AUTHORIZATION, CONTENT_TYPE, X_GERRIT_AUTH, X_REQUESTED_WITH)
|
||||
.map(s -> s.toLowerCase(Locale.US))
|
||||
.collect(ImmutableSet.toImmutableSet());
|
||||
|
||||
private static final int HEAP_EST_SIZE = 10 * 8 * 1024; // Presize 10 blocks.
|
||||
|
||||
@@ -499,10 +508,14 @@ public class RestApiServlet extends HttpServlet {
|
||||
}
|
||||
}
|
||||
|
||||
private void checkCors(HttpServletRequest req, HttpServletResponse res) {
|
||||
private void checkCors(HttpServletRequest req, HttpServletResponse res)
|
||||
throws BadRequestException {
|
||||
String origin = req.getHeader(ORIGIN);
|
||||
if (isRead(req) && !Strings.isNullOrEmpty(origin) && isOriginAllowed(origin)) {
|
||||
if (!Strings.isNullOrEmpty(origin)) {
|
||||
res.addHeader(VARY, ORIGIN);
|
||||
if (!isOriginAllowed(origin)) {
|
||||
throw new BadRequestException("origin not allowed");
|
||||
}
|
||||
setCorsHeaders(res, origin);
|
||||
}
|
||||
}
|
||||
@@ -516,8 +529,10 @@ public class RestApiServlet extends HttpServlet {
|
||||
private void doCorsPreflight(HttpServletRequest req, HttpServletResponse res)
|
||||
throws BadRequestException {
|
||||
CacheHeaders.setNotCacheable(res);
|
||||
res.setHeader(
|
||||
VARY, Joiner.on(", ").join(ImmutableList.of(ORIGIN, ACCESS_CONTROL_REQUEST_METHOD)));
|
||||
setHeaderList(
|
||||
res,
|
||||
VARY,
|
||||
ImmutableList.of(ORIGIN, ACCESS_CONTROL_REQUEST_METHOD, ACCESS_CONTROL_REQUEST_HEADERS));
|
||||
|
||||
String origin = req.getHeader(ORIGIN);
|
||||
if (Strings.isNullOrEmpty(origin) || !isOriginAllowed(origin)) {
|
||||
@@ -525,20 +540,17 @@ public class RestApiServlet extends HttpServlet {
|
||||
}
|
||||
|
||||
String method = req.getHeader(ACCESS_CONTROL_REQUEST_METHOD);
|
||||
if (!"GET".equals(method) && !"HEAD".equals(method)) {
|
||||
if (!ALLOWED_CORS_METHODS.contains(method)) {
|
||||
throw new BadRequestException(method + " not allowed in CORS");
|
||||
}
|
||||
|
||||
String headers = req.getHeader(ACCESS_CONTROL_REQUEST_HEADERS);
|
||||
if (headers != null) {
|
||||
res.addHeader(VARY, ACCESS_CONTROL_REQUEST_HEADERS);
|
||||
String badHeader =
|
||||
Streams.stream(Splitter.on(',').trimResults().split(headers))
|
||||
.filter(h -> !ALLOWED_CORS_REQUEST_HEADERS.contains(h))
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
if (badHeader != null) {
|
||||
throw new BadRequestException(badHeader + " not allowed in CORS");
|
||||
for (String reqHdr : Splitter.on(',').trimResults().split(headers)) {
|
||||
if (!ALLOWED_CORS_REQUEST_HEADERS.contains(reqHdr.toLowerCase(Locale.US))) {
|
||||
throw new BadRequestException(reqHdr + " not allowed in CORS");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -548,11 +560,19 @@ public class RestApiServlet extends HttpServlet {
|
||||
res.setContentLength(0);
|
||||
}
|
||||
|
||||
private void setCorsHeaders(HttpServletResponse res, String origin) {
|
||||
private static void setCorsHeaders(HttpServletResponse res, String origin) {
|
||||
res.setHeader(ACCESS_CONTROL_ALLOW_ORIGIN, origin);
|
||||
res.setHeader(ACCESS_CONTROL_ALLOW_CREDENTIALS, "true");
|
||||
res.setHeader(ACCESS_CONTROL_ALLOW_METHODS, "GET, OPTIONS");
|
||||
res.setHeader(ACCESS_CONTROL_ALLOW_HEADERS, Joiner.on(", ").join(ALLOWED_CORS_REQUEST_HEADERS));
|
||||
res.setHeader(ACCESS_CONTROL_MAX_AGE, "600");
|
||||
setHeaderList(
|
||||
res,
|
||||
ACCESS_CONTROL_ALLOW_METHODS,
|
||||
Iterables.concat(ALLOWED_CORS_METHODS, ImmutableList.of("OPTIONS")));
|
||||
setHeaderList(res, ACCESS_CONTROL_ALLOW_HEADERS, ALLOWED_CORS_REQUEST_HEADERS);
|
||||
}
|
||||
|
||||
private static void setHeaderList(HttpServletResponse res, String name, Iterable<String> values) {
|
||||
res.setHeader(name, Joiner.on(", ").join(values));
|
||||
}
|
||||
|
||||
private boolean isOriginAllowed(String origin) {
|
||||
|
||||
Reference in New Issue
Block a user