Workaround Gitiles bug on All-Users visibility
Gitiles has special FilteredRepository wrapper that
allows to carefully hide refs based on the project's ACLs.
There is however an optimisation that skips the filtering
in case a user has READ permissions on every ACLs patterns.
When the target repository is All-Users, the optimisation
turns into a security issue because it allows seeing everything
that belongs to everyone:
- draft comments
- PII of all users
- external ids
- draft edits
Block Gitiles or any other part of Gerrit to abuse of this
power when the target repository is All-Users, where nobody
can be authorised to skip the ACLs evaluation.
Cover the additional special case of the All-Users project
access with two explicit positive and negative tests,
so that the security check is covered.
Bug: Issue 13621
Change-Id: Ia6ea1a9fd5473adff534204aea7d8f25324a45b7
(cherry picked from commit 45071d6977)
This commit is contained in:
@@ -39,6 +39,7 @@ import com.google.gerrit.reviewdb.client.RefNames;
|
|||||||
import com.google.gerrit.reviewdb.server.ReviewDb;
|
import com.google.gerrit.reviewdb.server.ReviewDb;
|
||||||
import com.google.gerrit.server.CurrentUser;
|
import com.google.gerrit.server.CurrentUser;
|
||||||
import com.google.gerrit.server.account.GroupMembership;
|
import com.google.gerrit.server.account.GroupMembership;
|
||||||
|
import com.google.gerrit.server.config.AllUsersName;
|
||||||
import com.google.gerrit.server.config.GitReceivePackGroups;
|
import com.google.gerrit.server.config.GitReceivePackGroups;
|
||||||
import com.google.gerrit.server.config.GitUploadPackGroups;
|
import com.google.gerrit.server.config.GitUploadPackGroups;
|
||||||
import com.google.gerrit.server.git.GitRepositoryManager;
|
import com.google.gerrit.server.git.GitRepositoryManager;
|
||||||
@@ -140,6 +141,7 @@ public class ProjectControl {
|
|||||||
private final RefVisibilityControl refVisibilityControl;
|
private final RefVisibilityControl refVisibilityControl;
|
||||||
private final VisibleRefFilter.Factory visibleRefFilterFactory;
|
private final VisibleRefFilter.Factory visibleRefFilterFactory;
|
||||||
private final GitRepositoryManager gitRepositoryManager;
|
private final GitRepositoryManager gitRepositoryManager;
|
||||||
|
private final AllUsersName allUsersName;
|
||||||
|
|
||||||
private List<SectionMatcher> allSections;
|
private List<SectionMatcher> allSections;
|
||||||
private Map<String, RefControl> refControls;
|
private Map<String, RefControl> refControls;
|
||||||
@@ -156,6 +158,7 @@ public class ProjectControl {
|
|||||||
RefVisibilityControl refVisibilityControl,
|
RefVisibilityControl refVisibilityControl,
|
||||||
GitRepositoryManager gitRepositoryManager,
|
GitRepositoryManager gitRepositoryManager,
|
||||||
VisibleRefFilter.Factory visibleRefFilterFactory,
|
VisibleRefFilter.Factory visibleRefFilterFactory,
|
||||||
|
AllUsersName allUsersName,
|
||||||
@Assisted CurrentUser who,
|
@Assisted CurrentUser who,
|
||||||
@Assisted ProjectState ps) {
|
@Assisted ProjectState ps) {
|
||||||
this.changeControlFactory = changeControlFactory;
|
this.changeControlFactory = changeControlFactory;
|
||||||
@@ -167,6 +170,7 @@ public class ProjectControl {
|
|||||||
this.refVisibilityControl = refVisibilityControl;
|
this.refVisibilityControl = refVisibilityControl;
|
||||||
this.gitRepositoryManager = gitRepositoryManager;
|
this.gitRepositoryManager = gitRepositoryManager;
|
||||||
this.visibleRefFilterFactory = visibleRefFilterFactory;
|
this.visibleRefFilterFactory = visibleRefFilterFactory;
|
||||||
|
this.allUsersName = allUsersName;
|
||||||
user = who;
|
user = who;
|
||||||
state = ps;
|
state = ps;
|
||||||
}
|
}
|
||||||
@@ -262,7 +266,9 @@ public class ProjectControl {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private boolean allRefsAreVisible(Set<String> ignore) {
|
private boolean allRefsAreVisible(Set<String> ignore) {
|
||||||
return user.isInternalUser() || canPerformOnAllRefs(Permission.READ, ignore);
|
return user.isInternalUser()
|
||||||
|
|| (!getProject().getNameKey().equals(allUsersName)
|
||||||
|
&& canPerformOnAllRefs(Permission.READ, ignore));
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Returns whether the project is hidden. */
|
/** Returns whether the project is hidden. */
|
||||||
|
|||||||
@@ -61,6 +61,7 @@ import com.google.gerrit.server.git.TransferConfig;
|
|||||||
import com.google.gerrit.server.git.VisibleRefFilter;
|
import com.google.gerrit.server.git.VisibleRefFilter;
|
||||||
import com.google.gerrit.server.index.SingleVersionModule.SingleVersionListener;
|
import com.google.gerrit.server.index.SingleVersionModule.SingleVersionListener;
|
||||||
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.ProjectPermission;
|
import com.google.gerrit.server.permissions.ProjectPermission;
|
||||||
import com.google.gerrit.server.permissions.RefPermission;
|
import com.google.gerrit.server.permissions.RefPermission;
|
||||||
import com.google.gerrit.server.permissions.RefVisibilityControl;
|
import com.google.gerrit.server.permissions.RefVisibilityControl;
|
||||||
@@ -110,6 +111,16 @@ public class RefControlTest {
|
|||||||
assertThat(u.controlForRef(ref).isOwner()).named("NOT OWN " + ref).isFalse();
|
assertThat(u.controlForRef(ref).isOwner()).named("NOT OWN " + ref).isFalse();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void assertAllRefsAreVisible(ProjectControl u) throws PermissionBackendException {
|
||||||
|
assertThat(u.asForProject().test(ProjectPermission.READ)).named("all refs visible").isTrue();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void assertAllRefsAreNotVisible(ProjectControl u) throws PermissionBackendException {
|
||||||
|
assertThat(u.asForProject().test(ProjectPermission.READ))
|
||||||
|
.named("all refs NOT visible")
|
||||||
|
.isFalse();
|
||||||
|
}
|
||||||
|
|
||||||
private void assertCanAccess(ProjectControl u) {
|
private void assertCanAccess(ProjectControl u) {
|
||||||
boolean access = u.asForProject().testOrFalse(ProjectPermission.ACCESS);
|
boolean access = u.asForProject().testOrFalse(ProjectPermission.ACCESS);
|
||||||
assertThat(access).named("can access").isTrue();
|
assertThat(access).named("can access").isTrue();
|
||||||
@@ -199,6 +210,7 @@ public class RefControlTest {
|
|||||||
private final Map<Project.NameKey, ProjectState> all = new HashMap<>();
|
private final Map<Project.NameKey, ProjectState> all = new HashMap<>();
|
||||||
private Project.NameKey localKey = new Project.NameKey("local");
|
private Project.NameKey localKey = new Project.NameKey("local");
|
||||||
private ProjectConfig local;
|
private ProjectConfig local;
|
||||||
|
private ProjectConfig allUsers;
|
||||||
private Project.NameKey parentKey = new Project.NameKey("parent");
|
private Project.NameKey parentKey = new Project.NameKey("parent");
|
||||||
private ProjectConfig parent;
|
private ProjectConfig parent;
|
||||||
private InMemoryRepositoryManager repoManager;
|
private InMemoryRepositoryManager repoManager;
|
||||||
@@ -230,7 +242,7 @@ public class RefControlTest {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ProjectState getAllUsers() {
|
public ProjectState getAllUsers() {
|
||||||
return null;
|
return get(allUsersName);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -290,6 +302,11 @@ public class RefControlTest {
|
|||||||
LabelType cr = Util.codeReview();
|
LabelType cr = Util.codeReview();
|
||||||
allProjects.getLabelSections().put(cr.getName(), cr);
|
allProjects.getLabelSections().put(cr.getName(), cr);
|
||||||
add(allProjects);
|
add(allProjects);
|
||||||
|
|
||||||
|
Repository allUsersRepo = repoManager.createRepository(allUsersName);
|
||||||
|
allUsers = new ProjectConfig(new Project.NameKey(allUsersName.get()));
|
||||||
|
allUsers.load(allUsersRepo);
|
||||||
|
add(allUsers);
|
||||||
} catch (IOException | ConfigInvalidException e) {
|
} catch (IOException | ConfigInvalidException e) {
|
||||||
throw new RuntimeException(e);
|
throw new RuntimeException(e);
|
||||||
}
|
}
|
||||||
@@ -363,6 +380,24 @@ public class RefControlTest {
|
|||||||
assertAdminsAreOwnersAndDevsAreNot();
|
assertAdminsAreOwnersAndDevsAreNot();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void allRefsAreVisibleForRegularProject() throws Exception {
|
||||||
|
allow(local, READ, DEVS, "refs/*");
|
||||||
|
allow(local, READ, DEVS, "refs/groups/*");
|
||||||
|
allow(local, READ, DEVS, "refs/users/default");
|
||||||
|
|
||||||
|
assertAllRefsAreVisible(user(local, DEVS));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void allRefsAreNotVisibleForAllUsers() throws Exception {
|
||||||
|
allow(allUsers, READ, DEVS, "refs/*");
|
||||||
|
allow(allUsers, READ, DEVS, "refs/groups/*");
|
||||||
|
allow(allUsers, READ, DEVS, "refs/users/default");
|
||||||
|
|
||||||
|
assertAllRefsAreNotVisible(user(allUsers, DEVS));
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void branchDelegation1() {
|
public void branchDelegation1() {
|
||||||
allow(local, OWNER, ADMIN, "refs/*");
|
allow(local, OWNER, ADMIN, "refs/*");
|
||||||
@@ -897,6 +932,7 @@ public class RefControlTest {
|
|||||||
refVisibilityControl,
|
refVisibilityControl,
|
||||||
gitRepositoryManager,
|
gitRepositoryManager,
|
||||||
visibleRefFilterFactory,
|
visibleRefFilterFactory,
|
||||||
|
allUsersName,
|
||||||
new MockUser(name, memberOf),
|
new MockUser(name, memberOf),
|
||||||
newProjectState(local));
|
newProjectState(local));
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user