]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-13341 fix SSF-110
authorMichal Duda <michal.duda@sonarsource.com>
Mon, 29 Jun 2020 09:45:46 +0000 (11:45 +0200)
committersonartech <sonartech@sonarsource.com>
Tue, 30 Jun 2020 20:05:08 +0000 (20:05 +0000)
14 files changed:
server/sonar-db-dao/src/main/java/org/sonar/db/webhook/WebhookDao.java
server/sonar-db-dao/src/main/java/org/sonar/db/webhook/WebhookMapper.java
server/sonar-db-dao/src/main/resources/org/sonar/db/webhook/WebhookMapper.xml
server/sonar-db-dao/src/test/java/org/sonar/db/webhook/WebhookDaoTest.java
server/sonar-process/src/main/java/org/sonar/process/ProcessProperties.java
server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevelStartup.java
server/sonar-server/src/main/java/org/sonar/server/startup/DeprecatedWebhookUsageCheck.java [new file with mode: 0644]
server/sonar-server/src/main/java/org/sonar/server/webhook/ws/WebhookSupport.java
server/sonar-server/src/test/java/org/sonar/server/startup/DeprecatedWebhookUsageCheckTest.java [new file with mode: 0644]
server/sonar-server/src/test/java/org/sonar/server/webhook/ws/CreateActionTest.java
server/sonar-server/src/test/java/org/sonar/server/webhook/ws/DeleteActionTest.java
server/sonar-server/src/test/java/org/sonar/server/webhook/ws/ListActionTest.java
server/sonar-server/src/test/java/org/sonar/server/webhook/ws/UpdateActionTest.java
server/sonar-server/src/test/java/org/sonar/server/webhook/ws/WebhookSupportTest.java [new file with mode: 0644]

index 5d595079165b3e62a09e113bf840f74e442ea0ae..3256130f25f8c51f1995321a0a22d62523890378 100644 (file)
@@ -21,6 +21,7 @@ package org.sonar.db.webhook;
 
 import java.util.List;
 import java.util.Optional;
+import java.util.function.Consumer;
 import org.sonar.api.utils.System2;
 import org.sonar.db.Dao;
 import org.sonar.db.DbSession;
@@ -53,6 +54,13 @@ public class WebhookDao implements Dao {
     return mapper(dbSession).selectForProjectUuidOrderedByName(componentDto.uuid());
   }
 
