All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.google.gerrit.server.project.ChangeControl Maven / Gradle / Ivy

There is a newer version: 3.10.0-rc4
Show newest version
// Copyright (C) 2009 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 static com.google.common.base.Preconditions.checkArgument;

import com.google.common.collect.Lists;
import com.google.gerrit.common.Nullable;
import com.google.gerrit.common.data.LabelType;
import com.google.gerrit.common.data.LabelTypes;
import com.google.gerrit.common.data.PermissionRange;
import com.google.gerrit.common.data.RefConfigSection;
import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.reviewdb.client.PatchSet;
import com.google.gerrit.reviewdb.client.PatchSetApproval;
import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.ApprovalsUtil;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.PatchSetUtil;
import com.google.gerrit.server.notedb.ChangeNotes;
import com.google.gerrit.server.query.change.ChangeData;
import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
import com.google.inject.Singleton;
import java.io.IOException;
import java.util.Collection;
import java.util.List;

/** Access control management for a user accessing a single change. */
public class ChangeControl {
  @Singleton
  public static class GenericFactory {
    private final ProjectControl.GenericFactory projectControl;
    private final ChangeNotes.Factory notesFactory;

    @Inject
    GenericFactory(ProjectControl.GenericFactory p, ChangeNotes.Factory n) {
      projectControl = p;
      notesFactory = n;
    }

    public ChangeControl controlFor(
        ReviewDb db, Project.NameKey project, Change.Id changeId, CurrentUser user)
        throws OrmException {
      return controlFor(notesFactory.create(db, project, changeId), user);
    }

    public ChangeControl controlFor(ReviewDb db, Change change, CurrentUser user)
        throws OrmException {
      final Project.NameKey projectKey = change.getProject();
      try {
        return projectControl.controlFor(projectKey, user).controlFor(db, change);
      } catch (NoSuchProjectException e) {
        throw new NoSuchChangeException(change.getId(), e);
      } catch (IOException e) {
        // TODO: propagate this exception
        throw new NoSuchChangeException(change.getId(), e);
      }
    }

    public ChangeControl controlFor(ChangeNotes notes, CurrentUser user)
        throws NoSuchChangeException {
      try {
        return projectControl.controlFor(notes.getProjectName(), user).controlFor(notes);
      } catch (NoSuchProjectException | IOException e) {
        throw new NoSuchChangeException(notes.getChangeId(), e);
      }
    }

    public ChangeControl validateFor(ReviewDb db, Change.Id changeId, CurrentUser user)
        throws OrmException {
      return validateFor(db, notesFactory.createChecked(changeId), user);
    }

    public ChangeControl validateFor(ReviewDb db, ChangeNotes notes, CurrentUser user)
        throws OrmException {
      ChangeControl c = controlFor(notes, user);
      if (!c.isVisible(db)) {
        throw new NoSuchChangeException(c.getId());
      }
      return c;
    }
  }

  @Singleton
  public static class Factory {
    private final ChangeData.Factory changeDataFactory;
    private final ChangeNotes.Factory notesFactory;
    private final ApprovalsUtil approvalsUtil;
    private final PatchSetUtil patchSetUtil;

    @Inject
    Factory(
        ChangeData.Factory changeDataFactory,
        ChangeNotes.Factory notesFactory,
        ApprovalsUtil approvalsUtil,
        PatchSetUtil patchSetUtil) {
      this.changeDataFactory = changeDataFactory;
      this.notesFactory = notesFactory;
      this.approvalsUtil = approvalsUtil;
      this.patchSetUtil = patchSetUtil;
    }

    ChangeControl create(
        RefControl refControl, ReviewDb db, Project.NameKey project, Change.Id changeId)
        throws OrmException {
      return create(refControl, notesFactory.create(db, project, changeId));
    }

    /**
     * Create a change control for a change that was loaded from index. This method should only be
     * used when database access is harmful and potentially stale data from the index is acceptable.
     *
     * @param refControl ref control
     * @param change change loaded from secondary index
     * @return change control
     */
    ChangeControl createForIndexedChange(RefControl refControl, Change change) {
      return create(refControl, notesFactory.createFromIndexedChange(change));
    }

    ChangeControl create(RefControl refControl, ChangeNotes notes) {
      return new ChangeControl(changeDataFactory, approvalsUtil, refControl, notes, patchSetUtil);
    }
  }

  private final ChangeData.Factory changeDataFactory;
  private final ApprovalsUtil approvalsUtil;
  private final RefControl refControl;
  private final ChangeNotes notes;
  private final PatchSetUtil patchSetUtil;

  ChangeControl(
      ChangeData.Factory changeDataFactory,
      ApprovalsUtil approvalsUtil,
      RefControl refControl,
      ChangeNotes notes,
      PatchSetUtil patchSetUtil) {
    this.changeDataFactory = changeDataFactory;
    this.approvalsUtil = approvalsUtil;
    this.refControl = refControl;
    this.notes = notes;
    this.patchSetUtil = patchSetUtil;
  }

