Browse Source

SONAR-12722 drop `in_review` status

tags/8.2.0.32929
Jacek 4 years ago
parent
commit
b9e5b79c29
18 changed files with 304 additions and 196 deletions
  1. 2
    1
      server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v81/DbVersion81.java
  2. 58
    0
      server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v81/DropSecurityHotSpotsInReviewStatus.java
  3. 1
    1
      server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v81/DbVersion81Test.java
  4. 118
    0
      server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v81/DropSecurityHotSpotsInReviewStatusTest.java
  5. 54
    0
      server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v81/DropSecurityHotSpotsInReviewStatusTest/schema.sql
  6. 1
    7
      server/sonar-server-common/src/main/java/org/sonar/server/issue/index/SecurityStandardCategoryStatistics.java
  7. 1
    31
      server/sonar-server-common/src/main/java/org/sonar/server/issue/workflow/IssueWorkflow.java
  8. 2
    96
      server/sonar-server-common/src/test/java/org/sonar/server/issue/workflow/IssueWorkflowForSecurityHotspotsTest.java
  9. 1
    2
      server/sonar-server-common/src/test/java/org/sonar/server/issue/workflow/IssueWorkflowTest.java
  10. 1
    3
      server/sonar-webserver-es/src/main/java/org/sonar/server/issue/index/IssueIndex.java
  11. 49
    40
      server/sonar-webserver-es/src/test/java/org/sonar/server/issue/index/IssueIndexSecurityReportsTest.java
  12. 1
    0
      server/sonar-webserver-webapi/src/main/java/org/sonar/server/issue/ws/DoTransitionAction.java
  13. 1
    0
      server/sonar-webserver-webapi/src/main/java/org/sonar/server/issue/ws/SearchAction.java
  14. 6
    6
      server/sonar-webserver-webapi/src/test/java/org/sonar/server/issue/ws/BulkChangeActionTest.java
  15. 4
    4
      server/sonar-webserver-webapi/src/test/java/org/sonar/server/issue/ws/SearchActionFacetsTest.java
  16. 1
    1
      sonar-plugin-api/src/main/java/org/sonar/api/issue/DefaultTransitions.java
  17. 3
    2
      sonar-plugin-api/src/main/java/org/sonar/api/issue/Issue.java
  18. 0
    2
      sonar-ws/src/main/protobuf/ws-security.proto

+ 2
- 1
server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v81/DbVersion81.java View File

RenameDaysBeforeDeletingInactiveSLBSetting.class) RenameDaysBeforeDeletingInactiveSLBSetting.class)
.add(3112, "Migrate short and long living branches types to common BRANCH type", MigrateSlbsAndLlbsToCommonType.class) .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", .add(3113, "Migrate short and long living branches types to common BRANCH type in ce tasks table",
MigrateSlbsAndLlbsToCommonTypeInCeTasks.class);
MigrateSlbsAndLlbsToCommonTypeInCeTasks.class)
.add(3114, "Drop 'In Review' Security Hotspots status ", DropSecurityHotSpotsInReviewStatus.class);
} }
} }

+ 58
- 0
server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v81/DropSecurityHotSpotsInReviewStatus.java View File

