Browse Source

SONAR-12717 add SQ category and VulnerabilityProbability to response

also removed Rule fields from common proto which are useless in our case (status, lang and langName)
tags/8.2.0.32929
Sébastien Lesaint 4 years ago
parent
commit
9f42cc0040

+ 38
- 22
server/sonar-server-common/src/main/java/org/sonar/server/security/SecurityStandards.java View File

@@ -34,6 +34,9 @@ import org.sonar.core.util.stream.MoreCollectors;

import static java.util.Arrays.asList;
import static java.util.Collections.singleton;
import static org.sonar.server.security.SecurityStandards.VulnerabilityProbability.HIGH;
import static org.sonar.server.security.SecurityStandards.VulnerabilityProbability.LOW;
import static org.sonar.server.security.SecurityStandards.VulnerabilityProbability.MEDIUM;

@Immutable
public final class SecurityStandards {
@@ -54,37 +57,49 @@ public final class SecurityStandards {
SANS_TOP_25_RISKY_RESOURCE, RISKY_CWE,
SANS_TOP_25_POROUS_DEFENSES, POROUS_CWE);

public enum VulnerabilityProbability {
HIGH,
MEDIUM,
LOW
}

public enum SQCategory {
SQL_INJECTION("sql-injection"),
COMMAND_INJECTION("command-injection"),
PATH_TRAVERSAL_INJECTION("path-traversal-injection"),
LDAP_INJECTION("ldap-injection"),
XPATH_INJECT("xpath-injection"),
RCE("rce"),
DOS("dos"),
SSRF("ssrf"),
CSRF("csrf"),
XSS("xss"),
LOG_INJECTION("log-injection"),
HTTP_RESPONSE_SPLITTING("http-response-splitting"),
OPEN_REDIRECT("open-redirect"),
XXE("xxe"),
OBJECT_INJECTION("object-injection"),
WEAK_CRYPTOGRAPHY("weak-cryptography"),
AUTH("auth"),
INSECURE_CONF("insecure-conf"),
FILE_MANIPULATION("file-manipulation"),
OTHERS("others");
SQL_INJECTION("sql-injection", HIGH),
COMMAND_INJECTION("command-injection", HIGH),
PATH_TRAVERSAL_INJECTION("path-traversal-injection", HIGH),
LDAP_INJECTION("ldap-injection", LOW),
XPATH_INJECTION("xpath-injection", LOW),
RCE("rce", MEDIUM),
DOS("dos", MEDIUM),
SSRF("ssrf", MEDIUM),
CSRF("csrf", HIGH),
XSS("xss", HIGH),
LOG_INJECTION("log-injection", LOW),
HTTP_RESPONSE_SPLITTING("http-response-splitting", LOW),
OPEN_REDIRECT("open-redirect", MEDIUM),
XXE("xxe", MEDIUM),
OBJECT_INJECTION("object-injection", LOW),
WEAK_CRYPTOGRAPHY("weak-cryptography", MEDIUM),
AUTH("auth", HIGH),
INSECURE_CONF("insecure-conf", LOW),
FILE_MANIPULATION("file-manipulation", LOW),
OTHERS("others", LOW);

private final String key;
private final VulnerabilityProbability vulnerability;

SQCategory(String key) {
SQCategory(String key, VulnerabilityProbability vulnerability) {
this.key = key;
this.vulnerability = vulnerability;
}

public String getKey() {
return key;
}

public VulnerabilityProbability getVulnerability() {
return vulnerability;
}
}

public static final Map<SQCategory, Set<String>> CWES_BY_SQ_CATEGORY = ImmutableMap.<SQCategory, Set<String>>builder()
@@ -92,7 +107,7 @@ public final class SecurityStandards {
.put(SQCategory.COMMAND_INJECTION, ImmutableSet.of("77", "78", "88", "214"))
.put(SQCategory.PATH_TRAVERSAL_INJECTION, ImmutableSet.of("22"))
.put(SQCategory.LDAP_INJECTION, ImmutableSet.of("90"))
.put(SQCategory.XPATH_INJECT, ImmutableSet.of("643"))
.put(SQCategory.XPATH_INJECTION, ImmutableSet.of("643"))
.put(SQCategory.RCE, ImmutableSet.of("94", "95"))
.put(SQCategory.DOS, ImmutableSet.of("400", "624"))
.put(SQCategory.SSRF, ImmutableSet.of("918"))
@@ -108,6 +123,7 @@ public final class SecurityStandards {
.put(SQCategory.INSECURE_CONF, ImmutableSet.of("102", "215", "311", "315", "346", "614", "489", "942"))
.put(SQCategory.FILE_MANIPULATION, ImmutableSet.of("97", "73"))
.build();
public static final Ordering<SQCategory> SQ_CATEGORY_ORDERING = Ordering.explicit(Arrays.stream(SQCategory.values()).collect(Collectors.toList()));
public static final Ordering<String> SQ_CATEGORY_KEYS_ORDERING = Ordering.explicit(Arrays.stream(SQCategory.values()).map(SQCategory::getKey).collect(Collectors.toList()));

private final Set<String> standards;

+ 17
- 26
server/sonar-webserver-webapi/src/main/java/org/sonar/server/hotspot/ws/SearchAction.java View File

@@ -31,8 +31,6 @@ import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.search.SearchHit;
import org.sonar.api.resources.Language;
import org.sonar.api.resources.Languages;
import org.sonar.api.resources.Qualifiers;
import org.sonar.api.resources.Scopes;
import org.sonar.api.rule.RuleKey;
@@ -52,8 +50,8 @@ import org.sonar.server.exceptions.NotFoundException;
import org.sonar.server.issue.index.IssueIndex;
import org.sonar.server.issue.index.IssueQuery;
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;
@@ -72,14 +70,12 @@ public class SearchAction implements HotspotsWsAction {
private final UserSession userSession;
private final IssueIndex issueIndex;
private final DefaultOrganizationProvider defaultOrganizationProvider;
private final Languages languages;

public SearchAction(DbClient dbClient, UserSession userSession, IssueIndex issueIndex, DefaultOrganizationProvider defaultOrganizationProvider, Languages languages) {
public SearchAction(DbClient dbClient, UserSession userSession, IssueIndex issueIndex, DefaultOrganizationProvider defaultOrganizationProvider) {
this.dbClient = dbClient;
this.userSession = userSession;
this.issueIndex = issueIndex;
this.defaultOrganizationProvider = defaultOrganizationProvider;
this.languages = languages;
}

@Override
@@ -194,15 +190,15 @@ public class SearchAction implements HotspotsWsAction {
.setProject(hotspot.getProjectKey())
.setRule(hotspot.getRuleKey().toString());
ofNullable(hotspot.getStatus()).ifPresent(builder::setStatus);
// FIXME resolution field will be added later
// ofNullable(hotspot.getResolution()).ifPresent(builder::setResolution);
// FIXME resolution field will be added later
// ofNullable(hotspot.getResolution()).ifPresent(builder::setResolution);
ofNullable(hotspot.getLine()).ifPresent(builder::setLine);
builder.setMessage(nullToEmpty(hotspot.getMessage()));
ofNullable(hotspot.getAssigneeUuid()).ifPresent(builder::setAssignee);
// FIXME Filter author only if user is member of the organization (as done in issues/search WS)
// if (data.getUserOrganizationUuids().contains(component.getOrganizationUuid())) {
// if (data.getUserOrganizationUuids().contains(component.getOrganizationUuid())) {
builder.setAuthor(nullToEmpty(hotspot.getAuthorLogin()));
// }
// }
builder.setCreationDate(formatDateTime(hotspot.getIssueCreationDate()));
builder.setUpdateDate(formatDateTime(hotspot.getIssueUpdateDate()));

@@ -237,28 +233,22 @@ public class SearchAction implements HotspotsWsAction {
return;
}

Common.Rules.Builder rulesBuilder = Common.Rules.newBuilder();
Common.Rule.Builder ruleBuilder = Common.Rule.newBuilder();
Hotspots.Rule.Builder ruleBuilder = Hotspots.Rule.newBuilder();
for (RuleDefinitionDto rule : rules) {
SecurityStandards securityStandards = SecurityStandards.fromSecurityStandards(rule.getSecurityStandards());
SecurityStandards.SQCategory sqCategory = securityStandards.getSq()
.stream()
.min(SecurityStandards.SQ_CATEGORY_ORDERING)
.orElse(SecurityStandards.SQCategory.OTHERS);
ruleBuilder
.clear()
.setKey(rule.getKey().toString())
.setName(nullToEmpty(rule.getName()))
.setStatus(Common.RuleStatus.valueOf(rule.getStatus().name()));

String language = rule.getLanguage();
if (language == null) {
ruleBuilder.setLang("");
} else {
ruleBuilder.setLang(language);
Language lang = languages.get(language);
if (lang != null) {
ruleBuilder.setLangName(lang.getName());
}
}
rulesBuilder.addRules(ruleBuilder.build());
.setSecurityCategory(sqCategory.getKey())
.setVulnerabilityProbability(sqCategory.getVulnerability().name());

responseBuilder.addRules(ruleBuilder.build());
}
responseBuilder.setRules(rulesBuilder.build());
}

private static final class SearchResponseData {
@@ -293,5 +283,6 @@ public class SearchAction implements HotspotsWsAction {
Set<RuleDefinitionDto> getRules() {
return rules;
}

}
}

+ 10
- 66
server/sonar-webserver-webapi/src/test/java/org/sonar/server/hotspot/ws/SearchActionTest.java View File

@@ -28,9 +28,6 @@ import java.util.stream.IntStream;
import org.junit.Rule;
import org.junit.Test;
import org.sonar.api.issue.Issue;
import org.sonar.api.resources.AbstractLanguage;
import org.sonar.api.resources.Languages;
import org.sonar.api.rule.RuleKey;
import org.sonar.api.rules.RuleType;
import org.sonar.api.utils.System2;
import org.sonar.api.web.UserRole;
@@ -54,8 +51,6 @@ import org.sonar.server.permission.index.WebAuthorizationTypeSupport;
import org.sonar.server.tester.UserSessionRule;
import org.sonar.server.ws.TestRequest;
import org.sonar.server.ws.WsActionTester;
import org.sonarqube.ws.Common;
import org.sonarqube.ws.Common.RuleStatus;
import org.sonarqube.ws.Hotspots;
import org.sonarqube.ws.Hotspots.Component;
import org.sonarqube.ws.Hotspots.SearchWsResponse;
@@ -63,8 +58,6 @@ import org.sonarqube.ws.Hotspots.SearchWsResponse;
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.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import static org.sonar.api.rules.RuleType.SECURITY_HOTSPOT;
import static org.sonar.api.utils.DateUtils.formatDateTime;
import static org.sonar.core.util.stream.MoreCollectors.uniqueIndex;
@@ -88,8 +81,7 @@ public class SearchActionTest {
private IssueIndexer issueIndexer = new IssueIndexer(es.client(), dbClient, new IssueIteratorFactory(dbClient));
private StartupIndexer permissionIndexer = new PermissionIndexer(dbClient, es.client(), issueIndexer);

private Languages languages = mock(Languages.class);
private SearchAction underTest = new SearchAction(dbClient, userSessionRule, issueIndex, defaultOrganizationProvider, languages);
private SearchAction underTest = new SearchAction(dbClient, userSessionRule, issueIndex, defaultOrganizationProvider);
private WsActionTester actionTester = new WsActionTester(underTest);

@Test
@@ -158,7 +150,7 @@ public class SearchActionTest {

assertThat(response.getHotspotsList()).isEmpty();
assertThat(response.getComponentsList()).isEmpty();
assertThat(response.getRules().getRulesList()).isEmpty();
assertThat(response.getRulesList()).isEmpty();
}

@Test
@@ -173,7 +165,7 @@ public class SearchActionTest {

assertThat(response.getHotspotsList()).isEmpty();
assertThat(response.getComponentsList()).isEmpty();
assertThat(response.getRules().getRulesList()).isEmpty();
assertThat(response.getRulesList()).isEmpty();
}

@Test
@@ -196,7 +188,7 @@ public class SearchActionTest {

assertThat(response.getHotspotsList()).isEmpty();
assertThat(response.getComponentsList()).isEmpty();
assertThat(response.getRules().getRulesList()).isEmpty();
assertThat(response.getRulesList()).isEmpty();
}

@Test
@@ -230,8 +222,8 @@ public class SearchActionTest {
assertThat(response.getComponentsList())
.extracting(Component::getKey)
.containsOnly(project.getKey(), fileWithHotspot.getKey());
assertThat(response.getRules().getRulesList())
.extracting(Common.Rule::getKey)
assertThat(response.getRulesList())
.extracting(Hotspots.Rule::getKey)
.containsOnly(Arrays.stream(hotspots).map(t -> t.getRuleKey().toString()).toArray(String[]::new));
}

@@ -262,8 +254,8 @@ public class SearchActionTest {
assertThat(responseProject1.getComponentsList())
.extracting(Component::getKey)
.containsOnly(project1.getKey(), file1.getKey());
assertThat(responseProject1.getRules().getRulesList())
.extracting(Common.Rule::getKey)
assertThat(responseProject1.getRulesList())
.extracting(Hotspots.Rule::getKey)
.containsOnly(Arrays.stream(hotspots2).map(t -> t.getRuleKey().toString()).toArray(String[]::new));

SearchWsResponse responseProject2 = actionTester.newRequest()
@@ -276,8 +268,8 @@ public class SearchActionTest {
assertThat(responseProject2.getComponentsList())
.extracting(Component::getKey)
.containsOnly(project2.getKey(), file2.getKey());
assertThat(responseProject2.getRules().getRulesList())
.extracting(Common.Rule::getKey)
assertThat(responseProject2.getRulesList())
.extracting(Hotspots.Rule::getKey)
.containsOnly(Arrays.stream(hotspots2).map(t -> t.getRuleKey().toString()).toArray(String[]::new));
}

@@ -423,54 +415,6 @@ public class SearchActionTest {
assertThat(actualFile.getPath()).isEqualTo(file.path());
}

@Test
public void returns_details_of_rule_with_language_name_when_available() {
ComponentDto project = dbTester.components().insertPublicProject();
userSessionRule.registerComponents(project);
indexPermissions();
ComponentDto file = dbTester.components().insertComponent(newFileDto(project));
String language1 = randomAlphabetic(3);
String language2 = randomAlphabetic(2);
RuleDefinitionDto rule1a = newRule(SECURITY_HOTSPOT, t -> t.setLanguage(language1));
RuleDefinitionDto rule1b = newRule(SECURITY_HOTSPOT, t -> t.setLanguage(language1));
RuleDefinitionDto rule2 = newRule(SECURITY_HOTSPOT, t -> t.setLanguage(language2));
when(languages.get(language1)).thenReturn(new AbstractLanguage(language1, language1 + "_name") {
@Override
public String[] getFileSuffixes() {
return new String[0];
}
});
IssueDto hotspot1a = dbTester.issues().insert(rule1a, project, file, t -> t.setType(SECURITY_HOTSPOT));
IssueDto hotspot1b = dbTester.issues().insert(rule1b, project, file, t -> t.setType(SECURITY_HOTSPOT));
IssueDto hotspot2 = dbTester.issues().insert(rule2, project, file, t -> t.setType(SECURITY_HOTSPOT));
indexIssues();

SearchWsResponse response = actionTester.newRequest()
.setParam("projectKey", project.getKey())
.executeProtobuf(SearchWsResponse.class);

assertThat(response.getHotspotsList()).hasSize(3);
assertThat(response.getRules().getRulesList()).hasSize(3);
Map<RuleKey, Common.Rule> rulesByKey = response.getRules().getRulesList()
.stream()
.collect(uniqueIndex(t -> RuleKey.parse(t.getKey())));
Common.Rule actualRule1a = rulesByKey.get(hotspot1a.getRuleKey());
assertThat(actualRule1a.getName()).isEqualTo(rule1a.getName());
assertThat(actualRule1a.getLang()).isEqualTo(rule1a.getLanguage());
assertThat(actualRule1a.getLangName()).isEqualTo(rule1a.getLanguage() + "_name");
assertThat(actualRule1a.getStatus()).isEqualTo(RuleStatus.valueOf(rule1a.getStatus().name()));
Common.Rule actualRule1b = rulesByKey.get(hotspot1b.getRuleKey());
assertThat(actualRule1b.getName()).isEqualTo(rule1b.getName());
assertThat(actualRule1b.getLang()).isEqualTo(rule1b.getLanguage());
assertThat(actualRule1b.getLangName()).isEqualTo(rule1b.getLanguage() + "_name");
assertThat(actualRule1b.getStatus()).isEqualTo(RuleStatus.valueOf(rule1b.getStatus().name()));
Common.Rule actualRule2 = rulesByKey.get(hotspot2.getRuleKey());
assertThat(actualRule2.getName()).isEqualTo(rule2.getName());
assertThat(actualRule2.getLang()).isEqualTo(rule2.getLanguage());
assertThat(actualRule2.hasLangName()).isFalse();
assertThat(actualRule2.getStatus()).isEqualTo(RuleStatus.valueOf(rule2.getStatus().name()));
}

private void indexPermissions() {
permissionIndexer.indexOnStartup(permissionIndexer.getIndexTypes());
}

+ 8
- 1
sonar-ws/src/main/protobuf/ws-hotspots.proto View File

@@ -31,7 +31,7 @@ message SearchWsResponse {
optional sonarqube.ws.commons.Paging paging = 1;
repeated Hotspot hotspots = 2;
repeated Component components = 3;
optional sonarqube.ws.commons.Rules rules = 4;
repeated Rule rules = 4;
}

message Hotspot {
@@ -59,3 +59,10 @@ message Component {
optional string longName = 5;
optional string path = 6;
}

message Rule {
optional string key = 1;
optional string name = 2;
optional string securityCategory = 3;
optional string vulnerabilityProbability = 4;
}

Loading…
Cancel
Save