Skip to content

Commit da0cb6d

Browse files
christophstroblmp911de
authored andcommitted
DATAMONGO-2306 - Add field level encryption to JSON Schema.
We now support encrypted fields via MongoJsonSchema and allow XML configuration of com.mongodb.AutoEncryptionSettings via a dedicated factory bean. Original pull request: #766.
1 parent 56ac839 commit da0cb6d

File tree

9 files changed

+441
-11
lines changed

9 files changed

+441
-11
lines changed

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

+14-1
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import org.springframework.data.mongodb.MongoDbFactory;
2323
import org.springframework.lang.Nullable;
2424

25+
import com.mongodb.AutoEncryptionSettings;
2526
import com.mongodb.DBDecoderFactory;
2627
import com.mongodb.DBEncoderFactory;
2728
import com.mongodb.MongoClient;
@@ -73,6 +74,7 @@ public class MongoClientOptionsFactoryBean extends AbstractFactoryBean<MongoClie
7374

7475
private boolean ssl;
7576
private @Nullable SSLSocketFactory sslSocketFactory;
77+
private @Nullable AutoEncryptionSettings autoEncryptionSettings;
7678

7779
/**
7880
* Set the {@link MongoClient} description.
@@ -272,6 +274,16 @@ public void setServerSelectionTimeout(int serverSelectionTimeout) {
272274
this.serverSelectionTimeout = serverSelectionTimeout;
273275
}
274276

277+
/**
278+
* Set the {@link AutoEncryptionSettings} to be used.
279+
*
280+
* @param autoEncryptionSettings can be {@literal null}.
281+
* @since 2.2
282+
*/
283+
public void setAutoEncryptionSettings(@Nullable AutoEncryptionSettings autoEncryptionSettings) {
284+
this.autoEncryptionSettings = autoEncryptionSettings;
285+
}
286+
275287
/*
276288
* (non-Javadoc)
277289
* @see org.springframework.beans.factory.config.AbstractFactoryBean#createInstance()
@@ -304,7 +316,8 @@ protected MongoClientOptions createInstance() throws Exception {
304316
.requiredReplicaSetName(requiredReplicaSetName) //
305317
.serverSelectionTimeout(serverSelectionTimeout) //
306318
.sslEnabled(ssl) //
307-
.socketFactory(socketFactoryToUse) // TODO: Mongo Driver 4 - remove if not available
319+
.autoEncryptionSettings(autoEncryptionSettings) //
320+
.socketFactory(socketFactoryToUse) // TODO: Mongo Driver 4 -
308321
.socketKeepAlive(socketKeepAlive) // TODO: Mongo Driver 4 - remove if not available
309322
.socketTimeout(socketTimeout) //
310323
.threadsAllowedToBlockForConnectionMultiplier(threadsAllowedToBlockForConnectionMultiplier) //
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
/*
2+
* Copyright 2019 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.springframework.data.mongodb.core;
17+
18+
import java.util.Collections;
19+
import java.util.Map;
20+
21+
import org.bson.BsonDocument;
22+
import org.springframework.beans.factory.FactoryBean;
23+
24+
import com.mongodb.AutoEncryptionSettings;
25+
import com.mongodb.MongoClientSettings;
26+
27+
/**
28+
* {@link FactoryBean} for creating {@link AutoEncryptionSettings} using the {@link AutoEncryptionSettings.Builder}.
29+
*
30+
* @author Christoph Strobl
31+
* @since 2.2
32+
*/
33+
public class MongoEncryptionSettingsFactoryBean implements FactoryBean<AutoEncryptionSettings> {
34+
35+
private boolean bypassAutoEncryption;
36+
private String keyVaultNamespace;
37+
private Map<String, Object> extraOptions;
38+
private MongoClientSettings keyVaultClientSettings;
39+
private Map<String, Map<String, Object>> kmsProviders;
40+
private Map<String, BsonDocument> schemaMap;
41+
42+
/**
43+
* @param bypassAutoEncryption
44+
* @see AutoEncryptionSettings.Builder#bypassAutoEncryption(boolean)
45+
*/
46+
public void setBypassAutoEncryption(boolean bypassAutoEncryption) {
47+
this.bypassAutoEncryption = bypassAutoEncryption;
48+
}
49+
50+
/**
51+
* @param extraOptions
52+
* @see AutoEncryptionSettings.Builder#extraOptions(Map)
53+
*/
54+
public void setExtraOptions(Map<String, Object> extraOptions) {
55+
this.extraOptions = extraOptions;
56+
}
57+
58+
/**
59+
* @param keyVaultNamespace
60+
* @see AutoEncryptionSettings.Builder#keyVaultNamespace(String)
61+
*/
62+
public void setKeyVaultNamespace(String keyVaultNamespace) {
63+
this.keyVaultNamespace = keyVaultNamespace;
64+
}
65+
66+
/**
67+
* @param keyVaultClientSettings
68+
* @see AutoEncryptionSettings.Builder#keyVaultMongoClientSettings(MongoClientSettings)
69+
*/
70+
public void setKeyVaultClientSettings(MongoClientSettings keyVaultClientSettings) {
71+
this.keyVaultClientSettings = keyVaultClientSettings;
72+
}
73+
74+
/**
75+
* @param kmsProviders
76+
* @see AutoEncryptionSettings.Builder#kmsProviders(Map)
77+
*/
78+
public void setKmsProviders(Map<String, Map<String, Object>> kmsProviders) {
79+
this.kmsProviders = kmsProviders;
80+
}
81+
82+
/**
83+
* @param schemaMap
84+
* @see AutoEncryptionSettings.Builder#schemaMap(Map)
85+
*/
86+
public void setSchemaMap(Map<String, BsonDocument> schemaMap) {
87+
this.schemaMap = schemaMap;
88+
}
89+
90+
/*
91+
* (non-Javadoc)
92+
* @see org.springframework.beans.factory.FactoryBean#getObject()
93+
*/
94+
@Override
95+
public AutoEncryptionSettings getObject() {
96+
97+
return AutoEncryptionSettings.builder() //
98+
.bypassAutoEncryption(bypassAutoEncryption) //
99+
.keyVaultNamespace(keyVaultNamespace) //
100+
.keyVaultMongoClientSettings(keyVaultClientSettings) //
101+
.kmsProviders(orEmpty(kmsProviders)) //
102+
.extraOptions(orEmpty(extraOptions)) //
103+
.schemaMap(orEmpty(schemaMap)) //
104+
.build();
105+
}
106+
107+
private <K, V> Map<K, V> orEmpty(Map<K, V> source) {
108+
return source != null ? source : Collections.emptyMap();
109+
}
110+
111+
/*
112+
* (non-Javadoc)
113+
* @see org.springframework.beans.factory.FactoryBean#getObjectType()
114+
*/
115+
@Override
116+
public Class<?> getObjectType() {
117+
return AutoEncryptionSettings.class;
118+
}
119+
}

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