/*
* 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;

public class DropSecurityHotSpotsInReviewStatus extends DataChange {

private System2 system;

public DropSecurityHotSpotsInReviewStatus(Database db, System2 system) {
super(db);
this.system = system;
}

@Override
protected void execute(Context context) throws SQLException {
MassUpdate massUpdate = context.prepareMassUpdate();
massUpdate.select("select id,kee from issues where status = 'IN_REVIEW'");
massUpdate.update("update issues set status = 'TO_REVIEW' where id = ? and status = 'IN_REVIEW'");
massUpdate.update("insert into issue_changes(issue_key, change_type, change_data, created_at, updated_at, issue_change_creation_date) " +
"VALUES(?, 'diff', 'status=IN_REVIEW|TO_REVIEW', ?, ?, ?)");
massUpdate.execute((row, update, updateIndex) -> {

if (updateIndex == 0) {
update.setLong(1, row.getLong(1));
} else if (updateIndex == 1) {
long currentTime = system.now();
update.setString(1, row.getString(2))
.setLong(2, currentTime)
.setLong(3, currentTime)
.setLong(4, currentTime);
}
return true;
});
}
}

+ 1
- 1
server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v81/DbVersion81Test.java View File



@Test @Test
public void verify_migration_count() { public void verify_migration_count() {
verifyMigrationCount(underTest, 14);
verifyMigrationCount(underTest, 15);
} }


} }

+ 118
- 0
server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v81/DropSecurityHotSpotsInReviewStatusTest.java View File

/*
* 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.List;
import java.util.Map;
import java.util.function.Function;
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.issue.Issue.STATUSES;

public class DropSecurityHotSpotsInReviewStatusTest {

private final static String ISSUES_TABLE_NAME = "issues";
private final static int NUMBER_OF_ISSUES_IN_REVIEW = 3;

@Rule
public CoreDbTester db = CoreDbTester.createForSchema(DropSecurityHotSpotsInReviewStatusTest.class, "schema.sql");
@Rule
public ExpectedException expectedException = ExpectedException.none();

private System2 system2 = System2.INSTANCE;

private DataChange underTest = new DropSecurityHotSpotsInReviewStatus(db.database(), system2);

@Test
public void should_change_IN_REVIEW_statuses_only() throws SQLException {

Map<Integer, String> statuses = IntStream.range(0, STATUSES.size())
.boxed()
.collect(Collectors.toMap(Function.identity(), STATUSES::get));

statuses.forEach(this::insertIssue);

int startIndex = STATUSES.size();
int endIndex = startIndex + NUMBER_OF_ISSUES_IN_REVIEW;
IntStream.range(startIndex, endIndex).forEach(value -> insertIssue(value, "IN_REVIEW"));

underTest.execute();

IntStream.range(startIndex, endIndex).forEach(this::assertIssueChanged);

statuses.forEach(this::assertIssueNotChanged);

// 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("status=IN_REVIEW|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, String expectedStatus) {
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(expectedStatus);

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, String status) {
db.executeInsert(ISSUES_TABLE_NAME,
"kee", "issue-key-" + issueId,
"status", status,
"manual_severity", false);
}

}

+ 54
- 0
server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v81/DropSecurityHotSpotsInReviewStatusTest/schema.sql View File

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");

+ 1
- 7
server/sonar-server-common/src/main/java/org/sonar/server/issue/index/SecurityStandardCategoryStatistics.java View File

private final String category; private final String category;
private final long vulnerabilities; private final long vulnerabilities;
private final OptionalInt vulnerabiliyRating; private final OptionalInt vulnerabiliyRating;
private final long inReviewSecurityHotspots;
private final long toReviewSecurityHotspots; private final long toReviewSecurityHotspots;
private final long reviewedSecurityHotspots; private final long reviewedSecurityHotspots;
private final List<SecurityStandardCategoryStatistics> children; private final List<SecurityStandardCategoryStatistics> children;
private long activeRules; private long activeRules;
private long totalRules; private long totalRules;


public SecurityStandardCategoryStatistics(String category, long vulnerabilities, OptionalInt vulnerabiliyRating, long inReviewSecurityHotspots, long toReviewSecurityHotspots,
public SecurityStandardCategoryStatistics(String category, long vulnerabilities, OptionalInt vulnerabiliyRating, long toReviewSecurityHotspots,
long reviewedSecurityHotspots, @Nullable List<SecurityStandardCategoryStatistics> children) { long reviewedSecurityHotspots, @Nullable List<SecurityStandardCategoryStatistics> children) {
this.category = category; this.category = category;
this.vulnerabilities = vulnerabilities; this.vulnerabilities = vulnerabilities;
this.vulnerabiliyRating = vulnerabiliyRating; this.vulnerabiliyRating = vulnerabiliyRating;
this.inReviewSecurityHotspots = inReviewSecurityHotspots;
this.toReviewSecurityHotspots = toReviewSecurityHotspots; this.toReviewSecurityHotspots = toReviewSecurityHotspots;
this.reviewedSecurityHotspots = reviewedSecurityHotspots; this.reviewedSecurityHotspots = reviewedSecurityHotspots;
this.children = children; this.children = children;
return vulnerabiliyRating; return vulnerabiliyRating;
} }


public long getInReviewSecurityHotspots() {
return inReviewSecurityHotspots;
}

public long getToReviewSecurityHotspots() { public long getToReviewSecurityHotspots() {
return toReviewSecurityHotspots; return toReviewSecurityHotspots;
} }

+ 1
- 31
server/sonar-server-common/src/main/java/org/sonar/server/issue/workflow/IssueWorkflow.java View File

import static org.sonar.api.issue.Issue.RESOLUTION_WONT_FIX; import static org.sonar.api.issue.Issue.RESOLUTION_WONT_FIX;
import static org.sonar.api.issue.Issue.STATUS_CLOSED; import static org.sonar.api.issue.Issue.STATUS_CLOSED;
import static org.sonar.api.issue.Issue.STATUS_CONFIRMED; import static org.sonar.api.issue.Issue.STATUS_CONFIRMED;
import static org.sonar.api.issue.Issue.STATUS_IN_REVIEW;
import static org.sonar.api.issue.Issue.STATUS_OPEN; import static org.sonar.api.issue.Issue.STATUS_OPEN;
import static org.sonar.api.issue.Issue.STATUS_REOPENED; import static org.sonar.api.issue.Issue.STATUS_REOPENED;
import static org.sonar.api.issue.Issue.STATUS_RESOLVED; import static org.sonar.api.issue.Issue.STATUS_RESOLVED;
public void start() { public void start() {
StateMachine.Builder builder = StateMachine.builder() StateMachine.Builder builder = StateMachine.builder()
.states(STATUS_OPEN, STATUS_CONFIRMED, STATUS_REOPENED, STATUS_RESOLVED, STATUS_CLOSED, .states(STATUS_OPEN, STATUS_CONFIRMED, STATUS_REOPENED, STATUS_RESOLVED, STATUS_CLOSED,
STATUS_TO_REVIEW, STATUS_IN_REVIEW, STATUS_REVIEWED);
STATUS_TO_REVIEW, STATUS_REVIEWED);
buildManualTransitions(builder); buildManualTransitions(builder);
buildAutomaticTransitions(builder); buildAutomaticTransitions(builder);
buildSecurityHotspotTransitions(builder); buildSecurityHotspotTransitions(builder);


private static void buildSecurityHotspotTransitions(StateMachine.Builder builder) { private static void buildSecurityHotspotTransitions(StateMachine.Builder builder) {
builder builder
.transition(Transition.builder(DefaultTransitions.SET_AS_IN_REVIEW)
.from(STATUS_TO_REVIEW).to(STATUS_IN_REVIEW)
.conditions(new HasType(RuleType.SECURITY_HOTSPOT))
.requiredProjectPermission(UserRole.SECURITYHOTSPOT_ADMIN)
.build())
.transition(Transition.builder(DefaultTransitions.RESOLVE_AS_REVIEWED) .transition(Transition.builder(DefaultTransitions.RESOLVE_AS_REVIEWED)
.from(STATUS_TO_REVIEW).to(STATUS_REVIEWED) .from(STATUS_TO_REVIEW).to(STATUS_REVIEWED)
.conditions(new HasType(RuleType.SECURITY_HOTSPOT)) .conditions(new HasType(RuleType.SECURITY_HOTSPOT))
.functions(new SetResolution(RESOLUTION_FIXED)) .functions(new SetResolution(RESOLUTION_FIXED))
.requiredProjectPermission(UserRole.SECURITYHOTSPOT_ADMIN) .requiredProjectPermission(UserRole.SECURITYHOTSPOT_ADMIN)
.build()) .build())
.transition(Transition.builder(DefaultTransitions.RESOLVE_AS_REVIEWED)
.from(STATUS_IN_REVIEW).to(STATUS_REVIEWED)
.conditions(new HasType(RuleType.SECURITY_HOTSPOT))
.functions(new SetResolution(RESOLUTION_FIXED))
.requiredProjectPermission(UserRole.SECURITYHOTSPOT_ADMIN)
.build())
.transition(Transition.builder(DefaultTransitions.RESOLVE_AS_REVIEWED) .transition(Transition.builder(DefaultTransitions.RESOLVE_AS_REVIEWED)
.from(STATUS_OPEN).to(STATUS_REVIEWED) .from(STATUS_OPEN).to(STATUS_REVIEWED)
.conditions(new HasType(RuleType.VULNERABILITY), IsManualVulnerability.INSTANCE) .conditions(new HasType(RuleType.VULNERABILITY), IsManualVulnerability.INSTANCE)
.functions(new SetResolution(null), new SetType(RuleType.VULNERABILITY)) .functions(new SetResolution(null), new SetType(RuleType.VULNERABILITY))
.requiredProjectPermission(UserRole.SECURITYHOTSPOT_ADMIN) .requiredProjectPermission(UserRole.SECURITYHOTSPOT_ADMIN)
.build()) .build())
.transition(Transition.builder(DefaultTransitions.OPEN_AS_VULNERABILITY)
.from(STATUS_IN_REVIEW).to(STATUS_OPEN)
.conditions(new HasType(RuleType.SECURITY_HOTSPOT))
.functions(new SetType(RuleType.VULNERABILITY))
.requiredProjectPermission(UserRole.SECURITYHOTSPOT_ADMIN)
.build())
.transition(Transition.builder(DefaultTransitions.OPEN_AS_VULNERABILITY) .transition(Transition.builder(DefaultTransitions.OPEN_AS_VULNERABILITY)
.from(STATUS_TO_REVIEW).to(STATUS_OPEN) .from(STATUS_TO_REVIEW).to(STATUS_OPEN)
.conditions(new HasType(RuleType.SECURITY_HOTSPOT)) .conditions(new HasType(RuleType.SECURITY_HOTSPOT))
.requiredProjectPermission(UserRole.SECURITYHOTSPOT_ADMIN) .requiredProjectPermission(UserRole.SECURITYHOTSPOT_ADMIN)
.build()) .build())


.transition(Transition.builder(DefaultTransitions.RESET_AS_TO_REVIEW)
.from(STATUS_IN_REVIEW).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) .transition(Transition.builder(DefaultTransitions.RESET_AS_TO_REVIEW)
.from(STATUS_REVIEWED).to(STATUS_TO_REVIEW) .from(STATUS_REVIEWED).to(STATUS_TO_REVIEW)
.conditions(new HasType(RuleType.SECURITY_HOTSPOT)) .conditions(new HasType(RuleType.SECURITY_HOTSPOT))
.functions(SetClosed.INSTANCE, SetCloseDate.INSTANCE) .functions(SetClosed.INSTANCE, SetCloseDate.INSTANCE)
.automatic() .automatic()
.build()) .build())
.transition(Transition.builder(AUTOMATIC_CLOSE_TRANSITION)
.from(STATUS_IN_REVIEW).to(STATUS_CLOSED)
.conditions(IsBeingClosed.INSTANCE, new HasType(RuleType.SECURITY_HOTSPOT))
.functions(SetClosed.INSTANCE, SetCloseDate.INSTANCE)
.automatic()
.build())
.transition(Transition.builder(AUTOMATIC_CLOSE_TRANSITION) .transition(Transition.builder(AUTOMATIC_CLOSE_TRANSITION)
.from(STATUS_REVIEWED).to(STATUS_CLOSED) .from(STATUS_REVIEWED).to(STATUS_CLOSED)
.conditions(IsBeingClosed.INSTANCE, new HasType(RuleType.SECURITY_HOTSPOT)) .conditions(IsBeingClosed.INSTANCE, new HasType(RuleType.SECURITY_HOTSPOT))

+ 2
- 96
server/sonar-server-common/src/test/java/org/sonar/server/issue/workflow/IssueWorkflowForSecurityHotspotsTest.java View File

import static org.sonar.api.issue.Issue.RESOLUTION_FIXED; 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.RESOLUTION_REMOVED;
import static org.sonar.api.issue.Issue.STATUS_CLOSED; import static org.sonar.api.issue.Issue.STATUS_CLOSED;
import static org.sonar.api.issue.Issue.STATUS_IN_REVIEW;
import static org.sonar.api.issue.Issue.STATUS_OPEN; 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_RESOLVED;
import static org.sonar.api.issue.Issue.STATUS_REVIEWED; import static org.sonar.api.issue.Issue.STATUS_REVIEWED;
@RunWith(DataProviderRunner.class) @RunWith(DataProviderRunner.class)
public class IssueWorkflowForSecurityHotspotsTest { public class IssueWorkflowForSecurityHotspotsTest {


private static final String[] ALL_STATUSES_LEADING_TO_CLOSED = new String[] {STATUS_TO_REVIEW, STATUS_IN_REVIEW, STATUS_RESOLVED};
private static final String[] ALL_STATUSES_LEADING_TO_CLOSED = new String[] {STATUS_TO_REVIEW, STATUS_RESOLVED};


private static final String[] SUPPORTED_RESOLUTIONS_FOR_UNCLOSING = new String[] {RESOLUTION_FIXED, RESOLUTION_REMOVED}; private static final String[] SUPPORTED_RESOLUTIONS_FOR_UNCLOSING = new String[] {RESOLUTION_FIXED, RESOLUTION_REMOVED};




List<Transition> transitions = underTest.outTransitions(issue); List<Transition> transitions = underTest.outTransitions(issue);


assertThat(keys(transitions)).containsExactlyInAnyOrder("setinreview", "resolveasreviewed", "openasvulnerability");
}

@Test
public void list_out_transitions_in_status_in_review() {
underTest.start();
DefaultIssue issue = new DefaultIssue().setType(RuleType.SECURITY_HOTSPOT).setStatus(STATUS_IN_REVIEW);

List<Transition> transitions = underTest.outTransitions(issue);

assertThat(keys(transitions)).containsExactlyInAnyOrder("resolveasreviewed", "openasvulnerability", "resetastoreview");
assertThat(keys(transitions)).containsExactlyInAnyOrder("resolveasreviewed", "openasvulnerability");
} }


@Test @Test
assertThat(keys(transitions)).containsExactlyInAnyOrder("resolveasreviewed", "resetastoreview"); assertThat(keys(transitions)).containsExactlyInAnyOrder("resolveasreviewed", "resetastoreview");
} }


@Test
public void set_as_in_review() {
underTest.start();
DefaultIssue issue = new DefaultIssue()
.setType(RuleType.SECURITY_HOTSPOT)
.setIsFromHotspot(true)
.setStatus(STATUS_TO_REVIEW);

boolean result = underTest.doManualTransition(issue, DefaultTransitions.SET_AS_IN_REVIEW, IssueChangeContext.createUser(new Date(), "USER1"));

assertThat(result).isTrue();
assertThat(issue.getStatus()).isEqualTo(STATUS_IN_REVIEW);
assertThat(issue.resolution()).isNull();
}

@Test @Test
public void resolve_as_reviewed_from_to_review() { public void resolve_as_reviewed_from_to_review() {
underTest.start(); underTest.start();
assertThat(issue.resolution()).isEqualTo(RESOLUTION_FIXED); assertThat(issue.resolution()).isEqualTo(RESOLUTION_FIXED);
} }


@Test
public void resolve_as_reviewed_from_in_review() {
underTest.start();
DefaultIssue issue = new DefaultIssue()
.setType(RuleType.SECURITY_HOTSPOT)
.setIsFromHotspot(true)
.setStatus(STATUS_IN_REVIEW)
.setResolution(null);

boolean result = underTest.doManualTransition(issue, DefaultTransitions.RESOLVE_AS_REVIEWED, IssueChangeContext.createUser(new Date(), "USER1"));

assertThat(result).isTrue();
assertThat(issue.getStatus()).isEqualTo(STATUS_REVIEWED);
assertThat(issue.resolution()).isEqualTo(RESOLUTION_FIXED);
}

@Test
public void open_as_vulnerability_from_in_review() {
underTest.start();
DefaultIssue issue = new DefaultIssue()
.setType(RuleType.SECURITY_HOTSPOT)
.setIsFromHotspot(true)
.setStatus(STATUS_IN_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 @Test
public void open_as_vulnerability_from_to_review() { public void open_as_vulnerability_from_to_review() {
underTest.start(); underTest.start();
assertThat(issue.resolution()).isNull(); assertThat(issue.resolution()).isNull();
} }


@Test
public void reset_as_to_review_from_in_review() {
underTest.start();
DefaultIssue issue = new DefaultIssue()
.setType(RuleType.SECURITY_HOTSPOT)
.setIsFromHotspot(true)
.setStatus(STATUS_IN_REVIEW)
.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 @Test
public void reset_as_to_review_from_opened_as_vulnerability() { public void reset_as_to_review_from_opened_as_vulnerability() {
underTest.start(); underTest.start();
assertThat(issue.updateDate()).isEqualTo(DateUtils.truncate(now, Calendar.SECOND)); assertThat(issue.updateDate()).isEqualTo(DateUtils.truncate(now, Calendar.SECOND));
} }


@Test
public void automatically_close_resolved_security_hotspots_in_status_in_review() {
underTest.start();
DefaultIssue issue = new DefaultIssue()
.setType(RuleType.SECURITY_HOTSPOT)
.setResolution(null)
.setStatus(STATUS_IN_REVIEW)
.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 @Test
public void automatically_close_resolved_security_hotspots_in_status_reviewed() { public void automatically_close_resolved_security_hotspots_in_status_reviewed() {
underTest.start(); underTest.start();

+ 1
- 2
server/sonar-server-common/src/test/java/org/sonar/server/issue/workflow/IssueWorkflowTest.java View File

import static org.sonar.api.issue.Issue.RESOLUTION_WONT_FIX; import static org.sonar.api.issue.Issue.RESOLUTION_WONT_FIX;
import static org.sonar.api.issue.Issue.STATUS_CLOSED; import static org.sonar.api.issue.Issue.STATUS_CLOSED;
import static org.sonar.api.issue.Issue.STATUS_CONFIRMED; import static org.sonar.api.issue.Issue.STATUS_CONFIRMED;
import static org.sonar.api.issue.Issue.STATUS_IN_REVIEW;
import static org.sonar.api.issue.Issue.STATUS_OPEN; import static org.sonar.api.issue.Issue.STATUS_OPEN;
import static org.sonar.api.issue.Issue.STATUS_REOPENED; import static org.sonar.api.issue.Issue.STATUS_REOPENED;
import static org.sonar.api.issue.Issue.STATUS_RESOLVED; import static org.sonar.api.issue.Issue.STATUS_RESOLVED;
// issues statuses // issues statuses
expectedStatus.addAll(Arrays.asList(STATUS_OPEN, STATUS_CONFIRMED, STATUS_REOPENED, STATUS_RESOLVED, STATUS_CLOSED)); expectedStatus.addAll(Arrays.asList(STATUS_OPEN, STATUS_CONFIRMED, STATUS_REOPENED, STATUS_RESOLVED, STATUS_CLOSED));
// hostpots statuses // hostpots statuses
expectedStatus.addAll(Arrays.asList(STATUS_TO_REVIEW, STATUS_IN_REVIEW, STATUS_REVIEWED));
expectedStatus.addAll(Arrays.asList(STATUS_TO_REVIEW, STATUS_REVIEWED));


assertThat(underTest.statusKeys()).containsExactlyInAnyOrder(expectedStatus.toArray(new String[]{})); assertThat(underTest.statusKeys()).containsExactlyInAnyOrder(expectedStatus.toArray(new String[]{}));
} }

+ 1
- 3
server/sonar-webserver-es/src/main/java/org/sonar/server/issue/index/IssueIndex.java View File



long toReviewSecurityHotspots = ((InternalValueCount) ((InternalFilter) categoryBucket.getAggregations().get(AGG_TO_REVIEW_SECURITY_HOTSPOTS)).getAggregations().get(AGG_COUNT)) long toReviewSecurityHotspots = ((InternalValueCount) ((InternalFilter) categoryBucket.getAggregations().get(AGG_TO_REVIEW_SECURITY_HOTSPOTS)).getAggregations().get(AGG_COUNT))
.getValue(); .getValue();
long inReviewSecurityHotspots = ((InternalValueCount) ((InternalFilter) categoryBucket.getAggregations().get(AGG_IN_REVIEW_SECURITY_HOTSPOTS)).getAggregations().get(AGG_COUNT))
.getValue();
long reviewedSecurityHotspots = ((InternalValueCount) ((InternalFilter) categoryBucket.getAggregations().get(AGG_REVIEWED_SECURITY_HOTSPOTS)).getAggregations().get(AGG_COUNT)) long reviewedSecurityHotspots = ((InternalValueCount) ((InternalFilter) categoryBucket.getAggregations().get(AGG_REVIEWED_SECURITY_HOTSPOTS)).getAggregations().get(AGG_COUNT))
.getValue(); .getValue();


return new SecurityStandardCategoryStatistics(categoryName, vulnerabilities, severityRating, inReviewSecurityHotspots, toReviewSecurityHotspots,
return new SecurityStandardCategoryStatistics(categoryName, vulnerabilities, severityRating, toReviewSecurityHotspots,
reviewedSecurityHotspots, children); reviewedSecurityHotspots, children);
} }



+ 49
- 40
server/sonar-webserver-es/src/test/java/org/sonar/server/issue/index/IssueIndexSecurityReportsTest.java View File

import org.junit.Rule; import org.junit.Rule;
import org.junit.Test; import org.junit.Test;
import org.junit.rules.ExpectedException; import org.junit.rules.ExpectedException;
import org.sonar.api.impl.utils.TestSystem2;
import org.sonar.api.issue.Issue; import org.sonar.api.issue.Issue;
import org.sonar.api.rule.Severity; import org.sonar.api.rule.Severity;
import org.sonar.api.rules.RuleType; import org.sonar.api.rules.RuleType;
import org.sonar.api.utils.System2; import org.sonar.api.utils.System2;
import org.sonar.api.impl.utils.TestSystem2;
import org.sonar.db.DbTester; import org.sonar.db.DbTester;
import org.sonar.db.component.ComponentDto; import org.sonar.db.component.ComponentDto;
import org.sonar.db.organization.OrganizationDto; import org.sonar.db.organization.OrganizationDto;


assertThat(cweByOwasp.get("a1")).extracting(SecurityStandardCategoryStatistics::getCategory, SecurityStandardCategoryStatistics::getVulnerabilities, assertThat(cweByOwasp.get("a1")).extracting(SecurityStandardCategoryStatistics::getCategory, SecurityStandardCategoryStatistics::getVulnerabilities,
SecurityStandardCategoryStatistics::getVulnerabiliyRating, SecurityStandardCategoryStatistics::getToReviewSecurityHotspots, SecurityStandardCategoryStatistics::getVulnerabiliyRating, SecurityStandardCategoryStatistics::getToReviewSecurityHotspots,
SecurityStandardCategoryStatistics::getInReviewSecurityHotspots, SecurityStandardCategoryStatistics::getReviewedSecurityHotspots)
SecurityStandardCategoryStatistics::getReviewedSecurityHotspots)
.containsExactlyInAnyOrder( .containsExactlyInAnyOrder(
tuple("123", 1L /* openvul1 */, OptionalInt.of(3)/* MAJOR = C */, 0L, 0L, 0L),
tuple("456", 1L /* openvul1 */, OptionalInt.of(3)/* MAJOR = C */, 0L, 0L, 0L),
tuple("unknown", 0L, OptionalInt.empty(), 1L /* openhotspot1 */, 0L, 0L));
tuple("123", 1L /* openvul1 */, OptionalInt.of(3)/* MAJOR = C */, 0L, 0L),
tuple("456", 1L /* openvul1 */, OptionalInt.of(3)/* MAJOR = C */, 0L, 0L),
tuple("unknown", 0L, OptionalInt.empty(), 1L /* openhotspot1 */, 0L));
assertThat(cweByOwasp.get("a3")).extracting(SecurityStandardCategoryStatistics::getCategory, SecurityStandardCategoryStatistics::getVulnerabilities, assertThat(cweByOwasp.get("a3")).extracting(SecurityStandardCategoryStatistics::getCategory, SecurityStandardCategoryStatistics::getVulnerabilities,
SecurityStandardCategoryStatistics::getVulnerabiliyRating, SecurityStandardCategoryStatistics::getToReviewSecurityHotspots, SecurityStandardCategoryStatistics::getVulnerabiliyRating, SecurityStandardCategoryStatistics::getToReviewSecurityHotspots,
SecurityStandardCategoryStatistics::getInReviewSecurityHotspots, SecurityStandardCategoryStatistics::getReviewedSecurityHotspots)
SecurityStandardCategoryStatistics::getReviewedSecurityHotspots)
.containsExactlyInAnyOrder( .containsExactlyInAnyOrder(
tuple("123", 2L /* openvul1, openvul2 */, OptionalInt.of(3)/* MAJOR = C */, 0L, 0L, 0L),
tuple("456", 1L /* openvul1 */, OptionalInt.of(3)/* MAJOR = C */, 0L, 1L /* toReviewHotspot */, 0L),
tuple("unknown", 0L, OptionalInt.empty(), 1L /* openhotspot1 */, 0L, 0L));
tuple("123", 2L /* openvul1, openvul2 */, OptionalInt.of(3)/* MAJOR = C */, 0L, 0L),
tuple("456", 1L /* openvul1 */, OptionalInt.of(3)/* MAJOR = C */, 0L/* toReviewHotspot */, 0L),
tuple("unknown", 0L, OptionalInt.empty(), 1L /* openhotspot1 */, 0L));
} }


