Skip to content

Commit 270b720

Browse files
committed
Fix cast error in CryptoManager.
Closes #763.
1 parent eedad43 commit 270b720

File tree

5 files changed

+114
-59
lines changed

5 files changed

+114
-59
lines changed

spring-data-couchbase/src/main/java/org/springframework/data/couchbase/core/convert/CryptoConverter.java

+1-2
Original file line numberDiff line numberDiff line change
@@ -142,8 +142,7 @@ public CouchbaseDocument write(Object value, ValueConversionContext<? extends Pe
142142

143143
value = ctx.getConverter().getPotentiallyConvertedSimpleWrite(property, ctx.getAccessor(), false);
144144
if (conversions.isSimpleType(sourceType)) {
145-
String plainString;
146-
plainString = (String) value;
145+
String plainString = value.toString();
147146
if (sourceType == String.class || targetType == String.class) {
148147
plainString = "\"" + plainString.replaceAll("\"", "\\\"") + "\"";
149148
}

spring-data-couchbase/src/main/java/org/springframework/data/couchbase/core/convert/MappingCouchbaseConverter.java

+14-5
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@
7676
import org.springframework.util.CollectionUtils;
7777

7878
import com.couchbase.client.core.encryption.CryptoManager;
79+
import com.couchbase.client.java.encryption.annotation.Encrypted;
7980
import com.couchbase.client.java.json.JsonObject;
8081

8182
/**
@@ -287,7 +288,7 @@ public void doWithPersistentProperty(final CouchbasePersistentProperty prop) {
287288
* @return
288289
*/
289290
private boolean doesPropertyExistInSource(final CouchbasePersistentProperty property) {
290-
return property.isIdProperty() || source.containsKey(maybeMangle(property));
291+
return property.isIdProperty() || source.containsKey(property.getFieldName()) || source.containsKey(maybeMangle(property));
291292
}
292293

293294
private boolean isIdConstructionProperty(final CouchbasePersistentProperty property) {
@@ -937,9 +938,9 @@ private <R> R readValue(Object value, TypeInformation type, Object parent) {
937938
* @return the converted object.
938939
*/
939940
@SuppressWarnings("unchecked")
940-
private <R> R readValue(Object value, CouchbasePersistentProperty prop, Object parent) {
941+
private <R> R readValue(Object value, CouchbasePersistentProperty prop, Object parent, boolean noDecrypt) {
941942
Class<?> rawType = prop.getType();
942-
if (conversions.hasValueConverter(prop)) {
943+
if (conversions.hasValueConverter(prop) && !noDecrypt) {
943944
return (R) conversions.getPropertyValueConversions().getValueConverter(prop).read(value,
944945
new CouchbaseConversionContext(prop, this, null));
945946
} else if (conversions.hasCustomReadTarget(value.getClass(), rawType)) {
@@ -1055,15 +1056,23 @@ public CouchbasePropertyValueProvider(final CouchbaseDocument source,
10551056
@SuppressWarnings("unchecked")
10561057
public <R> R getPropertyValue(final CouchbasePersistentProperty property) {
10571058
String expression = property.getSpelExpression();
1058-
Object value = expression != null ? evaluator.evaluate(expression) : source.get(maybeMangle(property));
1059+
String maybeFieldName = maybeMangle(property);
1060+
Object value = expression != null ? evaluator.evaluate(expression) : source.get(maybeFieldName);
1061+
boolean noDecrypt=false;
1062+
if (value == null && !maybeFieldName.equals(property.getFieldName())) {
1063+
if (property.findAnnotation(Encrypted.class) != null
1064+
&& property.findAnnotation(Encrypted.class).migration().equals(Encrypted.Migration.FROM_UNENCRYPTED))
1065+
value = source.get(property.getFieldName());
1066+
noDecrypt = true;
1067+
}
10591068

10601069
if (property == entity.getIdProperty() && parent == null) {
10611070
return readValue(source.getId(), property.getTypeInformation(), source);
10621071
}
10631072
if (value == null) {
10641073
return null;
10651074
}
1066-
return readValue(value, property, source);
1075+
return readValue(value, property, source, noDecrypt);
10671076
}
10681077
}
10691078

spring-data-couchbase/src/test/java/org/springframework/data/couchbase/domain/Address.java

+10
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,14 @@
1515
*/
1616
package org.springframework.data.couchbase.domain;
1717

18+
import com.couchbase.client.java.encryption.annotation.Encrypted;
1819
import org.springframework.data.couchbase.core.mapping.Document;
1920

2021
@Document
2122
public class Address extends ComparableEntity {
2223

2324
private String street;
25+
private @Encrypted String encStreet;
2426
private String city;
2527
// for N1qlJoin
2628
private String id;
@@ -36,6 +38,14 @@ public void setStreet(String street) {
3638
this.street = street;
3739
}
3840

41+
public String getEncStreet() {
42+
return encStreet;
43+
}
44+
45+
public void setEncStreet(String encStreet) {
46+
this.encStreet = encStreet;
47+
}
48+
3949
public String getCity() {
4050
return city;
4151
}

spring-data-couchbase/src/test/java/org/springframework/data/couchbase/domain/UserEncrypted.java

+8-6
Original file line numberDiff line numberDiff line change
@@ -73,20 +73,22 @@ public UserEncrypted(final String id, final String firstname, final String lastn
7373
this.encryptedField = encryptedField;
7474
}
7575

76+
static DateTime NOW_DateTime = DateTime.now(DateTimeZone.UTC);
77+
static Date NOW_Date = Date.from(Instant.now());
7678
@Version protected long version;
77-
@Encrypted public String encryptedField;
79+
@Encrypted(migration= Encrypted.Migration.FROM_UNENCRYPTED)public String encryptedField;
7880
@Encrypted public Integer encInteger = 1;
7981
@Encrypted public Long encLong = Long.valueOf(1);
8082
@Encrypted public Boolean encBoolean = Boolean.TRUE;
8183
@Encrypted public BigInteger encBigInteger = new BigInteger("123");
8284
@Encrypted public BigDecimal encBigDecimal = new BigDecimal("456");
83-
@Encrypted public UUID encUUID = UUID.randomUUID();
84-
@Encrypted public DateTime encDateTime = DateTime.now(DateTimeZone.UTC);
85-
@Encrypted public Date encDate = Date.from(Instant.now());
85+
@Encrypted public UUID encUUID = UUID.fromString("00000000-0000-0000-0000-000000000000");
86+
@Encrypted public DateTime encDateTime = NOW_DateTime;
87+
@Encrypted public Date encDate = NOW_Date;
8688
@Encrypted public Address encAddress = new Address();
8789

88-
public Date plainDate = Date.from(Instant.now());
89-
public DateTime plainDateTime = DateTime.now(DateTimeZone.UTC);
90+
public Date plainDate = NOW_Date;
91+
public DateTime plainDateTime = NOW_DateTime;
9092

9193
public List nicknames = List.of("Happy", "Sleepy");
9294

spring-data-couchbase/src/test/java/org/springframework/data/couchbase/repository/CouchbaseRepositoryFieldLevelEncryptionIntegrationTests.java

+81-46
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818

1919
import static com.couchbase.client.core.util.CbCollections.mapOf;
2020
import static com.couchbase.client.java.query.QueryScanConsistency.REQUEST_PLUS;
21+
import static org.junit.Assert.assertNull;
2122
import static org.junit.jupiter.api.Assertions.assertEquals;
2223
import static org.junit.jupiter.api.Assertions.assertFalse;
2324
import static org.junit.jupiter.api.Assertions.assertTrue;
@@ -27,25 +28,23 @@
2728
import java.security.NoSuchAlgorithmException;
2829
import java.util.Base64;
2930
import java.util.HashMap;
31+
import java.util.List;
3032
import java.util.Map;
3133
import java.util.Optional;
3234
import java.util.UUID;
3335

3436
import javax.crypto.Mac;
3537
import javax.crypto.spec.SecretKeySpec;
3638

37-
import com.fasterxml.jackson.databind.ObjectMapper;
38-
import com.fasterxml.jackson.datatype.joda.JodaModule;
39-
import org.joda.time.DateTime;
4039
import org.junit.jupiter.api.BeforeEach;
4140
import org.junit.jupiter.api.Test;
4241
import org.springframework.beans.factory.annotation.Autowired;
4342
import org.springframework.context.annotation.Configuration;
43+
import org.springframework.dao.DataRetrievalFailureException;
4444
import org.springframework.data.couchbase.CouchbaseClientFactory;
4545
import org.springframework.data.couchbase.config.AbstractCouchbaseConfiguration;
4646
import org.springframework.data.couchbase.core.CouchbaseTemplate;
4747
import org.springframework.data.couchbase.domain.Address;
48-
import org.springframework.data.couchbase.domain.PersonValueRepository;
4948
import org.springframework.data.couchbase.domain.UserEncrypted;
5049
import org.springframework.data.couchbase.domain.UserEncryptedRepository;
5150
import org.springframework.data.couchbase.repository.config.EnableCouchbaseRepositories;
@@ -64,6 +63,9 @@
6463
import com.couchbase.client.encryption.EncryptionResult;
6564
import com.couchbase.client.encryption.Keyring;
6665
import com.couchbase.client.java.env.ClusterEnvironment;
66+
import com.couchbase.client.java.json.JsonObject;
67+
import com.fasterxml.jackson.databind.ObjectMapper;
68+
import com.fasterxml.jackson.datatype.joda.JodaModule;
6769

6870
/**
6971
* Repository KV tests
@@ -78,13 +80,20 @@ public class CouchbaseRepositoryFieldLevelEncryptionIntegrationTests extends Clu
7880
@Autowired UserEncryptedRepository userEncryptedRepository;
7981
@Autowired CouchbaseClientFactory clientFactory;
8082

81-
@Autowired PersonValueRepository personValueRepository;
8283
@Autowired CouchbaseTemplate couchbaseTemplate;
8384

8485
@BeforeEach
8586
public void beforeEach() {
8687
super.beforeEach();
87-
couchbaseTemplate.removeByQuery(UserEncrypted.class).withConsistency(REQUEST_PLUS).all();
88+
List<UserEncrypted> users = couchbaseTemplate.findByQuery(UserEncrypted.class).withConsistency(REQUEST_PLUS).all();
89+
for (UserEncrypted user : users) {
90+
couchbaseTemplate.removeById(UserEncrypted.class).one(user.getId());
91+
try { // may have also used upperCased-id
92+
couchbaseTemplate.removeById(UserEncrypted.class).one(user.getId().toUpperCase());
93+
} catch (DataRetrievalFailureException iae) {
94+
// ignore
95+
}
96+
}
8897
couchbaseTemplate.findByQuery(UserEncrypted.class).withConsistency(REQUEST_PLUS).all();
8998
}
9099

@@ -96,46 +105,92 @@ void javaSDKEncryption() {
96105
@Test
97106
@IgnoreWhen(clusterTypes = ClusterType.MOCKED)
98107
void saveAndFindById() {
108+
boolean cleanAfter = true;
99109
UserEncrypted user = new UserEncrypted(UUID.randomUUID().toString(), "saveAndFindById", "l", "hello");
100110
Address address = new Address(); // plaintext address with encrypted street
101-
// address.setEncStreet("Olcott Street");
102-
address.setStreet("Castro Street");
111+
address.setEncStreet("Castro Street");
103112
address.setCity("Santa Clara");
104113
user.addAddress(address);
105114
user.setHomeAddress(null);
106-
// cannot set encrypted fields within encrypted objects (i.e. setEncAddress())
107115
Address encAddress = new Address(); // encrypted address with plaintext street.
108116
encAddress.setStreet("Castro St");
109117
encAddress.setCity("Mountain View");
110118
user.setEncAddress(encAddress);
111119
assertFalse(userEncryptedRepository.existsById(user.getId()));
112-
DateTime beforeDateTime = user.plainDateTime.plus(1).minus(1);
113-
assertEquals(user.plainDateTime, beforeDateTime);
114-
System.err.println("before: "+beforeDateTime);
115120
userEncryptedRepository.save(user);
116-
DateTime afterDateTime = user.plainDateTime.plus(1).minus(1);
117-
assertEquals(beforeDateTime, afterDateTime);
118-
System.err.println("afterDateTime: "+afterDateTime);
119121
Optional<UserEncrypted> found = userEncryptedRepository.findById(user.getId());
120122
assertTrue(found.isPresent());
121-
System.err.println("Found: "+found.get());
122123
found.ifPresent(u -> assertEquals(user, u));
123124
assertTrue(userEncryptedRepository.existsById(user.getId()));
124-
125-
clientFactory.getCluster().bucket(config().bucketname()).defaultCollection().insert(user.getId().toUpperCase(), user);
125+
clientFactory.getCluster().bucket(config().bucketname()).defaultCollection().insert(user.getId().toUpperCase(),
126+
user);
126127
UserEncrypted sdkUser = clientFactory.getCluster().bucket(config().bucketname()).defaultCollection()
127128
.get(user.getId()).contentAs(UserEncrypted.class);
128-
System.err.println("user: : " + user);
129129
sdkUser.setId(user.getId());
130130
sdkUser.setVersion(user.getVersion());
131-
//assertTrue(user.encDateTime.equals( sdkUser.encDateTime));
132-
System.err.println("sdkUser : " + sdkUser);
133131
assertEquals(user.plainDateTime, found.get().plainDateTime);
134132
assertEquals(user.plainDateTime, sdkUser.plainDateTime);
135133
assertEquals(user.encDateTime, found.get().encDateTime);
136134
assertEquals(user.encDateTime, sdkUser.encDateTime);
135+
assertEquals(user, found.get());
137136
assertEquals(user, sdkUser);
138-
// userEncryptedRepository.delete(user);
137+
if (cleanAfter) {
138+
couchbaseTemplate.removeById(UserEncrypted.class).one(user.getId());
139+
try { // may have also used upperCased-id
140+
couchbaseTemplate.removeById(UserEncrypted.class).one(user.getId().toUpperCase());
141+
} catch (DataRetrievalFailureException iae) {
142+
// ignore
143+
}
144+
}
145+
}
146+
147+
@Test
148+
@IgnoreWhen(clusterTypes = ClusterType.MOCKED)
149+
void testFromMigration() {
150+
boolean cleanAfter = true;
151+
UserEncrypted user = new UserEncrypted(UUID.randomUUID().toString(), "testFromMigration", "l",
152+
"migrating from unencrypted");
153+
JsonObject jo = JsonObject.jo();
154+
jo.put("firstname", user.getFirstname());
155+
jo.put("lastname", user.getLastname());
156+
jo.put("encryptedField", user.encryptedField);
157+
jo.put("_class", UserEncrypted.class.getName());
158+
159+
// save it unencrypted
160+
clientFactory.getCluster().bucket(config().bucketname()).defaultCollection().insert(user.getId(), jo);
161+
JsonObject migration = clientFactory.getCluster().bucket(config().bucketname()).defaultCollection()
162+
.get(user.getId()).contentAsObject();
163+
assertEquals("migrating from unencrypted", migration.get("encryptedField"));
164+
assertNull( migration.get(CryptoManager.DEFAULT_ENCRYPTER_ALIAS+"encryptedField"));
165+
166+
167+
// it will be retrieved successfully
168+
Optional<UserEncrypted> found = userEncryptedRepository.findById(user.getId());
169+
assertTrue(found.isPresent());
170+
user.setVersion(found.get().getVersion());
171+
found.ifPresent(u -> assertEquals(user, u));
172+
// save it encrypted
173+
UserEncrypted saved = userEncryptedRepository.save(user);
174+
// it will be retrieved successfully
175+
Optional<UserEncrypted> foundEnc = userEncryptedRepository.findById(user.getId());
176+
assertTrue(foundEnc.isPresent());
177+
user.setVersion(foundEnc.get().getVersion());
178+
foundEnc.ifPresent(u -> assertEquals(user, u));
179+
180+
// retrieve it without decrypting
181+
JsonObject encrypted = clientFactory.getCluster().bucket(config().bucketname()).defaultCollection()
182+
.get(user.getId()).contentAsObject();
183+
assertEquals("myKey", ((JsonObject)encrypted.get(CryptoManager.DEFAULT_ENCRYPTED_FIELD_NAME_PREFIX+"encryptedField")).get("kid"));
184+
assertNull( encrypted.get("encryptedField"));
185+
186+
if (cleanAfter) {
187+
couchbaseTemplate.removeById(UserEncrypted.class).one(user.getId());
188+
try { // may have also used upperCased-id
189+
couchbaseTemplate.removeById(UserEncrypted.class).one(user.getId().toUpperCase());
190+
} catch (DataRetrievalFailureException iae) {
191+
// ignore
192+
}
193+
}
139194
}
140195

141196
@Configuration
@@ -163,7 +218,7 @@ public String getBucketName() {
163218
}
164219

165220
@Override
166-
public ObjectMapper couchbaseObjectMapper(CryptoManager cryptoManager){
221+
public ObjectMapper couchbaseObjectMapper(CryptoManager cryptoManager) {
167222
ObjectMapper om = super.couchbaseObjectMapper(cryptoManager);
168223
om.registerModule(new JodaModule());
169224
return om;
@@ -182,29 +237,8 @@ protected void configureEnvironment(ClusterEnvironment.Builder builder) {
182237
@Override
183238
protected CryptoManager cryptoManager() {
184239

185-
Decrypter decrypter = new Decrypter() {
186-
@Override
187-
public String algorithm() {
188-
return "myAlg";
189-
}
190-
191-
@Override
192-
public byte[] decrypt(EncryptionResult encrypted) {
193-
return Base64.getDecoder().decode(encrypted.getString("ciphertext"));
194-
}
195-
};
196-
197-
Encrypter encrypter = new Encrypter() {
198-
@Override
199-
public EncryptionResult encrypt(byte[] plaintext) {
200-
return EncryptionResult
201-
.fromMap(mapOf("alg", "myAlg", "ciphertext", Base64.getEncoder().encodeToString(plaintext)));
202-
}
203-
};
204240
Map<String, byte[]> keyMap = new HashMap();
205-
keyMap.put("myKey",
206-
new byte[] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
207-
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, });
241+
keyMap.put("myKey", new byte[64] /* all zeroes */);
208242
Keyring keyring = Keyring.fromMap(keyMap);
209243
// Provider secProvider;
210244
AeadAes256CbcHmacSha512Provider provider = AeadAes256CbcHmacSha512Provider.builder().keyring(keyring)
@@ -213,6 +247,7 @@ public EncryptionResult encrypt(byte[] plaintext) {
213247
.defaultEncrypter(provider.encrypterForKey("myKey")).build();
214248
}
215249

250+
// not used
216251
byte[] hmacMe(String cbc_secret_key, String cbc_api_message) {
217252
try {
218253
return hmac("hmacSHA256", cbc_secret_key.getBytes("utf-8"), cbc_api_message.getBytes("utf-8"));
@@ -221,6 +256,7 @@ byte[] hmacMe(String cbc_secret_key, String cbc_api_message) {
221256
}
222257
}
223258

259+
// not used
224260
static byte[] hmac(String algorithm, byte[] key, byte[] message)
225261
throws NoSuchAlgorithmException, InvalidKeyException {
226262
Mac mac = Mac.getInstance(algorithm);
@@ -229,5 +265,4 @@ static byte[] hmac(String algorithm, byte[] key, byte[] message)
229265
}
230266

231267
}
232-
233268
}

0 commit comments

Comments
 (0)