]> source.dussan.org Git - sonarqube.git/blob
56dbc23139cbeec62d1cb68aebb6eaa4dd50f858
[sonarqube.git] /
1 /*
2  * SonarQube
3  * Copyright (C) 2009-2024 SonarSource SA
4  * mailto:info AT sonarsource DOT com
5  *
6  * This program is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU Lesser General Public
8  * License as published by the Free Software Foundation; either
9  * version 3 of the License, or (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * Lesser General Public License for more details.
15  *
16  * You should have received a copy of the GNU Lesser General Public License
17  * along with this program; if not, write to the Free Software Foundation,
18  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
19  */
20 package org.sonar.ce.task.projectanalysis.pushevent;
21
22 import com.google.gson.Gson;
23 import java.nio.charset.StandardCharsets;
24 import java.util.Date;
25 import java.util.Set;
26 import org.assertj.core.groups.Tuple;
27 import org.junit.Before;
28 import org.junit.Rule;
29 import org.junit.Test;
30 import org.sonar.api.issue.Issue;
31 import org.sonar.api.issue.impact.Severity;
32 import org.sonar.api.issue.impact.SoftwareQuality;
33 import org.sonar.api.rule.RuleKey;
34 import org.sonar.api.rules.CleanCodeAttribute;
35 import org.sonar.api.rules.RuleType;
36 import org.sonar.api.utils.DateUtils;
37 import org.sonar.ce.task.projectanalysis.analysis.AnalysisMetadataHolderRule;
38 import org.sonar.ce.task.projectanalysis.analysis.TestBranch;
39 import org.sonar.ce.task.projectanalysis.component.Component.Type;
40 import org.sonar.ce.task.projectanalysis.component.MutableTreeRootHolderRule;
41 import org.sonar.ce.task.projectanalysis.component.ReportComponent;
42 import org.sonar.ce.task.projectanalysis.issue.RuleRepository;
43 import org.sonar.ce.task.projectanalysis.locations.flow.FlowGenerator;
44 import org.sonar.core.issue.DefaultIssue;
45 import org.sonar.core.issue.FieldDiffs;
46 import org.sonar.db.issue.ImpactDto;
47 import org.sonar.db.protobuf.DbCommons;
48 import org.sonar.db.protobuf.DbIssues;
49 import org.sonar.db.rule.RuleDto;
50 import org.sonar.server.issue.TaintChecker;
51
52 import static org.apache.commons.lang3.RandomStringUtils.randomAlphabetic;
53 import static org.assertj.core.api.Assertions.assertThat;
54 import static org.assertj.core.api.Assertions.fail;
55 import static org.assertj.core.api.Assertions.tuple;
56 import static org.mockito.ArgumentMatchers.any;
57 import static org.mockito.Mockito.mock;
58 import static org.mockito.Mockito.when;
59
60 public class PushEventFactoryTest {
61
62   private static final Gson gson = new Gson();
63   private static final String BRANCH_NAME = "develop";
64
65   private final TaintChecker taintChecker = mock(TaintChecker.class);
66   @Rule
67   public MutableTreeRootHolderRule treeRootHolder = new MutableTreeRootHolderRule();
68   @Rule
69   public AnalysisMetadataHolderRule analysisMetadataHolder = new AnalysisMetadataHolderRule()
70     .setBranch(new TestBranch(BRANCH_NAME));
71   private final FlowGenerator flowGenerator = new FlowGenerator(treeRootHolder);
72   private final RuleRepository ruleRepository = mock(RuleRepository.class);
73   private final PushEventFactory underTest = new PushEventFactory(treeRootHolder, analysisMetadataHolder, taintChecker, flowGenerator,
74     ruleRepository);
75
76   @Before
77   public void setUp() {
78     when(ruleRepository.getByKey(RuleKey.of("javasecurity", "S123"))).thenReturn(buildRule());
79     buildComponentTree();
80   }
81
82   @Test
83   public void raiseEventOnIssue_whenNewTaintVulnerability_shouldCreateRaisedEvent() {
84     DefaultIssue defaultIssue = createDefaultIssue()
85       .setNew(true)
86       .setRuleDescriptionContextKey(randomAlphabetic(6));
87
88     when(taintChecker.isTaintVulnerability(any())).thenReturn(true);
89
90     assertThat(underTest.raiseEventOnIssue("some-project-uuid", defaultIssue))
91       .isNotEmpty()
92       .hasValueSatisfying(pushEventDto -> {
93         assertThat(pushEventDto.getName()).isEqualTo("TaintVulnerabilityRaised");
94         verifyPayload(pushEventDto.getPayload(), defaultIssue);
95         assertThat(pushEventDto.getLanguage()).isEqualTo("java");
96         assertThat(pushEventDto.getProjectUuid()).isEqualTo("some-project-uuid");
97       });
98
99   }
100
101   private static void verifyPayload(byte[] payload, DefaultIssue defaultIssue) {
102     assertThat(payload).isNotNull();
103
104     TaintVulnerabilityRaised taintVulnerabilityRaised = gson.fromJson(new String(payload, StandardCharsets.UTF_8),
105       TaintVulnerabilityRaised.class);
106     assertThat(taintVulnerabilityRaised.getProjectKey()).isEqualTo(defaultIssue.projectKey());
107     assertThat(taintVulnerabilityRaised.getCreationDate()).isEqualTo(defaultIssue.creationDate().getTime());
108     assertThat(taintVulnerabilityRaised.getKey()).isEqualTo(defaultIssue.key());
109     assertThat(taintVulnerabilityRaised.getSeverity()).isEqualTo(defaultIssue.severity());
110     assertThat(taintVulnerabilityRaised.getRuleKey()).isEqualTo(defaultIssue.ruleKey().toString());
111     assertThat(taintVulnerabilityRaised.getType()).isEqualTo(defaultIssue.type().name());
112     assertThat(taintVulnerabilityRaised.getBranch()).isEqualTo(BRANCH_NAME);
113     assertThat(taintVulnerabilityRaised.getCleanCodeAttribute()).isEqualTo(CleanCodeAttribute.CONVENTIONAL.name());
114     assertThat(taintVulnerabilityRaised.getCleanCodeAttributeCategory()).isEqualTo(CleanCodeAttribute.CONVENTIONAL.getAttributeCategory().name());
115     assertThat(taintVulnerabilityRaised.getImpacts()).extracting(TaintVulnerabilityRaised.Impact::getSoftwareQuality, TaintVulnerabilityRaised.Impact::getSeverity)
116       .containsExactlyInAnyOrder(Tuple.tuple(SoftwareQuality.MAINTAINABILITY.name(), Severity.MEDIUM.name()),
117         Tuple.tuple(SoftwareQuality.RELIABILITY.name(), Severity.HIGH.name()));
118
119     String ruleDescriptionContextKey = taintVulnerabilityRaised.getRuleDescriptionContextKey().orElseGet(() -> fail("No rule description " +
120                                                                                                                     "context key"));
121     assertThat(ruleDescriptionContextKey).isEqualTo(defaultIssue.getRuleDescriptionContextKey().orElse(null));
122   }
123
124   @Test
125   public void raiseEventOnIssue_whenNewTaintVulnerabilityWithImpactAtRuleAndIssueLevel_shouldMergeImpacts() {
126     DefaultIssue defaultIssue = createDefaultIssue()
127       .setNew(true)
128       .addImpact(SoftwareQuality.MAINTAINABILITY, Severity.HIGH)
129       .setRuleDescriptionContextKey(randomAlphabetic(6));
130
131     when(taintChecker.isTaintVulnerability(any())).thenReturn(true);
132
133     assertThat(underTest.raiseEventOnIssue("some-project-uuid", defaultIssue))
134       .isNotEmpty()
135       .hasValueSatisfying(pushEventDto -> {
136         TaintVulnerabilityRaised taintVulnerabilityRaised = gson.fromJson(new String(pushEventDto.getPayload(), StandardCharsets.UTF_8),
137           TaintVulnerabilityRaised.class);
138         assertThat(taintVulnerabilityRaised.getImpacts()).extracting(TaintVulnerabilityRaised.Impact::getSoftwareQuality, TaintVulnerabilityRaised.Impact::getSeverity)
139           .containsExactlyInAnyOrder(tuple(SoftwareQuality.MAINTAINABILITY.name(), Severity.HIGH.name()), tuple(SoftwareQuality.RELIABILITY.name(), Severity.HIGH.name()));
140       });
141   }
142
143   @Test
144   public void raiseEventOnIssue_whenReopenedTaintVulnerability_shouldCreateRaisedEvent() {
145     DefaultIssue defaultIssue = createDefaultIssue()
146       .setChanged(true)
147       .setNew(false)
148       .setCopied(false)
149       .setCurrentChange(new FieldDiffs().setDiff("status", "CLOSED", "OPEN"));
150
151     when(taintChecker.isTaintVulnerability(any())).thenReturn(true);
152
153     assertThat(underTest.raiseEventOnIssue("some-project-uuid", defaultIssue))
154       .isNotEmpty()
155       .hasValueSatisfying(pushEventDto -> {
156         assertThat(pushEventDto.getName()).isEqualTo("TaintVulnerabilityRaised");
157         assertThat(pushEventDto.getPayload()).isNotNull();
158       });
159   }
160
161   @Test
162   public void raiseEventOnIssue_whenTaintVulnerabilityStatusChange_shouldSkipEvent() {
163     DefaultIssue defaultIssue = createDefaultIssue()
164       .setChanged(true)
165       .setNew(false)
166       .setCopied(false)
167       .setCurrentChange(new FieldDiffs().setDiff("status", "OPEN", "FIXED"));
168
169     when(taintChecker.isTaintVulnerability(any())).thenReturn(true);
170
171     assertThat(underTest.raiseEventOnIssue("some-project-uuid", defaultIssue)).isEmpty();
172   }
173
174   @Test
175   public void raiseEventOnIssue_whenCopiedTaintVulnerability_shouldCreateRaisedEvent() {
176     DefaultIssue defaultIssue = createDefaultIssue()
177       .setCopied(true);
178
179     when(taintChecker.isTaintVulnerability(any())).thenReturn(true);
180
181     assertThat(underTest.raiseEventOnIssue("some-project-uuid", defaultIssue))
182       .isNotEmpty()
183       .hasValueSatisfying(pushEventDto -> {
184         assertThat(pushEventDto.getName()).isEqualTo("TaintVulnerabilityRaised");
185         assertThat(pushEventDto.getPayload()).isNotNull();
186       });
187   }
188
189   @Test
190   public void raiseEventOnIssue_whenClosedTaintVulnerability_shouldCreateClosedEvent() {
191     DefaultIssue defaultIssue = createDefaultIssue()
192       .setNew(false)
193       .setCopied(false)
194       .setBeingClosed(true);
195
196     when(taintChecker.isTaintVulnerability(any())).thenReturn(true);
197
198     assertThat(underTest.raiseEventOnIssue("some-project-uuid", defaultIssue))
199       .isNotEmpty()
200       .hasValueSatisfying(pushEventDto -> {
201         assertThat(pushEventDto.getName()).isEqualTo("TaintVulnerabilityClosed");
202         assertThat(pushEventDto.getPayload()).isNotNull();
203       });
204   }
205
206   @Test
207   public void raiseEventOnIssue_whenChangedTaintVulnerability_shouldSkipEvent() {
208     DefaultIssue defaultIssue = new DefaultIssue()
209       .setComponentUuid("issue-component-uuid")
210       .setNew(false)
211       .setCopied(false)
212       .setChanged(true)
213       .setType(RuleType.VULNERABILITY)
214       .setCreationDate(DateUtils.parseDate("2022-01-01"))
215       .setRuleKey(RuleKey.of("javasecurity", "S123"));
216
217     when(taintChecker.isTaintVulnerability(any())).thenReturn(true);
218
219     assertThat(underTest.raiseEventOnIssue("some-project-uuid", defaultIssue)).isEmpty();
220   }
221
222   @Test
223   public void raiseEventOnIssue_whenIssueNotFromTaintVulnerabilityRepository_shouldSkipEvent() {
224     DefaultIssue defaultIssue = new DefaultIssue()
225       .setComponentUuid("issue-component-uuid")
226       .setChanged(true)
227       .setType(RuleType.VULNERABILITY)
228       .setRuleKey(RuleKey.of("weirdrepo", "S123"));
229
230     when(taintChecker.isTaintVulnerability(any())).thenReturn(false);
231
232     assertThat(underTest.raiseEventOnIssue("some-project-uuid", defaultIssue)).isEmpty();
233
234     defaultIssue = new DefaultIssue()
235       .setComponentUuid("issue-component-uuid")
236       .setChanged(false)
237       .setNew(false)
238       .setBeingClosed(true)
239       .setType(RuleType.VULNERABILITY)
240       .setRuleKey(RuleKey.of("weirdrepo", "S123"));
241
242     assertThat(underTest.raiseEventOnIssue("some-project-uuid", defaultIssue)).isEmpty();
243   }
244
245   @Test
246   public void raiseEventOnIssue_whenIssueDoesNotHaveLocations_shouldSkipEvent() {
247     DefaultIssue defaultIssue = new DefaultIssue()
248       .setComponentUuid("issue-component-uuid")
249       .setChanged(true)
250       .setType(RuleType.VULNERABILITY)
251       .setRuleKey(RuleKey.of("javasecurity", "S123"));
252
253     when(taintChecker.isTaintVulnerability(any())).thenReturn(false);
254
255     assertThat(underTest.raiseEventOnIssue("some-project-uuid", defaultIssue)).isEmpty();
256   }
257
258   @Test
259   public void raiseEventOnIssue_whenNewHotspot_shouldCreateRaisedEvent() {
260     DefaultIssue defaultIssue = createDefaultIssue()
261       .setType(RuleType.SECURITY_HOTSPOT)
262       .setStatus(Issue.STATUS_TO_REVIEW)
263       .setNew(true)
264       .setRuleDescriptionContextKey(randomAlphabetic(6));
265
266     assertThat(underTest.raiseEventOnIssue("some-project-uuid", defaultIssue))
267       .isNotEmpty()
268       .hasValueSatisfying(pushEventDto -> {
269         assertThat(pushEventDto.getName()).isEqualTo(SecurityHotspotRaised.EVENT_NAME);
270         verifyHotspotRaisedEventPayload(pushEventDto.getPayload(), defaultIssue);
271         assertThat(pushEventDto.getLanguage()).isEqualTo("java");
272         assertThat(pushEventDto.getProjectUuid()).isEqualTo("some-project-uuid");
273       });
274   }
275
276   private static void verifyHotspotRaisedEventPayload(byte[] payload, DefaultIssue defaultIssue) {
277     assertThat(payload).isNotNull();
278
279     SecurityHotspotRaised event = gson.fromJson(new String(payload, StandardCharsets.UTF_8), SecurityHotspotRaised.class);
280     assertThat(event.getProjectKey()).isEqualTo(defaultIssue.projectKey());
281     assertThat(event.getCreationDate()).isEqualTo(defaultIssue.creationDate().getTime());
282     assertThat(event.getKey()).isEqualTo(defaultIssue.key());
283     assertThat(event.getRuleKey()).isEqualTo(defaultIssue.ruleKey().toString());
284     assertThat(event.getStatus()).isEqualTo(Issue.STATUS_TO_REVIEW);
285     assertThat(event.getVulnerabilityProbability()).isEqualTo("LOW");
286     assertThat(event.getMainLocation()).isNotNull();
287     assertThat(event.getBranch()).isEqualTo(BRANCH_NAME);
288     assertThat(event.getAssignee()).isEqualTo("some-user-login");
289   }
290
291   @Test
292   public void raiseEventOnIssue_whenReopenedHotspot_shouldCreateRaisedEvent() {
293     DefaultIssue defaultIssue = createDefaultIssue()
294       .setType(RuleType.SECURITY_HOTSPOT)
295       .setChanged(true)
296       .setNew(false)
297       .setCopied(false)
298       .setCurrentChange(new FieldDiffs().setDiff("status", "CLOSED", "TO_REVIEW"));
299
300     assertThat(underTest.raiseEventOnIssue("some-project-uuid", defaultIssue))
301       .isNotEmpty()
302       .hasValueSatisfying(pushEventDto -> {
303         assertThat(pushEventDto.getName()).isEqualTo(SecurityHotspotRaised.EVENT_NAME);
304         assertThat(pushEventDto.getPayload()).isNotNull();
305       });
306   }
307
308   @Test
309   public void raiseEventOnIssue_whenCopiedHotspot_shouldCreateRaisedEvent() {
310     DefaultIssue defaultIssue = createDefaultIssue()
311       .setType(RuleType.SECURITY_HOTSPOT)
312       .setCopied(true);
313
314     assertThat(underTest.raiseEventOnIssue("some-project-uuid", defaultIssue))
315       .isNotEmpty()
316       .hasValueSatisfying(pushEventDto -> {
317         assertThat(pushEventDto.getName()).isEqualTo(SecurityHotspotRaised.EVENT_NAME);
318         assertThat(pushEventDto.getPayload()).isNotNull();
319       });
320   }
321
322   @Test
323   public void raiseEventOnIssue_whenClosedHotspot_shouldCreateClosedEvent() {
324     DefaultIssue defaultIssue = createDefaultIssue()
325       .setType(RuleType.SECURITY_HOTSPOT)
326       .setNew(false)
327       .setCopied(false)
328       .setBeingClosed(true)
329       .setStatus(Issue.STATUS_CLOSED)
330       .setResolution(Issue.RESOLUTION_FIXED);
331
332     assertThat(underTest.raiseEventOnIssue("some-project-uuid", defaultIssue))
333       .isNotEmpty()
334       .hasValueSatisfying(pushEventDto -> {
335         assertThat(pushEventDto.getName()).isEqualTo(SecurityHotspotClosed.EVENT_NAME);
336         verifyHotspotClosedEventPayload(pushEventDto.getPayload(), defaultIssue);
337         assertThat(pushEventDto.getLanguage()).isEqualTo("java");
338         assertThat(pushEventDto.getProjectUuid()).isEqualTo("some-project-uuid");
339       });
340   }
341
342   private static void verifyHotspotClosedEventPayload(byte[] payload, DefaultIssue defaultIssue) {
343     assertThat(payload).isNotNull();
344
345     SecurityHotspotClosed event = gson.fromJson(new String(payload, StandardCharsets.UTF_8), SecurityHotspotClosed.class);
346     assertThat(event.getProjectKey()).isEqualTo(defaultIssue.projectKey());
347     assertThat(event.getKey()).isEqualTo(defaultIssue.key());
348     assertThat(event.getStatus()).isEqualTo(Issue.STATUS_CLOSED);
349     assertThat(event.getResolution()).isEqualTo(Issue.RESOLUTION_FIXED);
350     assertThat(event.getFilePath()).isEqualTo("component-name");
351   }
352
353   @Test
354   public void raiseEventOnIssue_whenChangedHotspot_shouldSkipEvent() {
355     DefaultIssue defaultIssue = createDefaultIssue()
356       .setType(RuleType.SECURITY_HOTSPOT)
357       .setChanged(true)
358       .setNew(false)
359       .setCopied(false);
360
361     assertThat(underTest.raiseEventOnIssue("some-project-uuid", defaultIssue)).isEmpty();
362   }
363
364   @Test
365   public void raiseEventOnIssue_whenComponentUuidNull_shouldSkipEvent() {
366     DefaultIssue defaultIssue = createDefaultIssue()
367       .setComponentUuid(null);
368
369     assertThat(underTest.raiseEventOnIssue("some-project-uuid", defaultIssue)).isEmpty();
370   }
371
372   private void buildComponentTree() {
373     treeRootHolder.setRoot(ReportComponent.builder(Type.PROJECT, 1)
374       .setUuid("uuid_1")
375       .addChildren(ReportComponent.builder(Type.FILE, 2)
376         .setName("component-name")
377         .setUuid("issue-component-uuid")
378         .build())
379       .addChildren(ReportComponent.builder(Type.FILE, 3)
380         .setUuid("location-component-uuid")
381         .build())
382       .build());
383   }
384
385   private DefaultIssue createDefaultIssue() {
386     return new DefaultIssue()
387       .setKey("issue-key")
388       .setProjectKey("project-key")
389       .setComponentUuid("issue-component-uuid")
390       .setAssigneeUuid("some-user-uuid")
391       .setAssigneeLogin("some-user-login")
392       .setType(RuleType.VULNERABILITY)
393       .setLanguage("java")
394       .setCreationDate(new Date())
395       .setLocations(DbIssues.Locations.newBuilder()
396         .addFlow(DbIssues.Flow.newBuilder()
397           .addLocation(DbIssues.Location.newBuilder()
398             .setChecksum("checksum")
399             .setComponentId("location-component-uuid")
400             .build())
401           .build())
402         .setTextRange(DbCommons.TextRange.newBuilder()
403           .setStartLine(1)
404           .build())
405         .build())
406       .setRuleKey(RuleKey.of("javasecurity", "S123"));
407   }
408
409   private org.sonar.ce.task.projectanalysis.issue.Rule buildRule() {
410     RuleDto ruleDto = new RuleDto();
411     ruleDto.setRuleKey(RuleKey.of("javasecurity", "S123"));
412     ruleDto.setSecurityStandards(Set.of("owasp-a1"));
413     ruleDto.setCleanCodeAttribute(CleanCodeAttribute.CONVENTIONAL);
414     ruleDto.addDefaultImpact(new ImpactDto().setSoftwareQuality(SoftwareQuality.MAINTAINABILITY).setSeverity(Severity.MEDIUM));
415     ruleDto.addDefaultImpact(new ImpactDto().setSoftwareQuality(SoftwareQuality.RELIABILITY).setSeverity(Severity.HIGH));
416     return new org.sonar.ce.task.projectanalysis.issue.RuleImpl(ruleDto);
417   }
418 }