Browse Source

encapsulate context information about actual path and query operation

issue/4185-light
Christoph Strobl 9 months ago
parent
commit
5c91ca17b3
No known key found for this signature in database
GPG Key ID: E6054036D0C37A4B
  1. 60
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/MongoConversionContext.java
  2. 12
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/QueryMapper.java
  3. 5
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/encryption/ExplicitEncryptionContext.java
  4. 25
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/encryption/MongoEncryptionConverter.java
  5. 3
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/encryption/EncryptionContext.java
  6. 64
      spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/encryption/RangeEncryptionTests.java

60
spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/MongoConversionContext.java

@ -16,7 +16,6 @@ @@ -16,7 +16,6 @@
package org.springframework.data.mongodb.core.convert;
import org.bson.conversions.Bson;
import org.springframework.data.convert.ValueConversionContext;
import org.springframework.data.mapping.model.PropertyValueProvider;
import org.springframework.data.mapping.model.SpELContext;
@ -38,7 +37,7 @@ public class MongoConversionContext implements ValueConversionContext<MongoPersi @@ -38,7 +37,7 @@ public class MongoConversionContext implements ValueConversionContext<MongoPersi
@Nullable private final MongoPersistentProperty persistentProperty;
@Nullable private final SpELContext spELContext;
@Nullable private final String fieldNameAndQueryOperator;
@Nullable private final ConversionOperation conversionOperation;
public MongoConversionContext(PropertyValueProvider<MongoPersistentProperty> accessor,
@Nullable MongoPersistentProperty persistentProperty, MongoConverter mongoConverter) {
@ -53,19 +52,19 @@ public class MongoConversionContext implements ValueConversionContext<MongoPersi @@ -53,19 +52,19 @@ public class MongoConversionContext implements ValueConversionContext<MongoPersi
public MongoConversionContext(PropertyValueProvider<MongoPersistentProperty> accessor,
@Nullable MongoPersistentProperty persistentProperty, MongoConverter mongoConverter,
@Nullable String fieldNameAndQueryOperator) {
this(accessor, persistentProperty, mongoConverter, null, fieldNameAndQueryOperator);
@Nullable ConversionOperation conversionOperation) {
this(accessor, persistentProperty, mongoConverter, null, conversionOperation);
}
public MongoConversionContext(PropertyValueProvider<MongoPersistentProperty> accessor,
@Nullable MongoPersistentProperty persistentProperty, MongoConverter mongoConverter,
@Nullable SpELContext spELContext, @Nullable String fieldNameAndQueryOperator) {
@Nullable SpELContext spELContext, @Nullable ConversionOperation conversionOperation) {
this.accessor = accessor;
this.persistentProperty = persistentProperty;
this.mongoConverter = mongoConverter;
this.spELContext = spELContext;
this.fieldNameAndQueryOperator = fieldNameAndQueryOperator;
this.conversionOperation = conversionOperation;
}
@Override
@ -101,7 +100,52 @@ public class MongoConversionContext implements ValueConversionContext<MongoPersi @@ -101,7 +100,52 @@ public class MongoConversionContext implements ValueConversionContext<MongoPersi
}
@Nullable
public String getFieldNameAndQueryOperator() {
return fieldNameAndQueryOperator;
public ConversionOperation getConversionOperation() {
return conversionOperation;
}
public interface ConversionOperation {
String getOperator();
String getPath();
}
public static class WriteConversionOperation implements ConversionOperation {
private final String path;
public WriteConversionOperation(String path) {
this.path = path;
}
@Override
public String getOperator() {
return "write";
}
@Override
public String getPath() {
return path;
}
}
public static class QueryConversionOperation implements ConversionOperation {
private final String operator;
private final String path;
public QueryConversionOperation(@Nullable String operator, String path) {
this.operator = operator != null ? operator : "$eq";
this.path = path;
}
public String getOperator() {
return operator;
}
public String getPath() {
return path;
}
}
}

12
spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/QueryMapper.java

