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 11KB

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