Add ADD_PATCH_SET logic to lock patch sets
I've added a new permission to distinguish between creating a change and adding new patch sets. This is intended for projects that want to restrict the upload of new patch sets to the change owner (or any other group). Change-Id: If9f9f7ed0f0890fb4b854d0bf71e546ebe43ef96
This commit is contained in:
		@@ -564,6 +564,19 @@ that would otherwise be used. Or it may give permission to upload
 | 
			
		||||
new changes for code review, this depends on which namespace the
 | 
			
		||||
permission is granted to.
 | 
			
		||||
 | 
			
		||||
[[category_push_patch_set]]
 | 
			
		||||
=== Push Patch Set
 | 
			
		||||
 | 
			
		||||
This category controls which users are allowed to upload new patch sets to
 | 
			
		||||
existing changes. Irrespective of this permission, change owners are always
 | 
			
		||||
allowed to upload new patch sets for their changes. This permission needs to be
 | 
			
		||||
set on `refs/for/*`.
 | 
			
		||||
 | 
			
		||||
The absence of this permission will prevent users from uploading a
 | 
			
		||||
patch set to a change they do not own. By default, this permission is granted to
 | 
			
		||||
`Registered Users` on `refs/for/*` allowing all registered users to upload a new
 | 
			
		||||
patch set to any change on that ref.
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
[[category_push_direct]]
 | 
			
		||||
==== Direct Push
 | 
			
		||||
 
 | 
			
		||||
