diff options
author | Julien Lancelot <julien.lancelot@sonarsource.com> | 2019-03-12 18:51:57 +0100 |
---|---|---|
committer | SonarTech <sonartech@sonarsource.com> | 2019-03-18 20:20:59 +0100 |
commit | c5ad29eb81b2d31916ba89188d6e4deed29c46a8 (patch) | |
tree | 961b3a0323a5c1899b24ab8355c89129357884fd | |
parent | eb54e11ba036b966f82e599a449efca15c58475e (diff) | |
download | sonarqube-c5ad29eb81b2d31916ba89188d6e4deed29c46a8.tar.gz sonarqube-c5ad29eb81b2d31916ba89188d6e4deed29c46a8.zip |
SONAR-11812 Prevent setting a directory as favorite
7 files changed, 287 insertions, 41 deletions
diff --git a/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v77/DbVersion77.java b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v77/DbVersion77.java index d7a09e032ad..39de1e9f499 100644 --- a/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v77/DbVersion77.java +++ b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v77/DbVersion77.java @@ -34,6 +34,7 @@ public class DbVersion77 implements DbVersion { .add(2604, "Add baseline columns in PROJECT_BRANCHES", AddManualBaselineToProjectBranches.class) .add(2605, "Add SNAPSHOTS.PROJECT_VERSION", AddProjectVersionToSnapshot.class) .add(2606, "Drop DATA_TYPE column from FILE_SOURCES table", DropDataTypeFromFileSources.class) - .add(2607, "Add MEMBERS_SYNC_ENABLED column to ORGANIZATIONS_ALM_BINDING table", AddMembersSyncFlagToOrgAlmBinding.class); + .add(2607, "Add MEMBERS_SYNC_ENABLED column to ORGANIZATIONS_ALM_BINDING table", AddMembersSyncFlagToOrgAlmBinding.class) + .add(2608, "Delete favorites on not supported components", DeleteFavouritesOnNotSupportedComponentQualifiers.class); } } diff --git a/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v77/DeleteFavouritesOnNotSupportedComponentQualifiers.java b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v77/DeleteFavouritesOnNotSupportedComponentQualifiers.java new file mode 100644 index 00000000000..12a314b1ec6 --- /dev/null +++ b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v77/DeleteFavouritesOnNotSupportedComponentQualifiers.java @@ -0,0 +1,50 @@ +/* + * SonarQube + * Copyright (C) 2009-2019 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.server.platform.db.migration.version.v77; + +import java.sql.SQLException; +import org.sonar.db.Database; +import org.sonar.server.platform.db.migration.SupportsBlueGreen; +import org.sonar.server.platform.db.migration.step.DataChange; +import org.sonar.server.platform.db.migration.step.MassUpdate; + +@SupportsBlueGreen +public class DeleteFavouritesOnNotSupportedComponentQualifiers extends DataChange { + + public DeleteFavouritesOnNotSupportedComponentQualifiers(Database db) { + super(db); + } + + @Override + public void execute(Context context) throws SQLException { + MassUpdate massUpdate = context.prepareMassUpdate().rowPluralName("favourites"); + massUpdate.select("SELECT prop.id FROM properties prop " + + "INNER JOIN projects p ON p.id=prop.resource_id AND p.qualifier NOT IN ('TRK', 'FIL', 'VW', 'SVW', 'APP') " + + "WHERE prop_key=? AND user_id IS NOT NULL") + .setString(1, "favourite"); + massUpdate.update("DELETE FROM properties WHERE id=?"); + massUpdate.execute((row, update) -> { + int id = row.getInt(1); + update.setInt(1, id); + return true; + }); + } + +} diff --git a/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v77/DbVersion77Test.java b/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v77/DbVersion77Test.java index 57b6c229209..23b3f6cfc17 100644 --- a/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v77/DbVersion77Test.java +++ b/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v77/DbVersion77Test.java @@ -36,7 +36,7 @@ public class DbVersion77Test { @Test public void verify_migration_count() { - verifyMigrationCount(underTest, 8); + verifyMigrationCount(underTest, 9); } } diff --git a/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v77/DeleteFavouritesOnNotSupportedComponentQualifiersTest.java b/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v77/DeleteFavouritesOnNotSupportedComponentQualifiersTest.java new file mode 100644 index 00000000000..188cf9140e9 --- /dev/null +++ b/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v77/DeleteFavouritesOnNotSupportedComponentQualifiersTest.java @@ -0,0 +1,123 @@ +/* + * SonarQube + * Copyright (C) 2009-2019 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.server.platform.db.migration.version.v77; + +import java.sql.SQLException; +import org.junit.Rule; +import org.junit.Test; +import org.sonar.db.CoreDbTester; + +import static java.util.stream.Collectors.toList; +import static org.apache.commons.lang.math.RandomUtils.nextInt; +import static org.assertj.core.api.Assertions.assertThat; + +public class DeleteFavouritesOnNotSupportedComponentQualifiersTest { + + private static final String TABLE = "properties"; + private static final String FAVOURITE_PROPERTY = "favourite"; + + @Rule + public CoreDbTester db = CoreDbTester.createForSchema(DeleteFavouritesOnNotSupportedComponentQualifiersTest.class, "schema.sql"); + + private DeleteFavouritesOnNotSupportedComponentQualifiers underTest = new DeleteFavouritesOnNotSupportedComponentQualifiers(db.database()); + + @Test + public void delete_favorites_on_none_supported_component_qualifiers() throws SQLException { + int moduleId = insertComponent("BRC"); + insertProperty(FAVOURITE_PROPERTY, moduleId); + insertProperty(FAVOURITE_PROPERTY, moduleId); + insertProperty(FAVOURITE_PROPERTY, moduleId); + int libId = insertComponent("LIB"); + insertProperty(FAVOURITE_PROPERTY, libId); + + underTest.execute(); + + assertThat(db.countRowsOfTable(TABLE)).isZero(); + } + + @Test + public void ignore_favorites_on_supported_component_qualifiers() throws SQLException { + int projectId = insertComponent("TRK"); + int prop1 = insertProperty(FAVOURITE_PROPERTY, projectId); + int fileId = insertComponent("FIL"); + int prop2 = insertProperty(FAVOURITE_PROPERTY, fileId); + int portfolioId = insertComponent("VW"); + int prop3 = insertProperty(FAVOURITE_PROPERTY, portfolioId); + int subPortfolioId = insertComponent("SVW"); + int prop4 = insertProperty(FAVOURITE_PROPERTY, subPortfolioId); + int applicationId = insertComponent("APP"); + int prop5 = insertProperty(FAVOURITE_PROPERTY, applicationId); + + underTest.execute(); + + assertProperties(prop1, prop2, prop3, prop4, prop5); + } + + @Test + public void ignore_other_properties() throws SQLException { + int moduleId = insertComponent("BRC"); + int prop1 = insertProperty("other", moduleId); + int prop2 = insertProperty("other", moduleId); + int libId = insertComponent("LIB"); + int prop3 = insertProperty("other", libId); + + underTest.execute(); + + assertProperties(prop1, prop2, prop3); + } + + private void assertProperties(Integer... expectedIds) { + assertThat(db.select("SELECT id FROM properties") + .stream() + .map(map -> ((Long) map.get("ID")).intValue()) + .collect(toList())) + .containsExactlyInAnyOrder(expectedIds); + } + + private int insertProperty(String key, int componentId) { + int id = nextInt(); + db.executeInsert( + TABLE, + "ID", id, + "PROP_KEY", key, + "USER_ID", nextInt(), + "RESOURCE_ID", componentId, + "IS_EMPTY", true, + "CREATED_AT", 123456); + return id; + } + + private int insertComponent(String qualifier) { + int id = nextInt(); + String uuid = "uuid_" + id; + db.executeInsert( + "projects", + "ID", id, + "UUID", uuid, + "ORGANIZATION_UUID", "org_" + id, + "UUID_PATH", "path_" + id, + "ROOT_UUID", "root_" + id, + "PROJECT_UUID", "project_" + id, + "QUALIFIER", qualifier, + "ENABLED", true, + "PRIVATE", false); + return id; + } +} diff --git a/server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v77/DeleteFavouritesOnNotSupportedComponentQualifiersTest/schema.sql b/server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v77/DeleteFavouritesOnNotSupportedComponentQualifiersTest/schema.sql new file mode 100644 index 00000000000..65a4b55c97e --- /dev/null +++ b/server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v77/DeleteFavouritesOnNotSupportedComponentQualifiersTest/schema.sql @@ -0,0 +1,59 @@ +CREATE TABLE "PROPERTIES" ( + "ID" INTEGER NOT NULL GENERATED BY DEFAULT AS IDENTITY (START WITH 1, INCREMENT BY 1), + "PROP_KEY" VARCHAR(512) NOT NULL, + "RESOURCE_ID" INTEGER, + "USER_ID" INTEGER, + "IS_EMPTY" BOOLEAN NOT NULL, + "TEXT_VALUE" VARCHAR(4000), + "CLOB_VALUE" CLOB, + "CREATED_AT" BIGINT +); +CREATE INDEX "PROPERTIES_KEY" ON "PROPERTIES" ("PROP_KEY"); + +CREATE TABLE "PROJECTS" ( + "ID" INTEGER NOT NULL GENERATED BY DEFAULT AS IDENTITY (START WITH 1, INCREMENT BY 1), + "ORGANIZATION_UUID" VARCHAR(40) NOT NULL, + "KEE" VARCHAR(400), + "UUID" VARCHAR(50) NOT NULL, + "UUID_PATH" VARCHAR(1500) NOT NULL, + "ROOT_UUID" VARCHAR(50) NOT NULL, + "PROJECT_UUID" VARCHAR(50) NOT NULL, + "MODULE_UUID" VARCHAR(50), + "MODULE_UUID_PATH" VARCHAR(1500), + "MAIN_BRANCH_PROJECT_UUID" VARCHAR(50), + "NAME" VARCHAR(2000), + "DESCRIPTION" VARCHAR(2000), + "PRIVATE" BOOLEAN NOT NULL, + "TAGS" VARCHAR(500), + "ENABLED" BOOLEAN NOT NULL DEFAULT TRUE, + "SCOPE" VARCHAR(3), + "QUALIFIER" VARCHAR(10), + "DEPRECATED_KEE" VARCHAR(400), + "PATH" VARCHAR(2000), + "LANGUAGE" VARCHAR(20), + "COPY_COMPONENT_UUID" VARCHAR(50), + "LONG_NAME" VARCHAR(2000), + "DEVELOPER_UUID" VARCHAR(50), + "CREATED_AT" TIMESTAMP, + "AUTHORIZATION_UPDATED_AT" BIGINT, + "B_CHANGED" BOOLEAN, + "B_COPY_COMPONENT_UUID" VARCHAR(50), + "B_DESCRIPTION" VARCHAR(2000), + "B_ENABLED" BOOLEAN, + "B_UUID_PATH" VARCHAR(1500), + "B_LANGUAGE" VARCHAR(20), + "B_LONG_NAME" VARCHAR(500), + "B_MODULE_UUID" VARCHAR(50), + "B_MODULE_UUID_PATH" VARCHAR(1500), + "B_NAME" VARCHAR(500), + "B_PATH" VARCHAR(2000), + "B_QUALIFIER" VARCHAR(10) +); + +CREATE INDEX "PROJECTS_ORGANIZATION" ON "PROJECTS" ("ORGANIZATION_UUID"); +CREATE UNIQUE INDEX "PROJECTS_KEE" ON "PROJECTS" ("KEE"); +CREATE INDEX "PROJECTS_ROOT_UUID" ON "PROJECTS" ("ROOT_UUID"); +CREATE UNIQUE INDEX "PROJECTS_UUID" ON "PROJECTS" ("UUID"); +CREATE INDEX "PROJECTS_PROJECT_UUID" ON "PROJECTS" ("PROJECT_UUID"); +CREATE INDEX "PROJECTS_MODULE_UUID" ON "PROJECTS" ("MODULE_UUID"); +CREATE INDEX "PROJECTS_QUALIFIER" ON "PROJECTS" ("QUALIFIER"); diff --git a/server/sonar-server/src/main/java/org/sonar/server/favorite/ws/AddAction.java b/server/sonar-server/src/main/java/org/sonar/server/favorite/ws/AddAction.java index d8edb28f307..7523ae174db 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/favorite/ws/AddAction.java +++ b/server/sonar-server/src/main/java/org/sonar/server/favorite/ws/AddAction.java @@ -19,12 +19,12 @@ */ package org.sonar.server.favorite.ws; +import java.util.List; import java.util.function.Consumer; import org.sonar.api.server.ws.Change; import org.sonar.api.server.ws.Request; import org.sonar.api.server.ws.Response; import org.sonar.api.server.ws.WebService; -import org.sonar.api.web.UserRole; import org.sonar.db.DbClient; import org.sonar.db.DbSession; import org.sonar.db.component.ComponentDto; @@ -33,9 +33,18 @@ import org.sonar.server.favorite.FavoriteUpdater; import org.sonar.server.user.UserSession; import org.sonar.server.ws.KeyExamples; +import static com.google.common.base.Preconditions.checkArgument; +import static java.lang.String.format; +import static java.lang.String.join; +import static java.util.Arrays.asList; +import static org.sonar.api.web.UserRole.USER; import static org.sonar.server.favorite.ws.FavoritesWsParameters.PARAM_COMPONENT; public class AddAction implements FavoritesWsAction { + + private static final List<String> SUPPORTED_QUALIFIERS = asList("TRK", "VW", "APP", "SVW", "FIL"); + private static final String SUPPORTED_QUALIFIERS_AS_STRING = join(", ", SUPPORTED_QUALIFIERS); + private final UserSession userSession; private final DbClient dbClient; private final FavoriteUpdater favoriteUpdater; @@ -51,15 +60,17 @@ public class AddAction implements FavoritesWsAction { @Override public void define(WebService.NewController context) { WebService.NewAction action = context.createAction("add") - .setDescription("Add a component (project, directory, file etc.) as favorite for the authenticated user.<br>" + + .setDescription("Add a component (project, file etc.) as favorite for the authenticated user.<br>" + "Requires authentication and the following permission: 'Browse' on the project of the specified component.") .setSince("6.3") - .setChangelog(new Change("7.6", String.format("The use of module keys in parameter '%s' is deprecated", PARAM_COMPONENT))) + .setChangelog( + new Change("7.7", "It's no more possible to set a directory as favorite"), + new Change("7.6", format("The use of module keys in parameter '%s' is deprecated", PARAM_COMPONENT))) .setPost(true) .setHandler(this); action.createParam(PARAM_COMPONENT) - .setDescription("Component key") + .setDescription(format("Component key. Only components with qualifiers %s are supported", SUPPORTED_QUALIFIERS_AS_STRING)) .setRequired(true) .setExampleValue(KeyExamples.KEY_FILE_EXAMPLE_001); } @@ -74,9 +85,10 @@ public class AddAction implements FavoritesWsAction { return request -> { try (DbSession dbSession = dbClient.openSession(false)) { ComponentDto componentDto = componentFinder.getByKey(dbSession, request.mandatoryParam(PARAM_COMPONENT)); + checkArgument(SUPPORTED_QUALIFIERS.contains(componentDto.qualifier()), "Only components with qualifiers %s are supported", SUPPORTED_QUALIFIERS_AS_STRING); userSession .checkLoggedIn() - .checkComponentPermission(UserRole.USER, componentDto); + .checkComponentPermission(USER, componentDto); favoriteUpdater.add(dbSession, componentDto, userSession.isLoggedIn() ? userSession.getUserId() : null); dbSession.commit(); } diff --git a/server/sonar-server/src/test/java/org/sonar/server/favorite/ws/AddActionTest.java b/server/sonar-server/src/test/java/org/sonar/server/favorite/ws/AddActionTest.java index 0b7e75a2800..da1b5b18b68 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/favorite/ws/AddActionTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/favorite/ws/AddActionTest.java @@ -33,6 +33,7 @@ import org.sonar.db.component.ComponentDto; import org.sonar.db.organization.OrganizationDto; import org.sonar.db.property.PropertyDto; import org.sonar.db.property.PropertyQuery; +import org.sonar.db.user.UserDto; import org.sonar.server.component.TestComponentFinder; import org.sonar.server.exceptions.ForbiddenException; import org.sonar.server.exceptions.NotFoundException; @@ -47,14 +48,12 @@ import static java.lang.String.format; import static java.net.HttpURLConnection.HTTP_NO_CONTENT; import static java.util.Optional.ofNullable; import static org.assertj.core.api.Assertions.assertThat; +import static org.sonar.api.web.UserRole.USER; +import static org.sonar.db.component.ComponentTesting.newDirectory; import static org.sonar.db.component.ComponentTesting.newFileDto; -import static org.sonar.db.component.ComponentTesting.newPrivateProjectDto; import static org.sonar.server.favorite.ws.FavoritesWsParameters.PARAM_COMPONENT; public class AddActionTest { - private static final String PROJECT_KEY = "project-key"; - private static final String PROJECT_UUID = "project-uuid"; - private static final int USER_ID = 123; @Rule public ExpectedException expectedException = ExpectedException.none(); @@ -71,50 +70,52 @@ public class AddActionTest { @Test public void add_a_project() { - ComponentDto project = insertProjectAndPermissions(); + ComponentDto project = db.components().insertPrivateProject(); + UserDto user = db.users().insertUser(); + userSession.logIn(user).addProjectPermission(USER, project); - TestResponse result = call(PROJECT_KEY); + TestResponse result = call(project.getKey()); assertThat(result.getStatus()).isEqualTo(HTTP_NO_CONTENT); List<PropertyDto> favorites = dbClient.propertiesDao().selectByQuery(PropertyQuery.builder() - .setUserId(USER_ID) + .setUserId(user.getId()) .setKey("favourite") .build(), dbSession); assertThat(favorites).hasSize(1); PropertyDto favorite = favorites.get(0); assertThat(favorite) .extracting(PropertyDto::getResourceId, PropertyDto::getUserId, PropertyDto::getKey) - .containsOnly(project.getId(), USER_ID, "favourite"); + .containsOnly(project.getId(), user.getId(), "favourite"); } @Test public void add_a_file() { - ComponentDto project = insertProjectAndPermissions(); + ComponentDto project = db.components().insertPrivateProject(); ComponentDto file = db.components().insertComponent(newFileDto(project)); - userSession.addProjectPermission(UserRole.USER, project, file); + UserDto user = db.users().insertUser(); + userSession.logIn(user).addProjectPermission(USER, project); - call(file.getDbKey()); + call(file.getKey()); List<PropertyDto> favorites = dbClient.propertiesDao().selectByQuery(PropertyQuery.builder() - .setUserId(USER_ID) + .setUserId(user.getId()) .setKey("favourite") .build(), dbSession); assertThat(favorites).hasSize(1); PropertyDto favorite = favorites.get(0); assertThat(favorite) .extracting(PropertyDto::getResourceId, PropertyDto::getUserId, PropertyDto::getKey) - .containsOnly(file.getId(), USER_ID, "favourite"); + .containsOnly(file.getId(), user.getId(), "favourite"); } @Test public void fail_when_no_browse_permission_on_the_project() { - ComponentDto project = insertProject(); - userSession.logIn(); - userSession.addProjectPermission(UserRole.ADMIN, project); + ComponentDto project = db.components().insertPrivateProject(); + userSession.logIn().addProjectPermission(UserRole.ADMIN, project); expectedException.expect(ForbiddenException.class); - call(PROJECT_KEY); + call(project.getKey()); } @Test @@ -128,19 +129,20 @@ public class AddActionTest { @Test public void fail_when_user_is_not_authenticated() { - insertProject(); + ComponentDto project = db.components().insertPrivateProject(); expectedException.expect(UnauthorizedException.class); - call(PROJECT_KEY); + call(project.getKey()); } @Test public void fail_when_using_branch_db_key() throws Exception { OrganizationDto organization = db.organizations().insert(); ComponentDto project = db.components().insertMainBranch(organization); - userSession.logIn().addProjectPermission(UserRole.USER, project); ComponentDto branch = db.components().insertProjectBranch(project); + UserDto user = db.users().insertUser(); + userSession.logIn(user).addProjectPermission(USER, project); expectedException.expect(NotFoundException.class); expectedException.expectMessage(format("Component key '%s' not found", branch.getDbKey())); @@ -149,6 +151,19 @@ public class AddActionTest { } @Test + public void fail_on_directory() { + ComponentDto project = db.components().insertPrivateProject(); + ComponentDto directory = db.components().insertComponent(newDirectory(project, "dir")); + UserDto user = db.users().insertUser(); + userSession.logIn(user).addProjectPermission(USER, project); + + expectedException.expect(IllegalArgumentException.class); + expectedException.expectMessage("Only components with qualifiers TRK, VW, APP, SVW, FIL are supported"); + + call(directory.getKey()); + } + + @Test public void definition() { WebService.Action definition = ws.getDef(); @@ -157,20 +172,6 @@ public class AddActionTest { assertThat(definition.param("component").isRequired()).isTrue(); } - private ComponentDto insertProject() { - return db.components().insertComponent(newPrivateProjectDto(db.organizations().insert(), PROJECT_UUID).setDbKey(PROJECT_KEY)); - } - - private ComponentDto insertProjectAndPermissions() { - ComponentDto project = insertProject(); - userSession - .logIn() - .setUserId(USER_ID) - .addProjectPermission(UserRole.USER, project); - - return project; - } - private TestResponse call(@Nullable String componentKey) { TestRequest request = ws.newRequest(); ofNullable(componentKey).ifPresent(c -> request.setParam(PARAM_COMPONENT, c)); |