Merge "Enable plugin-based validation of user's ref related operations"
*submodules: * Update plugins/cookbook-plugin from branch 'master' - Add user ref operation validation example. The following example blocks any update to any ref that starts with 'refs/heads/protected-' prefix if it is performed by user that has no 'Administrate Server' capability. Change-Id: If5373d238a4d30fc450a4114e6af51d6536ae91a Signed-off-by: Jacek Centkowski <geminica.programs@gmail.com> - Add example implementation of ExternalIncludedIn extension point Change-Id: Ied5517cf9da58c748ad60bdf96444828a6051df3 Signed-off-by: Edwin Kempin <ekempin@google.com>
This commit is contained in:
commit
bbafaa36f5
@ -21,16 +21,18 @@ and cherry-pick buttons.
|
||||
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
|
||||
|
@ -100,6 +100,9 @@ import com.google.gerrit.server.git.MultiProgressMonitor.Task;
|
||||
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 @@ public class ReceiveCommits {
|
||||
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 @@ public class ReceiveCommits {
|
||||
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 @@ public class ReceiveCommits {
|
||||
@Nullable SearchingChangeCacheImpl changeCache,
|
||||
ChangeInserter.Factory changeInserterFactory,
|
||||
CommitValidators.Factory commitValidatorsFactory,
|
||||
RefOperationValidators.Factory refValidatorsFactory,
|
||||
@CanonicalWebUrl String canonicalWebUrl,
|
||||
RequestScopePropagator requestScopePropagator,
|
||||
SshInfo sshInfo,
|
||||
@ -392,6 +397,7 @@ public class ReceiveCommits {
|
||||
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 @@ public class ReceiveCommits {
|
||||
}
|
||||
|
||||
void sendMessages() {
|
||||
for (CommitValidationMessage m : messages) {
|
||||
for (ValidationMessage m : messages) {
|
||||
if (m.isError()) {
|
||||
messageSender.sendError(m.getMessage());
|
||||
} else {
|
||||
@ -1096,6 +1102,9 @@ public class ReceiveCommits {
|
||||
|
||||
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 @@ public class ReceiveCommits {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!validRefOperation(cmd)) {
|
||||
return;
|
||||
}
|
||||
validateNewCommits(ctl, cmd);
|
||||
batch.addCommand(cmd);
|
||||
} else {
|
||||
@ -1148,6 +1160,9 @@ public class ReceiveCommits {
|
||||
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 @@ public class ReceiveCommits {
|
||||
}
|
||||
|
||||
if (ctl.canForceUpdate()) {
|
||||
if (!validRefOperation(cmd)) {
|
||||
return;
|
||||
}
|
||||
batch.setAllowNonFastForwards(true).addCommand(cmd);
|
||||
} else {
|
||||
cmd.setResult(REJECTED_NONFASTFORWARD, " need '"
|
||||
@ -2399,6 +2417,21 @@ public class ReceiveCommits {
|
||||
}
|
||||
}
|
||||
|
||||
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()
|
||||
|
@ -55,6 +55,7 @@ public class CreateBranch implements RestModifyView<ProjectResource, BranchInput
|
||||
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 @@ public class CreateBranch implements RestModifyView<ProjectResource, BranchInput
|
||||
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 @@ public class CreateBranch implements RestModifyView<ProjectResource, BranchInput
|
||||
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:
|
||||
|
@ -50,16 +50,20 @@ public class DeleteBranch implements RestModifyView<BranchResource, Input> {
|
||||
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 @@ public class DeleteBranch implements RestModifyView<BranchResource, Input> {
|
||||
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 {
|
||||
|
@ -35,6 +35,7 @@ import org.eclipse.jgit.lib.BatchRefUpdate;
|
||||
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 @@ public class DeleteBranches
|
||||
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 @@ public class DeleteBranches
|
||||
}
|
||||
|
||||
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 @@ public class DeleteBranches
|
||||
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;
|
||||
}
|
||||
|
||||
|
@ -69,6 +69,7 @@ public class Module extends RestApiModule {
|
||||
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);
|
||||
|
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
@ -1 +1 @@
|
||||
Subproject commit 69b8f9f413ce83a71593a4068a3b8e81f684cbad
|
||||
Subproject commit fc39c552cffb94d15797d02e272fdc543c35b6bd
|
Loading…
x
Reference in New Issue
Block a user