@@ -741,6 +741,12 @@ public abstract class AbstractDaemonTest {
 | 
			
		||||
 | 
			
		||||
  protected PermissionRule block(String permission, AccountGroup.UUID id, String ref)
 | 
			
		||||
      throws Exception {
 | 
			
		||||
    return block(permission, id, ref, project);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  protected PermissionRule block(String permission,
 | 
			
		||||
      AccountGroup.UUID id, String ref, Project.NameKey project)
 | 
			
		||||
      throws Exception {
 | 
			
		||||
    ProjectConfig cfg = projectCache.checkedGet(project).getConfig();
 | 
			
		||||
    PermissionRule rule = Util.block(cfg, permission, id, ref);
 | 
			
		||||
    saveProjectConfig(project, cfg);
 | 
			
		||||
 
 | 
			
		||||
@@ -66,6 +66,7 @@ import com.google.gerrit.reviewdb.client.AccountGroup;
 | 
			
		||||
import com.google.gerrit.reviewdb.client.Change;
 | 
			
		||||
import com.google.gerrit.reviewdb.client.PatchSet;
 | 
			
		||||
import com.google.gerrit.reviewdb.client.RefNames;
 | 
			
		||||
import com.google.gerrit.reviewdb.client.Project;
 | 
			
		||||
import com.google.gerrit.server.change.ChangeResource;
 | 
			
		||||
import com.google.gerrit.server.config.AnonymousCowardNameProvider;
 | 
			
		||||
import com.google.gerrit.server.git.ProjectConfig;
 | 
			
		||||
@@ -1506,6 +1507,7 @@ public class ChangeIT extends AbstractDaemonTest {
 | 
			
		||||
        db, admin.getIdent(), adminTestRepo);
 | 
			
		||||
    PushOneCommit.Result r1 = push.to("refs/for/master");
 | 
			
		||||
    r1.assertOkStatus();
 | 
			
		||||
 | 
			
		||||
    // Amend draft as admin
 | 
			
		||||
    PushOneCommit.Result r2 = amendChange(
 | 
			
		||||
        r1.getChangeId(), "refs/drafts/master", admin, adminTestRepo);
 | 
			
		||||
@@ -1518,10 +1520,154 @@ public class ChangeIT extends AbstractDaemonTest {
 | 
			
		||||
    // Amend change as user
 | 
			
		||||
    PushOneCommit.Result r3 = amendChange(
 | 
			
		||||
        r1.getChangeId(), "refs/for/master", user, userTestRepo);
 | 
			
		||||
    r3.assertErrorStatus("cannot replace "
 | 
			
		||||
    r3.assertErrorStatus("cannot add patch set to "
 | 
			
		||||
        + r3.getChange().change().getChangeId() + ".");
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @Test
 | 
			
		||||
  public void createNewPatchSetWithoutPermission() throws Exception {
 | 
			
		||||
    // Create new project with clean permissions
 | 
			
		||||
    Project.NameKey p = createProject("addPatchSet1");
 | 
			
		||||
 | 
			
		||||
    // Clone separate repositories of the same project as admin and as user
 | 
			
		||||
    TestRepository<InMemoryRepository> adminTestRepo =
 | 
			
		||||
        cloneProject(p, admin);
 | 
			
		||||
    TestRepository<InMemoryRepository> userTestRepo =
 | 
			
		||||
        cloneProject(p, user);
 | 
			
		||||
 | 
			
		||||
    // Block default permission
 | 
			
		||||
    block(Permission.ADD_PATCH_SET,
 | 
			
		||||
        REGISTERED_USERS, "refs/for/*", p);
 | 
			
		||||
 | 
			
		||||
    // Create change as admin
 | 
			
		||||
    PushOneCommit push = pushFactory.create(
 | 
			
		||||
        db, admin.getIdent(), adminTestRepo);
 | 
			
		||||
    PushOneCommit.Result r1 = push.to("refs/for/master");
 | 
			
		||||
    r1.assertOkStatus();
 | 
			
		||||
 | 
			
		||||
    // Fetch change
 | 
			
		||||
    GitUtil.fetch(userTestRepo, r1.getPatchSet().getRefName() + ":ps");
 | 
			
		||||
    userTestRepo.reset("ps");
 | 
			
		||||
 | 
			
		||||
    // Amend change as user
 | 
			
		||||
    PushOneCommit.Result r2 =
 | 
			
		||||
        amendChange(r1.getChangeId(), "refs/for/master", user, userTestRepo);
 | 
			
		||||
    r2.assertErrorStatus("cannot add patch set to "
 | 
			
		||||
        + r1.getChange().getId().id + ".");
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @Test
 | 
			
		||||
  public void createNewSetPatchWithPermission() throws Exception {
 | 
			
		||||
    // Clone separate repositories of the same project as admin and as user
 | 
			
		||||
    TestRepository<?> adminTestRepo = cloneProject(project, admin);
 | 
			
		||||
    TestRepository<?> userTestRepo = cloneProject(project, user);
 | 
			
		||||
 | 
			
		||||
    // Create change as admin
 | 
			
		||||
    PushOneCommit push = pushFactory.create(
 | 
			
		||||
        db, admin.getIdent(), adminTestRepo);
 | 
			
		||||
    PushOneCommit.Result r1 = push.to("refs/for/master");
 | 
			
		||||
    r1.assertOkStatus();
 | 
			
		||||
 | 
			
		||||
    // Fetch change
 | 
			
		||||
    GitUtil.fetch(userTestRepo, r1.getPatchSet().getRefName() + ":ps");
 | 
			
		||||
    userTestRepo.reset("ps");
 | 
			
		||||
 | 
			
		||||
    // Amend change as user
 | 
			
		||||
    PushOneCommit.Result r2 = amendChange(
 | 
			
		||||
        r1.getChangeId(), "refs/for/master", user, userTestRepo);
 | 
			
		||||
    r2.assertOkStatus();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @Test
 | 
			
		||||
  public void createNewPatchSetAsOwnerWithoutPermission() throws Exception {
 | 
			
		||||
    // Create new project with clean permissions
 | 
			
		||||
    Project.NameKey p = createProject("addPatchSet2");
 | 
			
		||||
    // Clone separate repositories of the same project as admin and as user
 | 
			
		||||
    TestRepository<?> adminTestRepo = cloneProject(project, admin);
 | 
			
		||||
 | 
			
		||||
    // Block default permission
 | 
			
		||||
    block(Permission.ADD_PATCH_SET, REGISTERED_USERS, "refs/for/*", p);
 | 
			
		||||
 | 
			
		||||
    // Create change as admin
 | 
			
		||||
    PushOneCommit push =
 | 
			
		||||
        pushFactory.create(db, admin.getIdent(), adminTestRepo);
 | 
			
		||||
    PushOneCommit.Result r1 = push.to("refs/for/master");
 | 
			
		||||
    r1.assertOkStatus();
 | 
			
		||||
 | 
			
		||||
    // Fetch change
 | 
			
		||||
    GitUtil.fetch(adminTestRepo, r1.getPatchSet().getRefName() + ":ps");
 | 
			
		||||
    adminTestRepo.reset("ps");
 | 
			
		||||
 | 
			
		||||
    // Amend change as admin
 | 
			
		||||
    PushOneCommit.Result r2 = amendChange(
 | 
			
		||||
        r1.getChangeId(), "refs/for/master", admin, adminTestRepo);
 | 
			
		||||
    r2.assertOkStatus();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @Test
 | 
			
		||||
  public void createNewPatchSetAsReviewerOnDraftChange() throws Exception {
 | 
			
		||||
    // Clone separate repositories of the same project as admin and as user
 | 
			
		||||
    TestRepository<?> adminTestRepo = cloneProject(project, admin);
 | 
			
		||||
    TestRepository<?> userTestRepo = cloneProject(project, user);
 | 
			
		||||
 | 
			
		||||
    // Create change as admin
 | 
			
		||||
    PushOneCommit push = pushFactory.create(
 | 
			
		||||
        db, admin.getIdent(), adminTestRepo);
 | 
			
		||||
    PushOneCommit.Result r1 = push.to("refs/drafts/master");
 | 
			
		||||
    r1.assertOkStatus();
 | 
			
		||||
 | 
			
		||||
    // Add user as reviewer
 | 
			
		||||
    AddReviewerInput in = new AddReviewerInput();
 | 
			
		||||
    in.reviewer = user.email;
 | 
			
		||||
    gApi.changes()
 | 
			
		||||
        .id(r1.getChangeId())
 | 
			
		||||
        .addReviewer(in);
 | 
			
		||||
 | 
			
		||||
    // Fetch change
 | 
			
		||||
    GitUtil.fetch(userTestRepo, r1.getPatchSet().getRefName() + ":ps");
 | 
			
		||||
    userTestRepo.reset("ps");
 | 
			
		||||
 | 
			
		||||
    // Amend change as user
 | 
			
		||||
    PushOneCommit.Result r2 = amendChange(
 | 
			
		||||
        r1.getChangeId(), "refs/for/master", user, userTestRepo);
 | 
			
		||||
    r2.assertOkStatus();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @Test
 | 
			
		||||
  public void createNewDraftPatchSetOnDraftChange() throws Exception {
 | 
			
		||||
    // Create new project with clean permissions
 | 
			
		||||
    Project.NameKey p = createProject("addPatchSet4");
 | 
			
		||||
    // Clone separate repositories of the same project as admin and as user
 | 
			
		||||
    TestRepository<?> adminTestRepo = cloneProject(p, admin);
 | 
			
		||||
    TestRepository<?> userTestRepo = cloneProject(p, user);
 | 
			
		||||
 | 
			
		||||
    // Block default permission
 | 
			
		||||
    block(Permission.ADD_PATCH_SET, REGISTERED_USERS, "refs/for/*", p);
 | 
			
		||||
 | 
			
		||||
    // Create change as admin
 | 
			
		||||
    PushOneCommit push = pushFactory.create(
 | 
			
		||||
        db, admin.getIdent(), adminTestRepo);
 | 
			
		||||
    PushOneCommit.Result r1 = push.to("refs/drafts/master");
 | 
			
		||||
    r1.assertOkStatus();
 | 
			
		||||
 | 
			
		||||
    // Add user as reviewer
 | 
			
		||||
    AddReviewerInput in = new AddReviewerInput();
 | 
			
		||||
    in.reviewer = user.email;
 | 
			
		||||
    gApi.changes()
 | 
			
		||||
        .id(r1.getChangeId())
 | 
			
		||||
        .addReviewer(in);
 | 
			
		||||
 | 
			
		||||
    // Fetch change
 | 
			
		||||
    GitUtil.fetch(userTestRepo, r1.getPatchSet().getRefName() + ":ps");
 | 
			
		||||
    userTestRepo.reset("ps");
 | 
			
		||||
 | 
			
		||||
    // Amend change as user
 | 
			
		||||
    PushOneCommit.Result r2 = amendChange(
 | 
			
		||||
        r1.getChangeId(), "refs/drafts/master", user, userTestRepo);
 | 
			
		||||
    r2.assertErrorStatus("cannot add patch set to "
 | 
			
		||||
        + r1.getChange().getId().id + ".");
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private static Iterable<Account.Id> getReviewers(
 | 
			
		||||
      Collection<AccountInfo> r) {
 | 
			
		||||
    return Iterables.transform(r, new Function<AccountInfo, Account.Id>() {
 | 
			
		||||
 
 | 
			
		||||
@@ -16,6 +16,7 @@ package com.google.gerrit.acceptance.edit;
 | 
			
		||||
 | 
			
		||||
import static com.google.common.collect.Iterables.getOnlyElement;
 | 
			
		||||
import static com.google.common.truth.Truth.assertThat;
 | 
			
		||||
import static com.google.gerrit.server.group.SystemGroupBackend.REGISTERED_USERS;
 | 
			
		||||
import static java.nio.charset.StandardCharsets.UTF_8;
 | 
			
		||||
import static java.util.concurrent.TimeUnit.SECONDS;
 | 
			
		||||
 | 
			
		||||
@@ -29,6 +30,7 @@ import com.google.gerrit.acceptance.RestResponse;
 | 
			
		||||
import com.google.gerrit.acceptance.TestProjectInput;
 | 
			
		||||
import com.google.gerrit.common.RawInputUtil;
 | 
			
		||||
import com.google.gerrit.common.data.LabelType;
 | 
			
		||||
import com.google.gerrit.common.data.Permission;
 | 
			
		||||
import com.google.gerrit.extensions.api.changes.ReviewInput;
 | 
			
		||||
import com.google.gerrit.extensions.client.InheritableBoolean;
 | 
			
		||||
import com.google.gerrit.extensions.client.ListChangesOption;
 | 
			
		||||
@@ -42,6 +44,7 @@ import com.google.gerrit.extensions.restapi.BinaryResult;
 | 
			
		||||
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
 | 
			
		||||
import com.google.gerrit.reviewdb.client.Change;
 | 
			
		||||
import com.google.gerrit.reviewdb.client.PatchSet;
 | 
			
		||||
import com.google.gerrit.reviewdb.client.Project;
 | 
			
		||||
import com.google.gerrit.reviewdb.server.ReviewDb;
 | 
			
		||||
import com.google.gerrit.server.change.ChangeEdits.EditMessage;
 | 
			
		||||
import com.google.gerrit.server.change.ChangeEdits.Post;
 | 
			
		||||
@@ -61,6 +64,8 @@ import com.google.gwtorm.server.SchemaFactory;
 | 
			
		||||
import com.google.inject.Inject;
 | 
			
		||||
 | 
			
		||||
import org.apache.commons.codec.binary.StringUtils;
 | 
			
		||||
import org.eclipse.jgit.internal.storage.dfs.InMemoryRepository;
 | 
			
		||||
import org.eclipse.jgit.junit.TestRepository;
 | 
			
		||||
import org.eclipse.jgit.lib.ObjectId;
 | 
			
		||||
import org.eclipse.jgit.lib.PersonIdent;
 | 
			
		||||
import org.eclipse.jgit.lib.RefUpdate;
 | 
			
		||||
@@ -768,6 +773,28 @@ public class ChangeEditIT extends AbstractDaemonTest {
 | 
			
		||||
    assertThat(diff.diffHeader.get(0)).contains(FILE_NAME);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @Test
 | 
			
		||||
  public void createEditWithoutPushPatchSetPermission() throws Exception {
 | 
			
		||||
    // Create new project with clean permissions
 | 
			
		||||
    Project.NameKey p = createProject("addPatchSetEdit");
 | 
			
		||||
    // Clone repository as user
 | 
			
		||||
    TestRepository<InMemoryRepository> userTestRepo =
 | 
			
		||||
        cloneProject(p, user);
 | 
			
		||||
 | 
			
		||||
    // Block default permission
 | 
			
		||||
    block(Permission.ADD_PATCH_SET, REGISTERED_USERS, "refs/for/*", p);
 | 
			
		||||
 | 
			
		||||
    // Create change as user
 | 
			
		||||
    PushOneCommit push = pushFactory.create(
 | 
			
		||||
        db, user.getIdent(), userTestRepo);
 | 
			
		||||
    PushOneCommit.Result r1 = push.to("refs/for/master");
 | 
			
		||||
    r1.assertOkStatus();
 | 
			
		||||
 | 
			
		||||
    // Try to create edit as admin
 | 
			
		||||
    assertThat(modifier.createEdit(r1.getChange().change(),
 | 
			
		||||
        r1.getPatchSet())).isEqualTo(RefUpdate.Result.REJECTED);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private List<ChangeInfo> queryEdits() throws Exception {
 | 
			
		||||
    return query("project:{" + project.get() + "} has:edit");
 | 
			
		||||
  }
 | 
			
		||||
 
 | 
			
		||||
@@ -429,7 +429,8 @@ public abstract class AbstractPushForReview extends AbstractDaemonTest {
 | 
			
		||||
        PushOneCommit.SUBJECT, "b.txt", "anotherContent", r.getChangeId());
 | 
			
		||||
    revision(r).review(new ReviewInput().label("Patch-Set-Lock", 1));
 | 
			
		||||
    r = push.to("refs/for/master");
 | 
			
		||||
    r.assertErrorStatus("cannot replace " + r.getChange().change().getChangeId()
 | 
			
		||||
    r.assertErrorStatus("cannot add patch set to "
 | 
			
		||||
        + r.getChange().change().getChangeId()
 | 
			
		||||
        + ". Change is patch set locked.");
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -241,7 +241,6 @@ public class VisibleRefFilterIT extends AbstractDaemonTest {
 | 
			
		||||
      setApiUser(admin);
 | 
			
		||||
      editModifier.createEdit(c, ps1);
 | 
			
		||||
      setApiUser(user);
 | 
			
		||||
      editModifier.createEdit(c, ps1);
 | 
			
		||||
 | 
			
		||||
      assertRefs(
 | 
			
		||||
          // Change 1 is visible due to accessDatabase capability, even though
 | 
			
		||||
@@ -255,8 +254,7 @@ public class VisibleRefFilterIT extends AbstractDaemonTest {
 | 
			
		||||
          // See comment in subsetOfBranchesVisibleNotIncludingHead.
 | 
			
		||||
          "refs/tags/master-tag",
 | 
			
		||||
          // All edits are visible due to accessDatabase capability.
 | 
			
		||||
          "refs/users/00/1000000/edit-" + c1.get() + "/1",
 | 
			
		||||
          "refs/users/01/1000001/edit-" + c1.get() + "/1");
 | 
			
		||||
          "refs/users/00/1000000/edit-" + c1.get() + "/1");
 | 
			
		||||
    } finally {
 | 
			
		||||
      removeGlobalCapabilities(REGISTERED_USERS, GlobalCapability.ACCESS_DATABASE);
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -120,6 +120,7 @@ addPermission = Add Permission ...
 | 
			
		||||
# Permission Names
 | 
			
		||||
permissionNames = \
 | 
			
		||||
	abandon, \
 | 
			
		||||
	addPatchSet, \
 | 
			
		||||
	create, \
 | 
			
		||||
	deleteDrafts, \
 | 
			
		||||
	editHashtags, \
 | 
			
		||||
@@ -141,6 +142,7 @@ permissionNames = \
 | 
			
		||||
	viewDrafts
 | 
			
		||||
 | 
			
		||||
abandon = Abandon
 | 
			
		||||
addPatchSet = Add Patch Set
 | 
			
		||||
create = Create Reference
 | 
			
		||||
deleteDrafts = Delete Drafts
 | 
			
		||||
editHashtags = Edit Hashtags
 | 
			
		||||
 
 | 
			
		||||
@@ -20,6 +20,7 @@ import static com.google.gerrit.server.notedb.ReviewerStateInternal.CC;
 | 
			
		||||
import static com.google.gerrit.server.notedb.ReviewerStateInternal.REVIEWER;
 | 
			
		||||
 | 
			
		||||
import com.google.gerrit.extensions.api.changes.NotifyHandling;
 | 
			
		||||
import com.google.gerrit.extensions.restapi.AuthException;
 | 
			
		||||
import com.google.gerrit.extensions.restapi.ResourceConflictException;
 | 
			
		||||
import com.google.gerrit.reviewdb.client.Change;
 | 
			
		||||
import com.google.gerrit.reviewdb.client.ChangeMessage;
 | 
			
		||||
@@ -196,7 +197,7 @@ public class PatchSetInserter extends BatchUpdate.Op {
 | 
			
		||||
 | 
			
		||||
  @Override
 | 
			
		||||
  public void updateRepo(RepoContext ctx)
 | 
			
		||||
      throws ResourceConflictException, IOException {
 | 
			
		||||
      throws AuthException, ResourceConflictException, IOException, OrmException {
 | 
			
		||||
    init();
 | 
			
		||||
    validate(ctx);
 | 
			
		||||
    ctx.addRefUpdate(new ReceiveCommand(ObjectId.zeroId(),
 | 
			
		||||
@@ -288,10 +289,15 @@ public class PatchSetInserter extends BatchUpdate.Op {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private void validate(RepoContext ctx)
 | 
			
		||||
      throws ResourceConflictException, IOException {
 | 
			
		||||
      throws AuthException, ResourceConflictException, IOException,
 | 
			
		||||
      OrmException {
 | 
			
		||||
    CommitValidators cv = commitValidatorsFactory.create(
 | 
			
		||||
        origCtl.getRefControl(), sshInfo, ctx.getRepository());
 | 
			
		||||
 | 
			
		||||
    if (!origCtl.canAddPatchSet(ctx.getDb())) {
 | 
			
		||||
      throw new AuthException("cannot add patch set");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    String refName = getPatchSetId().toRefName();
 | 
			
		||||
    CommitReceivedEvent event = new CommitReceivedEvent(
 | 
			
		||||
        new ReceiveCommand(
 | 
			
		||||
 
 | 
			
		||||
@@ -35,7 +35,9 @@ import com.google.gerrit.server.GerritPersonIdent;
 | 
			
		||||
import com.google.gerrit.server.IdentifiedUser;
 | 
			
		||||
import com.google.gerrit.server.git.GitRepositoryManager;
 | 
			
		||||
import com.google.gerrit.server.index.change.ChangeIndexer;
 | 
			
		||||
import com.google.gerrit.server.project.ChangeControl;
 | 
			
		||||
import com.google.gerrit.server.project.InvalidChangeOperationException;
 | 
			
		||||
import com.google.gerrit.server.project.NoSuchChangeException;
 | 
			
		||||
import com.google.gwtorm.server.OrmException;
 | 
			
		||||
import com.google.inject.Inject;
 | 
			
		||||
import com.google.inject.Provider;
 | 
			
		||||
@@ -96,18 +98,21 @@ public class ChangeEditModifier {
 | 
			
		||||
  private final ChangeIndexer indexer;
 | 
			
		||||
  private final Provider<ReviewDb> reviewDb;
 | 
			
		||||
  private final Provider<CurrentUser> currentUser;
 | 
			
		||||
  private final ChangeControl.GenericFactory changeControlFactory;
 | 
			
		||||
 | 
			
		||||
  @Inject
 | 
			
		||||
  ChangeEditModifier(@GerritPersonIdent PersonIdent gerritIdent,
 | 
			
		||||
      GitRepositoryManager gitManager,
 | 
			
		||||
      ChangeIndexer indexer,
 | 
			
		||||
      Provider<ReviewDb> reviewDb,
 | 
			
		||||
      Provider<CurrentUser> currentUser) {
 | 
			
		||||
      Provider<CurrentUser> currentUser,
 | 
			
		||||
      ChangeControl.GenericFactory changeControlFactory) {
 | 
			
		||||
    this.gitManager = gitManager;
 | 
			
		||||
    this.indexer = indexer;
 | 
			
		||||
    this.reviewDb = reviewDb;
 | 
			
		||||
    this.currentUser = currentUser;
 | 
			
		||||
    this.tz = gerritIdent.getTimeZone();
 | 
			
		||||
    this.changeControlFactory = changeControlFactory;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
@@ -127,10 +132,19 @@ public class ChangeEditModifier {
 | 
			
		||||
    if (!currentUser.get().isIdentifiedUser()) {
 | 
			
		||||
      throw new AuthException("Authentication required");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    IdentifiedUser me = currentUser.get().asIdentifiedUser();
 | 
			
		||||
    String refPrefix = RefNames.refsEditPrefix(me.getAccountId(), change.getId());
 | 
			
		||||
 | 
			
		||||
    try {
 | 
			
		||||
      ChangeControl c =
 | 
			
		||||
          changeControlFactory.controlFor(reviewDb.get(), change, me);
 | 
			
		||||
      if (!c.canAddPatchSet(reviewDb.get())) {
 | 
			
		||||
        return RefUpdate.Result.REJECTED;
 | 
			
		||||
      }
 | 
			
		||||
    } catch (NoSuchChangeException e) {
 | 
			
		||||
      return RefUpdate.Result.NO_CHANGE;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    try (Repository repo = gitManager.openRepository(change.getProject())) {
 | 
			
		||||
      Map<String, Ref> refs = repo.getRefDatabase().getRefs(refPrefix);
 | 
			
		||||
      if (!refs.isEmpty()) {
 | 
			
		||||
 
 | 
			
		||||
@@ -2016,7 +2016,8 @@ public class ReceiveCommits {
 | 
			
		||||
        if (changeCtl.isPatchSetLocked(db)) {
 | 
			
		||||
          locked = ". Change is patch set locked.";
 | 
			
		||||
        }
 | 
			
		||||
        reject(inputCommand, "cannot replace " + ontoChange + locked);
 | 
			
		||||
        reject(inputCommand, "cannot add patch set to "
 | 
			
		||||
            + ontoChange + locked);
 | 
			
		||||
        return false;
 | 
			
		||||
      } else if (notes.getChange().getStatus().isClosed()) {
 | 
			
		||||
        reject(inputCommand, "change " + ontoChange + " closed");
 | 
			
		||||
 
 | 
			
		||||
@@ -314,11 +314,16 @@ public class ChangeControl {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /** Can this user add a patch set to this change? */
 | 
			
		||||
  public boolean canAddPatchSet(ReviewDb db)
 | 
			
		||||
      throws OrmException {
 | 
			
		||||
    return getRefControl().canUpload()
 | 
			
		||||
        && !isPatchSetLocked(db)
 | 
			
		||||
        && isPatchVisible(patchSetUtil.current(db, notes), db);
 | 
			
		||||
  public boolean canAddPatchSet(ReviewDb db) throws OrmException {
 | 
			
		||||
    if (!getRefControl().canUpload()
 | 
			
		||||
        || isPatchSetLocked(db)
 | 
			
		||||
        || !isPatchVisible(patchSetUtil.current(db, notes), db)) {
 | 
			
		||||
      return false;
 | 
			
		||||
    }
 | 
			
		||||
    if (isOwner()) {
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    return getRefControl().canAddPatchSet();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /** Is the current patch set locked against state changes? */
 | 
			
		||||
 
 | 
			
		||||
@@ -150,6 +150,13 @@ public class RefControl {
 | 
			
		||||
        && canWrite();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /** @return true if this user can add a new patch set to this ref */
 | 
			
		||||
  public boolean canAddPatchSet() {
 | 
			
		||||
    return projectControl.controlForRef("refs/for/" + getRefName())
 | 
			
		||||
        .canPerform(Permission.ADD_PATCH_SET)
 | 
			
		||||
        && canWrite();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /** @return true if this user can submit merge patch sets to this ref */
 | 
			
		||||
  public boolean canUploadMerges() {
 | 
			
		||||
    return projectControl.controlForRef("refs/for/" + getRefName())
 | 
			
		||||
 
 | 
			
		||||
@@ -138,10 +138,12 @@ public class AllProjectsCreator {
 | 
			
		||||
      AccessSection heads = config.getAccessSection(AccessSection.HEADS, true);
 | 
			
		||||
      AccessSection tags = config.getAccessSection("refs/tags/*", true);
 | 
			
		||||
      AccessSection meta = config.getAccessSection(RefNames.REFS_CONFIG, true);
 | 
			
		||||
      AccessSection refsFor = config.getAccessSection("refs/for/*", true);
 | 
			
		||||
      AccessSection magic = config.getAccessSection("refs/for/" + AccessSection.ALL, true);
 | 
			
		||||
 | 
			
		||||
      grant(config, cap, GlobalCapability.ADMINISTRATE_SERVER, admin);
 | 
			
		||||
      grant(config, all, Permission.READ, admin, anonymous);
 | 
			
		||||
      grant(config, refsFor, Permission.ADD_PATCH_SET, registered);
 | 
			
		||||
 | 
			
		||||
      if (batch != null) {
 | 
			
		||||
        Permission priority = cap.getPermission(GlobalCapability.PRIORITY, true);
 | 
			
		||||
 
 | 
			
		||||
@@ -33,7 +33,7 @@ import java.util.List;
 | 
			
		||||
/** A version of the database schema. */
 | 
			
		||||
public abstract class SchemaVersion {
 | 
			
		||||
  /** The current schema version. */
 | 
			
		||||
  public static final Class<Schema_127> C = Schema_127.class;
 | 
			
		||||
  public static final Class<Schema_128> C = Schema_128.class;
 | 
			
		||||
 | 
			
		||||
  public static int getBinaryVersion() {
 | 
			
		||||
    return guessVersion(C);
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,80 @@
 | 
			
		||||
// Copyright (C) 2016 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.schema;
 | 
			
		||||
 | 
			
		||||
import static com.google.gerrit.server.group.SystemGroupBackend.REGISTERED_USERS;
 | 
			
		||||
import static com.google.gerrit.server.schema.AclUtil.grant;
 | 
			
		||||
 | 
			
		||||
import com.google.gerrit.common.data.AccessSection;
 | 
			
		||||
import com.google.gerrit.common.data.GroupReference;
 | 
			
		||||
import com.google.gerrit.common.data.Permission;
 | 
			
		||||
import com.google.gerrit.reviewdb.server.ReviewDb;
 | 
			
		||||
import com.google.gerrit.server.GerritPersonIdent;
 | 
			
		||||
import com.google.gerrit.server.config.AllProjectsName;
 | 
			
		||||
import com.google.gerrit.server.extensions.events.GitReferenceUpdated;
 | 
			
		||||
import com.google.gerrit.server.git.GitRepositoryManager;
 | 
			
		||||
import com.google.gerrit.server.git.MetaDataUpdate;
 | 
			
		||||
import com.google.gerrit.server.git.ProjectConfig;
 | 
			
		||||
import com.google.gerrit.server.group.SystemGroupBackend;
 | 
			
		||||
import com.google.gwtorm.server.OrmException;
 | 
			
		||||
import com.google.inject.Inject;
 | 
			
		||||
import com.google.inject.Provider;
 | 
			
		||||
 | 
			
		||||
import org.eclipse.jgit.errors.ConfigInvalidException;
 | 
			
		||||
import org.eclipse.jgit.lib.PersonIdent;
 | 
			
		||||
import org.eclipse.jgit.lib.Repository;
 | 
			
		||||
 | 
			
		||||
import java.io.IOException;
 | 
			
		||||
 | 
			
		||||
public class Schema_128 extends SchemaVersion {
 | 
			
		||||
  private static final String COMMIT_MSG =
 | 
			
		||||
      "Add addPatchSet permission to all projects";
 | 
			
		||||
 | 
			
		||||
  private final GitRepositoryManager repoManager;
 | 
			
		||||
  private final AllProjectsName allProjectsName;
 | 
			
		||||
  private final PersonIdent serverUser;
 | 
			
		||||
 | 
			
		||||
  @Inject
 | 
			
		||||
  Schema_128(Provider<Schema_126> prior,
 | 
			
		||||
      GitRepositoryManager repoManager,
 | 
			
		||||
      AllProjectsName allProjectsName,
 | 
			
		||||
      @GerritPersonIdent PersonIdent serverUser) {
 | 
			
		||||
    super(prior);
 | 
			
		||||
    this.repoManager = repoManager;
 | 
			
		||||
    this.allProjectsName = allProjectsName;
 | 
			
		||||
    this.serverUser = serverUser;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @Override
 | 
			
		||||
  protected void migrateData(ReviewDb db, UpdateUI ui) throws OrmException {
 | 
			
		||||
    try (Repository git = repoManager.openRepository(allProjectsName);
 | 
			
		||||
        MetaDataUpdate md = new MetaDataUpdate(GitReferenceUpdated.DISABLED,
 | 
			
		||||
            allProjectsName, git)) {
 | 
			
		||||
      ProjectConfig config = ProjectConfig.read(md);
 | 
			
		||||
 | 
			
		||||
      GroupReference registered = SystemGroupBackend.getGroup(REGISTERED_USERS);
 | 
			
		||||
      AccessSection refsFor = config.getAccessSection("refs/for/*", true);
 | 
			
		||||
      grant(config, refsFor, Permission.ADD_PATCH_SET,
 | 
			
		||||
          false, false, registered);
 | 
			
		||||
 | 
			
		||||
      md.getCommitBuilder().setAuthor(serverUser);
 | 
			
		||||
      md.getCommitBuilder().setCommitter(serverUser);
 | 
			
		||||
      md.setMessage(COMMIT_MSG);
 | 
			
		||||
      config.commit(md);
 | 
			
		||||
    } catch (ConfigInvalidException | IOException ex) {
 | 
			
		||||
      throw new OrmException(ex);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
		Reference in New Issue
	
	Block a user