blob: 32e823279f68348522e18d8ea320a8c20472ef77 [file] [log] [blame]
// Copyright (C) 2016 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.api.project;
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth8.assertThat;
import static com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate.allow;
import static com.google.gerrit.extensions.client.ListChangesOption.MESSAGES;
import static com.google.gerrit.server.group.SystemGroupBackend.ANONYMOUS_USERS;
import static com.google.gerrit.server.group.SystemGroupBackend.REGISTERED_USERS;
import static com.google.gerrit.server.schema.AclUtil.grant;
import static com.google.gerrit.testing.GerritJUnit.assertThrows;
import static com.google.gerrit.truth.ConfigSubject.assertThat;
import static com.google.gerrit.truth.MapSubject.assertThatMap;
import com.google.gerrit.acceptance.AbstractDaemonTest;
import com.google.gerrit.acceptance.ExtensionRegistry;
import com.google.gerrit.acceptance.ExtensionRegistry.Registration;
import com.google.gerrit.acceptance.GitUtil;
import com.google.gerrit.acceptance.PushOneCommit;
import com.google.gerrit.acceptance.testsuite.project.ProjectOperations;
import com.google.gerrit.acceptance.testsuite.request.RequestScopeOperations;
import com.google.gerrit.common.data.GlobalCapability;
import com.google.gerrit.entities.AccessSection;
import com.google.gerrit.entities.GroupReference;
import com.google.gerrit.entities.LabelId;
import com.google.gerrit.entities.Permission;
import com.google.gerrit.entities.Project;
import com.google.gerrit.entities.RefNames;
import com.google.gerrit.extensions.api.access.AccessSectionInfo;
import com.google.gerrit.extensions.api.access.PermissionInfo;
import com.google.gerrit.extensions.api.access.PermissionRuleInfo;
import com.google.gerrit.extensions.api.access.ProjectAccessInfo;
import com.google.gerrit.extensions.api.access.ProjectAccessInput;
import com.google.gerrit.extensions.api.changes.ReviewInput;
import com.google.gerrit.extensions.api.projects.ProjectApi;
import com.google.gerrit.extensions.client.ChangeStatus;
import com.google.gerrit.extensions.common.ChangeInfo;
import com.google.gerrit.extensions.common.GroupInfo;
import com.google.gerrit.extensions.common.WebLinkInfo;
import com.google.gerrit.extensions.config.CapabilityDefinition;
import com.google.gerrit.extensions.config.PluginProjectPermissionDefinition;
import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.BadRequestException;
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
import com.google.gerrit.extensions.webui.FileHistoryWebLink;
import com.google.gerrit.server.config.AllProjectsNameProvider;
import com.google.gerrit.server.extensions.events.GitReferenceUpdated;
import com.google.gerrit.server.git.meta.MetaDataUpdate;
import com.google.gerrit.server.group.SystemGroupBackend;
import com.google.gerrit.server.project.ProjectConfig;
import com.google.gerrit.server.schema.GrantRevertPermission;
import com.google.inject.Inject;
import java.util.HashMap;
import java.util.Map;
import org.eclipse.jgit.internal.storage.dfs.InMemoryRepository;
import org.eclipse.jgit.junit.TestRepository;
import org.eclipse.jgit.lib.Config;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.RefUpdate;
import org.eclipse.jgit.lib.RefUpdate.Result;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.revwalk.RevCommit;
import org.junit.Before;
import org.junit.Test;
public class AccessIT extends AbstractDaemonTest {
private static final String REFS_ALL = Constants.R_REFS + "*";
private static final String REFS_HEADS = Constants.R_HEADS + "*";
private static final String REFS_META_VERSION = "refs/meta/version";
private static final String REFS_DRAFTS = "refs/draft-comments/*";
private static final String REFS_STARRED_CHANGES = "refs/starred-changes/*";
@Inject private ProjectOperations projectOperations;
@Inject private RequestScopeOperations requestScopeOperations;
@Inject private ExtensionRegistry extensionRegistry;
@Inject private GrantRevertPermission grantRevertPermission;
private Project.NameKey newProjectName;
@Before
public void setUp() throws Exception {
newProjectName = projectOperations.newProject().create();
}
@Test
public void grantRevertPermission() throws Exception {
String ref = "refs/*";
String groupId = "global:Registered-Users";
grantRevertPermission.execute(newProjectName);
ProjectAccessInfo info = pApi().access();
assertThat(info.local.containsKey(ref)).isTrue();
AccessSectionInfo accessSectionInfo = info.local.get(ref);
assertThat(accessSectionInfo.permissions.containsKey(Permission.REVERT)).isTrue();
PermissionInfo permissionInfo = accessSectionInfo.permissions.get(Permission.REVERT);
assertThat(permissionInfo.rules.containsKey(groupId)).isTrue();
PermissionRuleInfo permissionRuleInfo = permissionInfo.rules.get(groupId);
assertThat(permissionRuleInfo.action).isEqualTo(PermissionRuleInfo.Action.ALLOW);
}
@Test
public void grantRevertPermissionByOnNewRefAndDeletingOnOldRef() throws Exception {
String refsHeads = "refs/heads/*";
String refsStar = "refs/*";
String groupId = "global:Registered-Users";
GroupReference registeredUsers = systemGroupBackend.getGroup(REGISTERED_USERS);
try (Repository repo = repoManager.openRepository(newProjectName)) {
MetaDataUpdate md = new MetaDataUpdate(GitReferenceUpdated.DISABLED, newProjectName, repo);
ProjectConfig projectConfig = projectConfigFactory.read(md);
projectConfig.upsertAccessSection(
AccessSection.HEADS,
heads -> {
grant(projectConfig, heads, Permission.REVERT, registeredUsers);
});
md.getCommitBuilder().setAuthor(admin.newIdent());
md.getCommitBuilder().setCommitter(admin.newIdent());
md.setMessage("Add revert permission for all registered users\n");
projectConfig.commit(md);
}
grantRevertPermission.execute(newProjectName);
ProjectAccessInfo info = pApi().access();
// Revert permission is removed on refs/heads/*.
assertThat(info.local.containsKey(refsHeads)).isTrue();
AccessSectionInfo accessSectionInfo = info.local.get(refsHeads);
assertThat(accessSectionInfo.permissions.containsKey(Permission.REVERT)).isFalse();
// new permission is added on refs/* with Registered-Users.
assertThat(info.local.containsKey(refsStar)).isTrue();
accessSectionInfo = info.local.get(refsStar);
assertThat(accessSectionInfo.permissions.containsKey(Permission.REVERT)).isTrue();
PermissionInfo permissionInfo = accessSectionInfo.permissions.get(Permission.REVERT);
assertThat(permissionInfo.rules.containsKey(groupId)).isTrue();
PermissionRuleInfo permissionRuleInfo = permissionInfo.rules.get(groupId);
assertThat(permissionRuleInfo.action).isEqualTo(PermissionRuleInfo.Action.ALLOW);
}
@Test
public void grantRevertPermissionDoesntDeleteAdminsPreferences() throws Exception {
GroupReference registeredUsers = systemGroupBackend.getGroup(REGISTERED_USERS);
GroupReference otherGroup = systemGroupBackend.getGroup(ANONYMOUS_USERS);
try (Repository repo = repoManager.openRepository(newProjectName)) {
MetaDataUpdate md = new MetaDataUpdate(GitReferenceUpdated.DISABLED, newProjectName, repo);
ProjectConfig projectConfig = projectConfigFactory.read(md);
projectConfig.upsertAccessSection(
AccessSection.HEADS,
heads -> {
grant(projectConfig, heads, Permission.REVERT, registeredUsers);
grant(projectConfig, heads, Permission.REVERT, otherGroup);
});
md.getCommitBuilder().setAuthor(admin.newIdent());
md.getCommitBuilder().setCommitter(admin.newIdent());
md.setMessage("Add revert permission for all registered users\n");
projectConfig.commit(md);
}
projectCache.evict(newProjectName);
ProjectAccessInfo expected = pApi().access();
grantRevertPermission.execute(newProjectName);
projectCache.evict(newProjectName);
ProjectAccessInfo actual = pApi().access();
// Permissions don't change
assertThat(expected.local).isEqualTo(actual.local);
}
@Test
public void grantRevertPermissionOnlyWorksOnce() throws Exception {
grantRevertPermission.execute(newProjectName);
grantRevertPermission.execute(newProjectName);
try (Repository repo = repoManager.openRepository(newProjectName)) {
MetaDataUpdate md = new MetaDataUpdate(GitReferenceUpdated.DISABLED, newProjectName, repo);
ProjectConfig projectConfig = projectConfigFactory.read(md);
AccessSection all = projectConfig.getAccessSection(AccessSection.ALL);
Permission permission = all.getPermission(Permission.REVERT);
assertThat(permission.getRules()).hasSize(1);
}
}
@Test
public void getDefaultInheritance() throws Exception {
String inheritedName = pApi().access().inheritsFrom.name;
assertThat(inheritedName).isEqualTo(AllProjectsNameProvider.DEFAULT);
}
private Registration newFileHistoryWebLink() {
FileHistoryWebLink weblink =
new FileHistoryWebLink() {
@Override
public WebLinkInfo getFileHistoryWebLink(
String projectName, String revision, String fileName) {
return new WebLinkInfo(
"name", "imageURL", "http://view/" + projectName + "/" + fileName);
}
};
return extensionRegistry.newRegistration().add(weblink);
}
@Test
public void webLink() throws Exception {
try (Registration registration = newFileHistoryWebLink()) {
ProjectAccessInfo info = pApi().access();
assertThat(info.configWebLinks).hasSize(1);
assertThat(info.configWebLinks.get(0).url)
.isEqualTo("http://view/" + newProjectName + "/project.config");
}
}
@Test
public void webLinkNoRefsMetaConfig() throws Exception {
try (Repository repo = repoManager.openRepository(newProjectName);
Registration registration = newFileHistoryWebLink()) {
RefUpdate u = repo.updateRef(RefNames.REFS_CONFIG);
u.setForceUpdate(true);
assertThat(u.delete()).isEqualTo(Result.FORCED);
// This should not crash.
pApi().access();
}
}
@Test
public void addAccessSection() throws Exception {
RevCommit initialHead = projectOperations.project(newProjectName).getHead(RefNames.REFS_CONFIG);
ProjectAccessInput accessInput = newProjectAccessInput();
AccessSectionInfo accessSectionInfo = createDefaultAccessSectionInfo();
accessInput.add.put(REFS_HEADS, accessSectionInfo);
pApi().access(accessInput);
assertThat(pApi().access().local).isEqualTo(accessInput.add);
RevCommit updatedHead = projectOperations.project(newProjectName).getHead(RefNames.REFS_CONFIG);
eventRecorder.assertRefUpdatedEvents(
newProjectName.get(), RefNames.REFS_CONFIG, null, initialHead, initialHead, updatedHead);
}
@Test
public void addAccessSectionForPluginPermission() throws Exception {
try (Registration registration =
extensionRegistry
.newRegistration()
.add(
new PluginProjectPermissionDefinition() {
@Override
public String getDescription() {
return "A Plugin Project Permission";
}
},
"fooPermission")) {
ProjectAccessInput accessInput = newProjectAccessInput();
AccessSectionInfo accessSectionInfo = newAccessSectionInfo();
PermissionInfo foo = newPermissionInfo();
PermissionRuleInfo pri = new PermissionRuleInfo(PermissionRuleInfo.Action.ALLOW, false);
foo.rules.put(SystemGroupBackend.REGISTERED_USERS.get(), pri);
accessSectionInfo.permissions.put(
"plugin-" + ExtensionRegistry.PLUGIN_NAME + "-fooPermission", foo);
accessInput.add.put(REFS_HEADS, accessSectionInfo);
ProjectAccessInfo updatedAccessSectionInfo = pApi().access(accessInput);
assertThat(updatedAccessSectionInfo.local).isEqualTo(accessInput.add);
assertThat(pApi().access().local).isEqualTo(accessInput.add);
}
}
@Test
public void addAccessSectionWithInvalidPermission() throws Exception {
ProjectAccessInput accessInput = newProjectAccessInput();
AccessSectionInfo accessSectionInfo = createDefaultAccessSectionInfo();
PermissionInfo push = newPermissionInfo();
PermissionRuleInfo pri = new PermissionRuleInfo(PermissionRuleInfo.Action.ALLOW, false);
push.rules.put(SystemGroupBackend.REGISTERED_USERS.get(), pri);
accessSectionInfo.permissions.put("Invalid Permission", push);
accessInput.add.put(REFS_HEADS, accessSectionInfo);
BadRequestException ex =
assertThrows(BadRequestException.class, () -> pApi().access(accessInput));
assertThat(ex).hasMessageThat().isEqualTo("Unknown permission: Invalid Permission");
}
@Test
public void addAccessSectionWithInvalidLabelPermission() throws Exception {
ProjectAccessInput accessInput = newProjectAccessInput();
AccessSectionInfo accessSectionInfo = createDefaultAccessSectionInfo();
PermissionInfo push = newPermissionInfo();
PermissionRuleInfo pri = new PermissionRuleInfo(PermissionRuleInfo.Action.ALLOW, false);
push.rules.put(SystemGroupBackend.REGISTERED_USERS.get(), pri);
accessSectionInfo.permissions.put("label-Invalid Permission", push);
accessInput.add.put(REFS_HEADS, accessSectionInfo);
BadRequestException ex =
assertThrows(BadRequestException.class, () -> pApi().access(accessInput));
assertThat(ex).hasMessageThat().isEqualTo("Unknown permission: label-Invalid Permission");
}
@Test
public void createAccessChangeNop() throws Exception {
ProjectAccessInput accessInput = newProjectAccessInput();
assertThrows(BadRequestException.class, () -> pApi().accessChange(accessInput));
}
@Test
public void createAccessChangeEmptyConfig() throws Exception {
try (Repository repo = repoManager.openRepository(newProjectName)) {
RefUpdate ru = repo.updateRef(RefNames.REFS_CONFIG);
ru.setForceUpdate(true);
assertThat(ru.delete()).isEqualTo(Result.FORCED);
ProjectAccessInput accessInput = newProjectAccessInput();
AccessSectionInfo accessSection = newAccessSectionInfo();
PermissionInfo read = newPermissionInfo();
PermissionRuleInfo pri = new PermissionRuleInfo(PermissionRuleInfo.Action.BLOCK, false);
read.rules.put(SystemGroupBackend.REGISTERED_USERS.get(), pri);
accessSection.permissions.put(Permission.READ, read);
accessInput.add.put(REFS_HEADS, accessSection);
ChangeInfo out = pApi().accessChange(accessInput);
assertThat(out.status).isEqualTo(ChangeStatus.NEW);
}
}
@Test
public void createAccessChange() throws Exception {
projectOperations
.project(newProjectName)
.forUpdate()
.add(allow(Permission.READ).ref(RefNames.REFS_CONFIG).group(REGISTERED_USERS))
.update();
// User can see the branch
requestScopeOperations.setApiUser(user.id());
pApi().branch("refs/heads/master").get();
ProjectAccessInput accessInput = newProjectAccessInput();
AccessSectionInfo accessSection = newAccessSectionInfo();
// Deny read to registered users.
PermissionInfo read = newPermissionInfo();
PermissionRuleInfo pri = new PermissionRuleInfo(PermissionRuleInfo.Action.DENY, false);
read.rules.put(SystemGroupBackend.REGISTERED_USERS.get(), pri);
read.exclusive = true;
accessSection.permissions.put(Permission.READ, read);
accessInput.add.put(REFS_HEADS, accessSection);
requestScopeOperations.setApiUser(user.id());
ChangeInfo out = pApi().accessChange(accessInput);
assertThat(out.project).isEqualTo(newProjectName.get());
assertThat(out.branch).isEqualTo(RefNames.REFS_CONFIG);
assertThat(out.status).isEqualTo(ChangeStatus.NEW);
assertThat(out.submitted).isNull();
requestScopeOperations.setApiUser(admin.id());
ChangeInfo c = gApi.changes().id(out._number).get(MESSAGES);
assertThat(c.messages.stream().map(m -> m.message)).containsExactly("Uploaded patch set 1");
ReviewInput reviewIn = new ReviewInput();
reviewIn.label("Code-Review", (short) 2);
gApi.changes().id(out._number).current().review(reviewIn);
gApi.changes().id(out._number).current().submit();
// check that the change took effect.
requestScopeOperations.setApiUser(user.id());
assertThrows(ResourceNotFoundException.class, () -> pApi().branch("refs/heads/master").get());
// Restore.
accessInput.add.clear();
accessInput.remove.put(REFS_HEADS, accessSection);
requestScopeOperations.setApiUser(user.id());
requestScopeOperations.setApiUser(admin.id());
out = pApi().accessChange(accessInput);
gApi.changes().id(out._number).current().review(reviewIn);
gApi.changes().id(out._number).current().submit();
// Now it works again.
requestScopeOperations.setApiUser(user.id());
pApi().branch("refs/heads/master").get();
}
@Test
public void removePermission() throws Exception {
// Add initial permission set
ProjectAccessInput accessInput = newProjectAccessInput();
AccessSectionInfo accessSectionInfo = createDefaultAccessSectionInfo();
accessInput.add.put(REFS_HEADS, accessSectionInfo);
pApi().access(accessInput);
// Remove specific permission
AccessSectionInfo accessSectionToRemove = newAccessSectionInfo();
accessSectionToRemove.permissions.put(
Permission.LABEL + LabelId.CODE_REVIEW, newPermissionInfo());
ProjectAccessInput removal = newProjectAccessInput();
removal.remove.put(REFS_HEADS, accessSectionToRemove);
pApi().access(removal);
// Remove locally
accessInput.add.get(REFS_HEADS).permissions.remove(Permission.LABEL + LabelId.CODE_REVIEW);
// Check
assertThat(pApi().access().local).isEqualTo(accessInput.add);
}
@Test
public void removePermissionRule() throws Exception {
// Add initial permission set
ProjectAccessInput accessInput = newProjectAccessInput();
AccessSectionInfo accessSectionInfo = createDefaultAccessSectionInfo();
accessInput.add.put(REFS_HEADS, accessSectionInfo);
pApi().access(accessInput);
// Remove specific permission rule
AccessSectionInfo accessSectionToRemove = newAccessSectionInfo();
PermissionInfo codeReview = newPermissionInfo();
codeReview.label = LabelId.CODE_REVIEW;
PermissionRuleInfo pri = new PermissionRuleInfo(PermissionRuleInfo.Action.DENY, false);
codeReview.rules.put(SystemGroupBackend.REGISTERED_USERS.get(), pri);
accessSectionToRemove.permissions.put(Permission.LABEL + LabelId.CODE_REVIEW, codeReview);
ProjectAccessInput removal = newProjectAccessInput();
removal.remove.put(REFS_HEADS, accessSectionToRemove);
pApi().access(removal);
// Remove locally
accessInput
.add
.get(REFS_HEADS)
.permissions
.get(Permission.LABEL + LabelId.CODE_REVIEW)
.rules
.remove(SystemGroupBackend.REGISTERED_USERS.get());
// Check
assertThat(pApi().access().local).isEqualTo(accessInput.add);
}
@Test
public void removePermissionRulesAndCleanupEmptyEntries() throws Exception {
// Add initial permission set
ProjectAccessInput accessInput = newProjectAccessInput();
AccessSectionInfo accessSectionInfo = createDefaultAccessSectionInfo();
accessInput.add.put(REFS_HEADS, accessSectionInfo);
pApi().access(accessInput);
// Remove specific permission rules
AccessSectionInfo accessSectionToRemove = newAccessSectionInfo();
PermissionInfo codeReview = newPermissionInfo();
codeReview.label = LabelId.CODE_REVIEW;
PermissionRuleInfo pri = new PermissionRuleInfo(PermissionRuleInfo.Action.DENY, false);
codeReview.rules.put(SystemGroupBackend.REGISTERED_USERS.get(), pri);
pri = new PermissionRuleInfo(PermissionRuleInfo.Action.DENY, false);
codeReview.rules.put(SystemGroupBackend.PROJECT_OWNERS.get(), pri);
accessSectionToRemove.permissions.put(Permission.LABEL + LabelId.CODE_REVIEW, codeReview);
ProjectAccessInput removal = newProjectAccessInput();
removal.remove.put(REFS_HEADS, accessSectionToRemove);
pApi().access(removal);
// Remove locally
accessInput.add.get(REFS_HEADS).permissions.remove(Permission.LABEL + LabelId.CODE_REVIEW);
// Check
assertThat(pApi().access().local).isEqualTo(accessInput.add);
}
@Test
public void getPermissionsWithDisallowedUser() throws Exception {
// Add initial permission set
ProjectAccessInput accessInput = newProjectAccessInput();
AccessSectionInfo accessSectionInfo = createAccessSectionInfoDenyAll();
// Disallow READ
accessInput.add.put(REFS_META_VERSION, accessSectionInfo);
accessInput.add.put(REFS_DRAFTS, accessSectionInfo);
accessInput.add.put(REFS_STARRED_CHANGES, accessSectionInfo);
accessInput.add.put(REFS_HEADS, accessSectionInfo);
pApi().access(accessInput);
requestScopeOperations.setApiUser(user.id());
assertThrows(ResourceNotFoundException.class, () -> pApi().access());
}
@Test
public void setPermissionsWithDisallowedUser() throws Exception {
// Add initial permission set
ProjectAccessInput accessInput = newProjectAccessInput();
AccessSectionInfo accessSectionInfo = createAccessSectionInfoDenyAll();
// Disallow READ
accessInput.add.put(REFS_META_VERSION, accessSectionInfo);
accessInput.add.put(REFS_DRAFTS, accessSectionInfo);
accessInput.add.put(REFS_STARRED_CHANGES, accessSectionInfo);
accessInput.add.put(REFS_HEADS, accessSectionInfo);
pApi().access(accessInput);
// Create a change to apply
ProjectAccessInput accessInfoToApply = newProjectAccessInput();
AccessSectionInfo accessSectionInfoToApply = createDefaultAccessSectionInfo();
accessInfoToApply.add.put(REFS_HEADS, accessSectionInfoToApply);
requestScopeOperations.setApiUser(user.id());
assertThrows(ResourceNotFoundException.class, () -> pApi().access());
}
@Test
public void permissionsGroupMap() throws Exception {
// Add initial permission set
ProjectAccessInput accessInput = newProjectAccessInput();
AccessSectionInfo accessSection = newAccessSectionInfo();
PermissionInfo push = newPermissionInfo();
PermissionRuleInfo pri = new PermissionRuleInfo(PermissionRuleInfo.Action.ALLOW, false);
push.rules.put(SystemGroupBackend.PROJECT_OWNERS.get(), pri);
accessSection.permissions.put(Permission.PUSH, push);
PermissionInfo read = newPermissionInfo();
pri = new PermissionRuleInfo(PermissionRuleInfo.Action.ALLOW, false);
read.rules.put(SystemGroupBackend.ANONYMOUS_USERS.get(), pri);
accessSection.permissions.put(Permission.READ, read);
accessInput.add.put(REFS_ALL, accessSection);
ProjectAccessInfo result = pApi().access(accessInput);
assertThatMap(result.groups)
.keys()
.containsExactly(
SystemGroupBackend.PROJECT_OWNERS.get(), SystemGroupBackend.ANONYMOUS_USERS.get());
// Check the name, which is what the UI cares about; exhaustive
// coverage of GroupInfo should be in groups REST API tests.
assertThat(result.groups.get(SystemGroupBackend.PROJECT_OWNERS.get()).name)
.isEqualTo("Project Owners");
// Strip the ID, since it is in the key.
assertThat(result.groups.get(SystemGroupBackend.PROJECT_OWNERS.get()).id).isNull();
// Get call returns groups too.
ProjectAccessInfo loggedInResult = pApi().access();
assertThatMap(loggedInResult.groups)
.keys()
.containsExactly(
SystemGroupBackend.PROJECT_OWNERS.get(), SystemGroupBackend.ANONYMOUS_USERS.get());
GroupInfo owners = loggedInResult.groups.get(SystemGroupBackend.PROJECT_OWNERS.get());
assertThat(owners.name).isEqualTo("Project Owners");
assertThat(owners.id).isNull();
assertThat(owners.members).isNull();
assertThat(owners.includes).isNull();
// PROJECT_OWNERS is invisible to anonymous user, but GetAccess disregards visibility.
requestScopeOperations.setApiUserAnonymous();
ProjectAccessInfo anonResult = pApi().access();
assertThatMap(anonResult.groups)
.keys()
.containsExactly(
SystemGroupBackend.PROJECT_OWNERS.get(), SystemGroupBackend.ANONYMOUS_USERS.get());
}
@Test
public void updateParentAsUser() throws Exception {
// Create child
String newParentProjectName = projectOperations.newProject().create().get();
// Set new parent
ProjectAccessInput accessInput = newProjectAccessInput();
accessInput.parent = newParentProjectName;
requestScopeOperations.setApiUser(user.id());
AuthException thrown = assertThrows(AuthException.class, () -> pApi().access(accessInput));
assertThat(thrown).hasMessageThat().contains("administrate server not permitted");
}
@Test
public void updateParentAsAdministrator() throws Exception {
// Create parent
String newParentProjectName = projectOperations.newProject().create().get();
// Set new parent
ProjectAccessInput accessInput = newProjectAccessInput();
accessInput.parent = newParentProjectName;
pApi().access(accessInput);
assertThat(pApi().access().inheritsFrom.name).isEqualTo(newParentProjectName);
}
@Test
public void addGlobalCapabilityAsUser() throws Exception {
ProjectAccessInput accessInput = newProjectAccessInput();
AccessSectionInfo accessSectionInfo = createDefaultGlobalCapabilitiesAccessSectionInfo();
accessInput.add.put(AccessSection.GLOBAL_CAPABILITIES, accessSectionInfo);
requestScopeOperations.setApiUser(user.id());
assertThrows(
AuthException.class, () -> gApi.projects().name(allProjects.get()).access(accessInput));
}
@Test
public void addGlobalCapabilityAsAdmin() throws Exception {
ProjectAccessInput accessInput = newProjectAccessInput();
AccessSectionInfo accessSectionInfo = createDefaultGlobalCapabilitiesAccessSectionInfo();
accessInput.add.put(AccessSection.GLOBAL_CAPABILITIES, accessSectionInfo);
ProjectAccessInfo updatedAccessSectionInfo =
gApi.projects().name(allProjects.get()).access(accessInput);
assertThatMap(updatedAccessSectionInfo.local.get(AccessSection.GLOBAL_CAPABILITIES).permissions)
.keys()
.containsAtLeastElementsIn(accessSectionInfo.permissions.keySet());
}
@Test
public void addPluginGlobalCapability() throws Exception {
try (Registration registration =
extensionRegistry
.newRegistration()
.add(
new CapabilityDefinition() {
@Override
public String getDescription() {
return "A Plugin Global Capability";
}
},
"fooCapability")) {
ProjectAccessInput accessInput = newProjectAccessInput();
AccessSectionInfo accessSectionInfo = newAccessSectionInfo();
PermissionInfo foo = newPermissionInfo();
PermissionRuleInfo pri = new PermissionRuleInfo(PermissionRuleInfo.Action.ALLOW, false);
foo.rules.put(SystemGroupBackend.REGISTERED_USERS.get(), pri);
accessSectionInfo.permissions.put(ExtensionRegistry.PLUGIN_NAME + "-fooCapability", foo);
accessInput.add.put(AccessSection.GLOBAL_CAPABILITIES, accessSectionInfo);
ProjectAccessInfo updatedAccessSectionInfo =
gApi.projects().name(allProjects.get()).access(accessInput);
assertThatMap(
updatedAccessSectionInfo.local.get(AccessSection.GLOBAL_CAPABILITIES).permissions)
.keys()
.containsAtLeastElementsIn(accessSectionInfo.permissions.keySet());
}
}
@Test
public void addPermissionAsGlobalCapability() throws Exception {
ProjectAccessInput accessInput = newProjectAccessInput();
AccessSectionInfo accessSectionInfo = newAccessSectionInfo();
PermissionInfo push = newPermissionInfo();
PermissionRuleInfo pri = new PermissionRuleInfo(PermissionRuleInfo.Action.ALLOW, false);
push.rules.put(SystemGroupBackend.REGISTERED_USERS.get(), pri);
accessSectionInfo.permissions.put(Permission.PUSH, push);
accessInput.add.put(AccessSection.GLOBAL_CAPABILITIES, accessSectionInfo);
BadRequestException ex =
assertThrows(
BadRequestException.class,
() -> gApi.projects().name(allProjects.get()).access(accessInput));
assertThat(ex).hasMessageThat().isEqualTo("Unknown global capability: " + Permission.PUSH);
}
@Test
public void addInvalidGlobalCapability() throws Exception {
ProjectAccessInput accessInput = newProjectAccessInput();
AccessSectionInfo accessSectionInfo = createDefaultGlobalCapabilitiesAccessSectionInfo();
PermissionInfo permissionInfo = newPermissionInfo();
PermissionRuleInfo pri = new PermissionRuleInfo(PermissionRuleInfo.Action.ALLOW, false);
permissionInfo.rules.put(SystemGroupBackend.REGISTERED_USERS.get(), pri);
accessSectionInfo.permissions.put("Invalid Global Capability", permissionInfo);
accessInput.add.put(AccessSection.GLOBAL_CAPABILITIES, accessSectionInfo);
BadRequestException ex =
assertThrows(
BadRequestException.class,
() -> gApi.projects().name(allProjects.get()).access(accessInput));
assertThat(ex)
.hasMessageThat()
.isEqualTo("Unknown global capability: Invalid Global Capability");
}
@Test
public void addGlobalCapabilityForNonRootProject() throws Exception {
ProjectAccessInput accessInput = newProjectAccessInput();
AccessSectionInfo accessSectionInfo = createDefaultGlobalCapabilitiesAccessSectionInfo();
accessInput.add.put(AccessSection.GLOBAL_CAPABILITIES, accessSectionInfo);
assertThrows(BadRequestException.class, () -> pApi().access(accessInput));
}
@Test
public void addNonGlobalCapabilityToGlobalCapabilities() throws Exception {
ProjectAccessInput accessInput = newProjectAccessInput();
AccessSectionInfo accessSectionInfo = newAccessSectionInfo();
PermissionInfo permissionInfo = newPermissionInfo();
permissionInfo.rules.put(adminGroupUuid().get(), null);
accessSectionInfo.permissions.put(Permission.PUSH, permissionInfo);
accessInput.add.put(AccessSection.GLOBAL_CAPABILITIES, accessSectionInfo);
assertThrows(
BadRequestException.class,
() -> gApi.projects().name(allProjects.get()).access(accessInput));
}
@Test
public void removeGlobalCapabilityAsUser() throws Exception {
ProjectAccessInput accessInput = newProjectAccessInput();
AccessSectionInfo accessSectionInfo = createDefaultGlobalCapabilitiesAccessSectionInfo();
accessInput.remove.put(AccessSection.GLOBAL_CAPABILITIES, accessSectionInfo);
requestScopeOperations.setApiUser(user.id());
assertThrows(
AuthException.class, () -> gApi.projects().name(allProjects.get()).access(accessInput));
}
@Test
public void removeGlobalCapabilityAsAdmin() throws Exception {
ProjectAccessInput accessInput = newProjectAccessInput();
AccessSectionInfo accessSectionInfo = newAccessSectionInfo();
PermissionInfo permissionInfo = newPermissionInfo();
permissionInfo.rules.put(adminGroupUuid().get(), null);
accessSectionInfo.permissions.put(GlobalCapability.ACCESS_DATABASE, permissionInfo);
// Add and validate first as removing existing privileges such as
// administrateServer would break upcoming tests
accessInput.add.put(AccessSection.GLOBAL_CAPABILITIES, accessSectionInfo);
ProjectAccessInfo updatedProjectAccessInfo =
gApi.projects().name(allProjects.get()).access(accessInput);
assertThatMap(updatedProjectAccessInfo.local.get(AccessSection.GLOBAL_CAPABILITIES).permissions)
.keys()
.containsAtLeastElementsIn(accessSectionInfo.permissions.keySet());
// Remove
accessInput.add.clear();
accessInput.remove.put(AccessSection.GLOBAL_CAPABILITIES, accessSectionInfo);
updatedProjectAccessInfo = gApi.projects().name(allProjects.get()).access(accessInput);
assertThatMap(updatedProjectAccessInfo.local.get(AccessSection.GLOBAL_CAPABILITIES).permissions)
.keys()
.containsNoneIn(accessSectionInfo.permissions.keySet());
}
@Test
public void unknownPermissionRemainsUnchanged() throws Exception {
String access = "access";
String unknownPermission = "unknownPermission";
String registeredUsers = "group Registered Users";
String refsFor = "refs/for/*";
// Clone repository to forcefully add permission
TestRepository<InMemoryRepository> allProjectsRepo = cloneProject(allProjects, admin);
// Fetch permission ref
GitUtil.fetch(allProjectsRepo, "refs/meta/config:cfg");
allProjectsRepo.reset("cfg");
// Load current permissions
String config =
gApi.projects()
.name(allProjects.get())
.branch(RefNames.REFS_CONFIG)
.file(ProjectConfig.PROJECT_CONFIG)
.asString();
// Append and push unknown permission
Config cfg = new Config();
cfg.fromText(config);
cfg.setString(access, refsFor, unknownPermission, registeredUsers);
config = cfg.toText();
PushOneCommit push =
pushFactory.create(
admin.newIdent(), allProjectsRepo, "Subject", ProjectConfig.PROJECT_CONFIG, config);
push.to(RefNames.REFS_CONFIG).assertOkStatus();
// Verify that unknownPermission is present
config =
gApi.projects()
.name(allProjects.get())
.branch(RefNames.REFS_CONFIG)
.file(ProjectConfig.PROJECT_CONFIG)
.asString();
cfg.fromText(config);
assertThat(cfg).stringValue(access, refsFor, unknownPermission).isEqualTo(registeredUsers);
// Make permission change through API
ProjectAccessInput accessInput = newProjectAccessInput();
AccessSectionInfo accessSectionInfo = createDefaultAccessSectionInfo();
accessInput.add.put(refsFor, accessSectionInfo);
gApi.projects().name(allProjects.get()).access(accessInput);
accessInput.add.clear();
accessInput.remove.put(refsFor, accessSectionInfo);
gApi.projects().name(allProjects.get()).access(accessInput);
// Verify that unknownPermission is still present
config =
gApi.projects()
.name(allProjects.get())
.branch(RefNames.REFS_CONFIG)
.file(ProjectConfig.PROJECT_CONFIG)
.asString();
cfg.fromText(config);
assertThat(cfg).stringValue(access, refsFor, unknownPermission).isEqualTo(registeredUsers);
}
@Test
public void allUsersCanOnlyInheritFromAllProjects() throws Exception {
ProjectAccessInput accessInput = newProjectAccessInput();
accessInput.parent = project.get();
BadRequestException thrown =
assertThrows(
BadRequestException.class,
() -> gApi.projects().name(allUsers.get()).access(accessInput));
assertThat(thrown)
.hasMessageThat()
.contains(allUsers.get() + " must inherit from " + allProjects.get());
}
@Test
public void syncCreateGroupPermission_addAndRemoveCreateGroupCapability() throws Exception {
// Grant CREATE_GROUP to Registered Users
ProjectAccessInput accessInput = newProjectAccessInput();
AccessSectionInfo accessSection = newAccessSectionInfo();
PermissionInfo createGroup = newPermissionInfo();
PermissionRuleInfo pri = new PermissionRuleInfo(PermissionRuleInfo.Action.ALLOW, false);
createGroup.rules.put(SystemGroupBackend.REGISTERED_USERS.get(), pri);
accessSection.permissions.put(GlobalCapability.CREATE_GROUP, createGroup);
accessInput.add.put(AccessSection.GLOBAL_CAPABILITIES, accessSection);
gApi.projects().name(allProjects.get()).access(accessInput);
// Assert that the permission was synced from All-Projects (global) to All-Users (ref)
Map<String, AccessSectionInfo> local = gApi.projects().name("All-Users").access().local;
assertThatMap(local).keys().contains(RefNames.REFS_GROUPS + "*");
Map<String, PermissionInfo> permissions = local.get(RefNames.REFS_GROUPS + "*").permissions;
// READ is the default permission and should be preserved by the syncer
assertThatMap(permissions).keys().containsExactly(Permission.READ, Permission.CREATE);
Map<String, PermissionRuleInfo> rules = permissions.get(Permission.CREATE).rules;
assertThatMap(rules).values().containsExactly(pri);
// Revoke the permission
accessInput.add.clear();
accessInput.remove.put(AccessSection.GLOBAL_CAPABILITIES, accessSection);
gApi.projects().name(allProjects.get()).access(accessInput);
// Assert that the permission was synced from All-Projects (global) to All-Users (ref)
Map<String, AccessSectionInfo> local2 = gApi.projects().name("All-Users").access().local;
assertThatMap(local2).keys().contains(RefNames.REFS_GROUPS + "*");
Map<String, PermissionInfo> permissions2 = local2.get(RefNames.REFS_GROUPS + "*").permissions;
// READ is the default permission and should be preserved by the syncer
assertThatMap(permissions2).keys().containsExactly(Permission.READ);
}
@Test
public void syncCreateGroupPermission_addCreateGroupCapabilityToMultipleGroups()
throws Exception {
PermissionRuleInfo pri = new PermissionRuleInfo(PermissionRuleInfo.Action.ALLOW, false);
// Grant CREATE_GROUP to Registered Users
ProjectAccessInput accessInput = newProjectAccessInput();
AccessSectionInfo accessSection = newAccessSectionInfo();
PermissionInfo createGroup = newPermissionInfo();
createGroup.rules.put(SystemGroupBackend.REGISTERED_USERS.get(), pri);
accessSection.permissions.put(GlobalCapability.CREATE_GROUP, createGroup);
accessInput.add.put(AccessSection.GLOBAL_CAPABILITIES, accessSection);
gApi.projects().name(allProjects.get()).access(accessInput);
// Grant CREATE_GROUP to Administrators
accessInput = newProjectAccessInput();
accessSection = newAccessSectionInfo();
createGroup = newPermissionInfo();
createGroup.rules.put(adminGroupUuid().get(), pri);
accessSection.permissions.put(GlobalCapability.CREATE_GROUP, createGroup);
accessInput.add.put(AccessSection.GLOBAL_CAPABILITIES, accessSection);
gApi.projects().name(allProjects.get()).access(accessInput);
// Assert that the permissions were synced from All-Projects (global) to All-Users (ref)
Map<String, AccessSectionInfo> local = gApi.projects().name("All-Users").access().local;
assertThatMap(local).keys().contains(RefNames.REFS_GROUPS + "*");
Map<String, PermissionInfo> permissions = local.get(RefNames.REFS_GROUPS + "*").permissions;
// READ is the default permission and should be preserved by the syncer
assertThatMap(permissions).keys().containsExactly(Permission.READ, Permission.CREATE);
Map<String, PermissionRuleInfo> rules = permissions.get(Permission.CREATE).rules;
assertThatMap(rules)
.keys()
.containsExactly(SystemGroupBackend.REGISTERED_USERS.get(), adminGroupUuid().get());
assertThat(rules.get(SystemGroupBackend.REGISTERED_USERS.get())).isEqualTo(pri);
assertThat(rules.get(adminGroupUuid().get())).isEqualTo(pri);
}
@Test
public void addAccessSectionForInvalidRef() throws Exception {
ProjectAccessInput accessInput = newProjectAccessInput();
AccessSectionInfo accessSectionInfo = createDefaultAccessSectionInfo();
// 'refs/heads/stable_*' is invalid, correct would be '^refs/heads/stable_.*'
String invalidRef = Constants.R_HEADS + "stable_*";
accessInput.add.put(invalidRef, accessSectionInfo);
BadRequestException thrown =
assertThrows(BadRequestException.class, () -> pApi().access(accessInput));
assertThat(thrown).hasMessageThat().contains("Invalid Name: " + invalidRef);
}
@Test
public void createAccessChangeWithAccessSectionForInvalidRef() throws Exception {
ProjectAccessInput accessInput = newProjectAccessInput();
AccessSectionInfo accessSectionInfo = createDefaultAccessSectionInfo();
// 'refs/heads/stable_*' is invalid, correct would be '^refs/heads/stable_.*'
String invalidRef = Constants.R_HEADS + "stable_*";
accessInput.add.put(invalidRef, accessSectionInfo);
BadRequestException thrown =
assertThrows(BadRequestException.class, () -> pApi().accessChange(accessInput));
assertThat(thrown).hasMessageThat().contains("Invalid Name: " + invalidRef);
}
private ProjectApi pApi() throws Exception {
return gApi.projects().name(newProjectName.get());
}
private ProjectAccessInput newProjectAccessInput() {
ProjectAccessInput p = new ProjectAccessInput();
p.add = new HashMap<>();
p.remove = new HashMap<>();
return p;
}
private PermissionInfo newPermissionInfo() {
PermissionInfo p = new PermissionInfo(null, null);
p.rules = new HashMap<>();
return p;
}
private AccessSectionInfo newAccessSectionInfo() {
AccessSectionInfo a = new AccessSectionInfo();
a.permissions = new HashMap<>();
return a;
}
private AccessSectionInfo createDefaultAccessSectionInfo() {
AccessSectionInfo accessSection = newAccessSectionInfo();
PermissionInfo push = newPermissionInfo();
PermissionRuleInfo pri = new PermissionRuleInfo(PermissionRuleInfo.Action.ALLOW, false);
push.rules.put(SystemGroupBackend.REGISTERED_USERS.get(), pri);
accessSection.permissions.put(Permission.PUSH, push);
PermissionInfo codeReview = newPermissionInfo();
codeReview.label = LabelId.CODE_REVIEW;
pri = new PermissionRuleInfo(PermissionRuleInfo.Action.DENY, false);
codeReview.rules.put(SystemGroupBackend.REGISTERED_USERS.get(), pri);
pri = new PermissionRuleInfo(PermissionRuleInfo.Action.ALLOW, false);
pri.max = 1;
pri.min = -1;
codeReview.rules.put(SystemGroupBackend.PROJECT_OWNERS.get(), pri);
accessSection.permissions.put(Permission.LABEL + LabelId.CODE_REVIEW, codeReview);
return accessSection;
}
private AccessSectionInfo createDefaultGlobalCapabilitiesAccessSectionInfo() {
AccessSectionInfo accessSection = newAccessSectionInfo();
PermissionInfo email = newPermissionInfo();
PermissionRuleInfo pri = new PermissionRuleInfo(PermissionRuleInfo.Action.ALLOW, false);
email.rules.put(SystemGroupBackend.REGISTERED_USERS.get(), pri);
accessSection.permissions.put(GlobalCapability.EMAIL_REVIEWERS, email);
return accessSection;
}
private AccessSectionInfo createAccessSectionInfoDenyAll() {
AccessSectionInfo accessSection = newAccessSectionInfo();
PermissionInfo read = newPermissionInfo();
PermissionRuleInfo pri = new PermissionRuleInfo(PermissionRuleInfo.Action.DENY, false);
read.rules.put(SystemGroupBackend.ANONYMOUS_USERS.get(), pri);
accessSection.permissions.put(Permission.READ, read);
return accessSection;
}
}