@@ -279,12 +279,13 @@ public final class ProjectAnalysisTaskContainerPopulator implements ContainerPop | |||
IssuesRepositoryVisitor.class, | |||
RemoveProcessedComponentsVisitor.class, | |||
IssueOnReferenceBranchVisitor.class, | |||
TaintVulnerabilityVisitor.class, | |||
// visitors : order is important, measure computers must be executed at the end in order to access to every measures / issues | |||
AnalysisFromSonarQube94Visitor.class, | |||
LoadComponentUuidsHavingOpenIssuesVisitor.class, | |||
IntegrateIssuesVisitor.class, | |||
TaintChecker.class, | |||
TaintVulnerabilityVisitor.class, | |||
CloseIssuesOnRemovedComponentsVisitor.class, | |||
MaintainabilityMeasuresVisitor.class, | |||
NewMaintainabilityMeasuresVisitor.class, |
@@ -24,8 +24,12 @@ import java.util.Set; | |||
import org.sonar.ce.task.projectanalysis.component.Component; | |||
import org.sonar.ce.task.projectanalysis.component.CrawlerDepthLimit; | |||
import org.sonar.ce.task.projectanalysis.component.TypeAwareVisitorAdapter; | |||
import org.sonar.ce.task.projectanalysis.pushevent.PushEvent; | |||
import org.sonar.ce.task.projectanalysis.pushevent.PushEventRepository; | |||
import org.sonar.ce.task.projectanalysis.pushevent.TaintVulnerabilityClosed; | |||
import org.sonar.ce.task.projectanalysis.util.cache.DiskCache.CacheAppender; | |||
import org.sonar.core.issue.DefaultIssue; | |||
import org.sonar.server.issue.TaintChecker; | |||
import static org.sonar.ce.task.projectanalysis.component.ComponentVisitor.Order.POST_ORDER; | |||
@@ -38,14 +42,19 @@ public class CloseIssuesOnRemovedComponentsVisitor extends TypeAwareVisitorAdapt | |||
private final ComponentsWithUnprocessedIssues componentsWithUnprocessedIssues; | |||
private final ProtoIssueCache protoIssueCache; | |||
private final IssueLifecycle issueLifecycle; | |||
private final PushEventRepository pushEventRepository; | |||
private final TaintChecker taintChecker; | |||
public CloseIssuesOnRemovedComponentsVisitor(ComponentIssuesLoader issuesLoader, ComponentsWithUnprocessedIssues componentsWithUnprocessedIssues, ProtoIssueCache protoIssueCache, | |||
IssueLifecycle issueLifecycle) { | |||
IssueLifecycle issueLifecycle, PushEventRepository pushEventRepository, TaintChecker taintChecker) { | |||
super(CrawlerDepthLimit.PROJECT, POST_ORDER); | |||
this.issuesLoader = issuesLoader; | |||
this.componentsWithUnprocessedIssues = componentsWithUnprocessedIssues; | |||
this.protoIssueCache = protoIssueCache; | |||
this.issueLifecycle = issueLifecycle; | |||
this.pushEventRepository = pushEventRepository; | |||
this.taintChecker = taintChecker; | |||
} | |||
@Override | |||
@@ -63,8 +72,18 @@ public class CloseIssuesOnRemovedComponentsVisitor extends TypeAwareVisitorAdapt | |||
issue.setOnDisabledRule(false); | |||
issueLifecycle.doAutomaticTransition(issue); | |||
cacheAppender.append(issue); | |||
addPushEventIfTaintVulnerability(issue); | |||
} | |||
} | |||
} | |||
} | |||
private void addPushEventIfTaintVulnerability(DefaultIssue issue) { | |||
if (taintChecker.isTaintVulnerability(issue)) { | |||
TaintVulnerabilityClosed event = new TaintVulnerabilityClosed(issue.key(), issue.projectKey()); | |||
PushEvent<?> pushEvent = new PushEvent<TaintVulnerabilityClosed>().setName("TaintVulnerabilityClosed").setData(event); | |||
pushEventRepository.add(pushEvent); | |||
} | |||
} | |||
} |
@@ -0,0 +1,52 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2022 SonarSource SA | |||
* mailto:info AT sonarsource DOT com | |||
* | |||
* This program is free software; you can redistribute it and/or | |||
* modify it under the terms of the GNU Lesser General Public | |||
* License as published by the Free Software Foundation; either | |||
* version 3 of the License, or (at your option) any later version. | |||
* | |||
* This program is distributed in the hope that it will be useful, | |||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |||
* Lesser General Public License for more details. | |||
* | |||
* You should have received a copy of the GNU Lesser General Public License | |||
* along with this program; if not, write to the Free Software Foundation, | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
package org.sonar.ce.task.projectanalysis.pushevent; | |||
public class TaintVulnerabilityClosed { | |||
private String key; | |||
private String projectKey; | |||
public TaintVulnerabilityClosed() { | |||
// nothing to do | |||
} | |||
public TaintVulnerabilityClosed(String key, String projectKey) { | |||
this.key = key; | |||
this.projectKey = projectKey; | |||
} | |||
public String getKey() { | |||
return key; | |||
} | |||
public void setKey(String key) { | |||
this.key = key; | |||
} | |||
public String getProjectKey() { | |||
return projectKey; | |||
} | |||
public void setProjectKey(String projectKey) { | |||
this.projectKey = projectKey; | |||
} | |||
} |
@@ -22,7 +22,6 @@ package org.sonar.ce.task.projectanalysis.pushevent; | |||
import java.util.LinkedList; | |||
import java.util.List; | |||
import org.jetbrains.annotations.NotNull; | |||
import org.sonar.api.rules.RuleType; | |||
import org.sonar.ce.task.projectanalysis.analysis.AnalysisMetadataHolder; | |||
import org.sonar.ce.task.projectanalysis.component.Component; | |||
import org.sonar.ce.task.projectanalysis.component.TreeRootHolder; | |||
@@ -53,13 +52,22 @@ public class TaintVulnerabilityVisitor extends IssueVisitor { | |||
@Override | |||
public void onIssue(Component component, DefaultIssue issue) { | |||
if ((issue.isNew() || issue.isCopied()) && isTaintVulnerability(issue)) { | |||
if (!taintChecker.isTaintVulnerability(issue)) { | |||
return; | |||
} | |||
if (issue.isNew() || issue.isCopied()) { | |||
PushEvent<?> pushEvent = raiseTaintVulnerabilityRaisedEvent(component, issue); | |||
pushEventRepository.add(pushEvent); | |||
return; | |||
} | |||
if (issue.isBeingClosed()) { | |||
PushEvent<?> pushEvent = raiseTaintVulnerabilityClosedEvent(issue); | |||
pushEventRepository.add(pushEvent); | |||
} | |||
} | |||
public PushEvent<TaintVulnerabilityRaised> raiseTaintVulnerabilityRaisedEvent(Component component, DefaultIssue issue) { | |||
private PushEvent<TaintVulnerabilityRaised> raiseTaintVulnerabilityRaisedEvent(Component component, DefaultIssue issue) { | |||
TaintVulnerabilityRaised event = prepareEvent(component, issue); | |||
return new PushEvent<TaintVulnerabilityRaised>().setName("TaintVulnerabilityRaised").setData(event); | |||
} | |||
@@ -108,6 +116,11 @@ public class TaintVulnerabilityVisitor extends IssueVisitor { | |||
return event; | |||
} | |||
private static PushEvent<TaintVulnerabilityClosed> raiseTaintVulnerabilityClosedEvent(DefaultIssue issue) { | |||
TaintVulnerabilityClosed event = new TaintVulnerabilityClosed(issue.key(), issue.projectKey()); | |||
return new PushEvent<TaintVulnerabilityClosed>().setName("TaintVulnerabilityClosed").setData(event); | |||
} | |||
@NotNull | |||
private static TextRange getTextRange(DbCommons.TextRange source, String checksum) { | |||
TextRange textRange = new TextRange(); | |||
@@ -118,10 +131,4 @@ public class TaintVulnerabilityVisitor extends IssueVisitor { | |||
textRange.setHash(checksum); | |||
return textRange; | |||
} | |||
private boolean isTaintVulnerability(DefaultIssue issue) { | |||
return taintChecker.getTaintRepositories().contains(issue.getRuleKey().repository()) | |||
&& issue.getLocations() != null | |||
&& !RuleType.SECURITY_HOTSPOT.equals(issue.type()); | |||
} | |||
} |
@@ -26,16 +26,22 @@ import org.junit.Before; | |||
import org.junit.Rule; | |||
import org.junit.Test; | |||
import org.junit.rules.TemporaryFolder; | |||
import org.mockito.ArgumentCaptor; | |||
import org.sonar.api.rule.RuleKey; | |||
import org.sonar.api.rules.RuleType; | |||
import org.sonar.api.utils.System2; | |||
import org.sonar.ce.task.projectanalysis.component.ReportComponent; | |||
import org.sonar.ce.task.projectanalysis.component.VisitorsCrawler; | |||
import org.sonar.ce.task.projectanalysis.pushevent.PushEvent; | |||
import org.sonar.ce.task.projectanalysis.pushevent.PushEventRepository; | |||
import org.sonar.ce.task.projectanalysis.pushevent.TaintVulnerabilityClosed; | |||
import org.sonar.core.issue.DefaultIssue; | |||
import org.sonar.core.util.CloseableIterator; | |||
import org.sonar.server.issue.TaintChecker; | |||
import static com.google.common.collect.Sets.newHashSet; | |||
import static org.assertj.core.api.Assertions.assertThat; | |||
import static org.mockito.ArgumentMatchers.any; | |||
import static org.mockito.Mockito.mock; | |||
import static org.mockito.Mockito.verify; | |||
import static org.mockito.Mockito.verifyNoInteractions; | |||
@@ -54,12 +60,15 @@ public class CloseIssuesOnRemovedComponentsVisitorTest { | |||
IssueLifecycle issueLifecycle = mock(IssueLifecycle.class); | |||
ProtoIssueCache protoIssueCache; | |||
VisitorsCrawler underTest; | |||
PushEventRepository pushEventRepository = mock(PushEventRepository.class); | |||
TaintChecker taintChecker = mock(TaintChecker.class); | |||
@Before | |||
public void setUp() throws Exception { | |||
protoIssueCache = new ProtoIssueCache(temp.newFile(), System2.INSTANCE); | |||
underTest = new VisitorsCrawler( | |||
Arrays.asList(new CloseIssuesOnRemovedComponentsVisitor(issuesLoader, componentsWithUnprocessedIssues, protoIssueCache, issueLifecycle))); | |||
Arrays.asList(new CloseIssuesOnRemovedComponentsVisitor(issuesLoader, componentsWithUnprocessedIssues, | |||
protoIssueCache, issueLifecycle, pushEventRepository, taintChecker))); | |||
} | |||
@Test | |||
@@ -71,10 +80,41 @@ public class CloseIssuesOnRemovedComponentsVisitorTest { | |||
DefaultIssue issue = new DefaultIssue().setKey(issueUuid).setType(RuleType.BUG).setCreationDate(new Date()) | |||
.setComponentKey("c").setProjectUuid("u").setProjectKey("k").setRuleKey(RuleKey.of("r", "r")).setStatus("OPEN"); | |||
when(issuesLoader.loadOpenIssues(fileUuid)).thenReturn(Collections.singletonList(issue)); | |||
when(taintChecker.isTaintVulnerability(any())).thenReturn(false); | |||
underTest.visit(ReportComponent.builder(PROJECT, 1).build()); | |||
verify(issueLifecycle).doAutomaticTransition(issue); | |||
verifyNoInteractions(pushEventRepository); | |||
CloseableIterator<DefaultIssue> issues = protoIssueCache.traverse(); | |||
assertThat(issues.hasNext()).isTrue(); | |||
DefaultIssue result = issues.next(); | |||
assertThat(result.key()).isEqualTo(issueUuid); | |||
assertThat(result.isBeingClosed()).isTrue(); | |||
assertThat(result.isOnDisabledRule()).isFalse(); | |||
} | |||
@Test | |||
public void close_taint_vulnerability() { | |||
String fileUuid = "FILE1"; | |||
String issueUuid = "ABCD"; | |||
when(componentsWithUnprocessedIssues.getUuids()).thenReturn(newHashSet(fileUuid)); | |||
DefaultIssue issue = new DefaultIssue().setKey(issueUuid).setType(RuleType.BUG).setCreationDate(new Date()) | |||
.setComponentKey("c").setProjectUuid("u").setProjectKey("k").setRuleKey(RuleKey.of("r", "r")).setStatus("OPEN"); | |||
when(issuesLoader.loadOpenIssues(fileUuid)).thenReturn(Collections.singletonList(issue)); | |||
when(taintChecker.isTaintVulnerability(any())).thenReturn(true); | |||
underTest.visit(ReportComponent.builder(PROJECT, 1).build()); | |||
verify(issueLifecycle).doAutomaticTransition(issue); | |||
ArgumentCaptor<PushEvent<TaintVulnerabilityClosed>> pushEventCaptor = ArgumentCaptor.forClass(PushEvent.class); | |||
verify(pushEventRepository).add(pushEventCaptor.capture()); | |||
PushEvent<TaintVulnerabilityClosed> pushEvent = pushEventCaptor.getValue(); | |||
assertThat(pushEvent.getName()).isEqualTo("TaintVulnerabilityClosed"); | |||
CloseableIterator<DefaultIssue> issues = protoIssueCache.traverse(); | |||
assertThat(issues.hasNext()).isTrue(); | |||
@@ -0,0 +1,45 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2022 SonarSource SA | |||
* mailto:info AT sonarsource DOT com | |||
* | |||
* This program is free software; you can redistribute it and/or | |||
* modify it under the terms of the GNU Lesser General Public | |||
* License as published by the Free Software Foundation; either | |||
* version 3 of the License, or (at your option) any later version. | |||
* | |||
* This program is distributed in the hope that it will be useful, | |||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |||
* Lesser General Public License for more details. | |||
* | |||
* You should have received a copy of the GNU Lesser General Public License | |||
* along with this program; if not, write to the Free Software Foundation, | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
package org.sonar.ce.task.projectanalysis.pushevent; | |||
import org.junit.Test; | |||
import static org.assertj.core.api.Assertions.assertThat; | |||
import static org.junit.Assert.*; | |||
public class TaintVulnerabilityClosedTest { | |||
@Test | |||
public void testConstructor_Getters_Setters() { | |||
TaintVulnerabilityClosed underTest = new TaintVulnerabilityClosed(); | |||
assertThat(underTest.getKey()).isNull(); | |||
assertThat(underTest.getProjectKey()).isNull(); | |||
underTest = new TaintVulnerabilityClosed("issue-key", "project-key"); | |||
assertThat(underTest.getKey()).isEqualTo("issue-key"); | |||
assertThat(underTest.getProjectKey()).isEqualTo("project-key"); | |||
underTest.setKey("another-issue-key"); | |||
assertThat(underTest.getKey()).isEqualTo("another-issue-key"); | |||
underTest.setProjectKey("another-project-key"); | |||
assertThat(underTest.getProjectKey()).isEqualTo("another-project-key"); | |||
} | |||
} |
@@ -27,6 +27,7 @@ import org.junit.Test; | |||
import org.mockito.ArgumentMatcher; | |||
import org.sonar.api.rule.RuleKey; | |||
import org.sonar.api.rules.RuleType; | |||
import org.sonar.api.utils.DateUtils; | |||
import org.sonar.ce.task.projectanalysis.analysis.AnalysisMetadataHolderRule; | |||
import org.sonar.ce.task.projectanalysis.analysis.TestBranch; | |||
import org.sonar.ce.task.projectanalysis.component.Component; | |||
@@ -65,6 +66,7 @@ public class TaintVulnerabilityVisitorTest { | |||
public void setUp() { | |||
when(taintChecker.getTaintRepositories()).thenReturn(List.of("roslyn.sonaranalyzer.security.cs", | |||
"javasecurity", "jssecurity", "tssecurity", "phpsecurity", "pythonsecurity")); | |||
when(taintChecker.isTaintVulnerability(any())).thenReturn(true); | |||
} | |||
@Test | |||
@@ -91,13 +93,30 @@ public class TaintVulnerabilityVisitorTest { | |||
verify(repositoryMock).add(argThat(PushEventMatcher.eq(new PushEvent<>().setName("TaintVulnerabilityRaised")))); | |||
} | |||
@Test | |||
public void add_event_to_repository_if_taint_vulnerability_is_closed() { | |||
buildComponentTree(); | |||
Component component = createIssueComponent(); | |||
DefaultIssue defaultIssue = createDefaultIssue() | |||
.setNew(false) | |||
.setCopied(false) | |||
.setBeingClosed(true); | |||
underTest.onIssue(component, defaultIssue); | |||
verify(repositoryMock).add(argThat(PushEventMatcher.eq(new PushEvent<>().setName("TaintVulnerabilityClosed")))); | |||
} | |||
@Test | |||
public void skip_issue_if_issue_changed() { | |||
Component component = createIssueComponent(); | |||
DefaultIssue defaultIssue = new DefaultIssue() | |||
.setNew(false) | |||
.setCopied(false) | |||
.setChanged(true) | |||
.setType(RuleType.VULNERABILITY) | |||
.setCreationDate(DateUtils.parseDate("2022-01-01")) | |||
.setRuleKey(RuleKey.of("javasecurity", "S123")); | |||
underTest.onIssue(component, defaultIssue); | |||
@@ -114,6 +133,19 @@ public class TaintVulnerabilityVisitorTest { | |||
.setType(RuleType.VULNERABILITY) | |||
.setRuleKey(RuleKey.of("weirdrepo", "S123")); | |||
when(taintChecker.isTaintVulnerability(any())).thenReturn(false); | |||
underTest.onIssue(component, defaultIssue); | |||
verify(repositoryMock, times(0)).add(any(PushEvent.class)); | |||
defaultIssue = new DefaultIssue() | |||
.setChanged(false) | |||
.setNew(false) | |||
.setBeingClosed(true) | |||
.setType(RuleType.VULNERABILITY) | |||
.setRuleKey(RuleKey.of("weirdrepo", "S123")); | |||
underTest.onIssue(component, defaultIssue); | |||
verify(repositoryMock, times(0)).add(any(PushEvent.class)); | |||
@@ -128,6 +160,8 @@ public class TaintVulnerabilityVisitorTest { | |||
.setType(RuleType.SECURITY_HOTSPOT) | |||
.setRuleKey(RuleKey.of("javasecurity", "S123")); | |||
when(taintChecker.isTaintVulnerability(any())).thenReturn(false); | |||
underTest.onIssue(component, defaultIssue); | |||
verify(repositoryMock, times(0)).add(any(PushEvent.class)); | |||
@@ -142,6 +176,8 @@ public class TaintVulnerabilityVisitorTest { | |||
.setType(RuleType.VULNERABILITY) | |||
.setRuleKey(RuleKey.of("javasecurity", "S123")); | |||
when(taintChecker.isTaintVulnerability(any())).thenReturn(false); | |||
underTest.onIssue(component, defaultIssue); | |||
verify(repositoryMock, times(0)).add(any(PushEvent.class)); |
@@ -28,6 +28,8 @@ import java.util.function.Predicate; | |||
import java.util.stream.Collectors; | |||
import org.jetbrains.annotations.NotNull; | |||
import org.sonar.api.config.Configuration; | |||
import org.sonar.api.rules.RuleType; | |||
import org.sonar.core.issue.DefaultIssue; | |||
import org.sonar.db.issue.IssueDto; | |||
public class TaintChecker { | |||
@@ -88,4 +90,11 @@ public class TaintChecker { | |||
return repositories; | |||
} | |||
public boolean isTaintVulnerability(DefaultIssue issue) { | |||
return taintRepositories.contains(issue.getRuleKey().repository()) | |||
&& issue.getLocations() != null | |||
&& !RuleType.SECURITY_HOTSPOT.equals(issue.type()); | |||
} | |||
} |
@@ -22,9 +22,14 @@ package org.sonar.server.issue; | |||
import java.util.ArrayList; | |||
import java.util.List; | |||
import java.util.Map; | |||
import javax.annotation.Nullable; | |||
import org.junit.Test; | |||
import org.sonar.api.config.Configuration; | |||
import org.sonar.api.rules.RuleType; | |||
import org.sonar.core.issue.DefaultIssue; | |||
import org.sonar.db.issue.IssueDto; | |||
import org.sonar.db.protobuf.DbCommons; | |||
import org.sonar.db.protobuf.DbIssues; | |||
import static org.assertj.core.api.Assertions.assertThat; | |||
import static org.mockito.Mockito.mock; | |||
@@ -98,6 +103,29 @@ public class TaintCheckerTest { | |||
"tssecurity", "phpsecurity", "pythonsecurity", "extra-1", "extra-2"); | |||
} | |||
@Test | |||
public void test_isTaintVulnerability() { | |||
DefaultIssue taintWithoutLocation = createIssueWithRepository("noTaintIssue", "roslyn.sonaranalyzer.security.cs") | |||
.toDefaultIssue(); | |||
DefaultIssue taint = createIssueWithRepository("taintIssue", "roslyn.sonaranalyzer.security.cs") | |||
.setLocations(DbIssues.Locations.newBuilder() | |||
.setTextRange(DbCommons.TextRange.newBuilder().build()) | |||
.build()) | |||
.toDefaultIssue(); | |||
DefaultIssue issue = createIssueWithRepository("standardIssue", "java") | |||
.setLocations(DbIssues.Locations.newBuilder() | |||
.setTextRange(DbCommons.TextRange.newBuilder().build()) | |||
.build()) | |||
.toDefaultIssue(); | |||
DefaultIssue hotspot = createIssueWithRepository("hotspot", "roslyn.sonaranalyzer.security.cs", | |||
RuleType.SECURITY_HOTSPOT).toDefaultIssue(); | |||
assertThat(underTest.isTaintVulnerability(taintWithoutLocation)).isFalse(); | |||
assertThat(underTest.isTaintVulnerability(taint)).isTrue(); | |||
assertThat(underTest.isTaintVulnerability(issue)).isFalse(); | |||
assertThat(underTest.isTaintVulnerability(hotspot)).isFalse(); | |||
} | |||
private List<IssueDto> getIssues() { | |||
List<IssueDto> issues = new ArrayList<>(); | |||
@@ -116,9 +144,16 @@ public class TaintCheckerTest { | |||
} | |||
private IssueDto createIssueWithRepository(String issueKey, String repository) { | |||
return createIssueWithRepository(issueKey, repository, null); | |||
} | |||
private IssueDto createIssueWithRepository(String issueKey, String repository, @Nullable RuleType ruleType) { | |||
IssueDto issueDto = new IssueDto(); | |||
issueDto.setStatus("OPEN"); | |||
issueDto.setKee(issueKey); | |||
issueDto.setRuleKey(repository, "S1"); | |||
issueDto.setType(ruleType == null ? RuleType.VULNERABILITY : ruleType); | |||
return issueDto; | |||
} | |||