diff options
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.java | 231 |
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; + } } |