.add(2109, "Add EXTERNAL_ID on table users", AddExternalIdToUsers.class)
.add(2110, "Rename EXTERNAL_IDENTITY to EXTERNAL_LOGIN on table users", RenameExternalIdentityToExternalLoginOnUsers.class)
.add(2111, "Update null values from external columns and login of users", UpdateNullValuesFromExternalColumnsAndLoginOfUsers.class)
- .add(2112, "Populate EXTERNAL_ID on table users", PopulateExternalIdOnUsers.class)
+ .add(2112, "Fix duplication in EXTERNAL_ID and EXTERNAL_LOGIN on table users", FixDuplicationInExternalLoginOnUsers.class)
.add(2113, "Makes same columns of table users not nullable", MakeSomeColumnsOfUsersNotNullable.class)
.add(2114, "Add unique indexes on table users", AddUniqueIndexesOnUsers.class)
.add(2115, "Add ORGANIZATION_UUID on table users", AddOrganizationUuidToUsers.class)
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2019 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.v72;
+
+import java.sql.SQLException;
+import org.sonar.api.utils.System2;
+import org.sonar.db.Database;
+import org.sonar.server.platform.db.migration.step.DataChange;
+import org.sonar.server.platform.db.migration.step.MassUpdate;
+
+public class FixDuplicationInExternalLoginOnUsers extends DataChange {
+
+ private final System2 system2;
+
+ public FixDuplicationInExternalLoginOnUsers(Database db, System2 system2) {
+ super(db);
+ this.system2 = system2;
+ }
+
+ @Override
+ public void execute(Context context) throws SQLException {
+ MassUpdate massUpdate = context.prepareMassUpdate().rowPluralName("users having duplicated values in EXTERNAL_LOGIN");
+ massUpdate.select("SELECT u1.id, u1.login FROM users u1 " +
+ "WHERE EXISTS (SELECT 1 FROM users u2 WHERE u2.external_login = u1.external_login AND u2.id != u1.id)");
+ // EXTERNAL_ID is also updated because the column was added previously and content was copied from EXTERNAL_LOGIN
+ massUpdate.update("UPDATE users SET external_login=?, external_id=?, updated_at=? WHERE id=?");
+
+ long now = system2.now();
+ massUpdate.execute((row, update) -> {
+ long id = row.getLong(1);
+ String login = row.getString(2);
+ update.setString(1, login);
+ update.setString(2, login);
+ update.setLong(3, now);
+ update.setLong(4, id);
+ return true;
+ });
+ }
+}
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2019 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.v72;
-
-import java.sql.SQLException;
-import org.sonar.api.utils.System2;
-import org.sonar.db.Database;
-import org.sonar.server.platform.db.migration.step.DataChange;
-import org.sonar.server.platform.db.migration.step.MassUpdate;
-
-public class PopulateExternalIdOnUsers extends DataChange {
-
- private final System2 system2;
-
- public PopulateExternalIdOnUsers(Database db, System2 system2) {
- super(db);
- this.system2 = system2;
- }
-
- @Override
- public void execute(Context context) throws SQLException {
- MassUpdate massUpdate = context.prepareMassUpdate().rowPluralName("users");
- massUpdate.select("SELECT id, external_login FROM users WHERE external_id IS NULL");
- massUpdate.update("UPDATE users SET external_id=?, updated_at=? WHERE id=?");
-
- long now = system2.now();
- massUpdate.execute((row, update) -> {
- long id = row.getLong(1);
- String externalLogin = row.getString(2);
- update.setString(1, externalLogin);
- update.setLong(2, now);
- update.setLong(3, id);
- return true;
- });
- }
-}
/**
* The goal of this migration is to sanitize disabled USERS, regarding new way of authentication users.
- * Indeed, authentication will search for user but LOGIN but also buy using EXTERNAL_ID and EXTERNAL_PROVIDER.
+ * Indeed, authentication will search for user by LOGIN but also by using EXTERNAL_ID and EXTERNAL_PROVIDER.
*
* As a consequence, these columns must be set as NOT NULL in order to add a UNIQUE index on them.
*
@Override
public void execute(Context context) throws SQLException {
MassUpdate massUpdate = context.prepareMassUpdate().rowPluralName("users");
- massUpdate.select("SELECT id, login FROM users WHERE login IS NULL OR external_login IS NULL OR external_identity_provider IS NULL");
- massUpdate.update("UPDATE users SET login=?, external_login=?, external_identity_provider=?, updated_at=? WHERE id=?");
+ massUpdate.select("SELECT id, login, external_login, external_identity_provider FROM users " +
+ "WHERE login IS NULL OR external_login IS NULL OR external_id IS NULL OR external_identity_provider IS NULL");
+ massUpdate.update("UPDATE users SET login=?, external_login=?, external_id=?, external_identity_provider=?, updated_at=? WHERE id=?");
long now = system2.now();
massUpdate.execute((row, update) -> {
LOG.warn("No login has been found for user id '{}'. A UUID has been generated to not have null value.", id);
login = uuidFactory.create();
}
+ String externalLogin = row.getNullableString(3);
+ externalLogin = externalLogin == null ? login : externalLogin;
+
+ String externalIdentityProvider = row.getNullableString(4);
+
update.setString(1, login);
- update.setString(2, login);
- update.setString(3, "sonarqube");
- update.setLong(4, now);
- update.setLong(5, id);
+ update.setString(2, externalLogin);
+ update.setString(3, externalLogin);
+ update.setString(4, externalIdentityProvider == null ? "sonarqube" : externalIdentityProvider);
+ update.setLong(5, now);
+ update.setLong(6, id);
return true;
});
}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2019 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.v72;
+
+import java.sql.SQLException;
+import java.util.stream.Collectors;
+import org.assertj.core.groups.Tuple;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.sonar.api.utils.System2;
+import org.sonar.api.utils.internal.TestSystem2;
+import org.sonar.db.CoreDbTester;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.tuple;
+
+public class FixDuplicationInExternalLoginOnUsersTest {
+
+ private static final long PAST = 5_000_000_000L;
+ private static final long NOW = 10_000_000_000L;
+
+ @Rule
+ public ExpectedException expectedException = ExpectedException.none();
+
+ @Rule
+ public CoreDbTester db = CoreDbTester.createForSchema(FixDuplicationInExternalLoginOnUsersTest.class, "users.sql");
+
+ private System2 system2 = new TestSystem2().setNow(NOW);
+
+ private FixDuplicationInExternalLoginOnUsers underTest = new FixDuplicationInExternalLoginOnUsers(db.database(), system2);
+
+ @Test
+ public void fix_duplication() throws SQLException {
+ insertUser("USER_1", "EXT_LOGIN_1", "EXT_LOGIN_1");
+ insertUser("USER_2", "EXT_LOGIN_1", "EXT_LOGIN_1");
+ insertUser("USER_3", "EXT_LOGIN_2", "EXT_LOGIN_2");
+ insertUser("USER_4", "EXT_LOGIN_2", "EXT_LOGIN_2");
+ insertUser("USER_5", "user5", "user5");
+
+ underTest.execute();
+
+ assertUsers(
+ tuple("USER_1", "USER_1", "USER_1", NOW),
+ tuple("USER_2", "USER_2", "USER_2", NOW),
+ tuple("USER_3", "USER_3", "USER_3", NOW),
+ tuple("USER_4", "USER_4", "USER_4", NOW),
+ tuple("USER_5", "user5", "user5", PAST));
+ }
+
+ @Test
+ public void migration_is_reentrant() throws SQLException {
+ insertUser("USER_1", "EXT_LOGIN", "EXT_LOGIN");
+ insertUser("USER_2", "EXT_LOGIN", "EXT_LOGIN");
+
+ underTest.execute();
+ underTest.execute();
+
+ assertUsers(
+ tuple("USER_1", "USER_1", "USER_1", NOW),
+ tuple("USER_2", "USER_2", "USER_2", NOW));
+ }
+
+ private void assertUsers(Tuple... expectedTuples) {
+ assertThat(db.select("SELECT LOGIN, EXTERNAL_LOGIN, EXTERNAL_ID, UPDATED_AT FROM USERS")
+ .stream()
+ .map(map -> new Tuple(map.get("LOGIN"), map.get("EXTERNAL_LOGIN"), map.get("EXTERNAL_ID"), map.get("UPDATED_AT")))
+ .collect(Collectors.toList()))
+ .containsExactlyInAnyOrder(expectedTuples);
+ }
+
+ private void insertUser(String login, String externalLogin, String externalId) {
+ db.executeInsert("USERS",
+ "LOGIN", login,
+ "EXTERNAL_ID", externalId,
+ "EXTERNAL_LOGIN", externalLogin,
+ "CREATED_AT", PAST,
+ "UPDATED_AT", PAST,
+ "IS_ROOT", false,
+ "ONBOARDED", false);
+ }
+}
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2019 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.v72;
-
-import java.sql.SQLException;
-import java.util.stream.Collectors;
-import javax.annotation.Nullable;
-import org.assertj.core.groups.Tuple;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.rules.ExpectedException;
-import org.sonar.api.utils.System2;
-import org.sonar.api.utils.internal.TestSystem2;
-import org.sonar.db.CoreDbTester;
-
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.assertj.core.api.Assertions.tuple;
-
-public class PopulateExternalIdOnUsersTest {
-
- private static final long PAST = 5_000_000_000L;
- private static final long NOW = 10_000_000_000L;
-
- @Rule
- public ExpectedException expectedException = ExpectedException.none();
-
- @Rule
- public CoreDbTester db = CoreDbTester.createForSchema(PopulateExternalIdOnUsersTest.class, "users.sql");
-
- private System2 system2 = new TestSystem2().setNow(NOW);
-
- private PopulateExternalIdOnUsers underTest = new PopulateExternalIdOnUsers(db.database(), system2);
-
- @Test
- public void update_users() throws SQLException {
- insertUser("USER_1", "user1", null);
- insertUser("USER_2", "user2", "1234");
-
- underTest.execute();
-
- assertUsers(
- tuple("USER_1", "user1", NOW),
- tuple("USER_2", "1234", PAST));
- }
-
- @Test
- public void migration_is_reentrant() throws SQLException {
- insertUser("USER_1", "user1", null);
- insertUser("USER_2", "user2", "1234");
-
- underTest.execute();
- underTest.execute();
-
- assertUsers(
- tuple("USER_1", "user1", NOW),
- tuple("USER_2", "1234", PAST));
- }
-
- private void assertUsers(Tuple... expectedTuples) {
- assertThat(db.select("SELECT LOGIN, EXTERNAL_ID, UPDATED_AT FROM USERS")
- .stream()
- .map(map -> new Tuple(map.get("LOGIN"), map.get("EXTERNAL_ID"), map.get("UPDATED_AT")))
- .collect(Collectors.toList()))
- .containsExactlyInAnyOrder(expectedTuples);
- }
-
- private void insertUser(String login, String externalLogin, @Nullable String externalId) {
- db.executeInsert("USERS",
- "LOGIN", login,
- "EXTERNAL_ID", externalId,
- "EXTERNAL_LOGIN", externalLogin,
- "CREATED_AT", PAST,
- "UPDATED_AT", PAST,
- "IS_ROOT", false,
- "ONBOARDED", false);
- }
-}
@Test
public void update_users() throws SQLException {
- insertUser("USER_1", "user1", "github");
- insertUser("USER_2", null, null);
- insertUser("USER_3", "user", null);
- insertUser("USER_4", null, "github");
- insertUser(null, "user", "bitbucket");
- insertUser(null, null, null);
+ insertUser("USER_1", "user1", "user1", "github");
+ insertUser("USER_2", null, null, null);
+ insertUser("USER_3", "user", null, null);
+ insertUser("USER_4", null, "user", null);
+ insertUser("USER_5", null, null, "github");
+ insertUser(null, "user", "user", "bitbucket");
+ insertUser(null, null, null, null);
underTest.execute();
assertUsers(
- tuple("USER_1", "user1", "github", PAST),
- tuple("USER_2", "USER_2", "sonarqube", NOW),
- tuple("USER_3", "USER_3", "sonarqube", NOW),
- tuple("USER_4", "USER_4", "sonarqube", NOW),
- tuple("1", "1", "sonarqube", NOW),
- tuple("2", "2", "sonarqube", NOW));
+ tuple("USER_1", "user1", "user1", "github", PAST),
+ tuple("USER_2", "USER_2", "USER_2", "sonarqube", NOW),
+ tuple("USER_3", "user", "user", "sonarqube", NOW),
+ tuple("USER_4", "USER_4", "USER_4", "sonarqube", NOW),
+ tuple("USER_5", "USER_5", "USER_5", "github", NOW),
+ tuple("1", "user", "user", "bitbucket", NOW),
+ tuple("2", "2", "2", "sonarqube", NOW));
}
@Test
public void log_warning_when_login_is_null() throws SQLException {
- insertUser(null, "user", "bitbucket");
+ insertUser(null, "user", "user", "bitbucket");
long id = (long) db.selectFirst("SELECT ID FROM USERS").get("ID");
underTest.execute();
@Test
public void is_reentrant() throws SQLException {
- insertUser("USER_1", null, null);
+ insertUser("USER_1", null, null, null);
underTest.execute();
underTest.execute();
- assertUsers(tuple("USER_1", "USER_1", "sonarqube", NOW));
+ assertUsers(tuple("USER_1", "USER_1", "USER_1", "sonarqube", NOW));
}
private void assertUsers(Tuple... expectedTuples) {
- assertThat(db.select("SELECT LOGIN, EXTERNAL_LOGIN, EXTERNAL_IDENTITY_PROVIDER, UPDATED_AT FROM USERS")
+ assertThat(db.select("SELECT LOGIN, EXTERNAL_LOGIN, EXTERNAL_ID, EXTERNAL_IDENTITY_PROVIDER, UPDATED_AT FROM USERS")
.stream()
- .map(map -> new Tuple(map.get("LOGIN"), map.get("EXTERNAL_LOGIN"), map.get("EXTERNAL_IDENTITY_PROVIDER"), map.get("UPDATED_AT")))
+ .map(map -> new Tuple(map.get("LOGIN"), map.get("EXTERNAL_LOGIN"), map.get("EXTERNAL_ID"), map.get("EXTERNAL_IDENTITY_PROVIDER"), map.get("UPDATED_AT")))
.collect(toList()))
.containsExactlyInAnyOrder(expectedTuples);
}
- private void insertUser(@Nullable String login, @Nullable String externalLogin, @Nullable String externalIdentityProvider) {
+ private void insertUser(@Nullable String login, @Nullable String externalLogin, @Nullable String externalId, @Nullable String externalIdentityProvider) {
db.executeInsert("USERS",
"LOGIN", login,
"EXTERNAL_LOGIN", externalLogin,
+ "EXTERNAL_ID", externalLogin,
"EXTERNAL_IDENTITY_PROVIDER", externalIdentityProvider,
"CREATED_AT", PAST,
"UPDATED_AT", PAST,
--- /dev/null
+CREATE TABLE "USERS" (
+ "ID" INTEGER NOT NULL GENERATED BY DEFAULT AS IDENTITY (START WITH 1, INCREMENT BY 1),
+ "UUID" VARCHAR(255),
+ "LOGIN" VARCHAR(255),
+ "NAME" VARCHAR(200),
+ "EMAIL" VARCHAR(100),
+ "CRYPTED_PASSWORD" VARCHAR(100),
+ "SALT" VARCHAR(40),
+ "ACTIVE" BOOLEAN DEFAULT TRUE,
+ "SCM_ACCOUNTS" VARCHAR(4000),
+ "EXTERNAL_ID" VARCHAR(255),
+ "EXTERNAL_LOGIN" VARCHAR(255),
+ "EXTERNAL_IDENTITY_PROVIDER" VARCHAR(100),
+ "IS_ROOT" BOOLEAN NOT NULL,
+ "USER_LOCAL" BOOLEAN,
+ "ONBOARDED" BOOLEAN NOT NULL,
+ "CREATED_AT" BIGINT,
+ "UPDATED_AT" BIGINT,
+ "HOMEPAGE_TYPE" VARCHAR(40),
+ "HOMEPAGE_PARAMETER" VARCHAR(40)
+);
+CREATE UNIQUE INDEX "USERS_LOGIN" ON "USERS" ("LOGIN");
+CREATE INDEX "USERS_UPDATED_AT" ON "USERS" ("UPDATED_AT");
+++ /dev/null
-CREATE TABLE "USERS" (
- "ID" INTEGER NOT NULL GENERATED BY DEFAULT AS IDENTITY (START WITH 1, INCREMENT BY 1),
- "UUID" VARCHAR(255),
- "LOGIN" VARCHAR(255),
- "NAME" VARCHAR(200),
- "EMAIL" VARCHAR(100),
- "CRYPTED_PASSWORD" VARCHAR(100),
- "SALT" VARCHAR(40),
- "ACTIVE" BOOLEAN DEFAULT TRUE,
- "SCM_ACCOUNTS" VARCHAR(4000),
- "EXTERNAL_ID" VARCHAR(255),
- "EXTERNAL_LOGIN" VARCHAR(255),
- "EXTERNAL_IDENTITY_PROVIDER" VARCHAR(100),
- "IS_ROOT" BOOLEAN NOT NULL,
- "USER_LOCAL" BOOLEAN,
- "ONBOARDED" BOOLEAN NOT NULL,
- "CREATED_AT" BIGINT,
- "UPDATED_AT" BIGINT,
- "HOMEPAGE_TYPE" VARCHAR(40),
- "HOMEPAGE_PARAMETER" VARCHAR(40)
-);
-CREATE UNIQUE INDEX "USERS_LOGIN" ON "USERS" ("LOGIN");
-CREATE INDEX "USERS_UPDATED_AT" ON "USERS" ("UPDATED_AT");
if (user != null) {
return user;
}
- // Then, try with the external login, for instance when for instance external ID has changed
+ // Then, try with the external login, for instance when external ID has changed
user = dbClient.userDao().selectByExternalLoginAndIdentityProvider(dbSession, userIdentity.getProviderLogin(), provider.getKey());
if (user != null) {
return user;