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:
Shawn Pearce
2013-03-11 12:46:51 -07:00
parent 353e384e9e
commit 742a046fd0
8 changed files with 55 additions and 41 deletions

View File

@@ -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;

View File

@@ -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

View File

@@ -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;
}

View File

@@ -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

View File

@@ -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);

View File

@@ -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);
}

View File

@@ -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=");

View File

@@ -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);