Line data Source code
1 : // Copyright (C) 2012 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.git; 16 : 17 : import static com.google.gerrit.entities.RefNames.REFS_REJECT_COMMITS; 18 : import static java.nio.charset.StandardCharsets.UTF_8; 19 : 20 : import com.google.gerrit.entities.Project; 21 : import com.google.gerrit.entities.RefNames; 22 : import com.google.gerrit.extensions.restapi.AuthException; 23 : import com.google.gerrit.server.CurrentUser; 24 : import com.google.gerrit.server.GerritPersonIdent; 25 : import com.google.gerrit.server.IdentifiedUser; 26 : import com.google.gerrit.server.permissions.PermissionBackend; 27 : import com.google.gerrit.server.permissions.PermissionBackendException; 28 : import com.google.gerrit.server.permissions.ProjectPermission; 29 : import com.google.inject.Inject; 30 : import com.google.inject.Provider; 31 : import com.google.inject.Singleton; 32 : import java.io.IOException; 33 : import java.time.Instant; 34 : import java.time.ZoneId; 35 : import java.util.List; 36 : import org.eclipse.jgit.errors.IncorrectObjectTypeException; 37 : import org.eclipse.jgit.errors.MissingObjectException; 38 : import org.eclipse.jgit.lib.Constants; 39 : import org.eclipse.jgit.lib.ObjectId; 40 : import org.eclipse.jgit.lib.ObjectInserter; 41 : import org.eclipse.jgit.lib.PersonIdent; 42 : import org.eclipse.jgit.lib.Ref; 43 : import org.eclipse.jgit.lib.Repository; 44 : import org.eclipse.jgit.notes.Note; 45 : import org.eclipse.jgit.notes.NoteMap; 46 : import org.eclipse.jgit.revwalk.RevCommit; 47 : import org.eclipse.jgit.revwalk.RevWalk; 48 : 49 : /** 50 : * Logic for banning commits from being uploaded. 51 : * 52 : * <p>Gerrit has a per-project list of commits that are forbidden to be pushed. This class reads and 53 : * writes the banned commits list in {@code refs/meta/reject-commits}. 54 : */ 55 : @Singleton 56 : public class BanCommit { 57 : /** 58 : * Loads a list of commits to reject from {@code refs/meta/reject-commits}. 59 : * 60 : * @param repo repository from which the rejected commits should be loaded 61 : * @param walk open revwalk on repo. 62 : * @return NoteMap of commits to be rejected, null if there are none. 63 : * @throws IOException the map cannot be loaded. 64 : */ 65 : public static NoteMap loadRejectCommitsMap(Repository repo, RevWalk walk) throws IOException { 66 : try { 67 97 : Ref ref = repo.getRefDatabase().exactRef(RefNames.REFS_REJECT_COMMITS); 68 97 : if (ref == null) { 69 96 : return NoteMap.newEmptyMap(); 70 : } 71 : 72 2 : RevCommit map = walk.parseCommit(ref.getObjectId()); 73 2 : return NoteMap.read(walk.getObjectReader(), map); 74 0 : } catch (IOException badMap) { 75 0 : throw new IOException("Cannot load " + RefNames.REFS_REJECT_COMMITS, badMap); 76 : } 77 : } 78 : 79 : private final Provider<IdentifiedUser> currentUser; 80 : private final GitRepositoryManager repoManager; 81 : private final ZoneId zoneId; 82 : private final PermissionBackend permissionBackend; 83 : private final NotesBranchUtil.Factory notesBranchUtilFactory; 84 : 85 : @Inject 86 : BanCommit( 87 : Provider<IdentifiedUser> currentUser, 88 : GitRepositoryManager repoManager, 89 : @GerritPersonIdent PersonIdent gerritIdent, 90 : NotesBranchUtil.Factory notesBranchUtilFactory, 91 144 : PermissionBackend permissionBackend) { 92 144 : this.currentUser = currentUser; 93 144 : this.repoManager = repoManager; 94 144 : this.notesBranchUtilFactory = notesBranchUtilFactory; 95 144 : this.permissionBackend = permissionBackend; 96 144 : this.zoneId = gerritIdent.getZoneId(); 97 144 : } 98 : 99 : /** 100 : * Bans a list of commits from the given project. 101 : * 102 : * <p>The user must be specified, so it can be checked for the {@code BAN_COMMIT} permission. 103 : */ 104 : public BanCommitResult ban( 105 : Project.NameKey project, CurrentUser user, List<ObjectId> commitsToBan, String reason) 106 : throws AuthException, IOException, PermissionBackendException { 107 2 : permissionBackend.user(user).project(project).check(ProjectPermission.BAN_COMMIT); 108 : 109 2 : final BanCommitResult result = new BanCommitResult(); 110 2 : NoteMap banCommitNotes = NoteMap.newEmptyMap(); 111 : // Add a note for each banned commit to notes. 112 2 : try (Repository repo = repoManager.openRepository(project); 113 2 : RevWalk revWalk = new RevWalk(repo); 114 2 : ObjectInserter inserter = repo.newObjectInserter()) { 115 2 : ObjectId noteId = null; 116 2 : for (ObjectId commitToBan : commitsToBan) { 117 : try { 118 0 : revWalk.parseCommit(commitToBan); 119 2 : } catch (MissingObjectException e) { 120 : // Ignore exception, non-existing commits can be banned. 121 0 : } catch (IncorrectObjectTypeException e) { 122 0 : result.notACommit(commitToBan); 123 0 : continue; 124 2 : } 125 2 : if (noteId == null) { 126 2 : noteId = createNoteContent(reason, inserter); 127 : } 128 2 : banCommitNotes.set(commitToBan, noteId); 129 2 : } 130 2 : NotesBranchUtil notesBranchUtil = notesBranchUtilFactory.create(project, repo, inserter); 131 2 : NoteMap newlyCreated = 132 2 : notesBranchUtil.commitNewNotes( 133 : banCommitNotes, 134 : REFS_REJECT_COMMITS, 135 2 : createPersonIdent(), 136 2 : buildCommitMessage(commitsToBan, reason)); 137 : 138 2 : for (Note n : banCommitNotes) { 139 2 : if (newlyCreated.contains(n)) { 140 2 : result.commitBanned(n); 141 : } else { 142 1 : result.commitAlreadyBanned(n); 143 : } 144 2 : } 145 2 : return result; 146 : } 147 : } 148 : 149 : private ObjectId createNoteContent(String reason, ObjectInserter inserter) throws IOException { 150 2 : String noteContent = reason != null ? reason : ""; 151 2 : if (noteContent.length() > 0 && !noteContent.endsWith("\n")) { 152 0 : noteContent = noteContent + "\n"; 153 : } 154 2 : return inserter.insert(Constants.OBJ_BLOB, noteContent.getBytes(UTF_8)); 155 : } 156 : 157 : private PersonIdent createPersonIdent() { 158 2 : return currentUser.get().newCommitterIdent(Instant.now(), zoneId); 159 : } 160 : 161 : private static String buildCommitMessage(List<ObjectId> bannedCommits, String reason) { 162 2 : final StringBuilder commitMsg = new StringBuilder(); 163 2 : commitMsg.append("Banning "); 164 2 : commitMsg.append(bannedCommits.size()); 165 2 : commitMsg.append(" "); 166 2 : commitMsg.append(bannedCommits.size() == 1 ? "commit" : "commits"); 167 2 : commitMsg.append("\n\n"); 168 2 : if (reason != null) { 169 0 : commitMsg.append("Reason: "); 170 0 : commitMsg.append(reason); 171 0 : commitMsg.append("\n\n"); 172 : } 173 2 : commitMsg.append("The following commits are banned:\n"); 174 2 : final StringBuilder commitList = new StringBuilder(); 175 2 : for (ObjectId c : bannedCommits) { 176 2 : if (commitList.length() > 0) { 177 0 : commitList.append(",\n"); 178 : } 179 2 : commitList.append(c.getName()); 180 2 : } 181 2 : commitMsg.append(commitList); 182 2 : return commitMsg.toString(); 183 : } 184 : }