Skip to content

Commit 94d2f6f

Browse files
committed
Adding queryable encryption range support
Supports range style queries for encrypted fields
1 parent dd4579c commit 94d2f6f

File tree

14 files changed

+829
-26
lines changed

14 files changed

+829
-26
lines changed

Diff for: spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/CollectionOptions.java

+43-13
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import java.util.Optional;
2020
import java.util.function.Function;
2121

22+
import org.bson.conversions.Bson;
2223
import org.springframework.data.mongodb.core.mapping.Field;
2324
import org.springframework.data.mongodb.core.query.Collation;
2425
import org.springframework.data.mongodb.core.schema.MongoJsonSchema;
@@ -41,6 +42,7 @@
4142
* @author Mark Paluch
4243
* @author Andreas Zink
4344
* @author Ben Foster
45+
* @author Ross Lawley
4446
*/
4547
public class CollectionOptions {
4648

@@ -51,10 +53,11 @@ public class CollectionOptions {
5153
private ValidationOptions validationOptions;
5254
private @Nullable TimeSeriesOptions timeSeriesOptions;
5355
private @Nullable CollectionChangeStreamOptions changeStreamOptions;
56+
private @Nullable Bson encryptedFields;
5457

5558
private CollectionOptions(@Nullable Long size, @Nullable Long maxDocuments, @Nullable Boolean capped,
5659
@Nullable Collation collation, ValidationOptions validationOptions, @Nullable TimeSeriesOptions timeSeriesOptions,
57-
@Nullable CollectionChangeStreamOptions changeStreamOptions) {
60+
@Nullable CollectionChangeStreamOptions changeStreamOptions, @Nullable Bson encryptedFields) {
5861

5962
this.maxDocuments = maxDocuments;
6063
this.size = size;
@@ -63,6 +66,7 @@ private CollectionOptions(@Nullable Long size, @Nullable Long maxDocuments, @Nul
6366
this.validationOptions = validationOptions;
6467
this.timeSeriesOptions = timeSeriesOptions;
6568
this.changeStreamOptions = changeStreamOptions;
69+
this.encryptedFields = encryptedFields;
6670
}
6771

6872
/**
@@ -76,7 +80,7 @@ public static CollectionOptions just(Collation collation) {
7680

7781
Assert.notNull(collation, "Collation must not be null");
7882

79-
return new CollectionOptions(null, null, null, collation, ValidationOptions.none(), null, null);
83+
return new CollectionOptions(null, null, null, collation, ValidationOptions.none(), null, null, null);
8084
}
8185

8286
/**
@@ -86,7 +90,7 @@ public static CollectionOptions just(Collation collation) {
8690
* @since 2.0
8791
*/
8892
public static CollectionOptions empty() {
89-
return new CollectionOptions(null, null, null, null, ValidationOptions.none(), null, null);
93+
return new CollectionOptions(null, null, null, null, ValidationOptions.none(), null, null, null);
9094
}
9195

9296
/**
@@ -136,7 +140,7 @@ public static CollectionOptions emitChangedRevisions() {
136140
*/
137141
public CollectionOptions capped() {
138142
return new CollectionOptions(size, maxDocuments, true, collation, validationOptions, timeSeriesOptions,
139-
changeStreamOptions);
143+
changeStreamOptions, encryptedFields);
140144
}
141145

142146
/**
@@ -148,7 +152,7 @@ public CollectionOptions capped() {
148152
*/
149153
public CollectionOptions maxDocuments(long maxDocuments) {
150154
return new CollectionOptions(size, maxDocuments, capped, collation, validationOptions, timeSeriesOptions,
151-
changeStreamOptions);
155+
changeStreamOptions, encryptedFields);
152156
}
153157

154158
/**
@@ -160,7 +164,7 @@ public CollectionOptions maxDocuments(long maxDocuments) {
160164
*/
161165
public CollectionOptions size(long size) {
162166
return new CollectionOptions(size, maxDocuments, capped, collation, validationOptions, timeSeriesOptions,
163-
changeStreamOptions);
167+
changeStreamOptions, encryptedFields);
164168
}
165169

