blob: 33698fe30c4406159867a3c009f6183e1ef868c0 [file] [log] [blame]
// Copyright (C) 2010 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.server.permissions;
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth.assertWithMessage;
import static com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate.allow;
import static com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate.allowLabel;
import static com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate.block;
import static com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate.blockLabel;
import static com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate.deny;
import static com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate.labelPermissionKey;
import static com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate.permissionKey;
import static com.google.gerrit.entities.Permission.EDIT_TOPIC_NAME;
import static com.google.gerrit.entities.Permission.LABEL;
import static com.google.gerrit.entities.Permission.OWNER;
import static com.google.gerrit.entities.Permission.PUSH;
import static com.google.gerrit.entities.Permission.READ;
import static com.google.gerrit.entities.Permission.SUBMIT;
import static com.google.gerrit.entities.RefNames.REFS_CONFIG;
import static com.google.gerrit.server.group.SystemGroupBackend.ANONYMOUS_USERS;
import static com.google.gerrit.server.group.SystemGroupBackend.CHANGE_OWNER;
import static com.google.gerrit.server.group.SystemGroupBackend.REGISTERED_USERS;
import static com.google.gerrit.server.project.ProjectCache.illegalState;
import static com.google.gerrit.testing.GerritJUnit.assertThrows;
import com.google.common.collect.Lists;
import com.google.gerrit.acceptance.testsuite.project.ProjectOperations;
import com.google.gerrit.common.Nullable;
import com.google.gerrit.entities.AccountGroup;
import com.google.gerrit.entities.LabelId;
import com.google.gerrit.entities.LabelType;
import com.google.gerrit.entities.PermissionRange;
import com.google.gerrit.entities.Project;
import com.google.gerrit.exceptions.InvalidNameException;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.InternalUser;
import com.google.gerrit.server.account.GroupMembership;
import com.google.gerrit.server.account.ListGroupMembership;
import com.google.gerrit.server.config.AllProjectsName;
import com.google.gerrit.server.config.AllUsersName;
import com.google.gerrit.server.git.meta.MetaDataUpdate;
import com.google.gerrit.server.index.SingleVersionModule.SingleVersionListener;
import com.google.gerrit.server.project.ProjectCache;
import com.google.gerrit.server.project.ProjectConfig;
import com.google.gerrit.server.project.ProjectState;
import com.google.gerrit.server.project.RefPattern;
import com.google.gerrit.server.project.testing.TestLabels;
import com.google.gerrit.server.schema.SchemaCreator;
import com.google.gerrit.server.util.ThreadLocalRequestContext;
import com.google.gerrit.testing.InMemoryModule;
import com.google.gerrit.testing.InMemoryRepositoryManager;
import com.google.inject.Guice;
import com.google.inject.Inject;
import com.google.inject.Injector;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Optional;
import org.eclipse.jgit.junit.TestRepository;
import org.eclipse.jgit.lib.Repository;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
public class RefControlTest {
private static final AccountGroup.UUID ADMIN = AccountGroup.uuid("test.admin");
private static final AccountGroup.UUID DEVS = AccountGroup.uuid("test.devs");
private void assertAdminsAreOwnersAndDevsAreNot() throws Exception {
ProjectControl uBlah = user(localKey, DEVS);
ProjectControl uAdmin = user(localKey, DEVS, ADMIN);
assertWithMessage("not owner").that(uBlah.isOwner()).isFalse();
assertWithMessage("is owner").that(uAdmin.isOwner()).isTrue();
}
private void assertOwner(String ref, ProjectControl u) {
assertWithMessage("OWN " + ref).that(u.controlForRef(ref).isOwner()).isTrue();
}
private void assertNotOwner(ProjectControl u) {
assertWithMessage("not owner").that(u.isOwner()).isFalse();
}
private void assertAllRefsAreVisible(ProjectControl u) {
assertWithMessage("all refs visible")
.that(u.allRefsAreVisible(Collections.emptySet()))
.isTrue();
}
private void assertAllRefsAreNotVisible(ProjectControl u) {
assertWithMessage("all refs NOT visible")
.that(u.allRefsAreVisible(Collections.emptySet()))
.isFalse();
}
private void assertNotOwner(String ref, ProjectControl u) {
assertWithMessage("NOT OWN " + ref).that(u.controlForRef(ref).isOwner()).isFalse();
}
private void assertCanAccess(ProjectControl u) {
boolean access = u.asForProject().testOrFalse(ProjectPermission.ACCESS);
assertWithMessage("can access").that(access).isTrue();
}
private void assertAccessDenied(ProjectControl u) {
boolean access = u.asForProject().testOrFalse(ProjectPermission.ACCESS);
assertWithMessage("cannot access").that(access).isFalse();
}
private void assertCanRead(String ref, ProjectControl u) {
assertWithMessage("can read " + ref)
.that(
u.controlForRef(ref)
.hasReadPermissionOnRef(
true)) // This should be false but the test relies on inheritance into refs/tags
.isTrue();
}
private void assertCannotRead(String ref, ProjectControl u) {
assertWithMessage("cannot read " + ref)
.that(
u.controlForRef(ref)
.hasReadPermissionOnRef(
true)) // This should be false but the test relies on inheritance into refs/tags
.isFalse();
}
private void assertCanSubmit(String ref, ProjectControl u) {
assertWithMessage("can submit " + ref).that(u.controlForRef(ref).canSubmit(false)).isTrue();
}
private void assertCannotSubmit(String ref, ProjectControl u) {
assertWithMessage("can submit " + ref).that(u.controlForRef(ref).canSubmit(false)).isFalse();
}
private void assertCanUpload(ProjectControl u) {
assertWithMessage("can upload").that(u.canPushToAtLeastOneRef()).isTrue();
}
private void assertCreateChange(String ref, ProjectControl u) {
boolean create = u.asForProject().ref(ref).testOrFalse(RefPermission.CREATE_CHANGE);
assertWithMessage("can create change " + ref).that(create).isTrue();
}
private void assertCannotUpload(ProjectControl u) {
assertWithMessage("cannot upload").that(u.canPushToAtLeastOneRef()).isFalse();
}
private void assertCannotCreateChange(String ref, ProjectControl u) {
boolean create = u.asForProject().ref(ref).testOrFalse(RefPermission.CREATE_CHANGE);
assertWithMessage("cannot create change " + ref).that(create).isFalse();
}
private void assertCanUpdate(String ref, ProjectControl u) {
boolean update = u.asForProject().ref(ref).testOrFalse(RefPermission.UPDATE);
assertWithMessage("can update " + ref).that(update).isTrue();
}
private void assertCannotUpdate(String ref, ProjectControl u) {
boolean update = u.asForProject().ref(ref).testOrFalse(RefPermission.UPDATE);
assertWithMessage("cannot update " + ref).that(update).isFalse();
}
private void assertCanForceUpdate(String ref, ProjectControl u) {
boolean update = u.asForProject().ref(ref).testOrFalse(RefPermission.FORCE_UPDATE);
assertWithMessage("can force push " + ref).that(update).isTrue();
}
private void assertCannotForceUpdate(String ref, ProjectControl u) {
boolean update = u.asForProject().ref(ref).testOrFalse(RefPermission.FORCE_UPDATE);
assertWithMessage("cannot force push " + ref).that(update).isFalse();
}
private void assertCanVote(int score, PermissionRange range) {
assertWithMessage("can vote " + score).that(range.contains(score)).isTrue();
}
private void assertCannotVote(int score, PermissionRange range) {
assertWithMessage("cannot vote " + score).that(range.contains(score)).isFalse();
}
private final AccountGroup.UUID fixers = AccountGroup.uuid("test.fixers");
private final Project.NameKey localKey = Project.nameKey("local");
private final Project.NameKey parentKey = Project.nameKey("parent");
@Inject private AllProjectsName allProjectsName;
@Inject private AllUsersName allUsersName;
@Inject private InMemoryRepositoryManager repoManager;
@Inject private MetaDataUpdate.Server metaDataUpdateFactory;
@Inject private ProjectCache projectCache;
@Inject private ProjectControl.Factory projectControlFactory;
@Inject private ProjectOperations projectOperations;
@Inject private SchemaCreator schemaCreator;
@Inject private SingleVersionListener singleVersionListener;
@Inject private ThreadLocalRequestContext requestContext;
@Before
public void setUp() throws Exception {
Injector injector = Guice.createInjector(new InMemoryModule());
injector.injectMembers(this);
// Tests previously used ProjectConfig.Factory to create ProjectConfigs without going through
// the ProjectCache, which was wrong. Manually call getInstance so we don't store it in a
// field that is accessible to test methods.
ProjectConfig.Factory projectConfigFactory = injector.getInstance(ProjectConfig.Factory.class);
singleVersionListener.start();
try {
schemaCreator.create();
} finally {
singleVersionListener.stop();
}
// Clear out All-Projects and use the lowest-level API possible for project creation, so the
// only ACL entries are exactly what is initialized by this test, and we aren't subject to
// changing defaults in SchemaCreator or ProjectCreator.
try (Repository allProjectsRepo = repoManager.createRepository(allProjectsName);
TestRepository<Repository> tr = new TestRepository<>(allProjectsRepo)) {
tr.delete(REFS_CONFIG);
try (MetaDataUpdate md = metaDataUpdateFactory.create(allProjectsName)) {
ProjectConfig allProjectsConfig = projectConfigFactory.create(allProjectsName);
allProjectsConfig.load(md);
LabelType cr = TestLabels.codeReview();
allProjectsConfig.upsertLabelType(cr);
allProjectsConfig.commit(md);
}
}
repoManager.createRepository(parentKey).close();
repoManager.createRepository(localKey).close();
try (MetaDataUpdate md = metaDataUpdateFactory.create(localKey)) {
ProjectConfig newLocal = projectConfigFactory.create(localKey);
newLocal.load(md);
newLocal.updateProject(p -> p.setParent(parentKey));
newLocal.commit(md);
}
@SuppressWarnings("unused")
var unused = requestContext.setContext(() -> null);
}
@After
public void tearDown() throws Exception {
@SuppressWarnings("unused")
var unused = requestContext.setContext(null);
}
@Test
public void ownerProject() throws Exception {
projectOperations
.project(localKey)
.forUpdate()
.add(allow(OWNER).ref("refs/*").group(ADMIN))
.update();
assertAdminsAreOwnersAndDevsAreNot();
}
@Test
public void denyOwnerProject() throws Exception {
projectOperations
.project(localKey)
.forUpdate()
.add(allow(OWNER).ref("refs/*").group(ADMIN))
.add(deny(OWNER).ref("refs/*").group(DEVS))
.update();
assertAdminsAreOwnersAndDevsAreNot();
}
@Test
public void blockOwnerProject() throws Exception {
projectOperations
.project(localKey)
.forUpdate()
.add(allow(OWNER).ref("refs/*").group(ADMIN))
.add(block(OWNER).ref("refs/*").group(DEVS))
.update();
assertAdminsAreOwnersAndDevsAreNot();
}
@Test
public void allRefsAreVisibleForRegularProject() throws Exception {
projectOperations
.project(localKey)
.forUpdate()
.add(allow(READ).ref("refs/*").group(DEVS))
.add(allow(READ).ref("refs/groups/*").group(DEVS))
.add(allow(READ).ref("refs/users/default").group(DEVS))
.update();
assertAllRefsAreVisible(user(localKey, DEVS));
}
@Test
public void allRefsAreNotVisibleForAllUsers() throws Exception {
projectOperations
.project(allUsersName)
.forUpdate()
.add(allow(READ).ref("refs/*").group(DEVS))
.add(allow(READ).ref("refs/groups/*").group(DEVS))
.add(allow(READ).ref("refs/users/default").group(DEVS))
.update();
assertAllRefsAreNotVisible(user(allUsersName, DEVS));
}
@Test
public void userRefIsVisibleForInternalUser() throws Exception {
internalUser(localKey).controlForRef("refs/users/default").asForRef().check(RefPermission.READ);
}
@Test
public void branchDelegation1() throws Exception {
projectOperations
.project(localKey)
.forUpdate()
.add(allow(OWNER).ref("refs/*").group(ADMIN))
.add(allow(OWNER).ref("refs/heads/x/*").group(DEVS))
.update();
ProjectControl uDev = user(localKey, DEVS);
assertNotOwner(uDev);
assertOwner("refs/heads/x/*", uDev);
assertOwner("refs/heads/x/y", uDev);
assertOwner("refs/heads/x/y/*", uDev);
assertNotOwner("refs/*", uDev);
assertNotOwner("refs/heads/master", uDev);
}
@Test
public void branchDelegation2() throws Exception {
projectOperations
.project(localKey)
.forUpdate()
.add(allow(OWNER).ref("refs/*").group(ADMIN))
.add(allow(OWNER).ref("refs/heads/x/*").group(DEVS))
.add(allow(OWNER).ref("refs/heads/x/y/*").group(fixers))
.setExclusiveGroup(permissionKey(OWNER).ref("refs/heads/x/y/*"), true)
.update();
ProjectControl uDev = user(localKey, DEVS);
assertNotOwner(uDev);
assertOwner("refs/heads/x/*", uDev);
assertOwner("refs/heads/x/y", uDev);
assertOwner("refs/heads/x/y/*", uDev);
assertNotOwner("refs/*", uDev);
assertNotOwner("refs/heads/master", uDev);
ProjectControl uFix = user(localKey, fixers);
assertNotOwner(uFix);
assertOwner("refs/heads/x/y/*", uFix);
assertOwner("refs/heads/x/y/bar", uFix);
assertNotOwner("refs/heads/x/*", uFix);
assertNotOwner("refs/heads/x/y", uFix);
assertNotOwner("refs/*", uFix);
assertNotOwner("refs/heads/master", uFix);
}
@Test
public void inheritRead_SingleBranchDeniesUpload() throws Exception {
projectOperations
.project(parentKey)
.forUpdate()
.add(allow(READ).ref("refs/*").group(REGISTERED_USERS))
.add(allow(PUSH).ref("refs/for/refs/*").group(REGISTERED_USERS))
.update();
projectOperations
.project(localKey)
.forUpdate()
.add(allow(READ).ref("refs/heads/foobar").group(REGISTERED_USERS))
.setExclusiveGroup(permissionKey(READ).ref("refs/heads/foobar"), true)
.setExclusiveGroup(permissionKey(PUSH).ref("refs/for/refs/heads/foobar"), true)
.update();
ProjectControl u = user(localKey);
assertCanUpload(u);
assertCreateChange("refs/heads/master", u);
assertCannotCreateChange("refs/heads/foobar", u);
}
@Test
public void inheritRead_SingleBranchDoesNotOverrideInherited() throws Exception {
projectOperations
.project(parentKey)
.forUpdate()
.add(allow(READ).ref("refs/*").group(REGISTERED_USERS))
.add(allow(PUSH).ref("refs/for/refs/*").group(REGISTERED_USERS))
.update();
projectOperations
.project(localKey)
.forUpdate()
.add(allow(READ).ref("refs/heads/foobar").group(REGISTERED_USERS))
.update();
ProjectControl u = user(localKey);
assertCanUpload(u);
assertCreateChange("refs/heads/master", u);
assertCreateChange("refs/heads/foobar", u);
}
@Test
public void inheritDuplicateSections() throws Exception {
projectOperations
.project(parentKey)
.forUpdate()
.add(allow(READ).ref("refs/*").group(ADMIN))
.update();
projectOperations
.project(localKey)
.forUpdate()
.add(allow(READ).ref("refs/*").group(DEVS))
.update();
assertCanAccess(user(localKey, "a", ADMIN));
assertCanAccess(user(localKey, "d", DEVS));
}
@Test
public void inheritRead_OverrideWithDeny() throws Exception {
projectOperations
.project(parentKey)
.forUpdate()
.add(allow(READ).ref("refs/*").group(REGISTERED_USERS))
.update();
projectOperations
.project(localKey)
.forUpdate()
.add(deny(READ).ref("refs/*").group(REGISTERED_USERS))
.update();
assertAccessDenied(user(localKey));
}
@Test
public void inheritRead_AppendWithDenyOfRef() throws Exception {
projectOperations
.project(parentKey)
.forUpdate()
.add(allow(READ).ref("refs/*").group(REGISTERED_USERS))
.update();
projectOperations
.project(localKey)
.forUpdate()
.add(deny(READ).ref("refs/heads/*").group(REGISTERED_USERS))
.update();
ProjectControl u = user(localKey);
assertCanAccess(u);
assertCanRead("refs/master", u);
assertCanRead("refs/tags/foobar", u);
assertCanRead("refs/heads/master", u);
}
@Test
public void inheritRead_OverridesAndDeniesOfRef() throws Exception {
projectOperations
.project(parentKey)
.forUpdate()
.add(allow(READ).ref("refs/*").group(REGISTERED_USERS))
.update();
projectOperations
.project(localKey)
.forUpdate()
.add(deny(READ).ref("refs/*").group(REGISTERED_USERS))
.add(allow(READ).ref("refs/heads/*").group(REGISTERED_USERS))
.update();
ProjectControl u = user(localKey);
assertCanAccess(u);
assertCannotRead("refs/foobar", u);
assertCannotRead("refs/tags/foobar", u);
assertCanRead("refs/heads/foobar", u);
}
@Test
public void inheritSubmit_OverridesAndDeniesOfRef() throws Exception {
projectOperations
.project(parentKey)
.forUpdate()
.add(allow(SUBMIT).ref("refs/*").group(REGISTERED_USERS))
.update();
projectOperations
.project(localKey)
.forUpdate()
.add(deny(SUBMIT).ref("refs/*").group(REGISTERED_USERS))
.add(allow(SUBMIT).ref("refs/heads/*").group(REGISTERED_USERS))
.update();
ProjectControl u = user(localKey);
assertCannotSubmit("refs/foobar", u);
assertCannotSubmit("refs/tags/foobar", u);
assertCanSubmit("refs/heads/foobar", u);
}
@Test
public void cannotUploadToAnyRef() throws Exception {
projectOperations
.project(parentKey)
.forUpdate()
.add(allow(READ).ref("refs/*").group(REGISTERED_USERS))
.update();
projectOperations
.project(localKey)
.forUpdate()
.add(allow(READ).ref("refs/heads/*").group(DEVS))
.add(allow(PUSH).ref("refs/for/refs/heads/*").group(DEVS))
.update();
ProjectControl u = user(localKey);
assertCannotUpload(u);
assertCannotCreateChange("refs/heads/master", u);
}
@Test
public void usernamePatternCanUploadToAnyRef() throws Exception {
projectOperations
.project(localKey)
.forUpdate()
.add(allow(PUSH).ref("refs/heads/users/${username}/*").group(REGISTERED_USERS))
.update();
ProjectControl u = user(localKey, "a-registered-user");
assertCanUpload(u);
}
@Test
public void usernamePatternRegExpCanUploadToAnyRef() throws Exception {
projectOperations
.project(localKey)
.forUpdate()
.add(
allow(PUSH)
.ref("^refs/heads/users/${username}/(public|private)/.+")
.group(REGISTERED_USERS))
.update();
ProjectControl u = user(localKey, "a-registered-user");
assertCanUpload(u);
assertCanUpdate("refs/heads/users/a-registered-user/private/a", u);
}
@Test
public void usernamePatternNonRegex() throws Exception {
projectOperations
.project(localKey)
.forUpdate()
.add(allow(READ).ref("refs/sb/${username}/heads/*").group(DEVS))
.update();
ProjectControl u = user(localKey, "u", DEVS);
ProjectControl d = user(localKey, "d", DEVS);
assertCannotRead("refs/sb/d/heads/foobar", u);
assertCanRead("refs/sb/d/heads/foobar", d);
}
@Test
public void usernamePatternWithRegex() throws Exception {
projectOperations
.project(localKey)
.forUpdate()
.add(allow(READ).ref("^refs/sb/${username}/heads/.*").group(DEVS))
.update();
ProjectControl u = user(localKey, "d.v", DEVS);
ProjectControl d = user(localKey, "dev", DEVS);
assertCanAccess(u);
assertCanAccess(d);
assertCannotRead("refs/sb/dev/heads/foobar", u);
assertCanRead("refs/sb/dev/heads/foobar", d);
}
@Test
public void usernameEmailPatternWithRegex() throws Exception {
projectOperations
.project(localKey)
.forUpdate()
.add(allow(READ).ref("^refs/sb/${username}/heads/.*").group(DEVS))
.update();
ProjectControl u = user(localKey, "d.v@ger-rit.org", DEVS);
ProjectControl d = user(localKey, "dev@ger-rit.org", DEVS);
assertCannotRead("refs/sb/dev@ger-rit.org/heads/foobar", u);
assertCanRead("refs/sb/dev@ger-rit.org/heads/foobar", d);
}
@Test
public void sortWithRegex() throws Exception {
projectOperations
.project(localKey)
.forUpdate()
.add(allow(READ).ref("^refs/heads/.*").group(DEVS))
.update();
projectOperations
.project(parentKey)
.forUpdate()
.add(allow(READ).ref("^refs/heads/.*-QA-.*").group(ANONYMOUS_USERS))
.update();
ProjectControl u = user(localKey, DEVS);
ProjectControl d = user(localKey, DEVS);
assertCanRead("refs/heads/foo-QA-bar", u);
assertCanRead("refs/heads/foo-QA-bar", d);
}
@Test
public void blockRule_ParentBlocksChild() throws Exception {
projectOperations
.project(localKey)
.forUpdate()
.add(allow(PUSH).ref("refs/tags/*").group(DEVS))
.update();
projectOperations
.project(parentKey)
.forUpdate()
.add(block(PUSH).ref("refs/tags/*").group(ANONYMOUS_USERS))
.update();
ProjectControl u = user(localKey, DEVS);
assertCannotUpdate("refs/tags/V10", u);
}
@Test
public void blockRule_ParentBlocksChildEvenIfAlreadyBlockedInChild() throws Exception {
projectOperations
.project(localKey)
.forUpdate()
.add(allow(PUSH).ref("refs/tags/*").group(DEVS))
.add(block(PUSH).ref("refs/tags/*").group(ANONYMOUS_USERS))
.update();
projectOperations
.project(parentKey)
.forUpdate()
.add(block(PUSH).ref("refs/tags/*").group(ANONYMOUS_USERS))
.update();
ProjectControl u = user(localKey, DEVS);
assertCannotUpdate("refs/tags/V10", u);
}
@Test
public void blockPartialRangeLocally() throws Exception {
projectOperations
.project(localKey)
.forUpdate()
.add(blockLabel(LabelId.CODE_REVIEW).ref("refs/heads/master").group(DEVS).range(+1, +2))
.update();
ProjectControl u = user(localKey, DEVS);
PermissionRange range =
u.controlForRef("refs/heads/master").getRange(LABEL + LabelId.CODE_REVIEW);
assertCannotVote(2, range);
}
@Test
public void blockLabelRange_ParentBlocksChild() throws Exception {
projectOperations
.project(localKey)
.forUpdate()
.add(allowLabel(LabelId.CODE_REVIEW).ref("refs/heads/*").group(DEVS).range(-2, +2))
.update();
projectOperations
.project(parentKey)
.forUpdate()
.add(blockLabel(LabelId.CODE_REVIEW).ref("refs/heads/*").group(DEVS).range(-2, +2))
.update();
ProjectControl u = user(localKey, DEVS);
PermissionRange range =
u.controlForRef("refs/heads/master").getRange(LABEL + LabelId.CODE_REVIEW);
assertCanVote(-1, range);
assertCanVote(1, range);
assertCannotVote(-2, range);
assertCannotVote(2, range);
}
@Test
public void blockLabelRange_ParentBlocksChildEvenIfAlreadyBlockedInChild() throws Exception {
projectOperations
.project(localKey)
.forUpdate()
.add(allowLabel(LabelId.CODE_REVIEW).ref("refs/heads/*").group(DEVS).range(-2, +2))
.add(blockLabel(LabelId.CODE_REVIEW).ref("refs/heads/*").group(DEVS).range(-2, +2))
.update();
projectOperations
.project(parentKey)
.forUpdate()
.add(blockLabel(LabelId.CODE_REVIEW).ref("refs/heads/*").group(DEVS).range(-2, +2))
.update();
ProjectControl u = user(localKey, DEVS);
PermissionRange range =
u.controlForRef("refs/heads/master").getRange(LABEL + LabelId.CODE_REVIEW);
assertCanVote(-1, range);
assertCanVote(1, range);
assertCannotVote(-2, range);
assertCannotVote(2, range);
}
@Test
public void inheritSubmit_AllowInChildDoesntAffectUnblockInParent() throws Exception {
projectOperations
.project(parentKey)
.forUpdate()
.add(block(SUBMIT).ref("refs/heads/*").group(ANONYMOUS_USERS))
.add(allow(SUBMIT).ref("refs/heads/*").group(REGISTERED_USERS))
.update();
projectOperations
.project(localKey)
.forUpdate()
.add(allow(SUBMIT).ref("refs/heads/*").group(REGISTERED_USERS))
.update();
ProjectControl u = user(localKey);
assertWithMessage("submit is allowed")
.that(u.controlForRef("refs/heads/master").canPerform(SUBMIT))
.isTrue();
}
@Test
public void unblockNoForce() throws Exception {
projectOperations
.project(localKey)
.forUpdate()
.add(block(PUSH).ref("refs/heads/*").group(ANONYMOUS_USERS))
.add(allow(PUSH).ref("refs/heads/*").group(DEVS))
.update();
ProjectControl u = user(localKey, DEVS);
assertCanUpdate("refs/heads/master", u);
}
@Test
public void unblockForce() throws Exception {
projectOperations
.project(localKey)
.forUpdate()
.add(block(PUSH).ref("refs/heads/*").group(ANONYMOUS_USERS).force(true))
.add(allow(PUSH).ref("refs/heads/*").group(DEVS).force(true))
.update();
ProjectControl u = user(localKey, DEVS);
assertCanForceUpdate("refs/heads/master", u);
}
@Test
public void unblockRead_NotPossible() throws Exception {
projectOperations
.project(parentKey)
.forUpdate()
.add(block(READ).ref("refs/*").group(ANONYMOUS_USERS))
.add(allow(READ).ref("refs/*").group(ADMIN))
.update();
projectOperations
.project(localKey)
.forUpdate()
.add(allow(READ).ref("refs/*").group(ANONYMOUS_USERS))
.add(allow(READ).ref("refs/*").group(ADMIN))
.update();
ProjectControl u = user(localKey);
assertCannotRead("refs/heads/master", u);
}
@Test
public void unblockForceWithAllowNoForce_NotPossible() throws Exception {
projectOperations
.project(localKey)
.forUpdate()
.add(block(PUSH).ref("refs/heads/*").group(ANONYMOUS_USERS).force(true))
.add(allow(PUSH).ref("refs/heads/*").group(DEVS))
.update();
ProjectControl u = user(localKey, DEVS);
assertCannotForceUpdate("refs/heads/master", u);
}
@Test
public void unblockMoreSpecificRef_Fails() throws Exception {
projectOperations
.project(localKey)
.forUpdate()
.add(block(PUSH).ref("refs/heads/*").group(ANONYMOUS_USERS))
.add(allow(PUSH).ref("refs/heads/master").group(DEVS))
.update();
ProjectControl u = user(localKey, DEVS);
assertCannotUpdate("refs/heads/master", u);
}
@Test
public void unblockMoreSpecificRefInLocal_Fails() throws Exception {
projectOperations
.project(parentKey)
.forUpdate()
.add(block(PUSH).ref("refs/heads/*").group(ANONYMOUS_USERS))
.update();
projectOperations
.project(localKey)
.forUpdate()
.add(allow(PUSH).ref("refs/heads/master").group(DEVS))
.update();
ProjectControl u = user(localKey, DEVS);
assertCannotUpdate("refs/heads/master", u);
}
@Test
public void unblockMoreSpecificRefWithExclusiveFlag() throws Exception {
projectOperations
.project(localKey)
.forUpdate()
.add(block(PUSH).ref("refs/heads/*").group(ANONYMOUS_USERS))
.add(allow(PUSH).ref("refs/heads/master").group(DEVS))
.setExclusiveGroup(permissionKey(PUSH).ref("refs/heads/master"), true)
.update();
ProjectControl u = user(localKey, DEVS);
assertCanUpdate("refs/heads/master", u);
}
@Test
public void unblockVoteMoreSpecificRefWithExclusiveFlag() throws Exception {
projectOperations
.project(localKey)
.forUpdate()
.add(
blockLabel(LabelId.CODE_REVIEW).ref("refs/heads/*").group(ANONYMOUS_USERS).range(-1, 1))
.add(allowLabel(LabelId.CODE_REVIEW).ref("refs/heads/master").group(DEVS).range(-2, 2))
.setExclusiveGroup(labelPermissionKey(LabelId.CODE_REVIEW).ref("refs/heads/master"), true)
.update();
ProjectControl u = user(localKey, DEVS);
PermissionRange range =
u.controlForRef("refs/heads/master").getRange(LABEL + LabelId.CODE_REVIEW);
assertCanVote(-2, range);
}
@Test
public void unblockFromParentDoesNotAffectChild() throws Exception {
projectOperations
.project(parentKey)
.forUpdate()
.add(allow(PUSH).ref("refs/heads/master").group(DEVS))
.setExclusiveGroup(permissionKey(PUSH).ref("refs/heads/master"), true)
.update();
projectOperations
.project(localKey)
.forUpdate()
.add(block(PUSH).ref("refs/heads/master").group(DEVS))
.update();
ProjectControl u = user(localKey, DEVS);
assertCannotUpdate("refs/heads/master", u);
}
@Test
public void unblockFromParentDoesNotAffectChildDifferentGroups() throws Exception {
projectOperations
.project(parentKey)
.forUpdate()
.add(allow(PUSH).ref("refs/heads/master").group(DEVS))
.setExclusiveGroup(permissionKey(PUSH).ref("refs/heads/master"), true)
.update();
projectOperations
.project(localKey)
.forUpdate()
.add(block(PUSH).ref("refs/heads/master").group(ANONYMOUS_USERS))
.update();
ProjectControl u = user(localKey, DEVS);
assertCannotUpdate("refs/heads/master", u);
}
@Test
public void unblockMoreSpecificRefInLocalWithExclusiveFlag_Fails() throws Exception {
projectOperations
.project(parentKey)
.forUpdate()
.add(block(PUSH).ref("refs/heads/*").group(ANONYMOUS_USERS))
.update();
projectOperations
.project(localKey)
.forUpdate()
.add(allow(PUSH).ref("refs/heads/master").group(DEVS))
.setExclusiveGroup(permissionKey(PUSH).ref("refs/heads/master"), true)
.update();
ProjectControl u = user(localKey, DEVS);
assertCannotUpdate("refs/heads/master", u);
}
@Test
public void blockMoreSpecificRefWithinProject() throws Exception {
projectOperations
.project(localKey)
.forUpdate()
.add(block(PUSH).ref("refs/heads/secret").group(ANONYMOUS_USERS))
.add(allow(PUSH).ref("refs/heads/*").group(DEVS))
.setExclusiveGroup(permissionKey(PUSH).ref("refs/heads/*"), true)
.update();
ProjectControl u = user(localKey, DEVS);
assertCannotUpdate("refs/heads/secret", u);
assertCanUpdate("refs/heads/master", u);
}
@Test
public void unblockOtherPermissionWithMoreSpecificRefAndExclusiveFlag_Fails() throws Exception {
projectOperations
.project(localKey)
.forUpdate()
.add(block(PUSH).ref("refs/heads/*").group(ANONYMOUS_USERS))
.add(allow(PUSH).ref("refs/heads/master").group(DEVS))
.add(allow(SUBMIT).ref("refs/heads/master").group(DEVS))
.setExclusiveGroup(permissionKey(SUBMIT).ref("refs/heads/master"), true)
.update();
ProjectControl u = user(localKey, DEVS);
assertCannotUpdate("refs/heads/master", u);
}
@Test
public void unblockLargerScope_Fails() throws Exception {
projectOperations
.project(localKey)
.forUpdate()
.add(block(PUSH).ref("refs/heads/master").group(ANONYMOUS_USERS))
.add(allow(PUSH).ref("refs/heads/*").group(DEVS))
.update();
ProjectControl u = user(localKey, DEVS);
assertCannotUpdate("refs/heads/master", u);
}
@Test
public void unblockInLocal_Fails() throws Exception {
projectOperations
.project(parentKey)
.forUpdate()
.add(block(PUSH).ref("refs/heads/*").group(ANONYMOUS_USERS))
.update();
projectOperations
.project(localKey)
.forUpdate()
.add(allow(PUSH).ref("refs/heads/*").group(fixers))
.update();
ProjectControl f = user(localKey, fixers);
assertCannotUpdate("refs/heads/master", f);
}
@Test
public void unblockInParentBlockInLocal() throws Exception {
projectOperations
.project(parentKey)
.forUpdate()
.add(block(PUSH).ref("refs/heads/*").group(ANONYMOUS_USERS))
.add(allow(PUSH).ref("refs/heads/*").group(DEVS))
.update();
projectOperations
.project(localKey)
.forUpdate()
.add(block(PUSH).ref("refs/heads/*").group(DEVS))
.update();
ProjectControl d = user(localKey, DEVS);
assertCannotUpdate("refs/heads/master", d);
}
@Test
public void changeOwnerEditTopicName() throws Exception {
projectOperations
.project(localKey)
.forUpdate()
.add(allow(EDIT_TOPIC_NAME).ref("refs/heads/*").group(CHANGE_OWNER).force(true))
.update();
ProjectControl u = user(localKey, DEVS);
assertWithMessage("u can edit topic name")
.that(u.controlForRef("refs/heads/master").canForceEditTopicName(true))
.isTrue();
}
@Test
public void unblockForceEditTopicName() throws Exception {
projectOperations
.project(localKey)
.forUpdate()
.add(block(EDIT_TOPIC_NAME).ref("refs/heads/*").group(ANONYMOUS_USERS))
.add(allow(EDIT_TOPIC_NAME).ref("refs/heads/*").group(DEVS).force(true))
.update();
ProjectControl u = user(localKey, DEVS);
assertWithMessage("u can edit topic name")
.that(u.controlForRef("refs/heads/master").canForceEditTopicName(false))
.isTrue();
}
@Test
public void unblockInLocalForceEditTopicName_Fails() throws Exception {
projectOperations
.project(parentKey)
.forUpdate()
.add(block(EDIT_TOPIC_NAME).ref("refs/heads/*").group(ANONYMOUS_USERS))
.update();
projectOperations
.project(localKey)
.forUpdate()
.add(allow(EDIT_TOPIC_NAME).ref("refs/heads/*").group(DEVS).force(true))
.update();
ProjectControl u = user(localKey, REGISTERED_USERS);
assertWithMessage("u can't edit topic name")
.that(u.controlForRef("refs/heads/master").canForceEditTopicName(false))
.isFalse();
}
@Test
public void unblockRange() throws Exception {
projectOperations
.project(localKey)
.forUpdate()
.add(
blockLabel(LabelId.CODE_REVIEW)
.ref("refs/heads/*")
.group(ANONYMOUS_USERS)
.range(-1, +1))
.add(allowLabel(LabelId.CODE_REVIEW).ref("refs/heads/*").group(DEVS).range(-2, +2))
.update();
ProjectControl u = user(localKey, DEVS);
PermissionRange range =
u.controlForRef("refs/heads/master").getRange(LABEL + LabelId.CODE_REVIEW);
assertCanVote(-2, range);
assertCanVote(2, range);
}
@Test
public void unblockRangeOnMoreSpecificRef_Fails() throws Exception {
projectOperations
.project(localKey)
.forUpdate()
.add(
blockLabel(LabelId.CODE_REVIEW)
.ref("refs/heads/*")
.group(ANONYMOUS_USERS)
.range(-1, +1))
.add(allowLabel(LabelId.CODE_REVIEW).ref("refs/heads/master").group(DEVS).range(-2, +2))
.update();
ProjectControl u = user(localKey, DEVS);
PermissionRange range =
u.controlForRef("refs/heads/master").getRange(LABEL + LabelId.CODE_REVIEW);
assertCannotVote(-2, range);
assertCannotVote(2, range);
}
@Test
public void unblockRangeOnLargerScope_Fails() throws Exception {
projectOperations
.project(localKey)
.forUpdate()
.add(
blockLabel(LabelId.CODE_REVIEW)
.ref("refs/heads/master")
.group(ANONYMOUS_USERS)
.range(-1, +1))
.add(allowLabel(LabelId.CODE_REVIEW).ref("refs/heads/*").group(DEVS).range(-2, +2))
.update();
ProjectControl u = user(localKey, DEVS);
PermissionRange range =
u.controlForRef("refs/heads/master").getRange(LABEL + LabelId.CODE_REVIEW);
assertCannotVote(-2, range);
assertCannotVote(2, range);
}
@Test
public void nonconfiguredCannotVote() throws Exception {
projectOperations
.project(localKey)
.forUpdate()
.add(allowLabel(LabelId.CODE_REVIEW).ref("refs/heads/*").group(DEVS).range(-2, +2))
.update();
ProjectControl u = user(localKey, REGISTERED_USERS);
PermissionRange range =
u.controlForRef("refs/heads/master").getRange(LABEL + LabelId.CODE_REVIEW);
assertCannotVote(-1, range);
assertCannotVote(1, range);
}
@Test
public void unblockInLocalRange_Fails() throws Exception {
projectOperations
.project(parentKey)
.forUpdate()
.add(
blockLabel(LabelId.CODE_REVIEW).ref("refs/heads/*").group(ANONYMOUS_USERS).range(-1, 1))
.update();
projectOperations
.project(localKey)
.forUpdate()
.add(allowLabel(LabelId.CODE_REVIEW).ref("refs/heads/*").group(DEVS).range(-2, +2))
.update();
ProjectControl u = user(localKey, DEVS);
PermissionRange range =
u.controlForRef("refs/heads/master").getRange(LABEL + LabelId.CODE_REVIEW);
assertCannotVote(-2, range);
assertCannotVote(2, range);
}
@Test
public void unblockRangeForChangeOwner() throws Exception {
projectOperations
.project(localKey)
.forUpdate()
.add(allowLabel(LabelId.CODE_REVIEW).ref("refs/heads/*").group(CHANGE_OWNER).range(-2, +2))
.update();
ProjectControl u = user(localKey, DEVS);
PermissionRange range =
u.controlForRef("refs/heads/master").getRange(LABEL + LabelId.CODE_REVIEW, true);
assertCanVote(-2, range);
assertCanVote(2, range);
}
@Test
public void unblockRangeForNotChangeOwner() throws Exception {
projectOperations
.project(localKey)
.forUpdate()
.add(allowLabel(LabelId.CODE_REVIEW).ref("refs/heads/*").group(CHANGE_OWNER).range(-2, +2))
.update();
ProjectControl u = user(localKey, DEVS);
PermissionRange range =
u.controlForRef("refs/heads/master").getRange(LABEL + LabelId.CODE_REVIEW);
assertCannotVote(-2, range);
assertCannotVote(2, range);
}
@Test
public void blockChangeOwnerVote() throws Exception {
projectOperations
.project(localKey)
.forUpdate()
.add(blockLabel(LabelId.CODE_REVIEW).ref("refs/heads/*").group(CHANGE_OWNER).range(-2, +2))
.update();
ProjectControl u = user(localKey, DEVS);
PermissionRange range =
u.controlForRef("refs/heads/master").getRange(LABEL + LabelId.CODE_REVIEW);
assertCannotVote(-2, range);
assertCannotVote(2, range);
}
@Test
public void unionOfPermissibleVotes() throws Exception {
projectOperations
.project(localKey)
.forUpdate()
.add(allowLabel(LabelId.CODE_REVIEW).ref("refs/heads/*").group(DEVS).range(-1, +1))
.add(
allowLabel(LabelId.CODE_REVIEW)
.ref("refs/heads/*")
.group(REGISTERED_USERS)
.range(-2, +2))
.update();
ProjectControl u = user(localKey, DEVS);
PermissionRange range =
u.controlForRef("refs/heads/master").getRange(LABEL + LabelId.CODE_REVIEW);
assertCanVote(-2, range);
assertCanVote(2, range);
}
@Test
public void unionOfPermissibleVotesPermissionOrder() throws Exception {
projectOperations
.project(localKey)
.forUpdate()
.add(
allowLabel(LabelId.CODE_REVIEW)
.ref("refs/heads/*")
.group(REGISTERED_USERS)
.range(-2, +2))
.add(allowLabel(LabelId.CODE_REVIEW).ref("refs/heads/*").group(DEVS).range(-1, +1))
.update();
ProjectControl u = user(localKey, DEVS);
PermissionRange range =
u.controlForRef("refs/heads/master").getRange(LABEL + LabelId.CODE_REVIEW);
assertCanVote(-2, range);
assertCanVote(2, range);
}
@Test
public void unionOfBlockedVotes() throws Exception {
projectOperations
.project(parentKey)
.forUpdate()
.add(allowLabel(LabelId.CODE_REVIEW).ref("refs/heads/*").group(DEVS).range(-1, +1))
.add(
blockLabel(LabelId.CODE_REVIEW)
.ref("refs/heads/*")
.group(REGISTERED_USERS)
.range(-2, +2))
.update();
projectOperations
.project(localKey)
.forUpdate()
.add(
blockLabel(LabelId.CODE_REVIEW)
.ref("refs/heads/*")
.group(REGISTERED_USERS)
.range(-2, +1))
.update();
ProjectControl u = user(localKey, DEVS);
PermissionRange range =
u.controlForRef("refs/heads/master").getRange(LABEL + LabelId.CODE_REVIEW);
assertCanVote(-1, range);
assertCannotVote(1, range);
}
@Test
public void blockOwner() throws Exception {
projectOperations
.project(parentKey)
.forUpdate()
.add(block(OWNER).ref("refs/*").group(ANONYMOUS_USERS))
.update();
projectOperations
.project(localKey)
.forUpdate()
.add(allow(OWNER).ref("refs/*").group(DEVS))
.update();
assertThat(user(localKey, DEVS).isOwner()).isFalse();
}
@Test
public void validateRefPatternsOK() throws Exception {
RefPattern.validate("refs/*");
RefPattern.validate("^refs/heads/*");
RefPattern.validate("^refs/tags/[0-9a-zA-Z-_.]+");
RefPattern.validate("refs/heads/review/${username}/*");
RefPattern.validate("^refs/heads/review/${username}/.+");
}
@Test
public void testValidateBadRefPatternDoubleCaret() throws Exception {
assertThrows(InvalidNameException.class, () -> RefPattern.validate("^^refs/*"));
}
@Test
public void testValidateBadRefPatternDanglingCharacter() throws Exception {
assertThrows(
InvalidNameException.class,
() -> RefPattern.validate("^refs/heads/tmp/sdk/[0-9]{3,3}_R[1-9][A-Z][0-9]{3,3}*"));
}
@Test
public void validateRefPatternNoDanglingCharacter() throws Exception {
RefPattern.validate("^refs/heads/tmp/sdk/[0-9]{3,3}_R[1-9][A-Z][0-9]{3,3}");
}
private ProjectState getProjectState(Project.NameKey nameKey) throws Exception {
return projectCache.get(nameKey).orElseThrow(illegalState(nameKey));
}
private ProjectControl internalUser(Project.NameKey localKey) throws Exception {
return projectControlFactory.create(new InternalUser(), getProjectState(localKey));
}
private ProjectControl user(Project.NameKey localKey, AccountGroup.UUID... memberOf)
throws Exception {
return user(localKey, null, memberOf);
}
private ProjectControl user(
Project.NameKey localKey, @Nullable String name, AccountGroup.UUID... memberOf)
throws Exception {
return projectControlFactory.create(new MockUser(name, memberOf), getProjectState(localKey));
}
private static class MockUser extends CurrentUser {
@Nullable private final String username;
private final GroupMembership groups;
MockUser(@Nullable String name, AccountGroup.UUID[] groupId) {
username = name;
ArrayList<AccountGroup.UUID> groupIds = Lists.newArrayList(groupId);
groupIds.add(REGISTERED_USERS);
groupIds.add(ANONYMOUS_USERS);
groups = new ListGroupMembership(groupIds);
}
@Override
public GroupMembership getEffectiveGroups() {
return groups;
}
@Override
public Object getCacheKey() {
return new Object();
}
@Override
public Optional<String> getUserName() {
return Optional.ofNullable(username);
}
}
}