diff options
author | ssjenka <ssjenka@ops-slave-centos7-1.internal.sonarsource.com> | 2016-11-30 13:52:52 +0100 |
---|---|---|
committer | ssjenka <ssjenka@ops-slave-centos7-1.internal.sonarsource.com> | 2016-11-30 13:52:52 +0100 |
commit | 3c646719ac84403fab2ed7060db25dedcf29eb33 (patch) | |
tree | 641b0727f2a99d636c99b41bfe92533f7da856bf /server | |
parent | 1511b9d96b1690428bd794d13209183e56e26f15 (diff) | |
parent | 5eedd7e06021b78f49522f3d296bc9cd3939c415 (diff) | |
download | sonarqube-3c646719ac84403fab2ed7060db25dedcf29eb33.tar.gz sonarqube-3c646719ac84403fab2ed7060db25dedcf29eb33.zip |
Automatic merge from branch-6.2
* origin/branch-6.2:
SONAR-8437 fix listing of issue tags and authors
SONAR-8437 fix facet "assigned_to_me" of api/issues/search
SONAR-8437 escape selected value in ES facet of WS api/issues
SONAR-8436 escape param "tags" of WS api/rules/tags
Slight performance improvement in api/rules/tags
Fix Quality flaws related to org.sonar.server.rule
Diffstat (limited to 'server')
11 files changed, 191 insertions, 55 deletions
diff --git a/server/sonar-server/src/main/java/org/sonar/server/es/EsUtils.java b/server/sonar-server/src/main/java/org/sonar/server/es/EsUtils.java index 25770e0d97e..85f65400722 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/es/EsUtils.java +++ b/server/sonar-server/src/main/java/org/sonar/server/es/EsUtils.java @@ -31,6 +31,7 @@ import java.util.List; import java.util.Map; import java.util.NoSuchElementException; import java.util.Queue; +import java.util.regex.Pattern; import javax.annotation.CheckForNull; import javax.annotation.Nullable; import org.elasticsearch.action.bulk.BulkRequestBuilder; @@ -47,6 +48,7 @@ import static java.lang.String.format; public class EsUtils { public static final int SCROLL_TIME_IN_MINUTES = 3; + private static final Pattern SPECIAL_REGEX_CHARS = Pattern.compile("[{}()\\[\\].+*?^$\\\\|]"); private EsUtils() { // only static methods @@ -107,6 +109,15 @@ public class EsUtils { return new DocScrollIterator<>(esClient, scrollId, docConverter); } + /** + * Escapes regexp special characters so that text can be forwarded from end-user input + * to Elasticsearch regexp query (for instance attributes "include" and "exclude" of + * term aggregations. + */ + public static String escapeSpecialRegexChars(String str) { + return SPECIAL_REGEX_CHARS.matcher(str).replaceAll("\\\\$0"); + } + private static class DocScrollIterator<D extends BaseDoc> implements Iterator<D> { private final EsClient esClient; diff --git a/server/sonar-server/src/main/java/org/sonar/server/es/StickyFacetBuilder.java b/server/sonar-server/src/main/java/org/sonar/server/es/StickyFacetBuilder.java index 4c0070d08ac..11519a92055 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/es/StickyFacetBuilder.java +++ b/server/sonar-server/src/main/java/org/sonar/server/es/StickyFacetBuilder.java @@ -19,8 +19,11 @@ */ package org.sonar.server.es; -import com.google.common.base.Joiner; +import java.util.Arrays; import java.util.Map; +import java.util.Objects; +import java.util.stream.Collector; +import java.util.stream.Collectors; import javax.annotation.Nullable; import org.apache.commons.lang.ArrayUtils; import org.elasticsearch.index.query.BoolQueryBuilder; @@ -40,7 +43,7 @@ public class StickyFacetBuilder { private static final int FACET_DEFAULT_MIN_DOC_COUNT = 1; private static final int FACET_DEFAULT_SIZE = 10; private static final Order FACET_DEFAULT_ORDER = Terms.Order.count(false); - private static final Joiner PIPE_JOINER = Joiner.on('|'); + private static final Collector<CharSequence, ?, String> PIPE_JOINER = Collectors.joining("|"); private final QueryBuilder query; private final Map<String, QueryBuilder> filters; @@ -107,9 +110,14 @@ public class StickyFacetBuilder { public FilterAggregationBuilder addSelectedItemsToFacet(String fieldName, String facetName, FilterAggregationBuilder facetTopAggregation, Object... selected) { if (selected.length > 0) { + String includes = Arrays.stream(selected) + .filter(Objects::nonNull) + .map(s -> EsUtils.escapeSpecialRegexChars(s.toString())) + .collect(PIPE_JOINER); + TermsBuilder selectedTerms = AggregationBuilders.terms(facetName + "_selected") .field(fieldName) - .include(PIPE_JOINER.join(selected)); + .include(includes); if (subAggregation != null) { selectedTerms = selectedTerms.subAggregation(subAggregation); } diff --git a/server/sonar-server/src/main/java/org/sonar/server/issue/index/IssueIndex.java b/server/sonar-server/src/main/java/org/sonar/server/issue/index/IssueIndex.java index c914d9ad63a..318461fa5e1 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/issue/index/IssueIndex.java +++ b/server/sonar-server/src/main/java/org/sonar/server/issue/index/IssueIndex.java @@ -82,11 +82,13 @@ import org.sonar.server.user.UserSession; import org.sonar.server.view.index.ViewIndexDefinition; import static com.google.common.collect.Lists.newArrayList; +import static java.lang.String.format; import static org.elasticsearch.index.query.QueryBuilders.boolQuery; import static org.elasticsearch.index.query.QueryBuilders.existsQuery; import static org.elasticsearch.index.query.QueryBuilders.matchAllQuery; import static org.elasticsearch.index.query.QueryBuilders.termQuery; import static org.elasticsearch.index.query.QueryBuilders.termsQuery; +import static org.sonar.server.es.EsUtils.escapeSpecialRegexChars; import static org.sonarqube.ws.client.issue.IssuesWsParameters.ASSIGNEES; import static org.sonarqube.ws.client.issue.IssuesWsParameters.AUTHORS; import static org.sonarqube.ws.client.issue.IssuesWsParameters.CREATED_AT; @@ -209,7 +211,7 @@ public class IssueIndex extends BaseIndex { public IssueDoc getByKey(String key) { IssueDoc value = getNullableByKey(key); if (value == null) { - throw new NotFoundException(String.format("Issue with key '%s' does not exist", key)); + throw new NotFoundException(format("Issue with key '%s' does not exist", key)); } return value; } @@ -567,7 +569,9 @@ public class IssueIndex extends BaseIndex { FilterAggregationBuilder facetTopAggregation = AggregationBuilders .filter(facetName + "__filter") .filter(facetFilter) - .subAggregation(addEffortAggregationIfNeeded(query, AggregationBuilders.terms(facetName + "__terms").field(fieldName).include(login))); + .subAggregation(addEffortAggregationIfNeeded(query, AggregationBuilders.terms(facetName + "__terms") + .field(fieldName) + .include(escapeSpecialRegexChars(login)))); builder.addAggregation( AggregationBuilders.global(facetName) @@ -619,16 +623,15 @@ public class IssueIndex extends BaseIndex { .size(maxNumberOfTags) .order(Terms.Order.term(true)) .minDocCount(1L); - if (textQuery != null) { - issueTags.include(String.format(SUBSTRING_MATCH_REGEXP, textQuery)); - } TermsBuilder ruleTags = AggregationBuilders.terms(tagsOnRulesSubAggregation) .field(RuleIndexDefinition.FIELD_RULE_ALL_TAGS) .size(maxNumberOfTags) .order(Terms.Order.term(true)) .minDocCount(1L); if (textQuery != null) { - ruleTags.include(String.format(SUBSTRING_MATCH_REGEXP, textQuery)); + String escapedTextQuery = escapeSpecialRegexChars(textQuery); + issueTags.include(format(SUBSTRING_MATCH_REGEXP, escapedTextQuery)); + ruleTags.include(format(SUBSTRING_MATCH_REGEXP, escapedTextQuery)); } SearchResponse searchResponse = requestBuilder.addAggregation(topAggreg.subAggregation(issueTags).subAggregation(ruleTags)).get(); @@ -667,7 +670,7 @@ public class IssueIndex extends BaseIndex { .order(termsOrder) .minDocCount(1L); if (textQuery != null) { - aggreg.include(String.format(SUBSTRING_MATCH_REGEXP, textQuery)); + aggreg.include(format(SUBSTRING_MATCH_REGEXP, escapeSpecialRegexChars(textQuery))); } SearchResponse searchResponse = requestBuilder.addAggregation(aggreg).get(); @@ -701,7 +704,7 @@ public class IssueIndex extends BaseIndex { filter.must(termsQuery(IssueIndexDefinition.FIELD_ISSUE_COMPONENT_UUID, component.uuid())); break; default: - throw new IllegalStateException(String.format("Component of scope '%s' is not allowed", component.scope())); + throw new IllegalStateException(format("Component of scope '%s' is not allowed", component.scope())); } SearchRequestBuilder requestBuilder = getClient() diff --git a/server/sonar-server/src/main/java/org/sonar/server/rule/RuleService.java b/server/sonar-server/src/main/java/org/sonar/server/rule/RuleService.java index 581045bf5a1..48e70374318 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/rule/RuleService.java +++ b/server/sonar-server/src/main/java/org/sonar/server/rule/RuleService.java @@ -77,7 +77,8 @@ public class RuleService { } private void checkPermission() { - userSession.checkLoggedIn(); - userSession.checkPermission(GlobalPermissions.QUALITY_PROFILE_ADMIN); + userSession + .checkLoggedIn() + .checkPermission(GlobalPermissions.QUALITY_PROFILE_ADMIN); } } diff --git a/server/sonar-server/src/main/java/org/sonar/server/rule/index/RuleIndex.java b/server/sonar-server/src/main/java/org/sonar/server/rule/index/RuleIndex.java index d7fe053024f..e42af16478c 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/rule/index/RuleIndex.java +++ b/server/sonar-server/src/main/java/org/sonar/server/rule/index/RuleIndex.java @@ -64,9 +64,11 @@ import org.sonar.server.es.SearchOptions; import org.sonar.server.es.StickyFacetBuilder; import static org.elasticsearch.index.query.QueryBuilders.boolQuery; +import static org.elasticsearch.index.query.QueryBuilders.matchAllQuery; import static org.elasticsearch.index.query.QueryBuilders.matchQuery; import static org.elasticsearch.index.query.QueryBuilders.simpleQueryStringQuery; import static org.sonar.server.es.EsUtils.SCROLL_TIME_IN_MINUTES; +import static org.sonar.server.es.EsUtils.escapeSpecialRegexChars; import static org.sonar.server.es.EsUtils.scrollIds; import static org.sonar.server.rule.index.RuleIndexDefinition.FIELD_ACTIVE_RULE_INHERITANCE; import static org.sonar.server.rule.index.RuleIndexDefinition.FIELD_ACTIVE_RULE_PROFILE_KEY; @@ -168,7 +170,7 @@ public class RuleIndex extends BaseIndex { // No contextual query case String queryText = query.getQueryText(); if (StringUtils.isEmpty(queryText)) { - return QueryBuilders.matchAllQuery(); + return matchAllQuery(); } // Build RuleBased contextual query @@ -179,8 +181,7 @@ public class RuleIndex extends BaseIndex { qb.should(simpleQueryStringQuery(query.getQueryText()) .field(FIELD_RULE_NAME + "." + SEARCH_WORDS_SUFFIX, 20f) .field(FIELD_RULE_HTML_DESCRIPTION, 3f) - .defaultOperator(SimpleQueryStringBuilder.Operator.AND) - ).boost(20f); + .defaultOperator(SimpleQueryStringBuilder.Operator.AND)).boost(20f); // Match and partial Match queries // Search by key uses the "sortable" sub-field as it requires to be case-insensitive (lower-case filtering) @@ -295,7 +296,7 @@ public class RuleIndex extends BaseIndex { if (childrenFilter.hasClauses()) { childQuery = childrenFilter; } else { - childQuery = QueryBuilders.matchAllQuery(); + childQuery = matchAllQuery(); } /** Implementation of activation query */ @@ -469,31 +470,29 @@ public class RuleIndex extends BaseIndex { } public Set<String> terms(String fields, @Nullable String query, int size) { - Set<String> tags = new HashSet<>(); - String key = "_ref"; + String aggregationKey = "_ref"; - TermsBuilder terms = AggregationBuilders.terms(key) + TermsBuilder termsAggregation = AggregationBuilders.terms(aggregationKey) .field(fields) .size(size) .minDocCount(1); if (query != null) { - terms.include(".*" + query + ".*"); + termsAggregation.include(".*" + escapeSpecialRegexChars(query) + ".*"); } SearchRequestBuilder request = getClient() .prepareSearch(INDEX) - .setQuery(QueryBuilders.matchAllQuery()) - .addAggregation(terms); + .setQuery(matchAllQuery()) + .setSize(0) + .addAggregation(termsAggregation); SearchResponse esResponse = request.get(); - Terms aggregation = esResponse.getAggregations().get(key); - + Set<String> terms = new HashSet<>(); + Terms aggregation = esResponse.getAggregations().get(aggregationKey); if (aggregation != null) { - for (Terms.Bucket value : aggregation.getBuckets()) { - tags.add(value.getKeyAsString()); - } + aggregation.getBuckets().forEach(value -> terms.add(value.getKeyAsString())); } - return tags; + return terms; } private enum ToRuleKey implements Function<String, RuleKey> { diff --git a/server/sonar-server/src/test/java/org/sonar/server/es/EsUtilsTest.java b/server/sonar-server/src/test/java/org/sonar/server/es/EsUtilsTest.java index 25c6f83b403..95d81be899c 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/es/EsUtilsTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/es/EsUtilsTest.java @@ -33,6 +33,7 @@ import org.sonar.test.TestUtils; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; +import static org.sonar.server.es.EsUtils.escapeSpecialRegexChars; public class EsUtilsTest { @@ -74,4 +75,19 @@ public class EsUtilsTest { assertThat(EsUtils.parseDateTime("2017-07-14T04:40:00.000+02:00").getTime()).isEqualTo(1_500_000_000_000L); assertThat(EsUtils.parseDateTime(null)).isNull(); } + + @Test + public void test_escapeSpecialRegexChars() { + assertThat(escapeSpecialRegexChars("")).isEqualTo(""); + assertThat(escapeSpecialRegexChars("foo")).isEqualTo("foo"); + assertThat(escapeSpecialRegexChars("FOO")).isEqualTo("FOO"); + assertThat(escapeSpecialRegexChars("foo++")).isEqualTo("foo\\+\\+"); + assertThat(escapeSpecialRegexChars("foo[]")).isEqualTo("foo\\[\\]"); + assertThat(escapeSpecialRegexChars(".*")).isEqualTo("\\.\\*"); + assertThat(escapeSpecialRegexChars("foo\\d")).isEqualTo("foo\\\\d"); + assertThat(escapeSpecialRegexChars("^")).isEqualTo("\\^"); + assertThat(escapeSpecialRegexChars("$")).isEqualTo("\\$"); + assertThat(escapeSpecialRegexChars("|")).isEqualTo("\\|"); + assertThat(escapeSpecialRegexChars("a bit of | $ .* ^ everything")).isEqualTo("a bit of \\| \\$ \\.\\* \\^ everything"); + } } diff --git a/server/sonar-server/src/test/java/org/sonar/server/issue/IssueServiceMediumTest.java b/server/sonar-server/src/test/java/org/sonar/server/issue/IssueServiceMediumTest.java index f0b605833d7..ff940104b98 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/issue/IssueServiceMediumTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/issue/IssueServiceMediumTest.java @@ -245,6 +245,7 @@ public class IssueServiceMediumTest { assertThat(service.listTags("sys", 5)).containsOnly("systag1", "systag2"); assertThat(service.listTags(null, 1)).containsOnly("bug"); assertThat(service.listTags(null, Integer.MAX_VALUE)).containsOnly("convention", "java8", "bug", "systag1", "systag2", "tag1", "tag2"); + assertThat(service.listTags("invalidRegexp[", 5)).isEmpty(); } @Test @@ -303,7 +304,7 @@ public class IssueServiceMediumTest { } @Test - public void list_authors() { + public void test_listAuthors() { RuleDto rule = newRule(); ComponentDto project = newProject(); ComponentDto file = newFile(project); @@ -319,6 +320,16 @@ public class IssueServiceMediumTest { assertThat(service.listAuthors(null, Integer.MAX_VALUE)).containsExactly("anakin@skywalker.name", "luke.skywalker", "luke@skywalker.name"); } + @Test + public void listAuthors_escapes_regexp_special_characters() { + saveIssue(IssueTesting.newDto(newRule(), newFile(newProject()), newProject()).setAuthorLogin("name++")); + + assertThat(service.listAuthors("invalidRegexp[", 5)).isEmpty(); + assertThat(service.listAuthors("nam+", 5)).isEmpty(); + assertThat(service.listAuthors("name+", 5)).containsExactly("name++"); + assertThat(service.listAuthors(".*", 5)).isEmpty(); + } + private RuleDto newRule() { return newRule(RuleTesting.newXooX1()); } diff --git a/server/sonar-server/src/test/java/org/sonar/server/issue/ws/SearchActionMediumTest.java b/server/sonar-server/src/test/java/org/sonar/server/issue/ws/SearchActionMediumTest.java index a852f54abbb..8cf65711c2f 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/issue/ws/SearchActionMediumTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/issue/ws/SearchActionMediumTest.java @@ -387,6 +387,19 @@ public class SearchActionMediumTest { } @Test + public void assignedToMe_facet_must_escape_login_of_authenticated_user() throws Exception { + // login looks like an invalid regexp + userSessionRule.login("foo["); + + // should not fail + wsTester.newGetRequest(API_ENDPOINT, SEARCH_ACTION) + .setParam(WebService.Param.FACETS, "assigned_to_me") + .execute() + .assertJson(this.getClass(), "assignedToMe_facet_must_escape_login_of_authenticated_user.json"); + + } + + @Test public void filter_by_assigned_to_me() throws Exception { db.userDao().insert(session, new UserDto().setLogin("john").setName("John").setEmail("john@email.com")); diff --git a/server/sonar-server/src/test/java/org/sonar/server/rule/RuleServiceMediumTest.java b/server/sonar-server/src/test/java/org/sonar/server/rule/RuleServiceMediumTest.java index 79f86211988..80440da8f90 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/rule/RuleServiceMediumTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/rule/RuleServiceMediumTest.java @@ -19,26 +19,26 @@ */ package org.sonar.server.rule; -import com.google.common.collect.Sets; -import java.util.Collections; import java.util.Set; import org.junit.After; import org.junit.Before; import org.junit.ClassRule; +import org.junit.Rule; import org.junit.Test; +import org.junit.rules.ExpectedException; import org.sonar.api.rule.RuleKey; import org.sonar.core.permission.GlobalPermissions; import org.sonar.db.DbClient; import org.sonar.db.DbSession; import org.sonar.db.rule.RuleDao; import org.sonar.db.rule.RuleTesting; +import org.sonar.server.exceptions.ForbiddenException; import org.sonar.server.exceptions.UnauthorizedException; -import org.sonar.server.rule.index.RuleIndex; -import org.sonar.server.rule.index.RuleIndexDefinition; import org.sonar.server.rule.index.RuleIndexer; import org.sonar.server.tester.ServerTester; import org.sonar.server.tester.UserSessionRule; +import static com.google.common.collect.Sets.newHashSet; import static org.assertj.core.api.Assertions.assertThat; public class RuleServiceMediumTest { @@ -46,14 +46,16 @@ public class RuleServiceMediumTest { @ClassRule public static ServerTester tester = new ServerTester().withEsIndexes(); - @org.junit.Rule + @Rule public UserSessionRule userSessionRule = UserSessionRule.forServerTester(tester); - RuleDao dao = tester.get(RuleDao.class); - RuleIndex index = tester.get(RuleIndex.class); - RuleService service = tester.get(RuleService.class); - DbSession dbSession; - RuleIndexer ruleIndexer; + @Rule + public ExpectedException expectedException = ExpectedException.none(); + + private RuleDao dao = tester.get(RuleDao.class); + private RuleService service = tester.get(RuleService.class); + private DbSession dbSession; + private RuleIndexer ruleIndexer; @Before public void before() { @@ -68,30 +70,68 @@ public class RuleServiceMediumTest { } @Test - public void list_tags() { + public void listTags_returns_all_tags() { // insert db - RuleKey key1 = RuleKey.of("javascript", "S001"); - RuleKey key2 = RuleKey.of("java", "S001"); - dao.insert(dbSession, - RuleTesting.newDto(key1).setTags(Sets.newHashSet("tag1")).setSystemTags(Sets.newHashSet("sys1", "sys2"))); - dao.insert(dbSession, - RuleTesting.newDto(key2).setTags(Sets.newHashSet("tag2")).setSystemTags(Collections.<String>emptySet())); - dbSession.commit(); - ruleIndexer.index(); + insertRule(RuleKey.of("javascript", "S001"), newHashSet("tag1"), newHashSet("sys1", "sys2")); + insertRule(RuleKey.of("java", "S001"), newHashSet("tag2"), newHashSet()); // all tags, including system Set<String> tags = service.listTags(); assertThat(tags).containsOnly("tag1", "tag2", "sys1", "sys2"); + } - // verify in es - tags = index.terms(RuleIndexDefinition.FIELD_RULE_ALL_TAGS); - assertThat(tags).containsOnly("tag1", "tag2", "sys1", "sys2"); + @Test + public void listTags_returns_tags_filtered_by_name() { + insertRule(RuleKey.of("javascript", "S001"), newHashSet("tag1", "misra++"), newHashSet("sys1", "sys2")); + insertRule(RuleKey.of("java", "S001"), newHashSet("tag2"), newHashSet()); + + assertThat(service.listTags("missing", 10)).isEmpty(); + assertThat(service.listTags("", 10)).containsOnly("tag1", "misra++", "tag2", "sys1", "sys2"); + assertThat(service.listTags("tag", 10)).containsOnly("tag1", "tag2"); + assertThat(service.listTags("sys", 10)).containsOnly("sys1", "sys2"); + assertThat(service.listTags("misra", 10)).containsOnly("misra++"); + assertThat(service.listTags("misra+", 10)).containsOnly("misra++"); + assertThat(service.listTags("++", 10)).containsOnly("misra++"); + + // LIMITATION: case sensitive + assertThat(service.listTags("TAG", 10)).isEmpty(); + assertThat(service.listTags("TAg", 10)).isEmpty(); + assertThat(service.listTags("MISSing", 10)).isEmpty(); + + assertThat(service.listTags("misra-", 10)).isEmpty(); } - @Test(expected = UnauthorizedException.class) - public void do_not_delete_if_not_granted() { - userSessionRule.setGlobalPermissions(GlobalPermissions.SCAN_EXECUTION); + @Test + public void listTags_returns_empty_results_if_filter_contains_regexp_special_characters() { + insertRule(RuleKey.of("javascript", "S001"), newHashSet("misra++"), newHashSet("sys1", "sys2")); + + assertThat(service.listTags("mis[", 10)).isEmpty(); + assertThat(service.listTags("mis\\d", 10)).isEmpty(); + assertThat(service.listTags(".*", 10)).isEmpty(); + } + + @Test + public void delete_throws_UnauthorizedException_if_not_logged_in() { + expectedException.expect(UnauthorizedException.class); + expectedException.expectMessage("Authentication is required"); service.delete(RuleKey.of("java", "S001")); } + + @Test + public void delete_throws_ForbiddenException_if_not_administrator() { + userSessionRule.login().setGlobalPermissions(GlobalPermissions.SCAN_EXECUTION); + + expectedException.expect(ForbiddenException.class); + expectedException.expectMessage("Insufficient privileges"); + + service.delete(RuleKey.of("java", "S001")); + } + + private void insertRule(RuleKey key, Set<String> tags, Set<String> systemTags) { + dao.insert(dbSession, + RuleTesting.newDto(key).setTags(tags).setSystemTags(systemTags)); + dbSession.commit(); + ruleIndexer.index(); + } } diff --git a/server/sonar-server/src/test/java/org/sonar/server/rule/index/RuleIndexTest.java b/server/sonar-server/src/test/java/org/sonar/server/rule/index/RuleIndexTest.java index 4d44e20c9e8..dd4f79d4918 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/rule/index/RuleIndexTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/rule/index/RuleIndexTest.java @@ -253,6 +253,17 @@ public class RuleIndexTest { } @Test + public void tags_facet_supports_selected_value_with_regexp_special_characters() { + indexRules(newDoc(RuleKey.of("java", "S001")).setAllTags(singleton("misra++"))); + + RuleQuery query = new RuleQuery().setTags(singletonList("misra[")); + SearchOptions options = new SearchOptions().addFacets(RuleIndex.FACET_TAGS); + + // do not fail + assertThat(index.search(query, options).getTotal()).isEqualTo(0); + } + + @Test public void search_by_types() { indexRules( newDoc(RULE_KEY_1).setType(CODE_SMELL), diff --git a/server/sonar-server/src/test/resources/org/sonar/server/issue/ws/SearchActionMediumTest/assignedToMe_facet_must_escape_login_of_authenticated_user.json b/server/sonar-server/src/test/resources/org/sonar/server/issue/ws/SearchActionMediumTest/assignedToMe_facet_must_escape_login_of_authenticated_user.json new file mode 100644 index 00000000000..45df34e8f53 --- /dev/null +++ b/server/sonar-server/src/test/resources/org/sonar/server/issue/ws/SearchActionMediumTest/assignedToMe_facet_must_escape_login_of_authenticated_user.json @@ -0,0 +1,23 @@ +{ + "total": 0, + "p": 1, + "ps": 100, + "paging": { + "pageIndex": 1, + "pageSize": 100, + "total": 0 + }, + "issues": [], + "components": [], + "facets": [ + { + "property": "assigned_to_me", + "values": [ + { + "val": "foo[", + "count": 0 + } + ] + } + ] +} |