summaryrefslogtreecommitdiffstats
path: root/javatests/com/google/gerrit/server/permissions/RefControlTest.java
diff options
context:
space:
mode:
Diffstat (limited to 'javatests/com/google/gerrit/server/permissions/RefControlTest.java')
-rw-r--r--javatests/com/google/gerrit/server/permissions/RefControlTest.java1031
1 files changed, 1031 insertions, 0 deletions
diff --git a/javatests/com/google/gerrit/server/permissions/RefControlTest.java b/javatests/com/google/gerrit/server/permissions/RefControlTest.java
new file mode 100644
index 0000000000..a3f9f93923
--- /dev/null
+++ b/javatests/com/google/gerrit/server/permissions/RefControlTest.java
@@ -0,0 +1,1031 @@
+// 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.gerrit.common.data.Permission.EDIT_TOPIC_NAME;
+import static com.google.gerrit.common.data.Permission.LABEL;
+import static com.google.gerrit.common.data.Permission.OWNER;
+import static com.google.gerrit.common.data.Permission.PUSH;
+import static com.google.gerrit.common.data.Permission.READ;
+import static com.google.gerrit.common.data.Permission.SUBMIT;
+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.testing.Util.ADMIN;
+import static com.google.gerrit.server.project.testing.Util.DEVS;
+import static com.google.gerrit.server.project.testing.Util.allow;
+import static com.google.gerrit.server.project.testing.Util.allowExclusive;
+import static com.google.gerrit.server.project.testing.Util.block;
+import static com.google.gerrit.server.project.testing.Util.deny;
+import static com.google.gerrit.server.project.testing.Util.doNotInherit;
+import static com.google.gerrit.testing.InMemoryRepositoryManager.newRepository;
+
+import com.google.common.cache.Cache;
+import com.google.common.cache.CacheBuilder;
+import com.google.common.collect.ImmutableSortedSet;
+import com.google.common.collect.Lists;
+import com.google.gerrit.common.Nullable;
+import com.google.gerrit.common.data.LabelType;
+import com.google.gerrit.common.data.PermissionRange;
+import com.google.gerrit.common.data.PermissionRule;
+import com.google.gerrit.common.errors.InvalidNameException;
+import com.google.gerrit.extensions.api.projects.CommentLinkInfo;
+import com.google.gerrit.metrics.MetricMaker;
+import com.google.gerrit.reviewdb.client.AccountGroup;
+import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.reviewdb.server.ReviewDb;
+import com.google.gerrit.server.CurrentUser;
+import com.google.gerrit.server.account.CapabilityCollection;
+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.AllProjectsNameProvider;
+import com.google.gerrit.server.config.AllUsersName;
+import com.google.gerrit.server.config.AllUsersNameProvider;
+import com.google.gerrit.server.config.SitePaths;
+import com.google.gerrit.server.git.TransferConfig;
+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.Util;
+import com.google.gerrit.server.schema.SchemaCreator;
+import com.google.gerrit.server.util.RequestContext;
+import com.google.gerrit.server.util.ThreadLocalRequestContext;
+import com.google.gerrit.testing.InMemoryDatabase;
+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 com.google.inject.Provider;
+import com.google.inject.util.Providers;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+import org.eclipse.jgit.errors.ConfigInvalidException;
+import org.eclipse.jgit.internal.storage.dfs.InMemoryRepository;
+import org.eclipse.jgit.lib.Repository;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+public class RefControlTest {
+ private void assertAdminsAreOwnersAndDevsAreNot() {
+ ProjectControl uBlah = user(local, DEVS);
+ ProjectControl uAdmin = user(local, DEVS, ADMIN);
+
+ assertThat(uBlah.isOwner()).named("not owner").isFalse();
+ assertThat(uAdmin.isOwner()).named("is owner").isTrue();
+ }
+
+ private void assertOwner(String ref, ProjectControl u) {
+ assertThat(u.controlForRef(ref).isOwner()).named("OWN " + ref).isTrue();
+ }
+
+ private void assertNotOwner(ProjectControl u) {
+ assertThat(u.isOwner()).named("not owner").isFalse();
+ }
+
+ private void assertNotOwner(String ref, ProjectControl u) {
+ assertThat(u.controlForRef(ref).isOwner()).named("NOT OWN " + ref).isFalse();
+ }
+
+ private void assertCanAccess(ProjectControl u) {
+ boolean access = u.asForProject().testOrFalse(ProjectPermission.ACCESS);
+ assertThat(access).named("can access").isTrue();
+ }
+
+ private void assertAccessDenied(ProjectControl u) {
+ boolean access = u.asForProject().testOrFalse(ProjectPermission.ACCESS);
+ assertThat(access).named("cannot access").isFalse();
+ }
+
+ private void assertCanRead(String ref, ProjectControl u) {
+ assertThat(u.controlForRef(ref).isVisible()).named("can read " + ref).isTrue();
+ }
+
+ private void assertCannotRead(String ref, ProjectControl u) {
+ assertThat(u.controlForRef(ref).isVisible()).named("cannot read " + ref).isFalse();
+ }
+
+ private void assertCanSubmit(String ref, ProjectControl u) {
+ assertThat(u.controlForRef(ref).canSubmit(false)).named("can submit " + ref).isTrue();
+ }
+
+ private void assertCannotSubmit(String ref, ProjectControl u) {
+ assertThat(u.controlForRef(ref).canSubmit(false)).named("can submit " + ref).isFalse();
+ }
+
+ private void assertCanUpload(ProjectControl u) {
+ assertThat(u.canPushToAtLeastOneRef()).named("can upload").isTrue();
+ }
+
+ private void assertCreateChange(String ref, ProjectControl u) {
+ boolean create = u.asForProject().ref(ref).testOrFalse(RefPermission.CREATE_CHANGE);
+ assertThat(create).named("can create change " + ref).isTrue();
+ }
+
+ private void assertCannotUpload(ProjectControl u) {
+ assertThat(u.canPushToAtLeastOneRef()).named("cannot upload").isFalse();
+ }
+
+ private void assertCannotCreateChange(String ref, ProjectControl u) {
+ boolean create = u.asForProject().ref(ref).testOrFalse(RefPermission.CREATE_CHANGE);
+ assertThat(create).named("cannot create change " + ref).isFalse();
+ }
+
+ private void assertCanUpdate(String ref, ProjectControl u) {
+ boolean update = u.asForProject().ref(ref).testOrFalse(RefPermission.UPDATE);
+ assertThat(update).named("can update " + ref).isTrue();
+ }
+
+ private void assertCannotUpdate(String ref, ProjectControl u) {
+ boolean update = u.asForProject().ref(ref).testOrFalse(RefPermission.UPDATE);
+ assertThat(update).named("cannot update " + ref).isFalse();
+ }
+
+ private void assertCanForceUpdate(String ref, ProjectControl u) {
+ boolean update = u.asForProject().ref(ref).testOrFalse(RefPermission.FORCE_UPDATE);
+ assertThat(update).named("can force push " + ref).isTrue();
+ }
+
+ private void assertCannotForceUpdate(String ref, ProjectControl u) {
+ boolean update = u.asForProject().ref(ref).testOrFalse(RefPermission.FORCE_UPDATE);
+ assertThat(update).named("cannot force push " + ref).isFalse();
+ }
+
+ private void assertCanVote(int score, PermissionRange range) {
+ assertThat(range.contains(score)).named("can vote " + score).isTrue();
+ }
+
+ private void assertCannotVote(int score, PermissionRange range) {
+ assertThat(range.contains(score)).named("cannot vote " + score).isFalse();
+ }
+
+ private final AllProjectsName allProjectsName =
+ new AllProjectsName(AllProjectsNameProvider.DEFAULT);
+ private final AllUsersName allUsersName = new AllUsersName(AllUsersNameProvider.DEFAULT);
+ private final AccountGroup.UUID fixers = new AccountGroup.UUID("test.fixers");
+ private final Map<Project.NameKey, ProjectState> all = new HashMap<>();
+ private Project.NameKey localKey = new Project.NameKey("local");
+ private ProjectConfig local;
+ private Project.NameKey parentKey = new Project.NameKey("parent");
+ private ProjectConfig parent;
+ private InMemoryRepositoryManager repoManager;
+ private ProjectCache projectCache;
+ private PermissionCollection.Factory sectionSorter;
+ private ChangeControl.Factory changeControlFactory;
+ private ReviewDb db;
+
+ @Inject private PermissionBackend permissionBackend;
+ @Inject private CapabilityCollection.Factory capabilityCollectionFactory;
+ @Inject private SchemaCreator schemaCreator;
+ @Inject private SingleVersionListener singleVersionListener;
+ @Inject private InMemoryDatabase schemaFactory;
+ @Inject private ThreadLocalRequestContext requestContext;
+ @Inject private DefaultRefFilter.Factory refFilterFactory;
+ @Inject private TransferConfig transferConfig;
+ @Inject private MetricMaker metricMaker;
+
+ @Before
+ public void setUp() throws Exception {
+ repoManager = new InMemoryRepositoryManager();
+ projectCache =
+ new ProjectCache() {
+ @Override
+ public ProjectState getAllProjects() {
+ return get(allProjectsName);
+ }
+
+ @Override
+ public ProjectState getAllUsers() {
+ return null;
+ }
+
+ @Override
+ public ProjectState get(Project.NameKey projectName) {
+ return all.get(projectName);
+ }
+
+ @Override
+ public void evict(Project p) {}
+
+ @Override
+ public void remove(Project p) {}
+
+ @Override
+ public void remove(Project.NameKey name) {}
+
+ @Override
+ public ImmutableSortedSet<Project.NameKey> all() {
+ return ImmutableSortedSet.of();
+ }
+
+ @Override
+ public ImmutableSortedSet<Project.NameKey> byName(String prefix) {
+ return ImmutableSortedSet.of();
+ }
+
+ @Override
+ public void onCreateProject(Project.NameKey newProjectName) {}
+
+ @Override
+ public Set<AccountGroup.UUID> guessRelevantGroupUUIDs() {
+ return Collections.emptySet();
+ }
+
+ @Override
+ public ProjectState checkedGet(Project.NameKey projectName) throws IOException {
+ return all.get(projectName);
+ }
+
+ @Override
+ public void evict(Project.NameKey p) {}
+
+ @Override
+ public ProjectState checkedGet(Project.NameKey projectName, boolean strict)
+ throws Exception {
+ return all.get(projectName);
+ }
+ };
+
+ Injector injector = Guice.createInjector(new InMemoryModule());
+ injector.injectMembers(this);
+
+ try {
+ Repository repo = repoManager.createRepository(allProjectsName);
+ ProjectConfig allProjects = new ProjectConfig(new Project.NameKey(allProjectsName.get()));
+ allProjects.load(repo);
+ LabelType cr = Util.codeReview();
+ allProjects.getLabelSections().put(cr.getName(), cr);
+ add(allProjects);
+ } catch (IOException | ConfigInvalidException e) {
+ throw new RuntimeException(e);
+ }
+
+ db = schemaFactory.open();
+ singleVersionListener.start();
+ try {
+ schemaCreator.create(db);
+ } finally {
+ singleVersionListener.stop();
+ }
+
+ Cache<SectionSortCache.EntryKey, SectionSortCache.EntryVal> c =
+ CacheBuilder.newBuilder().build();
+ sectionSorter = new PermissionCollection.Factory(new SectionSortCache(c), metricMaker);
+
+ parent = new ProjectConfig(parentKey);
+ parent.load(newRepository(parentKey));
+ add(parent);
+
+ local = new ProjectConfig(localKey);
+ local.load(newRepository(localKey));
+ add(local);
+ local.getProject().setParentName(parentKey);
+
+ requestContext.setContext(
+ new RequestContext() {
+ @Override
+ public CurrentUser getUser() {
+ return null;
+ }
+
+ @Override
+ public Provider<ReviewDb> getReviewDbProvider() {
+ return Providers.of(db);
+ }
+ });
+
+ changeControlFactory = injector.getInstance(ChangeControl.Factory.class);
+ }
+
+ @After
+ public void tearDown() {
+ requestContext.setContext(null);
+ if (db != null) {
+ db.close();
+ }
+ InMemoryDatabase.drop(schemaFactory);
+ }
+
+ @Test
+ public void ownerProject() {
+ allow(local, OWNER, ADMIN, "refs/*");
+
+ assertAdminsAreOwnersAndDevsAreNot();
+ }
+
+ @Test
+ public void denyOwnerProject() {
+ allow(local, OWNER, ADMIN, "refs/*");
+ deny(local, OWNER, DEVS, "refs/*");
+
+ assertAdminsAreOwnersAndDevsAreNot();
+ }
+
+ @Test
+ public void blockOwnerProject() {
+ allow(local, OWNER, ADMIN, "refs/*");
+ block(local, OWNER, DEVS, "refs/*");
+
+ assertAdminsAreOwnersAndDevsAreNot();
+ }
+
+ @Test
+ public void branchDelegation1() {
+ allow(local, OWNER, ADMIN, "refs/*");
+ allow(local, OWNER, DEVS, "refs/heads/x/*");
+
+ ProjectControl uDev = user(local, 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() {
+ allow(local, OWNER, ADMIN, "refs/*");
+ allow(local, OWNER, DEVS, "refs/heads/x/*");
+ allow(local, OWNER, fixers, "refs/heads/x/y/*");
+ doNotInherit(local, OWNER, "refs/heads/x/y/*");
+
+ ProjectControl uDev = user(local, 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(local, 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() {
+ allow(parent, READ, REGISTERED_USERS, "refs/*");
+ allow(parent, PUSH, REGISTERED_USERS, "refs/for/refs/*");
+ allow(local, READ, REGISTERED_USERS, "refs/heads/foobar");
+ doNotInherit(local, READ, "refs/heads/foobar");
+ doNotInherit(local, PUSH, "refs/for/refs/heads/foobar");
+
+ ProjectControl u = user(local);
+ assertCanUpload(u);
+ assertCreateChange("refs/heads/master", u);
+ assertCannotCreateChange("refs/heads/foobar", u);
+ }
+
+ @Test
+ public void blockPushDrafts() {
+ allow(parent, PUSH, REGISTERED_USERS, "refs/for/refs/*");
+ block(parent, PUSH, ANONYMOUS_USERS, "refs/drafts/*");
+ allow(local, PUSH, REGISTERED_USERS, "refs/drafts/*");
+
+ ProjectControl u = user(local);
+ assertCreateChange("refs/heads/master", u);
+ assertThat(u.controlForRef("refs/drafts/master").canPerform(PUSH)).isFalse();
+ }
+
+ @Test
+ public void blockPushDraftsUnblockAdmin() {
+ block(parent, PUSH, ANONYMOUS_USERS, "refs/drafts/*");
+ allow(parent, PUSH, ADMIN, "refs/drafts/*");
+ allow(local, PUSH, REGISTERED_USERS, "refs/drafts/*");
+
+ ProjectControl u = user(local);
+ ProjectControl a = user(local, "a", ADMIN);
+
+ assertThat(a.controlForRef("refs/drafts/master").canPerform(PUSH))
+ .named("push is allowed")
+ .isTrue();
+ assertThat(u.controlForRef("refs/drafts/master").canPerform(PUSH))
+ .named("push is not allowed")
+ .isFalse();
+ }
+
+ @Test
+ public void inheritRead_SingleBranchDoesNotOverrideInherited() {
+ allow(parent, READ, REGISTERED_USERS, "refs/*");
+ allow(parent, PUSH, REGISTERED_USERS, "refs/for/refs/*");
+ allow(local, READ, REGISTERED_USERS, "refs/heads/foobar");
+
+ ProjectControl u = user(local);
+ assertCanUpload(u);
+ assertCreateChange("refs/heads/master", u);
+ assertCreateChange("refs/heads/foobar", u);
+ }
+
+ @Test
+ public void inheritDuplicateSections() throws Exception {
+ allow(parent, READ, ADMIN, "refs/*");
+ allow(local, READ, DEVS, "refs/heads/*");
+ assertCanAccess(user(local, "a", ADMIN));
+
+ local = new ProjectConfig(localKey);
+ local.load(newRepository(localKey));
+ local.getProject().setParentName(parentKey);
+ allow(local, READ, DEVS, "refs/*");
+ assertCanAccess(user(local, "d", DEVS));
+ }
+
+ @Test
+ public void inheritRead_OverrideWithDeny() {
+ allow(parent, READ, REGISTERED_USERS, "refs/*");
+ deny(local, READ, REGISTERED_USERS, "refs/*");
+
+ assertAccessDenied(user(local));
+ }
+
+ @Test
+ public void inheritRead_AppendWithDenyOfRef() {
+ allow(parent, READ, REGISTERED_USERS, "refs/*");
+ deny(local, READ, REGISTERED_USERS, "refs/heads/*");
+
+ ProjectControl u = user(local);
+ assertCanAccess(u);
+ assertCanRead("refs/master", u);
+ assertCanRead("refs/tags/foobar", u);
+ assertCanRead("refs/heads/master", u);
+ }
+
+ @Test
+ public void inheritRead_OverridesAndDeniesOfRef() {
+ allow(parent, READ, REGISTERED_USERS, "refs/*");
+ deny(local, READ, REGISTERED_USERS, "refs/*");
+ allow(local, READ, REGISTERED_USERS, "refs/heads/*");
+
+ ProjectControl u = user(local);
+ assertCanAccess(u);
+ assertCannotRead("refs/foobar", u);
+ assertCannotRead("refs/tags/foobar", u);
+ assertCanRead("refs/heads/foobar", u);
+ }
+
+ @Test
+ public void inheritSubmit_OverridesAndDeniesOfRef() {
+ allow(parent, SUBMIT, REGISTERED_USERS, "refs/*");
+ deny(local, SUBMIT, REGISTERED_USERS, "refs/*");
+ allow(local, SUBMIT, REGISTERED_USERS, "refs/heads/*");
+
+ ProjectControl u = user(local);
+ assertCannotSubmit("refs/foobar", u);
+ assertCannotSubmit("refs/tags/foobar", u);
+ assertCanSubmit("refs/heads/foobar", u);
+ }
+
+ @Test
+ public void cannotUploadToAnyRef() {
+ allow(parent, READ, REGISTERED_USERS, "refs/*");
+ allow(local, READ, DEVS, "refs/heads/*");
+ allow(local, PUSH, DEVS, "refs/for/refs/heads/*");
+
+ ProjectControl u = user(local);
+ assertCannotUpload(u);
+ assertCannotCreateChange("refs/heads/master", u);
+ }
+
+ @Test
+ public void usernamePatternCanUploadToAnyRef() {
+ allow(local, PUSH, REGISTERED_USERS, "refs/heads/users/${username}/*");
+ ProjectControl u = user(local, "a-registered-user");
+ assertCanUpload(u);
+ }
+
+ @Test
+ public void usernamePatternNonRegex() {
+ allow(local, READ, DEVS, "refs/sb/${username}/heads/*");
+
+ ProjectControl u = user(local, "u", DEVS);
+ ProjectControl d = user(local, "d", DEVS);
+ assertCannotRead("refs/sb/d/heads/foobar", u);
+ assertCanRead("refs/sb/d/heads/foobar", d);
+ }
+
+ @Test
+ public void usernamePatternWithRegex() {
+ allow(local, READ, DEVS, "^refs/sb/${username}/heads/.*");
+
+ ProjectControl u = user(local, "d.v", DEVS);
+ ProjectControl d = user(local, "dev", DEVS);
+ assertCannotRead("refs/sb/dev/heads/foobar", u);
+ assertCanRead("refs/sb/dev/heads/foobar", d);
+ }
+
+ @Test
+ public void usernameEmailPatternWithRegex() {
+ allow(local, READ, DEVS, "^refs/sb/${username}/heads/.*");
+
+ ProjectControl u = user(local, "d.v@ger-rit.org", DEVS);
+ ProjectControl d = user(local, "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() {
+ allow(local, READ, DEVS, "^refs/heads/.*");
+ allow(parent, READ, ANONYMOUS_USERS, "^refs/heads/.*-QA-.*");
+
+ ProjectControl u = user(local, DEVS);
+ ProjectControl d = user(local, DEVS);
+ assertCanRead("refs/heads/foo-QA-bar", u);
+ assertCanRead("refs/heads/foo-QA-bar", d);
+ }
+
+ @Test
+ public void blockRule_ParentBlocksChild() {
+ allow(local, PUSH, DEVS, "refs/tags/*");
+ block(parent, PUSH, ANONYMOUS_USERS, "refs/tags/*");
+ ProjectControl u = user(local, DEVS);
+ assertCannotUpdate("refs/tags/V10", u);
+ }
+
+ @Test
+ public void blockRule_ParentBlocksChildEvenIfAlreadyBlockedInChild() {
+ allow(local, PUSH, DEVS, "refs/tags/*");
+ block(local, PUSH, ANONYMOUS_USERS, "refs/tags/*");
+ block(parent, PUSH, ANONYMOUS_USERS, "refs/tags/*");
+
+ ProjectControl u = user(local, DEVS);
+ assertCannotUpdate("refs/tags/V10", u);
+ }
+
+ @Test
+ public void blockLabelRange_ParentBlocksChild() {
+ allow(local, LABEL + "Code-Review", -2, +2, DEVS, "refs/heads/*");
+ block(parent, LABEL + "Code-Review", -2, +2, DEVS, "refs/heads/*");
+
+ ProjectControl u = user(local, DEVS);
+
+ PermissionRange range = u.controlForRef("refs/heads/master").getRange(LABEL + "Code-Review");
+ assertCanVote(-1, range);
+ assertCanVote(1, range);
+ assertCannotVote(-2, range);
+ assertCannotVote(2, range);
+ }
+
+ @Test
+ public void blockLabelRange_ParentBlocksChildEvenIfAlreadyBlockedInChild() {
+ allow(local, LABEL + "Code-Review", -2, +2, DEVS, "refs/heads/*");
+ block(local, LABEL + "Code-Review", -2, +2, DEVS, "refs/heads/*");
+ block(parent, LABEL + "Code-Review", -2, +2, DEVS, "refs/heads/*");
+
+ ProjectControl u = user(local, DEVS);
+
+ PermissionRange range = u.controlForRef("refs/heads/master").getRange(LABEL + "Code-Review");
+ assertCanVote(-1, range);
+ assertCanVote(1, range);
+ assertCannotVote(-2, range);
+ assertCannotVote(2, range);
+ }
+
+ @Test
+ public void inheritSubmit_AllowInChildDoesntAffectUnblockInParent() {
+ block(parent, SUBMIT, ANONYMOUS_USERS, "refs/heads/*");
+ allow(parent, SUBMIT, REGISTERED_USERS, "refs/heads/*");
+ allow(local, SUBMIT, REGISTERED_USERS, "refs/heads/*");
+
+ ProjectControl u = user(local);
+ assertThat(u.controlForRef("refs/heads/master").canPerform(SUBMIT))
+ .named("submit is allowed")
+ .isTrue();
+ }
+
+ @Test
+ public void unblockNoForce() {
+ block(local, PUSH, ANONYMOUS_USERS, "refs/heads/*");
+ allow(local, PUSH, DEVS, "refs/heads/*");
+
+ ProjectControl u = user(local, DEVS);
+ assertCanUpdate("refs/heads/master", u);
+ }
+
+ @Test
+ public void unblockForce() {
+ PermissionRule r = block(local, PUSH, ANONYMOUS_USERS, "refs/heads/*");
+ r.setForce(true);
+ allow(local, PUSH, DEVS, "refs/heads/*").setForce(true);
+
+ ProjectControl u = user(local, DEVS);
+ assertCanForceUpdate("refs/heads/master", u);
+ }
+
+ @Test
+ public void unblockRead_NotPossible() {
+ block(parent, READ, ANONYMOUS_USERS, "refs/*");
+ allow(parent, READ, ADMIN, "refs/*");
+ allow(local, READ, ANONYMOUS_USERS, "refs/*");
+ allow(local, READ, ADMIN, "refs/*");
+ ProjectControl u = user(local);
+ assertCannotRead("refs/heads/master", u);
+ }
+
+ @Test
+ public void unblockForceWithAllowNoForce_NotPossible() {
+ PermissionRule r = block(local, PUSH, ANONYMOUS_USERS, "refs/heads/*");
+ r.setForce(true);
+ allow(local, PUSH, DEVS, "refs/heads/*");
+
+ ProjectControl u = user(local, DEVS);
+ assertCannotForceUpdate("refs/heads/master", u);
+ }
+
+ @Test
+ public void unblockMoreSpecificRef_Fails() {
+ block(local, PUSH, ANONYMOUS_USERS, "refs/heads/*");
+ allow(local, PUSH, DEVS, "refs/heads/master");
+
+ ProjectControl u = user(local, DEVS);
+ assertCannotUpdate("refs/heads/master", u);
+ }
+
+ @Test
+ public void unblockMoreSpecificRefInLocal_Fails() {
+ block(parent, PUSH, ANONYMOUS_USERS, "refs/heads/*");
+ allow(local, PUSH, DEVS, "refs/heads/master");
+
+ ProjectControl u = user(local, DEVS);
+ assertCannotUpdate("refs/heads/master", u);
+ }
+
+ @Test
+ public void unblockMoreSpecificRefWithExclusiveFlag() {
+ block(local, PUSH, ANONYMOUS_USERS, "refs/heads/*");
+ allow(local, PUSH, DEVS, "refs/heads/master", true);
+
+ ProjectControl u = user(local, DEVS);
+ assertCanUpdate("refs/heads/master", u);
+ }
+
+ @Test
+ public void unblockVoteMoreSpecificRefWithExclusiveFlag() {
+ String perm = LABEL + "Code-Review";
+
+ block(local, perm, -1, 1, ANONYMOUS_USERS, "refs/heads/*");
+ allowExclusive(local, perm, -2, 2, DEVS, "refs/heads/master");
+
+ ProjectControl u = user(local, DEVS);
+ PermissionRange range = u.controlForRef("refs/heads/master").getRange(perm);
+ assertCanVote(-2, range);
+ }
+
+ @Test
+ public void unblockFromParentDoesNotAffectChild() {
+ allow(parent, PUSH, DEVS, "refs/heads/master", true);
+ block(local, PUSH, DEVS, "refs/heads/master");
+
+ ProjectControl u = user(local, DEVS);
+ assertCannotUpdate("refs/heads/master", u);
+ }
+
+ @Test
+ public void unblockFromParentDoesNotAffectChildDifferentGroups() {
+ allow(parent, PUSH, DEVS, "refs/heads/master", true);
+ block(local, PUSH, ANONYMOUS_USERS, "refs/heads/master");
+
+ ProjectControl u = user(local, DEVS);
+ assertCannotUpdate("refs/heads/master", u);
+ }
+
+ @Test
+ public void unblockMoreSpecificRefInLocalWithExclusiveFlag_Fails() {
+ block(parent, PUSH, ANONYMOUS_USERS, "refs/heads/*");
+ allow(local, PUSH, DEVS, "refs/heads/master", true);
+
+ ProjectControl u = user(local, DEVS);
+ assertCannotUpdate("refs/heads/master", u);
+ }
+
+ @Test
+ public void blockMoreSpecificRefWithinProject() {
+ block(local, PUSH, ANONYMOUS_USERS, "refs/heads/secret");
+ allow(local, PUSH, DEVS, "refs/heads/*", true);
+
+ ProjectControl u = user(local, DEVS);
+ assertCannotUpdate("refs/heads/secret", u);
+ assertCanUpdate("refs/heads/master", u);
+ }
+
+ @Test
+ public void unblockOtherPermissionWithMoreSpecificRefAndExclusiveFlag_Fails() {
+ block(local, PUSH, ANONYMOUS_USERS, "refs/heads/*");
+ allow(local, PUSH, DEVS, "refs/heads/master");
+ allow(local, SUBMIT, DEVS, "refs/heads/master", true);
+
+ ProjectControl u = user(local, DEVS);
+ assertCannotUpdate("refs/heads/master", u);
+ }
+
+ @Test
+ public void unblockLargerScope_Fails() {
+ block(local, PUSH, ANONYMOUS_USERS, "refs/heads/master");
+ allow(local, PUSH, DEVS, "refs/heads/*");
+
+ ProjectControl u = user(local, DEVS);
+ assertCannotUpdate("refs/heads/master", u);
+ }
+
+ @Test
+ public void unblockInLocal_Fails() {
+ block(parent, PUSH, ANONYMOUS_USERS, "refs/heads/*");
+ allow(local, PUSH, fixers, "refs/heads/*");
+
+ ProjectControl f = user(local, fixers);
+ assertCannotUpdate("refs/heads/master", f);
+ }
+
+ @Test
+ public void unblockInParentBlockInLocal() {
+ block(parent, PUSH, ANONYMOUS_USERS, "refs/heads/*");
+ allow(parent, PUSH, DEVS, "refs/heads/*");
+ block(local, PUSH, DEVS, "refs/heads/*");
+
+ ProjectControl d = user(local, DEVS);
+ assertCannotUpdate("refs/heads/master", d);
+ }
+
+ @Test
+ public void unblockForceEditTopicName() {
+ block(local, EDIT_TOPIC_NAME, ANONYMOUS_USERS, "refs/heads/*");
+ allow(local, EDIT_TOPIC_NAME, DEVS, "refs/heads/*").setForce(true);
+
+ ProjectControl u = user(local, DEVS);
+ assertThat(u.controlForRef("refs/heads/master").canForceEditTopicName())
+ .named("u can edit topic name")
+ .isTrue();
+ }
+
+ @Test
+ public void unblockInLocalForceEditTopicName_Fails() {
+ block(parent, EDIT_TOPIC_NAME, ANONYMOUS_USERS, "refs/heads/*");
+ allow(local, EDIT_TOPIC_NAME, DEVS, "refs/heads/*").setForce(true);
+
+ ProjectControl u = user(local, REGISTERED_USERS);
+ assertThat(u.controlForRef("refs/heads/master").canForceEditTopicName())
+ .named("u can't edit topic name")
+ .isFalse();
+ }
+
+ @Test
+ public void unblockRange() {
+ block(local, LABEL + "Code-Review", -1, +1, ANONYMOUS_USERS, "refs/heads/*");
+ allow(local, LABEL + "Code-Review", -2, +2, DEVS, "refs/heads/*");
+
+ ProjectControl u = user(local, DEVS);
+ PermissionRange range = u.controlForRef("refs/heads/master").getRange(LABEL + "Code-Review");
+ assertCanVote(-2, range);
+ assertCanVote(2, range);
+ }
+
+ @Test
+ public void unblockRangeOnMoreSpecificRef_Fails() {
+ block(local, LABEL + "Code-Review", -1, +1, ANONYMOUS_USERS, "refs/heads/*");
+ allow(local, LABEL + "Code-Review", -2, +2, DEVS, "refs/heads/master");
+
+ ProjectControl u = user(local, DEVS);
+ PermissionRange range = u.controlForRef("refs/heads/master").getRange(LABEL + "Code-Review");
+ assertCannotVote(-2, range);
+ assertCannotVote(2, range);
+ }
+
+ @Test
+ public void unblockRangeOnLargerScope_Fails() {
+ block(local, LABEL + "Code-Review", -1, +1, ANONYMOUS_USERS, "refs/heads/master");
+ allow(local, LABEL + "Code-Review", -2, +2, DEVS, "refs/heads/*");
+
+ ProjectControl u = user(local, DEVS);
+ PermissionRange range = u.controlForRef("refs/heads/master").getRange(LABEL + "Code-Review");
+ assertCannotVote(-2, range);
+ assertCannotVote(2, range);
+ }
+
+ @Test
+ public void nonconfiguredCannotVote() {
+ allow(local, LABEL + "Code-Review", -2, +2, DEVS, "refs/heads/*");
+
+ ProjectControl u = user(local, REGISTERED_USERS);
+ PermissionRange range = u.controlForRef("refs/heads/master").getRange(LABEL + "Code-Review");
+ assertCannotVote(-1, range);
+ assertCannotVote(1, range);
+ }
+
+ @Test
+ public void unblockInLocalRange_Fails() {
+ block(parent, LABEL + "Code-Review", -1, 1, ANONYMOUS_USERS, "refs/heads/*");
+ allow(local, LABEL + "Code-Review", -2, +2, DEVS, "refs/heads/*");
+
+ ProjectControl u = user(local, DEVS);
+ PermissionRange range = u.controlForRef("refs/heads/master").getRange(LABEL + "Code-Review");
+ assertCannotVote(-2, range);
+ assertCannotVote(2, range);
+ }
+
+ @Test
+ public void unblockRangeForChangeOwner() {
+ allow(local, LABEL + "Code-Review", -2, +2, CHANGE_OWNER, "refs/heads/*");
+
+ ProjectControl u = user(local, DEVS);
+ PermissionRange range =
+ u.controlForRef("refs/heads/master").getRange(LABEL + "Code-Review", true);
+ assertCanVote(-2, range);
+ assertCanVote(2, range);
+ }
+
+ @Test
+ public void unblockRangeForNotChangeOwner() {
+ allow(local, LABEL + "Code-Review", -2, +2, CHANGE_OWNER, "refs/heads/*");
+
+ ProjectControl u = user(local, DEVS);
+ PermissionRange range = u.controlForRef("refs/heads/master").getRange(LABEL + "Code-Review");
+ assertCannotVote(-2, range);
+ assertCannotVote(2, range);
+ }
+
+ @Test
+ public void blockChangeOwnerVote() {
+ block(local, LABEL + "Code-Review", -2, +2, CHANGE_OWNER, "refs/heads/*");
+
+ ProjectControl u = user(local, DEVS);
+ PermissionRange range = u.controlForRef("refs/heads/master").getRange(LABEL + "Code-Review");
+ assertCannotVote(-2, range);
+ assertCannotVote(2, range);
+ }
+
+ @Test
+ public void unionOfPermissibleVotes() {
+ allow(local, LABEL + "Code-Review", -1, +1, DEVS, "refs/heads/*");
+ allow(local, LABEL + "Code-Review", -2, +2, REGISTERED_USERS, "refs/heads/*");
+
+ ProjectControl u = user(local, DEVS);
+ PermissionRange range = u.controlForRef("refs/heads/master").getRange(LABEL + "Code-Review");
+ assertCanVote(-2, range);
+ assertCanVote(2, range);
+ }
+
+ @Test
+ public void unionOfPermissibleVotesPermissionOrder() {
+ allow(local, LABEL + "Code-Review", -2, +2, REGISTERED_USERS, "refs/heads/*");
+ allow(local, LABEL + "Code-Review", -1, +1, DEVS, "refs/heads/*");
+
+ ProjectControl u = user(local, DEVS);
+ PermissionRange range = u.controlForRef("refs/heads/master").getRange(LABEL + "Code-Review");
+ assertCanVote(-2, range);
+ assertCanVote(2, range);
+ }
+
+ @Test
+ public void unionOfBlockedVotes() {
+ allow(parent, LABEL + "Code-Review", -1, +1, DEVS, "refs/heads/*");
+ block(parent, LABEL + "Code-Review", -2, +2, REGISTERED_USERS, "refs/heads/*");
+ block(local, LABEL + "Code-Review", -2, +1, REGISTERED_USERS, "refs/heads/*");
+
+ ProjectControl u = user(local, DEVS);
+ PermissionRange range = u.controlForRef("refs/heads/master").getRange(LABEL + "Code-Review");
+ assertCanVote(-1, range);
+ assertCannotVote(1, range);
+ }
+
+ @Test
+ public void blockOwner() {
+ block(parent, OWNER, ANONYMOUS_USERS, "refs/*");
+ allow(local, OWNER, DEVS, "refs/*");
+
+ assertThat(user(local, 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}/*");
+ }
+
+ @Test(expected = InvalidNameException.class)
+ public void testValidateBadRefPatternDoubleCaret() throws Exception {
+ RefPattern.validate("^^refs/*");
+ }
+
+ @Test(expected = InvalidNameException.class)
+ public void testValidateBadRefPatternDanglingCharacter() throws Exception {
+ 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 InMemoryRepository add(ProjectConfig pc) {
+ SitePaths sitePaths = null;
+ List<CommentLinkInfo> commentLinks = null;
+
+ InMemoryRepository repo;
+ try {
+ repo = repoManager.createRepository(pc.getName());
+ if (pc.getProject() == null) {
+ pc.load(repo);
+ }
+ } catch (IOException | ConfigInvalidException e) {
+ throw new RuntimeException(e);
+ }
+ all.put(
+ pc.getName(),
+ new ProjectState(
+ sitePaths,
+ projectCache,
+ allProjectsName,
+ allUsersName,
+ repoManager,
+ commentLinks,
+ capabilityCollectionFactory,
+ transferConfig,
+ metricMaker,
+ pc));
+ return repo;
+ }
+
+ private ProjectControl user(ProjectConfig local, AccountGroup.UUID... memberOf) {
+ return user(local, null, memberOf);
+ }
+
+ private ProjectControl user(
+ ProjectConfig local, @Nullable String name, AccountGroup.UUID... memberOf) {
+ return new ProjectControl(
+ Collections.<AccountGroup.UUID>emptySet(),
+ Collections.<AccountGroup.UUID>emptySet(),
+ sectionSorter,
+ changeControlFactory,
+ permissionBackend,
+ refFilterFactory,
+ new MockUser(name, memberOf),
+ newProjectState(local));
+ }
+
+ private ProjectState newProjectState(ProjectConfig local) {
+ add(local);
+ return all.get(local.getProject().getNameKey());
+ }
+
+ 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);
+ }
+ }
+}