+145
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,9 @@
3030
import org.springframework.data.mongodb.core.schema.TypedJsonSchemaObject.ObjectJsonSchemaObject;
3131
import org.springframework.data.mongodb.core.schema.TypedJsonSchemaObject.StringJsonSchemaObject;
3232
import org.springframework.data.mongodb.core.schema.TypedJsonSchemaObject.TimestampJsonSchemaObject;
33+
import org.springframework.lang.Nullable;
3334
import org.springframework.util.Assert;
35+
import org.springframework.util.ObjectUtils;
3436

3537
/**
3638
* {@link JsonSchemaProperty} implementation.
@@ -1044,4 +1046,147 @@ public boolean isRequired() {
10441046
return required;
10451047
}
10461048
}
1049+
1050+
/**
1051+
* {@link JsonSchemaProperty} implementation for encrypted fields.
1052+
*
1053+
* @author Christoph Strobl
1054+
* @since 2.2
1055+
*/
1056+
public static class EncryptedJsonSchemaProperty implements JsonSchemaProperty {
1057+
1058+
private final JsonSchemaProperty targetProperty;
1059+
private final @Nullable String algorithm;
1060+
private final @Nullable char[] keyId;
1061+
private final @Nullable char[] iv;
1062+
1063+
/**
1064+
* Create new instance of {@link EncryptedJsonSchemaProperty} wrapping the given {@link JsonSchemaProperty target}.
1065+
*
1066+
* @param target must not be {@literal null}.
1067+
*/
1068+
public EncryptedJsonSchemaProperty(JsonSchemaProperty target) {
1069+
this(target, null, null, null);
1070+
}
1071+
1072+
private EncryptedJsonSchemaProperty(JsonSchemaProperty target, @Nullable String algorithm, @Nullable char[] keyId,
1073+
@Nullable char[] iv) {
1074+
1075+
Assert.notNull(target, "Target must not be null!");
1076+
this.targetProperty = target;
1077+
this.algorithm = algorithm;
1078+
this.keyId = keyId;
1079+
this.iv = iv;
1080+
}
1081+
1082+
/**
1083+
* Create new instance of {@link EncryptedJsonSchemaProperty} wrapping the given {@link JsonSchemaProperty target}.
1084+
*
1085+
* @param target must not be {@literal null}.
1086+
* @return new instance of {@link EncryptedJsonSchemaProperty}.
1087+
*/
1088+
public static EncryptedJsonSchemaProperty encrypted(JsonSchemaProperty target) {
1089+
return new EncryptedJsonSchemaProperty(target);
1090+
}
1091+
1092+
/**
1093+
* Use {@literal AEAD_AES_256_CBC_HMAC_SHA_512-Random} algorithm.
1094+
*
1095+
* @return new instance of {@link EncryptedJsonSchemaProperty}.
1096+
*/
1097+
public EncryptedJsonSchemaProperty aead_aes_256_cbc_hmac_sha_512_random() {
1098+
return algorithm("AEAD_AES_256_CBC_HMAC_SHA_512-Random");
1099+
}
1100+
1101+
/**
1102+
* Use {@literal AEAD_AES_256_CBC_HMAC_SHA_512-Deterministic} algorithm.
1103+
*
1104+
* @return new instance of {@link EncryptedJsonSchemaProperty}.
1105+
*/
1106+
public EncryptedJsonSchemaProperty aead_aes_256_cbc_hmac_sha_512_deterministic() {
1107+
return algorithm("AEAD_AES_256_CBC_HMAC_SHA_512-Deterministic");
1108+
}
1109+
1110+
/**
1111+
* Use the given algorithm identified via its name.
1112+
*
1113+
* @return new instance of {@link EncryptedJsonSchemaProperty}.
1114+
*/
1115+
public EncryptedJsonSchemaProperty algorithm(String algorithm) {
1116+
return new EncryptedJsonSchemaProperty(targetProperty, algorithm, keyId, iv);
1117+
}
1118+
1119+
/**
1120+
* @param key
1121+
* @return
1122+
*/
1123+
public EncryptedJsonSchemaProperty keyId(char[] key) {
1124+
return new EncryptedJsonSchemaProperty(targetProperty, algorithm, key, iv);
1125+
}
1126+
1127+
public EncryptedJsonSchemaProperty keyId(String key) {
1128+
return keyId(key.toCharArray());
1129+
}
1130+
1131+
/*
1132+
* (non-Javadoc)
1133+
* @see org.springframework.data.mongodb.core.schema.JsonSchemaObject#toDocument()
1134+
*/
1135+
@Override
1136+
public Document toDocument() {
1137+
1138+
Document doc = targetProperty.toDocument();
1139+
Document propertySpecification = doc.get(targetProperty.getIdentifier(), Document.class);
1140+
1141+
Document enc = new Document();
1142+
1143+
if (!ObjectUtils.isEmpty(keyId)) {
1144+
enc.append("keyId", new String(keyId));
1145+
}
1146+
1147+
Type type = extractPropertyType(propertySpecification);
1148+
if (type != null) {
1149+
1150+
propertySpecification.remove(type.representation());
1151+
enc.append("bsonType", type.toBsonType().value()); // TODO: no samples with type -> is it bson type all the way?
1152+
}
1153+
1154+
enc.append("algorithm", algorithm);
1155+
1156+
propertySpecification.append("encrypt", enc);
1157+
1158+
return doc;
1159+
}
1160+
1161+
/*
1162+
* (non-Javadoc)
1163+
* @see org.springframework.data.mongodb.core.schema.JsonSchemaProperty#getIdentifier()
1164+
*/
1165+
@Override
1166+
public String getIdentifier() {
1167+
return targetProperty.getIdentifier();
1168+
}
1169+
1170+
/*
1171+
* (non-Javadoc)
1172+
* @see org.springframework.data.mongodb.core.schema.JsonSchemaObject#getTypes()
1173+
*/
1174+
@Override
1175+
public Set<Type> getTypes() {
1176+
return targetProperty.getTypes();
1177+
}
1178+
1179+
@Nullable
1180+
private Type extractPropertyType(Document source) {
1181+
1182+
if (source.containsKey("type")) {
1183+
return Type.of(source.get("type", String.class));
1184+
}
1185+
if (source.containsKey("bsonType")) {
1186+
return Type.of(source.get("bsonType", String.class));
1187+
}
1188+
1189+
return null;
1190+
}
1191+
}
10471192
}

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

