Send XSRF token as a cookie
For XSRF protection, the REST API requires a special header "X-Gerrit-Auth" containing a token known only to the JS running on the server's domain. Previously, this was set as a JS literal in a <script> tag directly in the output served at /. To support a purely static JS application, we can't depend on injecting JS literals into the body of /. Instead, provide the XSRF token via a cookie on the response for /. Note that this only affects how the server communicates the XSRF token to the client; we still require clients to send the token back in the X-Gerrit-Auth header. The server must ignore an XSRF token cookie sent by the client, since the cookie will be sent on all requests, including possibly-forged cross-site requests. As a minor optimization and to avoid confusion when looking at request traces, the client code discards the cookie as soon as it is stored in a JS variable. Change-Id: Ie24051b48186d6f85bccadfe139e2103b4228cbe
This commit is contained in:
parent
b459ab1d76
commit
aea5d3e2fd
@ -21,9 +21,20 @@ import java.util.List;
|
||||
|
||||
/** Data sent as part of the host page, to bootstrap the UI. */
|
||||
public class HostPageData {
|
||||
/**
|
||||
* Name of the cookie in which the XSRF token is sent from the server to the
|
||||
* client during host page bootstrapping.
|
||||
*/
|
||||
public static final String XSRF_COOKIE_NAME = "XSRF_TOKEN";
|
||||
|
||||
/**
|
||||
* Name of the HTTP header in which the client must send the XSRF token to the
|
||||
* server on each request.
|
||||
*/
|
||||
public static final String XSRF_HEADER_NAME = "X-Gerrit-Auth";
|
||||
|
||||
public String version;
|
||||
public DiffPreferencesInfo accountDiffPref;
|
||||
public String xGerritAuth;
|
||||
public Theme theme;
|
||||
public List<String> plugins;
|
||||
public List<Message> messages;
|
||||
|
@ -17,6 +17,7 @@ package com.google.gerrit.client;
|
||||
import static com.google.gerrit.common.data.GlobalCapability.CREATE_GROUP;
|
||||
import static com.google.gerrit.common.data.GlobalCapability.CREATE_PROJECT;
|
||||
import static com.google.gerrit.common.data.GlobalCapability.VIEW_PLUGINS;
|
||||
import static com.google.gerrit.common.data.HostPageData.XSRF_COOKIE_NAME;
|
||||
|
||||
import com.google.gerrit.client.account.AccountApi;
|
||||
import com.google.gerrit.client.account.AccountCapabilities;
|
||||
@ -477,8 +478,7 @@ public class Gerrit implements EntryPoint {
|
||||
if (result.accountDiffPref != null) {
|
||||
myAccountDiffPref = result.accountDiffPref;
|
||||
}
|
||||
if (result.xGerritAuth != null) {
|
||||
xGerritAuth = result.xGerritAuth;
|
||||
if (result.accountDiffPref != null) {
|
||||
// TODO: Support options on the GetDetail REST endpoint so that it can
|
||||
// also return the preferences. Then we can fetch everything with a
|
||||
// single request and we don't need the callback group anymore.
|
||||
@ -513,6 +513,8 @@ public class Gerrit implements EntryPoint {
|
||||
editPrefs = null;
|
||||
onModuleLoad2(result);
|
||||
}
|
||||
xGerritAuth = Cookies.getCookie(XSRF_COOKIE_NAME);
|
||||
Cookies.removeCookie(XSRF_COOKIE_NAME);
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
@ -21,6 +21,7 @@ import static com.google.gwt.http.client.RequestBuilder.PUT;
|
||||
|
||||
import com.google.gerrit.client.Gerrit;
|
||||
import com.google.gerrit.client.RpcStatus;
|
||||
import com.google.gerrit.common.data.HostPageData;
|
||||
import com.google.gwt.core.client.GWT;
|
||||
import com.google.gwt.core.client.JavaScriptObject;
|
||||
import com.google.gwt.core.client.Scheduler;
|
||||
@ -449,7 +450,7 @@ public class RestApi {
|
||||
}
|
||||
req.setHeader("Accept", JSON_TYPE);
|
||||
if (Gerrit.getXGerritAuth() != null) {
|
||||
req.setHeader("X-Gerrit-Auth", Gerrit.getXGerritAuth());
|
||||
req.setHeader(HostPageData.XSRF_HEADER_NAME, Gerrit.getXGerritAuth());
|
||||
}
|
||||
return req;
|
||||
}
|
||||
|
@ -16,6 +16,7 @@ package com.google.gerrit.httpd;
|
||||
|
||||
import static java.util.concurrent.TimeUnit.HOURS;
|
||||
|
||||
import com.google.gerrit.common.data.HostPageData;
|
||||
import com.google.gerrit.httpd.WebSessionManager.Key;
|
||||
import com.google.gerrit.httpd.WebSessionManager.Val;
|
||||
import com.google.gerrit.reviewdb.client.Account;
|
||||
@ -80,7 +81,7 @@ public abstract class CacheBasedWebSession implements WebSession {
|
||||
val = manager.createVal(key, val);
|
||||
}
|
||||
|
||||
String token = request.getHeader("X-Gerrit-Auth");
|
||||
String token = request.getHeader(HostPageData.XSRF_HEADER_NAME);
|
||||
if (val != null && token != null && token.equals(val.getAuth())) {
|
||||
okPaths.add(AccessPath.REST_API);
|
||||
}
|
||||
|
@ -68,6 +68,7 @@ import java.util.concurrent.TimeUnit;
|
||||
|
||||
import javax.servlet.ServletContext;
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.Cookie;
|
||||
import javax.servlet.http.HttpServlet;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
@ -193,10 +194,7 @@ public class HostPageServlet extends HttpServlet {
|
||||
StringWriter w = new StringWriter();
|
||||
CurrentUser user = currentUser.get();
|
||||
if (user.isIdentifiedUser()) {
|
||||
w.write(HPD_ID + ".xGerritAuth=");
|
||||
json(session.get().getXGerritAuth(), w);
|
||||
w.write(";");
|
||||
|
||||
setXGerritAuthCookie(req, rsp, session.get());
|
||||
w.write(HPD_ID + ".accountDiffPref=");
|
||||
json(getDiffPreferences(user.asIdentifiedUser()), w);
|
||||
w.write(";");
|
||||
@ -205,6 +203,7 @@ public class HostPageServlet extends HttpServlet {
|
||||
json(signedInTheme, w);
|
||||
w.write(";");
|
||||
} else {
|
||||
setXGerritAuthCookie(req, rsp, null);
|
||||
w.write(HPD_ID + ".theme=");
|
||||
json(signedOutTheme, w);
|
||||
w.write(";");
|
||||
@ -231,6 +230,23 @@ public class HostPageServlet extends HttpServlet {
|
||||
}
|
||||
}
|
||||
|
||||
private static void setXGerritAuthCookie(HttpServletRequest req,
|
||||
HttpServletResponse rsp, WebSession session) {
|
||||
String v = session != null ? session.getXGerritAuth() : "";
|
||||
Cookie c = new Cookie(HostPageData.XSRF_COOKIE_NAME, v);
|
||||
c.setPath("/");
|
||||
c.setHttpOnly(false);
|
||||
c.setSecure(isSecure(req));
|
||||
c.setMaxAge(session != null
|
||||
? -1 // Set the cookie for this browser session.
|
||||
: 0); // Remove the cookie (expire immediately).
|
||||
rsp.addCookie(c);
|
||||
}
|
||||
|
||||
private static boolean isSecure(HttpServletRequest req) {
|
||||
return req.isSecure() || "https".equals(req.getScheme());
|
||||
}
|
||||
|
||||
private DiffPreferencesInfo getDiffPreferences(IdentifiedUser user) {
|
||||
try {
|
||||
return getDiff.apply(new AccountResource(user));
|
||||
|
Loading…
Reference in New Issue
Block a user