You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

CreateActionTest.java 12KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300
  1. /*
  2. * SonarQube
  3. * Copyright (C) 2009-2023 SonarSource SA
  4. * mailto:info AT sonarsource DOT com
  5. *
  6. * This program is free software; you can redistribute it and/or
  7. * modify it under the terms of the GNU Lesser General Public
  8. * License as published by the Free Software Foundation; either
  9. * version 3 of the License, or (at your option) any later version.
  10. *
  11. * This program is distributed in the hope that it will be useful,
  12. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  14. * Lesser General Public License for more details.
  15. *
  16. * You should have received a copy of the GNU Lesser General Public License
  17. * along with this program; if not, write to the Free Software Foundation,
  18. * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
  19. */
  20. package org.sonar.server.webhook.ws;
  21. import org.junit.Rule;
  22. import org.junit.Test;
  23. import org.sonar.api.config.Configuration;
  24. import org.sonar.api.resources.ResourceTypes;
  25. import org.sonar.api.server.ws.WebService;
  26. import org.sonar.core.util.UuidFactory;
  27. import org.sonar.core.util.UuidFactoryFast;
  28. import org.sonar.db.DbClient;
  29. import org.sonar.db.DbTester;
  30. import org.sonar.db.component.ComponentDbTester;
  31. import org.sonar.db.component.ComponentDto;
  32. import org.sonar.db.project.ProjectDto;
  33. import org.sonar.db.webhook.WebhookDbTester;
  34. import org.sonar.server.component.ComponentFinder;
  35. import org.sonar.server.exceptions.ForbiddenException;
  36. import org.sonar.server.exceptions.NotFoundException;
  37. import org.sonar.server.exceptions.UnauthorizedException;
  38. import org.sonar.server.tester.UserSessionRule;
  39. import org.sonar.server.ws.TestRequest;
  40. import org.sonar.server.ws.WsActionTester;
  41. import org.sonarqube.ws.Webhooks.CreateWsResponse;
  42. import static java.lang.String.format;
  43. import static org.assertj.core.api.Assertions.assertThat;
  44. import static org.assertj.core.api.Assertions.assertThatThrownBy;
  45. import static org.assertj.core.api.AssertionsForClassTypes.tuple;
  46. import static org.mockito.Mockito.mock;
  47. import static org.sonar.api.web.UserRole.ADMIN;
  48. import static org.sonar.db.DbTester.create;
  49. import static org.sonar.db.permission.GlobalPermission.ADMINISTER;
  50. import static org.sonar.server.tester.UserSessionRule.standalone;
  51. import static org.sonar.server.webhook.ws.WebhooksWsParameters.NAME_PARAM;
  52. import static org.sonar.server.webhook.ws.WebhooksWsParameters.PROJECT_KEY_PARAM;
  53. import static org.sonar.server.webhook.ws.WebhooksWsParameters.URL_PARAM;
  54. import static org.sonar.server.ws.KeyExamples.NAME_WEBHOOK_EXAMPLE_001;
  55. import static org.sonar.server.ws.KeyExamples.URL_WEBHOOK_EXAMPLE_001;
  56. public class CreateActionTest {
  57. @Rule
  58. public UserSessionRule userSession = standalone();
  59. @Rule
  60. public DbTester db = create();
  61. private final DbClient dbClient = db.getDbClient();
  62. private final WebhookDbTester webhookDbTester = db.webhooks();
  63. private final ComponentDbTester componentDbTester = db.components();
  64. private final UuidFactory uuidFactory = UuidFactoryFast.getInstance();
  65. private final Configuration configuration = mock(Configuration.class);
  66. private final NetworkInterfaceProvider networkInterfaceProvider = mock(NetworkInterfaceProvider.class);
  67. private final WebhookSupport webhookSupport = new WebhookSupport(userSession, configuration, networkInterfaceProvider);
  68. private final ResourceTypes resourceTypes = mock(ResourceTypes.class);
  69. private final ComponentFinder componentFinder = new ComponentFinder(dbClient, resourceTypes);
  70. private final CreateAction underTest = new CreateAction(dbClient, userSession, uuidFactory, webhookSupport, componentFinder);
  71. private final WsActionTester wsActionTester = new WsActionTester(underTest);
  72. @Test
  73. public void test_ws_definition() {
  74. WebService.Action action = wsActionTester.getDef();
  75. assertThat(action).isNotNull();
  76. assertThat(action.isInternal()).isFalse();
  77. assertThat(action.isPost()).isTrue();
  78. assertThat(action.responseExampleAsString()).isNotEmpty();
  79. assertThat(action.params())
  80. .extracting(WebService.Param::key, WebService.Param::isRequired)
  81. .containsExactlyInAnyOrder(
  82. tuple("project", false),
  83. tuple("name", true),
  84. tuple("url", true),
  85. tuple("secret", false));
  86. }
  87. @Test
  88. public void create_a_webhook_with_400_length_project_key() {
  89. String longProjectKey = generateStringWithLength(400);
  90. ComponentDto project = componentDbTester.insertPrivateProject(componentDto -> componentDto.setKey(longProjectKey));
  91. userSession.logIn().addProjectPermission(ADMIN, project);
  92. CreateWsResponse response = wsActionTester.newRequest()
  93. .setParam("project", longProjectKey)
  94. .setParam("name", NAME_WEBHOOK_EXAMPLE_001)
  95. .setParam("url", URL_WEBHOOK_EXAMPLE_001)
  96. .setParam("secret", "a_secret")
  97. .executeProtobuf(CreateWsResponse.class);
  98. assertThat(response.getWebhook()).isNotNull();
  99. assertThat(response.getWebhook().getKey()).isNotNull();
  100. assertThat(response.getWebhook().getName()).isEqualTo(NAME_WEBHOOK_EXAMPLE_001);
  101. assertThat(response.getWebhook().getUrl()).isEqualTo(URL_WEBHOOK_EXAMPLE_001);
  102. assertThat(response.getWebhook().getHasSecret()).isTrue();
  103. }
  104. @Test
  105. public void create_a_webhook_with_secret() {
  106. userSession.logIn().addPermission(ADMINISTER);
  107. CreateWsResponse response = wsActionTester.newRequest()
  108. .setParam("name", NAME_WEBHOOK_EXAMPLE_001)
  109. .setParam("url", URL_WEBHOOK_EXAMPLE_001)
  110. .setParam("secret", "a_secret")
  111. .executeProtobuf(CreateWsResponse.class);
  112. assertThat(response.getWebhook()).isNotNull();
  113. assertThat(response.getWebhook().getKey()).isNotNull();
  114. assertThat(response.getWebhook().getName()).isEqualTo(NAME_WEBHOOK_EXAMPLE_001);
  115. assertThat(response.getWebhook().getUrl()).isEqualTo(URL_WEBHOOK_EXAMPLE_001);
  116. assertThat(response.getWebhook().getHasSecret()).isTrue();
  117. assertThat(webhookDbTester.selectWebhook(response.getWebhook().getKey()))
  118. .isPresent()
  119. .hasValueSatisfying(reloaded -> {
  120. assertThat(reloaded.getName()).isEqualTo(NAME_WEBHOOK_EXAMPLE_001);
  121. assertThat(reloaded.getUrl()).isEqualTo(URL_WEBHOOK_EXAMPLE_001);
  122. assertThat(reloaded.getProjectUuid()).isNull();
  123. assertThat(reloaded.getSecret()).isEqualTo("a_secret");
  124. });
  125. }
  126. @Test
  127. public void create_a_global_webhook() {
  128. userSession.logIn().addPermission(ADMINISTER);
  129. CreateWsResponse response = wsActionTester.newRequest()
  130. .setParam("name", NAME_WEBHOOK_EXAMPLE_001)
  131. .setParam("url", URL_WEBHOOK_EXAMPLE_001)
  132. .executeProtobuf(CreateWsResponse.class);
  133. assertThat(response.getWebhook()).isNotNull();
  134. assertThat(response.getWebhook().getKey()).isNotNull();
  135. assertThat(response.getWebhook().getName()).isEqualTo(NAME_WEBHOOK_EXAMPLE_001);
  136. assertThat(response.getWebhook().getUrl()).isEqualTo(URL_WEBHOOK_EXAMPLE_001);
  137. assertThat(response.getWebhook().getHasSecret()).isFalse();
  138. }
  139. @Test
  140. public void create_a_webhook_on_project() {
  141. ComponentDto project = componentDbTester.insertPrivateProject();
  142. userSession.logIn().addProjectPermission(ADMIN, project);
  143. CreateWsResponse response = wsActionTester.newRequest()
  144. .setParam("project", project.getKey())
  145. .setParam("name", NAME_WEBHOOK_EXAMPLE_001)
  146. .setParam("url", URL_WEBHOOK_EXAMPLE_001)
  147. .executeProtobuf(CreateWsResponse.class);
  148. assertThat(response.getWebhook()).isNotNull();
  149. assertThat(response.getWebhook().getKey()).isNotNull();
  150. assertThat(response.getWebhook().getName()).isEqualTo(NAME_WEBHOOK_EXAMPLE_001);
  151. assertThat(response.getWebhook().getUrl()).isEqualTo(URL_WEBHOOK_EXAMPLE_001);
  152. assertThat(response.getWebhook().getHasSecret()).isFalse();
  153. }
  154. @Test
  155. public void fail_if_project_does_not_exist() {
  156. userSession.logIn();
  157. TestRequest request = wsActionTester.newRequest()
  158. .setParam(PROJECT_KEY_PARAM, "nonexistent-project-uuid")
  159. .setParam(NAME_PARAM, NAME_WEBHOOK_EXAMPLE_001)
  160. .setParam(URL_PARAM, URL_WEBHOOK_EXAMPLE_001);
  161. assertThatThrownBy(request::execute)
  162. .isInstanceOf(NotFoundException.class)
  163. .hasMessage("Project 'nonexistent-project-uuid' not found");
  164. }
  165. @Test
  166. public void fail_if_crossing_maximum_quantity_of_webhooks_on_this_project() {
  167. ProjectDto project = componentDbTester.insertPrivateProjectDto();
  168. for (int i = 0; i < 10; i++) {
  169. webhookDbTester.insertWebhook(project);
  170. }
  171. userSession.logIn().addProjectPermission(ADMIN, project);
  172. TestRequest request = wsActionTester.newRequest()
  173. .setParam(PROJECT_KEY_PARAM, project.getKey())
  174. .setParam(NAME_PARAM, NAME_WEBHOOK_EXAMPLE_001)
  175. .setParam(URL_PARAM, URL_WEBHOOK_EXAMPLE_001);
  176. assertThatThrownBy(request::execute)
  177. .isInstanceOf(IllegalArgumentException.class)
  178. .hasMessage(format("Maximum number of webhook reached for project '%s'", project.getKey()));
  179. }
  180. @Test
  181. public void fail_if_crossing_maximum_quantity_of_global_webhooks() {
  182. for (int i = 0; i < 10; i++) {
  183. webhookDbTester.insertGlobalWebhook();
  184. }
  185. userSession.logIn().addPermission(ADMINISTER);
  186. TestRequest request = wsActionTester.newRequest()
  187. .setParam(NAME_PARAM, NAME_WEBHOOK_EXAMPLE_001)
  188. .setParam(URL_PARAM, URL_WEBHOOK_EXAMPLE_001);
  189. assertThatThrownBy(request::execute)
  190. .isInstanceOf(IllegalArgumentException.class)
  191. .hasMessage("Maximum number of global webhooks reached");
  192. }
  193. @Test
  194. public void fail_if_url_is_not_valid() {
  195. userSession.logIn().addPermission(ADMINISTER);
  196. TestRequest request = wsActionTester.newRequest()
  197. .setParam(NAME_PARAM, NAME_WEBHOOK_EXAMPLE_001)
  198. .setParam(URL_PARAM, "htp://www.wrong-protocol.com/");
  199. assertThatThrownBy(request::execute)
  200. .isInstanceOf(IllegalArgumentException.class);
  201. }
  202. @Test
  203. public void fail_if_credential_in_url_is_have_a_wrong_format() {
  204. userSession.logIn().addPermission(ADMINISTER);
  205. TestRequest request = wsActionTester.newRequest()
  206. .setParam(NAME_PARAM, NAME_WEBHOOK_EXAMPLE_001)
  207. .setParam(URL_PARAM, "http://:www.wrong-protocol.com/");
  208. assertThatThrownBy(request::execute)
  209. .isInstanceOf(IllegalArgumentException.class);
  210. }
  211. @Test
  212. public void return_UnauthorizedException_if_not_logged_in() {
  213. userSession.anonymous();
  214. TestRequest request = wsActionTester.newRequest()
  215. .setParam(NAME_PARAM, NAME_WEBHOOK_EXAMPLE_001)
  216. .setParam(URL_PARAM, URL_WEBHOOK_EXAMPLE_001);
  217. assertThatThrownBy(request::execute)
  218. .isInstanceOf(UnauthorizedException.class);
  219. }
  220. @Test
  221. public void throw_ForbiddenException_if_project_not_provided_but_user_is_not_administrator() {
  222. userSession.logIn();
  223. TestRequest request = wsActionTester.newRequest()
  224. .setParam(NAME_PARAM, NAME_WEBHOOK_EXAMPLE_001)
  225. .setParam(URL_PARAM, URL_WEBHOOK_EXAMPLE_001);
  226. assertThatThrownBy(request::execute)
  227. .isInstanceOf(ForbiddenException.class).hasMessage("Insufficient privileges");
  228. }
  229. @Test
  230. public void throw_ForbiddenException_if_not_project_administrator() {
  231. ComponentDto project = componentDbTester.insertPrivateProject();
  232. userSession.logIn();
  233. TestRequest request = wsActionTester.newRequest()
  234. .setParam(NAME_PARAM, NAME_WEBHOOK_EXAMPLE_001)
  235. .setParam(URL_PARAM, URL_WEBHOOK_EXAMPLE_001)
  236. .setParam(PROJECT_KEY_PARAM, project.getKey());
  237. assertThatThrownBy(request::execute)
  238. .isInstanceOf(ForbiddenException.class)
  239. .hasMessage("Insufficient privileges");
  240. }
  241. @Test
  242. public void throw_IllegalArgumentException_if_project_key_greater_than_400() {
  243. String longProjectKey = generateStringWithLength(401);
  244. userSession.logIn().addPermission(ADMINISTER);
  245. TestRequest request = wsActionTester.newRequest()
  246. .setParam("project", longProjectKey)
  247. .setParam("name", NAME_WEBHOOK_EXAMPLE_001)
  248. .setParam("url", URL_WEBHOOK_EXAMPLE_001)
  249. .setParam("secret", "a_secret");
  250. assertThatThrownBy(() -> request.executeProtobuf(CreateWsResponse.class))
  251. .isInstanceOf(IllegalArgumentException.class)
  252. .hasMessage("'project' length (401) is longer than the maximum authorized (400)");
  253. }
  254. private static String generateStringWithLength(int length) {
  255. return "x".repeat(Math.max(0, length));
  256. }
  257. }