summaryrefslogtreecommitdiffstats
path: root/gerrit-server/src/main/java/com/google/gerrit/server/git/VisibleRefFilter.java
diff options
context:
space:
mode:
Diffstat (limited to 'gerrit-server/src/main/java/com/google/gerrit/server/git/VisibleRefFilter.java')
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/git/VisibleRefFilter.java231
1 files changed, 159 insertions, 72 deletions
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/VisibleRefFilter.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/VisibleRefFilter.java
index 47d416c90f..c16c195d3a 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/VisibleRefFilter.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/VisibleRefFilter.java
@@ -18,27 +18,41 @@ import static com.google.gerrit.reviewdb.client.RefNames.REFS_CACHE_AUTOMERGE;
import static com.google.gerrit.reviewdb.client.RefNames.REFS_CHANGES;
import static com.google.gerrit.reviewdb.client.RefNames.REFS_CONFIG;
import static com.google.gerrit.reviewdb.client.RefNames.REFS_USERS_SELF;
+import static java.util.stream.Collectors.toMap;
-import com.google.common.collect.ImmutableSet;
import com.google.gerrit.common.Nullable;
+import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.reviewdb.client.Account;
+import com.google.gerrit.reviewdb.client.Branch;
import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.reviewdb.client.RefNames;
import com.google.gerrit.reviewdb.server.ReviewDb;
+import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.notedb.ChangeNotes;
+import com.google.gerrit.server.notedb.ChangeNotes.Factory.ChangeNotesResult;
+import com.google.gerrit.server.permissions.ChangePermission;
+import com.google.gerrit.server.permissions.GlobalPermission;
+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.project.ProjectControl;
+import com.google.gerrit.server.project.ProjectState;
import com.google.gerrit.server.query.change.ChangeData;
import com.google.gwtorm.server.OrmException;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+import com.google.inject.assistedinject.Assisted;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
-import java.util.HashSet;
import java.util.List;
import java.util.Map;
-import java.util.Set;
+import java.util.Objects;
+import java.util.stream.Stream;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.Ref;
import org.eclipse.jgit.lib.RefDatabase;
@@ -53,50 +67,73 @@ import org.slf4j.LoggerFactory;
public class VisibleRefFilter extends AbstractAdvertiseRefsHook {
private static final Logger log = LoggerFactory.getLogger(VisibleRefFilter.class);
+ public interface Factory {
+ VisibleRefFilter create(ProjectState projectState, Repository git);
+ }
+
private final TagCache tagCache;
private final ChangeNotes.Factory changeNotesFactory;
@Nullable private final SearchingChangeCacheImpl changeCache;
- private final Repository db;
- private final Project.NameKey projectName;
- private final ProjectControl projectCtl;
- private final ReviewDb reviewDb;
- private final boolean showMetadata;
+ private final Provider<ReviewDb> db;
+ private final Provider<CurrentUser> user;
+ private final PermissionBackend permissionBackend;
+ private final PermissionBackend.ForProject perm;
+ private final ProjectState projectState;
+ private final Repository git;
+ private ProjectControl projectCtl;
+ private boolean showMetadata = true;
private String userEditPrefix;
- private Set<Change.Id> visibleChanges;
+ private Map<Change.Id, Branch.NameKey> visibleChanges;
- public VisibleRefFilter(
+ @Inject
+ VisibleRefFilter(
TagCache tagCache,
ChangeNotes.Factory changeNotesFactory,
@Nullable SearchingChangeCacheImpl changeCache,
- Repository db,
- ProjectControl projectControl,
- ReviewDb reviewDb,
- boolean showMetadata) {
+ Provider<ReviewDb> db,
+ Provider<CurrentUser> user,
+ PermissionBackend permissionBackend,
+ @Assisted ProjectState projectState,
+ @Assisted Repository git) {
this.tagCache = tagCache;
this.changeNotesFactory = changeNotesFactory;
this.changeCache = changeCache;
this.db = db;
- this.projectName = projectControl.getProject().getNameKey();
- this.projectCtl = projectControl;
- this.reviewDb = reviewDb;
- this.showMetadata = showMetadata;
+ this.user = user;
+ this.permissionBackend = permissionBackend;
+ this.perm =
+ permissionBackend.user(user).database(db).project(projectState.getProject().getNameKey());
+ this.projectState = projectState;
+ this.git = git;
+ }
+
+ /** Show change references. Default is {@code true}. */
+ public VisibleRefFilter setShowMetadata(boolean show) {
+ showMetadata = show;
+ return this;
}
public Map<String, Ref> filter(Map<String, Ref> refs, boolean filterTagsSeparately) {
- if (projectCtl.getProjectState().isAllUsers()) {
+ if (projectState.isAllUsers()) {
refs = addUsersSelfSymref(refs);
}
- if (projectCtl.allRefsAreVisible(ImmutableSet.of(REFS_CONFIG))) {
- return fastHideRefsMetaConfig(refs);
+ PermissionBackend.WithUser withUser = permissionBackend.user(user);
+ PermissionBackend.ForProject forProject = withUser.project(projectState.getNameKey());
+ if (!projectState.isAllUsers()) {
+ if (checkProjectPermission(forProject, ProjectPermission.READ)) {
+ return refs;
+ } else if (checkProjectPermission(forProject, ProjectPermission.READ_NO_CONFIG)) {
+ return fastHideRefsMetaConfig(refs);
+ }
}
Account.Id userId;
boolean viewMetadata;
- if (projectCtl.getUser().isIdentifiedUser()) {
- IdentifiedUser user = projectCtl.getUser().asIdentifiedUser();
- userId = user.getAccountId();
- viewMetadata = user.getCapabilities().canAccessDatabase();
+ if (user.get().isIdentifiedUser()) {
+ viewMetadata = withUser.testOrFalse(GlobalPermission.ACCESS_DATABASE);
+ IdentifiedUser u = user.get().asIdentifiedUser();
+ userId = u.getAccountId();
userEditPrefix = RefNames.refsEditPrefix(userId);
} else {
userId = null;
@@ -106,12 +143,12 @@ public class VisibleRefFilter extends AbstractAdvertiseRefsHook {
Map<String, Ref> result = new HashMap<>();
List<Ref> deferredTags = new ArrayList<>();
+ projectCtl = projectState.controlFor(user.get());
for (Ref ref : refs.values()) {
String name = ref.getName();
Change.Id changeId;
Account.Id accountId;
- if (name.startsWith(REFS_CACHE_AUTOMERGE)
- || (!showMetadata && isMetadata(projectCtl, name))) {
+ if (name.startsWith(REFS_CACHE_AUTOMERGE) || (!showMetadata && isMetadata(name))) {
continue;
} else if (RefNames.isRefsEdit(name)) {
// Edits are visible only to the owning user, if change is visible.
@@ -125,8 +162,7 @@ public class VisibleRefFilter extends AbstractAdvertiseRefsHook {
}
} else if ((accountId = Account.Id.fromRef(name)) != null) {
// Account ref is visible only to corresponding account.
- if (viewMetadata
- || (accountId.equals(userId) && projectCtl.controlForRef(name).isVisible())) {
+ if (viewMetadata || (accountId.equals(userId) && canReadRef(name))) {
result.put(name, ref);
}
} else if (isTag(ref)) {
@@ -139,17 +175,22 @@ public class VisibleRefFilter extends AbstractAdvertiseRefsHook {
if (viewMetadata) {
result.put(name, ref);
}
- } else if (projectCtl.getProjectState().isAllUsers()
- && name.equals(RefNames.REFS_EXTERNAL_IDS)) {
+ } else if (projectState.isAllUsers() && name.equals(RefNames.REFS_EXTERNAL_IDS)) {
// The notes branch with the external IDs of all users must not be exposed to normal users.
if (viewMetadata) {
result.put(name, ref);
}
- } else if (projectCtl.controlForRef(ref.getLeaf().getName()).isVisible()) {
+ } else if (canReadRef(ref.getLeaf().getName())) {
// Use the leaf to lookup the control data. If the reference is
// symbolic we want the control around the final target. If its
// not symbolic then getLeaf() is a no-op returning ref itself.
result.put(name, ref);
+ } else if (isRefsUsersSelf(ref)) {
+ // viewMetadata allows to see all account refs, hence refs/users/self should be included as
+ // well
+ if (viewMetadata) {
+ result.put(name, ref);
+ }
}
}
@@ -159,11 +200,11 @@ public class VisibleRefFilter extends AbstractAdvertiseRefsHook {
if (!deferredTags.isEmpty() && (!result.isEmpty() || filterTagsSeparately)) {
TagMatcher tags =
tagCache
- .get(projectName)
+ .get(projectState.getNameKey())
.matcher(
tagCache,
- db,
- filterTagsSeparately ? filter(db.getAllRefs()).values() : result.values());
+ git,
+ filterTagsSeparately ? filter(git.getAllRefs()).values() : result.values());
for (Ref tag : deferredTags) {
if (tags.isReachable(tag)) {
result.put(tag.getName(), tag);
@@ -175,7 +216,7 @@ public class VisibleRefFilter extends AbstractAdvertiseRefsHook {
}
private Map<String, Ref> fastHideRefsMetaConfig(Map<String, Ref> refs) {
- if (refs.containsKey(REFS_CONFIG) && !projectCtl.controlForRef(REFS_CONFIG).isVisible()) {
+ if (refs.containsKey(REFS_CONFIG) && !canReadRef(REFS_CONFIG)) {
Map<String, Ref> r = new HashMap<>(refs);
r.remove(REFS_CONFIG);
return r;
@@ -184,8 +225,8 @@ public class VisibleRefFilter extends AbstractAdvertiseRefsHook {
}
private Map<String, Ref> addUsersSelfSymref(Map<String, Ref> refs) {
- if (projectCtl.getUser().isIdentifiedUser()) {
- Ref r = refs.get(RefNames.refsUsers(projectCtl.getUser().getAccountId()));
+ if (user.get().isIdentifiedUser()) {
+ Ref r = refs.get(RefNames.refsUsers(user.get().getAccountId()));
if (r != null) {
SymbolicRef s = new SymbolicRef(REFS_USERS_SELF, r);
refs = new HashMap<>(refs);
@@ -221,63 +262,109 @@ public class VisibleRefFilter extends AbstractAdvertiseRefsHook {
visibleChanges = visibleChangesBySearch();
}
}
- return visibleChanges.contains(changeId);
+ return visibleChanges.containsKey(changeId);
}
private boolean visibleEdit(String name) {
- if (userEditPrefix != null && name.startsWith(userEditPrefix)) {
- Change.Id id = Change.Id.fromEditRefPart(name);
- if (id != null) {
- return visible(id);
- }
+ Change.Id id = Change.Id.fromEditRefPart(name);
+ // Initialize if it wasn't yet
+ if (visibleChanges == null) {
+ visible(id);
+ }
+ if (id != null) {
+ return (userEditPrefix != null && name.startsWith(userEditPrefix) && visible(id))
+ || (visibleChanges.containsKey(id)
+ && projectCtl.controlForRef(visibleChanges.get(id)).isEditVisible());
}
return false;
}
- private Set<Change.Id> visibleChangesBySearch() {
- Project project = projectCtl.getProject();
+ private Map<Change.Id, Branch.NameKey> visibleChangesBySearch() {
+ Project.NameKey project = projectState.getNameKey();
try {
- Set<Change.Id> visibleChanges = new HashSet<>();
- for (ChangeData cd : changeCache.getChangeData(reviewDb, project.getNameKey())) {
- if (projectCtl.controlForIndexedChange(cd.change()).isVisible(reviewDb, cd)) {
- visibleChanges.add(cd.getId());
+ Map<Change.Id, Branch.NameKey> visibleChanges = new HashMap<>();
+ for (ChangeData cd : changeCache.getChangeData(db.get(), project)) {
+ ChangeNotes notes = changeNotesFactory.createFromIndexedChange(cd.change());
+ if (perm.indexedChange(cd, notes).test(ChangePermission.READ)) {
+ visibleChanges.put(cd.getId(), cd.change().getDest());
}
}
return visibleChanges;
- } catch (OrmException e) {
+ } catch (OrmException | PermissionBackendException e) {
log.error(
- "Cannot load changes for project "
- + project.getName()
- + ", assuming no changes are visible",
- e);
- return Collections.emptySet();
+ "Cannot load changes for project " + project + ", assuming no changes are visible", e);
+ return Collections.emptyMap();
}
}
- private Set<Change.Id> visibleChangesByScan() {
- Project.NameKey project = projectCtl.getProject().getNameKey();
+ private Map<Change.Id, Branch.NameKey> visibleChangesByScan() {
+ Project.NameKey p = projectState.getNameKey();
+ Stream<ChangeNotesResult> s;
try {
- Set<Change.Id> visibleChanges = new HashSet<>();
- for (ChangeNotes cn : changeNotesFactory.scan(db, reviewDb, project)) {
- if (projectCtl.controlFor(cn).isVisible(reviewDb)) {
- visibleChanges.add(cn.getChangeId());
- }
+ s = changeNotesFactory.scan(git, db.get(), p);
+ } catch (IOException e) {
+ log.error("Cannot load changes for project " + p + ", assuming no changes are visible", e);
+ return Collections.emptyMap();
+ }
+ return s.map(r -> toNotes(p, r))
+ .filter(Objects::nonNull)
+ .collect(toMap(n -> n.getChangeId(), n -> n.getChange().getDest()));
+ }
+
+ @Nullable
+ private ChangeNotes toNotes(Project.NameKey p, ChangeNotesResult r) {
+ if (r.error().isPresent()) {
+ log.warn("Failed to load change " + r.id() + " in " + p, r.error().get());
+ return null;
+ }
+ try {
+ if (perm.change(r.notes()).test(ChangePermission.READ)) {
+ return r.notes();
}
- return visibleChanges;
- } catch (IOException | OrmException e) {
- log.error(
- "Cannot load changes for project " + project + ", assuming no changes are visible", e);
- return Collections.emptySet();
+ } catch (PermissionBackendException e) {
+ log.warn("Failed to check permission for " + r.id() + " in " + p, e);
}
+ return null;
}
- private static boolean isMetadata(ProjectControl projectCtl, String name) {
- return name.startsWith(REFS_CHANGES)
- || RefNames.isRefsEdit(name)
- || (projectCtl.getProjectState().isAllUsers() && name.equals(RefNames.REFS_EXTERNAL_IDS));
+ private boolean isMetadata(String name) {
+ return name.startsWith(REFS_CHANGES) || RefNames.isRefsEdit(name);
}
private static boolean isTag(Ref ref) {
return ref.getLeaf().getName().startsWith(Constants.R_TAGS);
}
+
+ private static boolean isRefsUsersSelf(Ref ref) {
+ return ref.getName().startsWith(REFS_USERS_SELF);
+ }
+
+ private boolean canReadRef(String ref) {
+ try {
+ perm.ref(ref).check(RefPermission.READ);
+ return true;
+ } catch (AuthException e) {
+ return false;
+ } catch (PermissionBackendException e) {
+ log.error("unable to check permissions", e);
+ return false;
+ }
+ }
+
+ private boolean checkProjectPermission(
+ PermissionBackend.ForProject forProject, ProjectPermission perm) {
+ try {
+ forProject.check(perm);
+ } catch (AuthException e) {
+ return false;
+ } catch (PermissionBackendException e) {
+ log.error(
+ "Can't check permission for user {} on project {}",
+ user.get(),
+ projectState.getName(),
+ e);
+ return false;
+ }
+ return true;
+ }
}