private List<SecurityStandardCategoryStatistics> indexIssuesAndAssertOwaspReport(boolean includeCwe) { private List<SecurityStandardCategoryStatistics> indexIssuesAndAssertOwaspReport(boolean includeCwe) {
OrganizationDto org = newOrganizationDto(); OrganizationDto org = newOrganizationDto();
ComponentDto project = newPrivateProjectDto(org); ComponentDto project = newPrivateProjectDto(org);
indexIssues( indexIssues(
newDoc("openvul1", project).setOwaspTop10(asList("a1", "a3")).setCwe(asList("123", "456")).setType(RuleType.VULNERABILITY).setStatus(Issue.STATUS_OPEN).setSeverity(Severity.MAJOR),
newDoc("openvul2", project).setOwaspTop10(asList("a3", "a6")).setCwe(asList("123")).setType(RuleType.VULNERABILITY).setStatus(Issue.STATUS_REOPENED).setSeverity(Severity.MINOR),
newDoc("openvul1", project).setOwaspTop10(asList("a1", "a3")).setCwe(asList("123", "456")).setType(RuleType.VULNERABILITY).setStatus(Issue.STATUS_OPEN)
.setSeverity(Severity.MAJOR),
newDoc("openvul2", project).setOwaspTop10(asList("a3", "a6")).setCwe(asList("123")).setType(RuleType.VULNERABILITY).setStatus(Issue.STATUS_REOPENED)
.setSeverity(Severity.MINOR),
newDoc("notowaspvul", project).setOwaspTop10(singletonList(UNKNOWN_STANDARD)).setType(RuleType.VULNERABILITY).setStatus(Issue.STATUS_OPEN).setSeverity(Severity.CRITICAL), newDoc("notowaspvul", project).setOwaspTop10(singletonList(UNKNOWN_STANDARD)).setType(RuleType.VULNERABILITY).setStatus(Issue.STATUS_OPEN).setSeverity(Severity.CRITICAL),
newDoc("toreviewhotspot1", project).setOwaspTop10(asList("a1", "a3")).setCwe(singletonList(UNKNOWN_STANDARD)).setType(RuleType.SECURITY_HOTSPOT).setStatus(Issue.STATUS_TO_REVIEW),
newDoc("toreviewhotspot1", project).setOwaspTop10(asList("a1", "a3")).setCwe(singletonList(UNKNOWN_STANDARD)).setType(RuleType.SECURITY_HOTSPOT)
.setStatus(Issue.STATUS_TO_REVIEW),
newDoc("toreviewhotspot2", project).setOwaspTop10(asList("a3", "a6")).setType(RuleType.SECURITY_HOTSPOT).setStatus(Issue.STATUS_TO_REVIEW), newDoc("toreviewhotspot2", project).setOwaspTop10(asList("a3", "a6")).setType(RuleType.SECURITY_HOTSPOT).setStatus(Issue.STATUS_TO_REVIEW),
newDoc("inreviewhotspot", project).setOwaspTop10(asList("a5", "a3")).setCwe(asList("456")).setType(RuleType.SECURITY_HOTSPOT).setStatus(Issue.STATUS_IN_REVIEW),
newDoc("reviewedHotspot", project).setOwaspTop10(asList("a3", "a8")).setType(RuleType.SECURITY_HOTSPOT).setStatus(Issue.STATUS_REVIEWED).setResolution(Issue.RESOLUTION_FIXED),
newDoc("reviewedHotspot", project).setOwaspTop10(asList("a3", "a8")).setType(RuleType.SECURITY_HOTSPOT).setStatus(Issue.STATUS_REVIEWED)
.setResolution(Issue.RESOLUTION_FIXED),
newDoc("notowasphotspot", project).setOwaspTop10(singletonList(UNKNOWN_STANDARD)).setType(RuleType.SECURITY_HOTSPOT).setStatus(Issue.STATUS_TO_REVIEW)); newDoc("notowasphotspot", project).setOwaspTop10(singletonList(UNKNOWN_STANDARD)).setType(RuleType.SECURITY_HOTSPOT).setStatus(Issue.STATUS_TO_REVIEW));


List<SecurityStandardCategoryStatistics> owaspTop10Report = underTest.getOwaspTop10Report(project.uuid(), false, includeCwe); List<SecurityStandardCategoryStatistics> owaspTop10Report = underTest.getOwaspTop10Report(project.uuid(), false, includeCwe);
assertThat(owaspTop10Report) assertThat(owaspTop10Report)
.extracting(SecurityStandardCategoryStatistics::getCategory, SecurityStandardCategoryStatistics::getVulnerabilities, .extracting(SecurityStandardCategoryStatistics::getCategory, SecurityStandardCategoryStatistics::getVulnerabilities,
SecurityStandardCategoryStatistics::getVulnerabiliyRating, SecurityStandardCategoryStatistics::getToReviewSecurityHotspots, SecurityStandardCategoryStatistics::getVulnerabiliyRating, SecurityStandardCategoryStatistics::getToReviewSecurityHotspots,
SecurityStandardCategoryStatistics::getInReviewSecurityHotspots, SecurityStandardCategoryStatistics::getReviewedSecurityHotspots)
SecurityStandardCategoryStatistics::getReviewedSecurityHotspots)
.containsExactlyInAnyOrder( .containsExactlyInAnyOrder(
tuple("a1", 1L /* openvul1 */, OptionalInt.of(3)/* MAJOR = C */, 1L /* toreviewhotspot1 */, 0L, 0L),
tuple("a2", 0L, OptionalInt.empty(), 0L, 0L, 0L),
tuple("a3", 2L /* openvul1,openvul2 */, OptionalInt.of(3)/* MAJOR = C */, 2L/* toreviewhotspot1,toreviewhotspot2 */, 1L /* inReviewHotspot */, 1L /* reviewedHotspot */),
tuple("a4", 0L, OptionalInt.empty(), 0L, 0L, 0L),
tuple("a5", 0L, OptionalInt.empty(), 0L, 1L/* inReviewHotspot */, 0L),
tuple("a6", 1L /* openvul2 */, OptionalInt.of(2) /* MINOR = B */, 1L /* toreviewhotspot2 */, 0L, 0L),
tuple("a7", 0L, OptionalInt.empty(), 0L, 0L, 0L),
tuple("a8", 0L, OptionalInt.empty(), 0L, 0L, 1L /* reviewedHotspot */),
tuple("a9", 0L, OptionalInt.empty(), 0L, 0L, 0L),
tuple("a10", 0L, OptionalInt.empty(), 0L, 0L, 0L));
tuple("a1", 1L /* openvul1 */, OptionalInt.of(3)/* MAJOR = C */, 1L /* toreviewhotspot1 */, 0L),
tuple("a2", 0L, OptionalInt.empty(), 0L, 0L),
tuple("a3", 2L /* openvul1,openvul2 */, OptionalInt.of(3)/* MAJOR = C */, 2L/* toreviewhotspot1,toreviewhotspot2 */, 1L /* reviewedHotspot */),
tuple("a4", 0L, OptionalInt.empty(), 0L, 0L),
tuple("a5", 0L, OptionalInt.empty(), 0L, 0L),
tuple("a6", 1L /* openvul2 */, OptionalInt.of(2) /* MINOR = B */, 1L /* toreviewhotspot2 */, 0L),
tuple("a7", 0L, OptionalInt.empty(), 0L, 0L),
tuple("a8", 0L, OptionalInt.empty(), 0L, 1L /* reviewedHotspot */),
tuple("a9", 0L, OptionalInt.empty(), 0L, 0L),
tuple("a10", 0L, OptionalInt.empty(), 0L, 0L));
return owaspTop10Report; return owaspTop10Report;
} }


