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.

HotspotRuleDescriptionTest.java 12KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268
  1. /*
  2. * SonarQube
  3. * Copyright (C) 2009-2022 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.rule;
  21. import com.tngtech.java.junit.dataprovider.DataProvider;
  22. import com.tngtech.java.junit.dataprovider.DataProviderRunner;
  23. import com.tngtech.java.junit.dataprovider.UseDataProvider;
  24. import org.junit.Test;
  25. import org.junit.runner.RunWith;
  26. import org.sonar.db.rule.RuleDto;
  27. import org.sonar.db.rule.RuleTesting;
  28. import static org.apache.commons.lang.RandomStringUtils.randomAlphabetic;
  29. import static org.assertj.core.api.Assertions.assertThat;
  30. import static org.sonar.db.rule.RuleDescriptionSectionDto.createDefaultRuleDescriptionSection;
  31. @RunWith(DataProviderRunner.class)
  32. public class HotspotRuleDescriptionTest {
  33. @Test
  34. public void parse_returns_all_empty_fields_when_no_description() {
  35. RuleDto dto = RuleTesting.newRuleWithoutDescriptionSection();
  36. HotspotRuleDescription result = HotspotRuleDescription.from(dto);
  37. assertThat(result.getRisk()).isEmpty();
  38. assertThat(result.getVulnerable()).isEmpty();
  39. assertThat(result.getFixIt()).isEmpty();
  40. }
  41. @Test
  42. public void parse_returns_all_empty_fields_when_empty_description() {
  43. RuleDto dto = RuleTesting.newRuleWithoutDescriptionSection().addRuleDescriptionSectionDto(createDefaultRuleDescriptionSection("uuid", ""));
  44. HotspotRuleDescription result = HotspotRuleDescription.from(dto);
  45. assertThat(result.getRisk()).isEmpty();
  46. assertThat(result.getVulnerable()).isEmpty();
  47. assertThat(result.getFixIt()).isEmpty();
  48. }
  49. @Test
  50. @UseDataProvider("descriptionsWithoutTitles")
  51. public void parse_to_risk_description_fields_when_desc_contains_no_section(String description) {
  52. RuleDto dto = RuleTesting.newRuleWithoutDescriptionSection().addRuleDescriptionSectionDto(createDefaultRuleDescriptionSection("uuid", description));
  53. HotspotRuleDescription result = HotspotRuleDescription.from(dto);
  54. assertThat(result.getRisk()).contains(description);
  55. assertThat(result.getVulnerable()).isEmpty();
  56. assertThat(result.getFixIt()).isEmpty();
  57. }
  58. @DataProvider
  59. public static Object[][] descriptionsWithoutTitles() {
  60. return new Object[][] {
  61. {randomAlphabetic(123)},
  62. {"bar\n" +
  63. "acme\n" +
  64. "foo"}
  65. };
  66. }
  67. @Test
  68. public void parse_return_null_risk_when_desc_starts_with_ask_yourself_title() {
  69. RuleDto dto = RuleTesting.newRuleWithoutDescriptionSection().addRuleDescriptionSectionDto(
  70. createDefaultRuleDescriptionSection("uuid", (ASKATRISK + RECOMMENTEDCODINGPRACTICE)));
  71. HotspotRuleDescription result = HotspotRuleDescription.from(dto);
  72. assertThat(result.getRisk()).isEmpty();
  73. assertThat(result.getVulnerable()).contains(ASKATRISK);
  74. assertThat(result.getFixIt()).contains(RECOMMENTEDCODINGPRACTICE);
  75. }
  76. @Test
  77. public void parse_return_null_vulnerable_when_no_ask_yourself_whether_title() {
  78. RuleDto dto = RuleTesting.newRuleWithoutDescriptionSection()
  79. .addRuleDescriptionSectionDto(createDefaultRuleDescriptionSection("uuid", (DESCRIPTION + RECOMMENTEDCODINGPRACTICE)));
  80. HotspotRuleDescription result = HotspotRuleDescription.from(dto);
  81. assertThat(result.getRisk()).contains(DESCRIPTION);
  82. assertThat(result.getVulnerable()).isEmpty();
  83. assertThat(result.getFixIt()).contains(RECOMMENTEDCODINGPRACTICE);
  84. }
  85. @Test
  86. public void parse_return_null_fixIt_when_desc_has_no_Recommended_Secure_Coding_Practices_title() {
  87. RuleDto dto = RuleTesting.newRuleWithoutDescriptionSection()
  88. .addRuleDescriptionSectionDto(createDefaultRuleDescriptionSection("uuid", (DESCRIPTION + ASKATRISK)));
  89. HotspotRuleDescription result = HotspotRuleDescription.from(dto);
  90. assertThat(result.getRisk()).contains(DESCRIPTION);
  91. assertThat(result.getVulnerable()).contains(ASKATRISK);
  92. assertThat(result.getFixIt()).isEmpty();
  93. }
  94. @Test
  95. public void parse_with_noncompliant_section_not_removed() {
  96. RuleDto dto = RuleTesting.newRuleWithoutDescriptionSection().addRuleDescriptionSectionDto(
  97. createDefaultRuleDescriptionSection("uuid", (DESCRIPTION + NONCOMPLIANTCODE + COMPLIANTCODE)));
  98. HotspotRuleDescription result = HotspotRuleDescription.from(dto);
  99. assertThat(result.getRisk()).contains(DESCRIPTION);
  100. assertThat(result.getVulnerable()).contains(NONCOMPLIANTCODE);
  101. assertThat(result.getFixIt()).contains(COMPLIANTCODE);
  102. }
  103. @Test
  104. public void parse_moved_noncompliant_code() {
  105. RuleDto dto = RuleTesting.newRuleWithoutDescriptionSection().addRuleDescriptionSectionDto(
  106. createDefaultRuleDescriptionSection("uuid", (DESCRIPTION + RECOMMENTEDCODINGPRACTICE + NONCOMPLIANTCODE + SEE)));
  107. HotspotRuleDescription result = HotspotRuleDescription.from(dto);
  108. assertThat(result.getRisk()).contains(DESCRIPTION);
  109. assertThat(result.getVulnerable()).contains(NONCOMPLIANTCODE);
  110. assertThat(result.getFixIt()).contains(RECOMMENTEDCODINGPRACTICE + SEE);
  111. }
  112. @Test
  113. public void parse_moved_sensitivecode_code() {
  114. RuleDto dto = RuleTesting.newRuleWithoutDescriptionSection().addRuleDescriptionSectionDto(
  115. createDefaultRuleDescriptionSection("uuid", (DESCRIPTION + ASKATRISK + RECOMMENTEDCODINGPRACTICE + SENSITIVECODE + SEE)));
  116. HotspotRuleDescription result = HotspotRuleDescription.from(dto);
  117. assertThat(result.getRisk()).contains(DESCRIPTION);
  118. assertThat(result.getVulnerable()).contains(ASKATRISK + SENSITIVECODE);
  119. assertThat(result.getFixIt()).contains(RECOMMENTEDCODINGPRACTICE + SEE);
  120. }
  121. @Test
  122. public void parse_custom_rule_description() {
  123. String ruleDescription = "This is the custom rule description";
  124. String exceptionsContent = "This the exceptions section content";
  125. String askContent = "This is the ask section content";
  126. String recommendedContent = "This is the recommended section content";
  127. RuleDto dto = RuleTesting.newRuleWithoutDescriptionSection()
  128. .setTemplateUuid("123")
  129. .setDescriptionFormat(RuleDto.Format.MARKDOWN)
  130. .addRuleDescriptionSectionDto(createDefaultRuleDescriptionSection(
  131. "uuid", ruleDescription + "\n"
  132. + "== Exceptions" + "\n"
  133. + exceptionsContent + "\n"
  134. + "== Ask Yourself Whether" + "\n"
  135. + askContent + "\n"
  136. + "== Recommended Secure Coding Practices" + "\n"
  137. + recommendedContent + "\n"
  138. ));
  139. HotspotRuleDescription result = HotspotRuleDescription.from(dto);
  140. assertThat(result.getRisk().get()).hasToString(
  141. ruleDescription + "<br/>"
  142. + "<h2>Exceptions</h2>"
  143. + exceptionsContent + "<br/>"
  144. );
  145. assertThat(result.getVulnerable().get()).hasToString(
  146. "<h2>Ask Yourself Whether</h2>"
  147. + askContent + "<br/>"
  148. );
  149. assertThat(result.getFixIt().get()).hasToString(
  150. "<h2>Recommended Secure Coding Practices</h2>"
  151. + recommendedContent + "<br/>"
  152. );
  153. }
  154. /*
  155. * Bunch of static constant to create rule description.
  156. */
  157. private static final String DESCRIPTION = "<p>The use of operators pairs ( <code>=+</code>, <code>=-</code> or <code>=!</code> ) where the reversed, single operator was meant (<code>+=</code>,\n"
  158. +
  159. "<code>-=</code> or <code>!=</code>) will compile and run, but not produce the expected results.</p>\n" +
  160. "<p>This rule raises an issue when <code>=+</code>, <code>=-</code>, or <code>=!</code> is used without any spacing between the two operators and when\n" +
  161. "there is at least one whitespace character after.</p>\n";
  162. private static final String NONCOMPLIANTCODE = "<h2>Noncompliant Code Example</h2>\n" +
  163. "<pre>Integer target = -5;\n" +
  164. "Integer num = 3;\n" +
  165. "\n" +
  166. "target =- num; // Noncompliant; target = -3. Is that really what's meant?\n" +
  167. "target =+ num; // Noncompliant; target = 3\n" +
  168. "</pre>\n";
  169. private static final String COMPLIANTCODE = "<h2>Compliant Solution</h2>\n" +
  170. "<pre>Integer target = -5;\n" +
  171. "Integer num = 3;\n" +
  172. "\n" +
  173. "target = -num; // Compliant; intent to assign inverse value of num is clear\n" +
  174. "target += num;\n" +
  175. "</pre>\n";
  176. private static final String SEE = "<h2>See</h2>\n" +
  177. "<ul>\n" +
  178. " <li> <a href=\"https://cwe.mitre.org/data/definitions/352.html\">MITRE, CWE-352</a> - Cross-Site Request Forgery (CSRF) </li>\n" +
  179. " <li> <a href=\"https://www.owasp.org/index.php/Top_10-2017_A6-Security_Misconfiguration\">OWASP Top 10 2017 Category A6</a> - Security\n" +
  180. " Misconfiguration </li>\n" +
  181. " <li> <a href=\"https://www.owasp.org/index.php/Cross-Site_Request_Forgery_%28CSRF%29\">OWASP: Cross-Site Request Forgery</a> </li>\n" +
  182. " <li> <a href=\"https://www.sans.org/top25-software-errors/#cat1\">SANS Top 25</a> - Insecure Interaction Between Components </li>\n" +
  183. " <li> Derived from FindSecBugs rule <a href=\"https://find-sec-bugs.github.io/bugs.htm#SPRING_CSRF_PROTECTION_DISABLED\">SPRING_CSRF_PROTECTION_DISABLED</a> </li>\n" +
  184. " <li> <a href=\"https://docs.spring.io/spring-security/site/docs/current/reference/html/csrf.html#when-to-use-csrf-protection\">Spring Security\n" +
  185. " Official Documentation: When to use CSRF protection</a> </li>\n" +
  186. "</ul>\n";
  187. private static final String RECOMMENTEDCODINGPRACTICE = "<h2>Recommended Secure Coding Practices</h2>\n" +
  188. "<ul>\n" +
  189. " <li> activate Spring Security's CSRF protection. </li>\n" +
  190. "</ul>\n";
  191. private static final String ASKATRISK = "<h2>Ask Yourself Whether</h2>\n" +
  192. "<ul>\n" +
  193. " <li> Any URLs responding with <code>Access-Control-Allow-Origin: *</code> include sensitive content. </li>\n" +
  194. " <li> Any domains specified in <code>Access-Control-Allow-Origin</code> headers are checked against a whitelist. </li>\n" +
  195. "</ul>\n";
  196. private static final String SENSITIVECODE = "<h2>Sensitive Code Example</h2>\n" +
  197. "<pre>\n" +
  198. "// === Java Servlet ===\n" +
  199. "@Override\n" +
  200. "protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {\n" +
  201. " resp.setHeader(\"Content-Type\", \"text/plain; charset=utf-8\");\n" +
  202. " resp.setHeader(\"Access-Control-Allow-Origin\", \"http://localhost:8080\"); // Questionable\n" +
  203. " resp.setHeader(\"Access-Control-Allow-Credentials\", \"true\"); // Questionable\n" +
  204. " resp.setHeader(\"Access-Control-Allow-Methods\", \"GET\"); // Questionable\n" +
  205. " resp.getWriter().write(\"response\");\n" +
  206. "}\n" +
  207. "</pre>\n" +
  208. "<pre>\n" +
  209. "// === Spring MVC Controller annotation ===\n" +
  210. "@CrossOrigin(origins = \"http://domain1.com\") // Questionable\n" +
  211. "@RequestMapping(\"\")\n" +
  212. "public class TestController {\n" +
  213. " public String home(ModelMap model) {\n" +
  214. " model.addAttribute(\"message\", \"ok \");\n" +
  215. " return \"view\";\n" +
  216. " }\n" +
  217. "\n" +
  218. " @CrossOrigin(origins = \"http://domain2.com\") // Questionable\n" +
  219. " @RequestMapping(value = \"/test1\")\n" +
  220. " public ResponseEntity&lt;String&gt; test1() {\n" +
  221. " return ResponseEntity.ok().body(\"ok\");\n" +
  222. " }\n" +
  223. "}\n" +
  224. "</pre>\n";
  225. }