diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/MongoConversionContext.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/MongoConversionContext.java index acc8dfacb..93a07c1d9 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/MongoConversionContext.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/MongoConversionContext.java @@ -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 accessor, @Nullable MongoPersistentProperty persistentProperty, MongoConverter mongoConverter) { @@ -53,19 +52,19 @@ public class MongoConversionContext implements ValueConversionContext 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 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 { 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; diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/encryption/ExplicitEncryptionContext.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/encryption/ExplicitEncryptionContext.java index e78feba73..524b3c51b 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/encryption/ExplicitEncryptionContext.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/encryption/ExplicitEncryptionContext.java @@ -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 { @Override @Nullable - public String getFieldNameAndQueryOperator() { - return conversionContext.getFieldNameAndQueryOperator(); + public ConversionOperation getConversionOperation() { + return conversionContext.getConversionOperation(); } } diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/encryption/MongoEncryptionConverter.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/encryption/MongoEncryptionConverter.java index e56621964..999933033 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/encryption/MongoEncryptionConverter.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/encryption/MongoEncryptionConverter.java @@ -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 -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 " diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/encryption/EncryptionContext.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/encryption/EncryptionContext.java index 1643e2f95..f934ec881 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/encryption/EncryptionContext.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/encryption/EncryptionContext.java @@ -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 { * @return can be {@literal null}. */ @Nullable - default String getFieldNameAndQueryOperator() { + default ConversionOperation getConversionOperation() { return null; } } diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/encryption/RangeEncryptionTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/encryption/RangeEncryptionTests.java index 2419ba5bf..64bd2a4a9 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/encryption/RangeEncryptionTests.java +++ b/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; 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; 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; 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 { 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 { 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 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 { @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 { 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 { } } + 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); + } + } + }