166170
/**
@@ -172,7 +176,7 @@ public CollectionOptions size(long size) {
172176
*/
173177
public CollectionOptions collation(@Nullable Collation collation) {
174178
return new CollectionOptions(size, maxDocuments, capped, collation, validationOptions, timeSeriesOptions,
175-
changeStreamOptions);
179+
changeStreamOptions, encryptedFields);
176180
}
177181

178182
/**
@@ -293,7 +297,7 @@ public CollectionOptions validation(ValidationOptions validationOptions) {
293297

294298
Assert.notNull(validationOptions, "ValidationOptions must not be null");
295299
return new CollectionOptions(size, maxDocuments, capped, collation, validationOptions, timeSeriesOptions,
296-
changeStreamOptions);
300+
changeStreamOptions, encryptedFields);
297301
}
298302

299303
/**
@@ -307,7 +311,7 @@ public CollectionOptions timeSeries(TimeSeriesOptions timeSeriesOptions) {
307311

308312
Assert.notNull(timeSeriesOptions, "TimeSeriesOptions must not be null");
309313
return new CollectionOptions(size, maxDocuments, capped, collation, validationOptions, timeSeriesOptions,
310-
changeStreamOptions);
314+
changeStreamOptions, encryptedFields);
311315
}
312316

313317
/**
@@ -321,7 +325,19 @@ public CollectionOptions changeStream(CollectionChangeStreamOptions changeStream
321325

322326
Assert.notNull(changeStreamOptions, "ChangeStreamOptions must not be null");
323327
return new CollectionOptions(size, maxDocuments, capped, collation, validationOptions, timeSeriesOptions,
324-
changeStreamOptions);
328+
changeStreamOptions, encryptedFields);
329+
}
330+
331+
/**
332+
* Create new {@link CollectionOptions} with the given {@code encryptedFields}.
333+
*
334+
* @param encryptedFields can be null
335+
* @return new instance of {@link CollectionOptions}.
336+
* @since 4.5.0
337+
*/
338+
public CollectionOptions encryptedFields(@Nullable Bson encryptedFields) {
339+
return new CollectionOptions(size, maxDocuments, capped, collation, validationOptions, timeSeriesOptions,
340+
changeStreamOptions, encryptedFields);
325341
}
326342

327343
/**
@@ -392,12 +408,22 @@ public Optional<CollectionChangeStreamOptions> getChangeStreamOptions() {
392408
return Optional.ofNullable(changeStreamOptions);
393409
}
394410

411+
/**
412+
* Get the {@code encryptedFields} if available.
413+
*
414+
* @return {@link Optional#empty()} if not specified.
415+
* @since 4.5.0
416+
*/
417+
public Optional<Bson> getEncryptedFields() {
418+
return Optional.ofNullable(encryptedFields);
419+
}
420+
395421
@Override
396422
public String toString() {
397423
return "CollectionOptions{" + "maxDocuments=" + maxDocuments + ", size=" + size + ", capped=" + capped
398424
+ ", collation=" + collation + ", validationOptions=" + validationOptions + ", timeSeriesOptions="
399-
+ timeSeriesOptions + ", changeStreamOptions=" + changeStreamOptions + ", disableValidation="
400-
+ disableValidation() + ", strictValidation=" + strictValidation() + ", moderateValidation="
425+
+ timeSeriesOptions + ", changeStreamOptions=" + changeStreamOptions + ", encryptedFields=" + encryptedFields
426+
+ ", disableValidation=" + disableValidation() + ", strictValidation=" + strictValidation() + ", moderateValidation="
401427
+ moderateValidation() + ", warnOnValidationError=" + warnOnValidationError() + ", failOnValidationError="
402428
+ failOnValidationError() + '}';
403429
}
@@ -431,7 +457,10 @@ public boolean equals(@Nullable Object o) {
431457
if (!ObjectUtils.nullSafeEquals(timeSeriesOptions, that.timeSeriesOptions)) {
432458
return false;
433459
}
434-
return ObjectUtils.nullSafeEquals(changeStreamOptions, that.changeStreamOptions);
460+
if (!ObjectUtils.nullSafeEquals(changeStreamOptions, that.changeStreamOptions)) {
461+
return false;
462+
}
463+
return ObjectUtils.nullSafeEquals(encryptedFields, that.encryptedFields);
435464
}
436465

