import org.sonar.server.es.BaseDoc;
import org.sonar.server.permission.index.AuthorizationDoc;
import org.sonar.server.security.SecurityStandards;
+import org.sonar.server.security.SecurityStandards.VulnerabilityProbability;
import static org.sonar.server.issue.index.IssueIndexDefinition.TYPE_ISSUE;
@CheckForNull
public SecurityStandards.SQCategory getSonarSourceSecurityCategory() {
- String key = getNullableField(IssueIndexDefinition.FIELD_ISSUE_SONARSOURCE_SECURITY);
+ String key = getNullableField(IssueIndexDefinition.FIELD_ISSUE_SQ_SECURITY_CATEGORY);
return SecurityStandards.SQCategory.fromKey(key).orElse(null);
}
public IssueDoc setSonarSourceSecurityCategory(@Nullable SecurityStandards.SQCategory c) {
- setField(IssueIndexDefinition.FIELD_ISSUE_SONARSOURCE_SECURITY, c == null ? null : c.getKey());
+ setField(IssueIndexDefinition.FIELD_ISSUE_SQ_SECURITY_CATEGORY, c == null ? null : c.getKey());
+ return this;
+ }
+
+ @CheckForNull
+ public VulnerabilityProbability getVulnerabilityProbability() {
+ Integer score = getNullableField(IssueIndexDefinition.FIELD_ISSUE_VULNERABILITY_PROBABILITY);
+ return VulnerabilityProbability.byScore(score).orElse(null);
+ }
+
+ public IssueDoc setVulnerabilityProbability(@Nullable VulnerabilityProbability v) {
+ setField(IssueIndexDefinition.FIELD_ISSUE_VULNERABILITY_PROBABILITY, v == null ? null : v.getScore());
return this;
}
}
public static final String FIELD_ISSUE_OWASP_TOP_10 = "owaspTop10";
public static final String FIELD_ISSUE_SANS_TOP_25 = "sansTop25";
public static final String FIELD_ISSUE_CWE = "cwe";
- public static final String FIELD_ISSUE_SONARSOURCE_SECURITY = "sonarsourceSecurity";
+ public static final String FIELD_ISSUE_SQ_SECURITY_CATEGORY = "sonarsourceSecurity";
+ public static final String FIELD_ISSUE_VULNERABILITY_PROBABILITY = "vulnerabilityProbability";
private final Configuration config;
private final boolean enableSource;
mapping.keywordFieldBuilder(FIELD_ISSUE_OWASP_TOP_10).disableNorms().build();
mapping.keywordFieldBuilder(FIELD_ISSUE_SANS_TOP_25).disableNorms().build();
mapping.keywordFieldBuilder(FIELD_ISSUE_CWE).disableNorms().build();
- mapping.keywordFieldBuilder(FIELD_ISSUE_SONARSOURCE_SECURITY).disableNorms().build();
+ mapping.keywordFieldBuilder(FIELD_ISSUE_SQ_SECURITY_CATEGORY).disableNorms().build();
+ mapping.keywordFieldBuilder(FIELD_ISSUE_VULNERABILITY_PROBABILITY).disableNorms().build();
}
}
doc.setType(RuleType.valueOf(rs.getInt(22)));
SecurityStandards securityStandards = fromSecurityStandards(deserializeSecurityStandardsString(rs.getString(23)));
+ SecurityStandards.SQCategory sqCategory = securityStandards.getSqCategory();
doc.setOwaspTop10(securityStandards.getOwaspTop10());
doc.setCwe(securityStandards.getCwe());
doc.setSansTop25(securityStandards.getSansTop25());
- doc.setSonarSourceSecurityCategory(securityStandards.getSqCategory());
+ doc.setSonarSourceSecurityCategory(sqCategory);
+ doc.setVulnerabilityProbability(sqCategory.getVulnerability());
return doc;
}
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Ordering;
+import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
SANS_TOP_25_POROUS_DEFENSES, POROUS_CWE);
public enum VulnerabilityProbability {
- HIGH,
- MEDIUM,
- LOW
+ HIGH(3),
+ MEDIUM(2),
+ LOW(1);
+
+ private final int score;
+
+ VulnerabilityProbability(int index) {
+ this.score = index;
+ }
+
+ public int getScore() {
+ return score;
+ }
+
+ public static Optional<VulnerabilityProbability> byScore(@Nullable Integer score) {
+ if (score == null) {
+ return Optional.empty();
+ }
+ return Arrays.stream(values())
+ .filter(t -> t.score == score)
+ .findFirst();
+ }
}
public enum SQCategory {
import org.sonar.server.permission.index.IndexPermissions;
import org.sonar.server.security.SecurityStandards;
import org.sonar.server.security.SecurityStandards.SQCategory;
+import org.sonar.server.security.SecurityStandards.VulnerabilityProbability;
import static java.util.Arrays.asList;
import static java.util.Collections.emptyList;
assertThat(doc.getOwaspTop10()).isEmpty();
assertThat(doc.getSansTop25()).isEmpty();
assertThat(doc.getSonarSourceSecurityCategory()).isEqualTo(SQCategory.OTHERS);
+ assertThat(doc.getVulnerabilityProbability()).isEqualTo(VulnerabilityProbability.LOW);
}
@Test
import static org.sonar.server.issue.index.IssueIndexDefinition.FIELD_ISSUE_SANS_TOP_25;
import static org.sonar.server.issue.index.IssueIndexDefinition.FIELD_ISSUE_SEVERITY;
import static org.sonar.server.issue.index.IssueIndexDefinition.FIELD_ISSUE_SEVERITY_VALUE;
-import static org.sonar.server.issue.index.IssueIndexDefinition.FIELD_ISSUE_SONARSOURCE_SECURITY;
+import static org.sonar.server.issue.index.IssueIndexDefinition.FIELD_ISSUE_SQ_SECURITY_CATEGORY;
import static org.sonar.server.issue.index.IssueIndexDefinition.FIELD_ISSUE_STATUS;
import static org.sonar.server.issue.index.IssueIndexDefinition.FIELD_ISSUE_TAGS;
import static org.sonar.server.issue.index.IssueIndexDefinition.FIELD_ISSUE_TYPE;
+import static org.sonar.server.issue.index.IssueIndexDefinition.FIELD_ISSUE_VULNERABILITY_PROBABILITY;
import static org.sonar.server.issue.index.IssueIndexDefinition.TYPE_ISSUE;
import static org.sonar.server.security.SecurityStandards.SANS_TOP_25_INSECURE_INTERACTION;
import static org.sonar.server.security.SecurityStandards.SANS_TOP_25_POROUS_DEFENSES;
SANS_TOP_25(PARAM_SANS_TOP_25, FIELD_ISSUE_SANS_TOP_25, DEFAULT_FACET_SIZE),
CWE(PARAM_CWE, FIELD_ISSUE_CWE, DEFAULT_FACET_SIZE),
CREATED_AT(PARAM_CREATED_AT, FIELD_ISSUE_FUNC_CREATED_AT, DEFAULT_FACET_SIZE),
- SONARSOURCE_SECURITY(PARAM_SONARSOURCE_SECURITY, FIELD_ISSUE_SONARSOURCE_SECURITY, DEFAULT_FACET_SIZE);
+ SONARSOURCE_SECURITY(PARAM_SONARSOURCE_SECURITY, FIELD_ISSUE_SQ_SECURITY_CATEGORY, DEFAULT_FACET_SIZE);
private final String name;
private final String fieldName;
this.sorting.add(IssueQuery.SORT_BY_FILE_LINE, FIELD_ISSUE_LINE);
this.sorting.add(IssueQuery.SORT_BY_FILE_LINE, FIELD_ISSUE_SEVERITY_VALUE).reverse();
this.sorting.add(IssueQuery.SORT_BY_FILE_LINE, FIELD_ISSUE_KEY);
+ this.sorting.add(IssueQuery.SORT_HOTSPOTS, FIELD_ISSUE_VULNERABILITY_PROBABILITY).reverse();
+ this.sorting.add(IssueQuery.SORT_HOTSPOTS, FIELD_ISSUE_SQ_SECURITY_CATEGORY);
+ this.sorting.add(IssueQuery.SORT_HOTSPOTS, FIELD_ISSUE_RULE_ID);
+ this.sorting.add(IssueQuery.SORT_HOTSPOTS, FIELD_ISSUE_PROJECT_UUID);
+ this.sorting.add(IssueQuery.SORT_HOTSPOTS, FIELD_ISSUE_FILE_PATH);
+ this.sorting.add(IssueQuery.SORT_HOTSPOTS, FIELD_ISSUE_LINE);
+ this.sorting.add(IssueQuery.SORT_HOTSPOTS, FIELD_ISSUE_KEY);
// by default order by created date, project, file, line and issue key (in order to be deterministic when same ms)
this.sorting.addDefault(FIELD_ISSUE_FUNC_CREATED_AT).reverse();
filters.put(FIELD_ISSUE_SANS_TOP_25, createTermsFilter(FIELD_ISSUE_SANS_TOP_25, query.sansTop25()));
filters.put(FIELD_ISSUE_CWE, createTermsFilter(FIELD_ISSUE_CWE, query.cwe()));
addSeverityFilter(query, filters);
- filters.put(FIELD_ISSUE_SONARSOURCE_SECURITY, createTermsFilter(FIELD_ISSUE_SONARSOURCE_SECURITY, query.sonarsourceSecurity()));
+ filters.put(FIELD_ISSUE_SQ_SECURITY_CATEGORY, createTermsFilter(FIELD_ISSUE_SQ_SECURITY_CATEGORY, query.sonarsourceSecurity()));
addComponentRelatedFilters(query, filters);
addDatesFilter(filters, query);
Arrays.stream(SQCategory.values())
.forEach(sonarsourceCategory -> request.addAggregation(
newSecurityReportSubAggregations(
- AggregationBuilders.filter(sonarsourceCategory.getKey(), boolQuery().filter(termQuery(FIELD_ISSUE_SONARSOURCE_SECURITY, sonarsourceCategory.getKey()))),
+ AggregationBuilders.filter(sonarsourceCategory.getKey(), boolQuery().filter(termQuery(FIELD_ISSUE_SQ_SECURITY_CATEGORY, sonarsourceCategory.getKey()))),
includeCwe,
Optional.ofNullable(SecurityStandards.CWES_BY_SQ_CATEGORY.get(sonarsourceCategory)))));
return processSecurityReportSearchResults(request, includeCwe);
* Sort by project, file path then line id
*/
public static final String SORT_BY_FILE_LINE = "FILE_LINE";
+ /**
+ * Sort hotspots by vulnerabilityProbability, sqSecurityCategory, project, file path then line id
+ */
+ public static final String SORT_HOTSPOTS = "HOTSPOTS";
public static final Set<String> SORTS = ImmutableSet.of(SORT_BY_CREATION_DATE, SORT_BY_UPDATE_DATE, SORT_BY_CLOSE_DATE, SORT_BY_ASSIGNEE, SORT_BY_SEVERITY,
- SORT_BY_STATUS, SORT_BY_FILE_LINE);
+ SORT_BY_STATUS, SORT_BY_FILE_LINE, SORT_HOTSPOTS);
private final Collection<String> issueKeys;
private final Collection<String> severities;
import org.sonar.api.server.ws.Request;
import org.sonar.api.server.ws.Response;
import org.sonar.api.server.ws.WebService;
+import org.sonar.api.utils.Paging;
import org.sonar.api.web.UserRole;
import org.sonar.core.util.stream.MoreCollectors;
import org.sonar.db.DbClient;
import org.sonar.server.organization.DefaultOrganizationProvider;
import org.sonar.server.security.SecurityStandards;
import org.sonar.server.user.UserSession;
+import org.sonarqube.ws.Common;
import org.sonarqube.ws.Hotspots;
import static com.google.common.base.Strings.nullToEmpty;
import static org.sonar.api.server.ws.WebService.Param.PAGE;
import static org.sonar.api.server.ws.WebService.Param.PAGE_SIZE;
import static org.sonar.api.utils.DateUtils.formatDateTime;
+import static org.sonar.api.utils.Paging.forPageIndex;
import static org.sonar.core.util.stream.MoreCollectors.uniqueIndex;
import static org.sonar.server.ws.KeyExamples.KEY_PROJECT_EXAMPLE_001;
import static org.sonar.server.ws.WsUtils.writeProtobuf;
@Override
public void define(WebService.NewController controller) {
-
WebService.NewAction action = controller
.createAction("search")
.setHandler(this)
@Override
public void handle(Request request, Response response) throws Exception {
+ WsRequest wsRequest = toWsRequest(request);
try (DbSession dbSession = dbClient.openSession(false)) {
- String projectKey = request.mandatoryParam(PARAM_PROJECT_KEY);
- ComponentDto project = dbClient.componentDao().selectByKey(dbSession, projectKey)
- .filter(t -> Scopes.PROJECT.equals(t.scope()) && Qualifiers.PROJECT.equals(t.qualifier()))
- .filter(ComponentDto::isEnabled)
- .filter(t -> t.getMainBranchProjectUuid() == null)
- .orElseThrow(() -> new NotFoundException(String.format("Project '%s' not found", projectKey)));
- userSession.checkComponentPermission(UserRole.USER, project);
-
- List<IssueDto> orderedIssues = searchHotspots(request, dbSession, project);
- SearchResponseData searchResponseData = new SearchResponseData(orderedIssues);
+ ComponentDto project = validateRequest(dbSession, wsRequest);
+
+ SearchResponseData searchResponseData = searchHotspots(wsRequest, dbSession, project);
loadComponents(dbSession, searchResponseData);
loadRules(dbSession, searchResponseData);
writeProtobuf(formatResponse(searchResponseData), request, response);
}
}
- private List<IssueDto> searchHotspots(Request request, DbSession dbSession, ComponentDto project) {
- List<String> issueKeys = searchHotspots(request, project);
+ private static WsRequest toWsRequest(Request request) {
+ return new WsRequest(request.mandatoryParamAsInt(PAGE), request.mandatoryParamAsInt(PAGE_SIZE), request.mandatoryParam(PARAM_PROJECT_KEY));
+ }
+
+ private ComponentDto validateRequest(DbSession dbSession, WsRequest wsRequest) {
+ ComponentDto project = dbClient.componentDao().selectByKey(dbSession, wsRequest.getProjectKey())
+ .filter(t -> Scopes.PROJECT.equals(t.scope()) && Qualifiers.PROJECT.equals(t.qualifier()))
+ .filter(ComponentDto::isEnabled)
+ .filter(t -> t.getMainBranchProjectUuid() == null)
+ .orElseThrow(() -> new NotFoundException(String.format("Project '%s' not found", wsRequest.getProjectKey())));
+ userSession.checkComponentPermission(UserRole.USER, project);
+ return project;
+ }
+
+ private SearchResponseData searchHotspots(WsRequest wsRequest, DbSession dbSession, ComponentDto project) {
+ SearchResponse result = doIndexSearch(wsRequest, project);
+ List<String> issueKeys = Arrays.stream(result.getHits().getHits())
+ .map(SearchHit::getId)
+ .collect(MoreCollectors.toList(result.getHits().getHits().length));
+
+ List<IssueDto> hotspots = toIssueDtos(dbSession, issueKeys);
+
+ Paging paging = forPageIndex(wsRequest.getPage()).withPageSize(wsRequest.getIndex()).andTotal((int) result.getHits().getTotalHits());
+ return new SearchResponseData(paging, hotspots);
+ }
- List<IssueDto> unorderedIssues = dbClient.issueDao().selectByKeys(dbSession, issueKeys);
- Map<String, IssueDto> unorderedIssuesMap = unorderedIssues
+ private List<IssueDto> toIssueDtos(DbSession dbSession, List<String> issueKeys) {
+ List<IssueDto> unorderedHotspots = dbClient.issueDao().selectByKeys(dbSession, issueKeys);
+ Map<String, IssueDto> hotspotsByKey = unorderedHotspots
.stream()
- .collect(uniqueIndex(IssueDto::getKey, unorderedIssues.size()));
+ .collect(uniqueIndex(IssueDto::getKey, unorderedHotspots.size()));
return issueKeys.stream()
- .map(unorderedIssuesMap::get)
+ .map(hotspotsByKey::get)
.filter(Objects::nonNull)
.collect(Collectors.toList());
}
- private List<String> searchHotspots(Request request, ComponentDto project) {
+ private SearchResponse doIndexSearch(WsRequest wsRequest, ComponentDto project) {
IssueQuery.Builder builder = IssueQuery.builder()
.projectUuids(Collections.singletonList(project.uuid()))
.organizationUuid(project.getOrganizationUuid())
.types(Collections.singleton(RuleType.SECURITY_HOTSPOT.name()))
- .resolved(false);
+ .resolved(false)
+ .sort(IssueQuery.SORT_HOTSPOTS)
+ .asc(true);
IssueQuery query = builder.build();
SearchOptions searchOptions = new SearchOptions()
- .setPage(request.mandatoryParamAsInt(PAGE), request.mandatoryParamAsInt(PAGE_SIZE));
- SearchResponse result = issueIndex.search(query, searchOptions);
- return Arrays.stream(result.getHits().getHits())
- .map(SearchHit::getId)
- .collect(MoreCollectors.toList(result.getHits().getHits().length));
+ .setPage(wsRequest.page, wsRequest.index);
+ return issueIndex.search(query, searchOptions);
}
private void loadComponents(DbSession dbSession, SearchResponseData searchResponseData) {
private Hotspots.SearchWsResponse formatResponse(SearchResponseData searchResponseData) {
Hotspots.SearchWsResponse.Builder responseBuilder = Hotspots.SearchWsResponse.newBuilder();
+ formatPaging(searchResponseData, responseBuilder);
if (!searchResponseData.isEmpty()) {
formatHotspots(searchResponseData, responseBuilder);
formatComponents(searchResponseData, responseBuilder);
return responseBuilder.build();
}
+ private void formatPaging(SearchResponseData searchResponseData, Hotspots.SearchWsResponse.Builder responseBuilder) {
+ Paging paging = searchResponseData.getPaging();
+ Common.Paging.Builder pagingBuilder = Common.Paging.newBuilder()
+ .setPageIndex(paging.pageIndex())
+ .setPageSize(paging.pageSize())
+ .setTotal(paging.total());
+
+ responseBuilder.setPaging(pagingBuilder.build());
+ }
+
private static void formatHotspots(SearchResponseData searchResponseData, Hotspots.SearchWsResponse.Builder responseBuilder) {
List<IssueDto> orderedHotspots = searchResponseData.getOrderedHotspots();
if (orderedHotspots.isEmpty()) {
}
}
+ private static final class WsRequest {
+ private final int page;
+ private final int index;
+ private final String projectKey;
+
+ private WsRequest(int page, int index, String projectKey) {
+ this.page = page;
+ this.index = index;
+ this.projectKey = projectKey;
+ }
+
+ int getPage() {
+ return page;
+ }
+
+ int getIndex() {
+ return index;
+ }
+
+ String getProjectKey() {
+ return projectKey;
+ }
+ }
+
private static final class SearchResponseData {
+ private final Paging paging;
private final List<IssueDto> orderedHotspots;
private final Set<ComponentDto> components = new HashSet<>();
private final Set<RuleDefinitionDto> rules = new HashSet<>();
- private SearchResponseData(List<IssueDto> orderedHotspots) {
+ private SearchResponseData(Paging paging, List<IssueDto> orderedHotspots) {
+ this.paging = paging;
this.orderedHotspots = orderedHotspots;
}
return orderedHotspots.isEmpty();
}
+ public Paging getPaging() {
+ return paging;
+ }
+
List<IssueDto> getOrderedHotspots() {
return orderedHotspots;
}
*/
package org.sonar.server.hotspot.ws;
+import com.google.common.collect.Ordering;
import java.util.Arrays;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
import java.util.Map;
import java.util.Random;
+import java.util.Set;
import java.util.function.Consumer;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
+import java.util.stream.Stream;
import org.junit.Rule;
import org.junit.Test;
import org.sonar.api.issue.Issue;
import org.sonar.server.organization.TestDefaultOrganizationProvider;
import org.sonar.server.permission.index.PermissionIndexer;
import org.sonar.server.permission.index.WebAuthorizationTypeSupport;
+import org.sonar.server.security.SecurityStandards;
+import org.sonar.server.security.SecurityStandards.SQCategory;
import org.sonar.server.tester.UserSessionRule;
import org.sonar.server.ws.TestRequest;
import org.sonar.server.ws.WsActionTester;
import org.sonarqube.ws.Hotspots.Component;
import org.sonarqube.ws.Hotspots.SearchWsResponse;
+import static java.util.Collections.singleton;
import static org.apache.commons.lang.RandomStringUtils.randomAlphabetic;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.sonar.core.util.stream.MoreCollectors.uniqueIndex;
import static org.sonar.db.component.ComponentTesting.newDirectory;
import static org.sonar.db.component.ComponentTesting.newFileDto;
+import static org.sonar.db.issue.IssueTesting.newIssue;
public class SearchActionTest {
private static final Random RANDOM = new Random();
public void fails_with_ForbiddenException_if_project_is_private_and_not_allowed() {
ComponentDto project = dbTester.components().insertPrivateProject();
userSessionRule.registerComponents(project);
- TestRequest request = actionTester.newRequest()
- .setParam("projectKey", project.getKey());
+ TestRequest request = newRequest(project);
assertThatThrownBy(request::execute)
.isInstanceOf(ForbiddenException.class)
ComponentDto project = dbTester.components().insertPublicProject();
userSessionRule.registerComponents(project);
- SearchWsResponse response = actionTester.newRequest()
- .setParam("projectKey", project.getKey())
+ SearchWsResponse response = newRequest(project)
.executeProtobuf(SearchWsResponse.class);
assertThat(response.getHotspotsList()).isEmpty();
userSessionRule.registerComponents(project);
userSessionRule.logIn().addProjectPermission(UserRole.USER, project);
- SearchWsResponse response = actionTester.newRequest()
- .setParam("projectKey", project.getKey())
+ SearchWsResponse response = newRequest(project)
.executeProtobuf(SearchWsResponse.class);
assertThat(response.getHotspotsList()).isEmpty();
});
indexIssues();
- SearchWsResponse response = actionTester.newRequest()
- .setParam("projectKey", project.getKey())
+ SearchWsResponse response = newRequest(project)
.executeProtobuf(SearchWsResponse.class);
assertThat(response.getHotspotsList()).isEmpty();
.toArray(IssueDto[]::new);
indexIssues();
- SearchWsResponse response = actionTester.newRequest()
- .setParam("projectKey", project.getKey())
+ SearchWsResponse response = newRequest(project)
.executeProtobuf(SearchWsResponse.class);
assertThat(response.getHotspotsList())
.toArray(IssueDto[]::new);
indexIssues();
- SearchWsResponse responseProject1 = actionTester.newRequest()
- .setParam("projectKey", project1.getKey())
+ SearchWsResponse responseProject1 = newRequest(project1)
.executeProtobuf(SearchWsResponse.class);
assertThat(responseProject1.getHotspotsList())
.extracting(Hotspots.Rule::getKey)
.containsOnly(Arrays.stream(hotspots2).map(t -> t.getRuleKey().toString()).toArray(String[]::new));
- SearchWsResponse responseProject2 = actionTester.newRequest()
- .setParam("projectKey", project2.getKey())
+ SearchWsResponse responseProject2 = newRequest(project2)
.executeProtobuf(SearchWsResponse.class);
assertThat(responseProject2.getHotspotsList())
t -> t.setType(SECURITY_HOTSPOT).setResolution(randomAlphabetic(5)));
indexIssues();
- SearchWsResponse response = actionTester.newRequest()
- .setParam("projectKey", project.getKey())
+ SearchWsResponse response = newRequest(project)
.executeProtobuf(SearchWsResponse.class);
assertThat(response.getHotspotsList())
.setAuthorLogin(randomAlphabetic(8)));
indexIssues();
- SearchWsResponse response = actionTester.newRequest()
- .setParam("projectKey", project.getKey())
+ SearchWsResponse response = newRequest(project)
.executeProtobuf(SearchWsResponse.class);
assertThat(response.getHotspotsList()).hasSize(1);
.setAuthorLogin(null));
indexIssues();
- SearchWsResponse response = actionTester.newRequest()
- .setParam("projectKey", project.getKey())
+ SearchWsResponse response = newRequest(project)
.executeProtobuf(SearchWsResponse.class);
assertThat(response.getHotspotsList())
IssueDto projectHotspot = dbTester.issues().insert(rule, project, project, t -> t.setType(SECURITY_HOTSPOT));
indexIssues();
- SearchWsResponse response = actionTester.newRequest()
- .setParam("projectKey", project.getKey())
+ SearchWsResponse response = newRequest(project)
.executeProtobuf(SearchWsResponse.class);
assertThat(response.getHotspotsList())
assertThat(actualFile.getPath()).isEqualTo(file.path());
}
+ @Test
+ public void returns_hotspots_ordered_by_vulnerabilityProbability_score_then_rule_id() {
+ ComponentDto project = dbTester.components().insertPublicProject();
+ userSessionRule.registerComponents(project);
+ indexPermissions();
+ ComponentDto file = dbTester.components().insertComponent(newFileDto(project));
+ List<IssueDto> hotspots = Arrays.stream(SQCategory.values())
+ .sorted(Ordering.from(Comparator.<SQCategory>comparingInt(t1 -> t1.getVulnerability().getScore()).reversed())
+ .thenComparing(SQCategory::getKey))
+ .flatMap(sqCategory -> {
+ Set<String> cwes = SecurityStandards.CWES_BY_SQ_CATEGORY.get(sqCategory);
+ Set<String> securityStandards = singleton("cwe:" + (cwes == null ? "unknown" : cwes.iterator().next()));
+ RuleDefinitionDto rule1 = newRule(
+ SECURITY_HOTSPOT,
+ t -> t.setName("rule_" + sqCategory.name() + "_a").setSecurityStandards(securityStandards));
+ RuleDefinitionDto rule2 = newRule(
+ SECURITY_HOTSPOT,
+ t -> t.setName("rule_" + sqCategory.name() + "_b").setSecurityStandards(securityStandards));
+ return Stream.of(
+ newIssue(rule1, project, file).setKee(sqCategory + "_a").setType(SECURITY_HOTSPOT),
+ newIssue(rule2, project, file).setKee(sqCategory + "_b").setType(SECURITY_HOTSPOT));
+ })
+ .collect(Collectors.toList());
+ String[] expectedHotspotKeys = hotspots.stream().map(IssueDto::getKey).toArray(String[]::new);
+ // insert hotspots in random order
+ Collections.shuffle(hotspots);
+ hotspots.forEach(dbTester.issues()::insertIssue);
+ indexIssues();
+
+ SearchWsResponse response = newRequest(project)
+ .executeProtobuf(SearchWsResponse.class);
+
+ assertThat(response.getHotspotsList())
+ .extracting(Hotspots.Hotspot::getKey)
+ .containsExactly(expectedHotspotKeys);
+ }
+
+ @Test
+ public void returns_hotspots_ordered_by_file_path_then_line_then_key() {
+ ComponentDto project = dbTester.components().insertPublicProject();
+ userSessionRule.registerComponents(project);
+ indexPermissions();
+ ComponentDto file1 = dbTester.components().insertComponent(newFileDto(project).setPath("b/c/a"));
+ ComponentDto file2 = dbTester.components().insertComponent(newFileDto(project).setPath("b/c/b"));
+ ComponentDto file3 = dbTester.components().insertComponent(newFileDto(project).setPath("a/a/d"));
+ RuleDefinitionDto rule = newRule(SECURITY_HOTSPOT);
+ List<IssueDto> hotspots = Stream.of(
+ newIssue(rule, project, file3).setType(SECURITY_HOTSPOT).setLine(8),
+ newIssue(rule, project, file3).setType(SECURITY_HOTSPOT).setLine(10),
+ newIssue(rule, project, file1).setType(SECURITY_HOTSPOT).setLine(null),
+ newIssue(rule, project, file1).setType(SECURITY_HOTSPOT).setLine(9),
+ newIssue(rule, project, file1).setType(SECURITY_HOTSPOT).setLine(11).setKee("a"),
+ newIssue(rule, project, file1).setType(SECURITY_HOTSPOT).setLine(11).setKee("b"),
+ newIssue(rule, project, file2).setType(SECURITY_HOTSPOT).setLine(null),
+ newIssue(rule, project, file2).setType(SECURITY_HOTSPOT).setLine(2))
+ .collect(Collectors.toList());
+ String[] expectedHotspotKeys = hotspots.stream().map(IssueDto::getKey).toArray(String[]::new);
+ // insert hotspots in random order
+ Collections.shuffle(hotspots);
+ hotspots.forEach(dbTester.issues()::insertIssue);
+ indexIssues();
+
+ SearchWsResponse response = newRequest(project)
+ .executeProtobuf(SearchWsResponse.class);
+
+ assertThat(response.getHotspotsList())
+ .extracting(Hotspots.Hotspot::getKey)
+ .containsExactly(expectedHotspotKeys);
+ }
+
+ @Test
+ public void returns_first_page_with_100_results_by_default() {
+ ComponentDto project = dbTester.components().insertPublicProject();
+ userSessionRule.registerComponents(project);
+ indexPermissions();
+ ComponentDto file = dbTester.components().insertComponent(newFileDto(project));
+ RuleDefinitionDto rule = newRule(SECURITY_HOTSPOT);
+ int total = 436;
+ List<IssueDto> hotspots = IntStream.range(0, total)
+ .mapToObj(i -> newIssue(rule, project, file).setType(SECURITY_HOTSPOT).setLine(i))
+ .map(i -> dbTester.issues().insertIssue(i))
+ .collect(Collectors.toList());
+ indexIssues();
+
+ TestRequest request = newRequest(project);
+
+ SearchWsResponse response = request.executeProtobuf(SearchWsResponse.class);
+
+ assertThat(response.getHotspotsList())
+ .extracting(Hotspots.Hotspot::getKey)
+ .containsExactly(hotspots.stream().limit(100).map(IssueDto::getKey).toArray(String[]::new));
+ assertThat(response.getPaging().getTotal()).isEqualTo(hotspots.size());
+ assertThat(response.getPaging().getPageIndex()).isEqualTo(1);
+ assertThat(response.getPaging().getPageSize()).isEqualTo(100);
+ }
+
+ @Test
+ public void returns_specified_page_with_100_results_by_default() {
+ ComponentDto project = dbTester.components().insertPublicProject();
+ userSessionRule.registerComponents(project);
+ indexPermissions();
+ ComponentDto file = dbTester.components().insertComponent(newFileDto(project));
+ RuleDefinitionDto rule = newRule(SECURITY_HOTSPOT);
+
+ verifyPaging(project, file, rule, 336, 100);
+ }
+
+ @Test
+ public void returns_specified_page_with_specified_number_of_results() {
+ ComponentDto project = dbTester.components().insertPublicProject();
+ userSessionRule.registerComponents(project);
+ indexPermissions();
+ ComponentDto file = dbTester.components().insertComponent(newFileDto(project));
+ RuleDefinitionDto rule = newRule(SECURITY_HOTSPOT);
+ int total = 336;
+ int pageSize = 1 + new Random().nextInt(100);
+
+ verifyPaging(project, file, rule, total, pageSize);
+ }
+
+ private void verifyPaging(ComponentDto project, ComponentDto file, RuleDefinitionDto rule, int total, int pageSize) {
+ List<IssueDto> hotspots = IntStream.range(0, total)
+ .mapToObj(i -> newIssue(rule, project, file).setType(SECURITY_HOTSPOT).setLine(i).setKee("issue_" + i))
+ .map(i -> dbTester.issues().insertIssue(i))
+ .collect(Collectors.toList());
+ indexIssues();
+
+ SearchWsResponse response = newRequest(project)
+ .setParam("p", "3")
+ .setParam("ps", String.valueOf(pageSize))
+ .executeProtobuf(SearchWsResponse.class);
+
+ assertThat(response.getHotspotsList())
+ .extracting(Hotspots.Hotspot::getKey)
+ .containsExactly(hotspots.stream().skip(2 * pageSize).limit(pageSize).map(IssueDto::getKey).toArray(String[]::new));
+ assertThat(response.getPaging().getTotal()).isEqualTo(hotspots.size());
+ assertThat(response.getPaging().getPageIndex()).isEqualTo(3);
+ assertThat(response.getPaging().getPageSize()).isEqualTo(pageSize);
+
+ response = newRequest(project)
+ .setParam("p", "4")
+ .setParam("ps", String.valueOf(pageSize))
+ .executeProtobuf(SearchWsResponse.class);
+
+ assertThat(response.getHotspotsList())
+ .extracting(Hotspots.Hotspot::getKey)
+ .containsExactly(hotspots.stream().skip(3 * pageSize).limit(pageSize).map(IssueDto::getKey).toArray(String[]::new));
+ assertThat(response.getPaging().getTotal()).isEqualTo(hotspots.size());
+ assertThat(response.getPaging().getPageIndex()).isEqualTo(4);
+ assertThat(response.getPaging().getPageSize()).isEqualTo(pageSize);
+
+ int emptyPage = (hotspots.size() / pageSize) + 10;
+ response = newRequest(project)
+ .setParam("p", String.valueOf(emptyPage))
+ .setParam("ps", String.valueOf(pageSize))
+ .executeProtobuf(SearchWsResponse.class);
+
+ assertThat(response.getHotspotsList())
+ .extracting(Hotspots.Hotspot::getKey)
+ .isEmpty();
+ assertThat(response.getPaging().getTotal()).isEqualTo(hotspots.size());
+ assertThat(response.getPaging().getPageIndex()).isEqualTo(emptyPage);
+ assertThat(response.getPaging().getPageSize()).isEqualTo(pageSize);
+ }
+
+ private TestRequest newRequest(ComponentDto project) {
+ return actionTester.newRequest()
+ .setParam("projectKey", project.getKey());
+ }
+
private void indexPermissions() {
permissionIndexer.indexOnStartup(permissionIndexer.getIndexTypes());
}