import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Maps;
+import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
+import java.util.OptionalInt;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import org.elasticsearch.indices.TermsLookup;
import org.elasticsearch.search.aggregations.AggregationBuilder;
import org.elasticsearch.search.aggregations.AggregationBuilders;
+import org.elasticsearch.search.aggregations.HasAggregations;
import org.elasticsearch.search.aggregations.bucket.filter.FilterAggregationBuilder;
import org.elasticsearch.search.aggregations.bucket.filter.InternalFilter;
import org.elasticsearch.search.aggregations.bucket.histogram.DateHistogramInterval;
import org.elasticsearch.search.sort.FieldSortBuilder;
import org.joda.time.DateTimeZone;
import org.joda.time.Duration;
+import org.sonar.api.issue.Issue;
+import org.sonar.api.rule.Severity;
import org.sonar.api.rules.RuleType;
import org.sonar.api.utils.DateUtils;
import org.sonar.api.utils.System2;
import static org.sonar.core.util.stream.MoreCollectors.uniqueIndex;
import static org.sonar.server.es.BaseDoc.epochMillisToEpochSeconds;
import static org.sonar.server.es.EsUtils.escapeSpecialRegexChars;
+import static org.sonar.server.issue.IssueQuery.SANS_TOP_25_INSECURE_INTERACTION;
+import static org.sonar.server.issue.IssueQuery.SANS_TOP_25_POROUS_DEFENSES;
+import static org.sonar.server.issue.IssueQuery.SANS_TOP_25_RISKY_RESOURCE;
+import static org.sonar.server.issue.IssueQuery.UNKNOWN_STANDARD;
import static org.sonar.server.issue.index.IssueIndexDefinition.FIELD_ISSUE_ORGANIZATION_UUID;
import static org.sonar.server.issue.index.IssueIndexDefinition.INDEX_TYPE_ISSUE;
import static org.sonarqube.ws.client.issue.IssuesWsParameters.DEPRECATED_FACET_MODE_DEBT;
.collect(MoreCollectors.toList(branchUuids.size()));
}
+ public List<SecurityStandardCategoryStatistics> getSansTop25Report(String projectUuid, boolean includeCwe) {
+ SearchRequestBuilder request = prepareNonClosedVulnerabilitiesAndHotspotSearch(projectUuid);
+ Stream.of(SANS_TOP_25_INSECURE_INTERACTION, SANS_TOP_25_RISKY_RESOURCE, SANS_TOP_25_POROUS_DEFENSES).forEach(sansCategory -> {
+ AggregationBuilder sansCategoryAggs = AggregationBuilders
+ .filter(sansCategory, boolQuery()
+ .filter(termQuery(IssueIndexDefinition.FIELD_ISSUE_SANS_TOP_25, sansCategory)));
+ request.addAggregation(addSecurityReportSubAggregations(sansCategoryAggs, includeCwe));
+ });
+ return processSecurityReportSearchResults(request, includeCwe);
+ }
+
+ public List<SecurityStandardCategoryStatistics> getOwaspTop10Report(String projectUuid, boolean includeCwe) {
+ SearchRequestBuilder request = prepareNonClosedVulnerabilitiesAndHotspotSearch(projectUuid);
+ Stream.concat(IntStream.rangeClosed(1, 10).mapToObj(i -> "a" + i), Stream.of(UNKNOWN_STANDARD)).forEach(owaspCategory -> {
+ AggregationBuilder owaspCategoryAggs = AggregationBuilders
+ .filter(owaspCategory, boolQuery()
+ .filter(termQuery(IssueIndexDefinition.FIELD_ISSUE_OWASP_TOP_10, owaspCategory)));
+ request.addAggregation(addSecurityReportSubAggregations(owaspCategoryAggs, includeCwe));
+ });
+ return processSecurityReportSearchResults(request, includeCwe);
+ }
+
+ private static List<SecurityStandardCategoryStatistics> processSecurityReportSearchResults(SearchRequestBuilder request, boolean includeCwe) {
+ SearchResponse response = request.get();
+ return response.getAggregations().asList().stream()
+ .map(c -> processSecurityReportIssueSearchResults((InternalFilter) c, includeCwe))
+ .collect(MoreCollectors.toList());
+ }
+
+ private static SecurityStandardCategoryStatistics processSecurityReportIssueSearchResults(InternalFilter categoryBucket, boolean includeCwe) {
+ List<SecurityStandardCategoryStatistics> children = new ArrayList<>();
+
+ if (includeCwe) {
+ ((StringTerms) categoryBucket.getAggregations().get("cwe")).getBuckets()
+ .forEach(cweBucket -> children.add(processSecurityReportCategorySearchResults(cweBucket, cweBucket.getKeyAsString(), null)));
+ }
+
+ return processSecurityReportCategorySearchResults(categoryBucket, categoryBucket.getName(), children);
+ }
+
+ private static SecurityStandardCategoryStatistics processSecurityReportCategorySearchResults(HasAggregations categoryBucket, String categoryName,
+ @Nullable List<SecurityStandardCategoryStatistics> children) {
+ List<StringTerms.Bucket> severityBuckets = ((StringTerms) ((InternalFilter) categoryBucket.getAggregations().get("vulnerabilities")).getAggregations().get("severity"))
+ .getBuckets();
+ long vulnerabilities = severityBuckets.stream().mapToLong(b -> ((InternalValueCount) b.getAggregations().get("count")).getValue()).sum();
+ // Worst severity having at least one issue
+ OptionalInt severityRating = severityBuckets.stream()
+ .filter(b -> ((InternalValueCount) b.getAggregations().get("count")).getValue() != 0)
+ .mapToInt(b -> Severity.ALL.indexOf(b.getKeyAsString()) + 1)
+ .max();
+
+ long openSecurityHotspots = ((InternalValueCount) ((InternalFilter) categoryBucket.getAggregations().get("openSecurityHotspots")).getAggregations().get("count"))
+ .getValue();
+ long toReviewSecurityHotspots = ((InternalValueCount) ((InternalFilter) categoryBucket.getAggregations().get("toReviewSecurityHotspots")).getAggregations().get("count"))
+ .getValue();
+ long wontFixSecurityHotspots = ((InternalValueCount) ((InternalFilter) categoryBucket.getAggregations().get("wontFixSecurityHotspots")).getAggregations().get("count"))
+ .getValue();
+
+ return new SecurityStandardCategoryStatistics(categoryName, vulnerabilities, severityRating, toReviewSecurityHotspots, openSecurityHotspots,
+ wontFixSecurityHotspots, children);
+ }
+
+ private static AggregationBuilder addSecurityReportSubAggregations(AggregationBuilder categoriesAggs, boolean includeCwe) {
+ AggregationBuilder aggregationBuilder = addSecurityReportIssueCountAggregations(categoriesAggs);
+ if (includeCwe) {
+ categoriesAggs
+ .subAggregation(addSecurityReportIssueCountAggregations(AggregationBuilders.terms("cwe").field(IssueIndexDefinition.FIELD_ISSUE_CWE)));
+ }
+ return aggregationBuilder;
+ }
+
+ private static AggregationBuilder addSecurityReportIssueCountAggregations(AggregationBuilder categoryAggs) {
+ return categoryAggs
+ .subAggregation(
+ AggregationBuilders.filter("vulnerabilities", boolQuery()
+ .filter(termQuery(IssueIndexDefinition.FIELD_ISSUE_TYPE, RuleType.VULNERABILITY.name()))
+ .mustNot(existsQuery(IssueIndexDefinition.FIELD_ISSUE_RESOLUTION)))
+ .subAggregation(
+ AggregationBuilders.terms("severity").field(IssueIndexDefinition.FIELD_ISSUE_SEVERITY)
+ .subAggregation(
+ AggregationBuilders.count("count").field(IssueIndexDefinition.FIELD_ISSUE_KEY))))
+ .subAggregation(AggregationBuilders.filter("openSecurityHotspots", boolQuery()
+ .filter(termQuery(IssueIndexDefinition.FIELD_ISSUE_TYPE, RuleType.SECURITY_HOTSPOT.name()))
+ .mustNot(existsQuery(IssueIndexDefinition.FIELD_ISSUE_RESOLUTION)))
+ .subAggregation(
+ AggregationBuilders.count("count").field(IssueIndexDefinition.FIELD_ISSUE_KEY)))
+ .subAggregation(AggregationBuilders.filter("toReviewSecurityHotspots", boolQuery()
+ .filter(termQuery(IssueIndexDefinition.FIELD_ISSUE_TYPE, RuleType.SECURITY_HOTSPOT.name()))
+ .filter(termQuery(IssueIndexDefinition.FIELD_ISSUE_STATUS, Issue.STATUS_RESOLVED))
+ .filter(termQuery(IssueIndexDefinition.FIELD_ISSUE_RESOLUTION, Issue.RESOLUTION_FIXED)))
+ .subAggregation(
+ AggregationBuilders.count("count").field(IssueIndexDefinition.FIELD_ISSUE_KEY)))
+ .subAggregation(AggregationBuilders.filter("wontFixSecurityHotspots", boolQuery()
+ .filter(termQuery(IssueIndexDefinition.FIELD_ISSUE_TYPE, RuleType.SECURITY_HOTSPOT.name()))
+ .filter(termQuery(IssueIndexDefinition.FIELD_ISSUE_STATUS, Issue.STATUS_RESOLVED))
+ .filter(termQuery(IssueIndexDefinition.FIELD_ISSUE_RESOLUTION, Issue.RESOLUTION_WONT_FIX)))
+ .subAggregation(
+ AggregationBuilders.count("count").field(IssueIndexDefinition.FIELD_ISSUE_KEY)));
+ }
+
+ private SearchRequestBuilder prepareNonClosedVulnerabilitiesAndHotspotSearch(String projectUuid) {
+ return client.prepareSearch(IssueIndexDefinition.INDEX_TYPE_ISSUE)
+ .setQuery(
+ boolQuery()
+ .filter(termQuery(IssueIndexDefinition.FIELD_ISSUE_PROJECT_UUID, projectUuid))
+ .filter(termsQuery(IssueIndexDefinition.FIELD_ISSUE_TYPE, RuleType.SECURITY_HOTSPOT.name(), RuleType.VULNERABILITY.name()))
+ .mustNot(termQuery(IssueIndexDefinition.FIELD_ISSUE_STATUS, Issue.STATUS_CLOSED)))
+ .setSize(0);
+ }
+
}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 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.issue.index;
+
+import java.util.List;
+import java.util.OptionalInt;
+import javax.annotation.Nullable;
+
+public class SecurityStandardCategoryStatistics {
+
+ private final String category;
+ private final long vulnerabilities;
+ private final OptionalInt vulnerabiliyRating;
+ private final long toReviewSecurityHotspots;
+ private final long openSecurityHotspots;
+ private final long wontFixSecurityHotspots;
+ private final List<SecurityStandardCategoryStatistics> children;
+
+ public SecurityStandardCategoryStatistics(String category, long vulnerabilities, OptionalInt vulnerabiliyRating, long toReviewSecurityHotspots, long openSecurityHotspots,
+ long wontFixSecurityHotspots, @Nullable List<SecurityStandardCategoryStatistics> children) {
+ this.category = category;
+ this.vulnerabilities = vulnerabilities;
+ this.vulnerabiliyRating = vulnerabiliyRating;
+ this.toReviewSecurityHotspots = toReviewSecurityHotspots;
+ this.openSecurityHotspots = openSecurityHotspots;
+ this.wontFixSecurityHotspots = wontFixSecurityHotspots;
+ this.children = children;
+ }
+
+ public String getCategory() {
+ return category;
+ }
+
+ public long getVulnerabilities() {
+ return vulnerabilities;
+ }
+
+ public OptionalInt getVulnerabiliyRating() {
+ return vulnerabiliyRating;
+ }
+
+ public long getToReviewSecurityHotspots() {
+ return toReviewSecurityHotspots;
+ }
+
+ public long getOpenSecurityHotspots() {
+ return openSecurityHotspots;
+ }
+
+ public long getWontFixSecurityHotspots() {
+ return wontFixSecurityHotspots;
+ }
+
+ public List<SecurityStandardCategoryStatistics> getChildren() {
+ return children;
+ }
+}
import java.util.Date;
import java.util.List;
import java.util.Map;
+import java.util.OptionalInt;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import org.assertj.core.api.Fail;
import org.junit.rules.ExpectedException;
import org.sonar.api.issue.Issue;
import org.sonar.api.rule.Severity;
+import org.sonar.api.rules.RuleType;
import org.sonar.api.utils.System2;
import org.sonar.api.utils.internal.TestSystem2;
import org.sonar.db.DbTester;
import static org.sonar.db.user.GroupTesting.newGroupDto;
import static org.sonar.db.user.UserTesting.newUserDto;
import static org.sonar.server.issue.IssueDocTesting.newDoc;
+import static org.sonar.server.issue.IssueQuery.SANS_TOP_25_INSECURE_INTERACTION;
+import static org.sonar.server.issue.IssueQuery.SANS_TOP_25_POROUS_DEFENSES;
+import static org.sonar.server.issue.IssueQuery.SANS_TOP_25_RISKY_RESOURCE;
+import static org.sonar.server.issue.IssueQuery.UNKNOWN_STANDARD;
public class IssueIndexTest {
assertThat(underTest.searchBranchStatistics(project.uuid(), singletonList("unknown"))).isEmpty();
}
+ @Test
+ public void test_getOwaspTop10Report_dont_count_vulnerabilities_from_other_projects() {
+ OrganizationDto org = newOrganizationDto();
+ ComponentDto project = newPrivateProjectDto(org);
+ ComponentDto another = newPrivateProjectDto(org);
+ indexIssues(
+ newDoc("anotherProject", another).setOwaspTop10(singletonList("a1")).setType(RuleType.VULNERABILITY).setStatus(Issue.STATUS_OPEN).setSeverity(Severity.CRITICAL),
+ newDoc("openvul1", project).setOwaspTop10(singletonList("a1")).setType(RuleType.VULNERABILITY).setStatus(Issue.STATUS_OPEN).setSeverity(Severity.MAJOR));
+
+ List<SecurityStandardCategoryStatistics> owaspTop10Report = underTest.getOwaspTop10Report(project.uuid(), false);
+ assertThat(owaspTop10Report)
+ .extracting(SecurityStandardCategoryStatistics::getCategory, SecurityStandardCategoryStatistics::getVulnerabilities,
+ SecurityStandardCategoryStatistics::getVulnerabiliyRating)
+ .contains(
+ tuple("a1", 1L /* openvul1 */, OptionalInt.of(3)/* MAJOR = C */));
+
+ }
+
+ @Test
+ public void test_getOwaspTop10Report_dont_count_closed_vulnerabilities() {
+ OrganizationDto org = newOrganizationDto();
+ ComponentDto project = newPrivateProjectDto(org);
+ indexIssues(
+ newDoc("openvul1", project).setOwaspTop10(asList("a1")).setType(RuleType.VULNERABILITY).setStatus(Issue.STATUS_OPEN).setSeverity(Severity.MAJOR),
+ newDoc("notopenvul", project).setOwaspTop10(asList("a1")).setType(RuleType.VULNERABILITY).setStatus(Issue.STATUS_CLOSED).setResolution(Issue.RESOLUTION_FIXED)
+ .setSeverity(Severity.BLOCKER));
+
+ List<SecurityStandardCategoryStatistics> owaspTop10Report = underTest.getOwaspTop10Report(project.uuid(), false);
+ assertThat(owaspTop10Report)
+ .extracting(SecurityStandardCategoryStatistics::getCategory, SecurityStandardCategoryStatistics::getVulnerabilities,
+ SecurityStandardCategoryStatistics::getVulnerabiliyRating)
+ .contains(
+ tuple("a1", 1L /* openvul1 */, OptionalInt.of(3)/* MAJOR = C */));
+ }
+
+ @Test
+ public void test_getOwaspTop10Report_dont_count_old_vulnerabilities() {
+ OrganizationDto org = newOrganizationDto();
+ ComponentDto project = newPrivateProjectDto(org);
+ indexIssues(
+ // Previous vulnerabilities in projects that are not reanalyzed will have no owasp nor cwe attributes (not even 'unknown')
+ newDoc("openvulNotReindexed", project).setType(RuleType.VULNERABILITY).setStatus(Issue.STATUS_OPEN).setSeverity(Severity.MAJOR));
+
+ List<SecurityStandardCategoryStatistics> owaspTop10Report = underTest.getOwaspTop10Report(project.uuid(), false);
+ assertThat(owaspTop10Report)
+ .extracting(SecurityStandardCategoryStatistics::getVulnerabilities,
+ SecurityStandardCategoryStatistics::getVulnerabiliyRating)
+ .containsOnly(
+ tuple(0L, OptionalInt.empty()));
+ }
+
+ @Test
+ public void test_getOwaspTop10Report_dont_count_hotspots_from_other_projects() {
+ OrganizationDto org = newOrganizationDto();
+ ComponentDto project = newPrivateProjectDto(org);
+ ComponentDto another = newPrivateProjectDto(org);
+ indexIssues(
+ newDoc("openhotspot1", project).setOwaspTop10(asList("a1")).setType(RuleType.SECURITY_HOTSPOT).setStatus(Issue.STATUS_OPEN),
+ newDoc("anotherProject", another).setOwaspTop10(asList("a1")).setType(RuleType.SECURITY_HOTSPOT).setStatus(Issue.STATUS_OPEN));
+
+ List<SecurityStandardCategoryStatistics> owaspTop10Report = underTest.getOwaspTop10Report(project.uuid(), false);
+ assertThat(owaspTop10Report)
+ .extracting(SecurityStandardCategoryStatistics::getCategory, SecurityStandardCategoryStatistics::getOpenSecurityHotspots)
+ .contains(
+ tuple("a1", 1L /* openhotspot1 */));
+ }
+
+ @Test
+ public void test_getOwaspTop10Report_dont_count_closed_hotspots() {
+ OrganizationDto org = newOrganizationDto();
+ ComponentDto project = newPrivateProjectDto(org);
+ indexIssues(
+ newDoc("openhotspot1", project).setOwaspTop10(asList("a1")).setType(RuleType.SECURITY_HOTSPOT).setStatus(Issue.STATUS_OPEN),
+ newDoc("closedHotspot", project).setOwaspTop10(asList("a1")).setType(RuleType.SECURITY_HOTSPOT).setStatus(Issue.STATUS_CLOSED)
+ .setResolution(Issue.RESOLUTION_FIXED));
+
+ List<SecurityStandardCategoryStatistics> owaspTop10Report = underTest.getOwaspTop10Report(project.uuid(), false);
+ assertThat(owaspTop10Report)
+ .extracting(SecurityStandardCategoryStatistics::getCategory, SecurityStandardCategoryStatistics::getOpenSecurityHotspots)
+ .contains(
+ tuple("a1", 1L /* openhotspot1 */));
+ }
+
+ @Test
+ public void test_getOwaspTop10Report_aggregation_no_cwe() {
+ List<SecurityStandardCategoryStatistics> owaspTop10Report = indexIssuesAndAssertOwaspReport(false);
+
+ assertThat(owaspTop10Report).allMatch(category -> category.getChildren().isEmpty());
+ }
+
+ @Test
+ public void test_getOwaspTop10Report_aggregation_with_cwe() {
+ List<SecurityStandardCategoryStatistics> owaspTop10Report = indexIssuesAndAssertOwaspReport(true);
+
+ Map<String, List<SecurityStandardCategoryStatistics>> cweByOwasp = owaspTop10Report.stream()
+ .collect(Collectors.toMap(SecurityStandardCategoryStatistics::getCategory, SecurityStandardCategoryStatistics::getChildren));
+
+ assertThat(cweByOwasp.get("a1")).extracting(SecurityStandardCategoryStatistics::getCategory, SecurityStandardCategoryStatistics::getVulnerabilities,
+ SecurityStandardCategoryStatistics::getVulnerabiliyRating, SecurityStandardCategoryStatistics::getOpenSecurityHotspots,
+ SecurityStandardCategoryStatistics::getToReviewSecurityHotspots, SecurityStandardCategoryStatistics::getWontFixSecurityHotspots)
+ .containsExactlyInAnyOrder(
+ tuple("123", 1L /* openvul1 */, OptionalInt.of(3)/* MAJOR = C */, 0L, 0L, 0L),
+ tuple("456", 1L /* openvul1 */, OptionalInt.of(3)/* MAJOR = C */, 0L, 0L, 0L),
+ tuple("unknown", 0L, OptionalInt.empty(), 1L /* openhotspot1 */, 0L, 0L));
+ assertThat(cweByOwasp.get("a3")).extracting(SecurityStandardCategoryStatistics::getCategory, SecurityStandardCategoryStatistics::getVulnerabilities,
+ SecurityStandardCategoryStatistics::getVulnerabiliyRating, SecurityStandardCategoryStatistics::getOpenSecurityHotspots,
+ SecurityStandardCategoryStatistics::getToReviewSecurityHotspots, SecurityStandardCategoryStatistics::getWontFixSecurityHotspots)
+ .containsExactlyInAnyOrder(
+ tuple("123", 2L /* openvul1, openvul2 */, OptionalInt.of(3)/* MAJOR = C */, 0L, 0L, 0L),
+ tuple("456", 1L /* openvul1 */, OptionalInt.of(3)/* MAJOR = C */, 0L, 1L /* toReviewHotspot */, 0L),
+ tuple("unknown", 0L, OptionalInt.empty(), 1L /* openhotspot1 */, 0L, 0L));
+ }
+
+ private List<SecurityStandardCategoryStatistics> indexIssuesAndAssertOwaspReport(boolean includeCwe) {
+ OrganizationDto org = newOrganizationDto();
+ ComponentDto project = newPrivateProjectDto(org);
+ ComponentDto another = newPrivateProjectDto(org);
+ indexIssues(
+ newDoc("openvul1", project).setOwaspTop10(asList("a1", "a3")).setCwe(asList("123", "456")).setType(RuleType.VULNERABILITY).setStatus(Issue.STATUS_OPEN)
+ .setSeverity(Severity.MAJOR),
+ newDoc("openvul2", project).setOwaspTop10(asList("a3", "a6")).setCwe(asList("123")).setType(RuleType.VULNERABILITY).setStatus(Issue.STATUS_REOPENED)
+ .setSeverity(Severity.MINOR),
+ newDoc("notowaspvul", project).setOwaspTop10(singletonList(UNKNOWN_STANDARD)).setType(RuleType.VULNERABILITY).setStatus(Issue.STATUS_OPEN)
+ .setSeverity(Severity.CRITICAL),
+ newDoc("openhotspot1", project).setOwaspTop10(asList("a1", "a3")).setCwe(singletonList(UNKNOWN_STANDARD)).setType(RuleType.SECURITY_HOTSPOT)
+ .setStatus(Issue.STATUS_OPEN),
+ newDoc("openhotspot2", project).setOwaspTop10(asList("a3", "a6")).setType(RuleType.SECURITY_HOTSPOT).setStatus(Issue.STATUS_REOPENED),
+ newDoc("toReviewHotspot", project).setOwaspTop10(asList("a5", "a3")).setCwe(asList("456")).setType(RuleType.SECURITY_HOTSPOT).setStatus(Issue.STATUS_RESOLVED)
+ .setResolution(Issue.RESOLUTION_FIXED),
+ newDoc("WFHotspot", project).setOwaspTop10(asList("a3", "a8")).setType(RuleType.SECURITY_HOTSPOT).setStatus(Issue.STATUS_RESOLVED)
+ .setResolution(Issue.RESOLUTION_WONT_FIX),
+ newDoc("notowasphotspot", project).setOwaspTop10(singletonList(UNKNOWN_STANDARD)).setType(RuleType.SECURITY_HOTSPOT).setStatus(Issue.STATUS_OPEN));
+
+ List<SecurityStandardCategoryStatistics> owaspTop10Report = underTest.getOwaspTop10Report(project.uuid(), includeCwe);
+ assertThat(owaspTop10Report)
+ .extracting(SecurityStandardCategoryStatistics::getCategory, SecurityStandardCategoryStatistics::getVulnerabilities,
+ SecurityStandardCategoryStatistics::getVulnerabiliyRating, SecurityStandardCategoryStatistics::getOpenSecurityHotspots,
+ SecurityStandardCategoryStatistics::getToReviewSecurityHotspots, SecurityStandardCategoryStatistics::getWontFixSecurityHotspots)
+ .containsExactlyInAnyOrder(
+ tuple("a1", 1L /* openvul1 */, OptionalInt.of(3)/* MAJOR = C */, 1L /* openhotspot1 */, 0L, 0L),
+ tuple("a2", 0L, OptionalInt.empty(), 0L, 0L, 0L),
+ tuple("a3", 2L /* openvul1,openvul2 */, OptionalInt.of(3)/* MAJOR = C */, 2L/* openhotspot1,openhotspot2 */, 1L /* toReviewHotspot */, 1L /* WFHotspot */),
+ tuple("a4", 0L, OptionalInt.empty(), 0L, 0L, 0L),
+ tuple("a5", 0L, OptionalInt.empty(), 0L, 1L/* toReviewHotspot */, 0L),
+ tuple("a6", 1L /* openvul2 */, OptionalInt.of(2) /* MINOR = B */, 1L /* openhotspot2 */, 0L, 0L),
+ tuple("a7", 0L, OptionalInt.empty(), 0L, 0L, 0L),
+ tuple("a8", 0L, OptionalInt.empty(), 0L, 0L, 1L /* WFHotspot */),
+ tuple("a9", 0L, OptionalInt.empty(), 0L, 0L, 0L),
+ tuple("a10", 0L, OptionalInt.empty(), 0L, 0L, 0L),
+ tuple("unknown", 1L /* notowaspvul */, OptionalInt.of(4) /* CRITICAL = D */, 1L /* notowasphotspot */, 0L, 0L));
+ return owaspTop10Report;
+ }
+
+ @Test
+ public void test_getSansTop25Report_aggregation() {
+ OrganizationDto org = newOrganizationDto();
+ ComponentDto project = newPrivateProjectDto(org);
+ indexIssues(
+ newDoc("openvul1", project).setSansTop25(asList(SANS_TOP_25_INSECURE_INTERACTION, SANS_TOP_25_RISKY_RESOURCE)).setType(RuleType.VULNERABILITY).setStatus(Issue.STATUS_OPEN)
+ .setSeverity(Severity.MAJOR),
+ newDoc("openvul2", project).setSansTop25(asList(SANS_TOP_25_RISKY_RESOURCE, SANS_TOP_25_POROUS_DEFENSES)).setType(RuleType.VULNERABILITY).setStatus(Issue.STATUS_REOPENED)
+ .setSeverity(Severity.MINOR),
+ newDoc("notopenvul", project).setSansTop25(asList(SANS_TOP_25_RISKY_RESOURCE, SANS_TOP_25_POROUS_DEFENSES)).setType(RuleType.VULNERABILITY).setStatus(Issue.STATUS_CLOSED)
+ .setResolution(Issue.RESOLUTION_FIXED)
+ .setSeverity(Severity.BLOCKER),
+ newDoc("notsansvul", project).setSansTop25(singletonList(UNKNOWN_STANDARD)).setType(RuleType.VULNERABILITY).setStatus(Issue.STATUS_OPEN)
+ .setSeverity(Severity.CRITICAL),
+ newDoc("openhotspot1", project).setSansTop25(asList(SANS_TOP_25_INSECURE_INTERACTION, SANS_TOP_25_RISKY_RESOURCE)).setType(RuleType.SECURITY_HOTSPOT)
+ .setStatus(Issue.STATUS_OPEN),
+ newDoc("openhotspot2", project).setSansTop25(asList(SANS_TOP_25_RISKY_RESOURCE, SANS_TOP_25_POROUS_DEFENSES)).setType(RuleType.SECURITY_HOTSPOT)
+ .setStatus(Issue.STATUS_REOPENED),
+ newDoc("toReviewHotspot", project).setSansTop25(asList(SANS_TOP_25_RISKY_RESOURCE)).setType(RuleType.SECURITY_HOTSPOT).setStatus(Issue.STATUS_RESOLVED)
+ .setResolution(Issue.RESOLUTION_FIXED),
+ newDoc("WFHotspot", project).setSansTop25(asList(SANS_TOP_25_RISKY_RESOURCE)).setType(RuleType.SECURITY_HOTSPOT).setStatus(Issue.STATUS_RESOLVED)
+ .setResolution(Issue.RESOLUTION_WONT_FIX),
+ newDoc("notowasphotspot", project).setSansTop25(singletonList(UNKNOWN_STANDARD)).setType(RuleType.SECURITY_HOTSPOT).setStatus(Issue.STATUS_OPEN));
+
+ List<SecurityStandardCategoryStatistics> sansTop25Report = underTest.getSansTop25Report(project.uuid(), false);
+ assertThat(sansTop25Report)
+ .extracting(SecurityStandardCategoryStatistics::getCategory, SecurityStandardCategoryStatistics::getVulnerabilities,
+ SecurityStandardCategoryStatistics::getVulnerabiliyRating, SecurityStandardCategoryStatistics::getOpenSecurityHotspots,
+ SecurityStandardCategoryStatistics::getToReviewSecurityHotspots, SecurityStandardCategoryStatistics::getWontFixSecurityHotspots)
+ .containsExactlyInAnyOrder(
+ tuple(SANS_TOP_25_INSECURE_INTERACTION, 1L /* openvul1 */, OptionalInt.of(3)/* MAJOR = C */, 1L /* openhotspot1 */, 0L, 0L),
+ tuple(SANS_TOP_25_RISKY_RESOURCE, 2L /* openvul1,openvul2 */, OptionalInt.of(3)/* MAJOR = C */, 2L/* openhotspot1,openhotspot2 */, 1L /* toReviewHotspot */,
+ 1L /* WFHotspot */),
+ tuple(SANS_TOP_25_POROUS_DEFENSES, 1L /* openvul2 */, OptionalInt.of(2)/* MINOR = B */, 1L/* openhotspot2 */, 0L, 0L));
+
+ assertThat(sansTop25Report).allMatch(category -> category.getChildren().isEmpty());
+ }
+
private void addIssues(ComponentDto component, int bugs, int vulnerabilities, int codeSmelles) {
List<IssueDoc> issues = new ArrayList<>();
IntStream.range(0, bugs).forEach(b -> issues.add(newDoc(component).setType(BUG).setResolution(null)));
@Test
public void verify_security_standards_indexation() {
- RuleDefinitionDto rule = db.rules().insert(r -> r.setSecurityStandards(new HashSet<>(Arrays.asList("cwe:123,owaspTop10:a3,cwe:863"))));
+ RuleDefinitionDto rule = db.rules().insert(r -> r.setSecurityStandards(new HashSet<>(Arrays.asList("cwe:123", "owaspTop10:a3", "cwe:863"))));
ComponentDto project = db.components().insertPrivateProject(organization);
ComponentDto dir = db.components().insertComponent(ComponentTesting.newDirectory(project, "src/main/java/foo"));
ComponentDto file = db.components().insertComponent(newFileDto(project, dir, "F1"));
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;
DoNotFixNotificationDispatcher.newMetadata(),
NewIssuesNotificationFactory.class,
+ // Security reports
+ SecurityReportsWsModule.class,
+
// issues actions
AssignAction.class,
SetTypeAction.class,
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 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();
+ }
+
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 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
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 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);
+ }
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 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.List;
+import java.util.function.Function;
+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.server.component.ComponentFinder;
+import org.sonar.server.issue.index.IssueIndex;
+import org.sonar.server.issue.index.SecurityStandardCategoryStatistics;
+import org.sonar.server.user.UserSession;
+import org.sonarqube.ws.SecurityReports;
+
+import static java.lang.Integer.parseInt;
+import static java.util.Comparator.comparing;
+import static java.util.stream.Collectors.toList;
+import static org.sonar.api.web.UserRole.USER;
+import static org.sonar.server.issue.IssueQuery.UNKNOWN_STANDARD;
+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;
+
+public class ShowAction implements SecurityReportsWsAction {
+
+ 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")
+ .setHandler(this)
+ .setDescription("Return data used by security reports")
+ .setSince("7.3")
+ .setInternal(true);
+
+ action.createParam(PARAM_PROJECT)
+ .setDescription("Project 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)
+ .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 standard = request.mandatoryParam(PARAM_STANDARD);
+ boolean includeCwe = request.mandatoryParamAsBoolean(PARAM_INCLUDE_DISTRIBUTION);
+ switch (standard) {
+ case PARAM_OWASP_TOP_10:
+ List<SecurityStandardCategoryStatistics> owaspCategories = issueIndex.getOwaspTop10Report(projectDto.uuid(), includeCwe)
+ .stream()
+ .sorted(comparing(ShowAction::index))
+ .collect(toList());
+ writeResponse(request, response, owaspCategories);
+ break;
+ case PARAM_SANS_TOP_25:
+ writeResponse(request, response, issueIndex.getSansTop25Report(projectDto.uuid(), includeCwe));
+ break;
+ default:
+ throw new IllegalArgumentException("Unsupported standard: '" + standard + "'");
+ }
+ }
+
+ 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) {
+ 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 (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());
+ 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);
+ };
+ }
+
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 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.Arrays;
+import java.util.HashSet;
+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.ComponentDto;
+import org.sonar.db.component.ComponentTesting;
+import org.sonar.db.issue.IssueDto;
+import org.sonar.db.permission.GroupPermissionDto;
+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.AuthorizationTypeSupport;
+import org.sonar.server.permission.index.PermissionIndexer;
+import org.sonar.server.tester.UserSessionRule;
+import org.sonar.server.ws.WsActionTester;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.junit.rules.ExpectedException.none;
+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 DbSession session = db.getSession();
+ private IssueIndex issueIndex = new IssueIndex(es.client(), System2.INSTANCE, userSessionRule, new AuthorizationTypeSupport(userSessionRule));
+ private IssueIndexer issueIndexer = new IssueIndexer(es.client(), dbClient, new IssueIteratorFactory(dbClient));
+ 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;
+
+ @Before
+ public void setUp() {
+ user = db.users().insertUser("john");
+ userSessionRule.logIn(user);
+ }
+
+ @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() {
+
+ ComponentDto project = insertComponent(ComponentTesting.newPrivateProjectDto(db.getDefaultOrganization(), "PROJECT_ID").setDbKey("PROJECT_KEY"));
+ userSessionRule.addProjectPermission(UserRole.USER, project);
+ indexPermissions();
+ ComponentDto file = insertComponent(newFileDto(project, null, "FILE_ID").setDbKey("FILE_KEY"));
+ RuleDefinitionDto rule = newRule();
+ IssueDto issue1 = newIssue(rule, project, file)
+ .setStatus("OPEN")
+ .setSeverity("MAJOR")
+ .setType(RuleType.CODE_SMELL);
+ IssueDto issue2 = newIssue(rule, project, file)
+ .setStatus("OPEN")
+ .setSeverity("MAJOR")
+ .setType(RuleType.BUG);
+ IssueDto issue3 = newIssue(rule, 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() {
+
+ ComponentDto project = insertComponent(ComponentTesting.newPrivateProjectDto(db.getDefaultOrganization(), "PROJECT_ID").setDbKey("PROJECT_KEY"));
+ userSessionRule.addProjectPermission(UserRole.USER, project);
+ indexPermissions();
+ ComponentDto file = insertComponent(newFileDto(project, null, "FILE_ID").setDbKey("FILE_KEY"));
+ RuleDefinitionDto rule = newRule();
+ IssueDto issue1 = newIssue(rule, project, file)
+ .setStatus("OPEN")
+ .setSeverity("MAJOR")
+ .setType(RuleType.VULNERABILITY);
+ IssueDto issue2 = newIssue(rule, project, file)
+ .setStatus("OPEN")
+ .setSeverity("MAJOR")
+ .setType(RuleType.SECURITY_HOTSPOT);
+ IssueDto issue3 = newIssue(rule, project, file)
+ .setStatus(Issue.STATUS_RESOLVED)
+ .setResolution(Issue.RESOLUTION_FIXED)
+ .setSeverity("MAJOR")
+ .setType(RuleType.SECURITY_HOTSPOT);
+ IssueDto issue4 = newIssue(rule, 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() {
+
+ ComponentDto project = insertComponent(ComponentTesting.newPrivateProjectDto(db.getDefaultOrganization(), "PROJECT_ID").setDbKey("PROJECT_KEY"));
+ userSessionRule.addProjectPermission(UserRole.USER, project);
+ indexPermissions();
+ ComponentDto file = insertComponent(newFileDto(project, null, "FILE_ID").setDbKey("FILE_KEY"));
+ RuleDefinitionDto rule = newRule();
+ IssueDto issue1 = newIssue(rule, project, file)
+ .setStatus("OPEN")
+ .setSeverity("MAJOR")
+ .setType(RuleType.VULNERABILITY);
+ IssueDto issue2 = newIssue(rule, project, file)
+ .setStatus("OPEN")
+ .setSeverity("MAJOR")
+ .setType(RuleType.SECURITY_HOTSPOT);
+ IssueDto issue3 = newIssue(rule, project, file)
+ .setStatus(Issue.STATUS_RESOLVED)
+ .setResolution(Issue.RESOLUTION_FIXED)
+ .setSeverity("MAJOR")
+ .setType(RuleType.SECURITY_HOTSPOT);
+ IssueDto issue4 = newIssue(rule, 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(this.getClass().getResource("ShowActionTest/owaspWithCwe.json"));
+ }
+
+ @Test
+ public void sans_with_cwe() {
+
+ ComponentDto project = insertComponent(ComponentTesting.newPrivateProjectDto(db.getDefaultOrganization(), "PROJECT_ID").setDbKey("PROJECT_KEY"));
+ userSessionRule.addProjectPermission(UserRole.USER, project);
+ indexPermissions();
+ ComponentDto file = insertComponent(newFileDto(project, null, "FILE_ID").setDbKey("FILE_KEY"));
+ RuleDefinitionDto rule = newRule();
+ IssueDto issue1 = newIssue(rule, project, file)
+ .setStatus("OPEN")
+ .setSeverity("MAJOR")
+ .setType(RuleType.VULNERABILITY);
+ IssueDto issue2 = newIssue(rule, project, file)
+ .setStatus("OPEN")
+ .setSeverity("MAJOR")
+ .setType(RuleType.SECURITY_HOTSPOT);
+ IssueDto issue3 = newIssue(rule, project, file)
+ .setStatus(Issue.STATUS_RESOLVED)
+ .setResolution(Issue.RESOLUTION_FIXED)
+ .setSeverity("MAJOR")
+ .setType(RuleType.SECURITY_HOTSPOT);
+ IssueDto issue4 = newIssue(rule, 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", "sansTop25")
+ .setParam("project", project.getKey())
+ .setParam("includeDistribution", "true")
+ .execute().getInput())
+ .withStrictArrayOrder()
+ .isSimilarTo(this.getClass().getResource("ShowActionTest/sansWithCwe.json"));
+ }
+
+
+ private RuleDefinitionDto newRule() {
+ RuleDefinitionDto rule = RuleTesting.newRule()
+ .setSecurityStandards(new HashSet<>(Arrays.asList("owaspTop10:a2", "cwe:123", "cwe:89")));
+ db.rules().insert(rule);
+ return rule;
+ }
+
+ private void indexPermissions() {
+ permissionIndexer.indexOnStartup(permissionIndexer.getIndexTypes());
+ }
+
+ private void indexIssues() {
+ issueIndexer.indexOnStartup(issueIndexer.getIndexTypes());
+ }
+
+ private void grantPermissionToAnyone(ComponentDto project, String permission) {
+ dbClient.groupPermissionDao().insert(session,
+ new GroupPermissionDto()
+ .setOrganizationUuid(project.getOrganizationUuid())
+ .setGroupId(null)
+ .setResourceId(project.getId())
+ .setRole(permission));
+ session.commit();
+ userSessionRule.logIn().addProjectPermission(permission, project);
+ }
+
+ private ComponentDto insertComponent(ComponentDto component) {
+ dbClient.componentDao().insert(session, component);
+ session.commit();
+ return component;
+ }
+}
--- /dev/null
+{
+ "categories": [
+ {
+ "category": "a1",
+ "vulnerabilities": 0,
+ "toReviewSecurityHotspots": 0,
+ "openSecurityHotspots": 0,
+ "wontFixSecurityHotspots": 0,
+ "distribution": []
+ },
+ {
+ "category": "a2",
+ "vulnerabilities": 0,
+ "toReviewSecurityHotspots": 0,
+ "openSecurityHotspots": 0,
+ "wontFixSecurityHotspots": 0,
+ "distribution": []
+ },
+ {
+ "category": "a3",
+ "vulnerabilities": 0,
+ "toReviewSecurityHotspots": 0,
+ "openSecurityHotspots": 0,
+ "wontFixSecurityHotspots": 0,
+ "distribution": []
+ },
+ {
+ "category": "a4",
+ "vulnerabilities": 0,
+ "toReviewSecurityHotspots": 0,
+ "openSecurityHotspots": 0,
+ "wontFixSecurityHotspots": 0,
+ "distribution": []
+ },
+ {
+ "category": "a5",
+ "vulnerabilities": 0,
+ "toReviewSecurityHotspots": 0,
+ "openSecurityHotspots": 0,
+ "wontFixSecurityHotspots": 0,
+ "distribution": []
+ },
+ {
+ "category": "a6",
+ "vulnerabilities": 0,
+ "toReviewSecurityHotspots": 0,
+ "openSecurityHotspots": 0,
+ "wontFixSecurityHotspots": 0,
+ "distribution": []
+ },
+ {
+ "category": "a7",
+ "vulnerabilities": 0,
+ "toReviewSecurityHotspots": 0,
+ "openSecurityHotspots": 0,
+ "wontFixSecurityHotspots": 0,
+ "distribution": []
+ },
+ {
+ "category": "a8",
+ "vulnerabilities": 0,
+ "toReviewSecurityHotspots": 0,
+ "openSecurityHotspots": 0,
+ "wontFixSecurityHotspots": 0,
+ "distribution": []
+ },
+ {
+ "category": "a9",
+ "vulnerabilities": 0,
+ "toReviewSecurityHotspots": 0,
+ "openSecurityHotspots": 0,
+ "wontFixSecurityHotspots": 0,
+ "distribution": []
+ },
+ {
+ "category": "a10",
+ "vulnerabilities": 0,
+ "toReviewSecurityHotspots": 0,
+ "openSecurityHotspots": 0,
+ "wontFixSecurityHotspots": 0,
+ "distribution": []
+ },
+ {
+ "category": "unknown",
+ "vulnerabilities": 0,
+ "toReviewSecurityHotspots": 0,
+ "openSecurityHotspots": 0,
+ "wontFixSecurityHotspots": 0,
+ "distribution": []
+ }
+ ]
+}
\ No newline at end of file
--- /dev/null
+{
+ "categories": [
+ {
+ "category": "a1",
+ "vulnerabilities": 0,
+ "toReviewSecurityHotspots": 0,
+ "openSecurityHotspots": 0,
+ "wontFixSecurityHotspots": 0,
+ "distribution": []
+ },
+ {
+ "category": "a2",
+ "vulnerabilities": 1,
+ "vulnerabilityRating": 3,
+ "toReviewSecurityHotspots": 1,
+ "openSecurityHotspots": 1,
+ "wontFixSecurityHotspots": 1,
+ "distribution": []
+ },
+ {
+ "category": "a3",
+ "vulnerabilities": 0,
+ "toReviewSecurityHotspots": 0,
+ "openSecurityHotspots": 0,
+ "wontFixSecurityHotspots": 0,
+ "distribution": []
+ },
+ {
+ "category": "a4",
+ "vulnerabilities": 0,
+ "toReviewSecurityHotspots": 0,
+ "openSecurityHotspots": 0,
+ "wontFixSecurityHotspots": 0,
+ "distribution": []
+ },
+ {
+ "category": "a5",
+ "vulnerabilities": 0,
+ "toReviewSecurityHotspots": 0,
+ "openSecurityHotspots": 0,
+ "wontFixSecurityHotspots": 0,
+ "distribution": []
+ },
+ {
+ "category": "a6",
+ "vulnerabilities": 0,
+ "toReviewSecurityHotspots": 0,
+ "openSecurityHotspots": 0,
+ "wontFixSecurityHotspots": 0,
+ "distribution": []
+ },
+ {
+ "category": "a7",
+ "vulnerabilities": 0,
+ "toReviewSecurityHotspots": 0,
+ "openSecurityHotspots": 0,
+ "wontFixSecurityHotspots": 0,
+ "distribution": []
+ },
+ {
+ "category": "a8",
+ "vulnerabilities": 0,
+ "toReviewSecurityHotspots": 0,
+ "openSecurityHotspots": 0,
+ "wontFixSecurityHotspots": 0,
+ "distribution": []
+ },
+ {
+ "category": "a9",
+ "vulnerabilities": 0,
+ "toReviewSecurityHotspots": 0,
+ "openSecurityHotspots": 0,
+ "wontFixSecurityHotspots": 0,
+ "distribution": []
+ },
+ {
+ "category": "a10",
+ "vulnerabilities": 0,
+ "toReviewSecurityHotspots": 0,
+ "openSecurityHotspots": 0,
+ "wontFixSecurityHotspots": 0,
+ "distribution": []
+ },
+ {
+ "category": "unknown",
+ "vulnerabilities": 0,
+ "toReviewSecurityHotspots": 0,
+ "openSecurityHotspots": 0,
+ "wontFixSecurityHotspots": 0,
+ "distribution": []
+ }
+ ]
+}
\ No newline at end of file
--- /dev/null
+{
+ "categories": [
+ {
+ "category": "a1",
+ "vulnerabilities": 0,
+ "toReviewSecurityHotspots": 0,
+ "openSecurityHotspots": 0,
+ "wontFixSecurityHotspots": 0,
+ "distribution": []
+ },
+ {
+ "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
+ },
+ {
+ "cwe": "123",
+ "vulnerabilities": 1,
+ "vulnerabilityRating": 3,
+ "toReviewSecurityHotspots": 1,
+ "openSecurityHotspots": 1,
+ "wontFixSecurityHotspots": 1
+ }
+ ]
+ },
+ {
+ "category": "a3",
+ "vulnerabilities": 0,
+ "toReviewSecurityHotspots": 0,
+ "openSecurityHotspots": 0,
+ "wontFixSecurityHotspots": 0,
+ "distribution": []
+ },
+ {
+ "category": "a4",
+ "vulnerabilities": 0,
+ "toReviewSecurityHotspots": 0,
+ "openSecurityHotspots": 0,
+ "wontFixSecurityHotspots": 0,
+ "distribution": []
+ },
+ {
+ "category": "a5",
+ "vulnerabilities": 0,
+ "toReviewSecurityHotspots": 0,
+ "openSecurityHotspots": 0,
+ "wontFixSecurityHotspots": 0,
+ "distribution": []
+ },
+ {
+ "category": "a6",
+ "vulnerabilities": 0,
+ "toReviewSecurityHotspots": 0,
+ "openSecurityHotspots": 0,
+ "wontFixSecurityHotspots": 0,
+ "distribution": []
+ },
+ {
+ "category": "a7",
+ "vulnerabilities": 0,
+ "toReviewSecurityHotspots": 0,
+ "openSecurityHotspots": 0,
+ "wontFixSecurityHotspots": 0,
+ "distribution": []
+ },
+ {
+ "category": "a8",
+ "vulnerabilities": 0,
+ "toReviewSecurityHotspots": 0,
+ "openSecurityHotspots": 0,
+ "wontFixSecurityHotspots": 0,
+ "distribution": []
+ },
+ {
+ "category": "a9",
+ "vulnerabilities": 0,
+ "toReviewSecurityHotspots": 0,
+ "openSecurityHotspots": 0,
+ "wontFixSecurityHotspots": 0,
+ "distribution": []
+ },
+ {
+ "category": "a10",
+ "vulnerabilities": 0,
+ "toReviewSecurityHotspots": 0,
+ "openSecurityHotspots": 0,
+ "wontFixSecurityHotspots": 0,
+ "distribution": []
+ },
+ {
+ "category": "unknown",
+ "vulnerabilities": 0,
+ "toReviewSecurityHotspots": 0,
+ "openSecurityHotspots": 0,
+ "wontFixSecurityHotspots": 0,
+ "distribution": []
+ }
+ ]
+}
\ No newline at end of file
--- /dev/null
+{
+ "categories": [
+ {
+ "category": "porous-defenses",
+ "vulnerabilities": 0,
+ "toReviewSecurityHotspots": 0,
+ "openSecurityHotspots": 0,
+ "wontFixSecurityHotspots": 0,
+ "distribution": []
+ },
+ {
+ "category": "risky-resource",
+ "vulnerabilities": 0,
+ "toReviewSecurityHotspots": 0,
+ "openSecurityHotspots": 0,
+ "wontFixSecurityHotspots": 0,
+ "distribution": []
+ },
+ {
+ "category": "insecure-interaction",
+ "vulnerabilities": 1,
+ "vulnerabilityRating": 3,
+ "toReviewSecurityHotspots": 1,
+ "openSecurityHotspots": 1,
+ "wontFixSecurityHotspots": 1,
+ "distribution": [
+ {
+ "cwe": "89",
+ "vulnerabilities": 1,
+ "vulnerabilityRating": 3,
+ "toReviewSecurityHotspots": 1,
+ "openSecurityHotspots": 1,
+ "wontFixSecurityHotspots": 1
+ },
+ {
+ "cwe": "123",
+ "vulnerabilities": 1,
+ "vulnerabilityRating": 3,
+ "toReviewSecurityHotspots": 1,
+ "openSecurityHotspots": 1,
+ "wontFixSecurityHotspots": 1
+ }
+ ]
+ }
+ ]
+}
\ No newline at end of file
--- /dev/null
+// SonarQube, open source software quality management tool.
+// Copyright (C) 2008-2016 SonarSource
+// mailto:contact AT sonarsource DOT com
+//
+// SonarQube 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.
+//
+// SonarQube 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.
+
+syntax = "proto2";
+
+package sonarqube.ws.issues;
+
+import "ws-commons.proto";
+
+option java_package = "org.sonarqube.ws";
+option java_outer_classname = "SecurityReports";
+option optimize_for = SPEED;
+
+// Response of GET api/security_reports/show
+message ShowWsResponse {
+ repeated SecurityStandardCategoryStatistics categories = 6;
+}
+
+message SecurityStandardCategoryStatistics {
+ optional string category = 1;
+ optional int64 vulnerabilities = 2;
+ optional int64 vulnerabilityRating = 3;
+ optional int64 toReviewSecurityHotspots = 4;
+ optional int64 openSecurityHotspots = 5;
+ optional int64 wontFixSecurityHotspots = 6;
+ repeated CweStatistics distribution = 7;
+}
+
+message CweStatistics {
+ optional string cwe = 1;
+ optional int64 vulnerabilities = 2;
+ optional int64 vulnerabilityRating = 3;
+ optional int64 toReviewSecurityHotspots = 4;
+ optional int64 openSecurityHotspots = 5;
+ optional int64 wontFixSecurityHotspots = 6;
+}
+
+