437466
@Override
@@ -443,6 +472,7 @@ public int hashCode() {
443472
result = 31 * result + ObjectUtils.nullSafeHashCode(validationOptions);
444473
result = 31 * result + ObjectUtils.nullSafeHashCode(timeSeriesOptions);
445474
result = 31 * result + ObjectUtils.nullSafeHashCode(changeStreamOptions);
475+
result = 31 * result + ObjectUtils.nullSafeHashCode(encryptedFields);
446476
return result;
447477
}
448478

Diff for: spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/EncryptionAlgorithms.java

+2
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,13 @@
1919
* Encryption algorithms supported by MongoDB Client Side Field Level Encryption.
2020
*
2121
* @author Christoph Strobl
22+
* @author Ross Lawley
2223
* @since 3.3
2324
*/
2425
public final class EncryptionAlgorithms {
2526

2627
public static final String AEAD_AES_256_CBC_HMAC_SHA_512_Deterministic = "AEAD_AES_256_CBC_HMAC_SHA_512-Deterministic";
2728
public static final String AEAD_AES_256_CBC_HMAC_SHA_512_Random = "AEAD_AES_256_CBC_HMAC_SHA_512-Random";
29+
public static final String RANGE = "Range";
2830

2931
}

Diff for: spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/EntityOperations.java

+2
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@
8383
* @author Mark Paluch
8484
* @author Christoph Strobl
8585
* @author Ben Foster
86+
* @author Ross Lawley
8687
* @since 2.1
8788
* @see MongoTemplate
8889
* @see ReactiveMongoTemplate
@@ -378,6 +379,7 @@ public CreateCollectionOptions convertToCreateCollectionOptions(@Nullable Collec
378379
collectionOptions.getChangeStreamOptions().ifPresent(it -> result
379380
.changeStreamPreAndPostImagesOptions(new ChangeStreamPreAndPostImagesOptions(it.getPreAndPostImages())));
380381

382+
collectionOptions.getEncryptedFields().ifPresent(result::encryptedFields);
381383
return result;
382384
}
383385

Diff for: spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/MongoConversionContext.java

