also removed Rule fields from common proto which are useless in our case (status, lang and langName)
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 {
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()
.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"))
.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;
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;
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;
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
.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()));
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 {
Set<RuleDefinitionDto> getRules() {
return rules;
}
+
}
}
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;
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;
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;
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
assertThat(response.getHotspotsList()).isEmpty();
assertThat(response.getComponentsList()).isEmpty();
- assertThat(response.getRules().getRulesList()).isEmpty();
+ assertThat(response.getRulesList()).isEmpty();
}
@Test
assertThat(response.getHotspotsList()).isEmpty();
assertThat(response.getComponentsList()).isEmpty();
- assertThat(response.getRules().getRulesList()).isEmpty();
+ assertThat(response.getRulesList()).isEmpty();
}
@Test
assertThat(response.getHotspotsList()).isEmpty();
assertThat(response.getComponentsList()).isEmpty();
- assertThat(response.getRules().getRulesList()).isEmpty();
+ assertThat(response.getRulesList()).isEmpty();
}
@Test
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));
}
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()
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));
}
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());
}
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 {
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;
+}