.setResolution(Issue.RESOLUTION_FIXED) .setResolution(Issue.RESOLUTION_FIXED)
.setSeverity(Severity.BLOCKER), .setSeverity(Severity.BLOCKER),
newDoc("notsansvul", project).setSansTop25(singletonList(UNKNOWN_STANDARD)).setType(RuleType.VULNERABILITY).setStatus(Issue.STATUS_OPEN).setSeverity(Severity.CRITICAL), newDoc("notsansvul", project).setSansTop25(singletonList(UNKNOWN_STANDARD)).setType(RuleType.VULNERABILITY).setStatus(Issue.STATUS_OPEN).setSeverity(Severity.CRITICAL),
newDoc("toreviewhotspot1", project).setSansTop25(asList(SANS_TOP_25_INSECURE_INTERACTION, SANS_TOP_25_RISKY_RESOURCE)).setType(RuleType.SECURITY_HOTSPOT).setStatus(Issue.STATUS_TO_REVIEW),
newDoc("toreviewhotspot2", project).setSansTop25(asList(SANS_TOP_25_RISKY_RESOURCE, SANS_TOP_25_POROUS_DEFENSES)).setType(RuleType.SECURITY_HOTSPOT).setStatus(Issue.STATUS_TO_REVIEW),
newDoc("toreviewhotspot1", project).setSansTop25(asList(SANS_TOP_25_INSECURE_INTERACTION, SANS_TOP_25_RISKY_RESOURCE)).setType(RuleType.SECURITY_HOTSPOT)
.setStatus(Issue.STATUS_TO_REVIEW),
newDoc("toreviewhotspot2", project).setSansTop25(asList(SANS_TOP_25_RISKY_RESOURCE, SANS_TOP_25_POROUS_DEFENSES)).setType(RuleType.SECURITY_HOTSPOT)
.setStatus(Issue.STATUS_TO_REVIEW),
newDoc("inReviewHotspot", project).setSansTop25(asList(SANS_TOP_25_RISKY_RESOURCE)).setType(RuleType.SECURITY_HOTSPOT).setStatus(Issue.STATUS_IN_REVIEW), newDoc("inReviewHotspot", project).setSansTop25(asList(SANS_TOP_25_RISKY_RESOURCE)).setType(RuleType.SECURITY_HOTSPOT).setStatus(Issue.STATUS_IN_REVIEW),
newDoc("reviewedHotspot", project).setSansTop25(asList(SANS_TOP_25_RISKY_RESOURCE)).setType(RuleType.SECURITY_HOTSPOT).setStatus(Issue.STATUS_REVIEWED).setResolution(Issue.RESOLUTION_FIXED),
newDoc("reviewedHotspot", project).setSansTop25(asList(SANS_TOP_25_RISKY_RESOURCE)).setType(RuleType.SECURITY_HOTSPOT).setStatus(Issue.STATUS_REVIEWED)
.setResolution(Issue.RESOLUTION_FIXED),
newDoc("notowasphotspot", project).setSansTop25(singletonList(UNKNOWN_STANDARD)).setType(RuleType.SECURITY_HOTSPOT).setStatus(Issue.STATUS_TO_REVIEW)); newDoc("notowasphotspot", project).setSansTop25(singletonList(UNKNOWN_STANDARD)).setType(RuleType.SECURITY_HOTSPOT).setStatus(Issue.STATUS_TO_REVIEW));


