From 154f691fbd3254e9cfa16bf525d08a2842df9c80 Mon Sep 17 00:00:00 2001 From: Christoph Strobl Date: Tue, 16 Feb 2021 08:45:16 +0100 Subject: [PATCH] Fix case insensitive derived in queries on String properties. We now consider the IgnoreCase part of a derived query when used along with In. Strings will be quoted to avoid malicious strings from being handed over to the server as a regular expression to evaluate. See #3395 Original pull request: #3554. --- .../mongodb/core/query/MongoRegexCreator.java | 5 +++ .../repository/query/MongoQueryCreator.java | 32 ++++++++++++------- ...tractPersonRepositoryIntegrationTests.java | 15 +++++++++ .../mongodb/repository/PersonRepository.java | 2 ++ 4 files changed, 42 insertions(+), 12 deletions(-) diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/query/MongoRegexCreator.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/query/MongoRegexCreator.java index 5913bcb28..a0dad3aeb 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/query/MongoRegexCreator.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/query/MongoRegexCreator.java @@ -17,6 +17,7 @@ package org.springframework.data.mongodb.core.query; import java.util.regex.Pattern; +import org.bson.BsonRegularExpression; import org.springframework.lang.Nullable; /** @@ -102,6 +103,10 @@ public enum MongoRegexCreator { } } + public Object toCaseInsensitiveMatch(Object source) { + return source instanceof String ? new BsonRegularExpression(Pattern.quote((String)source), "i") : source; + } + private String prepareAndEscapeStringBeforeApplyingLikeRegex(String source, MatchMode matcherType) { if (MatchMode.REGEX == matcherType) { diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/MongoQueryCreator.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/MongoQueryCreator.java index 24e5033a6..ab8744792 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/MongoQueryCreator.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/MongoQueryCreator.java @@ -25,7 +25,6 @@ import java.util.regex.Pattern; import org.slf4j.Logger; import org.slf4j.LoggerFactory; - import org.springframework.data.domain.Range; import org.springframework.data.domain.Range.Bound; import org.springframework.data.domain.Sort; @@ -51,8 +50,10 @@ import org.springframework.data.repository.query.parser.Part; import org.springframework.data.repository.query.parser.Part.IgnoreCaseType; import org.springframework.data.repository.query.parser.Part.Type; import org.springframework.data.repository.query.parser.PartTree; +import org.springframework.data.util.Streamable; import org.springframework.util.Assert; import org.springframework.util.ClassUtils; +import org.springframework.util.ObjectUtils; /** * Custom query creator to create Mongo criterias. @@ -196,9 +197,9 @@ class MongoQueryCreator extends AbstractQueryCreator { case IS_NULL: return criteria.is(null); case NOT_IN: - return criteria.nin(nextAsArray(parameters)); + return criteria.nin(nextAsList(parameters, part)); case IN: - return criteria.in(nextAsArray(parameters)); + return criteria.in(nextAsList(parameters, part)); case LIKE: case STARTING_WITH: case ENDING_WITH: @@ -337,7 +338,7 @@ class MongoQueryCreator extends AbstractQueryCreator { Iterator parameters) { if (property.isCollectionLike()) { - return criteria.in(nextAsArray(parameters)); + return criteria.in(nextAsList(parameters, part)); } return addAppropriateLikeRegexTo(criteria, part, parameters.next()); @@ -400,17 +401,24 @@ class MongoQueryCreator extends AbstractQueryCreator { String.format("Expected parameter type of %s but got %s!", type, parameter.getClass())); } - private Object[] nextAsArray(Iterator iterator) { + private java.util.List nextAsList(Iterator iterator, Part part) { + + Streamable streamable = asStreamable(iterator.next()); + if(!isSimpleComparisionPossible(part)) { + streamable = streamable.map(MongoRegexCreator.INSTANCE::toCaseInsensitiveMatch); + } + + return streamable.toList(); + } - Object next = iterator.next(); + private Streamable asStreamable(Object value) { - if (next instanceof Collection) { - return ((Collection) next).toArray(); - } else if (next != null && next.getClass().isArray()) { - return (Object[]) next; + if (value instanceof Collection) { + return Streamable.of((Collection) value); + } else if (ObjectUtils.isArray(value)) { + return Streamable.of((Object[]) value); } - - return new Object[] { next }; + return Streamable.of(value); } private String toLikeRegex(String source, Part part) { diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/AbstractPersonRepositoryIntegrationTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/AbstractPersonRepositoryIntegrationTests.java index 8c17ad2b0..b79a10c5d 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/AbstractPersonRepositoryIntegrationTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/AbstractPersonRepositoryIntegrationTests.java @@ -1325,4 +1325,19 @@ public abstract class AbstractPersonRepositoryIntegrationTests { assertThat(repository.findDocumentById(dave.getId()).get()).containsEntry("firstname", dave.getFirstname()) .containsEntry("lastname", dave.getLastname()); } + + @Test // GH-3395 + public void caseInSensitiveInClause() { + assertThat(repository.findByLastnameIgnoreCaseIn("bEAuFoRd", "maTTheWs")).hasSize(3); + } + + @Test // GH-3395 + public void caseInSensitiveInClauseQuotesExpressions() { + assertThat(repository.findByLastnameIgnoreCaseIn(".*")).isEmpty(); + } + + @Test // GH-3395 + public void caseSensitiveInClauseIgnoresExpressions() { + assertThat(repository.findByFirstnameIn(".*")).isEmpty(); + } } diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/PersonRepository.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/PersonRepository.java index 7f5924148..5da0bfada 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/PersonRepository.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/PersonRepository.java @@ -125,6 +125,8 @@ public interface PersonRepository extends MongoRepository, Query @Query("{ 'lastname' : { '$regex' : '?0', '$options' : 'i'}}") Page findByLastnameLikeWithPageable(String lastname, Pageable pageable); + List findByLastnameIgnoreCaseIn(String... lastname); + /** * Returns all {@link Person}s with a firstname contained in the given varargs. *