Make sign-out really invalidate the user's session

By storing our own session table on the server we can delete the
session record when the user asks us to sign them out of their
browser session.  This cuts off any chance for an outside attacker
to have sniffed the cookie and replay it after the user has signed
out of their session.

The downside of this approach is the session table is stored in
memory, and has a finite size.  If too many concurrent sessions
occur at once, the older sessions will be logged out.

Bug: GERRIT-83
Signed-off-by: Shawn O. Pearce <sop@google.com>
This commit is contained in:
Shawn O. Pearce
2009-08-15 17:49:00 -07:00
parent 12b5d8438c
commit b09322bd39
19 changed files with 371 additions and 334 deletions

View File

@@ -117,18 +117,6 @@ more agreements.
+
By default this is false (no agreements are used).
auth.maxSessionAge::
+
Maximum number of minutes that an XSRF token or a session cookie
is permitted to be valid for.
+
By default this is 720 minutes (12 hours). Any browser session
which has not been used in this time span will ask the user to
login again.
+
Administrators may increase (or decrease) this setting to control
how long an idle session is allowed to remain alive.
auth.allowGoogleAccountUpgrade::
+
Allow old Gerrit1 users to seamlessly upgrade from Google Accounts
@@ -281,6 +269,12 @@ This cache is based off the `account_ssh_keys` table and the
`accounts.ssh_user_name` column in the database. If either is
modified directly, this cache should be flushed.
cache `"web_sessions"`::
+
Tracks the live user sessions coming in over HTTP. Flushing this
cache would cause all users to be signed out immediately, forcing
them to sign-in again.
See also link:cmd-flush-caches.html[gerrit flush-caches].
Section contactstore

View File

