Convert GitOverHttp and Gitweb to PermissionBackend

GitOverHttpServlet requires the caller has ACCESS permission
for the target project, as ref filtering may happen later.

GitwebServlet requires READ permission as there is no ref
filtering available within the gitweb CGI.

This slightly changes the behavior of GitOverHttpServlet by allowing
a project owner to access a hidden project when using Git over HTTP.

Change-Id: I454f03597bdf5ed8447e73985769c2fe4d478056
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/GitOverHttpServlet.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/GitOverHttpServlet.java
index 7a5956e..a77f660 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/GitOverHttpServlet.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/GitOverHttpServlet.java
@@ -19,6 +19,7 @@
 import com.google.gerrit.common.Nullable;
 import com.google.gerrit.common.data.Capable;
 import com.google.gerrit.extensions.registration.DynamicSet;
+import com.google.gerrit.extensions.restapi.AuthException;
 import com.google.gerrit.reviewdb.client.Project;
 import com.google.gerrit.reviewdb.server.ReviewDb;
 import com.google.gerrit.server.AccessPath;
@@ -34,6 +35,9 @@
 import com.google.gerrit.server.git.VisibleRefFilter;
 import com.google.gerrit.server.git.validators.UploadValidators;
 import com.google.gerrit.server.notedb.ChangeNotes;
+import com.google.gerrit.server.permissions.PermissionBackend;
+import com.google.gerrit.server.permissions.PermissionBackendException;
+import com.google.gerrit.server.permissions.ProjectPermission;
 import com.google.gerrit.server.project.NoSuchProjectException;
 import com.google.gerrit.server.project.ProjectControl;
 import com.google.inject.AbstractModule;
@@ -67,6 +71,7 @@
 import org.eclipse.jgit.transport.PreUploadHook;
 import org.eclipse.jgit.transport.PreUploadHookChain;
 import org.eclipse.jgit.transport.ReceivePack;
+import org.eclipse.jgit.transport.ServiceMayNotContinueException;
 import org.eclipse.jgit.transport.UploadPack;
 import org.eclipse.jgit.transport.resolver.ReceivePackFactory;
 import org.eclipse.jgit.transport.resolver.RepositoryResolver;
@@ -142,18 +147,26 @@
 
   static class Resolver implements RepositoryResolver<HttpServletRequest> {
     private final GitRepositoryManager manager;
-    private final ProjectControl.Factory projectControlFactory;
+    private final PermissionBackend permissionBackend;
+    private final Provider<CurrentUser> userProvider;
+    private final ProjectControl.GenericFactory projectControlFactory;
 
     @Inject
-    Resolver(GitRepositoryManager manager, ProjectControl.Factory projectControlFactory) {
+    Resolver(
+        GitRepositoryManager manager,
+        PermissionBackend permissionBackend,
+        Provider<CurrentUser> userProvider,
+        ProjectControl.GenericFactory projectControlFactory) {
       this.manager = manager;
+      this.permissionBackend = permissionBackend;
+      this.userProvider = userProvider;
       this.projectControlFactory = projectControlFactory;
     }
 
     @Override
     public Repository open(HttpServletRequest req, String projectName)
         throws RepositoryNotFoundException, ServiceNotAuthorizedException,
-            ServiceNotEnabledException {
+            ServiceNotEnabledException, ServiceMayNotContinueException {
       while (projectName.endsWith("/")) {
         projectName = projectName.substring(0, projectName.length() - 1);
       }
@@ -168,28 +181,31 @@
         }
       }
 
-      final ProjectControl pc;
-      try {
-        pc = projectControlFactory.controlFor(new Project.NameKey(projectName));
-      } catch (NoSuchProjectException err) {
-        throw new RepositoryNotFoundException(projectName);
-      }
-
-      CurrentUser user = pc.getUser();
+      CurrentUser user = userProvider.get();
       user.setAccessPath(AccessPath.GIT);
 
-      if (!pc.isVisible()) {
-        if (user instanceof AnonymousUser) {
-          throw new ServiceNotAuthorizedException();
-        }
-        throw new ServiceNotEnabledException();
-      }
-      req.setAttribute(ATT_CONTROL, pc);
-
       try {
-        return manager.openRepository(pc.getProject().getNameKey());
-      } catch (IOException e) {
-        throw new RepositoryNotFoundException(pc.getProject().getNameKey().get(), e);
+        Project.NameKey nameKey = new Project.NameKey(projectName);
+        ProjectControl pc;
+        try {
+          pc = projectControlFactory.controlFor(nameKey, user);
+        } catch (NoSuchProjectException err) {
+          throw new RepositoryNotFoundException(projectName);
+        }
+        req.setAttribute(ATT_CONTROL, pc);
+
+        try {
+          permissionBackend.user(user).project(nameKey).check(ProjectPermission.ACCESS);
+        } catch (AuthException e) {
+          if (user instanceof AnonymousUser) {
+            throw new ServiceNotAuthorizedException();
+          }
+          throw new ServiceNotEnabledException(e.getMessage());
+        }
+
+        return manager.openRepository(nameKey);
+      } catch (IOException | PermissionBackendException err) {
+        throw new ServiceMayNotContinueException(projectName + " unavailable", err);
       }
     }
   }
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/gitweb/GitwebServlet.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/gitweb/GitwebServlet.java
index f3abf2d..abc7fda 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/gitweb/GitwebServlet.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/gitweb/GitwebServlet.java
@@ -33,6 +33,7 @@
 import static java.nio.charset.StandardCharsets.UTF_8;
 
 import com.google.gerrit.common.PageLinks;
