Merge changes Ib468eb4b,I3dfd8f91
* changes: Unmark DETAILED_LABELS as requiring lazy load Obsolete PermissionBackend#indexedChange
This commit is contained in:
		@@ -140,7 +140,6 @@ public class ChangeJson {
 | 
				
			|||||||
          COMMIT_FOOTERS,
 | 
					          COMMIT_FOOTERS,
 | 
				
			||||||
          CURRENT_ACTIONS,
 | 
					          CURRENT_ACTIONS,
 | 
				
			||||||
          CURRENT_COMMIT,
 | 
					          CURRENT_COMMIT,
 | 
				
			||||||
          DETAILED_LABELS, // may need to load ChangeNotes to check remove reviewer permissions
 | 
					 | 
				
			||||||
          MESSAGES);
 | 
					          MESSAGES);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  @Singleton
 | 
					  @Singleton
 | 
				
			||||||
@@ -722,7 +721,10 @@ public class ChangeJson {
 | 
				
			|||||||
    // testRemoveReviewer check for a specific reviewer in the loop saving potentially many
 | 
					    // testRemoveReviewer check for a specific reviewer in the loop saving potentially many
 | 
				
			||||||
    // permission checks.
 | 
					    // permission checks.
 | 
				
			||||||
    boolean canRemoveAnyReviewer =
 | 
					    boolean canRemoveAnyReviewer =
 | 
				
			||||||
        permissionBackendForChange(userProvider.get(), cd).test(ChangePermission.REMOVE_REVIEWER);
 | 
					        permissionBackend
 | 
				
			||||||
 | 
					            .user(userProvider.get())
 | 
				
			||||||
 | 
					            .change(cd)
 | 
				
			||||||
 | 
					            .test(ChangePermission.REMOVE_REVIEWER);
 | 
				
			||||||
    for (LabelInfo label : labels) {
 | 
					    for (LabelInfo label : labels) {
 | 
				
			||||||
      if (label.all == null) {
 | 
					      if (label.all == null) {
 | 
				
			||||||
        continue;
 | 
					        continue;
 | 
				
			||||||
@@ -817,16 +819,4 @@ public class ChangeJson {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
    return map;
 | 
					    return map;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					 | 
				
			||||||
  /**
 | 
					 | 
				
			||||||
   * @return {@link com.google.gerrit.server.permissions.PermissionBackend.ForChange} constructed
 | 
					 | 
				
			||||||
   *     from either an index-backed or a database-backed {@link ChangeData} depending on {@code
 | 
					 | 
				
			||||||
   *     lazyload}.
 | 
					 | 
				
			||||||
   */
 | 
					 | 
				
			||||||
  private PermissionBackend.ForChange permissionBackendForChange(CurrentUser user, ChangeData cd) {
 | 
					 | 
				
			||||||
    PermissionBackend.WithUser withUser = permissionBackend.user(user);
 | 
					 | 
				
			||||||
    return lazyLoad
 | 
					 | 
				
			||||||
        ? withUser.change(cd)
 | 
					 | 
				
			||||||
        : withUser.indexedChange(cd, notesFactory.createFromIndexedChange(cd.change()));
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -131,7 +131,7 @@ public class LabelsJson {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    Map<String, Short> labels = null;
 | 
					    Map<String, Short> labels = null;
 | 
				
			||||||
    Set<LabelPermission.WithValue> can =
 | 
					    Set<LabelPermission.WithValue> can =
 | 
				
			||||||
        permissionBackendForChange(filterApprovalsBy, cd).testLabels(toCheck.values());
 | 
					        permissionBackend.absentUser(filterApprovalsBy).change(cd).testLabels(toCheck.values());
 | 
				
			||||||
    SetMultimap<String, String> permitted = LinkedHashMultimap.create();
 | 
					    SetMultimap<String, String> permitted = LinkedHashMultimap.create();
 | 
				
			||||||
    for (SubmitRecord rec : submitRecords(cd)) {
 | 
					    for (SubmitRecord rec : submitRecords(cd)) {
 | 
				
			||||||
      if (rec.labels == null) {
 | 
					      if (rec.labels == null) {
 | 
				
			||||||
@@ -452,7 +452,7 @@ public class LabelsJson {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    LabelTypes labelTypes = cd.getLabelTypes();
 | 
					    LabelTypes labelTypes = cd.getLabelTypes();
 | 
				
			||||||
    for (Account.Id accountId : allUsers) {
 | 
					    for (Account.Id accountId : allUsers) {
 | 
				
			||||||
      PermissionBackend.ForChange perm = permissionBackendForChange(accountId, cd);
 | 
					      PermissionBackend.ForChange perm = permissionBackend.absentUser(accountId).change(cd);
 | 
				
			||||||
      Map<String, VotingRangeInfo> pvr = getPermittedVotingRanges(permittedLabels(accountId, cd));
 | 
					      Map<String, VotingRangeInfo> pvr = getPermittedVotingRanges(permittedLabels(accountId, cd));
 | 
				
			||||||
      for (Map.Entry<String, LabelWithStatus> e : labels.entrySet()) {
 | 
					      for (Map.Entry<String, LabelWithStatus> e : labels.entrySet()) {
 | 
				
			||||||
        LabelType lt = labelTypes.byLabel(e.getKey());
 | 
					        LabelType lt = labelTypes.byLabel(e.getKey());
 | 
				
			||||||
@@ -492,18 +492,6 @@ public class LabelsJson {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  /**
 | 
					 | 
				
			||||||
   * @return {@link com.google.gerrit.server.permissions.PermissionBackend.ForChange} constructed
 | 
					 | 
				
			||||||
   *     from either an index-backed or a database-backed {@link ChangeData} depending on {@code
 | 
					 | 
				
			||||||
   *     lazyload}.
 | 
					 | 
				
			||||||
   */
 | 
					 | 
				
			||||||
  private PermissionBackend.ForChange permissionBackendForChange(Account.Id user, ChangeData cd) {
 | 
					 | 
				
			||||||
    PermissionBackend.WithUser withUser = permissionBackend.absentUser(user);
 | 
					 | 
				
			||||||
    return lazyLoad
 | 
					 | 
				
			||||||
        ? withUser.change(cd)
 | 
					 | 
				
			||||||
        : withUser.indexedChange(cd, notesFactory.createFromIndexedChange(cd.change()));
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  private List<SubmitRecord> submitRecords(ChangeData cd) {
 | 
					  private List<SubmitRecord> submitRecords(ChangeData cd) {
 | 
				
			||||||
    return cd.submitRecords(ChangeJson.SUBMIT_RULE_OPTIONS_LENIENT);
 | 
					    return cd.submitRecords(ChangeJson.SUBMIT_RULE_OPTIONS_LENIENT);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -60,7 +60,6 @@ import com.google.gerrit.server.account.AccountLoader;
 | 
				
			|||||||
import com.google.gerrit.server.account.GpgApiAdapter;
 | 
					import com.google.gerrit.server.account.GpgApiAdapter;
 | 
				
			||||||
import com.google.gerrit.server.git.GitRepositoryManager;
 | 
					import com.google.gerrit.server.git.GitRepositoryManager;
 | 
				
			||||||
import com.google.gerrit.server.git.MergeUtil;
 | 
					import com.google.gerrit.server.git.MergeUtil;
 | 
				
			||||||
import com.google.gerrit.server.notedb.ChangeNotes;
 | 
					 | 
				
			||||||
import com.google.gerrit.server.patch.PatchListNotAvailableException;
 | 
					import com.google.gerrit.server.patch.PatchListNotAvailableException;
 | 
				
			||||||
import com.google.gerrit.server.permissions.ChangePermission;
 | 
					import com.google.gerrit.server.permissions.ChangePermission;
 | 
				
			||||||
import com.google.gerrit.server.permissions.PermissionBackend;
 | 
					import com.google.gerrit.server.permissions.PermissionBackend;
 | 
				
			||||||
@@ -107,8 +106,6 @@ public class RevisionJson {
 | 
				
			|||||||
  private final AnonymousUser anonymous;
 | 
					  private final AnonymousUser anonymous;
 | 
				
			||||||
  private final GitRepositoryManager repoManager;
 | 
					  private final GitRepositoryManager repoManager;
 | 
				
			||||||
  private final PermissionBackend permissionBackend;
 | 
					  private final PermissionBackend permissionBackend;
 | 
				
			||||||
  private final ChangeNotes.Factory notesFactory;
 | 
					 | 
				
			||||||
  private final boolean lazyLoad;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
  @Inject
 | 
					  @Inject
 | 
				
			||||||
  RevisionJson(
 | 
					  RevisionJson(
 | 
				
			||||||
@@ -128,7 +125,6 @@ public class RevisionJson {
 | 
				
			|||||||
      ChangeKindCache changeKindCache,
 | 
					      ChangeKindCache changeKindCache,
 | 
				
			||||||
      GitRepositoryManager repoManager,
 | 
					      GitRepositoryManager repoManager,
 | 
				
			||||||
      PermissionBackend permissionBackend,
 | 
					      PermissionBackend permissionBackend,
 | 
				
			||||||
      ChangeNotes.Factory notesFactory,
 | 
					 | 
				
			||||||
      @Assisted Iterable<ListChangesOption> options) {
 | 
					      @Assisted Iterable<ListChangesOption> options) {
 | 
				
			||||||
    this.userProvider = userProvider;
 | 
					    this.userProvider = userProvider;
 | 
				
			||||||
    this.anonymous = anonymous;
 | 
					    this.anonymous = anonymous;
 | 
				
			||||||
@@ -145,10 +141,8 @@ public class RevisionJson {
 | 
				
			|||||||
    this.changeResourceFactory = changeResourceFactory;
 | 
					    this.changeResourceFactory = changeResourceFactory;
 | 
				
			||||||
    this.changeKindCache = changeKindCache;
 | 
					    this.changeKindCache = changeKindCache;
 | 
				
			||||||
    this.permissionBackend = permissionBackend;
 | 
					    this.permissionBackend = permissionBackend;
 | 
				
			||||||
    this.notesFactory = notesFactory;
 | 
					 | 
				
			||||||
    this.repoManager = repoManager;
 | 
					    this.repoManager = repoManager;
 | 
				
			||||||
    this.options = ImmutableSet.copyOf(options);
 | 
					    this.options = ImmutableSet.copyOf(options);
 | 
				
			||||||
    this.lazyLoad = containsAnyOf(this.options, ChangeJson.REQUIRE_LAZY_LOAD);
 | 
					 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  /**
 | 
					  /**
 | 
				
			||||||
@@ -346,22 +340,9 @@ public class RevisionJson {
 | 
				
			|||||||
    return options.contains(option);
 | 
					    return options.contains(option);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  /**
 | 
					 | 
				
			||||||
   * @return {@link com.google.gerrit.server.permissions.PermissionBackend.ForChange} constructed
 | 
					 | 
				
			||||||
   *     from either an index-backed or a database-backed {@link ChangeData} depending on {@code
 | 
					 | 
				
			||||||
   *     lazyload}.
 | 
					 | 
				
			||||||
   */
 | 
					 | 
				
			||||||
  private PermissionBackend.ForChange permissionBackendForChange(
 | 
					 | 
				
			||||||
      PermissionBackend.WithUser withUser, ChangeData cd) {
 | 
					 | 
				
			||||||
    return lazyLoad
 | 
					 | 
				
			||||||
        ? withUser.change(cd)
 | 
					 | 
				
			||||||
        : withUser.indexedChange(cd, notesFactory.createFromIndexedChange(cd.change()));
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  private boolean isWorldReadable(ChangeData cd) throws PermissionBackendException {
 | 
					  private boolean isWorldReadable(ChangeData cd) throws PermissionBackendException {
 | 
				
			||||||
    try {
 | 
					    try {
 | 
				
			||||||
      permissionBackendForChange(permissionBackend.user(anonymous), cd)
 | 
					      permissionBackend.user(anonymous).change(cd).check(ChangePermission.READ);
 | 
				
			||||||
          .check(ChangePermission.READ);
 | 
					 | 
				
			||||||
    } catch (AuthException ae) {
 | 
					    } catch (AuthException ae) {
 | 
				
			||||||
      return false;
 | 
					      return false;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -165,7 +165,7 @@ public class SearchingChangeCacheImpl implements GitReferenceUpdatedListener {
 | 
				
			|||||||
        List<CachedChange> result = new ArrayList<>(cds.size());
 | 
					        List<CachedChange> result = new ArrayList<>(cds.size());
 | 
				
			||||||
        for (ChangeData cd : cds) {
 | 
					        for (ChangeData cd : cds) {
 | 
				
			||||||
          result.add(
 | 
					          result.add(
 | 
				
			||||||
              new AutoValue_SearchingChangeCacheImpl_CachedChange(cd.change(), cd.getReviewers()));
 | 
					              new AutoValue_SearchingChangeCacheImpl_CachedChange(cd.change(), cd.reviewers()));
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        return Collections.unmodifiableList(result);
 | 
					        return Collections.unmodifiableList(result);
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -19,21 +19,16 @@ import static com.google.gerrit.server.permissions.LabelPermission.ForUser.ON_BE
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
import com.google.common.collect.Maps;
 | 
					import com.google.common.collect.Maps;
 | 
				
			||||||
import com.google.common.collect.Sets;
 | 
					import com.google.common.collect.Sets;
 | 
				
			||||||
import com.google.gerrit.common.Nullable;
 | 
					 | 
				
			||||||
import com.google.gerrit.common.data.Permission;
 | 
					import com.google.gerrit.common.data.Permission;
 | 
				
			||||||
import com.google.gerrit.common.data.PermissionRange;
 | 
					import com.google.gerrit.common.data.PermissionRange;
 | 
				
			||||||
import com.google.gerrit.entities.Account;
 | 
					import com.google.gerrit.entities.Account;
 | 
				
			||||||
import com.google.gerrit.entities.Change;
 | 
					import com.google.gerrit.entities.Change;
 | 
				
			||||||
import com.google.gerrit.entities.Project;
 | 
					 | 
				
			||||||
import com.google.gerrit.exceptions.StorageException;
 | 
					import com.google.gerrit.exceptions.StorageException;
 | 
				
			||||||
import com.google.gerrit.extensions.conditions.BooleanCondition;
 | 
					import com.google.gerrit.extensions.conditions.BooleanCondition;
 | 
				
			||||||
import com.google.gerrit.extensions.restapi.AuthException;
 | 
					import com.google.gerrit.extensions.restapi.AuthException;
 | 
				
			||||||
import com.google.gerrit.server.CurrentUser;
 | 
					import com.google.gerrit.server.CurrentUser;
 | 
				
			||||||
import com.google.gerrit.server.notedb.ChangeNotes;
 | 
					 | 
				
			||||||
import com.google.gerrit.server.permissions.PermissionBackend.ForChange;
 | 
					import com.google.gerrit.server.permissions.PermissionBackend.ForChange;
 | 
				
			||||||
import com.google.gerrit.server.query.change.ChangeData;
 | 
					import com.google.gerrit.server.query.change.ChangeData;
 | 
				
			||||||
import com.google.inject.Inject;
 | 
					 | 
				
			||||||
import com.google.inject.Singleton;
 | 
					 | 
				
			||||||
import java.util.Collection;
 | 
					import java.util.Collection;
 | 
				
			||||||
import java.util.EnumSet;
 | 
					import java.util.EnumSet;
 | 
				
			||||||
import java.util.Map;
 | 
					import java.util.Map;
 | 
				
			||||||
@@ -41,39 +36,16 @@ import java.util.Set;
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
/** Access control management for a user accessing a single change. */
 | 
					/** Access control management for a user accessing a single change. */
 | 
				
			||||||
class ChangeControl {
 | 
					class ChangeControl {
 | 
				
			||||||
  @Singleton
 | 
					 | 
				
			||||||
  static class Factory {
 | 
					 | 
				
			||||||
    private final ChangeData.Factory changeDataFactory;
 | 
					 | 
				
			||||||
    private final ChangeNotes.Factory notesFactory;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    @Inject
 | 
					 | 
				
			||||||
    Factory(ChangeData.Factory changeDataFactory, ChangeNotes.Factory notesFactory) {
 | 
					 | 
				
			||||||
      this.changeDataFactory = changeDataFactory;
 | 
					 | 
				
			||||||
      this.notesFactory = notesFactory;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    ChangeControl create(RefControl refControl, Project.NameKey project, Change.Id changeId) {
 | 
					 | 
				
			||||||
      return create(refControl, notesFactory.create(project, changeId));
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    ChangeControl create(RefControl refControl, ChangeNotes notes) {
 | 
					 | 
				
			||||||
      return new ChangeControl(changeDataFactory, refControl, notes);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  private final ChangeData.Factory changeDataFactory;
 | 
					 | 
				
			||||||
  private final RefControl refControl;
 | 
					  private final RefControl refControl;
 | 
				
			||||||
  private final ChangeNotes notes;
 | 
					  private final ChangeData changeData;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  private ChangeControl(
 | 
					  ChangeControl(RefControl refControl, ChangeData changeData) {
 | 
				
			||||||
      ChangeData.Factory changeDataFactory, RefControl refControl, ChangeNotes notes) {
 | 
					 | 
				
			||||||
    this.changeDataFactory = changeDataFactory;
 | 
					 | 
				
			||||||
    this.refControl = refControl;
 | 
					    this.refControl = refControl;
 | 
				
			||||||
    this.notes = notes;
 | 
					    this.changeData = changeData;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  ForChange asForChange(@Nullable ChangeData cd) {
 | 
					  ForChange asForChange() {
 | 
				
			||||||
    return new ForChangeImpl(cd);
 | 
					    return new ForChangeImpl();
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  private CurrentUser getUser() {
 | 
					  private CurrentUser getUser() {
 | 
				
			||||||
@@ -85,7 +57,7 @@ class ChangeControl {
 | 
				
			|||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  private Change getChange() {
 | 
					  private Change getChange() {
 | 
				
			||||||
    return notes.getChange();
 | 
					    return changeData.change();
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  /** Can this user see this change? */
 | 
					  /** Can this user see this change? */
 | 
				
			||||||
@@ -218,19 +190,13 @@ class ChangeControl {
 | 
				
			|||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  private class ForChangeImpl extends ForChange {
 | 
					  private class ForChangeImpl extends ForChange {
 | 
				
			||||||
    private ChangeData cd;
 | 
					 | 
				
			||||||
    private Map<String, PermissionRange> labels;
 | 
					    private Map<String, PermissionRange> labels;
 | 
				
			||||||
    private String resourcePath;
 | 
					    private String resourcePath;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    ForChangeImpl(@Nullable ChangeData cd) {
 | 
					    private ForChangeImpl() {}
 | 
				
			||||||
      this.cd = cd;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private ChangeData changeData() {
 | 
					    private ChangeData changeData() {
 | 
				
			||||||
      if (cd == null) {
 | 
					      return changeData;
 | 
				
			||||||
        cd = changeDataFactory.create(notes);
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
      return cd;
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @Override
 | 
					    @Override
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -31,7 +31,6 @@ public class DefaultPermissionBackendModule extends AbstractModule {
 | 
				
			|||||||
      // TODO(hiesel) Hide ProjectControl, RefControl, ChangeControl related bindings.
 | 
					      // TODO(hiesel) Hide ProjectControl, RefControl, ChangeControl related bindings.
 | 
				
			||||||
      factory(ProjectControl.Factory.class);
 | 
					      factory(ProjectControl.Factory.class);
 | 
				
			||||||
      factory(DefaultRefFilter.Factory.class);
 | 
					      factory(DefaultRefFilter.Factory.class);
 | 
				
			||||||
      bind(ChangeControl.Factory.class);
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -448,12 +448,11 @@ class DefaultRefFilter {
 | 
				
			|||||||
    try {
 | 
					    try {
 | 
				
			||||||
      Map<Change.Id, BranchNameKey> visibleChanges = new HashMap<>();
 | 
					      Map<Change.Id, BranchNameKey> visibleChanges = new HashMap<>();
 | 
				
			||||||
      for (ChangeData cd : changeCache.getChangeData(project)) {
 | 
					      for (ChangeData cd : changeCache.getChangeData(project)) {
 | 
				
			||||||
        ChangeNotes notes = changeNotesFactory.createFromIndexedChange(cd.change());
 | 
					 | 
				
			||||||
        if (!projectState.statePermitsRead()) {
 | 
					        if (!projectState.statePermitsRead()) {
 | 
				
			||||||
          continue;
 | 
					          continue;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        try {
 | 
					        try {
 | 
				
			||||||
          permissionBackendForProject.indexedChange(cd, notes).check(ChangePermission.READ);
 | 
					          permissionBackendForProject.change(cd).check(ChangePermission.READ);
 | 
				
			||||||
          visibleChanges.put(cd.getId(), cd.change().getDest());
 | 
					          visibleChanges.put(cd.getId(), cd.change().getDest());
 | 
				
			||||||
        } catch (AuthException e) {
 | 
					        } catch (AuthException e) {
 | 
				
			||||||
          // Do nothing.
 | 
					          // Do nothing.
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -172,11 +172,6 @@ public class FailedPermissionBackend {
 | 
				
			|||||||
      return new FailedChange(message, cause);
 | 
					      return new FailedChange(message, cause);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @Override
 | 
					 | 
				
			||||||
    public ForChange indexedChange(ChangeData cd, ChangeNotes notes) {
 | 
					 | 
				
			||||||
      return new FailedChange(message, cause);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    @Override
 | 
					    @Override
 | 
				
			||||||
    public void check(RefPermission perm) throws PermissionBackendException {
 | 
					    public void check(RefPermission perm) throws PermissionBackendException {
 | 
				
			||||||
      throw new PermissionBackendException(message, cause);
 | 
					      throw new PermissionBackendException(message, cause);
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -173,15 +173,6 @@ public abstract class PermissionBackend {
 | 
				
			|||||||
      return ref(notes.getChange().getDest()).change(notes);
 | 
					      return ref(notes.getChange().getDest()).change(notes);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * Returns an instance scoped for the change loaded from index, and its destination ref and
 | 
					 | 
				
			||||||
     * project. This method should only be used when database access is harmful and potentially
 | 
					 | 
				
			||||||
     * stale data from the index is acceptable.
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    public ForChange indexedChange(ChangeData cd, ChangeNotes notes) {
 | 
					 | 
				
			||||||
      return ref(notes.getChange().getDest()).indexedChange(cd, notes);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /** Verify scoped user can {@code perm}, throwing if denied. */
 | 
					    /** Verify scoped user can {@code perm}, throwing if denied. */
 | 
				
			||||||
    public abstract void check(GlobalOrPluginPermission perm)
 | 
					    public abstract void check(GlobalOrPluginPermission perm)
 | 
				
			||||||
        throws AuthException, PermissionBackendException;
 | 
					        throws AuthException, PermissionBackendException;
 | 
				
			||||||
@@ -289,15 +280,6 @@ public abstract class PermissionBackend {
 | 
				
			|||||||
      return ref(notes.getChange().getDest().branch()).change(notes);
 | 
					      return ref(notes.getChange().getDest().branch()).change(notes);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * Returns an instance scoped for the change loaded from index, and its destination ref and
 | 
					 | 
				
			||||||
     * project. This method should only be used when database access is harmful and potentially
 | 
					 | 
				
			||||||
     * stale data from the index is acceptable.
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    public ForChange indexedChange(ChangeData cd, ChangeNotes notes) {
 | 
					 | 
				
			||||||
      return ref(notes.getChange().getDest().branch()).indexedChange(cd, notes);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /** Verify scoped user can {@code perm}, throwing if denied. */
 | 
					    /** Verify scoped user can {@code perm}, throwing if denied. */
 | 
				
			||||||
    public abstract void check(CoreOrPluginProjectPermission perm)
 | 
					    public abstract void check(CoreOrPluginProjectPermission perm)
 | 
				
			||||||
        throws AuthException, PermissionBackendException;
 | 
					        throws AuthException, PermissionBackendException;
 | 
				
			||||||
@@ -386,12 +368,6 @@ public abstract class PermissionBackend {
 | 
				
			|||||||
    /** Returns an instance scoped to change. */
 | 
					    /** Returns an instance scoped to change. */
 | 
				
			||||||
    public abstract ForChange change(ChangeNotes notes);
 | 
					    public abstract ForChange change(ChangeNotes notes);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * @return instance scoped to change loaded from index. This method should only be used when
 | 
					 | 
				
			||||||
     *     database access is harmful and potentially stale data from the index is acceptable.
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    public abstract ForChange indexedChange(ChangeData cd, ChangeNotes notes);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /** Verify scoped user can {@code perm}, throwing if denied. */
 | 
					    /** Verify scoped user can {@code perm}, throwing if denied. */
 | 
				
			||||||
    public abstract void check(RefPermission perm) throws AuthException, PermissionBackendException;
 | 
					    public abstract void check(RefPermission perm) throws AuthException, PermissionBackendException;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -70,9 +70,9 @@ class ProjectControl {
 | 
				
			|||||||
  private final PermissionBackend permissionBackend;
 | 
					  private final PermissionBackend permissionBackend;
 | 
				
			||||||
  private final CurrentUser user;
 | 
					  private final CurrentUser user;
 | 
				
			||||||
  private final ProjectState state;
 | 
					  private final ProjectState state;
 | 
				
			||||||
  private final ChangeControl.Factory changeControlFactory;
 | 
					 | 
				
			||||||
  private final PermissionCollection.Factory permissionFilter;
 | 
					  private final PermissionCollection.Factory permissionFilter;
 | 
				
			||||||
  private final DefaultRefFilter.Factory refFilterFactory;
 | 
					  private final DefaultRefFilter.Factory refFilterFactory;
 | 
				
			||||||
 | 
					  private final ChangeData.Factory changeDataFactory;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  private List<SectionMatcher> allSections;
 | 
					  private List<SectionMatcher> allSections;
 | 
				
			||||||
  private Map<String, RefControl> refControls;
 | 
					  private Map<String, RefControl> refControls;
 | 
				
			||||||
@@ -83,17 +83,17 @@ class ProjectControl {
 | 
				
			|||||||
      @GitUploadPackGroups Set<AccountGroup.UUID> uploadGroups,
 | 
					      @GitUploadPackGroups Set<AccountGroup.UUID> uploadGroups,
 | 
				
			||||||
      @GitReceivePackGroups Set<AccountGroup.UUID> receiveGroups,
 | 
					      @GitReceivePackGroups Set<AccountGroup.UUID> receiveGroups,
 | 
				
			||||||
      PermissionCollection.Factory permissionFilter,
 | 
					      PermissionCollection.Factory permissionFilter,
 | 
				
			||||||
      ChangeControl.Factory changeControlFactory,
 | 
					 | 
				
			||||||
      PermissionBackend permissionBackend,
 | 
					      PermissionBackend permissionBackend,
 | 
				
			||||||
      DefaultRefFilter.Factory refFilterFactory,
 | 
					      DefaultRefFilter.Factory refFilterFactory,
 | 
				
			||||||
 | 
					      ChangeData.Factory changeDataFactory,
 | 
				
			||||||
      @Assisted CurrentUser who,
 | 
					      @Assisted CurrentUser who,
 | 
				
			||||||
      @Assisted ProjectState ps) {
 | 
					      @Assisted ProjectState ps) {
 | 
				
			||||||
    this.changeControlFactory = changeControlFactory;
 | 
					 | 
				
			||||||
    this.uploadGroups = uploadGroups;
 | 
					    this.uploadGroups = uploadGroups;
 | 
				
			||||||
    this.receiveGroups = receiveGroups;
 | 
					    this.receiveGroups = receiveGroups;
 | 
				
			||||||
    this.permissionFilter = permissionFilter;
 | 
					    this.permissionFilter = permissionFilter;
 | 
				
			||||||
    this.permissionBackend = permissionBackend;
 | 
					    this.permissionBackend = permissionBackend;
 | 
				
			||||||
    this.refFilterFactory = refFilterFactory;
 | 
					    this.refFilterFactory = refFilterFactory;
 | 
				
			||||||
 | 
					    this.changeDataFactory = changeDataFactory;
 | 
				
			||||||
    user = who;
 | 
					    user = who;
 | 
				
			||||||
    state = ps;
 | 
					    state = ps;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
@@ -102,13 +102,8 @@ class ProjectControl {
 | 
				
			|||||||
    return new ForProjectImpl();
 | 
					    return new ForProjectImpl();
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  ChangeControl controlFor(Change change) {
 | 
					  ChangeControl controlFor(ChangeData cd) {
 | 
				
			||||||
    return changeControlFactory.create(
 | 
					    return new ChangeControl(controlForRef(cd.change().getDest()), cd);
 | 
				
			||||||
        controlForRef(change.getDest()), change.getProject(), change.getId());
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  ChangeControl controlFor(ChangeNotes notes) {
 | 
					 | 
				
			||||||
    return changeControlFactory.create(controlForRef(notes.getChange().getDest()), notes);
 | 
					 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  RefControl controlForRef(BranchNameKey ref) {
 | 
					  RefControl controlForRef(BranchNameKey ref) {
 | 
				
			||||||
@@ -122,7 +117,7 @@ class ProjectControl {
 | 
				
			|||||||
    RefControl ctl = refControls.get(refName);
 | 
					    RefControl ctl = refControls.get(refName);
 | 
				
			||||||
    if (ctl == null) {
 | 
					    if (ctl == null) {
 | 
				
			||||||
      PermissionCollection relevant = permissionFilter.filter(access(), refName, user);
 | 
					      PermissionCollection relevant = permissionFilter.filter(access(), refName, user);
 | 
				
			||||||
      ctl = new RefControl(this, refName, relevant);
 | 
					      ctl = new RefControl(changeDataFactory, this, refName, relevant);
 | 
				
			||||||
      refControls.put(refName, ctl);
 | 
					      refControls.put(refName, ctl);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    return ctl;
 | 
					    return ctl;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -43,6 +43,7 @@ import java.util.Set;
 | 
				
			|||||||
class RefControl {
 | 
					class RefControl {
 | 
				
			||||||
  private static final FluentLogger logger = FluentLogger.forEnclosingClass();
 | 
					  private static final FluentLogger logger = FluentLogger.forEnclosingClass();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  private final ChangeData.Factory changeDataFactory;
 | 
				
			||||||
  private final ProjectControl projectControl;
 | 
					  private final ProjectControl projectControl;
 | 
				
			||||||
  private final String refName;
 | 
					  private final String refName;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -58,7 +59,12 @@ class RefControl {
 | 
				
			|||||||
  private Boolean canForgeCommitter;
 | 
					  private Boolean canForgeCommitter;
 | 
				
			||||||
  private Boolean isVisible;
 | 
					  private Boolean isVisible;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  RefControl(ProjectControl projectControl, String ref, PermissionCollection relevant) {
 | 
					  RefControl(
 | 
				
			||||||
 | 
					      ChangeData.Factory changeDataFactory,
 | 
				
			||||||
 | 
					      ProjectControl projectControl,
 | 
				
			||||||
 | 
					      String ref,
 | 
				
			||||||
 | 
					      PermissionCollection relevant) {
 | 
				
			||||||
 | 
					    this.changeDataFactory = changeDataFactory;
 | 
				
			||||||
    this.projectControl = projectControl;
 | 
					    this.projectControl = projectControl;
 | 
				
			||||||
    this.refName = ref;
 | 
					    this.refName = ref;
 | 
				
			||||||
    this.relevant = relevant;
 | 
					    this.relevant = relevant;
 | 
				
			||||||
@@ -440,7 +446,7 @@ class RefControl {
 | 
				
			|||||||
    @Override
 | 
					    @Override
 | 
				
			||||||
    public ForChange change(ChangeData cd) {
 | 
					    public ForChange change(ChangeData cd) {
 | 
				
			||||||
      try {
 | 
					      try {
 | 
				
			||||||
        return getProjectControl().controlFor(cd.notes()).asForChange(cd);
 | 
					        return getProjectControl().controlFor(cd).asForChange();
 | 
				
			||||||
      } catch (StorageException e) {
 | 
					      } catch (StorageException e) {
 | 
				
			||||||
        return FailedPermissionBackend.change("unavailable", e);
 | 
					        return FailedPermissionBackend.change("unavailable", e);
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
@@ -455,12 +461,9 @@ class RefControl {
 | 
				
			|||||||
          "expected change in project %s, not %s",
 | 
					          "expected change in project %s, not %s",
 | 
				
			||||||
          project,
 | 
					          project,
 | 
				
			||||||
          change.getProject());
 | 
					          change.getProject());
 | 
				
			||||||
      return getProjectControl().controlFor(notes).asForChange(null);
 | 
					      // Having ChangeNotes means it's OK to load values from NoteDb if needed.
 | 
				
			||||||
    }
 | 
					      // ChangeData.Factory will allow lazyLoading
 | 
				
			||||||
 | 
					      return getProjectControl().controlFor(changeDataFactory.create(notes)).asForChange();
 | 
				
			||||||
    @Override
 | 
					 | 
				
			||||||
    public ForChange indexedChange(ChangeData cd, ChangeNotes notes) {
 | 
					 | 
				
			||||||
      return getProjectControl().controlFor(notes).asForChange(cd);
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @Override
 | 
					    @Override
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -674,7 +674,9 @@ public class ChangeData {
 | 
				
			|||||||
  public ReviewerSet reviewers() {
 | 
					  public ReviewerSet reviewers() {
 | 
				
			||||||
    if (reviewers == null) {
 | 
					    if (reviewers == null) {
 | 
				
			||||||
      if (!lazyLoad) {
 | 
					      if (!lazyLoad) {
 | 
				
			||||||
        return ReviewerSet.empty();
 | 
					        // We are not allowed to load values from NoteDb. Reviewers were not populated with values
 | 
				
			||||||
 | 
					        // from the index. However, we need these values for permission checks.
 | 
				
			||||||
 | 
					        throw new IllegalStateException("reviewers not populated");
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
      reviewers = approvalsUtil.getReviewers(notes(), approvals().values());
 | 
					      reviewers = approvalsUtil.getReviewers(notes(), approvals().values());
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
@@ -685,10 +687,6 @@ public class ChangeData {
 | 
				
			|||||||
    this.reviewers = reviewers;
 | 
					    this.reviewers = reviewers;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  public ReviewerSet getReviewers() {
 | 
					 | 
				
			||||||
    return reviewers;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  public ReviewerByEmailSet reviewersByEmail() {
 | 
					  public ReviewerByEmailSet reviewersByEmail() {
 | 
				
			||||||
    if (reviewersByEmail == null) {
 | 
					    if (reviewersByEmail == null) {
 | 
				
			||||||
      if (!lazyLoad) {
 | 
					      if (!lazyLoad) {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -72,7 +72,6 @@ public class ChangeIsVisibleToPredicate extends IsVisibleToPredicate<ChangeData>
 | 
				
			|||||||
      return false;
 | 
					      return false;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    ChangeNotes notes = notesFactory.createFromIndexedChange(change);
 | 
					 | 
				
			||||||
    Optional<ProjectState> projectState = projectCache.get(cd.project());
 | 
					    Optional<ProjectState> projectState = projectCache.get(cd.project());
 | 
				
			||||||
    if (!projectState.isPresent()) {
 | 
					    if (!projectState.isPresent()) {
 | 
				
			||||||
      logger.atFine().log("Filter out change %s of non-existing project %s", cd, cd.project());
 | 
					      logger.atFine().log("Filter out change %s of non-existing project %s", cd, cd.project());
 | 
				
			||||||
@@ -88,7 +87,7 @@ public class ChangeIsVisibleToPredicate extends IsVisibleToPredicate<ChangeData>
 | 
				
			|||||||
            ? permissionBackend.absentUser(user.getAccountId())
 | 
					            ? permissionBackend.absentUser(user.getAccountId())
 | 
				
			||||||
            : permissionBackend.user(anonymousUserProvider.get());
 | 
					            : permissionBackend.user(anonymousUserProvider.get());
 | 
				
			||||||
    try {
 | 
					    try {
 | 
				
			||||||
      withUser.indexedChange(cd, notes).check(ChangePermission.READ);
 | 
					      withUser.change(cd).check(ChangePermission.READ);
 | 
				
			||||||
    } catch (PermissionBackendException e) {
 | 
					    } catch (PermissionBackendException e) {
 | 
				
			||||||
      Throwable cause = e.getCause();
 | 
					      Throwable cause = e.getCause();
 | 
				
			||||||
      if (cause instanceof RepositoryNotFoundException) {
 | 
					      if (cause instanceof RepositoryNotFoundException) {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -0,0 +1,70 @@
 | 
				
			|||||||
 | 
					// Copyright (C) 2020 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.acceptance.server.permissions;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import static com.google.common.truth.Truth.assertThat;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import com.google.common.collect.Iterables;
 | 
				
			||||||
 | 
					import com.google.gerrit.acceptance.AbstractDaemonTest;
 | 
				
			||||||
 | 
					import com.google.gerrit.entities.Change;
 | 
				
			||||||
 | 
					import com.google.gerrit.server.notedb.ChangeNotes;
 | 
				
			||||||
 | 
					import com.google.gerrit.server.permissions.ChangePermission;
 | 
				
			||||||
 | 
					import com.google.gerrit.server.permissions.PermissionBackend;
 | 
				
			||||||
 | 
					import com.google.gerrit.server.query.change.ChangeData;
 | 
				
			||||||
 | 
					import com.google.inject.Inject;
 | 
				
			||||||
 | 
					import org.junit.Test;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/** Asserts behavior on {@link PermissionBackend} using a fully-started Gerrit. */
 | 
				
			||||||
 | 
					public class PermissionBackendIT extends AbstractDaemonTest {
 | 
				
			||||||
 | 
					  @Inject PermissionBackend pb;
 | 
				
			||||||
 | 
					  @Inject ChangeNotes.Factory changeNotesFactory;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @Test
 | 
				
			||||||
 | 
					  public void changeDataFromIndex_canCheckReviewerState() throws Exception {
 | 
				
			||||||
 | 
					    Change.Id changeId = createChange().getChange().getId();
 | 
				
			||||||
 | 
					    gApi.changes().id(changeId.get()).setPrivate(true);
 | 
				
			||||||
 | 
					    gApi.changes().id(changeId.get()).addReviewer(user.email());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    ChangeData changeData =
 | 
				
			||||||
 | 
					        Iterables.getOnlyElement(queryProvider.get().byLegacyChangeId(changeId));
 | 
				
			||||||
 | 
					    boolean reviewerCanSee =
 | 
				
			||||||
 | 
					        pb.absentUser(user.id()).change(changeData).test(ChangePermission.READ);
 | 
				
			||||||
 | 
					    assertThat(reviewerCanSee).isTrue();
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @Test
 | 
				
			||||||
 | 
					  public void changeDataFromNoteDb_canCheckReviewerState() throws Exception {
 | 
				
			||||||
 | 
					    Change.Id changeId = createChange().getChange().getId();
 | 
				
			||||||
 | 
					    gApi.changes().id(changeId.get()).setPrivate(true);
 | 
				
			||||||
 | 
					    gApi.changes().id(changeId.get()).addReviewer(user.email());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    ChangeNotes notes = changeNotesFactory.create(project, changeId);
 | 
				
			||||||
 | 
					    ChangeData changeData = changeDataFactory.create(notes);
 | 
				
			||||||
 | 
					    boolean reviewerCanSee =
 | 
				
			||||||
 | 
					        pb.absentUser(user.id()).change(changeData).test(ChangePermission.READ);
 | 
				
			||||||
 | 
					    assertThat(reviewerCanSee).isTrue();
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @Test
 | 
				
			||||||
 | 
					  public void changeNotes_canCheckReviewerState() throws Exception {
 | 
				
			||||||
 | 
					    Change.Id changeId = createChange().getChange().getId();
 | 
				
			||||||
 | 
					    gApi.changes().id(changeId.get()).setPrivate(true);
 | 
				
			||||||
 | 
					    gApi.changes().id(changeId.get()).addReviewer(user.email());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    ChangeNotes notes = changeNotesFactory.create(project, changeId);
 | 
				
			||||||
 | 
					    boolean reviewerCanSee = pb.absentUser(user.id()).change(notes).test(ChangePermission.READ);
 | 
				
			||||||
 | 
					    assertThat(reviewerCanSee).isTrue();
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -65,7 +65,7 @@ public class SetReviewersIT extends AbstractDaemonTest {
 | 
				
			|||||||
    session.exec(
 | 
					    session.exec(
 | 
				
			||||||
        String.format("gerrit set-reviewers -%s %s %s", add ? "a" : "r", user.email(), id));
 | 
					        String.format("gerrit set-reviewers -%s %s %s", add ? "a" : "r", user.email(), id));
 | 
				
			||||||
    session.assertSuccess();
 | 
					    session.assertSuccess();
 | 
				
			||||||
    ImmutableSet<Account.Id> reviewers = change.getChange().getReviewers().all();
 | 
					    ImmutableSet<Account.Id> reviewers = change.getChange().reviewers().all();
 | 
				
			||||||
    if (add) {
 | 
					    if (add) {
 | 
				
			||||||
      assertThat(reviewers).contains(user.id());
 | 
					      assertThat(reviewers).contains(user.id());
 | 
				
			||||||
    } else {
 | 
					    } else {
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user