Line data Source code
1 : // Copyright (C) 2017 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.project; 16 : 17 : import com.google.common.collect.ImmutableList; 18 : import com.google.common.flogger.FluentLogger; 19 : import com.google.gerrit.entities.Project; 20 : import com.google.gerrit.server.CurrentUser; 21 : import com.google.gerrit.server.logging.Metadata; 22 : import com.google.gerrit.server.logging.TraceContext; 23 : import com.google.gerrit.server.logging.TraceContext.TraceTimer; 24 : import com.google.gerrit.server.permissions.PermissionBackend; 25 : import com.google.gerrit.server.permissions.PermissionBackend.RefFilterOptions; 26 : import com.google.gerrit.server.permissions.PermissionBackendException; 27 : import com.google.inject.Inject; 28 : import com.google.inject.Singleton; 29 : import java.io.IOException; 30 : import java.util.ArrayList; 31 : import java.util.Collection; 32 : import java.util.List; 33 : import java.util.Optional; 34 : import org.eclipse.jgit.errors.IncorrectObjectTypeException; 35 : import org.eclipse.jgit.errors.MissingObjectException; 36 : import org.eclipse.jgit.lib.Ref; 37 : import org.eclipse.jgit.lib.Repository; 38 : import org.eclipse.jgit.revwalk.ReachabilityChecker; 39 : import org.eclipse.jgit.revwalk.RevCommit; 40 : import org.eclipse.jgit.revwalk.RevWalk; 41 : 42 : /** 43 : * Report whether a commit is reachable from a set of commits. This is used for checking if a user 44 : * has read permissions on a commit. 45 : */ 46 : @Singleton 47 : public class Reachable { 48 149 : private static final FluentLogger logger = FluentLogger.forEnclosingClass(); 49 : 50 : private final PermissionBackend permissionBackend; 51 : 52 : @Inject 53 149 : Reachable(PermissionBackend permissionBackend) { 54 149 : this.permissionBackend = permissionBackend; 55 149 : } 56 : 57 : /** 58 : * Returns true if a commit is reachable from a given set of refs. This method enforces 59 : * permissions on the given set of refs and performs a reachability check. Tags are not filtered 60 : * separately and will only be returned if reachable by a provided ref. 61 : */ 62 : public boolean fromRefs( 63 : Project.NameKey project, Repository repo, RevCommit commit, List<Ref> refs) { 64 7 : return fromRefs(project, repo, commit, refs, Optional.empty()); 65 : } 66 : 67 : boolean fromRefs( 68 : Project.NameKey project, 69 : Repository repo, 70 : RevCommit commit, 71 : List<Ref> refs, 72 : Optional<CurrentUser> optionalUserProvider) { 73 24 : try (RevWalk rw = new RevWalk(repo)) { 74 24 : Collection<Ref> filtered = 75 : optionalUserProvider 76 24 : .map(permissionBackend::user) 77 24 : .orElse(permissionBackend.currentUser()) 78 24 : .project(project) 79 24 : .filter(refs, repo, RefFilterOptions.defaults()); 80 24 : Collection<RevCommit> visible = new ArrayList<>(); 81 24 : for (Ref r : filtered) { 82 : try { 83 24 : visible.add(rw.parseCommit(r.getObjectId())); 84 1 : } catch (IncorrectObjectTypeException notCommit) { 85 : // Its OK for a tag reference to point to a blob or a tree, this 86 : // is common in the Linux kernel or git.git repository. 87 1 : continue; 88 0 : } catch (MissingObjectException notHere) { 89 : // Log the problem with this branch, but keep processing. 90 0 : logger.atWarning().log( 91 : "Reference %s in %s points to dangling object %s", 92 0 : r.getName(), repo.getDirectory(), r.getObjectId()); 93 0 : continue; 94 24 : } 95 24 : } 96 : 97 : // The filtering above already produces a voluminous trace. To separate the permission check 98 : // from the reachability check, do the trace here: 99 24 : try (TraceTimer timer = 100 24 : TraceContext.newTimer( 101 : "ReachabilityChecker.areAllReachable", 102 24 : Metadata.builder().projectName(project.get()).resourceCount(refs.size()).build())) { 103 24 : ReachabilityChecker checker = rw.getObjectReader().createReachabilityChecker(rw); 104 24 : Optional<RevCommit> unreachable = 105 24 : checker.areAllReachable(ImmutableList.of(rw.parseCommit(commit)), visible.stream()); 106 24 : return !unreachable.isPresent(); 107 : } 108 0 : } catch (IOException | PermissionBackendException e) { 109 0 : logger.atSevere().withCause(e).log( 110 0 : "Cannot verify permissions to commit object %s in repository %s", commit.name(), project); 111 0 : return false; 112 : } 113 : } 114 : }