| // 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.httpd; | |
| import static java.util.concurrent.TimeUnit.HOURS; | |
| import com.google.common.annotations.VisibleForTesting; | |
| import com.google.common.base.Strings; | |
| import com.google.gerrit.common.Nullable; | |
| import com.google.gerrit.common.UsedAt; | |
| import com.google.gerrit.entities.Account; | |
| import com.google.gerrit.extensions.restapi.BadRequestException; | |
| import com.google.gerrit.httpd.restapi.ParameterParser; | |
| import com.google.gerrit.server.AccessPath; | |
| import com.google.gerrit.server.AnonymousUser; | |
| import com.google.gerrit.server.CurrentUser; | |
| import com.google.gerrit.server.IdentifiedUser; | |
| import com.google.gerrit.server.PropertyMap; | |
| import com.google.gerrit.server.account.AccountCache; | |
| import com.google.gerrit.server.account.AuthResult; | |
| import com.google.gerrit.server.account.externalids.ExternalId; | |
| import com.google.gerrit.server.config.AuthConfig; | |
| import com.google.inject.Provider; | |
| import java.util.EnumSet; | |
| import javax.servlet.http.Cookie; | |
| import javax.servlet.http.HttpServletRequest; | |
| import javax.servlet.http.HttpServletResponse; | |
| import org.eclipse.jgit.http.server.GitSmartHttpTools; | |
| public abstract class CacheBasedWebSession extends WebSession { | |
| @VisibleForTesting public static final String ACCOUNT_COOKIE = "GerritAccount"; | |
| @UsedAt(UsedAt.Project.PLUGIN_WEBSESSION_FLATFILE) | |
| public static final long MAX_AGE_MINUTES = HOURS.toMinutes(12); | |
| private final HttpServletRequest request; | |
| private final HttpServletResponse response; | |
| private final WebSessionManager manager; | |
| private final AuthConfig authConfig; | |
| private final Provider<AnonymousUser> anonymousProvider; | |
| private final IdentifiedUser.RequestFactory identified; | |
| private final EnumSet<AccessPath> okPaths = EnumSet.of(AccessPath.UNKNOWN); | |
| private final AccountCache byIdCache; | |
| private Cookie outCookie; | |
| private WebSessionManager.Key key; | |
| private WebSessionManager.Val val; | |
| private CurrentUser user; | |
| protected CacheBasedWebSession( | |
| HttpServletRequest request, | |
| HttpServletResponse response, | |
| WebSessionManager manager, | |
| AuthConfig authConfig, | |
| Provider<AnonymousUser> anonymousProvider, | |
| IdentifiedUser.RequestFactory identified, | |
| AccountCache byIdCache) { | |
| this.request = request; | |
| this.response = response; | |
| this.manager = manager; | |
| this.authConfig = authConfig; | |
| this.anonymousProvider = anonymousProvider; | |
| this.identified = identified; | |
| this.byIdCache = byIdCache; | |
| String cookie = readCookie(request); | |
| if (cookie != null) { | |
| authFromCookie(cookie); | |
| } else if (request.getRequestURI() == null || !GitSmartHttpTools.isGitClient(request)) { | |
| String token; | |
| try { | |
| token = ParameterParser.getQueryParams(request).accessToken(); | |
| } catch (BadRequestException e) { | |
| token = null; | |
| } | |
| if (token != null) { | |
| authFromQueryParameter(token); | |
| } | |
| } | |
| if (val != null && !checkAccountStatus(val.getAccountId())) { | |
| val = null; | |
| okPaths.clear(); | |
| } | |
| if (val != null && val.needsCookieRefresh()) { | |
| // Session is more than half old; update cache entry with new expiration date. | |
| val = manager.createVal(key, val); | |
| } | |
| } | |
| private void authFromCookie(String cookie) { | |
| key = new WebSessionManager.Key(cookie); | |
| val = manager.get(key); | |
| String token = request.getHeader(XsrfConstants.XSRF_HEADER_NAME); | |
| if (val != null && token != null && token.equals(val.getAuth())) { | |
| okPaths.add(AccessPath.REST_API); | |
| } | |
| } | |
| private void authFromQueryParameter(String accessToken) { | |
| key = new WebSessionManager.Key(accessToken); | |
| val = manager.get(key); | |
| if (val != null) { | |
| okPaths.add(AccessPath.REST_API); | |
| } | |
| } | |
| @Nullable | |
| private static String readCookie(HttpServletRequest request) { | |
| Cookie[] all = request.getCookies(); | |
| if (all != null) { | |
| for (Cookie c : all) { | |
| if (ACCOUNT_COOKIE.equals(c.getName())) { | |
| return Strings.emptyToNull(c.getValue()); | |
| } | |
| } | |
| } | |
| return null; | |
| } | |
| @Override | |
| public boolean isSignedIn() { | |
| return val != null; | |
| } | |
| @Override | |
| @Nullable | |
| public String getXGerritAuth() { | |
| return isSignedIn() ? val.getAuth() : null; | |
| } | |
| @Override | |
| public boolean isValidXGerritAuth(String keyIn) { | |
| return keyIn.equals(getXGerritAuth()); | |
| } | |
| @Override | |
| public boolean isAccessPathOk(AccessPath path) { | |
| return okPaths.contains(path); | |
| } | |
| @Override | |
| public void setAccessPathOk(AccessPath path, boolean ok) { | |
| if (ok) { | |
| okPaths.add(path); | |
| } else { | |
| okPaths.remove(path); | |
| } | |
| } | |
| @Override | |
| public CurrentUser getUser() { | |
| if (user == null) { | |
| if (isSignedIn()) { | |
| user = identified.create(val.getAccountId(), getUserProperties(val)); | |
| } else { | |
| user = anonymousProvider.get(); | |
| } | |
| } | |
| return user; | |
| } | |
| private static PropertyMap getUserProperties(@Nullable WebSessionManager.Val val) { | |
| if (val == null || val.getExternalId() == null) { | |
| return PropertyMap.EMPTY; | |
| } | |
| return PropertyMap.builder() | |
| .put(CurrentUser.LAST_LOGIN_EXTERNAL_ID_PROPERTY_KEY, val.getExternalId()) | |
| .build(); | |
| } | |
| @Override | |
| public void login(AuthResult res, boolean rememberMe) { | |
| Account.Id id = res.getAccountId(); | |
| ExternalId.Key identity = res.getExternalId(); | |
| if (val != null) { | |
| manager.destroy(key); | |
| } | |
| if (!checkAccountStatus(id)) { | |
| val = null; | |
| return; | |
| } | |
| key = manager.createKey(id); | |
| val = manager.createVal(key, id, rememberMe, identity, null, null); | |
| saveCookie(); | |
| user = identified.create(val.getAccountId(), getUserProperties(val)); | |
| } | |
| /** Set the user account for this current request only. */ | |
| @Override | |
| public void setUserAccountId(Account.Id id) { | |
| key = new WebSessionManager.Key("id:" + id); | |
| val = new WebSessionManager.Val(id, 0, false, null, 0, null, null); | |
| user = identified.runAs(id, user, PropertyMap.EMPTY); | |
| } | |
| @Override | |
| public void logout() { | |
| if (val != null) { | |
| manager.destroy(key); | |
| key = null; | |
| val = null; | |
| saveCookie(); | |
| user = anonymousProvider.get(); | |
| } | |
| } | |
| @Nullable | |
| @Override | |
| public String getSessionId() { | |
| return val != null ? val.getSessionId() : null; | |
| } | |
| private boolean checkAccountStatus(Account.Id id) { | |
| return byIdCache.get(id).filter(as -> as.account().isActive()).isPresent(); | |
| } | |
| private void saveCookie() { | |
| if (response == null) { | |
| return; | |
| } | |
| final String token; | |
| final int ageSeconds; | |
| if (key == null) { | |
| token = ""; | |
| ageSeconds = 0 /* erase at client */; | |
| } else { | |
| token = key.getToken(); | |
| ageSeconds = manager.getCookieAge(val); | |
| } | |
| String path = authConfig.getCookiePath(); | |
| if (Strings.isNullOrEmpty(path)) { | |
| path = request.getContextPath(); | |
| if (Strings.isNullOrEmpty(path)) { | |
| path = "/"; | |
| } | |
| } | |
| if (outCookie != null) { | |
| throw new IllegalStateException("Cookie " + ACCOUNT_COOKIE + " was set"); | |
| } | |
| outCookie = new Cookie(ACCOUNT_COOKIE, token); | |
| String domain = authConfig.getCookieDomain(); | |
| if (!Strings.isNullOrEmpty(domain)) { | |
| outCookie.setDomain(domain); | |
| } | |
| outCookie.setSecure(isSecure(request)); | |
| outCookie.setPath(path); | |
| outCookie.setMaxAge(ageSeconds); | |
| outCookie.setSecure(authConfig.getCookieSecure()); | |
| response.addCookie(outCookie); | |
| } | |
| private static boolean isSecure(HttpServletRequest req) { | |
| return req.isSecure() || "https".equals(req.getScheme()); | |
| } | |
| } |