Move RPC auth token from Authorization header to X-Gerrit-Auth
Servers that run with auth.type = HTTP or HTTP_LDAP are unable to use the web UI because the Authorization code supplied by the UI overrides the browser's native Authorization header and causes the request to be blocked at the HTTP reverse proxy, before Gerrit even sees the request. Instead insert a unique token into X-Gerrit-Auth, leaving the HTTP standard Authorization header unspecified and available for use in HTTP reverse proxies. Bug: issue 1743 Change-Id: Ice143405dc4d59ade56a6e4a385a4a0995e347ff
This commit is contained in:
@@ -23,7 +23,7 @@ import java.util.List;
|
||||
public class HostPageData {
|
||||
public Account account;
|
||||
public AccountDiffPreference accountDiffPref;
|
||||
public String authorization;
|
||||
public String xGerritAuth;
|
||||
public GerritConfig config;
|
||||
public Theme theme;
|
||||
public List<String> plugins;
|
||||
|
||||
@@ -105,7 +105,7 @@ public class Gerrit implements EntryPoint {
|
||||
private static HostPageData.Theme myTheme;
|
||||
private static Account myAccount;
|
||||
private static AccountDiffPreference myAccountDiffPref;
|
||||
private static String authorization;
|
||||
private static String xGerritAuth;
|
||||
|
||||
private static MorphingTabPanel menuLeft;
|
||||
private static LinkMenuBar menuRight;
|
||||
@@ -253,8 +253,8 @@ public class Gerrit implements EntryPoint {
|
||||
}
|
||||
|
||||
/** @return access token to prove user identity during REST API calls. */
|
||||
public static String getAuthorization() {
|
||||
return authorization;
|
||||
public static String getXGerritAuth() {
|
||||
return xGerritAuth;
|
||||
}
|
||||
|
||||
/** @return the currently signed in users's diff preferences; null if no diff preferences defined for the account */
|
||||
@@ -351,7 +351,7 @@ public class Gerrit implements EntryPoint {
|
||||
static void deleteSessionCookie() {
|
||||
myAccount = null;
|
||||
myAccountDiffPref = null;
|
||||
authorization = null;
|
||||
xGerritAuth = null;
|
||||
refreshMenuBar();
|
||||
|
||||
// If the cookie was HttpOnly, this request to delete it will
|
||||
@@ -401,7 +401,7 @@ public class Gerrit implements EntryPoint {
|
||||
myTheme = result.theme;
|
||||
if (result.account != null) {
|
||||
myAccount = result.account;
|
||||
authorization = result.authorization;
|
||||
xGerritAuth = result.xGerritAuth;
|
||||
}
|
||||
if (result.accountDiffPref != null) {
|
||||
myAccountDiffPref = result.accountDiffPref;
|
||||
@@ -548,7 +548,7 @@ public class Gerrit implements EntryPoint {
|
||||
JsonUtil.setDefaultXsrfManager(new XsrfManager() {
|
||||
@Override
|
||||
public String getToken(JsonDefTarget proxy) {
|
||||
return authorization;
|
||||
return xGerritAuth;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -335,8 +335,8 @@ public class RestApi {
|
||||
req.setHeader("If-None-Match", ifNoneMatch);
|
||||
}
|
||||
req.setHeader("Accept", JSON_TYPE);
|
||||
if (Gerrit.getAuthorization() != null) {
|
||||
req.setHeader("Authorization", Gerrit.getAuthorization());
|
||||
if (Gerrit.getXGerritAuth() != null) {
|
||||
req.setHeader("X-Gerrit-Auth", Gerrit.getXGerritAuth());
|
||||
}
|
||||
return req;
|
||||
}
|
||||
|
||||
@@ -90,25 +90,18 @@ public final class CacheBasedWebSession implements WebSession {
|
||||
|
||||
if (!GitSmartHttpTools.isGitClient(request)) {
|
||||
String cookie = readCookie();
|
||||
String token = request.getHeader("Authorization");
|
||||
if (token != null && token.startsWith("Bearer ")) {
|
||||
token = token.substring("Bearer ".length());
|
||||
okPaths.add(AccessPath.REST_API);
|
||||
} else {
|
||||
token = cookie;
|
||||
}
|
||||
|
||||
if (token != null) {
|
||||
key = new Key(token);
|
||||
if (cookie != null) {
|
||||
key = new Key(cookie);
|
||||
val = manager.get(key);
|
||||
|
||||
if (isSignedIn() && val.needsCookieRefresh()) {
|
||||
if (val != null && val.needsCookieRefresh()) {
|
||||
// Cookie is more than half old. Send the cookie again to the
|
||||
// client with an updated expiration date.
|
||||
val = manager.createVal(key, val);
|
||||
if (cookie != null) {
|
||||
saveCookie();
|
||||
}
|
||||
|
||||
String token = request.getHeader("X-Gerrit-Auth");
|
||||
if (val != null && token != null && token.equals(val.getAuth())) {
|
||||
okPaths.add(AccessPath.REST_API);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -133,13 +126,13 @@ public final class CacheBasedWebSession implements WebSession {
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getAuthorization() {
|
||||
return isSignedIn() ? "Bearer " + key.getToken() : null;
|
||||
public String getXGerritAuth() {
|
||||
return isSignedIn() ? val.getAuth() : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isValidAuthorization(String keyIn) {
|
||||
return keyIn.equals(getAuthorization());
|
||||
public boolean isValidXGerritAuth(String keyIn) {
|
||||
return keyIn.equals(getXGerritAuth());
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -183,7 +176,7 @@ public final class CacheBasedWebSession implements WebSession {
|
||||
}
|
||||
|
||||
key = manager.createKey(id);
|
||||
val = manager.createVal(key, id, rememberMe, identity, null);
|
||||
val = manager.createVal(key, id, rememberMe, identity, null, null);
|
||||
saveCookie();
|
||||
}
|
||||
|
||||
@@ -191,7 +184,7 @@ public final class CacheBasedWebSession implements WebSession {
|
||||
@Override
|
||||
public void setUserAccountId(Account.Id id) {
|
||||
key = new Key("id:" + id);
|
||||
val = new Val(id, 0, false, null, 0, null);
|
||||
val = new Val(id, 0, false, null, 0, null, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -22,8 +22,8 @@ import com.google.gerrit.server.account.AuthResult;
|
||||
|
||||
public interface WebSession {
|
||||
public boolean isSignedIn();
|
||||
public String getAuthorization();
|
||||
public boolean isValidAuthorization(String keyIn);
|
||||
public String getXGerritAuth();
|
||||
public boolean isValidXGerritAuth(String keyIn);
|
||||
public AccountExternalId.Key getLastLoginExternalId();
|
||||
public CurrentUser getCurrentUser();
|
||||
public void login(AuthResult res, boolean rememberMe);
|
||||
|
||||
@@ -69,6 +69,10 @@ class WebSessionManager {
|
||||
}
|
||||
|
||||
Key createKey(final Account.Id who) {
|
||||
return new Key(newUniqueToken(who));
|
||||
}
|
||||
|
||||
private String newUniqueToken(final Account.Id who) {
|
||||
try {
|
||||
final int nonceLen = 20;
|
||||
final ByteArrayOutputStream buf;
|
||||
@@ -80,7 +84,7 @@ class WebSessionManager {
|
||||
writeVarInt32(buf, who.get());
|
||||
writeBytes(buf, rnd);
|
||||
|
||||
return new Key(CookieBase64.encode(buf.toByteArray()));
|
||||
return CookieBase64.encode(buf.toByteArray());
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("Cannot produce new account cookie", e);
|
||||
}
|
||||
@@ -90,11 +94,11 @@ class WebSessionManager {
|
||||
final Account.Id who = val.getAccountId();
|
||||
final boolean remember = val.isPersistentCookie();
|
||||
final AccountExternalId.Key lastLogin = val.getExternalId();
|
||||
return createVal(key, who, remember, lastLogin, val.sessionId);
|
||||
return createVal(key, who, remember, lastLogin, val.sessionId, val.auth);
|
||||
}
|
||||
|
||||
Val createVal(final Key key, final Account.Id who, final boolean remember,
|
||||
final AccountExternalId.Key lastLogin, String sid) {
|
||||
final AccountExternalId.Key lastLogin, String sid, String auth) {
|
||||
// Refresh the cookie every hour or when it is half-expired.
|
||||
// This reduces the odds that the user session will be kicked
|
||||
// early but also avoids us needing to refresh the cookie on
|
||||
@@ -107,10 +111,13 @@ class WebSessionManager {
|
||||
final long refreshCookieAt = now + refresh;
|
||||
final long expiresAt = now + sessionMaxAgeMillis;
|
||||
if (sid == null) {
|
||||
sid = createKey(who).token;
|
||||
sid = newUniqueToken(who);
|
||||
}
|
||||
if (auth == null) {
|
||||
auth = newUniqueToken(who);
|
||||
}
|
||||
|
||||
Val val = new Val(who, refreshCookieAt, remember, lastLogin, expiresAt, sid);
|
||||
Val val = new Val(who, refreshCookieAt, remember, lastLogin, expiresAt, sid, auth);
|
||||
self.put(key.token, val);
|
||||
return val;
|
||||
}
|
||||
@@ -175,16 +182,18 @@ class WebSessionManager {
|
||||
private transient AccountExternalId.Key externalId;
|
||||
private transient long expiresAt;
|
||||
private transient String sessionId;
|
||||
private transient String auth;
|
||||
|
||||
Val(final Account.Id accountId, final long refreshCookieAt,
|
||||
final boolean persistentCookie, final AccountExternalId.Key externalId,
|
||||
final long expiresAt, final String sessionId) {
|
||||
final long expiresAt, final String sessionId, final String auth) {
|
||||
this.accountId = accountId;
|
||||
this.refreshCookieAt = refreshCookieAt;
|
||||
this.persistentCookie = persistentCookie;
|
||||
this.externalId = externalId;
|
||||
this.expiresAt = expiresAt;
|
||||
this.sessionId = sessionId;
|
||||
this.auth = auth;
|
||||
}
|
||||
|
||||
Account.Id getAccountId() {
|
||||
@@ -199,6 +208,10 @@ class WebSessionManager {
|
||||
return sessionId;
|
||||
}
|
||||
|
||||
String getAuth() {
|
||||
return auth;
|
||||
}
|
||||
|
||||
boolean needsCookieRefresh() {
|
||||
return refreshCookieAt <= now();
|
||||
}
|
||||
@@ -230,6 +243,11 @@ class WebSessionManager {
|
||||
writeVarInt32(out, 6);
|
||||
writeFixInt64(out, expiresAt);
|
||||
|
||||
if (auth != null) {
|
||||
writeVarInt32(out, 7);
|
||||
writeString(out, auth);
|
||||
}
|
||||
|
||||
writeVarInt32(out, 0);
|
||||
}
|
||||
|
||||
@@ -257,6 +275,9 @@ class WebSessionManager {
|
||||
case 6:
|
||||
expiresAt = readFixInt64(in);
|
||||
continue;
|
||||
case 7:
|
||||
auth = readString(in);
|
||||
continue;
|
||||
default:
|
||||
throw new IOException("Unknown tag found in object: " + tag);
|
||||
}
|
||||
|
||||
@@ -179,8 +179,8 @@ public class HostPageServlet extends HttpServlet {
|
||||
json(((IdentifiedUser) user).getAccount(), w);
|
||||
w.write(";");
|
||||
|
||||
w.write(HPD_ID + ".authorization=");
|
||||
json(session.get().getAuthorization(), w);
|
||||
w.write(HPD_ID + ".xGerritAuth=");
|
||||
json(session.get().getXGerritAuth(), w);
|
||||
w.write(";");
|
||||
|
||||
w.write(HPD_ID + ".accountDiffPref=");
|
||||
|
||||
@@ -272,7 +272,7 @@ final class GerritJsonServlet extends JsonServlet<GerritJsonServlet.GerritCall>
|
||||
//
|
||||
return !session.isSignedIn();
|
||||
|
||||
} else if (session.isSignedIn() && session.isValidAuthorization(keyIn)) {
|
||||
} else if (session.isSignedIn() && session.isValidXGerritAuth(keyIn)) {
|
||||
// The session must exist, and must be using this token.
|
||||
//
|
||||
session.getCurrentUser().setAccessPath(AccessPath.JSON_RPC);
|
||||
|
||||
Reference in New Issue
Block a user