]> source.dussan.org Git - sonarqube.git/blob
47915fff2cf056c74d8a7d88e650b990b08507f6
[sonarqube.git] /
1 /*
2  * SonarQube
3  * Copyright (C) 2009-2020 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.Rule;
27 import org.junit.Test;
28 import org.junit.rules.ExpectedException;
29 import org.sonar.api.impl.utils.TestSystem2;
30 import org.sonar.api.issue.Issue;
31 import org.sonar.api.rule.Severity;
32 import org.sonar.api.rules.RuleType;
33 import org.sonar.api.utils.System2;
34 import org.sonar.db.DbTester;
35 import org.sonar.db.component.ComponentDto;
36 import org.sonar.db.organization.OrganizationDto;
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.db.organization.OrganizationTesting.newOrganizationDto;
56 import static org.sonar.server.issue.IssueDocTesting.newDoc;
57 import static org.sonar.server.security.SecurityStandards.SANS_TOP_25_INSECURE_INTERACTION;
58 import static org.sonar.server.security.SecurityStandards.SANS_TOP_25_POROUS_DEFENSES;
59 import static org.sonar.server.security.SecurityStandards.SANS_TOP_25_RISKY_RESOURCE;
60 import static org.sonar.server.security.SecurityStandards.UNKNOWN_STANDARD;
61
62 public class IssueIndexSecurityReportsTest {
63
64   @Rule
65   public EsTester es = EsTester.create();
66   @Rule
67   public UserSessionRule userSessionRule = UserSessionRule.standalone();
68   @Rule
69   public ExpectedException expectedException = none();
70   private System2 system2 = new TestSystem2().setNow(1_500_000_000_000L).setDefaultTimeZone(getTimeZone("GMT-01:00"));
71   @Rule
72   public DbTester db = DbTester.create(system2);
73
74   private IssueIndexer issueIndexer = new IssueIndexer(es.client(), db.getDbClient(), new IssueIteratorFactory(db.getDbClient()));
75   private ViewIndexer viewIndexer = new ViewIndexer(db.getDbClient(), es.client());
76   private PermissionIndexerTester authorizationIndexer = new PermissionIndexerTester(es, issueIndexer);
77
78   private IssueIndex underTest = new IssueIndex(es.client(), system2, userSessionRule, new WebAuthorizationTypeSupport(userSessionRule));
79
80   @Test
81   public void getOwaspTop10Report_dont_count_vulnerabilities_from_other_projects() {
82     OrganizationDto org = newOrganizationDto();
83     ComponentDto project = newPrivateProjectDto(org);
84     ComponentDto another = newPrivateProjectDto(org);
85     indexIssues(
86       newDoc("anotherProject", another).setOwaspTop10(singletonList("a1")).setType(RuleType.VULNERABILITY).setStatus(Issue.STATUS_OPEN).setSeverity(Severity.CRITICAL),
87       newDoc("openvul1", project).setOwaspTop10(singletonList("a1")).setType(RuleType.VULNERABILITY).setStatus(Issue.STATUS_OPEN).setSeverity(Severity.MAJOR));
88
89     List<SecurityStandardCategoryStatistics> owaspTop10Report = underTest.getOwaspTop10Report(project.uuid(), false, false);
90     assertThat(owaspTop10Report)
91       .extracting(SecurityStandardCategoryStatistics::getCategory, SecurityStandardCategoryStatistics::getVulnerabilities,
92         SecurityStandardCategoryStatistics::getVulnerabiliyRating)
93       .contains(
94         tuple("a1", 1L /* openvul1 */, OptionalInt.of(3)/* MAJOR = C */));
95
96   }
97
98   @Test
99   public void getOwaspTop10Report_dont_count_closed_vulnerabilities() {
100     OrganizationDto org = newOrganizationDto();
101     ComponentDto project = newPrivateProjectDto(org);
102     indexIssues(
103       newDoc("openvul1", project).setOwaspTop10(asList("a1")).setType(RuleType.VULNERABILITY).setStatus(Issue.STATUS_OPEN).setSeverity(Severity.MAJOR),
104       newDoc("notopenvul", project).setOwaspTop10(asList("a1")).setType(RuleType.VULNERABILITY).setStatus(Issue.STATUS_CLOSED).setResolution(Issue.RESOLUTION_FIXED)
105         .setSeverity(Severity.BLOCKER));
106
107     List<SecurityStandardCategoryStatistics> owaspTop10Report = underTest.getOwaspTop10Report(project.uuid(), false, false);
108     assertThat(owaspTop10Report)
109       .extracting(SecurityStandardCategoryStatistics::getCategory, SecurityStandardCategoryStatistics::getVulnerabilities,
110         SecurityStandardCategoryStatistics::getVulnerabiliyRating)
111       .contains(
112         tuple("a1", 1L /* openvul1 */, OptionalInt.of(3)/* MAJOR = C */));
113   }
114
115   @Test
116   public void getOwaspTop10Report_dont_count_old_vulnerabilities() {
117     OrganizationDto org = newOrganizationDto();
118     ComponentDto project = newPrivateProjectDto(org);
119     indexIssues(
120       // Previous vulnerabilities in projects that are not reanalyzed will have no owasp nor cwe attributes (not even 'unknown')
121       newDoc("openvulNotReindexed", project).setType(RuleType.VULNERABILITY).setStatus(Issue.STATUS_OPEN).setSeverity(Severity.MAJOR));
122
123     List<SecurityStandardCategoryStatistics> owaspTop10Report = underTest.getOwaspTop10Report(project.uuid(), false, false);
124     assertThat(owaspTop10Report)
125       .extracting(SecurityStandardCategoryStatistics::getVulnerabilities,
126         SecurityStandardCategoryStatistics::getVulnerabiliyRating)
127       .containsOnly(
128         tuple(0L, OptionalInt.empty()));
129   }
130
131   @Test
132   public void getOwaspTop10Report_dont_count_hotspots_from_other_projects() {
133     OrganizationDto org = newOrganizationDto();
134     ComponentDto project = newPrivateProjectDto(org);
135     ComponentDto another = newPrivateProjectDto(org);
136     indexIssues(
137       newDoc("openhotspot1", project).setOwaspTop10(asList("a1")).setType(RuleType.SECURITY_HOTSPOT).setStatus(Issue.STATUS_TO_REVIEW),
138       newDoc("anotherProject", another).setOwaspTop10(asList("a1")).setType(RuleType.SECURITY_HOTSPOT).setStatus(Issue.STATUS_TO_REVIEW));
139
140     List<SecurityStandardCategoryStatistics> owaspTop10Report = underTest.getOwaspTop10Report(project.uuid(), false, false);
141     assertThat(owaspTop10Report)
142       .extracting(SecurityStandardCategoryStatistics::getCategory, SecurityStandardCategoryStatistics::getToReviewSecurityHotspots)
143       .contains(
144         tuple("a1", 1L /* openhotspot1 */));
145   }
146
147   @Test
148   public void getOwaspTop10Report_dont_count_closed_hotspots() {
149     OrganizationDto org = newOrganizationDto();
150     ComponentDto project = newPrivateProjectDto(org);
151     indexIssues(
152       newDoc("openhotspot1", project).setOwaspTop10(asList("a1")).setType(RuleType.SECURITY_HOTSPOT).setStatus(Issue.STATUS_TO_REVIEW),
153       newDoc("closedHotspot", project).setOwaspTop10(asList("a1")).setType(RuleType.SECURITY_HOTSPOT).setStatus(Issue.STATUS_CLOSED)
154         .setResolution(Issue.RESOLUTION_FIXED));
155
156     List<SecurityStandardCategoryStatistics> owaspTop10Report = underTest.getOwaspTop10Report(project.uuid(), false, false);
157     assertThat(owaspTop10Report)
158       .extracting(SecurityStandardCategoryStatistics::getCategory, SecurityStandardCategoryStatistics::getToReviewSecurityHotspots)
159       .contains(
160         tuple("a1", 1L /* openhotspot1 */));
161   }
162
163   @Test
164   public void getOwaspTop10Report_aggregation_no_cwe() {
165     List<SecurityStandardCategoryStatistics> owaspTop10Report = indexIssuesAndAssertOwaspReport(false);
166
167     assertThat(owaspTop10Report).allMatch(category -> category.getChildren().isEmpty());
168   }
169
170   @Test
171   public void getOwaspTop10Report_aggregation_with_cwe() {
172     List<SecurityStandardCategoryStatistics> owaspTop10Report = indexIssuesAndAssertOwaspReport(true);
173
174     Map<String, List<SecurityStandardCategoryStatistics>> cweByOwasp = owaspTop10Report.stream()
175       .collect(Collectors.toMap(SecurityStandardCategoryStatistics::getCategory, SecurityStandardCategoryStatistics::getChildren));
176
177     assertThat(cweByOwasp.get("a1")).extracting(SecurityStandardCategoryStatistics::getCategory, SecurityStandardCategoryStatistics::getVulnerabilities,
178       SecurityStandardCategoryStatistics::getVulnerabiliyRating, SecurityStandardCategoryStatistics::getToReviewSecurityHotspots,
179       SecurityStandardCategoryStatistics::getReviewedSecurityHotspots)
180       .containsExactlyInAnyOrder(
181         tuple("123", 1L /* openvul1 */, OptionalInt.of(3)/* MAJOR = C */, 0L, 0L),
182         tuple("456", 1L /* openvul1 */, OptionalInt.of(3)/* MAJOR = C */, 0L, 0L),
183         tuple("unknown", 0L, OptionalInt.empty(), 1L /* openhotspot1 */, 0L));
184     assertThat(cweByOwasp.get("a3")).extracting(SecurityStandardCategoryStatistics::getCategory, SecurityStandardCategoryStatistics::getVulnerabilities,
185       SecurityStandardCategoryStatistics::getVulnerabiliyRating, SecurityStandardCategoryStatistics::getToReviewSecurityHotspots,
186       SecurityStandardCategoryStatistics::getReviewedSecurityHotspots)
187       .containsExactlyInAnyOrder(
188         tuple("123", 2L /* openvul1, openvul2 */, OptionalInt.of(3)/* MAJOR = C */, 0L, 0L),
189         tuple("456", 1L /* openvul1 */, OptionalInt.of(3)/* MAJOR = C */, 0L/* toReviewHotspot */, 0L),
190         tuple("unknown", 0L, OptionalInt.empty(), 1L /* openhotspot1 */, 0L));
191   }
192
193   private List<SecurityStandardCategoryStatistics> indexIssuesAndAssertOwaspReport(boolean includeCwe) {
194     OrganizationDto org = newOrganizationDto();
195     ComponentDto project = newPrivateProjectDto(org);
196     indexIssues(
197       newDoc("openvul1", project).setOwaspTop10(asList("a1", "a3")).setCwe(asList("123", "456")).setType(RuleType.VULNERABILITY).setStatus(Issue.STATUS_OPEN)
198         .setSeverity(Severity.MAJOR),
199       newDoc("openvul2", project).setOwaspTop10(asList("a3", "a6")).setCwe(asList("123")).setType(RuleType.VULNERABILITY).setStatus(Issue.STATUS_REOPENED)
200         .setSeverity(Severity.MINOR),
201       newDoc("notowaspvul", project).setOwaspTop10(singletonList(UNKNOWN_STANDARD)).setType(RuleType.VULNERABILITY).setStatus(Issue.STATUS_OPEN).setSeverity(Severity.CRITICAL),
202       newDoc("toreviewhotspot1", project).setOwaspTop10(asList("a1", "a3")).setCwe(singletonList(UNKNOWN_STANDARD)).setType(RuleType.SECURITY_HOTSPOT)
203         .setStatus(Issue.STATUS_TO_REVIEW),
204       newDoc("toreviewhotspot2", project).setOwaspTop10(asList("a3", "a6")).setType(RuleType.SECURITY_HOTSPOT).setStatus(Issue.STATUS_TO_REVIEW),
205       newDoc("reviewedHotspot", project).setOwaspTop10(asList("a3", "a8")).setType(RuleType.SECURITY_HOTSPOT).setStatus(Issue.STATUS_REVIEWED)
206         .setResolution(Issue.RESOLUTION_FIXED),
207       newDoc("notowasphotspot", project).setOwaspTop10(singletonList(UNKNOWN_STANDARD)).setType(RuleType.SECURITY_HOTSPOT).setStatus(Issue.STATUS_TO_REVIEW));
208
209     List<SecurityStandardCategoryStatistics> owaspTop10Report = underTest.getOwaspTop10Report(project.uuid(), false, includeCwe);
210     assertThat(owaspTop10Report)
211       .extracting(SecurityStandardCategoryStatistics::getCategory, SecurityStandardCategoryStatistics::getVulnerabilities,
212         SecurityStandardCategoryStatistics::getVulnerabiliyRating, SecurityStandardCategoryStatistics::getToReviewSecurityHotspots,
213         SecurityStandardCategoryStatistics::getReviewedSecurityHotspots)
214       .containsExactlyInAnyOrder(
215         tuple("a1", 1L /* openvul1 */, OptionalInt.of(3)/* MAJOR = C */, 1L /* toreviewhotspot1 */, 0L),
216         tuple("a2", 0L, OptionalInt.empty(), 0L, 0L),
217         tuple("a3", 2L /* openvul1,openvul2 */, OptionalInt.of(3)/* MAJOR = C */, 2L/* toreviewhotspot1,toreviewhotspot2 */, 1L /* reviewedHotspot */),
218         tuple("a4", 0L, OptionalInt.empty(), 0L, 0L),
219         tuple("a5", 0L, OptionalInt.empty(), 0L, 0L),
220         tuple("a6", 1L /* openvul2 */, OptionalInt.of(2) /* MINOR = B */, 1L /* toreviewhotspot2 */, 0L),
221         tuple("a7", 0L, OptionalInt.empty(), 0L, 0L),
222         tuple("a8", 0L, OptionalInt.empty(), 0L, 1L /* reviewedHotspot */),
223         tuple("a9", 0L, OptionalInt.empty(), 0L, 0L),
224         tuple("a10", 0L, OptionalInt.empty(), 0L, 0L));
225     return owaspTop10Report;
226   }
227
228   @Test
229   public void getSansTop25Report_aggregation() {
230     OrganizationDto org = newOrganizationDto();
231     ComponentDto project = newPrivateProjectDto(org);
232     indexIssues(
233       newDoc("openvul1", project).setSansTop25(asList(SANS_TOP_25_INSECURE_INTERACTION, SANS_TOP_25_RISKY_RESOURCE)).setType(RuleType.VULNERABILITY).setStatus(Issue.STATUS_OPEN)
234         .setSeverity(Severity.MAJOR),
235       newDoc("openvul2", project).setSansTop25(asList(SANS_TOP_25_RISKY_RESOURCE, SANS_TOP_25_POROUS_DEFENSES)).setType(RuleType.VULNERABILITY).setStatus(Issue.STATUS_REOPENED)
236         .setSeverity(Severity.MINOR),
237       newDoc("notopenvul", project).setSansTop25(asList(SANS_TOP_25_RISKY_RESOURCE, SANS_TOP_25_POROUS_DEFENSES)).setType(RuleType.VULNERABILITY).setStatus(Issue.STATUS_CLOSED)
238         .setResolution(Issue.RESOLUTION_FIXED)
239         .setSeverity(Severity.BLOCKER),
240       newDoc("notsansvul", project).setSansTop25(singletonList(UNKNOWN_STANDARD)).setType(RuleType.VULNERABILITY).setStatus(Issue.STATUS_OPEN).setSeverity(Severity.CRITICAL),
241       newDoc("toreviewhotspot1", project).setSansTop25(asList(SANS_TOP_25_INSECURE_INTERACTION, SANS_TOP_25_RISKY_RESOURCE)).setType(RuleType.SECURITY_HOTSPOT)
242         .setStatus(Issue.STATUS_TO_REVIEW),
243       newDoc("toreviewhotspot2", project).setSansTop25(asList(SANS_TOP_25_RISKY_RESOURCE, SANS_TOP_25_POROUS_DEFENSES)).setType(RuleType.SECURITY_HOTSPOT)
244         .setStatus(Issue.STATUS_TO_REVIEW),
245       newDoc("inReviewHotspot", project).setSansTop25(asList(SANS_TOP_25_RISKY_RESOURCE)).setType(RuleType.SECURITY_HOTSPOT).setStatus(Issue.STATUS_IN_REVIEW),
246       newDoc("reviewedHotspot", project).setSansTop25(asList(SANS_TOP_25_RISKY_RESOURCE)).setType(RuleType.SECURITY_HOTSPOT).setStatus(Issue.STATUS_REVIEWED)
247         .setResolution(Issue.RESOLUTION_FIXED),
248       newDoc("notowasphotspot", project).setSansTop25(singletonList(UNKNOWN_STANDARD)).setType(RuleType.SECURITY_HOTSPOT).setStatus(Issue.STATUS_TO_REVIEW));
249
250     List<SecurityStandardCategoryStatistics> sansTop25Report = underTest.getSansTop25Report(project.uuid(), false, false);
251     assertThat(sansTop25Report)
252       .extracting(SecurityStandardCategoryStatistics::getCategory, SecurityStandardCategoryStatistics::getVulnerabilities,
253         SecurityStandardCategoryStatistics::getVulnerabiliyRating, SecurityStandardCategoryStatistics::getToReviewSecurityHotspots,
254         SecurityStandardCategoryStatistics::getReviewedSecurityHotspots)
255       .containsExactlyInAnyOrder(
256         tuple(SANS_TOP_25_INSECURE_INTERACTION, 1L /* openvul1 */, OptionalInt.of(3)/* MAJOR = C */, 1L /* toreviewhotspot1 */, 0L),
257         tuple(SANS_TOP_25_RISKY_RESOURCE, 2L /* openvul1,openvul2 */, OptionalInt.of(3)/* MAJOR = C */, 2L/* toreviewhotspot1,toreviewhotspot2 */,
258           1L /* reviewedHotspot */),
259         tuple(SANS_TOP_25_POROUS_DEFENSES, 1L /* openvul2 */, OptionalInt.of(2)/* MINOR = B */, 1L/* openhotspot2 */, 0L));
260
261     assertThat(sansTop25Report).allMatch(category -> category.getChildren().isEmpty());
262   }
263
264   @Test
265   public void getSansTop25Report_aggregation_on_portfolio() {
266     ComponentDto portfolio1 = db.components().insertPrivateApplication(db.getDefaultOrganization());
267     ComponentDto portfolio2 = db.components().insertPrivateApplication(db.getDefaultOrganization());
268     ComponentDto project1 = db.components().insertPrivateProject();
269     ComponentDto project2 = db.components().insertPrivateProject();
270
271     indexIssues(
272       newDoc("openvul1", project1).setSansTop25(asList(SANS_TOP_25_INSECURE_INTERACTION, SANS_TOP_25_RISKY_RESOURCE)).setType(RuleType.VULNERABILITY).setStatus(Issue.STATUS_OPEN)
273         .setSeverity(Severity.MAJOR),
274       newDoc("openvul2", project2).setSansTop25(asList(SANS_TOP_25_RISKY_RESOURCE, SANS_TOP_25_POROUS_DEFENSES)).setType(RuleType.VULNERABILITY).setStatus(Issue.STATUS_REOPENED)
275         .setSeverity(Severity.MINOR),
276       newDoc("notopenvul", project1).setSansTop25(asList(SANS_TOP_25_RISKY_RESOURCE, SANS_TOP_25_POROUS_DEFENSES)).setType(RuleType.VULNERABILITY).setStatus(Issue.STATUS_CLOSED)
277         .setResolution(Issue.RESOLUTION_FIXED)
278         .setSeverity(Severity.BLOCKER),
279       newDoc("notsansvul", project2).setSansTop25(singletonList(UNKNOWN_STANDARD)).setType(RuleType.VULNERABILITY).setStatus(Issue.STATUS_OPEN).setSeverity(Severity.CRITICAL),
280       newDoc("toreviewhotspot1", project1).setSansTop25(asList(SANS_TOP_25_INSECURE_INTERACTION, SANS_TOP_25_RISKY_RESOURCE)).setType(RuleType.SECURITY_HOTSPOT)
281         .setStatus(Issue.STATUS_TO_REVIEW),
282       newDoc("toreviewhotspot2", project2).setSansTop25(asList(SANS_TOP_25_RISKY_RESOURCE, SANS_TOP_25_POROUS_DEFENSES)).setType(RuleType.SECURITY_HOTSPOT)
283         .setStatus(Issue.STATUS_TO_REVIEW),
284       newDoc("reviewedHotspot", project2).setSansTop25(asList(SANS_TOP_25_RISKY_RESOURCE)).setType(RuleType.SECURITY_HOTSPOT).setStatus(Issue.STATUS_REVIEWED)
285         .setResolution(Issue.RESOLUTION_FIXED),
286       newDoc("notowasphotspot", project1).setSansTop25(singletonList(UNKNOWN_STANDARD)).setType(RuleType.SECURITY_HOTSPOT).setStatus(Issue.STATUS_TO_REVIEW));
287
288     indexView(portfolio1.uuid(), singletonList(project1.uuid()));
289     indexView(portfolio2.uuid(), singletonList(project2.uuid()));
290
291     List<SecurityStandardCategoryStatistics> sansTop25Report = underTest.getSansTop25Report(portfolio1.uuid(), true, false);
292     assertThat(sansTop25Report)
293       .extracting(SecurityStandardCategoryStatistics::getCategory, SecurityStandardCategoryStatistics::getVulnerabilities,
294         SecurityStandardCategoryStatistics::getVulnerabiliyRating, SecurityStandardCategoryStatistics::getToReviewSecurityHotspots,
295         SecurityStandardCategoryStatistics::getReviewedSecurityHotspots)
296       .containsExactlyInAnyOrder(
297         tuple(SANS_TOP_25_INSECURE_INTERACTION, 1L /* openvul1 */, OptionalInt.of(3)/* MAJOR = C */, 1L /* toreviewhotspot1 */, 0L),
298         tuple(SANS_TOP_25_RISKY_RESOURCE, 1L /* openvul1 */, OptionalInt.of(3)/* MAJOR = C */, 1L/* toreviewhotspot1 */, 0L),
299         tuple(SANS_TOP_25_POROUS_DEFENSES, 0L, OptionalInt.empty(), 0L, 0L));
300
301     assertThat(sansTop25Report).allMatch(category -> category.getChildren().isEmpty());
302   }
303
304   private void indexIssues(IssueDoc... issues) {
305     issueIndexer.index(asList(issues).iterator());
306     authorizationIndexer.allow(stream(issues).map(issue -> new IndexPermissions(issue.projectUuid(), PROJECT).allowAnyone()).collect(toList()));
307   }
308
309   private void indexView(String viewUuid, List<String> projects) {
310     viewIndexer.index(new ViewDoc().setUuid(viewUuid).setProjects(projects));
311   }
312
313 }