List<SecurityStandardCategoryStatistics> sansTop25Report = underTest.getSansTop25Report(project.uuid(), false, false); List<SecurityStandardCategoryStatistics> sansTop25Report = underTest.getSansTop25Report(project.uuid(), false, false);
assertThat(sansTop25Report) assertThat(sansTop25Report)
.extracting(SecurityStandardCategoryStatistics::getCategory, SecurityStandardCategoryStatistics::getVulnerabilities, .extracting(SecurityStandardCategoryStatistics::getCategory, SecurityStandardCategoryStatistics::getVulnerabilities,
SecurityStandardCategoryStatistics::getVulnerabiliyRating, SecurityStandardCategoryStatistics::getToReviewSecurityHotspots, SecurityStandardCategoryStatistics::getVulnerabiliyRating, SecurityStandardCategoryStatistics::getToReviewSecurityHotspots,
SecurityStandardCategoryStatistics::getInReviewSecurityHotspots, SecurityStandardCategoryStatistics::getReviewedSecurityHotspots)
SecurityStandardCategoryStatistics::getReviewedSecurityHotspots)
.containsExactlyInAnyOrder( .containsExactlyInAnyOrder(
tuple(SANS_TOP_25_INSECURE_INTERACTION, 1L /* openvul1 */, OptionalInt.of(3)/* MAJOR = C */, 1L /* toreviewhotspot1 */, 0L, 0L),
tuple(SANS_TOP_25_RISKY_RESOURCE, 2L /* openvul1,openvul2 */, OptionalInt.of(3)/* MAJOR = C */, 2L/* toreviewhotspot1,toreviewhotspot2 */, 1L /* inReviewHotspot */,1L /* reviewedHotspot */),
tuple(SANS_TOP_25_POROUS_DEFENSES, 1L /* openvul2 */, OptionalInt.of(2)/* MINOR = B */, 1L/* openhotspot2 */, 0L, 0L));
tuple(SANS_TOP_25_INSECURE_INTERACTION, 1L /* openvul1 */, OptionalInt.of(3)/* MAJOR = C */, 1L /* toreviewhotspot1 */, 0L),
tuple(SANS_TOP_25_RISKY_RESOURCE, 2L /* openvul1,openvul2 */, OptionalInt.of(3)/* MAJOR = C */, 2L/* toreviewhotspot1,toreviewhotspot2 */,
1L /* reviewedHotspot */),
tuple(SANS_TOP_25_POROUS_DEFENSES, 1L /* openvul2 */, OptionalInt.of(2)/* MINOR = B */, 1L/* openhotspot2 */, 0L));


