Browse Source

hacking

issue/4185-light
Christoph Strobl 10 months ago
parent
commit
96bb94b255
No known key found for this signature in database
GPG Key ID: E6054036D0C37A4B
  1. 25
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/CollectionOptions.java
  2. 3
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/schema/DocumentJsonSchema.java
  3. 37
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/schema/IdentifiableJsonSchemaProperty.java
  4. 4
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/schema/JsonSchemaProperty.java
  5. 2
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/schema/MergedJsonSchema.java
  6. 147
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/schema/QueryCharacteristics.java
  7. 51
      spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/encryption/RangeEncryptionTests.java
  8. 38
      spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/schema/MongoJsonSchemaUnitTests.java

25
spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/CollectionOptions.java

@ -16,13 +16,18 @@ @@ -16,13 +16,18 @@
package org.springframework.data.mongodb.core;
import java.time.Duration;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.function.Function;
import org.bson.conversions.Bson;
import org.springframework.data.mongodb.core.mapping.Field;
import org.springframework.data.mongodb.core.query.Collation;
import org.springframework.data.mongodb.core.schema.IdentifiableJsonSchemaProperty.QueryableJsonSchemaProperty;
import org.springframework.data.mongodb.core.schema.JsonSchemaProperty;
import org.springframework.data.mongodb.core.schema.MongoJsonSchema;
import org.springframework.data.mongodb.core.schema.QueryCharacteristics;
import org.springframework.data.mongodb.core.timeseries.Granularity;
import org.springframework.data.mongodb.core.timeseries.GranularityDefinition;
import org.springframework.data.mongodb.core.validation.Validator;
@ -57,7 +62,7 @@ public class CollectionOptions { @@ -57,7 +62,7 @@ public class CollectionOptions {
private CollectionOptions(@Nullable Long size, @Nullable Long maxDocuments, @Nullable Boolean capped,
@Nullable Collation collation, ValidationOptions validationOptions, @Nullable TimeSeriesOptions timeSeriesOptions,
@Nullable CollectionChangeStreamOptions changeStreamOptions, @Nullable Bson encryptedFields) {
@Nullable CollectionChangeStreamOptions changeStreamOptions, @Nullable Bson encryptedFields) {
this.maxDocuments = maxDocuments;
this.size = size;
@ -423,9 +428,9 @@ public class CollectionOptions { @@ -423,9 +428,9 @@ public class CollectionOptions {
return "CollectionOptions{" + "maxDocuments=" + maxDocuments + ", size=" + size + ", capped=" + capped
+ ", collation=" + collation + ", validationOptions=" + validationOptions + ", timeSeriesOptions="
+ timeSeriesOptions + ", changeStreamOptions=" + changeStreamOptions + ", encryptedFields=" + encryptedFields
+ ", disableValidation=" + disableValidation() + ", strictValidation=" + strictValidation() + ", moderateValidation="
+ moderateValidation() + ", warnOnValidationError=" + warnOnValidationError() + ", failOnValidationError="
+ failOnValidationError() + '}';
+ ", disableValidation=" + disableValidation() + ", strictValidation=" + strictValidation()
+ ", moderateValidation=" + moderateValidation() + ", warnOnValidationError=" + warnOnValidationError()
+ ", failOnValidationError=" + failOnValidationError() + '}';
}
@Override
@ -606,6 +611,18 @@ public class CollectionOptions { @@ -606,6 +611,18 @@ public class CollectionOptions {
}
}
public static class EncryptedCollectionOptions {
private List<QueryableJsonSchemaProperty> queryableProperties = new ArrayList<>();
public EncryptedCollectionOptions queryable(JsonSchemaProperty schemaObject, QueryCharacteristics characteristics) {
queryableProperties.add(JsonSchemaProperty.queryable(schemaObject, characteristics));
return this;
}
}
/**
* Encapsulation of options applied to define collections change stream behaviour.
*

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

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

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

@ -21,10 +21,11 @@ import java.util.LinkedHashSet; @@ -21,10 +21,11 @@ import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import java.util.UUID;
import java.util.stream.Collectors;
import org.bson.Document;
import org.springframework.data.domain.Range;
import org.springframework.data.mongodb.core.schema.QueryCharacteristics.QueryCharacteristic;
import org.springframework.data.mongodb.core.schema.TypedJsonSchemaObject.ArrayJsonSchemaObject;
import org.springframework.data.mongodb.core.schema.TypedJsonSchemaObject.BooleanJsonSchemaObject;
import org.springframework.data.mongodb.core.schema.TypedJsonSchemaObject.DateJsonSchemaObject;
@ -80,6 +81,40 @@ public class IdentifiableJsonSchemaProperty<T extends JsonSchemaObject> implemen @@ -80,6 +81,40 @@ 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);
List<Document> queries = characteristics.getCharacteristics().stream().map(QueryCharacteristic::toDocument).toList();
propertySpecification.append("queries", queries);
return propertySpecification;
}
@Override
public String getIdentifier() {
return targetProperty.getIdentifier();
}
@Override
public Set<Type> getTypes() {
return targetProperty.getTypes();
}
}
/**
* Convenience {@link JsonSchemaProperty} implementation without a {@code type} property.
*

4
spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/schema/JsonSchemaProperty.java

@ -69,6 +69,10 @@ public interface JsonSchemaProperty extends JsonSchemaObject { @@ -69,6 +69,10 @@ public interface JsonSchemaProperty extends JsonSchemaObject {
return EncryptedJsonSchemaProperty.encrypted(property);
}
static QueryableJsonSchemaProperty queryable(JsonSchemaProperty property, QueryCharacteristics queries) {
return new QueryableJsonSchemaProperty(property, queries);
}
/**
* Creates a new {@link StringJsonSchemaProperty} with given {@literal identifier} of {@code type : 'string'}.
*

2
spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/schema/MergedJsonSchema.java

@ -19,7 +19,9 @@ import java.util.ArrayList; @@ -19,7 +19,9 @@ import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.BiFunction;
import java.util.stream.Collectors;
import org.bson.Document;

147
spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/schema/QueryCharacteristics.java

@ -0,0 +1,147 @@ @@ -0,0 +1,147 @@
/*
* 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
*
* http://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.
*/
/*
* 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
*
* http://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.schema;
import java.util.ArrayList;
import java.util.List;
import org.bson.BsonNull;
import org.bson.Document;
import org.springframework.data.domain.Range;
import org.springframework.data.domain.Range.Bound;
import org.springframework.lang.Nullable;
/**
* @author Christoph Strobl
*/
public class QueryCharacteristics {
private static final QueryCharacteristics NONE = new QueryCharacteristics(List.of());
private final List<QueryCharacteristic> characteristics;
public QueryCharacteristics(List<QueryCharacteristic> characteristics) {
this.characteristics = characteristics;
}
public static QueryCharacteristics none() {
return NONE;
}
QueryCharacteristics(QueryCharacteristic... characteristics) {
this.characteristics = new ArrayList<>(characteristics.length);
for (QueryCharacteristic characteristic : characteristics) {
addQuery(characteristic);
}
}
public void addQuery(QueryCharacteristic characteristic) {
this.characteristics.add(characteristic);
}
List<QueryCharacteristic> getCharacteristics() {
return characteristics;
}
public static <T> RangeQuery<T> range() {
return new RangeQuery<>();
}
public interface QueryCharacteristic {
String type();
default Document toDocument() {
return new Document("queryType", type());
}
}
public static class RangeQuery<T> implements QueryCharacteristic {
private final @Nullable Range<T> valueRange;
private final @Nullable Integer trimFactor;
private final @Nullable Long sparsity;
private final @Nullable Long contention;
private RangeQuery() {
this(Range.unbounded(), null, null, null);
}
public RangeQuery(Range<T> valueRange, Integer trimFactor, Long sparsity, Long contention) {
this.valueRange = valueRange;
this.trimFactor = trimFactor;
this.sparsity = sparsity;
this.contention = contention;
}
@Override
public String type() {
return "range";
}
public RangeQuery<T> min(T lower) {
Range<T> range = Range.of(Bound.inclusive(lower),
valueRange != null ? valueRange.getUpperBound() : Bound.unbounded());
return new RangeQuery<>(range, trimFactor, sparsity, contention);
}
public RangeQuery<T> max(T upper) {
Range<T> range = Range.of(valueRange != null ? valueRange.getLowerBound() : Bound.unbounded(),
Bound.inclusive(upper));
return new RangeQuery<>(range, trimFactor, sparsity, contention);
}
public RangeQuery<T> trimFactor(int trimFactor) {
return new RangeQuery<>(valueRange, trimFactor, sparsity, contention);
}
public RangeQuery<T> sparsity(long sparsity) {
return new RangeQuery<>(valueRange, trimFactor, sparsity, contention);
}
public RangeQuery<T> contention(long contention) {
return new RangeQuery<>(valueRange, trimFactor, sparsity, contention);
}
@Override
public Document toDocument() {
return QueryCharacteristic.super.toDocument().append("contention", contention).append("trimFactor", trimFactor)
.append("sparsity", sparsity).append("min", valueRange.getLowerBound().getValue().orElse((T)BsonNull.VALUE))
.append("max", valueRange.getUpperBound().getValue().orElse((T)BsonNull.VALUE));
}
}
}

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

@ -20,7 +20,9 @@ import static org.assertj.core.api.Assertions.*; @@ -20,7 +20,9 @@ import static org.assertj.core.api.Assertions.*;
import static org.springframework.data.mongodb.core.EncryptionAlgorithms.*;
import static org.springframework.data.mongodb.core.query.Criteria.*;
import java.nio.charset.StandardCharsets;
import java.security.SecureRandom;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicReference;
@ -41,6 +43,7 @@ import com.mongodb.client.model.CreateEncryptedCollectionParams; @@ -41,6 +43,7 @@ import com.mongodb.client.model.CreateEncryptedCollectionParams;
import com.mongodb.client.model.Filters;
import com.mongodb.client.model.IndexOptions;
import com.mongodb.client.model.Indexes;
import com.mongodb.client.model.vault.DataKeyOptions;
import com.mongodb.client.vault.ClientEncryption;
import com.mongodb.client.vault.ClientEncryptions;
@ -200,19 +203,38 @@ class RangeEncryptionTests { @@ -200,19 +203,38 @@ class RangeEncryptionTests {
database.getCollection("test").drop();
ClientEncryption clientEncryption = mongoClientEncryption.getClientEncryption();
// BsonDocument encryptedFields = new BsonDocument().append("fields",
// new BsonArray(asList(
// new BsonDocument("keyId", BsonNull.VALUE).append("path", new BsonString("encryptedInt"))
// .append("bsonType", new BsonString("int"))
// .append("queries",
// new BsonDocument("queryType", new BsonString("range")).append("contention", new BsonInt64(0L))
// .append("trimFactor", new BsonInt32(1)).append("sparsity", new BsonInt64(1))
// .append("min", new BsonInt32(0)).append("max", new BsonInt32(200))),
// new BsonDocument("keyId", BsonNull.VALUE).append("path", new BsonString("encryptedLong"))
// .append("bsonType", new BsonString("long")).append("queries",
// new BsonDocument("queryType", new BsonString("range")).append("contention", new BsonInt64(0L))
// .append("trimFactor", new BsonInt32(1)).append("sparsity", new BsonInt64(1))
// .append("min", new BsonInt64(1000)).append("max", new BsonInt64(9999))))));
BsonBinary dataKey1 = clientEncryption.createDataKey(LOCAL_KMS_PROVIDER, new DataKeyOptions().keyAltNames(List.of("dek-1")));
BsonBinary dataKey2 = clientEncryption.createDataKey(LOCAL_KMS_PROVIDER, new DataKeyOptions().keyAltNames(List.of("dek-2")));
BsonDocument encryptedFields = new BsonDocument().append("fields",
new BsonArray(asList(
new BsonDocument("keyId", BsonNull.VALUE).append("path", new BsonString("encryptedInt"))
.append("bsonType", new BsonString("int"))
.append("queries",
new BsonDocument("queryType", new BsonString("range")).append("contention", new BsonInt64(0L))
.append("trimFactor", new BsonInt32(1)).append("sparsity", new BsonInt64(1))
.append("min", new BsonInt32(0)).append("max", new BsonInt32(200))),
new BsonDocument("keyId", BsonNull.VALUE).append("path", new BsonString("encryptedLong"))
.append("bsonType", new BsonString("long")).append("queries",
new BsonDocument("queryType", new BsonString("range")).append("contention", new BsonInt64(0L))
.append("trimFactor", new BsonInt32(1)).append("sparsity", new BsonInt64(1))
.append("min", new BsonInt64(1000)).append("max", new BsonInt64(9999))))));
new BsonArray(asList(
new BsonDocument("keyId", dataKey1).append("path", new BsonString("encryptedInt"))
.append("bsonType", new BsonString("int"))
.append("queries",
new BsonDocument("queryType", new BsonString("range")).append("contention", new BsonInt64(0L))
.append("trimFactor", new BsonInt32(1)).append("sparsity", new BsonInt64(1))
.append("min", new BsonInt32(0)).append("max", new BsonInt32(200))),
new BsonDocument("keyId", dataKey2).append("path", new BsonString("encryptedLong"))
.append("bsonType", new BsonString("long")).append("queries",
new BsonDocument("queryType", new BsonString("range")).append("contention", new BsonInt64(0L))
.append("trimFactor", new BsonInt32(1)).append("sparsity", new BsonInt64(1))
.append("min", new BsonInt64(1000)).append("max", new BsonInt64(9999))))));
BsonDocument local = clientEncryption.createEncryptedCollection(database, "test",
new CreateCollectionOptions().encryptedFields(encryptedFields),
@ -239,6 +261,7 @@ class RangeEncryptionTests { @@ -239,6 +261,7 @@ class RangeEncryptionTests {
builder.autoEncryptionSettings(AutoEncryptionSettings.builder() //
.kmsProviders(clientEncryptionSettings.getKmsProviders()) //
.keyVaultNamespace(clientEncryptionSettings.getKeyVaultNamespace()) //
.bypassAutoEncryption(true)
.bypassQueryAnalysis(true).build());
}
}
@ -298,9 +321,9 @@ class RangeEncryptionTests { @@ -298,9 +321,9 @@ class RangeEncryptionTests {
String name;
@ExplicitEncrypted(algorithm = RANGE, contentionFactor = 0L,
rangeOptions = "{\"min\": 0, \"max\": 200, \"trimFactor\": 1, \"sparsity\": 1}") Integer encryptedInt;
rangeOptions = "{\"min\": 0, \"max\": 200, \"trimFactor\": 1, \"sparsity\": 1}", keyAltName = "dek-1") Integer encryptedInt;
@ExplicitEncrypted(algorithm = RANGE, contentionFactor = 0L,
rangeOptions = "{\"min\": {\"$numberLong\": \"1000\"}, \"max\": {\"$numberLong\": \"9999\"}, \"trimFactor\": 1, \"sparsity\": 1}") Long encryptedLong;
rangeOptions = "{\"min\": {\"$numberLong\": \"1000\"}, \"max\": {\"$numberLong\": \"9999\"}, \"trimFactor\": 1, \"sparsity\": 1}", keyAltName = "dek-2") Long encryptedLong;
public String getId() {
return this.id;

38
spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/schema/MongoJsonSchemaUnitTests.java

@ -15,6 +15,7 @@ @@ -15,6 +15,7 @@
*/
package org.springframework.data.mongodb.core.schema;
import static java.util.Arrays.asList;
import static org.springframework.data.mongodb.core.schema.JsonSchemaProperty.*;
import static org.springframework.data.mongodb.test.util.Assertions.*;
@ -22,10 +23,18 @@ import java.util.Arrays; @@ -22,10 +23,18 @@ import java.util.Arrays;
import java.util.Collections;
import java.util.UUID;
import org.bson.BsonArray;
import org.bson.BsonDocument;
import org.bson.BsonInt32;
import org.bson.BsonInt64;
import org.bson.BsonNull;
import org.bson.BsonString;
import org.bson.Document;
import org.bson.json.JsonWriterSettings;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.data.mongodb.core.schema.IdentifiableJsonSchemaProperty.QueryableJsonSchemaProperty;
/**
* Unit tests for {@link MongoJsonSchema}.
@ -105,6 +114,35 @@ class MongoJsonSchemaUnitTests { @@ -105,6 +114,35 @@ class MongoJsonSchemaUnitTests {
.append("algorithm", "AEAD_AES_256_CBC_HMAC_SHA_512-Deterministic").append("bsonType", "string"))))));
}
@Test // GH-4185
void rendersQueryablePropertyCorrectly() {
QueryCharacteristics characteristics = new QueryCharacteristics();
characteristics.addQuery(QueryCharacteristics.range().contention(0).trimFactor(1).sparsity(1).min(0).max(200));
QueryableJsonSchemaProperty property = queryable(encrypted(number("mypath")), characteristics);
//Document document = MongoJsonSchema.builder().property(property).build().schemaDocument();
Document document = property.toDocument();
System.out.println(document.toJson(JsonWriterSettings.builder().indent(true).build()));
BsonDocument encryptedFields = new BsonDocument().append("fields",
new BsonArray(asList(
new BsonDocument("keyId", BsonNull.VALUE).append("path", new BsonString("encryptedInt"))
.append("bsonType", new BsonString("int"))
.append("queries",
new BsonDocument("queryType", new BsonString("range")).append("contention", new BsonInt64(0L))
.append("trimFactor", new BsonInt32(1)).append("sparsity", new BsonInt64(1))
.append("min", new BsonInt32(0)).append("max", new BsonInt32(200))),
new BsonDocument("keyId", BsonNull.VALUE).append("path", new BsonString("encryptedLong"))
.append("bsonType", new BsonString("long")).append("queries",
new BsonDocument("queryType", new BsonString("range")).append("contention", new BsonInt64(0L))
.append("trimFactor", new BsonInt32(1)).append("sparsity", new BsonInt64(1))
.append("min", new BsonInt64(1000)).append("max", new BsonInt64(9999))))));
}
@Test // DATAMONGO-1835
void throwsExceptionOnNullRoot() {
assertThatIllegalArgumentException().isThrownBy(() -> MongoJsonSchema.of((JsonSchemaObject) null));

Loading…
Cancel
Save