/* | |||||
* SonarQube | |||||
* Copyright (C) 2009-2022 SonarSource SA | |||||
* mailto:info AT sonarsource DOT com | |||||
* | |||||
* This program is free software; you can redistribute it and/or | |||||
* modify it under the terms of the GNU Lesser General Public | |||||
* License as published by the Free Software Foundation; either | |||||
* version 3 of the License, or (at your option) any later version. | |||||
* | |||||
* This program is distributed in the hope that it will be useful, | |||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |||||
* Lesser General Public License for more details. | |||||
* | |||||
* You should have received a copy of the GNU Lesser General Public License | |||||
* along with this program; if not, write to the Free Software Foundation, | |||||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||||
*/ | |||||
package org.sonar.server.rule; | |||||
import com.tngtech.java.junit.dataprovider.DataProvider; | |||||
import com.tngtech.java.junit.dataprovider.DataProviderRunner; | |||||
import com.tngtech.java.junit.dataprovider.UseDataProvider; | |||||
import org.junit.Test; | |||||
import org.junit.runner.RunWith; | |||||
import org.sonar.db.rule.RuleDto; | |||||
import org.sonar.db.rule.RuleTesting; | |||||
import static org.apache.commons.lang.RandomStringUtils.randomAlphabetic; | |||||
import static org.assertj.core.api.Assertions.assertThat; | |||||
import static org.sonar.db.rule.RuleDescriptionSectionDto.createDefaultRuleDescriptionSection; | |||||
@RunWith(DataProviderRunner.class) | |||||
public class HotspotRuleDescriptionTest { | |||||
@Test | |||||
public void parse_returns_all_empty_fields_when_no_description() { | |||||
RuleDto dto = RuleTesting.newRuleWithoutDescriptionSection(); | |||||
HotspotRuleDescription result = HotspotRuleDescription.from(dto); | |||||
assertThat(result.getRisk()).isEmpty(); | |||||
assertThat(result.getVulnerable()).isEmpty(); | |||||
assertThat(result.getFixIt()).isEmpty(); | |||||
} | |||||
@Test | |||||
public void parse_returns_all_empty_fields_when_empty_description() { | |||||
RuleDto dto = RuleTesting.newRuleWithoutDescriptionSection().addRuleDescriptionSectionDto(createDefaultRuleDescriptionSection("uuid", "")); | |||||
HotspotRuleDescription result = HotspotRuleDescription.from(dto); | |||||
assertThat(result.getRisk()).isEmpty(); | |||||
assertThat(result.getVulnerable()).isEmpty(); | |||||
assertThat(result.getFixIt()).isEmpty(); | |||||
} | |||||
@Test | |||||
@UseDataProvider("descriptionsWithoutTitles") | |||||
public void parse_to_risk_description_fields_when_desc_contains_no_section(String description) { | |||||
RuleDto dto = RuleTesting.newRuleWithoutDescriptionSection().addRuleDescriptionSectionDto(createDefaultRuleDescriptionSection("uuid", description)); | |||||
HotspotRuleDescription result = HotspotRuleDescription.from(dto); | |||||
assertThat(result.getRisk()).contains(description); | |||||
assertThat(result.getVulnerable()).isEmpty(); | |||||
assertThat(result.getFixIt()).isEmpty(); | |||||
} | |||||
@DataProvider | |||||
public static Object[][] descriptionsWithoutTitles() { | |||||
return new Object[][] { | |||||
{randomAlphabetic(123)}, | |||||
{"bar\n" + | |||||
"acme\n" + | |||||
"foo"} | |||||
}; | |||||
} | |||||
@Test | |||||
public void parse_return_null_risk_when_desc_starts_with_ask_yourself_title() { | |||||
RuleDto dto = RuleTesting.newRuleWithoutDescriptionSection().addRuleDescriptionSectionDto( | |||||
createDefaultRuleDescriptionSection("uuid", (ASKATRISK + RECOMMENTEDCODINGPRACTICE))); | |||||
HotspotRuleDescription result = HotspotRuleDescription.from(dto); | |||||
assertThat(result.getRisk()).isEmpty(); | |||||
assertThat(result.getVulnerable()).contains(ASKATRISK); | |||||
assertThat(result.getFixIt()).contains(RECOMMENTEDCODINGPRACTICE); | |||||
} | |||||
@Test | |||||
public void parse_return_null_vulnerable_when_no_ask_yourself_whether_title() { | |||||
RuleDto dto = RuleTesting.newRuleWithoutDescriptionSection() | |||||
.addRuleDescriptionSectionDto(createDefaultRuleDescriptionSection("uuid", (DESCRIPTION + RECOMMENTEDCODINGPRACTICE))); | |||||
HotspotRuleDescription result = HotspotRuleDescription.from(dto); | |||||
assertThat(result.getRisk()).contains(DESCRIPTION); | |||||
assertThat(result.getVulnerable()).isEmpty(); | |||||
assertThat(result.getFixIt()).contains(RECOMMENTEDCODINGPRACTICE); | |||||
} | |||||
@Test | |||||
public void parse_return_null_fixIt_when_desc_has_no_Recommended_Secure_Coding_Practices_title() { | |||||
RuleDto dto = RuleTesting.newRuleWithoutDescriptionSection() | |||||
.addRuleDescriptionSectionDto(createDefaultRuleDescriptionSection("uuid", (DESCRIPTION + ASKATRISK))); | |||||
HotspotRuleDescription result = HotspotRuleDescription.from(dto); | |||||
assertThat(result.getRisk()).contains(DESCRIPTION); | |||||
assertThat(result.getVulnerable()).contains(ASKATRISK); | |||||
assertThat(result.getFixIt()).isEmpty(); | |||||
} | |||||
@Test | |||||
public void parse_with_noncompliant_section_not_removed() { | |||||
RuleDto dto = RuleTesting.newRuleWithoutDescriptionSection().addRuleDescriptionSectionDto( | |||||
createDefaultRuleDescriptionSection("uuid", (DESCRIPTION + NONCOMPLIANTCODE + COMPLIANTCODE))); | |||||
HotspotRuleDescription result = HotspotRuleDescription.from(dto); | |||||
assertThat(result.getRisk()).contains(DESCRIPTION); | |||||
assertThat(result.getVulnerable()).contains(NONCOMPLIANTCODE); | |||||
assertThat(result.getFixIt()).contains(COMPLIANTCODE); | |||||
} | |||||
@Test | |||||
public void parse_moved_noncompliant_code() { | |||||
RuleDto dto = RuleTesting.newRuleWithoutDescriptionSection().addRuleDescriptionSectionDto( | |||||
createDefaultRuleDescriptionSection("uuid", (DESCRIPTION + RECOMMENTEDCODINGPRACTICE + NONCOMPLIANTCODE + SEE))); | |||||
HotspotRuleDescription result = HotspotRuleDescription.from(dto); | |||||
assertThat(result.getRisk()).contains(DESCRIPTION); | |||||
assertThat(result.getVulnerable()).contains(NONCOMPLIANTCODE); | |||||
assertThat(result.getFixIt()).contains(RECOMMENTEDCODINGPRACTICE + SEE); | |||||
} | |||||
@Test | |||||
public void parse_moved_sensitivecode_code() { | |||||
RuleDto dto = RuleTesting.newRuleWithoutDescriptionSection().addRuleDescriptionSectionDto( | |||||
createDefaultRuleDescriptionSection("uuid", (DESCRIPTION + ASKATRISK + RECOMMENTEDCODINGPRACTICE + SENSITIVECODE + SEE))); | |||||
HotspotRuleDescription result = HotspotRuleDescription.from(dto); | |||||
assertThat(result.getRisk()).contains(DESCRIPTION); | |||||
assertThat(result.getVulnerable()).contains(ASKATRISK + SENSITIVECODE); | |||||
assertThat(result.getFixIt()).contains(RECOMMENTEDCODINGPRACTICE + SEE); | |||||
} | |||||
@Test | |||||
public void parse_custom_rule_description() { | |||||
String ruleDescription = "This is the custom rule description"; | |||||
String exceptionsContent = "This the exceptions section content"; | |||||
String askContent = "This is the ask section content"; | |||||
String recommendedContent = "This is the recommended section content"; | |||||
RuleDto dto = RuleTesting.newRuleWithoutDescriptionSection() | |||||
.setTemplateUuid("123") | |||||
.setDescriptionFormat(RuleDto.Format.MARKDOWN) | |||||
.addRuleDescriptionSectionDto(createDefaultRuleDescriptionSection( | |||||
"uuid", ruleDescription + "\n" | |||||
+ "== Exceptions" + "\n" | |||||
+ exceptionsContent + "\n" | |||||
+ "== Ask Yourself Whether" + "\n" | |||||
+ askContent + "\n" | |||||
+ "== Recommended Secure Coding Practices" + "\n" | |||||
+ recommendedContent + "\n" | |||||
)); | |||||
HotspotRuleDescription result = HotspotRuleDescription.from(dto); | |||||
assertThat(result.getRisk().get()).hasToString( | |||||
ruleDescription + "<br/>" | |||||
+ "<h2>Exceptions</h2>" | |||||
+ exceptionsContent + "<br/>" | |||||
); | |||||
assertThat(result.getVulnerable().get()).hasToString( | |||||
"<h2>Ask Yourself Whether</h2>" | |||||
+ askContent + "<br/>" | |||||
); | |||||
assertThat(result.getFixIt().get()).hasToString( | |||||
"<h2>Recommended Secure Coding Practices</h2>" | |||||
+ recommendedContent + "<br/>" | |||||
); | |||||
} | |||||
/* | |||||
* Bunch of static constant to create rule description. | |||||
*/ | |||||
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" | |||||
+ | |||||
"<code>-=</code> or <code>!=</code>) will compile and run, but not produce the expected results.</p>\n" + | |||||
"<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" + | |||||
"there is at least one whitespace character after.</p>\n"; | |||||
private static final String NONCOMPLIANTCODE = "<h2>Noncompliant Code Example</h2>\n" + | |||||
"<pre>Integer target = -5;\n" + | |||||
"Integer num = 3;\n" + | |||||
"\n" + | |||||
"target =- num; // Noncompliant; target = -3. Is that really what's meant?\n" + | |||||
"target =+ num; // Noncompliant; target = 3\n" + | |||||
"</pre>\n"; | |||||
private static final String COMPLIANTCODE = "<h2>Compliant Solution</h2>\n" + | |||||
"<pre>Integer target = -5;\n" + | |||||
"Integer num = 3;\n" + | |||||
"\n" + | |||||
"target = -num; // Compliant; intent to assign inverse value of num is clear\n" + | |||||
"target += num;\n" + | |||||
"</pre>\n"; | |||||
private static final String SEE = "<h2>See</h2>\n" + | |||||
"<ul>\n" + | |||||
" <li> <a href=\"https://cwe.mitre.org/data/definitions/352.html\">MITRE, CWE-352</a> - Cross-Site Request Forgery (CSRF) </li>\n" + | |||||
" <li> <a href=\"https://www.owasp.org/index.php/Top_10-2017_A6-Security_Misconfiguration\">OWASP Top 10 2017 Category A6</a> - Security\n" + | |||||
" Misconfiguration </li>\n" + | |||||
" <li> <a href=\"https://www.owasp.org/index.php/Cross-Site_Request_Forgery_%28CSRF%29\">OWASP: Cross-Site Request Forgery</a> </li>\n" + | |||||
" <li> <a href=\"https://www.sans.org/top25-software-errors/#cat1\">SANS Top 25</a> - Insecure Interaction Between Components </li>\n" + | |||||
" <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" + | |||||
" <li> <a href=\"https://docs.spring.io/spring-security/site/docs/current/reference/html/csrf.html#when-to-use-csrf-protection\">Spring Security\n" + | |||||
" Official Documentation: When to use CSRF protection</a> </li>\n" + | |||||
"</ul>\n"; | |||||
private static final String RECOMMENTEDCODINGPRACTICE = "<h2>Recommended Secure Coding Practices</h2>\n" + | |||||
"<ul>\n" + | |||||
" <li> activate Spring Security's CSRF protection. </li>\n" + | |||||
"</ul>\n"; | |||||
private static final String ASKATRISK = "<h2>Ask Yourself Whether</h2>\n" + | |||||
"<ul>\n" + | |||||
" <li> Any URLs responding with <code>Access-Control-Allow-Origin: *</code> include sensitive content. </li>\n" + | |||||
" <li> Any domains specified in <code>Access-Control-Allow-Origin</code> headers are checked against a whitelist. </li>\n" + | |||||
"</ul>\n"; | |||||
private static final String SENSITIVECODE = "<h2>Sensitive Code Example</h2>\n" + | |||||
"<pre>\n" + | |||||
"// === Java Servlet ===\n" + | |||||
"@Override\n" + | |||||
"protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {\n" + | |||||
" resp.setHeader(\"Content-Type\", \"text/plain; charset=utf-8\");\n" + | |||||
" resp.setHeader(\"Access-Control-Allow-Origin\", \"http://localhost:8080\"); // Questionable\n" + | |||||
" resp.setHeader(\"Access-Control-Allow-Credentials\", \"true\"); // Questionable\n" + | |||||
" resp.setHeader(\"Access-Control-Allow-Methods\", \"GET\"); // Questionable\n" + | |||||
" resp.getWriter().write(\"response\");\n" + | |||||
"}\n" + | |||||
"</pre>\n" + | |||||
"<pre>\n" + | |||||
"// === Spring MVC Controller annotation ===\n" + | |||||
"@CrossOrigin(origins = \"http://domain1.com\") // Questionable\n" + | |||||
"@RequestMapping(\"\")\n" + | |||||
"public class TestController {\n" + | |||||
" public String home(ModelMap model) {\n" + | |||||
" model.addAttribute(\"message\", \"ok \");\n" + | |||||
" return \"view\";\n" + | |||||
" }\n" + | |||||
"\n" + | |||||
" @CrossOrigin(origins = \"http://domain2.com\") // Questionable\n" + | |||||
" @RequestMapping(value = \"/test1\")\n" + | |||||
" public ResponseEntity<String> test1() {\n" + | |||||
" return ResponseEntity.ok().body(\"ok\");\n" + | |||||
" }\n" + | |||||
"}\n" + | |||||
"</pre>\n"; | |||||
} |
import java.util.Optional; | import java.util.Optional; | ||||
import java.util.Set; | import java.util.Set; | ||||
import java.util.function.Function; | import java.util.function.Function; | ||||
import java.util.stream.Collectors; | |||||
import java.util.stream.Stream; | import java.util.stream.Stream; | ||||
import javax.annotation.CheckForNull; | import javax.annotation.CheckForNull; | ||||
import javax.annotation.Nullable; | import javax.annotation.Nullable; | ||||
import org.sonar.api.rule.RuleKey; | import org.sonar.api.rule.RuleKey; | ||||
import org.sonar.api.server.ws.Change; | |||||
import org.sonar.api.server.ws.Request; | import org.sonar.api.server.ws.Request; | ||||
import org.sonar.api.server.ws.Response; | import org.sonar.api.server.ws.Response; | ||||
import org.sonar.api.server.ws.WebService; | import org.sonar.api.server.ws.WebService; | ||||
import org.sonar.db.issue.IssueDto; | import org.sonar.db.issue.IssueDto; | ||||
import org.sonar.db.protobuf.DbIssues; | import org.sonar.db.protobuf.DbIssues; | ||||
import org.sonar.db.protobuf.DbIssues.Locations; | import org.sonar.db.protobuf.DbIssues.Locations; | ||||
import org.sonar.db.rule.RuleDescriptionSectionDto; | |||||
import org.sonar.db.rule.RuleDto; | import org.sonar.db.rule.RuleDto; | ||||
import org.sonar.db.user.UserDto; | import org.sonar.db.user.UserDto; | ||||
import org.sonar.markdown.Markdown; | |||||
import org.sonar.server.exceptions.NotFoundException; | import org.sonar.server.exceptions.NotFoundException; | ||||
import org.sonar.server.issue.IssueChangeWSSupport; | import org.sonar.server.issue.IssueChangeWSSupport; | ||||
import org.sonar.server.issue.IssueChangeWSSupport.FormattingContext; | import org.sonar.server.issue.IssueChangeWSSupport.FormattingContext; | ||||
import org.sonar.server.issue.IssueChangeWSSupport.Load; | import org.sonar.server.issue.IssueChangeWSSupport.Load; | ||||
import org.sonar.server.issue.TextRangeResponseFormatter; | import org.sonar.server.issue.TextRangeResponseFormatter; | ||||
import org.sonar.server.issue.ws.UserResponseFormatter; | import org.sonar.server.issue.ws.UserResponseFormatter; | ||||
import org.sonar.server.rule.HotspotRuleDescription; | |||||
import org.sonar.server.security.SecurityStandards; | import org.sonar.server.security.SecurityStandards; | ||||
import org.sonarqube.ws.Common; | import org.sonarqube.ws.Common; | ||||
import org.sonarqube.ws.Hotspots; | import org.sonarqube.ws.Hotspots; | ||||
import static java.lang.String.format; | import static java.lang.String.format; | ||||
import static java.util.Collections.singleton; | import static java.util.Collections.singleton; | ||||
import static java.util.Optional.ofNullable; | import static java.util.Optional.ofNullable; | ||||
import static java.util.stream.Collectors.toMap; | |||||
import static org.sonar.api.server.rule.RuleDescriptionSection.RuleDescriptionSectionKeys.ASSESS_THE_PROBLEM_SECTION_KEY; | |||||
import static org.sonar.api.server.rule.RuleDescriptionSection.RuleDescriptionSectionKeys.HOW_TO_FIX_SECTION_KEY; | |||||
import static org.sonar.api.server.rule.RuleDescriptionSection.RuleDescriptionSectionKeys.ROOT_CAUSE_SECTION_KEY; | |||||
import static org.sonar.api.utils.DateUtils.formatDateTime; | import static org.sonar.api.utils.DateUtils.formatDateTime; | ||||
import static org.sonar.core.util.stream.MoreCollectors.toSet; | import static org.sonar.core.util.stream.MoreCollectors.toSet; | ||||
import static org.sonar.db.rule.RuleDescriptionSectionDto.DEFAULT_KEY; | |||||
import static org.sonar.server.ws.WsUtils.writeProtobuf; | import static org.sonar.server.ws.WsUtils.writeProtobuf; | ||||
public class ShowAction implements HotspotsWsAction { | public class ShowAction implements HotspotsWsAction { | ||||
.createAction("show") | .createAction("show") | ||||
.setHandler(this) | .setHandler(this) | ||||
.setDescription("Provides the details of a Security Hotspot.") | .setDescription("Provides the details of a Security Hotspot.") | ||||
.setSince("8.1"); | |||||
.setSince("8.1") | |||||
.setChangelog(new Change("9.5", "The fields rule.riskDescription, rule.fixRecommendations, rule.vulnerabilityDescription of the response are deprecated." | |||||
+ " /api/rules/show endpoint should be used to fetch rule descriptions.")); | |||||
action.createParam(PARAM_HOTSPOT_KEY) | action.createParam(PARAM_HOTSPOT_KEY) | ||||
.setDescription("Key of the Security Hotspot") | .setDescription("Key of the Security Hotspot") | ||||
.setSecurityCategory(sqCategory.getKey()) | .setSecurityCategory(sqCategory.getKey()) | ||||
.setVulnerabilityProbability(sqCategory.getVulnerability().name()); | .setVulnerabilityProbability(sqCategory.getVulnerability().name()); | ||||
HotspotRuleDescription hotspotRuleDescription = HotspotRuleDescription.from(ruleDto); | |||||
hotspotRuleDescription.getVulnerable().ifPresent(ruleBuilder::setVulnerabilityDescription); | |||||
hotspotRuleDescription.getRisk().ifPresent(ruleBuilder::setRiskDescription); | |||||
hotspotRuleDescription.getFixIt().ifPresent(ruleBuilder::setFixRecommendations); | |||||
Map<String, String> sectionKeyToContent = getSectionKeyToContent(ruleDto); | |||||
Optional.ofNullable(sectionKeyToContent.get(DEFAULT_KEY)).ifPresent(ruleBuilder::setVulnerabilityDescription); | |||||
Optional.ofNullable(sectionKeyToContent.get(ROOT_CAUSE_SECTION_KEY)).ifPresent(ruleBuilder::setVulnerabilityDescription); | |||||
Optional.ofNullable(sectionKeyToContent.get(ASSESS_THE_PROBLEM_SECTION_KEY)).ifPresent(ruleBuilder::setRiskDescription); | |||||
Optional.ofNullable(sectionKeyToContent.get(HOW_TO_FIX_SECTION_KEY)).ifPresent(ruleBuilder::setFixRecommendations); | |||||
responseBuilder.setRule(ruleBuilder.build()); | responseBuilder.setRule(ruleBuilder.build()); | ||||
} | } | ||||
private static Map<String, String> getSectionKeyToContent(RuleDto ruleDefinitionDto) { | |||||
return ruleDefinitionDto.getRuleDescriptionSectionDtos().stream() | |||||
.collect(toMap(RuleDescriptionSectionDto::getKey, | |||||
section -> getContentAndConvertToHtmlIfNecessary(ruleDefinitionDto.getDescriptionFormat(), section))); | |||||
} | |||||
private static String getContentAndConvertToHtmlIfNecessary(@Nullable RuleDto.Format descriptionFormat, RuleDescriptionSectionDto section) { | |||||
if (RuleDto.Format.MARKDOWN.equals(descriptionFormat)) { | |||||
return Markdown.convertToHtml(section.getContent()); | |||||
} | |||||
return section.getContent(); | |||||
} | |||||
private void formatTextRange(ShowWsResponse.Builder hotspotBuilder, IssueDto hotspot) { | private void formatTextRange(ShowWsResponse.Builder hotspotBuilder, IssueDto hotspot) { | ||||
textRangeFormatter.formatTextRange(hotspot, hotspotBuilder::setTextRange); | textRangeFormatter.formatTextRange(hotspot, hotspotBuilder::setTextRange); | ||||
} | } | ||||
Map<String, ComponentDto> componentsByUuids = dbClient.componentDao().selectSubProjectsByComponentUuids(dbSession, | Map<String, ComponentDto> componentsByUuids = dbClient.componentDao().selectSubProjectsByComponentUuids(dbSession, | ||||
componentUuids) | componentUuids) | ||||
.stream() | .stream() | ||||
.collect(Collectors.toMap(ComponentDto::uuid, Function.identity(), (componentDto, componentDto2) -> componentDto2)); | |||||
.collect(toMap(ComponentDto::uuid, Function.identity(), (componentDto, componentDto2) -> componentDto2)); | |||||
Set<String> componentUuidsToLoad = copyOf(difference(componentUuids, componentsByUuids.keySet())); | Set<String> componentUuidsToLoad = copyOf(difference(componentUuids, componentsByUuids.keySet())); | ||||
if (!componentUuidsToLoad.isEmpty()) { | if (!componentUuidsToLoad.isEmpty()) { |
import org.sonar.db.issue.IssueDto; | import org.sonar.db.issue.IssueDto; | ||||
import org.sonar.db.protobuf.DbCommons; | import org.sonar.db.protobuf.DbCommons; | ||||
import org.sonar.db.protobuf.DbIssues; | import org.sonar.db.protobuf.DbIssues; | ||||
import org.sonar.db.rule.RuleDescriptionSectionDto; | |||||
import org.sonar.db.rule.RuleDto; | import org.sonar.db.rule.RuleDto; | ||||
import org.sonar.db.rule.RuleTesting; | import org.sonar.db.rule.RuleTesting; | ||||
import org.sonar.db.user.UserDto; | import org.sonar.db.user.UserDto; | ||||
import static org.mockito.Mockito.verify; | import static org.mockito.Mockito.verify; | ||||
import static org.mockito.Mockito.when; | import static org.mockito.Mockito.when; | ||||
import static org.sonar.api.rules.RuleType.SECURITY_HOTSPOT; | import static org.sonar.api.rules.RuleType.SECURITY_HOTSPOT; | ||||
import static org.sonar.api.server.rule.RuleDescriptionSection.RuleDescriptionSectionKeys.ASSESS_THE_PROBLEM_SECTION_KEY; | |||||
import static org.sonar.api.server.rule.RuleDescriptionSection.RuleDescriptionSectionKeys.HOW_TO_FIX_SECTION_KEY; | |||||
import static org.sonar.api.server.rule.RuleDescriptionSection.RuleDescriptionSectionKeys.INTRODUCTION_SECTION_KEY; | |||||
import static org.sonar.api.server.rule.RuleDescriptionSection.RuleDescriptionSectionKeys.RESOURCES_SECTION_KEY; | |||||
import static org.sonar.api.server.rule.RuleDescriptionSection.RuleDescriptionSectionKeys.ROOT_CAUSE_SECTION_KEY; | |||||
import static org.sonar.db.component.ComponentTesting.newFileDto; | import static org.sonar.db.component.ComponentTesting.newFileDto; | ||||
import static org.sonar.db.rule.RuleDescriptionSectionDto.createDefaultRuleDescriptionSection; | |||||
import static org.sonar.db.rule.RuleDescriptionSectionDto.DEFAULT_KEY; | |||||
import static org.sonar.db.rule.RuleDto.Format.HTML; | |||||
import static org.sonar.db.rule.RuleDto.Format.MARKDOWN; | import static org.sonar.db.rule.RuleDto.Format.MARKDOWN; | ||||
@RunWith(DataProviderRunner.class) | @RunWith(DataProviderRunner.class) | ||||
}; | }; | ||||
} | } | ||||
@Test | |||||
public void dispatch_description_sections_of_advanced_rule_in_relevant_field() { | |||||
ComponentDto project = dbTester.components().insertPrivateProject(); | |||||
userSessionRule.registerComponents(project); | |||||
userSessionRule.logIn().addProjectPermission(UserRole.USER, project); | |||||
ComponentDto file = dbTester.components().insertComponent(newFileDto(project)); | |||||
RuleDescriptionSectionDto introductionSection = generateSectionWithKey(INTRODUCTION_SECTION_KEY); | |||||
RuleDescriptionSectionDto rootCauseSection = generateSectionWithKey(ROOT_CAUSE_SECTION_KEY); | |||||
RuleDescriptionSectionDto assesTheProblemSection = generateSectionWithKey(ASSESS_THE_PROBLEM_SECTION_KEY); | |||||
RuleDescriptionSectionDto resourcesSection = generateSectionWithKey(RESOURCES_SECTION_KEY); | |||||
RuleDescriptionSectionDto howToFixSection = generateSectionWithKey(HOW_TO_FIX_SECTION_KEY); | |||||
RuleDescriptionSectionDto dummySection = generateSectionWithKey("dummySection"); | |||||
RuleDto rule = newRuleWithoutSection(SECURITY_HOTSPOT, | |||||
r -> r.addRuleDescriptionSectionDto(introductionSection) | |||||
.addRuleDescriptionSectionDto(rootCauseSection) | |||||
.addRuleDescriptionSectionDto(assesTheProblemSection) | |||||
.addRuleDescriptionSectionDto(resourcesSection) | |||||
.addRuleDescriptionSectionDto(howToFixSection) | |||||
.addRuleDescriptionSectionDto(dummySection) | |||||
.setDescriptionFormat(HTML)); | |||||
IssueDto hotspot = dbTester.issues().insertHotspot(rule, project, file); | |||||
mockChangelogAndCommentsFormattingContext(); | |||||
Hotspots.ShowWsResponse response = newRequest(hotspot) | |||||
.executeProtobuf(Hotspots.ShowWsResponse.class); | |||||
assertThat(response.getRule().getVulnerabilityDescription()).isEqualTo(rootCauseSection.getContent()); | |||||
assertThat(response.getRule().getRiskDescription()).isEqualTo(assesTheProblemSection.getContent()); | |||||
assertThat(response.getRule().getFixRecommendations()).isEqualTo(howToFixSection.getContent()); | |||||
} | |||||
@Test | |||||
public void fallbacks_to_default_section_in_case_of_legacy_rule() { | |||||
ComponentDto project = dbTester.components().insertPrivateProject(); | |||||
userSessionRule.registerComponents(project); | |||||
userSessionRule.logIn().addProjectPermission(UserRole.USER, project); | |||||
ComponentDto file = dbTester.components().insertComponent(newFileDto(project)); | |||||
RuleDescriptionSectionDto introductionSection = generateSectionWithKey(DEFAULT_KEY); | |||||
RuleDto rule = newRuleWithoutSection(SECURITY_HOTSPOT, | |||||
r -> r.addRuleDescriptionSectionDto(introductionSection) | |||||
.setDescriptionFormat(HTML)); | |||||
IssueDto hotspot = dbTester.issues().insertHotspot(rule, project, file); | |||||
mockChangelogAndCommentsFormattingContext(); | |||||
Hotspots.ShowWsResponse response = newRequest(hotspot) | |||||
.executeProtobuf(Hotspots.ShowWsResponse.class); | |||||
assertThat(response.getRule().getVulnerabilityDescription()).isEqualTo(introductionSection.getContent()); | |||||
assertThat(response.getRule().getRiskDescription()).isEmpty(); | |||||
assertThat(response.getRule().getFixRecommendations()).isEmpty(); | |||||
} | |||||
@Test | |||||
public void ignore_default_section_if_root_cause_provided() { | |||||
ComponentDto project = dbTester.components().insertPrivateProject(); | |||||
userSessionRule.registerComponents(project); | |||||
userSessionRule.logIn().addProjectPermission(UserRole.USER, project); | |||||
ComponentDto file = dbTester.components().insertComponent(newFileDto(project)); | |||||
RuleDescriptionSectionDto introductionSection = generateSectionWithKey(INTRODUCTION_SECTION_KEY); | |||||
RuleDescriptionSectionDto rootCauseSection = generateSectionWithKey(ROOT_CAUSE_SECTION_KEY); | |||||
RuleDescriptionSectionDto assesTheProblemSection = generateSectionWithKey(ASSESS_THE_PROBLEM_SECTION_KEY); | |||||
RuleDto rule = newRuleWithoutSection(SECURITY_HOTSPOT, | |||||
r -> r.addRuleDescriptionSectionDto(introductionSection) | |||||
.addRuleDescriptionSectionDto(rootCauseSection) | |||||
.addRuleDescriptionSectionDto(assesTheProblemSection) | |||||
.setDescriptionFormat(HTML)); | |||||
IssueDto hotspot = dbTester.issues().insertHotspot(rule, project, file); | |||||
mockChangelogAndCommentsFormattingContext(); | |||||
Hotspots.ShowWsResponse response = newRequest(hotspot) | |||||
.executeProtobuf(Hotspots.ShowWsResponse.class); | |||||
assertThat(response.getRule().getVulnerabilityDescription()).isEqualTo(rootCauseSection.getContent()); | |||||
assertThat(response.getRule().getRiskDescription()).isEqualTo(assesTheProblemSection.getContent()); | |||||
assertThat(response.getRule().getFixRecommendations()).isEmpty(); | |||||
} | |||||
private RuleDescriptionSectionDto generateSectionWithKey(String assessTheProblemSectionKey) { | |||||
return RuleDescriptionSectionDto.builder() | |||||
.uuid(uuidFactory.create()) | |||||
.key(assessTheProblemSectionKey) | |||||
.content(randomAlphabetic(200)) | |||||
.build(); | |||||
} | |||||
@Test | @Test | ||||
public void returns_html_description_for_custom_rules() { | public void returns_html_description_for_custom_rules() { | ||||
ComponentDto project = dbTester.components().insertPrivateProject(); | ComponentDto project = dbTester.components().insertPrivateProject(); | ||||
String description = "== Title\n<div>line1\nline2</div>"; | String description = "== Title\n<div>line1\nline2</div>"; | ||||
RuleDescriptionSectionDto sectionDto = RuleDescriptionSectionDto.builder() | |||||
.uuid(uuidFactory.create()) | |||||
.key(ASSESS_THE_PROBLEM_SECTION_KEY) | |||||
.content(description) | |||||
.build(); | |||||
RuleDto rule = newRuleWithoutSection(SECURITY_HOTSPOT, | RuleDto rule = newRuleWithoutSection(SECURITY_HOTSPOT, | ||||
r -> r.setTemplateUuid("123") | r -> r.setTemplateUuid("123") | ||||
.addRuleDescriptionSectionDto(createDefaultRuleDescriptionSection(uuidFactory.create(), description)) | |||||
.addRuleDescriptionSectionDto(sectionDto) | |||||
.setDescriptionFormat(MARKDOWN)); | .setDescriptionFormat(MARKDOWN)); | ||||
IssueDto hotspot = dbTester.issues().insertHotspot(rule, project, file); | IssueDto hotspot = dbTester.issues().insertHotspot(rule, project, file); | ||||
assertThat(response.getRule().getRiskDescription()).isEqualTo("<h2>Title</h2><div>line1<br/>line2</div>"); | assertThat(response.getRule().getRiskDescription()).isEqualTo("<h2>Title</h2><div>line1<br/>line2</div>"); | ||||
} | } | ||||
@Test | @Test | ||||
public void handles_null_description_for_custom_rules() { | public void handles_null_description_for_custom_rules() { | ||||
ComponentDto project = dbTester.components().insertPrivateProject(); | ComponentDto project = dbTester.components().insertPrivateProject(); |
optional string name = 2; | optional string name = 2; | ||||
optional string securityCategory = 3; | optional string securityCategory = 3; | ||||
optional string vulnerabilityProbability = 4; | optional string vulnerabilityProbability = 4; | ||||
optional string riskDescription = 5; | |||||
optional string vulnerabilityDescription = 6; | |||||
optional string fixRecommendations = 7; | |||||
optional string riskDescription = 5 [deprecated=true]; | |||||
optional string vulnerabilityDescription = 6 [deprecated=true]; | |||||
optional string fixRecommendations = 7 [deprecated=true]; | |||||
} | } |