assertThat(sansTop25Report).allMatch(category -> category.getChildren().isEmpty()); assertThat(sansTop25Report).allMatch(category -> category.getChildren().isEmpty());
} }
.setResolution(Issue.RESOLUTION_FIXED) .setResolution(Issue.RESOLUTION_FIXED)
.setSeverity(Severity.BLOCKER), .setSeverity(Severity.BLOCKER),
newDoc("notsansvul", project2).setSansTop25(singletonList(UNKNOWN_STANDARD)).setType(RuleType.VULNERABILITY).setStatus(Issue.STATUS_OPEN).setSeverity(Severity.CRITICAL), newDoc("notsansvul", project2).setSansTop25(singletonList(UNKNOWN_STANDARD)).setType(RuleType.VULNERABILITY).setStatus(Issue.STATUS_OPEN).setSeverity(Severity.CRITICAL),
newDoc("toreviewhotspot1", project1).setSansTop25(asList(SANS_TOP_25_INSECURE_INTERACTION, SANS_TOP_25_RISKY_RESOURCE)).setType(RuleType.SECURITY_HOTSPOT).setStatus(Issue.STATUS_TO_REVIEW),
newDoc("toreviewhotspot2", project2).setSansTop25(asList(SANS_TOP_25_RISKY_RESOURCE, SANS_TOP_25_POROUS_DEFENSES)).setType(RuleType.SECURITY_HOTSPOT).setStatus(Issue.STATUS_TO_REVIEW),
newDoc("inReviewHotspot", project1).setSansTop25(asList(SANS_TOP_25_RISKY_RESOURCE)).setType(RuleType.SECURITY_HOTSPOT).setStatus(Issue.STATUS_IN_REVIEW),
newDoc("reviewedHotspot", project2).setSansTop25(asList(SANS_TOP_25_RISKY_RESOURCE)).setType(RuleType.SECURITY_HOTSPOT).setStatus(Issue.STATUS_REVIEWED).setResolution(Issue.RESOLUTION_FIXED),
newDoc("toreviewhotspot1", project1).setSansTop25(asList(SANS_TOP_25_INSECURE_INTERACTION, SANS_TOP_25_RISKY_RESOURCE)).setType(RuleType.SECURITY_HOTSPOT)
.setStatus(Issue.STATUS_TO_REVIEW),
newDoc("toreviewhotspot2", project2).setSansTop25(asList(SANS_TOP_25_RISKY_RESOURCE, SANS_TOP_25_POROUS_DEFENSES)).setType(RuleType.SECURITY_HOTSPOT)
.setStatus(Issue.STATUS_TO_REVIEW),
newDoc("reviewedHotspot", project2).setSansTop25(asList(SANS_TOP_25_RISKY_RESOURCE)).setType(RuleType.SECURITY_HOTSPOT).setStatus(Issue.STATUS_REVIEWED)
.setResolution(Issue.RESOLUTION_FIXED),
newDoc("notowasphotspot", project1).setSansTop25(singletonList(UNKNOWN_STANDARD)).setType(RuleType.SECURITY_HOTSPOT).setStatus(Issue.STATUS_TO_REVIEW)); newDoc("notowasphotspot", project1).setSansTop25(singletonList(UNKNOWN_STANDARD)).setType(RuleType.SECURITY_HOTSPOT).setStatus(Issue.STATUS_TO_REVIEW));


