import org.sonar.api.rule.RuleKey;
import org.sonar.api.rule.RuleStatus;
import org.sonar.api.rule.Severity;
+import org.sonar.api.rules.RuleCharacteristic;
import org.sonar.api.rules.RuleQuery;
import org.sonar.api.rules.RuleType;
import org.sonar.api.server.debt.DebtRemediationFunction;
.setSystemTags(newHashSet("systag1", "systag2"))
.setSecurityStandards(newHashSet("owaspTop10:a1", "cwe:123"))
.setType(RuleType.BUG)
- .setCharacteristic(CLEAR)
+ .setCharacteristic(RuleCharacteristic.TESTED)
.setScope(Scope.ALL)
.setCreatedAt(1_500_000_000_000L)
.setUpdatedAt(2_000_000_000_000L);
assertThat(ruleDto.getSecurityStandards()).containsOnly("owaspTop10:a1", "cwe:123");
assertThat(ruleDto.getScope()).isEqualTo(Scope.ALL);
assertThat(ruleDto.getType()).isEqualTo(RuleType.BUG.getDbConstant());
- assertThat(ruleDto.getCharacteristic()).isEqualTo(CLEAR);
-
+ assertThat(ruleDto.getCharacteristic()).isEqualTo(RuleCharacteristic.TESTED);
assertThat(ruleDto.getCreatedAt()).isEqualTo(1_500_000_000_000L);
assertThat(ruleDto.getUpdatedAt()).isEqualTo(2_000_000_000_000L);
assertThat(ruleDto.getDescriptionFormat()).isEqualTo(RuleDto.Format.MARKDOWN);
assertThat(ruleDto.getGapDescription()).isEqualTo(rule.getGapDescription());
assertThat(ruleDto.getSystemTags()).containsAll(rule.getSystemTags());
assertThat(ruleDto.getType()).isEqualTo(rule.getType());
+ assertThat(ruleDto.getCharacteristic()).isEqualTo(rule.getCharacteristic());
+
}
@Test
@CheckForNull
public RuleCharacteristic getEffectiveRuleCharacteristic() {
- return ruleCharacteristic != null ? ruleCharacteristic : convertTypeToCharacteristic(ruleType);
- }
-
- private static RuleCharacteristic convertTypeToCharacteristic(int type) {
- RuleType ruleType = RuleType.valueOf(type);
- return convertToRuleCharacteristic(ruleType);
+ return ruleCharacteristic != null ? ruleCharacteristic : convertToRuleCharacteristic(ruleType);
}
public int getRuleType() {
import static java.util.Collections.emptySet;
import static java.util.Optional.ofNullable;
import static org.sonar.db.rule.RuleDescriptionSectionDto.DEFAULT_KEY;
-import static org.sonar.db.rule.RuleTypeToRuleCharacteristicConverter.convertToRuleCharacteristic;
public class RuleDto {
return this.characteristic;
}
- @CheckForNull
public RuleCharacteristic getEffectiveCharacteristic() {
- return characteristic != null ? characteristic : convertTypeToCharacteristic(type);
- }
-
- private static RuleCharacteristic convertTypeToCharacteristic(int type) {
- RuleType ruleType = RuleType.valueOf(type);
- return convertToRuleCharacteristic(ruleType);
+ return characteristic != null ? characteristic : RuleTypeToRuleCharacteristicConverter.convertToRuleCharacteristic(type);
}
public RuleDto setCharacteristic(RuleCharacteristic characteristic) {
import javax.annotation.CheckForNull;
import org.sonar.api.rule.RuleKey;
import org.sonar.api.rule.RuleStatus;
+import org.sonar.api.rules.RuleCharacteristic;
import org.sonar.api.rules.RuleType;
public class RuleForIndexingDto {
private long createdAt;
private long updatedAt;
private Set<RuleDescriptionSectionDto> ruleDescriptionSectionsDtos = new HashSet<>();
+ private RuleCharacteristic characteristic;
@VisibleForTesting
public RuleForIndexingDto() {
ruleForIndexingDto.type = r.getType();
ruleForIndexingDto.createdAt = r.getCreatedAt();
ruleForIndexingDto.updatedAt = r.getUpdatedAt();
+ ruleForIndexingDto.characteristic = r.getEffectiveCharacteristic();
if (r.getRuleDescriptionSectionDtos() != null) {
ruleForIndexingDto.setRuleDescriptionSectionsDtos(Sets.newHashSet(r.getRuleDescriptionSectionDtos()));
}
public void setType(int type) {
this.type = type;
}
+
+ public RuleCharacteristic getCharacteristic() {
+ return characteristic;
+ }
}
private RuleTypeToRuleCharacteristicConverter() {
}
+ public static RuleCharacteristic convertToRuleCharacteristic(int ruleType) {
+ if (ruleType == 0) {
+ return RuleCharacteristic.CLEAR;
+ }
+ return convertToRuleCharacteristic(RuleType.valueOf(ruleType));
+ }
+
public static RuleCharacteristic convertToRuleCharacteristic(RuleType ruleType) {
return switch (ruleType) {
case BUG -> RuleCharacteristic.ROBUST;
assertThat(effectiveCharacteristic).isEqualTo(characteristic).isEqualTo(RuleCharacteristic.COMPLIANT);
}
+ @Test
+ public void getEffectiveCharacteristic_whenType0_shouldReturnClearCharacteristic() {
+ RuleDto rule = new RuleDto().setType(0);
+
+ RuleCharacteristic effectiveCharacteristic = rule.getEffectiveCharacteristic();
+
+ assertThat(effectiveCharacteristic).isEqualTo(RuleCharacteristic.CLEAR);
+ }
+
}
return rule -> rule.setType(type);
}
+ public static Consumer<RuleDto> setCharacteristic(RuleCharacteristic characteristic) {
+ return rule -> rule.setCharacteristic(characteristic);
+ }
+
public static Consumer<RuleDto> setIsExternal(boolean isExternal) {
return rule -> rule.setIsExternal(isExternal);
}
import java.util.stream.Stream;
import org.junit.Rule;
import org.junit.Test;
+import org.sonar.api.rules.RuleCharacteristic;
import org.sonar.api.utils.System2;
import org.sonar.db.DbTester;
import org.sonar.db.component.ComponentDto;
assertThat(issue.getTags()).containsOnly("tag1", "tag2", "tag3");
assertThat(issue.effort().toMinutes()).isPositive();
assertThat(issue.type().getDbConstant()).isEqualTo(2);
+ assertThat(issue.characteristic()).isEqualTo(RuleCharacteristic.CLEAR);
}
@Test
import org.sonar.server.qualityprofile.index.ActiveRuleIndexer;
import org.sonar.server.security.SecurityStandards;
-import static com.google.common.collect.ImmutableSet.of;
+import static java.util.Set.of;
import static java.util.Arrays.asList;
import static java.util.Collections.emptyList;
import static java.util.Collections.emptySet;
import static org.sonar.api.rule.Severity.INFO;
import static org.sonar.api.rule.Severity.MAJOR;
import static org.sonar.api.rule.Severity.MINOR;
+import static org.sonar.api.rules.RuleCharacteristic.CLEAR;
+import static org.sonar.api.rules.RuleCharacteristic.COMPLIANT;
+import static org.sonar.api.rules.RuleCharacteristic.SECURE;
import static org.sonar.api.rules.RuleType.BUG;
import static org.sonar.api.rules.RuleType.CODE_SMELL;
import static org.sonar.api.rules.RuleType.SECURITY_HOTSPOT;
import static org.sonar.api.rules.RuleType.VULNERABILITY;
import static org.sonar.db.rule.RuleDescriptionSectionDto.createDefaultRuleDescriptionSection;
import static org.sonar.db.rule.RuleTesting.newRule;
+import static org.sonar.db.rule.RuleTesting.setCharacteristic;
import static org.sonar.db.rule.RuleTesting.setCreatedAt;
import static org.sonar.db.rule.RuleTesting.setIsExternal;
import static org.sonar.db.rule.RuleTesting.setIsTemplate;
assertThat(underTest.search(query, new SearchOptions()).getUuids()).hasSize(4);
}
+ @Test
+ public void search_by_characteristic() {
+ RuleDto secure = createRule(setCharacteristic(SECURE));
+ RuleDto compliant = createRule(setCharacteristic(COMPLIANT));
+ RuleDto clear = createRule(setCharacteristic(CLEAR));
+ index();
+
+ // find all
+ RuleQuery query = new RuleQuery();
+ assertThat(underTest.search(query, new SearchOptions()).getUuids()).hasSize(3);
+
+ // find secure
+ query = new RuleQuery().setCharacteristics(of(SECURE));
+ assertThat(underTest.search(query, new SearchOptions()).getUuids()).containsOnly(secure.getUuid());
+
+ // find compliant
+ query = new RuleQuery().setCharacteristics(of(COMPLIANT));
+ assertThat(underTest.search(query, new SearchOptions()).getUuids()).containsOnly(compliant.getUuid());
+
+ // find clear
+ query = new RuleQuery().setCharacteristics(of(CLEAR));
+ assertThat(underTest.search(query, new SearchOptions()).getUuids()).containsOnly(clear.getUuid());
+
+ // empty list => no filter
+ query = new RuleQuery().setTypes(of());
+ assertThat(underTest.search(query, new SearchOptions()).getUuids()).hasSize(3);
+
+ // null list => no filter
+ query = new RuleQuery().setTypes(null);
+ assertThat(underTest.search(query, new SearchOptions()).getUuids()).hasSize(3);
+ }
+
@Test
public void search_by_is_template() {
RuleDto ruleNoTemplate = createRule(setIsTemplate(false));
private List<String> statuses;
private List<String> tags;
private Set<String> types;
+ private Set<String> characteristics;
private List<String> pciDss32;
private List<String> pciDss40;
private List<String> owaspTop10;
this.owaspAsvsLevel = owaspAsvsLevel;
return this;
}
+
+ @CheckForNull
+ public Collection<String> getCharacteristics() {
+ return characteristics;
+ }
+
+ public SearchRequest setCharacteristics(@Nullable Collection<String> characteristics) {
+ this.characteristics = characteristics == null ? null : Set.copyOf(characteristics);
+ return this;
+ }
+
}
import javax.annotation.CheckForNull;
import javax.annotation.Nullable;
import org.sonar.api.rule.Severity;
+import org.sonar.api.rules.RuleCharacteristic;
import org.sonar.api.rules.RuleType;
import org.sonar.api.utils.Duration;
import org.sonar.server.es.BaseDoc;
return RuleType.valueOf(getField(IssueIndexDefinition.FIELD_ISSUE_TYPE));
}
+ public RuleCharacteristic characteristic() {
+ return RuleCharacteristic.valueOf(getField(IssueIndexDefinition.FIELD_ISSUE_CHARACTERISTIC));
+ }
+
@CheckForNull
public Duration effort() {
Number effort = getNullableField(IssueIndexDefinition.FIELD_ISSUE_EFFORT);
return this;
}
+ public IssueDoc setCharacteristic(RuleCharacteristic characteristic) {
+ setField(IssueIndexDefinition.FIELD_ISSUE_CHARACTERISTIC, characteristic.toString());
+ return this;
+ }
+
@CheckForNull
public Collection<String> getPciDss32() {
return getNullableField(IssueIndexDefinition.FIELD_ISSUE_PCI_DSS_32);
setField(IssueIndexDefinition.FIELD_ISSUE_NEW_CODE_REFERENCE, b);
return this;
}
+
+ public IssueDoc setCharacteristic(String characteristic) {
+ setField(IssueIndexDefinition.FIELD_ISSUE_CHARACTERISTIC, characteristic);
+ return this;
+ }
}
public static final String FIELD_ISSUE_STATUS = "status";
public static final String FIELD_ISSUE_TAGS = "tags";
public static final String FIELD_ISSUE_TYPE = "type";
+ public static final String FIELD_ISSUE_CHARACTERISTIC = "characteristic";
public static final String FIELD_ISSUE_PCI_DSS_32 = "pciDss-3.2";
public static final String FIELD_ISSUE_PCI_DSS_40 = "pciDss-4.0";
public static final String FIELD_ISSUE_OWASP_ASVS_40 = "owaspAsvs-4.0";
mapping.keywordFieldBuilder(FIELD_ISSUE_STATUS).disableNorms().addSubFields(SORTABLE_ANALYZER).build();
mapping.keywordFieldBuilder(FIELD_ISSUE_TAGS).disableNorms().build();
mapping.keywordFieldBuilder(FIELD_ISSUE_TYPE).disableNorms().build();
+ mapping.keywordFieldBuilder(FIELD_ISSUE_CHARACTERISTIC).disableNorms().build();
mapping.keywordFieldBuilder(FIELD_ISSUE_PCI_DSS_32).disableNorms().build();
mapping.keywordFieldBuilder(FIELD_ISSUE_PCI_DSS_40).disableNorms().build();
mapping.keywordFieldBuilder(FIELD_ISSUE_OWASP_ASVS_40).disableNorms().build();
import static org.sonar.api.utils.DateUtils.longToDate;
import static org.sonar.db.DatabaseUtils.getLong;
import static org.sonar.db.rule.RuleDto.deserializeSecurityStandardsString;
+import static org.sonar.db.rule.RuleTypeToRuleCharacteristicConverter.convertToRuleCharacteristic;
import static org.sonar.server.security.SecurityStandards.fromSecurityStandards;
/**
"c.branch_uuid",
"pb.is_main",
"pb.project_uuid",
-
- // column 22
"i.tags",
+
+ // column 21
"i.issue_type",
"r.security_standards",
"c.qualifier",
- "n.uuid"
+ "n.uuid",
+ "r.characteristic",
+ "r.rule_type"
};
private static final String SQL_ALL = "select " + StringUtils.join(FIELDS, ",") + " from issues i " +
doc.setFilePath(filePath);
doc.setDirectoryPath(extractDirPath(doc.filePath(), scope));
String branchUuid = rs.getString(17);
- boolean isMainBranch = rs.getBoolean( 18);
+ boolean isMainBranch = rs.getBoolean(18);
String projectUuid = rs.getString(19);
doc.setBranchUuid(branchUuid);
doc.setIsMainBranch(isMainBranch);
doc.setScope(Qualifiers.UNIT_TEST_FILE.equals(rs.getString(23)) ? IssueScope.TEST : IssueScope.MAIN);
doc.setIsNewCodeReference(!isNullOrEmpty(rs.getString(24)));
+
+ String characteristic = rs.getString(25);
+ doc.setCharacteristic(characteristic != null ? characteristic : convertToRuleCharacteristic(rs.getInt(26)).name());
+
return doc;
}
import org.apache.commons.lang.builder.ReflectionToStringBuilder;
import org.sonar.api.rule.RuleKey;
import org.sonar.api.rule.RuleStatus;
+import org.sonar.api.rules.RuleCharacteristic;
import org.sonar.api.rules.RuleType;
import org.sonar.db.rule.RuleDescriptionSectionDto;
import org.sonar.db.rule.RuleDto;
return this;
}
+ private RuleDoc setCharacteristic(RuleCharacteristic characteristic) {
+ setField(RuleIndexDefinition.FIELD_RULE_CHARACTERISTIC, characteristic.name());
+ return this;
+ }
+
+ public RuleCharacteristic characteristic() {
+ return RuleCharacteristic.valueOf(getField(RuleIndexDefinition.FIELD_RULE_CHARACTERISTIC));
+ }
+
public long createdAt() {
return getField(RuleIndexDefinition.FIELD_RULE_CREATED_AT);
}
.setSeverity(dto.getSeverityAsString())
.setStatus(dto.getStatus().toString())
.setType(dto.getTypeAsRuleType())
+ .setCharacteristic(dto.getCharacteristic())
.setCreatedAt(dto.getCreatedAt())
.setTags(Sets.union(dto.getTags(), dto.getSystemTags()))
.setUpdatedAt(dto.getUpdatedAt())
import org.elasticsearch.search.sort.SortOrder;
import org.sonar.api.rule.RuleStatus;
import org.sonar.api.rule.Severity;
+import org.sonar.api.rules.RuleCharacteristic;
import org.sonar.api.rules.RuleType;
import org.sonar.api.utils.System2;
import org.sonar.core.util.stream.MoreCollectors;
import static org.sonar.server.rule.index.RuleIndexDefinition.FIELD_ACTIVE_RULE_INHERITANCE;
import static org.sonar.server.rule.index.RuleIndexDefinition.FIELD_ACTIVE_RULE_PROFILE_UUID;
import static org.sonar.server.rule.index.RuleIndexDefinition.FIELD_ACTIVE_RULE_SEVERITY;
+import static org.sonar.server.rule.index.RuleIndexDefinition.FIELD_RULE_CHARACTERISTIC;
import static org.sonar.server.rule.index.RuleIndexDefinition.FIELD_RULE_CREATED_AT;
import static org.sonar.server.rule.index.RuleIndexDefinition.FIELD_RULE_CWE;
import static org.sonar.server.rule.index.RuleIndexDefinition.FIELD_RULE_HTML_DESCRIPTION;
public static final String FACET_ACTIVE_SEVERITIES = "active_severities";
public static final String FACET_STATUSES = "statuses";
public static final String FACET_TYPES = "types";
+ public static final String FACET_CHARACTERISTICS = "characteristics";
public static final String FACET_OLD_DEFAULT = "true";
public static final String FACET_CWE = "cwe";
/**
Collection<RuleType> types = query.getTypes();
if (isNotEmpty(types)) {
- List<String> typeNames = types.stream().map(RuleType::toString).collect(MoreCollectors.toList());
+ List<String> typeNames = types.stream().map(RuleType::toString).toList();
filters.put(FIELD_RULE_TYPE,
QueryBuilders.termsQuery(FIELD_RULE_TYPE, typeNames));
}
+ Collection<RuleCharacteristic> characteristics = query.getCharacteristics();
+ if (isNotEmpty(characteristics)) {
+ List<String> characteristicNames = characteristics.stream().map(RuleCharacteristic::name).toList();
+ filters.put(FIELD_RULE_CHARACTERISTIC,
+ QueryBuilders.termsQuery(FIELD_RULE_CHARACTERISTIC, characteristicNames));
+ }
+
if (query.getAvailableSinceLong() != null) {
filters.put("availableSince", QueryBuilders.rangeQuery(FIELD_RULE_CREATED_AT)
.gte(query.getAvailableSinceLong()));
stickyFacetBuilder.buildStickyFacet(FIELD_RULE_TYPE, FACET_TYPES,
(types == null) ? (new String[0]) : types.toArray()));
}
+ if (options.getFacets().contains(FACET_CHARACTERISTICS)) {
+ Collection<RuleCharacteristic> characteristics = query.getCharacteristics();
+ aggregations.put(FACET_CHARACTERISTICS,
+ stickyFacetBuilder.buildStickyFacet(FIELD_RULE_CHARACTERISTIC, FACET_CHARACTERISTICS,
+ (characteristics == null) ? (new String[0]) : characteristics.toArray()));
+ }
if (options.getFacets().contains(FACET_REPOSITORIES) || options.getFacets().contains(FACET_OLD_DEFAULT)) {
Collection<String> repositories = query.getRepositories();
aggregations.put(FACET_REPOSITORIES,
public static final String FIELD_RULE_IS_EXTERNAL = "isExternal";
public static final String FIELD_RULE_TEMPLATE_KEY = "templateKey";
public static final String FIELD_RULE_TYPE = "type";
+ public static final String FIELD_RULE_CHARACTERISTIC = "characteristic";
public static final String FIELD_RULE_CREATED_AT = "createdAt";
public static final String FIELD_RULE_UPDATED_AT = "updatedAt";
public static final String FIELD_RULE_CWE = "cwe";
ruleMapping.keywordFieldBuilder(FIELD_RULE_TEMPLATE_KEY).disableNorms().build();
ruleMapping.keywordFieldBuilder(FIELD_RULE_TYPE).disableNorms().build();
+ ruleMapping.keywordFieldBuilder(FIELD_RULE_CHARACTERISTIC).disableNorms().build();
ruleMapping.createLongField(FIELD_RULE_CREATED_AT);
ruleMapping.createLongField(FIELD_RULE_UPDATED_AT);
import javax.annotation.Nullable;
import org.sonar.api.rule.RuleStatus;
import org.sonar.api.rule.Severity;
+import org.sonar.api.rules.RuleCharacteristic;
import org.sonar.api.rules.RuleType;
import org.sonar.db.qualityprofile.QProfileDto;
private Collection<RuleStatus> statuses;
private Collection<String> tags;
private Collection<RuleType> types;
+ private Collection<RuleCharacteristic> characteristics;
private Boolean activation;
private QProfileDto profile;
private QProfileDto compareToQProfile;
return this;
}
+ @CheckForNull
+ public Collection<RuleCharacteristic> getCharacteristics() {
+ return characteristics;
+ }
+
+ public RuleQuery setCharacteristics(@Nullable Collection<RuleCharacteristic> characteristics) {
+ this.characteristics = characteristics;
+ return this;
+ }
+
@CheckForNull
public Collection<String> getInheritance() {
return inheritance;
*/
package org.sonar.server.issue;
+import java.util.List;
import org.junit.Test;
import static java.util.Arrays.asList;
.setScopes(asList("MAIN", "TEST"))
.setLanguages(singletonList("xoo"))
.setTags(asList("tag1", "tag2"))
+ .setCharacteristics(singletonList("CLEAR"))
.setAssigned(true)
.setCreatedAfter("2013-04-16T09:08:24+0200")
.setCreatedBefore("2013-04-17T09:08:24+0200")
assertThat(underTest.getOwaspAsvsLevel()).isEqualTo(2);
assertThat(underTest.getPciDss32()).containsExactly("1", "4");
assertThat(underTest.getPciDss40()).containsExactly("3", "5");
+ assertThat(underTest.getCharacteristics()).containsOnly("CLEAR");
}
@Test
import java.util.HashMap;
import org.sonar.api.resources.Scopes;
import org.sonar.api.rule.Severity;
+import org.sonar.api.rules.RuleCharacteristic;
import org.sonar.api.rules.RuleType;
import org.sonar.core.util.Uuids;
import org.sonar.db.component.ComponentDto;
doc.setKey(Uuids.createFast());
doc.setRuleUuid(Uuids.createFast());
doc.setType(RuleType.CODE_SMELL);
+ doc.setCharacteristic(RuleCharacteristic.COMPLIANT);
doc.setAssigneeUuid("assignee_uuid_" + randomAlphabetic(26));
doc.setAuthorLogin("author_" + randomAlphabetic(5));
doc.setScope(IssueScope.MAIN);
import org.joda.time.Duration;
import org.sonar.api.issue.Issue;
import org.sonar.api.rule.Severity;
+import org.sonar.api.rules.RuleCharacteristic;
import org.sonar.api.rules.RuleType;
import org.sonar.api.server.rule.RulesDefinition;
import org.sonar.api.server.rule.RulesDefinition.OwaspTop10Version;
import static org.sonar.server.issue.index.IssueIndex.Facet.STATUSES;
import static org.sonar.server.issue.index.IssueIndex.Facet.TAGS;
import static org.sonar.server.issue.index.IssueIndex.Facet.TYPES;
+import static org.sonar.server.issue.index.IssueIndex.Facet.CHARACTERISTICS;
import static org.sonar.server.issue.index.IssueIndexDefinition.FIELD_ISSUE_ASSIGNEE_UUID;
import static org.sonar.server.issue.index.IssueIndexDefinition.FIELD_ISSUE_AUTHOR_LOGIN;
import static org.sonar.server.issue.index.IssueIndexDefinition.FIELD_ISSUE_BRANCH_UUID;
+import static org.sonar.server.issue.index.IssueIndexDefinition.FIELD_ISSUE_CHARACTERISTIC;
import static org.sonar.server.issue.index.IssueIndexDefinition.FIELD_ISSUE_COMPONENT_UUID;
import static org.sonar.server.issue.index.IssueIndexDefinition.FIELD_ISSUE_CWE;
import static org.sonar.server.issue.index.IssueIndexDefinition.FIELD_ISSUE_DIRECTORY_PATH;
import static org.sonarqube.ws.client.issue.IssuesWsParameters.FACET_MODE_EFFORT;
import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_ASSIGNEES;
import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_AUTHOR;
+import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_CHARACTERISTICS;
import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_CREATED_AT;
import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_CWE;
import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_DIRECTORIES;
// Resolutions facet returns one more element than the number of resolutions to take into account unresolved issues
RESOLUTIONS(PARAM_RESOLUTIONS, FIELD_ISSUE_RESOLUTION, STICKY, Issue.RESOLUTIONS.size() + 1),
TYPES(PARAM_TYPES, FIELD_ISSUE_TYPE, STICKY, RuleType.values().length),
+ CHARACTERISTICS(PARAM_CHARACTERISTICS, FIELD_ISSUE_CHARACTERISTIC, STICKY, RuleCharacteristic.values().length),
SCOPES(PARAM_SCOPES, FIELD_ISSUE_SCOPE, STICKY, MAX_FACET_SIZE),
LANGUAGES(PARAM_LANGUAGES, FIELD_ISSUE_LANGUAGE, STICKY, MAX_FACET_SIZE),
RULES(PARAM_RULES, FIELD_ISSUE_RULE_UUID, STICKY, MAX_FACET_SIZE),
filters.addFilter(FIELD_ISSUE_LANGUAGE, LANGUAGES.getFilterScope(), createTermsFilter(FIELD_ISSUE_LANGUAGE, query.languages()));
filters.addFilter(FIELD_ISSUE_TAGS, TAGS.getFilterScope(), createTermsFilter(FIELD_ISSUE_TAGS, query.tags()));
filters.addFilter(FIELD_ISSUE_TYPE, TYPES.getFilterScope(), createTermsFilter(FIELD_ISSUE_TYPE, query.types()));
+ filters.addFilter(FIELD_ISSUE_CHARACTERISTIC, CHARACTERISTICS.getFilterScope(), createTermsFilter(FIELD_ISSUE_CHARACTERISTIC, query.characteristics()));
filters.addFilter(
FIELD_ISSUE_RESOLUTION, RESOLUTIONS.getFilterScope(),
createTermsFilter(FIELD_ISSUE_RESOLUTION, query.resolutions()));
addFacetIfNeeded(options, aggregationHelper, esRequest, AUTHOR, query.authors().toArray());
addFacetIfNeeded(options, aggregationHelper, esRequest, TAGS, query.tags().toArray());
addFacetIfNeeded(options, aggregationHelper, esRequest, TYPES, query.types().toArray());
+ addFacetIfNeeded(options, aggregationHelper, esRequest, CHARACTERISTICS, query.characteristics().toArray());
addSecurityCategoryFacetIfNeeded(PARAM_PCI_DSS_32, PCI_DSS_32, options, aggregationHelper, esRequest, query.pciDss32().toArray());
addSecurityCategoryFacetIfNeeded(PARAM_PCI_DSS_40, PCI_DSS_40, options, aggregationHelper, esRequest, query.pciDss40().toArray());
private final Collection<String> languages;
private final Collection<String> tags;
private final Collection<String> types;
+ private final Collection<String> characteristics;
private final Collection<String> owaspTop10;
private final Collection<String> pciDss32;
private final Collection<String> pciDss40;
this.languages = defaultCollection(builder.languages);
this.tags = defaultCollection(builder.tags);
this.types = defaultCollection(builder.types);
+ this.characteristics = defaultCollection(builder.characteristics);
this.pciDss32 = defaultCollection(builder.pciDss32);
this.pciDss40 = defaultCollection(builder.pciDss40);
this.owaspAsvs40 = defaultCollection(builder.owaspAsvs40);
return types;
}
+ public Collection<String> characteristics() {
+ return characteristics;
+ }
+
public Collection<String> pciDss32() {
return pciDss32;
}
private Collection<String> scopes;
private Collection<String> languages;
private Collection<String> tags;
+ private Collection<String> characteristics;
private Collection<String> types;
private Collection<String> pciDss32;
private Collection<String> pciDss40;
return this;
}
+ public Builder characteristics(@Nullable Collection<String> characteristics) {
+ this.characteristics = characteristics;
+ return this;
+ }
+
public Builder pciDss32(@Nullable Collection<String> o) {
this.pciDss32 = o;
return this;
this.newCodeOnReferenceByProjectUuids = newCodeOnReferenceByProjectUuids;
return this;
}
+
}
private static <T> Collection<T> defaultCollection(@Nullable Collection<T> c) {
.languages(request.getLanguages())
.tags(request.getTags())
.types(request.getTypes())
+ .characteristics(request.getCharacteristics())
.pciDss32(request.getPciDss32())
.pciDss40(request.getPciDss40())
.owaspAsvs40(request.getOwaspAsvs40())
import org.junit.Rule;
import org.junit.Test;
import org.sonar.api.resources.Languages;
+import org.sonar.api.rule.RuleKey;
import org.sonar.api.rule.RuleStatus;
+import org.sonar.api.rules.RuleCharacteristic;
import org.sonar.api.rules.RuleType;
import org.sonar.api.server.ws.WebService;
import org.sonar.api.utils.Durations;
import static org.sonar.api.issue.Issue.STATUS_OPEN;
import static org.sonar.api.issue.Issue.STATUS_RESOLVED;
import static org.sonar.api.resources.Qualifiers.UNIT_TEST_FILE;
+import static org.sonar.api.rules.RuleType.*;
import static org.sonar.api.rules.RuleType.CODE_SMELL;
import static org.sonar.api.server.ws.WebService.Param.FACETS;
import static org.sonar.api.utils.DateUtils.formatDateTime;
import static org.sonarqube.ws.client.issue.IssuesWsParameters.ACTION_SET_TAGS;
import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_ADDITIONAL_FIELDS;
import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_ASSIGNEES;
+import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_CHARACTERISTICS;
import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_COMPONENT_KEYS;
import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_CREATED_AFTER;
import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_HIDE_COMMENTS;
indexIssues();
userSession.logIn(john);
- ws.newRequest()
+ TestResponse response = ws.newRequest()
.setParam("additionalFields", "comments,users")
- .execute()
- .assertJson(this.getClass(), "issue_with_comments.json");
+ .execute();
+
+ response.assertJson(this.getClass(), "issue_with_comments.json");
}
@Test
.assertJson(this.getClass(), "load_additional_fields_with_issue_admin_permission.json");
}
+ @Test
+ public void search_by_characteristic_when_characteristic_not_set() {
+ RuleDto rule1 = newIssueRule(XOO_X1, r -> r.setType(CODE_SMELL).setCharacteristic(null));
+ RuleDto rule2 = newIssueRule(XOO_X2, r -> r.setType(RuleType.BUG).setCharacteristic(null));
+ ComponentDto project = db.components().insertPublicProject("PROJECT_ID", c -> c.setKey("PROJECT_KEY").setLanguage("java"));
+ ComponentDto file = db.components().insertComponent(newFileDto(project, null, "FILE_ID").setKey("FILE_KEY").setLanguage("java"));
+
+ db.issues().insertIssue(rule1, project, file);
+ db.issues().insertIssue(rule2, project, file);
+ session.commit();
+ indexIssues();
+
+ userSession.logIn("john")
+ .addProjectPermission(ISSUE_ADMIN, project); // granted by Anyone
+ indexPermissions();
+
+ SearchWsResponse searchWsResponse = ws.newRequest()
+ .setParam(PARAM_CHARACTERISTICS, Common.RuleCharacteristic.CLEAR.name() + "," + Common.RuleCharacteristic.ROBUST.name())
+ .executeProtobuf(SearchWsResponse.class);
+
+ List<Issue> issuesList = searchWsResponse.getIssuesList();
+
+ assertThat(issuesList).hasSize(2);
+ assertThat(issuesList.stream().filter(f -> f.getCharacteristic() == Common.RuleCharacteristic.CLEAR)).hasSize(1);
+ assertThat(issuesList.stream().filter(f -> f.getCharacteristic() == Common.RuleCharacteristic.ROBUST)).hasSize(1);
+ }
+
+ @Test
+ public void search_by_characteristic_when_characteristic_set() {
+ RuleDto rule1 = newIssueRule(XOO_X1, r -> r.setCharacteristic(RuleCharacteristic.PORTABLE));
+ RuleDto rule2 = newIssueRule(XOO_X2, r -> r.setCharacteristic(RuleCharacteristic.TESTED));
+ ComponentDto project = db.components().insertPublicProject("PROJECT_ID", c -> c.setKey("PROJECT_KEY").setLanguage("java"));
+ ComponentDto file = db.components().insertComponent(newFileDto(project, null, "FILE_ID").setKey("FILE_KEY").setLanguage("java"));
+
+ db.issues().insertIssue(rule1, project, file);
+ db.issues().insertIssue(rule2, project, file);
+ session.commit();
+ indexIssues();
+
+ userSession.logIn("john")
+ .addProjectPermission(ISSUE_ADMIN, project); // granted by Anyone
+ indexPermissions();
+
+ SearchWsResponse searchWsResponse = ws.newRequest()
+ .setParam(PARAM_CHARACTERISTICS, RuleCharacteristic.PORTABLE.name() + "," + RuleCharacteristic.TESTED.name())
+ .executeProtobuf(SearchWsResponse.class);
+
+ List<Issue> issuesList = searchWsResponse.getIssuesList();
+
+ assertThat(issuesList).hasSize(2);
+ assertThat(issuesList.stream().filter(f -> f.getCharacteristic() == Common.RuleCharacteristic.TESTED)).hasSize(1);
+ assertThat(issuesList.stream().filter(f -> f.getCharacteristic() == Common.RuleCharacteristic.PORTABLE)).hasSize(1);
+ }
+
@Test
public void search_by_rule_key() {
RuleDto rule = newIssueRule();
RuleDto hotspotRule = newHotspotRule();
db.issues().insertHotspot(hotspotRule, project, file);
insertIssues(i -> i.setType(RuleType.BUG), i -> i.setType(RuleType.VULNERABILITY),
- i -> i.setType(RuleType.CODE_SMELL));
+ i -> i.setType(CODE_SMELL));
indexPermissionsAndIssues();
TestRequest request = ws.newRequest()
- .setParam("types", RuleType.SECURITY_HOTSPOT.toString());
+ .setParam("types", SECURITY_HOTSPOT.toString());
assertThatThrownBy(request::execute)
.isInstanceOf(IllegalArgumentException.class)
.hasMessage("Value of parameter 'types' (SECURITY_HOTSPOT) must be one of: [CODE_SMELL, BUG, VULNERABILITY]");
"additionalFields", "asc", "assigned", "assignees", "author", "componentKeys", "branch", "pullRequest", "createdAfter", "createdAt",
"createdBefore", "createdInLast", "directories", "facets", "files", "issues", "scopes", "languages", "onComponentOnly",
"p", "projects", "ps", "resolutions", "resolved", "rules", "s", "severities", "statuses", "tags", "types", "pciDss-3.2", "pciDss-4.0", "owaspAsvs-4.0",
- "owaspAsvsLevel", "owaspTop10",
+ "owaspAsvsLevel", "owaspTop10", "characteristics",
"owaspTop10-2021", "sansTop25", "cwe", "sonarsourceSecurity", "timeZone", "inNewCodePeriod");
WebService.Param branch = def.param(PARAM_BRANCH);
}
private RuleDto newIssueRule() {
- RuleDto rule = newRule(XOO_X1, createDefaultRuleDescriptionSection(uuidFactory.create(), "Rule desc"))
+ return newIssueRule(XOO_X1);
+ }
+
+ private RuleDto newIssueRule(RuleKey key) {
+ return newIssueRule(key, r -> {
+ });
+ }
+
+ private RuleDto newIssueRule(RuleKey key, Consumer<RuleDto> consumer) {
+ RuleDto rule = newRule(key, createDefaultRuleDescriptionSection(uuidFactory.create(), "Rule desc"))
.setLanguage("xoo")
.setName("Rule name")
.setStatus(RuleStatus.READY);
+ consumer.accept(rule);
db.rules().insert(rule);
return rule;
}
@Rule
public UserSessionRule userSession = UserSessionRule.standalone();
- private DbClient dbClient = db.getDbClient();
+ private final DbClient dbClient = db.getDbClient();
private final QProfileWsSupport wsSupport = new QProfileWsSupport(dbClient, userSession);
- private RuleQueryFactory ruleQueryFactory = mock(RuleQueryFactory.class, Mockito.RETURNS_MOCKS);
+ private final RuleQueryFactory ruleQueryFactory = mock(RuleQueryFactory.class, Mockito.RETURNS_MOCKS);
- private QProfileRules qProfileRules = mock(QProfileRules.class, Mockito.RETURNS_DEEP_STUBS);
- private WsActionTester ws = new WsActionTester(new ActivateRulesAction(ruleQueryFactory, userSession, qProfileRules, wsSupport, dbClient));
+ private final QProfileRules qProfileRules = mock(QProfileRules.class, Mockito.RETURNS_DEEP_STUBS);
+ private final WsActionTester ws = new WsActionTester(new ActivateRulesAction(ruleQueryFactory, userSession, qProfileRules, wsSupport, dbClient));
@Test
public void define_bulk_activate_rule_action() {
assertThat(definition.isPost()).isTrue();
assertThat(definition.params()).extracting(WebService.Param::key).containsExactlyInAnyOrder(
"types",
+ "characteristics",
"template_key",
"languages",
"is_template",
assertThat(definition.isPost()).isTrue();
assertThat(definition.params()).extracting(WebService.Param::key).containsExactlyInAnyOrder(
"types",
+ "characteristics",
"template_key",
"languages",
"is_template",
import org.sonar.api.resources.Languages;
import org.sonar.api.rule.RuleKey;
import org.sonar.api.rule.RuleStatus;
+import org.sonar.api.rules.RuleCharacteristic;
import org.sonar.api.rules.RuleType;
import org.sonar.api.server.debt.DebtRemediationFunction;
import org.sonar.api.server.ws.WebService;
import static org.sonar.db.rule.RuleTesting.setSystemTags;
import static org.sonar.db.rule.RuleTesting.setTags;
import static org.sonar.server.rule.ws.RulesWsParameters.PARAM_ACTIVATION;
+import static org.sonar.server.rule.ws.RulesWsParameters.PARAM_CHARACTERISTICS;
import static org.sonar.server.rule.ws.RulesWsParameters.PARAM_COMPARE_TO_PROFILE;
import static org.sonar.server.rule.ws.RulesWsParameters.PARAM_QPROFILE;
import static org.sonar.server.rule.ws.RulesWsParameters.PARAM_RULE_KEY;
assertThat(def.since()).isEqualTo("4.4");
assertThat(def.isInternal()).isFalse();
assertThat(def.responseExampleAsString()).isNotEmpty();
- assertThat(def.params()).hasSize(28);
+ assertThat(def.params()).hasSize(29);
WebService.Param compareToProfile = def.param("compareToProfile");
assertThat(compareToProfile.since()).isEqualTo("6.5");
assertThat(result.getRepo()).isNotEmpty();
assertThat(result.getSeverity()).isNotEmpty();
assertThat(result.getType().name()).isEqualTo(RuleType.valueOf(rule.getType()).name());
+ assertThat(result.getCharacteristic().name()).isEqualTo(rule.getEffectiveCharacteristic().name());
}
@Test
// mandatory fields
assertThat(result.getKey()).isEqualTo(rule.getKey().toString());
assertThat(result.getType().getNumber()).isEqualTo(rule.getType());
+ assertThat(result.getCharacteristic().name()).isEqualTo(rule.getEffectiveCharacteristic().name());
// selected fields
assertThat(result.getCreatedAt()).isNotEmpty();
verify(request, rule1);
}
+ @Test
+ public void characteristics_shouldFilterOnCharacteristics() {
+ RuleDto rule1 = db.rules().insert(r -> r.setCharacteristic(RuleCharacteristic.ROBUST));
+ RuleDto rule2 = db.rules().insert(r -> r.setCharacteristic(RuleCharacteristic.COMPLIANT));
+ indexRules();
+
+ Consumer<TestRequest> populator = r -> r
+ .setParam(PARAM_CHARACTERISTICS, RuleCharacteristic.ROBUST.name());
+
+ TestRequest request = ws.newRequest();
+ populator.accept(request);
+
+ List<Rule> rulesList = request.executeProtobuf(SearchResponse.class).getRulesList();
+
+ assertThat(rulesList).hasSize(1);
+ assertThat(rulesList.get(0).getKey()).isEqualTo(rule1.getKey().toString());
+ }
+
+
+ @Test
+ public void characteristics_shouldGroupCharacteristicsInTheFacets() {
+ RuleDto rule1 = db.rules().insert(r -> r.setCharacteristic(RuleCharacteristic.ROBUST));
+ RuleDto rule2 = db.rules().insert(r -> r.setCharacteristic(RuleCharacteristic.COMPLIANT));
+ RuleDto rule3 = db.rules().insert(r -> r.setCharacteristic(RuleCharacteristic.COMPLIANT));
+ indexRules();
+
+ SearchResponse result = ws.newRequest()
+ .setParam("facets", "characteristics")
+ .executeProtobuf(SearchResponse.class);
+
+ Common.Facet facets = result.getFacets().getFacets(0);
+
+ int valuesCount = facets.getValuesCount();
+ assertThat(valuesCount).isEqualTo(RuleCharacteristic.values().length);
+
+ List<Common.FacetValue> valuesList = facets.getValuesList();
+ Common.FacetValue compliantFacetValue = valuesList.get(0);
+ assertThat(compliantFacetValue.getVal()).isEqualTo(RuleCharacteristic.COMPLIANT.name());
+ assertThat(compliantFacetValue.getCount()).isEqualTo(2);
+
+ Common.FacetValue robustFacetValue = valuesList.get(1);
+ assertThat(robustFacetValue.getVal()).isEqualTo(RuleCharacteristic.ROBUST.name());
+ assertThat(robustFacetValue.getCount()).isEqualTo(1);
+ }
+
@Test
public void when_searching_for_several_tags_combine_them_with_OR() {
RuleDto bothTagsRule = db.rules().insert(r -> r.setLanguage("java"), setTags("tag1", "tag2"));
--- /dev/null
+{
+ "total": 2,
+ "p": 1,
+ "ps": 100,
+ "paging": {
+ "pageIndex": 1,
+ "pageSize": 100,
+ "total": 2
+ },
+ "issues": [ {
+ "rule": "xoo:x1",
+ "component": "FILE_KEY",
+ "project": "PROJECT_KEY",
+ "flows": [],
+ "status": "OPEN",
+ "characteristic": "CONSISTENT"
+ }, {
+ "rule": "xoo:x1",
+ "component": "FILE_KEY",
+ "project": "PROJECT_KEY",
+ "flows": [],
+ "status": "OPEN",
+ "characteristic": "CLEAR"
+ }]
+}
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.search.SearchHit;
import org.sonar.api.rule.Severity;
+import org.sonar.api.rules.RuleCharacteristic;
import org.sonar.api.rules.RuleType;
import org.sonar.api.server.ws.Change;
import org.sonar.api.server.ws.Request;
import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_ASSIGNEES;
import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_AUTHOR;
import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_BRANCH;
+import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_CHARACTERISTICS;
import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_COMPONENT_KEYS;
import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_CREATED_AFTER;
import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_CREATED_AT;
public class SearchAction implements IssuesWsAction {
private static final String LOGIN_MYSELF = "__me__";
+ private static final String COMMA_SEPERATED_PARAMS_FORMAT = "%s,%s";
private static final Set<String> ISSUE_SCOPES = Arrays.stream(IssueScope.values()).map(Enum::name).collect(Collectors.toSet());
private static final EnumSet<RuleType> ALL_RULE_TYPES_EXCEPT_SECURITY_HOTSPOTS = EnumSet.complementOf(EnumSet.of(RuleType.SECURITY_HOTSPOT));
PARAM_LANGUAGES,
PARAM_TAGS,
PARAM_TYPES,
+ PARAM_CHARACTERISTICS,
PARAM_PCI_DSS_32,
PARAM_PCI_DSS_40,
PARAM_OWASP_ASVS_40,
+ "<br/>When issue indexation is in progress returns 503 service unavailable HTTP code.")
.setSince("3.6")
.setChangelog(
+ new Change("10.1", "Code characteristic is now included in the response. New parameter 'characteristics' added."),
new Change("10.0", "Parameter 'sansTop25' is deprecated"),
new Change("10.0", "The value 'sansTop25' for the parameter 'facets' has been deprecated"),
new Change("10.0", format("Deprecated value 'ASSIGNEE' in parameter '%s' is dropped", Param.SORT)),
.setDescription("Comma-separated list of types.")
.setSince("5.5")
.setPossibleValues(ALL_RULE_TYPES_EXCEPT_SECURITY_HOTSPOTS)
- .setExampleValue(format("%s,%s", RuleType.CODE_SMELL, RuleType.BUG));
+ .setExampleValue(format(COMMA_SEPERATED_PARAMS_FORMAT, RuleType.CODE_SMELL, RuleType.BUG));
+ action.createParam(PARAM_CHARACTERISTICS)
+ .setDescription("Comma-separated list of characteristics.")
+ .setSince("10.1")
+ .setPossibleValues(Arrays.stream(RuleCharacteristic.values()).map(Enum::name).toList())
+ .setExampleValue(format(COMMA_SEPERATED_PARAMS_FORMAT, RuleCharacteristic.CLEAR, RuleCharacteristic.COMPLIANT));
action.createParam(PARAM_OWASP_ASVS_LEVEL)
.setDescription("Level of OWASP ASVS categories.")
.setSince("9.7")
action.createParam(PARAM_SCOPES)
.setDescription("Comma-separated list of scopes. Available since 8.5")
.setPossibleValues(IssueScope.MAIN.name(), IssueScope.TEST.name())
- .setExampleValue(format("%s,%s", IssueScope.MAIN.name(), IssueScope.TEST.name()));
+ .setExampleValue(format(COMMA_SEPERATED_PARAMS_FORMAT, IssueScope.MAIN.name(), IssueScope.TEST.name()));
action.createParam(PARAM_LANGUAGES)
.setDescription("Comma-separated list of languages. Available since 4.4")
.setExampleValue("java,js");
addMandatoryValuesToFacet(facets, PARAM_SCOPES, ISSUE_SCOPES);
addMandatoryValuesToFacet(facets, PARAM_LANGUAGES, request.getLanguages());
addMandatoryValuesToFacet(facets, PARAM_TAGS, request.getTags());
+ addMandatoryValuesToFacet(facets, PARAM_CHARACTERISTICS, request.getCharacteristics());
setTypesFacet(facets);
.setSeverities(request.paramAsStrings(PARAM_SEVERITIES))
.setStatuses(request.paramAsStrings(PARAM_STATUSES))
.setTags(request.paramAsStrings(PARAM_TAGS))
+ .setCharacteristics(request.paramAsStrings(PARAM_CHARACTERISTICS))
.setTypes(allRuleTypesExceptHotspotsIfEmpty(request.paramAsStrings(PARAM_TYPES)))
.setPciDss32(request.paramAsStrings(PARAM_PCI_DSS_32))
.setPciDss40(request.paramAsStrings(PARAM_PCI_DSS_40))
private void addMandatoryFieldsToIssueBuilder(Issue.Builder issueBuilder, IssueDto dto, SearchResponseData data) {
issueBuilder.setKey(dto.getKey());
issueBuilder.setType(Common.RuleType.forNumber(dto.getType()));
+ issueBuilder.setCharacteristic(Common.RuleCharacteristic.valueOf(dto.getEffectiveRuleCharacteristic().name()));
ComponentDto component = data.getComponentByUuid(dto.getComponentUuid());
issueBuilder.setComponent(component.getKey());
if (dto.getType() != RuleType.SECURITY_HOTSPOT.getDbConstant()) {
issueBuilder.setSeverity(Common.Severity.valueOf(dto.getSeverity()));
}
+
ofNullable(data.getUserByUuid(dto.getAssigneeUuid())).ifPresent(assignee -> issueBuilder.setAssignee(assignee.getLogin()));
ofNullable(emptyToNull(dto.getResolution())).ifPresent(issueBuilder::setResolution);
issueBuilder.setStatus(dto.getStatus());
// Mandatory fields
ruleResponse.setKey(ruleDto.getKey().toString());
ruleResponse.setType(Common.RuleType.forNumber(ruleDto.getType()));
+ ruleResponse.setCharacteristic(Common.RuleCharacteristic.valueOf(ruleDto.getEffectiveCharacteristic().name()));
// Optional fields
setName(ruleResponse, ruleDto, fieldsToReturn);
import java.util.Date;
import java.util.List;
import org.sonar.api.rule.RuleStatus;
+import org.sonar.api.rules.RuleCharacteristic;
import org.sonar.api.rules.RuleType;
import org.sonar.api.server.ServerSide;
import org.sonar.api.server.ws.Request;
import static org.sonar.server.rule.ws.RulesWsParameters.PARAM_ACTIVATION;
import static org.sonar.server.rule.ws.RulesWsParameters.PARAM_ACTIVE_SEVERITIES;
import static org.sonar.server.rule.ws.RulesWsParameters.PARAM_AVAILABLE_SINCE;
+import static org.sonar.server.rule.ws.RulesWsParameters.PARAM_CHARACTERISTICS;
import static org.sonar.server.rule.ws.RulesWsParameters.PARAM_COMPARE_TO_PROFILE;
import static org.sonar.server.rule.ws.RulesWsParameters.PARAM_CWE;
import static org.sonar.server.rule.ws.RulesWsParameters.PARAM_INCLUDE_EXTERNAL;
query.setIsTemplate(request.paramAsBoolean(PARAM_IS_TEMPLATE));
query.setTemplateKey(request.param(PARAM_TEMPLATE_KEY));
query.setTypes(toEnums(request.paramAsStrings(PARAM_TYPES), RuleType.class));
+ query.setCharacteristics(toEnums(request.paramAsStrings(PARAM_CHARACTERISTICS), RuleCharacteristic.class));
query.setKey(request.param(PARAM_RULE_KEY));
query.setCwe(request.paramAsStrings(PARAM_CWE));
query.setOwaspTop10(request.paramAsStrings(PARAM_OWASP_TOP_10));
import java.util.Set;
import org.sonar.api.rule.RuleStatus;
import org.sonar.api.rule.Severity;
+import org.sonar.api.rules.RuleCharacteristic;
import org.sonar.api.rules.RuleType;
import org.sonar.api.server.ServerSide;
import org.sonar.api.server.ws.WebService;
import static org.sonar.server.rule.ws.RulesWsParameters.PARAM_ACTIVATION;
import static org.sonar.server.rule.ws.RulesWsParameters.PARAM_ACTIVE_SEVERITIES;
import static org.sonar.server.rule.ws.RulesWsParameters.PARAM_AVAILABLE_SINCE;
+import static org.sonar.server.rule.ws.RulesWsParameters.PARAM_CHARACTERISTICS;
import static org.sonar.server.rule.ws.RulesWsParameters.PARAM_COMPARE_TO_PROFILE;
import static org.sonar.server.rule.ws.RulesWsParameters.PARAM_CWE;
import static org.sonar.server.rule.ws.RulesWsParameters.PARAM_INCLUDE_EXTERNAL;
.setPossibleValues(RuleType.values())
.setExampleValue(RuleType.BUG);
+ action
+ .createParam(PARAM_CHARACTERISTICS)
+ .setSince("10.1")
+ .setDescription("Comma-separated list of characteristics. Returned rules match any of the characteristics (OR operator)")
+ .setPossibleValues(RuleCharacteristic.values())
+ .setExampleValue(RuleCharacteristic.CLEAR);
+
action
.createParam(PARAM_ACTIVATION)
.setDescription("Filter rules that are activated or deactivated on the selected Quality profile. Ignored if " +
public static final String PARAM_LANGUAGES = "languages";
public static final String PARAM_TAGS = "tags";
public static final String PARAM_TYPES = "types";
+ public static final String PARAM_CHARACTERISTICS = "characteristics";
public static final String PARAM_CWE = "cwe";
public static final String PARAM_OWASP_TOP_10 = "owaspTop10";
public static final String PARAM_OWASP_TOP_10_2021 = "owaspTop10-2021";
import com.google.common.collect.Maps;
import com.google.common.collect.Ordering;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import javax.annotation.CheckForNull;
import javax.annotation.Nullable;
import org.sonar.api.rule.Severity;
+import org.sonar.api.rules.RuleCharacteristic;
import org.sonar.api.rules.RuleType;
import org.sonar.api.server.ws.Change;
import org.sonar.api.server.ws.Request;
import static org.sonar.server.es.SearchOptions.MAX_PAGE_SIZE;
import static org.sonar.server.rule.index.RuleIndex.ALL_STATUSES_EXCEPT_REMOVED;
import static org.sonar.server.rule.index.RuleIndex.FACET_ACTIVE_SEVERITIES;
+import static org.sonar.server.rule.index.RuleIndex.FACET_CHARACTERISTICS;
import static org.sonar.server.rule.index.RuleIndex.FACET_CWE;
import static org.sonar.server.rule.index.RuleIndex.FACET_LANGUAGES;
import static org.sonar.server.rule.index.RuleIndex.FACET_OLD_DEFAULT;
import static org.sonar.server.rule.ws.RulesWsParameters.FIELD_DEPRECATED_KEYS;
import static org.sonar.server.rule.ws.RulesWsParameters.OPTIONAL_FIELDS;
import static org.sonar.server.rule.ws.RulesWsParameters.PARAM_ACTIVE_SEVERITIES;
+import static org.sonar.server.rule.ws.RulesWsParameters.PARAM_CHARACTERISTICS;
import static org.sonar.server.rule.ws.RulesWsParameters.PARAM_CWE;
import static org.sonar.server.rule.ws.RulesWsParameters.PARAM_LANGUAGES;
import static org.sonar.server.rule.ws.RulesWsParameters.PARAM_OWASP_TOP_10;
FACET_ACTIVE_SEVERITIES,
FACET_STATUSES,
FACET_TYPES,
+ FACET_CHARACTERISTICS,
FACET_OLD_DEFAULT,
FACET_CWE,
FACET_OWASP_TOP_10,
new Change("10.0", "The value 'debtRemFn' for the 'f' parameter has been deprecated, use 'remFn' instead"),
new Change("10.0", "The value 'defaultDebtRemFn' for the 'f' parameter has been deprecated, use 'defaultRemFn' instead"),
new Change("10.0", "The value 'sansTop25' for the parameter 'facets' has been deprecated"),
- new Change("10.0", "Parameter 'sansTop25' is deprecated")
+ new Change("10.0", "Parameter 'sansTop25' is deprecated"),
+ new Change("10.1", "The field 'characteristic' has been added to the response. New parameter 'characteristics has been added.")
);
action.createParam(FACETS)
addMandatoryFacetValues(results, FACET_ACTIVE_SEVERITIES, Severity.ALL);
addMandatoryFacetValues(results, FACET_TAGS, request.getTags());
addMandatoryFacetValues(results, FACET_TYPES, RuleType.names());
+ addMandatoryFacetValues(results, FACET_CHARACTERISTICS, Arrays.stream(RuleCharacteristic.values()).map(Enum::name).toList());
addMandatoryFacetValues(results, FACET_CWE, request.getCwe());
addMandatoryFacetValues(results, FACET_OWASP_TOP_10, request.getOwaspTop10());
addMandatoryFacetValues(results, FACET_OWASP_TOP_10_2021, request.getOwaspTop10For2021());
facetValuesByFacetKey.put(FACET_ACTIVE_SEVERITIES, request.getActiveSeverities());
facetValuesByFacetKey.put(FACET_TAGS, request.getTags());
facetValuesByFacetKey.put(FACET_TYPES, request.getTypes());
+ facetValuesByFacetKey.put(FACET_CHARACTERISTICS, request.getCharacteristics());
facetValuesByFacetKey.put(FACET_CWE, request.getCwe());
facetValuesByFacetKey.put(FACET_OWASP_TOP_10, request.getOwaspTop10());
facetValuesByFacetKey.put(FACET_OWASP_TOP_10_2021, request.getOwaspTop10For2021());
.setStatuses(request.paramAsStrings(PARAM_STATUSES))
.setTags(request.paramAsStrings(PARAM_TAGS))
.setTypes(request.paramAsStrings(PARAM_TYPES))
+ .setCharacteristics(request.paramAsStrings(PARAM_CHARACTERISTICS))
.setCwe(request.paramAsStrings(PARAM_CWE))
.setOwaspTop10(request.paramAsStrings(PARAM_OWASP_TOP_10))
.setOwaspTop10For2021(request.paramAsStrings(PARAM_OWASP_TOP_10_2021))
private List<String> statuses;
private List<String> tags;
private List<String> types;
+ private List<String> characteristics;
private List<String> cwe;
private List<String> owaspTop10;
private List<String> owaspTop10For2021;
return types;
}
+ private SearchRequest setCharacteristics(@Nullable List<String> characteristics) {
+ this.characteristics = characteristics;
+ return this;
+ }
+
+ @CheckForNull
+ private List<String> getCharacteristics() {
+ return characteristics;
+ }
+
public List<String> getCwe() {
return cwe;
}
this.sonarsourceSecurity = sonarsourceSecurity;
return this;
}
+
}
}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2023 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.rule.ws;
+
+import com.tngtech.java.junit.dataprovider.DataProvider;
+import com.tngtech.java.junit.dataprovider.DataProviderRunner;
+import com.tngtech.java.junit.dataprovider.UseDataProvider;
+import java.util.List;
+import java.util.Set;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.sonar.api.resources.Languages;
+import org.sonar.api.rule.RuleKey;
+import org.sonar.api.rules.RuleCharacteristic;
+import org.sonar.db.rule.RuleDto;
+import org.sonar.server.platform.db.migration.version.DatabaseVersion;
+import org.sonarqube.ws.Common;
+import org.sonarqube.ws.Rules;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+
+@RunWith(DataProviderRunner.class)
+public class RuleMapperTest {
+
+ @Test
+ @UseDataProvider("pluginApiEnumsMappedToProtobufEnums")
+ public void toWsRule_shouldMapCharacteristicEnumToProtobuf(RuleCharacteristic pluginApiEnum, Common.RuleCharacteristic protoEnum) {
+ RuleMapper ruleMapper = new RuleMapper(mock(Languages.class), mock(), mock());
+
+ RuleDto ruleDto = new RuleDto();
+ ruleDto.setRuleKey(RuleKey.of("repo", "key"));
+ ruleDto.setScope(RuleDto.Scope.ALL);
+ ruleDto.setCharacteristic(pluginApiEnum);
+ Rules.Rule rule = ruleMapper.toWsRule(ruleDto, new SearchAction.SearchResult(), Set.of());
+
+ assertThat(rule.getCharacteristic()).isEqualTo(protoEnum);
+ }
+
+ @DataProvider
+ public static Object[][] pluginApiEnumsMappedToProtobufEnums() {
+ return new Object[][] {
+ {RuleCharacteristic.CLEAR, Common.RuleCharacteristic.CLEAR},
+ {RuleCharacteristic.TESTED, Common.RuleCharacteristic.TESTED},
+ {RuleCharacteristic.ROBUST, Common.RuleCharacteristic.ROBUST},
+ {RuleCharacteristic.SECURE, Common.RuleCharacteristic.SECURE},
+ {RuleCharacteristic.CONSISTENT, Common.RuleCharacteristic.CONSISTENT},
+ {RuleCharacteristic.COMPLIANT, Common.RuleCharacteristic.COMPLIANT},
+ {RuleCharacteristic.STRUCTURED, Common.RuleCharacteristic.STRUCTURED},
+ {RuleCharacteristic.PORTABLE, Common.RuleCharacteristic.PORTABLE}
+ };
+ }
+}
\ No newline at end of file
public static final String PARAM_LANGUAGES = "languages";
public static final String PARAM_TAGS = "tags";
public static final String PARAM_TYPES = "types";
+ public static final String PARAM_CHARACTERISTICS = "characteristics";
public static final String PARAM_OWASP_ASVS_LEVEL = "owaspAsvsLevel";
public static final String PARAM_PCI_DSS = "pciDss";
public static final String PARAM_PCI_DSS_32 = "pciDss-3.2";
SECURITY_HOTSPOT = 4;
}
+enum RuleCharacteristic {
+ UNKNOWN_RULE_CHARACTERISTIC = 0;
+ CLEAR = 1;
+ CONSISTENT = 2;
+ STRUCTURED = 3;
+ TESTED = 4;
+ ROBUST = 5;
+ SECURE = 6;
+ PORTABLE = 7;
+ COMPLIANT = 8;
+}
+
enum BranchType {
reserved 1, 2;
optional string ruleDescriptionContextKey = 37;
repeated sonarqube.ws.commons.MessageFormatting messageFormattings = 38;
+ optional sonarqube.ws.commons.RuleCharacteristic characteristic = 39;
}
message Transitions {
optional sonarqube.ws.commons.RuleType type = 6;
optional Location mainLocation = 7;
optional bool closed = 8;
+ optional sonarqube.ws.commons.RuleCharacteristic characteristic = 9;
}
repeated Flow flows = 9;
optional bool assignedToSubscribedUser = 10;
optional string ruleDescriptionContextKey = 11;
+ optional sonarqube.ws.commons.RuleCharacteristic characteristic = 12;
}
message Flow {
optional DeprecatedKeys deprecatedKeys = 48;
optional DescriptionSections descriptionSections = 49;
optional EducationPrinciples educationPrinciples = 50;
+ optional sonarqube.ws.commons.RuleCharacteristic characteristic = 51;
message DescriptionSections {
repeated DescriptionSection descriptionSections = 1;