Browse Source

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.
2.2.x
Christoph Strobl 5 years ago committed by Mark Paluch
parent
commit
154f691fbd
No known key found for this signature in database
GPG Key ID: 4406B84C1661DCD1
  1. 5
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/query/MongoRegexCreator.java
  2. 32
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/MongoQueryCreator.java
  3. 15
      spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/AbstractPersonRepositoryIntegrationTests.java
  4. 2
      spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/PersonRepository.java

5
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; @@ -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 { @@ -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) {

32
spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/MongoQueryCreator.java

@ -25,7 +25,6 @@ import java.util.regex.Pattern; @@ -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; @@ -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<Query, Criteria> { @@ -196,9 +197,9 @@ class MongoQueryCreator extends AbstractQueryCreator<Query, Criteria> {
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<Query, Criteria> { @@ -337,7 +338,7 @@ class MongoQueryCreator extends AbstractQueryCreator<Query, Criteria> {
Iterator<Object> 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<Query, Criteria> { @@ -400,17 +401,24 @@ class MongoQueryCreator extends AbstractQueryCreator<Query, Criteria> {
String.format("Expected parameter type of %s but got %s!", type, parameter.getClass()));
}
private Object[] nextAsArray(Iterator<Object> iterator) {
private java.util.List<?> nextAsList(Iterator<Object> 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) {

15
spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/AbstractPersonRepositoryIntegrationTests.java

@ -1325,4 +1325,19 @@ public abstract class AbstractPersonRepositoryIntegrationTests { @@ -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();
}
}

2
spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/PersonRepository.java

@ -125,6 +125,8 @@ public interface PersonRepository extends MongoRepository<Person, String>, Query @@ -125,6 +125,8 @@ public interface PersonRepository extends MongoRepository<Person, String>, Query
@Query("{ 'lastname' : { '$regex' : '?0', '$options' : 'i'}}")
Page<Person> findByLastnameLikeWithPageable(String lastname, Pageable pageable);
List<Person> findByLastnameIgnoreCaseIn(String... lastname);
/**
* Returns all {@link Person}s with a firstname contained in the given varargs.
*

Loading…
Cancel
Save