indexView(portfolio1.uuid(), singletonList(project1.uuid())); indexView(portfolio1.uuid(), singletonList(project1.uuid()));
assertThat(sansTop25Report) assertThat(sansTop25Report)
.extracting(SecurityStandardCategoryStatistics::getCategory, SecurityStandardCategoryStatistics::getVulnerabilities, .extracting(SecurityStandardCategoryStatistics::getCategory, SecurityStandardCategoryStatistics::getVulnerabilities,
SecurityStandardCategoryStatistics::getVulnerabiliyRating, SecurityStandardCategoryStatistics::getToReviewSecurityHotspots, SecurityStandardCategoryStatistics::getVulnerabiliyRating, SecurityStandardCategoryStatistics::getToReviewSecurityHotspots,
SecurityStandardCategoryStatistics::getInReviewSecurityHotspots, SecurityStandardCategoryStatistics::getReviewedSecurityHotspots)
SecurityStandardCategoryStatistics::getReviewedSecurityHotspots)
.containsExactlyInAnyOrder( .containsExactlyInAnyOrder(
tuple(SANS_TOP_25_INSECURE_INTERACTION, 1L /* openvul1 */, OptionalInt.of(3)/* MAJOR = C */, 1L /* toreviewhotspot1 */, 0L, 0L),
tuple(SANS_TOP_25_RISKY_RESOURCE, 1L /* openvul1 */, OptionalInt.of(3)/* MAJOR = C */, 1L/* toreviewhotspot1 */, 1L /* inReviewHotspot */, 0L),
tuple(SANS_TOP_25_POROUS_DEFENSES, 0L, OptionalInt.empty(), 0L, 0L, 0L));
tuple(SANS_TOP_25_INSECURE_INTERACTION, 1L /* openvul1 */, OptionalInt.of(3)/* MAJOR = C */, 1L /* toreviewhotspot1 */, 0L),
tuple(SANS_TOP_25_RISKY_RESOURCE, 1L /* openvul1 */, OptionalInt.of(3)/* MAJOR = C */, 1L/* toreviewhotspot1 */, 0L),
tuple(SANS_TOP_25_POROUS_DEFENSES, 0L, OptionalInt.empty(), 0L, 0L));


assertThat(sansTop25Report).allMatch(category -> category.getChildren().isEmpty()); assertThat(sansTop25Report).allMatch(category -> category.getChildren().isEmpty());
} }

+ 1
- 0
server/sonar-webserver-webapi/src/main/java/org/sonar/server/issue/ws/DoTransitionAction.java View File

"The transitions involving security hotspots require the permission 'Administer Security Hotspot'.") "The transitions involving security hotspots require the permission 'Administer Security Hotspot'.")
.setSince("3.6") .setSince("3.6")
.setChangelog( .setChangelog(
new Change("8.1", SET_AS_IN_REVIEW + " transition has been deprecated"),
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.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("7.3", "added transitions for security hotspots"),
new Change("6.5", "the database ids of the components are removed from the response"), new Change("6.5", "the database ids of the components are removed from the response"),

+ 1
- 0
server/sonar-webserver-webapi/src/main/java/org/sonar/server/issue/ws/SearchAction.java View File

PARAM_COMPONENT_KEYS, PARAM_COMPONENT_UUIDS) PARAM_COMPONENT_KEYS, PARAM_COMPONENT_UUIDS)
.setSince("3.6") .setSince("3.6")
.setChangelog( .setChangelog(
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", 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"), new Change("7.8", "Security hotspots are returned by default"),
new Change("7.7", format("Value '%s' in parameter '%s' is deprecated, please use '%s' instead", DEPRECATED_PARAM_AUTHORS, FACETS, PARAM_AUTHOR)), new Change("7.7", format("Value '%s' in parameter '%s' is deprecated, please use '%s' instead", DEPRECATED_PARAM_AUTHORS, FACETS, PARAM_AUTHOR)),

+ 6
- 6
server/sonar-webserver-webapi/src/test/java/org/sonar/server/issue/ws/BulkChangeActionTest.java View File

import org.junit.Test; import org.junit.Test;
import org.junit.rules.ExpectedException; import org.junit.rules.ExpectedException;
import org.mockito.ArgumentCaptor; import org.mockito.ArgumentCaptor;
import org.sonar.api.impl.utils.TestSystem2;
import org.sonar.api.rules.RuleType; import org.sonar.api.rules.RuleType;
import org.sonar.api.server.ws.WebService; import org.sonar.api.server.ws.WebService;
import org.sonar.api.utils.System2; import org.sonar.api.utils.System2;
import org.sonar.api.impl.utils.TestSystem2;
import org.sonar.db.DbClient; import org.sonar.db.DbClient;
import org.sonar.db.DbTester; import org.sonar.db.DbTester;
import org.sonar.db.component.BranchType; import org.sonar.db.component.BranchType;
import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyZeroInteractions; import static org.mockito.Mockito.verifyZeroInteractions;
import static org.sonar.api.issue.DefaultTransitions.SET_AS_IN_REVIEW;
import static org.sonar.api.issue.DefaultTransitions.RESOLVE_AS_REVIEWED;
import static org.sonar.api.issue.Issue.RESOLUTION_FIXED; import static org.sonar.api.issue.Issue.RESOLUTION_FIXED;
import static org.sonar.api.issue.Issue.STATUS_CLOSED; import static org.sonar.api.issue.Issue.STATUS_CLOSED;
import static org.sonar.api.issue.Issue.STATUS_CONFIRMED; import static org.sonar.api.issue.Issue.STATUS_CONFIRMED;
import static org.sonar.api.issue.Issue.STATUS_IN_REVIEW;
import static org.sonar.api.issue.Issue.STATUS_OPEN; import static org.sonar.api.issue.Issue.STATUS_OPEN;
import static org.sonar.api.issue.Issue.STATUS_REVIEWED;
import static org.sonar.api.issue.Issue.STATUS_TO_REVIEW; import static org.sonar.api.issue.Issue.STATUS_TO_REVIEW;
import static org.sonar.api.rule.Severity.MAJOR; import static org.sonar.api.rule.Severity.MAJOR;
import static org.sonar.api.rule.Severity.MINOR; import static org.sonar.api.rule.Severity.MINOR;


BulkChangeWsResponse response = call(builder() BulkChangeWsResponse response = call(builder()
.setIssues(singletonList(issue.getKey())) .setIssues(singletonList(issue.getKey()))
.setDoTransition(SET_AS_IN_REVIEW)
.setDoTransition(RESOLVE_AS_REVIEWED)
.setSendNotifications(true) .setSendNotifications(true)
.build()); .build());


assertThat(builder.getIssues()).hasSize(1); assertThat(builder.getIssues()).hasSize(1);
ChangedIssue changedIssue = builder.getIssues().iterator().next(); ChangedIssue changedIssue = builder.getIssues().iterator().next();
assertThat(changedIssue.getKey()).isEqualTo(issue.getKey()); assertThat(changedIssue.getKey()).isEqualTo(issue.getKey());
assertThat(changedIssue.getNewStatus()).isEqualTo(STATUS_IN_REVIEW);
assertThat(changedIssue.getNewResolution()).isEmpty();
assertThat(changedIssue.getNewStatus()).isEqualTo(STATUS_REVIEWED);
assertThat(changedIssue.getNewResolution()).hasValue(RESOLUTION_FIXED);
assertThat(changedIssue.getAssignee()).isEmpty(); assertThat(changedIssue.getAssignee()).isEmpty();
assertThat(changedIssue.getRule()).isEqualTo(ruleOf(rule)); assertThat(changedIssue.getRule()).isEqualTo(ruleOf(rule));
assertThat(builder.getChange()).isEqualTo(new UserChange(NOW, userOf(user))); assertThat(builder.getChange()).isEqualTo(new UserChange(NOW, userOf(user)));

+ 4
- 4
server/sonar-webserver-webapi/src/test/java/org/sonar/server/issue/ws/SearchActionFacetsTest.java View File

.executeProtobuf(SearchWsResponse.class); .executeProtobuf(SearchWsResponse.class);


Map<String, Number> expectedStatuses = ImmutableMap.<String, Number>builder().put("OPEN", 1L).put("CONFIRMED", 0L) Map<String, Number> expectedStatuses = ImmutableMap.<String, Number>builder().put("OPEN", 1L).put("CONFIRMED", 0L)
.put("REOPENED", 0L).put("RESOLVED", 0L).put("CLOSED", 0L).put("IN_REVIEW", 0L).put("TO_REVIEW", 0L).put("REVIEWED", 0L).build();
.put("REOPENED", 0L).put("RESOLVED", 0L).put("CLOSED", 0L).put("TO_REVIEW", 0L).put("REVIEWED", 0L).build();


assertThat(response.getFacets().getFacetsList()) assertThat(response.getFacets().getFacetsList())
.extracting(Common.Facet::getProperty, facet -> facet.getValuesList().stream().collect(toMap(FacetValue::getVal, FacetValue::getCount))) .extracting(Common.Facet::getProperty, facet -> facet.getValuesList().stream().collect(toMap(FacetValue::getVal, FacetValue::getCount)))
.executeProtobuf(SearchWsResponse.class); .executeProtobuf(SearchWsResponse.class);


Map<String, Number> expectedStatuses = ImmutableMap.<String, Number>builder().put("OPEN", 10L).put("CONFIRMED", 0L) Map<String, Number> expectedStatuses = ImmutableMap.<String, Number>builder().put("OPEN", 10L).put("CONFIRMED", 0L)
.put("REOPENED", 0L).put("RESOLVED", 0L).put("CLOSED", 0L).put("IN_REVIEW", 0L).put("TO_REVIEW", 0L).put("REVIEWED", 0L).build();
.put("REOPENED", 0L).put("RESOLVED", 0L).put("CLOSED", 0L).put("TO_REVIEW", 0L).put("REVIEWED", 0L).build();


assertThat(response.getFacets().getFacetsList()) assertThat(response.getFacets().getFacetsList())
.extracting(Common.Facet::getProperty, facet -> facet.getValuesList().stream().collect(toMap(FacetValue::getVal, FacetValue::getCount))) .extracting(Common.Facet::getProperty, facet -> facet.getValuesList().stream().collect(toMap(FacetValue::getVal, FacetValue::getCount)))
// Assignees contains one additional element : it's the empty string that will return number of unassigned issues // Assignees contains one additional element : it's the empty string that will return number of unassigned issues
tuple("assignees", 101), tuple("assignees", 101),
// Following facets returned fixed number of elements // Following facets returned fixed number of elements
tuple("statuses", 8),
tuple("statuses", 7),
tuple("resolutions", 5), tuple("resolutions", 5),
tuple("severities", 5), tuple("severities", 5),
tuple("types", 4)); tuple("types", 4));
.executeProtobuf(SearchWsResponse.class); .executeProtobuf(SearchWsResponse.class);


