Allow to override permission-related checks.
This change adjusts visibility of some methods and makes other minor
changes to allow overriding permissions checks. This is required for
internal google implementation.
Google-Bug-Id: b/330832714
Release-Notes: skip
Change-Id: Idac46434228204a6825f0bd6b1e4b4535028ccca
diff --git a/java/com/google/gerrit/server/permissions/ChangeControl.java b/java/com/google/gerrit/server/permissions/ChangeControl.java
index 664ffa2..5d79d09 100644
--- a/java/com/google/gerrit/server/permissions/ChangeControl.java
+++ b/java/com/google/gerrit/server/permissions/ChangeControl.java
@@ -31,19 +31,26 @@
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.permissions.PermissionBackend.ForChange;
import com.google.gerrit.server.query.change.ChangeData;
+import com.google.inject.assistedinject.Assisted;
import java.util.Collection;
import java.util.EnumSet;
import java.util.Map;
import java.util.Set;
+import javax.inject.Inject;
/** Access control management for a user accessing a single change. */
-class ChangeControl {
+public class ChangeControl {
+ public interface Factory {
+ ChangeControl create(RefControl refControl, ChangeData changeData);
+ }
+
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
private final RefControl refControl;
private final ChangeData changeData;
- ChangeControl(RefControl refControl, ChangeData changeData) {
+ @Inject
+ protected ChangeControl(@Assisted RefControl refControl, @Assisted ChangeData changeData) {
this.refControl = refControl;
this.changeData = changeData;
}
diff --git a/java/com/google/gerrit/server/permissions/DefaultPermissionBackendModule.java b/java/com/google/gerrit/server/permissions/DefaultPermissionBackendModule.java
index 3f84dff..b5ec4c9 100644
--- a/java/com/google/gerrit/server/permissions/DefaultPermissionBackendModule.java
+++ b/java/com/google/gerrit/server/permissions/DefaultPermissionBackendModule.java
@@ -14,23 +14,32 @@
package com.google.gerrit.server.permissions;
-import com.google.gerrit.extensions.config.FactoryModule;
-import com.google.inject.AbstractModule;
+import com.google.inject.PrivateModule;
+import com.google.inject.assistedinject.FactoryModuleBuilder;
/** Binds the default {@link PermissionBackend}. */
-public class DefaultPermissionBackendModule extends AbstractModule {
+public class DefaultPermissionBackendModule extends PrivateModule {
+
@Override
protected void configure() {
- install(new LegacyControlsModule());
+ // TODO(hiesel) Hide ProjectControl, RefControl, ChangeControl related bindings.
+ install(new FactoryModuleBuilder().build(DefaultRefFilter.Factory.class));
+ installRefControlFactory();
+ installChangeControlFactory();
+ installProjectControlFactory();
+ // Expose only ProjectControl.Factory, so other modules can't use RefControl and ChangeControl.
+ expose(ProjectControl.Factory.class);
}
- /** Binds legacy ProjectControl, RefControl, ChangeControl. */
- public static class LegacyControlsModule extends FactoryModule {
- @Override
- protected void configure() {
- // TODO(hiesel) Hide ProjectControl, RefControl, ChangeControl related bindings.
- factory(ProjectControl.Factory.class);
- factory(DefaultRefFilter.Factory.class);
- }
+ protected void installProjectControlFactory() {
+ install(new FactoryModuleBuilder().build(ProjectControl.Factory.class));
+ }
+
+ protected void installChangeControlFactory() {
+ install(new FactoryModuleBuilder().build(ChangeControl.Factory.class));
+ }
+
+ protected void installRefControlFactory() {
+ install(new FactoryModuleBuilder().build(RefControl.Factory.class));
}
}
diff --git a/java/com/google/gerrit/server/permissions/DefaultRefFilter.java b/java/com/google/gerrit/server/permissions/DefaultRefFilter.java
index f179045..3d8ae4f 100644
--- a/java/com/google/gerrit/server/permissions/DefaultRefFilter.java
+++ b/java/com/google/gerrit/server/permissions/DefaultRefFilter.java
@@ -57,10 +57,10 @@
import org.eclipse.jgit.lib.Ref;
import org.eclipse.jgit.lib.Repository;
-class DefaultRefFilter {
+public class DefaultRefFilter {
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
- interface Factory {
+ public interface Factory {
DefaultRefFilter create(ProjectControl projectControl);
}
diff --git a/java/com/google/gerrit/server/permissions/ProjectControl.java b/java/com/google/gerrit/server/permissions/ProjectControl.java
index 09976a5..9a6db5d 100644
--- a/java/com/google/gerrit/server/permissions/ProjectControl.java
+++ b/java/com/google/gerrit/server/permissions/ProjectControl.java
@@ -42,7 +42,6 @@
import com.google.gerrit.server.config.GerritServerConfig;
import com.google.gerrit.server.config.GitReceivePackGroups;
import com.google.gerrit.server.config.GitUploadPackGroups;
-import com.google.gerrit.server.git.GitRepositoryManager;
import com.google.gerrit.server.group.SystemGroupBackend;
import com.google.gerrit.server.logging.Metadata;
import com.google.gerrit.server.logging.TraceContext;
@@ -69,22 +68,21 @@
import org.eclipse.jgit.lib.Repository;
/** Access control management for a user accessing a project's data. */
-class ProjectControl {
- interface Factory {
+public class ProjectControl {
+ public interface Factory {
ProjectControl create(CurrentUser who, ProjectState ps);
}
private final Set<AccountGroup.UUID> uploadGroups;
private final Set<AccountGroup.UUID> receiveGroups;
private final PermissionBackend permissionBackend;
- private final RefVisibilityControl refVisibilityControl;
- private final GitRepositoryManager repositoryManager;
private final CurrentUser user;
private final ProjectState state;
private final PermissionCollection.Factory permissionFilter;
private final DefaultRefFilter.Factory refFilterFactory;
- private final ChangeData.Factory changeDataFactory;
private final AllUsersName allUsersName;
+ private final RefControl.Factory refControlFactory;
+ private final ChangeControl.Factory changeControlFactory;
private List<SectionMatcher> allSections;
private Map<String, RefControl> refControls;
@@ -92,29 +90,27 @@
private Config cfg;
@Inject
- ProjectControl(
+ protected ProjectControl(
@GitUploadPackGroups Set<AccountGroup.UUID> uploadGroups,
@GitReceivePackGroups Set<AccountGroup.UUID> receiveGroups,
PermissionCollection.Factory permissionFilter,
PermissionBackend permissionBackend,
- RefVisibilityControl refVisibilityControl,
- GitRepositoryManager repositoryManager,
DefaultRefFilter.Factory refFilterFactory,
- ChangeData.Factory changeDataFactory,
AllUsersName allUsersName,
@GerritServerConfig Config cfg,
+ RefControl.Factory refControlFactory,
+ ChangeControl.Factory changeControlFactory,
@Assisted CurrentUser who,
@Assisted ProjectState ps) {
this.uploadGroups = uploadGroups;
this.receiveGroups = receiveGroups;
this.permissionFilter = permissionFilter;
this.permissionBackend = permissionBackend;
- this.refVisibilityControl = refVisibilityControl;
- this.repositoryManager = repositoryManager;
this.refFilterFactory = refFilterFactory;
- this.changeDataFactory = changeDataFactory;
this.allUsersName = allUsersName;
this.cfg = cfg;
+ this.refControlFactory = refControlFactory;
+ this.changeControlFactory = changeControlFactory;
user = who;
state = ps;
}
@@ -124,7 +120,7 @@
}
ChangeControl controlFor(ChangeData cd) {
- return new ChangeControl(controlForRef(cd.branchOrThrow()), cd);
+ return changeControlFactory.create(controlForRef(cd.branchOrThrow()), cd);
}
RefControl controlForRef(BranchNameKey ref) {
@@ -138,19 +134,17 @@
RefControl ctl = refControls.get(refName);
if (ctl == null) {
PermissionCollection relevant = permissionFilter.filter(access(), refName, user);
- ctl =
- new RefControl(
- changeDataFactory, refVisibilityControl, this, repositoryManager, refName, relevant);
+ ctl = refControlFactory.create(this, refName, relevant);
refControls.put(refName, ctl);
}
return ctl;
}
- CurrentUser getUser() {
+ protected CurrentUser getUser() {
return user;
}
- ProjectState getProjectState() {
+ protected ProjectState getProjectState() {
return state;
}
@@ -361,6 +355,13 @@
}
}
+ @UsedAt(UsedAt.Project.GOOGLE)
+ protected boolean canUpdateConfigWithoutCreatingChange() {
+ // In google, the implementation use more complicated logic - this is why it is placed inside
+ // a ProjectControl.
+ return !cfg.getBoolean("gerrit", "requireChangeForConfigUpdate", false);
+ }
+
private class ForProjectImpl extends ForProject {
private String resourcePath;
@@ -488,12 +489,5 @@
}
throw new PermissionBackendException(perm + " unsupported");
}
-
- @UsedAt(UsedAt.Project.GOOGLE)
- protected boolean canUpdateConfigWithoutCreatingChange() {
- // In google, the implementation use more complicated logic - this is why it is placed inside
- // a ProjectControl.
- return !cfg.getBoolean("gerrit", "requireChangeForConfigUpdate", false);
- }
}
}
diff --git a/java/com/google/gerrit/server/permissions/RefControl.java b/java/com/google/gerrit/server/permissions/RefControl.java
index 9920631..ce8548d 100644
--- a/java/com/google/gerrit/server/permissions/RefControl.java
+++ b/java/com/google/gerrit/server/permissions/RefControl.java
@@ -37,6 +37,8 @@
import com.google.gerrit.server.permissions.PermissionBackend.ForRef;
import com.google.gerrit.server.query.change.ChangeData;
import com.google.gerrit.server.util.MagicBranch;
+import com.google.inject.Inject;
+import com.google.inject.assistedinject.Assisted;
import java.io.IOException;
import java.util.Collection;
import java.util.EnumSet;
@@ -47,8 +49,17 @@
import org.eclipse.jgit.lib.Ref;
import org.eclipse.jgit.lib.Repository;
-/** Manages access control for Git references (aka branches, tags). */
-class RefControl {
+/**
+ * Manages access control for Git references (aka branches, tags).
+ *
+ * <p>Do not use this class directly - instead use {@link ProjectControl} class. This class is
+ * public only because it is extended in google-owned implementation.
+ */
+public class RefControl {
+ public interface Factory {
+ RefControl create(ProjectControl projectControl, String ref, PermissionCollection relevant);
+ }
+
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
private final ChangeData.Factory changeDataFactory;
@@ -67,22 +78,23 @@
private Boolean canForgeCommitter;
private Boolean hasReadPermissionOnRef;
- RefControl(
+ @Inject
+ protected RefControl(
ChangeData.Factory changeDataFactory,
RefVisibilityControl refVisibilityControl,
- ProjectControl projectControl,
GitRepositoryManager repositoryManager,
- String ref,
- PermissionCollection relevant) {
+ @Assisted ProjectControl projectControl,
+ @Assisted String ref,
+ @Assisted PermissionCollection relevant) {
this.changeDataFactory = changeDataFactory;
this.refVisibilityControl = refVisibilityControl;
- this.projectControl = projectControl;
this.repositoryManager = repositoryManager;
+ this.projectControl = projectControl;
this.refName = ref;
this.relevant = relevant;
}
- ProjectControl getProjectControl() {
+ protected ProjectControl getProjectControl() {
return projectControl;
}
@@ -90,6 +102,10 @@
return projectControl.getUser();
}
+ protected String getRefName() {
+ return refName;
+ }
+
/** Is this user a ref owner? */
boolean isOwner() {
if (owner == null) {
@@ -139,7 +155,7 @@
}
/** Returns true if this user can submit patch sets to this ref */
- boolean canSubmit(boolean isChangeOwner) {
+ protected boolean canSubmit(boolean isChangeOwner) {
if (RefNames.REFS_CONFIG.equals(refName)) {
// Always allow project owners to submit configuration changes.
// Submitting configuration changes modifies the access control
@@ -297,7 +313,7 @@
return pr.getAction() == Action.BLOCK && (!pr.getForce() || withForce);
}
- private PermissionRange toRange(String permissionName, boolean isChangeOwner) {
+ protected PermissionRange toRange(String permissionName, boolean isChangeOwner) {
int blockAllowMin = Integer.MIN_VALUE, blockAllowMax = Integer.MAX_VALUE;
projectLoop:
@@ -609,98 +625,98 @@
public BooleanCondition testCond(RefPermission perm) {
return new PermissionBackendCondition.ForRef(this, perm, getUser());
}
+ }
- private boolean can(RefPermission perm) throws PermissionBackendException {
- switch (perm) {
- case READ:
- /* Internal users such as plugin users should be able to read all refs. */
- if (getUser().isInternalUser()) {
- return true;
- }
- if (refName.startsWith(Constants.R_TAGS)) {
- return isTagVisible();
- }
- return refVisibilityControl.isVisible(projectControl, refName);
- case CREATE:
- // TODO This isn't an accurate test.
- return canPerform(refPermissionName(perm));
- case DELETE:
- return canDelete();
- case UPDATE:
- return canUpdate();
- case FORCE_UPDATE:
- return canForceUpdate();
- case SET_HEAD:
- return projectControl.isOwner();
+ protected boolean can(RefPermission perm) throws PermissionBackendException {
+ switch (perm) {
+ case READ:
+ /* Internal users such as plugin users should be able to read all refs. */
+ if (getUser().isInternalUser()) {
+ return true;
+ }
+ if (refName.startsWith(Constants.R_TAGS)) {
+ return isTagVisible();
+ }
+ return refVisibilityControl.isVisible(projectControl, refName);
+ case CREATE:
+ // TODO This isn't an accurate test.
+ return canPerform(refPermissionName(perm));
+ case DELETE:
+ return canDelete();
+ case UPDATE:
+ return canUpdate();
+ case FORCE_UPDATE:
+ return canForceUpdate();
+ case SET_HEAD:
+ return projectControl.isOwner();
- case FORGE_AUTHOR:
- return canForgeAuthor();
- case FORGE_COMMITTER:
- return canForgeCommitter();
- case FORGE_SERVER:
- return canForgeGerritServerIdentity();
- case MERGE:
- return canUploadMerges();
+ case FORGE_AUTHOR:
+ return canForgeAuthor();
+ case FORGE_COMMITTER:
+ return canForgeCommitter();
+ case FORGE_SERVER:
+ return canForgeGerritServerIdentity();
+ case MERGE:
+ return canUploadMerges();
- case CREATE_CHANGE:
- return canUpload();
+ case CREATE_CHANGE:
+ return canUpload();
- case CREATE_TAG:
- case CREATE_SIGNED_TAG:
- return canPerform(refPermissionName(perm));
+ case CREATE_TAG:
+ case CREATE_SIGNED_TAG:
+ return canPerform(refPermissionName(perm));
- case UPDATE_BY_SUBMIT:
- return projectControl.controlForRef(MagicBranch.NEW_CHANGE + refName).canSubmit(true);
+ case UPDATE_BY_SUBMIT:
+ return projectControl.controlForRef(MagicBranch.NEW_CHANGE + refName).canSubmit(true);
- case READ_PRIVATE_CHANGES:
- return canPerform(Permission.VIEW_PRIVATE_CHANGES);
+ case READ_PRIVATE_CHANGES:
+ return canPerform(Permission.VIEW_PRIVATE_CHANGES);
- case READ_CONFIG:
- return projectControl
- .controlForRef(RefNames.REFS_CONFIG)
- .canPerform(RefPermission.READ.name());
- case WRITE_CONFIG:
- return isOwner();
+ case READ_CONFIG:
+ return projectControl
+ .controlForRef(RefNames.REFS_CONFIG)
+ .canPerform(RefPermission.READ.name());
+ case WRITE_CONFIG:
+ return isOwner();
- case SKIP_VALIDATION:
- return canForgeAuthor()
- && canForgeCommitter()
- && canForgeGerritServerIdentity()
- && canUploadMerges();
- }
- throw new PermissionBackendException(perm + " unsupported");
+ case SKIP_VALIDATION:
+ return canForgeAuthor()
+ && canForgeCommitter()
+ && canForgeGerritServerIdentity()
+ && canUploadMerges();
+ }
+ throw new PermissionBackendException(perm + " unsupported");
+ }
+
+ private boolean isTagVisible() throws PermissionBackendException {
+ if (projectControl.asForProject().test(ProjectPermission.READ)) {
+ // The user has READ on refs/* with no effective block permission. This is the broadest
+ // permission one can assign. There is no way to grant access to (specific) tags in Gerrit,
+ // so we have to assume that these users can see all tags because there could be tags that
+ // aren't reachable by any visible ref while the user can see all non-Gerrit refs. This
+ // matches Gerrit's historic behavior.
+ // This makes it so that these users could see commits that they can't see otherwise
+ // (e.g. a private change ref) if a tag was attached to it. Tags are meant to be used on
+ // the regular Git tree that users interact with, not on any of the Gerrit trees, so this
+ // is a negligible risk.
+ return true;
}
- private boolean isTagVisible() throws PermissionBackendException {
- if (projectControl.asForProject().test(ProjectPermission.READ)) {
- // The user has READ on refs/* with no effective block permission. This is the broadest
- // permission one can assign. There is no way to grant access to (specific) tags in Gerrit,
- // so we have to assume that these users can see all tags because there could be tags that
- // aren't reachable by any visible ref while the user can see all non-Gerrit refs. This
- // matches Gerrit's historic behavior.
- // This makes it so that these users could see commits that they can't see otherwise
- // (e.g. a private change ref) if a tag was attached to it. Tags are meant to be used on
- // the regular Git tree that users interact with, not on any of the Gerrit trees, so this
- // is a negligible risk.
- return true;
+ try (Repository repo =
+ repositoryManager.openRepository(projectControl.getProject().getNameKey())) {
+ // Tag visibility requires going through RefFilter because it entails loading all taggable
+ // refs and filtering them all by visibility.
+ Ref resolvedRef = repo.getRefDatabase().exactRef(refName);
+ if (resolvedRef == null) {
+ return false;
}
-
- try (Repository repo =
- repositoryManager.openRepository(projectControl.getProject().getNameKey())) {
- // Tag visibility requires going through RefFilter because it entails loading all taggable
- // refs and filtering them all by visibility.
- Ref resolvedRef = repo.getRefDatabase().exactRef(refName);
- if (resolvedRef == null) {
- return false;
- }
- return projectControl.asForProject()
- .filter(
- ImmutableList.of(resolvedRef), repo, PermissionBackend.RefFilterOptions.defaults())
- .stream()
- .anyMatch(r -> refName.equals(r.getName()));
- } catch (IOException e) {
- throw new PermissionBackendException(e);
- }
+ return projectControl.asForProject()
+ .filter(
+ ImmutableList.of(resolvedRef), repo, PermissionBackend.RefFilterOptions.defaults())
+ .stream()
+ .anyMatch(r -> refName.equals(r.getName()));
+ } catch (IOException e) {
+ throw new PermissionBackendException(e);
}
}
diff --git a/java/com/google/gerrit/server/permissions/RefVisibilityControl.java b/java/com/google/gerrit/server/permissions/RefVisibilityControl.java
index c2d1139..bae1fef 100644
--- a/java/com/google/gerrit/server/permissions/RefVisibilityControl.java
+++ b/java/com/google/gerrit/server/permissions/RefVisibilityControl.java
@@ -38,7 +38,7 @@
* authoritatively tell if a ref is accessible by a user.
*/
@Singleton
-class RefVisibilityControl {
+public class RefVisibilityControl {
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
private final PermissionBackend permissionBackend;