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