  public ChangeControl forUser(final CurrentUser who) {
    if (getUser().equals(who)) {
      return this;
    }
    return new ChangeControl(
        changeDataFactory, approvalsUtil, getRefControl().forUser(who), notes, patchSetUtil);
  }

  public RefControl getRefControl() {
    return refControl;
  }

  public CurrentUser getUser() {
    return getRefControl().getUser();
  }

  public ProjectControl getProjectControl() {
    return getRefControl().getProjectControl();
  }

  public Project getProject() {
    return getProjectControl().getProject();
  }

  public Change.Id getId() {
    return notes.getChangeId();
  }

  public Change getChange() {
    return notes.getChange();
  }

  public ChangeNotes getNotes() {
    return notes;
  }

  /** Can this user see this change? */
  public boolean isVisible(ReviewDb db) throws OrmException {
    return isVisible(db, null);
  }

  /** Can this user see this change? */
  public boolean isVisible(ReviewDb db, @Nullable ChangeData cd) throws OrmException {
    if (getChange().getStatus() == Change.Status.DRAFT && !isDraftVisible(db, cd)) {
      return false;
    }
    return isRefVisible();
  }

  /** Can the user see this change? Does not account for draft status */
  public boolean isRefVisible() {
    return getRefControl().isVisible();
  }

  /** Can this user see the given patchset? */
  public boolean isPatchVisible(PatchSet ps, ReviewDb db) throws OrmException {
    if (ps != null && ps.isDraft() && !isDraftVisible(db, null)) {
      return false;
    }
    return isVisible(db);
  }

  /** Can this user see the given patchset? */
  public boolean isPatchVisible(PatchSet ps, ChangeData cd) throws OrmException {
    checkArgument(
        cd.getId().equals(ps.getId().getParentKey()), "%s not for change %s", ps, cd.getId());
    if (ps.isDraft() && !isDraftVisible(cd.db(), cd)) {
      return false;
    }
    return isVisible(cd.db());
  }

  /** Can this user abandon this change? */
  public boolean canAbandon(ReviewDb db) throws OrmException {
    return (isOwner() // owner (aka creator) of the change can abandon
            || getRefControl().isOwner() // branch owner can abandon
            || getProjectControl().isOwner() // project owner can abandon
            || getUser().getCapabilities().canAdministrateServer() // site administers are god
            || getRefControl().canAbandon() // user can abandon a specific ref
        )
        && !isPatchSetLocked(db);
  }

  /** Can this user change the destination branch of this change to the new ref? */
  public boolean canMoveTo(String ref, ReviewDb db) throws OrmException {
    return getProjectControl().controlForRef(ref).canUpload() && canAbandon(db);
  }

  /** Can this user publish this draft change or any draft patch set of this change? */
  public boolean canPublish(final ReviewDb db) throws OrmException {
    return (isOwner() || getRefControl().canPublishDrafts()) && isVisible(db);
  }

  /** Can this user delete this change or any patch set of this change? */
  public boolean canDelete(ReviewDb db, Change.Status status) throws OrmException {
    if (!isVisible(db)) {
      return false;
    }

    switch (status) {
      case DRAFT:
        return (isOwner() || getRefControl().canDeleteDrafts());
      case NEW:
      case ABANDONED:
        return (isAdmin() || getRefControl().canDeleteChanges(isOwner()));
      case MERGED:
      default:
        return false;
    }
  }

  /** Can this user rebase this change? */
  public boolean canRebase(ReviewDb db) throws OrmException {
    return (isOwner() || getRefControl().canSubmit(isOwner()) || getRefControl().canRebase())
        && !isPatchSetLocked(db);
  }

  /** Can this user restore this change? */
  public boolean canRestore(ReviewDb db) throws OrmException {
    return canAbandon(db) // Anyone who can abandon the change can restore it back
        && getRefControl().canUpload(); // as long as you can upload too
  }

  /** All available label types for this change. */
  public LabelTypes getLabelTypes() {
    String destBranch = getChange().getDest().get();
    List all = getProjectControl().getLabelTypes().getLabelTypes();

    List r = Lists.newArrayListWithCapacity(all.size());
    for (LabelType l : all) {
      List refs = l.getRefPatterns();
      if (refs == null) {
        r.add(l);
      } else {
        for (String refPattern : refs) {
          if (RefConfigSection.isValid(refPattern) && match(destBranch, refPattern)) {
            r.add(l);
            break;
          }
        }
      }
    }

    return new LabelTypes(r);
  }

  /** All value ranges of any allowed label permission. */
  public List getLabelRanges() {
    return getRefControl().getLabelRanges(isOwner());
  }

  /** The range of permitted values associated with a label permission. */
  public PermissionRange getRange(String permission) {
    return getRefControl().getRange(permission, isOwner());
  }

