/*
* 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.Date;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import javax.annotation.Nullable;
import org.assertj.core.groups.Tuple;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.sonar.api.utils.System2;
import org.sonar.api.utils.log.LogTester;
import org.sonar.db.CoreDbTester;
import org.sonar.server.platform.db.migration.version.v63.DefaultOrganizationUuidProviderImpl;
import static java.lang.String.format;
import static org.apache.commons.lang.math.RandomUtils.nextLong;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.tuple;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import static org.sonar.api.utils.log.LoggerLevel.WARN;
public class RestoreSonarUsersGroupsTest {
private static final Date PAST = new Date(100_000_000_000L);
private static final Date NOW = new Date(500_000_000_000L);
private static final String DEFAULT_ORGANIZATION_UUID = "def-org";
private static final String SONAR_USERS_NAME = "sonar-users";
private static final String SONAR_USERS_PENDING_DESCRIPTION = "<PENDING>";
private static final String SONAR_USERS_FINAL_DESCRIPTION = "Any new users created will automatically join this group";
@Rule
public ExpectedException expectedException = ExpectedException.none();
@Rule
public LogTester logTester = new LogTester();
@Rule
public CoreDbTester db = CoreDbTester.createForSchema(RestoreSonarUsersGroupsTest.class, "initial.sql");
private System2 system2 = mock(System2.class);
private RestoreSonarUsersGroups underTest = new RestoreSonarUsersGroups(db.database(), system2, new DefaultOrganizationUuidProviderImpl());
@Before
public void setUp() throws Exception {
when(system2.now()).thenReturn(NOW.getTime());
}
@Test
public void insert_sonar_users_group_when_it_does_not_exist() throws SQLException {
setupDefaultOrganization();
setDefaultGroup("default-group");
underTest.execute();
checkSonarUsersHasBeenCreated();
}
@Test
public void display_log_when_creating_sonar_users_group() throws SQLException {
setupDefaultOrganization();
setDefaultGroup("default-group");
underTest.execute();
checkSonarUsersHasBeenCreated();
assertThat(logTester.logs(WARN)).containsOnly("The default group has been updated from 'default-group' to 'sonar-users'. Please verify your permission schema that everything is in order");
}
@Test
public void copy_permission_from_existing_default_group_to_sonar_users_when_it_does_not_exist() throws Exception {
setupDefaultOrganization();
long defaultGroupId = setDefaultGroup("default-group");
insertGroupRole(defaultGroupId, "user", null);
insertGroupRole(defaultGroupId, "admin", 1L);
insertPermissionTemplate(defaultGroupId, "user", 10L);
underTest.execute();
checkSonarUsersHasBeenCreated();
checkUserRolesOnSonarUsers(tuple("user", null, DEFAULT_ORGANIZATION_UUID), tuple("admin", 1L, DEFAULT_ORGANIZATION_UUID));
checkPermissionTemplatesOnSonarUsers(tuple("user", 10L, NOW, NOW));
}
@Test
public void update_sonar_users_group_when_existing_with_incorrect_description() throws Exception {
setupDefaultOrganization();
insertGroup(SONAR_USERS_NAME, "Other description", PAST, PAST);
underTest.execute();
checkSonarUsersHasBeenUpdated();
}
@Test
public void update_sonar_users_group_when_default_group_setting_is_null() throws SQLException {
setupDefaultOrganization();
insertDefaultGroupProperty(null);
insertGroup(SONAR_USERS_NAME, "Other description", PAST, PAST);
underTest.execute();
checkSonarUsersHasBeenUpdated();
}
@Test
public void does_nothing_when_sonar_users_exist_with_right_description() throws SQLException {
setupDefaultOrganization();
insertGroup(SONAR_USERS_NAME, SONAR_USERS_FINAL_DESCRIPTION, PAST, PAST);
underTest.execute();
checkSonarUsersHasNotBeenUpdated();
}
@Test
public void display_log_when_moving_default_group_to_sonar_users_group() throws SQLException {
setupDefaultOrganization();
insertGroup(SONAR_USERS_NAME, "wrong desc", PAST, PAST);
setDefaultGroup("default-group");
underTest.execute();
checkSonarUsersHasBeenUpdated();
assertThat(logTester.logs(WARN)).containsOnly("The default group has been updated from 'default-group' to 'sonar-users'. Please verify your permission schema that everything is in order");
}
@Test
public void does_not_copy_permission_existing_default_group_to_sonar_users_when_it_already_exists() throws Exception {
setupDefaultOrganization();
long defaultGroupId = setDefaultGroup("default-group");
insertGroupRole(defaultGroupId, "user", null);
insertGroupRole(defaultGroupId, "admin", 1L);
insertPermissionTemplate(defaultGroupId, "user", 10L);
// sonar-users has no permission on it
insertGroup(SONAR_USERS_NAME, SONAR_USERS_FINAL_DESCRIPTION, PAST, PAST);
underTest.execute();
checkSonarUsersHasNotBeenUpdated();
// No permission set on sonar-users
checkUserRolesOnSonarUsers();
checkPermissionTemplatesOnSonarUsers();
}
@Test
public void does_not_display_log_when_default_group_is_sonar_users() throws SQLException {
setupDefaultOrganization();
insertGroup(SONAR_USERS_NAME, SONAR_USERS_FINAL_DESCRIPTION, PAST, PAST);
insertDefaultGroupProperty(SONAR_USERS_NAME);
underTest.execute();
assertThat(logTester.logs(WARN)).isEmpty();
}
@Test
public void continue_migration_when_description_is_pending() throws Exception {
setupDefaultOrganization();
// Default group with is permissions
long defaultGroupId = setDefaultGroup("default-group");
insertGroupRole(defaultGroupId, "admin", 1L);
insertGroupRole(defaultGroupId, "user", 2L);
insertGroupRole(defaultGroupId, "codeviewer", null);
insertPermissionTemplate(defaultGroupId, "user", 10L);
insertPermissionTemplate(defaultGroupId, "admin", 11L);
// sonar-users group with partial permissions from default group
long sonarUsersGroupId = insertGroup(SONAR_USERS_NAME, SONAR_USERS_PENDING_DESCRIPTION, PAST, PAST);
insertGroupRole(sonarUsersGroupId, "admin", 1L);
insertPermissionTemplate(sonarUsersGroupId, "user", 10L);
underTest.execute();
checkSonarUsersHasBeenUpdated();
checkUserRolesOnSonarUsers(tuple("admin", 1L, DEFAULT_ORGANIZATION_UUID), tuple("user", 2L, DEFAULT_ORGANIZATION_UUID), tuple("codeviewer", null, DEFAULT_ORGANIZATION_UUID));
checkPermissionTemplatesOnSonarUsers(tuple("user", 10L, PAST, PAST), tuple("admin", 11L, NOW, NOW));
}
@Test
public void does_not_update_other_groups() throws SQLException {
setupDefaultOrganization();
insertGroup("another-group", "another-group", PAST, PAST);
insertGroup(SONAR_USERS_NAME, SONAR_USERS_FINAL_DESCRIPTION, PAST, PAST);
underTest.execute();
checkSonarUsersHasNotBeenUpdated();
assertThat(db.countRowsOfTable("groups")).isEqualTo(2);
}
@Test
public void migration_is_reentrant() throws Exception {
setupDefaultOrganization();
long defaultGroupId = setDefaultGroup("default-group");
insertGroupRole(defaultGroupId, "user", null);
insertGroupRole(defaultGroupId, "admin", 1L);
insertPermissionTemplate(defaultGroupId, "user", 10L);
underTest.execute();
checkSonarUsersHasBeenCreated();
checkUserRolesOnSonarUsers(tuple("user", null, DEFAULT_ORGANIZATION_UUID), tuple("admin", 1L, DEFAULT_ORGANIZATION_UUID));
checkPermissionTemplatesOnSonarUsers(tuple("user", 10L, NOW, NOW));
underTest.execute();
checkSonarUsersHasBeenCreated();
checkUserRolesOnSonarUsers(tuple("user", null, DEFAULT_ORGANIZATION_UUID), tuple("admin", 1L, DEFAULT_ORGANIZATION_UUID));
checkPermissionTemplatesOnSonarUsers(tuple("user", 10L, NOW, NOW));
}
@Test
public void fail_when_no_default_group_in_setting_and_sonar_users_does_not_exist() throws Exception {
setupDefaultOrganization();
expectedException.expect(IllegalStateException.class);
expectedException.expectMessage("Default group setting sonar.defaultGroup is defined to a 'sonar-users' group but it doesn't exist.");
underTest.execute();
}
@Test
public void fail_when_default_group_setting_is_set_to_an_unknown_group() throws SQLException {
setupDefaultOrganization();
insertDefaultGroupProperty("unknown");
insertGroup(SONAR_USERS_NAME, SONAR_USERS_FINAL_DESCRIPTION, PAST, PAST);
expectedException.expect(IllegalStateException.class);
expectedException.expectMessage("Default group setting sonar.defaultGroup is defined to an unknown group.");
underTest.execute();
}
private void checkSonarUsersHasBeenCreated() {
Map<String, Object> group = selectSonarUsersGroup();
checkSonarUsersCommonInfo(group);
assertThat(group.get("CREATED_AT")).isEqualTo(NOW);
assertThat(group.get("UPDATED_AT")).isEqualTo(NOW);
}
private void checkSonarUsersHasBeenUpdated() {
Map<String, Object> group = selectSonarUsersGroup();
checkSonarUsersCommonInfo(group);
assertThat(group.get("CREATED_AT")).isEqualTo(PAST);
assertThat(group.get("UPDATED_AT")).isEqualTo(NOW);
}
private void checkSonarUsersHasNotBeenUpdated() {
Map<String, Object> group = selectSonarUsersGroup();
checkSonarUsersCommonInfo(group);
assertThat(group.get("CREATED_AT")).isEqualTo(PAST);
assertThat(group.get("UPDATED_AT")).isEqualTo(PAST);
}
private void checkSonarUsersCommonInfo(Map<String, Object> group) {
assertThat(group.get("NAME")).isEqualTo("sonar-users");
assertThat(group.get("DESCRIPTION")).isEqualTo("Any new users created will automatically join this group");
assertThat(group.get("ORGANIZATION_UUID")).isEqualTo(DEFAULT_ORGANIZATION_UUID);
}
private void checkUserRolesOnSonarUsers(Tuple... expectedTuples) {
List<Tuple> tuples = db.select("select gr.role, gr.resource_id, gr.organization_uuid from group_roles gr " +
"inner join groups g on g.id=gr.group_id " +
"where g.name='sonar-users'").stream()
.map(map -> new Tuple(map.get("ROLE"), map.get("RESOURCE_ID"), map.get("ORGANIZATION_UUID")))
.collect(Collectors.toList());
assertThat(tuples).containsOnly(expectedTuples);
}
private void checkPermissionTemplatesOnSonarUsers(Tuple... expectedTuples) {
List<Tuple> tuples = db.select("select ptg.permission_reference, ptg.template_id, ptg.created_at, ptg.updated_at from perm_templates_groups ptg " +
"inner join groups g on g.id=ptg.group_id " +
"where g.name='sonar-users'").stream()
.map(map -> new Tuple(map.get("PERMISSION_REFERENCE"), map.get("TEMPLATE_ID"), map.get("CREATED_AT"), map.get("UPDATED_AT")))
.collect(Collectors.toList());
assertThat(tuples).containsOnly(expectedTuples);
}
private Map<String, Object> selectSonarUsersGroup() {
return db.selectFirst("select name, description, organization_uuid, created_at, updated_at from groups where name='sonar-users'");
}
private long insertGroup(String name, String description, Date createdAt, Date updatedAt) {
db.executeInsert(
"GROUPS",
"NAME", name,
"DESCRIPTION", description,
"ORGANIZATION_UUID", DEFAULT_ORGANIZATION_UUID,
"CREATED_AT", createdAt,
"UPDATED_AT", updatedAt);
return (Long) db.selectFirst(format("select id from groups where name='%s'", name)).get("ID");
}
private void insertDefaultGroupProperty(@Nullable String groupName) {
db.executeInsert(
"PROPERTIES",
"PROP_KEY", "sonar.defaultGroup",
"TEXT_VALUE", groupName,
"IS_EMPTY", Boolean.toString(groupName == null),
"CREATED_AT", "1000");
}
private void insertGroupRole(@Nullable Long groupId, String permission, @Nullable Long projectId) {
db.executeInsert(
"GROUP_ROLES",
"ORGANIZATION_UUID", DEFAULT_ORGANIZATION_UUID,
"GROUP_ID", groupId,
"RESOURCE_ID", projectId,
"ROLE", permission);
}
private void insertPermissionTemplate(@Nullable Long groupId, String permission, @Nullable Long templateId) {
db.executeInsert(
"PERM_TEMPLATES_GROUPS",
"GROUP_ID", groupId,
"TEMPLATE_ID", templateId,
"PERMISSION_REFERENCE", permission,
"CREATED_AT", PAST,
"UPDATED_AT", PAST);
}
private void setupDefaultOrganization() {
db.executeInsert("ORGANIZATIONS",
"UUID", DEFAULT_ORGANIZATION_UUID,
"KEE", DEFAULT_ORGANIZATION_UUID, "NAME",
DEFAULT_ORGANIZATION_UUID, "GUARDED", false,
"CREATED_AT", nextLong(),
"UPDATED_AT", nextLong());
db.executeInsert("INTERNAL_PROPERTIES",
"KEE", "organization.default",
"IS_EMPTY", "false",
"TEXT_VALUE", DEFAULT_ORGANIZATION_UUID);
}
private long setDefaultGroup(String name) {
insertDefaultGroupProperty(name);
return insertGroup(name, name, PAST, PAST);
}
}