+42
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
*/
1616
package org.springframework.data.mongodb.core.schema;
1717

18+
import lombok.EqualsAndHashCode;
1819
import lombok.RequiredArgsConstructor;
1920

2021
import java.math.BigDecimal;
@@ -428,6 +429,23 @@ static Type jsonTypeOf(String name) {
428429
return new JsonType(name);
429430
}
430431

432+
/**
433+
* Create a {@link Type} with its default {@link Type#representation() representation} via the name.
434+
*
435+
* @param name must not be {@literal null}.
436+
* @return the matching type instance.
437+
* @since 2.2
438+
*/
439+
static Type of(String name) {
440+
441+
Type type = jsonTypeOf(name);
442+
if (jsonTypes().contains(type)) {
443+
return type;
444+
}
445+
446+
return bsonTypeOf(name);
447+
}
448+
431449
/**
432450
* @return all known JSON types.
433451
*/
@@ -456,11 +474,34 @@ static Set<Type> bsonTypes() {
456474
*/
457475
Object value();
458476

477+
/**
478+
* Get the {@literal bsonType} representation of the given type.
479+
*
480+
* @return never {@literal null}.
481+
* @since 2.2
482+
*/
483+
default Type toBsonType() {
484+
485+
if (representation().equals("bsonType")) {
486+
return this;
487+
}
488+
489+
if (value().equals(Type.booleanType().value())) {
490+
return bsonTypeOf("bool");
491+
}
492+
if (value().equals(Type.numberType().value())) {
493+
return bsonTypeOf("long");
494+
}
495+
496+
return bsonTypeOf((String) value());
497+
}
498+
459499
/**
460500
* @author Christpoh Strobl
461501
* @since 2.1
462502
*/
463503
@RequiredArgsConstructor
504+
@EqualsAndHashCode
464505
class JsonType implements Type {
465506

466507
private final String name;
@@ -489,6 +530,7 @@ public String value() {
489530
* @since 2.1
490531
*/
491532
@RequiredArgsConstructor
533+
@EqualsAndHashCode
492534
class BsonType implements Type {
493535

494536
private final String name;

0 commit comments

Comments
 (0)