summaryrefslogtreecommitdiffstats
path: root/gerrit-server/src/main/java/com/google/gerrit/server/project/ListProjects.java
diff options
context:
space:
mode:
Diffstat (limited to 'gerrit-server/src/main/java/com/google/gerrit/server/project/ListProjects.java')
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/project/ListProjects.java278
1 files changed, 171 insertions, 107 deletions
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/ListProjects.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/ListProjects.java
index eeabac8b09..9ed04b0c43 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/ListProjects.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/ListProjects.java
@@ -14,15 +14,18 @@
package com.google.gerrit.server.project;
+import static com.google.gerrit.extensions.client.ProjectState.HIDDEN;
import static java.nio.charset.StandardCharsets.UTF_8;
import com.google.common.base.Strings;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;
+import com.google.gerrit.common.Nullable;
import com.google.gerrit.common.data.GroupReference;
import com.google.gerrit.common.errors.NoSuchGroupException;
import com.google.gerrit.extensions.common.ProjectInfo;
import com.google.gerrit.extensions.common.WebLinkInfo;
+import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.BadRequestException;
import com.google.gerrit.extensions.restapi.BinaryResult;
import com.google.gerrit.extensions.restapi.RestReadView;
@@ -38,6 +41,10 @@ import com.google.gerrit.server.WebLinks;
import com.google.gerrit.server.account.GroupControl;
import com.google.gerrit.server.git.GitRepositoryManager;
import com.google.gerrit.server.group.GroupsCollection;
+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.permissions.RefPermission;
import com.google.gerrit.server.util.RegexListSearcher;
import com.google.gerrit.server.util.TreeFormatter;
import com.google.gson.reflect.TypeToken;
@@ -50,17 +57,19 @@ import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Collections;
import java.util.HashMap;
-import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
-import java.util.Set;
+import java.util.Objects;
import java.util.SortedMap;
import java.util.SortedSet;
import java.util.TreeMap;
import java.util.TreeSet;
+import java.util.stream.Stream;
+import java.util.stream.StreamSupport;
import org.eclipse.jgit.errors.RepositoryNotFoundException;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.Ref;
@@ -79,12 +88,22 @@ public class ListProjects implements RestReadView<TopLevelResource> {
boolean matches(Repository git) throws IOException {
return !PERMISSIONS.matches(git);
}
+
+ @Override
+ boolean useMatch() {
+ return true;
+ }
},
PARENT_CANDIDATES {
@Override
boolean matches(Repository git) {
return true;
}
+
+ @Override
+ boolean useMatch() {
+ return false;
+ }
},
PERMISSIONS {
@Override
@@ -94,15 +113,27 @@ public class ListProjects implements RestReadView<TopLevelResource> {
&& head.isSymbolic()
&& RefNames.REFS_CONFIG.equals(head.getLeaf().getName());
}
+
+ @Override
+ boolean useMatch() {
+ return true;
+ }
},
ALL {
@Override
boolean matches(Repository git) {
return true;
}
+
+ @Override
+ boolean useMatch() {
+ return false;
+ }
};
abstract boolean matches(Repository git) throws IOException;
+
+ abstract boolean useMatch();
}
private final CurrentUser currentUser;
@@ -110,6 +141,7 @@ public class ListProjects implements RestReadView<TopLevelResource> {
private final GroupsCollection groupsCollection;
private final GroupControl.Factory groupControlFactory;
private final GitRepositoryManager repoManager;
+ private final PermissionBackend permissionBackend;
private final ProjectNode.Factory projectNodeFactory;
private final WebLinks webLinks;
@@ -221,6 +253,7 @@ public class ListProjects implements RestReadView<TopLevelResource> {
GroupsCollection groupsCollection,
GroupControl.Factory groupControlFactory,
GitRepositoryManager repoManager,
+ PermissionBackend permissionBackend,
ProjectNode.Factory projectNodeFactory,
WebLinks webLinks) {
this.currentUser = currentUser;
@@ -228,6 +261,7 @@ public class ListProjects implements RestReadView<TopLevelResource> {
this.groupsCollection = groupsCollection;
this.groupControlFactory = groupControlFactory;
this.repoManager = repoManager;
+ this.permissionBackend = permissionBackend;
this.projectNodeFactory = projectNodeFactory;
this.webLinks = webLinks;
}
@@ -254,7 +288,8 @@ public class ListProjects implements RestReadView<TopLevelResource> {
}
@Override
- public Object apply(TopLevelResource resource) throws BadRequestException {
+ public Object apply(TopLevelResource resource)
+ throws BadRequestException, PermissionBackendException {
if (format == OutputFormat.TEXT) {
ByteArrayOutputStream buf = new ByteArrayOutputStream();
display(buf);
@@ -265,141 +300,125 @@ public class ListProjects implements RestReadView<TopLevelResource> {
return apply();
}
- public SortedMap<String, ProjectInfo> apply() throws BadRequestException {
+ public SortedMap<String, ProjectInfo> apply()
+ throws BadRequestException, PermissionBackendException {
format = OutputFormat.JSON;
return display(null);
}
- public SortedMap<String, ProjectInfo> display(OutputStream displayOutputStream)
- throws BadRequestException {
+ public SortedMap<String, ProjectInfo> display(@Nullable OutputStream displayOutputStream)
+ throws BadRequestException, PermissionBackendException {
+ if (groupUuid != null) {
+ try {
+ if (!groupControlFactory.controlFor(groupUuid).isVisible()) {
+ return Collections.emptySortedMap();
+ }
+ } catch (NoSuchGroupException ex) {
+ return Collections.emptySortedMap();
+ }
+ }
+
PrintWriter stdout = null;
if (displayOutputStream != null) {
stdout =
new PrintWriter(new BufferedWriter(new OutputStreamWriter(displayOutputStream, UTF_8)));
}
+ if (type == FilterType.PARENT_CANDIDATES) {
+ // Historically, PARENT_CANDIDATES implied showDescription.
+ showDescription = true;
+ }
+
int foundIndex = 0;
int found = 0;
TreeMap<String, ProjectInfo> output = new TreeMap<>();
Map<String, String> hiddenNames = new HashMap<>();
- Set<String> rejected = new HashSet<>();
-
+ Map<Project.NameKey, Boolean> accessibleParents = new HashMap<>();
+ PermissionBackend.WithUser perm = permissionBackend.user(currentUser);
final TreeMap<Project.NameKey, ProjectNode> treeMap = new TreeMap<>();
try {
- for (final Project.NameKey projectName : scan()) {
+ Iterable<Project.NameKey> projectNames = filter(perm)::iterator;
+ for (Project.NameKey projectName : projectNames) {
final ProjectState e = projectCache.get(projectName);
- if (e == null) {
+ if (e == null || (!all && e.getProject().getState() == HIDDEN)) {
// If we can't get it from the cache, pretend its not present.
- //
+ // If all wasn't selected, and its HIDDEN, pretend its not present.
continue;
}
final ProjectControl pctl = e.controlFor(currentUser);
- if (groupUuid != null) {
- try {
- if (!groupControlFactory.controlFor(groupUuid).isVisible()) {
- break;
- }
- } catch (NoSuchGroupException ex) {
- break;
- }
- if (!pctl.getLocalGroups()
- .contains(GroupReference.forGroup(groupsCollection.parseId(groupUuid.get())))) {
- continue;
- }
+ if (groupUuid != null
+ && !pctl.getProjectState()
+ .getLocalGroups()
+ .contains(GroupReference.forGroup(groupsCollection.parseId(groupUuid.get())))) {
+ continue;
}
ProjectInfo info = new ProjectInfo();
- if (type == FilterType.PARENT_CANDIDATES) {
- ProjectState parentState = Iterables.getFirst(e.parents(), null);
- if (parentState != null
- && !output.keySet().contains(parentState.getProject().getName())
- && !rejected.contains(parentState.getProject().getName())) {
- ProjectControl parentCtrl = parentState.controlFor(currentUser);
- if (parentCtrl.isVisible() || parentCtrl.isOwner()) {
- info.name = parentState.getProject().getName();
- info.description = Strings.emptyToNull(parentState.getProject().getDescription());
- info.state = parentState.getProject().getState();
- } else {
- rejected.add(parentState.getProject().getName());
- continue;
- }
- } else {
- continue;
- }
-
- } else {
- final boolean isVisible = pctl.isVisible() || (all && pctl.isOwner());
- if (showTree && !format.isJson()) {
- treeMap.put(projectName, projectNodeFactory.create(pctl.getProject(), isVisible));
- continue;
- }
-
- if (!isVisible && !(showTree && pctl.isOwner())) {
- // Require the project itself to be visible to the user.
- //
- continue;
- }
+ if (showTree && !format.isJson()) {
+ treeMap.put(projectName, projectNodeFactory.create(pctl.getProject(), true));
+ continue;
+ }
- info.name = projectName.get();
- if (showTree && format.isJson()) {
- ProjectState parent = Iterables.getFirst(e.parents(), null);
- if (parent != null) {
- ProjectControl parentCtrl = parent.controlFor(currentUser);
- if (parentCtrl.isVisible() || parentCtrl.isOwner()) {
- info.parent = parent.getProject().getName();
- } else {
- info.parent = hiddenNames.get(parent.getProject().getName());
- if (info.parent == null) {
- info.parent = "?-" + (hiddenNames.size() + 1);
- hiddenNames.put(parent.getProject().getName(), info.parent);
- }
+ info.name = projectName.get();
+ if (showTree && format.isJson()) {
+ ProjectState parent = Iterables.getFirst(e.parents(), null);
+ if (parent != null) {
+ if (isParentAccessible(accessibleParents, perm, parent)) {
+ info.parent = parent.getName();
+ } else {
+ info.parent = hiddenNames.get(parent.getName());
+ if (info.parent == null) {
+ info.parent = "?-" + (hiddenNames.size() + 1);
+ hiddenNames.put(parent.getName(), info.parent);
}
}
}
- if (showDescription) {
- info.description = Strings.emptyToNull(e.getProject().getDescription());
- }
+ }
- info.state = e.getProject().getState();
+ if (showDescription) {
+ info.description = Strings.emptyToNull(e.getProject().getDescription());
+ }
+ info.state = e.getProject().getState();
- try {
- if (!showBranch.isEmpty()) {
- try (Repository git = repoManager.openRepository(projectName)) {
- if (!type.matches(git)) {
- continue;
- }
+ try {
+ if (!showBranch.isEmpty()) {
+ try (Repository git = repoManager.openRepository(projectName)) {
+ if (!type.matches(git)) {
+ continue;
+ }
- List<Ref> refs = getBranchRefs(projectName, pctl);
- if (!hasValidRef(refs)) {
- continue;
- }
+ List<Ref> refs = getBranchRefs(projectName, pctl);
+ if (!hasValidRef(refs)) {
+ continue;
+ }
- for (int i = 0; i < showBranch.size(); i++) {
- Ref ref = refs.get(i);
- if (ref != null && ref.getObjectId() != null) {
- if (info.branches == null) {
- info.branches = new LinkedHashMap<>();
- }
- info.branches.put(showBranch.get(i), ref.getObjectId().name());
+ for (int i = 0; i < showBranch.size(); i++) {
+ Ref ref = refs.get(i);
+ if (ref != null && ref.getObjectId() != null) {
+ if (info.branches == null) {
+ info.branches = new LinkedHashMap<>();
}
+ info.branches.put(showBranch.get(i), ref.getObjectId().name());
}
}
- } else if (!showTree && type != FilterType.ALL) {
- try (Repository git = repoManager.openRepository(projectName)) {
- if (!type.matches(git)) {
- continue;
- }
+ }
+ } else if (!showTree && type.useMatch()) {
+ try (Repository git = repoManager.openRepository(projectName)) {
+ if (!type.matches(git)) {
+ continue;
}
}
-
- } catch (RepositoryNotFoundException err) {
- // If the Git repository is gone, the project doesn't actually exist anymore.
- continue;
- } catch (IOException err) {
- log.warn("Unexpected error reading " + projectName, err);
- continue;
}
+ } catch (RepositoryNotFoundException err) {
+ // If the Git repository is gone, the project doesn't actually exist anymore.
+ continue;
+ } catch (IOException err) {
+ log.warn("Unexpected error reading " + projectName, err);
+ continue;
+ }
+
+ if (type != FilterType.PARENT_CANDIDATES) {
List<WebLinkInfo> links = webLinks.getProjectLinks(projectName.get());
info.webLinks = links.isEmpty() ? null : links;
}
@@ -407,7 +426,6 @@ public class ListProjects implements RestReadView<TopLevelResource> {
if (foundIndex++ < start) {
continue;
}
-
if (limit > 0 && ++found > limit) {
break;
}
@@ -459,6 +477,47 @@ public class ListProjects implements RestReadView<TopLevelResource> {
}
}
+ private Stream<Project.NameKey> filter(PermissionBackend.WithUser perm)
+ throws BadRequestException {
+ Stream<Project.NameKey> matches = StreamSupport.stream(scan().spliterator(), false);
+ if (type == FilterType.PARENT_CANDIDATES) {
+ matches =
+ matches.map(projectCache::get).map(this::parentOf).filter(Objects::nonNull).sorted();
+ }
+ return matches.filter(p -> perm.project(p).testOrFalse(ProjectPermission.ACCESS));
+ }
+
+ private Project.NameKey parentOf(ProjectState ps) {
+ if (ps == null) {
+ return null;
+ }
+ Project.NameKey parent = ps.getProject().getParent();
+ if (parent != null) {
+ if (projectCache.get(parent) != null) {
+ return parent;
+ }
+ log.warn("parent project {} of project {} not found", parent.get(), ps.getName());
+ }
+ return null;
+ }
+
+ private boolean isParentAccessible(
+ Map<Project.NameKey, Boolean> checked, PermissionBackend.WithUser perm, ProjectState p)
+ throws PermissionBackendException {
+ Project.NameKey name = p.getNameKey();
+ Boolean b = checked.get(name);
+ if (b == null) {
+ try {
+ perm.project(name).check(ProjectPermission.ACCESS);
+ b = true;
+ } catch (AuthException denied) {
+ b = false;
+ }
+ checked.put(name, b);
+ }
+ return b;
+ }
+
private Iterable<Project.NameKey> scan() throws BadRequestException {
if (matchPrefix != null) {
checkMatchOptions(matchSubstring == null && matchRegex == null);
@@ -495,12 +554,12 @@ public class ListProjects implements RestReadView<TopLevelResource> {
}
private void printProjectTree(
- final PrintWriter stdout, final TreeMap<Project.NameKey, ProjectNode> treeMap) {
+ final PrintWriter stdout, TreeMap<Project.NameKey, ProjectNode> treeMap) {
final SortedSet<ProjectNode> sortedNodes = new TreeSet<>();
// Builds the inheritance tree using a list.
//
- for (final ProjectNode key : treeMap.values()) {
+ for (ProjectNode key : treeMap.values()) {
if (key.isAllProjects()) {
sortedNodes.add(key);
continue;
@@ -522,16 +581,21 @@ public class ListProjects implements RestReadView<TopLevelResource> {
private List<Ref> getBranchRefs(Project.NameKey projectName, ProjectControl projectControl) {
Ref[] result = new Ref[showBranch.size()];
try (Repository git = repoManager.openRepository(projectName)) {
+ PermissionBackend.ForProject perm = permissionBackend.user(currentUser).project(projectName);
for (int i = 0; i < showBranch.size(); i++) {
Ref ref = git.findRef(showBranch.get(i));
- if ((ref != null
- && ref.getObjectId() != null
- && (projectControl.controlForRef(ref.getLeaf().getName()).isVisible()))
- || (all && projectControl.isOwner())) {
+ if (all && projectControl.isOwner()) {
result[i] = ref;
+ } else if (ref != null && ref.getObjectId() != null) {
+ try {
+ perm.ref(ref.getLeaf().getName()).check(RefPermission.READ);
+ result[i] = ref;
+ } catch (AuthException e) {
+ continue;
+ }
}
}
- } catch (IOException ioe) {
+ } catch (IOException | PermissionBackendException e) {
// Fall through and return what is available.
}
return Arrays.asList(result);