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:
Hugo Arès 2016-08-19 11:58:33 +00:00 committed by Gerrit Code Review
commit bbafaa36f5
8 changed files with 126 additions and 12 deletions

View File

@ -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

View File

@ -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()

View File

@ -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:

View File

@ -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 {

View File

@ -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;
}

View File

@ -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);

View File

@ -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