From 628a25dc393859c3e0aa6a4ceec8047a223ab28e Mon Sep 17 00:00:00 2001 From: Teryk Bellahsene Date: Thu, 22 Jun 2017 13:09:11 +0200 Subject: [PATCH] SONAR-9448 Sanitize api/qualityprofiles/export - add parameter 'profile' as the main parameter to identify a quality profile - deprecate parameters 'name' et 'language' --- .../qualityprofile/ws/ExportAction.java | 52 +++++++++++----- .../qualityprofile/ws/ExportActionTest.java | 59 +++++++++++++++++-- 2 files changed, 91 insertions(+), 20 deletions(-) diff --git a/server/sonar-server/src/main/java/org/sonar/server/qualityprofile/ws/ExportAction.java b/server/sonar-server/src/main/java/org/sonar/server/qualityprofile/ws/ExportAction.java index 0b1f1b228fd..5194c3c9764 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/qualityprofile/ws/ExportAction.java +++ b/server/sonar-server/src/main/java/org/sonar/server/qualityprofile/ws/ExportAction.java @@ -45,14 +45,18 @@ import org.sonar.server.util.LanguageParamUtils; import org.sonarqube.ws.MediaTypes; import static java.nio.charset.StandardCharsets.UTF_8; +import static org.sonar.core.util.Uuids.UUID_EXAMPLE_01; +import static org.sonar.server.qualityprofile.ws.QProfileWsSupport.createOrganizationParam; import static org.sonar.server.ws.WsUtils.checkFound; +import static org.sonar.server.ws.WsUtils.checkRequest; import static org.sonarqube.ws.client.component.ComponentsWsParameters.PARAM_ORGANIZATION; +import static org.sonarqube.ws.client.qualityprofile.QualityProfileWsParameters.PARAM_LANGUAGE; +import static org.sonarqube.ws.client.qualityprofile.QualityProfileWsParameters.PARAM_PROFILE; public class ExportAction implements QProfileWsAction { - private static final String PARAM_PROFILE_NAME = "name"; - private static final String PARAM_LANGUAGE = "language"; - private static final String PARAM_FORMAT = "exporterKey"; + private static final String PARAM_NAME = "name"; + private static final String PARAM_EXPORTER_KEY = "exporterKey"; private final DbClient dbClient; private final QProfileBackuper backuper; @@ -76,15 +80,25 @@ public class ExportAction implements QProfileWsAction { .setResponseExample(getClass().getResource("export-example.xml")) .setHandler(this); - action.createParam(PARAM_PROFILE_NAME) - .setDescription("The name of the quality profile to export. If left empty, will export the default profile for the language.") + action.createParam(PARAM_PROFILE) + .setDescription("Quality profile key") + .setSince("6.5") + .setExampleValue(UUID_EXAMPLE_01); + + action.createParam(PARAM_NAME) + .setDescription("Quality profile name to export. If left empty, the default profile for the language is exported. If this parameter is set, '%s' must not be set.", + PARAM_PROFILE) + .setDeprecatedSince("6.5") .setExampleValue("My Sonar way"); action.createParam(PARAM_LANGUAGE) - .setDescription("The language for the quality profile.") + .setDescription("Quality profile language. If this parameter is set, '%s' must not be set.", PARAM_PROFILE) + .setDeprecatedSince("6.5") .setExampleValue(LanguageParamUtils.getExampleValue(languages)) - .setPossibleValues(LanguageParamUtils.getLanguageKeys(languages)) - .setRequired(true); + .setPossibleValues(LanguageParamUtils.getLanguageKeys(languages)); + + createOrganizationParam(action) + .setSince("6.4"); Set exporterKeys = Arrays.stream(languages.all()) .map(language -> exporters.exportersForLanguage(language.getKey())) @@ -92,26 +106,26 @@ public class ExportAction implements QProfileWsAction { .map(ProfileExporter::getKey) .collect(MoreCollectors.toSet()); if (!exporterKeys.isEmpty()) { - action.createParam(PARAM_FORMAT) + action.createParam(PARAM_EXPORTER_KEY) .setDescription("Output format. If left empty, the same format as api/qualityprofiles/backup is used. " + "Possible values are described by api/qualityprofiles/exporters.") .setPossibleValues(exporterKeys) // This deprecated key is only there to be able to deal with redirection from /profiles/export .setDeprecatedKey("format", "6.3"); } - - QProfileWsSupport.createOrganizationParam(action).setSince("6.4"); } @Override public void handle(Request request, Response response) throws Exception { - String name = request.param(PARAM_PROFILE_NAME); - String language = request.mandatoryParam(PARAM_LANGUAGE); - String exporterKey = exporters.exportersForLanguage(language).isEmpty() ? null : request.param(PARAM_FORMAT); + String key = request.param(PARAM_PROFILE); + String name = request.param(PARAM_NAME); + String language = request.param(PARAM_LANGUAGE); + checkRequest(key != null ^ language != null, "Either '%s' or '%s' must be provided.", PARAM_PROFILE, PARAM_LANGUAGE); try (DbSession dbSession = dbClient.openSession(false)) { OrganizationDto organization = wsSupport.getOrganizationByKey(dbSession, request.param(PARAM_ORGANIZATION)); - QProfileDto profile = loadProfile(dbSession, organization, language, name); + QProfileDto profile = loadProfile(dbSession, organization, key, language, name); + String exporterKey = exporters.exportersForLanguage(profile.getLanguage()).isEmpty() ? null : request.param(PARAM_EXPORTER_KEY); writeResponse(dbSession, profile, exporterKey, response); } } @@ -131,8 +145,14 @@ public class ExportAction implements QProfileWsAction { } } - private QProfileDto loadProfile(DbSession dbSession, OrganizationDto organization, String language, @Nullable String name) { + private QProfileDto loadProfile(DbSession dbSession, OrganizationDto organization, @Nullable String key, @Nullable String language, @Nullable String name) { QProfileDto profile; + if (key != null) { + profile = dbClient.qualityProfileDao().selectByUuid(dbSession, key); + return checkFound(profile, "Could not find profile with key '%s'", key); + } + + checkRequest(language != null, "Parameter '%s' must be provided", PARAM_LANGUAGE); if (name == null) { // return the default profile profile = dbClient.qualityProfileDao().selectDefaultProfile(dbSession, organization, language); diff --git a/server/sonar-server/src/test/java/org/sonar/server/qualityprofile/ws/ExportActionTest.java b/server/sonar-server/src/test/java/org/sonar/server/qualityprofile/ws/ExportActionTest.java index 397a0b4b76b..269debcf3be 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/qualityprofile/ws/ExportActionTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/qualityprofile/ws/ExportActionTest.java @@ -36,6 +36,7 @@ import org.sonar.db.DbSession; import org.sonar.db.DbTester; import org.sonar.db.organization.OrganizationDto; import org.sonar.db.qualityprofile.QProfileDto; +import org.sonar.server.exceptions.BadRequestException; import org.sonar.server.exceptions.NotFoundException; import org.sonar.server.language.LanguageTesting; import org.sonar.server.organization.TestDefaultOrganizationProvider; @@ -47,6 +48,8 @@ import org.sonar.server.ws.WsActionTester; import static java.lang.String.format; import static org.assertj.core.api.Assertions.assertThat; +import static org.sonarqube.ws.client.qualityprofile.QualityProfileWsParameters.PARAM_LANGUAGE; +import static org.sonarqube.ws.client.qualityprofile.QualityProfileWsParameters.PARAM_PROFILE; public class ExportActionTest { @@ -65,24 +68,30 @@ public class ExportActionTest { private QProfileWsSupport wsSupport = new QProfileWsSupport(dbClient, userSession, TestDefaultOrganizationProvider.from(db)); @Test - public void test_definition_without_exporters() { + public void definition_without_exporters() { WebService.Action definition = newWsActionTester().getDef(); assertThat(definition.isPost()).isFalse(); assertThat(definition.isInternal()).isFalse(); - assertThat(definition.params()).extracting("key").containsOnly("language", "name", "organization"); + assertThat(definition.params()).extracting(WebService.Param::key).containsExactlyInAnyOrder("profile", "language", "name", "organization"); WebService.Param organizationParam = definition.param("organization"); assertThat(organizationParam.since()).isEqualTo("6.4"); assertThat(organizationParam.isInternal()).isTrue(); + WebService.Param profile = definition.param("profile"); + assertThat(profile.since()).isEqualTo("6.5"); + WebService.Param name = definition.param("name"); + assertThat(name.deprecatedSince()).isEqualTo("6.5"); + WebService.Param language = definition.param("language"); + assertThat(language.deprecatedSince()).isEqualTo("6.5"); } @Test - public void test_definition_with_exporters() { + public void definition_with_exporters() { WebService.Action definition = newWsActionTester(newExporter("polop"), newExporter("palap")).getDef(); assertThat(definition.isPost()).isFalse(); assertThat(definition.isInternal()).isFalse(); - assertThat(definition.params()).extracting("key").containsOnly("language", "name", "organization", "exporterKey"); + assertThat(definition.params()).extracting("key").containsExactlyInAnyOrder("profile", "language", "name", "organization", "exporterKey"); WebService.Param exportersParam = definition.param("exporterKey"); assertThat(exportersParam.possibleValues()).containsOnly("polop", "palap"); assertThat(exportersParam.deprecatedKey()).isEqualTo("format"); @@ -90,6 +99,46 @@ public class ExportActionTest { assertThat(exportersParam.isInternal()).isFalse(); } + @Test + public void export_profile_with_key() { + QProfileDto profile = createProfile(db.getDefaultOrganization(), false); + + WsActionTester tester = newWsActionTester(newExporter("polop"), newExporter("palap")); + String result = tester.newRequest() + .setParam(PARAM_PROFILE, profile.getKee()) + .setParam("exporterKey", "polop").execute() + .getInput(); + + assertThat(result).isEqualTo("Profile " + profile.getLanguage() + "/" + profile.getName() + " exported by polop"); + } + + @Test + public void fail_if_profile_key_is_unknown() { + expectedException.expect(NotFoundException.class); + expectedException.expectMessage("Could not find profile with key 'PROFILE-KEY-404'"); + + WsActionTester ws = newWsActionTester(newExporter("polop"), newExporter("palap")); + ws.newRequest() + .setParam(PARAM_PROFILE, "PROFILE-KEY-404") + .setParam("exporterKey", "polop").execute() + .getInput(); + } + + @Test + public void fail_if_profile_key_and_language_provided() { + QProfileDto profile = createProfile(db.getDefaultOrganization(), false); + + expectedException.expect(BadRequestException.class); + expectedException.expectMessage("Either 'profile' or 'language' must be provided."); + + WsActionTester ws = newWsActionTester(newExporter("polop"), newExporter("palap")); + ws.newRequest() + .setParam(PARAM_PROFILE, profile.getKee()) + .setParam(PARAM_LANGUAGE, profile.getLanguage()) + .setParam("exporterKey", "polop").execute() + .getInput(); + } + @Test public void export_profile_in_default_organization() { QProfileDto profile = createProfile(db.getDefaultOrganization(), false); @@ -161,6 +210,8 @@ public class ExportActionTest { @Test public void throw_IAE_if_export_with_specified_key_does_not_exist() throws Exception { + QProfileDto profile = createProfile(db.getDefaultOrganization(), true); + expectedException.expect(IllegalArgumentException.class); expectedException.expectMessage("Value of parameter 'exporterKey' (unknown) must be one of: [polop, palap]"); -- 2.39.5