/*
* SonarQube
* Copyright (C) 2009-2017 SonarSource SA
* mailto:info AT sonarsource DOT com
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
package org.sonar.server.platform.db.migration.version.v64;
import java.sql.SQLException;
import java.util.Map;
import java.util.Random;
import java.util.Set;
import java.util.stream.Stream;
import javax.annotation.Nullable;
import org.junit.Rule;
import org.junit.Test;
import org.sonar.core.util.stream.MoreCollectors;
import org.sonar.db.CoreDbTester;
import static java.lang.String.valueOf;
import static org.assertj.core.api.Assertions.assertThat;
public class MakeComponentsPrivateBasedOnPermissionsTest {
private static final String ROLE_USER = "user";
private static final String ROLE_CODEVIEWER = "codeviewer";
private static final String PROJECT_QUALIFIER = "TRK";
private static final String VIEW_QUALIFIER = "VW";
@Rule
public CoreDbTester db = CoreDbTester.createForSchema(MakeComponentsPrivateBasedOnPermissionsTest.class, "projects_and_group_roles_and_user_roles.sql");
private final Random random = new Random();
private final String randomPublicConditionRole = random.nextBoolean() ? ROLE_CODEVIEWER : ROLE_USER;
private final String randomQualifier = random.nextBoolean() ? PROJECT_QUALIFIER : VIEW_QUALIFIER;
private final String randomRole = "role_" + random.nextInt(12);
private final int randomUserId = random.nextInt(500);
private final int randomGroupId = random.nextInt(500);
private MakeComponentsPrivateBasedOnPermissions underTest = new MakeComponentsPrivateBasedOnPermissions(db.database());
@Test
public void execute_does_nothing_on_empty_tables() throws SQLException {
underTest.execute();
}
@Test
public void execute_makes_project_private_if_group_AnyOne_has_global_permission_USER() throws SQLException {
long pId = insertRootComponent("p1", false);
insertGroupPermission(ROLE_USER, null, null);
insertGroupPermission(randomRole, pId, randomGroupId);
underTest.execute();
assertThat(isPrivate("p1")).isTrue();
}
@Test
public void execute_makes_project_private_if_group_AnyOne_has_global_permission_BROWSE() throws SQLException {
long pId = insertRootComponent("p1", false);
insertGroupPermission(ROLE_CODEVIEWER, null, null);
insertUserPermission(randomRole, pId, randomUserId);
underTest.execute();
assertThat(isPrivate("p1")).isTrue();
}
@Test
public void execute_makes_project_private_if_group_other_than_AnyOne_has_permission_BROWSE_on_other_project() throws SQLException {
long pId1 = insertRootComponent("p1", false);
insertGroupPermission(ROLE_CODEVIEWER, pId1, random.nextInt(30));
underTest.execute();
assertThat(isPrivate("p1")).isTrue();
}
@Test
public void execute_makes_project_private_if_group_other_than_AnyOne_has_permission_USER_on_other_project() throws SQLException {
long pId1 = insertRootComponent("p1", false);
insertGroupPermission(ROLE_USER, pId1, random.nextInt(30));
underTest.execute();
assertThat(isPrivate("p1")).isTrue();
}
@Test
public void execute_keeps_project_public_if_group_AnyOne_has_permission_USER_on_it() throws SQLException {
long pId1 = insertRootComponent("p1", false);
insertGroupPermission(ROLE_USER, pId1, null);
underTest.execute();
assertThat(isPrivate("p1")).isFalse();
}
@Test
public void execute_keeps_project_public_if_group_AnyOne_has_permission_BROWSE_on_it() throws SQLException {
long pId1 = insertRootComponent("p1", false);
insertGroupPermission(ROLE_CODEVIEWER, pId1, null);
underTest.execute();
assertThat(isPrivate("p1")).isFalse();
}
@Test
public void execute_keeps_project_public_if_only_group_AnyOne_has_permission_on_it() throws SQLException {
long pId1 = insertRootComponent("p1", false);
insertGroupPermission(randomRole, pId1, null);
underTest.execute();
assertThat(isPrivate("p1")).isFalse();
}
@Test
public void execute_keeps_project_public_if_project_has_no_permission() throws SQLException {
insertRootComponent("p1", false);
underTest.execute();
assertThat(isPrivate("p1")).isFalse();
}
@Test
public void execute_does_not_change_private_projects_to_public_when_they_actually_should_be_because_they_have_USER_or_BROWSE_on_group_Anyone() throws SQLException {
long p1Id = insertRootComponent("p1", true);
long p2Id = insertRootComponent("p2", true);
long p3Id = insertRootComponent("p3", true);
insertGroupPermission(ROLE_CODEVIEWER, p1Id, null);
insertGroupPermission(ROLE_USER, p1Id, null);
insertGroupPermission(ROLE_CODEVIEWER, p2Id, null);
insertGroupPermission(ROLE_USER, p3Id, null);
underTest.execute();
assertThat(isPrivate("p1")).isTrue();
assertThat(isPrivate("p2")).isTrue();
assertThat(isPrivate("p3")).isTrue();
}
@Test
public void execute_changes_non_root_rows_to_private_based_on_permissions_of_their_root_row() throws SQLException {
// root stays public, children are unchanged
long pId1 = insertRootComponent("root1", false);
insertGroupPermission(randomPublicConditionRole, pId1, null);
insertComponent("u1", "root1", false);
// root becomes privates, children are changed accordingly
long pId2 = insertRootComponent("root2", false);
int someUserId = random.nextInt(50);
insertGroupPermission(randomRole, pId2, someUserId);
insertComponent("u2", "root2", false);
insertComponent("u3", "root2", true);
underTest.execute();
assertThat(isPrivate("root1")).isFalse();
assertThat(isPrivate("u1")).isFalse();
assertThat(isPrivate("root2")).isTrue();
assertThat(isPrivate("u2")).isTrue();
assertThat(isPrivate("u3")).isTrue();
}
@Test
public void execute_does_not_fix_inconsistencies_of_non_root_rows_if_root_stays_public_or_is_already_private() throws SQLException {
// root stays public, children are unchanged
long pId1 = insertRootComponent("root1", false);
insertGroupPermission(randomPublicConditionRole, pId1, null);
insertComponent("u1", "root1", false);
insertComponent("u2", "root1", true); // inconsistent information is not fixed
// root is already private but children are inconsistent => not fixed
insertRootComponent("root2", true);
insertGroupPermission(randomPublicConditionRole, pId1, null);
insertComponent("u3", "root2", false);
insertComponent("u4", "root2", true);
underTest.execute();
assertThat(isPrivate("root1")).isFalse();
assertThat(isPrivate("u1")).isFalse();
assertThat(isPrivate("u2")).isTrue();
assertThat(isPrivate("root2")).isTrue();
assertThat(isPrivate("u3")).isFalse();
assertThat(isPrivate("u4")).isTrue();
}
@Test
public void execute_does_change_non_root_rows_which_root_does_not_exist() throws SQLException {
// non existent root, won't be changed
long pId1 = insertComponent("u1", "non existent root", false);
insertGroupPermission(randomPublicConditionRole, pId1, null);
insertComponent("u2", "non existent root", true);
underTest.execute();
assertThat(isPrivate("u1")).isFalse();
assertThat(isPrivate("u2")).isTrue();
}
@Test
public void execute_deletes_any_permission_to_group_Anyone_for_root_components_which_are_made_private() throws SQLException {
long idRoot1 = insertRootComponent("root1", false);
int someGroupId = random.nextInt(50);
int someUserId = random.nextInt(50);
insertGroupPermission(randomRole, idRoot1, null);
insertGroupPermission(randomRole, idRoot1, someGroupId);
insertUserPermission(randomRole, idRoot1, someUserId);
underTest.execute();
assertThat(isPrivate("root1")).isTrue();
assertThat(permissionsOfGroupAnyone(idRoot1)).isEmpty();
assertThat(permissionsOfGroup(idRoot1, someGroupId)).containsOnly(randomRole, ROLE_USER, ROLE_CODEVIEWER);
assertThat(permissionsOfUser(idRoot1, someUserId)).containsOnly(randomRole, ROLE_USER, ROLE_CODEVIEWER);
}
@Test
public void execute_ensures_any_user_of_with_at_least_one_permission_on_root_component_which_is_made_private_also_has_permissions_USER_and_CODEVIEWER() throws SQLException {
long idRoot = insertRootComponent("root1", false);
String someRole = "role_" + random.nextInt(12);
int user1 = insertUser();
int user2 = insertUser();
insertUserPermission(someRole, idRoot, user1);
underTest.execute();
assertThat(isPrivate("root1")).isTrue();
assertThat(permissionsOfGroupAnyone(idRoot)).isEmpty();
assertThat(permissionsOfUser(idRoot, user1)).containsOnly(someRole, ROLE_USER, ROLE_CODEVIEWER);
assertThat(permissionsOfUser(idRoot, user2)).isEmpty();
}
@Test
public void execute_ensures_any_group_of_with_at_least_one_permission_on_root_component_which_is_made_private_also_has_permissions_USER_and_CODEVIEWER() throws SQLException {
long idRoot = insertRootComponent("root1", false);
String someRole = "role_" + random.nextInt(12);
int group1 = insertGroup();
int group2 = insertGroup();
insertGroupPermission(someRole, idRoot, group1);
underTest.execute();
assertThat(isPrivate("root1")).isTrue();
assertThat(permissionsOfGroup(idRoot, group1)).containsOnly(someRole, ROLE_USER, ROLE_CODEVIEWER);
assertThat(permissionsOfGroup(idRoot, group2)).isEmpty();
}
@Test
public void execute_does_not_delete_permissions_to_group_Anyone_for_root_components_which_are_already_private() throws SQLException {
long idRoot = insertRootComponent("root1", true);
String someRole = "role_" + random.nextInt(12);
int someGroupId = random.nextInt(50);
int someUserId = random.nextInt(50);
insertGroupPermission(someRole, idRoot, null);
insertGroupPermission(someRole, idRoot, someGroupId);
insertGroupPermission(randomPublicConditionRole, idRoot, someGroupId);
insertUserPermission(someRole, idRoot, someUserId);
insertUserPermission(randomPublicConditionRole, idRoot, someUserId);
underTest.execute();
assertThat(isPrivate("root1")).isTrue();
assertThat(permissionsOfGroupAnyone(idRoot)).containsOnly(someRole);
assertThat(permissionsOfGroup(idRoot, someGroupId)).containsOnly(someRole, ROLE_USER, ROLE_CODEVIEWER);
assertThat(permissionsOfUser(idRoot, someUserId)).containsOnly(someRole, ROLE_USER, ROLE_CODEVIEWER);
}
@Test
public void execute_ensures_any_user_of_with_at_least_one_permission_on_root_component_which_is_already_private_also_has_permissions_USER_and_CODEVIEWER() throws SQLException {
long idRoot = insertRootComponent("root1", true);
String someRole = "role_" + random.nextInt(12);
int user1 = insertUser();
int user2 = insertUser();
insertUserPermission(someRole, idRoot, user1);
underTest.execute();
assertThat(isPrivate("root1")).isTrue();
assertThat(permissionsOfGroupAnyone(idRoot)).isEmpty();
assertThat(permissionsOfUser(idRoot, user1)).containsOnly(someRole, ROLE_USER, ROLE_CODEVIEWER);
assertThat(permissionsOfUser(idRoot, user2)).isEmpty();
}
@Test
public void execute_ensures_any_group_of_with_at_least_one_permission_on_root_component_which_is_already_private_also_has_permissions_USER_and_CODEVIEWER() throws SQLException {
long idRoot = insertRootComponent("root1", true);
String someRole = "role_" + random.nextInt(12);
int group1 = insertGroup();
int group2 = insertGroup();
insertGroupPermission(someRole, idRoot, group1);
underTest.execute();
assertThat(isPrivate("root1")).isTrue();
assertThat(permissionsOfGroup(idRoot, group1)).containsOnly(someRole, ROLE_USER, ROLE_CODEVIEWER);
assertThat(permissionsOfGroup(idRoot, group2)).isEmpty();
}
@Test
public void execute_deletes_any_USER_or_BROWSE_permission_of_public_project() throws SQLException {
long idRoot = insertRootComponent("root1", false);
int someGroupId = random.nextInt(55);
int someUserId = random.nextInt(55);
String someRole = "role_" + random.nextInt(12);
Stream.of(ROLE_USER, ROLE_CODEVIEWER, someRole)
.forEach(role -> {
insertGroupPermission(role, idRoot, null);
insertGroupPermission(role, idRoot, someGroupId);
insertUserPermission(role, idRoot, someUserId);
});
assertThat(isPrivate("root1")).isFalse();
assertThat(permissionsOfGroupAnyone(idRoot)).containsOnly(ROLE_USER, ROLE_CODEVIEWER, someRole);
assertThat(permissionsOfGroup(idRoot, someGroupId)).containsOnly(ROLE_USER, ROLE_CODEVIEWER, someRole);
assertThat(permissionsOfUser(idRoot, someUserId)).containsOnly(ROLE_USER, ROLE_CODEVIEWER, someRole);
underTest.execute();
assertThat(isPrivate("root1")).isFalse();
assertThat(permissionsOfGroupAnyone(idRoot)).containsOnly(someRole);
assertThat(permissionsOfGroup(idRoot, someGroupId)).containsOnly(someRole);
assertThat(permissionsOfUser(idRoot, someUserId)).containsOnly(someRole);
}
private long insertRootComponent(String uuid, boolean isPrivate) {
db.executeInsert(
"PROJECTS",
"ORGANIZATION_UUID", "org_" + uuid,
"SCOPE", "PRJ",
"QUALIFIER", randomQualifier,
"UUID", uuid,
"UUID_PATH", "path_" + uuid,
"ROOT_UUID", "root_" + uuid,
"PROJECT_UUID", uuid,
"PRIVATE", valueOf(isPrivate));
return (long) db.selectFirst("select id as \"ID\" from projects where uuid='" + uuid + "'").get("ID");
}
private long insertComponent(String uuid, String projectUuid, boolean isPrivate) {
db.executeInsert(
"PROJECTS",
"ORGANIZATION_UUID", "org_" + uuid,
"UUID", uuid,
"UUID_PATH", "path_" + uuid,
"ROOT_UUID", "root_" + uuid,
"PROJECT_UUID", projectUuid,
"PRIVATE", valueOf(isPrivate));
return (long) db.selectFirst("select id as \"ID\" from projects where uuid='" + uuid + "'").get("ID");
}
private void insertGroupPermission(String role, @Nullable Long resourceId, @Nullable Integer groupId) {
db.executeInsert(
"GROUP_ROLES",
"ORGANIZATION_UUID", "org" + random.nextInt(50),
"GROUP_ID", groupId == null ? null : valueOf(groupId),
"RESOURCE_ID", resourceId == null ? null : valueOf(resourceId),
"ROLE", role);
}
private int groupCount = Math.abs(random.nextInt(22));
private int insertGroup() {
String name = "group" + groupCount++;
db.executeInsert(
"GROUPS",
"ORGANIZATION_UUID", "org" + random.nextInt(12),
"NAME", name);
return ((Long) db.selectFirst("select id as \"ID\" from groups where name='" + name + "'").get("ID")).intValue();
}
private void insertUserPermission(String role, @Nullable Long resourceId, int userId) {
db.executeInsert(
"USER_ROLES",
"ORGANIZATION_UUID", "org_" + random.nextInt(66),
"USER_ID", valueOf(userId),
"RESOURCE_ID", resourceId == null ? null : valueOf(resourceId),
"ROLE", role);
}
private int userCount = Math.abs(random.nextInt(22));
private int insertUser() {
String login = "user" + userCount++;
db.executeInsert(
"USERS",
"LOGIN", login,
"IS_ROOT", String.valueOf(false));
return ((Long) db.selectFirst("select id as \"ID\" from users where login='" + login + "'").get("ID")).intValue();
}
private boolean isPrivate(String uuid) {
Map<String, Object> row = db.selectFirst("select private as \"PRIVATE\" from projects where uuid = '" + uuid + "'");
return (boolean) row.get("PRIVATE");
}
private Set<String> permissionsOfGroupAnyone(long resourceId) {
return db.select("select role from group_roles where group_id is null and resource_id = " + resourceId)
.stream()
.flatMap(map -> map.entrySet().stream())
.map(entry -> (String) entry.getValue())
.collect(MoreCollectors.toSet());
}
private Set<String> permissionsOfGroup(long resourceId, int groupId) {
return db.select("select role from group_roles where group_id = " + groupId + " and resource_id = " + resourceId)
.stream()
.flatMap(map -> map.entrySet().stream())
.map(entry -> (String) entry.getValue())
.collect(MoreCollectors.toSet());
}
private Set<String> permissionsOfUser(long resourceId, int userId) {
return db.select("select role from user_roles where resource_id = " + resourceId + " and user_id = " + userId)
.stream()
.flatMap(map -> map.entrySet().stream())
.map(entry -> (String) entry.getValue())
.collect(MoreCollectors.toSet());
}
}