@@ -48,15 +48,6 @@ import com.google.gwtjsonrpc.client.JsonUtil;
import java.util.ArrayList;
public class Gerrit implements EntryPoint {
/**
* Name of the Cookie our authentication data is stored in.
* <p>
* If this cookie has a value we assume we are signed in.
*
* @see #isSignedIn()
*/
public static final String ACCOUNT_COOKIE = "GerritAccount";
public static final GerritConstants C = GWT.create(GerritConstants.class);
public static final GerritMessages M = GWT.create(GerritMessages.class);
public static final GerritIcons ICONS = GWT.create(GerritIcons.class);

View File

@@ -29,14 +29,10 @@ import java.util.Collection;
/** Authentication related settings from {@code gerrit.config}. */
@Singleton
public class AuthConfig {
private final int sessionAge;
private final LoginType loginType;
private final String httpHeader;
private final String logoutUrl;
private final String[] trusted;
private final SignedToken xsrfToken;
private final SignedToken accountToken;
private final SignedToken emailReg;
private final boolean allowGoogleAccountUpgrade;
@@ -44,24 +40,10 @@ public class AuthConfig {
@Inject
AuthConfig(@GerritServerConfig final Config cfg, final SystemConfig s)
throws XsrfException {
sessionAge = cfg.getInt("auth", "maxsessionage", 12 * 60) * 60;
loginType = toType(cfg);
httpHeader = cfg.getString("auth", null, "httpheader");
logoutUrl = cfg.getString("auth", null, "logouturl");
trusted = toTrusted(cfg);
xsrfToken = new SignedToken(getSessionAge(), s.xsrfPrivateKey);
final int accountCookieAge;
switch (getLoginType()) {
case HTTP:
accountCookieAge = -1; // expire when the browser closes
break;
case OPENID:
default:
accountCookieAge = getSessionAge();
break;
}
accountToken = new SignedToken(accountCookieAge, s.accountPrivateKey);
emailReg = new SignedToken(5 * 24 * 60 * 60, s.accountPrivateKey);
allowGoogleAccountUpgrade =
@@ -114,19 +96,6 @@ public class AuthConfig {
return logoutUrl;
}
/** Time (in seconds) that user sessions stay "signed in". */
public int getSessionAge() {
return sessionAge;
}
public SignedToken getXsrfToken() {
return xsrfToken;
}
public SignedToken getAccountToken() {
return accountToken;
}
public SignedToken getEmailRegistrationToken() {
return emailReg;
}

View File

@@ -41,6 +41,7 @@ public class CacheManagerProvider implements Provider<CacheManager> {
private static final int MB = 1024 * 1024;
private static final int ONE_DAY = 24 * 60;
private static final int D_MAXAGE = 3 * 30 * ONE_DAY;
private static final int D_SESSIONAGE = ONE_DAY / 2;
private final Configuration mgr = new Configuration();
Configuration toConfiguration() {
@@ -59,6 +60,7 @@ public class CacheManagerProvider implements Provider<CacheManager> {
mgr.addCache(named("groups"));
mgr.addCache(named("projects"));
mgr.addCache(named("sshkeys"));
mgr.addCache(disk(tti(named("web_sessions"), D_SESSIONAGE)));
return mgr;
}
@@ -140,6 +142,14 @@ public class CacheManagerProvider implements Provider<CacheManager> {
return c;
}
private CacheConfiguration tti(final CacheConfiguration c, final int age) {
final String name = c.getName();
c.setTimeToIdleSeconds(config.getInt("cache", name, "maxage", age) * 60);
c.setTimeToLiveSeconds(0 /* until idle out, or removed */);
c.setEternal(c.getTimeToIdleSeconds() == 0);
return c;
}
private CacheConfiguration disk(final CacheConfiguration c) {
final String name = c.getName();
if (mgr.getDiskStoreConfiguration() != null) {

View File

@@ -1,65 +0,0 @@
// Copyright (C) 2009 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.google.gerrit.server.http;
import com.google.gerrit.client.Gerrit;
import com.google.gerrit.client.reviewdb.Account;
import com.google.gwtjsonrpc.server.ValidToken;
/** Data encoded into the {@link Gerrit#ACCOUNT_COOKIE} value. */
class AccountCookie {
private Account.Id accountId;
private boolean remember;
AccountCookie(final Account.Id id, final boolean remember) {
this.accountId = id;
this.remember = remember;
}
Account.Id getAccountId() {
return accountId;
}
boolean isRemember() {
return remember;
}
@Override
public String toString() {
return getAccountId().toString() + "." + (isRemember() ? "t" : "f");
}
static AccountCookie parse(final ValidToken tok) {
if (tok == null) {
return null;
}
final String str = tok.getData();
if (str == null || str.length() == 0) {
return null;
}
final String[] parts = str.split("\\.");
if (parts.length == 0 || parts.length > 2) {
return null;
}
final Account.Id accountId = Account.Id.parse(parts[0]);
final boolean remember = parts.length == 2 ? "t".equals(parts[0]) : true;
return new AccountCookie(accountId, remember);
}
}

View File

@@ -39,16 +39,16 @@ import javax.servlet.http.HttpServletResponse;
@Singleton
public class BecomeAnyAccountLoginServlet extends HttpServlet {
private final SchemaFactory<ReviewDb> schema;
private final Provider<GerritCall> callFactory;
private final Provider<WebSession> webSession;
private final Provider<String> urlProvider;
private final byte[] raw;
@Inject
BecomeAnyAccountLoginServlet(final Provider<GerritCall> cf,
BecomeAnyAccountLoginServlet(final Provider<WebSession> ws,
final SchemaFactory<ReviewDb> sf,
final @CanonicalWebUrl @Nullable Provider<String> up,
final ServletContext servletContext) throws IOException {
callFactory = cf;
webSession = ws;
schema = sf;
urlProvider = up;
@@ -95,9 +95,10 @@ public class BecomeAnyAccountLoginServlet extends HttpServlet {
if (accounts.size() == 1) {
final Account account = accounts.get(0);
final GerritCall call = callFactory.get();
call.noCache();
call.setAccount(account.getId(), false);
rsp.setHeader("Expires", "Fri, 01 Jan 1980 00:00:00 GMT");
rsp.setHeader("Pragma", "no-cache");
rsp.setHeader("Cache-Control", "no-cache, must-revalidate");
webSession.get().login(account.getId(), false);
rsp.sendRedirect(urlProvider.get());
} else {

View File

@@ -1,132 +0,0 @@
// Copyright (C) 2008 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.google.gerrit.server.http;
import com.google.gerrit.client.Gerrit;
import com.google.gerrit.client.reviewdb.Account;
import com.google.gerrit.server.config.AuthConfig;
import com.google.gwtjsonrpc.server.ActiveCall;
import com.google.gwtjsonrpc.server.SignedToken;
import com.google.gwtjsonrpc.server.ValidToken;
import com.google.gwtjsonrpc.server.XsrfException;
import com.google.gwtorm.client.OrmException;
import com.google.inject.Inject;
import com.google.inject.servlet.RequestScoped;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@RequestScoped
public class GerritCall extends ActiveCall {
private final SignedToken sessionKey;
private final int sessionAge;
private boolean accountRead;
private Account.Id accountId;
private boolean rememberAccount;
@Inject
GerritCall(final AuthConfig ac, final HttpServletRequest i,
final HttpServletResponse o) {
super(i, o);
setXsrfSignedToken(ac.getXsrfToken());
sessionKey = ac.getAccountToken();
sessionAge = ac.getSessionAge();
}
@Override
public void onFailure(final Throwable error) {
if (error instanceof OrmException) {
onInternalFailure(error);
} else {
super.onFailure(error);
}
}
@Override
public String getUser() {
initAccount();
return accountId != null ? accountId.toString() : null;
}
public void setAccount(final Account.Id id, final boolean remember) {
accountRead = true;
accountId = id;
rememberAccount = remember;
setAccountCookie();
}
public void logout() {
accountRead = true;
accountId = null;
rememberAccount = false;
removeCookie(Gerrit.ACCOUNT_COOKIE);
}
public Account.Id getAccountId() {
initAccount();
return accountId;
}
private void initAccount() {
if (!accountRead) {
accountRead = true;
accountId = null;
rememberAccount = false;
final ValidToken t = getCookie(Gerrit.ACCOUNT_COOKIE, sessionKey);
if (t != null) {
final AccountCookie cookie;
try {
cookie = AccountCookie.parse(t);
} catch (RuntimeException e) {
return;
}
accountId = cookie.getAccountId();
rememberAccount = cookie.isRemember();
if (t.needsRefresh()) {
// The cookie is valid, but its getting stale. Update it with a
// newer date so it doesn't expire on an active user.
//
setAccountCookie();
}
}
}
}
private void setAccountCookie() {
final AccountCookie ac = new AccountCookie(accountId, rememberAccount);
String val;
int age;
try {
val = sessionKey.newToken(ac.toString());
age = ac.isRemember() ? sessionAge : -1;
} catch (XsrfException e) {
val = "";
age = 0;
}
String path = getHttpServletRequest().getContextPath();
if (path.equals("")) {
path = "/";
}
final Cookie c = new Cookie(Gerrit.ACCOUNT_COOKIE, val);
c.setMaxAge(age);
c.setPath(path);
httpResponse.addCookie(c);
}
}

View File

@@ -16,11 +16,11 @@ package com.google.gerrit.server.http;
import com.google.gerrit.client.rpc.NotSignedInException;
import com.google.gerrit.client.rpc.SignInRequired;
import com.google.gerrit.server.config.AuthConfig;
import com.google.gson.GsonBuilder;
import com.google.gwtjsonrpc.client.RemoteJsonService;
import com.google.gwtjsonrpc.server.ActiveCall;
import com.google.gwtjsonrpc.server.JsonServlet;
import com.google.gwtjsonrpc.server.SignedToken;
import com.google.gwtorm.client.OrmException;
import com.google.inject.Inject;
import com.google.inject.Provider;
@@ -31,28 +31,20 @@ import javax.servlet.http.HttpServletResponse;
* Base JSON servlet to ensure the current user is not forged.
*/
@SuppressWarnings("serial")
final class GerritJsonServlet extends JsonServlet<GerritCall> {
private final Provider<GerritCall> callFactory;
final class GerritJsonServlet extends JsonServlet<GerritJsonServlet.GerritCall> {
private final Provider<WebSession> session;
private final RemoteJsonService service;
private final SignedToken xsrf;
@Inject
GerritJsonServlet(final Provider<GerritCall> cf, final AuthConfig authConfig,
final RemoteJsonService s) {
callFactory = cf;
GerritJsonServlet(final Provider<WebSession> w, final RemoteJsonService s) {
session = w;
service = s;
xsrf = authConfig.getXsrfToken();
}
@Override
protected SignedToken createXsrfSignedToken() {
return xsrf;
}
@Override
protected GerritCall createActiveCall(final HttpServletRequest req,
final HttpServletResponse resp) {
return callFactory.get();
final HttpServletResponse rsp) {
return new GerritCall(session.get(), req, rsp);
}
@Override
@@ -78,11 +70,7 @@ final class GerritJsonServlet extends JsonServlet<GerritCall> {
// valid XSRF token *and* have the user signed in. Doing these
// checks also validates that they agree on the user identity.
//
if (!call.requireXsrfValid()) {
return;
}
if (call.getAccountId() == null) {
if (!call.requireXsrfValid() || !session.get().isSignedIn()) {
call.onFailure(new NotSignedInException());
return;
}
@@ -93,4 +81,39 @@ final class GerritJsonServlet extends JsonServlet<GerritCall> {
protected Object createServiceHandle() {
return service;
}
static class GerritCall extends ActiveCall {
private final WebSession session;
GerritCall(final WebSession session, final HttpServletRequest i,
final HttpServletResponse o) {
super(i, o);
this.session = session;
}
@Override
public void onFailure(final Throwable error) {
if (error instanceof OrmException) {
onInternalFailure(error);
} else {
super.onFailure(error);
}
}
@Override
public boolean xsrfValidate() {
final String keyIn = getXsrfKeyIn();
if (keyIn == null || "".equals(keyIn)) {
// Anonymous requests don't need XSRF protection, they shouldn't
// be able to cause critical state changes.
//
return !session.isSignedIn();
} else {
// The session must exist, and must be using this token.
//
return session.isSignedIn() && session.isTokenValid(keyIn);
}
}
}
}

View File

@@ -23,7 +23,6 @@ import com.google.gerrit.server.config.Nullable;
import com.google.gerrit.server.config.SitePath;
import com.google.gwt.user.server.rpc.RPCServletUtils;
import com.google.gwtjsonrpc.server.JsonServlet;
import com.google.gwtjsonrpc.server.XsrfException;
import com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.inject.Singleton;
@@ -51,7 +50,7 @@ import javax.servlet.http.HttpServletResponse;
@Singleton
public class HostPageServlet extends HttpServlet {
private final Provider<CurrentUser> currentUser;
private final Provider<GerritCall> jsonCall;
private final Provider<WebSession> webSession;
private final File sitePath;
private final GerritConfig config;
private final Provider<String> urlProvider;
@@ -60,13 +59,13 @@ public class HostPageServlet extends HttpServlet {
@Inject
HostPageServlet(final Provider<CurrentUser> cu,
final Provider<GerritCall> cp, @SitePath final File path,
final Provider<WebSession> ws, @SitePath final File path,
final GerritConfig gc,
@CanonicalWebUrl @Nullable final Provider<String> up,
@CanonicalWebUrl @Nullable final String configuredUrl,
final ServletContext servletContext) throws IOException {
currentUser = cu;
jsonCall = cp;
webSession = ws;
urlProvider = up;
sitePath = path;
config = gc;
@@ -210,22 +209,9 @@ public class HostPageServlet extends HttpServlet {
final HostPageData pageData = new HostPageData();
pageData.config = config;
// Grab the current GerritCall to gain access to the XSRF token
// generator, and force a token to be constructed for this user.
// We can then send the XSRF token as part of the initial data
// vector in the host page, saving the client 1 round trip as it
// bootstraps itself.
//
try {
final GerritCall call = jsonCall.get();
call.xsrfValidate();
pageData.xsrfToken = call.getXsrfKeyOut();
} catch (XsrfException e) {
log("Cannot create initial XSRF token for user", e);
}
final CurrentUser user = currentUser.get();
if (user instanceof IdentifiedUser) {
pageData.xsrfToken = webSession.get().getToken();
pageData.userAccount = ((IdentifiedUser) user).getAccount();
}

View File

@@ -45,14 +45,14 @@ import javax.servlet.http.HttpServletResponse;
*/
@Singleton
class HttpAuthFilter implements Filter {
private final Provider<GerritCall> gerritCall;
private final Provider<WebSession> webSession;
private final byte[] signInRaw;
private final byte[] signInGzip;
@Inject
HttpAuthFilter(final Provider<GerritCall> gerritCall,
HttpAuthFilter(final Provider<WebSession> webSession,
final ServletContext servletContext) throws IOException {
this.gerritCall = gerritCall;
this.webSession = webSession;
final String hostPageName = "WEB-INF/LoginRedirect.html";
final String doc = HtmlDomUtil.readFile(servletContext, "/" + hostPageName);
@@ -68,7 +68,7 @@ class HttpAuthFilter implements Filter {
public void doFilter(final ServletRequest request,
final ServletResponse response, final FilterChain chain)
throws IOException, ServletException {
if (gerritCall.get().getAccountId() == null) {
if (!webSession.get().isSignedIn()) {
// Not signed in yet. Since the browser state might have an anchor
// token which we want to capture and carry through the auth process
// we send back JavaScript now to capture that, and do the real work

View File

@@ -14,31 +14,22 @@
package com.google.gerrit.server.http;
import com.google.gerrit.client.reviewdb.Account;
import com.google.gerrit.server.AnonymousUser;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.IdentifiedUser;
import com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.inject.Singleton;
import com.google.inject.servlet.RequestScoped;
@Singleton
@RequestScoped
class HttpCurrentUserProvider implements Provider<CurrentUser> {
private final Provider<GerritCall> call;
private final AnonymousUser anonymous;
private final IdentifiedUser.RequestFactory identified;
private final WebSession session;
@Inject
HttpCurrentUserProvider(final Provider<GerritCall> c, final AnonymousUser a,
final IdentifiedUser.RequestFactory f) {
call = c;
anonymous = a;
identified = f;
HttpCurrentUserProvider(final WebSession session) {
this.session = session;
}
@Override
public CurrentUser get() {
final Account.Id id = call.get().getAccountId();
return id != null ? identified.create(id) : anonymous;
return session.getCurrentUser();
}
}

View File

@@ -24,16 +24,15 @@ import com.google.inject.servlet.RequestScoped;
@RequestScoped
class HttpIdentifiedUserProvider implements Provider<IdentifiedUser> {
private final Provider<CurrentUser> currentUser;
private final CurrentUser user;
@Inject
HttpIdentifiedUserProvider(final Provider<CurrentUser> u) {
currentUser = u;
HttpIdentifiedUserProvider(final CurrentUser u) {
user = u;
}
@Override
public IdentifiedUser get() {
final CurrentUser user = currentUser.get();
if (user instanceof IdentifiedUser) {
return (IdentifiedUser) user;
}

View File

@@ -50,17 +50,17 @@ class HttpLoginServlet extends HttpServlet {
LoggerFactory.getLogger(HttpLoginServlet.class);
private static final String AUTHORIZATION = "Authorization";
private final Provider<GerritCall> gerritCall;
private final Provider<WebSession> webSession;
private final Provider<String> urlProvider;
private final AccountManager accountManager;
private final String loginHeader;
@Inject
HttpLoginServlet(final AuthConfig authConfig,
final Provider<GerritCall> gerritCall,
final Provider<WebSession> webSession,
@CanonicalWebUrl @Nullable final Provider<String> urlProvider,
final AccountManager accountManager) {
this.gerritCall = gerritCall;
this.webSession = webSession;
this.urlProvider = urlProvider;
this.accountManager = accountManager;
@@ -95,7 +95,7 @@ class HttpLoginServlet extends HttpServlet {
return;
}
gerritCall.get().setAccount(arsp.getAccountId(), false);
webSession.get().login(arsp.getAccountId(), false);
final StringBuilder rdr = new StringBuilder();
rdr.append(urlProvider.get());
rdr.append('#');

View File

@@ -30,16 +30,16 @@ import javax.servlet.http.HttpServletResponse;
@Singleton
class HttpLogoutServlet extends HttpServlet {
private final Provider<GerritCall> gerritCall;
private final Provider<WebSession> webSession;
private final Provider<String> urlProvider;
private final String logoutUrl;
@Inject
HttpLogoutServlet(final AuthConfig authConfig,
final Provider<GerritCall> gerritCall,
final Provider<WebSession> webSession,
@CanonicalWebUrl @Nullable final Provider<String> urlProvider,
final AccountManager accountManager) {
this.gerritCall = gerritCall;
this.webSession = webSession;
this.urlProvider = urlProvider;
this.logoutUrl = authConfig.getLogoutURL();
}
@@ -47,7 +47,7 @@ class HttpLogoutServlet extends HttpServlet {
@Override
protected void doGet(final HttpServletRequest req,
final HttpServletResponse rsp) throws IOException {
gerritCall.get().logout();
webSession.get().logout();
if (logoutUrl != null) {
rsp.sendRedirect(logoutUrl);
} else {

View File

@@ -66,10 +66,12 @@ class WebModule extends FactoryModule {
bind(GerritConfig.class).toProvider(GerritConfigProvider.class).in(
SINGLETON);
bind(AccountManager.class).in(SINGLETON);
bind(GerritCall.class).in(RequestScoped.class);
bind(SocketAddress.class).annotatedWith(RemotePeer.class).toProvider(
HttpRemotePeerProvider.class).in(RequestScoped.class);
bind(WebSession.class).in(RequestScoped.class);
bind(WebSessionManager.class).in(SINGLETON);
bind(CurrentUser.class).toProvider(HttpCurrentUserProvider.class).in(
RequestScoped.class);
bind(IdentifiedUser.class).toProvider(HttpIdentifiedUserProvider.class).in(

View File

@@ -0,0 +1,127 @@
package com.google.gerrit.server.http;
import com.google.gerrit.client.reviewdb.Account;
import com.google.gerrit.server.AnonymousUser;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.http.WebSessionManager.Key;
import com.google.gerrit.server.http.WebSessionManager.Val;
import com.google.inject.Inject;
import com.google.inject.servlet.RequestScoped;
import net.sf.ehcache.Element;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@RequestScoped
public final class WebSession {
private static final String ACCOUNT_COOKIE = "GerritAccount";
private final HttpServletRequest request;
private final HttpServletResponse response;
private final WebSessionManager manager;
private final AnonymousUser anonymous;
private final IdentifiedUser.RequestFactory identified;
private Cookie outCookie;
private Element element;
@Inject
WebSession(final HttpServletRequest request,
final HttpServletResponse response, final WebSessionManager manager,
final AnonymousUser anonymous,
final IdentifiedUser.RequestFactory identified) {
this.request = request;
this.response = response;
this.manager = manager;
this.anonymous = anonymous;
this.identified = identified;
this.element = manager.get(readCookie());
if (isSignedIn() && val().refreshCookieAt <= System.currentTimeMillis()) {
// Cookie is more than half old. Send it again to the client with a
// fresh expiration date.
//
final int age;
age = element.getTimeToIdle();
val().refreshCookieAt = System.currentTimeMillis() + (age / 2 * 1000L);
saveCookie(key().token, age);
}
}
private String readCookie() {
final Cookie[] all = request.getCookies();
if (all != null) {
for (final Cookie c : all) {
if (ACCOUNT_COOKIE.equals(c.getName())) {
final String v = c.getValue();
return v != null && !"".equals(v) ? v : null;
}
}
}
return null;
}
public boolean isSignedIn() {
return element != null;
}
String getToken() {
return isSignedIn() ? key().token : null;
}
boolean isTokenValid(final String keyIn) {
return isSignedIn() && key().token.equals(keyIn);
}
CurrentUser getCurrentUser() {
return isSignedIn() ? identified.create(val().accountId) : anonymous;
}
public void login(final Account.Id id, final boolean rememberMe) {
logout();
element = manager.create(id);
final int age;
if (rememberMe) {
age = element.getTimeToIdle();
val().refreshCookieAt = System.currentTimeMillis() + (age / 2 * 1000L);
} else {
age = -1 /* don't store on client disk */;
val().refreshCookieAt = Long.MAX_VALUE;
}
saveCookie(key().token, age);
}
public void logout() {
if (element != null) {
manager.destroy(element);
element = null;
saveCookie("", 0 /* erase at client */);
}
}
private Key key() {
return ((Key) element.getKey());
}
private Val val() {
return ((Val) element.getObjectValue());
}
private void saveCookie(final String val, final int age) {
if (outCookie == null) {
String path = request.getContextPath();
if (path.equals("")) {
path = "/";
}
outCookie = new Cookie(ACCOUNT_COOKIE, val);
outCookie.setPath(path);
outCookie.setMaxAge(age);
response.addCookie(outCookie);
} else {
outCookie.setMaxAge(age);
outCookie.setValue(val);
}
}
}

View File

@@ -0,0 +1,130 @@
// Copyright (C) 2009 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.google.gerrit.server.http;
import com.google.gerrit.client.reviewdb.Account;
import com.google.inject.Inject;
import com.google.inject.Singleton;
import net.sf.ehcache.Cache;
import net.sf.ehcache.CacheException;
import net.sf.ehcache.CacheManager;
import net.sf.ehcache.Element;
import org.spearce.jgit.util.Base64;
import org.spearce.jgit.util.NB;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.security.SecureRandom;
@Singleton
class WebSessionManager {
private final int tokenLen;
private final SecureRandom prng;
private final Cache self;
@Inject
WebSessionManager(final CacheManager mgr) {
tokenLen = 40 - 4;
prng = new SecureRandom();
self = mgr.getCache("web_sessions");
}
Element create(final Account.Id who) {
final int accountId = who.get();
final byte[] rnd = new byte[tokenLen];
prng.nextBytes(rnd);
final byte[] buf = new byte[4 + tokenLen];
NB.encodeInt32(buf, 0, accountId);
System.arraycopy(rnd, 0, buf, 4, rnd.length);
final String token = Base64.encodeBytes(rnd, Base64.DONT_BREAK_LINES);
final Val v = new Val(who);
final Element m = new Element(new Key(token), v);
self.put(m);
return m;
}
Element get(final String token) {
if (token != null && !"".equals(token)) {
try {
return self.get(new Key(token));
} catch (IllegalStateException e) {
return null;
} catch (CacheException e) {
return null;
}
} else {
return null;
}
}
void destroy(final Element cacheEntry) {
self.remove(cacheEntry.getKey());
}
static final class Key implements Serializable {
static final long serialVersionUID = 1L;
transient String token;
Key(final String t) {
token = t;
}
@Override
public int hashCode() {
return token.hashCode();
}
@Override
public boolean equals(Object obj) {
return obj instanceof Key && token.equals(((Key) obj).token);
}
private void writeObject(final ObjectOutputStream out) throws IOException {
out.writeUTF(token);
}
private void readObject(final ObjectInputStream in) throws IOException {
token = in.readUTF();
}
}
static final class Val implements Serializable {
static final long serialVersionUID = Key.serialVersionUID;
transient Account.Id accountId;
transient long refreshCookieAt;
Val(final Account.Id accountId) {
this.accountId = accountId;
}
private void writeObject(final ObjectOutputStream out) throws IOException {
out.writeInt(accountId.get());
out.writeLong(refreshCookieAt);
}
private void readObject(final ObjectInputStream in) throws IOException {
accountId = new Account.Id(in.readInt());
refreshCookieAt = in.readLong();
}
}
}

View File

@@ -26,7 +26,7 @@ import com.google.gerrit.server.account.AccountException;
import com.google.gerrit.server.account.AccountManager;
import com.google.gerrit.server.config.CanonicalWebUrl;
import com.google.gerrit.server.config.Nullable;
import com.google.gerrit.server.http.GerritCall;
import com.google.gerrit.server.http.WebSession;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwtorm.client.KeyUtil;
import com.google.inject.Inject;
@@ -88,7 +88,7 @@ class OpenIdServiceImpl implements OpenIdService {
private static final String SCHEMA_LASTNAME =
"http://schema.openid.net/namePerson/last";
private final Provider<GerritCall> callFactory;
private final Provider<WebSession> webSession;
private final Provider<IdentifiedUser> identifiedUser;
private final Provider<String> urlProvider;
private final AccountManager accountManager;
@@ -96,12 +96,12 @@ class OpenIdServiceImpl implements OpenIdService {
private final SelfPopulatingCache discoveryCache;
@Inject
OpenIdServiceImpl(final Provider<GerritCall> cf,
OpenIdServiceImpl(final Provider<WebSession> cf,
final Provider<IdentifiedUser> iu,
@CanonicalWebUrl @Nullable final Provider<String> up,
final CacheManager cacheMgr, final AccountManager am)
throws ConsumerException {
callFactory = cf;
webSession = cf;
identifiedUser = iu;
urlProvider = up;
accountManager = am;
@@ -296,7 +296,7 @@ class OpenIdServiceImpl implements OpenIdService {
lastId.setMaxAge(0);
}
rsp.addCookie(lastId);
callFactory.get().setAccount(arsp.getAccountId(), remember);
webSession.get().login(arsp.getAccountId(), remember);
callback(arsp.isNew(), req, rsp);
break;
@@ -350,7 +350,7 @@ class OpenIdServiceImpl implements OpenIdService {
private void cancel(final HttpServletRequest req,
final HttpServletResponse rsp) throws IOException {
if (isSignIn(signInMode(req))) {
callFactory.get().logout();
webSession.get().logout();
}
callback(false, req, rsp);
}
@@ -360,7 +360,7 @@ class OpenIdServiceImpl implements OpenIdService {
throws IOException {
final SignInDialog.Mode mode = signInMode(req);
if (isSignIn(mode)) {
callFactory.get().logout();
webSession.get().logout();
}
final StringBuilder rdr = new StringBuilder();
rdr.append(urlProvider.get());

View File

@@ -104,6 +104,17 @@ final class AdminFlushCaches extends CacheCommand {
}
private boolean flush(final String cacheName) {
return all || caches.contains(cacheName);
if (caches.contains(cacheName)) {
return true;
} else if (all) {
if ("web_sessions".equals(cacheName)) {
return false;
}
return true;
} else {
return false;
}
}
}