Enable plugin-based validation of user's ref related operations
After user is authorized to perform a given ref operation, but just
before it is performed, invoke validation from plugins.
Validation is invoked for ref create, delete and update (including
non-ff) operations caused by user through either push or call to
existing ref related Gerrit endpoints (create/delete branch/es).
Change-Id: I11cbe51f54e7d133b336ab503577653b6fe0f443
Signed-off-by: Jacek Centkowski <geminica.programs@gmail.com>
diff --git a/Documentation/config-validation.txt b/Documentation/config-validation.txt
index 27b39eb..2707e5c 100644
--- a/Documentation/config-validation.txt
+++ b/Documentation/config-validation.txt
@@ -21,16 +21,18 @@
Out of the box, Gerrit includes a plugin that checks the length of the
subject and body lines of commit messages on uploaded commits.
-[[ref-operation-validation]]
-== Ref operation validation
+[[user-ref-operations-validation]]
+== User ref operations validation
Plugins implementing the `RefOperationValidationListener` interface can
-perform additional validation checks against ref creation/deletion operation
-before it is applied to the git repository.
+perform additional validation checks against user ref operations (resulting
+from either push or corresponding Gerrit REST/SSH endpoints call e.g.
+create branch etc.). Namely including ref creation, deletion and update
+(also non-fast-forward) before they are applied to the git repository.
-If the ref operation fails the validation, the plugin can throw an exception
-which will cause the operation to fail.
+The plugin can throw an exception which will cause the operation to fail,
+and prevent the ref update from being applied.
[[pre-merge-validation]]
== Pre-merge validation
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/ReceiveCommits.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/ReceiveCommits.java
index 21fe05c..2f73360 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/ReceiveCommits.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/ReceiveCommits.java
@@ -100,6 +100,9 @@
import com.google.gerrit.server.git.validators.CommitValidationException;
import com.google.gerrit.server.git.validators.CommitValidationMessage;
import com.google.gerrit.server.git.validators.CommitValidators;
+import com.google.gerrit.server.git.validators.RefOperationValidationException;
+import com.google.gerrit.server.git.validators.RefOperationValidators;
+import com.google.gerrit.server.git.validators.ValidationMessage;
import com.google.gerrit.server.mail.MailUtil.MailRecipients;
import com.google.gerrit.server.notedb.ChangeNotes;
import com.google.gerrit.server.notedb.NotesMigration;
@@ -289,6 +292,7 @@
private final ProjectCache projectCache;
private final String canonicalWebUrl;
private final CommitValidators.Factory commitValidatorsFactory;
+ private final RefOperationValidators.Factory refValidatorsFactory;
private final TagCache tagCache;
private final AccountCache accountCache;
private final ChangeInserter.Factory changeInserterFactory;
@@ -329,7 +333,7 @@
private final NotesMigration notesMigration;
private final ChangeEditUtil editUtil;
- private final List<CommitValidationMessage> messages = new ArrayList<>();
+ private final List<ValidationMessage> messages = new ArrayList<>();
private ListMultimap<Error, String> errors = LinkedListMultimap.create();
private Task newProgress;
private Task replaceProgress;
@@ -355,6 +359,7 @@
@Nullable SearchingChangeCacheImpl changeCache,
ChangeInserter.Factory changeInserterFactory,
CommitValidators.Factory commitValidatorsFactory,
+ RefOperationValidators.Factory refValidatorsFactory,
@CanonicalWebUrl String canonicalWebUrl,
RequestScopePropagator requestScopePropagator,
SshInfo sshInfo,
@@ -392,6 +397,7 @@
this.accountCache = accountCache;
this.changeInserterFactory = changeInserterFactory;
this.commitValidatorsFactory = commitValidatorsFactory;
+ this.refValidatorsFactory = refValidatorsFactory;
this.requestScopePropagator = requestScopePropagator;
this.sshInfo = sshInfo;
this.allProjectsName = allProjectsName;
@@ -544,7 +550,7 @@
}
void sendMessages() {
- for (CommitValidationMessage m : messages) {
+ for (ValidationMessage m : messages) {
if (m.isError()) {
messageSender.sendError(m.getMessage());
} else {
@@ -1096,6 +1102,9 @@
RefControl ctl = projectControl.controlForRef(cmd.getRefName());
if (ctl.canCreate(db, rp.getRepository(), obj)) {
+ if (!validRefOperation(cmd)) {
+ return;
+ }
validateNewCommits(ctl, cmd);
batch.addCommand(cmd);
} else {
@@ -1111,6 +1120,9 @@
return;
}
+ if (!validRefOperation(cmd)) {
+ return;
+ }
validateNewCommits(ctl, cmd);
batch.addCommand(cmd);
} else {
@@ -1148,6 +1160,9 @@
errors.put(Error.DELETE_CHANGES, ctl.getRefName());
reject(cmd, "cannot delete changes");
} else if (ctl.canDelete()) {
+ if (!validRefOperation(cmd)) {
+ return;
+ }
batch.addCommand(cmd);
} else {
if (RefNames.REFS_CONFIG.equals(ctl.getRefName())) {
@@ -1182,6 +1197,9 @@
}
if (ctl.canForceUpdate()) {
+ if (!validRefOperation(cmd)) {
+ return;
+ }
batch.setAllowNonFastForwards(true).addCommand(cmd);
} else {
cmd.setResult(REJECTED_NONFASTFORWARD, " need '"
@@ -2399,6 +2417,21 @@
}
}
+ private boolean validRefOperation(ReceiveCommand cmd) {
+ RefOperationValidators refValidators =
+ refValidatorsFactory.create(getProject(), user, cmd);
+
+ try {
+ messages.addAll(refValidators.validateForRefOperation());
+ } catch (RefOperationValidationException e) {
+ messages.addAll(Lists.newArrayList(e.getMessages()));
+ reject(cmd, e.getMessage());
+ return false;
+ }
+
+ return true;
+ }
+
private void validateNewCommits(RefControl ctl, ReceiveCommand cmd) {
if (ctl.canForgeAuthor()
&& ctl.canForgeCommitter()
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/CreateBranch.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/CreateBranch.java
index 674e788..c7b2922 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/CreateBranch.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/CreateBranch.java
@@ -55,6 +55,7 @@
private final GitRepositoryManager repoManager;
private final Provider<ReviewDb> db;
private final GitReferenceUpdated referenceUpdated;
+ private final RefValidationHelper refCreationValidator;
private String ref;
@Inject
@@ -62,11 +63,14 @@
GitRepositoryManager repoManager,
Provider<ReviewDb> db,
GitReferenceUpdated referenceUpdated,
+ RefValidationHelper.Factory refHelperFactory,
@Assisted String ref) {
this.identifiedUser = identifiedUser;
this.repoManager = repoManager;
this.db = db;
this.referenceUpdated = referenceUpdated;
+ this.refCreationValidator =
+ refHelperFactory.create(ReceiveCommand.Type.CREATE);
this.ref = ref;
}
@@ -123,6 +127,8 @@
u.setNewObjectId(object.copy());
u.setRefLogIdent(identifiedUser.get().newRefLogIdent());
u.setRefLogMessage("created via REST from " + input.revision, false);
+ refCreationValidator.validateRefOperation(
+ rsrc.getName(), identifiedUser.get(), u);
final RefUpdate.Result result = u.update(rw);
switch (result) {
case FAST_FORWARD:
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/DeleteBranch.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/DeleteBranch.java
index 9db12b8..091cba3 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/DeleteBranch.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/DeleteBranch.java
@@ -50,16 +50,20 @@
private final GitRepositoryManager repoManager;
private final Provider<InternalChangeQuery> queryProvider;
private final GitReferenceUpdated referenceUpdated;
+ private final RefValidationHelper refDeletionValidator;
@Inject
DeleteBranch(Provider<IdentifiedUser> identifiedUser,
GitRepositoryManager repoManager,
Provider<InternalChangeQuery> queryProvider,
- GitReferenceUpdated referenceUpdated) {
+ GitReferenceUpdated referenceUpdated,
+ RefValidationHelper.Factory refHelperFactory) {
this.identifiedUser = identifiedUser;
this.repoManager = repoManager;
this.queryProvider = queryProvider;
this.referenceUpdated = referenceUpdated;
+ this.refDeletionValidator =
+ refHelperFactory.create(ReceiveCommand.Type.DELETE);
}
@Override
@@ -78,6 +82,8 @@
RefUpdate.Result result;
RefUpdate u = r.updateRef(rsrc.getRef());
u.setForceUpdate(true);
+ refDeletionValidator.validateRefOperation(
+ rsrc.getName(), identifiedUser.get(), u);
int remainingLockFailureCalls = MAX_LOCK_FAILURE_CALLS;
for (;;) {
try {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/DeleteBranches.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/DeleteBranches.java
index e0a84eb..f4fa446 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/DeleteBranches.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/DeleteBranches.java
@@ -35,6 +35,7 @@
import org.eclipse.jgit.lib.NullProgressMonitor;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.Ref;
+import org.eclipse.jgit.lib.RefUpdate;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.revwalk.RevWalk;
import org.eclipse.jgit.transport.ReceiveCommand;
@@ -53,16 +54,20 @@
private final GitRepositoryManager repoManager;
private final Provider<InternalChangeQuery> queryProvider;
private final GitReferenceUpdated referenceUpdated;
+ private final RefValidationHelper refDeletionValidator;
@Inject
DeleteBranches(Provider<IdentifiedUser> identifiedUser,
GitRepositoryManager repoManager,
Provider<InternalChangeQuery> queryProvider,
- GitReferenceUpdated referenceUpdated) {
+ GitReferenceUpdated referenceUpdated,
+ RefValidationHelper.Factory refHelperFactory) {
this.identifiedUser = identifiedUser;
this.repoManager = repoManager;
this.queryProvider = queryProvider;
this.referenceUpdated = referenceUpdated;
+ this.refDeletionValidator =
+ refHelperFactory.create(ReceiveCommand.Type.DELETE);
}
@Override
@@ -100,7 +105,8 @@
}
private ReceiveCommand createDeleteCommand(ProjectResource project,
- Repository r, String branch) throws OrmException, IOException {
+ Repository r, String branch)
+ throws OrmException, IOException, ResourceConflictException {
Ref ref = r.getRefDatabase().getRef(branch);
ReceiveCommand command;
if (ref == null) {
@@ -120,6 +126,10 @@
if (!queryProvider.get().setLimit(1).byBranchOpen(branchKey).isEmpty()) {
command.setResult(Result.REJECTED_OTHER_REASON, "it has open changes");
}
+ RefUpdate u = r.updateRef(branch);
+ u.setForceUpdate(true);
+ refDeletionValidator.validateRefOperation(
+ project.getName(), identifiedUser.get(), u);
return command;
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/Module.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/Module.java
index 9706298..8a6145a 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/Module.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/Module.java
@@ -69,6 +69,7 @@
post(PROJECT_KIND, "branches:delete").to(DeleteBranches.class);
factory(CreateBranch.Factory.class);
get(BRANCH_KIND, "mergeable").to(CheckMergeability.class);
+ factory(RefValidationHelper.Factory.class);
get(BRANCH_KIND, "reflog").to(GetReflog.class);
child(BRANCH_KIND, "files").to(FilesCollection.class);
get(FILE_KIND, "content").to(GetContent.class);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/RefValidationHelper.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/RefValidationHelper.java
new file mode 100644
index 0000000..6e2fd5d
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/RefValidationHelper.java
@@ -0,0 +1,56 @@
+// Copyright (C) 2014 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.project;
+
+import com.google.gerrit.extensions.restapi.ResourceConflictException;
+import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.server.IdentifiedUser;
+import com.google.gerrit.server.git.validators.RefOperationValidators;
+import com.google.gerrit.server.validators.ValidationException;
+import com.google.inject.Inject;
+import com.google.inject.assistedinject.Assisted;
+
+import org.eclipse.jgit.lib.RefUpdate;
+import org.eclipse.jgit.transport.ReceiveCommand.Type;
+
+public class RefValidationHelper {
+ public interface Factory {
+ RefValidationHelper create(Type operationType);
+ }
+
+ private final RefOperationValidators.Factory refValidatorsFactory;
+ private final Type operationType;
+
+ @Inject
+ RefValidationHelper(RefOperationValidators.Factory refValidatorsFactory,
+ @Assisted Type operationType) {
+ this.refValidatorsFactory = refValidatorsFactory;
+ this.operationType = operationType;
+ }
+
+ public void validateRefOperation(String projectName, IdentifiedUser user,
+ RefUpdate update) throws ResourceConflictException {
+ RefOperationValidators refValidators =
+ refValidatorsFactory.create(
+ new Project(new Project.NameKey(projectName)),
+ user,
+ RefOperationValidators.getCommand(update, operationType));
+ try {
+ refValidators.validateForRefOperation();
+ } catch (ValidationException e) {
+ throw new ResourceConflictException(e.getMessage());
+ }
+ }
+}