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:
@@ -117,18 +117,6 @@ more agreements.
|
|||||||
+
|
+
|
||||||
By default this is false (no agreements are used).
|
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::
|
auth.allowGoogleAccountUpgrade::
|
||||||
+
|
+
|
||||||
Allow old Gerrit1 users to seamlessly upgrade from Google Accounts
|
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
|
`accounts.ssh_user_name` column in the database. If either is
|
||||||
modified directly, this cache should be flushed.
|
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].
|
See also link:cmd-flush-caches.html[gerrit flush-caches].
|
||||||
|
|
||||||
Section contactstore
|
Section contactstore
|
||||||
|
|||||||
@@ -48,15 +48,6 @@ import com.google.gwtjsonrpc.client.JsonUtil;
|
|||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
|
||||||
public class Gerrit implements EntryPoint {
|
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 GerritConstants C = GWT.create(GerritConstants.class);
|
||||||
public static final GerritMessages M = GWT.create(GerritMessages.class);
|
public static final GerritMessages M = GWT.create(GerritMessages.class);
|
||||||
public static final GerritIcons ICONS = GWT.create(GerritIcons.class);
|
public static final GerritIcons ICONS = GWT.create(GerritIcons.class);
|
||||||
|
|||||||
@@ -29,14 +29,10 @@ import java.util.Collection;
|
|||||||
/** Authentication related settings from {@code gerrit.config}. */
|
/** Authentication related settings from {@code gerrit.config}. */
|
||||||
@Singleton
|
@Singleton
|
||||||
public class AuthConfig {
|
public class AuthConfig {
|
||||||
private final int sessionAge;
|
|
||||||
private final LoginType loginType;
|
private final LoginType loginType;
|
||||||
private final String httpHeader;
|
private final String httpHeader;
|
||||||
private final String logoutUrl;
|
private final String logoutUrl;
|
||||||
private final String[] trusted;
|
private final String[] trusted;
|
||||||
|
|
||||||
private final SignedToken xsrfToken;
|
|
||||||
private final SignedToken accountToken;
|
|
||||||
private final SignedToken emailReg;
|
private final SignedToken emailReg;
|
||||||
|
|
||||||
private final boolean allowGoogleAccountUpgrade;
|
private final boolean allowGoogleAccountUpgrade;
|
||||||
@@ -44,24 +40,10 @@ public class AuthConfig {
|
|||||||
@Inject
|
@Inject
|
||||||
AuthConfig(@GerritServerConfig final Config cfg, final SystemConfig s)
|
AuthConfig(@GerritServerConfig final Config cfg, final SystemConfig s)
|
||||||
throws XsrfException {
|
throws XsrfException {
|
||||||
sessionAge = cfg.getInt("auth", "maxsessionage", 12 * 60) * 60;
|
|
||||||
loginType = toType(cfg);
|
loginType = toType(cfg);
|
||||||
httpHeader = cfg.getString("auth", null, "httpheader");
|
httpHeader = cfg.getString("auth", null, "httpheader");
|
||||||
logoutUrl = cfg.getString("auth", null, "logouturl");
|
logoutUrl = cfg.getString("auth", null, "logouturl");
|
||||||
trusted = toTrusted(cfg);
|
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);
|
emailReg = new SignedToken(5 * 24 * 60 * 60, s.accountPrivateKey);
|
||||||
|
|
||||||
allowGoogleAccountUpgrade =
|
allowGoogleAccountUpgrade =
|
||||||
@@ -114,19 +96,6 @@ public class AuthConfig {
|
|||||||
return logoutUrl;
|
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() {
|
public SignedToken getEmailRegistrationToken() {
|
||||||
return emailReg;
|
return emailReg;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -41,6 +41,7 @@ public class CacheManagerProvider implements Provider<CacheManager> {
|
|||||||
private static final int MB = 1024 * 1024;
|
private static final int MB = 1024 * 1024;
|
||||||
private static final int ONE_DAY = 24 * 60;
|
private static final int ONE_DAY = 24 * 60;
|
||||||
private static final int D_MAXAGE = 3 * 30 * ONE_DAY;
|
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();
|
private final Configuration mgr = new Configuration();
|
||||||
|
|
||||||
Configuration toConfiguration() {
|
Configuration toConfiguration() {
|
||||||
@@ -59,6 +60,7 @@ public class CacheManagerProvider implements Provider<CacheManager> {
|
|||||||
mgr.addCache(named("groups"));
|
mgr.addCache(named("groups"));
|
||||||
mgr.addCache(named("projects"));
|
mgr.addCache(named("projects"));
|
||||||
mgr.addCache(named("sshkeys"));
|
mgr.addCache(named("sshkeys"));
|
||||||
|
mgr.addCache(disk(tti(named("web_sessions"), D_SESSIONAGE)));
|
||||||
|
|
||||||
return mgr;
|
return mgr;
|
||||||
}
|
}
|
||||||
@@ -140,6 +142,14 @@ public class CacheManagerProvider implements Provider<CacheManager> {
|
|||||||
return c;
|
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) {
|
private CacheConfiguration disk(final CacheConfiguration c) {
|
||||||
final String name = c.getName();
|
final String name = c.getName();
|
||||||
if (mgr.getDiskStoreConfiguration() != null) {
|
if (mgr.getDiskStoreConfiguration() != null) {
|
||||||
|
|||||||
@@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -39,16 +39,16 @@ import javax.servlet.http.HttpServletResponse;
|
|||||||
@Singleton
|
@Singleton
|
||||||
public class BecomeAnyAccountLoginServlet extends HttpServlet {
|
public class BecomeAnyAccountLoginServlet extends HttpServlet {
|
||||||
private final SchemaFactory<ReviewDb> schema;
|
private final SchemaFactory<ReviewDb> schema;
|
||||||
private final Provider<GerritCall> callFactory;
|
private final Provider<WebSession> webSession;
|
||||||
private final Provider<String> urlProvider;
|
private final Provider<String> urlProvider;
|
||||||
private final byte[] raw;
|
private final byte[] raw;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
BecomeAnyAccountLoginServlet(final Provider<GerritCall> cf,
|
BecomeAnyAccountLoginServlet(final Provider<WebSession> ws,
|
||||||
final SchemaFactory<ReviewDb> sf,
|
final SchemaFactory<ReviewDb> sf,
|
||||||
final @CanonicalWebUrl @Nullable Provider<String> up,
|
final @CanonicalWebUrl @Nullable Provider<String> up,
|
||||||
final ServletContext servletContext) throws IOException {
|
final ServletContext servletContext) throws IOException {
|
||||||
callFactory = cf;
|
webSession = ws;
|
||||||
schema = sf;
|
schema = sf;
|
||||||
urlProvider = up;
|
urlProvider = up;
|
||||||
|
|
||||||
@@ -95,9 +95,10 @@ public class BecomeAnyAccountLoginServlet extends HttpServlet {
|
|||||||
|
|
||||||
if (accounts.size() == 1) {
|
if (accounts.size() == 1) {
|
||||||
final Account account = accounts.get(0);
|
final Account account = accounts.get(0);
|
||||||
final GerritCall call = callFactory.get();
|
rsp.setHeader("Expires", "Fri, 01 Jan 1980 00:00:00 GMT");
|
||||||
call.noCache();
|
rsp.setHeader("Pragma", "no-cache");
|
||||||
call.setAccount(account.getId(), false);
|
rsp.setHeader("Cache-Control", "no-cache, must-revalidate");
|
||||||
|
webSession.get().login(account.getId(), false);
|
||||||
rsp.sendRedirect(urlProvider.get());
|
rsp.sendRedirect(urlProvider.get());
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -16,11 +16,11 @@ package com.google.gerrit.server.http;
|
|||||||
|
|
||||||
import com.google.gerrit.client.rpc.NotSignedInException;
|
import com.google.gerrit.client.rpc.NotSignedInException;
|
||||||
import com.google.gerrit.client.rpc.SignInRequired;
|
import com.google.gerrit.client.rpc.SignInRequired;
|
||||||
import com.google.gerrit.server.config.AuthConfig;
|
|
||||||
import com.google.gson.GsonBuilder;
|
import com.google.gson.GsonBuilder;
|
||||||
import com.google.gwtjsonrpc.client.RemoteJsonService;
|
import com.google.gwtjsonrpc.client.RemoteJsonService;
|
||||||
|
import com.google.gwtjsonrpc.server.ActiveCall;
|
||||||
import com.google.gwtjsonrpc.server.JsonServlet;
|
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.Inject;
|
||||||
import com.google.inject.Provider;
|
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.
|
* Base JSON servlet to ensure the current user is not forged.
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings("serial")
|
@SuppressWarnings("serial")
|
||||||
final class GerritJsonServlet extends JsonServlet<GerritCall> {
|
final class GerritJsonServlet extends JsonServlet<GerritJsonServlet.GerritCall> {
|
||||||
private final Provider<GerritCall> callFactory;
|
private final Provider<WebSession> session;
|
||||||
private final RemoteJsonService service;
|
private final RemoteJsonService service;
|
||||||
private final SignedToken xsrf;
|
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
GerritJsonServlet(final Provider<GerritCall> cf, final AuthConfig authConfig,
|
GerritJsonServlet(final Provider<WebSession> w, final RemoteJsonService s) {
|
||||||
final RemoteJsonService s) {
|
session = w;
|
||||||
callFactory = cf;
|
|
||||||
service = s;
|
service = s;
|
||||||
xsrf = authConfig.getXsrfToken();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected SignedToken createXsrfSignedToken() {
|
|
||||||
return xsrf;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected GerritCall createActiveCall(final HttpServletRequest req,
|
protected GerritCall createActiveCall(final HttpServletRequest req,
|
||||||
final HttpServletResponse resp) {
|
final HttpServletResponse rsp) {
|
||||||
return callFactory.get();
|
return new GerritCall(session.get(), req, rsp);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -78,11 +70,7 @@ final class GerritJsonServlet extends JsonServlet<GerritCall> {
|
|||||||
// valid XSRF token *and* have the user signed in. Doing these
|
// valid XSRF token *and* have the user signed in. Doing these
|
||||||
// checks also validates that they agree on the user identity.
|
// checks also validates that they agree on the user identity.
|
||||||
//
|
//
|
||||||
if (!call.requireXsrfValid()) {
|
if (!call.requireXsrfValid() || !session.get().isSignedIn()) {
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (call.getAccountId() == null) {
|
|
||||||
call.onFailure(new NotSignedInException());
|
call.onFailure(new NotSignedInException());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -93,4 +81,39 @@ final class GerritJsonServlet extends JsonServlet<GerritCall> {
|
|||||||
protected Object createServiceHandle() {
|
protected Object createServiceHandle() {
|
||||||
return service;
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,7 +23,6 @@ import com.google.gerrit.server.config.Nullable;
|
|||||||
import com.google.gerrit.server.config.SitePath;
|
import com.google.gerrit.server.config.SitePath;
|
||||||
import com.google.gwt.user.server.rpc.RPCServletUtils;
|
import com.google.gwt.user.server.rpc.RPCServletUtils;
|
||||||
import com.google.gwtjsonrpc.server.JsonServlet;
|
import com.google.gwtjsonrpc.server.JsonServlet;
|
||||||
import com.google.gwtjsonrpc.server.XsrfException;
|
|
||||||
import com.google.inject.Inject;
|
import com.google.inject.Inject;
|
||||||
import com.google.inject.Provider;
|
import com.google.inject.Provider;
|
||||||
import com.google.inject.Singleton;
|
import com.google.inject.Singleton;
|
||||||
@@ -51,7 +50,7 @@ import javax.servlet.http.HttpServletResponse;
|
|||||||
@Singleton
|
@Singleton
|
||||||
public class HostPageServlet extends HttpServlet {
|
public class HostPageServlet extends HttpServlet {
|
||||||
private final Provider<CurrentUser> currentUser;
|
private final Provider<CurrentUser> currentUser;
|
||||||
private final Provider<GerritCall> jsonCall;
|
private final Provider<WebSession> webSession;
|
||||||
private final File sitePath;
|
private final File sitePath;
|
||||||
private final GerritConfig config;
|
private final GerritConfig config;
|
||||||
private final Provider<String> urlProvider;
|
private final Provider<String> urlProvider;
|
||||||
@@ -60,13 +59,13 @@ public class HostPageServlet extends HttpServlet {
|
|||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
HostPageServlet(final Provider<CurrentUser> cu,
|
HostPageServlet(final Provider<CurrentUser> cu,
|
||||||
final Provider<GerritCall> cp, @SitePath final File path,
|
final Provider<WebSession> ws, @SitePath final File path,
|
||||||
final GerritConfig gc,
|
final GerritConfig gc,
|
||||||
@CanonicalWebUrl @Nullable final Provider<String> up,
|
@CanonicalWebUrl @Nullable final Provider<String> up,
|
||||||
@CanonicalWebUrl @Nullable final String configuredUrl,
|
@CanonicalWebUrl @Nullable final String configuredUrl,
|
||||||
final ServletContext servletContext) throws IOException {
|
final ServletContext servletContext) throws IOException {
|
||||||
currentUser = cu;
|
currentUser = cu;
|
||||||
jsonCall = cp;
|
webSession = ws;
|
||||||
urlProvider = up;
|
urlProvider = up;
|
||||||
sitePath = path;
|
sitePath = path;
|
||||||
config = gc;
|
config = gc;
|
||||||
@@ -210,22 +209,9 @@ public class HostPageServlet extends HttpServlet {
|
|||||||
final HostPageData pageData = new HostPageData();
|
final HostPageData pageData = new HostPageData();
|
||||||
pageData.config = config;
|
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();
|
final CurrentUser user = currentUser.get();
|
||||||
if (user instanceof IdentifiedUser) {
|
if (user instanceof IdentifiedUser) {
|
||||||
|
pageData.xsrfToken = webSession.get().getToken();
|
||||||
pageData.userAccount = ((IdentifiedUser) user).getAccount();
|
pageData.userAccount = ((IdentifiedUser) user).getAccount();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -45,14 +45,14 @@ import javax.servlet.http.HttpServletResponse;
|
|||||||
*/
|
*/
|
||||||
@Singleton
|
@Singleton
|
||||||
class HttpAuthFilter implements Filter {
|
class HttpAuthFilter implements Filter {
|
||||||
private final Provider<GerritCall> gerritCall;
|
private final Provider<WebSession> webSession;
|
||||||
private final byte[] signInRaw;
|
private final byte[] signInRaw;
|
||||||
private final byte[] signInGzip;
|
private final byte[] signInGzip;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
HttpAuthFilter(final Provider<GerritCall> gerritCall,
|
HttpAuthFilter(final Provider<WebSession> webSession,
|
||||||
final ServletContext servletContext) throws IOException {
|
final ServletContext servletContext) throws IOException {
|
||||||
this.gerritCall = gerritCall;
|
this.webSession = webSession;
|
||||||
|
|
||||||
final String hostPageName = "WEB-INF/LoginRedirect.html";
|
final String hostPageName = "WEB-INF/LoginRedirect.html";
|
||||||
final String doc = HtmlDomUtil.readFile(servletContext, "/" + hostPageName);
|
final String doc = HtmlDomUtil.readFile(servletContext, "/" + hostPageName);
|
||||||
@@ -68,7 +68,7 @@ class HttpAuthFilter implements Filter {
|
|||||||
public void doFilter(final ServletRequest request,
|
public void doFilter(final ServletRequest request,
|
||||||
final ServletResponse response, final FilterChain chain)
|
final ServletResponse response, final FilterChain chain)
|
||||||
throws IOException, ServletException {
|
throws IOException, ServletException {
|
||||||
if (gerritCall.get().getAccountId() == null) {
|
if (!webSession.get().isSignedIn()) {
|
||||||
// Not signed in yet. Since the browser state might have an anchor
|
// Not signed in yet. Since the browser state might have an anchor
|
||||||
// token which we want to capture and carry through the auth process
|
// 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
|
// we send back JavaScript now to capture that, and do the real work
|
||||||
|
|||||||
@@ -14,31 +14,22 @@
|
|||||||
|
|
||||||
package com.google.gerrit.server.http;
|
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.CurrentUser;
|
||||||
import com.google.gerrit.server.IdentifiedUser;
|
|
||||||
import com.google.inject.Inject;
|
import com.google.inject.Inject;
|
||||||
import com.google.inject.Provider;
|
import com.google.inject.Provider;
|
||||||
import com.google.inject.Singleton;
|
import com.google.inject.servlet.RequestScoped;
|
||||||
|
|
||||||
@Singleton
|
@RequestScoped
|
||||||
class HttpCurrentUserProvider implements Provider<CurrentUser> {
|
class HttpCurrentUserProvider implements Provider<CurrentUser> {
|
||||||
private final Provider<GerritCall> call;
|
private final WebSession session;
|
||||||
private final AnonymousUser anonymous;
|
|
||||||
private final IdentifiedUser.RequestFactory identified;
|
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
HttpCurrentUserProvider(final Provider<GerritCall> c, final AnonymousUser a,
|
HttpCurrentUserProvider(final WebSession session) {
|
||||||
final IdentifiedUser.RequestFactory f) {
|
this.session = session;
|
||||||
call = c;
|
|
||||||
anonymous = a;
|
|
||||||
identified = f;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public CurrentUser get() {
|
public CurrentUser get() {
|
||||||
final Account.Id id = call.get().getAccountId();
|
return session.getCurrentUser();
|
||||||
return id != null ? identified.create(id) : anonymous;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,16 +24,15 @@ import com.google.inject.servlet.RequestScoped;
|
|||||||
|
|
||||||
@RequestScoped
|
@RequestScoped
|
||||||
class HttpIdentifiedUserProvider implements Provider<IdentifiedUser> {
|
class HttpIdentifiedUserProvider implements Provider<IdentifiedUser> {
|
||||||
private final Provider<CurrentUser> currentUser;
|
private final CurrentUser user;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
HttpIdentifiedUserProvider(final Provider<CurrentUser> u) {
|
HttpIdentifiedUserProvider(final CurrentUser u) {
|
||||||
currentUser = u;
|
user = u;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public IdentifiedUser get() {
|
public IdentifiedUser get() {
|
||||||
final CurrentUser user = currentUser.get();
|
|
||||||
if (user instanceof IdentifiedUser) {
|
if (user instanceof IdentifiedUser) {
|
||||||
return (IdentifiedUser) user;
|
return (IdentifiedUser) user;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -50,17 +50,17 @@ class HttpLoginServlet extends HttpServlet {
|
|||||||
LoggerFactory.getLogger(HttpLoginServlet.class);
|
LoggerFactory.getLogger(HttpLoginServlet.class);
|
||||||
|
|
||||||
private static final String AUTHORIZATION = "Authorization";
|
private static final String AUTHORIZATION = "Authorization";
|
||||||
private final Provider<GerritCall> gerritCall;
|
private final Provider<WebSession> webSession;
|
||||||
private final Provider<String> urlProvider;
|
private final Provider<String> urlProvider;
|
||||||
private final AccountManager accountManager;
|
private final AccountManager accountManager;
|
||||||
private final String loginHeader;
|
private final String loginHeader;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
HttpLoginServlet(final AuthConfig authConfig,
|
HttpLoginServlet(final AuthConfig authConfig,
|
||||||
final Provider<GerritCall> gerritCall,
|
final Provider<WebSession> webSession,
|
||||||
@CanonicalWebUrl @Nullable final Provider<String> urlProvider,
|
@CanonicalWebUrl @Nullable final Provider<String> urlProvider,
|
||||||
final AccountManager accountManager) {
|
final AccountManager accountManager) {
|
||||||
this.gerritCall = gerritCall;
|
this.webSession = webSession;
|
||||||
this.urlProvider = urlProvider;
|
this.urlProvider = urlProvider;
|
||||||
this.accountManager = accountManager;
|
this.accountManager = accountManager;
|
||||||
|
|
||||||
@@ -95,7 +95,7 @@ class HttpLoginServlet extends HttpServlet {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
gerritCall.get().setAccount(arsp.getAccountId(), false);
|
webSession.get().login(arsp.getAccountId(), false);
|
||||||
final StringBuilder rdr = new StringBuilder();
|
final StringBuilder rdr = new StringBuilder();
|
||||||
rdr.append(urlProvider.get());
|
rdr.append(urlProvider.get());
|
||||||
rdr.append('#');
|
rdr.append('#');
|
||||||
|
|||||||
@@ -30,16 +30,16 @@ import javax.servlet.http.HttpServletResponse;
|
|||||||
|
|
||||||
@Singleton
|
@Singleton
|
||||||
class HttpLogoutServlet extends HttpServlet {
|
class HttpLogoutServlet extends HttpServlet {
|
||||||
private final Provider<GerritCall> gerritCall;
|
private final Provider<WebSession> webSession;
|
||||||
private final Provider<String> urlProvider;
|
private final Provider<String> urlProvider;
|
||||||
private final String logoutUrl;
|
private final String logoutUrl;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
HttpLogoutServlet(final AuthConfig authConfig,
|
HttpLogoutServlet(final AuthConfig authConfig,
|
||||||
final Provider<GerritCall> gerritCall,
|
final Provider<WebSession> webSession,
|
||||||
@CanonicalWebUrl @Nullable final Provider<String> urlProvider,
|
@CanonicalWebUrl @Nullable final Provider<String> urlProvider,
|
||||||
final AccountManager accountManager) {
|
final AccountManager accountManager) {
|
||||||
this.gerritCall = gerritCall;
|
this.webSession = webSession;
|
||||||
this.urlProvider = urlProvider;
|
this.urlProvider = urlProvider;
|
||||||
this.logoutUrl = authConfig.getLogoutURL();
|
this.logoutUrl = authConfig.getLogoutURL();
|
||||||
}
|
}
|
||||||
@@ -47,7 +47,7 @@ class HttpLogoutServlet extends HttpServlet {
|
|||||||
@Override
|
@Override
|
||||||
protected void doGet(final HttpServletRequest req,
|
protected void doGet(final HttpServletRequest req,
|
||||||
final HttpServletResponse rsp) throws IOException {
|
final HttpServletResponse rsp) throws IOException {
|
||||||
gerritCall.get().logout();
|
webSession.get().logout();
|
||||||
if (logoutUrl != null) {
|
if (logoutUrl != null) {
|
||||||
rsp.sendRedirect(logoutUrl);
|
rsp.sendRedirect(logoutUrl);
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -66,10 +66,12 @@ class WebModule extends FactoryModule {
|
|||||||
bind(GerritConfig.class).toProvider(GerritConfigProvider.class).in(
|
bind(GerritConfig.class).toProvider(GerritConfigProvider.class).in(
|
||||||
SINGLETON);
|
SINGLETON);
|
||||||
bind(AccountManager.class).in(SINGLETON);
|
bind(AccountManager.class).in(SINGLETON);
|
||||||
bind(GerritCall.class).in(RequestScoped.class);
|
|
||||||
bind(SocketAddress.class).annotatedWith(RemotePeer.class).toProvider(
|
bind(SocketAddress.class).annotatedWith(RemotePeer.class).toProvider(
|
||||||
HttpRemotePeerProvider.class).in(RequestScoped.class);
|
HttpRemotePeerProvider.class).in(RequestScoped.class);
|
||||||
|
|
||||||
|
bind(WebSession.class).in(RequestScoped.class);
|
||||||
|
bind(WebSessionManager.class).in(SINGLETON);
|
||||||
|
|
||||||
bind(CurrentUser.class).toProvider(HttpCurrentUserProvider.class).in(
|
bind(CurrentUser.class).toProvider(HttpCurrentUserProvider.class).in(
|
||||||
RequestScoped.class);
|
RequestScoped.class);
|
||||||
bind(IdentifiedUser.class).toProvider(HttpIdentifiedUserProvider.class).in(
|
bind(IdentifiedUser.class).toProvider(HttpIdentifiedUserProvider.class).in(
|
||||||
|
|||||||
127
src/main/java/com/google/gerrit/server/http/WebSession.java
Normal file
127
src/main/java/com/google/gerrit/server/http/WebSession.java
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -26,7 +26,7 @@ import com.google.gerrit.server.account.AccountException;
|
|||||||
import com.google.gerrit.server.account.AccountManager;
|
import com.google.gerrit.server.account.AccountManager;
|
||||||
import com.google.gerrit.server.config.CanonicalWebUrl;
|
import com.google.gerrit.server.config.CanonicalWebUrl;
|
||||||
import com.google.gerrit.server.config.Nullable;
|
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.gwt.user.client.rpc.AsyncCallback;
|
||||||
import com.google.gwtorm.client.KeyUtil;
|
import com.google.gwtorm.client.KeyUtil;
|
||||||
import com.google.inject.Inject;
|
import com.google.inject.Inject;
|
||||||
@@ -88,7 +88,7 @@ class OpenIdServiceImpl implements OpenIdService {
|
|||||||
private static final String SCHEMA_LASTNAME =
|
private static final String SCHEMA_LASTNAME =
|
||||||
"http://schema.openid.net/namePerson/last";
|
"http://schema.openid.net/namePerson/last";
|
||||||
|
|
||||||
private final Provider<GerritCall> callFactory;
|
private final Provider<WebSession> webSession;
|
||||||
private final Provider<IdentifiedUser> identifiedUser;
|
private final Provider<IdentifiedUser> identifiedUser;
|
||||||
private final Provider<String> urlProvider;
|
private final Provider<String> urlProvider;
|
||||||
private final AccountManager accountManager;
|
private final AccountManager accountManager;
|
||||||
@@ -96,12 +96,12 @@ class OpenIdServiceImpl implements OpenIdService {
|
|||||||
private final SelfPopulatingCache discoveryCache;
|
private final SelfPopulatingCache discoveryCache;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
OpenIdServiceImpl(final Provider<GerritCall> cf,
|
OpenIdServiceImpl(final Provider<WebSession> cf,
|
||||||
final Provider<IdentifiedUser> iu,
|
final Provider<IdentifiedUser> iu,
|
||||||
@CanonicalWebUrl @Nullable final Provider<String> up,
|
@CanonicalWebUrl @Nullable final Provider<String> up,
|
||||||
final CacheManager cacheMgr, final AccountManager am)
|
final CacheManager cacheMgr, final AccountManager am)
|
||||||
throws ConsumerException {
|
throws ConsumerException {
|
||||||
callFactory = cf;
|
webSession = cf;
|
||||||
identifiedUser = iu;
|
identifiedUser = iu;
|
||||||
urlProvider = up;
|
urlProvider = up;
|
||||||
accountManager = am;
|
accountManager = am;
|
||||||
@@ -296,7 +296,7 @@ class OpenIdServiceImpl implements OpenIdService {
|
|||||||
lastId.setMaxAge(0);
|
lastId.setMaxAge(0);
|
||||||
}
|
}
|
||||||
rsp.addCookie(lastId);
|
rsp.addCookie(lastId);
|
||||||
callFactory.get().setAccount(arsp.getAccountId(), remember);
|
webSession.get().login(arsp.getAccountId(), remember);
|
||||||
callback(arsp.isNew(), req, rsp);
|
callback(arsp.isNew(), req, rsp);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
@@ -350,7 +350,7 @@ class OpenIdServiceImpl implements OpenIdService {
|
|||||||
private void cancel(final HttpServletRequest req,
|
private void cancel(final HttpServletRequest req,
|
||||||
final HttpServletResponse rsp) throws IOException {
|
final HttpServletResponse rsp) throws IOException {
|
||||||
if (isSignIn(signInMode(req))) {
|
if (isSignIn(signInMode(req))) {
|
||||||
callFactory.get().logout();
|
webSession.get().logout();
|
||||||
}
|
}
|
||||||
callback(false, req, rsp);
|
callback(false, req, rsp);
|
||||||
}
|
}
|
||||||
@@ -360,7 +360,7 @@ class OpenIdServiceImpl implements OpenIdService {
|
|||||||
throws IOException {
|
throws IOException {
|
||||||
final SignInDialog.Mode mode = signInMode(req);
|
final SignInDialog.Mode mode = signInMode(req);
|
||||||
if (isSignIn(mode)) {
|
if (isSignIn(mode)) {
|
||||||
callFactory.get().logout();
|
webSession.get().logout();
|
||||||
}
|
}
|
||||||
final StringBuilder rdr = new StringBuilder();
|
final StringBuilder rdr = new StringBuilder();
|
||||||
rdr.append(urlProvider.get());
|
rdr.append(urlProvider.get());
|
||||||
|
|||||||
@@ -104,6 +104,17 @@ final class AdminFlushCaches extends CacheCommand {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private boolean flush(final String cacheName) {
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user