]> source.dussan.org Git - sonarqube.git/blob
58c5e0ddeeafb4197ced2e009f9a4a2032f9252c
[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 com.google.gson.GsonBuilder;
24 import java.util.EnumMap;
25 import java.util.List;
26 import java.util.Map;
27 import java.util.Optional;
28 import java.util.Set;
29 import org.jetbrains.annotations.NotNull;
30 import org.sonar.api.ce.ComputeEngineSide;
31 import org.sonar.api.issue.impact.Severity;
32 import org.sonar.api.issue.impact.SoftwareQuality;
33 import org.sonar.api.rules.RuleType;
34 import org.sonar.ce.task.projectanalysis.analysis.AnalysisMetadataHolder;
35 import org.sonar.ce.task.projectanalysis.component.Component;
36 import org.sonar.ce.task.projectanalysis.component.TreeRootHolder;
37 import org.sonar.ce.task.projectanalysis.issue.Rule;
38 import org.sonar.ce.task.projectanalysis.issue.RuleRepository;
39 import org.sonar.ce.task.projectanalysis.locations.flow.FlowGenerator;
40 import org.sonar.ce.task.projectanalysis.locations.flow.Location;
41 import org.sonar.ce.task.projectanalysis.locations.flow.TextRange;
42 import org.sonar.core.issue.DefaultIssue;
43 import org.sonar.db.protobuf.DbCommons;
44 import org.sonar.db.protobuf.DbIssues;
45 import org.sonar.db.pushevent.PushEventDto;
46 import org.sonar.server.issue.TaintChecker;
47 import org.sonar.server.security.SecurityStandards;
48
49 import static java.nio.charset.StandardCharsets.UTF_8;
50 import static java.util.Objects.requireNonNull;
51 import static org.sonar.server.security.SecurityStandards.fromSecurityStandards;
52
53 @ComputeEngineSide
54 public class PushEventFactory {
55   private static final Gson GSON = new GsonBuilder().create();
56
57   private final TreeRootHolder treeRootHolder;
58   private final AnalysisMetadataHolder analysisMetadataHolder;
59   private final TaintChecker taintChecker;
60   private final FlowGenerator flowGenerator;
61   private final RuleRepository ruleRepository;
62
63   public PushEventFactory(TreeRootHolder treeRootHolder, AnalysisMetadataHolder analysisMetadataHolder, TaintChecker taintChecker,
64     FlowGenerator flowGenerator, RuleRepository ruleRepository) {
65     this.treeRootHolder = treeRootHolder;
66     this.analysisMetadataHolder = analysisMetadataHolder;
67     this.taintChecker = taintChecker;
68     this.flowGenerator = flowGenerator;
69     this.ruleRepository = ruleRepository;
70   }
71
72   public Optional<PushEventDto> raiseEventOnIssue(String projectUuid, DefaultIssue currentIssue) {
73     var currentIssueComponentUuid = currentIssue.componentUuid();
74     if (currentIssueComponentUuid == null) {
75       return Optional.empty();
76     }
77
78     var component = treeRootHolder.getComponentByUuid(currentIssueComponentUuid);
79
80     if (isTaintVulnerability(currentIssue)) {
81       return raiseTaintVulnerabilityEvent(projectUuid, component, currentIssue);
82     }
83     if (isSecurityHotspot(currentIssue)) {
84       return raiseSecurityHotspotEvent(projectUuid, component, currentIssue);
85     }
86     return Optional.empty();
87   }
88
89   private boolean isTaintVulnerability(DefaultIssue issue) {
90     return taintChecker.isTaintVulnerability(issue);
91   }
92
93   private static boolean isSecurityHotspot(DefaultIssue issue) {
94     return RuleType.SECURITY_HOTSPOT.equals(issue.type());
95   }
96
97   private Optional<PushEventDto> raiseTaintVulnerabilityEvent(String projectUuid, Component component, DefaultIssue issue) {
98     if (shouldCreateRaisedEvent(issue)) {
99       return Optional.of(raiseTaintVulnerabilityRaisedEvent(projectUuid, component, issue));
100     }
101     if (issue.isBeingClosed()) {
102       return Optional.of(raiseTaintVulnerabilityClosedEvent(projectUuid, issue));
103     }
104     return Optional.empty();
105   }
106
107   private Optional<PushEventDto> raiseSecurityHotspotEvent(String projectUuid, Component component, DefaultIssue issue) {
108     if (shouldCreateRaisedEvent(issue)) {
109       return Optional.of(raiseSecurityHotspotRaisedEvent(projectUuid, component, issue));
110     }
111     if (issue.isBeingClosed()) {
112       return Optional.of(raiseSecurityHotspotClosedEvent(projectUuid, component, issue));
113     }
114     return Optional.empty();
115   }
116
117   private static boolean shouldCreateRaisedEvent(DefaultIssue issue) {
118     return issue.isNew() || issue.isCopied() || isReopened(issue);
119   }
120
121   private static boolean isReopened(DefaultIssue currentIssue) {
122     var currentChange = currentIssue.currentChange();
123     if (currentChange == null) {
124       return false;
125     }
126     var status = currentChange.get("status");
127     return status != null && Set.of("CLOSED|OPEN", "CLOSED|TO_REVIEW").contains(status.toString());
128   }
129
130   private PushEventDto raiseTaintVulnerabilityRaisedEvent(String projectUuid, Component component, DefaultIssue issue) {
131     TaintVulnerabilityRaised event = prepareEvent(component, issue);
132     return createPushEventDto(projectUuid, issue, event);
133   }
134
135   private TaintVulnerabilityRaised prepareEvent(Component component, DefaultIssue issue) {
136     TaintVulnerabilityRaised event = new TaintVulnerabilityRaised();
137     event.setProjectKey(issue.projectKey());
138     event.setCreationDate(issue.creationDate().getTime());
139     event.setKey(issue.key());
140     event.setSeverity(issue.severity());
141     event.setRuleKey(issue.getRuleKey().toString());
142     event.setType(issue.type().name());
143     event.setBranch(analysisMetadataHolder.getBranch().getName());
144     event.setMainLocation(prepareMainLocation(component, issue));
145     event.setFlows(flowGenerator.convertFlows(component.getName(), requireNonNull(issue.getLocations())));
146     issue.getRuleDescriptionContextKey().ifPresent(event::setRuleDescriptionContextKey);
147
148     Rule rule = ruleRepository.getByKey(issue.getRuleKey());
149     event.setCleanCodeAttribute(rule.cleanCodeAttribute().name());
150     event.setCleanCodeAttributeCategory(rule.cleanCodeAttribute().getAttributeCategory().name());
151     event.setImpacts(computeEffectiveImpacts(rule.getDefaultImpacts(), issue.impacts()));
152     return event;
153   }
154
155   private static List<TaintVulnerabilityRaised.Impact> computeEffectiveImpacts(Map<SoftwareQuality, Severity> defaultImpacts, Map<SoftwareQuality, Severity> impacts) {
156     Map<SoftwareQuality, Severity> impactMap = new EnumMap<>(defaultImpacts);
157     impacts.forEach((softwareQuality, severity) -> impactMap.computeIfPresent(softwareQuality, (existingSoftwareQuality, existingSeverity) -> severity));
158     return impactMap.entrySet().stream()
159       .map(e -> {
160         TaintVulnerabilityRaised.Impact impact = new TaintVulnerabilityRaised.Impact();
161         impact.setSoftwareQuality(e.getKey().name());
162         impact.setSeverity(e.getValue().name());
163         return impact;
164       }).toList();
165   }
166
167   private static Location prepareMainLocation(Component component, DefaultIssue issue) {
168     DbIssues.Locations issueLocations = requireNonNull(issue.getLocations());
169     TextRange mainLocationTextRange = getTextRange(issueLocations.getTextRange(), issueLocations.getChecksum());
170
171     Location mainLocation = new Location();
172     Optional.ofNullable(issue.getMessage()).ifPresent(mainLocation::setMessage);
173     mainLocation.setFilePath(component.getName());
174     mainLocation.setTextRange(mainLocationTextRange);
175     return mainLocation;
176   }
177
178   private static PushEventDto createPushEventDto(String projectUuid, DefaultIssue issue, IssueEvent event) {
179     return new PushEventDto()
180       .setName(event.getEventName())
181       .setProjectUuid(projectUuid)
182       .setLanguage(issue.language())
183       .setPayload(serializeEvent(event));
184   }
185
186   private static PushEventDto raiseTaintVulnerabilityClosedEvent(String projectUuid, DefaultIssue issue) {
187     TaintVulnerabilityClosed event = new TaintVulnerabilityClosed(issue.key(), issue.projectKey());
188     return createPushEventDto(projectUuid, issue, event);
189   }
190
191   private PushEventDto raiseSecurityHotspotRaisedEvent(String projectUuid, Component component, DefaultIssue issue) {
192     SecurityHotspotRaised event = new SecurityHotspotRaised();
193     event.setKey(issue.key());
194     event.setProjectKey(issue.projectKey());
195     event.setStatus(issue.getStatus());
196     event.setCreationDate(issue.creationDate().getTime());
197     event.setMainLocation(prepareMainLocation(component, issue));
198     event.setRuleKey(issue.getRuleKey().toString());
199     event.setVulnerabilityProbability(getVulnerabilityProbability(issue));
200     event.setBranch(analysisMetadataHolder.getBranch().getName());
201     event.setAssignee(issue.assigneeLogin());
202
203     return createPushEventDto(projectUuid, issue, event);
204   }
205
206   private String getVulnerabilityProbability(DefaultIssue issue) {
207     Rule rule = ruleRepository.getByKey(issue.getRuleKey());
208     SecurityStandards.SQCategory sqCategory = fromSecurityStandards(rule.getSecurityStandards()).getSqCategory();
209     return sqCategory.getVulnerability().name();
210   }
211
212   private static PushEventDto raiseSecurityHotspotClosedEvent(String projectUuid, Component component, DefaultIssue issue) {
213     SecurityHotspotClosed event = new SecurityHotspotClosed();
214     event.setKey(issue.key());
215     event.setProjectKey(issue.projectKey());
216     event.setStatus(issue.getStatus());
217     event.setResolution(issue.resolution());
218     event.setFilePath(component.getName());
219
220     return createPushEventDto(projectUuid, issue, event);
221   }
222
223   private static byte[] serializeEvent(IssueEvent event) {
224     return GSON.toJson(event).getBytes(UTF_8);
225   }
226
227   @NotNull
228   private static TextRange getTextRange(DbCommons.TextRange source, String checksum) {
229     TextRange textRange = new TextRange();
230     textRange.setStartLine(source.getStartLine());
231     textRange.setStartLineOffset(source.getStartOffset());
232     textRange.setEndLine(source.getEndLine());
233     textRange.setEndLineOffset(source.getEndOffset());
234     textRange.setHash(checksum);
235     return textRange;
236   }
237
238 }