Make project state check in READ explicit

The majority of code in {Project,Ref,Change}Control is now about
permissions, but not all. Exceptions include checks for a project's
state. This is confusing, because users are presented with an exception
telling them that they lack some kind of permission while the real
reason for the failed operation is that the project's current state
doesn't permit the operation.

This is part of a series of commits to remove all project state checks
from *Control classes and make explicit checks instead.

Calls from resources in restapi/change/* need no explicit check if the
project state permits reads as this is checked in ChangesCollection.

Change-Id: Ifde8885cf48d7a63af52fe5ce3347f880f131d48
This commit is contained in:
Patrick Hiesel
2018-01-15 17:58:15 +01:00
parent a9a057e1db
commit 0431dc2cae
20 changed files with 171 additions and 70 deletions

View File

@@ -15,6 +15,7 @@
package com.google.gerrit.httpd.raw; package com.google.gerrit.httpd.raw;
import com.google.gerrit.extensions.restapi.AuthException; import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.ResourceConflictException;
import com.google.gerrit.extensions.restapi.Url; import com.google.gerrit.extensions.restapi.Url;
import com.google.gerrit.reviewdb.client.Change; import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.reviewdb.client.Patch; import com.google.gerrit.reviewdb.client.Patch;
@@ -29,6 +30,7 @@ import com.google.gerrit.server.permissions.ChangePermission;
import com.google.gerrit.server.permissions.PermissionBackend; import com.google.gerrit.server.permissions.PermissionBackend;
import com.google.gerrit.server.permissions.PermissionBackendException; import com.google.gerrit.server.permissions.PermissionBackendException;
import com.google.gerrit.server.project.NoSuchChangeException; import com.google.gerrit.server.project.NoSuchChangeException;
import com.google.gerrit.server.project.ProjectCache;
import com.google.gwtorm.server.OrmException; import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject; import com.google.inject.Inject;
import com.google.inject.Provider; import com.google.inject.Provider;
@@ -57,6 +59,7 @@ public class CatServlet extends HttpServlet {
private final PatchSetUtil psUtil; private final PatchSetUtil psUtil;
private final ChangeNotes.Factory changeNotesFactory; private final ChangeNotes.Factory changeNotesFactory;
private final PermissionBackend permissionBackend; private final PermissionBackend permissionBackend;
private final ProjectCache projectCache;
@Inject @Inject
CatServlet( CatServlet(
@@ -65,13 +68,15 @@ public class CatServlet extends HttpServlet {
ChangeEditUtil ceu, ChangeEditUtil ceu,
PatchSetUtil psu, PatchSetUtil psu,
ChangeNotes.Factory cnf, ChangeNotes.Factory cnf,
PermissionBackend pb) { PermissionBackend pb,
ProjectCache pc) {
requestDb = sf; requestDb = sf;
userProvider = usrprv; userProvider = usrprv;
changeEditUtil = ceu; changeEditUtil = ceu;
psUtil = psu; psUtil = psu;
changeNotesFactory = cnf; changeNotesFactory = cnf;
permissionBackend = pb; permissionBackend = pb;
projectCache = pc;
} }
@Override @Override
@@ -131,6 +136,7 @@ public class CatServlet extends HttpServlet {
.change(notes) .change(notes)
.database(requestDb) .database(requestDb)
.check(ChangePermission.READ); .check(ChangePermission.READ);
projectCache.checkedGet(notes.getProjectName()).checkStatePermitsRead();
if (patchKey.getParentKey().get() == 0) { if (patchKey.getParentKey().get() == 0) {
// change edit // change edit
Optional<ChangeEdit> edit = changeEditUtil.byChange(notes); Optional<ChangeEdit> edit = changeEditUtil.byChange(notes);
@@ -148,10 +154,10 @@ public class CatServlet extends HttpServlet {
} }
revision = patchSet.getRevision().get(); revision = patchSet.getRevision().get();
} }
} catch (NoSuchChangeException | AuthException e) { } catch (ResourceConflictException | NoSuchChangeException | AuthException e) {
rsp.sendError(HttpServletResponse.SC_NOT_FOUND); rsp.sendError(HttpServletResponse.SC_NOT_FOUND);
return; return;
} catch (OrmException | PermissionBackendException e) { } catch (OrmException | PermissionBackendException | IOException e) {
getServletContext().log("Cannot query database", e); getServletContext().log("Cannot query database", e);
rsp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); rsp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
return; return;

View File

@@ -49,10 +49,12 @@ import com.google.gerrit.server.permissions.ChangePermission;
import com.google.gerrit.server.permissions.LabelPermission; import com.google.gerrit.server.permissions.LabelPermission;
import com.google.gerrit.server.permissions.PermissionBackend; import com.google.gerrit.server.permissions.PermissionBackend;
import com.google.gerrit.server.permissions.PermissionBackendException; import com.google.gerrit.server.permissions.PermissionBackendException;
import com.google.gerrit.server.project.ProjectCache;
import com.google.gerrit.server.util.LabelVote; import com.google.gerrit.server.util.LabelVote;
import com.google.gwtorm.server.OrmException; import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject; import com.google.inject.Inject;
import com.google.inject.Singleton; import com.google.inject.Singleton;
import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import java.util.Collections;
@@ -109,6 +111,7 @@ public class ApprovalsUtil {
private final IdentifiedUser.GenericFactory userFactory; private final IdentifiedUser.GenericFactory userFactory;
private final ApprovalCopier copier; private final ApprovalCopier copier;
private final PermissionBackend permissionBackend; private final PermissionBackend permissionBackend;
private final ProjectCache projectCache;
@VisibleForTesting @VisibleForTesting
@Inject @Inject
@@ -116,11 +119,13 @@ public class ApprovalsUtil {
NotesMigration migration, NotesMigration migration,
IdentifiedUser.GenericFactory userFactory, IdentifiedUser.GenericFactory userFactory,
ApprovalCopier copier, ApprovalCopier copier,
PermissionBackend permissionBackend) { PermissionBackend permissionBackend,
ProjectCache projectCache) {
this.migration = migration; this.migration = migration;
this.userFactory = userFactory; this.userFactory = userFactory;
this.copier = copier; this.copier = copier;
this.permissionBackend = permissionBackend; this.permissionBackend = permissionBackend;
this.projectCache = projectCache;
} }
/** /**
@@ -263,8 +268,9 @@ public class ApprovalsUtil {
private boolean canSee(ReviewDb db, ChangeNotes notes, Account.Id accountId) { private boolean canSee(ReviewDb db, ChangeNotes notes, Account.Id accountId) {
try { try {
IdentifiedUser user = userFactory.create(accountId); IdentifiedUser user = userFactory.create(accountId);
return permissionBackend.user(user).change(notes).database(db).test(ChangePermission.READ); return projectCache.checkedGet(notes.getProjectName()).statePermitsRead()
} catch (PermissionBackendException e) { && permissionBackend.user(user).change(notes).database(db).test(ChangePermission.READ);
} catch (IOException | PermissionBackendException e) {
log.warn( log.warn(
String.format( String.format(
"Failed to check if account %d can see change %d", "Failed to check if account %d can see change %d",

View File

@@ -467,10 +467,11 @@ public class ChangeInserter implements InsertChangeOp {
try { try {
IdentifiedUser user = userFactory.create(accountId); IdentifiedUser user = userFactory.create(accountId);
return permissionBackend return permissionBackend
.user(user) .user(user)
.change(notes) .change(notes)
.database(db) .database(db)
.test(ChangePermission.READ); .test(ChangePermission.READ)
&& projectState.statePermitsRead();
} catch (PermissionBackendException e) { } catch (PermissionBackendException e) {
log.warn( log.warn(
String.format( String.format(

View File

@@ -124,6 +124,7 @@ import com.google.gerrit.server.permissions.PermissionBackend;
import com.google.gerrit.server.permissions.PermissionBackendException; import com.google.gerrit.server.permissions.PermissionBackendException;
import com.google.gerrit.server.project.NoSuchChangeException; import com.google.gerrit.server.project.NoSuchChangeException;
import com.google.gerrit.server.project.ProjectCache; import com.google.gerrit.server.project.ProjectCache;
import com.google.gerrit.server.project.ProjectState;
import com.google.gerrit.server.project.RemoveReviewerControl; import com.google.gerrit.server.project.RemoveReviewerControl;
import com.google.gerrit.server.project.SubmitRuleOptions; import com.google.gerrit.server.project.SubmitRuleOptions;
import com.google.gerrit.server.query.change.ChangeData; import com.google.gerrit.server.query.change.ChangeData;
@@ -1469,13 +1470,19 @@ public class ChangeJson {
: withUser.indexedChange(cd, notesFactory.createFromIndexedChange(cd.change())); : withUser.indexedChange(cd, notesFactory.createFromIndexedChange(cd.change()));
} }
private boolean isWorldReadable(ChangeData cd) throws OrmException, PermissionBackendException { private boolean isWorldReadable(ChangeData cd)
throws OrmException, PermissionBackendException, IOException {
try { try {
permissionBackendForChange(anonymous, cd).check(ChangePermission.READ); permissionBackendForChange(anonymous, cd).check(ChangePermission.READ);
return true;
} catch (AuthException ae) { } catch (AuthException ae) {
return false; return false;
} }
ProjectState projectState = projectCache.checkedGet(cd.project());
if (projectState == null) {
log.error("project state for project " + cd.project() + " is null");
return false;
}
return projectState.statePermitsRead();
} }
@AutoValue @AutoValue

View File

@@ -163,7 +163,7 @@ public class EventBroker implements EventDispatcher {
return false; return false;
} }
ProjectState pe = projectCache.get(change.getProject()); ProjectState pe = projectCache.get(change.getProject());
if (pe == null) { if (pe == null || !pe.statePermitsRead()) {
return false; return false;
} }
ReviewDb db = dbProvider.get(); ReviewDb db = dbProvider.get();

View File

@@ -33,6 +33,8 @@ import com.google.gerrit.server.permissions.ChangePermission;
import com.google.gerrit.server.permissions.PermissionBackend; import com.google.gerrit.server.permissions.PermissionBackend;
import com.google.gerrit.server.permissions.PermissionBackendException; import com.google.gerrit.server.permissions.PermissionBackendException;
import com.google.gerrit.server.project.NoSuchProjectException; import com.google.gerrit.server.project.NoSuchProjectException;
import com.google.gerrit.server.project.ProjectCache;
import com.google.gerrit.server.project.ProjectState;
import com.google.gerrit.server.query.change.ChangeData; import com.google.gerrit.server.query.change.ChangeData;
import com.google.gerrit.server.query.change.InternalChangeQuery; import com.google.gerrit.server.query.change.InternalChangeQuery;
import com.google.gwtorm.server.OrmException; import com.google.gwtorm.server.OrmException;
@@ -87,10 +89,14 @@ public class LocalMergeSuperSetComputation implements MergeSuperSetComputation {
private final Provider<InternalChangeQuery> queryProvider; private final Provider<InternalChangeQuery> queryProvider;
private final Map<QueryKey, List<ChangeData>> queryCache; private final Map<QueryKey, List<ChangeData>> queryCache;
private final Map<Branch.NameKey, Optional<RevCommit>> heads; private final Map<Branch.NameKey, Optional<RevCommit>> heads;
private final ProjectCache projectCache;
@Inject @Inject
LocalMergeSuperSetComputation( LocalMergeSuperSetComputation(
PermissionBackend permissionBackend, Provider<InternalChangeQuery> queryProvider) { PermissionBackend permissionBackend,
Provider<InternalChangeQuery> queryProvider,
ProjectCache projectCache) {
this.projectCache = projectCache;
this.permissionBackend = permissionBackend; this.permissionBackend = permissionBackend;
this.queryProvider = queryProvider; this.queryProvider = queryProvider;
this.queryCache = new HashMap<>(); this.queryCache = new HashMap<>();
@@ -171,8 +177,12 @@ public class LocalMergeSuperSetComputation implements MergeSuperSetComputation {
} }
private boolean isVisible(ReviewDb db, ChangeSet changeSet, ChangeData cd, CurrentUser user) private boolean isVisible(ReviewDb db, ChangeSet changeSet, ChangeData cd, CurrentUser user)
throws PermissionBackendException { throws PermissionBackendException, IOException {
boolean visible = changeSet.ids().contains(cd.getId()); ProjectState projectState = projectCache.checkedGet(cd.project());
boolean visible =
changeSet.ids().contains(cd.getId())
&& (projectState != null)
&& projectState.statePermitsRead();
if (visible if (visible
&& !permissionBackend.user(user).change(cd).database(db).test(ChangePermission.READ)) { && !permissionBackend.user(user).change(cd).database(db).test(ChangePermission.READ)) {
// We thought the change was visible, but it isn't. // We thought the change was visible, but it isn't.

View File

@@ -27,6 +27,8 @@ import com.google.gerrit.server.index.change.ChangeField;
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;
import com.google.gerrit.server.permissions.PermissionBackendException; import com.google.gerrit.server.permissions.PermissionBackendException;
import com.google.gerrit.server.project.ProjectCache;
import com.google.gerrit.server.project.ProjectState;
import com.google.gerrit.server.query.change.ChangeData; import com.google.gerrit.server.query.change.ChangeData;
import com.google.gerrit.server.query.change.InternalChangeQuery; import com.google.gerrit.server.query.change.InternalChangeQuery;
import com.google.gwtorm.server.OrmException; import com.google.gwtorm.server.OrmException;
@@ -75,6 +77,7 @@ public class MergeSuperSet {
private final DynamicItem<MergeSuperSetComputation> mergeSuperSetComputation; private final DynamicItem<MergeSuperSetComputation> mergeSuperSetComputation;
private final PermissionBackend permissionBackend; private final PermissionBackend permissionBackend;
private final Config cfg; private final Config cfg;
private final ProjectCache projectCache;
private MergeOpRepoManager orm; private MergeOpRepoManager orm;
private boolean closeOrm; private boolean closeOrm;
@@ -86,13 +89,15 @@ public class MergeSuperSet {
Provider<InternalChangeQuery> queryProvider, Provider<InternalChangeQuery> queryProvider,
Provider<MergeOpRepoManager> repoManagerProvider, Provider<MergeOpRepoManager> repoManagerProvider,
DynamicItem<MergeSuperSetComputation> mergeSuperSetComputation, DynamicItem<MergeSuperSetComputation> mergeSuperSetComputation,
PermissionBackend permissionBackend) { PermissionBackend permissionBackend,
ProjectCache projectCache) {
this.cfg = cfg; this.cfg = cfg;
this.changeDataFactory = changeDataFactory; this.changeDataFactory = changeDataFactory;
this.queryProvider = queryProvider; this.queryProvider = queryProvider;
this.repoManagerProvider = repoManagerProvider; this.repoManagerProvider = repoManagerProvider;
this.mergeSuperSetComputation = mergeSuperSetComputation; this.mergeSuperSetComputation = mergeSuperSetComputation;
this.permissionBackend = permissionBackend; this.permissionBackend = permissionBackend;
this.projectCache = projectCache;
} }
public static boolean wholeTopicEnabled(Config config) { public static boolean wholeTopicEnabled(Config config) {
@@ -115,9 +120,17 @@ public class MergeSuperSet {
} }
ChangeData cd = changeDataFactory.create(db, change.getProject(), change.getId()); ChangeData cd = changeDataFactory.create(db, change.getProject(), change.getId());
ProjectState projectState = projectCache.checkedGet(cd.project());
ChangeSet changeSet = ChangeSet changeSet =
new ChangeSet( new ChangeSet(
cd, permissionBackend.user(user).change(cd).database(db).test(ChangePermission.READ)); cd,
projectState != null
&& projectState.statePermitsRead()
&& permissionBackend
.user(user)
.change(cd)
.database(db)
.test(ChangePermission.READ));
if (wholeTopicEnabled(cfg)) { if (wholeTopicEnabled(cfg)) {
return completeChangeSetIncludingTopics(db, changeSet, user); return completeChangeSetIncludingTopics(db, changeSet, user);
} }
@@ -149,7 +162,7 @@ public class MergeSuperSet {
CurrentUser user, CurrentUser user,
Set<String> topicsSeen, Set<String> topicsSeen,
Set<String> visibleTopicsSeen) Set<String> visibleTopicsSeen)
throws OrmException, PermissionBackendException { throws OrmException, PermissionBackendException, IOException {
List<ChangeData> visibleChanges = new ArrayList<>(); List<ChangeData> visibleChanges = new ArrayList<>();
List<ChangeData> nonVisibleChanges = new ArrayList<>(); List<ChangeData> nonVisibleChanges = new ArrayList<>();
@@ -208,7 +221,10 @@ public class MergeSuperSet {
} }
private boolean canRead(ReviewDb db, CurrentUser user, ChangeData cd) private boolean canRead(ReviewDb db, CurrentUser user, ChangeData cd)
throws PermissionBackendException { throws PermissionBackendException, IOException {
return permissionBackend.user(user).change(cd).database(db).test(ChangePermission.READ); ProjectState projectState = projectCache.checkedGet(cd.project());
return projectState != null
&& projectState.statePermitsRead()
&& permissionBackend.user(user).change(cd).database(db).test(ChangePermission.READ);
} }
} }

View File

@@ -316,7 +316,8 @@ public class VisibleRefFilter extends AbstractAdvertiseRefsHook {
Map<Change.Id, Branch.NameKey> visibleChanges = new HashMap<>(); Map<Change.Id, Branch.NameKey> visibleChanges = new HashMap<>();
for (ChangeData cd : changeCache.getChangeData(db.get(), project)) { for (ChangeData cd : changeCache.getChangeData(db.get(), project)) {
ChangeNotes notes = changeNotesFactory.createFromIndexedChange(cd.change()); ChangeNotes notes = changeNotesFactory.createFromIndexedChange(cd.change());
if (perm.indexedChange(cd, notes).test(ChangePermission.READ)) { if (projectState.statePermitsRead()
&& perm.indexedChange(cd, notes).test(ChangePermission.READ)) {
visibleChanges.put(cd.getId(), cd.change().getDest()); visibleChanges.put(cd.getId(), cd.change().getDest());
} }
} }
@@ -349,7 +350,7 @@ public class VisibleRefFilter extends AbstractAdvertiseRefsHook {
return null; return null;
} }
try { try {
if (perm.change(r.notes()).test(ChangePermission.READ)) { if (projectState.statePermitsRead() && perm.change(r.notes()).test(ChangePermission.READ)) {
return r.notes(); return r.notes();
} }
} catch (PermissionBackendException e) { } catch (PermissionBackendException e) {

View File

@@ -408,11 +408,12 @@ public abstract class ChangeEmail extends NotificationEmail {
@Override @Override
protected boolean isVisibleTo(Account.Id to) throws OrmException, PermissionBackendException { protected boolean isVisibleTo(Account.Id to) throws OrmException, PermissionBackendException {
return args.permissionBackend return projectState.statePermitsRead()
.user(args.identifiedUserFactory.create(to)) && args.permissionBackend
.change(changeData) .user(args.identifiedUserFactory.create(to))
.database(args.db.get()) .change(changeData)
.test(ChangePermission.READ); .database(args.db.get())
.test(ChangePermission.READ);
} }
/** Find all users who are authors of any part of this change. */ /** Find all users who are authors of any part of this change. */

