@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();
}
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);
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
db.users().insertToken(user1, t -> t.setProjectKey("projectToKeep"));
- underTest.deleteByProjectKey(dbSession, "projectToDelete");
+ underTest.deleteByProjectUuid(dbSession, "projectToDeleteKey", "projectToDelete");
assertThat(underTest.selectByUser(dbSession, user1)).hasSize(1);
}
}
- 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));
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);
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">
name,
token_hash,
created_at,
- project_key,
+ project_uuid,
type,
expiration_date
) values (
#{name, jdbcType=VARCHAR},
#{tokenHash, jdbcType=VARCHAR},
#{createdAt, jdbcType=BIGINT},
- #{projectKey, jdbcType=VARCHAR},
+ #{projectUuid, jdbcType=VARCHAR},
#{type, jdbcType=VARCHAR},
#{expirationDate, jdbcType=BIGINT}
)
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>
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>
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>
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>
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>
"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);
.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");
--- /dev/null
+/*
+ * 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());
+ }
+ }
+ }
+}
.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)
;
}
}
--- /dev/null
+/*
+ * 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);
+ }
+}
--- /dev/null
+/*
+ * 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;
+ });
+ }
+}
--- /dev/null
+/*
+ * 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
--- /dev/null
+/*
+ * 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;
--- /dev/null
+/*
+ * 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
--- /dev/null
+/*
+ * 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);
+ }
+}
--- /dev/null
+/*
+ * 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;
+ }
+}
--- /dev/null
+/*
+ * 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
--- /dev/null
+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);
--- /dev/null
+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);
--- /dev/null
+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);
--- /dev/null
+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);
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 + ":"));
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);
}
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;
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() {
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();
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);
}
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);
}
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;
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;
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;
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);
}
}
- private UserTokenDto getUserTokenDtoFromRequest(Request request) {
+ private UserTokenDto getUserTokenDtoFromRequest(DbSession dbSession, Request request) {
LocalDate expirationDate = getExpirationDateFromRequest(request);
validation.validateExpirationDate(expirationDate);
if (expirationDate != null) {
userTokenDtoFromRequest.setExpirationDate(expirationDate.atStartOfDay(ZoneOffset.UTC).toInstant().toEpochMilli());
}
- getProjectKeyFromRequest(request).ifPresent(userTokenDtoFromRequest::setProjectKey);
+ setProjectFromRequest(dbSession, userTokenDtoFromRequest, request);
return userTokenDtoFromRequest;
}
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) {