5 changed files with 206 additions and 51 deletions
@ -0,0 +1,141 @@ |
|||||||
|
/* |
||||||
|
* 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 |
||||||
|
* |
||||||
|
* 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, |
||||||
|
* 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.encryption; |
||||||
|
|
||||||
|
import static org.springframework.data.mongodb.core.schema.JsonSchemaProperty.encrypted; |
||||||
|
import static org.springframework.data.mongodb.core.schema.JsonSchemaProperty.int32; |
||||||
|
import static org.springframework.data.mongodb.core.schema.JsonSchemaProperty.int64; |
||||||
|
import static org.springframework.data.mongodb.core.schema.JsonSchemaProperty.queryable; |
||||||
|
import static org.springframework.data.mongodb.core.schema.QueryCharacteristics.range; |
||||||
|
import static org.springframework.data.mongodb.test.util.Assertions.assertThat; |
||||||
|
|
||||||
|
import java.util.List; |
||||||
|
import java.util.UUID; |
||||||
|
import java.util.stream.Stream; |
||||||
|
|
||||||
|
import org.bson.BsonBinary; |
||||||
|
import org.bson.Document; |
||||||
|
import org.bson.UuidRepresentation; |
||||||
|
import org.junit.jupiter.api.BeforeEach; |
||||||
|
import org.junit.jupiter.api.extension.ExtendWith; |
||||||
|
import org.junit.jupiter.params.ParameterizedTest; |
||||||
|
import org.junit.jupiter.params.provider.Arguments; |
||||||
|
import org.junit.jupiter.params.provider.MethodSource; |
||||||
|
import org.springframework.beans.factory.annotation.Autowired; |
||||||
|
import org.springframework.context.annotation.Configuration; |
||||||
|
import org.springframework.data.mongodb.config.AbstractMongoClientConfiguration; |
||||||
|
import org.springframework.data.mongodb.core.CollectionOptions; |
||||||
|
import org.springframework.data.mongodb.core.MongoTemplate; |
||||||
|
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.test.util.Client; |
||||||
|
import org.springframework.data.mongodb.test.util.MongoClientExtension; |
||||||
|
import org.springframework.test.context.ContextConfiguration; |
||||||
|
import org.springframework.test.context.junit.jupiter.SpringExtension; |
||||||
|
|
||||||
|
import com.mongodb.client.MongoClient; |
||||||
|
|
||||||
|
/** |
||||||
|
* @author Christoph Strobl |
||||||
|
*/ |
||||||
|
@ExtendWith({ MongoClientExtension.class, SpringExtension.class }) |
||||||
|
@ContextConfiguration |
||||||
|
public class MongoQueryableEncryptionCollectionCreationTests { |
||||||
|
|
||||||
|
public static final String COLLECTION_NAME = "enc-collection"; |
||||||
|
static @Client MongoClient mongoClient; |
||||||
|
|
||||||
|
@Configuration |
||||||
|
static class Config extends AbstractMongoClientConfiguration { |
||||||
|
|
||||||
|
@Override |
||||||
|
public MongoClient mongoClient() { |
||||||
|
return mongoClient; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
protected String getDatabaseName() { |
||||||
|
return "encryption-schema-tests"; |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
@Autowired MongoTemplate template; |
||||||
|
|
||||||
|
@BeforeEach |
||||||
|
void beforeEach() { |
||||||
|
template.dropCollection(COLLECTION_NAME); |
||||||
|
} |
||||||
|
|
||||||
|
@ParameterizedTest // GH-4185
|
||||||
|
@MethodSource("collectionOptions") |
||||||
|
public void createsCollectionWithEncryptedFieldsCorrectly(CollectionOptions collectionOptions) { |
||||||
|
|
||||||
|
template.createCollection(COLLECTION_NAME, collectionOptions); |
||||||
|
|
||||||
|
Document encryptedFields = readEncryptedFieldsFromDatabase(COLLECTION_NAME); |
||||||
|
assertThat(encryptedFields).containsKey("fields"); |
||||||
|
|
||||||
|
List<Document> fields = encryptedFields.get("fields", List.of()); |
||||||
|
assertThat(fields.get(0)).containsEntry("path", "encryptedInt") //
|
||||||
|
.containsEntry("bsonType", "int") //
|
||||||
|
.containsEntry("queries", List |
||||||
|
.of(Document.parse("{'queryType': 'range', 'contention': { '$numberLong' : '1' }, 'min': 5, 'max': 100}"))); |
||||||
|
|
||||||
|
assertThat(fields.get(1)).containsEntry("path", "nested.encryptedLong") //
|
||||||
|
.containsEntry("bsonType", "long") //
|
||||||
|
.containsEntry("queries", List.of(Document.parse( |
||||||
|
"{'queryType': 'range', 'contention': { '$numberLong' : '0' }, 'min': { '$numberLong' : '-1' }, 'max': { '$numberLong' : '1' }}"))); |
||||||
|
} |
||||||
|
|
||||||
|
private static Stream<Arguments> collectionOptions() { |
||||||
|
|
||||||
|
BsonBinary key1 = new BsonBinary(UUID.randomUUID(), UuidRepresentation.STANDARD); |
||||||
|
BsonBinary key2 = new BsonBinary(UUID.randomUUID(), UuidRepresentation.STANDARD); |
||||||
|
|
||||||
|
CollectionOptions manualOptions = CollectionOptions.encrypted(options -> options //
|
||||||
|
.queryable(encrypted(int32("encryptedInt")).keys(key1), range().min(5).max(100).contention(1)) //
|
||||||
|
.queryable(encrypted(JsonSchemaProperty.int64("nested.encryptedLong")).keys(key2), |
||||||
|
range().min(-1L).max(1L).contention(0))); |
||||||
|
|
||||||
|
CollectionOptions schemaOptions = CollectionOptions.encrypted(MongoJsonSchema.builder() |
||||||
|
.property(queryable(encrypted(int32("encryptedInt")).keys(key1), |
||||||
|
new QueryCharacteristics(List.of(range().min(5).max(100).contention(1))))) |
||||||
|
.property(queryable(encrypted(int64("nested.encryptedLong")).keys(key2), |
||||||
|
new QueryCharacteristics(List.of(range().min(-1L).max(1L).contention(0))))) |
||||||
|
.build()); |
||||||
|
|
||||||
|
return Stream.of(Arguments.of(manualOptions), Arguments.of(schemaOptions)); |
||||||
|
} |
||||||
|
|
||||||
|
Document readEncryptedFieldsFromDatabase(String collectionName) { |
||||||
|
|
||||||
|
Document collectionInfo = template |
||||||
|
.executeCommand(new Document("listCollections", 1).append("filter", new Document("name", collectionName))); |
||||||
|
|
||||||
|
if (collectionInfo.containsKey("cursor")) { |
||||||
|
collectionInfo = (Document) collectionInfo.get("cursor", Document.class).get("firstBatch", List.class).iterator() |
||||||
|
.next(); |
||||||
|
} |
||||||
|
|
||||||
|
if (!collectionInfo.containsKey("options")) { |
||||||
|
return new Document(); |
||||||
|
} |
||||||
|
|
||||||
|
return collectionInfo.get("options", Document.class).get("encryptedFields", Document.class); |
||||||
|
} |
||||||
|
} |
||||||
Loading…
Reference in new issue