Redirect to login if not authenticated

If a user isn't authenticated, redirect to login akin to the Gitweb
integration.  This avoids leaking information about repository
existence, but gives users a chance to log in if they have not yet.
This is especially useful as sessions expire: if the first page
viewed is Gitiles, they would receive a "Not Found" page instead of
creating a new session.

Change-Id: If06ec2a0ed85533b81a0e70d3545ee5129c7e3a6
diff --git a/src/main/java/com/googlesource/gerrit/plugins/gitiles/HttpModule.java b/src/main/java/com/googlesource/gerrit/plugins/gitiles/HttpModule.java
index 9913999..a6bfbbd 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/gitiles/HttpModule.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/gitiles/HttpModule.java
@@ -15,10 +15,13 @@
 package com.googlesource.gerrit.plugins.gitiles;
 
 import com.google.gerrit.server.config.SitePaths;
+import com.google.gerrit.server.CurrentUser;
 import com.google.gitiles.GitilesAccess;
 import com.google.gitiles.GitilesServlet;
 import com.google.gitiles.GitilesUrls;
 import com.google.gitiles.GitilesView;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
 import com.google.inject.Provides;
 import com.google.inject.Singleton;
 import com.google.inject.name.Named;
@@ -47,6 +50,15 @@
 import javax.servlet.http.HttpServletRequestWrapper;
 
 class HttpModule extends ServletModule {
+  private final Provider<CurrentUser> userProvider;
+  private final GitilesUrls urls;
+
+  @Inject
+  HttpModule(Provider<CurrentUser> userProvider, GitilesUrls urls) {
+    this.userProvider = userProvider;
+    this.urls = urls;
+  }
+
   protected Filter createPathFilter() {
     return new Filter() {
       @Override
@@ -84,6 +96,7 @@
   protected void configureServlets() {
     // Filter all paths so we can decode escaped entities in the URI
     filter("/*").through(createPathFilter());
+    filter("/*").through(new LoginFilter(userProvider, urls));
     // Let /+static, /+Documentation, etc. fall through to default servlet, but
     // handle everything else.
     serveRegex("^(/)$", "^(/[^+].*)").with(GitilesServlet.class);
diff --git a/src/main/java/com/googlesource/gerrit/plugins/gitiles/LoginFilter.java b/src/main/java/com/googlesource/gerrit/plugins/gitiles/LoginFilter.java
new file mode 100644
index 0000000..606cf2f
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/gitiles/LoginFilter.java
@@ -0,0 +1,97 @@
+// Copyright (C) 2015 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.googlesource.gerrit.plugins.gitiles;
+
+import com.google.gerrit.extensions.restapi.Url;
+import com.google.gerrit.server.CurrentUser;
+import com.google.gerrit.server.IdentifiedUser;
+import com.google.gitiles.BaseServlet;
+import com.google.gitiles.GitilesUrls;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+
+import java.io.IOException;
+
+import javax.servlet.Filter;
+import javax.servlet.FilterChain;
+import javax.servlet.FilterConfig;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.servlet.http.HttpServletResponseWrapper;
+
+class LoginFilter implements Filter {
+  private final Provider<CurrentUser> userProvider;
+  private final GitilesUrls urls;
+
+  @Inject
+  LoginFilter(Provider<CurrentUser> userProvider, GitilesUrls urls) {
+    this.userProvider = userProvider;
+    this.urls = urls;
+  }
+
+  @Override
+  public void doFilter(ServletRequest request, ServletResponse response,
+      FilterChain chain) throws IOException, ServletException {
+    final HttpServletRequest req = (HttpServletRequest) request;
+    HttpServletResponseWrapper rsp = new HttpServletResponseWrapper((HttpServletResponse) response) {
+      @Override
+      public void sendError(int sc) throws IOException {
+        CurrentUser user = userProvider.get();
+        if (sc == SC_UNAUTHORIZED && !(user instanceof IdentifiedUser)) {
+          sendRedirect(getLoginRedirectUrl((HttpServletRequest) req));
+          return;
+        }
+        super.sendError(sc);
+      }
+
+      @Override
+      public void sendError(int sc, String msg) throws IOException {
+        CurrentUser user = userProvider.get();
+        if (sc == SC_UNAUTHORIZED && !(user instanceof IdentifiedUser)) {
+          sendRedirect(getLoginRedirectUrl((HttpServletRequest) req));
+          return;
+        }
+        super.sendError(sc, msg);
+      }
+    };
+    chain.doFilter(request, rsp);
+  }
+
+  @Override
+  public void init(FilterConfig filterConfig) throws ServletException {
+  }
+
+  @Override
+  public void destroy() {
+  }
+
+  private String getLoginRedirectUrl(HttpServletRequest req) {
+    String baseUrl = urls.getBaseGerritUrl(req);
+    String loginUrl = baseUrl + "login/";
+    String token = req.getRequestURL().toString();
+    if (!baseUrl.isEmpty()) {
+      token = token.substring(baseUrl.length());
+    }
+
+    String queryString = req.getQueryString();
+    if (queryString != null && !queryString.isEmpty()) {
+      token = token.concat("?" + queryString);
+    }
+    return (loginUrl + Url.encode(token));
+  }
+}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/gitiles/Resolver.java b/src/main/java/com/googlesource/gerrit/plugins/gitiles/Resolver.java
index 26999c0..63db101 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/gitiles/Resolver.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/gitiles/Resolver.java
@@ -17,13 +17,16 @@
 import static com.google.common.base.Preconditions.checkState;
 
 import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.server.CurrentUser;
 import com.google.gerrit.server.project.NoSuchProjectException;
 import com.google.inject.Inject;
+import com.google.inject.Provider;
 
 import org.eclipse.jgit.errors.RepositoryNotFoundException;
 import org.eclipse.jgit.lib.Repository;
 import org.eclipse.jgit.transport.ServiceMayNotContinueException;
 import org.eclipse.jgit.transport.resolver.RepositoryResolver;
+import org.eclipse.jgit.transport.resolver.ServiceNotAuthorizedException;
 
 import java.io.IOException;
 
@@ -32,6 +35,7 @@
 class Resolver implements RepositoryResolver<HttpServletRequest> {
   private static final String NAME_KEY_ATTRIBUTE = Resolver.class.getName()
       + "/NameKey";
+  private final Provider<CurrentUser> userProvider;
 
   static Project.NameKey getNameKey(HttpServletRequest req) {
     return (Project.NameKey) req.getAttribute(NAME_KEY_ATTRIBUTE);
@@ -40,13 +44,16 @@
   private final FilteredRepository.Factory repoFactory;
 
   @Inject
-  Resolver(FilteredRepository.Factory repoFactory) {
+  Resolver(FilteredRepository.Factory repoFactory,
+      Provider<CurrentUser> userProvider) {
     this.repoFactory = repoFactory;
+    this.userProvider = userProvider;
   }
 
   @Override
   public Repository open(HttpServletRequest req, String name)
-      throws RepositoryNotFoundException, ServiceMayNotContinueException {
+      throws RepositoryNotFoundException, ServiceMayNotContinueException,
+      ServiceNotAuthorizedException {
     Project.NameKey oldName = getNameKey(req);
     checkState(oldName == null, "Resolved multiple repositories on %s: %s, %s",
         req.getRequestURL(), oldName, name);
@@ -55,7 +62,14 @@
     try {
       return repoFactory.create(nameKey);
     } catch (NoSuchProjectException e) {
-      throw new RepositoryNotFoundException(name, e);
+      if (userProvider.get().isIdentifiedUser()) {
+        throw new RepositoryNotFoundException(name, e);
+      } else {
+        // Allow anonymous users a chance to login.
+        // Avoid leaking information by not distinguishing between
+        // project not existing and no access rights.
+        throw new ServiceNotAuthorizedException();
+      }
     } catch (IOException e) {
       ServiceMayNotContinueException err =
           new ServiceMayNotContinueException("error opening repository " + name);