// 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.base.Strings;
import com.google.gerrit.common.data.HostPageData;
import com.google.gerrit.httpd.WebSessionManager.Key;
import com.google.gerrit.httpd.WebSessionManager.Val;
import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.reviewdb.client.AccountExternalId;
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.account.AuthResult;
import com.google.gerrit.server.config.AuthConfig;
import com.google.inject.Provider;
import com.google.inject.servlet.RequestScoped;

import org.eclipse.jgit.http.server.GitSmartHttpTools;

import java.util.EnumSet;

import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@RequestScoped
public abstract class CacheBasedWebSession implements WebSession {
  private static final String ACCOUNT_COOKIE = "GerritAccount";
  protected 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 Cookie outCookie;

  private Key key;
  private Val val;
  private CurrentUser user;

  protected CacheBasedWebSession(final HttpServletRequest request,
      final HttpServletResponse response,
      final WebSessionManager manager,
      final AuthConfig authConfig,
      final Provider<AnonymousUser> anonymousProvider,
      final IdentifiedUser.RequestFactory identified) {
    this.request = request;
    this.response = response;
    this.manager = manager;
    this.authConfig = authConfig;
    this.anonymousProvider = anonymousProvider;
    this.identified = identified;

    if (request.getRequestURI() == null
        || !GitSmartHttpTools.isGitClient(request)) {
      String cookie = readCookie();
      if (cookie != null) {
        key = new Key(cookie);
        val = manager.get(key);
        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);
        }

        String token = request.getHeader(HostPageData.XSRF_HEADER_NAME);
        if (val != null && token != null && token.equals(val.getAuth())) {
          okPaths.add(AccessPath.REST_API);
        }
      }
    }
  }

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

  @Override
  public boolean isSignedIn() {
    return val != null;
  }

  @Override
  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 AccountExternalId.Key getLastLoginExternalId() {
    return val != null ? val.getExternalId() : null;
  }

  @Override
  public CurrentUser getUser() {
    if (user == null) {
      if (isSignedIn()) {
        user = identified.create(val.getAccountId());
      } else {
        user = anonymousProvider.get();
      }
    }
    return user;
  }

  @Override
  public void login(final AuthResult res, final boolean rememberMe) {
    final Account.Id id = res.getAccountId();
    final AccountExternalId.Key identity = res.getExternalId();

    if (val != null) {
      manager.destroy(key);
    }

    key = manager.createKey(id);
    val = manager.createVal(key, id, rememberMe, identity, null, null);
    saveCookie();
  }

  /** Set the user account for this current request only. */
  @Override
  public void setUserAccountId(Account.Id id) {
    key = new Key("id:" + id);
    val = new Val(id, 0, false, null, 0, null, null);
    user = identified.runAs(id, user);
  }

  @Override
  public void logout() {
    if (val != null) {
      manager.destroy(key);
      key = null;
      val = null;
      saveCookie();
    }
  }

  @Override
  public String getSessionId() {
    return val != null ? val.getSessionId() : null;
  }

  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(final HttpServletRequest req) {
    return req.isSecure() || "https".equals(req.getScheme());
  }
}

@@ -5056,16 +5056,66 @@
 okie();%0A
+    user = identified.create(val.getAccountId());%0A
   %7D%0A%0A  /
@@ -5501,24 +5501,62 @@
 veCookie();%0A
+      user = anonymousProvider.get();%0A
     %7D%0A  %7D%0A%0A 

Fix CacheBasedWebSession current user on login/logout