@@ -181,7 +181,6 @@ import org.sonar.server.rule.ws.RuleQueryFactory; | |||
import org.sonar.server.rule.ws.RuleWsSupport; | |||
import org.sonar.server.rule.ws.RulesWs; | |||
import org.sonar.server.rule.ws.TagsAction; | |||
import org.sonar.server.securityreport.ws.SecurityReportsWsModule; | |||
import org.sonar.server.setting.ws.SettingsWsModule; | |||
import org.sonar.server.source.HtmlSourceDecorator; | |||
import org.sonar.server.source.SourceService; | |||
@@ -416,9 +415,6 @@ public class PlatformLevel4 extends PlatformLevel { | |||
MyNewIssuesNotificationHandler.class, | |||
MyNewIssuesNotificationHandler.newMetadata(), | |||
// Security reports | |||
SecurityReportsWsModule.class, | |||
// issues actions | |||
AssignAction.class, | |||
SetTypeAction.class, |
@@ -1,43 +0,0 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2019 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.securityreport.ws; | |||
import org.sonar.api.server.ws.WebService; | |||
public class SecurityReportsWs implements WebService { | |||
private final SecurityReportsWsAction[] actions; | |||
public SecurityReportsWs(SecurityReportsWsAction... actions) { | |||
this.actions = actions; | |||
} | |||
@Override | |||
public void define(Context context) { | |||
NewController controller = context.createController("api/security_reports"); | |||
controller.setDescription("Return data needed by the security reports"); | |||
controller.setSince("7.3"); | |||
for (SecurityReportsWsAction action : actions) { | |||
action.define(controller); | |||
} | |||
controller.done(); | |||
} | |||
} |
@@ -1,26 +0,0 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2019 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.securityreport.ws; | |||
import org.sonar.server.ws.WsAction; | |||
interface SecurityReportsWsAction extends WsAction { | |||
// Marker interface | |||
} |
@@ -1,31 +0,0 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2019 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.securityreport.ws; | |||
import org.sonar.core.platform.Module; | |||
public class SecurityReportsWsModule extends Module { | |||
@Override | |||
protected void configureModule() { | |||
add( | |||
SecurityReportsWs.class, | |||
ShowAction.class); | |||
} | |||
} |
@@ -1,291 +0,0 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2019 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.securityreport.ws; | |||
import com.google.common.collect.ArrayListMultimap; | |||
import com.google.common.collect.Multimap; | |||
import java.util.List; | |||
import java.util.Set; | |||
import java.util.function.Function; | |||
import org.sonar.api.resources.Qualifiers; | |||
import org.sonar.api.rules.RuleType; | |||
import org.sonar.api.server.ws.Request; | |||
import org.sonar.api.server.ws.Response; | |||
import org.sonar.api.server.ws.WebService; | |||
import org.sonar.db.DbClient; | |||
import org.sonar.db.DbSession; | |||
import org.sonar.db.component.ComponentDto; | |||
import org.sonar.db.measure.LiveMeasureDto; | |||
import org.sonar.db.qualityprofile.OrgActiveRuleDto; | |||
import org.sonar.db.rule.RuleDto; | |||
import org.sonar.server.component.ComponentFinder; | |||
import org.sonar.server.issue.index.IssueIndex; | |||
import org.sonar.server.issue.index.SecurityStandardCategoryStatistics; | |||
import org.sonar.server.qualityprofile.QPMeasureData; | |||
import org.sonar.server.qualityprofile.QualityProfile; | |||
import org.sonar.server.security.SecurityStandardHelper; | |||
import org.sonar.server.user.UserSession; | |||
import org.sonarqube.ws.SecurityReports; | |||
import static java.lang.Integer.parseInt; | |||
import static java.util.Arrays.asList; | |||
import static java.util.Collections.emptySortedSet; | |||
import static java.util.Comparator.comparing; | |||
import static java.util.stream.Collectors.toList; | |||
import static org.sonar.api.measures.CoreMetrics.QUALITY_PROFILES_KEY; | |||
import static org.sonar.api.web.UserRole.USER; | |||
import static org.sonar.server.security.SecurityStandardHelper.UNKNOWN_STANDARD; | |||
import static org.sonar.server.security.SecurityStandardHelper.getCwe; | |||
import static org.sonar.server.security.SecurityStandardHelper.getOwaspTop10; | |||
import static org.sonar.server.security.SecurityStandardHelper.getSansTop25; | |||
import static org.sonar.server.security.SecurityStandardHelper.getSonarSourceSecurityCategories; | |||
import static org.sonar.server.ws.WsUtils.writeProtobuf; | |||
import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_OWASP_TOP_10; | |||
import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_SANS_TOP_25; | |||
import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_SONARSOURCE_SECURITY; | |||
public class ShowAction implements SecurityReportsWsAction { | |||
private static final String UNSUPPORTED_STANDARD_MSG = "Unsupported standard: '%s'"; | |||
private static final String PARAM_PROJECT = "project"; | |||
private static final String PARAM_BRANCH = "branch"; | |||
private static final String PARAM_INCLUDE_DISTRIBUTION = "includeDistribution"; | |||
private static final String PARAM_STANDARD = "standard"; | |||
private static final String OWASP_CAT_PREFIX = "a"; | |||
private final UserSession userSession; | |||
private final ComponentFinder componentFinder; | |||
private final IssueIndex issueIndex; | |||
private final DbClient dbClient; | |||
public ShowAction(UserSession userSession, ComponentFinder componentFinder, IssueIndex issueIndex, DbClient dbClient) { | |||
this.userSession = userSession; | |||
this.componentFinder = componentFinder; | |||
this.issueIndex = issueIndex; | |||
this.dbClient = dbClient; | |||
} | |||
@Override | |||
public void define(WebService.NewController controller) { | |||
WebService.NewAction action = controller | |||
.createAction("show") | |||
.setResponseExample(getClass().getResource("show-example.json")) | |||
.setHandler(this) | |||
.setDescription("Return data used by security reports") | |||
.setSince("7.3") | |||
.setInternal(true); | |||
action.createParam(PARAM_PROJECT) | |||
.setDescription("Project, view or application key") | |||
.setRequired(true); | |||
action.createParam(PARAM_BRANCH) | |||
.setDescription("Branch name") | |||
.setExampleValue("branch-2.0"); | |||
action.createParam(PARAM_STANDARD) | |||
.setDescription("Security standard") | |||
.setPossibleValues(PARAM_OWASP_TOP_10, PARAM_SANS_TOP_25, PARAM_SONARSOURCE_SECURITY) | |||
.setRequired(true); | |||
action.createParam(PARAM_INCLUDE_DISTRIBUTION) | |||
.setDescription("To return CWE distribution") | |||
.setBooleanPossibleValues() | |||
.setDefaultValue("false"); | |||
} | |||
@Override | |||
public final void handle(Request request, Response response) { | |||
String projectKey = request.mandatoryParam(PARAM_PROJECT); | |||
ComponentDto projectDto; | |||
try (DbSession dbSession = dbClient.openSession(false)) { | |||
projectDto = componentFinder.getByKeyAndOptionalBranchOrPullRequest(dbSession, projectKey, request.param(PARAM_BRANCH), null); | |||
} | |||
userSession.checkComponentPermission(USER, projectDto); | |||
String qualifier = projectDto.qualifier(); | |||
boolean isViewOrApp; | |||
switch (qualifier) { | |||
case Qualifiers.VIEW: | |||
case Qualifiers.SUBVIEW: | |||
case Qualifiers.APP: | |||
isViewOrApp = true; | |||
break; | |||
case Qualifiers.PROJECT: | |||
isViewOrApp = false; | |||
break; | |||
default: | |||
throw new IllegalArgumentException("Unsupported component type " + qualifier); | |||
} | |||
String standard = request.mandatoryParam(PARAM_STANDARD); | |||
boolean includeCwe = request.mandatoryParamAsBoolean(PARAM_INCLUDE_DISTRIBUTION); | |||
List<SecurityStandardCategoryStatistics> statistics; | |||
switch (standard) { | |||
case PARAM_OWASP_TOP_10: | |||
statistics = issueIndex.getOwaspTop10Report(projectDto.uuid(), isViewOrApp, includeCwe) | |||
.stream() | |||
.sorted(comparing(ShowAction::index)) | |||
.collect(toList()); | |||
break; | |||
case PARAM_SANS_TOP_25: | |||
statistics = issueIndex.getSansTop25Report(projectDto.uuid(), isViewOrApp, includeCwe); | |||
break; | |||
case PARAM_SONARSOURCE_SECURITY: | |||
statistics = issueIndex.getSonarSourceReport(projectDto.uuid(), isViewOrApp, includeCwe); | |||
break; | |||
default: | |||
throw new IllegalArgumentException(String.format(UNSUPPORTED_STANDARD_MSG, standard)); | |||
} | |||
if (!isViewOrApp) { | |||
// App and Portfolios don't have a quality profile | |||
completeRulesStatistics(statistics, projectDto, standard, includeCwe); | |||
} | |||
writeResponse(request, response, statistics, isViewOrApp); | |||
} | |||
private void completeRulesStatistics(List<SecurityStandardCategoryStatistics> input, ComponentDto project, String standard, boolean includeCwe) { | |||
try (DbSession dbSession = dbClient.openSession(false)) { | |||
Set<QualityProfile> qualityProfiles = dbClient.liveMeasureDao().selectMeasure(dbSession, project.projectUuid(), QUALITY_PROFILES_KEY) | |||
.map(LiveMeasureDto::getDataAsString) | |||
.map(data -> QPMeasureData.fromJson(data).getProfiles()) | |||
.orElse(emptySortedSet()); | |||
List<OrgActiveRuleDto> activeRuleDtos = dbClient.activeRuleDao().selectByTypeAndProfileUuids(dbSession, | |||
asList(RuleType.SECURITY_HOTSPOT.getDbConstant(), RuleType.VULNERABILITY.getDbConstant()), | |||
qualityProfiles.stream() | |||
.map(QualityProfile::getQpKey) | |||
.collect(toList())); | |||
Multimap<String, OrgActiveRuleDto> activeRulesByCategory = ArrayListMultimap.create(); | |||
activeRuleDtos | |||
.forEach(r -> { | |||
List<String> cwe = getCwe(r.getSecurityStandards()); | |||
if (includeCwe) { | |||
cwe.forEach(s -> activeRulesByCategory.put(s, r)); | |||
} | |||
switch (standard) { | |||
case PARAM_OWASP_TOP_10: | |||
getOwaspTop10(r.getSecurityStandards()).forEach(s -> activeRulesByCategory.put(s, r)); | |||
break; | |||
case PARAM_SANS_TOP_25: | |||
getSansTop25(cwe).forEach(s -> activeRulesByCategory.put(s, r)); | |||
break; | |||
case PARAM_SONARSOURCE_SECURITY: | |||
SecurityStandardHelper.getSonarSourceSecurityCategories(cwe).forEach(s -> activeRulesByCategory.put(s, r)); | |||
break; | |||
default: | |||
throw new IllegalArgumentException(String.format(UNSUPPORTED_STANDARD_MSG, standard)); | |||
} | |||
}); | |||
List<RuleDto> ruleDtos = dbClient.ruleDao().selectByTypeAndLanguages(dbSession, | |||
project.getOrganizationUuid(), | |||
asList(RuleType.SECURITY_HOTSPOT.getDbConstant(), RuleType.VULNERABILITY.getDbConstant()), | |||
qualityProfiles.stream() | |||
.map(QualityProfile::getLanguageKey) | |||
.collect(toList())); | |||
Multimap<String, RuleDto> rulesByCategory = ArrayListMultimap.create(); | |||
ruleDtos | |||
.forEach(r -> { | |||
List<String> cwe = getCwe(r.getSecurityStandards()); | |||
if (includeCwe) { | |||
cwe.forEach(s -> rulesByCategory.put(s, r)); | |||
} | |||
switch (standard) { | |||
case PARAM_OWASP_TOP_10: | |||
getOwaspTop10(r.getSecurityStandards()).forEach(s -> rulesByCategory.put(s, r)); | |||
break; | |||
case PARAM_SANS_TOP_25: | |||
getSansTop25(cwe).forEach(s -> rulesByCategory.put(s, r)); | |||
break; | |||
case PARAM_SONARSOURCE_SECURITY: | |||
getSonarSourceSecurityCategories(cwe).forEach(s -> rulesByCategory.put(s, r)); | |||
break; | |||
default: | |||
throw new IllegalArgumentException(String.format(UNSUPPORTED_STANDARD_MSG, standard)); | |||
} | |||
}); | |||
input.forEach(c -> { | |||
c.setTotalRules(rulesByCategory.get(c.getCategory()).size()); | |||
c.setActiveRules(activeRulesByCategory.get(c.getCategory()).size()); | |||
c.getChildren().forEach(child -> { | |||
child.setTotalRules(rulesByCategory.get(child.getCategory()).size()); | |||
child.setActiveRules(activeRulesByCategory.get(child.getCategory()).size()); | |||
}); | |||
}); | |||
} | |||
} | |||
private static Integer index(SecurityStandardCategoryStatistics owaspCat) { | |||
if (owaspCat.getCategory().startsWith(OWASP_CAT_PREFIX)) { | |||
return parseInt(owaspCat.getCategory().substring(OWASP_CAT_PREFIX.length())); | |||
} | |||
// unknown | |||
return 11; | |||
} | |||
private static void writeResponse(Request request, Response response, List<SecurityStandardCategoryStatistics> categories, boolean isViewOrApp) { | |||
SecurityReports.ShowWsResponse.Builder builder = SecurityReports.ShowWsResponse.newBuilder(); | |||
categories.forEach(cat -> { | |||
SecurityReports.SecurityStandardCategoryStatistics.Builder catBuilder = SecurityReports.SecurityStandardCategoryStatistics.newBuilder(); | |||
catBuilder | |||
.setCategory(cat.getCategory()) | |||
.setVulnerabilities(cat.getVulnerabilities()); | |||
cat.getVulnerabiliyRating().ifPresent(catBuilder::setVulnerabilityRating); | |||
catBuilder | |||
.setOpenSecurityHotspots(cat.getOpenSecurityHotspots()) | |||
.setToReviewSecurityHotspots(cat.getToReviewSecurityHotspots()) | |||
.setWontFixSecurityHotspots(cat.getWontFixSecurityHotspots()); | |||
if (!isViewOrApp) { | |||
catBuilder.setTotalRules(cat.getTotalRules()) | |||
.setActiveRules(cat.getActiveRules()); | |||
} | |||
if (cat.getChildren() != null) { | |||
cat.getChildren().stream() | |||
.sorted(comparing(cweIndex())) | |||
.forEach(cwe -> { | |||
SecurityReports.CweStatistics.Builder cweBuilder = SecurityReports.CweStatistics.newBuilder(); | |||
cweBuilder | |||
.setCwe(cwe.getCategory()) | |||
.setVulnerabilities(cwe.getVulnerabilities()); | |||
cwe.getVulnerabiliyRating().ifPresent(cweBuilder::setVulnerabilityRating); | |||
cweBuilder | |||
.setOpenSecurityHotspots(cwe.getOpenSecurityHotspots()) | |||
.setToReviewSecurityHotspots(cwe.getToReviewSecurityHotspots()) | |||
.setWontFixSecurityHotspots(cwe.getWontFixSecurityHotspots()); | |||
if (!isViewOrApp) { | |||
cweBuilder.setActiveRules(cwe.getActiveRules()) | |||
.setTotalRules(cwe.getTotalRules()); | |||
} | |||
catBuilder.addDistribution(cweBuilder); | |||
}); | |||
} | |||
builder.addCategories(catBuilder); | |||
}); | |||
writeProtobuf(builder.build(), request, response); | |||
} | |||
private static Function<SecurityStandardCategoryStatistics, Integer> cweIndex() { | |||
return securityStandardCategoryStatistics -> { | |||
String category = securityStandardCategoryStatistics.getCategory(); | |||
return category.equals(UNKNOWN_STANDARD) ? Integer.MAX_VALUE : parseInt(category); | |||
}; | |||
} | |||
} |
@@ -1,23 +0,0 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2019 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. | |||
*/ | |||
@ParametersAreNonnullByDefault | |||
package org.sonar.server.securityreport.ws; | |||
import javax.annotation.ParametersAreNonnullByDefault; |
@@ -1,136 +0,0 @@ | |||
{ | |||
"categories": [ | |||
{ | |||
"category": "a1", | |||
"vulnerabilities": 0, | |||
"toReviewSecurityHotspots": 0, | |||
"openSecurityHotspots": 0, | |||
"wontFixSecurityHotspots": 0, | |||
"distribution": [], | |||
"activeRules": 1, | |||
"totalRules": 1 | |||
}, | |||
{ | |||
"category": "a2", | |||
"vulnerabilities": 1, | |||
"vulnerabilityRating": 3, | |||
"toReviewSecurityHotspots": 1, | |||
"openSecurityHotspots": 1, | |||
"wontFixSecurityHotspots": 1, | |||
"distribution": [ | |||
{ | |||
"cwe": "89", | |||
"vulnerabilities": 1, | |||
"vulnerabilityRating": 3, | |||
"toReviewSecurityHotspots": 1, | |||
"openSecurityHotspots": 1, | |||
"wontFixSecurityHotspots": 1, | |||
"activeRules": 1, | |||
"totalRules": 1 | |||
}, | |||
{ | |||
"cwe": "123", | |||
"vulnerabilities": 1, | |||
"vulnerabilityRating": 3, | |||
"toReviewSecurityHotspots": 1, | |||
"openSecurityHotspots": 1, | |||
"wontFixSecurityHotspots": 1, | |||
"activeRules": 1, | |||
"totalRules": 1 | |||
} | |||
], | |||
"activeRules": 1, | |||
"totalRules": 1 | |||
}, | |||
{ | |||
"category": "a3", | |||
"vulnerabilities": 0, | |||
"toReviewSecurityHotspots": 0, | |||
"openSecurityHotspots": 0, | |||
"wontFixSecurityHotspots": 0, | |||
"distribution": [], | |||
"activeRules": 0, | |||
"totalRules": 1 | |||
}, | |||
{ | |||
"category": "a4", | |||
"vulnerabilities": 0, | |||
"toReviewSecurityHotspots": 0, | |||
"openSecurityHotspots": 0, | |||
"wontFixSecurityHotspots": 0, | |||
"distribution": [], | |||
"activeRules": 0, | |||
"totalRules": 0 | |||
}, | |||
{ | |||
"category": "a5", | |||
"vulnerabilities": 0, | |||
"toReviewSecurityHotspots": 0, | |||
"openSecurityHotspots": 0, | |||
"wontFixSecurityHotspots": 0, | |||
"distribution": [], | |||
"activeRules": 0, | |||
"totalRules": 0 | |||
}, | |||
{ | |||
"category": "a6", | |||
"vulnerabilities": 0, | |||
"toReviewSecurityHotspots": 0, | |||
"openSecurityHotspots": 0, | |||
"wontFixSecurityHotspots": 0, | |||
"distribution": [], | |||
"activeRules": 0, | |||
"totalRules": 0 | |||
}, | |||
{ | |||
"category": "a7", | |||
"vulnerabilities": 0, | |||
"toReviewSecurityHotspots": 0, | |||
"openSecurityHotspots": 0, | |||
"wontFixSecurityHotspots": 0, | |||
"distribution": [], | |||
"activeRules": 0, | |||
"totalRules": 0 | |||
}, | |||
{ | |||
"category": "a8", | |||
"vulnerabilities": 0, | |||
"toReviewSecurityHotspots": 0, | |||
"openSecurityHotspots": 0, | |||
"wontFixSecurityHotspots": 0, | |||
"distribution": [], | |||
"activeRules": 0, | |||
"totalRules": 0 | |||
}, | |||
{ | |||
"category": "a9", | |||
"vulnerabilities": 0, | |||
"toReviewSecurityHotspots": 0, | |||
"openSecurityHotspots": 0, | |||
"wontFixSecurityHotspots": 0, | |||
"distribution": [], | |||
"activeRules": 0, | |||
"totalRules": 0 | |||
}, | |||
{ | |||
"category": "a10", | |||
"vulnerabilities": 0, | |||
"toReviewSecurityHotspots": 0, | |||
"openSecurityHotspots": 0, | |||
"wontFixSecurityHotspots": 0, | |||
"distribution": [], | |||
"activeRules": 0, | |||
"totalRules": 0 | |||
}, | |||
{ | |||
"category": "unknown", | |||
"vulnerabilities": 0, | |||
"toReviewSecurityHotspots": 0, | |||
"openSecurityHotspots": 0, | |||
"wontFixSecurityHotspots": 0, | |||
"distribution": [], | |||
"activeRules": 1, | |||
"totalRules": 2 | |||
} | |||
] | |||
} |
@@ -1,455 +0,0 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2019 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.securityreport.ws; | |||
import java.util.Date; | |||
import java.util.List; | |||
import java.util.Set; | |||
import org.junit.Before; | |||
import org.junit.Rule; | |||
import org.junit.Test; | |||
import org.junit.rules.ExpectedException; | |||
import org.sonar.api.issue.Issue; | |||
import org.sonar.api.rules.RuleType; | |||
import org.sonar.api.server.ws.WebService; | |||
import org.sonar.api.utils.System2; | |||
import org.sonar.api.web.UserRole; | |||
import org.sonar.db.DbClient; | |||
import org.sonar.db.DbSession; | |||
import org.sonar.db.DbTester; | |||
import org.sonar.db.component.ComponentDbTester; | |||
import org.sonar.db.component.ComponentDto; | |||
import org.sonar.db.component.ComponentTesting; | |||
import org.sonar.db.issue.IssueDto; | |||
import org.sonar.db.metric.MetricDto; | |||
import org.sonar.db.qualityprofile.QProfileDto; | |||
import org.sonar.db.rule.RuleDefinitionDto; | |||
import org.sonar.db.rule.RuleTesting; | |||
import org.sonar.db.user.UserDto; | |||
import org.sonar.server.component.TestComponentFinder; | |||
import org.sonar.server.es.EsTester; | |||
import org.sonar.server.es.StartupIndexer; | |||
import org.sonar.server.issue.index.IssueIndex; | |||
import org.sonar.server.issue.index.IssueIndexer; | |||
import org.sonar.server.issue.index.IssueIteratorFactory; | |||
import org.sonar.server.permission.index.PermissionIndexer; | |||
import org.sonar.server.permission.index.WebAuthorizationTypeSupport; | |||
import org.sonar.server.qualityprofile.QPMeasureData; | |||
import org.sonar.server.qualityprofile.QualityProfile; | |||
import org.sonar.server.tester.UserSessionRule; | |||
import org.sonar.server.view.index.ViewDoc; | |||
import org.sonar.server.view.index.ViewIndexer; | |||
import org.sonar.server.ws.WsActionTester; | |||
import static com.google.common.collect.Sets.newHashSet; | |||
import static java.util.Collections.singletonList; | |||
import static org.assertj.core.api.Assertions.assertThat; | |||
import static org.junit.rules.ExpectedException.none; | |||
import static org.sonar.api.measures.CoreMetrics.QUALITY_PROFILES_KEY; | |||
import static org.sonar.api.measures.Metric.ValueType.STRING; | |||
import static org.sonar.db.component.ComponentTesting.newFileDto; | |||
import static org.sonar.db.issue.IssueTesting.newIssue; | |||
import static org.sonar.server.tester.UserSessionRule.standalone; | |||
import static org.sonar.test.JsonAssert.assertJson; | |||
public class ShowActionTest { | |||
@Rule | |||
public UserSessionRule userSessionRule = standalone(); | |||
@Rule | |||
public DbTester db = DbTester.create(); | |||
@Rule | |||
public EsTester es = EsTester.create(); | |||
@Rule | |||
public ExpectedException expectedException = none(); | |||
private DbClient dbClient = db.getDbClient(); | |||
private ComponentDbTester componentDbTester = db.components(); | |||
private DbSession session = db.getSession(); | |||
private IssueIndex issueIndex = new IssueIndex(es.client(), System2.INSTANCE, userSessionRule, new WebAuthorizationTypeSupport(userSessionRule)); | |||
private IssueIndexer issueIndexer = new IssueIndexer(es.client(), dbClient, new IssueIteratorFactory(dbClient)); | |||
private ViewIndexer viewIndexer = new ViewIndexer(db.getDbClient(), es.client()); | |||
private WsActionTester ws = new WsActionTester(new ShowAction(userSessionRule, TestComponentFinder.from(db), issueIndex, dbClient)); | |||
private StartupIndexer permissionIndexer = new PermissionIndexer(dbClient, es.client(), issueIndexer); | |||
private UserDto user; | |||
private ComponentDto project; | |||
private QProfileDto qualityProfile; | |||
private RuleDefinitionDto rule1; | |||
private RuleDefinitionDto rule2; | |||
private RuleDefinitionDto rule3; | |||
private RuleDefinitionDto rule4; | |||
private RuleDefinitionDto rule5; | |||
private MetricDto qpMetric; | |||
@Before | |||
public void setUp() { | |||
user = db.users().insertUser("john"); | |||
userSessionRule.logIn(user); | |||
project = componentDbTester.insertComponent(ComponentTesting.newPrivateProjectDto(db.getDefaultOrganization(), "PROJECT_ID").setDbKey("PROJECT_KEY")); | |||
qualityProfile = db.qualityProfiles().insert(db.getDefaultOrganization(), qp -> qp.setLanguage("java")); | |||
// owasp : a2 and TopSans25 insecure | |||
rule1 = newRule(newHashSet("owaspTop10:a2", "cwe:123", "cwe:89"), RuleType.SECURITY_HOTSPOT); | |||
// owasp : a1 and TopSans25 risky | |||
rule2 = newRule(newHashSet("owaspTop10:a1", "cwe:22", "cwe:190"), RuleType.SECURITY_HOTSPOT); | |||
// owasp : a4 and TopSans25 porous, not activated | |||
rule3 = newRule(newHashSet("owaspTop10:a3", "cwe:759", "cwe:000"), RuleType.SECURITY_HOTSPOT); | |||
// cwe with unknown, not activated | |||
rule4 = newRule(newHashSet("cwe:999"), RuleType.SECURITY_HOTSPOT); | |||
// TopSans25 insecure | |||
rule5 = newRule(newHashSet("cwe:78"), RuleType.VULNERABILITY); | |||
db.qualityProfiles().activateRule(qualityProfile, rule1); | |||
db.qualityProfiles().activateRule(qualityProfile, rule2); | |||
db.qualityProfiles().activateRule(qualityProfile, rule5); | |||
qpMetric = db.measures().insertMetric(m -> m.setValueType(STRING.name()).setKey(QUALITY_PROFILES_KEY)); | |||
insertQPLiveMeasure(project); | |||
} | |||
@Test | |||
public void test_definition() { | |||
WebService.Action def = ws.getDef(); | |||
assertThat(def.key()).isEqualTo("show"); | |||
assertThat(def.isInternal()).isTrue(); | |||
assertThat(def.isPost()).isFalse(); | |||
assertThat(def.since()).isEqualTo("7.3"); | |||
assertThat(def.params()).extracting("key").containsExactlyInAnyOrder("standard", "project", "branch", "includeDistribution"); | |||
} | |||
@Test | |||
public void owasp_empty() { | |||
userSessionRule.addProjectPermission(UserRole.USER, project); | |||
indexPermissions(); | |||
ComponentDto file = componentDbTester.insertComponent(newFileDto(project, null, "FILE_ID").setDbKey("FILE_KEY")); | |||
IssueDto issue1 = newIssue(rule1, project, file) | |||
.setStatus("OPEN") | |||
.setSeverity("MAJOR") | |||
.setType(RuleType.CODE_SMELL); | |||
IssueDto issue2 = newIssue(rule1, project, file) | |||
.setStatus("OPEN") | |||
.setSeverity("MAJOR") | |||
.setType(RuleType.BUG); | |||
IssueDto issue3 = newIssue(rule1, project, file) | |||
.setStatus("OPEN") | |||
.setSeverity("MAJOR") | |||
.setType(RuleType.CODE_SMELL); | |||
dbClient.issueDao().insert(session, issue1, issue2, issue3); | |||
session.commit(); | |||
indexIssues(); | |||
assertJson(ws.newRequest() | |||
.setParam("standard", "owaspTop10") | |||
.setParam("project", project.getKey()) | |||
.execute().getInput()) | |||
.withStrictArrayOrder() | |||
.isSimilarTo(this.getClass().getResource("ShowActionTest/empty.json")); | |||
} | |||
@Test | |||
public void owasp_without_cwe() { | |||
userSessionRule.addProjectPermission(UserRole.USER, project); | |||
indexPermissions(); | |||
ComponentDto file = componentDbTester.insertComponent(newFileDto(project, null, "FILE_ID").setDbKey("FILE_KEY")); | |||
IssueDto issue1 = newIssue(rule1, project, file) | |||
.setStatus("OPEN") | |||
.setSeverity("MAJOR") | |||
.setType(RuleType.VULNERABILITY); | |||
IssueDto issue2 = newIssue(rule1, project, file) | |||
.setStatus("OPEN") | |||
.setSeverity("MAJOR") | |||
.setType(RuleType.SECURITY_HOTSPOT); | |||
IssueDto issue3 = newIssue(rule1, project, file) | |||
.setStatus(Issue.STATUS_RESOLVED) | |||
.setResolution(Issue.RESOLUTION_FIXED) | |||
.setSeverity("MAJOR") | |||
.setType(RuleType.SECURITY_HOTSPOT); | |||
IssueDto issue4 = newIssue(rule1, project, file) | |||
.setStatus(Issue.STATUS_RESOLVED) | |||
.setResolution(Issue.RESOLUTION_WONT_FIX) | |||
.setSeverity("MAJOR") | |||
.setType(RuleType.SECURITY_HOTSPOT); | |||
dbClient.issueDao().insert(session, issue1, issue2, issue3, issue4); | |||
session.commit(); | |||
indexIssues(); | |||
assertJson(ws.newRequest() | |||
.setParam("standard", "owaspTop10") | |||
.setParam("project", project.getKey()) | |||
.execute().getInput()) | |||
.withStrictArrayOrder() | |||
.isSimilarTo(this.getClass().getResource("ShowActionTest/owaspNoCwe.json")); | |||
} | |||
@Test | |||
public void owasp_with_cwe__matching_json_example() { | |||
userSessionRule.addProjectPermission(UserRole.USER, project); | |||
indexPermissions(); | |||
ComponentDto file = componentDbTester.insertComponent(newFileDto(project, null, "FILE_ID").setDbKey("FILE_KEY")); | |||
IssueDto issue1 = newIssue(rule1, project, file) | |||
.setStatus("OPEN") | |||
.setSeverity("MAJOR") | |||
.setType(RuleType.VULNERABILITY); | |||
IssueDto issue2 = newIssue(rule1, project, file) | |||
.setStatus("OPEN") | |||
.setSeverity("MAJOR") | |||
.setType(RuleType.SECURITY_HOTSPOT); | |||
IssueDto issue3 = newIssue(rule1, project, file) | |||
.setStatus(Issue.STATUS_RESOLVED) | |||
.setResolution(Issue.RESOLUTION_FIXED) | |||
.setSeverity("MAJOR") | |||
.setType(RuleType.SECURITY_HOTSPOT); | |||
IssueDto issue4 = newIssue(rule1, project, file) | |||
.setStatus(Issue.STATUS_RESOLVED) | |||
.setResolution(Issue.RESOLUTION_WONT_FIX) | |||
.setSeverity("MAJOR") | |||
.setType(RuleType.SECURITY_HOTSPOT); | |||
dbClient.issueDao().insert(session, issue1, issue2, issue3, issue4); | |||
session.commit(); | |||
indexIssues(); | |||
assertJson(ws.newRequest() | |||
.setParam("standard", "owaspTop10") | |||
.setParam("project", project.getKey()) | |||
.setParam("includeDistribution", "true") | |||
.execute().getInput()) | |||
.withStrictArrayOrder() | |||
.isSimilarTo(getClass().getResource("show-example.json")); | |||
} | |||
@Test | |||
public void sans_with_cwe() { | |||
userSessionRule.addProjectPermission(UserRole.USER, project); | |||
indexPermissions(); | |||
ComponentDto file = componentDbTester.insertComponent(newFileDto(project, null, "FILE_ID").setDbKey("FILE_KEY")); | |||
IssueDto issue1 = newIssue(rule1, project, file) | |||
.setStatus("OPEN") | |||
.setSeverity("MAJOR") | |||
.setType(RuleType.VULNERABILITY); | |||
IssueDto issue2 = newIssue(rule1, project, file) | |||
.setStatus("OPEN") | |||
.setSeverity("MAJOR") | |||
.setType(RuleType.SECURITY_HOTSPOT); | |||
IssueDto issue3 = newIssue(rule1, project, file) | |||
.setStatus(Issue.STATUS_RESOLVED) | |||
.setResolution(Issue.RESOLUTION_FIXED) | |||
.setSeverity("MAJOR") | |||
.setType(RuleType.SECURITY_HOTSPOT); | |||
IssueDto issue4 = newIssue(rule5, project, file) | |||
.setStatus(Issue.STATUS_RESOLVED) | |||
.setResolution(Issue.RESOLUTION_WONT_FIX) | |||
.setSeverity("MAJOR") | |||
.setType(RuleType.VULNERABILITY); | |||
dbClient.issueDao().insert(session, issue1, issue2, issue3, issue4); | |||
session.commit(); | |||
indexIssues(); | |||
assertJson(ws.newRequest() | |||
.setParam("standard", "sansTop25") | |||
.setParam("project", project.getKey()) | |||
.setParam("includeDistribution", "true") | |||
.execute().getInput()) | |||
.withStrictArrayOrder() | |||
.isSimilarTo(this.getClass().getResource("ShowActionTest/sansWithCwe.json")); | |||
} | |||
@Test | |||
public void sans_with_cwe_for_branches() { | |||
ComponentDto project1 = db.components().insertPrivateProject(p -> p.setDbKey("prj1")); | |||
ComponentDto project1Branch1 = db.components().insertProjectBranch(project1); | |||
ComponentDto fileOnProject1Branch1 = db.components().insertComponent(newFileDto(project1Branch1)); | |||
ComponentDto project2 = db.components().insertPrivateProject(p -> p.setDbKey("prj2")); | |||
insertQPLiveMeasure(project1); | |||
insertQPLiveMeasure(project1Branch1); | |||
insertQPLiveMeasure(project2); | |||
userSessionRule.addProjectPermission(UserRole.USER, project1); | |||
userSessionRule.addProjectPermission(UserRole.USER, project2); | |||
indexPermissions(); | |||
IssueDto issue1 = newIssue(rule1, project1Branch1, fileOnProject1Branch1) | |||
.setStatus("OPEN") | |||
.setSeverity("MAJOR") | |||
.setType(RuleType.VULNERABILITY); | |||
IssueDto issue2 = newIssue(rule1, project1Branch1, fileOnProject1Branch1) | |||
.setStatus("OPEN") | |||
.setSeverity("MAJOR") | |||
.setType(RuleType.SECURITY_HOTSPOT); | |||
IssueDto issue3 = newIssue(rule1, project1Branch1, fileOnProject1Branch1) | |||
.setStatus(Issue.STATUS_RESOLVED) | |||
.setResolution(Issue.RESOLUTION_FIXED) | |||
.setSeverity("MAJOR") | |||
.setType(RuleType.SECURITY_HOTSPOT); | |||
IssueDto issue4 = newIssue(rule5, project1Branch1, fileOnProject1Branch1) | |||
.setStatus(Issue.STATUS_RESOLVED) | |||
.setResolution(Issue.RESOLUTION_WONT_FIX) | |||
.setSeverity("MAJOR") | |||
.setType(RuleType.VULNERABILITY); | |||
dbClient.issueDao().insert(session, issue1, issue2, issue3, issue4); | |||
session.commit(); | |||
indexIssues(); | |||
assertJson(ws.newRequest() | |||
.setParam("standard", "sansTop25") | |||
.setParam("project", project1Branch1.getKey()) | |||
.setParam("branch", project1Branch1.getBranch()) | |||
.setParam("includeDistribution", "true") | |||
.execute().getInput()) | |||
.withStrictArrayOrder() | |||
.isSimilarTo(this.getClass().getResource("ShowActionTest/sansWithCwe.json")); | |||
} | |||
@Test | |||
public void sonarsource_security_without_cwe() { | |||
userSessionRule.addProjectPermission(UserRole.USER, project); | |||
indexPermissions(); | |||
ComponentDto file = componentDbTester.insertComponent(newFileDto(project, null, "FILE_ID").setDbKey("FILE_KEY")); | |||
IssueDto issue1 = newIssue(rule1, project, file) | |||
.setStatus("OPEN") | |||
.setSeverity("MAJOR") | |||
.setType(RuleType.VULNERABILITY); | |||
IssueDto issue2 = newIssue(rule1, project, file) | |||
.setStatus("OPEN") | |||
.setSeverity("MAJOR") | |||
.setType(RuleType.SECURITY_HOTSPOT); | |||
IssueDto issue3 = newIssue(rule1, project, file) | |||
.setStatus(Issue.STATUS_RESOLVED) | |||
.setResolution(Issue.RESOLUTION_FIXED) | |||
.setSeverity("MAJOR") | |||
.setType(RuleType.SECURITY_HOTSPOT); | |||
IssueDto issue4 = newIssue(rule1, project, file) | |||
.setStatus(Issue.STATUS_RESOLVED) | |||
.setResolution(Issue.RESOLUTION_WONT_FIX) | |||
.setSeverity("MAJOR") | |||
.setType(RuleType.SECURITY_HOTSPOT); | |||
dbClient.issueDao().insert(session, issue1, issue2, issue3, issue4); | |||
session.commit(); | |||
indexIssues(); | |||
assertJson(ws.newRequest() | |||
.setParam("standard", "sonarsourceSecurity") | |||
.setParam("project", project.getKey()) | |||
.setParam("includeDistribution", "false") | |||
.execute().getInput()) | |||
.withStrictArrayOrder() | |||
.isSimilarTo(this.getClass().getResource("ShowActionTest/sonarsourceSecurityNoCwe.json")); | |||
} | |||
@Test | |||
public void sonarsource_security_with_cwe() { | |||
userSessionRule.addProjectPermission(UserRole.USER, project); | |||
indexPermissions(); | |||
ComponentDto file = componentDbTester.insertComponent(newFileDto(project, null, "FILE_ID").setDbKey("FILE_KEY")); | |||
IssueDto issue1 = newIssue(rule1, project, file) | |||
.setStatus("OPEN") | |||
.setSeverity("MAJOR") | |||
.setType(RuleType.VULNERABILITY); | |||
IssueDto issue2 = newIssue(rule1, project, file) | |||
.setStatus("OPEN") | |||
.setSeverity("MAJOR") | |||
.setType(RuleType.SECURITY_HOTSPOT); | |||
IssueDto issue3 = newIssue(rule1, project, file) | |||
.setStatus(Issue.STATUS_RESOLVED) | |||
.setResolution(Issue.RESOLUTION_FIXED) | |||
.setSeverity("MAJOR") | |||
.setType(RuleType.SECURITY_HOTSPOT); | |||
IssueDto issue4 = newIssue(rule1, project, file) | |||
.setStatus(Issue.STATUS_RESOLVED) | |||
.setResolution(Issue.RESOLUTION_WONT_FIX) | |||
.setSeverity("MAJOR") | |||
.setType(RuleType.SECURITY_HOTSPOT); | |||
dbClient.issueDao().insert(session, issue1, issue2, issue3, issue4); | |||
session.commit(); | |||
indexIssues(); | |||
assertJson(ws.newRequest() | |||
.setParam("standard", "sonarsourceSecurity") | |||
.setParam("project", project.getKey()) | |||
.setParam("includeDistribution", "true") | |||
.execute().getInput()) | |||
.withStrictArrayOrder() | |||
.isSimilarTo(this.getClass().getResource("ShowActionTest/sonarsourceSecurityWithCwe.json")); | |||
} | |||
@Test | |||
public void dont_return_rules_on_application() { | |||
ComponentDto application = db.components().insertPrivateApplication(db.getDefaultOrganization()); | |||
indexView(application.uuid(), singletonList(project.uuid())); | |||
userSessionRule.addProjectPermission(UserRole.USER, application); | |||
indexPermissions(); | |||
ComponentDto file = componentDbTester.insertComponent(newFileDto(project, null, "FILE_ID").setDbKey("FILE_KEY")); | |||
IssueDto issue1 = newIssue(rule1, project, file) | |||
.setStatus("OPEN") | |||
.setSeverity("MAJOR") | |||
.setType(RuleType.VULNERABILITY); | |||
IssueDto issue2 = newIssue(rule1, project, file) | |||
.setStatus("OPEN") | |||
.setSeverity("MAJOR") | |||
.setType(RuleType.SECURITY_HOTSPOT); | |||
IssueDto issue3 = newIssue(rule1, project, file) | |||
.setStatus(Issue.STATUS_RESOLVED) | |||
.setResolution(Issue.RESOLUTION_FIXED) | |||
.setSeverity("MAJOR") | |||
.setType(RuleType.SECURITY_HOTSPOT); | |||
IssueDto issue4 = newIssue(rule1, project, file) | |||
.setStatus(Issue.STATUS_RESOLVED) | |||
.setResolution(Issue.RESOLUTION_WONT_FIX) | |||
.setSeverity("MAJOR") | |||
.setType(RuleType.SECURITY_HOTSPOT); | |||
dbClient.issueDao().insert(session, issue1, issue2, issue3, issue4); | |||
session.commit(); | |||
indexIssues(); | |||
assertJson(ws.newRequest() | |||
.setParam("standard", "sonarsourceSecurity") | |||
.setParam("project", application.getKey()) | |||
.setParam("includeDistribution", "true") | |||
.execute().getInput()) | |||
.withStrictArrayOrder() | |||
.isSimilarTo(this.getClass().getResource("ShowActionTest/sonarsourceSecurityOnApplication.json")); | |||
} | |||
private RuleDefinitionDto newRule(Set<String> standards, RuleType type) { | |||
RuleDefinitionDto rule = RuleTesting.newRule() | |||
.setType(type) | |||
.setLanguage("java") | |||
.setSecurityStandards(standards); | |||
db.rules().insert(rule); | |||
return rule; | |||
} | |||
private void indexPermissions() { | |||
permissionIndexer.indexOnStartup(permissionIndexer.getIndexTypes()); | |||
} | |||
private void indexIssues() { | |||
issueIndexer.indexOnStartup(issueIndexer.getIndexTypes()); | |||
} | |||
private void indexView(String viewUuid, List<String> projects) { | |||
viewIndexer.index(new ViewDoc().setUuid(viewUuid).setProjects(projects)); | |||
} | |||
private void insertQPLiveMeasure(ComponentDto project) { | |||
QualityProfile qp = new QualityProfile(qualityProfile.getKee(), qualityProfile.getName(), qualityProfile.getLanguage(), new Date()); | |||
db.measures().insertLiveMeasure(project, qpMetric, lm -> lm.setData(QPMeasureData.toJson(new QPMeasureData(singletonList(qp))))); | |||
} | |||
} |