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 e3fdbe37c..2136e4021 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 @@ -43,6 +43,7 @@ import org.springframework.data.mongodb.core.encryption.EncryptionOptions; import org.springframework.data.mongodb.core.mapping.Encrypted; import org.springframework.data.mongodb.core.mapping.ExplicitEncrypted; import org.springframework.data.mongodb.core.mapping.MongoPersistentProperty; +import org.springframework.data.mongodb.core.mapping.RangeEncrypted; import org.springframework.data.mongodb.util.BsonUtils; import org.springframework.lang.Nullable; import org.springframework.util.ObjectUtils; @@ -180,23 +181,28 @@ public class MongoEncryptionConverter implements EncryptingConverter= 0) { - queryableEncryptionOptions = queryableEncryptionOptions - .contentionFactor(explicitEncryptedAnnotation.contentionFactor()); - } + if (rangeEncryptedAnnotation.contentionFactor() >= 0) { + queryableEncryptionOptions = queryableEncryptionOptions + .contentionFactor(rangeEncryptedAnnotation.contentionFactor()); + } - boolean isPartOfARangeQuery = algorithm.equalsIgnoreCase(RANGE) && fieldNameAndQueryOperator != null; - if (isPartOfARangeQuery) { - encryptExpression = true; - queryableEncryptionOptions = queryableEncryptionOptions.queryType("range"); + boolean isPartOfARangeQuery = fieldNameAndQueryOperator != null; + if (isPartOfARangeQuery) { + encryptExpression = true; + queryableEncryptionOptions = queryableEncryptionOptions.queryType("range"); + } + encryptionOptions = new EncryptionOptions(algorithm, key, queryableEncryptionOptions); } - encryptionOptions = new EncryptionOptions(algorithm, key, queryableEncryptionOptions); } if (encryptExpression) { diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/mapping/ExplicitEncrypted.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/mapping/ExplicitEncrypted.java index a8aedce8b..37d1019f6 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/mapping/ExplicitEncrypted.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/mapping/ExplicitEncrypted.java @@ -86,24 +86,6 @@ public @interface ExplicitEncrypted { */ String keyAltName() default ""; - /** - * Set the contention factor - *

- * Only required when using {@literal range} encryption. - * @return the contention factor - */ - long contentionFactor() default -1; - - /** - * Set the {@literal range} options - *

- * Should be valid extended json representing the range options and including the following values: - * {@code min}, {@code max}, {@code trimFactor} and {@code sparsity}. - * - * @return the json representation of range options - */ - String rangeOptions() default ""; - /** * The {@link EncryptingConverter} type handling the {@literal en-/decryption} of the annotated property. * diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/mapping/RangeEncrypted.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/mapping/RangeEncrypted.java new file mode 100644 index 000000000..6db52ae79 --- /dev/null +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/mapping/RangeEncrypted.java @@ -0,0 +1,65 @@ +/* + * Copyright 2025 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.mongodb.core.mapping; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.springframework.core.annotation.AliasFor; +import org.springframework.data.convert.PropertyValueConverter; +import org.springframework.data.convert.ValueConverter; +import org.springframework.data.mongodb.core.convert.encryption.EncryptingConverter; +import org.springframework.data.mongodb.core.convert.encryption.MongoEncryptionConverter; + +/** + * @author Christoph Strobl + * @since 2025/03 + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.FIELD) +@Encrypted(algorithm = "Range") +@ValueConverter +public @interface RangeEncrypted { + + /** + * Set the contention factor + *

+ * Only required when using {@literal range} encryption. + * @return the contention factor + */ + long contentionFactor() default -1; + + /** + * Set the {@literal range} options + *

+ * Should be valid extended json representing the range options and including the following values: + * {@code min}, {@code max}, {@code trimFactor} and {@code sparsity}. + * + * @return the json representation of range options + */ + String rangeOptions() default ""; + + /** + * The {@link EncryptingConverter} type handling the {@literal en-/decryption} of the annotated property. + * + * @return the configured {@link EncryptingConverter}. A {@link MongoEncryptionConverter} by default. + */ + @AliasFor(annotation = ValueConverter.class, value = "value") + Class value() default MongoEncryptionConverter.class; + +} 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 6b51a239a..24627dbc2 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 @@ -76,6 +76,7 @@ import org.springframework.data.mongodb.core.MongoTemplate; import org.springframework.data.mongodb.core.convert.MongoCustomConversions.MongoConverterConfigurationAdapter; import org.springframework.data.mongodb.core.convert.encryption.MongoEncryptionConverter; import org.springframework.data.mongodb.core.mapping.ExplicitEncrypted; +import org.springframework.data.mongodb.core.mapping.RangeEncrypted; import org.springframework.data.mongodb.core.query.Query; import org.springframework.data.mongodb.core.schema.JsonSchemaProperty; import org.springframework.data.mongodb.core.schema.QueryCharacteristics; @@ -353,12 +354,11 @@ class RangeEncryptionTests { static class Person { String id; -// @ExplicitEncrypted(algorithm = AEAD_AES_256_CBC_HMAC_SHA_512_Deterministic) String name; - @ExplicitEncrypted(algorithm = RANGE, contentionFactor = 0L, + @RangeEncrypted(contentionFactor = 0L, rangeOptions = "{\"min\": 0, \"max\": 200, \"trimFactor\": 1, \"sparsity\": 1}") Integer encryptedInt; - @ExplicitEncrypted(algorithm = RANGE, contentionFactor = 0L, + @RangeEncrypted(contentionFactor = 0L, rangeOptions = "{\"min\": {\"$numberLong\": \"1000\"}, \"max\": {\"$numberLong\": \"9999\"}, \"trimFactor\": 1, \"sparsity\": 1}") Long encryptedLong; public String getId() {