+import com.google.gerrit.extensions.restapi.AuthException;
 import com.google.gerrit.extensions.restapi.Url;
 import com.google.gerrit.reviewdb.client.Project;
 import com.google.gerrit.server.AnonymousUser;
@@ -44,8 +45,10 @@
 import com.google.gerrit.server.config.SitePaths;
 import com.google.gerrit.server.git.GitRepositoryManager;
 import com.google.gerrit.server.git.LocalDiskRepositoryManager;
-import com.google.gerrit.server.project.NoSuchProjectException;
-import com.google.gerrit.server.project.ProjectControl;
+import com.google.gerrit.server.permissions.PermissionBackend;
+import com.google.gerrit.server.permissions.PermissionBackendException;
+import com.google.gerrit.server.permissions.ProjectPermission;
+import com.google.gerrit.server.project.ProjectCache;
 import com.google.gerrit.server.ssh.SshInfo;
 import com.google.gwtexpui.server.CacheHeaders;
 import com.google.inject.Inject;
@@ -92,7 +95,8 @@
   private final Path gitwebCgi;
   private final URI gitwebUrl;
   private final LocalDiskRepositoryManager repoManager;
-  private final ProjectControl.Factory projectControl;
+  private final ProjectCache projectCache;
+  private final PermissionBackend permissionBackend;
   private final Provider<AnonymousUser> anonymousUserProvider;
   private final Provider<CurrentUser> userProvider;
   private final EnvList _env;
@@ -100,7 +104,8 @@
   @Inject
   GitwebServlet(
       GitRepositoryManager repoManager,
-      ProjectControl.Factory projectControl,
+      ProjectCache projectCache,
+      PermissionBackend permissionBackend,
       Provider<AnonymousUser> anonymousUserProvider,
       Provider<CurrentUser> userProvider,
       SitePaths site,
@@ -113,7 +118,8 @@
       throw new ProvisionException("Gitweb can only be used with LocalDiskRepositoryManager");
     }
     this.repoManager = (LocalDiskRepositoryManager) repoManager;
-    this.projectControl = projectControl;
+    this.projectCache = projectCache;
+    this.permissionBackend = permissionBackend;
     this.anonymousUserProvider = anonymousUserProvider;
     this.userProvider = userProvider;
     this.gitwebCgi = gitwebCgiConfig.getGitwebCgi();
@@ -402,35 +408,39 @@
       name = name.substring(0, name.length() - 4);
     }
 
