Du kan inte välja fler än 25 ämnen Ämnen måste starta med en bokstav eller siffra, kan innehålla bindestreck ('-') och vara max 35 tecken långa.

SetActionIT.java 46KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265
  1. /*
  2. * SonarQube
  3. * Copyright (C) 2009-2024 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.setting.ws;
  21. import com.google.gson.Gson;
  22. import com.tngtech.java.junit.dataprovider.DataProvider;
  23. import com.tngtech.java.junit.dataprovider.DataProviderRunner;
  24. import com.tngtech.java.junit.dataprovider.UseDataProvider;
  25. import java.net.HttpURLConnection;
  26. import java.util.List;
  27. import java.util.Map;
  28. import java.util.concurrent.ThreadLocalRandom;
  29. import javax.annotation.Nullable;
  30. import org.junit.Before;
  31. import org.junit.Rule;
  32. import org.junit.Test;
  33. import org.junit.runner.RunWith;
  34. import org.sonar.api.PropertyType;
  35. import org.sonar.api.config.PropertyDefinition;
  36. import org.sonar.api.config.PropertyDefinitions;
  37. import org.sonar.api.config.PropertyFieldDefinition;
  38. import org.sonar.api.resources.Qualifiers;
  39. import org.sonar.api.server.ws.WebService;
  40. import org.sonar.api.server.ws.WebService.Param;
  41. import org.sonar.api.utils.System2;
  42. import org.sonar.api.web.UserRole;
  43. import org.sonar.db.DbClient;
  44. import org.sonar.db.DbSession;
  45. import org.sonar.db.DbTester;
  46. import org.sonar.db.component.ComponentDto;
  47. import org.sonar.db.component.ComponentTesting;
  48. import org.sonar.db.component.ProjectData;
  49. import org.sonar.db.portfolio.PortfolioDto;
  50. import org.sonar.db.project.ProjectDto;
  51. import org.sonar.db.property.PropertyDbTester;
  52. import org.sonar.db.property.PropertyDto;
  53. import org.sonar.db.property.PropertyQuery;
  54. import org.sonar.process.ProcessProperties;
  55. import org.sonar.scanner.protocol.GsonHelper;
  56. import org.sonar.server.exceptions.BadRequestException;
  57. import org.sonar.server.exceptions.ForbiddenException;
  58. import org.sonar.server.exceptions.NotFoundException;
  59. import org.sonar.server.l18n.I18nRule;
  60. import org.sonar.server.setting.SettingsChangeNotifier;
  61. import org.sonar.server.tester.UserSessionRule;
  62. import org.sonar.server.ws.TestRequest;
  63. import org.sonar.server.ws.TestResponse;
  64. import org.sonar.server.ws.WsActionTester;
  65. import static java.lang.String.format;
  66. import static java.util.Collections.singletonList;
  67. import static org.assertj.core.api.Assertions.assertThat;
  68. import static org.assertj.core.api.Assertions.assertThatThrownBy;
  69. import static org.assertj.core.groups.Tuple.tuple;
  70. import static org.sonar.db.property.PropertyTesting.newComponentPropertyDto;
  71. import static org.sonar.db.property.PropertyTesting.newGlobalPropertyDto;
  72. import static org.sonar.db.user.UserTesting.newUserDto;
  73. @RunWith(DataProviderRunner.class)
  74. public class SetActionIT {
  75. private static final Gson GSON = GsonHelper.create();
  76. @Rule
  77. public UserSessionRule userSession = UserSessionRule.standalone().logIn();
  78. @Rule
  79. public DbTester db = DbTester.create(System2.INSTANCE);
  80. private final PropertyDbTester propertyDb = new PropertyDbTester(db);
  81. private final DbClient dbClient = db.getDbClient();
  82. private final DbSession dbSession = db.getSession();
  83. private final I18nRule i18n = new I18nRule();
  84. private final PropertyDefinitions definitions = new PropertyDefinitions(System2.INSTANCE);
  85. private final FakeSettingsNotifier settingsChangeNotifier = new FakeSettingsNotifier(dbClient);
  86. private final SettingsUpdater settingsUpdater = new SettingsUpdater(dbClient, definitions);
  87. private final SettingValidations validations = new SettingValidations(definitions, dbClient, i18n);
  88. private final SetAction underTest = new SetAction(definitions, dbClient, userSession, settingsUpdater, settingsChangeNotifier, validations);
  89. private final WsActionTester ws = new WsActionTester(underTest);
  90. @Before
  91. public void setUp() {
  92. // by default test doesn't care about permissions
  93. userSession.logIn().setSystemAdministrator();
  94. }
  95. @DataProvider
  96. public static Object[][] securityJsonProperties() {
  97. return new Object[][] {
  98. {"sonar.security.config.javasecurity"},
  99. {"sonar.security.config.phpsecurity"},
  100. {"sonar.security.config.pythonsecurity"},
  101. {"sonar.security.config.roslyn.sonaranalyzer.security.cs"}
  102. };
  103. }
  104. @Test
  105. public void empty_204_response() {
  106. TestResponse result = ws.newRequest()
  107. .setParam("key", "my.key")
  108. .setParam("value", "my value")
  109. .execute();
  110. assertThat(result.getStatus()).isEqualTo(HttpURLConnection.HTTP_NO_CONTENT);
  111. assertThat(result.getInput()).isEmpty();
  112. }
  113. @Test
  114. public void persist_new_global_setting() {
  115. callForGlobalSetting("my.key", "my,value");
  116. assertGlobalSetting("my.key", "my,value");
  117. assertThat(settingsChangeNotifier.wasCalled).isTrue();
  118. }
  119. @Test
  120. public void update_existing_global_setting() {
  121. propertyDb.insertProperty(newGlobalPropertyDto("my.key", "my value"), null, null, null, null);
  122. assertGlobalSetting("my.key", "my value");
  123. callForGlobalSetting("my.key", "my new value");
  124. assertGlobalSetting("my.key", "my new value");
  125. assertThat(settingsChangeNotifier.wasCalled).isTrue();
  126. }
  127. @Test
  128. public void persist_new_project_setting() {
  129. propertyDb.insertProperty(newGlobalPropertyDto("my.key", "my global value"), null, null, null, null);
  130. ProjectDto project = db.components().insertPrivateProject().getProjectDto();
  131. logInAsProjectAdministrator(project);
  132. callForProjectSettingByKey("my.key", "my project value", project.getKey());
  133. assertGlobalSetting("my.key", "my global value");
  134. assertComponentSetting("my.key", "my project value", project.getUuid());
  135. assertThat(settingsChangeNotifier.wasCalled).isFalse();
  136. }
  137. @Test
  138. public void persist_new_subportfolio_setting() {
  139. propertyDb.insertProperty(newGlobalPropertyDto("my.key", "my global value"), null, null, null, null);
  140. ComponentDto portfolio = db.components().insertPrivatePortfolio();
  141. ComponentDto subportfolio = db.components().insertSubportfolio(portfolio);
  142. logInAsPortfolioAdministrator(db.components().getPortfolioDto(portfolio));
  143. callForProjectSettingByKey("my.key", "my project value", subportfolio.getKey());
  144. assertGlobalSetting("my.key", "my global value");
  145. assertComponentSetting("my.key", "my project value", subportfolio.uuid());
  146. assertThat(settingsChangeNotifier.wasCalled).isFalse();
  147. }
  148. @Test
  149. public void persist_project_property_with_project_admin_permission() {
  150. ProjectDto project = db.components().insertPrivateProject().getProjectDto();
  151. logInAsProjectAdministrator(project);
  152. callForProjectSettingByKey("my.key", "my value", project.getKey());
  153. assertComponentSetting("my.key", "my value", project.getUuid());
  154. }
  155. @Test
  156. public void update_existing_project_setting() {
  157. propertyDb.insertProperty(newGlobalPropertyDto("my.key", "my global value"), null, null,
  158. null, null);
  159. ProjectDto project = db.components().insertPrivateProject().getProjectDto();
  160. propertyDb.insertProperty(newComponentPropertyDto("my.key", "my project value", project), project.getKey(),
  161. project.getName(), null, null);
  162. assertComponentSetting("my.key", "my project value", project.getUuid());
  163. logInAsProjectAdministrator(project);
  164. callForProjectSettingByKey("my.key", "my new project value", project.getKey());
  165. assertComponentSetting("my.key", "my new project value", project.getUuid());
  166. }
  167. @Test
  168. public void persist_several_multi_value_setting() {
  169. callForMultiValueGlobalSetting("my.key", List.of("first,Value", "second,Value", "third,Value"));
  170. String expectedValue = "first%2CValue,second%2CValue,third%2CValue";
  171. assertGlobalSetting("my.key", expectedValue);
  172. assertThat(settingsChangeNotifier.wasCalled).isTrue();
  173. }
  174. @Test
  175. public void persist_one_multi_value_setting() {
  176. callForMultiValueGlobalSetting("my.key", List.of("first,Value"));
  177. assertGlobalSetting("my.key", "first%2CValue");
  178. }
  179. @Test
  180. public void persist_property_set_setting() {
  181. definitions.addComponent(PropertyDefinition
  182. .builder("my.key")
  183. .name("foo")
  184. .description("desc")
  185. .category("cat")
  186. .subCategory("subCat")
  187. .type(PropertyType.PROPERTY_SET)
  188. .defaultValue("default")
  189. .fields(List.of(
  190. PropertyFieldDefinition.build("firstField")
  191. .name("First Field")
  192. .type(PropertyType.STRING)
  193. .build(),
  194. PropertyFieldDefinition.build("secondField")
  195. .name("Second Field")
  196. .type(PropertyType.STRING)
  197. .build()))
  198. .build());
  199. callForGlobalPropertySet("my.key", List.of(
  200. GSON.toJson(Map.of("firstField", "firstValue", "secondField", "secondValue")),
  201. GSON.toJson(Map.of("firstField", "anotherFirstValue", "secondField", "anotherSecondValue")),
  202. GSON.toJson(Map.of("firstField", "yetFirstValue", "secondField", "yetSecondValue"))));
  203. assertThat(dbClient.propertiesDao().selectGlobalProperties(dbSession)).hasSize(7);
  204. assertGlobalSetting("my.key", "1,2,3");
  205. assertGlobalSetting("my.key.1.firstField", "firstValue");
  206. assertGlobalSetting("my.key.1.secondField", "secondValue");
  207. assertGlobalSetting("my.key.2.firstField", "anotherFirstValue");
  208. assertGlobalSetting("my.key.2.secondField", "anotherSecondValue");
  209. assertGlobalSetting("my.key.3.firstField", "yetFirstValue");
  210. assertGlobalSetting("my.key.3.secondField", "yetSecondValue");
  211. assertThat(settingsChangeNotifier.wasCalled).isTrue();
  212. }
  213. @Test
  214. public void update_property_set_setting() {
  215. definitions.addComponent(PropertyDefinition
  216. .builder("my.key")
  217. .name("foo")
  218. .description("desc")
  219. .category("cat")
  220. .subCategory("subCat")
  221. .type(PropertyType.PROPERTY_SET)
  222. .defaultValue("default")
  223. .fields(List.of(
  224. PropertyFieldDefinition.build("firstField")
  225. .name("First Field")
  226. .type(PropertyType.STRING)
  227. .build(),
  228. PropertyFieldDefinition.build("secondField")
  229. .name("Second Field")
  230. .type(PropertyType.STRING)
  231. .build()))
  232. .build());
  233. propertyDb.insertProperties(null, null, null, null,
  234. newGlobalPropertyDto("my.key", "1,2,3,4"),
  235. newGlobalPropertyDto("my.key.1.firstField", "oldFirstValue"),
  236. newGlobalPropertyDto("my.key.1.secondField", "oldSecondValue"),
  237. newGlobalPropertyDto("my.key.2.firstField", "anotherOldFirstValue"),
  238. newGlobalPropertyDto("my.key.2.secondField", "anotherOldSecondValue"),
  239. newGlobalPropertyDto("my.key.3.firstField", "oldFirstValue"),
  240. newGlobalPropertyDto("my.key.3.secondField", "oldSecondValue"),
  241. newGlobalPropertyDto("my.key.4.firstField", "anotherOldFirstValue"),
  242. newGlobalPropertyDto("my.key.4.secondField", "anotherOldSecondValue"));
  243. callForGlobalPropertySet("my.key", List.of(
  244. GSON.toJson(Map.of("firstField", "firstValue", "secondField", "secondValue")),
  245. GSON.toJson(Map.of("firstField", "anotherFirstValue", "secondField", "anotherSecondValue")),
  246. GSON.toJson(Map.of("firstField", "yetFirstValue", "secondField", "yetSecondValue"))));
  247. assertThat(dbClient.propertiesDao().selectGlobalProperties(dbSession)).hasSize(7);
  248. assertGlobalSetting("my.key", "1,2,3");
  249. assertGlobalSetting("my.key.1.firstField", "firstValue");
  250. assertGlobalSetting("my.key.1.secondField", "secondValue");
  251. assertGlobalSetting("my.key.2.firstField", "anotherFirstValue");
  252. assertGlobalSetting("my.key.2.secondField", "anotherSecondValue");
  253. assertGlobalSetting("my.key.3.firstField", "yetFirstValue");
  254. assertGlobalSetting("my.key.3.secondField", "yetSecondValue");
  255. assertThat(settingsChangeNotifier.wasCalled).isTrue();
  256. }
  257. @Test
  258. public void update_property_set_on_component_setting() {
  259. definitions.addComponent(PropertyDefinition
  260. .builder("my.key")
  261. .name("foo")
  262. .description("desc")
  263. .category("cat")
  264. .subCategory("subCat")
  265. .type(PropertyType.PROPERTY_SET)
  266. .defaultValue("default")
  267. .onQualifiers(Qualifiers.PROJECT)
  268. .fields(List.of(
  269. PropertyFieldDefinition.build("firstField")
  270. .name("First Field")
  271. .type(PropertyType.STRING)
  272. .build(),
  273. PropertyFieldDefinition.build("secondField")
  274. .name("Second Field")
  275. .type(PropertyType.STRING)
  276. .build()))
  277. .build());
  278. ProjectDto project = db.components().insertPrivateProject().getProjectDto();
  279. propertyDb.insertProperties(null, null, null, null,
  280. newGlobalPropertyDto("my.key", "1"),
  281. newGlobalPropertyDto("my.key.1.firstField", "oldFirstValue"),
  282. newGlobalPropertyDto("my.key.1.secondField", "oldSecondValue"));
  283. propertyDb.insertProperties(null, project.getKey(), project.getName(), project.getQualifier(),
  284. newComponentPropertyDto("my.key", "1", project),
  285. newComponentPropertyDto("my.key.1.firstField", "componentFirstValue", project),
  286. newComponentPropertyDto("my.key.1.firstField", "componentSecondValue", project));
  287. logInAsProjectAdministrator(project);
  288. callForComponentPropertySet("my.key", List.of(
  289. GSON.toJson(Map.of("firstField", "firstValue", "secondField", "secondValue")),
  290. GSON.toJson(Map.of("firstField", "anotherFirstValue", "secondField", "anotherSecondValue"))),
  291. project.getKey());
  292. assertThat(dbClient.propertiesDao().selectGlobalProperties(dbSession)).hasSize(3);
  293. assertThat(dbClient.propertiesDao().selectEntityProperties(dbSession, project.getUuid())).hasSize(5);
  294. assertGlobalSetting("my.key", "1");
  295. assertGlobalSetting("my.key.1.firstField", "oldFirstValue");
  296. assertGlobalSetting("my.key.1.secondField", "oldSecondValue");
  297. String projectUuid = project.getUuid();
  298. assertComponentSetting("my.key", "1,2", projectUuid);
  299. assertComponentSetting("my.key.1.firstField", "firstValue", projectUuid);
  300. assertComponentSetting("my.key.1.secondField", "secondValue", projectUuid);
  301. assertComponentSetting("my.key.2.firstField", "anotherFirstValue", projectUuid);
  302. assertComponentSetting("my.key.2.secondField", "anotherSecondValue", projectUuid);
  303. assertThat(settingsChangeNotifier.wasCalled).isFalse();
  304. }
  305. @Test
  306. public void persist_multi_value_with_type_logIn() {
  307. definitions.addComponent(PropertyDefinition
  308. .builder("my.key")
  309. .name("foo")
  310. .description("desc")
  311. .category("cat")
  312. .subCategory("subCat")
  313. .type(PropertyType.USER_LOGIN)
  314. .defaultValue("default")
  315. .multiValues(true)
  316. .build());
  317. db.users().insertUser(newUserDto().setLogin("login.1"));
  318. db.users().insertUser(newUserDto().setLogin("login.2"));
  319. callForMultiValueGlobalSetting("my.key", List.of("login.1", "login.2"));
  320. assertGlobalSetting("my.key", "login.1,login.2");
  321. }
  322. @Test
  323. public void user_setting_is_not_updated() {
  324. propertyDb.insertProperty(newGlobalPropertyDto("my.key", "my user value").setUserUuid("42"), null, null,
  325. null, "user_login");
  326. propertyDb.insertProperty(newGlobalPropertyDto("my.key", "my global value"), null, null, null, null);
  327. callForGlobalSetting("my.key", "my new global value");
  328. assertGlobalSetting("my.key", "my new global value");
  329. assertUserSetting("my.key", "my user value", "42");
  330. }
  331. @Test
  332. public void persist_global_property_with_deprecated_key() {
  333. definitions.addComponent(PropertyDefinition
  334. .builder("my.key")
  335. .deprecatedKey("my.old.key")
  336. .name("foo")
  337. .description("desc")
  338. .category("cat")
  339. .subCategory("subCat")
  340. .type(PropertyType.STRING)
  341. .defaultValue("default")
  342. .build());
  343. callForGlobalSetting("my.old.key", "My Value");
  344. assertGlobalSetting("my.key", "My Value");
  345. }
  346. @Test
  347. public void persist_JSON_property() {
  348. definitions.addComponent(PropertyDefinition
  349. .builder("my.key")
  350. .name("foo")
  351. .description("desc")
  352. .category("cat")
  353. .subCategory("subCat")
  354. .type(PropertyType.JSON)
  355. .build());
  356. callForGlobalSetting("my.key", "{\"test\":\"value\"}");
  357. assertGlobalSetting("my.key", "{\"test\":\"value\"}");
  358. }
  359. @Test
  360. public void fail_if_JSON_invalid_for_JSON_property() {
  361. definitions.addComponent(PropertyDefinition
  362. .builder("my.key")
  363. .name("foo")
  364. .description("desc")
  365. .category("cat")
  366. .subCategory("subCat")
  367. .type(PropertyType.JSON)
  368. .build());
  369. assertThatThrownBy(() -> callForGlobalSetting("my.key", "{\"test\":\"value\""))
  370. .isInstanceOf(IllegalArgumentException.class)
  371. .hasMessage("Provided JSON is invalid");
  372. assertThatThrownBy(() -> callForGlobalSetting("my.key", "{\"test\":\"value\",}"))
  373. .isInstanceOf(IllegalArgumentException.class)
  374. .hasMessage("Provided JSON is invalid");
  375. assertThatThrownBy(() -> callForGlobalSetting("my.key", "{\"test\":[\"value\",]}"))
  376. .isInstanceOf(IllegalArgumentException.class)
  377. .hasMessage("Provided JSON is invalid");
  378. }
  379. @Test
  380. @UseDataProvider("securityJsonProperties")
  381. public void successfully_validate_json_schema(String securityPropertyKey) {
  382. String security_custom_config = """
  383. {
  384. "S3649": {
  385. "sources": [
  386. {
  387. "methodId": "My\\\\Namespace\\\\ClassName\\\\ServerRequest::getQuery"
  388. }
  389. ],
  390. "sanitizers": [
  391. {
  392. "methodId": "str_replace", "args": [
  393. 0
  394. ]
  395. }
  396. ],
  397. "validators": [
  398. {
  399. "methodId": "is_valid", "args": [
  400. 1
  401. ]
  402. }
  403. ],
  404. "sinks": [
  405. {
  406. "methodId": "mysql_query",
  407. "args": [1]
  408. }
  409. ]
  410. }
  411. }""";
  412. definitions.addComponent(PropertyDefinition
  413. .builder(securityPropertyKey)
  414. .name("foo")
  415. .description("desc")
  416. .category("cat")
  417. .subCategory("subCat")
  418. .type(PropertyType.JSON)
  419. .build());
  420. callForGlobalSetting(securityPropertyKey, security_custom_config);
  421. assertGlobalSetting(securityPropertyKey, security_custom_config);
  422. }
  423. @Test
  424. @UseDataProvider("securityJsonProperties")
  425. public void fail_json_schema_validation_when_property_has_incorrect_type(String securityPropertyKey) {
  426. String security_custom_config = """
  427. {
  428. "S3649": {
  429. "sources": [
  430. {
  431. "methodId": "My\\\\Namespace\\\\ClassName\\\\ServerRequest::getQuery"
  432. }
  433. ],
  434. "sinks": [
  435. {
  436. "methodId": 12345,
  437. "args": [1]
  438. }
  439. ]
  440. }
  441. }""";
  442. definitions.addComponent(PropertyDefinition
  443. .builder(securityPropertyKey)
  444. .name("foo")
  445. .description("desc")
  446. .category("cat")
  447. .subCategory("subCat")
  448. .type(PropertyType.JSON)
  449. .build());
  450. assertThatThrownBy(() -> callForGlobalSetting(securityPropertyKey, security_custom_config))
  451. .isInstanceOf(IllegalArgumentException.class)
  452. .hasMessageContaining("expected type: string, actual: integer at line 10, character 21, pointer: #/S3649/sinks/0/methodId");
  453. }
  454. @Test
  455. @UseDataProvider("securityJsonProperties")
  456. public void fail_json_schema_validation_when_sanitizers_have_no_args(String securityPropertyKey) {
  457. String security_custom_config = """
  458. {
  459. "S3649": {
  460. "sources": [
  461. {
  462. "methodId": "My\\\\Namespace\\\\ClassName\\\\ServerRequest::getQuery"
  463. }
  464. ],
  465. "sanitizers": [
  466. {
  467. "methodId": "SomeSanitizer"
  468. }
  469. ]
  470. }
  471. }""";
  472. definitions.addComponent(PropertyDefinition
  473. .builder(securityPropertyKey)
  474. .name("foo")
  475. .description("desc")
  476. .category("cat")
  477. .subCategory("subCat")
  478. .type(PropertyType.JSON)
  479. .build());
  480. assertThatThrownBy(() -> callForGlobalSetting(securityPropertyKey, security_custom_config))
  481. .isInstanceOf(IllegalArgumentException.class)
  482. .hasMessageContaining("required properties are missing: args at line 9, character 8, pointer: #/S3649/sanitizers/0");
  483. }
  484. @Test
  485. @UseDataProvider("securityJsonProperties")
  486. public void fail_json_schema_validation_when_validators_have_empty_args_array(String securityPropertyKey) {
  487. String security_custom_config = """
  488. {
  489. "S3649": {
  490. "sources": [
  491. {
  492. "methodId": "My\\\\Namespace\\\\ClassName\\\\ServerRequest::getQuery"
  493. }
  494. ],
  495. "validators": [
  496. {
  497. "methodId": "SomeValidator",
  498. "args": []
  499. }
  500. ]
  501. }
  502. }""";
  503. definitions.addComponent(PropertyDefinition
  504. .builder(securityPropertyKey)
  505. .name("foo")
  506. .description("desc")
  507. .category("cat")
  508. .subCategory("subCat")
  509. .type(PropertyType.JSON)
  510. .build());
  511. assertThatThrownBy(() -> callForGlobalSetting(securityPropertyKey, security_custom_config))
  512. .isInstanceOf(IllegalArgumentException.class)
  513. .hasMessageContaining("expected minimum items: 1, found only 0 at line 11, character 18, pointer: #/S3649/validators/0/args");
  514. }
  515. @Test
  516. @UseDataProvider("securityJsonProperties")
  517. public void fail_json_schema_validation_when_property_has_unknown_attribute(String securityPropertyKey) {
  518. String security_custom_config = """
  519. {
  520. "S3649": {
  521. "sources": [
  522. {
  523. "methodId": "My\\\\Namespace\\\\ClassName\\\\ServerRequest::getQuery"
  524. }
  525. ],
  526. "unknown": [
  527. {
  528. "methodId": 12345,
  529. "args": [1]
  530. }
  531. ]
  532. }
  533. }""";
  534. definitions.addComponent(PropertyDefinition
  535. .builder(securityPropertyKey)
  536. .name("foo")
  537. .description("desc")
  538. .category("cat")
  539. .subCategory("subCat")
  540. .type(PropertyType.JSON)
  541. .build());
  542. assertThatThrownBy(() -> callForGlobalSetting(securityPropertyKey, security_custom_config))
  543. .isInstanceOf(IllegalArgumentException.class)
  544. .hasMessageContaining("false schema always fails at line 8, character 16, pointer: #/S3649/unknown");
  545. }
  546. @Test
  547. public void persist_global_setting_with_non_ascii_characters() {
  548. callForGlobalSetting("my.key", "fi±∞…");
  549. assertGlobalSetting("my.key", "fi±∞…");
  550. assertThat(settingsChangeNotifier.wasCalled).isTrue();
  551. }
  552. @Test
  553. public void fail_when_no_key() {
  554. assertThatThrownBy(() -> callForGlobalSetting(null, "my value"))
  555. .isInstanceOf(IllegalArgumentException.class);
  556. }
  557. @Test
  558. public void fail_when_empty_key_value() {
  559. assertThatThrownBy(() -> callForGlobalSetting(" ", "my value"))
  560. .isInstanceOf(IllegalArgumentException.class)
  561. .hasMessage("The 'key' parameter is missing");
  562. }
  563. @Test
  564. public void fail_when_no_value() {
  565. assertThatThrownBy(() -> callForGlobalSetting("my.key", null))
  566. .isInstanceOf(BadRequestException.class)
  567. .hasMessage("Either 'value', 'values' or 'fieldValues' must be provided");
  568. }
  569. @Test
  570. public void fail_when_empty_value() {
  571. assertThatThrownBy(() -> callForGlobalSetting("my.key", ""))
  572. .isInstanceOf(BadRequestException.class)
  573. .hasMessage("A non empty value must be provided");
  574. }
  575. @Test
  576. public void fail_when_one_empty_value_on_multi_value() {
  577. List<String> values = List.of("oneValue", " ", "anotherValue");
  578. assertThatThrownBy(() -> callForMultiValueGlobalSetting("my.key", values))
  579. .isInstanceOf(BadRequestException.class)
  580. .hasMessage("A non empty value must be provided");
  581. }
  582. @Test
  583. public void throw_ForbiddenException_if_not_system_administrator() {
  584. userSession.logIn().setNonSystemAdministrator();
  585. assertThatThrownBy(() -> callForGlobalSetting("my.key", "my value"))
  586. .isInstanceOf(ForbiddenException.class)
  587. .hasMessage("Insufficient privileges");
  588. }
  589. @Test
  590. public void fail_when_data_and_type_do_not_match() {
  591. definitions.addComponent(PropertyDefinition
  592. .builder("my.key")
  593. .name("foo")
  594. .description("desc")
  595. .category("cat")
  596. .subCategory("subCat")
  597. .type(PropertyType.INTEGER)
  598. .defaultValue("default")
  599. .build());
  600. i18n.put("property.error.notInteger", "Not an integer error message");
  601. assertThatThrownBy(() -> callForGlobalSetting("my.key", "My Value"))
  602. .isInstanceOf(BadRequestException.class)
  603. .hasMessage("Not an integer error message");
  604. }
  605. @Test
  606. public void fail_when_data_and_login_type_with_invalid_logIn() {
  607. definitions.addComponent(PropertyDefinition
  608. .builder("my.key")
  609. .name("foo")
  610. .description("desc")
  611. .category("cat")
  612. .subCategory("subCat")
  613. .type(PropertyType.USER_LOGIN)
  614. .defaultValue("default")
  615. .multiValues(true)
  616. .build());
  617. db.users().insertUser(newUserDto().setLogin("login.1"));
  618. db.users().insertUser(newUserDto().setLogin("login.2").setActive(false));
  619. List<String> values = List.of("login.1", "login.2");
  620. assertThatThrownBy(() -> callForMultiValueGlobalSetting("my.key", values))
  621. .isInstanceOf(BadRequestException.class)
  622. .hasMessage("Error when validating login setting with key 'my.key' and values [login.1, login.2]. A value is not a valid login.");
  623. }
  624. @Test
  625. public void fail_when_data_and_type_do_not_match_with_unknown_error_key() {
  626. definitions.addComponent(PropertyDefinition
  627. .builder("my.key")
  628. .name("foo")
  629. .description("desc")
  630. .category("cat")
  631. .subCategory("subCat")
  632. .type(PropertyType.INTEGER)
  633. .defaultValue("default")
  634. .build());
  635. List<String> values = List.of("My Value", "My Other Value");
  636. assertThatThrownBy(() -> callForMultiValueGlobalSetting("my.key", values))
  637. .isInstanceOf(BadRequestException.class)
  638. .hasMessage("Error when validating setting with key 'my.key' and value [My Value, My Other Value]");
  639. }
  640. @Test
  641. public void fail_when_global_with_property_only_on_projects() {
  642. definitions.addComponent(PropertyDefinition
  643. .builder("my.key")
  644. .name("foo")
  645. .description("desc")
  646. .category("cat")
  647. .subCategory("subCat")
  648. .type(PropertyType.INTEGER)
  649. .defaultValue("default")
  650. .onlyOnQualifiers(Qualifiers.PROJECT)
  651. .build());
  652. assertThatThrownBy(() -> callForGlobalSetting("my.key", "42"))
  653. .isInstanceOf(BadRequestException.class)
  654. .hasMessage("Setting 'my.key' cannot be global");
  655. }
  656. @Test
  657. public void fail_when_view_property_when_on_projects_only() {
  658. definitions.addComponent(PropertyDefinition
  659. .builder("my.key")
  660. .name("foo")
  661. .description("desc")
  662. .category("cat")
  663. .subCategory("subCat")
  664. .type(PropertyType.STRING)
  665. .defaultValue("default")
  666. .onQualifiers(Qualifiers.PROJECT)
  667. .build());
  668. ComponentDto view = db.components().insertPublicPortfolio();
  669. i18n.put("qualifier." + Qualifiers.VIEW, "View");
  670. assertThatThrownBy(() -> {
  671. logInAsPortfolioAdministrator(db.components().getPortfolioDto(view));
  672. callForProjectSettingByKey("my.key", "My Value", view.getKey());
  673. })
  674. .isInstanceOf(BadRequestException.class)
  675. .hasMessage("Setting 'my.key' cannot be set on a View");
  676. }
  677. @Test
  678. public void fail_when_property_with_definition_when_component_qualifier_does_not_match() {
  679. PortfolioDto portfolio = db.components().insertPrivatePortfolioDto();
  680. definitions.addComponent(PropertyDefinition
  681. .builder("my.key")
  682. .name("foo")
  683. .description("desc")
  684. .category("cat")
  685. .subCategory("subCat")
  686. .type(PropertyType.STRING)
  687. .defaultValue("default")
  688. .onQualifiers(Qualifiers.PROJECT)
  689. .build());
  690. i18n.put("qualifier." + portfolio.getQualifier(), "CptLabel");
  691. logInAsPortfolioAdministrator(portfolio);
  692. assertThatThrownBy(() -> callForProjectSettingByKey("my.key", "My Value", portfolio.getKey()))
  693. .isInstanceOf(BadRequestException.class)
  694. .hasMessage("Setting 'my.key' cannot be set on a CptLabel");
  695. }
  696. @Test
  697. public void succeed_for_property_without_definition_when_set_on_project_component() {
  698. ProjectDto project = randomPublicOrPrivateProject().getProjectDto();
  699. succeedForPropertyWithoutDefinitionAndValidComponent(project);
  700. }
  701. @Test
  702. public void fail_for_property_without_definition_when_set_on_directory_component() {
  703. ProjectData projectData = randomPublicOrPrivateProject();
  704. ComponentDto directory = db.components().insertComponent(ComponentTesting.newDirectory(projectData.getMainBranchComponent(), "A/B"));
  705. failForPropertyWithoutDefinitionOnUnsupportedComponent(projectData.getProjectDto(), directory);
  706. }
  707. @Test
  708. public void fail_for_property_without_definition_when_set_on_file_component() {
  709. ProjectData projectData = randomPublicOrPrivateProject();
  710. ComponentDto file = db.components().insertComponent(ComponentTesting.newFileDto(projectData.getMainBranchComponent()));
  711. failForPropertyWithoutDefinitionOnUnsupportedComponent(projectData.getProjectDto(), file);
  712. }
  713. @Test
  714. public void succeed_for_property_without_definition_when_set_on_view_component() {
  715. PortfolioDto view = db.components().insertPrivatePortfolioDto();
  716. succeedForPropertyWithoutDefinitionAndValidComponent(view);
  717. }
  718. @Test
  719. public void succeed_for_property_without_definition_when_set_on_subview_component() {
  720. ComponentDto view = db.components().insertPrivatePortfolio();
  721. ComponentDto subview = db.components().insertComponent(ComponentTesting.newSubPortfolio(view));
  722. failForPropertyWithoutDefinitionOnUnsupportedComponent(db.components().getPortfolioDto(view), subview);
  723. }
  724. @Test
  725. public void fail_for_property_without_definition_when_set_on_projectCopy_component() {
  726. ComponentDto view = db.components().insertPrivatePortfolio();
  727. ComponentDto projectCopy = db.components().insertComponent(ComponentTesting.newProjectCopy("a", db.components().insertPrivateProject().getMainBranchComponent(), view));
  728. failForPropertyWithoutDefinitionOnUnsupportedComponent(db.components().getPortfolioDto(view), projectCopy);
  729. }
  730. private void succeedForPropertyWithoutDefinitionAndValidComponent(ProjectDto project) {
  731. logInAsProjectAdministrator(project);
  732. callForProjectSettingByKey("my.key", "My Value", project.getKey());
  733. assertComponentSetting("my.key", "My Value", project.getUuid());
  734. }
  735. private void succeedForPropertyWithoutDefinitionAndValidComponent(PortfolioDto portfolioDto) {
  736. logInAsPortfolioAdministrator(portfolioDto);
  737. callForProjectSettingByKey("my.key", "My Value", portfolioDto.getKey());
  738. assertComponentSetting("my.key", "My Value", portfolioDto.getUuid());
  739. }
  740. private void failForPropertyWithoutDefinitionOnUnsupportedComponent(ProjectDto project, ComponentDto component) {
  741. i18n.put("qualifier." + component.qualifier(), "QualifierLabel");
  742. logInAsProjectAdministrator(project);
  743. assertThatThrownBy(() -> callForProjectSettingByKey("my.key", "My Value", component.getKey()))
  744. .isInstanceOf(NotFoundException.class)
  745. .hasMessage(String.format("Component key '%s' not found", component.getKey()));
  746. }
  747. private void failForPropertyWithoutDefinitionOnUnsupportedComponent(PortfolioDto portfolio, ComponentDto component) {
  748. i18n.put("qualifier." + component.qualifier(), "QualifierLabel");
  749. logInAsPortfolioAdministrator(portfolio);
  750. assertThatThrownBy(() -> callForProjectSettingByKey("my.key", "My Value", component.getKey()))
  751. .isInstanceOf(NotFoundException.class)
  752. .hasMessage(String.format("Component key '%s' not found", component.getKey()));
  753. }
  754. @Test
  755. public void fail_when_single_and_multi_value_provided() {
  756. List<String> value = List.of("Another Value");
  757. assertThatThrownBy(() -> call("my.key", "My Value", value, null, null))
  758. .isInstanceOf(BadRequestException.class)
  759. .hasMessage("Either 'value', 'values' or 'fieldValues' must be provided");
  760. }
  761. @Test
  762. public void fail_when_multi_definition_and_single_value_provided() {
  763. definitions.addComponent(PropertyDefinition
  764. .builder("my.key")
  765. .name("foo")
  766. .description("desc")
  767. .category("cat")
  768. .type(PropertyType.STRING)
  769. .multiValues(true)
  770. .build());
  771. assertThatThrownBy(() -> callForGlobalSetting("my.key", "My Value"))
  772. .isInstanceOf(BadRequestException.class)
  773. .hasMessage("Parameter 'value' must be used for single value setting. Parameter 'values' must be used for multi value setting.");
  774. }
  775. @Test
  776. public void fail_when_single_definition_and_multi_value_provided() {
  777. definitions.addComponent(PropertyDefinition
  778. .builder("my.key")
  779. .name("foo")
  780. .description("desc")
  781. .category("cat")
  782. .type(PropertyType.STRING)
  783. .multiValues(false)
  784. .build());
  785. assertThatThrownBy(() -> callForMultiValueGlobalSetting("my.key", List.of("My Value")))
  786. .isInstanceOf(BadRequestException.class)
  787. .hasMessage("Parameter 'value' must be used for single value setting. Parameter 'values' must be used for multi value setting.");
  788. }
  789. @Test
  790. public void fail_when_empty_values_on_one_property_set() {
  791. definitions.addComponent(PropertyDefinition
  792. .builder("my.key")
  793. .name("foo")
  794. .description("desc")
  795. .category("cat")
  796. .subCategory("subCat")
  797. .type(PropertyType.PROPERTY_SET)
  798. .defaultValue("default")
  799. .fields(List.of(
  800. PropertyFieldDefinition.build("firstField")
  801. .name("First Field")
  802. .type(PropertyType.STRING)
  803. .build(),
  804. PropertyFieldDefinition.build("secondField")
  805. .name("Second Field")
  806. .type(PropertyType.STRING)
  807. .build()))
  808. .build());
  809. assertThatThrownBy(() -> callForGlobalPropertySet("my.key", List.of(
  810. GSON.toJson(Map.of("firstField", "firstValue", "secondField", "secondValue")),
  811. GSON.toJson(Map.of("firstField", "", "secondField", "")),
  812. GSON.toJson(Map.of("firstField", "yetFirstValue", "secondField", "yetSecondValue")))))
  813. .isInstanceOf(BadRequestException.class)
  814. .hasMessage("A non empty value must be provided");
  815. }
  816. @Test
  817. public void do_not_fail_when_only_one_empty_value_on_one_property_set() {
  818. definitions.addComponent(PropertyDefinition
  819. .builder("my.key")
  820. .name("foo")
  821. .description("desc")
  822. .category("cat")
  823. .subCategory("subCat")
  824. .type(PropertyType.PROPERTY_SET)
  825. .defaultValue("default")
  826. .fields(List.of(
  827. PropertyFieldDefinition.build("firstField")
  828. .name("First Field")
  829. .type(PropertyType.STRING)
  830. .build(),
  831. PropertyFieldDefinition.build("secondField")
  832. .name("Second Field")
  833. .type(PropertyType.STRING)
  834. .build()))
  835. .build());
  836. callForGlobalPropertySet("my.key", List.of(
  837. GSON.toJson(Map.of("firstField", "firstValue", "secondField", "secondValue")),
  838. GSON.toJson(Map.of("firstField", "anotherFirstValue", "secondField", "")),
  839. GSON.toJson(Map.of("firstField", "yetFirstValue", "secondField", "yetSecondValue"))));
  840. assertGlobalSetting("my.key", "1,2,3");
  841. }
  842. @Test
  843. public void fail_when_property_set_setting_is_not_defined() {
  844. assertThatThrownBy(() -> callForGlobalPropertySet("my.key", singletonList("{\"field\":\"value\"}")))
  845. .isInstanceOf(BadRequestException.class)
  846. .hasMessage("Setting 'my.key' is undefined");
  847. }
  848. @Test
  849. public void fail_when_property_set_with_unknown_field() {
  850. definitions.addComponent(PropertyDefinition
  851. .builder("my.key")
  852. .name("foo")
  853. .description("desc")
  854. .category("cat")
  855. .subCategory("subCat")
  856. .type(PropertyType.PROPERTY_SET)
  857. .defaultValue("default")
  858. .fields(List.of(
  859. PropertyFieldDefinition.build("field")
  860. .name("Field")
  861. .type(PropertyType.STRING)
  862. .build()))
  863. .build());
  864. List<String> values = List.of(GSON.toJson(Map.of("field", "value", "unknownField", "anotherValue")));
  865. assertThatThrownBy(() -> callForGlobalPropertySet("my.key", values))
  866. .isInstanceOf(BadRequestException.class)
  867. .hasMessage("Unknown field key 'unknownField' for setting 'my.key'");
  868. }
  869. @Test
  870. public void fail_when_property_set_has_field_with_incorrect_type() {
  871. definitions.addComponent(PropertyDefinition
  872. .builder("my.key")
  873. .name("foo")
  874. .description("desc")
  875. .category("cat")
  876. .subCategory("subCat")
  877. .type(PropertyType.PROPERTY_SET)
  878. .defaultValue("default")
  879. .fields(List.of(
  880. PropertyFieldDefinition.build("field")
  881. .name("Field")
  882. .type(PropertyType.INTEGER)
  883. .build()))
  884. .build());
  885. List<String> values = List.of(GSON.toJson(Map.of("field", "notAnInt")));
  886. assertThatThrownBy(() -> callForGlobalPropertySet("my.key", values))
  887. .isInstanceOf(BadRequestException.class)
  888. .hasMessage("Error when validating setting with key 'my.key'. Field 'field' has incorrect value 'notAnInt'.");
  889. }
  890. @Test
  891. public void fail_when_property_set_has_a_null_field_value() {
  892. definitions.addComponent(PropertyDefinition
  893. .builder("my.key")
  894. .name("foo")
  895. .description("desc")
  896. .category("cat")
  897. .subCategory("subCat")
  898. .type(PropertyType.PROPERTY_SET)
  899. .defaultValue("default")
  900. .fields(List.of(
  901. PropertyFieldDefinition.build("field")
  902. .name("Field")
  903. .type(PropertyType.STRING)
  904. .build()))
  905. .build());
  906. List<String> values = List.of("{\"field\": null}");
  907. assertThatThrownBy(() -> callForGlobalPropertySet("my.key", values))
  908. .isInstanceOf(BadRequestException.class)
  909. .hasMessage("A non empty value must be provided");
  910. }
  911. @Test
  912. public void fail_when_property_set_with_invalid_json() {
  913. definitions.addComponent(PropertyDefinition
  914. .builder("my.key")
  915. .name("foo")
  916. .description("desc")
  917. .category("cat")
  918. .subCategory("subCat")
  919. .type(PropertyType.PROPERTY_SET)
  920. .defaultValue("default")
  921. .fields(List.of(
  922. PropertyFieldDefinition.build("field")
  923. .name("Field")
  924. .type(PropertyType.STRING)
  925. .build()))
  926. .build());
  927. List<String> values = List.of("incorrectJson:incorrectJson");
  928. assertThatThrownBy(() -> callForGlobalPropertySet("my.key", values))
  929. .isInstanceOf(BadRequestException.class)
  930. .hasMessage("JSON 'incorrectJson:incorrectJson' does not respect expected format for setting 'my.key'. " +
  931. "Ex: {\"field1\":\"value1\", \"field2\":\"value2\"}");
  932. }
  933. @Test
  934. public void fail_when_property_set_with_json_of_the_wrong_format() {
  935. definitions.addComponent(PropertyDefinition
  936. .builder("my.key")
  937. .name("foo")
  938. .description("desc")
  939. .category("cat")
  940. .subCategory("subCat")
  941. .type(PropertyType.PROPERTY_SET)
  942. .defaultValue("default")
  943. .fields(List.of(
  944. PropertyFieldDefinition.build("field")
  945. .name("Field")
  946. .type(PropertyType.STRING)
  947. .build()))
  948. .build());
  949. List<String> values = List.of("[{\"field\":\"v1\"}, {\"field\":\"v2\"}]");
  950. assertThatThrownBy(() -> callForGlobalPropertySet("my.key", values))
  951. .isInstanceOf(BadRequestException.class)
  952. .hasMessage("JSON '[{\"field\":\"v1\"}, {\"field\":\"v2\"}]' does not respect expected format for setting 'my.key'. " +
  953. "Ex: {\"field1\":\"value1\", \"field2\":\"value2\"}");
  954. }
  955. @Test
  956. public void fail_when_property_set_on_component_of_global_setting() {
  957. definitions.addComponent(PropertyDefinition
  958. .builder("my.key")
  959. .name("foo")
  960. .description("desc")
  961. .category("cat")
  962. .subCategory("subCat")
  963. .type(PropertyType.PROPERTY_SET)
  964. .defaultValue("default")
  965. .fields(List.of(PropertyFieldDefinition.build("firstField").name("First Field").type(PropertyType.STRING).build()))
  966. .build());
  967. i18n.put("qualifier." + Qualifiers.PROJECT, "Project");
  968. ProjectDto project = db.components().insertPrivateProject().getProjectDto();
  969. logInAsProjectAdministrator(project);
  970. List<String> values = List.of(GSON.toJson(Map.of("firstField", "firstValue")));
  971. assertThatThrownBy(() -> callForComponentPropertySet("my.key", values, project.getKey()))
  972. .isInstanceOf(BadRequestException.class)
  973. .hasMessage("Setting 'my.key' cannot be set on a Project");
  974. }
  975. @Test
  976. public void fail_when_component_not_found() {
  977. TestRequest testRequest = ws.newRequest()
  978. .setParam("key", "foo")
  979. .setParam("value", "2")
  980. .setParam("component", "unknown");
  981. assertThatThrownBy(testRequest::execute)
  982. .isInstanceOf(NotFoundException.class)
  983. .hasMessage("Component key 'unknown' not found");
  984. }
  985. @Test
  986. public void fail_when_setting_key_is_defined_in_sonar_properties() {
  987. ProjectDto project = db.components().insertPrivateProject().getProjectDto();
  988. logInAsProjectAdministrator(project);
  989. String settingKey = ProcessProperties.Property.JDBC_URL.getKey();
  990. TestRequest testRequest = ws.newRequest()
  991. .setParam("key", settingKey)
  992. .setParam("value", "any value")
  993. .setParam("component", project.getKey());
  994. assertThatThrownBy(testRequest::execute)
  995. .isInstanceOf(IllegalArgumentException.class)
  996. .hasMessage(format("Setting '%s' can only be used in sonar.properties", settingKey));
  997. }
  998. @Test
  999. public void definition() {
  1000. WebService.Action definition = ws.getDef();
  1001. assertThat(definition.key()).isEqualTo("set");
  1002. assertThat(definition.isPost()).isTrue();
  1003. assertThat(definition.isInternal()).isFalse();
  1004. assertThat(definition.since()).isEqualTo("6.1");
  1005. assertThat(definition.params()).extracting(Param::key)
  1006. .containsOnly("key", "value", "values", "fieldValues", "component");
  1007. }
  1008. @Test
  1009. public void call_whenEmailPropertyValid_shouldSucceed() {
  1010. definitions.addComponent(PropertyDefinition
  1011. .builder("my.key")
  1012. .name("foo")
  1013. .description("desc")
  1014. .type(PropertyType.EMAIL)
  1015. .build());
  1016. callForGlobalSetting("my.key", "test@sonarsource.com");
  1017. assertGlobalSetting("my.key", "test@sonarsource.com");
  1018. }
  1019. @Test
  1020. public void call_whenEmailPropertyInvalid_shouldFail() {
  1021. definitions.addComponent(PropertyDefinition
  1022. .builder("my.key")
  1023. .name("foo")
  1024. .description("desc")
  1025. .type(PropertyType.EMAIL)
  1026. .build());
  1027. i18n.put("property.error.notEmail", "Not a valid email address");
  1028. assertThatThrownBy(() -> callForGlobalSetting("my.key", "test1@sonarsource.com,test2@sonarsource.com"))
  1029. .isInstanceOf(BadRequestException.class)
  1030. .hasMessage("Not a valid email address");
  1031. }
  1032. private void assertGlobalSetting(String key, String value) {
  1033. PropertyDto result = dbClient.propertiesDao().selectGlobalProperty(key);
  1034. assertThat(result)
  1035. .extracting(PropertyDto::getKey, PropertyDto::getValue, PropertyDto::getEntityUuid)
  1036. .containsExactly(key, value, null);
  1037. }
  1038. private void assertUserSetting(String key, String value, String userUuid) {
  1039. List<PropertyDto> result = dbClient.propertiesDao().selectByQuery(PropertyQuery.builder().setKey(key).setUserUuid(userUuid).build(), dbSession);
  1040. assertThat(result).hasSize(1)
  1041. .extracting(PropertyDto::getKey, PropertyDto::getValue, PropertyDto::getUserUuid)
  1042. .containsExactly(tuple(key, value, userUuid));
  1043. }
  1044. private void assertComponentSetting(String key, String value, String entityUuid) {
  1045. PropertyDto result = dbClient.propertiesDao().selectProjectProperty(db.getSession(), entityUuid, key);
  1046. assertThat(result)
  1047. .isNotNull()
  1048. .extracting(PropertyDto::getKey, PropertyDto::getValue, PropertyDto::getEntityUuid)
  1049. .containsExactly(key, value, entityUuid);
  1050. }
  1051. private void callForGlobalSetting(@Nullable String key, @Nullable String value) {
  1052. call(key, value, null, null, null);
  1053. }
  1054. private void callForMultiValueGlobalSetting(@Nullable String key, @Nullable List<String> values) {
  1055. call(key, null, values, null, null);
  1056. }
  1057. private void callForGlobalPropertySet(@Nullable String key, @Nullable List<String> fieldValues) {
  1058. call(key, null, null, fieldValues, null);
  1059. }
  1060. private void callForComponentPropertySet(@Nullable String key, @Nullable List<String> fieldValues, @Nullable String componentKey) {
  1061. call(key, null, null, fieldValues, componentKey);
  1062. }
  1063. private void callForProjectSettingByKey(@Nullable String key, @Nullable String value, @Nullable String componentKey) {
  1064. call(key, value, null, null, componentKey);
  1065. }
  1066. private void call(@Nullable String key, @Nullable String value, @Nullable List<String> values, @Nullable List<String> fieldValues, @Nullable String componentKey) {
  1067. TestRequest request = ws.newRequest();
  1068. if (key != null) {
  1069. request.setParam("key", key);
  1070. }
  1071. if (value != null) {
  1072. request.setParam("value", value);
  1073. }
  1074. if (values != null) {
  1075. request.setMultiParam("values", values);
  1076. }
  1077. if (fieldValues != null) {
  1078. request.setMultiParam("fieldValues", fieldValues);
  1079. }
  1080. if (componentKey != null) {
  1081. request.setParam("component", componentKey);
  1082. }
  1083. request.execute();
  1084. }
  1085. private static class FakeSettingsNotifier extends SettingsChangeNotifier {
  1086. private final DbClient dbClient;
  1087. private boolean wasCalled = false;
  1088. private FakeSettingsNotifier(DbClient dbClient) {
  1089. this.dbClient = dbClient;
  1090. }
  1091. @Override
  1092. public void onGlobalPropertyChange(String key, @Nullable String value) {
  1093. wasCalled = true;
  1094. PropertyDto property = dbClient.propertiesDao().selectGlobalProperty(key);
  1095. assertThat(property.getValue()).isEqualTo(value);
  1096. }
  1097. }
  1098. private void logInAsPortfolioAdministrator(PortfolioDto portfolio) {
  1099. userSession.logIn().addPortfolioPermission(UserRole.ADMIN, portfolio);
  1100. }
  1101. private void logInAsProjectAdministrator(ProjectDto project) {
  1102. userSession.logIn().addProjectPermission(UserRole.ADMIN, project);
  1103. }
  1104. private ProjectData randomPublicOrPrivateProject() {
  1105. return ThreadLocalRandom.current().nextBoolean() ? db.components().insertPrivateProject() : db.components().insertPublicProject();
  1106. }
  1107. }