Merge "ChangeField: Remove duplicate calls to stored() for int range fields"
diff --git a/java/com/google/gerrit/httpd/GitOverHttpServlet.java b/java/com/google/gerrit/httpd/GitOverHttpServlet.java
index 05c1d58..739726e 100644
--- a/java/com/google/gerrit/httpd/GitOverHttpServlet.java
+++ b/java/com/google/gerrit/httpd/GitOverHttpServlet.java
@@ -184,7 +184,7 @@
       try {
         Project.NameKey nameKey = new Project.NameKey(projectName);
         ProjectState state = projectCache.checkedGet(nameKey);
-        if (state == null) {
+        if (state == null || !state.statePermitsRead()) {
           throw new RepositoryNotFoundException(nameKey.get());
         }
         req.setAttribute(ATT_STATE, state);
diff --git a/java/com/google/gerrit/httpd/rpc/project/ProjectAccessFactory.java b/java/com/google/gerrit/httpd/rpc/project/ProjectAccessFactory.java
index c51fb9b..f117b24 100644
--- a/java/com/google/gerrit/httpd/rpc/project/ProjectAccessFactory.java
+++ b/java/com/google/gerrit/httpd/rpc/project/ProjectAccessFactory.java
@@ -259,8 +259,14 @@
       throws NoSuchProjectException, IOException, PermissionBackendException,
           ResourceConflictException {
     ProjectState state = projectCache.checkedGet(projectName);
+    // Hidden projects(permitsRead = false) should only be accessible by the project owners.
+    // READ_CONFIG is checked here because it's only allowed to project owners(ACCESS may also
+    // be allowed for other users). Allowing project owners to access here will help them to view
+    // and update the config of hidden projects easily.
+    ProjectPermission permissionToCheck =
+        state.statePermitsRead() ? ProjectPermission.ACCESS : ProjectPermission.READ_CONFIG;
     try {
-      permissionBackend.currentUser().project(projectName).check(ProjectPermission.ACCESS);
+      permissionBackend.currentUser().project(projectName).check(permissionToCheck);
     } catch (AuthException e) {
       throw new NoSuchProjectException(projectName);
     }
diff --git a/java/com/google/gerrit/server/args4j/ProjectHandler.java b/java/com/google/gerrit/server/args4j/ProjectHandler.java
index e97c171..1d40b53 100644
--- a/java/com/google/gerrit/server/args4j/ProjectHandler.java
+++ b/java/com/google/gerrit/server/args4j/ProjectHandler.java
@@ -79,7 +79,13 @@
       if (state == null) {
         throw new CmdLineException(owner, String.format("project %s not found", nameWithoutSuffix));
       }
-      permissionBackend.currentUser().project(nameKey).check(ProjectPermission.ACCESS);
+      // Hidden projects(permitsRead = false) should only be accessible by the project owners.
+      // READ_CONFIG is checked here because it's only allowed to project owners(ACCESS may also
+      // be allowed for other users). Allowing project owners to access here will help them to view
+      // and update the config of hidden projects easily.
+      ProjectPermission permissionToCheck =
+          state.statePermitsRead() ? ProjectPermission.ACCESS : ProjectPermission.READ_CONFIG;
+      permissionBackend.currentUser().project(nameKey).check(permissionToCheck);
     } catch (AuthException e) {
       throw new CmdLineException(owner, new NoSuchProjectException(nameKey).getMessage());
     } catch (PermissionBackendException | IOException e) {
diff --git a/java/com/google/gerrit/server/events/EventBroker.java b/java/com/google/gerrit/server/events/EventBroker.java
index 8f12cb3..62e8d12 100644
--- a/java/com/google/gerrit/server/events/EventBroker.java
+++ b/java/com/google/gerrit/server/events/EventBroker.java
@@ -150,6 +150,11 @@
 
   protected boolean isVisibleTo(Project.NameKey project, CurrentUser user) {
     try {
+      ProjectState state = projectCache.get(project);
+      if (state == null || !state.statePermitsRead()) {
+        return false;
+      }
+
       permissionBackend.user(user).project(project).check(ProjectPermission.ACCESS);
       return true;
     } catch (AuthException | PermissionBackendException e) {
diff --git a/java/com/google/gerrit/server/permissions/ProjectControl.java b/java/com/google/gerrit/server/permissions/ProjectControl.java
index 30ed180..dbd60ea 100644
--- a/java/com/google/gerrit/server/permissions/ProjectControl.java
+++ b/java/com/google/gerrit/server/permissions/ProjectControl.java
@@ -202,11 +202,6 @@
     return false;
   }
 
-  /** Returns whether the project is hidden. */
-  private boolean isHidden() {
-    return getProject().getState().equals(com.google.gerrit.extensions.client.ProjectState.HIDDEN);
-  }
-
   private boolean canAddRefs() {
     return (canPerformOnAnyRef(Permission.CREATE) || isAdmin());
   }
@@ -400,8 +395,7 @@
     private boolean can(ProjectPermission perm) throws PermissionBackendException {
       switch (perm) {
         case ACCESS:
-          return (!isHidden() && (user.isInternalUser() || canPerformOnAnyRef(Permission.READ)))
-              || isOwner();
+          return user.isInternalUser() || isOwner() || canPerformOnAnyRef(Permission.READ);
 
         case READ:
           return allRefsAreVisible(Collections.emptySet());
diff --git a/java/com/google/gerrit/server/project/ChildProjects.java b/java/com/google/gerrit/server/project/ChildProjects.java
index a29a152..868d0af 100644
--- a/java/com/google/gerrit/server/project/ChildProjects.java
+++ b/java/com/google/gerrit/server/project/ChildProjects.java
@@ -53,7 +53,7 @@
 
   /** Gets all child projects recursively. */
   public List<ProjectInfo> list(Project.NameKey parent) throws PermissionBackendException {
-    Map<Project.NameKey, Project> projects = readAllProjects();
+    Map<Project.NameKey, Project> projects = readAllReadableProjects();
     Multimap<Project.NameKey, Project.NameKey> children = parentToChildren(projects);
     PermissionBackend.WithUser perm = permissionBackend.currentUser();
 
@@ -62,11 +62,11 @@
     return results;
   }
 
-  private Map<Project.NameKey, Project> readAllProjects() {
+  private Map<Project.NameKey, Project> readAllReadableProjects() {
     Map<Project.NameKey, Project> projects = new HashMap<>();
     for (Project.NameKey name : projectCache.all()) {
       ProjectState c = projectCache.get(name);
-      if (c != null) {
+      if (c != null && c.statePermitsRead()) {
         projects.put(c.getNameKey(), c.getProject());
       }
     }
diff --git a/java/com/google/gerrit/server/project/RefUtil.java b/java/com/google/gerrit/server/project/RefUtil.java
index 62e48be..e42a7df 100644
--- a/java/com/google/gerrit/server/project/RefUtil.java
+++ b/java/com/google/gerrit/server/project/RefUtil.java
@@ -39,6 +39,8 @@
 public class RefUtil {
   private static final Logger log = LoggerFactory.getLogger(RefUtil.class);
 
+  private RefUtil() {}
+
   public static ObjectId parseBaseRevision(
       Repository repo, Project.NameKey projectName, String baseRevision)
       throws InvalidRevisionException {
diff --git a/java/com/google/gerrit/server/project/SuggestParentCandidates.java b/java/com/google/gerrit/server/project/SuggestParentCandidates.java
index 014029b..99833af 100644
--- a/java/com/google/gerrit/server/project/SuggestParentCandidates.java
+++ b/java/com/google/gerrit/server/project/SuggestParentCandidates.java
@@ -44,17 +44,17 @@
   public List<Project.NameKey> getNameKeys() throws PermissionBackendException {
     return permissionBackend
         .currentUser()
-        .filter(ProjectPermission.ACCESS, parents())
+        .filter(ProjectPermission.ACCESS, readableParents())
         .stream()
         .sorted()
         .collect(toList());
   }
 
-  private Set<Project.NameKey> parents() {
+  private Set<Project.NameKey> readableParents() {
     Set<Project.NameKey> parents = new HashSet<>();
     for (Project.NameKey p : projectCache.all()) {
       ProjectState ps = projectCache.get(p);
-      if (ps != null) {
+      if (ps != null && ps.statePermitsRead()) {
         Project.NameKey parent = ps.getProject().getParent();
         if (parent != null) {
           parents.add(parent);
diff --git a/java/com/google/gerrit/server/query/account/InternalAccountQuery.java b/java/com/google/gerrit/server/query/account/InternalAccountQuery.java
index f1be580..02386ae 100644
--- a/java/com/google/gerrit/server/query/account/InternalAccountQuery.java
+++ b/java/com/google/gerrit/server/query/account/InternalAccountQuery.java
@@ -17,11 +17,9 @@
 import static java.util.stream.Collectors.toList;
 import static java.util.stream.Collectors.toSet;
 
-import com.google.common.base.Joiner;
 import com.google.common.collect.ArrayListMultimap;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableListMultimap;
-import com.google.common.collect.Lists;
 import com.google.common.collect.Multimap;
 import com.google.gerrit.index.FieldDef;
 import com.google.gerrit.index.IndexConfig;
@@ -37,8 +35,6 @@
 import java.util.Arrays;
 import java.util.List;
 import java.util.Set;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
 
 /**
  * Query wrapper for the account index.
@@ -47,8 +43,6 @@
  * holding on to a single instance.
  */
 public class InternalAccountQuery extends InternalQuery<AccountState> {
-  private static final Logger log = LoggerFactory.getLogger(InternalAccountQuery.class);
-
   @Inject
   InternalAccountQuery(
       AccountQueryProcessor queryProcessor,
@@ -94,28 +88,6 @@
     return query(AccountPredicates.externalIdIncludingSecondaryEmails(externalId.toString()));
   }
 
-  public AccountState oneByExternalId(String externalId) throws OrmException {
-    return oneByExternalId(ExternalId.Key.parse(externalId));
-  }
-
-  public AccountState oneByExternalId(String scheme, String id) throws OrmException {
-    return oneByExternalId(ExternalId.Key.create(scheme, id));
-  }
-
-  public AccountState oneByExternalId(ExternalId.Key externalId) throws OrmException {
-    List<AccountState> accountStates = byExternalId(externalId);
-    if (accountStates.size() == 1) {
-      return accountStates.get(0);
-    } else if (accountStates.size() > 0) {
-      StringBuilder msg = new StringBuilder();
-      msg.append("Ambiguous external ID ").append(externalId).append(" for accounts: ");
-      Joiner.on(", ")
-          .appendTo(msg, Lists.transform(accountStates, AccountState.ACCOUNT_ID_FUNCTION));
-      log.warn(msg.toString());
-    }
-    return null;
-  }
-
   public List<AccountState> byFullName(String fullName) throws OrmException {
     return query(AccountPredicates.fullName(fullName));
   }
diff --git a/java/com/google/gerrit/server/restapi/config/ListTasks.java b/java/com/google/gerrit/server/restapi/config/ListTasks.java
index 71ee5ad..fb2819c 100644
--- a/java/com/google/gerrit/server/restapi/config/ListTasks.java
+++ b/java/com/google/gerrit/server/restapi/config/ListTasks.java
@@ -28,6 +28,8 @@
 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.project.ProjectState;
 import com.google.gerrit.server.util.IdGenerator;
 import com.google.inject.Inject;
 import com.google.inject.Provider;
@@ -46,13 +48,18 @@
   private final PermissionBackend permissionBackend;
   private final WorkQueue workQueue;
   private final Provider<CurrentUser> self;
+  private final ProjectCache projectCache;
 
   @Inject
   public ListTasks(
-      PermissionBackend permissionBackend, WorkQueue workQueue, Provider<CurrentUser> self) {
+      PermissionBackend permissionBackend,
+      WorkQueue workQueue,
+      Provider<CurrentUser> self,
+      ProjectCache projectCache) {
     this.permissionBackend = permissionBackend;
     this.workQueue = workQueue;
     this.self = self;
+    this.projectCache = projectCache;
   }
 
   @Override
@@ -77,14 +84,17 @@
       if (task.projectName != null) {
         Boolean visible = visibilityCache.get(task.projectName);
         if (visible == null) {
-          try {
-            permissionBackend
-                .user(user)
-                .project(new Project.NameKey(task.projectName))
-                .check(ProjectPermission.ACCESS);
-            visible = true;
-          } catch (AuthException e) {
+          Project.NameKey nameKey = new Project.NameKey(task.projectName);
+          ProjectState state = projectCache.get(nameKey);
+          if (state == null || !state.statePermitsRead()) {
             visible = false;
+          } else {
+            try {
+              permissionBackend.user(user).project(nameKey).check(ProjectPermission.ACCESS);
+              visible = true;
+            } catch (AuthException e) {
+              visible = false;
+            }
           }
           visibilityCache.put(task.projectName, visible);
         }
diff --git a/java/com/google/gerrit/server/restapi/config/TasksCollection.java b/java/com/google/gerrit/server/restapi/config/TasksCollection.java
index f5b6e56..dda54a0 100644
--- a/java/com/google/gerrit/server/restapi/config/TasksCollection.java
+++ b/java/com/google/gerrit/server/restapi/config/TasksCollection.java
@@ -18,8 +18,10 @@
 import com.google.gerrit.extensions.restapi.AuthException;
 import com.google.gerrit.extensions.restapi.ChildCollection;
 import com.google.gerrit.extensions.restapi.IdString;
+import com.google.gerrit.extensions.restapi.ResourceConflictException;
 import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
 import com.google.gerrit.extensions.restapi.RestView;
+import com.google.gerrit.reviewdb.client.Project;
 import com.google.gerrit.server.CurrentUser;
 import com.google.gerrit.server.config.ConfigResource;
 import com.google.gerrit.server.config.TaskResource;
@@ -30,6 +32,8 @@
 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.project.ProjectState;
 import com.google.inject.Inject;
 import com.google.inject.Provider;
 import com.google.inject.Singleton;
@@ -41,6 +45,7 @@
   private final WorkQueue workQueue;
   private final Provider<CurrentUser> self;
   private final PermissionBackend permissionBackend;
+  private final ProjectCache projectCache;
 
   @Inject
   TasksCollection(
@@ -48,12 +53,14 @@
       ListTasks list,
       WorkQueue workQueue,
       Provider<CurrentUser> self,
-      PermissionBackend permissionBackend) {
+      PermissionBackend permissionBackend,
+      ProjectCache projectCache) {
     this.views = views;
     this.list = list;
     this.workQueue = workQueue;
     this.self = self;
     this.permissionBackend = permissionBackend;
+    this.projectCache = projectCache;
   }
 
   @Override
@@ -63,7 +70,8 @@
 
   @Override
   public TaskResource parse(ConfigResource parent, IdString id)
-      throws ResourceNotFoundException, AuthException, PermissionBackendException {
+      throws ResourceNotFoundException, AuthException, PermissionBackendException,
+          ResourceConflictException {
     CurrentUser user = self.get();
     if (!user.isIdentifiedUser()) {
       throw new AuthException("Authentication required");
@@ -78,11 +86,16 @@
 
     Task<?> task = workQueue.getTask(taskId);
     if (task instanceof ProjectTask) {
+      Project.NameKey nameKey = ((ProjectTask<?>) task).getProjectNameKey();
+      ProjectState state = projectCache.get(nameKey);
+      if (state == null) {
+        throw new ResourceNotFoundException(String.format("project %s not found", nameKey));
+      }
+
+      state.checkStatePermitsRead();
+
       try {
-        permissionBackend
-            .user(user)
-            .project(((ProjectTask<?>) task).getProjectNameKey())
-            .check(ProjectPermission.ACCESS);
+        permissionBackend.user(user).project(nameKey).check(ProjectPermission.ACCESS);
         return new TaskResource(task);
       } catch (AuthException e) {
         // Fall through and try view queue permission.
diff --git a/java/com/google/gerrit/server/restapi/project/ListChildProjects.java b/java/com/google/gerrit/server/restapi/project/ListChildProjects.java
index 7db4f84..3067c89 100644
--- a/java/com/google/gerrit/server/restapi/project/ListChildProjects.java
+++ b/java/com/google/gerrit/server/restapi/project/ListChildProjects.java
@@ -80,7 +80,9 @@
     Map<Project.NameKey, Project> children = new HashMap<>();
     for (Project.NameKey name : projectCache.all()) {
       ProjectState c = projectCache.get(name);
-      if (c != null && parent.equals(c.getProject().getParent(allProjects))) {
+      if (c != null
+          && parent.equals(c.getProject().getParent(allProjects))
+          && c.statePermitsRead()) {
         children.put(c.getNameKey(), c.getProject());
       }
     }
diff --git a/java/com/google/gerrit/server/restapi/project/ListDashboards.java b/java/com/google/gerrit/server/restapi/project/ListDashboards.java
index 08b4069..882e922 100644
--- a/java/com/google/gerrit/server/restapi/project/ListDashboards.java
+++ b/java/com/google/gerrit/server/restapi/project/ListDashboards.java
@@ -88,8 +88,11 @@
   private Collection<ProjectState> tree(ProjectResource rsrc) throws PermissionBackendException {
     Map<Project.NameKey, ProjectState> tree = new LinkedHashMap<>();
     for (ProjectState ps : rsrc.getProjectState().tree()) {
-      tree.put(ps.getNameKey(), ps);
+      if (ps.statePermitsRead()) {
+        tree.put(ps.getNameKey(), ps);
+      }
     }
+
     tree.keySet()
         .retainAll(permissionBackend.currentUser().filter(ProjectPermission.ACCESS, tree.keySet()));
     return tree.values();
diff --git a/java/com/google/gerrit/server/restapi/project/ListProjects.java b/java/com/google/gerrit/server/restapi/project/ListProjects.java
index a1572c6..9a8232e 100644
--- a/java/com/google/gerrit/server/restapi/project/ListProjects.java
+++ b/java/com/google/gerrit/server/restapi/project/ListProjects.java
@@ -14,6 +14,7 @@
 
 package com.google.gerrit.server.restapi.project;
 
+import static com.google.common.base.Preconditions.checkNotNull;
 import static com.google.gerrit.extensions.client.ProjectState.HIDDEN;
 import static java.nio.charset.StandardCharsets.UTF_8;
 import static java.util.stream.Collectors.toList;
@@ -521,11 +522,28 @@
     if (type == FilterType.PARENT_CANDIDATES) {
       matches = parentsOf(matches);
     }
-    // TODO(dborowitz): Streamified PermissionBackend#filter.
-    return perm.filter(ProjectPermission.ACCESS, matches.collect(toList()))
-        .stream()
-        .sorted()
-        .collect(toList());
+
+    List<Project.NameKey> results = new ArrayList<>();
+    List<Project.NameKey> projectNameKeys = matches.sorted().collect(toList());
+    for (Project.NameKey nameKey : projectNameKeys) {
+      ProjectState state = projectCache.get(nameKey);
+      checkNotNull(state, "Failed to load project %s", nameKey);
+
+      // Hidden projects(permitsRead = false) should only be accessible by the project owners.
+      // READ_CONFIG is checked here because it's only allowed to project owners(ACCESS may also
+      // be allowed for other users). Allowing project owners to access here will help them to view
+      // and update the config of hidden projects easily.
+      ProjectPermission permissionToCheck =
+          state.statePermitsRead() ? ProjectPermission.ACCESS : ProjectPermission.READ_CONFIG;
+      try {
+        perm.project(nameKey).check(permissionToCheck);
+        results.add(nameKey);
+      } catch (AuthException e) {
+        // Not added to results.
+      }
+    }
+
+    return results;
   }
 
   private Stream<Project.NameKey> parentsOf(Stream<Project.NameKey> matches) {
@@ -551,13 +569,19 @@
   }
 
   private boolean isParentAccessible(
-      Map<Project.NameKey, Boolean> checked, PermissionBackend.WithUser perm, ProjectState p)
+      Map<Project.NameKey, Boolean> checked, PermissionBackend.WithUser perm, ProjectState state)
       throws PermissionBackendException {
-    Project.NameKey name = p.getNameKey();
+    Project.NameKey name = state.getNameKey();
     Boolean b = checked.get(name);
     if (b == null) {
       try {
-        perm.project(name).check(ProjectPermission.ACCESS);
+        // Hidden projects(permitsRead = false) should only be accessible by the project owners.
+        // READ_CONFIG is checked here because it's only allowed to project owners(ACCESS may also
+        // be allowed for other users). Allowing project owners to access here will help them to view
+        // and update the config of hidden projects easily.
+        ProjectPermission permissionToCheck =
+            state.statePermitsRead() ? ProjectPermission.ACCESS : ProjectPermission.READ_CONFIG;
+        perm.project(name).check(permissionToCheck);
         b = true;
       } catch (AuthException denied) {
         b = false;
diff --git a/java/com/google/gerrit/server/restapi/project/ProjectsCollection.java b/java/com/google/gerrit/server/restapi/project/ProjectsCollection.java
index c5fb41e..3af8424 100644
--- a/java/com/google/gerrit/server/restapi/project/ProjectsCollection.java
+++ b/java/com/google/gerrit/server/restapi/project/ProjectsCollection.java
@@ -150,8 +150,14 @@
     }
 
     if (checkAccess) {
+      // Hidden projects(permitsRead = false) should only be accessible by the project owners.
+      // READ_CONFIG is checked here because it's only allowed to project owners(ACCESS may also
+      // be allowed for other users). Allowing project owners to access here will help them to view
+      // and update the config of hidden projects easily.
+      ProjectPermission permissionToCheck =
+          state.statePermitsRead() ? ProjectPermission.ACCESS : ProjectPermission.READ_CONFIG;
       try {
-        permissionBackend.currentUser().project(nameKey).check(ProjectPermission.ACCESS);
+        permissionBackend.currentUser().project(nameKey).check(permissionToCheck);
       } catch (AuthException e) {
         return null; // Pretend like not found on access denied.
       }
diff --git a/java/com/google/gerrit/sshd/commands/KillCommand.java b/java/com/google/gerrit/sshd/commands/KillCommand.java
index a7e751a..ef12f5f 100644
--- a/java/com/google/gerrit/sshd/commands/KillCommand.java
+++ b/java/com/google/gerrit/sshd/commands/KillCommand.java
@@ -20,6 +20,7 @@
 import com.google.gerrit.extensions.annotations.RequiresAnyCapability;
 import com.google.gerrit.extensions.restapi.AuthException;
 import com.google.gerrit.extensions.restapi.IdString;
+import com.google.gerrit.extensions.restapi.ResourceConflictException;
 import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
 import com.google.gerrit.server.config.ConfigResource;
 import com.google.gerrit.server.config.TaskResource;
@@ -51,7 +52,10 @@
       try {
         TaskResource taskRsrc = tasksCollection.parse(cfgRsrc, IdString.fromDecoded(id));
         deleteTask.apply(taskRsrc, null);
-      } catch (AuthException | ResourceNotFoundException | PermissionBackendException e) {
+      } catch (AuthException
+          | ResourceNotFoundException
+          | ResourceConflictException
+          | PermissionBackendException e) {
         stderr.print("kill: " + id + ": No such task\n");
       }
     }
diff --git a/plugins/replication b/plugins/replication
index dc1e7ff..9b08f32 160000
--- a/plugins/replication
+++ b/plugins/replication
@@ -1 +1 @@
-Subproject commit dc1e7ff434fd7fce90dd6f6c4e312280bd884bd0
+Subproject commit 9b08f324462e62872ac071175e16e7553ba76e2b