-    final Project.NameKey nameKey = new Project.NameKey(name);
-    final ProjectControl project;
+    Project.NameKey nameKey = new Project.NameKey(name);
     try {
-      project = projectControl.validateFor(nameKey);
-      if (!project.allRefsAreVisible() && !project.isOwner()) {
-        // Pretend the project doesn't exist
-        throw new NoSuchProjectException(nameKey);
+      if (projectCache.checkedGet(nameKey) == null) {
+        notFound(req, rsp);
+        return;
       }
-    } catch (NoSuchProjectException e) {
-      if (userProvider.get().isIdentifiedUser()) {
-        rsp.sendError(HttpServletResponse.SC_NOT_FOUND);
-      } else {
-        // Allow anonymous users a chance to login.
-        // Avoid leaking information by not distinguishing between
-        // project not existing and no access rights.
-        rsp.sendRedirect(getLoginRedirectUrl(req));
-      }
+      permissionBackend.user(userProvider).project(nameKey).check(ProjectPermission.READ);
+    } catch (AuthException e) {
+      notFound(req, rsp);
+      return;
+    } catch (IOException | PermissionBackendException err) {
+      log.error("cannot load " + name, err);
+      rsp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
       return;
     }
 
     try (Repository repo = repoManager.openRepository(nameKey)) {
       CacheHeaders.setNotCacheable(rsp);
-      exec(req, rsp, project);
+      exec(req, rsp, nameKey);
     } catch (RepositoryNotFoundException e) {
       getServletContext().log("Cannot open repository", e);
       rsp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
     }
   }
 
+  private void notFound(HttpServletRequest req, HttpServletResponse rsp) throws IOException {
+    if (userProvider.get().isIdentifiedUser()) {
+      rsp.sendError(HttpServletResponse.SC_NOT_FOUND);
+    } else {
+      rsp.sendRedirect(getLoginRedirectUrl(req));
+    }
+  }
+
   private static String getLoginRedirectUrl(HttpServletRequest req) {
     String contextPath = req.getContextPath();
     String loginUrl = contextPath + "/login/";
@@ -462,8 +472,7 @@
     return params;
   }
 
-  private void exec(
-      final HttpServletRequest req, final HttpServletResponse rsp, final ProjectControl project)
+  private void exec(HttpServletRequest req, HttpServletResponse rsp, Project.NameKey project)
       throws IOException {
     final Process proc =
         Runtime.getRuntime()
@@ -512,7 +521,7 @@
     }
   }
 
-  private String[] makeEnv(final HttpServletRequest req, final ProjectControl project) {
+  private String[] makeEnv(HttpServletRequest req, Project.NameKey nameKey) {
     final EnvList env = new EnvList(_env);
     final int contentLength = Math.max(0, req.getContentLength());
 
@@ -551,20 +560,21 @@
     }
 
     env.set("GERRIT_CONTEXT_PATH", req.getContextPath() + "/");
-    env.set("GERRIT_PROJECT_NAME", project.getProject().getName());
+    env.set("GERRIT_PROJECT_NAME", nameKey.get());
 
-    env.set(
-        "GITWEB_PROJECTROOT",
-        repoManager.getBasePath(project.getProject().getNameKey()).toAbsolutePath().toString());
+    env.set("GITWEB_PROJECTROOT", repoManager.getBasePath(nameKey).toAbsolutePath().toString());
 
-    if (project.forUser(anonymousUserProvider.get()).isVisible()) {
+    if (permissionBackend
+        .user(anonymousUserProvider)
+        .project(nameKey)
+        .testOrFalse(ProjectPermission.READ)) {
       env.set("GERRIT_ANONYMOUS_READ", "1");
     }
 
     String remoteUser = null;
-    if (project.getUser().isIdentifiedUser()) {
-      final IdentifiedUser u = project.getUser().asIdentifiedUser();
-      final String user = u.getUserName();
+    if (userProvider.get().isIdentifiedUser()) {
+      IdentifiedUser u = userProvider.get().asIdentifiedUser();
+      String user = u.getUserName();
       env.set("GERRIT_USER_NAME", user);
       if (user != null && !user.isEmpty()) {
         remoteUser = user;