Browse Source

some more updates

issue/4185-light
Christoph Strobl 9 months ago
parent
commit
3923fefe7d
No known key found for this signature in database
GPG Key ID: E6054036D0C37A4B
  1. 46
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/MongoConversionContext.java
  2. 8
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/QueryMapper.java
  3. 6
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/encryption/ExplicitEncryptionContext.java
  4. 25
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/encryption/MongoEncryptionConverter.java
  5. 4
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/encryption/EncryptionContext.java
  6. 2
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/mapping/Queryable.java
  7. 3
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/schema/DocumentJsonSchema.java
  8. 110
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/schema/IdentifiableJsonSchemaProperty.java
  9. 2
      spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/encryption/MongoQueryableEncryptionCollectionCreationTests.java
  10. 29
      spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/encryption/RangeEncryptionTests.java

46
spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/MongoConversionContext.java

@ -37,7 +37,7 @@ public class MongoConversionContext implements ValueConversionContext<MongoPersi @@ -37,7 +37,7 @@ public class MongoConversionContext implements ValueConversionContext<MongoPersi
@Nullable private final MongoPersistentProperty persistentProperty;
@Nullable private final SpELContext spELContext;
@Nullable private final ConversionOperation conversionOperation;
@Nullable private final OperatorContext operatorContext;
public MongoConversionContext(PropertyValueProvider<MongoPersistentProperty> accessor,
@Nullable MongoPersistentProperty persistentProperty, MongoConverter mongoConverter) {
@ -52,19 +52,19 @@ public class MongoConversionContext implements ValueConversionContext<MongoPersi @@ -52,19 +52,19 @@ public class MongoConversionContext implements ValueConversionContext<MongoPersi
public MongoConversionContext(PropertyValueProvider<MongoPersistentProperty> accessor,
@Nullable MongoPersistentProperty persistentProperty, MongoConverter mongoConverter,
@Nullable ConversionOperation conversionOperation) {
this(accessor, persistentProperty, mongoConverter, null, conversionOperation);
@Nullable OperatorContext operatorContext) {
this(accessor, persistentProperty, mongoConverter, null, operatorContext);
}
public MongoConversionContext(PropertyValueProvider<MongoPersistentProperty> accessor,
@Nullable MongoPersistentProperty persistentProperty, MongoConverter mongoConverter,
@Nullable SpELContext spELContext, @Nullable ConversionOperation conversionOperation) {
@Nullable SpELContext spELContext, @Nullable OperatorContext operatorContext) {
this.accessor = accessor;
this.persistentProperty = persistentProperty;
this.mongoConverter = mongoConverter;
this.spELContext = spELContext;
this.conversionOperation = conversionOperation;
this.operatorContext = operatorContext;
}
@Override
@ -100,25 +100,39 @@ public class MongoConversionContext implements ValueConversionContext<MongoPersi @@ -100,25 +100,39 @@ public class MongoConversionContext implements ValueConversionContext<MongoPersi
}
@Nullable
public ConversionOperation getConversionOperation() {
return conversionOperation;
public OperatorContext getOperatorContext() {
return operatorContext;
}
public interface ConversionOperation {
String getOperator();
String getPath();
/**
* The {@link OperatorContext} provides access to the actual conversion intent like a write operation or a query
* operator such as {@literal $gte}.
*
* @since 4.5
*/
public interface OperatorContext {
/**
* The operator the conversion is used in.
* @return {@literal write} for simple write operations during save, or a query operator.
*/
String getOperator();
/**
* The context path the operator is used in.
* @return never {@literal null}.
*/
String getPath();
}
public static class WriteConversionOperation implements ConversionOperation {
public static class WriteOperatorContext implements OperatorContext {
private final String path;
public WriteConversionOperation(String path) {
public WriteOperatorContext(String path) {
this.path = path;
}
@Override
public String getOperator() {
return "write";
@ -130,12 +144,12 @@ public class MongoConversionContext implements ValueConversionContext<MongoPersi @@ -130,12 +144,12 @@ public class MongoConversionContext implements ValueConversionContext<MongoPersi
}
}
public static class QueryConversionOperation implements ConversionOperation {
public static class QueryOperatorContext implements OperatorContext {
private final String operator;
private final String path;
public QueryConversionOperation(@Nullable String operator, String path) {
public QueryOperatorContext(@Nullable String operator, String path) {
this.operator = operator != null ? operator : "$eq";
this.path = path;
}

8
spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/QueryMapper.java

@ -58,8 +58,8 @@ import org.springframework.data.mongodb.MongoExpression; @@ -58,8 +58,8 @@ import org.springframework.data.mongodb.MongoExpression;
import org.springframework.data.mongodb.core.aggregation.AggregationExpression;
import org.springframework.data.mongodb.core.aggregation.RelaxedTypeBasedAggregationOperationContext;
import org.springframework.data.mongodb.core.convert.MappingMongoConverter.NestedDocument;
import org.springframework.data.mongodb.core.convert.MongoConversionContext.ConversionOperation;
import org.springframework.data.mongodb.core.convert.MongoConversionContext.QueryConversionOperation;
import org.springframework.data.mongodb.core.convert.MongoConversionContext.OperatorContext;
import org.springframework.data.mongodb.core.convert.MongoConversionContext.QueryOperatorContext;
import org.springframework.data.mongodb.core.mapping.FieldName;
import org.springframework.data.mongodb.core.mapping.MongoPersistentEntity;
import org.springframework.data.mongodb.core.mapping.MongoPersistentProperty;
@ -674,7 +674,7 @@ public class QueryMapper { @@ -674,7 +674,7 @@ public class QueryMapper {
MongoPersistentProperty property = documentField.getProperty();
ConversionOperation criteriaContext = new QueryConversionOperation(isKeyword(documentField.name) ? documentField.name : "$eq", property.getFieldName());
OperatorContext criteriaContext = new QueryOperatorContext(isKeyword(documentField.name) ? documentField.name : "$eq", property.getFieldName());
MongoConversionContext conversionContext = new MongoConversionContext(NoPropertyPropertyValueProvider.INSTANCE,
property, converter, criteriaContext);
@ -709,7 +709,7 @@ public class QueryMapper { @@ -709,7 +709,7 @@ public class QueryMapper {
MongoConversionContext fieldConversionContext = new MongoConversionContext(
NoPropertyPropertyValueProvider.INSTANCE, property, converter,
new QueryConversionOperation(key, conversionContext.getConversionOperation().getPath()));
new QueryOperatorContext(key, conversionContext.getOperatorContext().getPath()));
return convertValueWithConversionContext(documentField, val, val, valueConverter, fieldConversionContext);
}
return val;

6
spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/encryption/ExplicitEncryptionContext.java

@ -16,7 +16,7 @@ @@ -16,7 +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.convert.MongoConversionContext.OperatorContext;
import org.springframework.data.mongodb.core.encryption.EncryptionContext;
import org.springframework.data.mongodb.core.mapping.MongoPersistentProperty;
import org.springframework.data.util.TypeInformation;
@ -71,7 +71,7 @@ class ExplicitEncryptionContext implements EncryptionContext { @@ -71,7 +71,7 @@ class ExplicitEncryptionContext implements EncryptionContext {
@Override
@Nullable
public ConversionOperation getConversionOperation() {
return conversionContext.getConversionOperation();
public OperatorContext getConversionOperation() {
return conversionContext.getOperatorContext();
}
}

25
spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/encryption/MongoEncryptionConverter.java

@ -34,7 +34,7 @@ import org.bson.Document; @@ -34,7 +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.convert.MongoConversionContext.OperatorContext;
import org.springframework.data.mongodb.core.encryption.Encryption;
import org.springframework.data.mongodb.core.encryption.EncryptionContext;
import org.springframework.data.mongodb.core.encryption.EncryptionKey;
@ -175,21 +175,21 @@ public class MongoEncryptionConverter implements EncryptingConverter<Object, Obj @@ -175,21 +175,21 @@ public class MongoEncryptionConverter implements EncryptingConverter<Object, Obj
String algorithm = annotation.algorithm();
EncryptionKey key = keyResolver.getKey(context);
ConversionOperation conversionOperation = context.getConversionOperation();
OperatorContext operatorContext = context.getConversionOperation();
EncryptionOptions encryptionOptions = new EncryptionOptions(algorithm, key,
getEQOptions(persistentProperty, conversionOperation));
getEQOptions(persistentProperty, operatorContext));
if (conversionOperation != null && encryptionOptions.queryableEncryptionOptions() != null
if (operatorContext != null && encryptionOptions.queryableEncryptionOptions() != null
&& !encryptionOptions.queryableEncryptionOptions().getQueryType().equals("equality")) {
return encryptExpression(conversionOperation, value, encryptionOptions);
return encryptExpression(operatorContext, value, encryptionOptions);
} else {
return encryptValue(value, context, persistentProperty, encryptionOptions);
}
}
private static @Nullable QueryableEncryptionOptions getEQOptions(MongoPersistentProperty persistentProperty,
ConversionOperation conversionOperation) {
OperatorContext operatorContext) {
Queryable queryableAnnotation = persistentProperty.findAnnotation(Queryable.class);
if (queryableAnnotation == null || !StringUtils.hasText(queryableAnnotation.queryType())) {
@ -207,12 +207,9 @@ public class MongoEncryptionConverter implements EncryptingConverter<Object, Obj @@ -207,12 +207,9 @@ public class MongoEncryptionConverter implements EncryptingConverter<Object, Obj
queryableEncryptionOptions = queryableEncryptionOptions.contentionFactor(queryableAnnotation.contentionFactor());
}
boolean isPartOfARangeQuery = conversionOperation != null;
boolean isPartOfARangeQuery = operatorContext != null;
if (isPartOfARangeQuery) {
queryableEncryptionOptions = queryableEncryptionOptions.queryType(queryableAnnotation.queryType()); // should the
// type move
// to an extra
// annotation?
queryableEncryptionOptions = queryableEncryptionOptions.queryType(queryableAnnotation.queryType());
}
return queryableEncryptionOptions;
}
@ -256,13 +253,13 @@ public class MongoEncryptionConverter implements EncryptingConverter<Object, Obj @@ -256,13 +253,13 @@ public class MongoEncryptionConverter implements EncryptingConverter<Object, Obj
* @param encryptionOptions the options
* @return the encrypted range value for use in a range query
*/
private BsonValue encryptExpression(ConversionOperation conversionOperation, Object value,
private BsonValue encryptExpression(OperatorContext operatorContext, Object value,
EncryptionOptions encryptionOptions) {
BsonValue doc = BsonUtils.simpleToBsonValue(value);
String fieldName = conversionOperation.getPath();
String queryOperator = conversionOperation.getOperator();
String fieldName = operatorContext.getPath();
String queryOperator = operatorContext.getOperator();
if (!RANGE_OPERATORS.contains(queryOperator)) {
throw new AssertionError(String.format("Not a valid range query. Querying a range encrypted field but the "

4
spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/encryption/EncryptionContext.java

@ -16,7 +16,7 @@ @@ -16,7 +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.convert.MongoConversionContext.OperatorContext;
import org.springframework.data.mongodb.core.mapping.MongoPersistentProperty;
import org.springframework.data.util.TypeInformation;
import org.springframework.expression.EvaluationContext;
@ -136,7 +136,7 @@ public interface EncryptionContext { @@ -136,7 +136,7 @@ public interface EncryptionContext {
* @return can be {@literal null}.
*/
@Nullable
default ConversionOperation getConversionOperation() {
default OperatorContext getConversionOperation() {
return null;
}
}

2
spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/mapping/Queryable.java

@ -5,7 +5,7 @@ @@ -5,7 +5,7 @@
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
* 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,

3
spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/schema/DocumentJsonSchema.java

@ -15,9 +15,6 @@ @@ -15,9 +15,6 @@
*/
package org.springframework.data.mongodb.core.schema;
import java.util.Collections;
import java.util.Set;
import org.bson.Document;
import org.springframework.util.Assert;

110
spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/schema/IdentifiableJsonSchemaProperty.java

@ -80,55 +80,6 @@ public class IdentifiableJsonSchemaProperty<T extends JsonSchemaObject> implemen @@ -80,55 +80,6 @@ public class IdentifiableJsonSchemaProperty<T extends JsonSchemaObject> implemen
return jsonSchemaObjectDelegate.getTypes();
}
public static class QueryableJsonSchemaProperty implements JsonSchemaProperty {
private final JsonSchemaProperty targetProperty;
private final QueryCharacteristics characteristics;
public QueryableJsonSchemaProperty(JsonSchemaProperty target, QueryCharacteristics characteristics) {
this.targetProperty = target;
this.characteristics = characteristics;
}
@Override
public Document toDocument() {
Document doc = targetProperty.toDocument();
Document propertySpecification = doc.get(targetProperty.getIdentifier(), Document.class);
if (propertySpecification.containsKey("encrypt")) {
Document encrypt = propertySpecification.get("encrypt", Document.class);
List<Document> queries = characteristics.getCharacteristics().stream().map(QueryCharacteristic::toDocument)
.toList();
encrypt.append("queries", queries);
}
return doc;
}
@Override
public String getIdentifier() {
return targetProperty.getIdentifier();
}
@Override
public Set<Type> getTypes() {
return targetProperty.getTypes();
}
boolean isEncrypted() {
return targetProperty instanceof EncryptedJsonSchemaProperty;
}
public JsonSchemaProperty getTargetProperty() {
return targetProperty;
}
public QueryCharacteristics getCharacteristics() {
return characteristics;
}
}
/**
* Convenience {@link JsonSchemaProperty} implementation without a {@code type} property.
*
@ -1164,6 +1115,11 @@ public class IdentifiableJsonSchemaProperty<T extends JsonSchemaObject> implemen @@ -1164,6 +1115,11 @@ public class IdentifiableJsonSchemaProperty<T extends JsonSchemaObject> implemen
return new EncryptedJsonSchemaProperty(targetProperty, algorithm, keyId, null);
}
/**
* @param keyId must not be {@literal null}.
* @return new instance of {@link EncryptedJsonSchemaProperty}.
* @since 4.5
*/
public EncryptedJsonSchemaProperty keyId(Object keyId) {
return new EncryptedJsonSchemaProperty(targetProperty, algorithm, keyId, null);
}
@ -1247,4 +1203,60 @@ public class IdentifiableJsonSchemaProperty<T extends JsonSchemaObject> implemen @@ -1247,4 +1203,60 @@ public class IdentifiableJsonSchemaProperty<T extends JsonSchemaObject> implemen
return null;
}
}
/**
* {@link JsonSchemaProperty} implementation typically wrapping {@link EncryptedJsonSchemaProperty encrypted
* properties} to mark them as queryable.
*
* @author Christoph Strobl
* @since 4.5
*/
public static class QueryableJsonSchemaProperty implements JsonSchemaProperty {
private final JsonSchemaProperty targetProperty;
private final QueryCharacteristics characteristics;
public QueryableJsonSchemaProperty(JsonSchemaProperty target, QueryCharacteristics characteristics) {
this.targetProperty = target;
this.characteristics = characteristics;
}
@Override
public Document toDocument() {
Document doc = targetProperty.toDocument();
Document propertySpecification = doc.get(targetProperty.getIdentifier(), Document.class);
if (propertySpecification.containsKey("encrypt")) {
Document encrypt = propertySpecification.get("encrypt", Document.class);
List<Document> queries = characteristics.getCharacteristics().stream().map(QueryCharacteristic::toDocument)
.toList();
encrypt.append("queries", queries);
}
return doc;
}
@Override
public String getIdentifier() {
return targetProperty.getIdentifier();
}
@Override
public Set<Type> getTypes() {
return targetProperty.getTypes();
}
boolean isEncrypted() {
return targetProperty instanceof EncryptedJsonSchemaProperty;
}
public JsonSchemaProperty getTargetProperty() {
return targetProperty;
}
public QueryCharacteristics getCharacteristics() {
return characteristics;
}
}
}

2
spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/encryption/MongoQueryableEncryptionCollectionCreationTests.java

@ -1,5 +1,5 @@ @@ -1,5 +1,5 @@
/*
* Copyright 2025. the original author or authors.
* 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.

29
spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/encryption/RangeEncryptionTests.java

@ -26,15 +26,12 @@ import java.util.Map; @@ -26,15 +26,12 @@ import java.util.Map;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Supplier;
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.bson.types.Binary;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
@ -64,6 +61,7 @@ import org.springframework.data.mongodb.test.util.MongoClientExtension; @@ -64,6 +61,7 @@ import org.springframework.data.mongodb.test.util.MongoClientExtension;
import org.springframework.data.util.Lazy;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import org.springframework.util.StringUtils;
import com.mongodb.AutoEncryptionSettings;
import com.mongodb.ClientEncryptionSettings;
@ -84,7 +82,6 @@ import com.mongodb.client.model.vault.RangeOptions; @@ -84,7 +82,6 @@ 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
@ -146,7 +143,7 @@ class RangeEncryptionTests { @@ -146,7 +143,7 @@ class RangeEncryptionTests {
assertThat(result).containsEntry("encryptedInt", 101);
}
@Test
@Test // GH-4185
void canLesserThanEqualMatchRangeEncryptedField() {
Person source = createPerson();
@ -156,7 +153,7 @@ class RangeEncryptionTests { @@ -156,7 +153,7 @@ class RangeEncryptionTests {
assertThat(loaded).isEqualTo(source);
}
@Test
@Test // GH-4185
void canQueryMixOfEqualityEncryptedAndUnencrypted() {
Person source = template.insert(createPerson());
@ -166,7 +163,7 @@ class RangeEncryptionTests { @@ -166,7 +163,7 @@ class RangeEncryptionTests {
assertThat(loaded).isEqualTo(source);
}
@Test
@Test // GH-4185
void canQueryMixOfRangeEncryptedAndUnencrypted() {
Person source = template.insert(createPerson());
@ -177,7 +174,7 @@ class RangeEncryptionTests { @@ -177,7 +174,7 @@ class RangeEncryptionTests {
assertThat(loaded).isEqualTo(source);
}
@Test
@Test // GH-4185
void canQueryEqualityEncryptedField() {
Person source = createPerson();
@ -187,7 +184,7 @@ class RangeEncryptionTests { @@ -187,7 +184,7 @@ class RangeEncryptionTests {
assertThat(loaded).isEqualTo(source);
}
@Test
@Test // GH-4185
void canExcludeSafeContentFromResult() {
Person source = createPerson();
@ -200,7 +197,7 @@ class RangeEncryptionTests { @@ -200,7 +197,7 @@ class RangeEncryptionTests {
assertThat(loaded).isEqualTo(source);
}
@Test
@Test // GH-4185
void canRangeMatchRangeEncryptedField() {
Person source = createPerson();
@ -211,7 +208,7 @@ class RangeEncryptionTests { @@ -211,7 +208,7 @@ class RangeEncryptionTests {
assertThat(loaded).isEqualTo(source);
}
@Test
@Test // GH-4185
void canReplaceEntityWithRangeEncryptedField() {
Person source = createPerson();
@ -225,7 +222,7 @@ class RangeEncryptionTests { @@ -225,7 +222,7 @@ class RangeEncryptionTests {
assertThat(loaded).isEqualTo(source);
}
@Test
@Test // GH-4185
void canUpdateRangeEncryptedField() {
Person source = createPerson();
@ -239,7 +236,7 @@ class RangeEncryptionTests { @@ -239,7 +236,7 @@ class RangeEncryptionTests {
assertThat(loaded.encryptedLong).isEqualTo(5000L);
}
@Test
@Test // GH-4185
void errorsWhenUsingNonRangeOperatorEqOnRangeEncryptedField() {
Person source = createPerson();
@ -250,10 +247,9 @@ class RangeEncryptionTests { @@ -250,10 +247,9 @@ class RangeEncryptionTests {
.isInstanceOf(AssertionError.class)
.hasMessageStartingWith("Not a valid range query. Querying a range encrypted field but "
+ "the query operator '$eq' for field path 'encryptedInt' is not a range query.");
}
@Test
@Test // GH-4185
void errorsWhenUsingNonRangeOperatorInOnRangeEncryptedField() {
Person source = createPerson();
@ -264,10 +260,10 @@ class RangeEncryptionTests { @@ -264,10 +260,10 @@ class RangeEncryptionTests {
.isInstanceOf(AssertionError.class)
.hasMessageStartingWith("Not a valid range query. Querying a range encrypted field but "
+ "the query operator '$in' for field path 'encryptedLong' is not a range query.");
}
private Person createPerson() {
Person source = new Person();
source.id = "id-1";
source.unencryptedValue = "y2k";
@ -460,7 +456,6 @@ class RangeEncryptionTests { @@ -460,7 +456,6 @@ class RangeEncryptionTests {
String name;
@ValueConverter(MongoEncryptionConverter.class)
// @Encrypted(algorithm = "Indexed", queries = {@Queryable(queryType = "equality", contentionFactor = 0)})
@Encrypted(algorithm = "Indexed") //
@Queryable(queryType = "equality", contentionFactor = 0) //
Integer age;

Loading…
Cancel
Save