Skip to content

Commit dca508f

Browse files
christophstroblmp911de
authored andcommitted
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.
1 parent 742448a commit dca508f

File tree

4 files changed

+42
-12
lines changed

4 files changed

+42
-12
lines changed

spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/query/MongoRegexCreator.java

+5
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717

1818
import java.util.regex.Pattern;
1919

20+
import org.bson.BsonRegularExpression;
2021
import org.springframework.lang.Nullable;
2122

2223
/**
@@ -102,6 +103,10 @@ public String toRegularExpression(@Nullable String source, @Nullable MatchMode m
102103
}
103104
}
104105

106+
public Object toCaseInsensitiveMatch(Object source) {
107+
return source instanceof String ? new BsonRegularExpression(Pattern.quote((String)source), "i") : source;
108+
}
109+
105110
private String prepareAndEscapeStringBeforeApplyingLikeRegex(String source, MatchMode matcherType) {
106111

107112
if (MatchMode.REGEX == matcherType) {

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

+20-12
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@
2525

2626
import org.slf4j.Logger;
2727
import org.slf4j.LoggerFactory;
28-
2928
import org.springframework.data.domain.Range;
3029
import org.springframework.data.domain.Range.Bound;
3130
import org.springframework.data.domain.Sort;
@@ -51,8 +50,10 @@
5150
import org.springframework.data.repository.query.parser.Part.IgnoreCaseType;
5251
import org.springframework.data.repository.query.parser.Part.Type;
5352
import org.springframework.data.repository.query.parser.PartTree;
53+
import org.springframework.data.util.Streamable;
5454
import org.springframework.util.Assert;
5555
import org.springframework.util.ClassUtils;
56+
import org.springframework.util.ObjectUtils;
5657

5758
/**
5859
* Custom query creator to create Mongo criterias.
@@ -196,9 +197,9 @@ private Criteria from(Part part, MongoPersistentProperty property, Criteria crit
196197
case IS_NULL:
197198
return criteria.is(null);
198199
case NOT_IN:
199-
return criteria.nin(nextAsArray(parameters));
200+
return criteria.nin(nextAsList(parameters, part));
200201
case IN:
201-
return criteria.in(nextAsArray(parameters));
202+
return criteria.in(nextAsList(parameters, part));
202203
case LIKE:
203204
case STARTING_WITH:
204205
case ENDING_WITH:
@@ -337,7 +338,7 @@ private Criteria createContainingCriteria(Part part, MongoPersistentProperty pro
337338
Iterator<Object> parameters) {
338339

339340
if (property.isCollectionLike()) {
340-
return criteria.in(nextAsArray(parameters));
341+
return criteria.in(nextAsList(parameters, part));
341342
}
342343

343344
return addAppropriateLikeRegexTo(criteria, part, parameters.next());
@@ -400,17 +401,24 @@ private <T> T nextAs(Iterator<Object> iterator, Class<T> type) {
400401
String.format("Expected parameter type of %s but got %s!", type, parameter.getClass()));
401402
}
402403

403-
private Object[] nextAsArray(Iterator<Object> iterator) {
404+
private java.util.List<?> nextAsList(Iterator<Object> iterator, Part part) {
405+
406+
Streamable<?> streamable = asStreamable(iterator.next());
407+
if(!isSimpleComparisionPossible(part)) {
408+
streamable = streamable.map(MongoRegexCreator.INSTANCE::toCaseInsensitiveMatch);
409+
}
410+
411+
return streamable.toList();
412+
}
404413

405-
Object next = iterator.next();
414+
private Streamable<?> asStreamable(Object value) {
406415

407-
if (next instanceof Collection) {
408-
return ((Collection<?>) next).toArray();
409-
} else if (next != null && next.getClass().isArray()) {
410-
return (Object[]) next;
416+
if (value instanceof Collection) {
417+
return Streamable.of((Collection<?>) value);
418+
} else if (ObjectUtils.isArray(value)) {
419+
return Streamable.of((Object[]) value);
411420
}
412-
413-
return new Object[] { next };
421+
return Streamable.of(value);
414422
}
415423

416424
private String toLikeRegex(String source, Part part) {

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

+15
Original file line numberDiff line numberDiff line change
@@ -1356,4 +1356,19 @@ void findWithMoreThan10Arguments() {
13561356
this.alicia.getEmail(), this.alicia.getAge(), Sex.FEMALE, this.alicia.createdAt, alicia.getSkills(), "street",
13571357
"zipCode", "city", alicia.getUniqueId(), credentials.username, credentials.password)).isNotNull();
13581358
}
1359+
1360+
@Test // GH-3395
1361+
void caseInSensitiveInClause() {
1362+
assertThat(repository.findByLastnameIgnoreCaseIn("bEAuFoRd", "maTTheWs")).hasSize(3);
1363+
}
1364+
1365+
@Test // GH-3395
1366+
void caseInSensitiveInClauseQuotesExpressions() {
1367+
assertThat(repository.findByLastnameIgnoreCaseIn(".*")).isEmpty();
1368+
}
1369+
1370+
@Test // GH-3395
1371+
void caseSensitiveInClauseIgnoresExpressions() {
1372+
assertThat(repository.findByFirstnameIn(".*")).isEmpty();
1373+
}
13591374
}

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

+2
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,8 @@ public interface PersonRepository extends MongoRepository<Person, String>, Query
125125
@Query("{ 'lastname' : { '$regex' : '?0', '$options' : 'i'}}")
126126
Page<Person> findByLastnameLikeWithPageable(String lastname, Pageable pageable);
127127

128+
List<Person> findByLastnameIgnoreCaseIn(String... lastname);
129+
128130
/**
129131
* Returns all {@link Person}s with a firstname contained in the given varargs.
130132
*

0 commit comments

Comments
 (0)