+  public void scrollAll(DbSession dbSession, Consumer<WebhookDto> consumer) {
+    mapper(dbSession).scrollAll(context -> {
+      WebhookDto webhook = context.getResultObject();
+      consumer.accept(webhook);
+    });
+  }
+
   public void insert(DbSession dbSession, WebhookDto dto) {
     checkState(dto.getOrganizationUuid() != null || dto.getProjectUuid() != null,
       "A webhook can not be created if not linked to an organization or a project.");
index 043cc539231a0daa3acda6fb4bcbdf88605d9ae7..5a19e0564f87e7dc56f923525491fdb1f97b3be4 100644 (file)
@@ -22,6 +22,7 @@ package org.sonar.db.webhook;
 import java.util.List;
 import javax.annotation.CheckForNull;
 import org.apache.ibatis.annotations.Param;
+import org.apache.ibatis.session.ResultHandler;
 
 public interface WebhookMapper {
 
@@ -32,6 +33,8 @@ public interface WebhookMapper {
 
   List<WebhookDto> selectForOrganizationUuidOrderedByName(@Param("organizationUuid") String organizationUuid);
 
+  void scrollAll(ResultHandler<WebhookDto> handler);
+
   void insert(WebhookDto dto);
 
   void update(WebhookDto dto);
index cd2a7ee39195ddbf6f2236b73a4b6a46bd26dfb6..aa6c1039e69a0b8a706a10d7af469b222d44db47 100644 (file)
   </sql>
 
   <select id="selectByUuid" parameterType="String" resultType="org.sonar.db.webhook.WebhookDto">
-    select <include refid="sqlColumns" />
+    select <include refid="sqlColumns"/>
     from webhooks
     where uuid = #{webhookUuid,jdbcType=VARCHAR}
   </select>
 
   <select id="selectForOrganizationUuidOrderedByName" parameterType="String" resultType="org.sonar.db.webhook.WebhookDto">
-    select <include refid="sqlColumns" />
+    select <include refid="sqlColumns"/>
     from webhooks
     where organization_uuid = #{organizationUuid,jdbcType=VARCHAR}
     order by name asc
   </select>
 
   <select id="selectForProjectUuidOrderedByName" parameterType="String" resultType="org.sonar.db.webhook.WebhookDto">
-    select <include refid="sqlColumns" />
+    select <include refid="sqlColumns"/>
     from webhooks
     where project_uuid = #{projectUuid,jdbcType=VARCHAR}
     order by name asc
     project_uuid = #{projectUuid,jdbcType=VARCHAR}
   </delete>
 
+  <select id="scrollAll" resultType="org.sonar.db.webhook.WebhookDto" fetchSize="${_scrollFetchSize}" resultSetType="FORWARD_ONLY">
+    select
+    <include refid="sqlColumns"/>
+    from webhooks
+  </select>
+
 </mapper>
index 5037d0058f0047d0af935124e3c72fc0ba87d8fc..dcae2288bf9ed79bdbdc6d966a02fd8d4a2e435a 100644 (file)
@@ -19,7 +19,9 @@
  */
 package org.sonar.db.webhook;
 
+import java.util.ArrayList;
 import java.util.Date;
+import java.util.List;
 import java.util.Optional;
 import org.junit.Rule;
 import org.junit.Test;
@@ -34,6 +36,7 @@ import org.sonar.db.organization.OrganizationDbTester;
 import org.sonar.db.organization.OrganizationDto;
 
 import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.groups.Tuple.tuple;
 
 public class WebhookDaoTest {
 
@@ -43,7 +46,7 @@ public class WebhookDaoTest {
   @Rule
   public ExpectedException expectedException = ExpectedException.none();
 
-  private System2 system2 = System2.INSTANCE;
+  private final System2 system2 = System2.INSTANCE;
 
   private final DbClient dbClient = dbTester.getDbClient();
   private final DbSession dbSession = dbTester.getSession();
@@ -214,6 +217,26 @@ public class WebhookDaoTest {
     underTest.insert(dbSession, dto);
   }
 
+  @Test
+  public void scrollAll() {
+    WebhookDto w1 = new WebhookDto().setProjectUuid("p1").setName("W1").setUuid("W1").setUrl("http://abc.com/w1");
+    WebhookDto w2 = new WebhookDto().setProjectUuid("p2").setName("W2").setUuid("W2").setUrl("http://abc.com/w2");
+    WebhookDto w3 = new WebhookDto().setOrganizationUuid("432").setName("W3").setUuid("W3").setUrl("http://abc.com/w3");
+    webhookDbTester.insert(w1);
+    webhookDbTester.insert(w2);
+    webhookDbTester.insert(w3);
+    List<WebhookDto> result = new ArrayList<>();
+
+    underTest.scrollAll(dbSession, result::add);
+
+    assertThat(result).extracting(WebhookDto::getUuid, WebhookDto::getName, WebhookDto::getUrl, WebhookDto::getOrganizationUuid, WebhookDto::getProjectUuid)
+      .containsExactlyInAnyOrder(
+        tuple(w1.getUuid(), w1.getName(), w1.getUrl(), null, w1.getProjectUuid()),
+        tuple(w2.getUuid(), w2.getName(), w2.getUrl(), null, w2.getProjectUuid()),
+        tuple(w3.getUuid(), w3.getName(), w3.getUrl(), w3.getOrganizationUuid(), null));
+
+  }
+
   private WebhookDto selectByUuid(String uuid) {
     Optional<WebhookDto> dto = underTest.selectByUuid(dbSession, uuid);
     assertThat(dto).isPresent();
index 34e6b61298919117ae1ad4903d91afd39bf109cd..c19671d5b804d479705d8fd790e78f9d9feb7432 100644 (file)
@@ -33,9 +33,8 @@ import javax.annotation.Nullable;
 import org.sonar.core.extension.CoreExtension;
 import org.sonar.core.extension.ServiceLoaderWrapper;
 
-import static java.lang.String.format;
-
 import static com.google.common.base.Preconditions.checkState;
+import static java.lang.String.format;
 
 /**
  * Constants shared by search, web server and app processes.
@@ -112,6 +111,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()),
 
     SONAR_TELEMETRY_ENABLE("sonar.telemetry.enable", "true"),
     SONAR_TELEMETRY_URL("sonar.telemetry.url", "https://telemetry.sonarsource.com/sonarqube"),
index 38c80290b2a9c33baa2cc021272f9b0568d7388e..bc22c93397a26f93d024eee11c4e022695f7879c 100644 (file)
@@ -37,6 +37,7 @@ import org.sonar.server.qualityprofile.RegisterQualityProfiles;
 import org.sonar.server.rule.RegisterRules;
 import org.sonar.server.rule.WebServerRuleFinder;
 import org.sonar.server.startup.GeneratePluginIndex;
+import org.sonar.server.startup.DeprecatedWebhookUsageCheck;
 import org.sonar.server.startup.RegisterMetrics;
 import org.sonar.server.startup.RegisterPermissionTemplates;
 import org.sonar.server.startup.RegisterPlugins;
@@ -66,6 +67,7 @@ public class PlatformLevelStartup extends PlatformLevel {
       BuiltInQualityProfilesUpdateListener.class,
       BuiltInQProfileInsertImpl.class,
       BuiltInQProfileUpdateImpl.class,
+      DeprecatedWebhookUsageCheck.class,
       RegisterQualityProfiles.class,
       RegisterPermissionTemplates.class,
       RenameDeprecatedPropertyKeys.class);
diff --git a/server/sonar-server/src/main/java/org/sonar/server/startup/DeprecatedWebhookUsageCheck.java b/server/sonar-server/src/main/java/org/sonar/server/startup/DeprecatedWebhookUsageCheck.java
new file mode 100644 (file)
index 0000000..6b84466
--- /dev/null
@@ -0,0 +1,85 @@
+/*
+ * 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.startup;
+
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+import java.util.function.Consumer;
+import okhttp3.HttpUrl;
+import org.picocontainer.Startable;
+import org.sonar.api.utils.log.Logger;
+import org.sonar.api.utils.log.Loggers;
+import org.sonar.db.DbClient;
+import org.sonar.db.DbSession;
+import org.sonar.db.component.ComponentDto;
+import org.sonar.db.webhook.WebhookDto;
+
+public class DeprecatedWebhookUsageCheck implements Startable {
+  private static final Logger LOG = Loggers.get(DeprecatedWebhookUsageCheck.class);
+  private final DbClient dbClient;
+
+  public DeprecatedWebhookUsageCheck(DbClient dbClient) {
+    this.dbClient = dbClient;
+  }
+
+  @Override
+  public void start() {
+    try (DbSession dbSession = dbClient.openSession(false)) {
+      dbClient.webhookDao().scrollAll(dbSession, new WebhookConsumer(dbClient, dbSession).consumer);
+    }
+  }
+
+  @Override
+  public void stop() {
+    // nothing to do here
+  }
+
+  static class WebhookConsumer {
+    private DbClient dbClient;
+    private DbSession session;
+
+    final Consumer<WebhookDto> consumer = webhookDto -> {
+      HttpUrl url = HttpUrl.parse(webhookDto.getUrl());
+      if (url != null) {
+        try {
+          InetAddress address = InetAddress.getByName(url.host());
+          if (address.isLoopbackAddress() || address.isAnyLocalAddress()) {
+            if (webhookDto.getProjectUuid() != null) {
+              ComponentDto project = dbClient.componentDao().selectOrFailByUuid(session, webhookDto.getProjectUuid());
+              LOG.warn("Webhook '{}' for project '{}' uses an invalid, unsafe URL and will be automatically removed in a " +
+                "future version of SonarQube. You should update the URL of that webhook or ask a project administrator to do it.",
+                webhookDto.getName(), project.name());
+            } else {
+              LOG.warn("Global webhook '{}' uses an invalid, unsafe URL and will be automatically removed in a future version of SonarQube. " +
+                "You should update the URL of that webhook.", webhookDto.getName());
+            }
+          }
+        } catch (UnknownHostException e) {
+          // do nothing
+        }
+      }
+    };
+
+    WebhookConsumer(DbClient dbClient, DbSession session) {
+      this.dbClient = dbClient;
+      this.session = session;
+    }
+  }
+}
index 3a31239648d0284de028f7ce5c4192800ef89f1a..daa74e2b9c70efc672d2cc26eed2a5770fbe373a 100644 (file)
  */
 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.component.ComponentDto;
 import org.sonar.db.organization.OrganizationDto;
 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(ComponentDto componentDto) {
@@ -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-server/src/test/java/org/sonar/server/startup/DeprecatedWebhookUsageCheckTest.java b/server/sonar-server/src/test/java/org/sonar/server/startup/DeprecatedWebhookUsageCheckTest.java
new file mode 100644 (file)
index 0000000..ad17cbb
--- /dev/null
@@ -0,0 +1,116 @@
+/*
+ * 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.startup;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.sonar.api.utils.log.LogTester;
+import org.sonar.api.utils.log.LoggerLevel;
+import org.sonar.db.DbClient;
+import org.sonar.db.DbSession;
+import org.sonar.db.component.ComponentDao;
+import org.sonar.db.component.ComponentDto;
+import org.sonar.db.webhook.WebhookDao;
+import org.sonar.db.webhook.WebhookDto;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+public class DeprecatedWebhookUsageCheckTest {
+
+  @Rule
+  public LogTester logTester = new LogTester();
+
+  private final WebhookDao webhookDao = mock(WebhookDao.class);
+  private final ComponentDao componentDao = mock(ComponentDao.class);
+  private final DbClient dbClient = mock(DbClient.class);
+  private final DeprecatedWebhookUsageCheck underTest = new DeprecatedWebhookUsageCheck(dbClient);
+
+  @Before
+  public void setup() {
+    when(dbClient.webhookDao()).thenReturn(webhookDao);
+    when(dbClient.componentDao()).thenReturn(componentDao);
+    when(componentDao.selectOrFailByUuid(any(), any())).thenReturn(new ComponentDto().setName("ProjectA"));
+  }
+
+  @Test
+  public void start() {
+    underTest.start();
+
+    verify(webhookDao, times(1)).scrollAll(any(), any());
+  }
+
+  @Test
+  public void stop() {
+    underTest.start();
+    underTest.stop();
+
+    verify(webhookDao, times(1)).scrollAll(any(), any());
+  }
+
+  @Test
+  public void logsWarningWhenDeprecatedAddressIsUsedInProjectLevelWebhook() {
+    DeprecatedWebhookUsageCheck.WebhookConsumer handler = new DeprecatedWebhookUsageCheck.WebhookConsumer(dbClient, mock(DbSession.class));
+
+    handler.consumer.accept(new WebhookDto().setName("W1").setProjectUuid("u1").setUrl("http://127.0.0.1/webhook"));
+
+    assertThat(logTester.logs()).hasSize(1);
+    assertThat(logTester.logs(LoggerLevel.WARN)).hasSize(1);
+    assertThat(logTester.logs(LoggerLevel.WARN))
+      .containsExactlyInAnyOrder("Webhook 'W1' for project 'ProjectA' uses an invalid, unsafe URL and will be automatically removed in a " +
+        "future version of SonarQube. You should update the URL of that webhook or ask a project administrator to do it.");
+  }
+
+  @Test
+  public void logsWarningWhenDeprecatedAddressIsUsedInGlobalLevelWebhook() {
+    DeprecatedWebhookUsageCheck.WebhookConsumer handler = new DeprecatedWebhookUsageCheck.WebhookConsumer(dbClient, mock(DbSession.class));
+
+    handler.consumer.accept(new WebhookDto().setName("W1").setOrganizationUuid("org1").setUrl("http://127.0.0.1/webhook"));
+
+    assertThat(logTester.logs()).hasSize(1);
+    assertThat(logTester.logs(LoggerLevel.WARN)).hasSize(1);
+    assertThat(logTester.logs(LoggerLevel.WARN))
+      .containsExactlyInAnyOrder("Global webhook 'W1' uses an invalid, unsafe URL and will be automatically removed in a future version of SonarQube. " +
+        "You should update the URL of that webhook.");
+  }
+
+  @Test
+  public void doesNotLogAnythingWhenWebhookHasNonDeprecatedAddress() {
+    DeprecatedWebhookUsageCheck.WebhookConsumer handler = new DeprecatedWebhookUsageCheck.WebhookConsumer(dbClient, mock(DbSession.class));
+
+    handler.consumer.accept(new WebhookDto().setName("W1").setProjectUuid("u1").setUrl("http://abc.com/webhook"));
+
+    assertThat(logTester.logs()).isEmpty();
+  }
+
+  @Test
+  public void doesNotLogAnythingWhenUnknownHost() {
+    DeprecatedWebhookUsageCheck.WebhookConsumer handler = new DeprecatedWebhookUsageCheck.WebhookConsumer(dbClient, mock(DbSession.class));
+
+    handler.consumer.accept(new WebhookDto().setName("W1").setProjectUuid("u1").setUrl("http://nice/webhook"));
+
+    assertThat(logTester.logs()).isEmpty();
+  }
+}
index 06fe6016c0fb7c0dabd6d21ba04f995e168b69fd..237a7309e8dbe044cfc2bae85c7ba4994333986e 100644 (file)
@@ -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;
@@ -44,6 +45,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;
@@ -66,17 +68,16 @@ 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 org.sonar.server.webhook.ws.CreateAction underTest = new CreateAction(dbClient, userSession, defaultOrganizationProvider, uuidFactory, webhookSupport);
-  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 CreateAction underTest = new CreateAction(dbClient, userSession, defaultOrganizationProvider, uuidFactory, webhookSupport);
+  private final WsActionTester wsActionTester = new WsActionTester(underTest);
 
   @Test
   public void test_ws_definition() {
index 7558aee567944bab68adc70c3aacc291e4bf5327..837976c2b6cfd65051fb8ba8c952aabe72a08aab 100644 (file)
@@ -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.DbSession;
@@ -47,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;
@@ -65,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() {
index db5736cd83f967081e3a900d1394c99de7fa45c7..85f9dc00aee246cab4a294422cb7f82880091879 100644 (file)
@@ -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;
@@ -47,6 +48,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;
@@ -71,16 +73,16 @@ 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 ListAction underTest = new ListAction(dbClient, userSession, defaultOrganizationProvider, webhookSupport);
-
-  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 ListAction underTest = new ListAction(dbClient, userSession, defaultOrganizationProvider, webhookSupport);
+  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() {
index 9dd2f5e9d11eec8608c6d8cbab10af987de7df53..5a3a97713d8805b0fae3418508a07d5e7c27c275 100644 (file)
@@ -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;
@@ -44,6 +45,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;
@@ -62,16 +64,15 @@ 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 UpdateAction underTest = new UpdateAction(dbClient, userSession, webhookSupport);
-  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 UpdateAction underTest = new UpdateAction(dbClient, userSession, webhookSupport);
+  private final WsActionTester wsActionTester = new WsActionTester(underTest);
 
   @Test
   public void test_ws_definition() {
diff --git a/server/sonar-server/src/test/java/org/sonar/server/webhook/ws/WebhookSupportTest.java b/server/sonar-server/src/test/java/org/sonar/server/webhook/ws/WebhookSupportTest.java
new file mode 100644 (file)
index 0000000..cca13f0
--- /dev/null
@@ -0,0 +1,91 @@
+/*
+ * 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.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();
+  }
+}