From 43ca5e813a07202b02796ff32ccbc4ad825e37ac Mon Sep 17 00:00:00 2001 From: Christoph Strobl Date: Thu, 20 Mar 2025 14:17:20 +0100 Subject: [PATCH] alright - so far so good - now we only need a ton of tests --- .../data/mongodb/core/CollectionOptions.java | 12 +--- .../core/MappingMongoJsonSchemaCreator.java | 1 + .../IdentifiableJsonSchemaProperty.java | 9 ++- ...appingMongoJsonSchemaCreatorUnitTests.java | 58 ++++++++++++++++++- .../core/encryption/RangeEncryptionTests.java | 7 ++- 5 files changed, 71 insertions(+), 16 deletions(-) diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/CollectionOptions.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/CollectionOptions.java index 063bdf819..afe3abdb0 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/CollectionOptions.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/CollectionOptions.java @@ -697,13 +697,7 @@ public class CollectionOptions { if (entry.getValue().containsKey("bsonType")) { field.append("bsonType", entry.getValue().get("bsonType")); } - Document query = new Document("queryType", entry.getValue().get("algorithm", "range").toLowerCase()); - query.append("contention", entry.getValue().get("contention")); - query.append("trimFactor", entry.getValue().get("trimFactor")); - query.append("sparsity", entry.getValue().get("sparsity")); - query.append("min", entry.getValue().get("min")); - query.append("max", entry.getValue().get("max")); - field.append("queries", List.of(query)); + field.put("queries", entry.getValue().get("queries")); fields.add(field); } } @@ -727,9 +721,9 @@ public class CollectionOptions { String path = currentPath == null ? entry.getKey() : (currentPath + "." + entry.getKey()); if (nested.containsKey("encrypt")) { Document target = new Document(nested.get("encrypt", Document.class)); - if(nested.containsKey("queries")) { + if (nested.containsKey("queries")) { List queries = nested.get("queries", List.class); - if(!queries.isEmpty() && queries.iterator().next() instanceof Document qd) { + if (!queries.isEmpty() && queries.iterator().next() instanceof Document qd) { target.putAll(qd); } } 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 e500dcf1f..4baa86c7f 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 @@ -312,6 +312,7 @@ class MappingMongoJsonSchemaCreator implements MongoJsonSchemaCreator { if (!rangeEncrypted.rangeOptions().isEmpty()) { options.putAll(Document.parse(rangeEncrypted.rangeOptions())); } + options.put("contention", rangeEncrypted.contentionFactor()); return options; } }; diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/schema/IdentifiableJsonSchemaProperty.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/schema/IdentifiableJsonSchemaProperty.java index 4ebc2194f..1e278b118 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/schema/IdentifiableJsonSchemaProperty.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/schema/IdentifiableJsonSchemaProperty.java @@ -96,9 +96,12 @@ public class IdentifiableJsonSchemaProperty implemen Document doc = targetProperty.toDocument(); Document propertySpecification = doc.get(targetProperty.getIdentifier(), Document.class); - List queries = characteristics.getCharacteristics().stream().map(QueryCharacteristic::toDocument) - .toList(); - propertySpecification.append("queries", queries); + if (propertySpecification.containsKey("encrypt")) { + Document encrypt = propertySpecification.get("encrypt", Document.class); + List queries = characteristics.getCharacteristics().stream().map(QueryCharacteristic::toDocument) + .toList(); + encrypt.append("queries", queries); + } return doc; } diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/MappingMongoJsonSchemaCreatorUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/MappingMongoJsonSchemaCreatorUnitTests.java index d18ed6f11..2b4c2e490 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/MappingMongoJsonSchemaCreatorUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/MappingMongoJsonSchemaCreatorUnitTests.java @@ -15,7 +15,8 @@ */ package org.springframework.data.mongodb.core; -import static org.springframework.data.mongodb.test.util.Assertions.*; +import static org.springframework.data.mongodb.test.util.Assertions.assertThat; +import static org.springframework.data.mongodb.test.util.Assertions.assertThatExceptionOfType; import java.util.Collections; import java.util.Date; @@ -38,6 +39,7 @@ import org.springframework.data.mongodb.core.mapping.Field; import org.springframework.data.mongodb.core.mapping.FieldType; import org.springframework.data.mongodb.core.mapping.MongoId; import org.springframework.data.mongodb.core.mapping.MongoMappingContext; +import org.springframework.data.mongodb.core.mapping.RangeEncrypted; import org.springframework.data.mongodb.core.schema.JsonSchemaObject.Type; import org.springframework.data.mongodb.core.schema.JsonSchemaProperty; import org.springframework.data.mongodb.core.schema.MongoJsonSchema; @@ -282,6 +284,38 @@ class MappingMongoJsonSchemaCreatorUnitTests { .containsEntry("properties.domainTypeValue", Document.parse("{'encrypt': {'bsonType': 'object' } }")); } + @Test // GH-4454 + void qeRangeEncryptedProperties() { + + MongoJsonSchema schema = MongoJsonSchemaCreator.create() // + .filter(MongoJsonSchemaCreator.encryptedOnly()) // filter non encrypted fields + .createSchemaFor(RangeEncryptedRoot.class); + + String expectedForInt = """ + { 'encrypt' : { + 'algorithm' : 'Range', + 'bsonType' : 'int', + 'queries' : [ + { 'contention' : { '$numberLong' : '0' }, 'max' : 200, 'min' : 0, 'queryType' : 'range', 'sparsity' : 1, 'trimFactor' : 1 } + ] + }}"""; + + String expectedForLong = """ + { 'encrypt' : { + 'algorithm' : 'Range', + 'bsonType' : 'long', + 'queries' : [ + { contention : { '$numberLong' : '1' }, 'max' : 1, 'min' : -1, 'queryType' : 'range', 'sparsity' : 1, 'trimFactor' : 1 } + ] + }}"""; + + assertThat(schema.schemaDocument()) // + .doesNotContainKey("properties.unencrypted") // + .containsEntry("properties.encryptedInt", Document.parse(expectedForInt)) + .containsEntry("properties.nested.properties.encrypted_long", Document.parse(expectedForLong)); + + } + // --> TYPES AND JSON // --> ENUM @@ -311,7 +345,8 @@ class MappingMongoJsonSchemaCreatorUnitTests { " 'binaryDataProperty' : { 'bsonType' : 'binData' }," + // " 'collectionProperty' : { 'type' : 'array' }," + // " 'simpleTypeCollectionProperty' : { 'type' : 'array', 'items' : { 'type' : 'string' } }," + // - " 'complexTypeCollectionProperty' : { 'type' : 'array', 'items' : { 'type' : 'object', 'properties' : { 'field' : { 'type' : 'string'} } } }" + // + " 'complexTypeCollectionProperty' : { 'type' : 'array', 'items' : { 'type' : 'object', 'properties' : { 'field' : { 'type' : 'string'} } } }" + + // " 'enumTypeCollectionProperty' : { 'type' : 'array', 'items' : " + JUST_SOME_ENUM + " }" + // " 'mapProperty' : { 'type' : 'object' }," + // " 'objectProperty' : { 'type' : 'object' }," + // @@ -692,4 +727,23 @@ class MappingMongoJsonSchemaCreatorUnitTests { static class WithEncryptedEntityLikeProperty { @Encrypted SomeDomainType domainTypeValue; } + + static class RangeEncryptedRoot { + + String unencrypted; + + @RangeEncrypted(contentionFactor = 0L, rangeOptions = "{ 'min': 0, 'max': 200, 'trimFactor': 1, 'sparsity': 1}") // + Integer encryptedInt; + + NestedRangeEncrypted nested; + + } + + static class NestedRangeEncrypted { + + @Field("encrypted_long") + @RangeEncrypted(contentionFactor = 1L, rangeOptions = "{ 'min': -1, 'max': 1, 'trimFactor': 1, 'sparsity': 1}") // + Long encryptedLong; + } + } 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 6e33edc5e..78a61b448 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 @@ -119,10 +119,12 @@ class RangeEncryptionTests { Document result = template.execute(Person.class, col -> { + BsonDocument filterSource = new BsonDocument("encryptedInt", new BsonDocument("$gte", new BsonInt32(100))); BsonDocument filter = clientEncryption.getClientEncryption().encryptExpression( - new Document("$and", List.of(new BsonDocument("encryptedInt", new BsonDocument("$gte", new BsonInt32(100))))), + new Document("$and", List.of(filterSource)), encryptExpressionOptions); Document first = col.find(filter).first(); +// Document first = col.find(filterSource).first(); System.out.println("first.toJson(): " + first.toJson()); return first; }); @@ -290,7 +292,8 @@ class RangeEncryptionTests { builder.autoEncryptionSettings(AutoEncryptionSettings.builder() // .kmsProviders(clientEncryptionSettings.getKmsProviders()) // .keyVaultNamespace(clientEncryptionSettings.getKeyVaultNamespace()) // - .bypassQueryAnalysis(true).build()); + .bypassQueryAnalysis(true) + .build()); } }