// 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.rest.project; import static com.google.common.truth.Truth.assertThat; import com.google.gerrit.acceptance.AbstractDaemonTest; import com.google.gerrit.acceptance.GitUtil; import com.google.gerrit.acceptance.PushOneCommit; import com.google.gerrit.common.data.AccessSection; import com.google.gerrit.common.data.GlobalCapability; import com.google.gerrit.common.data.Permission; import com.google.gerrit.extensions.api.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.projects.ProjectApi; 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.reviewdb.client.AccountGroup; import com.google.gerrit.reviewdb.client.Project; import com.google.gerrit.reviewdb.client.RefNames; import com.google.gerrit.server.config.AllProjectsNameProvider; import com.google.gerrit.server.group.SystemGroupBackend; import java.util.HashMap; 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.revwalk.RevCommit; import org.junit.Before; import org.junit.Test; public class AccessIT extends AbstractDaemonTest { private final String PROJECT_NAME = "newProject"; private final String REFS_ALL = Constants.R_REFS + "*"; private final String REFS_HEADS = Constants.R_HEADS + "*"; private final String LABEL_CODE_REVIEW = "Code-Review"; private String newProjectName; private ProjectApi pApi; @Before public void setUp() throws Exception { newProjectName = createProject(PROJECT_NAME).get(); pApi = gApi.projects().name(newProjectName); } @Test public void getDefaultInheritance() throws Exception { String inheritedName = pApi.access().inheritsFrom.name; assertThat(inheritedName).isEqualTo(AllProjectsNameProvider.DEFAULT); } @Test public void addAccessSection() throws Exception { Project.NameKey p = new Project.NameKey(newProjectName); RevCommit initialHead = getRemoteHead(p, 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 = getRemoteHead(p, RefNames.REFS_CONFIG); eventRecorder.assertRefUpdatedEvents( p.get(), RefNames.REFS_CONFIG, null, initialHead, initialHead, updatedHead); } @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 + LABEL_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 + LABEL_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 = LABEL_CODE_REVIEW; PermissionRuleInfo pri = new PermissionRuleInfo(PermissionRuleInfo.Action.DENY, false); codeReview.rules.put(SystemGroupBackend.REGISTERED_USERS.get(), pri); accessSectionToRemove.permissions.put(Permission.LABEL + LABEL_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 + LABEL_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 = LABEL_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 + LABEL_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 + LABEL_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_ALL, accessSectionInfo); pApi.access(accessInput); setApiUser(user); exception.expect(ResourceNotFoundException.class); gApi.projects().name(newProjectName).access(); } @Test public void setPermissionsWithDisallowedUser() throws Exception { // Add initial permission set ProjectAccessInput accessInput = newProjectAccessInput(); AccessSectionInfo accessSectionInfo = createAccessSectionInfoDenyAll(); // Disallow READ accessInput.add.put(REFS_ALL, accessSectionInfo); pApi.access(accessInput); // Create a change to apply ProjectAccessInput accessInfoToApply = newProjectAccessInput(); AccessSectionInfo accessSectionInfoToApply = createDefaultAccessSectionInfo(); accessInfoToApply.add.put(REFS_HEADS, accessSectionInfoToApply); setApiUser(user); exception.expect(ResourceNotFoundException.class); gApi.projects().name(newProjectName).access(); } @Test public void updateParentAsUser() throws Exception { // Create child String newParentProjectName = createProject(PROJECT_NAME + "PA").get(); // Set new parent ProjectAccessInput accessInput = newProjectAccessInput(); accessInput.parent = newParentProjectName; setApiUser(user); exception.expect(AuthException.class); exception.expectMessage("administrate server not permitted"); gApi.projects().name(newProjectName).access(accessInput); } @Test public void updateParentAsAdministrator() throws Exception { // Create parent String newParentProjectName = createProject(PROJECT_NAME + "PA").get(); // Set new parent ProjectAccessInput accessInput = newProjectAccessInput(); accessInput.parent = newParentProjectName; gApi.projects().name(newProjectName).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); setApiUser(user); exception.expect(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); assertThat( updatedAccessSectionInfo .local .get(AccessSection.GLOBAL_CAPABILITIES) .permissions .keySet()) .containsAllIn(accessSectionInfo.permissions.keySet()); } @Test public void addGlobalCapabilityForNonRootProject() throws Exception { ProjectAccessInput accessInput = newProjectAccessInput(); AccessSectionInfo accessSectionInfo = createDefaultGlobalCapabilitiesAccessSectionInfo(); accessInput.add.put(AccessSection.GLOBAL_CAPABILITIES, accessSectionInfo); exception.expect(BadRequestException.class); pApi.access(accessInput); } @Test public void addNonGlobalCapabilityToGlobalCapabilities() throws Exception { AccountGroup adminGroup = groupCache.get(new AccountGroup.NameKey("Administrators")); ProjectAccessInput accessInput = newProjectAccessInput(); AccessSectionInfo accessSectionInfo = newAccessSectionInfo(); PermissionInfo permissionInfo = newPermissionInfo(); permissionInfo.rules.put(adminGroup.getGroupUUID().get(), null); accessSectionInfo.permissions.put(Permission.PUSH, permissionInfo); accessInput.add.put(AccessSection.GLOBAL_CAPABILITIES, accessSectionInfo); exception.expect(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); setApiUser(user); exception.expect(AuthException.class); gApi.projects().name(allProjects.get()).access(accessInput); } @Test public void removeGlobalCapabilityAsAdmin() throws Exception { AccountGroup adminGroup = groupCache.get(new AccountGroup.NameKey("Administrators")); ProjectAccessInput accessInput = newProjectAccessInput(); AccessSectionInfo accessSectionInfo = newAccessSectionInfo(); PermissionInfo permissionInfo = newPermissionInfo(); permissionInfo.rules.put(adminGroup.getGroupUUID().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); assertThat( updatedProjectAccessInfo .local .get(AccessSection.GLOBAL_CAPABILITIES) .permissions .keySet()) .containsAllIn(accessSectionInfo.permissions.keySet()); // Remove accessInput.add.clear(); accessInput.remove.put(AccessSection.GLOBAL_CAPABILITIES, accessSectionInfo); updatedProjectAccessInfo = gApi.projects().name(allProjects.get()).access(accessInput); assertThat( updatedProjectAccessInfo .local .get(AccessSection.GLOBAL_CAPABILITIES) .permissions .keySet()) .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("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( db, admin.getIdent(), allProjectsRepo, "Subject", "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("project.config") .asString(); cfg.fromText(config); assertThat(cfg.getString(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("project.config") .asString(); cfg.fromText(config); assertThat(cfg.getString(access, refsFor, unknownPermission)).isEqualTo(registeredUsers); } 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 = LABEL_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 + LABEL_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; } }