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. */
|
/** Data sent as part of the host page, to bootstrap the UI. */
|
||||||
public class HostPageData {
|
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 String version;
|
||||||
public DiffPreferencesInfo accountDiffPref;
|
public DiffPreferencesInfo accountDiffPref;
|
||||||
public String xGerritAuth;
|
|
||||||
public Theme theme;
|
public Theme theme;
|
||||||
public List<String> plugins;
|
public List<String> plugins;
|
||||||
public List<Message> messages;
|
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_GROUP;
|
||||||
import static com.google.gerrit.common.data.GlobalCapability.CREATE_PROJECT;
|
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.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.AccountApi;
|
||||||
import com.google.gerrit.client.account.AccountCapabilities;
|
import com.google.gerrit.client.account.AccountCapabilities;
|
||||||
@ -477,8 +478,7 @@ public class Gerrit implements EntryPoint {
|
|||||||
if (result.accountDiffPref != null) {
|
if (result.accountDiffPref != null) {
|
||||||
myAccountDiffPref = result.accountDiffPref;
|
myAccountDiffPref = result.accountDiffPref;
|
||||||
}
|
}
|
||||||
if (result.xGerritAuth != null) {
|
if (result.accountDiffPref != null) {
|
||||||
xGerritAuth = result.xGerritAuth;
|
|
||||||
// TODO: Support options on the GetDetail REST endpoint so that it can
|
// TODO: Support options on the GetDetail REST endpoint so that it can
|
||||||
// also return the preferences. Then we can fetch everything with a
|
// also return the preferences. Then we can fetch everything with a
|
||||||
// single request and we don't need the callback group anymore.
|
// single request and we don't need the callback group anymore.
|
||||||
@ -513,6 +513,8 @@ public class Gerrit implements EntryPoint {
|
|||||||
editPrefs = null;
|
editPrefs = null;
|
||||||
onModuleLoad2(result);
|
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.Gerrit;
|
||||||
import com.google.gerrit.client.RpcStatus;
|
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.GWT;
|
||||||
import com.google.gwt.core.client.JavaScriptObject;
|
import com.google.gwt.core.client.JavaScriptObject;
|
||||||
import com.google.gwt.core.client.Scheduler;
|
import com.google.gwt.core.client.Scheduler;
|
||||||
@ -449,7 +450,7 @@ public class RestApi {
|
|||||||
}
|
}
|
||||||
req.setHeader("Accept", JSON_TYPE);
|
req.setHeader("Accept", JSON_TYPE);
|
||||||
if (Gerrit.getXGerritAuth() != null) {
|
if (Gerrit.getXGerritAuth() != null) {
|
||||||
req.setHeader("X-Gerrit-Auth", Gerrit.getXGerritAuth());
|
req.setHeader(HostPageData.XSRF_HEADER_NAME, Gerrit.getXGerritAuth());
|
||||||
}
|
}
|
||||||
return req;
|
return req;
|
||||||
}
|
}
|
||||||
|
@ -16,6 +16,7 @@ package com.google.gerrit.httpd;
|
|||||||
|
|
||||||
import static java.util.concurrent.TimeUnit.HOURS;
|
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.Key;
|
||||||
import com.google.gerrit.httpd.WebSessionManager.Val;
|
import com.google.gerrit.httpd.WebSessionManager.Val;
|
||||||
import com.google.gerrit.reviewdb.client.Account;
|
import com.google.gerrit.reviewdb.client.Account;
|
||||||
@ -80,7 +81,7 @@ public abstract class CacheBasedWebSession implements WebSession {
|
|||||||
val = manager.createVal(key, val);
|
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())) {
|
if (val != null && token != null && token.equals(val.getAuth())) {
|
||||||
okPaths.add(AccessPath.REST_API);
|
okPaths.add(AccessPath.REST_API);
|
||||||
}
|
}
|
||||||
|
@ -68,6 +68,7 @@ import java.util.concurrent.TimeUnit;
|
|||||||
|
|
||||||
import javax.servlet.ServletContext;
|
import javax.servlet.ServletContext;
|
||||||
import javax.servlet.ServletException;
|
import javax.servlet.ServletException;
|
||||||
|
import javax.servlet.http.Cookie;
|
||||||
import javax.servlet.http.HttpServlet;
|
import javax.servlet.http.HttpServlet;
|
||||||
import javax.servlet.http.HttpServletRequest;
|
import javax.servlet.http.HttpServletRequest;
|
||||||
import javax.servlet.http.HttpServletResponse;
|
import javax.servlet.http.HttpServletResponse;
|
||||||
@ -193,10 +194,7 @@ public class HostPageServlet extends HttpServlet {
|
|||||||
StringWriter w = new StringWriter();
|
StringWriter w = new StringWriter();
|
||||||
CurrentUser user = currentUser.get();
|
CurrentUser user = currentUser.get();
|
||||||
if (user.isIdentifiedUser()) {
|
if (user.isIdentifiedUser()) {
|
||||||
w.write(HPD_ID + ".xGerritAuth=");
|
setXGerritAuthCookie(req, rsp, session.get());
|
||||||
json(session.get().getXGerritAuth(), w);
|
|
||||||
w.write(";");
|
|
||||||
|
|
||||||
w.write(HPD_ID + ".accountDiffPref=");
|
w.write(HPD_ID + ".accountDiffPref=");
|
||||||
json(getDiffPreferences(user.asIdentifiedUser()), w);
|
json(getDiffPreferences(user.asIdentifiedUser()), w);
|
||||||
w.write(";");
|
w.write(";");
|
||||||
@ -205,6 +203,7 @@ public class HostPageServlet extends HttpServlet {
|
|||||||
json(signedInTheme, w);
|
json(signedInTheme, w);
|
||||||
w.write(";");
|
w.write(";");
|
||||||
} else {
|
} else {
|
||||||
|
setXGerritAuthCookie(req, rsp, null);
|
||||||
w.write(HPD_ID + ".theme=");
|
w.write(HPD_ID + ".theme=");
|
||||||
json(signedOutTheme, w);
|
json(signedOutTheme, w);
|
||||||
w.write(";");
|
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) {
|
private DiffPreferencesInfo getDiffPreferences(IdentifiedUser user) {
|
||||||
try {
|
try {
|
||||||
return getDiff.apply(new AccountResource(user));
|
return getDiff.apply(new AccountResource(user));
|
||||||
|
Loading…
Reference in New Issue
Block a user