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