+22-2
Original file line numberDiff line numberDiff line change
@@ -28,29 +28,44 @@
2828
* {@link ValueConversionContext} that allows to delegate read/write to an underlying {@link MongoConverter}.
2929
*
3030
* @author Christoph Strobl
31+
* @author Ross Lawley
3132
* @since 3.4
3233
*/
3334
public class MongoConversionContext implements ValueConversionContext<MongoPersistentProperty> {
3435

3536
private final PropertyValueProvider<MongoPersistentProperty> accessor; // TODO: generics
36-
private final @Nullable MongoPersistentProperty persistentProperty;
3737
private final MongoConverter mongoConverter;
3838

39+
@Nullable private final MongoPersistentProperty persistentProperty;
3940
@Nullable private final SpELContext spELContext;
41+
@Nullable private final String fieldNameAndQueryOperator;
4042

4143
public MongoConversionContext(PropertyValueProvider<MongoPersistentProperty> accessor,
4244
@Nullable MongoPersistentProperty persistentProperty, MongoConverter mongoConverter) {
43-
this(accessor, persistentProperty, mongoConverter, null);
45+
this(accessor, persistentProperty, mongoConverter, null, null);
4446
}
4547

4648
public MongoConversionContext(PropertyValueProvider<MongoPersistentProperty> accessor,
4749
@Nullable MongoPersistentProperty persistentProperty, MongoConverter mongoConverter,
4850
@Nullable SpELContext spELContext) {
51+
this(accessor, persistentProperty, mongoConverter, spELContext, null);
52+
}
53+
54+
public MongoConversionContext(PropertyValueProvider<MongoPersistentProperty> accessor,
55+
@Nullable MongoPersistentProperty persistentProperty, MongoConverter mongoConverter,
56+
@Nullable String fieldNameAndQueryOperator) {
57+
this(accessor, persistentProperty, mongoConverter, null, fieldNameAndQueryOperator);
58+
}
59+
60+
public MongoConversionContext(PropertyValueProvider<MongoPersistentProperty> accessor,
61+
@Nullable MongoPersistentProperty persistentProperty, MongoConverter mongoConverter,
62+
@Nullable SpELContext spELContext, @Nullable String fieldNameAndQueryOperator) {
4963

5064
this.accessor = accessor;
5165
this.persistentProperty = persistentProperty;
5266
this.mongoConverter = mongoConverter;
5367
this.spELContext = spELContext;
68+
this.fieldNameAndQueryOperator = fieldNameAndQueryOperator;
5469
}
5570

5671
@Override
@@ -84,4 +99,9 @@ public <T> T read(@Nullable Object value, TypeInformation<T> target) {
8499
public SpELContext getSpELContext() {
85100
return spELContext;
86101
}
102+
103+
@Nullable
104+
public String getFieldNameAndQueryOperator() {
105+
return fieldNameAndQueryOperator;
106+
}
87107
}

Diff for: spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/QueryMapper.java

+20-2
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@
8888
* @author David Julia
8989
* @author Divya Srivastava
9090
* @author Gyungrai Wang
91+
* @author Ross Lawley
9192
*/
9293
public class QueryMapper {
9394

@@ -670,9 +671,23 @@ private Object convertValue(Field documentField, Object sourceValue, Object valu
670671
PropertyValueConverter<Object, Object, ValueConversionContext<MongoPersistentProperty>> valueConverter) {
671672

672673
MongoPersistentProperty property = documentField.getProperty();
674+
675+
String fieldNameAndQueryOperator = property != null && !property.getFieldName().equals(documentField.name)
676+
? property.getFieldName() + "." + documentField.name
677+
: documentField.name;
678+
673679
MongoConversionContext conversionContext = new MongoConversionContext(NoPropertyPropertyValueProvider.INSTANCE,
674-
property, converter);
680+
property, converter, fieldNameAndQueryOperator);
681+
682+
return convertValueWithConversionContext(documentField, sourceValue, value, valueConverter, conversionContext);
683+
}
675684

685+
@Nullable
686+
private Object convertValueWithConversionContext(Field documentField, Object sourceValue, Object value,
687+
PropertyValueConverter<Object, Object, ValueConversionContext<MongoPersistentProperty>> valueConverter,
688+
MongoConversionContext conversionContext) {
689+
690+
MongoPersistentProperty property = documentField.getProperty();
676691
/* might be an $in clause with multiple entries */
677692
if (property != null && !property.isCollectionLike() && sourceValue instanceof Collection<?> collection) {
678693

@@ -692,7 +707,10 @@ private Object convertValue(Field documentField, Object sourceValue, Object valu
692707

693708
return BsonUtils.mapValues(document, (key, val) -> {
694709
if (isKeyword(key)) {
695-
return getMappedValue(documentField, val);
710+
MongoConversionContext fieldConversionContext = new MongoConversionContext(
711+
NoPropertyPropertyValueProvider.INSTANCE, property, converter,
712+
conversionContext.getFieldNameAndQueryOperator() + "." + key);
713+
return convertValueWithConversionContext(documentField, val, val, valueConverter, fieldConversionContext);
696714
}
697715
return val;
698716
});

Diff for: spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/encryption/ExplicitEncryptionContext.java

+7
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
* Default {@link EncryptionContext} implementation.
2727
*
2828
* @author Christoph Strobl
29+
* @author Ross Lawley
2930
* @since 4.1
3031
*/
3132
class ExplicitEncryptionContext implements EncryptionContext {
@@ -66,4 +67,10 @@ public <T> T read(@Nullable Object value, TypeInformation<T> target) {
6667
public <T> T write(@Nullable Object value, TypeInformation<T> target) {
6768
return conversionContext.write(value, target);
6869
}
70+
71+
@Override
72+
@Nullable
73+
public String getFieldNameAndQueryOperator() {
74+
return conversionContext.getFieldNameAndQueryOperator();
75+
}
6976
}

0 commit comments

Comments
 (0)