issue.setCreationDate(changeContext.date());
issue.setUpdateDate(changeContext.date());
issue.setEffort(debtCalculator.calculate(issue));
- issue.setIsFromHotspot(rule.getType() == RuleType.SECURITY_HOTSPOT);
setType(issue, rule);
setStatus(issue, rule);
}
// In case issue was moved from module or folder to the root project
raw.setChanged(true);
}
- raw.setIsFromHotspot(rule.getType() == RuleType.SECURITY_HOTSPOT);
setType(raw, rule);
copyFields(raw, base);
base.changes().forEach(raw::addChange);
- if (raw.isFromHotspot() != base.isFromHotspot()) {
- // This is to force DB update of the issue
- raw.setChanged(true);
- }
- if (raw.isFromHotspot() && !base.isFromHotspot()) {
- // First analysis after rule type was changed to security_hotspot. Issue will be reset to an open hotspot
- updater.setType(raw, RuleType.SECURITY_HOTSPOT, changeContext);
- updater.setStatus(raw, Issue.STATUS_TO_REVIEW, changeContext);
- updater.setResolution(raw, null, changeContext);
- }
if (base.manualSeverity()) {
raw.setManualSeverity(true);
ComponentDto file = db.components().insertComponent(ComponentTesting.newFileDto(project));
RuleDefinitionDto rule = db.rules().insert(t -> t.setType(CODE_SMELL));
Date issueDate = addDays(NOW, -10);
- IssueDto issue = db.issues().insert(rule, project, file, t -> t.setStatus(STATUS_CLOSED).setIssueCloseDate(issueDate).setType(CODE_SMELL).setIsFromHotspot(false));
+ IssueDto issue = db.issues().insert(rule, project, file, t -> t.setStatus(STATUS_CLOSED).setIssueCloseDate(issueDate).setType(CODE_SMELL));
db.issues().insertFieldDiffs(issue, newToClosedDiffsWithLine(issueDate, 10));
db.issues().insertFieldDiffs(issue, newToClosedDiffsWithLine(addDays(issueDate, 3), 20));
db.issues().insertFieldDiffs(issue, newToClosedDiffsWithLine(addDays(issueDate, 1), 30));
ComponentDto file = db.components().insertComponent(ComponentTesting.newFileDto(project));
RuleDefinitionDto rule = db.rules().insert(t -> t.setType(CODE_SMELL));
Date issueDate = addDays(NOW, -10);
- IssueDto issue = db.issues().insert(rule, project, file, t -> t.setStatus(STATUS_CLOSED).setIssueCloseDate(issueDate).setType(CODE_SMELL).setIsFromHotspot(false));
+ IssueDto issue = db.issues().insert(rule, project, file, t -> t.setStatus(STATUS_CLOSED).setIssueCloseDate(issueDate).setType(CODE_SMELL));
db.issues().insertFieldDiffs(issue, newToClosedDiffsWithLine(issueDate, 10));
db.issues().insertFieldDiffs(issue, newToClosedDiffsWithLine(addDays(issueDate, 2), null));
db.issues().insertFieldDiffs(issue, newToClosedDiffsWithLine(addDays(issueDate, 1), 30));
ComponentDto file = db.components().insertComponent(ComponentTesting.newFileDto(project));
RuleDefinitionDto rule = db.rules().insert(t -> t.setType(CODE_SMELL));
Date issueDate = addDays(NOW, -10);
- IssueDto closedIssue = db.issues().insert(rule, project, file, t -> t.setStatus(STATUS_CLOSED).setIssueCloseDate(issueDate).setType(CODE_SMELL).setIsFromHotspot(false));
+ IssueDto closedIssue = db.issues().insert(rule, project, file, t -> t.setStatus(STATUS_CLOSED).setIssueCloseDate(issueDate).setType(CODE_SMELL));
db.issues().insertFieldDiffs(closedIssue, newToClosedDiffsWithLine(issueDate, 10));
- IssueDto issueNoCloseDate = db.issues().insert(rule, project, file, t -> t.setStatus(STATUS_CLOSED).setIsFromHotspot(false));
+ IssueDto issueNoCloseDate = db.issues().insert(rule, project, file, t -> t.setStatus(STATUS_CLOSED));
db.issues().insertFieldDiffs(issueNoCloseDate, newToClosedDiffsWithLine(issueDate, 10));
when(system2.now()).thenReturn(NOW.getTime());
};
IssueDto[] issues = Arrays.stream(issueDates)
.map(issueDate -> {
- IssueDto closedIssue = db.issues().insert(rule, project, file, t -> t.setStatus(STATUS_CLOSED).setIssueCloseDate(issueDate).setType(CODE_SMELL).setIsFromHotspot(false));
+ IssueDto closedIssue = db.issues().insert(rule, project, file, t -> t.setStatus(STATUS_CLOSED).setIssueCloseDate(issueDate).setType(CODE_SMELL));
db.issues().insertFieldDiffs(closedIssue, newToClosedDiffsWithLine(issueDate, 10));
return closedIssue;
})
assertThat(issue.effort()).isEqualTo(DEFAULT_DURATION);
assertThat(issue.isNew()).isTrue();
assertThat(issue.isCopied()).isFalse();
- assertThat(issue.isFromHotspot()).isFalse();
}
@Test
assertThat(issue.effort()).isEqualTo(DEFAULT_DURATION);
assertThat(issue.isNew()).isTrue();
assertThat(issue.isCopied()).isFalse();
- assertThat(issue.isFromHotspot()).isTrue();
}
@Test
verify(updater).setPastLocations(raw, issueLocations);
}
- @Test
- public void mergeExistingOpenIssue_vulnerability_changed_to_hotspot_should_be_to_review() {
- rule.setType(RuleType.SECURITY_HOTSPOT);
- DefaultIssue raw = new DefaultIssue()
- .setNew(true)
- .setKey("RAW_KEY")
- .setRuleKey(XOO_X1)
- .setCreationDate(parseDate("2015-10-01"))
- .setUpdateDate(parseDate("2015-10-02"))
- .setCloseDate(parseDate("2015-10-03"));
-
- DbIssues.Locations issueLocations = DbIssues.Locations.newBuilder()
- .setTextRange(DbCommons.TextRange.newBuilder()
- .setStartLine(10)
- .setEndLine(12)
- .build())
- .build();
- DefaultIssue base = new DefaultIssue()
- .setKey("BASE_KEY")
- .setType(RuleType.VULNERABILITY)
- // First analysis before rule was changed to hotspot
- .setIsFromHotspot(false)
- .setCreationDate(parseDate("2015-01-01"))
- .setUpdateDate(parseDate("2015-01-02"))
- .setResolution(RESOLUTION_FALSE_POSITIVE)
- .setStatus(STATUS_RESOLVED)
- .setSeverity(BLOCKER)
- .setAssigneeUuid("base assignee uuid")
- .setAuthorLogin("base author")
- .setTags(newArrayList("base tag"))
- .setSelectedAt(1000L)
- .setLine(10)
- .setMessage("message")
- .setGap(15d)
- .setEffort(Duration.create(15L))
- .setManualSeverity(false)
- .setLocations(issueLocations);
-
- when(debtCalculator.calculate(raw)).thenReturn(DEFAULT_DURATION);
-
- underTest.mergeExistingOpenIssue(raw, base);
-
- assertThat(raw.isNew()).isFalse();
- assertThat(raw.key()).isEqualTo("BASE_KEY");
- assertThat(raw.creationDate()).isEqualTo(base.creationDate());
- assertThat(raw.updateDate()).isEqualTo(base.updateDate());
- assertThat(raw.assignee()).isEqualTo("base assignee uuid");
- assertThat(raw.authorLogin()).isEqualTo("base author");
- assertThat(raw.tags()).containsOnly("base tag");
- assertThat(raw.effort()).isEqualTo(DEFAULT_DURATION);
- assertThat(raw.selectedAt()).isEqualTo(1000L);
- assertThat(raw.isFromHotspot()).isTrue();
- assertThat(raw.isChanged()).isTrue();
-
- verify(updater).setType(raw, RuleType.SECURITY_HOTSPOT, issueChangeContext);
- verify(updater).setStatus(raw, STATUS_TO_REVIEW, issueChangeContext);
- verify(updater).setResolution(raw, null, issueChangeContext);
- verify(updater).setPastSeverity(raw, BLOCKER, issueChangeContext);
- verify(updater).setPastLine(raw, 10);
- verify(updater).setPastMessage(raw, "message", issueChangeContext);
- verify(updater).setPastEffort(raw, Duration.create(15L), issueChangeContext);
- verify(updater).setPastLocations(raw, issueLocations);
- }
-
@Test
public void mergeExistingOpenIssue_with_manual_severity() {
DefaultIssue raw = new DefaultIssue()
.setRuleId(ruleId)
.setRuleKey(issue.ruleKey().repository(), issue.ruleKey().rule())
.setExternal(issue.isFromExternalRuleEngine())
- .setIsFromHotspot(issue.isFromHotspot())
.setTags(issue.tags())
.setComponentUuid(issue.componentUuid())
.setComponentKey(issue.componentKey())
.setAuthorLogin(issue.authorLogin())
.setRuleKey(issue.ruleKey().repository(), issue.ruleKey().rule())
.setExternal(issue.isFromExternalRuleEngine())
- .setIsFromHotspot(issue.isFromHotspot())
.setTags(issue.tags())
.setComponentUuid(issue.componentUuid())
.setComponentKey(issue.componentKey())
return this;
}
- public boolean isFromHotspot() {
- return isFromHotspot;
- }
-
- public IssueDto setIsFromHotspot(boolean value) {
- isFromHotspot = value;
- return this;
- }
-
public String getComponentKey() {
return componentKey;
}
issue.setSelectedAt(selectedAt);
issue.setLocations(parseLocations());
issue.setIsFromExternalRuleEngine(isExternal);
- issue.setIsFromHotspot(isFromHotspot);
return issue;
}
}
p.path as filePath,
root.kee as projectKey,
i.project_uuid as projectUuid,
- i.issue_type as type,
- i.from_hotspot as "isFromHotspot"
+ i.issue_type as type
</sql>
<sql id="sortColumn">
p.scope,
p.organization_uuid as "organizationUuid",
i.tags,
- i.issue_type as "issueType",
- i.from_hotspot as "isFromHotspot"
+ i.issue_type as "issueType"
</sql>
INSERT INTO issues (kee, rule_id, severity, manual_severity,
message, line, locations, gap, effort, status, tags,
resolution, checksum, assignee, author_login, issue_attributes, issue_creation_date, issue_update_date,
- issue_close_date, created_at, updated_at, component_uuid, project_uuid, issue_type, from_hotspot)
+ issue_close_date, created_at, updated_at, component_uuid, project_uuid, issue_type)
VALUES (#{kee,jdbcType=VARCHAR}, #{ruleId,jdbcType=INTEGER},
#{severity,jdbcType=VARCHAR},
#{manualSeverity,jdbcType=BOOLEAN}, #{message,jdbcType=VARCHAR}, #{line,jdbcType=INTEGER},
#{issueAttributes,jdbcType=VARCHAR},
#{issueCreationTime,jdbcType=BIGINT},#{issueUpdateTime,jdbcType=BIGINT}, #{issueCloseTime,jdbcType=BIGINT},
#{createdAt,jdbcType=BIGINT}, #{updatedAt,jdbcType=BIGINT},
- #{componentUuid,jdbcType=VARCHAR}, #{projectUuid,jdbcType=VARCHAR}, #{type,jdbcType=INTEGER}, #{isFromHotspot,jdbcType=BOOLEAN})
+ #{componentUuid,jdbcType=VARCHAR}, #{projectUuid,jdbcType=VARCHAR}, #{type,jdbcType=INTEGER})
</insert>
<!--
issue_update_date=#{issueUpdateTime,jdbcType=BIGINT},
issue_close_date=#{issueCloseTime,jdbcType=BIGINT},
updated_at=#{updatedAt,jdbcType=BIGINT},
- issue_type=#{type,jdbcType=INTEGER},
- from_hotspot=#{isFromHotspot,jdbcType=BOOLEAN}
+ issue_type=#{type,jdbcType=INTEGER}
where kee = #{kee}
</update>
issue_update_date=#{issueUpdateTime,jdbcType=BIGINT},
issue_close_date=#{issueCloseTime,jdbcType=BIGINT},
updated_at=#{updatedAt,jdbcType=BIGINT},
- issue_type=#{type,jdbcType=INTEGER},
- from_hotspot=#{isFromHotspot,jdbcType=BOOLEAN}
+ issue_type=#{type,jdbcType=INTEGER}
where kee = #{kee} and updated_at <= #{selectedAt}
</update>
(r.is_external is NULL or r.is_external = ${_false}) and
i.component_uuid = #{componentUuid,jdbcType=VARCHAR} and
i.status <> 'CLOSED' and
- i.issue_type <> 4 and (i.from_hotspot is NULL or i.from_hotspot = ${_false})
+ i.issue_type <> 4
</select>
<select id="scrollClosedByComponentUuid" resultType="Issue" fetchSize="${_scrollFetchSize}" resultSetType="FORWARD_ONLY">
and i.issue_close_date is not null
and i.issue_close_date >= #{closeDateAfter,jdbcType=BIGINT}
and i.issue_type <> 4
- and (i.from_hotspot is null or i.from_hotspot = ${_false})
order by
i.kee, ic.issue_change_creation_date desc
</select>
i.project_uuid = #{projectUuid, jdbcType=VARCHAR} and
p.module_uuid_path like #{likeModuleUuidPath, jdbcType=VARCHAR} escape '/' and
i.status <> 'CLOSED' and
- i.issue_type <> 4 and (i.from_hotspot is NULL or i.from_hotspot = ${_false})
+ i.issue_type <> 4
</select>
<select id="selectIssueGroupsByBaseComponent" resultType="org.sonar.db.issue.IssueGroupDto" parameterType="map">
IssueDto openIssueOnProject = db.issues().insert(rule, project, project, i -> i.setStatus("OPEN").setResolution(null).setType(randomRuleTypeExceptHotspot()));
IssueDto securityHotspot = db.issues().insert(rule, project, file, i -> i.setType(RuleType.SECURITY_HOTSPOT));
- IssueDto manualVulnerability = db.issues().insert(rule, project, file, i -> i.setType(RuleType.VULNERABILITY).setIsFromHotspot(true));
RuleDefinitionDto external = db.rules().insert(ruleDefinitionDto -> ruleDefinitionDto.setIsExternal(true));
IssueDto issueFromExteralruleOnFile = db.issues().insert(external, project, file, i -> i.setKee("ON_FILE_FROM_EXTERNAL").setType(randomRuleTypeExceptHotspot()));
i -> i.setStatus("OPEN").setResolution(null).setType(randomRuleTypeExceptHotspot()));
IssueDto securityHotspot = db.issues().insert(rule, project, file, i -> i.setType(RuleType.SECURITY_HOTSPOT));
- IssueDto manualVulnerability = db.issues().insert(rule, project, file, i -> i.setType(RuleType.VULNERABILITY).setIsFromHotspot(true));
RuleDefinitionDto external = db.rules().insert(ruleDefinitionDto -> ruleDefinitionDto.setIsExternal(true));
IssueDto issueFromExteralruleOnFile = db.issues().insert(external, project, file, i -> i.setKee("ON_FILE_FROM_EXTERNAL").setType(randomRuleTypeExceptHotspot()));
.containsOnly(tuple(issue.getKey(), issueChange.getChangeData()));
}
- @Test
- public void scrollClosedByComponentUuid_returns_closed_issues_without_isHotspot_flag() {
- RuleType ruleType = randomSupportedRuleType();
- OrganizationDto organization = dbTester.organizations().insert();
- ComponentDto component = randomComponent(organization);
- IssueDto noHotspotFlagIssue = insertNewClosedIssue(component, ruleType);
- IssueChangeDto noFlagIssueChange = insertToClosedDiff(noHotspotFlagIssue);
- manuallySetToNullFromHotpotsColumn(noHotspotFlagIssue);
- IssueDto issue = insertNewClosedIssue(component, ruleType);
- IssueChangeDto issueChange = insertToClosedDiff(issue);
-
- RecorderResultHandler resultHandler = new RecorderResultHandler();
- underTest.scrollClosedByComponentUuid(component.uuid(), NO_FILTERING_ON_CLOSE_DATE, resultHandler);
-
- assertThat(resultHandler.issues)
- .extracting(IssueDto::getKey, t -> t.getClosedChangeData().get())
- .containsOnly(
- tuple(issue.getKey(), issueChange.getChangeData()),
- tuple(noHotspotFlagIssue.getKey(), noFlagIssueChange.getChangeData()));
- }
-
@Test
public void scrollClosedByComponentUuid_does_not_return_closed_issues_without_close_date() {
RuleType ruleType = randomSupportedRuleType();
.containsOnly(issues[3].getKey(), issues[1].getKey(), issues[2].getKey(), issues[0].getKey());
}
- private void manuallySetToNullFromHotpotsColumn(IssueDto fromHostSpotIssue) {
- dbTester.executeUpdateSql("update issues set from_hotspot = null where kee = '" + fromHostSpotIssue.getKey() + "'");
- dbTester.commit();
- }
-
- @Test
- @UseDataProvider("closedIssuesSupportedRuleTypes")
- public void scrollClosedByComponentUuid_does_not_return_closed_issues_with_isHotspot_flag_true(RuleType ruleType) {
- OrganizationDto organization = dbTester.organizations().insert();
- ComponentDto component = randomComponent(organization);
- IssueDto fromHostSpotIssue = insertNewClosedIssue(component, ruleType, t -> t.setIsFromHotspot(true));
- insertToClosedDiff(fromHostSpotIssue);
- IssueDto issue = insertNewClosedIssue(component, ruleType);
- IssueChangeDto issueChange = insertToClosedDiff(issue);
-
- RecorderResultHandler resultHandler = new RecorderResultHandler();
- underTest.scrollClosedByComponentUuid(component.uuid(), NO_FILTERING_ON_CLOSE_DATE, resultHandler);
-
- assertThat(resultHandler.issues)
- .extracting(IssueDto::getKey, t -> t.getClosedChangeData().get())
- .containsOnly(tuple(issue.getKey(), issueChange.getChangeData()));
- }
-
@Test
@UseDataProvider("closedIssuesSupportedRuleTypes")
public void scrollClosedByComponentUuid_return_one_row_per_status_diff_to_CLOSED_sorted_by_most_recent_creation_date_first(RuleType ruleType) {
.add(3112, "Migrate short and long living branches types to common BRANCH type", MigrateSlbsAndLlbsToCommonType.class)
.add(3113, "Migrate short and long living branches types to common BRANCH type in ce tasks table",
MigrateSlbsAndLlbsToCommonTypeInCeTasks.class)
- .add(3114, "Drop 'In Review' Security Hotspots status ", DropSecurityHotSpotsInReviewStatus.class);
+ .add(3114, "Drop 'In Review' Security Hotspots status ", DropSecurityHotSpotsInReviewStatus.class)
+ .add(3115, "Migrate Manual Vulnerabilities to Security Hotspots ", MigrateManualVulnerabilitiesToSecurityHotSpots.class);
}
}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2020 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.platform.db.migration.version.v81;
+
+import java.sql.SQLException;
+import org.sonar.api.utils.System2;
+import org.sonar.db.Database;
+import org.sonar.server.platform.db.migration.step.DataChange;
+import org.sonar.server.platform.db.migration.step.MassUpdate;
+
+import static org.sonar.api.issue.Issue.STATUS_TO_REVIEW;
+import static org.sonar.api.rules.RuleType.VULNERABILITY;
+
+public class MigrateManualVulnerabilitiesToSecurityHotSpots extends DataChange {
+ private System2 system;
+
+ public MigrateManualVulnerabilitiesToSecurityHotSpots(Database db, System2 system) {
+ super(db);
+ this.system = system;
+ }
+
+ @Override
+ protected void execute(Context context) throws SQLException {
+ MassUpdate updateIssues = context.prepareMassUpdate();
+ updateIssues.select("select id, kee, project_uuid, component_uuid from issues where from_hotspot = ? and issue_type = ?")
+ .setBoolean(1, true)
+ .setInt(2, 3);
+ updateIssues.update("update issues set issue_type = ?, status = ? where id = ? and from_hotspot = ? and issue_type = ?");
+ updateIssues.update("insert into issue_changes(issue_key, change_type, change_data, created_at, updated_at, issue_change_creation_date) " +
+ "VALUES(?, ?, ?, ?, ?, ?)");
+
+ updateIssues.execute((row, update, updateIndex) -> {
+ if (updateIndex == 0) {
+ update.setInt(1, 4)
+ .setString(2, STATUS_TO_REVIEW)
+ .setLong(3, row.getLong(1))
+ .setBoolean(4, true)
+ .setInt(5, VULNERABILITY.getDbConstant());
+ } else if (updateIndex == 1) {
+ long currentTime = system.now();
+ update.setString(1, row.getString(2))
+ .setString(2, "diff")
+ .setString(3, "type=VULNERABILITY|SECURITY_HOTSPOT,status=OPEN|TO_REVIEW")
+ .setLong(4, currentTime)
+ .setLong(5, currentTime)
+ .setLong(6, currentTime);
+ }
+ return true;
+ });
+ }
+}
@Test
public void verify_migration_count() {
- verifyMigrationCount(underTest, 15);
+ verifyMigrationCount(underTest, 16);
}
}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2020 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.platform.db.migration.version.v81;
+
+import java.sql.SQLException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Random;
+import java.util.stream.Collectors;
+import java.util.stream.IntStream;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.sonar.api.utils.System2;
+import org.sonar.db.CoreDbTester;
+import org.sonar.server.platform.db.migration.step.DataChange;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.sonar.api.rules.RuleType.BUG;
+import static org.sonar.api.rules.RuleType.CODE_SMELL;
+import static org.sonar.api.rules.RuleType.SECURITY_HOTSPOT;
+import static org.sonar.api.rules.RuleType.VULNERABILITY;
+
+public class MigrateManualVulnerabilitiesToSecurityHotSpotsTest {
+
+ private final static String ISSUES_TABLE_NAME = "issues";
+ private final static int TOTAL_NUMBER_OF_ISSUES = 9;
+
+ @Rule
+ public CoreDbTester db = CoreDbTester.createForSchema(MigrateManualVulnerabilitiesToSecurityHotSpotsTest.class, "schema.sql");
+ @Rule
+ public ExpectedException expectedException = ExpectedException.none();
+
+ private System2 system2 = System2.INSTANCE;
+
+ private DataChange underTest = new MigrateManualVulnerabilitiesToSecurityHotSpots(db.database(), system2);
+
+ @Test
+ public void should_migrate_manual_vulnerabilities_only() throws SQLException {
+ Random random = new Random();
+ List<Integer> range = IntStream.range(0, TOTAL_NUMBER_OF_ISSUES).boxed()
+ .collect(Collectors.toCollection(ArrayList::new));
+ Collections.shuffle(range);
+
+ insertIssue(range.get(0), CODE_SMELL.getDbConstant(), random.nextBoolean());
+ insertIssue(range.get(1), BUG.getDbConstant(), random.nextBoolean());
+ insertIssue(range.get(2), VULNERABILITY.getDbConstant(), false);
+ insertIssue(range.get(3), SECURITY_HOTSPOT.getDbConstant(), random.nextBoolean());
+ insertIssue(range.get(4), -1, random.nextBoolean());
+
+ insertIssue(range.get(5), VULNERABILITY.getDbConstant(), true);
+ insertIssue(range.get(6), VULNERABILITY.getDbConstant(), true);
+ insertIssue(range.get(7), VULNERABILITY.getDbConstant(), true);
+ insertIssue(range.get(8), VULNERABILITY.getDbConstant(), true);
+
+ underTest.execute();
+
+ assertIssueNotChanged(range.get(0), CODE_SMELL.getDbConstant());
+ assertIssueNotChanged(range.get(1), BUG.getDbConstant());
+ assertIssueNotChanged(range.get(2), VULNERABILITY.getDbConstant());
+ assertIssueNotChanged(range.get(3), SECURITY_HOTSPOT.getDbConstant());
+ assertIssueNotChanged(range.get(4), -1);
+
+ assertIssueChanged(range.get(5));
+ assertIssueChanged(range.get(6));
+ assertIssueChanged(range.get(7));
+ assertIssueChanged(range.get(8));
+
+ // should not fail if executed twice
+ underTest.execute();
+ }
+
+ @Test
+ public void should_not_fail_if_no_issues() throws SQLException {
+ underTest.execute();
+ assertThat(db.countRowsOfTable("issues")).isEqualTo(0);
+ }
+
+ private void assertIssueChanged(int issueId) {
+ List<Map<String, Object>> row = db.select(String.format("select status from issues where kee = '%s'", "issue-key-" + issueId));
+ assertThat(row).hasSize(1);
+ assertThat(row.get(0).get("STATUS"))
+ .isEqualTo("TO_REVIEW");
+
+ List<Map<String, Object>> changelogRows = db.select(String.format("select change_type, change_data, created_at, updated_at, issue_change_creation_date" +
+ " from issue_changes where issue_key = '%s'", "issue-key-" + issueId));
+ assertThat(changelogRows).hasSize(1);
+
+ Map<String, Object> changelogRow = changelogRows.get(0);
+ assertThat(changelogRow.get("CHANGE_TYPE")).isEqualTo("diff");
+ assertThat(changelogRow.get("CHANGE_DATA")).isEqualTo("type=VULNERABILITY|SECURITY_HOTSPOT,status=OPEN|TO_REVIEW");
+
+ assertThat(changelogRow.get("CREATED_AT")).isNotNull();
+ assertThat(changelogRow.get("UPDATED_AT")).isNotNull();
+ assertThat(changelogRow.get("ISSUE_CHANGE_CREATION_DATE")).isNotNull();
+ }
+
+ private void assertIssueNotChanged(int issueId, int expectedType) {
+ List<Map<String, Object>> row = db.select(String.format("select issue_type, status from issues where kee = '%s'", "issue-key-" + issueId));
+ assertThat(row).hasSize(1);
+
+ Map<String, Object> issueData = row.get(0);
+ assertThat(issueData.get("STATUS"))
+ .isNull();
+ assertThat(issueData.get("ISSUE_TYPE"))
+ .isEqualTo(expectedType);
+
+ List<Map<String, Object>> changelogRows = db.select(String.format("select change_type, change_data, created_at, updated_at, issue_change_creation_date" +
+ " from issue_changes where issue_key = '%s'", "issue-key-" + issueId));
+ assertThat(changelogRows).isEmpty();
+ }
+
+ private void insertIssue(int issueId, int issueType, boolean fromHotspot) {
+ db.executeInsert(ISSUES_TABLE_NAME,
+ "kee", "issue-key-" + issueId,
+ "issue_type", issueType,
+ "from_hotspot", fromHotspot,
+ "manual_severity", false);
+ }
+
+}
--- /dev/null
+CREATE TABLE "ISSUES"(
+ "ID" BIGINT NOT NULL AUTO_INCREMENT (1,1),
+ "KEE" VARCHAR(50) NOT NULL,
+ "RULE_ID" INTEGER,
+ "SEVERITY" VARCHAR(10),
+ "MANUAL_SEVERITY" BOOLEAN NOT NULL,
+ "MESSAGE" VARCHAR(4000),
+ "LINE" INTEGER,
+ "GAP" DOUBLE,
+ "STATUS" VARCHAR(20),
+ "RESOLUTION" VARCHAR(20),
+ "CHECKSUM" VARCHAR(1000),
+ "REPORTER" VARCHAR(255),
+ "ASSIGNEE" VARCHAR(255),
+ "AUTHOR_LOGIN" VARCHAR(255),
+ "ACTION_PLAN_KEY" VARCHAR(50),
+ "ISSUE_ATTRIBUTES" VARCHAR(4000),
+ "EFFORT" INTEGER,
+ "CREATED_AT" BIGINT,
+ "UPDATED_AT" BIGINT,
+ "ISSUE_CREATION_DATE" BIGINT,
+ "ISSUE_UPDATE_DATE" BIGINT,
+ "ISSUE_CLOSE_DATE" BIGINT,
+ "TAGS" VARCHAR(4000),
+ "COMPONENT_UUID" VARCHAR(50),
+ "PROJECT_UUID" VARCHAR(50),
+ "LOCATIONS" BLOB,
+ "ISSUE_TYPE" TINYINT,
+ "FROM_HOTSPOT" BOOLEAN
+);
+ALTER TABLE "ISSUES" ADD CONSTRAINT "PK_ISSUES" PRIMARY KEY("ID");
+CREATE INDEX "ISSUES_ASSIGNEE" ON "ISSUES"("ASSIGNEE");
+CREATE INDEX "ISSUES_COMPONENT_UUID" ON "ISSUES"("COMPONENT_UUID");
+CREATE INDEX "ISSUES_CREATION_DATE" ON "ISSUES"("ISSUE_CREATION_DATE");
+CREATE UNIQUE INDEX "ISSUES_KEE" ON "ISSUES"("KEE");
+CREATE INDEX "ISSUES_PROJECT_UUID" ON "ISSUES"("PROJECT_UUID");
+CREATE INDEX "ISSUES_RESOLUTION" ON "ISSUES"("RESOLUTION");
+CREATE INDEX "ISSUES_RULE_ID" ON "ISSUES"("RULE_ID");
+CREATE INDEX "ISSUES_UPDATED_AT" ON "ISSUES"("UPDATED_AT");
+
+CREATE TABLE "ISSUE_CHANGES"(
+ "ID" BIGINT NOT NULL AUTO_INCREMENT (1,1),
+ "KEE" VARCHAR(50),
+ "ISSUE_KEY" VARCHAR(50) NOT NULL,
+ "USER_LOGIN" VARCHAR(255),
+ "CHANGE_TYPE" VARCHAR(20),
+ "CHANGE_DATA" CLOB(2147483647),
+ "CREATED_AT" BIGINT,
+ "UPDATED_AT" BIGINT,
+ "ISSUE_CHANGE_CREATION_DATE" BIGINT
+);
+ALTER TABLE "ISSUE_CHANGES" ADD CONSTRAINT "PK_ISSUE_CHANGES" PRIMARY KEY("ID");
+CREATE INDEX "ISSUE_CHANGES_ISSUE_KEY" ON "ISSUE_CHANGES"("ISSUE_KEY");
+CREATE INDEX "ISSUE_CHANGES_KEE" ON "ISSUE_CHANGES"("KEE");
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2020 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.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();
- }
-}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2020 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.rules.RuleType;
+import org.sonar.core.issue.DefaultIssue;
+
+enum IsNotHotspot implements Condition {
+ INSTANCE;
+
+ @Override
+ public boolean matches(Issue issue) {
+ return ((DefaultIssue) issue).type() != RuleType.SECURITY_HOTSPOT;
+ }
+}
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2020 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.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();
- }
-}
// confirm
.transition(Transition.builder(DefaultTransitions.CONFIRM)
.from(STATUS_OPEN).to(STATUS_CONFIRMED)
- .conditions(IsNotHotspotNorManualVulnerability.INSTANCE)
+ .conditions(IsNotHotspot.INSTANCE)
.functions(new SetResolution(null))
.build())
.transition(Transition.builder(DefaultTransitions.CONFIRM)
.from(STATUS_REOPENED).to(STATUS_CONFIRMED)
- .conditions(IsNotHotspotNorManualVulnerability.INSTANCE)
+ .conditions(IsNotHotspot.INSTANCE)
.functions(new SetResolution(null))
.build())
// resolve as fixed
.transition(Transition.builder(DefaultTransitions.RESOLVE)
.from(STATUS_OPEN).to(STATUS_RESOLVED)
- .conditions(IsNotHotspotNorManualVulnerability.INSTANCE)
+ .conditions(IsNotHotspot.INSTANCE)
.functions(new SetResolution(RESOLUTION_FIXED))
.requiredProjectPermission(UserRole.ISSUE_ADMIN)
.build())
.transition(Transition.builder(DefaultTransitions.RESOLVE)
.from(STATUS_REOPENED).to(STATUS_RESOLVED)
- .conditions(IsNotHotspotNorManualVulnerability.INSTANCE)
+ .conditions(IsNotHotspot.INSTANCE)
.functions(new SetResolution(RESOLUTION_FIXED))
.requiredProjectPermission(UserRole.ISSUE_ADMIN)
.build())
.transition(Transition.builder(DefaultTransitions.RESOLVE)
.from(STATUS_CONFIRMED).to(STATUS_RESOLVED)
- .conditions(IsNotHotspotNorManualVulnerability.INSTANCE)
+ .conditions(IsNotHotspot.INSTANCE)
.functions(new SetResolution(RESOLUTION_FIXED))
.requiredProjectPermission(UserRole.ISSUE_ADMIN)
.build())
// reopen
.transition(Transition.builder(DefaultTransitions.UNCONFIRM)
.from(STATUS_CONFIRMED).to(STATUS_REOPENED)
- .conditions(IsNotHotspotNorManualVulnerability.INSTANCE)
+ .conditions(IsNotHotspot.INSTANCE)
.functions(new SetResolution(null))
.build())
.transition(Transition.builder(DefaultTransitions.REOPEN)
.from(STATUS_RESOLVED).to(STATUS_REOPENED)
- .conditions(IsNotHotspotNorManualVulnerability.INSTANCE)
+ .conditions(IsNotHotspot.INSTANCE)
.functions(new SetResolution(null))
.build())
// resolve as false-positive
.transition(Transition.builder(DefaultTransitions.FALSE_POSITIVE)
.from(STATUS_OPEN).to(STATUS_RESOLVED)
- .conditions(IsNotHotspotNorManualVulnerability.INSTANCE)
+ .conditions(IsNotHotspot.INSTANCE)
.functions(new SetResolution(RESOLUTION_FALSE_POSITIVE), UnsetAssignee.INSTANCE)
.requiredProjectPermission(UserRole.ISSUE_ADMIN)
.build())
.transition(Transition.builder(DefaultTransitions.FALSE_POSITIVE)
.from(STATUS_REOPENED).to(STATUS_RESOLVED)
- .conditions(IsNotHotspotNorManualVulnerability.INSTANCE)
+ .conditions(IsNotHotspot.INSTANCE)
.functions(new SetResolution(RESOLUTION_FALSE_POSITIVE), UnsetAssignee.INSTANCE)
.requiredProjectPermission(UserRole.ISSUE_ADMIN)
.build())
.transition(Transition.builder(DefaultTransitions.FALSE_POSITIVE)
.from(STATUS_CONFIRMED).to(STATUS_RESOLVED)
- .conditions(IsNotHotspotNorManualVulnerability.INSTANCE)
+ .conditions(IsNotHotspot.INSTANCE)
.functions(new SetResolution(RESOLUTION_FALSE_POSITIVE), UnsetAssignee.INSTANCE)
.requiredProjectPermission(UserRole.ISSUE_ADMIN)
.build())
// resolve as won't fix
.transition(Transition.builder(DefaultTransitions.WONT_FIX)
.from(STATUS_OPEN).to(STATUS_RESOLVED)
- .conditions(IsNotHotspotNorManualVulnerability.INSTANCE)
+ .conditions(IsNotHotspot.INSTANCE)
.functions(new SetResolution(RESOLUTION_WONT_FIX), UnsetAssignee.INSTANCE)
.requiredProjectPermission(UserRole.ISSUE_ADMIN)
.build())
.transition(Transition.builder(DefaultTransitions.WONT_FIX)
.from(STATUS_REOPENED).to(STATUS_RESOLVED)
- .conditions(IsNotHotspotNorManualVulnerability.INSTANCE)
+ .conditions(IsNotHotspot.INSTANCE)
.functions(new SetResolution(RESOLUTION_WONT_FIX), UnsetAssignee.INSTANCE)
.requiredProjectPermission(UserRole.ISSUE_ADMIN)
.build())
.transition(Transition.builder(DefaultTransitions.WONT_FIX)
.from(STATUS_CONFIRMED).to(STATUS_RESOLVED)
- .conditions(IsNotHotspotNorManualVulnerability.INSTANCE)
+ .conditions(IsNotHotspot.INSTANCE)
.functions(new SetResolution(RESOLUTION_WONT_FIX), UnsetAssignee.INSTANCE)
.requiredProjectPermission(UserRole.ISSUE_ADMIN)
.build());
.functions(new SetResolution(RESOLUTION_FIXED))
.requiredProjectPermission(UserRole.SECURITYHOTSPOT_ADMIN)
.build())
- .transition(Transition.builder(DefaultTransitions.RESOLVE_AS_REVIEWED)
- .from(STATUS_OPEN).to(STATUS_REVIEWED)
- .conditions(new HasType(RuleType.VULNERABILITY), IsManualVulnerability.INSTANCE)
- .functions(new SetType(RuleType.SECURITY_HOTSPOT), new SetResolution(RESOLUTION_FIXED))
- .requiredProjectPermission(UserRole.SECURITYHOTSPOT_ADMIN)
- .build())
-
- .transition(Transition.builder(DefaultTransitions.OPEN_AS_VULNERABILITY)
- .from(STATUS_REVIEWED).to(STATUS_OPEN)
- .conditions(new HasType(RuleType.SECURITY_HOTSPOT))
- .functions(new SetResolution(null), new SetType(RuleType.VULNERABILITY))
- .requiredProjectPermission(UserRole.SECURITYHOTSPOT_ADMIN)
- .build())
- .transition(Transition.builder(DefaultTransitions.OPEN_AS_VULNERABILITY)
- .from(STATUS_TO_REVIEW).to(STATUS_OPEN)
- .conditions(new HasType(RuleType.SECURITY_HOTSPOT))
- .functions(new SetType(RuleType.VULNERABILITY))
- .requiredProjectPermission(UserRole.SECURITYHOTSPOT_ADMIN)
- .build())
.transition(Transition.builder(DefaultTransitions.RESET_AS_TO_REVIEW)
.from(STATUS_REVIEWED).to(STATUS_TO_REVIEW)
.conditions(new HasType(RuleType.SECURITY_HOTSPOT))
.functions(new SetResolution(null))
.requiredProjectPermission(UserRole.SECURITYHOTSPOT_ADMIN)
- .build())
- .transition(Transition.builder(DefaultTransitions.RESET_AS_TO_REVIEW)
- .from(STATUS_OPEN).to(STATUS_TO_REVIEW)
- .conditions(new HasType(RuleType.VULNERABILITY), IsManualVulnerability.INSTANCE)
- .functions(new SetType(RuleType.SECURITY_HOTSPOT), new SetResolution(null))
- .requiredProjectPermission(UserRole.SECURITYHOTSPOT_ADMIN)
- .build())
- ;
+ .build());
}
private static void buildAutomaticTransitions(StateMachine.Builder builder) {
// Reopen issues that are marked as resolved but that are still alive.
.transition(Transition.builder("automaticreopen")
.from(STATUS_RESOLVED).to(STATUS_REOPENED)
- .conditions(new NotCondition(IsBeingClosed.INSTANCE), new HasResolution(RESOLUTION_FIXED), IsNotHotspotNorManualVulnerability.INSTANCE)
+ .conditions(new NotCondition(IsBeingClosed.INSTANCE), new HasResolution(RESOLUTION_FIXED), IsNotHotspot.INSTANCE)
.functions(new SetResolution(null), UnsetCloseDate.INSTANCE)
.automatic()
.build())
.conditions(
new PreviousStatusWas(STATUS_OPEN),
new HasResolution(RESOLUTION_REMOVED, RESOLUTION_FIXED),
- IsNotHotspotNorManualVulnerability.INSTANCE)
+ IsNotHotspot.INSTANCE)
.functions(RestoreResolutionFunction.INSTANCE, UnsetCloseDate.INSTANCE)
.automatic()
.build())
.conditions(
new PreviousStatusWas(STATUS_REOPENED),
new HasResolution(RESOLUTION_REMOVED, RESOLUTION_FIXED),
- IsNotHotspotNorManualVulnerability.INSTANCE)
+ IsNotHotspot.INSTANCE)
.functions(RestoreResolutionFunction.INSTANCE, UnsetCloseDate.INSTANCE)
.automatic()
.build())
.conditions(
new PreviousStatusWas(STATUS_CONFIRMED),
new HasResolution(RESOLUTION_REMOVED, RESOLUTION_FIXED),
- IsNotHotspotNorManualVulnerability.INSTANCE)
+ IsNotHotspot.INSTANCE)
.functions(RestoreResolutionFunction.INSTANCE, UnsetCloseDate.INSTANCE)
.automatic()
.build())
.conditions(
new PreviousStatusWas(STATUS_RESOLVED),
new HasResolution(RESOLUTION_REMOVED, RESOLUTION_FIXED),
- IsNotHotspotNorManualVulnerability.INSTANCE)
+ IsNotHotspot.INSTANCE)
.functions(RestoreResolutionFunction.INSTANCE, UnsetCloseDate.INSTANCE)
.automatic()
.build());
import org.junit.Test;
import org.junit.runner.RunWith;
import org.sonar.api.issue.DefaultTransitions;
-import org.sonar.api.issue.Issue;
import org.sonar.api.rule.RuleKey;
import org.sonar.api.rules.RuleType;
import org.sonar.core.issue.DefaultIssue;
import static org.sonar.api.issue.Issue.RESOLUTION_FIXED;
import static org.sonar.api.issue.Issue.RESOLUTION_REMOVED;
import static org.sonar.api.issue.Issue.STATUS_CLOSED;
-import static org.sonar.api.issue.Issue.STATUS_OPEN;
import static org.sonar.api.issue.Issue.STATUS_RESOLVED;
import static org.sonar.api.issue.Issue.STATUS_REVIEWED;
import static org.sonar.api.issue.Issue.STATUS_TO_REVIEW;
List<Transition> transitions = underTest.outTransitions(issue);
- assertThat(keys(transitions)).containsExactlyInAnyOrder("resolveasreviewed", "openasvulnerability");
+ assertThat(keys(transitions)).containsExactlyInAnyOrder("resolveasreviewed");
}
@Test
List<Transition> transitions = underTest.outTransitions(issue);
- assertThat(keys(transitions)).containsExactlyInAnyOrder("openasvulnerability", "resetastoreview");
- }
-
- @Test
- public void list_out_vulnerability_transitions_in_status_open() {
- underTest.start();
- DefaultIssue issue = new DefaultIssue().setType(RuleType.VULNERABILITY).setResolution(RESOLUTION_FIXED).setStatus(STATUS_OPEN).setIsFromHotspot(true);
-
- List<Transition> transitions = underTest.outTransitions(issue);
-
- assertThat(keys(transitions)).containsExactlyInAnyOrder("resolveasreviewed", "resetastoreview");
+ assertThat(keys(transitions)).containsExactlyInAnyOrder("resetastoreview");
}
@Test
underTest.start();
DefaultIssue issue = new DefaultIssue()
.setType(RuleType.SECURITY_HOTSPOT)
- .setIsFromHotspot(true)
.setStatus(STATUS_TO_REVIEW);
boolean result = underTest.doManualTransition(issue, DefaultTransitions.RESOLVE_AS_REVIEWED, IssueChangeContext.createUser(new Date(), "USER1"));
assertThat(issue.resolution()).isEqualTo(RESOLUTION_FIXED);
}
- @Test
- public void open_as_vulnerability_from_to_review() {
- underTest.start();
- DefaultIssue issue = new DefaultIssue()
- .setType(RuleType.SECURITY_HOTSPOT)
- .setIsFromHotspot(true)
- .setStatus(STATUS_TO_REVIEW)
- .setResolution(null);
-
- boolean result = underTest.doManualTransition(issue, DefaultTransitions.OPEN_AS_VULNERABILITY, IssueChangeContext.createUser(new Date(), "USER1"));
-
- assertThat(result).isTrue();
- assertThat(issue.type()).isEqualTo(RuleType.VULNERABILITY);
- assertThat(issue.getStatus()).isEqualTo(Issue.STATUS_OPEN);
- assertThat(issue.resolution()).isNull();
- }
-
- @Test
- public void open_as_vulnerability_from_reviewed() {
- underTest.start();
- DefaultIssue issue = new DefaultIssue()
- .setType(RuleType.SECURITY_HOTSPOT)
- .setIsFromHotspot(true)
- .setResolution(RESOLUTION_FIXED)
- .setStatus(STATUS_REVIEWED);
-
- boolean result = underTest.doManualTransition(issue, DefaultTransitions.OPEN_AS_VULNERABILITY, IssueChangeContext.createUser(new Date(), "USER1"));
-
- assertThat(result).isTrue();
- assertThat(issue.type()).isEqualTo(RuleType.VULNERABILITY);
- assertThat(issue.getStatus()).isEqualTo(Issue.STATUS_OPEN);
- assertThat(issue.resolution()).isNull();
- }
-
@Test
public void reset_as_to_review_from_reviewed() {
underTest.start();
DefaultIssue issue = new DefaultIssue()
.setType(RuleType.SECURITY_HOTSPOT)
- .setIsFromHotspot(true)
.setStatus(STATUS_REVIEWED)
.setResolution(RESOLUTION_FIXED);
assertThat(issue.resolution()).isNull();
}
- @Test
- public void reset_as_to_review_from_opened_as_vulnerability() {
- underTest.start();
- DefaultIssue issue = new DefaultIssue()
- .setType(RuleType.VULNERABILITY)
- .setIsFromHotspot(true)
- .setStatus(STATUS_OPEN)
- .setResolution(null);
-
- boolean result = underTest.doManualTransition(issue, DefaultTransitions.RESET_AS_TO_REVIEW, IssueChangeContext.createUser(new Date(), "USER1"));
- assertThat(result).isTrue();
- assertThat(issue.type()).isEqualTo(RuleType.SECURITY_HOTSPOT);
- assertThat(issue.getStatus()).isEqualTo(STATUS_TO_REVIEW);
- assertThat(issue.resolution()).isNull();
- }
-
@Test
public void automatically_close_resolved_security_hotspots_in_status_to_review() {
underTest.start();
assertThat(issue.updateDate()).isEqualTo(DateUtils.truncate(now, Calendar.SECOND));
}
- @Test
- public void automatically_close_hotspots_opened_as_vulnerability() {
- underTest.start();
- DefaultIssue issue = new DefaultIssue()
- .setType(RuleType.VULNERABILITY)
- .setResolution(null)
- .setStatus(STATUS_OPEN)
- .setIsFromHotspot(true)
- .setNew(false)
- .setBeingClosed(true);
- Date now = new Date();
-
- underTest.doAutomaticTransition(issue, IssueChangeContext.createScan(now));
-
- assertThat(issue.resolution()).isEqualTo(RESOLUTION_FIXED);
- assertThat(issue.status()).isEqualTo(STATUS_CLOSED);
- assertThat(issue.closeDate()).isNotNull();
- assertThat(issue.updateDate()).isEqualTo(DateUtils.truncate(now, Calendar.SECOND));
- }
-
@Test
@UseDataProvider("allStatusesLeadingToClosed")
public void do_not_automatically_reopen_closed_issues_of_security_hotspots(String previousStatus) {
assertThat(issue.resolution()).isNull();
}
- @Test
- @UseDataProvider("allStatusesLeadingToClosed")
- public void do_not_automatically_reopen_closed_issues_of_manual_vulnerability(String previousStatus) {
- DefaultIssue[] issues = Arrays.stream(SUPPORTED_RESOLUTIONS_FOR_UNCLOSING)
- .map(resolution -> {
- DefaultIssue issue = newClosedIssue(resolution);
- setStatusPreviousToClosed(issue, previousStatus);
- issue.setIsFromHotspot(true);
- return issue;
- })
- .toArray(DefaultIssue[]::new);
- Date now = new Date();
- underTest.start();
-
- Arrays.stream(issues).forEach(issue -> {
- underTest.doAutomaticTransition(issue, IssueChangeContext.createScan(now));
-
- assertThat(issue.status()).isEqualTo(STATUS_CLOSED);
- assertThat(issue.updateDate()).isNull();
- });
- }
-
- @Test
- public void do_not_allow_to_doManualTransition_when_condition_fails() {
- underTest.start();
- DefaultIssue issue = new DefaultIssue()
- .setKey("ABCDE")
- // Detect is only available on hotspot
- .setType(RuleType.VULNERABILITY)
- .setIsFromHotspot(false)
- .setStatus(STATUS_OPEN)
- .setResolution(null)
- .setRuleKey(XOO_X1);
-
- assertThat(underTest.doManualTransition(issue, DefaultTransitions.RESET_AS_TO_REVIEW, IssueChangeContext.createScan(new Date()))).isFalse();
- }
-
private Collection<String> keys(List<Transition> transitions) {
return transitions.stream().map(Transition::key).collect(MoreCollectors.toList());
}
import java.util.Collection;
import java.util.Map;
import org.sonar.api.issue.Issue;
-import org.sonar.server.issue.workflow.IsUnResolved;
import org.sonar.api.rules.RuleType;
import org.sonar.api.web.UserRole;
import org.sonar.core.issue.DefaultIssue;
+import org.sonar.server.issue.workflow.IsUnResolved;
import org.sonar.server.user.UserSession;
import static com.google.common.base.Preconditions.checkArgument;
}
private boolean isCurrentUserIssueAdmin(Issue issue) {
- return !((DefaultIssue) issue).isFromHotspot() && userSession.hasComponentUuidPermission(UserRole.ISSUE_ADMIN, issue.projectUuid());
+ return userSession.hasComponentUuidPermission(UserRole.ISSUE_ADMIN, issue.projectUuid());
}
@Override
import static java.util.Objects.requireNonNull;
import static java.util.function.Function.identity;
import static java.util.stream.Collectors.toMap;
+import static org.sonar.api.issue.DefaultTransitions.OPEN_AS_VULNERABILITY;
import static org.sonar.api.issue.DefaultTransitions.REOPEN;
+import static org.sonar.api.issue.DefaultTransitions.SET_AS_IN_REVIEW;
import static org.sonar.api.rule.Severity.BLOCKER;
import static org.sonar.api.rules.RuleType.BUG;
import static org.sonar.core.util.Uuids.UUID_EXAMPLE_01;
"Requires authentication.")
.setSince("3.7")
.setChangelog(
+ new Change("8.1", OPEN_AS_VULNERABILITY + " transition is no more supported"),
+ new Change("8.1", SET_AS_IN_REVIEW + " transition is no more supported"),
new Change("6.3", "'actions' parameter is ignored"))
.setHandler(this)
.setResponseExample(getClass().getResource("bulk_change-example.json"))
"The transitions involving security hotspots require the permission 'Administer Security Hotspot'.")
.setSince("3.6")
.setChangelog(
- new Change("8.1", SET_AS_IN_REVIEW + " transition has been deprecated"),
+ new Change("8.1", OPEN_AS_VULNERABILITY + " transition is no more supported"),
+ new Change("8.1", SET_AS_IN_REVIEW + " transition is no more supported"),
new Change("7.8", format("added '%s', %s, %s and %s transitions for security hotspots ", SET_AS_IN_REVIEW, RESOLVE_AS_REVIEWED, OPEN_AS_VULNERABILITY, RESET_AS_TO_REVIEW)),
new Change("7.3", "added transitions for security hotspots"),
new Change("6.5", "the database ids of the components are removed from the response"),
PARAM_COMPONENT_KEYS, PARAM_COMPONENT_UUIDS)
.setSince("3.6")
.setChangelog(
+ new Change("8.1", "response field 'fromHotspot' has been deprecated and is no more populated"),
new Change("8.1", format("Status %s for Security Hotspots has been deprecated", STATUS_IN_REVIEW)),
new Change("7.8", format("added new Security Hotspots statuses : %s, %s and %s", STATUS_TO_REVIEW, STATUS_IN_REVIEW, STATUS_REVIEWED)),
new Change("7.8", "Security hotspots are returned by default"),
if (dto.isExternal()) {
issueBuilder.setExternalRuleEngine(engineNameFrom(dto.getRuleKey()));
}
- issueBuilder.setFromHotspot(dto.isFromHotspot());
if (dto.getType() != RuleType.SECURITY_HOTSPOT.getDbConstant()) {
issueBuilder.setSeverity(Common.Severity.valueOf(dto.getSeverity()));
}
}
availableActions.add(ASSIGN_KEY);
availableActions.add("set_tags");
- if (!issue.isFromHotspot() && userSession.hasComponentPermission(ISSUE_ADMIN, project)) {
+ if (ruleType != RuleType.SECURITY_HOTSPOT && 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;
import org.sonar.server.issue.IssueFinder;
import org.sonar.server.user.UserSession;
+import static org.sonar.api.rules.RuleType.SECURITY_HOTSPOT;
import static org.sonar.api.web.UserRole.ISSUE_ADMIN;
import static org.sonarqube.ws.client.issue.IssuesWsParameters.ACTION_SET_TYPE;
import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_ISSUE;
private SearchResponseData setType(DbSession session, String issueKey, RuleType ruleType) {
IssueDto issueDto = issueFinder.getByKey(session, issueKey);
DefaultIssue issue = issueDto.toDefaultIssue();
- if (issue.isFromHotspot()) {
+
+ if (SECURITY_HOTSPOT == issue.type()) {
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());
*/
package org.sonar.server.issue.ws;
+import com.google.common.collect.Sets;
+import com.tngtech.java.junit.dataprovider.DataProvider;
+import com.tngtech.java.junit.dataprovider.DataProviderRunner;
+import com.tngtech.java.junit.dataprovider.UseDataProvider;
+import java.util.EnumSet;
import java.util.List;
+import java.util.Set;
+import java.util.stream.Collectors;
import javax.annotation.Nullable;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
+import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
+import org.sonar.api.rules.RuleType;
import org.sonar.api.server.ws.Request;
import org.sonar.api.server.ws.Response;
import org.sonar.api.server.ws.WebService;
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.rules.RuleType.SECURITY_HOTSPOT;
import static org.sonar.api.web.UserRole.ISSUE_ADMIN;
import static org.sonar.api.web.UserRole.USER;
import static org.sonar.db.component.ComponentTesting.newFileDto;
import static org.sonar.db.issue.IssueTesting.newDto;
import static org.sonar.db.rule.RuleTesting.newRuleDto;
+@RunWith(DataProviderRunner.class)
public class SetTypeActionTest {
@Rule
responseWriter, system2));
@Test
- public void set_type() {
+ @UseDataProvider("allTypesFromToExceptHotspots")
+ public void set_type(RuleType from, RuleType to) {
long now = 1_999_777_234L;
when(system2.now()).thenReturn(now);
- IssueDto issueDto = issueDbTester.insertIssue(newIssue().setType(CODE_SMELL));
+ IssueDto issueDto = issueDbTester.insertIssue(newIssue().setType(from));
setUserWithBrowseAndAdministerIssuePermission(issueDto);
- call(issueDto.getKey(), BUG.name());
+ call(issueDto.getKey(), to.name());
verify(responseWriter).write(eq(issueDto.getKey()), preloadedSearchResponseDataCaptor.capture(), any(Request.class), any(Response.class));
- verifyContentOfPreloadedSearchResponseData(issueDto);
IssueDto issueReloaded = dbClient.issueDao().selectByKey(dbTester.getSession(), issueDto.getKey()).get();
- assertThat(issueReloaded.getType()).isEqualTo(BUG.getDbConstant());
+ assertThat(issueReloaded.getType()).isEqualTo(to.getDbConstant());
- assertThat(issueChangePostProcessor.calledComponents())
- .extracting(ComponentDto::uuid)
- .containsExactlyInAnyOrder(issueDto.getComponentUuid());
- }
-
- @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());
+ if (from != to) {
+ verifyContentOfPreloadedSearchResponseData(issueDto);
+ assertThat(issueChangePostProcessor.calledComponents())
+ .extracting(ComponentDto::uuid)
+ .containsExactlyInAnyOrder(issueDto.getComponentUuid());
+ } else {
+ assertThat(issueChangePostProcessor.wasCalled())
+ .isFalse();
+ }
}
@Test
}
@Test
- public void fail_when_missing_administer_issue_permission() {
- IssueDto issueDto = issueDbTester.insertIssue();
+ @UseDataProvider("allTypesExceptSecurityHotspot")
+ public void fail_type_except_hotspot_when_missing_administer_issue_permission(RuleType type) {
+ IssueDto issueDto = issueDbTester.insertIssue(issue -> issue.setType(type));
logInAndAddProjectPermission("john", issueDto, USER);
expectedException.expect(ForbiddenException.class);
- call(issueDto.getKey(), BUG.name());
+ call(issueDto.getKey(), type.name());
+ }
+
+ @Test
+ @UseDataProvider("allTypesExceptSecurityHotspot")
+ public void fail_if_trying_to_change_type_of_a_hotspot(RuleType type) {
+ long now = 1_999_777_234L;
+ when(system2.now()).thenReturn(now);
+ IssueDto issueDto = issueDbTester.insertIssue(newIssue().setType(SECURITY_HOTSPOT));
+ setUserWithBrowseAndAdministerIssuePermission(issueDto);
+
+ expectedException.expect(IllegalArgumentException.class);
+ call(issueDto.getKey(), type.name());
}
@Test
.extracting(ComponentDto::uuid)
.containsOnly(issue.getComponentUuid(), issue.getProjectUuid());
}
+
+ @DataProvider
+ public static Object[][] allTypesExceptSecurityHotspot() {
+ return EnumSet.allOf(RuleType.class)
+ .stream()
+ .filter(ruleType -> SECURITY_HOTSPOT != ruleType)
+ .map(t -> new Object[] {t})
+ .toArray(Object[][]::new);
+ }
+
+ @DataProvider
+ public static Object[][] allTypesFromToExceptHotspots() {
+ Set<RuleType> set = EnumSet.allOf(RuleType.class)
+ .stream()
+ .filter(ruleType -> SECURITY_HOTSPOT != ruleType)
+ .collect(Collectors.toSet());
+ return Sets.cartesianProduct(set, set)
+ .stream()
+ .map(ruleTypes -> new Object[] {ruleTypes.get(0), ruleTypes.get(1)})
+ .toArray(Object[][]::new);
+ }
}
private Date updateDate;
private Date closeDate;
- private boolean isFromHotspot = false;
-
// Current changes
private FieldDiffs currentChange = null;
}
}
- public DefaultIssue setIsFromHotspot(boolean value) {
- this.isFromHotspot = value;
- return this;
- }
-
- public boolean isFromHotspot() {
- return isFromHotspot;
- }
-
public DefaultIssue setTags(Collection<String> tags) {
this.tags = new LinkedHashSet<>(tags);
return this;
/**
* @since 7.8
+ * @deprecated since 8.1, security hotspots can no longer be converted to vulnerabilities
*/
+ @Deprecated
String OPEN_AS_VULNERABILITY = "openasvulnerability";
/**
* @since 4.4
*/
List<String> ALL = unmodifiableList(asList(CONFIRM, UNCONFIRM, REOPEN, RESOLVE, FALSE_POSITIVE, WONT_FIX, CLOSE,
- SET_AS_IN_REVIEW, RESOLVE_AS_REVIEWED, OPEN_AS_VULNERABILITY,RESET_AS_TO_REVIEW));
+ SET_AS_IN_REVIEW, RESOLVE_AS_REVIEWED, RESET_AS_TO_REVIEW));
}