Browse Source

move things and think about it a little longer

issue/4185-light
Christoph Strobl 9 months ago
parent
commit
5574e1069b
No known key found for this signature in database
GPG Key ID: E6054036D0C37A4B
  1. 42
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MappingMongoJsonSchemaCreator.java
  2. 34
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/encryption/MongoEncryptionConverter.java
  3. 19
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/encryption/EncryptionOptions.java
  4. 2
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/encryption/MongoClientEncryption.java
  5. 12
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/mapping/Encrypted.java
  6. 5
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/mapping/RangeEncrypted.java
  7. 18
      spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/encryption/RangeEncryptionTests.java

42
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()); enc = enc.keys(property.getEncryptionKeyIds());
} }
RangeEncrypted rangeEncrypted = property.findAnnotation(RangeEncrypted.class); if (!StringUtils.hasText(encrypted.queryType())) {
if (rangeEncrypted != null) { return enc;
}
QueryCharacteristic characteristic = new QueryCharacteristic() {
@Override QueryCharacteristic characteristic = new QueryCharacteristic() {
public String queryType() {
return "range";
}
@Override @Override
public Document toDocument() { public String queryType() {
return encrypted.queryType();
}
Document options = QueryCharacteristic.super.toDocument(); @Override
options.put("contention", rangeEncrypted.contentionFactor()); public Document toDocument() {
if (!rangeEncrypted.rangeOptions().isEmpty()) { Document options = QueryCharacteristic.super.toDocument();
options.putAll(Document.parse(rangeEncrypted.rangeOptions()));
}
return options; RangeEncrypted rangeEncrypted = property.findAnnotation(RangeEncrypted.class);
if (rangeEncrypted != null) {
options.put("contention", rangeEncrypted.contentionFactor());
} }
}; if (!encrypted.queryAttributes().isEmpty()) {
return new QueryableJsonSchemaProperty(enc, QueryCharacteristics.of(List.of(characteristic))); options.putAll(Document.parse(encrypted.queryAttributes()));
} }
return enc;
return options;
}
};
return new QueryableJsonSchemaProperty(enc, QueryCharacteristics.of(List.of(characteristic)));
} }
private JsonSchemaProperty createObjectSchemaPropertyForEntity(List<MongoPersistentProperty> path, private JsonSchemaProperty createObjectSchemaPropertyForEntity(List<MongoPersistentProperty> path,

34
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.data.mongodb.util.BsonUtils;
import org.springframework.lang.Nullable; import org.springframework.lang.Nullable;
import org.springframework.util.ObjectUtils; import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;
/** /**
* Default implementation of {@link EncryptingConverter}. Properties used with this converter must be annotated with * Default implementation of {@link EncryptingConverter}. Properties used with this converter must be annotated with
@ -168,7 +169,7 @@ public class MongoEncryptionConverter implements EncryptingConverter<Object, Obj
if (annotation == null) { if (annotation == null) {
throw new IllegalStateException(String.format("Property %s.%s is not annotated with @Encrypted", throw new IllegalStateException(String.format("Property %s.%s is not annotated with @Encrypted",
getProperty(context).getOwner().getName(), getProperty(context).getName())); persistentProperty.getOwner().getName(), persistentProperty.getName()));
} }
String algorithm = annotation.algorithm(); String algorithm = annotation.algorithm();
@ -178,42 +179,45 @@ public class MongoEncryptionConverter implements EncryptingConverter<Object, Obj
EncryptionOptions encryptionOptions = new EncryptionOptions(algorithm, key, EncryptionOptions encryptionOptions = new EncryptionOptions(algorithm, key,
getEQOptions(persistentProperty, fieldNameAndQueryOperator)); getEQOptions(persistentProperty, fieldNameAndQueryOperator));
if (fieldNameAndQueryOperator != null if (fieldNameAndQueryOperator != null && encryptionOptions.queryableEncryptionOptions() != null) {
&& !encryptionOptions.queryableEncryptionOptions().equals(QueryableEncryptionOptions.none())) {
return encryptExpression(fieldNameAndQueryOperator, value, encryptionOptions); return encryptExpression(fieldNameAndQueryOperator, value, encryptionOptions);
} else { } else {
return encryptValue(value, context, persistentProperty, encryptionOptions); return encryptValue(value, context, persistentProperty, encryptionOptions);
} }
} }
private static QueryableEncryptionOptions getEQOptions(MongoPersistentProperty persistentProperty, private static @Nullable QueryableEncryptionOptions getEQOptions(MongoPersistentProperty persistentProperty,
String fieldNameAndQueryOperator) { String fieldNameAndQueryOperator) {
QueryableEncryptionOptions queryableEncryptionOptions = QueryableEncryptionOptions.none(); Encrypted encryptedAnnotation = persistentProperty.findAnnotation(Encrypted.class);
RangeEncrypted rangeEncryptedAnnotation = persistentProperty.findAnnotation(RangeEncrypted.class); if (encryptedAnnotation != null && !StringUtils.hasText(encryptedAnnotation.queryType())) {
if (rangeEncryptedAnnotation == null) { return null;
return queryableEncryptionOptions;
} }
String rangeOptions = rangeEncryptedAnnotation.rangeOptions(); QueryableEncryptionOptions queryableEncryptionOptions = QueryableEncryptionOptions.none();
if (!rangeOptions.isEmpty()) {
queryableEncryptionOptions = queryableEncryptionOptions.attributes(Document.parse(rangeOptions)); String queryAttributes = encryptedAnnotation.queryAttributes();
if (!queryAttributes.isEmpty()) {
queryableEncryptionOptions = queryableEncryptionOptions.attributes(Document.parse(queryAttributes));
} }
if (rangeEncryptedAnnotation.contentionFactor() >= 0) { RangeEncrypted rangeEncryptedAnnotation = persistentProperty.findAnnotation(RangeEncrypted.class);
if (rangeEncryptedAnnotation != null && rangeEncryptedAnnotation.contentionFactor() >= 0) {
queryableEncryptionOptions = queryableEncryptionOptions queryableEncryptionOptions = queryableEncryptionOptions
.contentionFactor(rangeEncryptedAnnotation.contentionFactor()); .contentionFactor(rangeEncryptedAnnotation.contentionFactor());
} }
boolean isPartOfARangeQuery = fieldNameAndQueryOperator != null; boolean isPartOfARangeQuery = fieldNameAndQueryOperator != null;
if (isPartOfARangeQuery) { if (isPartOfARangeQuery || !encryptedAnnotation.queryType().isEmpty()) {
queryableEncryptionOptions = queryableEncryptionOptions.queryType("range"); queryableEncryptionOptions = queryableEncryptionOptions.queryType(encryptedAnnotation.queryType()); // should the type move to an extra annotation?
queryableEncryptionOptions = queryableEncryptionOptions.contentionFactor(1l);
} }
return queryableEncryptionOptions; return queryableEncryptionOptions;
} }
private BsonBinary encryptValue(Object value, EncryptionContext context, MongoPersistentProperty persistentProperty, private BsonBinary encryptValue(Object value, EncryptionContext context, MongoPersistentProperty persistentProperty,
EncryptionOptions encryptionOptions) { EncryptionOptions encryptionOptions) {
if (!persistentProperty.isEntity()) { if (!persistentProperty.isEntity()) {
if (persistentProperty.isCollectionLike()) { if (persistentProperty.isCollectionLike()) {
@ -227,6 +231,7 @@ public class MongoEncryptionConverter implements EncryptingConverter<Object, Obj
} }
return encryption.encrypt(BsonUtils.simpleToBsonValue(value), encryptionOptions); return encryption.encrypt(BsonUtils.simpleToBsonValue(value), encryptionOptions);
} }
if (persistentProperty.isCollectionLike()) { if (persistentProperty.isCollectionLike()) {
return encryption.encrypt(collectionLikeToBsonValue(value, persistentProperty, context), encryptionOptions); return encryption.encrypt(collectionLikeToBsonValue(value, persistentProperty, context), encryptionOptions);
} }
@ -251,6 +256,7 @@ public class MongoEncryptionConverter implements EncryptingConverter<Object, Obj
*/ */
private BsonValue encryptExpression(String fieldNameAndQueryOperator, Object value, private BsonValue encryptExpression(String fieldNameAndQueryOperator, Object value,
EncryptionOptions encryptionOptions) { EncryptionOptions encryptionOptions) {
BsonValue doc = BsonUtils.simpleToBsonValue(value); BsonValue doc = BsonUtils.simpleToBsonValue(value);
String fieldName = fieldNameAndQueryOperator; String fieldName = fieldNameAndQueryOperator;

19
spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/encryption/EncryptionOptions.java

@ -18,14 +18,10 @@ package org.springframework.data.mongodb.core.encryption;
import java.util.Map; import java.util.Map;
import java.util.Objects; import java.util.Objects;
import org.springframework.data.mongodb.util.BsonUtils;
import org.springframework.lang.Nullable; import org.springframework.lang.Nullable;
import org.springframework.util.Assert; import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
import org.springframework.util.ObjectUtils; import org.springframework.util.ObjectUtils;
import com.mongodb.client.model.vault.RangeOptions;
/** /**
* Options used to provide additional information when {@link Encryption encrypting} values. like the * Options used to provide additional information when {@link Encryption encrypting} values. like the
* {@link #algorithm()} to be used. * {@link #algorithm()} to be used.
@ -38,13 +34,14 @@ public class EncryptionOptions {
private final String algorithm; private final String algorithm;
private final EncryptionKey key; private final EncryptionKey key;
private final QueryableEncryptionOptions queryableEncryptionOptions; private final @Nullable QueryableEncryptionOptions queryableEncryptionOptions;
public EncryptionOptions(String algorithm, EncryptionKey key) { public EncryptionOptions(String algorithm, EncryptionKey key) {
this(algorithm, key, QueryableEncryptionOptions.NONE); this(algorithm, key, null);
} }
public EncryptionOptions(String algorithm, EncryptionKey key, QueryableEncryptionOptions queryableEncryptionOptions) { public EncryptionOptions(String algorithm, EncryptionKey key,
@Nullable QueryableEncryptionOptions queryableEncryptionOptions) {
Assert.hasText(algorithm, "Algorithm must not be empty"); Assert.hasText(algorithm, "Algorithm must not be empty");
Assert.notNull(key, "EncryptionKey must not be empty"); Assert.notNull(key, "EncryptionKey must not be empty");
@ -63,7 +60,11 @@ public class EncryptionOptions {
return algorithm; return algorithm;
} }
public QueryableEncryptionOptions queryableEncryptionOptions() { /**
* @return {@literal null} if not set.
* @since 4.5
*/
public @Nullable QueryableEncryptionOptions queryableEncryptionOptions() {
return queryableEncryptionOptions; return queryableEncryptionOptions;
} }
@ -113,7 +114,7 @@ public class EncryptionOptions {
*/ */
public static class QueryableEncryptionOptions { public static class QueryableEncryptionOptions {
public static final QueryableEncryptionOptions NONE = new QueryableEncryptionOptions(null, null, Map.of()); private static final QueryableEncryptionOptions NONE = new QueryableEncryptionOptions(null, null, Map.of());
private final @Nullable String queryType; private final @Nullable String queryType;
private final @Nullable Long contentionFactor; private final @Nullable Long contentionFactor;

2
spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/encryption/MongoClientEncryption.java

@ -87,7 +87,7 @@ public class MongoClientEncryption implements Encryption<BsonValue, BsonBinary>
encryptOptions = encryptOptions.keyId((BsonBinary) options.key().value()); encryptOptions = encryptOptions.keyId((BsonBinary) options.key().value());
} }
if (options.queryableEncryptionOptions().isEmpty()) { if (options.queryableEncryptionOptions() == null) {
return encryptOptions; return encryptOptions;
} }

12
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 * @see org.springframework.data.mongodb.core.EncryptionAlgorithms
*/ */
String algorithm() default ""; 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 "";
} }

5
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.RetentionPolicy;
import java.lang.annotation.Target; import java.lang.annotation.Target;
import org.springframework.core.annotation.AliasFor;
/** /**
* @author Christoph Strobl * @author Christoph Strobl
* @author Ross Lawley * @author Ross Lawley
@ -27,7 +29,7 @@ import java.lang.annotation.Target;
*/ */
@Retention(RetentionPolicy.RUNTIME) @Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD) @Target(ElementType.FIELD)
@Encrypted(algorithm = "Range") @Encrypted(algorithm = "Range", queryType = "range")
public @interface RangeEncrypted { public @interface RangeEncrypted {
/** /**
@ -47,5 +49,6 @@ public @interface RangeEncrypted {
* *
* @return the json representation of range options * @return the json representation of range options
*/ */
@AliasFor(annotation = Encrypted.class, value = "queryAttributes")
String rangeOptions() default ""; String rangeOptions() default "";
} }

18
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.BsonBinary;
import org.bson.BsonDocument; import org.bson.BsonDocument;
import org.bson.BsonInt32; import org.bson.BsonInt32;
import org.bson.BsonString;
import org.bson.BsonValue; import org.bson.BsonValue;
import org.bson.Document; import org.bson.Document;
import org.junit.jupiter.api.AfterEach; 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.MongoTemplate;
import org.springframework.data.mongodb.core.convert.MongoCustomConversions.MongoConverterConfigurationAdapter; import org.springframework.data.mongodb.core.convert.MongoCustomConversions.MongoConverterConfigurationAdapter;
import org.springframework.data.mongodb.core.convert.encryption.MongoEncryptionConverter; 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.MongoMappingContext;
import org.springframework.data.mongodb.core.mapping.RangeEncrypted; import org.springframework.data.mongodb.core.mapping.RangeEncrypted;
import org.springframework.data.mongodb.core.query.Query; 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))) .rangeOptions(new RangeOptions().min(new BsonInt32(0)).max(new BsonInt32(200)))
.keyId(keyHolder.getEncryptionKey("encryptedInt")).queryType("range"); .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"); 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("encryptedInt", clientEncryption.getClientEncryption().encrypt(new BsonInt32(101), encryptOptions));
source.put("_class", Person.class.getName()); source.put("_class", Person.class.getName());
@ -129,6 +135,7 @@ class RangeEncryptionTests {
@Test @Test
void canLesserThanEqualMatchRangeEncryptedField() { void canLesserThanEqualMatchRangeEncryptedField() {
Person source = createPerson(); Person source = createPerson();
template.insert(source); template.insert(source);
@ -138,6 +145,7 @@ class RangeEncryptionTests {
@Test @Test
void canRangeMatchRangeEncryptedField() { void canRangeMatchRangeEncryptedField() {
Person source = createPerson(); Person source = createPerson();
template.insert(source); template.insert(source);
@ -149,6 +157,7 @@ class RangeEncryptionTests {
@Test @Test
void canUpdateRangeEncryptedField() { void canUpdateRangeEncryptedField() {
Person source = createPerson(); Person source = createPerson();
template.insert(source); template.insert(source);
@ -162,6 +171,7 @@ class RangeEncryptionTests {
@Test @Test
void errorsWhenUsingNonRangeOperatorEqOnRangeEncryptedField() { void errorsWhenUsingNonRangeOperatorEqOnRangeEncryptedField() {
Person source = createPerson(); Person source = createPerson();
template.insert(source); template.insert(source);
@ -175,6 +185,7 @@ class RangeEncryptionTests {
@Test @Test
void errorsWhenUsingNonRangeOperatorInOnRangeEncryptedField() { void errorsWhenUsingNonRangeOperatorInOnRangeEncryptedField() {
Person source = createPerson(); Person source = createPerson();
template.insert(source); template.insert(source);
@ -347,6 +358,8 @@ class RangeEncryptionTests {
static class Person { static class Person {
String id; String id;
@ValueConverter(MongoEncryptionConverter.class)
@Encrypted(algorithm = "Range", queryType = "equality")
String name; String name;
@ValueConverter(MongoEncryptionConverter.class) @ValueConverter(MongoEncryptionConverter.class)
@ -359,6 +372,9 @@ class RangeEncryptionTests {
rangeOptions = "{\"min\": {\"$numberLong\": \"1000\"}, \"max\": {\"$numberLong\": \"9999\"}, \"trimFactor\": 1, \"sparsity\": 1}") // rangeOptions = "{\"min\": {\"$numberLong\": \"1000\"}, \"max\": {\"$numberLong\": \"9999\"}, \"trimFactor\": 1, \"sparsity\": 1}") //
Long encryptedLong; Long encryptedLong;
public String getId() { public String getId() {
return this.id; return this.id;
} }

Loading…
Cancel
Save