diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MappingMongoJsonSchemaCreator.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MappingMongoJsonSchemaCreator.java index 55bd12f2d..9e6d91b33 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MappingMongoJsonSchemaCreator.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MappingMongoJsonSchemaCreator.java @@ -296,32 +296,34 @@ class MappingMongoJsonSchemaCreator implements MongoJsonSchemaCreator { enc = enc.keys(property.getEncryptionKeyIds()); } - RangeEncrypted rangeEncrypted = property.findAnnotation(RangeEncrypted.class); - if (rangeEncrypted != null) { - - QueryCharacteristic characteristic = new QueryCharacteristic() { + if (!StringUtils.hasText(encrypted.queryType())) { + return enc; + } - @Override - public String queryType() { - return "range"; - } + QueryCharacteristic characteristic = new QueryCharacteristic() { - @Override - public Document toDocument() { + @Override + public String queryType() { + return encrypted.queryType(); + } - Document options = QueryCharacteristic.super.toDocument(); - options.put("contention", rangeEncrypted.contentionFactor()); + @Override + public Document toDocument() { - if (!rangeEncrypted.rangeOptions().isEmpty()) { - options.putAll(Document.parse(rangeEncrypted.rangeOptions())); - } + Document options = QueryCharacteristic.super.toDocument(); - return options; + RangeEncrypted rangeEncrypted = property.findAnnotation(RangeEncrypted.class); + if (rangeEncrypted != null) { + options.put("contention", rangeEncrypted.contentionFactor()); } - }; - return new QueryableJsonSchemaProperty(enc, QueryCharacteristics.of(List.of(characteristic))); - } - return enc; + if (!encrypted.queryAttributes().isEmpty()) { + options.putAll(Document.parse(encrypted.queryAttributes())); + } + + return options; + } + }; + return new QueryableJsonSchemaProperty(enc, QueryCharacteristics.of(List.of(characteristic))); } private JsonSchemaProperty createObjectSchemaPropertyForEntity(List path, 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 274bfa85a..7edb49963 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 @@ -45,6 +45,7 @@ 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; +import org.springframework.util.StringUtils; /** * Default implementation of {@link EncryptingConverter}. Properties used with this converter must be annotated with @@ -168,7 +169,7 @@ public class MongoEncryptionConverter implements EncryptingConverter= 0) { + RangeEncrypted rangeEncryptedAnnotation = persistentProperty.findAnnotation(RangeEncrypted.class); + if (rangeEncryptedAnnotation != null && rangeEncryptedAnnotation.contentionFactor() >= 0) { queryableEncryptionOptions = queryableEncryptionOptions .contentionFactor(rangeEncryptedAnnotation.contentionFactor()); } boolean isPartOfARangeQuery = fieldNameAndQueryOperator != null; - if (isPartOfARangeQuery) { - queryableEncryptionOptions = queryableEncryptionOptions.queryType("range"); + if (isPartOfARangeQuery || !encryptedAnnotation.queryType().isEmpty()) { + queryableEncryptionOptions = queryableEncryptionOptions.queryType(encryptedAnnotation.queryType()); // should the type move to an extra annotation? + queryableEncryptionOptions = queryableEncryptionOptions.contentionFactor(1l); } return queryableEncryptionOptions; } private BsonBinary encryptValue(Object value, EncryptionContext context, MongoPersistentProperty persistentProperty, EncryptionOptions encryptionOptions) { + if (!persistentProperty.isEntity()) { if (persistentProperty.isCollectionLike()) { @@ -227,6 +231,7 @@ public class MongoEncryptionConverter implements EncryptingConverter encryptOptions = encryptOptions.keyId((BsonBinary) options.key().value()); } - if (options.queryableEncryptionOptions().isEmpty()) { + if (options.queryableEncryptionOptions() == null) { return encryptOptions; } diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/mapping/Encrypted.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/mapping/Encrypted.java index 3e169026a..4915f7b35 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/mapping/Encrypted.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/mapping/Encrypted.java @@ -109,4 +109,16 @@ public @interface Encrypted { * @see org.springframework.data.mongodb.core.EncryptionAlgorithms */ String algorithm() default ""; + + /** + * @return empty {@link String} if not set. + * @since 4.5 + */ + String queryType() default ""; + + /** + * @return empty {@link String} if not set. + * @since 4.5 + */ + String queryAttributes() default ""; } 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 index 658b21426..224fcbc99 100644 --- 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 @@ -20,6 +20,8 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.springframework.core.annotation.AliasFor; + /** * @author Christoph Strobl * @author Ross Lawley @@ -27,7 +29,7 @@ import java.lang.annotation.Target; */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.FIELD) -@Encrypted(algorithm = "Range") +@Encrypted(algorithm = "Range", queryType = "range") public @interface RangeEncrypted { /** @@ -47,5 +49,6 @@ public @interface RangeEncrypted { * * @return the json representation of range options */ + @AliasFor(annotation = Encrypted.class, value = "queryAttributes") String rangeOptions() default ""; } 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 613e1cd7e..d041a359b 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 @@ -30,6 +30,7 @@ import java.util.stream.Collectors; import org.bson.BsonBinary; import org.bson.BsonDocument; import org.bson.BsonInt32; +import org.bson.BsonString; import org.bson.BsonValue; import org.bson.Document; import org.junit.jupiter.api.AfterEach; @@ -48,6 +49,7 @@ import org.springframework.data.mongodb.core.MongoJsonSchemaCreator; 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.Encrypted; import org.springframework.data.mongodb.core.mapping.MongoMappingContext; import org.springframework.data.mongodb.core.mapping.RangeEncrypted; import org.springframework.data.mongodb.core.query.Query; @@ -108,8 +110,12 @@ class RangeEncryptionTests { .rangeOptions(new RangeOptions().min(new BsonInt32(0)).max(new BsonInt32(200))) .keyId(keyHolder.getEncryptionKey("encryptedInt")).queryType("range"); + EncryptOptions stringEncOptions = new EncryptOptions("Range") + .contentionFactor(8L).rangeOptions(new RangeOptions()) + .keyId(keyHolder.getEncryptionKey("name")).queryType("equality"); + Document source = new Document("_id", "id-1"); - source.put("name", "It's a Me, Mario!"); + source.put("name", clientEncryption.getClientEncryption().encrypt(new BsonString("It's a Me, Mario!"), stringEncOptions)); source.put("encryptedInt", clientEncryption.getClientEncryption().encrypt(new BsonInt32(101), encryptOptions)); source.put("_class", Person.class.getName()); @@ -129,6 +135,7 @@ class RangeEncryptionTests { @Test void canLesserThanEqualMatchRangeEncryptedField() { + Person source = createPerson(); template.insert(source); @@ -138,6 +145,7 @@ class RangeEncryptionTests { @Test void canRangeMatchRangeEncryptedField() { + Person source = createPerson(); template.insert(source); @@ -149,6 +157,7 @@ class RangeEncryptionTests { @Test void canUpdateRangeEncryptedField() { + Person source = createPerson(); template.insert(source); @@ -162,6 +171,7 @@ class RangeEncryptionTests { @Test void errorsWhenUsingNonRangeOperatorEqOnRangeEncryptedField() { + Person source = createPerson(); template.insert(source); @@ -175,6 +185,7 @@ class RangeEncryptionTests { @Test void errorsWhenUsingNonRangeOperatorInOnRangeEncryptedField() { + Person source = createPerson(); template.insert(source); @@ -347,6 +358,8 @@ class RangeEncryptionTests { static class Person { String id; + @ValueConverter(MongoEncryptionConverter.class) + @Encrypted(algorithm = "Range", queryType = "equality") String name; @ValueConverter(MongoEncryptionConverter.class) @@ -359,6 +372,9 @@ class RangeEncryptionTests { rangeOptions = "{\"min\": {\"$numberLong\": \"1000\"}, \"max\": {\"$numberLong\": \"9999\"}, \"trimFactor\": 1, \"sparsity\": 1}") // Long encryptedLong; + + + public String getId() { return this.id; }