]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-18917 Project analysis token isn't updated when project is rekeyed
authorDuarte Meneses <duarte.meneses@sonarsource.com>
Fri, 28 Apr 2023 21:58:36 +0000 (16:58 -0500)
committersonartech <sonartech@sonarsource.com>
Thu, 4 May 2023 20:03:11 +0000 (20:03 +0000)
26 files changed:
server/sonar-db-dao/src/it/java/org/sonar/db/user/UserTokenDaoIT.java
server/sonar-db-dao/src/it/java/org/sonar/db/user/UserTokenDaoWithPersisterIT.java
server/sonar-db-dao/src/main/java/org/sonar/db/user/UserTokenDao.java
server/sonar-db-dao/src/main/java/org/sonar/db/user/UserTokenMapper.java
server/sonar-db-dao/src/main/resources/org/sonar/db/user/UserTokenMapper.xml
server/sonar-db-dao/src/schema/schema-sq.ddl
server/sonar-db-dao/src/testFixtures/java/org/sonar/db/user/UserTokenTesting.java
server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v101/CreateProjectUuidInUserTokens.java [new file with mode: 0644]
server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v101/DbVersion101.java
server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v101/DropProjectKeyInUserTokens.java [new file with mode: 0644]
server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v101/PopulateProjectUuidInUserTokens.java [new file with mode: 0644]
server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v101/RemoveOrphanUserTokens.java [new file with mode: 0644]
server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v101/package-info.java [new file with mode: 0644]
server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v101/CreateProjectUuidInUserTokensTest.java [new file with mode: 0644]
server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v101/DropProjectKeyInUserTokensTest.java [new file with mode: 0644]
server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v101/PopulateProjectUuidInUserTokensTest.java [new file with mode: 0644]
server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v101/RemoveOrphanUserTokensTest.java [new file with mode: 0644]
server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v101/CreateProjectUuidInUserTokensTest/schema.sql [new file with mode: 0644]
server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v101/DropProjectKeyInUserTokensTest/schema.sql [new file with mode: 0644]
server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v101/PopulateProjectUuidInUserTokensTest/schema.sql [new file with mode: 0644]
server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v101/RemoveOrphanUserTokensTest/schema.sql [new file with mode: 0644]
server/sonar-webserver-auth/src/test/java/org/sonar/server/usertoken/UserTokenAuthenticationTest.java
server/sonar-webserver-webapi/src/it/java/org/sonar/server/usertoken/ws/GenerateActionIT.java
server/sonar-webserver-webapi/src/it/java/org/sonar/server/usertoken/ws/SearchActionIT.java
server/sonar-webserver-webapi/src/main/java/org/sonar/server/component/ComponentCleanerService.java
server/sonar-webserver-webapi/src/main/java/org/sonar/server/usertoken/ws/GenerateAction.java

index 25d2489d049b8d857534c9c6b0fe56b8a25f77c8..f3c653ae3a8d9580b588f21feaeeba52ece7f6be 100644 (file)
@@ -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);
index b35215b48632d6fbf42c7852ce1a4e1c71f77dd1..f29b20ca96fc56a2528fffc41170bf380395dd27 100644 (file)
@@ -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);
 
index 8fd8223fcfc27b9cb91a34169a382df5b31499d9..589f36556148c0b15f97b41816ebeda1dd06869a 100644 (file)
@@ -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));
index 7a8825ab9e4dba67598b81e6729a972fee550fda..bca0a7ec695754498d24b3c1e3271d2b815654b5 100644 (file)
@@ -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<UserTokenCount> countTokensByUserUuids(@Param("userUuids") List<String> userUuids);
 
index 3e52e4907e21bb53f2c30d40ee6f762b59977138..775f344e500498ab683ad01e5bcd7a86aad56570 100644 (file)
     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"
   </sql>
 
   <insert id="insert" parameterType="UserToken" useGeneratedKeys="false">
@@ -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
     <include refid="userTokensColumns"/>
     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}
   </select>
 
@@ -59,7 +59,7 @@
     SELECT
     <include refid="userTokensColumns"/>
     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}
   </select>
 
@@ -67,7 +67,7 @@
     SELECT
     <include refid="userTokensColumns"/>
     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}
   </select>
 
@@ -75,7 +75,7 @@
     SELECT
     <include refid="userTokensColumns"/>
     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}
   </select>
   
@@ -97,8 +97,8 @@
     DELETE FROM user_tokens WHERE user_uuid=#{userUuid, jdbcType=VARCHAR} and name=#{name, jdbcType=VARCHAR}
   </delete>
 
-  <delete id="deleteByProjectKey">
-    DELETE FROM user_tokens WHERE project_key=#{projectKey, jdbcType=VARCHAR}
+  <delete id="deleteByProjectUuid">
+    DELETE FROM user_tokens WHERE project_uuid=#{projectUuid, jdbcType=VARCHAR}
   </delete>
 
 </mapper>
index 08a75b195bee4e547d856471752bb86778e25e8d..4395b873456ccc76fea13600edfbad21373df617 100644 (file)
@@ -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);
index 0b11e43c729c85c0294a3a3c8aaf6d7f45552e62..c7b8921e84164d77dbee8746de9994a0d289e38b 100644 (file)
@@ -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 (file)
index 0000000..7a992da
--- /dev/null
@@ -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());
+      }
+    }
+  }
+}
index 8285e6f6d241888fd8c113c10fb17371b43ae184..59f01e643cd1b9008ca5f825fb5da8925789188f 100644 (file)
@@ -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 (file)
index 0000000..9595869
--- /dev/null
@@ -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 (file)
index 0000000..f25b101
--- /dev/null
@@ -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 (file)
index 0000000..9345262
--- /dev/null
@@ -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 (file)
index 0000000..d64b33a
--- /dev/null
@@ -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 (file)
index 0000000..b626159
--- /dev/null
@@ -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 (file)
index 0000000..f3b5f8a
--- /dev/null
@@ -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 (file)
index 0000000..d828218
--- /dev/null
@@ -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<String, Object> 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<String, Object> 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 (file)
index 0000000..b659b6b
--- /dev/null
@@ -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<String, Object> 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<String, Object> 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 (file)
index 0000000..b67d2d9
--- /dev/null
@@ -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 (file)
index 0000000..64375f3
--- /dev/null
@@ -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 (file)
index 0000000..ae9e813
--- /dev/null
@@ -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 (file)
index 0000000..0079535
--- /dev/null
@@ -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);
index 597f85f7cf39ebe6652f18b03e3db64122c0d893..381addb91be994aa839c8ea3701b6122efe309fd 100644 (file)
@@ -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);
   }
index be1192976fff1b4cb9415894bc04da8577e5fed7..80f28384922333c7c801588c4539e208670b4e17 100644 (file)
@@ -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() {
index 134107f5586e03dc99de15c28f54c81fcfcf3831..66b2629bc80eb6574a31ad830bfa1a48d01ca93a 100644 (file)
@@ -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();
 
index 435bd6dc4e856920d543c3b9c3b38e1e2c90c0fb..ccbfb44bf1abd9e1213db3677410174ed623eee5 100644 (file)
@@ -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);
   }
 
index 163a50c7bf82f73d1c22448238077b4f9d38c504..ccc162e7fe96fc62c72cad86f82629f189d343cd 100644 (file)
@@ -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<String> 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) {