From 269eb98300821838ed90792726fe0246113e61ba Mon Sep 17 00:00:00 2001 From: Duarte Meneses Date: Fri, 28 Apr 2023 16:58:36 -0500 Subject: [PATCH] SONAR-18917 Project analysis token isn't updated when project is rekeyed --- .../org/sonar/db/user/UserTokenDaoIT.java | 14 +-- .../db/user/UserTokenDaoWithPersisterIT.java | 12 +- .../java/org/sonar/db/user/UserTokenDao.java | 4 +- .../org/sonar/db/user/UserTokenMapper.java | 2 +- .../org/sonar/db/user/UserTokenMapper.xml | 20 ++-- server/sonar-db-dao/src/schema/schema-sq.ddl | 4 +- .../org/sonar/db/user/UserTokenTesting.java | 3 +- .../v101/CreateProjectUuidInUserTokens.java | 55 +++++++++ .../migration/version/v101/DbVersion101.java | 4 + .../v101/DropProjectKeyInUserTokens.java | 32 +++++ .../v101/PopulateProjectUuidInUserTokens.java | 58 +++++++++ .../version/v101/RemoveOrphanUserTokens.java | 56 +++++++++ .../migration/version/v101/package-info.java | 23 ++++ .../CreateProjectUuidInUserTokensTest.java | 52 ++++++++ .../v101/DropProjectKeyInUserTokensTest.java | 51 ++++++++ .../PopulateProjectUuidInUserTokensTest.java | 112 ++++++++++++++++++ .../v101/RemoveOrphanUserTokensTest.java | 97 +++++++++++++++ .../schema.sql | 14 +++ .../DropProjectKeyInUserTokensTest/schema.sql | 15 +++ .../schema.sql | 31 +++++ .../RemoveOrphanUserTokensTest/schema.sql | 31 +++++ .../UserTokenAuthenticationTest.java | 4 +- .../server/usertoken/ws/GenerateActionIT.java | 5 +- .../server/usertoken/ws/SearchActionIT.java | 4 +- .../component/ComponentCleanerService.java | 4 +- .../server/usertoken/ws/GenerateAction.java | 27 +++-- 26 files changed, 688 insertions(+), 46 deletions(-) create mode 100644 server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v101/CreateProjectUuidInUserTokens.java create mode 100644 server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v101/DropProjectKeyInUserTokens.java create mode 100644 server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v101/PopulateProjectUuidInUserTokens.java create mode 100644 server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v101/RemoveOrphanUserTokens.java create mode 100644 server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v101/package-info.java create mode 100644 server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v101/CreateProjectUuidInUserTokensTest.java create mode 100644 server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v101/DropProjectKeyInUserTokensTest.java create mode 100644 server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v101/PopulateProjectUuidInUserTokensTest.java create mode 100644 server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v101/RemoveOrphanUserTokensTest.java create mode 100644 server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v101/CreateProjectUuidInUserTokensTest/schema.sql create mode 100644 server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v101/DropProjectKeyInUserTokensTest/schema.sql create mode 100644 server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v101/PopulateProjectUuidInUserTokensTest/schema.sql create mode 100644 server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v101/RemoveOrphanUserTokensTest/schema.sql diff --git a/server/sonar-db-dao/src/it/java/org/sonar/db/user/UserTokenDaoIT.java b/server/sonar-db-dao/src/it/java/org/sonar/db/user/UserTokenDaoIT.java index 25d2489d049..f3c653ae3a8 100644 --- a/server/sonar-db-dao/src/it/java/org/sonar/db/user/UserTokenDaoIT.java +++ b/server/sonar-db-dao/src/it/java/org/sonar/db/user/UserTokenDaoIT.java @@ -69,13 +69,13 @@ public class UserTokenDaoIT { @Test public void insert_project_analysis_token() { UserTokenDto projectAnalysisToken = newProjectAnalysisToken(); - ComponentDto project = db.components().insertPublicProject(p -> p.setKey(projectAnalysisToken.getProjectKey())); + ComponentDto project = db.components().insertPublicProject(projectAnalysisToken.getProjectUuid()); underTest.insert(db.getSession(), projectAnalysisToken, "login"); UserTokenDto projectAnalysisTokenFromDb = underTest.selectByTokenHash(db.getSession(), projectAnalysisToken.getTokenHash()); assertTokenStandardFields(projectAnalysisToken, projectAnalysisTokenFromDb); - assertThat(projectAnalysisTokenFromDb.getProjectUuid()).isEqualTo(project.uuid()); - assertThat(projectAnalysisTokenFromDb.getProjectKey()).isEqualTo(projectAnalysisToken.getProjectKey()); + assertThat(projectAnalysisTokenFromDb.getProjectUuid()).isEqualTo(projectAnalysisToken.getProjectUuid()); + assertThat(projectAnalysisTokenFromDb.getProjectKey()).isEqualTo(project.getKey()); assertThat(projectAnalysisTokenFromDb.getProjectName()).isEqualTo(project.name()); assertThat(projectAnalysisTokenFromDb.getExpirationDate()).isNull(); } @@ -172,11 +172,11 @@ public class UserTokenDaoIT { public void delete_tokens_by_projectKey() { UserDto user1 = db.users().insertUser(); UserDto user2 = db.users().insertUser(); - db.users().insertToken(user1, t -> t.setProjectKey("projectKey1")); - db.users().insertToken(user1, t -> t.setProjectKey("projectKey2")); - db.users().insertToken(user2, t -> t.setProjectKey("projectKey1")); + db.users().insertToken(user1, t -> t.setProjectUuid("projectUuid1")); + db.users().insertToken(user1, t -> t.setProjectUuid("projectUuid2")); + db.users().insertToken(user2, t -> t.setProjectUuid("projectUuid1")); - underTest.deleteByProjectKey(dbSession, "projectKey1"); + underTest.deleteByProjectUuid(dbSession, "projectKey1", "projectUuid1"); db.commit(); assertThat(underTest.selectByUser(dbSession, user1)).hasSize(1); diff --git a/server/sonar-db-dao/src/it/java/org/sonar/db/user/UserTokenDaoWithPersisterIT.java b/server/sonar-db-dao/src/it/java/org/sonar/db/user/UserTokenDaoWithPersisterIT.java index b35215b4863..f29b20ca96f 100644 --- a/server/sonar-db-dao/src/it/java/org/sonar/db/user/UserTokenDaoWithPersisterIT.java +++ b/server/sonar-db-dao/src/it/java/org/sonar/db/user/UserTokenDaoWithPersisterIT.java @@ -155,18 +155,18 @@ public class UserTokenDaoWithPersisterIT { public void delete_token_by_projectKey_is_persisted() { UserDto user1 = db.users().insertUser(); UserDto user2 = db.users().insertUser(); - db.users().insertToken(user1, t -> t.setProjectKey("projectToDelete")); - db.users().insertToken(user1, t -> t.setProjectKey("projectToKeep")); - db.users().insertToken(user2, t -> t.setProjectKey("projectToDelete")); + db.users().insertToken(user1, t -> t.setProjectUuid("projectToDelete")); + db.users().insertToken(user1, t -> t.setProjectUuid("projectToKeep")); + db.users().insertToken(user2, t -> t.setProjectUuid("projectToDelete")); - underTest.deleteByProjectKey(dbSession, "projectToDelete"); + underTest.deleteByProjectUuid(dbSession, "projectToDeleteKey", "projectToDelete"); assertThat(underTest.selectByUser(dbSession, user1)).hasSize(1); assertThat(underTest.selectByUser(dbSession, user2)).isEmpty(); verify(auditPersister).deleteUserToken(eq(db.getSession()), newValueCaptor.capture()); assertThat(newValueCaptor.getValue()) .extracting(UserTokenNewValue::getProjectKey, UserTokenNewValue::getType) - .containsExactly("projectToDelete", "PROJECT_ANALYSIS_TOKEN"); + .containsExactly("projectToDeleteKey", "PROJECT_ANALYSIS_TOKEN"); } @Test @@ -175,7 +175,7 @@ public class UserTokenDaoWithPersisterIT { db.users().insertToken(user1, t -> t.setProjectKey("projectToKeep")); - underTest.deleteByProjectKey(dbSession, "projectToDelete"); + underTest.deleteByProjectUuid(dbSession, "projectToDeleteKey", "projectToDelete"); assertThat(underTest.selectByUser(dbSession, user1)).hasSize(1); diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/user/UserTokenDao.java b/server/sonar-db-dao/src/main/java/org/sonar/db/user/UserTokenDao.java index 8fd8223fcfc..589f3655614 100644 --- a/server/sonar-db-dao/src/main/java/org/sonar/db/user/UserTokenDao.java +++ b/server/sonar-db-dao/src/main/java/org/sonar/db/user/UserTokenDao.java @@ -110,8 +110,8 @@ public class UserTokenDao implements Dao { } } - public void deleteByProjectKey(DbSession dbSession, String projectKey) { - int deletedRows = mapper(dbSession).deleteByProjectKey(projectKey); + public void deleteByProjectUuid(DbSession dbSession, String projectKey, String projectUuid) { + int deletedRows = mapper(dbSession).deleteByProjectUuid(projectUuid); if (deletedRows > 0) { auditPersister.deleteUserToken(dbSession, new UserTokenNewValue(projectKey)); diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/user/UserTokenMapper.java b/server/sonar-db-dao/src/main/java/org/sonar/db/user/UserTokenMapper.java index 7a8825ab9e4..bca0a7ec695 100644 --- a/server/sonar-db-dao/src/main/java/org/sonar/db/user/UserTokenMapper.java +++ b/server/sonar-db-dao/src/main/java/org/sonar/db/user/UserTokenMapper.java @@ -38,7 +38,7 @@ public interface UserTokenMapper { int deleteByUserUuidAndName(@Param("userUuid") String userUuid, @Param("name") String name); - int deleteByProjectKey(@Param("projectKey") String projectKey); + int deleteByProjectUuid(@Param("projectUuid") String projectUuid); List countTokensByUserUuids(@Param("userUuids") List userUuids); diff --git a/server/sonar-db-dao/src/main/resources/org/sonar/db/user/UserTokenMapper.xml b/server/sonar-db-dao/src/main/resources/org/sonar/db/user/UserTokenMapper.xml index 3e52e4907e2..775f344e500 100644 --- a/server/sonar-db-dao/src/main/resources/org/sonar/db/user/UserTokenMapper.xml +++ b/server/sonar-db-dao/src/main/resources/org/sonar/db/user/UserTokenMapper.xml @@ -10,11 +10,11 @@ t.token_hash as "tokenHash", t.last_connection_date as "lastConnectionDate", t.created_at as "createdAt", - t.project_key as "projectKey", + t.project_uuid as "projectUuid", t.type as "type", t.expiration_date as "expirationDate", p.name as "projectName", - p.uuid as "projectUuid" + p.kee as "projectKey" @@ -24,7 +24,7 @@ name, token_hash, created_at, - project_key, + project_uuid, type, expiration_date ) values ( @@ -33,7 +33,7 @@ #{name, jdbcType=VARCHAR}, #{tokenHash, jdbcType=VARCHAR}, #{createdAt, jdbcType=BIGINT}, - #{projectKey, jdbcType=VARCHAR}, + #{projectUuid, jdbcType=VARCHAR}, #{type, jdbcType=VARCHAR}, #{expirationDate, jdbcType=BIGINT} ) @@ -51,7 +51,7 @@ SELECT FROM user_tokens t - LEFT JOIN projects p on t.project_key = p.kee + LEFT JOIN projects p on t.project_uuid = p.uuid WHERE t.token_hash=#{tokenHash, jdbcType=VARCHAR} @@ -59,7 +59,7 @@ SELECT FROM user_tokens t - LEFT JOIN projects p on t.project_key = p.kee + LEFT JOIN projects p on t.project_uuid = p.uuid WHERE t.expiration_date = #{timestamp, jdbcType=BIGINT} @@ -67,7 +67,7 @@ SELECT FROM user_tokens t - LEFT JOIN projects p on t.project_key = p.kee + LEFT JOIN projects p on t.project_uuid = p.uuid WHERE t.user_uuid=#{userUuid, jdbcType=VARCHAR} and t.name=#{name, jdbcType=VARCHAR} @@ -75,7 +75,7 @@ SELECT FROM user_tokens t - LEFT JOIN projects p on t.project_key = p.kee + LEFT JOIN projects p on t.project_uuid = p.uuid WHERE t.user_uuid=#{userUuid, jdbcType=VARCHAR} @@ -97,8 +97,8 @@ DELETE FROM user_tokens WHERE user_uuid=#{userUuid, jdbcType=VARCHAR} and name=#{name, jdbcType=VARCHAR} - - DELETE FROM user_tokens WHERE project_key=#{projectKey, jdbcType=VARCHAR} + + DELETE FROM user_tokens WHERE project_uuid=#{projectUuid, jdbcType=VARCHAR} diff --git a/server/sonar-db-dao/src/schema/schema-sq.ddl b/server/sonar-db-dao/src/schema/schema-sq.ddl index 08a75b195be..4395b873456 100644 --- a/server/sonar-db-dao/src/schema/schema-sq.ddl +++ b/server/sonar-db-dao/src/schema/schema-sq.ddl @@ -994,9 +994,9 @@ CREATE TABLE "USER_TOKENS"( "TOKEN_HASH" CHARACTER VARYING(255) NOT NULL, "LAST_CONNECTION_DATE" BIGINT, "CREATED_AT" BIGINT NOT NULL, - "PROJECT_KEY" CHARACTER VARYING(255), "TYPE" CHARACTER VARYING(100) NOT NULL, - "EXPIRATION_DATE" BIGINT + "EXPIRATION_DATE" BIGINT, + "PROJECT_UUID" CHARACTER VARYING(40) ); ALTER TABLE "USER_TOKENS" ADD CONSTRAINT "PK_USER_TOKENS" PRIMARY KEY("UUID"); CREATE UNIQUE INDEX "USER_TOKENS_USER_UUID_NAME" ON "USER_TOKENS"("USER_UUID" NULLS FIRST, "NAME" NULLS FIRST); diff --git a/server/sonar-db-dao/src/testFixtures/java/org/sonar/db/user/UserTokenTesting.java b/server/sonar-db-dao/src/testFixtures/java/org/sonar/db/user/UserTokenTesting.java index 0b11e43c729..c7b8921e841 100644 --- a/server/sonar-db-dao/src/testFixtures/java/org/sonar/db/user/UserTokenTesting.java +++ b/server/sonar-db-dao/src/testFixtures/java/org/sonar/db/user/UserTokenTesting.java @@ -42,7 +42,8 @@ public class UserTokenTesting { .setUserUuid("userUuid_" + randomAlphanumeric(40)) .setName("name_" + randomAlphanumeric(20)) .setTokenHash("hash_" + randomAlphanumeric(30)) - .setProjectKey("projectUuid_" + randomAlphanumeric(40)) + .setProjectUuid("projectUuid_" + randomAlphanumeric(20)) + .setProjectKey("projectKey_" + randomAlphanumeric(40)) .setProjectName("Project " + randomAlphanumeric(40)) .setCreatedAt(nextLong()) .setType("PROJECT_ANALYSIS_TOKEN"); diff --git a/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v101/CreateProjectUuidInUserTokens.java b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v101/CreateProjectUuidInUserTokens.java new file mode 100644 index 00000000000..7a992da801f --- /dev/null +++ b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v101/CreateProjectUuidInUserTokens.java @@ -0,0 +1,55 @@ +/* + * SonarQube + * Copyright (C) 2009-2023 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.v101; + +import java.sql.Connection; +import java.sql.SQLException; +import org.sonar.db.Database; +import org.sonar.db.DatabaseUtils; +import org.sonar.server.platform.db.migration.def.ColumnDef; +import org.sonar.server.platform.db.migration.def.VarcharColumnDef; +import org.sonar.server.platform.db.migration.sql.AddColumnsBuilder; +import org.sonar.server.platform.db.migration.step.DdlChange; + +import static org.sonar.server.platform.db.migration.def.VarcharColumnDef.UUID_SIZE; + +public class CreateProjectUuidInUserTokens extends DdlChange { + + private static final String TABLE_NAME = "user_tokens"; + private static final String COLUMN_NAME = "project_uuid"; + + public CreateProjectUuidInUserTokens(Database db) { + super(db); + } + + @Override + public void execute(DdlChange.Context context) throws SQLException { + try (Connection c = getDatabase().getDataSource().getConnection()) { + if (!DatabaseUtils.tableColumnExists(c, TABLE_NAME, COLUMN_NAME)) { + ColumnDef columnDef = VarcharColumnDef.newVarcharColumnDefBuilder() + .setColumnName(COLUMN_NAME) + .setIsNullable(true) + .setLimit(UUID_SIZE) + .build(); + context.execute(new AddColumnsBuilder(getDialect(), TABLE_NAME).addColumn(columnDef).build()); + } + } + } +} diff --git a/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v101/DbVersion101.java b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v101/DbVersion101.java index 8285e6f6d24..59f01e643cd 100644 --- a/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v101/DbVersion101.java +++ b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v101/DbVersion101.java @@ -50,6 +50,10 @@ public class DbVersion101 implements DbVersion { .add(10_1_006, "Update value of 'is_main' in 'project_branches' table", UpdateIsMainColumnInProjectBranches.class) .add(10_1_007, "Alter column 'is_main' in 'project_branches' table - make it not nullable", AlterIsMainColumnInProjectBranches.class) .add(10_1_008, "Increase size of 'internal_properties.kee' from 20 to 40 characters", IncreaseKeeColumnSizeInInternalProperties.class) + .add(10_1_009, "Create column 'project_uuid' in 'user_tokens", CreateProjectUuidInUserTokens.class) + .add(10_1_010, "Remove user tokens linked to unexistent project", RemoveOrphanUserTokens.class) + .add(10_1_011, "Populate 'project_key' in 'user_tokens'", PopulateProjectUuidInUserTokens.class) + .add(10_1_012, "Drop column 'project_key' in 'user_tokens", DropProjectKeyInUserTokens.class) ; } } diff --git a/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v101/DropProjectKeyInUserTokens.java b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v101/DropProjectKeyInUserTokens.java new file mode 100644 index 00000000000..95958695aa0 --- /dev/null +++ b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v101/DropProjectKeyInUserTokens.java @@ -0,0 +1,32 @@ +/* + * SonarQube + * Copyright (C) 2009-2023 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.v101; + +import org.sonar.db.Database; +import org.sonar.server.platform.db.migration.step.DropColumnChange; + +class DropProjectKeyInUserTokens extends DropColumnChange { + static final String TABLE_NAME = "user_tokens"; + static final String COLUMN_NAME = "project_key"; + + public DropProjectKeyInUserTokens(Database db) { + super(db, TABLE_NAME, COLUMN_NAME); + } +} diff --git a/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v101/PopulateProjectUuidInUserTokens.java b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v101/PopulateProjectUuidInUserTokens.java new file mode 100644 index 00000000000..f25b101b3c0 --- /dev/null +++ b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v101/PopulateProjectUuidInUserTokens.java @@ -0,0 +1,58 @@ +/* + * SonarQube + * Copyright (C) 2009-2023 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.v101; + +import java.sql.Connection; +import java.sql.SQLException; +import org.sonar.db.Database; +import org.sonar.db.DatabaseUtils; +import org.sonar.server.platform.db.migration.step.DataChange; +import org.sonar.server.platform.db.migration.step.MassUpdate; + +public class PopulateProjectUuidInUserTokens extends DataChange { + private static final String SELECT_QUERY = """ + SELECT ut.uuid as tokenUuid, p.uuid as projectUuid + FROM user_tokens ut + INNER JOIN projects p ON ut.project_key = p.kee and ut.project_key is not null + """; + + public PopulateProjectUuidInUserTokens(Database db) { + super(db); + } + + @Override + protected void execute(Context context) throws SQLException { + try (Connection connection = getDatabase().getDataSource().getConnection()) { + if (!DatabaseUtils.tableColumnExists(connection, "user_tokens", "project_key")) { + return; + } + } + MassUpdate massUpdate = context.prepareMassUpdate(); + massUpdate.select(SELECT_QUERY); + massUpdate.update("update user_tokens set project_uuid = ? where uuid = ?"); + massUpdate.execute((row, update) -> { + String uuid = row.getString(1); + String projectUuid = row.getString(2); + update.setString(1, projectUuid); + update.setString(2, uuid); + return true; + }); + } +} diff --git a/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v101/RemoveOrphanUserTokens.java b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v101/RemoveOrphanUserTokens.java new file mode 100644 index 00000000000..9345262d503 --- /dev/null +++ b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v101/RemoveOrphanUserTokens.java @@ -0,0 +1,56 @@ +/* + * SonarQube + * Copyright (C) 2009-2023 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.v101; + +import java.sql.Connection; +import java.sql.SQLException; +import org.sonar.db.Database; +import org.sonar.db.DatabaseUtils; +import org.sonar.server.platform.db.migration.step.DataChange; +import org.sonar.server.platform.db.migration.step.MassUpdate; + +public class RemoveOrphanUserTokens extends DataChange { + private static final String SELECT_QUERY = """ + SELECT ut.uuid as tokenUuid + FROM user_tokens ut + LEFT OUTER JOIN projects p ON ut.project_key = p.kee + WHERE p.uuid is null + """; + + public RemoveOrphanUserTokens(Database db) { + super(db); + } + + @Override + protected void execute(Context context) throws SQLException { + try (Connection connection = getDatabase().getDataSource().getConnection()) { + if (DatabaseUtils.tableColumnExists(connection, "user_tokens", "project_key")) { + MassUpdate massUpdate = context.prepareMassUpdate(); + massUpdate.select(SELECT_QUERY); + massUpdate.update("delete user_tokens where uuid = ?"); + massUpdate.execute((row, update) -> { + String uuid = row.getString(1); + update.setString(1, uuid); + return true; + }); + } + } + } +} \ No newline at end of file diff --git a/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v101/package-info.java b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v101/package-info.java new file mode 100644 index 00000000000..d64b33a68c6 --- /dev/null +++ b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v101/package-info.java @@ -0,0 +1,23 @@ +/* + * SonarQube + * Copyright (C) 2009-2023 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. + */ +@ParametersAreNonnullByDefault +package org.sonar.server.platform.db.migration.version.v101; + +import javax.annotation.ParametersAreNonnullByDefault; diff --git a/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v101/CreateProjectUuidInUserTokensTest.java b/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v101/CreateProjectUuidInUserTokensTest.java new file mode 100644 index 00000000000..b626159e228 --- /dev/null +++ b/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v101/CreateProjectUuidInUserTokensTest.java @@ -0,0 +1,52 @@ +/* + * SonarQube + * Copyright (C) 2009-2023 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.v101; + +import java.sql.SQLException; +import org.junit.Rule; +import org.junit.Test; +import org.sonar.db.CoreDbTester; + +import static java.sql.Types.VARCHAR; + +public class CreateProjectUuidInUserTokensTest { + private static final String TABLE_NAME = "user_tokens"; + private static final String COLUMN_NAME = "project_uuid"; + + @Rule + public final CoreDbTester db = CoreDbTester.createForSchema(CreateProjectUuidInUserTokensTest.class, "schema.sql"); + + private final CreateProjectUuidInUserTokens underTest = new CreateProjectUuidInUserTokens(db.database()); + + @Test + public void migration_creates_new_column() throws SQLException { + db.assertColumnDoesNotExist(TABLE_NAME, COLUMN_NAME); + underTest.execute(); + db.assertColumnDefinition(TABLE_NAME, COLUMN_NAME, VARCHAR, 40, null); + } + + @Test + public void migration_is_reentrant() throws SQLException { + db.assertColumnDoesNotExist(TABLE_NAME, COLUMN_NAME); + underTest.execute(); + underTest.execute(); + db.assertColumnDefinition(TABLE_NAME, COLUMN_NAME, VARCHAR, 40, null); + } +} \ No newline at end of file diff --git a/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v101/DropProjectKeyInUserTokensTest.java b/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v101/DropProjectKeyInUserTokensTest.java new file mode 100644 index 00000000000..f3b5f8a3011 --- /dev/null +++ b/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v101/DropProjectKeyInUserTokensTest.java @@ -0,0 +1,51 @@ +/* + * SonarQube + * Copyright (C) 2009-2023 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.v101; + +import java.sql.SQLException; +import java.sql.Types; +import org.junit.Rule; +import org.junit.Test; +import org.sonar.db.CoreDbTester; +import org.sonar.server.platform.db.migration.step.DdlChange; + +import static org.sonar.server.platform.db.migration.version.v101.DropProjectKeyInUserTokens.COLUMN_NAME; +import static org.sonar.server.platform.db.migration.version.v101.DropProjectKeyInUserTokens.TABLE_NAME; + +public class DropProjectKeyInUserTokensTest { + @Rule + public final CoreDbTester db = CoreDbTester.createForSchema(DropProjectKeyInUserTokensTest.class, "schema.sql"); + private final DdlChange underTest = new DropProjectKeyInUserTokens(db.database()); + + @Test + public void drops_column() throws SQLException { + db.assertColumnDefinition(TABLE_NAME, COLUMN_NAME, Types.VARCHAR, 255, true); + underTest.execute(); + db.assertColumnDoesNotExist(TABLE_NAME, COLUMN_NAME); + } + + @Test + public void migration_is_reentrant() throws SQLException { + db.assertColumnDefinition(TABLE_NAME, COLUMN_NAME, Types.VARCHAR, 255, true); + underTest.execute(); + underTest.execute(); + db.assertColumnDoesNotExist(TABLE_NAME, COLUMN_NAME); + } +} diff --git a/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v101/PopulateProjectUuidInUserTokensTest.java b/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v101/PopulateProjectUuidInUserTokensTest.java new file mode 100644 index 00000000000..d8282182157 --- /dev/null +++ b/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v101/PopulateProjectUuidInUserTokensTest.java @@ -0,0 +1,112 @@ +/* + * SonarQube + * Copyright (C) 2009-2023 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.v101; + +import java.sql.SQLException; +import java.util.HashMap; +import java.util.Map; +import javax.annotation.Nullable; +import org.junit.Rule; +import org.junit.Test; +import org.sonar.core.util.UuidFactory; +import org.sonar.core.util.UuidFactoryFast; +import org.sonar.db.CoreDbTester; +import org.sonar.server.platform.db.migration.step.DataChange; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.groups.Tuple.tuple; + +public class PopulateProjectUuidInUserTokensTest { + private final UuidFactory uuidFactory = UuidFactoryFast.getInstance(); + + @Rule + public CoreDbTester db = CoreDbTester.createForSchema(PopulateProjectUuidInUserTokensTest.class, "schema.sql"); + + private final DataChange underTest = new PopulateProjectUuidInUserTokens(db.database()); + + @Test + public void migration_populates_project_uuid_for_tokens() throws SQLException { + String project1Uuid = insertProject("project1"); + String project2Uuid = insertProject("project2"); + + String token1Uuid = insertUserToken("project1"); + String token2Uuid = insertUserToken("project1"); + String token3Uuid = insertUserToken("project2"); + String token4Uuid = insertUserToken(null); + + underTest.execute(); + assertThat(db.select("select * from user_tokens")) + .extracting(r -> r.get("UUID"), r -> r.get("PROJECT_UUID")) + .containsOnly( + tuple(token1Uuid, project1Uuid), + tuple(token2Uuid, project1Uuid), + tuple(token3Uuid, project2Uuid), + tuple(token4Uuid, null)); + } + + @Test + public void migration_should_be_reentrant() throws SQLException { + String project1Uuid = insertProject("project1"); + String project2Uuid = insertProject("project2"); + + String token1Uuid = insertUserToken("project1"); + String token2Uuid = insertUserToken("project1"); + String token3Uuid = insertUserToken("project2"); + String token4Uuid = insertUserToken(null); + + underTest.execute(); + underTest.execute(); + + assertThat(db.select("select * from user_tokens")) + .extracting(r -> r.get("UUID"), r -> r.get("PROJECT_UUID")) + .containsOnly( + tuple(token1Uuid, project1Uuid), + tuple(token2Uuid, project1Uuid), + tuple(token3Uuid, project2Uuid), + tuple(token4Uuid, null)); + } + + private String insertUserToken(@Nullable String projectKey) { + Map map = new HashMap<>(); + String uuid = uuidFactory.create(); + map.put("UUID", uuid); + map.put("USER_UUID", "user" + uuid); + map.put("NAME", "name" + uuid); + map.put("TOKEN_HASH", "token" + uuid); + map.put("CREATED_AT", 1); + map.put("PROJECT_KEY", projectKey); + map.put("TYPE", "PROJECT_ANALYSIS_TOKEN"); + + db.executeInsert("user_tokens", map); + return uuid; + } + + private String insertProject(String projectKey) { + Map map = new HashMap<>(); + String uuid = uuidFactory.create(); + map.put("UUID", uuid); + map.put("KEE", projectKey); + map.put("QUALIFIER", "TRK"); + map.put("PRIVATE", true); + map.put("UPDATED_AT", System.currentTimeMillis()); + db.executeInsert("projects", map); + return uuid; + } +} diff --git a/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v101/RemoveOrphanUserTokensTest.java b/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v101/RemoveOrphanUserTokensTest.java new file mode 100644 index 00000000000..b659b6b722d --- /dev/null +++ b/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v101/RemoveOrphanUserTokensTest.java @@ -0,0 +1,97 @@ +/* + * SonarQube + * Copyright (C) 2009-2023 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.v101; + +import java.sql.SQLException; +import java.util.HashMap; +import java.util.Map; +import org.junit.Rule; +import org.junit.Test; +import org.sonar.core.util.UuidFactory; +import org.sonar.core.util.UuidFactoryFast; +import org.sonar.db.CoreDbTester; +import org.sonar.server.platform.db.migration.sql.DropColumnsBuilder; +import org.sonar.server.platform.db.migration.step.DataChange; + +import static org.assertj.core.api.Assertions.assertThat; + +public class RemoveOrphanUserTokensTest { + private final UuidFactory uuidFactory = UuidFactoryFast.getInstance(); + + @Rule + public CoreDbTester db = CoreDbTester.createForSchema(RemoveOrphanUserTokensTest.class, "schema.sql"); + + private final DataChange underTest = new RemoveOrphanUserTokens(db.database()); + + @Test + public void migration_deletes_orphan_tokens() throws SQLException { + String project1Uuid = insertProject("project1"); + + String token1Uuid = insertUserToken("project1"); + String token2Uuid = insertUserToken("orphan"); + + underTest.execute(); + assertThat(db.select("select * from user_tokens")) + .extracting(r -> r.get("UUID")) + .containsOnly(token1Uuid); + } + + @Test + public void migration_should_be_reentrant() throws SQLException { + String project1Uuid = insertProject("project1"); + + String token1Uuid = insertUserToken("project1"); + String token2Uuid = insertUserToken("orphan"); + + underTest.execute(); + underTest.execute(); + + assertThat(db.select("select * from user_tokens")) + .extracting(r -> r.get("UUID")) + .containsOnly(token1Uuid); + } + + private String insertUserToken(String projectKey) { + Map map = new HashMap<>(); + String uuid = uuidFactory.create(); + map.put("UUID", uuid); + map.put("USER_UUID", "user" + uuid); + map.put("NAME", "name" + uuid); + map.put("TOKEN_HASH", "token" + uuid); + map.put("CREATED_AT", 1); + map.put("PROJECT_KEY", projectKey); + map.put("TYPE", "PROJECT_ANALYSIS_TOKEN"); + + db.executeInsert("user_tokens", map); + return uuid; + } + + private String insertProject(String projectKey) { + Map map = new HashMap<>(); + String uuid = uuidFactory.create(); + map.put("UUID", uuid); + map.put("KEE", projectKey); + map.put("QUALIFIER", "TRK"); + map.put("PRIVATE", true); + map.put("UPDATED_AT", System.currentTimeMillis()); + db.executeInsert("projects", map); + return uuid; + } +} \ No newline at end of file diff --git a/server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v101/CreateProjectUuidInUserTokensTest/schema.sql b/server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v101/CreateProjectUuidInUserTokensTest/schema.sql new file mode 100644 index 00000000000..b67d2d94b37 --- /dev/null +++ b/server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v101/CreateProjectUuidInUserTokensTest/schema.sql @@ -0,0 +1,14 @@ +CREATE TABLE "USER_TOKENS"( + "UUID" CHARACTER VARYING(40) NOT NULL, + "USER_UUID" CHARACTER VARYING(255) NOT NULL, + "NAME" CHARACTER VARYING(100) NOT NULL, + "TOKEN_HASH" CHARACTER VARYING(255) NOT NULL, + "LAST_CONNECTION_DATE" BIGINT, + "CREATED_AT" BIGINT NOT NULL, + "PROJECT_KEY" CHARACTER VARYING(255), + "TYPE" CHARACTER VARYING(100) NOT NULL, + "EXPIRATION_DATE" BIGINT +); +ALTER TABLE "USER_TOKENS" ADD CONSTRAINT "PK_USER_TOKENS" PRIMARY KEY("UUID"); +CREATE UNIQUE INDEX "USER_TOKENS_USER_UUID_NAME" ON "USER_TOKENS"("USER_UUID" NULLS FIRST, "NAME" NULLS FIRST); +CREATE UNIQUE INDEX "USER_TOKENS_TOKEN_HASH" ON "USER_TOKENS"("TOKEN_HASH" NULLS FIRST); diff --git a/server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v101/DropProjectKeyInUserTokensTest/schema.sql b/server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v101/DropProjectKeyInUserTokensTest/schema.sql new file mode 100644 index 00000000000..64375f3827a --- /dev/null +++ b/server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v101/DropProjectKeyInUserTokensTest/schema.sql @@ -0,0 +1,15 @@ +CREATE TABLE "USER_TOKENS"( + "UUID" CHARACTER VARYING(40) NOT NULL, + "USER_UUID" CHARACTER VARYING(255) NOT NULL, + "NAME" CHARACTER VARYING(100) NOT NULL, + "TOKEN_HASH" CHARACTER VARYING(255) NOT NULL, + "LAST_CONNECTION_DATE" BIGINT, + "CREATED_AT" BIGINT NOT NULL, + "PROJECT_KEY" CHARACTER VARYING(255), + "TYPE" CHARACTER VARYING(100) NOT NULL, + "EXPIRATION_DATE" BIGINT, + "PROJECT_UUID" CHARACTER VARYING(40) +); +ALTER TABLE "USER_TOKENS" ADD CONSTRAINT "PK_USER_TOKENS" PRIMARY KEY("UUID"); +CREATE UNIQUE INDEX "USER_TOKENS_USER_UUID_NAME" ON "USER_TOKENS"("USER_UUID" NULLS FIRST, "NAME" NULLS FIRST); +CREATE UNIQUE INDEX "USER_TOKENS_TOKEN_HASH" ON "USER_TOKENS"("TOKEN_HASH" NULLS FIRST); diff --git a/server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v101/PopulateProjectUuidInUserTokensTest/schema.sql b/server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v101/PopulateProjectUuidInUserTokensTest/schema.sql new file mode 100644 index 00000000000..ae9e813e805 --- /dev/null +++ b/server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v101/PopulateProjectUuidInUserTokensTest/schema.sql @@ -0,0 +1,31 @@ +CREATE TABLE "USER_TOKENS"( + "UUID" CHARACTER VARYING(40) NOT NULL, + "USER_UUID" CHARACTER VARYING(255) NOT NULL, + "NAME" CHARACTER VARYING(100) NOT NULL, + "TOKEN_HASH" CHARACTER VARYING(255) NOT NULL, + "LAST_CONNECTION_DATE" BIGINT, + "CREATED_AT" BIGINT NOT NULL, + "PROJECT_KEY" CHARACTER VARYING(255), + "TYPE" CHARACTER VARYING(100) NOT NULL, + "EXPIRATION_DATE" BIGINT, + "PROJECT_UUID" CHARACTER VARYING(40) +); +ALTER TABLE "USER_TOKENS" ADD CONSTRAINT "PK_USER_TOKENS" PRIMARY KEY("UUID"); +CREATE UNIQUE INDEX "USER_TOKENS_USER_UUID_NAME" ON "USER_TOKENS"("USER_UUID" NULLS FIRST, "NAME" NULLS FIRST); +CREATE UNIQUE INDEX "USER_TOKENS_TOKEN_HASH" ON "USER_TOKENS"("TOKEN_HASH" NULLS FIRST); + +CREATE TABLE "PROJECTS"( + "UUID" CHARACTER VARYING(40) NOT NULL, + "KEE" CHARACTER VARYING(400) NOT NULL, + "QUALIFIER" CHARACTER VARYING(10) NOT NULL, + "NAME" CHARACTER VARYING(2000), + "DESCRIPTION" CHARACTER VARYING(2000), + "PRIVATE" BOOLEAN NOT NULL, + "TAGS" CHARACTER VARYING(500), + "CREATED_AT" BIGINT, + "UPDATED_AT" BIGINT NOT NULL, + "NCLOC" BIGINT +); +ALTER TABLE "PROJECTS" ADD CONSTRAINT "PK_NEW_PROJECTS" PRIMARY KEY("UUID"); +CREATE UNIQUE INDEX "UNIQ_PROJECTS_KEE" ON "PROJECTS"("KEE" NULLS FIRST); +CREATE INDEX "IDX_QUALIFIER" ON "PROJECTS"("QUALIFIER" NULLS FIRST); diff --git a/server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v101/RemoveOrphanUserTokensTest/schema.sql b/server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v101/RemoveOrphanUserTokensTest/schema.sql new file mode 100644 index 00000000000..0079535cda8 --- /dev/null +++ b/server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v101/RemoveOrphanUserTokensTest/schema.sql @@ -0,0 +1,31 @@ +CREATE TABLE "USER_TOKENS"( + "UUID" CHARACTER VARYING(40) NOT NULL, + "USER_UUID" CHARACTER VARYING(255) NOT NULL, + "NAME" CHARACTER VARYING(100) NOT NULL, + "TOKEN_HASH" CHARACTER VARYING(255) NOT NULL, + "LAST_CONNECTION_DATE" BIGINT, + "CREATED_AT" BIGINT NOT NULL, + "PROJECT_KEY" CHARACTER VARYING(255), + "TYPE" CHARACTER VARYING(100) NOT NULL, + "EXPIRATION_DATE" BIGINT, + "PROJECT_UUID" CHARACTER VARYING(40) +); +ALTER TABLE "USER_TOKENS" ADD CONSTRAINT "PK_USER_TOKENS" PRIMARY KEY("UUID"); +CREATE UNIQUE INDEX "USER_TOKENS_USER_UUID_NAME" ON "USER_TOKENS"("USER_UUID" NULLS FIRST, "NAME" NULLS FIRST); +CREATE UNIQUE INDEX "USER_TOKENS_TOKEN_HASH" ON "USER_TOKENS"("TOKEN_HASH" NULLS FIRST); + +CREATE TABLE "PROJECTS"( + "UUID" CHARACTER VARYING(40) NOT NULL, + "KEE" CHARACTER VARYING(400) NOT NULL, + "QUALIFIER" CHARACTER VARYING(10) NOT NULL, + "NAME" CHARACTER VARYING(2000), + "DESCRIPTION" CHARACTER VARYING(2000), + "PRIVATE" BOOLEAN NOT NULL, + "TAGS" CHARACTER VARYING(500), + "CREATED_AT" BIGINT, + "UPDATED_AT" BIGINT NOT NULL, + "NCLOC" BIGINT +); +ALTER TABLE "PROJECTS" ADD CONSTRAINT "PK_NEW_PROJECTS" PRIMARY KEY("UUID"); +CREATE UNIQUE INDEX "UNIQ_PROJECTS_KEE" ON "PROJECTS"("KEE" NULLS FIRST); +CREATE INDEX "IDX_QUALIFIER" ON "PROJECTS"("QUALIFIER" NULLS FIRST); diff --git a/server/sonar-webserver-auth/src/test/java/org/sonar/server/usertoken/UserTokenAuthenticationTest.java b/server/sonar-webserver-auth/src/test/java/org/sonar/server/usertoken/UserTokenAuthenticationTest.java index 597f85f7cf3..381addb91be 100644 --- a/server/sonar-webserver-auth/src/test/java/org/sonar/server/usertoken/UserTokenAuthenticationTest.java +++ b/server/sonar-webserver-auth/src/test/java/org/sonar/server/usertoken/UserTokenAuthenticationTest.java @@ -253,7 +253,7 @@ public class UserTokenAuthenticationTest { public void authenticate_givenProjectToken_resultContainsUuid() { UserDto user = db.users().insertUser(); String tokenName = db.users().insertToken(user, t -> t.setTokenHash(PROJECT_ANALYSIS_TOKEN_HASH) - .setProjectKey("project-key") + .setProjectUuid("project-uuid") .setType(PROJECT_ANALYSIS_TOKEN.name())).getName(); when(request.getHeader(AUTHORIZATION_HEADER)).thenReturn("Basic " + toBase64(EXAMPLE_PROJECT_ANALYSIS_TOKEN + ":")); @@ -262,7 +262,7 @@ public class UserTokenAuthenticationTest { assertThat(result).isPresent(); assertThat(result.get().getTokenDto().getUuid()).isNotNull(); assertThat(result.get().getTokenDto().getType()).isEqualTo(PROJECT_ANALYSIS_TOKEN.name()); - assertThat(result.get().getTokenDto().getProjectKey()).isEqualTo("project-key"); + assertThat(result.get().getTokenDto().getProjectUuid()).isEqualTo("project-uuid"); verify(authenticationEvent).loginSuccess(request, user.getLogin(), AuthenticationEvent.Source.local(SONARQUBE_TOKEN)); verify(request).setAttribute("TOKEN_NAME", tokenName); } diff --git a/server/sonar-webserver-webapi/src/it/java/org/sonar/server/usertoken/ws/GenerateActionIT.java b/server/sonar-webserver-webapi/src/it/java/org/sonar/server/usertoken/ws/GenerateActionIT.java index be1192976ff..80f28384922 100644 --- a/server/sonar-webserver-webapi/src/it/java/org/sonar/server/usertoken/ws/GenerateActionIT.java +++ b/server/sonar-webserver-webapi/src/it/java/org/sonar/server/usertoken/ws/GenerateActionIT.java @@ -35,8 +35,10 @@ import org.sonar.api.server.ws.WebService; import org.sonar.api.utils.System2; import org.sonar.db.DbTester; import org.sonar.db.component.ComponentDto; +import org.sonar.db.component.ResourceTypesRule; import org.sonar.db.user.TokenType; import org.sonar.db.user.UserDto; +import org.sonar.server.component.ComponentFinder; import org.sonar.server.exceptions.BadRequestException; import org.sonar.server.exceptions.ForbiddenException; import org.sonar.server.exceptions.NotFoundException; @@ -88,9 +90,10 @@ public class GenerateActionIT { private final MapSettings mapSettings = new MapSettings(); private final Configuration configuration = mapSettings.asConfig(); private final GenerateActionValidation validation = new GenerateActionValidation(configuration, runtime); + private final ComponentFinder componentFinder = new ComponentFinder(db.getDbClient(), new ResourceTypesRule()); private final WsActionTester ws = new WsActionTester( - new GenerateAction(db.getDbClient(), System2.INSTANCE, tokenGenerator, new UserTokenSupport(db.getDbClient(), userSession), validation)); + new GenerateAction(db.getDbClient(), System2.INSTANCE, componentFinder, tokenGenerator, new UserTokenSupport(db.getDbClient(), userSession), validation)); @Before public void setUp() { diff --git a/server/sonar-webserver-webapi/src/it/java/org/sonar/server/usertoken/ws/SearchActionIT.java b/server/sonar-webserver-webapi/src/it/java/org/sonar/server/usertoken/ws/SearchActionIT.java index 134107f5586..66b2629bc80 100644 --- a/server/sonar-webserver-webapi/src/it/java/org/sonar/server/usertoken/ws/SearchActionIT.java +++ b/server/sonar-webserver-webapi/src/it/java/org/sonar/server/usertoken/ws/SearchActionIT.java @@ -76,10 +76,10 @@ public class SearchActionIT { db.users().insertProjectAnalysisToken(user1, t -> t.setName("Project scan on Jenkins") .setCreatedAt(1428523067221L) .setExpirationDate(1563055200000L) - .setProjectKey(project1.getKey())); + .setProjectUuid(project1.uuid())); db.users().insertProjectAnalysisToken(user2, t -> t.setName("Project scan on Travis") .setCreatedAt(141456787123L) - .setProjectKey(project1.getKey())); + .setProjectUuid(project1.uuid())); logInAsSystemAdministrator(); diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/component/ComponentCleanerService.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/component/ComponentCleanerService.java index 435bd6dc4e8..ccbfb44bf1a 100644 --- a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/component/ComponentCleanerService.java +++ b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/component/ComponentCleanerService.java @@ -62,7 +62,7 @@ public class ComponentCleanerService { public void delete(DbSession dbSession, ProjectDto project) { dbClient.purgeDao().deleteProject(dbSession, project.getUuid(), project.getQualifier(), project.getName(), project.getKey()); dbClient.userDao().cleanHomepage(dbSession, project); - dbClient.userTokenDao().deleteByProjectKey(dbSession, project.getKey()); + dbClient.userTokenDao().deleteByProjectUuid(dbSession, project.getKey(), project.getUuid()); projectIndexers.commitAndIndexProjects(dbSession, singletonList(project), PROJECT_DELETION); } @@ -76,7 +76,7 @@ public class ComponentCleanerService { checkArgument(hasProjectScope(component) && isDeletable(component), "Only projects can be deleted"); dbClient.purgeDao().deleteProject(dbSession, component.uuid(), component.qualifier(), component.name(), component.getKey()); dbClient.userDao().cleanHomepage(dbSession, component); - dbClient.userTokenDao().deleteByProjectKey(dbSession, component.getKey()); + dbClient.userTokenDao().deleteByProjectUuid(dbSession, component.getKey(), component.uuid()); projectIndexers.commitAndIndexComponents(dbSession, singletonList(component), PROJECT_DELETION); } diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/usertoken/ws/GenerateAction.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/usertoken/ws/GenerateAction.java index 163a50c7bf8..ccc162e7fe9 100644 --- a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/usertoken/ws/GenerateAction.java +++ b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/usertoken/ws/GenerateAction.java @@ -23,7 +23,6 @@ import java.time.LocalDate; import java.time.ZoneOffset; import java.time.format.DateTimeFormatter; import java.time.format.DateTimeParseException; -import java.util.Optional; import org.jetbrains.annotations.Nullable; import org.sonar.api.server.ws.Change; import org.sonar.api.server.ws.Request; @@ -32,9 +31,11 @@ import org.sonar.api.server.ws.WebService; import org.sonar.api.utils.System2; import org.sonar.db.DbClient; import org.sonar.db.DbSession; +import org.sonar.db.project.ProjectDto; import org.sonar.db.user.TokenType; import org.sonar.db.user.UserDto; import org.sonar.db.user.UserTokenDto; +import org.sonar.server.component.ComponentFinder; import org.sonar.server.exceptions.ServerException; import org.sonar.server.usertoken.TokenGenerator; import org.sonarqube.ws.UserTokens; @@ -61,13 +62,16 @@ public class GenerateAction implements UserTokensWsAction { private final DbClient dbClient; private final System2 system; + private final ComponentFinder componentFinder; private final TokenGenerator tokenGenerator; private final UserTokenSupport userTokenSupport; private final GenerateActionValidation validation; - public GenerateAction(DbClient dbClient, System2 system, TokenGenerator tokenGenerator, UserTokenSupport userTokenSupport, GenerateActionValidation validation) { + public GenerateAction(DbClient dbClient, System2 system, ComponentFinder componentFinder, TokenGenerator tokenGenerator, + UserTokenSupport userTokenSupport, GenerateActionValidation validation) { this.dbClient = dbClient; this.system = system; + this.componentFinder = componentFinder; this.tokenGenerator = tokenGenerator; this.userTokenSupport = userTokenSupport; this.validation = validation; @@ -122,7 +126,7 @@ public class GenerateAction implements UserTokensWsAction { String token = generateToken(request, dbSession); String tokenHash = hashToken(dbSession, token); - UserTokenDto userTokenDtoFromRequest = getUserTokenDtoFromRequest(request); + UserTokenDto userTokenDtoFromRequest = getUserTokenDtoFromRequest(dbSession, request); userTokenDtoFromRequest.setTokenHash(tokenHash); UserDto user = userTokenSupport.getUser(dbSession, request); @@ -134,7 +138,7 @@ public class GenerateAction implements UserTokensWsAction { } } - private UserTokenDto getUserTokenDtoFromRequest(Request request) { + private UserTokenDto getUserTokenDtoFromRequest(DbSession dbSession, Request request) { LocalDate expirationDate = getExpirationDateFromRequest(request); validation.validateExpirationDate(expirationDate); @@ -145,7 +149,7 @@ public class GenerateAction implements UserTokensWsAction { if (expirationDate != null) { userTokenDtoFromRequest.setExpirationDate(expirationDate.atStartOfDay(ZoneOffset.UTC).toInstant().toEpochMilli()); } - getProjectKeyFromRequest(request).ifPresent(userTokenDtoFromRequest::setProjectKey); + setProjectFromRequest(dbSession, userTokenDtoFromRequest, request); return userTokenDtoFromRequest; } @@ -171,12 +175,15 @@ public class GenerateAction implements UserTokensWsAction { return tokenGenerator.generate(tokenType); } - public static Optional getProjectKeyFromRequest(Request request) { - String projectKey = null; - if (PROJECT_ANALYSIS_TOKEN.equals(getTokenTypeFromRequest(request))) { - projectKey = request.mandatoryParam(PARAM_PROJECT_KEY).trim(); + public void setProjectFromRequest(DbSession session, UserTokenDto token, Request request) { + if (!PROJECT_ANALYSIS_TOKEN.equals(getTokenTypeFromRequest(request))) { + return; } - return Optional.ofNullable(projectKey); + + String projectKey = request.mandatoryParam(PARAM_PROJECT_KEY).trim(); + ProjectDto project = componentFinder.getProjectByKey(session, projectKey); + token.setProjectUuid(project.getUuid()); + token.setProjectKey(project.getKey()); } private static TokenType getTokenTypeFromRequest(Request request) { -- 2.39.5