View File

@@ -42,6 +42,7 @@ import com.google.gerrit.server.permissions.PermissionBackend;
import com.google.gerrit.server.permissions.PermissionBackendException; import com.google.gerrit.server.permissions.PermissionBackendException;
import com.google.gerrit.server.project.InvalidChangeOperationException; import com.google.gerrit.server.project.InvalidChangeOperationException;
import com.google.gerrit.server.project.NoSuchChangeException; import com.google.gerrit.server.project.NoSuchChangeException;
import com.google.gerrit.server.project.ProjectCache;
import com.google.gwtorm.server.OrmException; import com.google.gwtorm.server.OrmException;
import com.google.inject.Provider; import com.google.inject.Provider;
import com.google.inject.assistedinject.Assisted; import com.google.inject.assistedinject.Assisted;
@@ -93,6 +94,7 @@ public class PatchScriptFactory implements Callable<PatchScript> {
private final ChangeEditUtil editReader; private final ChangeEditUtil editReader;
private final Provider<CurrentUser> userProvider; private final Provider<CurrentUser> userProvider;
private final PermissionBackend permissionBackend; private final PermissionBackend permissionBackend;
private final ProjectCache projectCache;
private Optional<ChangeEdit> edit; private Optional<ChangeEdit> edit;
private final Change.Id changeId; private final Change.Id changeId;
@@ -116,6 +118,7 @@ public class PatchScriptFactory implements Callable<PatchScript> {
ChangeEditUtil editReader, ChangeEditUtil editReader,
Provider<CurrentUser> userProvider, Provider<CurrentUser> userProvider,
PermissionBackend permissionBackend, PermissionBackend permissionBackend,
ProjectCache projectCache,
@Assisted ChangeNotes notes, @Assisted ChangeNotes notes,
@Assisted String fileName, @Assisted String fileName,
@Assisted("patchSetA") @Nullable PatchSet.Id patchSetA, @Assisted("patchSetA") @Nullable PatchSet.Id patchSetA,
@@ -131,6 +134,7 @@ public class PatchScriptFactory implements Callable<PatchScript> {
this.editReader = editReader; this.editReader = editReader;
this.userProvider = userProvider; this.userProvider = userProvider;
this.permissionBackend = permissionBackend; this.permissionBackend = permissionBackend;
this.projectCache = projectCache;
this.fileName = fileName; this.fileName = fileName;
this.psa = patchSetA; this.psa = patchSetA;
@@ -152,6 +156,7 @@ public class PatchScriptFactory implements Callable<PatchScript> {
ChangeEditUtil editReader, ChangeEditUtil editReader,
Provider<CurrentUser> userProvider, Provider<CurrentUser> userProvider,
PermissionBackend permissionBackend, PermissionBackend permissionBackend,
ProjectCache projectCache,
@Assisted ChangeNotes notes, @Assisted ChangeNotes notes,
@Assisted String fileName, @Assisted String fileName,
@Assisted int parentNum, @Assisted int parentNum,
@@ -167,6 +172,7 @@ public class PatchScriptFactory implements Callable<PatchScript> {
this.editReader = editReader; this.editReader = editReader;
this.userProvider = userProvider; this.userProvider = userProvider;
this.permissionBackend = permissionBackend; this.permissionBackend = permissionBackend;
this.projectCache = projectCache;
this.fileName = fileName; this.fileName = fileName;
this.psa = null; this.psa = null;
@@ -209,6 +215,10 @@ public class PatchScriptFactory implements Callable<PatchScript> {
} }
} }
if (!projectCache.checkedGet(notes.getProjectName()).statePermitsRead()) {
throw new NoSuchChangeException(changeId);
}
try (Repository git = repoManager.openRepository(notes.getProjectName())) { try (Repository git = repoManager.openRepository(notes.getProjectName())) {
bId = toObjectId(psEntityB); bId = toObjectId(psEntityB);
if (parentNum < 0) { if (parentNum < 0) {

View File

@@ -130,7 +130,7 @@ class ChangeControl {
if (getChange().isPrivate() && !isPrivateVisible(db, cd)) { if (getChange().isPrivate() && !isPrivateVisible(db, cd)) {
return false; return false;
} }
return refControl.isVisible() && getProjectControl().getProject().getState().permitsRead(); return refControl.isVisible();
} }
/** Can this user abandon this change? */ /** Can this user abandon this change? */

View File

@@ -24,8 +24,11 @@ import com.google.gerrit.server.permissions.ChangePermission;
import com.google.gerrit.server.permissions.PermissionBackend; import com.google.gerrit.server.permissions.PermissionBackend;
import com.google.gerrit.server.permissions.PermissionBackendException; import com.google.gerrit.server.permissions.PermissionBackendException;
import com.google.gerrit.server.project.NoSuchProjectException; import com.google.gerrit.server.project.NoSuchProjectException;
import com.google.gerrit.server.project.ProjectCache;
import com.google.gerrit.server.project.ProjectState;
import com.google.gwtorm.server.OrmException; import com.google.gwtorm.server.OrmException;
import com.google.inject.Provider; import com.google.inject.Provider;
import java.io.IOException;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@@ -36,17 +39,20 @@ public class ChangeIsVisibleToPredicate extends IsVisibleToPredicate<ChangeData>
protected final ChangeNotes.Factory notesFactory; protected final ChangeNotes.Factory notesFactory;
protected final CurrentUser user; protected final CurrentUser user;
protected final PermissionBackend permissionBackend; protected final PermissionBackend permissionBackend;
protected final ProjectCache projectCache;
public ChangeIsVisibleToPredicate( public ChangeIsVisibleToPredicate(
Provider<ReviewDb> db, Provider<ReviewDb> db,
ChangeNotes.Factory notesFactory, ChangeNotes.Factory notesFactory,
CurrentUser user, CurrentUser user,
PermissionBackend permissionBackend) { PermissionBackend permissionBackend,
ProjectCache projectCache) {
super(ChangeQueryBuilder.FIELD_VISIBLETO, IndexUtils.describe(user)); super(ChangeQueryBuilder.FIELD_VISIBLETO, IndexUtils.describe(user));
this.db = db; this.db = db;
this.notesFactory = notesFactory; this.notesFactory = notesFactory;
this.user = user; this.user = user;
this.permissionBackend = permissionBackend; this.permissionBackend = permissionBackend;
this.projectCache = projectCache;
} }
@Override @Override
@@ -60,6 +66,20 @@ public class ChangeIsVisibleToPredicate extends IsVisibleToPredicate<ChangeData>
} }
ChangeNotes notes = notesFactory.createFromIndexedChange(change); ChangeNotes notes = notesFactory.createFromIndexedChange(change);
try {
ProjectState projectState = projectCache.checkedGet(cd.project());
if (projectState == null) {
logger.info("No such project: {}", cd.project());
return false;
}
if (!projectState.statePermitsRead()) {
return false;
}
} catch (IOException e) {
throw new OrmException("unable to read project state", e);
}
boolean visible; boolean visible;
try { try {
visible = visible =

View File

@@ -915,7 +915,8 @@ public class ChangeQueryBuilder extends QueryBuilder<ChangeData> {
} }
public Predicate<ChangeData> visibleto(CurrentUser user) { public Predicate<ChangeData> visibleto(CurrentUser user) {
return new ChangeIsVisibleToPredicate(args.db, args.notesFactory, user, args.permissionBackend); return new ChangeIsVisibleToPredicate(
args.db, args.notesFactory, user, args.permissionBackend, args.projectCache);
} }
public Predicate<ChangeData> is_visible() throws QueryParseException { public Predicate<ChangeData> is_visible() throws QueryParseException {

View File

@@ -34,6 +34,7 @@ import com.google.gerrit.server.index.change.ChangeSchemaDefinitions;
import com.google.gerrit.server.index.change.IndexedChangeQuery; import com.google.gerrit.server.index.change.IndexedChangeQuery;
import com.google.gerrit.server.notedb.ChangeNotes; import com.google.gerrit.server.notedb.ChangeNotes;
import com.google.gerrit.server.permissions.PermissionBackend; import com.google.gerrit.server.permissions.PermissionBackend;
import com.google.gerrit.server.project.ProjectCache;
import com.google.inject.Inject; import com.google.inject.Inject;
import com.google.inject.Provider; import com.google.inject.Provider;
import java.util.ArrayList; import java.util.ArrayList;
@@ -63,6 +64,7 @@ public class ChangeQueryProcessor extends QueryProcessor<ChangeData>
private final ChangeNotes.Factory notesFactory; private final ChangeNotes.Factory notesFactory;
private final DynamicMap<ChangeAttributeFactory> attributeFactories; private final DynamicMap<ChangeAttributeFactory> attributeFactories;
private final PermissionBackend permissionBackend; private final PermissionBackend permissionBackend;
private final ProjectCache projectCache;
static { static {
// It is assumed that basic rewrites do not touch visibleto predicates. // It is assumed that basic rewrites do not touch visibleto predicates.
@@ -82,7 +84,8 @@ public class ChangeQueryProcessor extends QueryProcessor<ChangeData>
Provider<ReviewDb> db, Provider<ReviewDb> db,
ChangeNotes.Factory notesFactory, ChangeNotes.Factory notesFactory,
DynamicMap<ChangeAttributeFactory> attributeFactories, DynamicMap<ChangeAttributeFactory> attributeFactories,
PermissionBackend permissionBackend) { PermissionBackend permissionBackend,
ProjectCache projectCache) {
super( super(
metricMaker, metricMaker,
ChangeSchemaDefinitions.INSTANCE, ChangeSchemaDefinitions.INSTANCE,
@@ -96,6 +99,7 @@ public class ChangeQueryProcessor extends QueryProcessor<ChangeData>
this.notesFactory = notesFactory; this.notesFactory = notesFactory;
this.attributeFactories = attributeFactories; this.attributeFactories = attributeFactories;
this.permissionBackend = permissionBackend; this.permissionBackend = permissionBackend;
this.projectCache = projectCache;
} }
@Override @Override
@@ -138,7 +142,8 @@ public class ChangeQueryProcessor extends QueryProcessor<ChangeData>
protected Predicate<ChangeData> enforceVisibility(Predicate<ChangeData> pred) { protected Predicate<ChangeData> enforceVisibility(Predicate<ChangeData> pred) {
return new AndChangeSource( return new AndChangeSource(
pred, pred,
new ChangeIsVisibleToPredicate(db, notesFactory, userProvider.get(), permissionBackend), new ChangeIsVisibleToPredicate(
db, notesFactory, userProvider.get(), permissionBackend, projectCache),
start); start);
} }
} }

View File

@@ -30,6 +30,7 @@ import com.google.gerrit.server.project.ProjectCache;
import com.google.gerrit.server.project.ProjectState; import com.google.gerrit.server.project.ProjectState;
import com.google.gwtorm.server.OrmException; import com.google.gwtorm.server.OrmException;
import com.google.inject.Provider; import com.google.inject.Provider;
import java.io.IOException;
public class EqualsLabelPredicate extends ChangeIndexPredicate { public class EqualsLabelPredicate extends ChangeIndexPredicate {
protected final ProjectCache projectCache; protected final ProjectCache projectCache;
@@ -123,8 +124,11 @@ public class EqualsLabelPredicate extends ChangeIndexPredicate {
try { try {
PermissionBackend.ForChange perm = PermissionBackend.ForChange perm =
permissionBackend.user(reviewer).database(dbProvider).change(cd); permissionBackend.user(reviewer).database(dbProvider).change(cd);
return perm.test(ChangePermission.READ); ProjectState projectState = projectCache.checkedGet(cd.project());
} catch (PermissionBackendException e) { return projectState != null
&& projectState.statePermitsRead()
&& perm.test(ChangePermission.READ);
} catch (PermissionBackendException | IOException e) {
return false; return false;
} }
} }

View File

@@ -135,13 +135,17 @@ public class ChangesCollection
return createChange; return createChange;
} }
private boolean canRead(ChangeNotes notes) throws PermissionBackendException { private boolean canRead(ChangeNotes notes) throws PermissionBackendException, IOException {
try { try {
permissionBackend.user(user).change(notes).database(db).check(ChangePermission.READ); permissionBackend.user(user).change(notes).database(db).check(ChangePermission.READ);
return true;
} catch (AuthException e) { } catch (AuthException e) {
return false; return false;
} }
ProjectState projectState = projectCache.checkedGet(notes.getProjectName());
if (projectState == null) {
return false;
}
return projectState.statePermitsRead();
} }
private void checkProjectStatePermitsRead(Project.NameKey project) private void checkProjectStatePermitsRead(Project.NameKey project)

View File

@@ -101,7 +101,7 @@ public class GetRelated implements RestReadView<RevisionResource> {
reloadChangeIfStale(cds, basePs); reloadChangeIfStale(cds, basePs);
for (RelatedChangesSorter.PatchSetData d : sorter.sort(cds, basePs, rsrc.getUser())) { for (RelatedChangesSorter.PatchSetData d : sorter.sort(cds, basePs)) {
PatchSet ps = d.patchSet(); PatchSet ps = d.patchSet();
RevCommit commit; RevCommit commit;
if (isEdit && ps.getId().equals(basePs.getId())) { if (isEdit && ps.getId().equals(basePs.getId())) {

View File

@@ -34,6 +34,8 @@ import com.google.gerrit.server.git.GitRepositoryManager;
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;
import com.google.gerrit.server.permissions.PermissionBackendException; import com.google.gerrit.server.permissions.PermissionBackendException;
import com.google.gerrit.server.project.ProjectCache;
import com.google.gerrit.server.project.ProjectState;
import com.google.gerrit.server.query.change.ChangeData; import com.google.gerrit.server.query.change.ChangeData;
import com.google.gwtorm.server.OrmException; import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject; import com.google.inject.Inject;
@@ -61,25 +63,30 @@ class RelatedChangesSorter {
private final GitRepositoryManager repoManager; private final GitRepositoryManager repoManager;
private final PermissionBackend permissionBackend; private final PermissionBackend permissionBackend;
private final Provider<ReviewDb> dbProvider; private final Provider<ReviewDb> dbProvider;
private final ProjectCache projectCache;
private final Provider<CurrentUser> currentUserProvider;
@Inject @Inject
RelatedChangesSorter( RelatedChangesSorter(
GitRepositoryManager repoManager, GitRepositoryManager repoManager,
PermissionBackend permissionBackend, PermissionBackend permissionBackend,
Provider<ReviewDb> dbProvider) { Provider<ReviewDb> dbProvider,
ProjectCache projectCache,
Provider<CurrentUser> currentUserProvider) {
this.repoManager = repoManager; this.repoManager = repoManager;
this.permissionBackend = permissionBackend; this.permissionBackend = permissionBackend;
this.dbProvider = dbProvider; this.dbProvider = dbProvider;
this.projectCache = projectCache;
this.currentUserProvider = currentUserProvider;
} }
public List<PatchSetData> sort(List<ChangeData> in, PatchSet startPs, CurrentUser user) public List<PatchSetData> sort(List<ChangeData> in, PatchSet startPs)
throws OrmException, IOException, PermissionBackendException { throws OrmException, IOException, PermissionBackendException {
checkArgument(!in.isEmpty(), "Input may not be empty"); checkArgument(!in.isEmpty(), "Input may not be empty");
// Map of all patch sets, keyed by commit SHA-1. // Map of all patch sets, keyed by commit SHA-1.
Map<String, PatchSetData> byId = collectById(in); Map<String, PatchSetData> byId = collectById(in);
PatchSetData start = byId.get(startPs.getRevision().get()); PatchSetData start = byId.get(startPs.getRevision().get());
checkArgument(start != null, "%s not found in %s", startPs, in); checkArgument(start != null, "%s not found in %s", startPs, in);
PermissionBackend.WithUser perm = permissionBackend.user(user).database(dbProvider);
// Map of patch set -> immediate parent. // Map of patch set -> immediate parent.
ListMultimap<PatchSetData, PatchSetData> parents = ListMultimap<PatchSetData, PatchSetData> parents =
@@ -106,9 +113,9 @@ class RelatedChangesSorter {
} }
} }
Collection<PatchSetData> ancestors = walkAncestors(perm, parents, start); Collection<PatchSetData> ancestors = walkAncestors(parents, start);
List<PatchSetData> descendants = List<PatchSetData> descendants =
walkDescendants(perm, children, start, otherPatchSetsOfStart, ancestors); walkDescendants(children, start, otherPatchSetsOfStart, ancestors);
List<PatchSetData> result = new ArrayList<>(ancestors.size() + descendants.size() - 1); List<PatchSetData> result = new ArrayList<>(ancestors.size() + descendants.size() - 1);
result.addAll(Lists.reverse(descendants)); result.addAll(Lists.reverse(descendants));
result.addAll(ancestors); result.addAll(ancestors);
@@ -140,17 +147,15 @@ class RelatedChangesSorter {
return result; return result;
} }
private static Collection<PatchSetData> walkAncestors( private Collection<PatchSetData> walkAncestors(
PermissionBackend.WithUser perm, ListMultimap<PatchSetData, PatchSetData> parents, PatchSetData start)
ListMultimap<PatchSetData, PatchSetData> parents, throws PermissionBackendException, IOException {
PatchSetData start)
throws PermissionBackendException {
LinkedHashSet<PatchSetData> result = new LinkedHashSet<>(); LinkedHashSet<PatchSetData> result = new LinkedHashSet<>();
Deque<PatchSetData> pending = new ArrayDeque<>(); Deque<PatchSetData> pending = new ArrayDeque<>();
pending.add(start); pending.add(start);
while (!pending.isEmpty()) { while (!pending.isEmpty()) {
PatchSetData psd = pending.remove(); PatchSetData psd = pending.remove();
if (result.contains(psd) || !isVisible(psd, perm)) { if (result.contains(psd) || !isVisible(psd)) {
continue; continue;
} }
result.add(psd); result.add(psd);
@@ -159,26 +164,24 @@ class RelatedChangesSorter {
return result; return result;
} }
private static List<PatchSetData> walkDescendants( private List<PatchSetData> walkDescendants(
PermissionBackend.WithUser perm,
ListMultimap<PatchSetData, PatchSetData> children, ListMultimap<PatchSetData, PatchSetData> children,
PatchSetData start, PatchSetData start,
List<PatchSetData> otherPatchSetsOfStart, List<PatchSetData> otherPatchSetsOfStart,
Iterable<PatchSetData> ancestors) Iterable<PatchSetData> ancestors)
throws PermissionBackendException { throws PermissionBackendException, IOException {
Set<Change.Id> alreadyEmittedChanges = new HashSet<>(); Set<Change.Id> alreadyEmittedChanges = new HashSet<>();
addAllChangeIds(alreadyEmittedChanges, ancestors); addAllChangeIds(alreadyEmittedChanges, ancestors);
// Prefer descendants found by following the original patch set passed in. // Prefer descendants found by following the original patch set passed in.
List<PatchSetData> result = List<PatchSetData> result =
walkDescendentsImpl(perm, alreadyEmittedChanges, children, ImmutableList.of(start)); walkDescendentsImpl(alreadyEmittedChanges, children, ImmutableList.of(start));
addAllChangeIds(alreadyEmittedChanges, result); addAllChangeIds(alreadyEmittedChanges, result);
// Then, go back and add new indirect descendants found by following any // Then, go back and add new indirect descendants found by following any
// other patch sets of start. These show up after all direct descendants, // other patch sets of start. These show up after all direct descendants,
// because we wouldn't know where in the walk to insert them. // because we wouldn't know where in the walk to insert them.
result.addAll( result.addAll(walkDescendentsImpl(alreadyEmittedChanges, children, otherPatchSetsOfStart));
walkDescendentsImpl(perm, alreadyEmittedChanges, children, otherPatchSetsOfStart));
return result; return result;
} }
@@ -189,12 +192,11 @@ class RelatedChangesSorter {
} }
} }
private static List<PatchSetData> walkDescendentsImpl( private List<PatchSetData> walkDescendentsImpl(
PermissionBackend.WithUser perm,
Set<Change.Id> alreadyEmittedChanges, Set<Change.Id> alreadyEmittedChanges,
ListMultimap<PatchSetData, PatchSetData> children, ListMultimap<PatchSetData, PatchSetData> children,
List<PatchSetData> start) List<PatchSetData> start)
throws PermissionBackendException { throws PermissionBackendException, IOException {
if (start.isEmpty()) { if (start.isEmpty()) {
return ImmutableList.of(); return ImmutableList.of();
} }
@@ -205,7 +207,7 @@ class RelatedChangesSorter {
pending.addAll(start); pending.addAll(start);
while (!pending.isEmpty()) { while (!pending.isEmpty()) {
PatchSetData psd = pending.remove(); PatchSetData psd = pending.remove();
if (seen.contains(psd) || !isVisible(psd, perm)) { if (seen.contains(psd) || !isVisible(psd)) {
continue; continue;
} }
seen.add(psd); seen.add(psd);
@@ -236,14 +238,16 @@ class RelatedChangesSorter {
return result; return result;
} }
private static boolean isVisible(PatchSetData psd, PermissionBackend.WithUser perm) private boolean isVisible(PatchSetData psd) throws PermissionBackendException, IOException {
throws PermissionBackendException { PermissionBackend.WithUser perm =
permissionBackend.user(currentUserProvider).database(dbProvider);
try { try {
perm.change(psd.data()).check(ChangePermission.READ); perm.change(psd.data()).check(ChangePermission.READ);
return true;
} catch (AuthException e) { } catch (AuthException e) {
return false; return false;
} }
ProjectState state = projectCache.checkedGet(psd.data().project());
return state != null && state.statePermitsRead();
} }
@AutoValue @AutoValue

View File

@@ -33,6 +33,7 @@ import com.google.gerrit.server.edit.ChangeEditUtil;
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;
import com.google.gerrit.server.permissions.PermissionBackendException; import com.google.gerrit.server.permissions.PermissionBackendException;
import com.google.gerrit.server.project.ProjectCache;
import com.google.gwtorm.server.OrmException; import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject; import com.google.inject.Inject;
import com.google.inject.Provider; import com.google.inject.Provider;
@@ -51,6 +52,7 @@ public class Revisions implements ChildCollection<ChangeResource, RevisionResour
private final ChangeEditUtil editUtil; private final ChangeEditUtil editUtil;
private final PatchSetUtil psUtil; private final PatchSetUtil psUtil;
private final PermissionBackend permissionBackend; private final PermissionBackend permissionBackend;
private final ProjectCache projectCache;
@Inject @Inject
Revisions( Revisions(
@@ -58,12 +60,14 @@ public class Revisions implements ChildCollection<ChangeResource, RevisionResour
Provider<ReviewDb> dbProvider, Provider<ReviewDb> dbProvider,
ChangeEditUtil editUtil, ChangeEditUtil editUtil,
PatchSetUtil psUtil, PatchSetUtil psUtil,
PermissionBackend permissionBackend) { PermissionBackend permissionBackend,
ProjectCache projectCache) {
this.views = views; this.views = views;
this.dbProvider = dbProvider; this.dbProvider = dbProvider;
this.editUtil = editUtil; this.editUtil = editUtil;
this.psUtil = psUtil; this.psUtil = psUtil;
this.permissionBackend = permissionBackend; this.permissionBackend = permissionBackend;
this.projectCache = projectCache;
} }
@Override @Override
@@ -105,14 +109,14 @@ public class Revisions implements ChildCollection<ChangeResource, RevisionResour
} }
} }
private boolean visible(ChangeResource change) throws PermissionBackendException { private boolean visible(ChangeResource change) throws PermissionBackendException, IOException {
try { try {
permissionBackend permissionBackend
.user(change.getUser()) .user(change.getUser())
.change(change.getNotes()) .change(change.getNotes())
.database(dbProvider) .database(dbProvider)
.check(ChangePermission.READ); .check(ChangePermission.READ);
return true; return projectCache.checkedGet(change.getProject()).statePermitsRead();
} catch (AuthException e) { } catch (AuthException e) {
return false; return false;
} }

View File

@@ -92,11 +92,12 @@ public class ChangeArgumentParser {
if (!changes.containsKey(notes.getChangeId()) if (!changes.containsKey(notes.getChangeId())
&& inProject(projectState, notes.getProjectName()) && inProject(projectState, notes.getProjectName())
&& (canMaintainServer && (canMaintainServer
|| permissionBackend || (permissionBackend
.user(currentUser) .user(currentUser)
.change(notes) .change(notes)
.database(db) .database(db)
.test(ChangePermission.READ))) { .test(ChangePermission.READ)
&& projectState.statePermitsRead()))) {
toAdd.add(notes); toAdd.add(notes);
} }
} }