3 * Copyright (C) 2009-2020 SonarSource SA
4 * mailto:info AT sonarsource DOT com
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.
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.
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.
20 package org.sonar.server.issue.index;
22 import java.util.List;
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;
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;
62 public class IssueIndexSecurityReportsTest {
65 public EsTester es = EsTester.create();
67 public UserSessionRule userSessionRule = UserSessionRule.standalone();
69 public ExpectedException expectedException = none();
70 private System2 system2 = new TestSystem2().setNow(1_500_000_000_000L).setDefaultTimeZone(getTimeZone("GMT-01:00"));
72 public DbTester db = DbTester.create(system2);
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);
78 private IssueIndex underTest = new IssueIndex(es.client(), system2, userSessionRule, new WebAuthorizationTypeSupport(userSessionRule));
81 public void getOwaspTop10Report_dont_count_vulnerabilities_from_other_projects() {
82 OrganizationDto org = newOrganizationDto();
83 ComponentDto project = newPrivateProjectDto(org);
84 ComponentDto another = newPrivateProjectDto(org);
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));
89 List<SecurityStandardCategoryStatistics> owaspTop10Report = underTest.getOwaspTop10Report(project.uuid(), false, false);
90 assertThat(owaspTop10Report)
91 .extracting(SecurityStandardCategoryStatistics::getCategory, SecurityStandardCategoryStatistics::getVulnerabilities,
92 SecurityStandardCategoryStatistics::getVulnerabiliyRating)
94 tuple("a1", 1L /* openvul1 */, OptionalInt.of(3)/* MAJOR = C */));
99 public void getOwaspTop10Report_dont_count_closed_vulnerabilities() {
100 OrganizationDto org = newOrganizationDto();
101 ComponentDto project = newPrivateProjectDto(org);
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));
107 List<SecurityStandardCategoryStatistics> owaspTop10Report = underTest.getOwaspTop10Report(project.uuid(), false, false);
108 assertThat(owaspTop10Report)
109 .extracting(SecurityStandardCategoryStatistics::getCategory, SecurityStandardCategoryStatistics::getVulnerabilities,
110 SecurityStandardCategoryStatistics::getVulnerabiliyRating)
112 tuple("a1", 1L /* openvul1 */, OptionalInt.of(3)/* MAJOR = C */));
116 public void getOwaspTop10Report_dont_count_old_vulnerabilities() {
117 OrganizationDto org = newOrganizationDto();
118 ComponentDto project = newPrivateProjectDto(org);
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));
123 List<SecurityStandardCategoryStatistics> owaspTop10Report = underTest.getOwaspTop10Report(project.uuid(), false, false);
124 assertThat(owaspTop10Report)
125 .extracting(SecurityStandardCategoryStatistics::getVulnerabilities,
126 SecurityStandardCategoryStatistics::getVulnerabiliyRating)
128 tuple(0L, OptionalInt.empty()));
132 public void getOwaspTop10Report_dont_count_hotspots_from_other_projects() {
133 OrganizationDto org = newOrganizationDto();
134 ComponentDto project = newPrivateProjectDto(org);
135 ComponentDto another = newPrivateProjectDto(org);
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));
140 List<SecurityStandardCategoryStatistics> owaspTop10Report = underTest.getOwaspTop10Report(project.uuid(), false, false);
141 assertThat(owaspTop10Report)
142 .extracting(SecurityStandardCategoryStatistics::getCategory, SecurityStandardCategoryStatistics::getToReviewSecurityHotspots)
144 tuple("a1", 1L /* openhotspot1 */));
148 public void getOwaspTop10Report_dont_count_closed_hotspots() {
149 OrganizationDto org = newOrganizationDto();
150 ComponentDto project = newPrivateProjectDto(org);
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));
156 List<SecurityStandardCategoryStatistics> owaspTop10Report = underTest.getOwaspTop10Report(project.uuid(), false, false);
157 assertThat(owaspTop10Report)
158 .extracting(SecurityStandardCategoryStatistics::getCategory, SecurityStandardCategoryStatistics::getToReviewSecurityHotspots)
160 tuple("a1", 1L /* openhotspot1 */));
164 public void getOwaspTop10Report_aggregation_no_cwe() {
165 List<SecurityStandardCategoryStatistics> owaspTop10Report = indexIssuesAndAssertOwaspReport(false);
167 assertThat(owaspTop10Report).allMatch(category -> category.getChildren().isEmpty());
171 public void getOwaspTop10Report_aggregation_with_cwe() {
172 List<SecurityStandardCategoryStatistics> owaspTop10Report = indexIssuesAndAssertOwaspReport(true);
174 Map<String, List<SecurityStandardCategoryStatistics>> cweByOwasp = owaspTop10Report.stream()
175 .collect(Collectors.toMap(SecurityStandardCategoryStatistics::getCategory, SecurityStandardCategoryStatistics::getChildren));
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));
193 private List<SecurityStandardCategoryStatistics> indexIssuesAndAssertOwaspReport(boolean includeCwe) {
194 OrganizationDto org = newOrganizationDto();
195 ComponentDto project = newPrivateProjectDto(org);
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));
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;
229 public void getSansTop25Report_aggregation() {
230 OrganizationDto org = newOrganizationDto();
231 ComponentDto project = newPrivateProjectDto(org);
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));
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));
261 assertThat(sansTop25Report).allMatch(category -> category.getChildren().isEmpty());
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();
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));
288 indexView(portfolio1.uuid(), singletonList(project1.uuid()));
289 indexView(portfolio2.uuid(), singletonList(project2.uuid()));
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));
301 assertThat(sansTop25Report).allMatch(category -> category.getChildren().isEmpty());
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()));
309 private void indexView(String viewUuid, List<String> projects) {
310 viewIndexer.index(new ViewDoc().setUuid(viewUuid).setProjects(projects));