aboutsummaryrefslogtreecommitdiffstats
path: root/server
diff options
context:
space:
mode:
authorJulien HENRY <julien.henry@sonarsource.com>2018-06-13 10:34:09 +0200
committersonartech <sonartech@sonarsource.com>2018-07-04 09:31:04 +0200
commitb497380a7647d69e6c2cb486faccce34121da08b (patch)
treeda8eb32ecb3d969be7478b9a5007631b9a3eb4e7 /server
parent376544a07a2259a0399d7f2d179c3c1c42a443a9 (diff)
downloadsonarqube-b497380a7647d69e6c2cb486faccce34121da08b.tar.gz
sonarqube-b497380a7647d69e6c2cb486faccce34121da08b.zip
SONAR-10874 New issue transitions for security hotspots
Diffstat (limited to 'server')
-rw-r--r--server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/issue/IssueLifecycle.java4
-rw-r--r--server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/issue/RuleTypeCopier.java4
-rw-r--r--server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/issue/IssueLifecycleTest.java33
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/issue/AssignAction.java3
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/issue/SetSeverityAction.java11
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/issue/SetTypeAction.java7
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/issue/workflow/Function.java3
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/issue/workflow/FunctionExecutor.java7
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/issue/workflow/HasType.java45
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/issue/workflow/IsManualVulnerability.java37
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/issue/workflow/IsNotHotspotNorManualVulnerability.java34
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/issue/workflow/IssueWorkflow.java94
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/issue/workflow/SetType.java36
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/issue/ws/AssignAction.java2
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/issue/ws/DoTransitionAction.java6
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/issue/ws/SearchAction.java3
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/issue/ws/SearchResponseData.java2
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/issue/ws/SearchResponseFormat.java1
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/issue/ws/SearchResponseLoader.java18
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/issue/ws/SetTypeAction.java3
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/issue/SetSeverityActionTest.java11
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/issue/ws/AssignActionTest.java23
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/issue/ws/SetTypeActionTest.java13
-rw-r--r--server/sonar-web/src/main/js/app/types.ts1
-rw-r--r--server/sonar-web/src/main/js/apps/issues/conciseIssuesList/__tests__/ConciseIssue-test.tsx3
-rw-r--r--server/sonar-web/src/main/js/apps/issues/conciseIssuesList/__tests__/__snapshots__/ConciseIssue-test.tsx.snap1
-rw-r--r--server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/LineCode-test.tsx1
-rw-r--r--server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/LineIssuesIndicator-test.tsx1
-rw-r--r--server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/LineIssuesList-test.tsx1
-rw-r--r--server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/__snapshots__/LineCode-test.tsx.snap2
-rw-r--r--server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/__snapshots__/LineIssuesList-test.tsx.snap2
-rw-r--r--server/sonar-web/src/main/js/components/issue/components/IssueActionsBar.js5
-rw-r--r--server/sonar-web/src/main/js/components/issue/components/IssueMessage.js10
-rw-r--r--server/sonar-web/src/main/js/components/issue/components/IssueTitleBar.js1
-rw-r--r--server/sonar-web/src/main/js/components/issue/components/IssueType.js6
-rw-r--r--server/sonar-web/src/main/js/components/issue/components/__tests__/IssueMessage-test.js1
-rw-r--r--server/sonar-web/src/main/js/components/issue/components/__tests__/IssueTitleBar-test.js3
-rw-r--r--server/sonar-web/src/main/js/components/issue/components/__tests__/IssueType-test.js6
-rw-r--r--server/sonar-web/src/main/js/components/issue/components/__tests__/__snapshots__/IssueTitleBar-test.js.snap5
-rw-r--r--server/sonar-web/src/main/js/components/issue/types.js1
40 files changed, 374 insertions, 76 deletions
diff --git a/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/issue/IssueLifecycle.java b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/issue/IssueLifecycle.java
index c1166f7d55a..52855f4234b 100644
--- a/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/issue/IssueLifecycle.java
+++ b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/issue/IssueLifecycle.java
@@ -23,7 +23,6 @@ import com.google.common.annotations.VisibleForTesting;
import java.util.Date;
import java.util.Optional;
import org.sonar.api.issue.Issue;
-import org.sonar.api.rules.RuleType;
import org.sonar.core.issue.DefaultIssue;
import org.sonar.core.issue.DefaultIssueComment;
import org.sonar.core.issue.FieldDiffs;
@@ -132,9 +131,6 @@ public class IssueLifecycle {
public void mergeExistingOpenIssue(DefaultIssue raw, DefaultIssue base) {
raw.setKey(base.key());
raw.setNew(false);
- if (raw.type() == RuleType.SECURITY_HOTSPOT) {
- raw.setIsFromHotspot(true);
- }
copyFields(raw, base);
if (base.manualSeverity()) {
diff --git a/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/issue/RuleTypeCopier.java b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/issue/RuleTypeCopier.java
index de0a30cd9da..cecfee91350 100644
--- a/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/issue/RuleTypeCopier.java
+++ b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/issue/RuleTypeCopier.java
@@ -33,13 +33,13 @@ public class RuleTypeCopier extends IssueVisitor {
@Override
public void onIssue(Component component, DefaultIssue issue) {
+ Rule rule = ruleRepository.getByKey(issue.ruleKey());
if (issue.type() == null) {
- Rule rule = ruleRepository.getByKey(issue.ruleKey());
if (!rule.isExternal()) {
// rule type should never be null for rules created by plugins (non-external rules)
issue.setType(rule.getType());
}
}
- issue.setIsFromHotspot(issue.type() == RuleType.SECURITY_HOTSPOT);
+ issue.setIsFromHotspot(rule.getType() == RuleType.SECURITY_HOTSPOT);
}
}
diff --git a/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/issue/IssueLifecycleTest.java b/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/issue/IssueLifecycleTest.java
index 23de9153071..e76865e0c92 100644
--- a/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/issue/IssueLifecycleTest.java
+++ b/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/issue/IssueLifecycleTest.java
@@ -23,7 +23,6 @@ import com.google.common.collect.ImmutableMap;
import java.util.Date;
import org.junit.Rule;
import org.junit.Test;
-import org.sonar.api.rules.RuleType;
import org.sonar.api.utils.Duration;
import org.sonar.ce.task.projectanalysis.analysis.AnalysisMetadataHolderRule;
import org.sonar.ce.task.projectanalysis.analysis.Branch;
@@ -86,38 +85,6 @@ public class IssueLifecycleTest {
}
@Test
- public void set_fromHotspot_flag_for_existing_vulnerability() {
- DefaultIssue raw = new DefaultIssue()
- .setNew(true)
- .setKey("RAW_KEY")
- .setType(RuleType.SECURITY_HOTSPOT)
- .setCreationDate(parseDate("2015-10-01"))
- .setUpdateDate(parseDate("2015-10-02"))
- .setCloseDate(parseDate("2015-10-03"));
-
- DefaultIssue base = new DefaultIssue()
- .setKey("BASE_KEY")
- .setType(RuleType.VULNERABILITY)
- .setResolution(RESOLUTION_FIXED)
- .setStatus(STATUS_CLOSED)
- .setSeverity(BLOCKER)
- .setCreationDate(parseDate("2015-01-01"))
- .setUpdateDate(parseDate("2015-01-02"))
- .setCloseDate(parseDate("2015-01-03"));
-
- underTest.mergeExistingOpenIssue(raw, base);
-
- assertThat(raw.isNew()).isFalse();
- assertThat(raw.key()).isNotNull();
- assertThat(raw.key()).isEqualTo(base.key());
- assertThat(raw.creationDate()).isEqualTo(base.creationDate());
- assertThat(raw.updateDate()).isEqualTo(base.updateDate());
- assertThat(raw.closeDate()).isEqualTo(base.closeDate());
- assertThat(raw.type()).isEqualTo(RuleType.VULNERABILITY);
- assertThat(raw.isFromHotspot()).isTrue();
- }
-
- @Test
public void mergeIssueFromShortLivingBranch() {
DefaultIssue raw = new DefaultIssue()
.setKey("raw");
diff --git a/server/sonar-server/src/main/java/org/sonar/server/issue/AssignAction.java b/server/sonar-server/src/main/java/org/sonar/server/issue/AssignAction.java
index 90aa92e568b..5a2054cfab4 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/issue/AssignAction.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/issue/AssignAction.java
@@ -25,6 +25,7 @@ import java.util.Map;
import java.util.Set;
import javax.annotation.Nullable;
import org.sonar.api.issue.condition.IsUnResolved;
+import org.sonar.api.rules.RuleType;
import org.sonar.api.server.ServerSide;
import org.sonar.core.issue.DefaultIssue;
import org.sonar.db.DbClient;
@@ -51,7 +52,7 @@ public class AssignAction extends Action {
super(ASSIGN_KEY);
this.dbClient = dbClient;
this.issueFieldsSetter = issueFieldsSetter;
- super.setConditions(new IsUnResolved());
+ super.setConditions(new IsUnResolved(), issue -> ((DefaultIssue) issue).type() != RuleType.SECURITY_HOTSPOT);
}
@Override
diff --git a/server/sonar-server/src/main/java/org/sonar/server/issue/SetSeverityAction.java b/server/sonar-server/src/main/java/org/sonar/server/issue/SetSeverityAction.java
index 3b905d1011f..cc7fe251dd8 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/issue/SetSeverityAction.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/issue/SetSeverityAction.java
@@ -21,14 +21,16 @@ package org.sonar.server.issue;
import java.util.Collection;
import java.util.Map;
+import org.sonar.api.issue.Issue;
import org.sonar.api.issue.condition.IsUnResolved;
+import org.sonar.api.rules.RuleType;
import org.sonar.api.server.ServerSide;
-import org.sonar.api.web.UserRole;
import org.sonar.core.issue.DefaultIssue;
import org.sonar.server.user.UserSession;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Strings.isNullOrEmpty;
+import static org.sonar.api.web.UserRole.ISSUE_ADMIN;
@ServerSide
public class SetSeverityAction extends Action {
@@ -43,11 +45,12 @@ public class SetSeverityAction extends Action {
super(SET_SEVERITY_KEY);
this.issueUpdater = issueUpdater;
this.userSession = userSession;
- super.setConditions(new IsUnResolved(), issue -> isCurrentUserIssueAdmin(issue.projectUuid()));
+ super.setConditions(new IsUnResolved(), this::isCurrentUserIssueAdminOrSecurityAuditor);
}
- private boolean isCurrentUserIssueAdmin(String projectUuid) {
- return userSession.hasComponentUuidPermission(UserRole.ISSUE_ADMIN, projectUuid);
+ private boolean isCurrentUserIssueAdminOrSecurityAuditor(Issue issue) {
+ DefaultIssue defaultIssue = (DefaultIssue) issue;
+ return ((defaultIssue.type() != RuleType.SECURITY_HOTSPOT && userSession.hasComponentUuidPermission(ISSUE_ADMIN, issue.projectUuid())));
}
@Override
diff --git a/server/sonar-server/src/main/java/org/sonar/server/issue/SetTypeAction.java b/server/sonar-server/src/main/java/org/sonar/server/issue/SetTypeAction.java
index 193fe499247..8b6d7f67759 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/issue/SetTypeAction.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/issue/SetTypeAction.java
@@ -21,6 +21,7 @@ package org.sonar.server.issue;
import java.util.Collection;
import java.util.Map;
+import org.sonar.api.issue.Issue;
import org.sonar.api.issue.condition.IsUnResolved;
import org.sonar.api.rules.RuleType;
import org.sonar.api.web.UserRole;
@@ -42,11 +43,11 @@ public class SetTypeAction extends Action {
super(SET_TYPE_KEY);
this.issueUpdater = issueUpdater;
this.userSession = userSession;
- super.setConditions(new IsUnResolved(), issue -> isCurrentUserIssueAdmin(issue.projectUuid()));
+ super.setConditions(new IsUnResolved(), this::isCurrentUserIssueAdmin);
}
- private boolean isCurrentUserIssueAdmin(String projectUuid) {
- return userSession.hasComponentUuidPermission(UserRole.ISSUE_ADMIN, projectUuid);
+ private boolean isCurrentUserIssueAdmin(Issue issue) {
+ return !((DefaultIssue) issue).isFromHotspot() && userSession.hasComponentUuidPermission(UserRole.ISSUE_ADMIN, issue.projectUuid());
}
@Override
diff --git a/server/sonar-server/src/main/java/org/sonar/server/issue/workflow/Function.java b/server/sonar-server/src/main/java/org/sonar/server/issue/workflow/Function.java
index c289816ba5e..77d24bc593e 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/issue/workflow/Function.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/issue/workflow/Function.java
@@ -21,6 +21,7 @@ package org.sonar.server.issue.workflow;
import javax.annotation.Nullable;
import org.sonar.api.issue.Issue;
+import org.sonar.api.rules.RuleType;
import org.sonar.db.user.UserDto;
interface Function {
@@ -34,6 +35,8 @@ interface Function {
Context setCloseDate(boolean b);
Context setLine(@Nullable Integer line);
+
+ Context setType(@Nullable RuleType type);
}
void execute(Context context);
diff --git a/server/sonar-server/src/main/java/org/sonar/server/issue/workflow/FunctionExecutor.java b/server/sonar-server/src/main/java/org/sonar/server/issue/workflow/FunctionExecutor.java
index bb76fad9de2..d92fa575d80 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/issue/workflow/FunctionExecutor.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/issue/workflow/FunctionExecutor.java
@@ -22,6 +22,7 @@ package org.sonar.server.issue.workflow;
import javax.annotation.Nullable;
import org.sonar.api.ce.ComputeEngineSide;
import org.sonar.api.issue.Issue;
+import org.sonar.api.rules.RuleType;
import org.sonar.api.server.ServerSide;
import org.sonar.core.issue.DefaultIssue;
import org.sonar.core.issue.IssueChangeContext;
@@ -86,5 +87,11 @@ public class FunctionExecutor {
updater.setLine(issue, line);
return this;
}
+
+ @Override
+ public Function.Context setType(RuleType type) {
+ updater.setType(issue, type, changeContext);
+ return this;
+ }
}
}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/issue/workflow/HasType.java b/server/sonar-server/src/main/java/org/sonar/server/issue/workflow/HasType.java
new file mode 100644
index 00000000000..4767298a0e5
--- /dev/null
+++ b/server/sonar-server/src/main/java/org/sonar/server/issue/workflow/HasType.java
@@ -0,0 +1,45 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 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.server.issue.workflow;
+
+import java.util.EnumSet;
+import java.util.Set;
+import org.sonar.api.issue.Issue;
+import org.sonar.api.issue.condition.Condition;
+import org.sonar.api.rules.RuleType;
+import org.sonar.core.issue.DefaultIssue;
+
+import static java.util.Arrays.asList;
+
+public class HasType implements Condition {
+
+ private final Set<RuleType> types;
+
+ public HasType(RuleType first, RuleType... others) {
+ this.types = EnumSet.noneOf(RuleType.class);
+ this.types.add(first);
+ this.types.addAll(asList(others));
+ }
+
+ @Override
+ public boolean matches(Issue issue) {
+ return types.contains(((DefaultIssue) issue).type());
+ }
+}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/issue/workflow/IsManualVulnerability.java b/server/sonar-server/src/main/java/org/sonar/server/issue/workflow/IsManualVulnerability.java
new file mode 100644
index 00000000000..7eadb25831f
--- /dev/null
+++ b/server/sonar-server/src/main/java/org/sonar/server/issue/workflow/IsManualVulnerability.java
@@ -0,0 +1,37 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 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.server.issue.workflow;
+
+import org.sonar.api.issue.Issue;
+import org.sonar.api.issue.condition.Condition;
+import org.sonar.api.rules.RuleType;
+import org.sonar.core.issue.DefaultIssue;
+
+/**
+ * The vulnerability originally come from a hotspot that was moved to vulnerability by a security auditor.
+ */
+enum IsManualVulnerability implements Condition {
+ INSTANCE;
+
+ @Override
+ public boolean matches(Issue issue) {
+ return ((DefaultIssue) issue).type() == RuleType.VULNERABILITY && ((DefaultIssue) issue).isFromHotspot();
+ }
+}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/issue/workflow/IsNotHotspotNorManualVulnerability.java b/server/sonar-server/src/main/java/org/sonar/server/issue/workflow/IsNotHotspotNorManualVulnerability.java
new file mode 100644
index 00000000000..af9c359247c
--- /dev/null
+++ b/server/sonar-server/src/main/java/org/sonar/server/issue/workflow/IsNotHotspotNorManualVulnerability.java
@@ -0,0 +1,34 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 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.server.issue.workflow;
+
+import org.sonar.api.issue.Issue;
+import org.sonar.api.issue.condition.Condition;
+import org.sonar.api.rules.RuleType;
+import org.sonar.core.issue.DefaultIssue;
+
+enum IsNotHotspotNorManualVulnerability implements Condition {
+ INSTANCE;
+
+ @Override
+ public boolean matches(Issue issue) {
+ return ((DefaultIssue) issue).type() != RuleType.SECURITY_HOTSPOT && !((DefaultIssue) issue).isFromHotspot();
+ }
+}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/issue/workflow/IssueWorkflow.java b/server/sonar-server/src/main/java/org/sonar/server/issue/workflow/IssueWorkflow.java
index 0c22a8a0935..aec49fbf75e 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/issue/workflow/IssueWorkflow.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/issue/workflow/IssueWorkflow.java
@@ -26,6 +26,7 @@ import org.sonar.api.issue.DefaultTransitions;
import org.sonar.api.issue.Issue;
import org.sonar.api.issue.condition.HasResolution;
import org.sonar.api.issue.condition.NotCondition;
+import org.sonar.api.rules.RuleType;
import org.sonar.api.server.ServerSide;
import org.sonar.api.web.UserRole;
import org.sonar.core.issue.DefaultIssue;
@@ -57,52 +58,64 @@ public class IssueWorkflow implements Startable {
buildManualTransitions(builder);
buildAutomaticTransitions(builder);
+ buildSecurityHotspotTransitions(builder);
machine = builder.build();
}
private static void buildManualTransitions(StateMachine.Builder builder) {
- builder.transition(Transition.builder(DefaultTransitions.CONFIRM)
- .from(Issue.STATUS_OPEN).to(Issue.STATUS_CONFIRMED)
- .functions(new SetResolution(null))
- .build())
+ builder
+ .transition(Transition.builder(DefaultTransitions.CONFIRM)
+ .from(Issue.STATUS_OPEN).to(Issue.STATUS_CONFIRMED)
+ .conditions(IsNotHotspotNorManualVulnerability.INSTANCE)
+ .functions(new SetResolution(null))
+ .build())
.transition(Transition.builder(DefaultTransitions.CONFIRM)
.from(Issue.STATUS_REOPENED).to(Issue.STATUS_CONFIRMED)
+ .conditions(IsNotHotspotNorManualVulnerability.INSTANCE)
.functions(new SetResolution(null))
.build())
.transition(Transition.builder(DefaultTransitions.UNCONFIRM)
.from(Issue.STATUS_CONFIRMED).to(Issue.STATUS_REOPENED)
+ .conditions(IsNotHotspotNorManualVulnerability.INSTANCE)
.functions(new SetResolution(null))
.build())
.transition(Transition.builder(DefaultTransitions.RESOLVE)
.from(Issue.STATUS_OPEN).to(Issue.STATUS_RESOLVED)
+ .conditions(IsNotHotspotNorManualVulnerability.INSTANCE)
.functions(new SetResolution(Issue.RESOLUTION_FIXED))
.build())
.transition(Transition.builder(DefaultTransitions.RESOLVE)
.from(Issue.STATUS_REOPENED).to(Issue.STATUS_RESOLVED)
+ .conditions(IsNotHotspotNorManualVulnerability.INSTANCE)
.functions(new SetResolution(Issue.RESOLUTION_FIXED))
.build())
.transition(Transition.builder(DefaultTransitions.RESOLVE)
.from(Issue.STATUS_CONFIRMED).to(Issue.STATUS_RESOLVED)
+ .conditions(IsNotHotspotNorManualVulnerability.INSTANCE)
.functions(new SetResolution(Issue.RESOLUTION_FIXED))
.build())
.transition(Transition.builder(DefaultTransitions.REOPEN)
.from(Issue.STATUS_RESOLVED).to(Issue.STATUS_REOPENED)
+ .conditions(IsNotHotspotNorManualVulnerability.INSTANCE)
.functions(new SetResolution(null))
.build())
// resolve as false-positive
.transition(Transition.builder(DefaultTransitions.FALSE_POSITIVE)
.from(Issue.STATUS_OPEN).to(Issue.STATUS_RESOLVED)
+ .conditions(IsNotHotspotNorManualVulnerability.INSTANCE)
.functions(new SetResolution(Issue.RESOLUTION_FALSE_POSITIVE), UnsetAssignee.INSTANCE)
.requiredProjectPermission(UserRole.ISSUE_ADMIN)
.build())
.transition(Transition.builder(DefaultTransitions.FALSE_POSITIVE)
.from(Issue.STATUS_REOPENED).to(Issue.STATUS_RESOLVED)
+ .conditions(IsNotHotspotNorManualVulnerability.INSTANCE)
.functions(new SetResolution(Issue.RESOLUTION_FALSE_POSITIVE), UnsetAssignee.INSTANCE)
.requiredProjectPermission(UserRole.ISSUE_ADMIN)
.build())
.transition(Transition.builder(DefaultTransitions.FALSE_POSITIVE)
.from(Issue.STATUS_CONFIRMED).to(Issue.STATUS_RESOLVED)
+ .conditions(IsNotHotspotNorManualVulnerability.INSTANCE)
.functions(new SetResolution(Issue.RESOLUTION_FALSE_POSITIVE), UnsetAssignee.INSTANCE)
.requiredProjectPermission(UserRole.ISSUE_ADMIN)
.build())
@@ -110,19 +123,90 @@ public class IssueWorkflow implements Startable {
// resolve as won't fix
.transition(Transition.builder(DefaultTransitions.WONT_FIX)
.from(Issue.STATUS_OPEN).to(Issue.STATUS_RESOLVED)
+ .conditions(IsNotHotspotNorManualVulnerability.INSTANCE)
.functions(new SetResolution(Issue.RESOLUTION_WONT_FIX), UnsetAssignee.INSTANCE)
.requiredProjectPermission(UserRole.ISSUE_ADMIN)
.build())
.transition(Transition.builder(DefaultTransitions.WONT_FIX)
.from(Issue.STATUS_REOPENED).to(Issue.STATUS_RESOLVED)
+ .conditions(IsNotHotspotNorManualVulnerability.INSTANCE)
.functions(new SetResolution(Issue.RESOLUTION_WONT_FIX), UnsetAssignee.INSTANCE)
.requiredProjectPermission(UserRole.ISSUE_ADMIN)
.build())
.transition(Transition.builder(DefaultTransitions.WONT_FIX)
.from(Issue.STATUS_CONFIRMED).to(Issue.STATUS_RESOLVED)
+ .conditions(IsNotHotspotNorManualVulnerability.INSTANCE)
.functions(new SetResolution(Issue.RESOLUTION_WONT_FIX), UnsetAssignee.INSTANCE)
.requiredProjectPermission(UserRole.ISSUE_ADMIN)
.build());
+ }
+
+ private static void buildSecurityHotspotTransitions(StateMachine.Builder builder) {
+ builder
+ .transition(Transition.builder(DefaultTransitions.DETECT)
+ .from(Issue.STATUS_OPEN).to(Issue.STATUS_OPEN)
+ .conditions(new HasType(RuleType.SECURITY_HOTSPOT))
+ .functions(new SetType(RuleType.VULNERABILITY))
+ .requiredProjectPermission(UserRole.ISSUE_ADMIN) // TODO need to check new permission
+ .build())
+ .transition(Transition.builder(DefaultTransitions.DETECT)
+ .from(Issue.STATUS_REOPENED).to(Issue.STATUS_OPEN)
+ .conditions(new HasType(RuleType.SECURITY_HOTSPOT))
+ .functions(new SetType(RuleType.VULNERABILITY))
+ .requiredProjectPermission(UserRole.ISSUE_ADMIN) // TODO need to check new permission
+ .build())
+ .transition(Transition.builder(DefaultTransitions.DETECT)
+ .from(Issue.STATUS_RESOLVED).to(Issue.STATUS_OPEN)
+ .conditions(new HasType(RuleType.SECURITY_HOTSPOT), new HasResolution(Issue.RESOLUTION_WONT_FIX))
+ .functions(new SetType(RuleType.VULNERABILITY), new SetResolution(null))
+ .requiredProjectPermission(UserRole.ISSUE_ADMIN) // TODO need to check new permission
+ .build())
+ .transition(Transition.builder(DefaultTransitions.DISMISS)
+ .from(Issue.STATUS_OPEN).to(Issue.STATUS_REOPENED)
+ .conditions(IsManualVulnerability.INSTANCE)
+ .functions(new SetType(RuleType.SECURITY_HOTSPOT))
+ .requiredProjectPermission(UserRole.ISSUE_ADMIN) // TODO need to check new permission
+ .build())
+ .transition(Transition.builder(DefaultTransitions.REQUEST_REVIEW)
+ .from(Issue.STATUS_OPEN).to(Issue.STATUS_RESOLVED)
+ .conditions(IsManualVulnerability.INSTANCE)
+ .functions(new SetType(RuleType.SECURITY_HOTSPOT), new SetResolution(Issue.RESOLUTION_FIXED))
+ .build())
+ .transition(Transition.builder(DefaultTransitions.REQUEST_REVIEW)
+ .from(Issue.STATUS_REOPENED).to(Issue.STATUS_RESOLVED)
+ .conditions(IsManualVulnerability.INSTANCE)
+ .functions(new SetType(RuleType.SECURITY_HOTSPOT), new SetResolution(Issue.RESOLUTION_FIXED))
+ .build())
+ .transition(Transition.builder(DefaultTransitions.REJECT)
+ .from(Issue.STATUS_RESOLVED).to(Issue.STATUS_REOPENED)
+ .conditions(new HasType(RuleType.SECURITY_HOTSPOT), new HasResolution(Issue.RESOLUTION_FIXED))
+ .functions(new SetType(RuleType.VULNERABILITY), new SetResolution(null))
+ .requiredProjectPermission(UserRole.ISSUE_ADMIN) // TODO need to check new permission
+ .build())
+ .transition(Transition.builder(DefaultTransitions.ACCEPT)
+ .from(Issue.STATUS_RESOLVED).to(Issue.STATUS_RESOLVED)
+ .conditions(new HasType(RuleType.SECURITY_HOTSPOT), new HasResolution(Issue.RESOLUTION_FIXED))
+ .functions(new SetResolution(Issue.RESOLUTION_WONT_FIX))
+ .requiredProjectPermission(UserRole.ISSUE_ADMIN) // TODO need to check new permission
+ .build())
+ .transition(Transition.builder(DefaultTransitions.CLEAR)
+ .from(Issue.STATUS_OPEN).to(Issue.STATUS_RESOLVED)
+ .conditions(new HasType(RuleType.SECURITY_HOTSPOT))
+ .functions(new SetResolution(Issue.RESOLUTION_WONT_FIX))
+ .requiredProjectPermission(UserRole.ISSUE_ADMIN) // TODO need to check new permission
+ .build())
+ .transition(Transition.builder(DefaultTransitions.CLEAR)
+ .from(Issue.STATUS_REOPENED).to(Issue.STATUS_RESOLVED)
+ .conditions(new HasType(RuleType.SECURITY_HOTSPOT))
+ .functions(new SetResolution(Issue.RESOLUTION_WONT_FIX))
+ .requiredProjectPermission(UserRole.ISSUE_ADMIN) // TODO need to check new permission
+ .build())
+ .transition(Transition.builder(DefaultTransitions.REOPEN_HOTSPOT)
+ .from(Issue.STATUS_RESOLVED).to(Issue.STATUS_REOPENED)
+ .conditions(new HasType(RuleType.SECURITY_HOTSPOT), new HasResolution(Issue.RESOLUTION_WONT_FIX))
+ .functions(new SetResolution(null))
+ .requiredProjectPermission(UserRole.ISSUE_ADMIN) // TODO need to check new permission
+ .build());
}
@@ -157,7 +241,7 @@ public class IssueWorkflow implements Startable {
// Reopen issues that are marked as resolved but that are still alive.
.transition(Transition.builder("automaticreopen")
.from(Issue.STATUS_RESOLVED).to(Issue.STATUS_REOPENED)
- .conditions(new NotCondition(IsBeingClosed.INSTANCE), new HasResolution(Issue.RESOLUTION_FIXED))
+ .conditions(new NotCondition(IsBeingClosed.INSTANCE), new HasResolution(Issue.RESOLUTION_FIXED), IsNotHotspotNorManualVulnerability.INSTANCE)
.functions(new SetResolution(null), new SetCloseDate(false))
.automatic()
.build());
diff --git a/server/sonar-server/src/main/java/org/sonar/server/issue/workflow/SetType.java b/server/sonar-server/src/main/java/org/sonar/server/issue/workflow/SetType.java
new file mode 100644
index 00000000000..47f3487c613
--- /dev/null
+++ b/server/sonar-server/src/main/java/org/sonar/server/issue/workflow/SetType.java
@@ -0,0 +1,36 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 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.server.issue.workflow;
+
+import javax.annotation.Nullable;
+import org.sonar.api.rules.RuleType;
+
+public class SetType implements Function {
+ private final RuleType type;
+
+ public SetType(@Nullable RuleType type) {
+ this.type = type;
+ }
+
+ @Override
+ public void execute(Context context) {
+ context.setType(type);
+ }
+}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/issue/ws/AssignAction.java b/server/sonar-server/src/main/java/org/sonar/server/issue/ws/AssignAction.java
index ce8714515b8..a44ea87e12a 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/issue/ws/AssignAction.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/issue/ws/AssignAction.java
@@ -26,6 +26,7 @@ import java.util.Optional;
import javax.annotation.CheckForNull;
import javax.annotation.Nullable;
import org.apache.commons.lang.BooleanUtils;
+import org.sonar.api.rules.RuleType;
import org.sonar.api.server.ws.Change;
import org.sonar.api.server.ws.Request;
import org.sonar.api.server.ws.Response;
@@ -115,6 +116,7 @@ public class AssignAction implements IssuesWsAction {
try (DbSession dbSession = dbClient.openSession(false)) {
IssueDto issueDto = issueFinder.getByKey(dbSession, issueKey);
DefaultIssue issue = issueDto.toDefaultIssue();
+ checkArgument(issue.type() != RuleType.SECURITY_HOTSPOT,"It is not allowed to assign a security hotspot");
UserDto user = getUser(dbSession, login);
if (user != null) {
checkMembership(dbSession, issueDto, user);
diff --git a/server/sonar-server/src/main/java/org/sonar/server/issue/ws/DoTransitionAction.java b/server/sonar-server/src/main/java/org/sonar/server/issue/ws/DoTransitionAction.java
index 17c2e938dfb..37d5fda28f2 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/issue/ws/DoTransitionAction.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/issue/ws/DoTransitionAction.java
@@ -68,11 +68,13 @@ public class DoTransitionAction implements IssuesWsAction {
public void define(WebService.NewController controller) {
WebService.NewAction action = controller.createAction(ACTION_DO_TRANSITION)
.setDescription("Do workflow transition on an issue. Requires authentication and Browse permission on project.<br/>" +
- "The transitions '" + DefaultTransitions.WONT_FIX + "' and '" + DefaultTransitions.FALSE_POSITIVE + "' require the permission 'Administer Issues'.")
+ "The transitions '" + DefaultTransitions.WONT_FIX + "' and '" + DefaultTransitions.FALSE_POSITIVE + "' require the permission 'Administer Issues'.<br/>" +
+ "The transitions involving security hotspots (except '" + DefaultTransitions.REQUEST_REVIEW + "') require the permission 'Administer Security Hotspot'.")
.setSince("3.6")
.setChangelog(
new Change("6.5", "the database ids of the components are removed from the response"),
- new Change("6.5", "the response field components.uuid is deprecated. Use components.key instead."))
+ new Change("6.5", "the response field components.uuid is deprecated. Use components.key instead."),
+ new Change("7.3", "added transitions for security hotspots"))
.setHandler(this)
.setResponseExample(Resources.getResource(this.getClass(), "do_transition-example.json"))
.setPost(true);
diff --git a/server/sonar-server/src/main/java/org/sonar/server/issue/ws/SearchAction.java b/server/sonar-server/src/main/java/org/sonar/server/issue/ws/SearchAction.java
index 5fe1c04436f..f12eb753de2 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/issue/ws/SearchAction.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/issue/ws/SearchAction.java
@@ -173,7 +173,8 @@ public class SearchAction implements IssuesWsAction {
new Change("5.5", "parameters 'reporters', 'actionPlans' and 'planned' are dropped and therefore ignored (drop of action plan and manual issue features)"),
new Change("5.5", "response field 'debt' is renamed 'effort'"),
new Change("7.2", "response field 'externalRuleEngine' added to issues that have been imported from an external rule engine"),
- new Change("7.2", format("value '%s' in parameter '%s' is deprecated, it won't have any effect", SORT_BY_ASSIGNEE, Param.SORT)))
+ new Change("7.2", format("value '%s' in parameter '%s' is deprecated, it won't have any effect", SORT_BY_ASSIGNEE, Param.SORT)),
+ new Change("7.3", "response field 'fromHotspot' added to issues that are security hotspots"))
.setResponseExample(getClass().getResource("search-example.json"));
action.addPagingParams(100, MAX_LIMIT);
diff --git a/server/sonar-server/src/main/java/org/sonar/server/issue/ws/SearchResponseData.java b/server/sonar-server/src/main/java/org/sonar/server/issue/ws/SearchResponseData.java
index b5acc861c56..e39cbff71ef 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/issue/ws/SearchResponseData.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/issue/ws/SearchResponseData.java
@@ -144,7 +144,7 @@ public class SearchResponseData {
}
}
- public void addActions(String issueKey, List<String> actions) {
+ public void addActions(String issueKey, Iterable<String> actions) {
actionsByIssueKey.putAll(issueKey, actions);
}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/issue/ws/SearchResponseFormat.java b/server/sonar-server/src/main/java/org/sonar/server/issue/ws/SearchResponseFormat.java
index 4bfbc23ff8d..2bf22c04931 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/issue/ws/SearchResponseFormat.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/issue/ws/SearchResponseFormat.java
@@ -179,6 +179,7 @@ public class SearchResponseFormat {
if (dto.isExternal()) {
issueBuilder.setExternalRuleEngine(engineNameFrom(dto.getRuleKey()));
}
+ issueBuilder.setFromHotspot(dto.isFromHotspot());
issueBuilder.setSeverity(Common.Severity.valueOf(dto.getSeverity()));
setNullable(data.getUserByUuid(dto.getAssigneeUuid()), assignee -> issueBuilder.setAssignee(assignee.getLogin()));
setNullable(emptyToNull(dto.getResolution()), issueBuilder::setResolution);
diff --git a/server/sonar-server/src/main/java/org/sonar/server/issue/ws/SearchResponseLoader.java b/server/sonar-server/src/main/java/org/sonar/server/issue/ws/SearchResponseLoader.java
index e66f427cebb..dfe4fea1788 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/issue/ws/SearchResponseLoader.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/issue/ws/SearchResponseLoader.java
@@ -36,6 +36,7 @@ import java.util.stream.Collectors;
import java.util.stream.Stream;
import javax.annotation.Nullable;
import org.sonar.api.rule.RuleKey;
+import org.sonar.api.rules.RuleType;
import org.sonar.core.issue.DefaultIssue;
import org.sonar.core.util.stream.MoreCollectors;
import org.sonar.db.DbClient;
@@ -53,8 +54,8 @@ import org.sonarqube.ws.client.issue.IssuesWsParameters;
import static com.google.common.base.MoreObjects.firstNonNull;
import static com.google.common.collect.ImmutableSet.copyOf;
-import static com.google.common.collect.Lists.newArrayList;
import static com.google.common.collect.Sets.difference;
+import static com.google.common.collect.Sets.newHashSet;
import static java.util.Collections.emptyList;
import static java.util.Objects.requireNonNull;
import static java.util.Optional.ofNullable;
@@ -267,20 +268,25 @@ public class SearchResponseLoader {
}
}
- private List<String> listAvailableActions(IssueDto issue, ComponentDto project) {
- List<String> availableActions = newArrayList();
+ private Set<String> listAvailableActions(IssueDto issue, ComponentDto project) {
+ Set<String> availableActions = newHashSet();
String login = userSession.getLogin();
if (login == null) {
- return Collections.emptyList();
+ return Collections.emptySet();
}
+ RuleType ruleType = RuleType.valueOf(issue.getType());
availableActions.add(COMMENT_KEY);
if (issue.getResolution() != null) {
return availableActions;
}
- availableActions.add(ASSIGN_KEY);
+ if (ruleType != RuleType.SECURITY_HOTSPOT) {
+ availableActions.add(ASSIGN_KEY);
+ }
availableActions.add("set_tags");
- if (userSession.hasComponentPermission(ISSUE_ADMIN, project)) {
+ if (!issue.isFromHotspot() && userSession.hasComponentPermission(ISSUE_ADMIN, project)) {
availableActions.add(SET_TYPE_KEY);
+ }
+ if ((ruleType != RuleType.SECURITY_HOTSPOT && userSession.hasComponentPermission(ISSUE_ADMIN, project))) {
availableActions.add(SET_SEVERITY_KEY);
}
return availableActions;
diff --git a/server/sonar-server/src/main/java/org/sonar/server/issue/ws/SetTypeAction.java b/server/sonar-server/src/main/java/org/sonar/server/issue/ws/SetTypeAction.java
index 241b6be963f..5675ac9b730 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/issue/ws/SetTypeAction.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/issue/ws/SetTypeAction.java
@@ -106,6 +106,9 @@ public class SetTypeAction implements IssuesWsAction {
private SearchResponseData setType(DbSession session, String issueKey, RuleType ruleType) {
IssueDto issueDto = issueFinder.getByKey(session, issueKey);
DefaultIssue issue = issueDto.toDefaultIssue();
+ if (issue.isFromHotspot()) {
+ throw new IllegalArgumentException("Changing type of a security hotspot is not permitted");
+ }
userSession.checkComponentUuidPermission(ISSUE_ADMIN, issue.projectUuid());
IssueChangeContext context = IssueChangeContext.createUser(new Date(system2.now()), userSession.getUuid());
diff --git a/server/sonar-server/src/test/java/org/sonar/server/issue/SetSeverityActionTest.java b/server/sonar-server/src/test/java/org/sonar/server/issue/SetSeverityActionTest.java
index bc5aedea1d1..1a02b737dea 100644
--- a/server/sonar-server/src/test/java/org/sonar/server/issue/SetSeverityActionTest.java
+++ b/server/sonar-server/src/test/java/org/sonar/server/issue/SetSeverityActionTest.java
@@ -27,6 +27,7 @@ import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.sonar.api.issue.Issue;
+import org.sonar.api.rules.RuleType;
import org.sonar.core.issue.DefaultIssue;
import org.sonar.core.issue.FieldDiffs;
import org.sonar.core.issue.IssueChangeContext;
@@ -103,6 +104,16 @@ public class SetSeverityActionTest {
}
@Test
+ public void doesnt_support_security_hotspots() {
+ IssueDto issueDto = newIssue().setSeverity(MAJOR);
+ DefaultIssue issue = issueDto.toDefaultIssue();
+ setUserWithBrowseAndAdministerIssuePermission(issueDto);
+
+ assertThat(action.supports(issue.setType(RuleType.CODE_SMELL))).isTrue();
+ assertThat(action.supports(issue.setType(RuleType.SECURITY_HOTSPOT))).isFalse();
+ }
+
+ @Test
public void support_only_issues_with_issue_admin_permission() {
IssueDto authorizedIssue = newIssue().setSeverity(MAJOR);
setUserWithBrowseAndAdministerIssuePermission(authorizedIssue);
diff --git a/server/sonar-server/src/test/java/org/sonar/server/issue/ws/AssignActionTest.java b/server/sonar-server/src/test/java/org/sonar/server/issue/ws/AssignActionTest.java
index e9a1efd5cfe..fa8786037e6 100644
--- a/server/sonar-server/src/test/java/org/sonar/server/issue/ws/AssignActionTest.java
+++ b/server/sonar-server/src/test/java/org/sonar/server/issue/ws/AssignActionTest.java
@@ -24,6 +24,7 @@ import javax.annotation.Nullable;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
+import org.sonar.api.rules.RuleType;
import org.sonar.api.utils.internal.TestSystem2;
import org.sonar.db.DbClient;
import org.sonar.db.DbSession;
@@ -198,6 +199,21 @@ public class AssignActionTest {
}
@Test
+ public void fail_when_trying_to_assign_hotspot() {
+ IssueDto issueDto = db.issues().insertIssue(i -> i.setType(RuleType.SECURITY_HOTSPOT));
+ setUserWithBrowsePermission(issueDto);
+ UserDto arthur = insertUser("arthur");
+
+ expectedException.expect(IllegalArgumentException.class);
+ expectedException.expectMessage("It is not allowed to assign a security hotspot");
+
+ ws.newRequest()
+ .setParam("issue", issueDto.getKey())
+ .setParam("assignee", "arthur")
+ .execute();
+ }
+
+ @Test
public void fail_when_assignee_is_disabled() {
IssueDto issue = newIssueWithBrowsePermission();
db.users().insertUser(user -> user.setActive(false));
@@ -239,7 +255,7 @@ public class AssignActionTest {
@Test
public void fail_when_assignee_is_not_member_of_organization_of_project_issue() {
OrganizationDto org = db.organizations().insert(organizationDto -> organizationDto.setKey("Organization key"));
- IssueDto issueDto = db.issues().insertIssue(org);
+ IssueDto issueDto = db.issues().insertIssue(org, i -> i.setType(RuleType.CODE_SMELL));
setUserWithBrowsePermission(issueDto);
OrganizationDto otherOrganization = db.organizations().insert();
UserDto assignee = db.users().insertUser("arthur");
@@ -261,11 +277,12 @@ public class AssignActionTest {
}
private IssueDto newIssue(String assignee) {
- IssueDto issue = db.issues().insertIssue(
+ IssueDto issue = db.issues().insertIssue(
issueDto -> issueDto
.setAssigneeUuid(assignee)
.setCreatedAt(PAST).setIssueCreationTime(PAST)
- .setUpdatedAt(PAST).setIssueUpdateTime(PAST));
+ .setUpdatedAt(PAST).setIssueUpdateTime(PAST)
+ .setType(RuleType.CODE_SMELL));
return issue;
}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/issue/ws/SetTypeActionTest.java b/server/sonar-server/src/test/java/org/sonar/server/issue/ws/SetTypeActionTest.java
index d6fe59d9f61..710fa2131ab 100644
--- a/server/sonar-server/src/test/java/org/sonar/server/issue/ws/SetTypeActionTest.java
+++ b/server/sonar-server/src/test/java/org/sonar/server/issue/ws/SetTypeActionTest.java
@@ -64,6 +64,7 @@ import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import static org.sonar.api.rules.RuleType.BUG;
import static org.sonar.api.rules.RuleType.CODE_SMELL;
+import static org.sonar.api.rules.RuleType.VULNERABILITY;
import static org.sonar.api.web.UserRole.ISSUE_ADMIN;
import static org.sonar.api.web.UserRole.USER;
import static org.sonar.core.util.Protobuf.setNullable;
@@ -119,6 +120,18 @@ public class SetTypeActionTest {
}
@Test
+ public void prevent_changing_type_security_hotspot() {
+ long now = 1_999_777_234L;
+ when(system2.now()).thenReturn(now);
+ IssueDto issueDto = issueDbTester.insertIssue(newIssue().setType(VULNERABILITY).setIsFromHotspot(true));
+ setUserWithBrowseAndAdministerIssuePermission(issueDto);
+
+ expectedException.expect(IllegalArgumentException.class);
+ expectedException.expectMessage("Changing type of a security hotspot is not permitted");
+ call(issueDto.getKey(), BUG.name());
+ }
+
+ @Test
public void insert_entry_in_changelog_when_setting_type() {
IssueDto issueDto = issueDbTester.insertIssue(newIssue().setType(CODE_SMELL));
setUserWithBrowseAndAdministerIssuePermission(issueDto);
diff --git a/server/sonar-web/src/main/js/app/types.ts b/server/sonar-web/src/main/js/app/types.ts
index 75ac42d3237..15639fc96c3 100644
--- a/server/sonar-web/src/main/js/app/types.ts
+++ b/server/sonar-web/src/main/js/app/types.ts
@@ -231,6 +231,7 @@ export interface Issue {
fromExternalRule?: boolean;
key: string;
flows: FlowLocation[][];
+ fromHotspot: boolean;
line?: number;
message: string;
organization: string;
diff --git a/server/sonar-web/src/main/js/apps/issues/conciseIssuesList/__tests__/ConciseIssue-test.tsx b/server/sonar-web/src/main/js/apps/issues/conciseIssuesList/__tests__/ConciseIssue-test.tsx
index 639ab681d9b..549928cb0ea 100644
--- a/server/sonar-web/src/main/js/apps/issues/conciseIssuesList/__tests__/ConciseIssue-test.tsx
+++ b/server/sonar-web/src/main/js/apps/issues/conciseIssuesList/__tests__/ConciseIssue-test.tsx
@@ -40,7 +40,8 @@ const issue = {
status: '',
type: '',
secondaryLocations: [],
- flows: []
+ flows: [],
+ fromHotspot: false
};
it('should render', () => {
diff --git a/server/sonar-web/src/main/js/apps/issues/conciseIssuesList/__tests__/__snapshots__/ConciseIssue-test.tsx.snap b/server/sonar-web/src/main/js/apps/issues/conciseIssuesList/__tests__/__snapshots__/ConciseIssue-test.tsx.snap
index ee41d9fd306..744a5418ee6 100644
--- a/server/sonar-web/src/main/js/apps/issues/conciseIssuesList/__tests__/__snapshots__/ConciseIssue-test.tsx.snap
+++ b/server/sonar-web/src/main/js/apps/issues/conciseIssuesList/__tests__/__snapshots__/ConciseIssue-test.tsx.snap
@@ -14,6 +14,7 @@ exports[`should render 1`] = `
"componentUuid": "",
"creationDate": "",
"flows": Array [],
+ "fromHotspot": false,
"key": "",
"message": "",
"organization": "",
diff --git a/server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/LineCode-test.tsx b/server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/LineCode-test.tsx
index c9dc513d817..5fd8d5e434a 100644
--- a/server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/LineCode-test.tsx
+++ b/server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/LineCode-test.tsx
@@ -30,6 +30,7 @@ const issueBase: Issue = {
creationDate: '',
key: '',
flows: [],
+ fromHotspot: false,
message: '',
organization: '',
project: '',
diff --git a/server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/LineIssuesIndicator-test.tsx b/server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/LineIssuesIndicator-test.tsx
index b4fd9fc6e70..d63bbf1a2be 100644
--- a/server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/LineIssuesIndicator-test.tsx
+++ b/server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/LineIssuesIndicator-test.tsx
@@ -31,6 +31,7 @@ const issueBase: Issue = {
creationDate: '',
key: '',
flows: [],
+ fromHotspot: false,
message: '',
organization: '',
project: '',
diff --git a/server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/LineIssuesList-test.tsx b/server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/LineIssuesList-test.tsx
index 7e9c9ec5bbe..bd621904bb9 100644
--- a/server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/LineIssuesList-test.tsx
+++ b/server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/LineIssuesList-test.tsx
@@ -30,6 +30,7 @@ const issueBase: Issue = {
creationDate: '',
key: '',
flows: [],
+ fromHotspot: false,
message: '',
organization: '',
project: '',
diff --git a/server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/__snapshots__/LineCode-test.tsx.snap b/server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/__snapshots__/LineCode-test.tsx.snap
index dc0f5c92200..5546edb6645 100644
--- a/server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/__snapshots__/LineCode-test.tsx.snap
+++ b/server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/__snapshots__/LineCode-test.tsx.snap
@@ -53,6 +53,7 @@ exports[`render code 1`] = `
"componentUuid": "",
"creationDate": "",
"flows": Array [],
+ "fromHotspot": false,
"key": "issue-1",
"message": "",
"organization": "",
@@ -74,6 +75,7 @@ exports[`render code 1`] = `
"componentUuid": "",
"creationDate": "",
"flows": Array [],
+ "fromHotspot": false,
"key": "issue-2",
"message": "",
"organization": "",
diff --git a/server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/__snapshots__/LineIssuesList-test.tsx.snap b/server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/__snapshots__/LineIssuesList-test.tsx.snap
index d19a4e96ef0..2fa14dfb1b3 100644
--- a/server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/__snapshots__/LineIssuesList-test.tsx.snap
+++ b/server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/__snapshots__/LineIssuesList-test.tsx.snap
@@ -15,6 +15,7 @@ exports[`render issues list 1`] = `
"componentUuid": "",
"creationDate": "",
"flows": Array [],
+ "fromHotspot": false,
"key": "foo",
"message": "",
"organization": "",
@@ -47,6 +48,7 @@ exports[`render issues list 1`] = `
"componentUuid": "",
"creationDate": "",
"flows": Array [],
+ "fromHotspot": false,
"key": "bar",
"message": "",
"organization": "",
diff --git a/server/sonar-web/src/main/js/components/issue/components/IssueActionsBar.js b/server/sonar-web/src/main/js/components/issue/components/IssueActionsBar.js
index f12909c0f9a..10d0d20281b 100644
--- a/server/sonar-web/src/main/js/components/issue/components/IssueActionsBar.js
+++ b/server/sonar-web/src/main/js/components/issue/components/IssueActionsBar.js
@@ -89,6 +89,7 @@ export default class IssueActionsBar extends React.PureComponent {
const canAssign = issue.actions.includes('assign');
const canComment = issue.actions.includes('comment');
const canSetSeverity = issue.actions.includes('set_severity');
+ const canSetType = issue.actions.includes('set_type');
const canSetTags = issue.actions.includes('set_tags');
const hasTransitions = issue.transitions && issue.transitions.length > 0;
@@ -97,8 +98,8 @@ export default class IssueActionsBar extends React.PureComponent {
<ul className="issue-meta-list">
<li className="issue-meta">
<IssueType
- canSetSeverity={canSetSeverity}
- isOpen={this.props.currentPopup === 'set-type' && canSetSeverity}
+ canSetType={canSetType}
+ isOpen={this.props.currentPopup === 'set-type' && canSetType}
issue={issue}
setIssueProperty={this.setIssueProperty}
togglePopup={this.props.togglePopup}
diff --git a/server/sonar-web/src/main/js/components/issue/components/IssueMessage.js b/server/sonar-web/src/main/js/components/issue/components/IssueMessage.js
index 5b4e82d413b..6b20d3e9246 100644
--- a/server/sonar-web/src/main/js/components/issue/components/IssueMessage.js
+++ b/server/sonar-web/src/main/js/components/issue/components/IssueMessage.js
@@ -27,7 +27,8 @@ import { Button } from '../../ui/buttons';
export default class IssueMessage extends React.PureComponent {
/*:: props: {
- engine?: string;
+ engine?: string,
+ manualVulnerability: boolean,
message: string,
organization: string,
rule: string
@@ -63,6 +64,13 @@ export default class IssueMessage extends React.PureComponent {
</div>
</Tooltip>
)}
+ {this.props.manualVulnerability && (
+ <Tooltip overlay={translate('issue.manual_vulnerability.description')}>
+ <div className="outline-badge badge-tiny-height spacer-left vertical-text-top">
+ {translate('issue.manual_vulnerability')}
+ </div>
+ </Tooltip>
+ )}
</div>
);
}
diff --git a/server/sonar-web/src/main/js/components/issue/components/IssueTitleBar.js b/server/sonar-web/src/main/js/components/issue/components/IssueTitleBar.js
index 1781e6d637d..3e1bea15210 100644
--- a/server/sonar-web/src/main/js/components/issue/components/IssueTitleBar.js
+++ b/server/sonar-web/src/main/js/components/issue/components/IssueTitleBar.js
@@ -75,6 +75,7 @@ export default function IssueTitleBar(props /*: Props */) {
<div className="issue-row">
<IssueMessage
engine={issue.externalRuleEngine}
+ manualVulnerability={issue.fromHotspot && issue.type === 'VULNERABILITY'}
message={issue.message}
organization={issue.organization}
rule={issue.rule}
diff --git a/server/sonar-web/src/main/js/components/issue/components/IssueType.js b/server/sonar-web/src/main/js/components/issue/components/IssueType.js
index 380e8668a32..909a718cb97 100644
--- a/server/sonar-web/src/main/js/components/issue/components/IssueType.js
+++ b/server/sonar-web/src/main/js/components/issue/components/IssueType.js
@@ -30,7 +30,7 @@ import { translate } from '../../../helpers/l10n';
/*::
type Props = {
- canSetSeverity: boolean,
+ canSetType: boolean,
isOpen: boolean,
issue: Issue,
setIssueProperty: (string, string, apiCall: (Object) => Promise<*>, string) => void,
@@ -55,12 +55,12 @@ export default class IssueType extends React.PureComponent {
render() {
const { issue } = this.props;
- if (this.props.canSetSeverity) {
+ if (this.props.canSetType) {
return (
<div className="dropdown">
<Toggler
onRequestClose={this.handleClose}
- open={this.props.isOpen && this.props.canSetSeverity}
+ open={this.props.isOpen && this.props.canSetType}
overlay={<SetTypePopup issue={issue} onSelect={this.setType} />}>
<Button
className="button-link issue-action issue-action-with-options js-issue-set-type"
diff --git a/server/sonar-web/src/main/js/components/issue/components/__tests__/IssueMessage-test.js b/server/sonar-web/src/main/js/components/issue/components/__tests__/IssueMessage-test.js
index 785bc2a21f1..0edabcb727f 100644
--- a/server/sonar-web/src/main/js/components/issue/components/__tests__/IssueMessage-test.js
+++ b/server/sonar-web/src/main/js/components/issue/components/__tests__/IssueMessage-test.js
@@ -24,6 +24,7 @@ import IssueMessage from '../IssueMessage';
it('should render with the message and a link to open the rule', () => {
const element = shallow(
<IssueMessage
+ manualVulnerability={false}
rule="javascript:S1067"
message="Reduce the number of conditional operators (4) used in the expression"
organization="myorg"
diff --git a/server/sonar-web/src/main/js/components/issue/components/__tests__/IssueTitleBar-test.js b/server/sonar-web/src/main/js/components/issue/components/__tests__/IssueTitleBar-test.js
index 9134a0c1f79..38e804a1cd5 100644
--- a/server/sonar-web/src/main/js/components/issue/components/__tests__/IssueTitleBar-test.js
+++ b/server/sonar-web/src/main/js/components/issue/components/__tests__/IssueTitleBar-test.js
@@ -36,7 +36,8 @@ const issue = {
rule: 'javascript:S1067',
message: 'Reduce the number of conditional operators (4) used in the expression',
flows: [],
- secondaryLocations: []
+ secondaryLocations: [],
+ fromHotspot: false
};
const issueWithLocations = {
diff --git a/server/sonar-web/src/main/js/components/issue/components/__tests__/IssueType-test.js b/server/sonar-web/src/main/js/components/issue/components/__tests__/IssueType-test.js
index 51132f338ca..78bb502686b 100644
--- a/server/sonar-web/src/main/js/components/issue/components/__tests__/IssueType-test.js
+++ b/server/sonar-web/src/main/js/components/issue/components/__tests__/IssueType-test.js
@@ -29,7 +29,7 @@ const issue = {
it('should render without the action when the correct rights are missing', () => {
const element = shallow(
<IssueType
- canSetSeverity={false}
+ canSetType={false}
isOpen={false}
issue={issue}
setIssueProperty={jest.fn()}
@@ -42,7 +42,7 @@ it('should render without the action when the correct rights are missing', () =>
it('should render with the action', () => {
const element = shallow(
<IssueType
- canSetSeverity={true}
+ canSetType={true}
isOpen={false}
issue={issue}
setIssueProperty={jest.fn()}
@@ -56,7 +56,7 @@ it('should open the popup when the button is clicked', () => {
const toggle = jest.fn();
const element = shallow(
<IssueType
- canSetSeverity={true}
+ canSetType={true}
isOpen={false}
issue={issue}
setIssueProperty={jest.fn()}
diff --git a/server/sonar-web/src/main/js/components/issue/components/__tests__/__snapshots__/IssueTitleBar-test.js.snap b/server/sonar-web/src/main/js/components/issue/components/__tests__/__snapshots__/IssueTitleBar-test.js.snap
index ac521d1dfc9..b61379d35ab 100644
--- a/server/sonar-web/src/main/js/components/issue/components/__tests__/__snapshots__/IssueTitleBar-test.js.snap
+++ b/server/sonar-web/src/main/js/components/issue/components/__tests__/__snapshots__/IssueTitleBar-test.js.snap
@@ -11,6 +11,7 @@ exports[`should render the titlebar correctly 1`] = `
className="issue-row"
>
<IssueMessage
+ manualVulnerability={false}
message="Reduce the number of conditional operators (4) used in the expression"
organization="myorg"
rule="javascript:S1067"
@@ -31,6 +32,7 @@ exports[`should render the titlebar correctly 1`] = `
Object {
"creationDate": "2017-03-01T09:36:01+0100",
"flows": Array [],
+ "fromHotspot": false,
"key": "AVsae-CQS-9G3txfbFN2",
"line": 25,
"message": "Reduce the number of conditional operators (4) used in the expression",
@@ -95,6 +97,7 @@ exports[`should render the titlebar with the filter 1`] = `
className="issue-row"
>
<IssueMessage
+ manualVulnerability={false}
message="Reduce the number of conditional operators (4) used in the expression"
organization="myorg"
rule="javascript:S1067"
@@ -115,6 +118,7 @@ exports[`should render the titlebar with the filter 1`] = `
Object {
"creationDate": "2017-03-01T09:36:01+0100",
"flows": Array [],
+ "fromHotspot": false,
"key": "AVsae-CQS-9G3txfbFN2",
"line": 25,
"message": "Reduce the number of conditional operators (4) used in the expression",
@@ -177,6 +181,7 @@ exports[`should render the titlebar with the filter 1`] = `
Object {
"creationDate": "2017-03-01T09:36:01+0100",
"flows": Array [],
+ "fromHotspot": false,
"key": "AVsae-CQS-9G3txfbFN2",
"line": 25,
"message": "Reduce the number of conditional operators (4) used in the expression",
diff --git a/server/sonar-web/src/main/js/components/issue/types.js b/server/sonar-web/src/main/js/components/issue/types.js
index 2a5ce7ed3fa..c436d91f1e3 100644
--- a/server/sonar-web/src/main/js/components/issue/types.js
+++ b/server/sonar-web/src/main/js/components/issue/types.js
@@ -68,6 +68,7 @@ export type Issue = {
externalRuleEngine?: string,
key: string,
flows: Array<Array<FlowLocation>>,
+ fromHotspot: boolean,
line?: number,
message: string,
organization: string,