diff options
author | Michal Duda <michal.duda@sonarsource.com> | 2020-06-26 13:53:27 +0200 |
---|---|---|
committer | sonartech <sonartech@sonarsource.com> | 2020-06-30 20:05:42 +0000 |
commit | cf787e3f6ab12481d3e75a8a8af77d1d7edc8f6f (patch) | |
tree | d9cf4eb28fe34cf528f3dc71c3d55f1ae7b4cb2b /server | |
parent | b3840f39050b6a805c321748c84726038ccb3f6a (diff) | |
download | sonarqube-cf787e3f6ab12481d3e75a8a8af77d1d7edc8f6f.tar.gz sonarqube-cf787e3f6ab12481d3e75a8a8af77d1d7edc8f6f.zip |
SONAR-13341 fix SSF-110
Diffstat (limited to 'server')
11 files changed, 399 insertions, 49 deletions
diff --git a/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v84/DbVersion84.java b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v84/DbVersion84.java index 490e434c402..040d8d14711 100644 --- a/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v84/DbVersion84.java +++ b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v84/DbVersion84.java @@ -780,6 +780,8 @@ public class DbVersion84 implements DbVersion { .add(3803, "Add 'need_issue_sync' column to 'project_branches' table", AddProjectBranchesNeedIssueSync.class) .add(3804, "Populate 'need_issue_sync' of 'project_branches'", PopulateProjectBranchesNeedIssueSync.class) .add(3805, "Make 'need_issue_sync' of 'project_branches' not null", MakeProjectBranchesNeedIssueSyncNonNull.class) + + .add(3806, "Drop local webhooks", DropLocalWebhooks.class) ; } } diff --git a/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v84/DropLocalWebhooks.java b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v84/DropLocalWebhooks.java new file mode 100644 index 00000000000..6f6f1bc5cd8 --- /dev/null +++ b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v84/DropLocalWebhooks.java @@ -0,0 +1,72 @@ +/* + * SonarQube + * Copyright (C) 2009-2020 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.v84; + +import java.net.InetAddress; +import java.net.MalformedURLException; +import java.net.URL; +import java.net.UnknownHostException; +import java.sql.SQLException; +import org.sonar.api.utils.log.Logger; +import org.sonar.api.utils.log.Loggers; +import org.sonar.db.Database; +import org.sonar.server.platform.db.migration.step.DataChange; +import org.sonar.server.platform.db.migration.step.MassUpdate; + +public class DropLocalWebhooks extends DataChange { + private static final Logger LOG = Loggers.get(DropLocalWebhooks.class); + + public DropLocalWebhooks(Database db) { + super(db); + } + + @Override + protected void execute(Context context) throws SQLException { + MassUpdate massUpdate = context.prepareMassUpdate(); + massUpdate.select("select w.uuid, w.name, w.url, w.project_uuid, p.name from webhooks w left join projects p on p.uuid = w.project_uuid"); + massUpdate.update("delete from webhooks where uuid = ?"); + massUpdate.execute((row, update) -> { + try { + String webhookName = row.getString(2); + String webhookUrl = row.getString(3); + URL url = new URL(webhookUrl); + InetAddress address = InetAddress.getByName(url.getHost()); + if (address.isLoopbackAddress() || address.isAnyLocalAddress()) { + boolean projectLevel = row.getString(4) != null; + if (projectLevel) { + String projectName = row.getString(5); + LOG.warn("Webhook '{}' for project '{}' has been removed because it used an invalid, unsafe URL. Please recreate " + + "this webhook with a valid URL or ask a project administrator to do it if it is still needed.", webhookName, projectName); + } else { + LOG.warn("Global webhook '{}' has been removed because it used an invalid, unsafe URL. Please recreate this webhook with a valid URL" + + " if it is still needed.", webhookName); + } + + update.setString(1, row.getString(1)); + return true; + } + } catch (MalformedURLException | UnknownHostException e) { + return false; + } + + return false; + }); + } +} diff --git a/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v84/DropLocalWebhooksTest.java b/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v84/DropLocalWebhooksTest.java new file mode 100644 index 00000000000..930518f22f3 --- /dev/null +++ b/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v84/DropLocalWebhooksTest.java @@ -0,0 +1,132 @@ +/* + * SonarQube + * Copyright (C) 2009-2020 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.v84; + +import java.sql.SQLException; +import java.util.List; +import javax.annotation.Nullable; +import org.junit.Rule; +import org.junit.Test; +import org.sonar.api.utils.System2; +import org.sonar.api.utils.log.LogTester; +import org.sonar.api.utils.log.LoggerLevel; +import org.sonar.db.CoreDbTester; +import org.sonar.server.platform.db.migration.step.DataChange; + +import static org.assertj.core.api.Assertions.assertThat; + +public class DropLocalWebhooksTest { + + @Rule + public LogTester logTester = new LogTester(); + + private static final String TABLE_NAME = "webhooks"; + + @Rule + public CoreDbTester dbTester = CoreDbTester.createForSchema(DropLocalWebhooksTest.class, "schema.sql"); + + private final DataChange underTest = new DropLocalWebhooks(dbTester.database()); + + @Test + public void execute() throws SQLException { + prepareWebhooks(); + + underTest.execute(); + + verifyMigrationResult(); + } + + @Test + public void migrationIsReEntrant() throws SQLException { + prepareWebhooks(); + + underTest.execute(); + underTest.execute(); + + verifyMigrationResult(); + } + + @Test + public void migrationIsSuccessfulWhenNoWebhooksDeleted() throws SQLException { + insertProject("p1", "pn1"); + insertWebhook("uuid-1", "https://10.15.15.15:5555/some_webhook", "p1"); + insertWebhook("uuid-5", "https://some.valid.address.com/random_webhook", null); + + underTest.execute(); + + assertThat(dbTester.countRowsOfTable(TABLE_NAME)).isEqualTo(2); + assertThat(logTester.logs(LoggerLevel.WARN)).isEmpty(); + } + + @Test + public void migrationIsSuccessfulWhenNoWebhooksInDb() throws SQLException { + insertProject("p1", "pn1"); + + underTest.execute(); + + assertThat(dbTester.countRowsOfTable(TABLE_NAME)).isZero(); + assertThat(logTester.logs(LoggerLevel.WARN)).isEmpty(); + } + + private void prepareWebhooks() { + insertProject("p1", "pn1"); + insertProject("p2", "pn2"); + insertWebhook("uuid-1", "https://10.15.15.15:5555/some_webhook", "p1"); + insertWebhook("uuid-2", "https://0.0.0.0/some_webhook", "p1"); + insertWebhook("uuid-3", "https://172.16.16.16:6666/some_webhook", "p2"); + insertWebhook("uuid-4", "https://127.0.0.1/some_webhook", "p2"); + insertWebhook("uuid-5", "https://some.valid.address.com/random_webhook", null); + insertWebhook("uuid-6", "https://248.235.76.254:7777/some_webhook", null); + insertWebhook("uuid-7", "https://localhost/some_webhook", null); + } + + private void verifyMigrationResult() { + assertThat(dbTester.countRowsOfTable(TABLE_NAME)).isEqualTo(4); + assertThat(dbTester.select("select uuid from " + TABLE_NAME).stream().map(columns -> columns.get("UUID"))) + .containsOnly("uuid-1", "uuid-3", "uuid-5", "uuid-6"); + + List<String> logs = logTester.logs(LoggerLevel.WARN); + assertThat(logs).hasSize(3); + assertThat(logs).containsExactlyInAnyOrder( + "Global webhook 'webhook-uuid-7' has been removed because it used an invalid, unsafe URL. Please recreate this webhook with a valid URL if it is still needed.", + "Webhook 'webhook-uuid-4' for project 'pn2' has been removed because it used an invalid, unsafe URL. Please recreate this webhook with a valid URL or ask a project administrator to do it if it is still needed.", + "Webhook 'webhook-uuid-2' for project 'pn1' has been removed because it used an invalid, unsafe URL. Please recreate this webhook with a valid URL or ask a project administrator to do it if it is still needed."); + } + + private void insertProject(String uuid, String name) { + dbTester.executeInsert("PROJECTS", + "NAME", name, + "ORGANIZATION_UUID", "default", + "KEE", uuid + "-key", + "UUID", uuid, + "PRIVATE", Boolean.toString(false), + "QUALIFIER", "TRK", + "UPDATED_AT", System2.INSTANCE.now()); + } + + private void insertWebhook(String uuid, String url, @Nullable String projectUuid) { + dbTester.executeInsert(TABLE_NAME, + "UUID", uuid, + "NAME", "webhook-" + uuid, + "PROJECT_UUID", projectUuid, + "URL", url, + "CREATED_AT", System2.INSTANCE.now()); + } +} diff --git a/server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v84/DropLocalWebhooksTest/schema.sql b/server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v84/DropLocalWebhooksTest/schema.sql new file mode 100644 index 00000000000..de11e9e16ed --- /dev/null +++ b/server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v84/DropLocalWebhooksTest/schema.sql @@ -0,0 +1,29 @@ +CREATE TABLE "PROJECTS"( + "UUID" VARCHAR(40) NOT NULL, + "KEE" VARCHAR(400) NOT NULL, + "QUALIFIER" VARCHAR(10) NOT NULL, + "ORGANIZATION_UUID" VARCHAR(40) NOT NULL, + "NAME" VARCHAR(2000), + "DESCRIPTION" VARCHAR(2000), + "PRIVATE" BOOLEAN NOT NULL, + "TAGS" VARCHAR(500), + "CREATED_AT" BIGINT, + "UPDATED_AT" BIGINT NOT NULL +); +ALTER TABLE "PROJECTS" ADD CONSTRAINT "PK_NEW_PROJECTS" PRIMARY KEY("UUID"); +CREATE UNIQUE INDEX "UNIQ_PROJECTS_KEE" ON "PROJECTS"("KEE"); +CREATE INDEX "IDX_QUALIFIER" ON "PROJECTS"("QUALIFIER"); + +CREATE TABLE "WEBHOOKS"( + "UUID" VARCHAR(40) NOT NULL, + "ORGANIZATION_UUID" VARCHAR(40), + "PROJECT_UUID" VARCHAR(40), + "NAME" VARCHAR(100) NOT NULL, + "URL" VARCHAR(2000) NOT NULL, + "SECRET" VARCHAR(200), + "CREATED_AT" BIGINT NOT NULL, + "UPDATED_AT" BIGINT +); +ALTER TABLE "WEBHOOKS" ADD CONSTRAINT "PK_WEBHOOKS" PRIMARY KEY("UUID"); +CREATE INDEX "ORGANIZATION_WEBHOOK" ON "WEBHOOKS"("ORGANIZATION_UUID"); +CREATE INDEX "PROJECT_WEBHOOK" ON "WEBHOOKS"("PROJECT_UUID"); diff --git a/server/sonar-process/src/main/java/org/sonar/process/ProcessProperties.java b/server/sonar-process/src/main/java/org/sonar/process/ProcessProperties.java index 45d01f448f2..ec4288bc663 100644 --- a/server/sonar-process/src/main/java/org/sonar/process/ProcessProperties.java +++ b/server/sonar-process/src/main/java/org/sonar/process/ProcessProperties.java @@ -129,6 +129,7 @@ public class ProcessProperties { SONAR_WEB_SSO_REFRESH_INTERVAL_IN_MINUTES("sonar.web.sso.refreshIntervalInMinutes", "5"), SONAR_SECURITY_REALM("sonar.security.realm"), SONAR_AUTHENTICATOR_IGNORE_STARTUP_FAILURE("sonar.authenticator.ignoreStartupFailure", "false"), + SONAR_VALIDATE_WEBHOOKS("sonar.validateWebhooks", Boolean.TRUE.toString()), LDAP_SERVERS("ldap.servers"), LDAP_URL("ldap.url"), diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/webhook/ws/WebhookSupport.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/webhook/ws/WebhookSupport.java index a18987be054..013dbcb58e8 100644 --- a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/webhook/ws/WebhookSupport.java +++ b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/webhook/ws/WebhookSupport.java @@ -19,6 +19,10 @@ */ package org.sonar.server.webhook.ws; +import java.net.InetAddress; +import java.net.UnknownHostException; +import okhttp3.HttpUrl; +import org.sonar.api.config.Configuration; import org.sonar.db.organization.OrganizationDto; import org.sonar.db.project.ProjectDto; import org.sonar.server.exceptions.NotFoundException; @@ -27,13 +31,16 @@ import org.sonar.server.user.UserSession; import static java.lang.String.format; import static org.sonar.api.web.UserRole.ADMIN; import static org.sonar.db.permission.OrganizationPermission.ADMINISTER; +import static org.sonar.process.ProcessProperties.Property.SONAR_VALIDATE_WEBHOOKS; public class WebhookSupport { private final UserSession userSession; + private final Configuration configuration; - public WebhookSupport(UserSession userSession) { + public WebhookSupport(UserSession userSession, Configuration configuration) { this.userSession = userSession; + this.configuration = configuration; } void checkPermission(ProjectDto projectDto) { @@ -45,8 +52,19 @@ public class WebhookSupport { } void checkUrlPattern(String url, String message, Object... messageArguments) { - if (okhttp3.HttpUrl.parse(url) == null) { - throw new IllegalArgumentException(format(message, messageArguments)); + try { + HttpUrl okUrl = HttpUrl.parse(url); + if (okUrl == null) { + throw new IllegalArgumentException(String.format(message, messageArguments)); + } + InetAddress address = InetAddress.getByName(okUrl.host()); + if (configuration.getBoolean(SONAR_VALIDATE_WEBHOOKS.getKey()).orElse(true) + && (address.isLoopbackAddress() || address.isAnyLocalAddress())) { + throw new IllegalArgumentException("Invalid URL"); + } + } catch (UnknownHostException e) { + // if a host can not be resolved the deliveries will fail - no need to block it from being set + // this will only happen for public URLs } } diff --git a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/webhook/ws/CreateActionTest.java b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/webhook/ws/CreateActionTest.java index 95004f6d9f7..80a61729171 100644 --- a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/webhook/ws/CreateActionTest.java +++ b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/webhook/ws/CreateActionTest.java @@ -22,6 +22,7 @@ package org.sonar.server.webhook.ws; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; +import org.sonar.api.config.Configuration; import org.sonar.api.server.ws.WebService; import org.sonar.core.util.UuidFactory; import org.sonar.core.util.UuidFactoryFast; @@ -46,6 +47,7 @@ import static java.lang.String.format; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.AssertionsForClassTypes.tuple; import static org.junit.rules.ExpectedException.none; +import static org.mockito.Mockito.mock; import static org.sonar.api.web.UserRole.ADMIN; import static org.sonar.db.DbTester.create; import static org.sonar.db.permission.OrganizationPermission.ADMINISTER; @@ -68,18 +70,17 @@ public class CreateActionTest { @Rule public DbTester db = create(); - private DbClient dbClient = db.getDbClient(); - private WebhookDbTester webhookDbTester = db.webhooks(); - private OrganizationDbTester organizationDbTester = db.organizations(); - private ComponentDbTester componentDbTester = db.components(); - - private DefaultOrganizationProvider defaultOrganizationProvider = from(db); - private UuidFactory uuidFactory = UuidFactoryFast.getInstance(); - - private WebhookSupport webhookSupport = new WebhookSupport(userSession); - private ComponentFinder componentFinder = new ComponentFinder(dbClient, null); - private org.sonar.server.webhook.ws.CreateAction underTest = new CreateAction(dbClient, userSession, defaultOrganizationProvider, uuidFactory, webhookSupport, componentFinder); - private WsActionTester wsActionTester = new WsActionTester(underTest); + private final DbClient dbClient = db.getDbClient(); + private final WebhookDbTester webhookDbTester = db.webhooks(); + private final OrganizationDbTester organizationDbTester = db.organizations(); + private final ComponentDbTester componentDbTester = db.components(); + private final DefaultOrganizationProvider defaultOrganizationProvider = from(db); + private final UuidFactory uuidFactory = UuidFactoryFast.getInstance(); + private final Configuration configuration = mock(Configuration.class); + private final WebhookSupport webhookSupport = new WebhookSupport(userSession, configuration); + private final ComponentFinder componentFinder = new ComponentFinder(dbClient, null); + private final CreateAction underTest = new CreateAction(dbClient, userSession, defaultOrganizationProvider, uuidFactory, webhookSupport, componentFinder); + private final WsActionTester wsActionTester = new WsActionTester(underTest); @Test public void test_ws_definition() { diff --git a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/webhook/ws/DeleteActionTest.java b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/webhook/ws/DeleteActionTest.java index 435254cbd1d..ced3736ce2a 100644 --- a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/webhook/ws/DeleteActionTest.java +++ b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/webhook/ws/DeleteActionTest.java @@ -23,12 +23,12 @@ import java.util.Optional; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; +import org.sonar.api.config.Configuration; import org.sonar.api.server.ws.WebService; import org.sonar.db.DbClient; import org.sonar.db.DbSession; import org.sonar.db.DbTester; import org.sonar.db.component.ComponentDbTester; -import org.sonar.db.component.ComponentDto; import org.sonar.db.organization.OrganizationDbTester; import org.sonar.db.organization.OrganizationDto; import org.sonar.db.project.ProjectDto; @@ -48,6 +48,7 @@ import static java.net.HttpURLConnection.HTTP_NO_CONTENT; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.AssertionsForClassTypes.tuple; import static org.junit.rules.ExpectedException.none; +import static org.mockito.Mockito.mock; import static org.sonar.api.web.UserRole.ADMIN; import static org.sonar.db.DbTester.create; import static org.sonar.db.permission.OrganizationPermission.ADMINISTER; @@ -66,19 +67,18 @@ public class DeleteActionTest { @Rule public DbTester db = create(); - private DbClient dbClient = db.getDbClient(); + private final DbClient dbClient = db.getDbClient(); private final DbSession dbSession = db.getSession(); - private WebhookDbTester webhookDbTester = db.webhooks(); - private WebhookDeliveryDbTester webhookDeliveryDbTester = db.webhookDelivery(); + private final WebhookDbTester webhookDbTester = db.webhooks(); + private final WebhookDeliveryDbTester webhookDeliveryDbTester = db.webhookDelivery(); private final WebhookDeliveryDao deliveryDao = dbClient.webhookDeliveryDao(); - private OrganizationDbTester organizationDbTester = db.organizations(); - private ComponentDbTester componentDbTester = db.components(); - - private DefaultOrganizationProvider defaultOrganizationProvider = from(db); - - private WebhookSupport webhookSupport = new WebhookSupport(userSession); - private DeleteAction underTest = new DeleteAction(dbClient, userSession, webhookSupport); - private WsActionTester wsActionTester = new WsActionTester(underTest); + private final OrganizationDbTester organizationDbTester = db.organizations(); + private final ComponentDbTester componentDbTester = db.components(); + private final DefaultOrganizationProvider defaultOrganizationProvider = from(db); + private final Configuration configuration = mock(Configuration.class); + private final WebhookSupport webhookSupport = new WebhookSupport(userSession, configuration); + private final DeleteAction underTest = new DeleteAction(dbClient, userSession, webhookSupport); + private final WsActionTester wsActionTester = new WsActionTester(underTest); @Test public void test_ws_definition() { diff --git a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/webhook/ws/ListActionTest.java b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/webhook/ws/ListActionTest.java index 9c869211bab..4f05ce0fc23 100644 --- a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/webhook/ws/ListActionTest.java +++ b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/webhook/ws/ListActionTest.java @@ -23,6 +23,7 @@ import java.util.List; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; +import org.sonar.api.config.Configuration; import org.sonar.api.server.ws.WebService; import org.sonar.api.server.ws.WebService.Param; import org.sonar.db.DbClient; @@ -49,6 +50,7 @@ import static java.lang.String.format; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.AssertionsForClassTypes.tuple; import static org.junit.rules.ExpectedException.none; +import static org.mockito.Mockito.mock; import static org.sonar.api.web.UserRole.ADMIN; import static org.sonar.db.DbTester.create; import static org.sonar.db.permission.OrganizationPermission.ADMINISTER; @@ -73,17 +75,18 @@ public class ListActionTest { @Rule public DbTester db = create(); - private DbClient dbClient = db.getDbClient(); - private DefaultOrganizationProvider defaultOrganizationProvider = from(db); - private WebhookSupport webhookSupport = new WebhookSupport(userSession); - private ComponentFinder componentFinder = new ComponentFinder(dbClient, null); - private ListAction underTest = new ListAction(dbClient, userSession, defaultOrganizationProvider, webhookSupport, componentFinder); - - private ComponentDbTester componentDbTester = db.components(); - private WebhookDbTester webhookDbTester = db.webhooks(); - private WebhookDeliveryDbTester webhookDeliveryDbTester = db.webhookDelivery(); - private OrganizationDbTester organizationDbTester = db.organizations(); - private WsActionTester wsActionTester = new WsActionTester(underTest); + private final DbClient dbClient = db.getDbClient(); + private final DefaultOrganizationProvider defaultOrganizationProvider = from(db); + private final Configuration configuration = mock(Configuration.class); + private final WebhookSupport webhookSupport = new WebhookSupport(userSession, configuration); + private final ComponentFinder componentFinder = new ComponentFinder(dbClient, null); + private final ListAction underTest = new ListAction(dbClient, userSession, defaultOrganizationProvider, webhookSupport, componentFinder); + + private final ComponentDbTester componentDbTester = db.components(); + private final WebhookDbTester webhookDbTester = db.webhooks(); + private final WebhookDeliveryDbTester webhookDeliveryDbTester = db.webhookDelivery(); + private final OrganizationDbTester organizationDbTester = db.organizations(); + private final WsActionTester wsActionTester = new WsActionTester(underTest); @Test public void definition() { diff --git a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/webhook/ws/UpdateActionTest.java b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/webhook/ws/UpdateActionTest.java index 85180cf8183..55b30756a43 100644 --- a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/webhook/ws/UpdateActionTest.java +++ b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/webhook/ws/UpdateActionTest.java @@ -23,6 +23,7 @@ import java.util.Optional; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; +import org.sonar.api.config.Configuration; import org.sonar.api.server.ws.WebService; import org.sonar.db.DbClient; import org.sonar.db.DbTester; @@ -45,6 +46,7 @@ import static java.net.HttpURLConnection.HTTP_NO_CONTENT; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.AssertionsForClassTypes.tuple; import static org.junit.rules.ExpectedException.none; +import static org.mockito.Mockito.mock; import static org.sonar.api.web.UserRole.ADMIN; import static org.sonar.db.DbTester.create; import static org.sonar.db.permission.OrganizationPermission.ADMINISTER; @@ -63,17 +65,16 @@ public class UpdateActionTest { @Rule public DbTester db = create(); - private DbClient dbClient = db.getDbClient(); - private WebhookDbTester webhookDbTester = db.webhooks(); - private OrganizationDbTester organizationDbTester = db.organizations(); - private ComponentDbTester componentDbTester = db.components(); - - private DefaultOrganizationProvider defaultOrganizationProvider = from(db); - - private WebhookSupport webhookSupport = new WebhookSupport(userSession); - private ComponentFinder componentFinder = new ComponentFinder(dbClient, null); - private UpdateAction underTest = new UpdateAction(dbClient, userSession, webhookSupport, componentFinder); - private WsActionTester wsActionTester = new WsActionTester(underTest); + private final DbClient dbClient = db.getDbClient(); + private final WebhookDbTester webhookDbTester = db.webhooks(); + private final OrganizationDbTester organizationDbTester = db.organizations(); + private final ComponentDbTester componentDbTester = db.components(); + private final DefaultOrganizationProvider defaultOrganizationProvider = from(db); + private final Configuration configuration = mock(Configuration.class); + private final WebhookSupport webhookSupport = new WebhookSupport(userSession, configuration); + private final ComponentFinder componentFinder = new ComponentFinder(dbClient, null); + private final UpdateAction underTest = new UpdateAction(dbClient, userSession, webhookSupport, componentFinder); + private final WsActionTester wsActionTester = new WsActionTester(underTest); @Test public void test_ws_definition() { diff --git a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/webhook/ws/WebhookSupportTest.java b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/webhook/ws/WebhookSupportTest.java new file mode 100644 index 00000000000..eb9980e98a3 --- /dev/null +++ b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/webhook/ws/WebhookSupportTest.java @@ -0,0 +1,91 @@ +/* + * SonarQube + * Copyright (C) 2009-2020 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.webhook.ws; + +import com.tngtech.java.junit.dataprovider.DataProvider; +import com.tngtech.java.junit.dataprovider.DataProviderRunner; +import com.tngtech.java.junit.dataprovider.UseDataProvider; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.sonar.api.config.Configuration; +import org.sonar.server.user.UserSession; + +import static java.util.Optional.of; +import static org.assertj.core.api.Assertions.assertThatCode; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +@RunWith(DataProviderRunner.class) +public class WebhookSupportTest { + private final Configuration configuration = mock(Configuration.class); + private final WebhookSupport underTest = new WebhookSupport(mock(UserSession.class), configuration); + + @DataProvider + public static Object[][] validUrls() { + return new Object[][] { + {"https://some.valid.address.com/random_webhook"}, + {"https://248.235.76.254/some_webhook"}, + {"https://248.235.76.254:8454/some_webhook"}, + + // local addresses are allowed too + {"https://192.168.0.1/some_webhook"}, + {"https://192.168.0.1:8888/some_webhook"}, + {"https://10.15.15.15/some_webhook"}, + {"https://10.15.15.15:7777/some_webhook"}, + {"https://172.16.16.16/some_webhook"}, + {"https://172.16.16.16:9999/some_webhook"}, + }; + } + + @DataProvider + public static Object[][] loopbackUrls() { + return new Object[][] { + {"https://0.0.0.0/some_webhook"}, + {"https://0.0.0.0:8888/some_webhook"}, + {"https://127.0.0.1/some_webhook"}, + {"https://127.0.0.1:7777/some_webhook"}, + {"https://localhost/some_webhook"}, + {"https://localhost:9999/some_webhook"}, + }; + } + + @Test + @UseDataProvider("validUrls") + public void checkUrlPatternSuccessfulForValidAddress(String url) { + assertThatCode(() -> underTest.checkUrlPattern(url, "msg")).doesNotThrowAnyException(); + } + + @Test + @UseDataProvider("loopbackUrls") + public void checkUrlPatternFailsForLoopbackAddress(String url) { + assertThatThrownBy(() -> underTest.checkUrlPattern(url, "msg")) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("Invalid URL"); + } + + @Test + @UseDataProvider("loopbackUrls") + public void checkUrlPatternSuccessfulForLoopbackAddressWhenSonarValidateWebhooksPropertyDisabled(String url) { + when(configuration.getBoolean("sonar.validateWebhooks")).thenReturn(of(false)); + + assertThatCode(() -> underTest.checkUrlPattern(url, "msg")).doesNotThrowAnyException(); + } +} |