From 3fbed7b93c7fcd6fb1494406c7fb4da8408abf4b Mon Sep 17 00:00:00 2001 From: Christoph Strobl Date: Fri, 28 Mar 2025 13:12:58 +0100 Subject: [PATCH] Fix update of encrypted field --- .../mongodb/core/convert/QueryMapper.java | 4 +- .../mongodb/core/convert/UpdateMapper.java | 13 ++++ .../core/encryption/RangeEncryptionTests.java | 67 +++++++++++++------ 3 files changed, 60 insertions(+), 24 deletions(-) diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/QueryMapper.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/QueryMapper.java index 1cd0b3bed..8c2fc48b3 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/QueryMapper.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/QueryMapper.java @@ -683,7 +683,7 @@ public class QueryMapper { } @Nullable - private Object convertValueWithConversionContext(Field documentField, Object sourceValue, Object value, + protected Object convertValueWithConversionContext(Field documentField, Object sourceValue, Object value, PropertyValueConverter> valueConverter, MongoConversionContext conversionContext) { @@ -1624,7 +1624,7 @@ public class QueryMapper { return converter; } - private enum NoPropertyPropertyValueProvider implements PropertyValueProvider { + enum NoPropertyPropertyValueProvider implements PropertyValueProvider { INSTANCE; diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/UpdateMapper.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/UpdateMapper.java index 35cb578c2..59fca9a9c 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/UpdateMapper.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/UpdateMapper.java @@ -24,6 +24,8 @@ import java.util.Map.Entry; import org.bson.Document; import org.bson.conversions.Bson; import org.springframework.core.convert.converter.Converter; +import org.springframework.data.convert.PropertyValueConverter; +import org.springframework.data.convert.ValueConversionContext; import org.springframework.data.domain.Sort; import org.springframework.data.domain.Sort.Order; import org.springframework.data.mapping.Association; @@ -160,6 +162,17 @@ public class UpdateMapper extends QueryMapper { return super.getMappedObjectForField(field, rawValue); } + protected Object convertValueWithConversionContext(Field documentField, Object sourceValue, Object value, + + PropertyValueConverter> valueConverter, + MongoConversionContext conversionContext) { + + MongoConversionContext ctx = new MongoConversionContext(NoPropertyPropertyValueProvider.INSTANCE, + conversionContext.getProperty(), converter, conversionContext.getSpELContext(), null); + + return super.convertValueWithConversionContext(documentField, sourceValue, value, valueConverter, ctx); + } + private Entry getMappedUpdateModifier(Field field, Object rawValue) { Object value; 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 d9e6a4f94..015104fa5 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 @@ -54,6 +54,7 @@ import org.springframework.data.mongodb.core.mapping.MongoMappingContext; import org.springframework.data.mongodb.core.mapping.Queryable; import org.springframework.data.mongodb.core.mapping.RangeEncrypted; import org.springframework.data.mongodb.core.query.Query; +import org.springframework.data.mongodb.core.query.Update; import org.springframework.data.mongodb.core.schema.MongoJsonSchema; import org.springframework.data.mongodb.test.util.EnableIfMongoServerVersion; import org.springframework.data.mongodb.test.util.EnableIfReplicaSetAvailable; @@ -78,6 +79,7 @@ import com.mongodb.client.model.IndexOptions; import com.mongodb.client.model.Indexes; import com.mongodb.client.model.vault.EncryptOptions; 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; @@ -101,7 +103,7 @@ class RangeEncryptionTests { } @Test // GH-4185 - void canGreaterThanEqualMatchRangeEncryptedField() { + void manuallyEncryptedValuesCanBeSavedAndRetrievedCorrectly() { EncryptOptions encryptOptions = new EncryptOptions("Range").contentionFactor(1L) .keyId(keyHolder.getEncryptionKey("encryptedInt")) @@ -111,21 +113,18 @@ class RangeEncryptionTests { .rangeOptions(new RangeOptions().min(new BsonInt32(0)).max(new BsonInt32(200))) .keyId(keyHolder.getEncryptionKey("encryptedInt")).queryType("range"); - /* - @Encrypted(algorithm = "Indexed") - @Queryable(queryType = "equality", contentionFactor = 0) - */ - EncryptOptions equalityEncOptions = new EncryptOptions("Indexed") - .contentionFactor(0L) - .keyId(keyHolder.getEncryptionKey("age"));; + EncryptOptions equalityEncOptions = new EncryptOptions("Indexed").contentionFactor(0L) + .keyId(keyHolder.getEncryptionKey("age")); + ; - EncryptOptions equalityEncOptionsString = new EncryptOptions("Indexed") - .contentionFactor(0L) - .keyId(keyHolder.getEncryptionKey("name"));; + EncryptOptions equalityEncOptionsString = new EncryptOptions("Indexed").contentionFactor(0L) + .keyId(keyHolder.getEncryptionKey("name")); + ; Document source = new Document("_id", "id-1"); - source.put("name", clientEncryption.getClientEncryption().encrypt(new BsonString("It's a Me, Mario!"), equalityEncOptionsString)); + source.put("name", + clientEncryption.getClientEncryption().encrypt(new BsonString("It's a Me, Mario!"), equalityEncOptionsString)); source.put("age", clientEncryption.getClientEncryption().encrypt(new BsonInt32(101), equalityEncOptions)); source.put("encryptedInt", clientEncryption.getClientEncryption().encrypt(new BsonInt32(101), encryptOptions)); source.put("_class", Person.class.getName()); @@ -155,7 +154,7 @@ class RangeEncryptionTests { } @Test - void eqQueryWorksOnEncryptedField() { + void canQueryEqualityEncryptedField() { Person source = createPerson(); template.insert(source); @@ -165,19 +164,31 @@ class RangeEncryptionTests { } @Test - void canRangeMatchRangeEncryptedField() { + void canExcludeSafeContentFromResult() { Person source = createPerson(); template.insert(source); Query q = Query.query(where("encryptedLong").lte(1001L).gte(1001L)); q.fields().exclude("__safeContent__"); + Person loaded = template.query(Person.class).matching(q).firstValue(); assertThat(loaded).isEqualTo(source); } @Test - void canUpdateRangeEncryptedField() { + void canRangeMatchRangeEncryptedField() { + + Person source = createPerson(); + template.insert(source); + + Query q = Query.query(where("encryptedLong").lte(1001L).gte(1001L)); + Person loaded = template.query(Person.class).matching(q).firstValue(); + assertThat(loaded).isEqualTo(source); + } + + @Test + void canReplaceEntityWithRangeEncryptedField() { Person source = createPerson(); template.insert(source); @@ -190,6 +201,20 @@ class RangeEncryptionTests { assertThat(loaded).isEqualTo(source); } + @Test + void canUpdateRangeEncryptedField() { + + Person source = createPerson(); + template.insert(source); + + UpdateResult updateResult = template.update(Person.class).matching(where("id").is(source.id)) + .apply(Update.update("encryptedLong", 5000L)).first(); + assertThat(updateResult.getModifiedCount()).isOne(); + + Person loaded = template.query(Person.class).matching(where("id").is(source.id)).firstValue(); + assertThat(loaded.encryptedLong).isEqualTo(5000L); + } + @Test void errorsWhenUsingNonRangeOperatorEqOnRangeEncryptedField() { @@ -382,13 +407,14 @@ class RangeEncryptionTests { String id; @ValueConverter(MongoEncryptionConverter.class) - @Encrypted(algorithm = "Indexed") - @Queryable(queryType = "equality", contentionFactor = 0) + @Encrypted(algorithm = "Indexed") // + @Queryable(queryType = "equality", contentionFactor = 0) // String name; @ValueConverter(MongoEncryptionConverter.class) - @Encrypted(algorithm = "Indexed") - @Queryable(queryType = "equality", contentionFactor = 0) + //@Encrypted(algorithm = "Indexed", queries = {@Queryable(queryType = "equality", contentionFactor = 0)}) + @Encrypted(algorithm = "Indexed") // + @Queryable(queryType = "equality", contentionFactor = 0) // Integer age; @ValueConverter(MongoEncryptionConverter.class) @@ -401,9 +427,6 @@ class RangeEncryptionTests { rangeOptions = "{\"min\": {\"$numberLong\": \"1000\"}, \"max\": {\"$numberLong\": \"9999\"}, \"trimFactor\": 1, \"sparsity\": 1}") // Long encryptedLong; - - - public String getId() { return this.id; }