]> source.dussan.org Git - sonarqube.git/blob
028a0c11843ebce0edb1b619b8ddd54bb010f090
[sonarqube.git] /
1 /*
2  * SonarQube
3  * Copyright (C) 2009-2023 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.junit.Before;
27 import org.junit.Rule;
28 import org.junit.Test;
29 import org.sonar.api.issue.Issue;
30 import org.sonar.api.rule.RuleKey;
31 import org.sonar.api.rules.RuleType;
32 import org.sonar.api.utils.DateUtils;
33 import org.sonar.ce.task.projectanalysis.analysis.AnalysisMetadataHolderRule;
34 import org.sonar.ce.task.projectanalysis.analysis.TestBranch;
35 import org.sonar.ce.task.projectanalysis.component.Component.Type;
36 import org.sonar.ce.task.projectanalysis.component.MutableTreeRootHolderRule;
37 import org.sonar.ce.task.projectanalysis.component.ReportComponent;
38 import org.sonar.ce.task.projectanalysis.issue.RuleRepository;
39 import org.sonar.ce.task.projectanalysis.locations.flow.FlowGenerator;
40 import org.sonar.core.issue.DefaultIssue;
41 import org.sonar.core.issue.FieldDiffs;
42 import org.sonar.db.protobuf.DbCommons;
43 import org.sonar.db.protobuf.DbIssues;
44 import org.sonar.db.rule.RuleDto;
45 import org.sonar.server.issue.TaintChecker;
46
47 import static org.apache.commons.lang.RandomStringUtils.randomAlphabetic;
48 import static org.assertj.core.api.Assertions.assertThat;
49 import static org.assertj.core.api.Assertions.fail;
50 import static org.mockito.ArgumentMatchers.any;
51 import static org.mockito.Mockito.mock;
52 import static org.mockito.Mockito.when;
53
54 public class PushEventFactoryTest {
55
56   private static final Gson gson = new Gson();
57   private static final String BRANCH_NAME = "develop";
58
59   private final TaintChecker taintChecker = mock(TaintChecker.class);
60   @Rule
61   public MutableTreeRootHolderRule treeRootHolder = new MutableTreeRootHolderRule();
62   @Rule
63   public AnalysisMetadataHolderRule analysisMetadataHolder = new AnalysisMetadataHolderRule()
64     .setBranch(new TestBranch(BRANCH_NAME));
65   private final FlowGenerator flowGenerator = new FlowGenerator(treeRootHolder);
66   private final RuleRepository ruleRepository = mock(RuleRepository.class);
67   private final PushEventFactory underTest = new PushEventFactory(treeRootHolder, analysisMetadataHolder, taintChecker, flowGenerator,
68     ruleRepository);
69
70   @Before
71   public void setUp() {
72     when(ruleRepository.getByKey(RuleKey.of("javasecurity", "S123"))).thenReturn(buildRule());
73     buildComponentTree();
74   }
75
76   @Test
77   public void raiseEventOnIssue_whenNewTaintVulnerability_shouldCreateRaisedEvent() {
78     DefaultIssue defaultIssue = createDefaultIssue()
79       .setNew(true)
80       .setRuleDescriptionContextKey(randomAlphabetic(6));
81
82     when(taintChecker.isTaintVulnerability(any())).thenReturn(true);
83
84     assertThat(underTest.raiseEventOnIssue("some-project-uuid", defaultIssue))
85       .isNotEmpty()
86       .hasValueSatisfying(pushEventDto -> {
87         assertThat(pushEventDto.getName()).isEqualTo("TaintVulnerabilityRaised");
88         verifyPayload(pushEventDto.getPayload(), defaultIssue);
89         assertThat(pushEventDto.getLanguage()).isEqualTo("java");
90         assertThat(pushEventDto.getProjectUuid()).isEqualTo("some-project-uuid");
91       });
92
93   }
94
95   private static void verifyPayload(byte[] payload, DefaultIssue defaultIssue) {
96     assertThat(payload).isNotNull();
97
98     TaintVulnerabilityRaised taintVulnerabilityRaised = gson.fromJson(new String(payload, StandardCharsets.UTF_8),
99       TaintVulnerabilityRaised.class);
100     assertThat(taintVulnerabilityRaised.getProjectKey()).isEqualTo(defaultIssue.projectKey());
101     assertThat(taintVulnerabilityRaised.getCreationDate()).isEqualTo(defaultIssue.creationDate().getTime());
102     assertThat(taintVulnerabilityRaised.getKey()).isEqualTo(defaultIssue.key());
103     assertThat(taintVulnerabilityRaised.getSeverity()).isEqualTo(defaultIssue.severity());
104     assertThat(taintVulnerabilityRaised.getRuleKey()).isEqualTo(defaultIssue.ruleKey().toString());
105     assertThat(taintVulnerabilityRaised.getType()).isEqualTo(defaultIssue.type().name());
106     assertThat(taintVulnerabilityRaised.getBranch()).isEqualTo(BRANCH_NAME);
107     String ruleDescriptionContextKey = taintVulnerabilityRaised.getRuleDescriptionContextKey().orElseGet(() -> fail("No rule description " +
108       "context key"));
109     assertThat(ruleDescriptionContextKey).isEqualTo(defaultIssue.getRuleDescriptionContextKey().orElse(null));
110   }
111
112   @Test
113   public void raiseEventOnIssue_whenReopenedTaintVulnerability_shouldCreateRaisedEvent() {
114     DefaultIssue defaultIssue = createDefaultIssue()
115       .setChanged(true)
116       .setNew(false)
117       .setCopied(false)
118       .setCurrentChange(new FieldDiffs().setDiff("status", "CLOSED", "OPEN"));
119
120     when(taintChecker.isTaintVulnerability(any())).thenReturn(true);
121
122     assertThat(underTest.raiseEventOnIssue("some-project-uuid", defaultIssue))
123       .isNotEmpty()
124       .hasValueSatisfying(pushEventDto -> {
125         assertThat(pushEventDto.getName()).isEqualTo("TaintVulnerabilityRaised");
126         assertThat(pushEventDto.getPayload()).isNotNull();
127       });
128   }
129
130   @Test
131   public void raiseEventOnIssue_whenTaintVulnerabilityStatusChange_shouldSkipEvent() {
132     DefaultIssue defaultIssue = createDefaultIssue()
133       .setChanged(true)
134       .setNew(false)
135       .setCopied(false)
136       .setCurrentChange(new FieldDiffs().setDiff("status", "OPEN", "FIXED"));
137
138     when(taintChecker.isTaintVulnerability(any())).thenReturn(true);
139
140     assertThat(underTest.raiseEventOnIssue("some-project-uuid", defaultIssue)).isEmpty();
141   }
142
143   @Test
144   public void raiseEventOnIssue_whenCopiedTaintVulnerability_shouldCreateRaisedEvent() {
145     DefaultIssue defaultIssue = createDefaultIssue()
146       .setCopied(true);
147
148     when(taintChecker.isTaintVulnerability(any())).thenReturn(true);
149
150     assertThat(underTest.raiseEventOnIssue("some-project-uuid", defaultIssue))
151       .isNotEmpty()
152       .hasValueSatisfying(pushEventDto -> {
153         assertThat(pushEventDto.getName()).isEqualTo("TaintVulnerabilityRaised");
154         assertThat(pushEventDto.getPayload()).isNotNull();
155       });
156   }
157
158   @Test
159   public void raiseEventOnIssue_whenClosedTaintVulnerability_shouldCreateClosedEvent() {
160     DefaultIssue defaultIssue = createDefaultIssue()
161       .setNew(false)
162       .setCopied(false)
163       .setBeingClosed(true);
164
165     when(taintChecker.isTaintVulnerability(any())).thenReturn(true);
166
167     assertThat(underTest.raiseEventOnIssue("some-project-uuid", defaultIssue))
168       .isNotEmpty()
169       .hasValueSatisfying(pushEventDto -> {
170         assertThat(pushEventDto.getName()).isEqualTo("TaintVulnerabilityClosed");
171         assertThat(pushEventDto.getPayload()).isNotNull();
172       });
173   }
174
175   @Test
176   public void raiseEventOnIssue_whenChangedTaintVulnerability_shouldSkipEvent() {
177     DefaultIssue defaultIssue = new DefaultIssue()
178       .setComponentUuid("issue-component-uuid")
179       .setNew(false)
180       .setCopied(false)
181       .setChanged(true)
182       .setType(RuleType.VULNERABILITY)
183       .setCreationDate(DateUtils.parseDate("2022-01-01"))
184       .setRuleKey(RuleKey.of("javasecurity", "S123"));
185
186     when(taintChecker.isTaintVulnerability(any())).thenReturn(true);
187
188     assertThat(underTest.raiseEventOnIssue("some-project-uuid", defaultIssue)).isEmpty();
189   }
190
191   @Test
192   public void raiseEventOnIssue_whenIssueNotFromTaintVulnerabilityRepository_shouldSkipEvent() {
193     DefaultIssue defaultIssue = new DefaultIssue()
194       .setComponentUuid("issue-component-uuid")
195       .setChanged(true)
196       .setType(RuleType.VULNERABILITY)
197       .setRuleKey(RuleKey.of("weirdrepo", "S123"));
198
199     when(taintChecker.isTaintVulnerability(any())).thenReturn(false);
200
201     assertThat(underTest.raiseEventOnIssue("some-project-uuid", defaultIssue)).isEmpty();
202
203     defaultIssue = new DefaultIssue()
204       .setComponentUuid("issue-component-uuid")
205       .setChanged(false)
206       .setNew(false)
207       .setBeingClosed(true)
208       .setType(RuleType.VULNERABILITY)
209       .setRuleKey(RuleKey.of("weirdrepo", "S123"));
210
211     assertThat(underTest.raiseEventOnIssue("some-project-uuid", defaultIssue)).isEmpty();
212   }
213
214   @Test
215   public void raiseEventOnIssue_whenIssueDoesNotHaveLocations_shouldSkipEvent() {
216     DefaultIssue defaultIssue = new DefaultIssue()
217       .setComponentUuid("issue-component-uuid")
218       .setChanged(true)
219       .setType(RuleType.VULNERABILITY)
220       .setRuleKey(RuleKey.of("javasecurity", "S123"));
221
222     when(taintChecker.isTaintVulnerability(any())).thenReturn(false);
223
224     assertThat(underTest.raiseEventOnIssue("some-project-uuid", defaultIssue)).isEmpty();
225   }
226
227   @Test
228   public void raiseEventOnIssue_whenNewHotspot_shouldCreateRaisedEvent() {
229     DefaultIssue defaultIssue = createDefaultIssue()
230       .setType(RuleType.SECURITY_HOTSPOT)
231       .setStatus(Issue.STATUS_TO_REVIEW)
232       .setNew(true)
233       .setRuleDescriptionContextKey(randomAlphabetic(6));
234
235     assertThat(underTest.raiseEventOnIssue("some-project-uuid", defaultIssue))
236       .isNotEmpty()
237       .hasValueSatisfying(pushEventDto -> {
238         assertThat(pushEventDto.getName()).isEqualTo(SecurityHotspotRaised.EVENT_NAME);
239         verifyHotspotRaisedEventPayload(pushEventDto.getPayload(), defaultIssue);
240         assertThat(pushEventDto.getLanguage()).isEqualTo("java");
241         assertThat(pushEventDto.getProjectUuid()).isEqualTo("some-project-uuid");
242       });
243   }
244
245   private static void verifyHotspotRaisedEventPayload(byte[] payload, DefaultIssue defaultIssue) {
246     assertThat(payload).isNotNull();
247
248     SecurityHotspotRaised event = gson.fromJson(new String(payload, StandardCharsets.UTF_8), SecurityHotspotRaised.class);
249     assertThat(event.getProjectKey()).isEqualTo(defaultIssue.projectKey());
250     assertThat(event.getCreationDate()).isEqualTo(defaultIssue.creationDate().getTime());
251     assertThat(event.getKey()).isEqualTo(defaultIssue.key());
252     assertThat(event.getRuleKey()).isEqualTo(defaultIssue.ruleKey().toString());
253     assertThat(event.getStatus()).isEqualTo(Issue.STATUS_TO_REVIEW);
254     assertThat(event.getVulnerabilityProbability()).isEqualTo("LOW");
255     assertThat(event.getMainLocation()).isNotNull();
256     assertThat(event.getBranch()).isEqualTo(BRANCH_NAME);
257     assertThat(event.getAssignee()).isEqualTo("some-user-login");
258   }
259
260   @Test
261   public void raiseEventOnIssue_whenReopenedHotspot_shouldCreateRaisedEvent() {
262     DefaultIssue defaultIssue = createDefaultIssue()
263       .setType(RuleType.SECURITY_HOTSPOT)
264       .setChanged(true)
265       .setNew(false)
266       .setCopied(false)
267       .setCurrentChange(new FieldDiffs().setDiff("status", "CLOSED", "TO_REVIEW"));
268
269     assertThat(underTest.raiseEventOnIssue("some-project-uuid", defaultIssue))
270       .isNotEmpty()
271       .hasValueSatisfying(pushEventDto -> {
272         assertThat(pushEventDto.getName()).isEqualTo(SecurityHotspotRaised.EVENT_NAME);
273         assertThat(pushEventDto.getPayload()).isNotNull();
274       });
275   }
276
277   @Test
278   public void raiseEventOnIssue_whenCopiedHotspot_shouldCreateRaisedEvent() {
279     DefaultIssue defaultIssue = createDefaultIssue()
280       .setType(RuleType.SECURITY_HOTSPOT)
281       .setCopied(true);
282
283     assertThat(underTest.raiseEventOnIssue("some-project-uuid", defaultIssue))
284       .isNotEmpty()
285       .hasValueSatisfying(pushEventDto -> {
286         assertThat(pushEventDto.getName()).isEqualTo(SecurityHotspotRaised.EVENT_NAME);
287         assertThat(pushEventDto.getPayload()).isNotNull();
288       });
289   }
290
291   @Test
292   public void raiseEventOnIssue_whenClosedHotspot_shouldCreateClosedEvent() {
293     DefaultIssue defaultIssue = createDefaultIssue()
294       .setType(RuleType.SECURITY_HOTSPOT)
295       .setNew(false)
296       .setCopied(false)
297       .setBeingClosed(true)
298       .setStatus(Issue.STATUS_CLOSED)
299       .setResolution(Issue.RESOLUTION_FIXED);
300
301     assertThat(underTest.raiseEventOnIssue("some-project-uuid", defaultIssue))
302       .isNotEmpty()
303       .hasValueSatisfying(pushEventDto -> {
304         assertThat(pushEventDto.getName()).isEqualTo(SecurityHotspotClosed.EVENT_NAME);
305         verifyHotspotClosedEventPayload(pushEventDto.getPayload(), defaultIssue);
306         assertThat(pushEventDto.getLanguage()).isEqualTo("java");
307         assertThat(pushEventDto.getProjectUuid()).isEqualTo("some-project-uuid");
308       });
309   }
310
311   private static void verifyHotspotClosedEventPayload(byte[] payload, DefaultIssue defaultIssue) {
312     assertThat(payload).isNotNull();
313
314     SecurityHotspotClosed event = gson.fromJson(new String(payload, StandardCharsets.UTF_8), SecurityHotspotClosed.class);
315     assertThat(event.getProjectKey()).isEqualTo(defaultIssue.projectKey());
316     assertThat(event.getKey()).isEqualTo(defaultIssue.key());
317     assertThat(event.getStatus()).isEqualTo(Issue.STATUS_CLOSED);
318     assertThat(event.getResolution()).isEqualTo(Issue.RESOLUTION_FIXED);
319     assertThat(event.getFilePath()).isEqualTo("component-name");
320   }
321
322   @Test
323   public void raiseEventOnIssue_whenChangedHotspot_shouldSkipEvent() {
324     DefaultIssue defaultIssue = createDefaultIssue()
325       .setType(RuleType.SECURITY_HOTSPOT)
326       .setChanged(true)
327       .setNew(false)
328       .setCopied(false);
329
330     assertThat(underTest.raiseEventOnIssue("some-project-uuid", defaultIssue)).isEmpty();
331   }
332
333   @Test
334   public void raiseEventOnIssue_whenComponentUuidNull_shouldSkipEvent() {
335     DefaultIssue defaultIssue = createDefaultIssue()
336       .setComponentUuid(null);
337
338     assertThat(underTest.raiseEventOnIssue("some-project-uuid", defaultIssue)).isEmpty();
339   }
340
341   private void buildComponentTree() {
342     treeRootHolder.setRoot(ReportComponent.builder(Type.PROJECT, 1)
343       .setUuid("uuid_1")
344       .addChildren(ReportComponent.builder(Type.FILE, 2)
345         .setName("component-name")
346         .setUuid("issue-component-uuid")
347         .build())
348       .addChildren(ReportComponent.builder(Type.FILE, 3)
349         .setUuid("location-component-uuid")
350         .build())
351       .build());
352   }
353
354   private DefaultIssue createDefaultIssue() {
355     return new DefaultIssue()
356       .setKey("issue-key")
357       .setProjectKey("project-key")
358       .setComponentUuid("issue-component-uuid")
359       .setAssigneeUuid("some-user-uuid")
360       .setAssigneeLogin("some-user-login")
361       .setType(RuleType.VULNERABILITY)
362       .setLanguage("java")
363       .setCreationDate(new Date())
364       .setLocations(DbIssues.Locations.newBuilder()
365         .addFlow(DbIssues.Flow.newBuilder()
366           .addLocation(DbIssues.Location.newBuilder()
367             .setChecksum("checksum")
368             .setComponentId("location-component-uuid")
369             .build())
370           .build())
371         .setTextRange(DbCommons.TextRange.newBuilder()
372           .setStartLine(1)
373           .build())
374         .build())
375       .setRuleKey(RuleKey.of("javasecurity", "S123"));
376   }
377
378   private org.sonar.ce.task.projectanalysis.issue.Rule buildRule() {
379     RuleDto ruleDto = new RuleDto();
380     ruleDto.setRuleKey(RuleKey.of("javasecurity", "S123"));
381     ruleDto.setSecurityStandards(Set.of("owasp-a1"));
382     return new org.sonar.ce.task.projectanalysis.issue.RuleImpl(ruleDto);
383   }
384 }