blob: fd2385b98d2f8de6ca46135538ae8ab8a7df8e96 [file] [log] [blame]
// Copyright (C) 2014 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.git;
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.TruthJUnit.assume;
import static com.google.gerrit.server.group.SystemGroupBackend.REGISTERED_USERS;
import com.google.gerrit.acceptance.AbstractDaemonTest;
import com.google.gerrit.acceptance.AcceptanceTestRequestScope;
import com.google.gerrit.acceptance.NoHttpd;
import com.google.gerrit.acceptance.PushOneCommit;
import com.google.gerrit.common.Nullable;
import com.google.gerrit.common.data.AccessSection;
import com.google.gerrit.common.data.GlobalCapability;
import com.google.gerrit.common.data.Permission;
import com.google.gerrit.extensions.api.projects.BranchInput;
import com.google.gerrit.reviewdb.client.AccountGroup;
import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.reviewdb.client.PatchSet;
import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.reviewdb.client.RefNames;
import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.edit.ChangeEditModifier;
import com.google.gerrit.server.git.ProjectConfig;
import com.google.gerrit.server.git.SearchingChangeCacheImpl;
import com.google.gerrit.server.git.TagCache;
import com.google.gerrit.server.git.VisibleRefFilter;
import com.google.gerrit.server.project.ProjectControl;
import com.google.gerrit.server.project.Util;
import com.google.gerrit.testutil.DisabledReviewDb;
import com.google.inject.Inject;
import com.google.inject.Provider;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.Ref;
import org.eclipse.jgit.lib.RefUpdate;
import org.eclipse.jgit.lib.Repository;
import org.junit.Before;
import org.junit.Test;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
@NoHttpd
public class VisibleRefFilterIT extends AbstractDaemonTest {
@Inject
private ChangeEditModifier editModifier;
@Inject
private ProjectControl.GenericFactory projectControlFactory;
@Inject
@Nullable
private SearchingChangeCacheImpl changeCache;
@Inject
private TagCache tagCache;
@Inject
private Provider<CurrentUser> userProvider;
private AccountGroup.UUID admins;
private Change.Id c1;
private Change.Id c2;
private String r1;
private String r2;
@Before
public void setUp() throws Exception {
admins = groupCache.get(new AccountGroup.NameKey("Administrators"))
.getGroupUUID();
setUpPermissions();
setUpChanges();
}
private void setUpPermissions() throws Exception {
// Remove read permissions for all users besides admin. This method is
// idempotent, so is safe to call on every test setup.
ProjectConfig pc = projectCache.checkedGet(allProjects).getConfig();
for (AccessSection sec : pc.getAccessSections()) {
sec.removePermission(Permission.READ);
}
Util.allow(pc, Permission.READ, admins, "refs/*");
saveProjectConfig(allProjects, pc);
}
private static String changeRefPrefix(Change.Id id) {
String ps = new PatchSet.Id(id, 1).toRefName();
return ps.substring(0, ps.length() - 1);
}
private void setUpChanges() throws Exception {
gApi.projects()
.name(project.get())
.branch("branch")
.create(new BranchInput());
allow(Permission.SUBMIT, admins, "refs/for/refs/heads/*");
PushOneCommit.Result mr = pushFactory.create(db, admin.getIdent(), testRepo)
.to("refs/for/master%submit");
mr.assertOkStatus();
c1 = mr.getChange().getId();
r1 = changeRefPrefix(c1);
PushOneCommit.Result br = pushFactory.create(db, admin.getIdent(), testRepo)
.to("refs/for/branch%submit");
br.assertOkStatus();
c2 = br.getChange().getId();
r2 = changeRefPrefix(c2);
try (Repository repo = repoManager.openRepository(project)) {
// master-tag -> master
RefUpdate mtu = repo.updateRef("refs/tags/master-tag");
mtu.setExpectedOldObjectId(ObjectId.zeroId());
mtu.setNewObjectId(repo.exactRef("refs/heads/master").getObjectId());
assertThat(mtu.update()).isEqualTo(RefUpdate.Result.NEW);
// branch-tag -> branch
RefUpdate btu = repo.updateRef("refs/tags/branch-tag");
btu.setExpectedOldObjectId(ObjectId.zeroId());
btu.setNewObjectId(repo.exactRef("refs/heads/branch").getObjectId());
assertThat(btu.update()).isEqualTo(RefUpdate.Result.NEW);
}
}
@Test
public void allRefsVisibleNoRefsMetaConfig() throws Exception {
ProjectConfig cfg = projectCache.checkedGet(project).getConfig();
Util.allow(cfg, Permission.READ, REGISTERED_USERS, "refs/*");
Util.allow(cfg, Permission.READ, admins, RefNames.REFS_CONFIG);
Util.doNotInherit(cfg, Permission.READ, RefNames.REFS_CONFIG);
saveProjectConfig(project, cfg);
setApiUser(user);
assertRefs(
"HEAD",
r1 + "1",
r1 + "meta",
r2 + "1",
r2 + "meta",
"refs/heads/branch",
"refs/heads/master",
"refs/tags/branch-tag",
"refs/tags/master-tag");
}
@Test
public void allRefsVisibleWithRefsMetaConfig() throws Exception {
allow(Permission.READ, REGISTERED_USERS, "refs/*");
allow(Permission.READ, REGISTERED_USERS, RefNames.REFS_CONFIG);
assertRefs(
"HEAD",
r1 + "1",
r1 + "meta",
r2 + "1",
r2 + "meta",
"refs/heads/branch",
"refs/heads/master",
RefNames.REFS_CONFIG,
"refs/tags/branch-tag",
"refs/tags/master-tag");
}
@Test
public void subsetOfBranchesVisibleIncludingHead() throws Exception {
allow(Permission.READ, REGISTERED_USERS, "refs/heads/master");
deny(Permission.READ, REGISTERED_USERS, "refs/heads/branch");
setApiUser(user);
assertRefs(
"HEAD",
r1 + "1",
r1 + "meta",
"refs/heads/master",
"refs/tags/master-tag");
}
@Test
public void subsetOfBranchesVisibleNotIncludingHead() throws Exception {
deny(Permission.READ, REGISTERED_USERS, "refs/heads/master");
allow(Permission.READ, REGISTERED_USERS, "refs/heads/branch");
setApiUser(user);
assertRefs(
r2 + "1",
r2 + "meta",
"refs/heads/branch",
"refs/tags/branch-tag",
// master branch is not visible but master-tag is reachable from branch
// (since PushOneCommit always bases changes on each other).
"refs/tags/master-tag");
}
@Test
public void subsetOfBranchesVisibleWithEdit() throws Exception {
allow(Permission.READ, REGISTERED_USERS, "refs/heads/master");
deny(Permission.READ, REGISTERED_USERS, "refs/heads/branch");
Change c = notesFactory.createChecked(db, project, c1).getChange();
PatchSet ps1 = getPatchSet(new PatchSet.Id(c1, 1));
// Admin's edit is not visible.
setApiUser(admin);
editModifier.createEdit(c, ps1);
// User's edit is visible.
setApiUser(user);
editModifier.createEdit(c, ps1);
assertRefs(
"HEAD",
r1 + "1",
r1 + "meta",
"refs/heads/master",
"refs/tags/master-tag",
"refs/users/01/1000001/edit-" + c1.get() + "/1");
}
@Test
public void subsetOfRefsVisibleWithAccessDatabase() throws Exception {
allowGlobalCapabilities(REGISTERED_USERS, GlobalCapability.ACCESS_DATABASE);
try {
deny(Permission.READ, REGISTERED_USERS, "refs/heads/master");
allow(Permission.READ, REGISTERED_USERS, "refs/heads/branch");
Change c = notesFactory.createChecked(db, project, c1).getChange();
PatchSet ps1 = getPatchSet(new PatchSet.Id(c1, 1));
setApiUser(admin);
editModifier.createEdit(c, ps1);
setApiUser(user);
assertRefs(
// Change 1 is visible due to accessDatabase capability, even though
// refs/heads/master is not.
r1 + "1",
r1 + "meta",
r2 + "1",
r2 + "meta",
"refs/heads/branch",
"refs/tags/branch-tag",
// See comment in subsetOfBranchesVisibleNotIncludingHead.
"refs/tags/master-tag",
// All edits are visible due to accessDatabase capability.
"refs/users/00/1000000/edit-" + c1.get() + "/1");
} finally {
removeGlobalCapabilities(REGISTERED_USERS, GlobalCapability.ACCESS_DATABASE);
}
}
@Test
public void draftRefs() throws Exception {
allow(Permission.READ, REGISTERED_USERS, "refs/heads/*");
PushOneCommit.Result br = pushFactory.create(db, admin.getIdent(), testRepo)
.to("refs/drafts/master");
br.assertOkStatus();
Change.Id c3 = br.getChange().getId();
String r3 = changeRefPrefix(c3);
// Only admin can see admin's draft change.
setApiUser(admin);
assertRefs(
"HEAD",
r1 + "1",
r1 + "meta",
r2 + "1",
r2 + "meta",
r3 + "1",
r3 + "meta",
"refs/heads/branch",
"refs/heads/master",
RefNames.REFS_CONFIG,
"refs/tags/branch-tag",
"refs/tags/master-tag");
// user can't.
setApiUser(user);
assertRefs(
"HEAD",
r1 + "1",
r1 + "meta",
r2 + "1",
r2 + "meta",
"refs/heads/branch",
"refs/heads/master",
"refs/tags/branch-tag",
"refs/tags/master-tag");
}
@Test
public void noSearchingChangeCacheImpl() throws Exception {
allow(Permission.READ, REGISTERED_USERS, "refs/heads/*");
setApiUser(user);
try (Repository repo = repoManager.openRepository(project)) {
assertRefs(
repo,
new VisibleRefFilter(tagCache, notesFactory, null, repo,
projectControl(), db, true),
// Can't use stored values from the index so DB must be enabled.
false,
"HEAD",
r1 + "1",
r1 + "meta",
r2 + "1",
r2 + "meta",
"refs/heads/branch",
"refs/heads/master",
"refs/tags/branch-tag",
"refs/tags/master-tag");
}
}
@Test
public void sequencesWithAccessDatabase() throws Exception {
assume().that(notesMigration.readChangeSequence()).isTrue();
try (Repository repo = repoManager.openRepository(allProjects)) {
setApiUser(user);
assertRefs(repo, newFilter(db, repo, allProjects), true);
allowGlobalCapabilities(
REGISTERED_USERS, GlobalCapability.ACCESS_DATABASE);
try {
setApiUser(user);
assertRefs(
repo, newFilter(db, repo, allProjects), true,
"refs/sequences/changes");
} finally {
removeGlobalCapabilities(
REGISTERED_USERS, GlobalCapability.ACCESS_DATABASE);
}
}
}
/**
* Assert that refs seen by a non-admin user match expected.
*
* @param expectedWithMeta expected refs, in order. If NoteDb is disabled by
* the configuration, any NoteDb refs (i.e. ending in "/meta") are removed
* from the expected list before comparing to the actual results.
* @throws Exception
*/
private void assertRefs(String... expectedWithMeta) throws Exception {
try (Repository repo = repoManager.openRepository(project)) {
assertRefs(
repo,
new VisibleRefFilter(tagCache, notesFactory, changeCache, repo,
projectControl(), new DisabledReviewDb(), true),
true,
expectedWithMeta);
}
}
private void assertRefs(Repository repo, VisibleRefFilter filter,
boolean disableDb, String... expectedWithMeta) throws Exception {
List<String> expected = new ArrayList<>(expectedWithMeta.length);
for (String r : expectedWithMeta) {
if (notesMigration.writeChanges() || !r.endsWith(RefNames.META_SUFFIX)) {
expected.add(r);
}
}
AcceptanceTestRequestScope.Context ctx = null;
if (disableDb) {
ctx = disableDb();
}
try {
Map<String, Ref> all = repo.getAllRefs();
assertThat(filter.filter(all, false).keySet())
.containsExactlyElementsIn(expected);
} finally {
if (disableDb) {
enableDb(ctx);
}
}
}
private ProjectControl projectControl() throws Exception {
return projectControlFactory.controlFor(project, userProvider.get());
}
private VisibleRefFilter newFilter(ReviewDb db, Repository repo,
Project.NameKey project) throws Exception {
return new VisibleRefFilter(
tagCache, notesFactory, null, repo,
projectControlFactory.controlFor(project, userProvider.get()),
db, true);
}
}