@ -58,6 +58,8 @@ import org.springframework.data.mongodb.MongoExpression; @@ -58,6 +58,8 @@ import org.springframework.data.mongodb.MongoExpression;
import org.springframework.data.mongodb.core.aggregation.AggregationExpression;
import org.springframework.data.mongodb.core.aggregation.RelaxedTypeBasedAggregationOperationContext;
import org.springframework.data.mongodb.core.convert.MappingMongoConverter.NestedDocument;
import org.springframework.data.mongodb.core.convert.MongoConversionContext.ConversionOperation;
import org.springframework.data.mongodb.core.convert.MongoConversionContext.QueryConversionOperation;
import org.springframework.data.mongodb.core.mapping.FieldName;
import org.springframework.data.mongodb.core.mapping.MongoPersistentEntity;
import org.springframework.data.mongodb.core.mapping.MongoPersistentProperty;
@ -672,12 +674,9 @@ public class QueryMapper { @@ -672,12 +674,9 @@ public class QueryMapper {
MongoPersistentProperty property = documentField.getProperty();
String fieldNameAndQueryOperator = property != null && !property.getFieldName().equals(documentField.name)
? property.getFieldName() + "." + documentField.name
: documentField.name;
ConversionOperation criteriaContext = new QueryConversionOperation(isKeyword(documentField.name) ? documentField.name : "$eq", property.getFieldName());
MongoConversionContext conversionContext = new MongoConversionContext(NoPropertyPropertyValueProvider.INSTANCE,
property, converter, fieldNameAndQueryOperator);
property, converter, criteriaContext);
return convertValueWithConversionContext(documentField, sourceValue, value, valueConverter, conversionContext);
}
@ -707,9 +706,10 @@ public class QueryMapper { @@ -707,9 +706,10 @@ public class QueryMapper {
return BsonUtils.mapValues(document, (key, val) -> {
if (isKeyword(key)) {
MongoConversionContext fieldConversionContext = new MongoConversionContext(
NoPropertyPropertyValueProvider.INSTANCE, property, converter,
conversionContext.getFieldNameAndQueryOperator() + "." + key);
new QueryConversionOperation(key, conversionContext.getConversionOperation().getPath()));
return convertValueWithConversionContext(documentField, val, val, valueConverter, fieldConversionContext);
}
return val;

5
spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/encryption/ExplicitEncryptionContext.java

@ -16,6 +16,7 @@ @@ -16,6 +16,7 @@
package org.springframework.data.mongodb.core.convert.encryption;
import org.springframework.data.mongodb.core.convert.MongoConversionContext;
import org.springframework.data.mongodb.core.convert.MongoConversionContext.ConversionOperation;
import org.springframework.data.mongodb.core.encryption.EncryptionContext;
import org.springframework.data.mongodb.core.mapping.MongoPersistentProperty;
import org.springframework.data.util.TypeInformation;
@ -70,7 +71,7 @@ class ExplicitEncryptionContext implements EncryptionContext { @@ -70,7 +71,7 @@ class ExplicitEncryptionContext implements EncryptionContext {
@Override
@Nullable
public String getFieldNameAndQueryOperator() {
return conversionContext.getFieldNameAndQueryOperator();
public ConversionOperation getConversionOperation() {
return conversionContext.getConversionOperation();
}
}

25
spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/encryption/MongoEncryptionConverter.java

@ -34,6 +34,7 @@ import org.bson.Document; @@ -34,6 +34,7 @@ import org.bson.Document;
import org.bson.types.Binary;
import org.springframework.core.CollectionFactory;
import org.springframework.data.mongodb.core.convert.MongoConversionContext;
import org.springframework.data.mongodb.core.convert.MongoConversionContext.ConversionOperation;
import org.springframework.data.mongodb.core.encryption.Encryption;
import org.springframework.data.mongodb.core.encryption.EncryptionContext;
import org.springframework.data.mongodb.core.encryption.EncryptionKey;
@ -174,21 +175,21 @@ public class MongoEncryptionConverter implements EncryptingConverter<Object, Obj @@ -174,21 +175,21 @@ public class MongoEncryptionConverter implements EncryptingConverter<Object, Obj
String algorithm = annotation.algorithm();
EncryptionKey key = keyResolver.getKey(context);
String fieldNameAndQueryOperator = context.getFieldNameAndQueryOperator();
ConversionOperation conversionOperation = context.getConversionOperation();
EncryptionOptions encryptionOptions = new EncryptionOptions(algorithm, key,
getEQOptions(persistentProperty, fieldNameAndQueryOperator));
getEQOptions(persistentProperty, conversionOperation));
if (fieldNameAndQueryOperator != null && encryptionOptions.queryableEncryptionOptions() != null
if (conversionOperation != null && encryptionOptions.queryableEncryptionOptions() != null
&& !encryptionOptions.queryableEncryptionOptions().getQueryType().equals("equality")) {
return encryptExpression(fieldNameAndQueryOperator, value, encryptionOptions);
return encryptExpression(conversionOperation, value, encryptionOptions);
} else {
return encryptValue(value, context, persistentProperty, encryptionOptions);
}
}
private static @Nullable QueryableEncryptionOptions getEQOptions(MongoPersistentProperty persistentProperty,
String fieldNameAndQueryOperator) {
ConversionOperation conversionOperation) {
Queryable queryableAnnotation = persistentProperty.findAnnotation(Queryable.class);
if (queryableAnnotation == null || !StringUtils.hasText(queryableAnnotation.queryType())) {
@ -206,7 +207,7 @@ public class MongoEncryptionConverter implements EncryptingConverter<Object, Obj @@ -206,7 +207,7 @@ public class MongoEncryptionConverter implements EncryptingConverter<Object, Obj
queryableEncryptionOptions = queryableEncryptionOptions.contentionFactor(queryableAnnotation.contentionFactor());
}
boolean isPartOfARangeQuery = fieldNameAndQueryOperator != null;
boolean isPartOfARangeQuery = conversionOperation != null;
if (isPartOfARangeQuery) {
queryableEncryptionOptions = queryableEncryptionOptions.queryType(queryableAnnotation.queryType()); // should the
// type move
@ -255,19 +256,13 @@ public class MongoEncryptionConverter implements EncryptingConverter<Object, Obj @@ -255,19 +256,13 @@ public class MongoEncryptionConverter implements EncryptingConverter<Object, Obj
* @param encryptionOptions the options
* @return the encrypted range value for use in a range query
*/
private BsonValue encryptExpression(String fieldNameAndQueryOperator, Object value,
private BsonValue encryptExpression(ConversionOperation conversionOperation, Object value,
EncryptionOptions encryptionOptions) {
BsonValue doc = BsonUtils.simpleToBsonValue(value);
String fieldName = fieldNameAndQueryOperator;
String queryOperator = EQUALITY_OPERATOR;
int pos = fieldNameAndQueryOperator.lastIndexOf(".$");
if (pos > -1) {
fieldName = fieldNameAndQueryOperator.substring(0, pos);
queryOperator = fieldNameAndQueryOperator.substring(pos + 1);
}
String fieldName = conversionOperation.getPath();
String queryOperator = conversionOperation.getOperator();
if (!RANGE_OPERATORS.contains(queryOperator)) {
throw new AssertionError(String.format("Not a valid range query. Querying a range encrypted field but the "

3
spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/encryption/EncryptionContext.java

@ -16,6 +16,7 @@ @@ -16,6 +16,7 @@
package org.springframework.data.mongodb.core.encryption;
import org.springframework.data.mapping.PersistentProperty;
import org.springframework.data.mongodb.core.convert.MongoConversionContext.ConversionOperation;
import org.springframework.data.mongodb.core.mapping.MongoPersistentProperty;
import org.springframework.data.util.TypeInformation;
import org.springframework.expression.EvaluationContext;
@ -135,7 +136,7 @@ public interface EncryptionContext { @@ -135,7 +136,7 @@ public interface EncryptionContext {
* @return can be {@literal null}.
*/
@Nullable
default String getFieldNameAndQueryOperator() {
default ConversionOperation getConversionOperation() {
return null;
}
}

64
spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/encryption/RangeEncryptionTests.java

@ -20,6 +20,7 @@ import static org.assertj.core.api.Assertions.assertThatThrownBy; @@ -20,6 +20,7 @@ import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.springframework.data.mongodb.core.query.Criteria.where;
import java.security.SecureRandom;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
@ -33,6 +34,7 @@ import org.bson.BsonInt32; @@ -33,6 +34,7 @@ import org.bson.BsonInt32;
import org.bson.BsonString;
import org.bson.BsonValue;
import org.bson.Document;
import org.bson.types.Binary;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
@ -82,6 +84,7 @@ import com.mongodb.client.model.vault.RangeOptions; @@ -82,6 +84,7 @@ import com.mongodb.client.model.vault.RangeOptions;
import com.mongodb.client.result.UpdateResult;
import com.mongodb.client.vault.ClientEncryption;
import com.mongodb.client.vault.ClientEncryptions;
import org.springframework.util.StringUtils;
/**
* @author Ross Lawley
@ -272,6 +275,8 @@ class RangeEncryptionTests { @@ -272,6 +275,8 @@ class RangeEncryptionTests {
source.age = 42;
source.encryptedInt = 101;
source.encryptedLong = 1001L;
source.nested = new NestedWithQEFields();
source.nested.value = "Luigi time!";
return source;
}
@ -330,8 +335,17 @@ class RangeEncryptionTests { @@ -330,8 +335,17 @@ class RangeEncryptionTests {
BsonDocument local = clientEncryption.createEncryptedCollection(database, "test", createCollectionOptions,
new CreateEncryptedCollectionParams(LOCAL_KMS_PROVIDER));
return local.getArray("fields").stream().map(BsonValue::asDocument).collect(
Collectors.toMap(field -> field.getString("path").getValue(), field -> field.getBinary("keyId")));
Map<String, BsonBinary> keyMap = new LinkedHashMap<>();
for (Object o : local.getArray("fields")) {
if (o instanceof BsonDocument db) {
String path = db.getString("path").getValue();
BsonBinary binary = db.getBinary("keyId");
for (String part : path.split("\\.")) {
keyMap.put(part, binary);
}
}
}
return keyMap;
}
});
@ -341,8 +355,18 @@ class RangeEncryptionTests { @@ -341,8 +355,18 @@ class RangeEncryptionTests {
@Bean
MongoEncryptionConverter encryptingConverter(MongoClientEncryption mongoClientEncryption,
EncryptionKeyHolder keyHolder) {
return new MongoEncryptionConverter(mongoClientEncryption, EncryptionKeyResolver
.annotated((ctx) -> EncryptionKey.keyId(keyHolder.getEncryptionKey(ctx.getProperty().getFieldName()))));
return new MongoEncryptionConverter(mongoClientEncryption, EncryptionKeyResolver.annotated((ctx) -> {
String path = ctx.getProperty().getFieldName();
if (ctx.getProperty().getMongoField().getName().isPath()) {
path = StringUtils.arrayToDelimitedString(ctx.getProperty().getMongoField().getName().parts(), ".");
}
if (ctx.getConversionOperation() != null) {
path = ctx.getConversionOperation().getPath();
}
return EncryptionKey.keyId(keyHolder.getEncryptionKey(path));
}));
}
@Bean
@ -451,6 +475,8 @@ class RangeEncryptionTests { @@ -451,6 +475,8 @@ class RangeEncryptionTests {
rangeOptions = "{\"min\": {\"$numberLong\": \"1000\"}, \"max\": {\"$numberLong\": \"9999\"}, \"trimFactor\": 1, \"sparsity\": 1}") //
Long encryptedLong;
NestedWithQEFields nested;
public String getId() {
return this.id;
}
@ -509,4 +535,34 @@ class RangeEncryptionTests { @@ -509,4 +535,34 @@ class RangeEncryptionTests {
}
}
static class NestedWithQEFields {
@ValueConverter(MongoEncryptionConverter.class)
@Encrypted(algorithm = "Indexed") //
@Queryable(queryType = "equality", contentionFactor = 0) //
String value;
@Override
public String toString() {
return "NestedWithQEFields{" + "value='" + value + '\'' + '}';
}
@Override
public boolean equals(Object o) {
if (o == this) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
NestedWithQEFields that = (NestedWithQEFields) o;
return Objects.equals(value, that.value);
}
@Override
public int hashCode() {
return Objects.hash(value);
}
}
}

Loading…
Cancel
Save