Map<String, Number> expectedStatuses = ImmutableMap.<String, Number>builder().put("OPEN", 1L).put("CONFIRMED", 0L) Map<String, Number> expectedStatuses = ImmutableMap.<String, Number>builder().put("OPEN", 1L).put("CONFIRMED", 0L)
.put("REOPENED", 0L).put("RESOLVED", 0L).put("CLOSED", 0L).put("IN_REVIEW", 0L).put("TO_REVIEW", 0L).put("REVIEWED", 0L).build();
.put("REOPENED", 0L).put("RESOLVED", 0L).put("CLOSED", 0L).put("TO_REVIEW", 0L).put("REVIEWED", 0L).build();


assertThat(response.getFacets().getFacetsList()) assertThat(response.getFacets().getFacetsList())
.extracting(Common.Facet::getProperty, facet -> facet.getValuesList().stream().collect(toMap(FacetValue::getVal, FacetValue::getCount))) .extracting(Common.Facet::getProperty, facet -> facet.getValuesList().stream().collect(toMap(FacetValue::getVal, FacetValue::getCount)))

+ 1
- 1
sonar-plugin-api/src/main/java/org/sonar/api/issue/DefaultTransitions.java View File

String WONT_FIX = "wontfix"; String WONT_FIX = "wontfix";


/** /**
* @since 7.8
* @deprecated since 8.1, transition has no effect
*/ */
String SET_AS_IN_REVIEW = "setinreview"; String SET_AS_IN_REVIEW = "setinreview";



+ 3
- 2
sonar-plugin-api/src/main/java/org/sonar/api/issue/Issue.java View File

String STATUS_TO_REVIEW = "TO_REVIEW"; String STATUS_TO_REVIEW = "TO_REVIEW";


/** /**
* @since 7.8
* @deprecated since 8.1, status has been mapped as `TO_REVIEW`
*/ */
@Deprecated
String STATUS_IN_REVIEW = "IN_REVIEW"; String STATUS_IN_REVIEW = "IN_REVIEW";


/** /**
* @since 4.4 * @since 4.4
*/ */
List<String> STATUSES = unmodifiableList(asList(STATUS_OPEN, STATUS_CONFIRMED, STATUS_REOPENED, STATUS_RESOLVED, STATUS_CLOSED, List<String> STATUSES = unmodifiableList(asList(STATUS_OPEN, STATUS_CONFIRMED, STATUS_REOPENED, STATUS_RESOLVED, STATUS_CLOSED,
STATUS_TO_REVIEW, STATUS_IN_REVIEW, STATUS_REVIEWED));
STATUS_TO_REVIEW, STATUS_REVIEWED));


/** /**
* Unique generated key. It looks like "d2de809c-1512-4ae2-9f34-f5345c9f1a13". * Unique generated key. It looks like "d2de809c-1512-4ae2-9f34-f5345c9f1a13".

+ 0
- 2
sonar-ws/src/main/protobuf/ws-security.proto View File

optional string category = 1; optional string category = 1;
optional int64 vulnerabilities = 2; optional int64 vulnerabilities = 2;
optional int64 vulnerabilityRating = 3; optional int64 vulnerabilityRating = 3;
optional int64 inReviewSecurityHotspots = 4;
optional int64 toReviewSecurityHotspots = 5; optional int64 toReviewSecurityHotspots = 5;
optional int64 reviewedSecurityHotspots = 6; optional int64 reviewedSecurityHotspots = 6;
repeated CweStatistics distribution = 7; repeated CweStatistics distribution = 7;
optional string cwe = 1; optional string cwe = 1;
optional int64 vulnerabilities = 2; optional int64 vulnerabilities = 2;
optional int64 vulnerabilityRating = 3; optional int64 vulnerabilityRating = 3;
optional int64 inReviewSecurityHotspots = 4;
optional int64 toReviewSecurityHotspots = 5; optional int64 toReviewSecurityHotspots = 5;
optional int64 reviewedSecurityHotspots = 6; optional int64 reviewedSecurityHotspots = 6;
optional int64 activeRules = 7; optional int64 activeRules = 7;

Loading…
Cancel
Save