]> source.dussan.org Git - sonarqube.git/blob
e7c6c8dab8665cc474c173be43a77f8cf203faa3
[sonarqube.git] /
1 /*
2  * SonarQube
3  * Copyright (C) 2009-2021 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
27 import org.junit.Rule;
28 import org.junit.Test;
29 import org.junit.rules.ExpectedException;
30 import org.sonar.api.impl.utils.TestSystem2;
31 import org.sonar.api.issue.Issue;
32 import org.sonar.api.rule.Severity;
33 import org.sonar.api.rules.RuleType;
34 import org.sonar.api.utils.System2;
35 import org.sonar.db.DbTester;
36 import org.sonar.db.component.ComponentDto;
37 import org.sonar.server.es.EsTester;
38 import org.sonar.server.permission.index.IndexPermissions;
39 import org.sonar.server.permission.index.PermissionIndexerTester;
40 import org.sonar.server.permission.index.WebAuthorizationTypeSupport;
41 import org.sonar.server.tester.UserSessionRule;
42 import org.sonar.server.view.index.ViewDoc;
43 import org.sonar.server.view.index.ViewIndexer;
44
45 import static java.util.Arrays.asList;
46 import static java.util.Arrays.stream;
47 import static java.util.Collections.singletonList;
48 import static java.util.TimeZone.getTimeZone;
49 import static java.util.stream.Collectors.toList;
50 import static org.assertj.core.api.Assertions.assertThat;
51 import static org.assertj.core.api.Assertions.tuple;
52 import static org.junit.rules.ExpectedException.none;
53 import static org.sonar.api.resources.Qualifiers.PROJECT;
54 import static org.sonar.db.component.ComponentTesting.newPrivateProjectDto;
55 import static org.sonar.server.issue.IssueDocTesting.newDoc;
56 import static org.sonar.server.security.SecurityStandards.SANS_TOP_25_INSECURE_INTERACTION;
57 import static org.sonar.server.security.SecurityStandards.SANS_TOP_25_POROUS_DEFENSES;
58 import static org.sonar.server.security.SecurityStandards.SANS_TOP_25_RISKY_RESOURCE;
59 import static org.sonar.server.security.SecurityStandards.UNKNOWN_STANDARD;
60
61 public class IssueIndexSecurityReportsTest {
62
63   @Rule
64   public EsTester es = EsTester.create();
65   @Rule
66   public UserSessionRule userSessionRule = UserSessionRule.standalone();
67   @Rule
68   public ExpectedException expectedException = none();
69   private System2 system2 = new TestSystem2().setNow(1_500_000_000_000L).setDefaultTimeZone(getTimeZone("GMT-01:00"));
70   @Rule
71   public DbTester db = DbTester.create(system2);
72
73   private IssueIndexer issueIndexer = new IssueIndexer(es.client(), db.getDbClient(), new IssueIteratorFactory(db.getDbClient()), null);
74   private ViewIndexer viewIndexer = new ViewIndexer(db.getDbClient(), es.client());
75   private PermissionIndexerTester authorizationIndexer = new PermissionIndexerTester(es, issueIndexer);
76
77   private IssueIndex underTest = new IssueIndex(es.client(), system2, userSessionRule, new WebAuthorizationTypeSupport(userSessionRule));
78
79   @Test
80   public void getOwaspTop10Report_dont_count_vulnerabilities_from_other_projects() {
81     ComponentDto project = newPrivateProjectDto();
82     ComponentDto another = newPrivateProjectDto();
83     indexIssues(
84       newDoc("anotherProject", another).setOwaspTop10(singletonList("a1")).setType(RuleType.VULNERABILITY).setStatus(Issue.STATUS_OPEN).setSeverity(Severity.CRITICAL),
85       newDoc("openvul1", project).setOwaspTop10(singletonList("a1")).setType(RuleType.VULNERABILITY).setStatus(Issue.STATUS_OPEN).setSeverity(Severity.MAJOR));
86
87     List<SecurityStandardCategoryStatistics> owaspTop10Report = underTest.getOwaspTop10Report(project.uuid(), false, false);
88     assertThat(owaspTop10Report)
89       .extracting(SecurityStandardCategoryStatistics::getCategory, SecurityStandardCategoryStatistics::getVulnerabilities,
90         SecurityStandardCategoryStatistics::getVulnerabiliyRating)
91       .contains(
92         tuple("a1", 1L /* openvul1 */, OptionalInt.of(3)/* MAJOR = C */));
93
94   }
95
96   @Test
97   public void getOwaspTop10Report_dont_count_closed_vulnerabilities() {
98     ComponentDto project = newPrivateProjectDto();
99     indexIssues(
100       newDoc("openvul1", project).setOwaspTop10(asList("a1")).setType(RuleType.VULNERABILITY).setStatus(Issue.STATUS_OPEN).setSeverity(Severity.MAJOR),
101       newDoc("notopenvul", project).setOwaspTop10(asList("a1")).setType(RuleType.VULNERABILITY).setStatus(Issue.STATUS_CLOSED).setResolution(Issue.RESOLUTION_FIXED)
102         .setSeverity(Severity.BLOCKER));
103
104     List<SecurityStandardCategoryStatistics> owaspTop10Report = underTest.getOwaspTop10Report(project.uuid(), false, false);
105     assertThat(owaspTop10Report)
106       .extracting(SecurityStandardCategoryStatistics::getCategory, SecurityStandardCategoryStatistics::getVulnerabilities,
107         SecurityStandardCategoryStatistics::getVulnerabiliyRating)
108       .contains(
109         tuple("a1", 1L /* openvul1 */, OptionalInt.of(3)/* MAJOR = C */));
110   }
111
112   @Test
113   public void getOwaspTop10Report_dont_count_old_vulnerabilities() {
114     ComponentDto project = newPrivateProjectDto();
115     indexIssues(
116       // Previous vulnerabilities in projects that are not reanalyzed will have no owasp nor cwe attributes (not even 'unknown')
117       newDoc("openvulNotReindexed", project).setType(RuleType.VULNERABILITY).setStatus(Issue.STATUS_OPEN).setSeverity(Severity.MAJOR));
118
119     List<SecurityStandardCategoryStatistics> owaspTop10Report = underTest.getOwaspTop10Report(project.uuid(), false, false);
120     assertThat(owaspTop10Report)
121       .extracting(SecurityStandardCategoryStatistics::getVulnerabilities,
122         SecurityStandardCategoryStatistics::getVulnerabiliyRating)
123       .containsOnly(
124         tuple(0L, OptionalInt.empty()));
125   }
126
127   @Test
128   public void getOwaspTop10Report_dont_count_hotspots_from_other_projects() {
129     ComponentDto project = newPrivateProjectDto();
130     ComponentDto another = newPrivateProjectDto();
131     indexIssues(
132       newDoc("openhotspot1", project).setOwaspTop10(asList("a1")).setType(RuleType.SECURITY_HOTSPOT).setStatus(Issue.STATUS_TO_REVIEW),
133       newDoc("anotherProject", another).setOwaspTop10(asList("a1")).setType(RuleType.SECURITY_HOTSPOT).setStatus(Issue.STATUS_TO_REVIEW));
134
135     List<SecurityStandardCategoryStatistics> owaspTop10Report = underTest.getOwaspTop10Report(project.uuid(), false, false);
136     assertThat(owaspTop10Report)
137       .extracting(SecurityStandardCategoryStatistics::getCategory, SecurityStandardCategoryStatistics::getToReviewSecurityHotspots)
138       .contains(
139         tuple("a1", 1L /* openhotspot1 */));
140   }
141
142   @Test
143   public void getOwaspTop10Report_dont_count_closed_hotspots() {
144     ComponentDto project = newPrivateProjectDto();
145     indexIssues(
146       newDoc("openhotspot1", project).setOwaspTop10(asList("a1")).setType(RuleType.SECURITY_HOTSPOT).setStatus(Issue.STATUS_TO_REVIEW),
147       newDoc("closedHotspot", project).setOwaspTop10(asList("a1")).setType(RuleType.SECURITY_HOTSPOT).setStatus(Issue.STATUS_CLOSED)
148         .setResolution(Issue.RESOLUTION_FIXED));
149
150     List<SecurityStandardCategoryStatistics> owaspTop10Report = underTest.getOwaspTop10Report(project.uuid(), false, false);
151     assertThat(owaspTop10Report)
152       .extracting(SecurityStandardCategoryStatistics::getCategory, SecurityStandardCategoryStatistics::getToReviewSecurityHotspots)
153       .contains(
154         tuple("a1", 1L /* openhotspot1 */));
155   }
156
157   @Test
158   public void getOwaspTop10Report_aggregation_no_cwe() {
159     List<SecurityStandardCategoryStatistics> owaspTop10Report = indexIssuesAndAssertOwaspReport(false);
160
161     assertThat(owaspTop10Report)
162       .isNotEmpty()
163       .allMatch(category -> category.getChildren().isEmpty());
164   }
165
166   @Test
167   public void getOwaspTop10Report_aggregation_with_cwe() {
168     List<SecurityStandardCategoryStatistics> owaspTop10Report = indexIssuesAndAssertOwaspReport(true);
169
170     Map<String, List<SecurityStandardCategoryStatistics>> cweByOwasp = owaspTop10Report.stream()
171       .collect(Collectors.toMap(SecurityStandardCategoryStatistics::getCategory, SecurityStandardCategoryStatistics::getChildren));
172
173     assertThat(cweByOwasp.get("a1")).extracting(SecurityStandardCategoryStatistics::getCategory, SecurityStandardCategoryStatistics::getVulnerabilities,
174       SecurityStandardCategoryStatistics::getVulnerabiliyRating, SecurityStandardCategoryStatistics::getToReviewSecurityHotspots,
175       SecurityStandardCategoryStatistics::getReviewedSecurityHotspots, SecurityStandardCategoryStatistics::getSecurityReviewRating)
176       .containsExactlyInAnyOrder(
177         tuple("123", 1L /* openvul1 */, OptionalInt.of(3)/* MAJOR = C */, 0L, 0L, 1),
178         tuple("456", 1L /* openvul1 */, OptionalInt.of(3)/* MAJOR = C */, 0L, 0L, 1),
179         tuple("unknown", 0L, OptionalInt.empty(), 1L /* openhotspot1 */, 0L, 5));
180     assertThat(cweByOwasp.get("a3")).extracting(SecurityStandardCategoryStatistics::getCategory, SecurityStandardCategoryStatistics::getVulnerabilities,
181       SecurityStandardCategoryStatistics::getVulnerabiliyRating, SecurityStandardCategoryStatistics::getToReviewSecurityHotspots,
182       SecurityStandardCategoryStatistics::getReviewedSecurityHotspots, SecurityStandardCategoryStatistics::getSecurityReviewRating)
183       .containsExactlyInAnyOrder(
184         tuple("123", 2L /* openvul1, openvul2 */, OptionalInt.of(3)/* MAJOR = C */, 0L, 0L, 1),
185         tuple("456", 1L /* openvul1 */, OptionalInt.of(3)/* MAJOR = C */, 0L, 0L, 1),
186         tuple("unknown", 0L, OptionalInt.empty(), 1L /* openhotspot1 */, 0L, 5));
187   }
188
189   private List<SecurityStandardCategoryStatistics> indexIssuesAndAssertOwaspReport(boolean includeCwe) {
190     ComponentDto project = newPrivateProjectDto();
191     indexIssues(
192       newDoc("openvul1", project).setOwaspTop10(asList("a1", "a3")).setCwe(asList("123", "456")).setType(RuleType.VULNERABILITY).setStatus(Issue.STATUS_OPEN)
193         .setSeverity(Severity.MAJOR),
194       newDoc("openvul2", project).setOwaspTop10(asList("a3", "a6")).setCwe(asList("123")).setType(RuleType.VULNERABILITY).setStatus(Issue.STATUS_REOPENED)
195         .setSeverity(Severity.MINOR),
196       newDoc("notowaspvul", project).setOwaspTop10(singletonList(UNKNOWN_STANDARD)).setType(RuleType.VULNERABILITY).setStatus(Issue.STATUS_OPEN).setSeverity(Severity.CRITICAL),
197       newDoc("toreviewhotspot1", project).setOwaspTop10(asList("a1", "a3")).setCwe(singletonList(UNKNOWN_STANDARD)).setType(RuleType.SECURITY_HOTSPOT)
198         .setStatus(Issue.STATUS_TO_REVIEW),
199       newDoc("toreviewhotspot2", project).setOwaspTop10(asList("a3", "a6")).setType(RuleType.SECURITY_HOTSPOT).setStatus(Issue.STATUS_TO_REVIEW),
200       newDoc("reviewedHotspot", project).setOwaspTop10(asList("a3", "a8")).setType(RuleType.SECURITY_HOTSPOT).setStatus(Issue.STATUS_REVIEWED)
201         .setResolution(Issue.RESOLUTION_FIXED),
202       newDoc("notowasphotspot", project).setOwaspTop10(singletonList(UNKNOWN_STANDARD)).setType(RuleType.SECURITY_HOTSPOT).setStatus(Issue.STATUS_TO_REVIEW));
203
204     List<SecurityStandardCategoryStatistics> owaspTop10Report = underTest.getOwaspTop10Report(project.uuid(), false, includeCwe);
205     assertThat(owaspTop10Report)
206       .extracting(SecurityStandardCategoryStatistics::getCategory, SecurityStandardCategoryStatistics::getVulnerabilities,
207         SecurityStandardCategoryStatistics::getVulnerabiliyRating, SecurityStandardCategoryStatistics::getToReviewSecurityHotspots,
208         SecurityStandardCategoryStatistics::getReviewedSecurityHotspots, SecurityStandardCategoryStatistics::getSecurityReviewRating)
209       .containsExactlyInAnyOrder(
210         tuple("a1", 1L /* openvul1 */, OptionalInt.of(3)/* MAJOR = C */, 1L /* toreviewhotspot1 */, 0L, 5),
211         tuple("a2", 0L, OptionalInt.empty(), 0L, 0L, 1),
212         tuple("a3", 2L /* openvul1,openvul2 */, OptionalInt.of(3)/* MAJOR = C */, 2L/* toreviewhotspot1,toreviewhotspot2 */, 1L /* reviewedHotspot */, 4),
213         tuple("a4", 0L, OptionalInt.empty(), 0L, 0L, 1),
214         tuple("a5", 0L, OptionalInt.empty(), 0L, 0L, 1),
215         tuple("a6", 1L /* openvul2 */, OptionalInt.of(2) /* MINOR = B */, 1L /* toreviewhotspot2 */, 0L, 5),
216         tuple("a7", 0L, OptionalInt.empty(), 0L, 0L, 1),
217         tuple("a8", 0L, OptionalInt.empty(), 0L, 1L /* reviewedHotspot */, 1),
218         tuple("a9", 0L, OptionalInt.empty(), 0L, 0L, 1),
219         tuple("a10", 0L, OptionalInt.empty(), 0L, 0L, 1));
220     return owaspTop10Report;
221   }
222
223   @Test
224   public void getSansTop25Report_aggregation() {
225     ComponentDto project = newPrivateProjectDto();
226     indexIssues(
227       newDoc("openvul1", project).setSansTop25(asList(SANS_TOP_25_INSECURE_INTERACTION, SANS_TOP_25_RISKY_RESOURCE)).setType(RuleType.VULNERABILITY).setStatus(Issue.STATUS_OPEN)
228         .setSeverity(Severity.MAJOR),
229       newDoc("openvul2", project).setSansTop25(asList(SANS_TOP_25_RISKY_RESOURCE, SANS_TOP_25_POROUS_DEFENSES)).setType(RuleType.VULNERABILITY).setStatus(Issue.STATUS_REOPENED)
230         .setSeverity(Severity.MINOR),
231       newDoc("notopenvul", project).setSansTop25(asList(SANS_TOP_25_RISKY_RESOURCE, SANS_TOP_25_POROUS_DEFENSES)).setType(RuleType.VULNERABILITY).setStatus(Issue.STATUS_CLOSED)
232         .setResolution(Issue.RESOLUTION_FIXED)
233         .setSeverity(Severity.BLOCKER),
234       newDoc("notsansvul", project).setSansTop25(singletonList(UNKNOWN_STANDARD)).setType(RuleType.VULNERABILITY).setStatus(Issue.STATUS_OPEN).setSeverity(Severity.CRITICAL),
235       newDoc("toreviewhotspot1", project).setSansTop25(asList(SANS_TOP_25_INSECURE_INTERACTION, SANS_TOP_25_RISKY_RESOURCE)).setType(RuleType.SECURITY_HOTSPOT)
236         .setStatus(Issue.STATUS_TO_REVIEW),
237       newDoc("toreviewhotspot2", project).setSansTop25(asList(SANS_TOP_25_RISKY_RESOURCE, SANS_TOP_25_POROUS_DEFENSES)).setType(RuleType.SECURITY_HOTSPOT)
238         .setStatus(Issue.STATUS_TO_REVIEW),
239       newDoc("inReviewHotspot", project).setSansTop25(asList(SANS_TOP_25_RISKY_RESOURCE)).setType(RuleType.SECURITY_HOTSPOT).setStatus(Issue.STATUS_IN_REVIEW),
240       newDoc("reviewedHotspot", project).setSansTop25(asList(SANS_TOP_25_RISKY_RESOURCE)).setType(RuleType.SECURITY_HOTSPOT).setStatus(Issue.STATUS_REVIEWED)
241         .setResolution(Issue.RESOLUTION_FIXED),
242       newDoc("notowasphotspot", project).setSansTop25(singletonList(UNKNOWN_STANDARD)).setType(RuleType.SECURITY_HOTSPOT).setStatus(Issue.STATUS_TO_REVIEW));
243
244     List<SecurityStandardCategoryStatistics> sansTop25Report = underTest.getSansTop25Report(project.uuid(), false, false);
245     assertThat(sansTop25Report)
246       .extracting(SecurityStandardCategoryStatistics::getCategory, SecurityStandardCategoryStatistics::getVulnerabilities,
247         SecurityStandardCategoryStatistics::getVulnerabiliyRating, SecurityStandardCategoryStatistics::getToReviewSecurityHotspots,
248         SecurityStandardCategoryStatistics::getReviewedSecurityHotspots, SecurityStandardCategoryStatistics::getSecurityReviewRating)
249       .containsExactlyInAnyOrder(
250         tuple(SANS_TOP_25_INSECURE_INTERACTION, 1L /* openvul1 */, OptionalInt.of(3)/* MAJOR = C */, 1L /* toreviewhotspot1 */, 0L, 5),
251         tuple(SANS_TOP_25_RISKY_RESOURCE, 2L /* openvul1,openvul2 */, OptionalInt.of(3)/* MAJOR = C */, 2L/* toreviewhotspot1,toreviewhotspot2 */,
252           1L /* reviewedHotspot */, 4),
253         tuple(SANS_TOP_25_POROUS_DEFENSES, 1L /* openvul2 */, OptionalInt.of(2)/* MINOR = B */, 1L/* openhotspot2 */, 0L, 5));
254
255     assertThat(sansTop25Report).allMatch(category -> category.getChildren().isEmpty());
256   }
257
258   @Test
259   public void getSansTop25Report_aggregation_on_portfolio() {
260     ComponentDto portfolio1 = db.components().insertPrivateApplication();
261     ComponentDto portfolio2 = db.components().insertPrivateApplication();
262     ComponentDto project1 = db.components().insertPrivateProject();
263     ComponentDto project2 = db.components().insertPrivateProject();
264
265     indexIssues(
266       newDoc("openvul1", project1).setSansTop25(asList(SANS_TOP_25_INSECURE_INTERACTION, SANS_TOP_25_RISKY_RESOURCE)).setType(RuleType.VULNERABILITY).setStatus(Issue.STATUS_OPEN)
267         .setSeverity(Severity.MAJOR),
268       newDoc("openvul2", project2).setSansTop25(asList(SANS_TOP_25_RISKY_RESOURCE, SANS_TOP_25_POROUS_DEFENSES)).setType(RuleType.VULNERABILITY).setStatus(Issue.STATUS_REOPENED)
269         .setSeverity(Severity.MINOR),
270       newDoc("notopenvul", project1).setSansTop25(asList(SANS_TOP_25_RISKY_RESOURCE, SANS_TOP_25_POROUS_DEFENSES)).setType(RuleType.VULNERABILITY).setStatus(Issue.STATUS_CLOSED)
271         .setResolution(Issue.RESOLUTION_FIXED)
272         .setSeverity(Severity.BLOCKER),
273       newDoc("notsansvul", project2).setSansTop25(singletonList(UNKNOWN_STANDARD)).setType(RuleType.VULNERABILITY).setStatus(Issue.STATUS_OPEN).setSeverity(Severity.CRITICAL),
274       newDoc("toreviewhotspot1", project1).setSansTop25(asList(SANS_TOP_25_INSECURE_INTERACTION, SANS_TOP_25_RISKY_RESOURCE)).setType(RuleType.SECURITY_HOTSPOT)
275         .setStatus(Issue.STATUS_TO_REVIEW),
276       newDoc("toreviewhotspot2", project2).setSansTop25(asList(SANS_TOP_25_RISKY_RESOURCE, SANS_TOP_25_POROUS_DEFENSES)).setType(RuleType.SECURITY_HOTSPOT)
277         .setStatus(Issue.STATUS_TO_REVIEW),
278       newDoc("reviewedHotspot", project2).setSansTop25(asList(SANS_TOP_25_RISKY_RESOURCE)).setType(RuleType.SECURITY_HOTSPOT).setStatus(Issue.STATUS_REVIEWED)
279         .setResolution(Issue.RESOLUTION_FIXED),
280       newDoc("notowasphotspot", project1).setSansTop25(singletonList(UNKNOWN_STANDARD)).setType(RuleType.SECURITY_HOTSPOT).setStatus(Issue.STATUS_TO_REVIEW));
281
282     indexView(portfolio1.uuid(), singletonList(project1.uuid()));
283     indexView(portfolio2.uuid(), singletonList(project2.uuid()));
284
285     List<SecurityStandardCategoryStatistics> sansTop25Report = underTest.getSansTop25Report(portfolio1.uuid(), true, false);
286     assertThat(sansTop25Report)
287       .extracting(SecurityStandardCategoryStatistics::getCategory, SecurityStandardCategoryStatistics::getVulnerabilities,
288         SecurityStandardCategoryStatistics::getVulnerabiliyRating, SecurityStandardCategoryStatistics::getToReviewSecurityHotspots,
289         SecurityStandardCategoryStatistics::getReviewedSecurityHotspots, SecurityStandardCategoryStatistics::getSecurityReviewRating)
290       .containsExactlyInAnyOrder(
291         tuple(SANS_TOP_25_INSECURE_INTERACTION, 1L /* openvul1 */, OptionalInt.of(3)/* MAJOR = C */, 1L /* toreviewhotspot1 */, 0L, 5),
292         tuple(SANS_TOP_25_RISKY_RESOURCE, 1L /* openvul1 */, OptionalInt.of(3)/* MAJOR = C */, 1L/* toreviewhotspot1 */, 0L, 5),
293         tuple(SANS_TOP_25_POROUS_DEFENSES, 0L, OptionalInt.empty(), 0L, 0L, 1));
294
295     assertThat(sansTop25Report).allMatch(category -> category.getChildren().isEmpty());
296   }
297
298   @Test
299   public void getCWETop25Report_aggregation() {
300     ComponentDto project = newPrivateProjectDto();
301     indexIssues(
302       newDoc("openvul", project).setCwe(asList("119")).setType(RuleType.VULNERABILITY).setStatus(Issue.STATUS_OPEN)
303         .setSeverity(Severity.MAJOR),
304       newDoc("notopenvul", project).setCwe(asList("119")).setType(RuleType.VULNERABILITY).setStatus(Issue.STATUS_CLOSED)
305         .setResolution(Issue.RESOLUTION_FIXED)
306         .setSeverity(Severity.BLOCKER),
307       newDoc("toreviewhotspot", project).setCwe(asList("89")).setType(RuleType.SECURITY_HOTSPOT)
308         .setStatus(Issue.STATUS_TO_REVIEW),
309       newDoc("only2020", project).setCwe(asList("862")).setType(RuleType.VULNERABILITY).setStatus(Issue.STATUS_REOPENED)
310         .setSeverity(Severity.MINOR),
311       newDoc("unknown", project).setCwe(asList("999")).setType(RuleType.VULNERABILITY).setStatus(Issue.STATUS_REOPENED)
312         .setSeverity(Severity.MINOR));
313
314     List<SecurityStandardCategoryStatistics> cweTop25Reports = underTest.getCweTop25Reports(project.uuid(), false);
315
316     List<String> listOfYears = cweTop25Reports.stream()
317       .map(SecurityStandardCategoryStatistics::getCategory)
318       .collect(toList());
319
320     assertThat(listOfYears).contains("2019", "2020", "2021");
321
322     SecurityStandardCategoryStatistics cwe2019 = cweTop25Reports.stream()
323       .filter(s -> s.getCategory().equals("2019"))
324       .findAny().get();
325     assertThat(cwe2019.getChildren()).hasSize(25);
326     assertThat(findRuleInCweByYear(cwe2019, "119")).isNotNull()
327       .extracting(SecurityStandardCategoryStatistics::getVulnerabilities,
328         SecurityStandardCategoryStatistics::getToReviewSecurityHotspots,
329         SecurityStandardCategoryStatistics::getReviewedSecurityHotspots)
330       .containsExactlyInAnyOrder(1L, 0L, 0L);
331     assertThat(findRuleInCweByYear(cwe2019, "89")).isNotNull()
332       .extracting(SecurityStandardCategoryStatistics::getVulnerabilities,
333         SecurityStandardCategoryStatistics::getToReviewSecurityHotspots,
334         SecurityStandardCategoryStatistics::getReviewedSecurityHotspots)
335       .containsExactlyInAnyOrder(0L, 1L, 0L);
336     assertThat(findRuleInCweByYear(cwe2019, "862")).isNull();
337     assertThat(findRuleInCweByYear(cwe2019, "999")).isNull();
338
339     SecurityStandardCategoryStatistics cwe2020 = cweTop25Reports.stream()
340       .filter(s -> s.getCategory().equals("2020"))
341       .findAny().get();
342     assertThat(cwe2020.getChildren()).hasSize(25);
343     assertThat(findRuleInCweByYear(cwe2020, "119")).isNotNull()
344       .extracting(SecurityStandardCategoryStatistics::getVulnerabilities,
345         SecurityStandardCategoryStatistics::getToReviewSecurityHotspots,
346         SecurityStandardCategoryStatistics::getReviewedSecurityHotspots)
347       .containsExactlyInAnyOrder(1L, 0L, 0L);
348     assertThat(findRuleInCweByYear(cwe2020, "89")).isNotNull()
349       .extracting(SecurityStandardCategoryStatistics::getVulnerabilities,
350         SecurityStandardCategoryStatistics::getToReviewSecurityHotspots,
351         SecurityStandardCategoryStatistics::getReviewedSecurityHotspots)
352       .containsExactlyInAnyOrder(0L, 1L, 0L);
353     assertThat(findRuleInCweByYear(cwe2020, "862")).isNotNull()
354       .extracting(SecurityStandardCategoryStatistics::getVulnerabilities,
355         SecurityStandardCategoryStatistics::getToReviewSecurityHotspots,
356         SecurityStandardCategoryStatistics::getReviewedSecurityHotspots)
357       .containsExactlyInAnyOrder(1L, 0L, 0L);
358     assertThat(findRuleInCweByYear(cwe2020, "999")).isNull();
359
360     SecurityStandardCategoryStatistics cwe2021 = cweTop25Reports.stream()
361       .filter(s -> s.getCategory().equals("2021"))
362       .findAny().get();
363     assertThat(cwe2021.getChildren()).hasSize(25);
364     assertThat(findRuleInCweByYear(cwe2021, "119")).isNotNull()
365       .extracting(SecurityStandardCategoryStatistics::getVulnerabilities,
366         SecurityStandardCategoryStatistics::getToReviewSecurityHotspots,
367         SecurityStandardCategoryStatistics::getReviewedSecurityHotspots)
368       .containsExactlyInAnyOrder(1L, 0L, 0L);
369     assertThat(findRuleInCweByYear(cwe2021, "89")).isNotNull()
370       .extracting(SecurityStandardCategoryStatistics::getVulnerabilities,
371         SecurityStandardCategoryStatistics::getToReviewSecurityHotspots,
372         SecurityStandardCategoryStatistics::getReviewedSecurityHotspots)
373       .containsExactlyInAnyOrder(0L, 1L, 0L);
374     assertThat(findRuleInCweByYear(cwe2021, "862")).isNull();
375     assertThat(findRuleInCweByYear(cwe2021, "999")).isNull();
376   }
377
378   @Test
379   public void getCWETop25Report_aggregation_on_portfolio() {
380     ComponentDto application = db.components().insertPrivateApplication();
381     ComponentDto project1 = db.components().insertPrivateProject();
382     ComponentDto project2 = db.components().insertPrivateProject();
383
384     indexIssues(
385       newDoc("openvul1", project1).setCwe(asList("119")).setType(RuleType.VULNERABILITY).setStatus(Issue.STATUS_OPEN)
386         .setSeverity(Severity.MAJOR),
387       newDoc("openvul2", project2).setCwe(asList("119")).setType(RuleType.VULNERABILITY).setStatus(Issue.STATUS_REOPENED)
388         .setSeverity(Severity.MINOR),
389       newDoc("toreviewhotspot", project1).setCwe(asList("89")).setType(RuleType.SECURITY_HOTSPOT)
390         .setStatus(Issue.STATUS_TO_REVIEW),
391       newDoc("only2020", project2).setCwe(asList("862")).setType(RuleType.VULNERABILITY).setStatus(Issue.STATUS_REOPENED)
392         .setSeverity(Severity.MINOR),
393       newDoc("unknown", project2).setCwe(asList("999")).setType(RuleType.VULNERABILITY).setStatus(Issue.STATUS_REOPENED)
394         .setSeverity(Severity.MINOR));
395
396     indexView(application.uuid(), asList(project1.uuid(), project2.uuid()));
397
398     List<SecurityStandardCategoryStatistics> cweTop25Reports = underTest.getCweTop25Reports(application.uuid(), true);
399
400     List<String> listOfYears = cweTop25Reports.stream()
401       .map(SecurityStandardCategoryStatistics::getCategory)
402       .collect(toList());
403
404     assertThat(listOfYears).contains("2019", "2020", "2021");
405
406     SecurityStandardCategoryStatistics cwe2019 = cweTop25Reports.stream()
407       .filter(s -> s.getCategory().equals("2019"))
408       .findAny().get();
409     assertThat(cwe2019.getChildren()).hasSize(25);
410     assertThat(findRuleInCweByYear(cwe2019, "119")).isNotNull()
411       .extracting(SecurityStandardCategoryStatistics::getVulnerabilities,
412         SecurityStandardCategoryStatistics::getToReviewSecurityHotspots,
413         SecurityStandardCategoryStatistics::getReviewedSecurityHotspots)
414       .containsExactlyInAnyOrder(2L, 0L, 0L);
415     assertThat(findRuleInCweByYear(cwe2019, "89")).isNotNull()
416       .extracting(SecurityStandardCategoryStatistics::getVulnerabilities,
417         SecurityStandardCategoryStatistics::getToReviewSecurityHotspots,
418         SecurityStandardCategoryStatistics::getReviewedSecurityHotspots)
419       .containsExactlyInAnyOrder(0L, 1L, 0L);
420     assertThat(findRuleInCweByYear(cwe2019, "862")).isNull();
421     assertThat(findRuleInCweByYear(cwe2019, "999")).isNull();
422
423     SecurityStandardCategoryStatistics cwe2020 = cweTop25Reports.stream()
424       .filter(s -> s.getCategory().equals("2020"))
425       .findAny().get();
426     assertThat(cwe2020.getChildren()).hasSize(25);
427     assertThat(findRuleInCweByYear(cwe2020, "119")).isNotNull()
428       .extracting(SecurityStandardCategoryStatistics::getVulnerabilities,
429         SecurityStandardCategoryStatistics::getToReviewSecurityHotspots,
430         SecurityStandardCategoryStatistics::getReviewedSecurityHotspots)
431       .containsExactlyInAnyOrder(2L, 0L, 0L);
432     assertThat(findRuleInCweByYear(cwe2020, "89")).isNotNull()
433       .extracting(SecurityStandardCategoryStatistics::getVulnerabilities,
434         SecurityStandardCategoryStatistics::getToReviewSecurityHotspots,
435         SecurityStandardCategoryStatistics::getReviewedSecurityHotspots)
436       .containsExactlyInAnyOrder(0L, 1L, 0L);
437     assertThat(findRuleInCweByYear(cwe2020, "862")).isNotNull()
438       .extracting(SecurityStandardCategoryStatistics::getVulnerabilities,
439         SecurityStandardCategoryStatistics::getToReviewSecurityHotspots,
440         SecurityStandardCategoryStatistics::getReviewedSecurityHotspots)
441       .containsExactlyInAnyOrder(1L, 0L, 0L);
442     assertThat(findRuleInCweByYear(cwe2020, "999")).isNull();
443
444     SecurityStandardCategoryStatistics cwe2021 = cweTop25Reports.stream()
445       .filter(s -> s.getCategory().equals("2021"))
446       .findAny().get();
447     assertThat(cwe2021.getChildren()).hasSize(25);
448     assertThat(findRuleInCweByYear(cwe2021, "119")).isNotNull()
449       .extracting(SecurityStandardCategoryStatistics::getVulnerabilities,
450         SecurityStandardCategoryStatistics::getToReviewSecurityHotspots,
451         SecurityStandardCategoryStatistics::getReviewedSecurityHotspots)
452       .containsExactlyInAnyOrder(2L, 0L, 0L);
453     assertThat(findRuleInCweByYear(cwe2021, "89")).isNotNull()
454       .extracting(SecurityStandardCategoryStatistics::getVulnerabilities,
455         SecurityStandardCategoryStatistics::getToReviewSecurityHotspots,
456         SecurityStandardCategoryStatistics::getReviewedSecurityHotspots)
457       .containsExactlyInAnyOrder(0L, 1L, 0L);
458     assertThat(findRuleInCweByYear(cwe2021, "862")).isNull();
459     assertThat(findRuleInCweByYear(cwe2021, "999")).isNull();
460   }
461
462   private SecurityStandardCategoryStatistics findRuleInCweByYear(SecurityStandardCategoryStatistics statistics, String cweId) {
463     return statistics.getChildren().stream().filter(stat -> stat.getCategory().equals(cweId)).findAny().orElse(null);
464   }
465
466   private void indexIssues(IssueDoc... issues) {
467     issueIndexer.index(asList(issues).iterator());
468     authorizationIndexer.allow(stream(issues).map(issue -> new IndexPermissions(issue.projectUuid(), PROJECT).allowAnyone()).collect(toList()));
469   }
470
471   private void indexView(String viewUuid, List<String> projects) {
472     viewIndexer.index(new ViewDoc().setUuid(viewUuid).setProjects(projects));
473   }
474
475 }