blob: f390a30cc341dcd9da2e2c4ed927e5cdc9b74079 [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.ericsson.gerrit.plugins.projectgroupstructure;
import static com.google.common.truth.Truth.assertThat;
import static com.google.gerrit.server.group.SystemGroupBackend.REGISTERED_USERS;
import com.google.common.base.Charsets;
import com.google.common.collect.Lists;
import com.google.common.hash.Hashing;
import com.google.gerrit.acceptance.LightweightPluginDaemonTest;
import com.google.gerrit.acceptance.RestResponse;
import com.google.gerrit.acceptance.TestPlugin;
import com.google.gerrit.common.data.GlobalCapability;
import com.google.gerrit.common.data.GroupReference;
import com.google.gerrit.extensions.api.groups.GroupApi;
import com.google.gerrit.extensions.api.projects.ProjectInput;
import com.google.gerrit.extensions.restapi.Url;
import com.google.gerrit.reviewdb.client.AccountGroup;
import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.server.project.ProjectConfig;
import com.google.gerrit.server.project.ProjectState;
import org.junit.Before;
import org.junit.Test;
@TestPlugin(
name = "project-group-structure",
sysModule = "com.ericsson.gerrit.plugins.projectgroupstructure.Module")
public class ProjectCreationValidatorIT extends LightweightPluginDaemonTest {
private static final String PLUGIN_NAME = "project-group-structure";
@Override
@Before
public void setUpTestPlugin() throws Exception {
super.setUpTestPlugin();
// These access rights are mandatory configuration for this plugin as
// documented in config.md
allowGlobalCapabilities(REGISTERED_USERS, GlobalCapability.CREATE_GROUP);
allowGlobalCapabilities(REGISTERED_USERS, GlobalCapability.CREATE_PROJECT);
}
@Test
public void shouldProjectWithASpaceInTheirName() throws Exception {
ProjectInput in = new ProjectInput();
in.permissionsOnly = true;
RestResponse r = userRestSession.put("/projects/" + Url.encode("project with space"), in);
r.assertConflict();
assertThat(r.getEntityContent()).contains("Project name cannot contains spaces");
}
@Test
public void shouldAllowAnyUsersToCreateUnderAllProjects() throws Exception {
ProjectInput in = new ProjectInput();
in.permissionsOnly = true;
adminRestSession.put("/projects/" + name("someProject"), in).assertCreated();
userRestSession.put("/projects/" + name("someOtherProject"), in).assertCreated();
}
@Test
public void shouldBlockRootProjectWithSlashesInTheirName() throws Exception {
// Root project is OK without slash
String parent = name("parentProject");
ProjectInput in = new ProjectInput();
in.permissionsOnly = true;
userRestSession.put("/projects/" + parent, in).assertCreated();
// Creation is rejected when root project name contains slashes
RestResponse r = userRestSession.put("/projects/" + Url.encode("a/parentProject"), in);
r.assertConflict();
assertThat(r.getEntityContent()).contains("Root project names cannot contain slashes");
}
@Test
public void shouldBlockProjectWithParentNotPartOfProjectName() throws Exception {
// Root project is OK without parent part of the name
ProjectInput in = new ProjectInput();
in.permissionsOnly = true;
String parent = name("parentProject");
userRestSession.put("/projects/" + parent, in).assertCreated();
// Creation is rejected when project name does not start with parent
in = new ProjectInput();
in.parent = parent;
RestResponse r = userRestSession.put("/projects/childProject", in);
r.assertConflict();
assertThat(r.getEntityContent()).contains("Project name must start with parent project name");
// Creation is OK when project name starts with parent
userRestSession.put("/projects/" + Url.encode(parent + "/childProject"), in).assertCreated();
// Creation is rejected when project name does not start with nested parent
String nestedParent = parent + "/childProject";
in.parent = nestedParent;
r = userRestSession.put("/projects/grandchild", in);
r.assertConflict();
assertThat(r.getEntityContent()).contains("Project name must start with parent project name");
// Creation is OK when project name starts with nested parent
userRestSession
.put("/projects/" + Url.encode(nestedParent + "/grandchild"), in)
.assertCreated();
}
@Test
public void shouldBlockCreationToNonOwnersOfParentProject() throws Exception {
String ownerGroup = name("groupA");
GroupApi g = gApi.groups().create(ownerGroup);
String parent = name("parentProject");
ProjectInput in = new ProjectInput();
in.permissionsOnly = true;
in.owners = Lists.newArrayList(ownerGroup);
adminRestSession.put("/projects/" + parent, in).assertCreated();
// Creation is rejected when user is not owner of parent
in = new ProjectInput();
in.parent = parent;
RestResponse r = userRestSession.put("/projects/" + Url.encode(parent + "/childProject"), in);
r.assertConflict();
assertThat(r.getEntityContent()).contains("You must be owner of the parent project");
// Creation is OK when user is owner of parent
g.addMembers(user.username());
userRestSession.put("/projects/" + Url.encode(parent + "/childProject"), in).assertCreated();
}
@Test
public void shouldAllowAdminsToByPassAnyRule() throws Exception {
// Root project with a slash
String parent = name("orgA/parentProject");
ProjectInput in = new ProjectInput();
in.permissionsOnly = true;
adminRestSession.put("/projects/" + Url.encode(parent), in).assertCreated();
// Child project without name of parent as prefix
in = new ProjectInput();
in.parent = parent;
adminRestSession.put("/projects/" + Url.encode("orgA/childProject"), in).assertCreated();
}
@Test
public void shouldMakeUserOwnerOnRootProjectCreation() throws Exception {
// normal case, when <project-name>-admins group does not exist
String rootProject = name("rootProject");
ProjectInput in = new ProjectInput();
in.permissionsOnly = true;
userRestSession.put("/projects/" + rootProject, in).assertCreated();
ProjectState projectState = projectCache.get(new Project.NameKey(rootProject));
assertThat(projectState.getOwners().size()).isEqualTo(1);
assertThat(projectState.getOwners())
.contains(
groupCache.get(new AccountGroup.NameKey(rootProject + "-admins")).get().getGroupUUID());
// case when <project-name>-admins group already exists
rootProject = name("rootProject2");
String existingGroupName = rootProject + "-admins";
gApi.groups().create(existingGroupName);
userRestSession.put("/projects/" + rootProject, in).assertCreated();
projectState = projectCache.get(new Project.NameKey(rootProject));
assertThat(projectState.getOwners().size()).isEqualTo(1);
String expectedOwnerGroup =
existingGroupName
+ "-"
+ Hashing.sha256()
.hashString(existingGroupName, Charsets.UTF_8)
.toString()
.substring(0, 7);
assertThat(projectState.getOwners())
.contains(
groupCache.get(new AccountGroup.NameKey(expectedOwnerGroup)).get().getGroupUUID());
}
@Test
public void shouldBlockRootCodeProject() throws Exception {
RestResponse r = userRestSession.put("/projects/" + Url.encode("project1"));
r.assertConflict();
assertThat(r.getEntityContent()).contains("Regular projects are not allowed as root");
}
@Test
public void shouldAllowCreationIfUserIsInDelegatingGroup() throws Exception {
String ownerGroup = name("groupA");
gApi.groups().create(ownerGroup);
String parent = name("parentProject");
ProjectInput in = new ProjectInput();
in.permissionsOnly = true;
in.owners = Lists.newArrayList(ownerGroup);
adminRestSession.put("/projects/" + parent, in).assertCreated();
in = new ProjectInput();
in.parent = parent;
RestResponse r = userRestSession.put("/projects/" + Url.encode(parent + "/childProject"), in);
r.assertConflict();
assertThat(r.getEntityContent()).contains("You must be owner of the parent project");
// the user is in the delegating group
String delegatingGroup = name("groupB");
GroupApi dGroup = gApi.groups().create(delegatingGroup);
dGroup.addMembers(user.username());
// the group is in the project.config
Project.NameKey parentNameKey = new Project.NameKey(parent);
try (ProjectConfigUpdate cfgUpdate = updateProject(parentNameKey)) {
ProjectConfig cfg = cfgUpdate.getConfig();
String gId = gApi.groups().id(delegatingGroup).get().id;
cfg.getPluginConfig(PLUGIN_NAME)
.setGroupReference(
ProjectCreationValidator.DELEGATE_PROJECT_CREATION_TO,
new GroupReference(AccountGroup.UUID.parse(gId), delegatingGroup));
cfgUpdate.save();
}
userRestSession.put("/projects/" + Url.encode(parent + "/childProject"), in).assertCreated();
}
@Test
public void shouldMakeUserOwnerIfNotAlreadyOwnerByInheritance() throws Exception {
String parent = name("parentProject");
ProjectInput in = new ProjectInput();
in.permissionsOnly = true;
adminRestSession.put("/projects/" + parent, in).assertCreated();
String delegatingGroup = name("someGroup");
GroupApi dGroup = gApi.groups().create(delegatingGroup);
dGroup.addMembers(user.username());
Project.NameKey parentNameKey = new Project.NameKey(parent);
try (ProjectConfigUpdate cfgUpdate = updateProject(parentNameKey)) {
ProjectConfig cfg = cfgUpdate.getConfig();
String gId = gApi.groups().id(delegatingGroup).get().id;
cfg.getPluginConfig(PLUGIN_NAME)
.setGroupReference(
ProjectCreationValidator.DELEGATE_PROJECT_CREATION_TO,
new GroupReference(AccountGroup.UUID.parse(gId), delegatingGroup));
cfgUpdate.save();
}
// normal case, when <project-name>-admins group does not exist
in = new ProjectInput();
in.parent = parent;
String childProject = parent + "/childProject";
userRestSession.put("/projects/" + Url.encode(childProject), in).assertCreated();
ProjectState projectState = projectCache.get(new Project.NameKey(childProject));
assertThat(projectState.getOwners().size()).isEqualTo(1);
assertThat(projectState.getOwners())
.contains(
groupCache
.get(new AccountGroup.NameKey(childProject + "-admins"))
.get()
.getGroupUUID());
// case when <project-name>-admins group already exists
String childProject2 = parent + "/childProject2";
String existingGroupName = childProject2 + "-admins";
gApi.groups().create(existingGroupName);
userRestSession.put("/projects/" + Url.encode(childProject2), in).assertCreated();
projectState = projectCache.get(new Project.NameKey(childProject2));
assertThat(projectState.getOwners().size()).isEqualTo(1);
String expectedOwnerGroup =
existingGroupName
+ "-"
+ Hashing.sha256()
.hashString(existingGroupName, Charsets.UTF_8)
.toString()
.substring(0, 7);
assertThat(projectState.getOwners())
.contains(
groupCache.get(new AccountGroup.NameKey(expectedOwnerGroup)).get().getGroupUUID());
}
@Test
public void shouldNotMakeUserOwnerIfNotAlreadyOwnerByInheritanceAndGrantingIsDisabled()
throws Exception {
String parent = name("parentProject");
ProjectInput in = new ProjectInput();
in.permissionsOnly = true;
adminRestSession.put("/projects/" + parent, in).assertCreated();
String delegatingGroup = name("someGroup");
GroupApi dGroup = gApi.groups().create(delegatingGroup);
dGroup.addMembers(user.username());
Project.NameKey parentNameKey = new Project.NameKey(parent);
try (ProjectConfigUpdate cfgUpdate = updateProject(parentNameKey)) {
ProjectConfig cfg = cfgUpdate.getConfig();
String gId = gApi.groups().id(delegatingGroup).get().id;
cfg.getPluginConfig(PLUGIN_NAME)
.setGroupReference(
ProjectCreationValidator.DELEGATE_PROJECT_CREATION_TO,
new GroupReference(AccountGroup.UUID.parse(gId), delegatingGroup));
cfg.getPluginConfig(PLUGIN_NAME)
.setBoolean(ProjectCreationValidator.DISABLE_GRANTING_PROJECT_OWNERSHIP, true);
cfgUpdate.save();
}
in = new ProjectInput();
in.parent = parent;
String childProject = parent + "/childProject";
userRestSession.put("/projects/" + Url.encode(childProject), in).assertCreated();
ProjectState projectState = projectCache.get(new Project.NameKey(childProject));
assertThat(projectState.getOwners().size()).isEqualTo(0);
}
@Test
public void shouldBlockCreationIfGroupRefIsNotUsed() throws Exception {
String ownerGroup = name("groupA");
gApi.groups().create(ownerGroup);
String parent = name("parentProject");
ProjectInput in = new ProjectInput();
in.permissionsOnly = true;
in.owners = Lists.newArrayList(ownerGroup);
adminRestSession.put("/projects/" + parent, in).assertCreated();
in = new ProjectInput();
in.parent = parent;
RestResponse r = userRestSession.put("/projects/" + Url.encode(parent + "/childProject"), in);
r.assertConflict();
assertThat(r.getEntityContent()).contains("You must be owner of the parent project");
// the user is in the delegating group
String delegatingGroup = name("groupB");
GroupApi dGroup = gApi.groups().create(delegatingGroup);
dGroup.addMembers(user.username());
// the group is in the project.config
Project.NameKey parentNameKey = new Project.NameKey(parent);
try (ProjectConfigUpdate cfgUpdate = updateProject(parentNameKey)) {
ProjectConfig cfg = cfgUpdate.getConfig();
cfg.getPluginConfig(PLUGIN_NAME)
.setString(ProjectCreationValidator.DELEGATE_PROJECT_CREATION_TO, delegatingGroup);
cfgUpdate.save();
}
userRestSession.put("/projects/" + Url.encode(parent + "/childProject"), in).assertConflict();
}
@Test
public void shouldAllowCreationIfUserIsInDelegatingGroupNested() throws Exception {
String ownerGroup = name("groupA");
gApi.groups().create(ownerGroup);
String parent = name("parentProject");
ProjectInput in = new ProjectInput();
in.permissionsOnly = true;
in.owners = Lists.newArrayList(ownerGroup);
adminRestSession.put("/projects/" + parent, in).assertCreated();
in = new ProjectInput();
in.parent = parent;
RestResponse r = userRestSession.put("/projects/" + Url.encode(parent + "/childProject"), in);
r.assertConflict();
assertThat(r.getEntityContent()).contains("You must be owner of the parent project");
// the user is in the nested delegating group
String delegatingGroup = name("groupB");
GroupApi dGroup = gApi.groups().create(delegatingGroup);
String nestedGroup = name("groupC");
GroupApi nGroup = gApi.groups().create(nestedGroup);
nGroup.addMembers(user.username());
dGroup.addGroups(nestedGroup);
// the group is in the project.config
Project.NameKey parentNameKey = new Project.NameKey(parent);
try (ProjectConfigUpdate cfgUpdate = updateProject(parentNameKey)) {
ProjectConfig cfg = cfgUpdate.getConfig();
String gId = gApi.groups().id(delegatingGroup).get().id;
cfg.getPluginConfig(PLUGIN_NAME)
.setGroupReference(
ProjectCreationValidator.DELEGATE_PROJECT_CREATION_TO,
new GroupReference(AccountGroup.UUID.parse(gId), delegatingGroup));
cfgUpdate.save();
}
userRestSession.put("/projects/" + Url.encode(parent + "/childProject"), in).assertCreated();
}
@Test
public void shouldBlockCreationIfUserIsNotInDelegatingGroup() throws Exception {
String ownerGroup = name("groupA");
gApi.groups().create(ownerGroup);
String parent = name("parentProject");
ProjectInput in = new ProjectInput();
in.permissionsOnly = true;
in.owners = Lists.newArrayList(ownerGroup);
adminRestSession.put("/projects/" + parent, in).assertCreated();
in = new ProjectInput();
in.parent = parent;
RestResponse r = userRestSession.put("/projects/" + Url.encode(parent + "/childProject"), in);
r.assertConflict();
assertThat(r.getEntityContent()).contains("You must be owner of the parent project");
// the user is in the delegating group
String delegatingGroup = name("groupB");
gApi.groups().create(delegatingGroup);
// The user is not added to the delegated group
// the group is in the project.config
Project.NameKey parentNameKey = new Project.NameKey(parent);
try (ProjectConfigUpdate cfgUpdate = updateProject(parentNameKey)) {
ProjectConfig cfg = cfgUpdate.getConfig();
String gId = gApi.groups().id(delegatingGroup).get().id;
cfg.getPluginConfig(PLUGIN_NAME)
.setGroupReference(
ProjectCreationValidator.DELEGATE_PROJECT_CREATION_TO,
new GroupReference(AccountGroup.UUID.parse(gId), delegatingGroup));
cfgUpdate.save();
}
userRestSession.put("/projects/" + Url.encode(parent + "/childProject"), in).assertConflict();
}
@Test
public void shouldBlockCreationIfDelegatingGroupDoesNotExist() throws Exception {
String ownerGroup = name("groupA");
gApi.groups().create(ownerGroup);
String parent = name("parentProject");
ProjectInput in = new ProjectInput();
in.permissionsOnly = true;
in.owners = Lists.newArrayList(ownerGroup);
adminRestSession.put("/projects/" + parent, in).assertCreated();
in = new ProjectInput();
in.parent = parent;
RestResponse r = userRestSession.put("/projects/" + Url.encode(parent + "/childProject"), in);
r.assertConflict();
assertThat(r.getEntityContent()).contains("You must be owner of the parent project");
// The delegating group is not created
String delegatingGroup = name("groupB");
// the group is in the project.config
Project.NameKey parentNameKey = new Project.NameKey(parent);
try (ProjectConfigUpdate cfgUpdate = updateProject(parentNameKey)) {
ProjectConfig cfg = cfgUpdate.getConfig();
String gId = "fake-gId";
cfg.getPluginConfig(PLUGIN_NAME)
.setGroupReference(
ProjectCreationValidator.DELEGATE_PROJECT_CREATION_TO,
new GroupReference(AccountGroup.UUID.parse(gId), delegatingGroup));
cfgUpdate.save();
}
userRestSession.put("/projects/" + Url.encode(parent + "/childProject"), in).assertConflict();
}
@Test
public void shouldNotBlockCreationIfDelegatingGroupIsRenamed() throws Exception {
String ownerGroup = name("groupA");
gApi.groups().create(ownerGroup);
String parent = name("parentProject");
ProjectInput in = new ProjectInput();
in.permissionsOnly = true;
in.owners = Lists.newArrayList(ownerGroup);
adminRestSession.put("/projects/" + parent, in).assertCreated();
in = new ProjectInput();
in.parent = parent;
RestResponse r = userRestSession.put("/projects/" + Url.encode(parent + "/childProject"), in);
r.assertConflict();
assertThat(r.getEntityContent()).contains("You must be owner of the parent project");
// the user is in the delegating group
String delegatingGroup = name("groupB");
GroupApi dGroup = gApi.groups().create(delegatingGroup);
dGroup.addMembers(user.username());
// the group is in the project.config
Project.NameKey parentNameKey = new Project.NameKey(parent);
try (ProjectConfigUpdate cfgUpdate = updateProject(parentNameKey)) {
ProjectConfig cfg = cfgUpdate.getConfig();
String gId = gApi.groups().id(delegatingGroup).get().id;
cfg.getPluginConfig("project-group-structure")
.setGroupReference(
ProjectCreationValidator.DELEGATE_PROJECT_CREATION_TO,
new GroupReference(AccountGroup.UUID.parse(gId), delegatingGroup));
cfgUpdate.save();
}
String newDelegatingGroup = name("groupC");
gApi.groups().id(delegatingGroup).name(newDelegatingGroup);
userRestSession.put("/projects/" + Url.encode(parent + "/childProject"), in).assertCreated();
}
}