]> source.dussan.org Git - sonarqube.git/blob
38b17ff5f3e650551badef237ab985f8e6ea87c6
[sonarqube.git] /
1 /*
2  * SonarQube
3  * Copyright (C) 2009-2024 SonarSource SA
4  * mailto:info AT sonarsource DOT com
5  *
6  * This program is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU Lesser General Public
8  * License as published by the Free Software Foundation; either
9  * version 3 of the License, or (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * Lesser General Public License for more details.
15  *
16  * You should have received a copy of the GNU Lesser General Public License
17  * along with this program; if not, write to the Free Software Foundation,
18  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
19  */
20 package org.sonar.server.issue.index;
21
22 import java.util.ArrayList;
23 import java.util.List;
24 import java.util.Map;
25 import java.util.OptionalInt;
26 import java.util.stream.Collectors;
27 import org.jetbrains.annotations.NotNull;
28 import org.junit.jupiter.api.Test;
29 import org.sonar.api.issue.Issue;
30 import org.sonar.api.rule.Severity;
31 import org.sonar.api.rules.RuleType;
32 import org.sonar.api.server.rule.RulesDefinition.StigVersion;
33 import org.sonar.db.component.ComponentDto;
34 import org.sonar.server.view.index.ViewDoc;
35
36 import static java.lang.Integer.parseInt;
37 import static java.util.Arrays.asList;
38 import static java.util.Collections.singletonList;
39 import static java.util.Comparator.comparing;
40 import static java.util.stream.Collectors.toList;
41 import static org.assertj.core.api.Assertions.assertThat;
42 import static org.assertj.core.api.Assertions.tuple;
43 import static org.sonar.api.server.rule.RulesDefinition.OwaspAsvsVersion;
44 import static org.sonar.api.server.rule.RulesDefinition.OwaspTop10Version.Y2017;
45 import static org.sonar.api.server.rule.RulesDefinition.OwaspTop10Version.Y2021;
46 import static org.sonar.api.server.rule.RulesDefinition.PciDssVersion;
47 import static org.sonar.db.component.ComponentTesting.newPrivateProjectDto;
48 import static org.sonar.server.issue.IssueDocTesting.newDocForProject;
49 import static org.sonar.server.security.SecurityStandards.StigSupportedRequirement.V222391;
50 import static org.sonar.server.security.SecurityStandards.StigSupportedRequirement.V222397;
51 import static org.sonar.server.security.SecurityStandards.UNKNOWN_STANDARD;
52
53 class IssueIndexSecurityReportsTest extends IssueIndexTestCommon {
54
55   @Test
56   void getOwaspTop10Report_dont_count_vulnerabilities_from_other_projects() {
57     ComponentDto project = newPrivateProjectDto();
58     ComponentDto another = newPrivateProjectDto();
59
60     IssueDoc openVulDoc = newDocForProject("openvul1", project).setOwaspTop10(singletonList("a1")).setType(RuleType.VULNERABILITY).setStatus(Issue.STATUS_OPEN).setSeverity(Severity.MAJOR);
61     openVulDoc.setOwaspTop10For2021(singletonList("a2")).setType(RuleType.VULNERABILITY).setStatus(Issue.STATUS_OPEN).setSeverity(Severity.MAJOR);
62
63     IssueDoc otherProjectDoc = newDocForProject("anotherProject", another).setOwaspTop10(singletonList("a1")).setType(RuleType.VULNERABILITY).setStatus(Issue.STATUS_OPEN).setSeverity(Severity.CRITICAL);
64     otherProjectDoc.setOwaspTop10For2021(singletonList("a2")).setType(RuleType.VULNERABILITY).setStatus(Issue.STATUS_OPEN).setSeverity(Severity.CRITICAL);
65
66     indexIssues(openVulDoc, otherProjectDoc);
67
68     List<SecurityStandardCategoryStatistics> owaspTop10Report = underTest.getOwaspTop10Report(project.uuid(), false, false, Y2017);
69     assertThat(owaspTop10Report)
70       .extracting(SecurityStandardCategoryStatistics::getCategory, SecurityStandardCategoryStatistics::getVulnerabilities,
71         SecurityStandardCategoryStatistics::getVulnerabilityRating)
72       .contains(
73         tuple("a1", 1L /* openvul1 */, OptionalInt.of(3)/* MAJOR = C */));
74
75     List<SecurityStandardCategoryStatistics> owaspTop10For2021Report = underTest.getOwaspTop10Report(project.uuid(), false, false, Y2021);
76     assertThat(owaspTop10For2021Report)
77       .extracting(SecurityStandardCategoryStatistics::getCategory, SecurityStandardCategoryStatistics::getVulnerabilities,
78         SecurityStandardCategoryStatistics::getVulnerabilityRating)
79       .contains(
80         tuple("a2", 1L /* openvul1 */, OptionalInt.of(3)/* MAJOR = C */));
81
82   }
83
84   @Test
85   void getOwaspTop10Report_dont_count_closed_vulnerabilities() {
86     ComponentDto project = newPrivateProjectDto();
87     indexIssues(
88       newDocForProject("openvul1", project).setOwaspTop10(List.of("a1")).setType(RuleType.VULNERABILITY).setStatus(Issue.STATUS_OPEN).setSeverity(Severity.MAJOR),
89       newDocForProject("openvul12021", project).setOwaspTop10For2021(List.of("a2")).setType(RuleType.VULNERABILITY).setStatus(Issue.STATUS_OPEN).setSeverity(Severity.MAJOR),
90       newDocForProject("notopenvul", project).setOwaspTop10(List.of("a1")).setType(RuleType.VULNERABILITY).setStatus(Issue.STATUS_CLOSED).setResolution(Issue.RESOLUTION_FIXED)
91         .setSeverity(Severity.BLOCKER),
92       newDocForProject("notopenvul2021", project).setOwaspTop10For2021(List.of("a2")).setType(RuleType.VULNERABILITY).setStatus(Issue.STATUS_CLOSED).setResolution(Issue.RESOLUTION_FIXED)
93         .setSeverity(Severity.BLOCKER));
94
95     List<SecurityStandardCategoryStatistics> owaspTop10Report = underTest.getOwaspTop10Report(project.uuid(), false, false, Y2017);
96     assertThat(owaspTop10Report)
97       .extracting(SecurityStandardCategoryStatistics::getCategory, SecurityStandardCategoryStatistics::getVulnerabilities,
98         SecurityStandardCategoryStatistics::getVulnerabilityRating)
99       .contains(
100         tuple("a1", 1L /* openvul1 */, OptionalInt.of(3)/* MAJOR = C */));
101
102     List<SecurityStandardCategoryStatistics> owaspTop10For2021Report = underTest.getOwaspTop10Report(project.uuid(), false, false, Y2021);
103     assertThat(owaspTop10For2021Report)
104       .extracting(SecurityStandardCategoryStatistics::getCategory, SecurityStandardCategoryStatistics::getVulnerabilities,
105         SecurityStandardCategoryStatistics::getVulnerabilityRating)
106       .contains(
107         tuple("a2", 1L /* openvul1 */, OptionalInt.of(3)/* MAJOR = C */));
108   }
109
110   @Test
111   void getOwaspTop10Report_dont_count_old_vulnerabilities() {
112     ComponentDto project = newPrivateProjectDto();
113     indexIssues(
114       // Previous vulnerabilities in projects that are not reanalyzed will have no owasp nor cwe attributes (not even 'unknown')
115       newDocForProject("openvulNotReindexed", project).setType(RuleType.VULNERABILITY).setStatus(Issue.STATUS_OPEN).setSeverity(Severity.MAJOR));
116
117     List<SecurityStandardCategoryStatistics> owaspTop10Report = underTest.getOwaspTop10Report(project.uuid(), false, false, Y2017);
118     assertThat(owaspTop10Report)
119       .extracting(SecurityStandardCategoryStatistics::getVulnerabilities,
120         SecurityStandardCategoryStatistics::getVulnerabilityRating)
121       .containsOnly(
122         tuple(0L, OptionalInt.empty()));
123
124     List<SecurityStandardCategoryStatistics> owaspTop10For2021Report = underTest.getOwaspTop10Report(project.uuid(), false, false, Y2021);
125     assertThat(owaspTop10For2021Report)
126       .extracting(SecurityStandardCategoryStatistics::getVulnerabilities,
127         SecurityStandardCategoryStatistics::getVulnerabilityRating)
128       .containsOnly(
129         tuple(0L, OptionalInt.empty()));
130   }
131
132   @Test
133   void getOwaspTop10Report_dont_count_hotspots_from_other_projects() {
134     ComponentDto project = newPrivateProjectDto();
135     ComponentDto another = newPrivateProjectDto();
136     indexIssues(
137       newDocForProject("openhotspot1", project).setOwaspTop10(List.of("a1")).setType(RuleType.SECURITY_HOTSPOT).setStatus(Issue.STATUS_TO_REVIEW),
138       newDocForProject("openhotspot2021", project).setOwaspTop10For2021(List.of("a2")).setType(RuleType.SECURITY_HOTSPOT).setStatus(Issue.STATUS_TO_REVIEW),
139       newDocForProject("anotherProject", another).setOwaspTop10(List.of("a1")).setType(RuleType.SECURITY_HOTSPOT).setStatus(Issue.STATUS_TO_REVIEW),
140       newDocForProject("anotherProject2021", another).setOwaspTop10For2021(List.of("a2")).setType(RuleType.SECURITY_HOTSPOT).setStatus(Issue.STATUS_TO_REVIEW));
141
142     List<SecurityStandardCategoryStatistics> owaspTop10Report = underTest.getOwaspTop10Report(project.uuid(), false, false, Y2017);
143     assertThat(owaspTop10Report)
144       .extracting(SecurityStandardCategoryStatistics::getCategory, SecurityStandardCategoryStatistics::getToReviewSecurityHotspots)
145       .contains(
146         tuple("a1", 1L /* openhotspot1 */));
147
148     List<SecurityStandardCategoryStatistics> owaspTop10For2021Report = underTest.getOwaspTop10Report(project.uuid(), false, false, Y2021);
149     assertThat(owaspTop10For2021Report)
150       .extracting(SecurityStandardCategoryStatistics::getCategory, SecurityStandardCategoryStatistics::getToReviewSecurityHotspots)
151       .contains(
152         tuple("a2", 1L /* openhotspot1 */));
153   }
154
155   @Test
156   void getOwaspTop10Report_dont_count_closed_hotspots() {
157     ComponentDto project = newPrivateProjectDto();
158     indexIssues(
159       newDocForProject("openhotspot1", project).setOwaspTop10(List.of("a1")).setType(RuleType.SECURITY_HOTSPOT).setStatus(Issue.STATUS_TO_REVIEW),
160       newDocForProject("openhotspot2021", project).setOwaspTop10For2021(List.of("a2")).setType(RuleType.SECURITY_HOTSPOT).setStatus(Issue.STATUS_TO_REVIEW),
161       newDocForProject("closedHotspot", project).setOwaspTop10(List.of("a1")).setType(RuleType.SECURITY_HOTSPOT).setStatus(Issue.STATUS_CLOSED)
162         .setResolution(Issue.RESOLUTION_FIXED),
163       newDocForProject("closedHotspot2021", project).setOwaspTop10For2021(List.of("a2")).setType(RuleType.SECURITY_HOTSPOT).setStatus(Issue.STATUS_CLOSED)
164         .setResolution(Issue.RESOLUTION_FIXED));
165
166     List<SecurityStandardCategoryStatistics> owaspTop10Report = underTest.getOwaspTop10Report(project.uuid(), false, false, Y2017);
167     assertThat(owaspTop10Report)
168       .extracting(SecurityStandardCategoryStatistics::getCategory, SecurityStandardCategoryStatistics::getToReviewSecurityHotspots)
169       .contains(
170         tuple("a1", 1L /* openhotspot1 */));
171
172     List<SecurityStandardCategoryStatistics> owaspTop10For2021Report = underTest.getOwaspTop10Report(project.uuid(), false, false, Y2021);
173     assertThat(owaspTop10For2021Report)
174       .extracting(SecurityStandardCategoryStatistics::getCategory, SecurityStandardCategoryStatistics::getToReviewSecurityHotspots)
175       .contains(
176         tuple("a2", 1L /* openhotspot1 */));
177   }
178
179   @Test
180   void getOwaspTop10Report_aggregation_no_cwe() {
181     List<SecurityStandardCategoryStatistics> owaspTop10Report = indexIssuesAndAssertOwaspReport(false);
182
183     assertThat(owaspTop10Report)
184       .isNotEmpty()
185       .allMatch(category -> category.getChildren().isEmpty());
186   }
187
188   @Test
189   void getPciDss32Report_aggregation() {
190     List<SecurityStandardCategoryStatistics> pciDss32Report = indexIssuesAndAssertPciDss32Report();
191
192     assertThat(pciDss32Report)
193       .isNotEmpty();
194
195     assertThat(pciDss32Report.get(0).getChildren()).hasSize(2);
196     assertThat(pciDss32Report.get(1).getChildren()).isEmpty();
197     assertThat(pciDss32Report.get(2).getChildren()).hasSize(4);
198     assertThat(pciDss32Report.get(3).getChildren()).isEmpty();
199     assertThat(pciDss32Report.get(4).getChildren()).isEmpty();
200     assertThat(pciDss32Report.get(5).getChildren()).hasSize(2);
201     assertThat(pciDss32Report.get(6).getChildren()).isEmpty();
202     assertThat(pciDss32Report.get(7).getChildren()).hasSize(1);
203     assertThat(pciDss32Report.get(8).getChildren()).isEmpty();
204     assertThat(pciDss32Report.get(9).getChildren()).hasSize(1);
205     assertThat(pciDss32Report.get(10).getChildren()).isEmpty();
206     assertThat(pciDss32Report.get(11).getChildren()).isEmpty();
207   }
208
209   @Test
210   void getOwaspAsvs40Report_aggregation() {
211     List<SecurityStandardCategoryStatistics> owaspAsvsReport = indexIssuesAndAssertOwaspAsvsReport();
212
213     assertThat(owaspAsvsReport)
214       .isNotEmpty();
215
216     assertThat(owaspAsvsReport.get(0).getChildren()).isEmpty();
217     assertThat(owaspAsvsReport.get(1).getChildren()).hasSize(2);
218     assertThat(owaspAsvsReport.get(2).getChildren()).hasSize(4);
219     assertThat(owaspAsvsReport.get(3).getChildren()).isEmpty();
220     assertThat(owaspAsvsReport.get(4).getChildren()).isEmpty();
221     assertThat(owaspAsvsReport.get(5).getChildren()).hasSize(2);
222     assertThat(owaspAsvsReport.get(6).getChildren()).hasSize(1);
223     assertThat(owaspAsvsReport.get(7).getChildren()).hasSize(1);
224     assertThat(owaspAsvsReport.get(8).getChildren()).isEmpty();
225     assertThat(owaspAsvsReport.get(9).getChildren()).hasSize(1);
226     assertThat(owaspAsvsReport.get(10).getChildren()).isEmpty();
227     assertThat(owaspAsvsReport.get(11).getChildren()).isEmpty();
228     assertThat(owaspAsvsReport.get(12).getChildren()).isEmpty();
229     assertThat(owaspAsvsReport.get(13).getChildren()).isEmpty();
230   }
231
232   @Test
233   void getOwaspAsvs40ReportGroupedByLevel_aggregation() {
234     List<SecurityStandardCategoryStatistics> owaspAsvsReportGroupedByLevel = indexIssuesAndAssertOwaspAsvsReportGroupedByLevel();
235
236     assertThat(owaspAsvsReportGroupedByLevel)
237       .isNotEmpty();
238
239     assertThat(owaspAsvsReportGroupedByLevel.get(0).getChildren()).hasSize(3);
240     assertThat(owaspAsvsReportGroupedByLevel.get(1).getChildren()).hasSize(7);
241     assertThat(owaspAsvsReportGroupedByLevel.get(2).getChildren()).hasSize(11);
242   }
243
244   @Test
245   void getOwaspTop10Report_aggregation_with_cwe() {
246     List<SecurityStandardCategoryStatistics> owaspTop10Report = indexIssuesAndAssertOwaspReport(true);
247
248     Map<String, List<SecurityStandardCategoryStatistics>> cweByOwasp = owaspTop10Report.stream()
249       .collect(Collectors.toMap(SecurityStandardCategoryStatistics::getCategory, SecurityStandardCategoryStatistics::getChildren));
250
251     assertThat(cweByOwasp.get("a1")).extracting(SecurityStandardCategoryStatistics::getCategory, SecurityStandardCategoryStatistics::getVulnerabilities,
252         SecurityStandardCategoryStatistics::getVulnerabilityRating, SecurityStandardCategoryStatistics::getToReviewSecurityHotspots,
253         SecurityStandardCategoryStatistics::getReviewedSecurityHotspots, SecurityStandardCategoryStatistics::getSecurityReviewRating)
254       .containsExactlyInAnyOrder(
255         tuple("123", 1L /* openvul1 */, OptionalInt.of(3)/* MAJOR = C */, 0L, 0L, 1),
256         tuple("456", 1L /* openvul1 */, OptionalInt.of(3)/* MAJOR = C */, 0L, 0L, 1),
257         tuple("unknown", 0L, OptionalInt.empty(), 1L /* openhotspot1 */, 0L, 5));
258     assertThat(cweByOwasp.get("a3")).extracting(SecurityStandardCategoryStatistics::getCategory, SecurityStandardCategoryStatistics::getVulnerabilities,
259         SecurityStandardCategoryStatistics::getVulnerabilityRating, SecurityStandardCategoryStatistics::getToReviewSecurityHotspots,
260         SecurityStandardCategoryStatistics::getReviewedSecurityHotspots, SecurityStandardCategoryStatistics::getSecurityReviewRating)
261       .containsExactlyInAnyOrder(
262         tuple("123", 2L /* openvul1, openvul2 */, OptionalInt.of(3)/* MAJOR = C */, 0L, 0L, 1),
263         tuple("456", 1L /* openvul1 */, OptionalInt.of(3)/* MAJOR = C */, 0L, 0L, 1),
264         tuple("unknown", 0L, OptionalInt.empty(), 1L /* openhotspot1 */, 0L, 5));
265   }
266
267   @Test
268   void getOwaspTop10For2021Report_aggregation_with_cwe() {
269     List<SecurityStandardCategoryStatistics> owaspTop10Report = indexIssuesAndAssertOwasp2021Report(true);
270
271     Map<String, List<SecurityStandardCategoryStatistics>> cweByOwasp = owaspTop10Report.stream()
272       .collect(Collectors.toMap(SecurityStandardCategoryStatistics::getCategory, SecurityStandardCategoryStatistics::getChildren));
273
274     assertThat(cweByOwasp.get("a1")).extracting(SecurityStandardCategoryStatistics::getCategory, SecurityStandardCategoryStatistics::getVulnerabilities,
275         SecurityStandardCategoryStatistics::getVulnerabilityRating, SecurityStandardCategoryStatistics::getToReviewSecurityHotspots,
276         SecurityStandardCategoryStatistics::getReviewedSecurityHotspots, SecurityStandardCategoryStatistics::getSecurityReviewRating)
277       .containsExactlyInAnyOrder(
278         tuple("123", 1L /* openvul1 */, OptionalInt.of(3)/* MAJOR = C */, 0L, 0L, 1),
279         tuple("456", 1L /* openvul1 */, OptionalInt.of(3)/* MAJOR = C */, 0L, 0L, 1),
280         tuple("unknown", 0L, OptionalInt.empty(), 1L /* openhotspot1 */, 0L, 5));
281     assertThat(cweByOwasp.get("a3")).extracting(SecurityStandardCategoryStatistics::getCategory, SecurityStandardCategoryStatistics::getVulnerabilities,
282         SecurityStandardCategoryStatistics::getVulnerabilityRating, SecurityStandardCategoryStatistics::getToReviewSecurityHotspots,
283         SecurityStandardCategoryStatistics::getReviewedSecurityHotspots, SecurityStandardCategoryStatistics::getSecurityReviewRating)
284       .containsExactlyInAnyOrder(
285         tuple("123", 2L /* openvul1, openvul2 */, OptionalInt.of(3)/* MAJOR = C */, 0L, 0L, 1),
286         tuple("456", 1L /* openvul1 */, OptionalInt.of(3)/* MAJOR = C */, 0L, 0L, 1),
287         tuple("unknown", 0L, OptionalInt.empty(), 1L /* openhotspot1 */, 0L, 5));
288   }
289
290   private List<SecurityStandardCategoryStatistics> indexIssuesAndAssertOwaspReport(boolean includeCwe) {
291     ComponentDto project = newPrivateProjectDto();
292     indexIssues(
293       newDocForProject("openvul1", project).setOwaspTop10(asList("a1", "a3")).setCwe(asList("123", "456")).setType(RuleType.VULNERABILITY).setStatus(Issue.STATUS_OPEN)
294         .setSeverity(Severity.MAJOR),
295       newDocForProject("openvul2", project).setOwaspTop10(asList("a3", "a6")).setCwe(List.of("123")).setType(RuleType.VULNERABILITY).setStatus(Issue.STATUS_REOPENED)
296         .setSeverity(Severity.MINOR),
297       newDocForProject("notowaspvul", project).setOwaspTop10(singletonList(UNKNOWN_STANDARD)).setType(RuleType.VULNERABILITY).setStatus(Issue.STATUS_OPEN).setSeverity(Severity.CRITICAL),
298       newDocForProject("toreviewhotspot1", project).setOwaspTop10(asList("a1", "a3")).setCwe(singletonList(UNKNOWN_STANDARD)).setType(RuleType.SECURITY_HOTSPOT)
299         .setStatus(Issue.STATUS_TO_REVIEW),
300       newDocForProject("toreviewhotspot2", project).setOwaspTop10(asList("a3", "a6")).setType(RuleType.SECURITY_HOTSPOT).setStatus(Issue.STATUS_TO_REVIEW),
301       newDocForProject("reviewedHotspot", project).setOwaspTop10(asList("a3", "a8")).setType(RuleType.SECURITY_HOTSPOT).setStatus(Issue.STATUS_REVIEWED)
302         .setResolution(Issue.RESOLUTION_FIXED),
303       newDocForProject("notowasphotspot", project).setOwaspTop10(singletonList(UNKNOWN_STANDARD)).setType(RuleType.SECURITY_HOTSPOT).setStatus(Issue.STATUS_TO_REVIEW));
304
305     List<SecurityStandardCategoryStatistics> owaspTop10Report = underTest.getOwaspTop10Report(project.uuid(), false, includeCwe, Y2017);
306     assertThat(owaspTop10Report)
307       .extracting(SecurityStandardCategoryStatistics::getCategory, SecurityStandardCategoryStatistics::getVulnerabilities,
308         SecurityStandardCategoryStatistics::getVulnerabilityRating, SecurityStandardCategoryStatistics::getToReviewSecurityHotspots,
309         SecurityStandardCategoryStatistics::getReviewedSecurityHotspots, SecurityStandardCategoryStatistics::getSecurityReviewRating)
310       .containsExactlyInAnyOrder(
311         tuple("a1", 1L /* openvul1 */, OptionalInt.of(3)/* MAJOR = C */, 1L /* toreviewhotspot1 */, 0L, 5),
312         tuple("a2", 0L, OptionalInt.empty(), 0L, 0L, 1),
313         tuple("a3", 2L /* openvul1,openvul2 */, OptionalInt.of(3)/* MAJOR = C */, 2L/* toreviewhotspot1,toreviewhotspot2 */, 1L /* reviewedHotspot */, 4),
314         tuple("a4", 0L, OptionalInt.empty(), 0L, 0L, 1),
315         tuple("a5", 0L, OptionalInt.empty(), 0L, 0L, 1),
316         tuple("a6", 1L /* openvul2 */, OptionalInt.of(2) /* MINOR = B */, 1L /* toreviewhotspot2 */, 0L, 5),
317         tuple("a7", 0L, OptionalInt.empty(), 0L, 0L, 1),
318         tuple("a8", 0L, OptionalInt.empty(), 0L, 1L /* reviewedHotspot */, 1),
319         tuple("a9", 0L, OptionalInt.empty(), 0L, 0L, 1),
320         tuple("a10", 0L, OptionalInt.empty(), 0L, 0L, 1));
321     return owaspTop10Report;
322   }
323
324   private List<SecurityStandardCategoryStatistics> indexIssuesAndAssertPciDss32Report() {
325     ComponentDto project = newPrivateProjectDto();
326     indexIssues(
327       newDocForProject("openvul1", project).setPciDss32(asList("1.2.0", "3.4.5")).setType(RuleType.VULNERABILITY).setStatus(Issue.STATUS_OPEN)
328         .setSeverity(Severity.MAJOR),
329       newDocForProject("openvul2", project).setPciDss32(asList("3.3.2", "6.5")).setType(RuleType.VULNERABILITY).setStatus(Issue.STATUS_REOPENED)
330         .setSeverity(Severity.MINOR),
331       newDocForProject("openvul3", project).setPciDss32(asList("10.1.2", "6.5")).setType(RuleType.VULNERABILITY).setStatus(Issue.STATUS_REOPENED)
332         .setSeverity(Severity.MINOR),
333       newDocForProject("notpcidssvul", project).setPciDss32(singletonList(UNKNOWN_STANDARD)).setType(RuleType.VULNERABILITY).setStatus(Issue.STATUS_OPEN).setSeverity(Severity.CRITICAL),
334       newDocForProject("toreviewhotspot1", project).setPciDss32(asList("1.3.0", "3.3.2")).setType(RuleType.SECURITY_HOTSPOT)
335         .setStatus(Issue.STATUS_TO_REVIEW),
336       newDocForProject("toreviewhotspot2", project).setPciDss32(asList("3.5.6", "6.4.5")).setType(RuleType.SECURITY_HOTSPOT).setStatus(Issue.STATUS_TO_REVIEW),
337       newDocForProject("reviewedHotspot", project).setPciDss32(asList("3.1.1", "8.6")).setType(RuleType.SECURITY_HOTSPOT).setStatus(Issue.STATUS_REVIEWED)
338         .setResolution(Issue.RESOLUTION_FIXED),
339       newDocForProject("notpcidsshotspot", project).setPciDss32(singletonList(UNKNOWN_STANDARD)).setType(RuleType.SECURITY_HOTSPOT).setStatus(Issue.STATUS_TO_REVIEW));
340
341     List<SecurityStandardCategoryStatistics> pciDssReport = underTest.getPciDssReport(project.uuid(), false, PciDssVersion.V3_2).stream()
342       .sorted(comparing(s -> parseInt(s.getCategory())))
343       .collect(toList());
344     assertThat(pciDssReport)
345       .extracting(SecurityStandardCategoryStatistics::getCategory, SecurityStandardCategoryStatistics::getVulnerabilities,
346         SecurityStandardCategoryStatistics::getVulnerabilityRating, SecurityStandardCategoryStatistics::getToReviewSecurityHotspots,
347         SecurityStandardCategoryStatistics::getReviewedSecurityHotspots, SecurityStandardCategoryStatistics::getSecurityReviewRating)
348       .containsExactlyInAnyOrder(
349         tuple("1", 1L /* openvul1 */, OptionalInt.of(3)/* MAJOR = C */, 1L /* toreviewhotspot1 */, 0L, 5),
350         tuple("2", 0L, OptionalInt.empty(), 0L, 0L, 1),
351         tuple("3", 2L /* openvul1,openvul2 */, OptionalInt.of(3)/* MAJOR = C */, 2L/* toreviewhotspot1,toreviewhotspot2 */, 1L /* reviewedHotspot */, 4),
352         tuple("4", 0L, OptionalInt.empty(), 0L, 0L, 1),
353         tuple("5", 0L, OptionalInt.empty(), 0L, 0L, 1),
354         tuple("6", 2L /* openvul2 */, OptionalInt.of(2) /* MINOR = B */, 1L /* toreviewhotspot2 */, 0L, 5),
355         tuple("7", 0L, OptionalInt.empty(), 0L, 0L, 1),
356         tuple("8", 0L, OptionalInt.empty(), 0L, 1L /* reviewedHotspot */, 1),
357         tuple("9", 0L, OptionalInt.empty(), 0L, 0L, 1),
358         tuple("10", 1L, OptionalInt.of(2), 0L, 0L, 1),
359         tuple("11", 0L, OptionalInt.empty(), 0L, 0L, 1),
360         tuple("12", 0L, OptionalInt.empty(), 0L, 0L, 1));
361
362     return pciDssReport;
363   }
364
365   private List<SecurityStandardCategoryStatistics> indexIssuesAndAssertOwaspAsvsReport() {
366     ComponentDto project = getProjectWithOwaspAsvsIssuesIndexed();
367
368     List<SecurityStandardCategoryStatistics> owaspAsvsReport = underTest.getOwaspAsvsReport(project.uuid(), false, OwaspAsvsVersion.V4_0, 3).stream()
369       .sorted(comparing(s -> parseInt(s.getCategory())))
370       .collect(toList());
371     assertThat(owaspAsvsReport)
372       .extracting(SecurityStandardCategoryStatistics::getCategory, SecurityStandardCategoryStatistics::getVulnerabilities,
373         SecurityStandardCategoryStatistics::getVulnerabilityRating, SecurityStandardCategoryStatistics::getToReviewSecurityHotspots,
374         SecurityStandardCategoryStatistics::getReviewedSecurityHotspots, SecurityStandardCategoryStatistics::getSecurityReviewRating)
375       .containsExactlyInAnyOrder(
376         tuple("1", 0L, OptionalInt.empty(), 0L, 0L, 1),
377         tuple("2", 1L /* openvul1 */, OptionalInt.of(3)/* MAJOR = C */, 1L /* toreviewhotspot1 */, 0L, 5),
378         tuple("3", 2L /* openvul1,openvul2 */, OptionalInt.of(3)/* MAJOR = C */, 2L/* toreviewhotspot1,toreviewhotspot2 */, 1L /* reviewedHotspot */, 4),
379         tuple("4", 0L, OptionalInt.empty(), 0L, 0L, 1),
380         tuple("5", 0L, OptionalInt.empty(), 0L, 0L, 1),
381         tuple("6", 2L /* openvul2 */, OptionalInt.of(2) /* MINOR = B */, 0L, 0L, 1),
382         tuple("7", 0L /* openvul2 */, OptionalInt.empty() /* MINOR = B */, 1L /* toreviewhotspot2 */, 0L, 5),
383         tuple("8", 0L, OptionalInt.empty(), 0L, 1L /* reviewedHotspot */, 1),
384         tuple("9", 0L, OptionalInt.empty(), 0L, 0L, 1),
385         tuple("10", 1L, OptionalInt.of(2), 0L, 0L, 1),
386         tuple("11", 0L, OptionalInt.empty(), 0L, 0L, 1),
387         tuple("12", 0L, OptionalInt.empty(), 0L, 0L, 1),
388         tuple("13", 0L, OptionalInt.empty(), 0L, 0L, 1),
389         tuple("14", 0L, OptionalInt.empty(), 0L, 0L, 1));
390
391     return owaspAsvsReport;
392   }
393
394   private List<SecurityStandardCategoryStatistics> indexIssuesAndAssertOwaspAsvsReportGroupedByLevel() {
395     ComponentDto project = getProjectWithOwaspAsvsIssuesIndexed();
396
397     List<SecurityStandardCategoryStatistics> owaspAsvsReportGroupedByLevel = new ArrayList<>();
398     owaspAsvsReportGroupedByLevel.addAll(underTest.getOwaspAsvsReportGroupedByLevel(project.uuid(), false, OwaspAsvsVersion.V4_0, 1).stream()
399       .sorted(comparing(s -> parseInt(s.getCategory())))
400       .toList());
401     owaspAsvsReportGroupedByLevel.addAll(underTest.getOwaspAsvsReportGroupedByLevel(project.uuid(), false, OwaspAsvsVersion.V4_0, 2).stream()
402       .sorted(comparing(s -> parseInt(s.getCategory())))
403       .toList());
404     owaspAsvsReportGroupedByLevel.addAll(underTest.getOwaspAsvsReportGroupedByLevel(project.uuid(), false, OwaspAsvsVersion.V4_0, 3).stream()
405       .sorted(comparing(s -> parseInt(s.getCategory())))
406       .toList());
407
408     assertThat(owaspAsvsReportGroupedByLevel)
409       .extracting(SecurityStandardCategoryStatistics::getCategory, SecurityStandardCategoryStatistics::getVulnerabilities,
410         SecurityStandardCategoryStatistics::getVulnerabilityRating, SecurityStandardCategoryStatistics::getToReviewSecurityHotspots,
411         SecurityStandardCategoryStatistics::getReviewedSecurityHotspots, SecurityStandardCategoryStatistics::getSecurityReviewRating)
412       .containsExactlyInAnyOrder(
413         tuple("l1", 1L /* openvul2 */, OptionalInt.of(2), 1L /* toreviewhotspot2 */, 0L, 5),
414         tuple("l2", 2L /* openvul1, openvul2 */, OptionalInt.of(3)/* MAJOR = C */, 2L /* toreviewhotspot1, toreviewhotspot2 */, 1L /* reviewedHotspot */, 4),
415         tuple("l3", 3L /* openvul1,openvul2,openvul3 */, OptionalInt.of(3)/* MAJOR = C */, 2L/* toreviewhotspot1,toreviewhotspot2 */, 1L /* reviewedHotspot */, 4));
416
417     return owaspAsvsReportGroupedByLevel;
418   }
419
420   @NotNull
421   private ComponentDto getProjectWithOwaspAsvsIssuesIndexed() {
422     ComponentDto project = newPrivateProjectDto();
423     indexIssues(
424       newDocForProject("openvul1", project).setOwaspAsvs40(asList("2.4.1", "3.2.4")).setType(RuleType.VULNERABILITY).setStatus(Issue.STATUS_OPEN)
425         .setSeverity(Severity.MAJOR),
426       newDocForProject("openvul2", project).setOwaspAsvs40(asList("3.4.5", "6.2.1")).setType(RuleType.VULNERABILITY).setStatus(Issue.STATUS_REOPENED)
427         .setSeverity(Severity.MINOR),
428       newDocForProject("openvul3", project).setOwaspAsvs40(asList("10.2.4", "6.2.8")).setType(RuleType.VULNERABILITY).setStatus(Issue.STATUS_REOPENED)
429         .setSeverity(Severity.MINOR),
430       newDocForProject("notowaspasvsvul", project).setOwaspAsvs40(singletonList(UNKNOWN_STANDARD)).setType(RuleType.VULNERABILITY).setStatus(Issue.STATUS_OPEN).setSeverity(Severity.CRITICAL),
431       newDocForProject("toreviewhotspot1", project).setOwaspAsvs40(asList("2.2.5", "3.2.4")).setType(RuleType.SECURITY_HOTSPOT)
432         .setStatus(Issue.STATUS_TO_REVIEW),
433       newDocForProject("toreviewhotspot2", project).setOwaspAsvs40(asList("3.6.1", "7.1.1")).setType(RuleType.SECURITY_HOTSPOT).setStatus(Issue.STATUS_TO_REVIEW),
434       newDocForProject("reviewedHotspot", project).setOwaspAsvs40(asList("3.3.3", "8.3.7")).setType(RuleType.SECURITY_HOTSPOT).setStatus(Issue.STATUS_REVIEWED)
435         .setResolution(Issue.RESOLUTION_FIXED),
436       newDocForProject("notowaspasvshotspot", project).setOwaspAsvs40(singletonList(UNKNOWN_STANDARD)).setType(RuleType.SECURITY_HOTSPOT).setStatus(Issue.STATUS_TO_REVIEW));
437     return project;
438   }
439
440   private List<SecurityStandardCategoryStatistics> indexIssuesAndAssertOwasp2021Report(boolean includeCwe) {
441     ComponentDto project = newPrivateProjectDto();
442     indexIssues(
443       newDocForProject("openvul1", project).setOwaspTop10For2021(asList("a1", "a3")).setCwe(asList("123", "456")).setType(RuleType.VULNERABILITY).setStatus(Issue.STATUS_OPEN)
444         .setSeverity(Severity.MAJOR),
445       newDocForProject("openvul2", project).setOwaspTop10For2021(asList("a3", "a6")).setCwe(List.of("123")).setType(RuleType.VULNERABILITY).setStatus(Issue.STATUS_REOPENED)
446         .setSeverity(Severity.MINOR),
447       newDocForProject("notowaspvul", project).setOwaspTop10For2021(singletonList(UNKNOWN_STANDARD)).setType(RuleType.VULNERABILITY).setStatus(Issue.STATUS_OPEN).setSeverity(Severity.CRITICAL),
448       newDocForProject("toreviewhotspot1", project).setOwaspTop10For2021(asList("a1", "a3")).setCwe(singletonList(UNKNOWN_STANDARD)).setType(RuleType.SECURITY_HOTSPOT)
449         .setStatus(Issue.STATUS_TO_REVIEW),
450       newDocForProject("toreviewhotspot2", project).setOwaspTop10For2021(asList("a3", "a6")).setType(RuleType.SECURITY_HOTSPOT).setStatus(Issue.STATUS_TO_REVIEW),
451       newDocForProject("reviewedHotspot", project).setOwaspTop10For2021(asList("a3", "a8")).setType(RuleType.SECURITY_HOTSPOT).setStatus(Issue.STATUS_REVIEWED)
452         .setResolution(Issue.RESOLUTION_FIXED),
453       newDocForProject("notowasphotspot", project).setOwaspTop10For2021(singletonList(UNKNOWN_STANDARD)).setType(RuleType.SECURITY_HOTSPOT).setStatus(Issue.STATUS_TO_REVIEW));
454
455     List<SecurityStandardCategoryStatistics> owaspTop10Report = underTest.getOwaspTop10Report(project.uuid(), false, includeCwe, Y2021);
456     assertThat(owaspTop10Report)
457       .extracting(SecurityStandardCategoryStatistics::getCategory, SecurityStandardCategoryStatistics::getVulnerabilities,
458         SecurityStandardCategoryStatistics::getVulnerabilityRating, SecurityStandardCategoryStatistics::getToReviewSecurityHotspots,
459         SecurityStandardCategoryStatistics::getReviewedSecurityHotspots, SecurityStandardCategoryStatistics::getSecurityReviewRating)
460       .containsExactlyInAnyOrder(
461         tuple("a1", 1L /* openvul1 */, OptionalInt.of(3)/* MAJOR = C */, 1L /* toreviewhotspot1 */, 0L, 5),
462         tuple("a2", 0L, OptionalInt.empty(), 0L, 0L, 1),
463         tuple("a3", 2L /* openvul1,openvul2 */, OptionalInt.of(3)/* MAJOR = C */, 2L/* toreviewhotspot1,toreviewhotspot2 */, 1L /* reviewedHotspot */, 4),
464         tuple("a4", 0L, OptionalInt.empty(), 0L, 0L, 1),
465         tuple("a5", 0L, OptionalInt.empty(), 0L, 0L, 1),
466         tuple("a6", 1L /* openvul2 */, OptionalInt.of(2) /* MINOR = B */, 1L /* toreviewhotspot2 */, 0L, 5),
467         tuple("a7", 0L, OptionalInt.empty(), 0L, 0L, 1),
468         tuple("a8", 0L, OptionalInt.empty(), 0L, 1L /* reviewedHotspot */, 1),
469         tuple("a9", 0L, OptionalInt.empty(), 0L, 0L, 1),
470         tuple("a10", 0L, OptionalInt.empty(), 0L, 0L, 1));
471     return owaspTop10Report;
472   }
473
474   @Test
475   void getPciDssReport_aggregation_on_portfolio() {
476     ComponentDto portfolio1 = db.components().insertPrivateApplication().getMainBranchComponent();
477     ComponentDto portfolio2 = db.components().insertPrivateApplication().getMainBranchComponent();
478     ComponentDto project1 = db.components().insertPrivateProject().getMainBranchComponent();
479     ComponentDto project2 = db.components().insertPrivateProject().getMainBranchComponent();
480
481     indexIssues(
482       newDocForProject("openvul1", project1).setPciDss32(asList("1.2.0", "3.4.5")).setType(RuleType.VULNERABILITY).setStatus(Issue.STATUS_OPEN)
483         .setSeverity(Severity.MAJOR),
484       newDocForProject("openvul2", project2).setPciDss32(asList("3.3.2", "6.5")).setType(RuleType.VULNERABILITY).setStatus(Issue.STATUS_REOPENED)
485         .setSeverity(Severity.MINOR),
486       newDocForProject("openvul3", project1).setPciDss32(asList("10.1.2", "6.5")).setType(RuleType.VULNERABILITY).setStatus(Issue.STATUS_REOPENED)
487         .setSeverity(Severity.MINOR),
488       newDocForProject("notpcidssvul", project1).setPciDss32(singletonList(UNKNOWN_STANDARD)).setType(RuleType.VULNERABILITY).setStatus(Issue.STATUS_OPEN).setSeverity(Severity.CRITICAL),
489       newDocForProject("toreviewhotspot1", project2).setPciDss32(asList("1.3.0", "3.3.2")).setType(RuleType.SECURITY_HOTSPOT)
490         .setStatus(Issue.STATUS_TO_REVIEW),
491       newDocForProject("toreviewhotspot2", project1).setPciDss32(asList("3.5.6", "6.4.5")).setType(RuleType.SECURITY_HOTSPOT).setStatus(Issue.STATUS_TO_REVIEW),
492       newDocForProject("reviewedHotspot", project2).setPciDss32(asList("3.1.1", "8.6")).setType(RuleType.SECURITY_HOTSPOT).setStatus(Issue.STATUS_REVIEWED)
493         .setResolution(Issue.RESOLUTION_FIXED),
494       newDocForProject("notpcidsshotspot", project1).setPciDss32(singletonList(UNKNOWN_STANDARD)).setType(RuleType.SECURITY_HOTSPOT).setStatus(Issue.STATUS_TO_REVIEW));
495
496     indexView(portfolio1.uuid(), singletonList(project1.uuid()));
497     indexView(portfolio2.uuid(), singletonList(project2.uuid()));
498
499     List<SecurityStandardCategoryStatistics> pciDssReport = underTest.getPciDssReport(portfolio1.uuid(), true, PciDssVersion.V3_2).stream()
500       .sorted(comparing(s -> parseInt(s.getCategory())))
501       .collect(toList());
502     assertThat(pciDssReport)
503       .extracting(SecurityStandardCategoryStatistics::getCategory, SecurityStandardCategoryStatistics::getVulnerabilities,
504         SecurityStandardCategoryStatistics::getVulnerabilityRating, SecurityStandardCategoryStatistics::getToReviewSecurityHotspots,
505         SecurityStandardCategoryStatistics::getReviewedSecurityHotspots, SecurityStandardCategoryStatistics::getSecurityReviewRating)
506       .containsExactlyInAnyOrder(
507         tuple("1", 1L /* openvul1 */, OptionalInt.of(3)/* MAJOR = C */, 0L, 0L, 1),
508         tuple("2", 0L, OptionalInt.empty(), 0L, 0L, 1),
509         tuple("3", 1L /* openvul1 */, OptionalInt.of(3)/* MAJOR = C */, 1L/* toreviewhotspot2 */, 0L, 5),
510         tuple("4", 0L, OptionalInt.empty(), 0L, 0L, 1),
511         tuple("5", 0L, OptionalInt.empty(), 0L, 0L, 1),
512         tuple("6", 1L /* openvul3 */, OptionalInt.of(2) /* MINOR = B */, 1L /* toreviewhotspot2 */, 0L, 5),
513         tuple("7", 0L, OptionalInt.empty(), 0L, 0L, 1),
514         tuple("8", 0L, OptionalInt.empty(), 0L, 0L /* reviewedHotspot */, 1),
515         tuple("9", 0L, OptionalInt.empty(), 0L, 0L, 1),
516         tuple("10", 1L /* openvul3 */, OptionalInt.of(2), 0L, 0L, 1),
517         tuple("11", 0L, OptionalInt.empty(), 0L, 0L, 1),
518         tuple("12", 0L, OptionalInt.empty(), 0L, 0L, 1));
519   }
520
521   @Test
522   void getOwaspAsvsReport_aggregation_on_portfolio() {
523     ComponentDto portfolio1 = db.components().insertPrivateApplication().getMainBranchComponent();
524     ComponentDto portfolio2 = db.components().insertPrivateApplication().getMainBranchComponent();
525     ComponentDto project1 = db.components().insertPrivateProject().getMainBranchComponent();
526     ComponentDto project2 = db.components().insertPrivateProject().getMainBranchComponent();
527
528     indexIssues(
529       newDocForProject("openvul1", project1).setOwaspAsvs40(asList("2.1.1", "3.4.5")).setType(RuleType.VULNERABILITY).setStatus(Issue.STATUS_OPEN)
530         .setSeverity(Severity.MAJOR),
531       newDocForProject("openvul2", project2).setOwaspAsvs40(asList("3.3.2", "6.2.1")).setType(RuleType.VULNERABILITY).setStatus(Issue.STATUS_REOPENED)
532         .setSeverity(Severity.MINOR),
533       newDocForProject("openvul3", project1).setOwaspAsvs40(asList("10.3.2", "6.2.1")).setType(RuleType.VULNERABILITY).setStatus(Issue.STATUS_REOPENED)
534         .setSeverity(Severity.MINOR),
535       newDocForProject("notowaspasvsvul", project1).setOwaspAsvs40(singletonList(UNKNOWN_STANDARD)).setType(RuleType.VULNERABILITY).setStatus(Issue.STATUS_OPEN).setSeverity(Severity.CRITICAL),
536       newDocForProject("toreviewhotspot1", project2).setOwaspAsvs40(asList("2.1.3", "3.3.2")).setType(RuleType.SECURITY_HOTSPOT)
537         .setStatus(Issue.STATUS_TO_REVIEW),
538       newDocForProject("toreviewhotspot2", project1).setOwaspAsvs40(asList("3.4.4", "6.2.1")).setType(RuleType.SECURITY_HOTSPOT).setStatus(Issue.STATUS_TO_REVIEW),
539       newDocForProject("reviewedHotspot", project2).setOwaspAsvs40(asList("3.1.1", "8.3.1")).setType(RuleType.SECURITY_HOTSPOT).setStatus(Issue.STATUS_REVIEWED)
540         .setResolution(Issue.RESOLUTION_FIXED),
541       newDocForProject("notowaspasvshotspot", project1).setOwaspAsvs40(singletonList(UNKNOWN_STANDARD)).setType(RuleType.SECURITY_HOTSPOT).setStatus(Issue.STATUS_TO_REVIEW));
542
543     indexView(portfolio1.uuid(), singletonList(project1.uuid()));
544     indexView(portfolio2.uuid(), singletonList(project2.uuid()));
545
546     List<SecurityStandardCategoryStatistics> owaspAsvsReport = underTest.getOwaspAsvsReport(portfolio1.uuid(), true, OwaspAsvsVersion.V4_0, 1).stream()
547       .sorted(comparing(s -> parseInt(s.getCategory())))
548       .collect(toList());
549     assertThat(owaspAsvsReport)
550       .extracting(SecurityStandardCategoryStatistics::getCategory, SecurityStandardCategoryStatistics::getVulnerabilities,
551         SecurityStandardCategoryStatistics::getVulnerabilityRating, SecurityStandardCategoryStatistics::getToReviewSecurityHotspots,
552         SecurityStandardCategoryStatistics::getReviewedSecurityHotspots, SecurityStandardCategoryStatistics::getSecurityReviewRating)
553       .containsExactlyInAnyOrder(
554         tuple("1", 0L, OptionalInt.empty(), 0L, 0L, 1),
555         tuple("2", 1L /* openvul1 */, OptionalInt.of(3)/* MAJOR = C */, 0L, 0L, 1),
556         tuple("3", 1L /* openvul1 */, OptionalInt.of(3)/* MAJOR = C */, 1L/* toreviewhotspot2 */, 0L, 5),
557         tuple("4", 0L, OptionalInt.empty(), 0L, 0L, 1),
558         tuple("5", 0L, OptionalInt.empty(), 0L, 0L, 1),
559         tuple("6", 1L /* openvul3 */, OptionalInt.of(2) /* MINOR = B */, 1L /* toreviewhotspot2 */, 0L, 5),
560         tuple("7", 0L, OptionalInt.empty(), 0L, 0L, 1),
561         tuple("8", 0L, OptionalInt.empty(), 0L, 0L /* reviewedHotspot */, 1),
562         tuple("9", 0L, OptionalInt.empty(), 0L, 0L, 1),
563         tuple("10", 1L /* openvul3 */, OptionalInt.of(2), 0L, 0L, 1),
564         tuple("11", 0L, OptionalInt.empty(), 0L, 0L, 1),
565         tuple("12", 0L, OptionalInt.empty(), 0L, 0L, 1),
566         tuple("13", 0L, OptionalInt.empty(), 0L, 0L, 1),
567         tuple("14", 0L, OptionalInt.empty(), 0L, 0L, 1));
568   }
569
570   @Test
571   void getCWETop25Report_aggregation() {
572     ComponentDto project = newPrivateProjectDto();
573     indexIssues(
574       newDocForProject("openvul", project).setCwe(List.of("119")).setType(RuleType.VULNERABILITY).setStatus(Issue.STATUS_OPEN)
575         .setSeverity(Severity.MAJOR),
576       newDocForProject("notopenvul", project).setCwe(List.of("119")).setType(RuleType.VULNERABILITY).setStatus(Issue.STATUS_CLOSED)
577         .setResolution(Issue.RESOLUTION_FIXED)
578         .setSeverity(Severity.BLOCKER),
579       newDocForProject("toreviewhotspot", project).setCwe(List.of("89")).setType(RuleType.SECURITY_HOTSPOT)
580         .setStatus(Issue.STATUS_TO_REVIEW),
581       newDocForProject("only2020", project).setCwe(List.of("862")).setType(RuleType.VULNERABILITY).setStatus(Issue.STATUS_REOPENED)
582         .setSeverity(Severity.MINOR),
583       newDocForProject("unknown", project).setCwe(List.of("999")).setType(RuleType.VULNERABILITY).setStatus(Issue.STATUS_REOPENED)
584         .setSeverity(Severity.MINOR));
585
586     List<SecurityStandardCategoryStatistics> cweTop25Reports = underTest.getCweTop25Reports(project.uuid(), false);
587
588     List<String> listOfYears = cweTop25Reports.stream()
589       .map(SecurityStandardCategoryStatistics::getCategory)
590       .collect(toList());
591
592     assertThat(listOfYears).contains("2021", "2022", "2023");
593
594     SecurityStandardCategoryStatistics cwe2021 = cweTop25Reports.stream()
595       .filter(s -> s.getCategory().equals("2021"))
596       .findAny().get();
597     assertThat(cwe2021.getChildren()).hasSize(25);
598     assertThat(findRuleInCweByYear(cwe2021, "119")).isNotNull()
599       .extracting(SecurityStandardCategoryStatistics::getVulnerabilities,
600         SecurityStandardCategoryStatistics::getToReviewSecurityHotspots,
601         SecurityStandardCategoryStatistics::getReviewedSecurityHotspots)
602       .containsExactlyInAnyOrder(1L, 0L, 0L);
603     assertThat(findRuleInCweByYear(cwe2021, "89")).isNotNull()
604       .extracting(SecurityStandardCategoryStatistics::getVulnerabilities,
605         SecurityStandardCategoryStatistics::getToReviewSecurityHotspots,
606         SecurityStandardCategoryStatistics::getReviewedSecurityHotspots)
607       .containsExactlyInAnyOrder(0L, 1L, 0L);
608     assertThat(findRuleInCweByYear(cwe2021, "295")).isNull();
609     assertThat(findRuleInCweByYear(cwe2021, "999")).isNull();
610
611     SecurityStandardCategoryStatistics cwe2022 = cweTop25Reports.stream()
612       .filter(s -> s.getCategory().equals("2022"))
613       .findAny().get();
614     assertThat(cwe2022.getChildren()).hasSize(25);
615     assertThat(findRuleInCweByYear(cwe2022, "119")).isNotNull()
616       .extracting(SecurityStandardCategoryStatistics::getVulnerabilities,
617         SecurityStandardCategoryStatistics::getToReviewSecurityHotspots,
618         SecurityStandardCategoryStatistics::getReviewedSecurityHotspots)
619       .containsExactlyInAnyOrder(1L, 0L, 0L);
620     assertThat(findRuleInCweByYear(cwe2022, "89")).isNotNull()
621       .extracting(SecurityStandardCategoryStatistics::getVulnerabilities,
622         SecurityStandardCategoryStatistics::getToReviewSecurityHotspots,
623         SecurityStandardCategoryStatistics::getReviewedSecurityHotspots)
624       .containsExactlyInAnyOrder(0L, 1L, 0L);
625     assertThat(findRuleInCweByYear(cwe2022, "950")).isNull();
626     assertThat(findRuleInCweByYear(cwe2022, "999")).isNull();
627
628     SecurityStandardCategoryStatistics cwe2023 = cweTop25Reports.stream()
629       .filter(s -> s.getCategory().equals("2023"))
630       .findAny().get();
631     assertThat(cwe2023.getChildren()).hasSize(25);
632     assertThat(findRuleInCweByYear(cwe2023, "119")).isNotNull()
633       .extracting(SecurityStandardCategoryStatistics::getVulnerabilities,
634         SecurityStandardCategoryStatistics::getToReviewSecurityHotspots,
635         SecurityStandardCategoryStatistics::getReviewedSecurityHotspots)
636       .containsExactlyInAnyOrder(1L, 0L, 0L);
637     assertThat(findRuleInCweByYear(cwe2023, "89")).isNotNull()
638       .extracting(SecurityStandardCategoryStatistics::getVulnerabilities,
639         SecurityStandardCategoryStatistics::getToReviewSecurityHotspots,
640         SecurityStandardCategoryStatistics::getReviewedSecurityHotspots)
641       .containsExactlyInAnyOrder(0L, 1L, 0L);
642     assertThat(findRuleInCweByYear(cwe2023, "862")).isNotNull()
643       .extracting(SecurityStandardCategoryStatistics::getVulnerabilities,
644         SecurityStandardCategoryStatistics::getToReviewSecurityHotspots,
645         SecurityStandardCategoryStatistics::getReviewedSecurityHotspots)
646       .containsExactlyInAnyOrder(1L, 0L, 0L);
647     assertThat(findRuleInCweByYear(cwe2023, "999")).isNull();
648   }
649
650   @Test
651   void getCWETop25Report_aggregation_on_portfolio() {
652     ComponentDto application = db.components().insertPrivateApplication().getMainBranchComponent();
653     ComponentDto project1 = db.components().insertPrivateProject().getMainBranchComponent();
654     ComponentDto project2 = db.components().insertPrivateProject().getMainBranchComponent();
655
656     indexIssues(
657       newDocForProject("openvul1", project1).setCwe(List.of("119")).setType(RuleType.VULNERABILITY).setStatus(Issue.STATUS_OPEN)
658         .setSeverity(Severity.MAJOR),
659       newDocForProject("openvul2", project2).setCwe(List.of("119")).setType(RuleType.VULNERABILITY).setStatus(Issue.STATUS_REOPENED)
660         .setSeverity(Severity.MINOR),
661       newDocForProject("toreviewhotspot", project1).setCwe(List.of("89")).setType(RuleType.SECURITY_HOTSPOT)
662         .setStatus(Issue.STATUS_TO_REVIEW),
663       newDocForProject("only2020", project2).setCwe(List.of("862")).setType(RuleType.VULNERABILITY).setStatus(Issue.STATUS_REOPENED)
664         .setSeverity(Severity.MINOR),
665       newDocForProject("unknown", project2).setCwe(List.of("999")).setType(RuleType.VULNERABILITY).setStatus(Issue.STATUS_REOPENED)
666         .setSeverity(Severity.MINOR));
667
668     indexView(application.uuid(), asList(project1.uuid(), project2.uuid()));
669
670     List<SecurityStandardCategoryStatistics> cweTop25Reports = underTest.getCweTop25Reports(application.uuid(), true);
671
672     List<String> listOfYears = cweTop25Reports.stream()
673       .map(SecurityStandardCategoryStatistics::getCategory)
674       .collect(toList());
675
676     assertThat(listOfYears).contains("2021", "2022", "2023");
677
678     SecurityStandardCategoryStatistics cwe2021 = cweTop25Reports.stream()
679       .filter(s -> s.getCategory().equals("2021"))
680       .findAny().get();
681     assertThat(cwe2021.getChildren()).hasSize(25);
682     assertThat(findRuleInCweByYear(cwe2021, "119")).isNotNull()
683       .extracting(SecurityStandardCategoryStatistics::getVulnerabilities,
684         SecurityStandardCategoryStatistics::getToReviewSecurityHotspots,
685         SecurityStandardCategoryStatistics::getReviewedSecurityHotspots)
686       .containsExactlyInAnyOrder(2L, 0L, 0L);
687     assertThat(findRuleInCweByYear(cwe2021, "89")).isNotNull()
688       .extracting(SecurityStandardCategoryStatistics::getVulnerabilities,
689         SecurityStandardCategoryStatistics::getToReviewSecurityHotspots,
690         SecurityStandardCategoryStatistics::getReviewedSecurityHotspots)
691       .containsExactlyInAnyOrder(0L, 1L, 0L);
692     assertThat(findRuleInCweByYear(cwe2021, "295")).isNull();
693     assertThat(findRuleInCweByYear(cwe2021, "999")).isNull();
694
695
696     SecurityStandardCategoryStatistics cwe2022 = cweTop25Reports.stream()
697       .filter(s -> s.getCategory().equals("2022"))
698       .findAny().get();
699     assertThat(cwe2022.getChildren()).hasSize(25);
700     assertThat(findRuleInCweByYear(cwe2022, "119")).isNotNull()
701       .extracting(SecurityStandardCategoryStatistics::getVulnerabilities,
702         SecurityStandardCategoryStatistics::getToReviewSecurityHotspots,
703         SecurityStandardCategoryStatistics::getReviewedSecurityHotspots)
704       .containsExactlyInAnyOrder(2L, 0L, 0L);
705     assertThat(findRuleInCweByYear(cwe2022, "89")).isNotNull()
706       .extracting(SecurityStandardCategoryStatistics::getVulnerabilities,
707         SecurityStandardCategoryStatistics::getToReviewSecurityHotspots,
708         SecurityStandardCategoryStatistics::getReviewedSecurityHotspots)
709       .containsExactlyInAnyOrder(0L, 1L, 0L);
710     assertThat(findRuleInCweByYear(cwe2022, "295")).isNull();
711     assertThat(findRuleInCweByYear(cwe2022, "999")).isNull();
712
713     SecurityStandardCategoryStatistics cwe2023 = cweTop25Reports.stream()
714       .filter(s -> s.getCategory().equals("2023"))
715       .findAny().get();
716     assertThat(cwe2023.getChildren()).hasSize(25);
717     assertThat(findRuleInCweByYear(cwe2023, "119")).isNotNull()
718       .extracting(SecurityStandardCategoryStatistics::getVulnerabilities,
719         SecurityStandardCategoryStatistics::getToReviewSecurityHotspots,
720         SecurityStandardCategoryStatistics::getReviewedSecurityHotspots)
721       .containsExactlyInAnyOrder(2L, 0L, 0L);
722     assertThat(findRuleInCweByYear(cwe2023, "89")).isNotNull()
723       .extracting(SecurityStandardCategoryStatistics::getVulnerabilities,
724         SecurityStandardCategoryStatistics::getToReviewSecurityHotspots,
725         SecurityStandardCategoryStatistics::getReviewedSecurityHotspots)
726       .containsExactlyInAnyOrder(0L, 1L, 0L);
727     assertThat(findRuleInCweByYear(cwe2023, "862")).isNotNull()
728       .extracting(SecurityStandardCategoryStatistics::getVulnerabilities,
729         SecurityStandardCategoryStatistics::getToReviewSecurityHotspots,
730         SecurityStandardCategoryStatistics::getReviewedSecurityHotspots)
731       .containsExactlyInAnyOrder(1L, 0L, 0L);
732     assertThat(findRuleInCweByYear(cwe2023, "999")).isNull();
733   }
734
735   @Test
736   void getStigAsdV5R3_whenRequestingReportOnApplication_ShouldAggregateBasedOnStigRequirement() {
737     ComponentDto application = db.components().insertPrivateApplication().getMainBranchComponent();
738     ComponentDto project1 = db.components().insertPrivateProject().getMainBranchComponent();
739     ComponentDto project2 = db.components().insertPrivateProject().getMainBranchComponent();
740
741     indexIssues(
742       newDocForProject("openvul1", project1).setStigAsdV5R3(List.of(V222391.getRequirement())).setType(RuleType.VULNERABILITY).setStatus(Issue.STATUS_OPEN)
743         .setSeverity(Severity.MAJOR),
744       newDocForProject("openvul2", project2).setStigAsdV5R3(List.of(V222391.getRequirement())).setType(RuleType.VULNERABILITY).setStatus(Issue.STATUS_REOPENED)
745         .setSeverity(Severity.MINOR),
746       newDocForProject("toreviewhotspot", project1).setStigAsdV5R3(List.of(V222397.getRequirement())).setType(RuleType.SECURITY_HOTSPOT)
747         .setStatus(Issue.STATUS_TO_REVIEW),
748
749       newDocForProject("unknown", project2).setStigAsdV5R3(List.of("V-999999")).setType(RuleType.VULNERABILITY).setStatus(Issue.STATUS_REOPENED)
750         .setSeverity(Severity.MINOR));
751
752     indexView(application.uuid(), asList(project1.uuid(), project2.uuid()));
753
754     Map<String, SecurityStandardCategoryStatistics> statisticsToMap = underTest.getStig(application.uuid(), true, StigVersion.ASD_V5R3)
755       .stream().collect(Collectors.toMap(SecurityStandardCategoryStatistics::getCategory, e -> e));
756
757     assertThat(statisticsToMap).hasSize(41)
758       .hasEntrySatisfying(V222391.getRequirement(), stat -> {
759         assertThat(stat.getVulnerabilities()).isEqualTo(2);
760         assertThat(stat.getToReviewSecurityHotspots()).isZero();
761         assertThat(stat.getReviewedSecurityHotspots()).isZero();
762       })
763       .hasEntrySatisfying(V222397.getRequirement(), stat -> {
764         assertThat(stat.getVulnerabilities()).isZero();
765         assertThat(stat.getToReviewSecurityHotspots()).isEqualTo(1);
766         assertThat(stat.getReviewedSecurityHotspots()).isZero();
767       });
768   }
769
770   @Test
771   void getStigAsdV5R3_whenRequestingReportOnProject_ShouldAggregateBasedOnStigRequirement() {
772     ComponentDto branch = newPrivateProjectDto();
773     indexIssues(
774       newDocForProject("openvul", branch).setStigAsdV5R3(List.of(V222391.getRequirement())).setType(RuleType.VULNERABILITY).setStatus(Issue.STATUS_OPEN)
775         .setSeverity(Severity.MAJOR),
776       newDocForProject("openvul2", branch).setStigAsdV5R3(List.of(V222391.getRequirement())).setType(RuleType.VULNERABILITY).setStatus(Issue.STATUS_OPEN)
777         .setSeverity(Severity.MAJOR),
778       newDocForProject("notopenvul", branch).setStigAsdV5R3(List.of(V222391.getRequirement())).setType(RuleType.VULNERABILITY).setStatus(Issue.STATUS_CLOSED)
779         .setResolution(Issue.RESOLUTION_FIXED)
780         .setSeverity(Severity.BLOCKER),
781       newDocForProject("toreviewhotspot", branch).setStigAsdV5R3(List.of(V222397.getRequirement())).setType(RuleType.SECURITY_HOTSPOT)
782         .setStatus(Issue.STATUS_TO_REVIEW),
783       newDocForProject("reviewedHostpot", branch).setStigAsdV5R3(List.of(V222397.getRequirement())).setType(RuleType.SECURITY_HOTSPOT)
784         .setStatus(Issue.STATUS_REVIEWED).setResolution(Issue.RESOLUTION_FIXED));
785
786     Map<String, SecurityStandardCategoryStatistics> statisticsToMap = underTest.getStig(branch.uuid(), false, StigVersion.ASD_V5R3)
787       .stream().collect(Collectors.toMap(SecurityStandardCategoryStatistics::getCategory, e -> e));
788
789     assertThat(statisticsToMap).hasSize(41)
790       .hasEntrySatisfying(V222391.getRequirement(), stat -> {
791         assertThat(stat.getVulnerabilities()).isEqualTo(2);
792         assertThat(stat.getToReviewSecurityHotspots()).isZero();
793         assertThat(stat.getReviewedSecurityHotspots()).isZero();
794         assertThat(stat.getVulnerabilityRating()).as("MAJOR = C").isEqualTo(OptionalInt.of(3));
795       })
796       .hasEntrySatisfying(V222397.getRequirement(), stat -> {
797         assertThat(stat.getVulnerabilities()).isZero();
798         assertThat(stat.getToReviewSecurityHotspots()).isEqualTo(1);
799         assertThat(stat.getReviewedSecurityHotspots()).isEqualTo(1);
800         assertThat(stat.getSecurityReviewRating()).as("50% of hotspots are reviewed, so rating is C").isEqualTo(3);
801       });
802   }
803
804   private SecurityStandardCategoryStatistics findRuleInCweByYear(SecurityStandardCategoryStatistics statistics, String cweId) {
805     return statistics.getChildren().stream().filter(stat -> stat.getCategory().equals(cweId)).findAny().orElse(null);
806   }
807
808   private void indexView(String viewUuid, List<String> projectBranchUuids) {
809     viewIndexer.index(new ViewDoc().setUuid(viewUuid).setProjectBranchUuids(projectBranchUuids));
810   }
811
812 }