  /** Can this user add a patch set to this change? */
  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? */
  public boolean isPatchSetLocked(ReviewDb db) throws OrmException {
    if (getChange().getStatus() == Change.Status.MERGED) {
      return false;
    }

    for (PatchSetApproval ap :
        approvalsUtil.byPatchSet(db, this, getChange().currentPatchSetId())) {
      LabelType type = getLabelTypes().byLabel(ap.getLabel());
      if (type != null
          && ap.getValue() == 1
          && type.getFunctionName().equalsIgnoreCase("PatchSetLock")) {
        return true;
      }
    }
    return false;
  }

  /** Is this user the owner of the change? */
  public boolean isOwner() {
    if (getUser().isIdentifiedUser()) {
      Account.Id id = getUser().asIdentifiedUser().getAccountId();
      return id.equals(getChange().getOwner());
    }
    return false;
  }

  /** Is this user assigned to this change? */
  public boolean isAssignee() {
    Account.Id currentAssignee = notes.getChange().getAssignee();
    if (currentAssignee != null && getUser().isIdentifiedUser()) {
      Account.Id id = getUser().getAccountId();
      return id.equals(currentAssignee);
    }
    return false;
  }

  /** Is this user a reviewer for the change? */
  public boolean isReviewer(ReviewDb db) throws OrmException {
    return isReviewer(db, null);
  }

  /** Is this user a reviewer for the change? */
  public boolean isReviewer(ReviewDb db, @Nullable ChangeData cd) throws OrmException {
    if (getUser().isIdentifiedUser()) {
      Collection results = changeData(db, cd).reviewers().all();
      return results.contains(getUser().getAccountId());
    }
    return false;
  }

  public boolean isAdmin() {
    return getUser().getCapabilities().canAdministrateServer();
  }

  /** @return true if the user is allowed to remove this reviewer. */
  public boolean canRemoveReviewer(PatchSetApproval approval) {
    return canRemoveReviewer(approval.getAccountId(), approval.getValue());
  }

  public boolean canRemoveReviewer(Account.Id reviewer, int value) {
    if (getChange().getStatus().isOpen()) {
      // A user can always remove themselves.
      //
      if (getUser().isIdentifiedUser()) {
        if (getUser().getAccountId().equals(reviewer)) {
          return true; // can remove self
        }
      }

      // The change owner may remove any zero or positive score.
      //
      if (isOwner() && 0 <= value) {
        return true;
      }

      // Users with the remove reviewer permission, the branch owner, project
      // owner and site admin can remove anyone
      if (getRefControl().canRemoveReviewer() // has removal permissions
          || getRefControl().isOwner() // branch owner
          || getProjectControl().isOwner() // project owner
          || getUser().getCapabilities().canAdministrateServer()) {
        return true;
      }
    }

    return false;
  }

  /** Can this user edit the topic name? */
  public boolean canEditTopicName() {
    if (getChange().getStatus().isOpen()) {
      return isOwner() // owner (aka creator) of the change can edit topic
          || getRefControl().isOwner() // branch owner can edit topic
          || getProjectControl().isOwner() // project owner can edit topic
          || getUser().getCapabilities().canAdministrateServer() // site administers are god
          || getRefControl().canEditTopicName() // user can edit topic on a specific ref
      ;
    }
    return getRefControl().canForceEditTopicName();
  }

  /** Can this user edit the description? */
  public boolean canEditDescription() {
    if (getChange().getStatus().isOpen()) {
      return isOwner() // owner (aka creator) of the change can edit desc
          || getRefControl().isOwner() // branch owner can edit desc
          || getProjectControl().isOwner() // project owner can edit desc
          || getUser().getCapabilities().canAdministrateServer() // site administers are god
      ;
    }
    return false;
  }

  public boolean canEditAssignee() {
    return isOwner()
        || getProjectControl().isOwner()
        || getRefControl().canEditAssignee()
        || isAssignee();
  }

  /** Can this user edit the hashtag name? */
  public boolean canEditHashtags() {
    return isOwner() // owner (aka creator) of the change can edit hashtags
        || getRefControl().isOwner() // branch owner can edit hashtags
        || getProjectControl().isOwner() // project owner can edit hashtags
        || getUser().getCapabilities().canAdministrateServer() // site administers are god
        || getRefControl().canEditHashtags(); // user can edit hashtag on a specific ref
  }

  public boolean canSubmit() {
    return getRefControl().canSubmit(isOwner());
  }

  public boolean canSubmitAs() {
    return getRefControl().canSubmitAs();
  }

  private boolean match(String destBranch, String refPattern) {
    return RefPatternMatcher.getMatcher(refPattern).match(destBranch, getUser());
  }

  private ChangeData changeData(ReviewDb db, @Nullable ChangeData cd) {
    return cd != null ? cd : changeDataFactory.create(db, this);
  }

  public boolean isDraftVisible(ReviewDb db, ChangeData cd) throws OrmException {
    return isOwner()
        || isReviewer(db, cd)
        || getRefControl().canViewDrafts()
        || getUser().isInternalUser();
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy