diff options
author | Julien Lancelot <julien.lancelot@sonarsource.com> | 2017-03-30 10:26:34 +0200 |
---|---|---|
committer | Julien Lancelot <julien.lancelot@sonarsource.com> | 2017-04-13 11:51:55 +0200 |
commit | 411a6b3bd9d1dabb5ff4b2c0f1b933d94185215f (patch) | |
tree | b15dc02580502ba9dd054f6020618e9bb01a42e8 | |
parent | 7d7aef051d26029cfcd09836e3f4a1228a579c6b (diff) | |
download | sonarqube-411a6b3bd9d1dabb5ff4b2c0f1b933d94185215f.tar.gz sonarqube-411a6b3bd9d1dabb5ff4b2c0f1b933d94185215f.zip |
SONAR-9014 Restore sonar-users group
If sonar-users doesn't exist, create it and copy permissions from existing default group on it
If it already exists, only update its description if it's the wrong one
7 files changed, 647 insertions, 2 deletions
diff --git a/server/sonar-db-core/src/main/resources/org/sonar/db/version/rows-h2.sql b/server/sonar-db-core/src/main/resources/org/sonar/db/version/rows-h2.sql index 710248fe604..be656d01a44 100644 --- a/server/sonar-db-core/src/main/resources/org/sonar/db/version/rows-h2.sql +++ b/server/sonar-db-core/src/main/resources/org/sonar/db/version/rows-h2.sql @@ -552,6 +552,7 @@ INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('1615'); INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('1616'); INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('1617'); INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('1618'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('1619'); INSERT INTO USERS(ID, LOGIN, NAME, EMAIL, EXTERNAL_IDENTITY, EXTERNAL_IDENTITY_PROVIDER, USER_LOCAL, CRYPTED_PASSWORD, SALT, IS_ROOT, CREATED_AT, UPDATED_AT) VALUES (1, 'admin', 'Administrator', '', 'admin', 'sonarqube', true, 'a373a0e667abb2604c1fd571eb4ad47fe8cc0878', '48bc4b0d93179b5103fd3885ea9119498e9d161b', false, '1418215735482', '1418215735482'); ALTER TABLE USERS ALTER COLUMN ID RESTART WITH 2; diff --git a/server/sonar-db-core/src/test/java/org/sonar/db/AbstractDbTester.java b/server/sonar-db-core/src/test/java/org/sonar/db/AbstractDbTester.java index c5a7a4c6b31..297b4bc4ab6 100644 --- a/server/sonar-db-core/src/test/java/org/sonar/db/AbstractDbTester.java +++ b/server/sonar-db-core/src/test/java/org/sonar/db/AbstractDbTester.java @@ -33,9 +33,11 @@ import java.sql.ResultSet; import java.sql.ResultSetMetaData; import java.sql.SQLException; import java.sql.Statement; +import java.sql.Timestamp; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; +import java.util.Date; import java.util.HashSet; import java.util.Iterator; import java.util.List; @@ -238,6 +240,8 @@ public class AbstractDbTester<T extends CoreTestDb> extends ExternalResource { } else if (value instanceof Integer) { // To be consistent, all INTEGER types are mapped as Long value = ((Integer) value).longValue(); + } else if (value instanceof Timestamp) { + value = new Date(((Timestamp) value).getTime()); } columns.put(metaData.getColumnLabel(i), value); } diff --git a/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v64/DbVersion64.java b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v64/DbVersion64.java index 39301e3da37..0147618043a 100644 --- a/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v64/DbVersion64.java +++ b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v64/DbVersion64.java @@ -46,6 +46,7 @@ public class DbVersion64 implements DbVersion { .add(1616, "Populate table RULES_METADATA", PopulateRulesMetadata.class) .add(1617, "Drop metadata columns from RULES", DropMetadataColumnsFromRules.class) // ensure the index is made unique even on existing 6.4-SNAPSHOT instances (such as next or the developer machines) - .add(1618, "Make index on ORGANIZATIONS.KEE unique", org.sonar.server.platform.db.migration.version.v63.MakeIndexOnOrganizationsKeeUnique.class); + .add(1618, "Make index on ORGANIZATIONS.KEE unique", org.sonar.server.platform.db.migration.version.v63.MakeIndexOnOrganizationsKeeUnique.class) + .add(1619, "Restore 'sonar-users' group", RestoreSonarUsersGroups.class); } } diff --git a/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v64/RestoreSonarUsersGroups.java b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v64/RestoreSonarUsersGroups.java new file mode 100644 index 00000000000..9ac20c19274 --- /dev/null +++ b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v64/RestoreSonarUsersGroups.java @@ -0,0 +1,211 @@ +/* + * 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 javax.annotation.CheckForNull; +import org.sonar.api.utils.System2; +import org.sonar.api.utils.log.Logger; +import org.sonar.api.utils.log.Loggers; +import org.sonar.db.Database; +import org.sonar.server.platform.db.migration.step.DataChange; +import org.sonar.server.platform.db.migration.step.MassUpdate; +import org.sonar.server.platform.db.migration.step.Select; +import org.sonar.server.platform.db.migration.version.v63.DefaultOrganizationUuid; + +import static com.google.common.base.Preconditions.checkState; +import static java.lang.String.format; +import static java.util.Objects.requireNonNull; + +/** + * If sonar-users doesn't exist, create it and copy permissions from existing default group + * If it already exists, only update description if it's the wrong one + */ +public class RestoreSonarUsersGroups extends DataChange { + + private static final Logger LOG = Loggers.get(RestoreSonarUsersGroups.class); + + 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"; + private static final String DEFAULT_GROUP_SETTING = "sonar.defaultGroup"; + + private final System2 system2; + private final DefaultOrganizationUuid defaultOrganizationUuid; + + public RestoreSonarUsersGroups(Database db, System2 system2, DefaultOrganizationUuid defaultOrganizationUuid) { + super(db); + this.system2 = system2; + this.defaultOrganizationUuid = defaultOrganizationUuid; + } + + @Override + protected void execute(Context context) throws SQLException { + Date now = new Date(system2.now()); + Group sonarUsersGroup = selectSonarUsersGroup(context); + Group defaultGroup = searchDefaultGroup(context); + if (sonarUsersGroup == null) { + createSonarUsersGroupAndCopyPermissionsFromDefaultGroup(context, defaultGroup, now); + displayWarnLog(defaultGroup); + } else { + if (SONAR_USERS_PENDING_DESCRIPTION.equals(sonarUsersGroup.getDescription())) { + copyAllPermissionsFromDefaultGroupToSonarUsers(context, sonarUsersGroup, defaultGroup, now); + } else if (!SONAR_USERS_FINAL_DESCRIPTION.equals(sonarUsersGroup.getDescription())) { + updateSonarUsersGroupDescription(context, SONAR_USERS_FINAL_DESCRIPTION, sonarUsersGroup.getId(), now); + } + if (sonarUsersGroup.getId() != defaultGroup.getId()) { + displayWarnLog(defaultGroup); + } + } + } + + private void createSonarUsersGroupAndCopyPermissionsFromDefaultGroup(Context context, Group defaultGroup, Date now) throws SQLException { + insertSonarUsersGroupWithPendingDescription(context, defaultOrganizationUuid.get(context), now); + Group sonarUsersGroupId = requireNonNull(selectSonarUsersGroup(context), format("Creation of '%s' group has failed", SONAR_USERS_NAME)); + copyAllPermissionsFromDefaultGroupToSonarUsers(context, sonarUsersGroupId, defaultGroup, now); + } + + private static void copyAllPermissionsFromDefaultGroupToSonarUsers(Context context, Group sonarUsersGroup, Group defaultGroupId, Date now) throws SQLException { + copyGlobalAndProjectPermissionsFromDefaultGroupToSonarUsers(context, defaultGroupId, sonarUsersGroup); + copyPermissionTemplatesFromDefaultGroupToSonarUsers(context, defaultGroupId, sonarUsersGroup, now); + updateSonarUsersGroupDescription(context, SONAR_USERS_FINAL_DESCRIPTION, sonarUsersGroup.getId(), now); + } + + private static void copyGlobalAndProjectPermissionsFromDefaultGroupToSonarUsers(Context context, Group defaultGroupId, Group sonarUsersGroupId) throws SQLException { + MassUpdate massUpdate = context.prepareMassUpdate().rowPluralName("global and project permissions"); + massUpdate.select("SELECT gr.resource_id, gr.role, gr.organization_uuid FROM group_roles gr " + + "WHERE gr.group_id=? AND NOT EXISTS " + + "(SELECT 1 FROM group_roles gr2 WHERE gr2.resource_id=gr.resource_id AND gr2.role=gr.role AND gr2.organization_uuid=gr.organization_uuid AND gr2.group_id=?)") + .setLong(1, defaultGroupId.getId()) + .setLong(2, sonarUsersGroupId.getId()); + massUpdate.update("INSERT INTO group_roles (group_id, resource_id, role, organization_uuid) values (?, ?, ?, ?)"); + massUpdate.execute((row, update) -> { + update.setLong(1, sonarUsersGroupId.getId()); + update.setLong(2, row.getNullableLong(1)); + update.setString(3, row.getString(2)); + update.setString(4, row.getString(3)); + return true; + }); + } + + private static void copyPermissionTemplatesFromDefaultGroupToSonarUsers(Context context, Group defaultGroupId, Group sonarUsersGroupId, Date now) throws SQLException { + MassUpdate massUpdate = context.prepareMassUpdate().rowPluralName("permission templates"); + massUpdate.select("SELECT ptg.template_id, ptg.permission_reference FROM perm_templates_groups ptg " + + "WHERE ptg.group_id=? AND NOT EXISTS " + + "(SELECT 1 FROM perm_templates_groups ptg2 WHERE ptg2.template_id=ptg.template_id AND ptg2.permission_reference=ptg.permission_reference AND ptg2.group_id=?)") + .setLong(1, defaultGroupId.getId()) + .setLong(2, sonarUsersGroupId.getId()); + massUpdate.update("INSERT INTO perm_templates_groups (group_id, template_id, permission_reference, created_at, updated_at) values (?, ?, ?, ?, ?)"); + massUpdate.execute((row, update) -> { + update + .setLong(1, sonarUsersGroupId.getId()) + .setLong(2, row.getLong(1)) + .setString(3, row.getString(2)) + .setDate(4, now) + .setDate(5, now); + return true; + }); + } + + private static void insertSonarUsersGroupWithPendingDescription(Context context, String organizationUuid, Date now) throws SQLException { + context.prepareUpsert("INSERT into groups (name, description, organization_uuid, created_at, updated_at) values (?, ?, ?, ?, ?)") + .setString(1, SONAR_USERS_NAME) + .setString(2, SONAR_USERS_PENDING_DESCRIPTION) + .setString(3, organizationUuid) + .setDate(4, now) + .setDate(5, now) + .execute() + .commit(); + } + + private static void updateSonarUsersGroupDescription(Context context, String description, long sonarUsersGroupId, Date now) throws SQLException { + context.prepareUpsert("UPDATE groups SET description=?, updated_at=? WHERE id=?") + .setString(1, description) + .setDate(2, now) + .setLong(3, sonarUsersGroupId) + .execute() + .commit(); + } + + private static Group searchDefaultGroup(Context context) throws SQLException { + String defaultGroupName = selectDefaultGroupNameFromProperties(context); + if (defaultGroupName == null) { + Group sonarUsersGroup = selectSonarUsersGroup(context); + checkState(sonarUsersGroup != null, "Default group setting %s is defined to a 'sonar-users' group but it doesn't exist.", DEFAULT_GROUP_SETTING); + return sonarUsersGroup; + } + Group defaultGroup = selectGroupByName(context, defaultGroupName); + checkState(defaultGroup != null, "Default group setting %s is defined to an unknown group.", DEFAULT_GROUP_SETTING); + return defaultGroup; + } + + @CheckForNull + private static Group selectSonarUsersGroup(Context context) throws SQLException { + return selectGroupByName(context, SONAR_USERS_NAME); + } + + @CheckForNull + private static String selectDefaultGroupNameFromProperties(Context context) throws SQLException { + return context.prepareSelect("SELECT is_empty,text_value FROM properties WHERE prop_key=? AND is_empty=?") + .setString(1, DEFAULT_GROUP_SETTING) + .setBoolean(2, false) + .get(row -> { + boolean isEmpty = row.getBoolean(1); + return isEmpty ? null : row.getString(2); + }); + } + + @CheckForNull + private static Group selectGroupByName(Context context, String name) throws SQLException { + return context.prepareSelect("SELECT id, name, description FROM groups WHERE name=?") + .setString(1, name) + .get(Group::new); + } + + private static void displayWarnLog(Group defaultGroup) { + LOG.warn("The default group has been updated from '{}' to '{}'. Please verify your permission schema that everything is in order", + defaultGroup.getName(), SONAR_USERS_NAME); + } + + private static class Group { + private final long id; + private final String name; + private final String description; + + Group(Select.Row row) throws SQLException { + this.id = row.getLong(1); + this.name = row.getString(2); + this.description = row.getString(3); + } + + long getId() { + return id; + } + + String getName() { + return name; + } + + public String getDescription() { + return description; + } + } +} diff --git a/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v64/DbVersion64Test.java b/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v64/DbVersion64Test.java index f30d2ab0e8b..a0c631d5f09 100644 --- a/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v64/DbVersion64Test.java +++ b/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v64/DbVersion64Test.java @@ -35,7 +35,7 @@ public class DbVersion64Test { @Test public void verify_migration_count() { - verifyMigrationCount(underTest, 19); + verifyMigrationCount(underTest, 20); } } diff --git a/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v64/RestoreSonarUsersGroupsTest.java b/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v64/RestoreSonarUsersGroupsTest.java new file mode 100644 index 00000000000..1c8e76a0663 --- /dev/null +++ b/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v64/RestoreSonarUsersGroupsTest.java @@ -0,0 +1,363 @@ +/* + * 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.DefaultOrganizationUuidImpl; + +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 DefaultOrganizationUuidImpl()); + + @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); + } + +} diff --git a/server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v64/RestoreSonarUsersGroupsTest/initial.sql b/server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v64/RestoreSonarUsersGroupsTest/initial.sql new file mode 100644 index 00000000000..54244e9d6ed --- /dev/null +++ b/server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v64/RestoreSonarUsersGroupsTest/initial.sql @@ -0,0 +1,65 @@ +CREATE TABLE "GROUPS" ( + "ID" INTEGER NOT NULL GENERATED BY DEFAULT AS IDENTITY (START WITH 1, INCREMENT BY 1), + "ORGANIZATION_UUID" VARCHAR(40) NOT NULL, + "NAME" VARCHAR(500), + "DESCRIPTION" VARCHAR(200), + "CREATED_AT" TIMESTAMP, + "UPDATED_AT" TIMESTAMP +); + +CREATE TABLE "GROUP_ROLES" ( + "ID" INTEGER NOT NULL GENERATED BY DEFAULT AS IDENTITY (START WITH 1, INCREMENT BY 1), + "ORGANIZATION_UUID" VARCHAR(40) NOT NULL, + "GROUP_ID" INTEGER, + "RESOURCE_ID" INTEGER, + "ROLE" VARCHAR(64) NOT NULL +); +CREATE INDEX "GROUP_ROLES_RESOURCE" ON "GROUP_ROLES" ("RESOURCE_ID"); +CREATE UNIQUE INDEX "UNIQ_GROUP_ROLES" ON "GROUP_ROLES" ("ORGANIZATION_UUID", "GROUP_ID", "RESOURCE_ID", "ROLE"); + +CREATE TABLE "PERM_TEMPLATES_GROUPS" ( + "ID" INTEGER NOT NULL GENERATED BY DEFAULT AS IDENTITY (START WITH 1, INCREMENT BY 1), + "GROUP_ID" INTEGER, + "TEMPLATE_ID" INTEGER NOT NULL, + "PERMISSION_REFERENCE" VARCHAR(64) NOT NULL, + "CREATED_AT" TIMESTAMP, + "UPDATED_AT" TIMESTAMP +); + +CREATE TABLE "PROPERTIES" ( + "ID" INTEGER NOT NULL GENERATED BY DEFAULT AS IDENTITY (START WITH 1, INCREMENT BY 1), + "PROP_KEY" VARCHAR(512) NOT NULL, + "RESOURCE_ID" INTEGER, + "USER_ID" INTEGER, + "IS_EMPTY" BOOLEAN NOT NULL, + "TEXT_VALUE" VARCHAR(4000), + "CLOB_VALUE" CLOB(2147483647), + "CREATED_AT" BIGINT +); +CREATE INDEX "PROPERTIES_KEY" ON "PROPERTIES" ("PROP_KEY"); + +CREATE TABLE "ORGANIZATIONS" ( + "UUID" VARCHAR(40) NOT NULL PRIMARY KEY, + "KEE" VARCHAR(32) NOT NULL, + "NAME" VARCHAR(64) NOT NULL, + "DESCRIPTION" VARCHAR(256), + "URL" VARCHAR(256), + "AVATAR_URL" VARCHAR(256), + "GUARDED" BOOLEAN NOT NULL, + "USER_ID" INTEGER, + "DEFAULT_PERM_TEMPLATE_PROJECT" VARCHAR(40), + "DEFAULT_PERM_TEMPLATE_VIEW" VARCHAR(40), + "CREATED_AT" BIGINT NOT NULL, + "UPDATED_AT" BIGINT NOT NULL +); +CREATE UNIQUE INDEX "PK_ORGANIZATIONS" ON "ORGANIZATIONS" ("UUID"); +CREATE UNIQUE INDEX "ORGANIZATION_KEY" ON "ORGANIZATIONS" ("KEE"); + +CREATE TABLE "INTERNAL_PROPERTIES" ( + "KEE" VARCHAR(50) NOT NULL PRIMARY KEY, + "IS_EMPTY" BOOLEAN NOT NULL, + "TEXT_VALUE" VARCHAR(4000), + "CLOB_VALUE" CLOB, + "CREATED_AT" BIGINT +); +CREATE UNIQUE INDEX "UNIQ_INTERNAL_PROPERTIES" ON "INTERNAL_PROPERTIES" ("KEE"); |