aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJulien Lancelot <julien.lancelot@sonarsource.com>2017-03-30 10:26:34 +0200
committerJulien Lancelot <julien.lancelot@sonarsource.com>2017-04-13 11:51:55 +0200
commit411a6b3bd9d1dabb5ff4b2c0f1b933d94185215f (patch)
treeb15dc02580502ba9dd054f6020618e9bb01a42e8
parent7d7aef051d26029cfcd09836e3f4a1228a579c6b (diff)
downloadsonarqube-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
-rw-r--r--server/sonar-db-core/src/main/resources/org/sonar/db/version/rows-h2.sql1
-rw-r--r--server/sonar-db-core/src/test/java/org/sonar/db/AbstractDbTester.java4
-rw-r--r--server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v64/DbVersion64.java3
-rw-r--r--server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v64/RestoreSonarUsersGroups.java211
-rw-r--r--server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v64/DbVersion64Test.java2
-rw-r--r--server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v64/RestoreSonarUsersGroupsTest.java363
-rw-r--r--server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v64/RestoreSonarUsersGroupsTest/initial.sql65
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");