Line data Source code
1 : // Copyright (C) 2021 The Android Open Source Project 2 : // 3 : // Licensed under the Apache License, Version 2.0 (the "License"); 4 : // you may not use this file except in compliance with the License. 5 : // You may obtain a copy of the License at 6 : // 7 : // http://www.apache.org/licenses/LICENSE-2.0 8 : // 9 : // Unless required by applicable law or agreed to in writing, software 10 : // distributed under the License is distributed on an "AS IS" BASIS, 11 : // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 : // See the License for the specific language governing permissions and 13 : // limitations under the License. 14 : 15 : package com.google.gerrit.server.change; 16 : 17 : import static java.util.stream.Collectors.toSet; 18 : 19 : import com.google.common.collect.ImmutableMap; 20 : import com.google.gerrit.entities.Project; 21 : import com.google.gerrit.server.git.GitRepositoryManager; 22 : import com.google.gerrit.server.permissions.PermissionBackend; 23 : import com.google.gerrit.server.permissions.PermissionBackend.RefFilterOptions; 24 : import com.google.gerrit.server.permissions.PermissionBackendException; 25 : import com.google.inject.Inject; 26 : import java.io.IOException; 27 : import java.util.Collection; 28 : import java.util.HashMap; 29 : import java.util.HashSet; 30 : import java.util.List; 31 : import java.util.Map; 32 : import java.util.Set; 33 : import org.eclipse.jgit.errors.IncorrectObjectTypeException; 34 : import org.eclipse.jgit.errors.MissingObjectException; 35 : import org.eclipse.jgit.lib.ObjectId; 36 : import org.eclipse.jgit.lib.Ref; 37 : import org.eclipse.jgit.lib.RefDatabase; 38 : import org.eclipse.jgit.lib.Repository; 39 : import org.eclipse.jgit.revwalk.RevCommit; 40 : import org.eclipse.jgit.revwalk.RevWalk; 41 : 42 : public class IncludedInRefs { 43 : protected final GitRepositoryManager repoManager; 44 : protected final PermissionBackend permissionBackend; 45 : 46 : @Inject 47 143 : IncludedInRefs(GitRepositoryManager repoManager, PermissionBackend permissionBackend) { 48 143 : this.repoManager = repoManager; 49 143 : this.permissionBackend = permissionBackend; 50 143 : } 51 : 52 : public Map<String, Set<String>> apply( 53 : Project.NameKey project, Set<String> commits, Set<String> refNames) 54 : throws IOException, PermissionBackendException { 55 1 : try (Repository repo = repoManager.openRepository(project)) { 56 1 : Set<Ref> visibleRefs = getVisibleRefs(repo, refNames, project); 57 : 58 1 : if (!visibleRefs.isEmpty()) { 59 1 : try (RevWalk revWalk = new RevWalk(repo)) { 60 1 : revWalk.setRetainBody(false); 61 1 : Set<RevCommit> revCommits = getRevCommits(commits, revWalk); 62 : 63 1 : if (!revCommits.isEmpty()) { 64 1 : return commitsIncludedIn( 65 1 : revCommits, IncludedInUtil.getSortedRefs(visibleRefs, revWalk), revWalk); 66 : } 67 1 : } 68 : } 69 1 : } 70 1 : return ImmutableMap.of(); 71 : } 72 : 73 : private Set<Ref> getVisibleRefs(Repository repo, Set<String> refNames, Project.NameKey project) 74 : throws PermissionBackendException { 75 1 : RefDatabase refDb = repo.getRefDatabase(); 76 1 : Set<Ref> refs = new HashSet<>(); 77 1 : for (String refName : refNames) { 78 : try { 79 1 : Ref ref = refDb.exactRef(refName); 80 1 : if (ref != null) { 81 1 : refs.add(ref); 82 : } 83 0 : } catch (IOException e) { 84 : // Ignore and continue to process rest of the refs so as to keep 85 : // the behavior similar to the ref not being visible to the user. 86 : // This will ensure that there is no information leak about the 87 : // ref when the ref is corrupted and is not visible to the user. 88 1 : } 89 1 : } 90 1 : return filterReadableRefs(project, refs, repo); 91 : } 92 : 93 : private Set<RevCommit> getRevCommits(Set<String> commits, RevWalk revWalk) throws IOException { 94 1 : Set<RevCommit> revCommits = new HashSet<>(); 95 1 : for (String commit : commits) { 96 : try { 97 1 : revCommits.add(revWalk.parseCommit(ObjectId.fromString(commit))); 98 1 : } catch (MissingObjectException | IncorrectObjectTypeException | IllegalArgumentException e) { 99 : // Ignore and continue to process the rest of the commits so as to keep 100 : // the behavior similar to the commit not being included in any of the 101 : // visible specified refs. This will ensure that there is no information 102 : // leak about the commit when the commit is not visible to the user. 103 1 : } 104 1 : } 105 1 : return revCommits; 106 : } 107 : 108 : private Map<String, Set<String>> commitsIncludedIn( 109 : Collection<RevCommit> commits, Collection<Ref> refs, RevWalk revWalk) throws IOException { 110 1 : Map<String, Set<String>> refsByCommit = new HashMap<>(); 111 1 : for (RevCommit commit : commits) { 112 1 : List<Ref> matchingRefs = revWalk.getMergedInto(commit, refs); 113 1 : if (matchingRefs.size() > 0) { 114 1 : refsByCommit.put( 115 1 : commit.getName(), matchingRefs.stream().map(Ref::getName).collect(toSet())); 116 : } 117 1 : } 118 1 : return refsByCommit; 119 : } 120 : 121 : /** 122 : * Filter readable refs according to the caller's refs visibility. 123 : * 124 : * @param project specific Gerrit project. 125 : * @param inputRefs a list of refs 126 : * @param repo repository opened for the Gerrit project. 127 : * @return set of visible refs to the caller 128 : */ 129 : private Set<Ref> filterReadableRefs(Project.NameKey project, Set<Ref> inputRefs, Repository repo) 130 : throws PermissionBackendException { 131 1 : PermissionBackend.ForProject perm = permissionBackend.currentUser().project(project); 132 1 : return perm.filter(inputRefs, repo, RefFilterOptions.defaults()).stream().collect(toSet()); 133 : } 134 : }