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