Field Level Encryption Support. (#1546)
Note that an @Encrypted property object that contains an @Encrypted property is not supported and will result in an exception. Closes #763.
This commit is contained in:
@@ -200,6 +200,13 @@ The generated documentation is available from `target/site/reference/html/index.
|
||||
popd
|
||||
----
|
||||
|
||||
=== Intellij Issue with Importing pom.xml
|
||||
|
||||
There is an issue in Intellij that prevents it from importing modules when one of the module
|
||||
directories has the same name as the project directory. The work-around is to create a new module (any name, any type will suffice).
|
||||
When Intellij creates the new module, it will also recognize the existing modules. Once the new module is
|
||||
created, it can be deleted and Intellij will now recognize the existing modules.
|
||||
|
||||
The generated documentation is available from `target/site/reference/html/index.html`.
|
||||
|
||||
== Examples
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
@@ -217,6 +218,13 @@
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.couchbase.client</groupId>
|
||||
<artifactId>couchbase-encryption</artifactId>
|
||||
<version>3.1.0</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
<repositories>
|
||||
@@ -303,7 +311,9 @@
|
||||
</goals>
|
||||
<configuration>
|
||||
<outputDirectory>target/generated-test-sources</outputDirectory>
|
||||
<processor>org.springframework.data.couchbase.repository.support.CouchbaseAnnotationProcessor</processor>
|
||||
<processor>
|
||||
org.springframework.data.couchbase.repository.support.CouchbaseAnnotationProcessor
|
||||
</processor>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
|
||||
@@ -18,8 +18,9 @@ package org.springframework.data.couchbase.config;
|
||||
|
||||
import static com.couchbase.client.java.ClusterOptions.clusterOptions;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import org.springframework.beans.factory.config.BeanDefinition;
|
||||
@@ -27,6 +28,7 @@ import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Role;
|
||||
import org.springframework.core.convert.converter.GenericConverter;
|
||||
import org.springframework.core.type.filter.AnnotationTypeFilter;
|
||||
import org.springframework.data.convert.CustomConversions;
|
||||
import org.springframework.data.couchbase.CouchbaseClientFactory;
|
||||
@@ -35,6 +37,7 @@ import org.springframework.data.couchbase.core.CouchbaseTemplate;
|
||||
import org.springframework.data.couchbase.core.ReactiveCouchbaseTemplate;
|
||||
import org.springframework.data.couchbase.core.convert.CouchbaseCustomConversions;
|
||||
import org.springframework.data.couchbase.core.convert.MappingCouchbaseConverter;
|
||||
import org.springframework.data.couchbase.core.convert.OtherConverters;
|
||||
import org.springframework.data.couchbase.core.convert.translation.JacksonTranslationService;
|
||||
import org.springframework.data.couchbase.core.convert.translation.TranslationService;
|
||||
import org.springframework.data.couchbase.core.mapping.CouchbaseMappingContext;
|
||||
@@ -149,7 +152,9 @@ public abstract class AbstractCouchbaseConfiguration {
|
||||
if (!nonShadowedJacksonPresent()) {
|
||||
throw new CouchbaseException("non-shadowed Jackson not present");
|
||||
}
|
||||
builder.jsonSerializer(JacksonJsonSerializer.create(couchbaseObjectMapper()));
|
||||
CryptoManager cryptoManager = cryptoManager();
|
||||
builder.jsonSerializer(JacksonJsonSerializer.create(couchbaseObjectMapper(cryptoManager)));
|
||||
builder.cryptoManager(cryptoManager);
|
||||
configureEnvironment(builder);
|
||||
return builder.build();
|
||||
}
|
||||
@@ -280,8 +285,8 @@ public abstract class AbstractCouchbaseConfiguration {
|
||||
@Bean
|
||||
public TranslationService couchbaseTranslationService() {
|
||||
final JacksonTranslationService jacksonTranslationService = new JacksonTranslationService();
|
||||
jacksonTranslationService.setObjectMapper(couchbaseObjectMapper(cryptoManager()));
|
||||
jacksonTranslationService.afterPropertiesSet();
|
||||
|
||||
// for sdk3, we need to ask the mapper _it_ uses to ignore extra fields...
|
||||
JacksonTransformers.MAPPER.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
|
||||
return jacksonTranslationService;
|
||||
@@ -308,10 +313,25 @@ public abstract class AbstractCouchbaseConfiguration {
|
||||
*/
|
||||
|
||||
public ObjectMapper couchbaseObjectMapper() {
|
||||
ObjectMapper mapper = new ObjectMapper();
|
||||
return couchbaseObjectMapper(cryptoManager());
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@link ObjectMapper} for the jsonSerializer of the ClusterEnvironment
|
||||
*
|
||||
* @param cryptoManager
|
||||
* @return ObjectMapper
|
||||
*/
|
||||
|
||||
ObjectMapper mapper;
|
||||
|
||||
public ObjectMapper couchbaseObjectMapper(CryptoManager cryptoManager) {
|
||||
if (mapper != null) {
|
||||
return mapper;
|
||||
}
|
||||
mapper = new ObjectMapper(); // or use the one from the Java SDK (?) JacksonTransformers.MAPPER
|
||||
mapper.configure(com.fasterxml.jackson.databind.DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
|
||||
mapper.registerModule(new JsonValueModule());
|
||||
CryptoManager cryptoManager = null;
|
||||
if (cryptoManager != null) {
|
||||
mapper.registerModule(new EncryptionModule(cryptoManager));
|
||||
}
|
||||
@@ -320,7 +340,7 @@ public abstract class AbstractCouchbaseConfiguration {
|
||||
|
||||
/**
|
||||
* The default blocking transaction manager. It is an implementation of CallbackPreferringTransactionManager
|
||||
* CallbackPreferrringTransactionmanagers do not play well with test-cases that rely
|
||||
* CallbackPreferringTransactionManagers do not play well with test-cases that rely
|
||||
* on @TestTransaction/@BeforeTransaction/@AfterTransaction
|
||||
*
|
||||
* @param clientFactory
|
||||
@@ -341,6 +361,7 @@ public abstract class AbstractCouchbaseConfiguration {
|
||||
TransactionTemplate couchbaseTransactionTemplate(CouchbaseCallbackTransactionManager couchbaseTransactionManager) {
|
||||
return new TransactionTemplate(couchbaseTransactionManager);
|
||||
}
|
||||
|
||||
/**
|
||||
* The default TransactionalOperator.
|
||||
*
|
||||
@@ -379,11 +400,28 @@ public abstract class AbstractCouchbaseConfiguration {
|
||||
* and {@link #couchbaseMappingContext(CustomConversions)}. Returns an empty {@link CustomConversions} instance by
|
||||
* default.
|
||||
*
|
||||
* @param cryptoManagerOptional optional cryptoManager. Make varargs for backwards compatibility.
|
||||
* @return must not be {@literal null}.
|
||||
*/
|
||||
@Bean(name = BeanNames.COUCHBASE_CUSTOM_CONVERSIONS)
|
||||
public CustomConversions customConversions() {
|
||||
return new CouchbaseCustomConversions(Collections.emptyList());
|
||||
public CustomConversions customConversions(CryptoManager... cryptoManagerOptional) {
|
||||
assert (cryptoManagerOptional == null || cryptoManagerOptional.length <= 1);
|
||||
CryptoManager cryptoManager = cryptoManagerOptional != null && cryptoManagerOptional.length == 1
|
||||
? cryptoManagerOptional[0]
|
||||
: null;
|
||||
List<GenericConverter> newConverters = new ArrayList();
|
||||
// the cryptoConverters take an argument, so they cannot be created in the
|
||||
// static block of CouchbaseCustomConversions. And they must be set before the super() constructor
|
||||
// in CouchbaseCustomerConversions
|
||||
if (cryptoManager != null) {
|
||||
newConverters.addAll(OtherConverters.getCryptoConverters(cryptoManager));
|
||||
}
|
||||
return new CouchbaseCustomConversions(newConverters);
|
||||
}
|
||||
|
||||
@Bean
|
||||
protected CryptoManager cryptoManager() {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -20,8 +20,11 @@ import java.util.Collections;
|
||||
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
import org.springframework.core.convert.ConversionService;
|
||||
import org.springframework.core.convert.TypeDescriptor;
|
||||
import org.springframework.core.convert.support.GenericConversionService;
|
||||
import org.springframework.data.convert.CustomConversions;
|
||||
import org.springframework.data.couchbase.core.mapping.CouchbasePersistentProperty;
|
||||
import org.springframework.data.mapping.model.ConvertingPropertyAccessor;
|
||||
import org.springframework.data.mapping.model.EntityInstantiators;
|
||||
|
||||
/**
|
||||
@@ -29,6 +32,7 @@ import org.springframework.data.mapping.model.EntityInstantiators;
|
||||
*
|
||||
* @author Michael Nitschinger
|
||||
* @author Mark Paluch
|
||||
* @author Michael Reiche
|
||||
*/
|
||||
public abstract class AbstractCouchbaseConverter implements CouchbaseConverter, InitializingBean {
|
||||
|
||||
@@ -93,6 +97,36 @@ public abstract class AbstractCouchbaseConverter implements CouchbaseConverter,
|
||||
conversions.registerConvertersIn(conversionService);
|
||||
}
|
||||
|
||||
/**
|
||||
* This convertForWriteIfNeeded takes a property and accessor so that the annotations can be accessed (ie. @Encrypted)
|
||||
*
|
||||
* @param prop the property to be converted to the class that would actually be stored.
|
||||
* @param accessor the property accessor
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
public Object convertForWriteIfNeeded(CouchbasePersistentProperty prop, ConvertingPropertyAccessor<Object> accessor) {
|
||||
Object value = accessor.getProperty(prop, prop.getType());
|
||||
if (value == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Object result = this.conversions.getCustomWriteTarget(prop.getType()) //
|
||||
.map(it -> this.conversionService.convert(value, new TypeDescriptor(prop.getField()),
|
||||
TypeDescriptor.valueOf(it))) //
|
||||
.orElseGet(() -> Enum.class.isAssignableFrom(value.getClass()) ? ((Enum<?>) value).name() : value);
|
||||
|
||||
return result;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* This convertForWriteIfNeed takes only the value to convert. It cannot access the annotations of the Field being
|
||||
* converted.
|
||||
*
|
||||
* @param value the value to be converted to the class that would actually be stored.
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
public Object convertForWriteIfNeeded(Object value) {
|
||||
if (value == null) {
|
||||
|
||||
@@ -22,6 +22,7 @@ import org.springframework.data.couchbase.core.mapping.CouchbaseDocument;
|
||||
import org.springframework.data.couchbase.core.mapping.CouchbasePersistentEntity;
|
||||
import org.springframework.data.couchbase.core.mapping.CouchbasePersistentProperty;
|
||||
import org.springframework.data.mapping.Alias;
|
||||
import org.springframework.data.mapping.model.ConvertingPropertyAccessor;
|
||||
import org.springframework.data.util.TypeInformation;
|
||||
|
||||
/**
|
||||
@@ -37,13 +38,24 @@ public interface CouchbaseConverter
|
||||
|
||||
/**
|
||||
* Convert the value if necessary to the class that would actually be stored, or leave it as is if no conversion
|
||||
* needed.
|
||||
* needed. This method cannot access the annotations of the field.
|
||||
*
|
||||
* @param value the value to be converted to the class that would actually be stored.
|
||||
* @return the converted value (or the same value if no conversion necessary).
|
||||
*/
|
||||
Object convertForWriteIfNeeded(Object value);
|
||||
|
||||
/**
|
||||
* Convert the value if necessary to the class that would actually be stored, or leave it as is if no conversion
|
||||
* needed. This method can access the annotations of the field.
|
||||
*
|
||||
* @param source the property to be converted to the class that would actually be stored.
|
||||
* @param accessor the property accessor
|
||||
* @return the converted value (or the same value if no conversion necessary).
|
||||
*/
|
||||
Object convertForWriteIfNeeded(final CouchbasePersistentProperty source,
|
||||
final ConvertingPropertyAccessor<Object> accessor);
|
||||
|
||||
/**
|
||||
* Return the Class that would actually be stored for a given Class.
|
||||
*
|
||||
|
||||
@@ -22,6 +22,8 @@ import java.util.List;
|
||||
|
||||
import org.springframework.data.mapping.model.SimpleTypeHolder;
|
||||
|
||||
import com.couchbase.client.core.encryption.CryptoManager;
|
||||
|
||||
/**
|
||||
* Value object to capture custom conversion.
|
||||
* <p>
|
||||
@@ -32,6 +34,7 @@ import org.springframework.data.mapping.model.SimpleTypeHolder;
|
||||
* @author Oliver Gierke
|
||||
* @author Mark Paluch
|
||||
* @author Subhashni Balakrishnan
|
||||
* @Michael Reiche
|
||||
* @see org.springframework.data.convert.CustomConversions
|
||||
* @see SimpleTypeHolder
|
||||
* @since 2.0
|
||||
@@ -42,6 +45,18 @@ public class CouchbaseCustomConversions extends org.springframework.data.convert
|
||||
|
||||
private static final List<Object> STORE_CONVERTERS;
|
||||
|
||||
private CryptoManager cryptoManager;
|
||||
|
||||
/**
|
||||
* Expose the CryptoManager used by a DecryptingReadingConverter or EncryptingWritingConverter, if any. There can only
|
||||
* be one. MappingCouchbaseConverter needs it.
|
||||
*
|
||||
* @return cryptoManager
|
||||
*/
|
||||
public CryptoManager getCryptoManager() {
|
||||
return cryptoManager;
|
||||
}
|
||||
|
||||
static {
|
||||
|
||||
List<Object> converters = new ArrayList<>();
|
||||
@@ -61,5 +76,31 @@ public class CouchbaseCustomConversions extends org.springframework.data.convert
|
||||
*/
|
||||
public CouchbaseCustomConversions(final List<?> converters) {
|
||||
super(STORE_CONVERSIONS, converters);
|
||||
for (Object c : converters) {
|
||||
if (c instanceof DecryptingReadingConverter) {
|
||||
CryptoManager foundCryptoManager = ((DecryptingReadingConverter) c).cryptoManager;
|
||||
if (foundCryptoManager == null) {
|
||||
throw new RuntimeException(("DecryptingReadingConverter must have a cryptoManager"));
|
||||
} else {
|
||||
if (cryptoManager != null && this.cryptoManager != cryptoManager) {
|
||||
throw new RuntimeException(
|
||||
"all DecryptingReadingConverters and EncryptingWringConverters must use " + " a single CryptoManager");
|
||||
}
|
||||
}
|
||||
cryptoManager = foundCryptoManager;
|
||||
}
|
||||
if (c instanceof EncryptingWritingConverter) {
|
||||
CryptoManager foundCryptoManager = ((EncryptingWritingConverter) c).cryptoManager;
|
||||
if (foundCryptoManager == null) {
|
||||
throw new RuntimeException(("EncryptingWritingConverter must have a cryptoManager"));
|
||||
} else {
|
||||
if (cryptoManager != null && this.cryptoManager != cryptoManager) {
|
||||
throw new RuntimeException(
|
||||
"all DecryptingReadingConverters and EncryptingWringConverters must use " + " a single CryptoManager");
|
||||
}
|
||||
}
|
||||
cryptoManager = foundCryptoManager;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
/*
|
||||
* Copyright 2022 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.couchbase.core.convert;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.math.BigInteger;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import org.springframework.core.convert.ConversionService;
|
||||
import org.springframework.core.convert.TypeDescriptor;
|
||||
import org.springframework.core.convert.converter.ConditionalGenericConverter;
|
||||
import org.springframework.data.convert.ReadingConverter;
|
||||
import org.springframework.data.couchbase.core.mapping.CouchbaseDocument;
|
||||
|
||||
import com.couchbase.client.core.encryption.CryptoManager;
|
||||
import com.couchbase.client.java.encryption.annotation.Encrypted;
|
||||
|
||||
/**
|
||||
* Use the cryptoManager to decrypt a field
|
||||
*
|
||||
* @author Michael Reiche
|
||||
*/
|
||||
@ReadingConverter
|
||||
public class DecryptingReadingConverter implements ConditionalGenericConverter {
|
||||
|
||||
CryptoManager cryptoManager;
|
||||
ConversionService conversionService;
|
||||
|
||||
public DecryptingReadingConverter(CryptoManager cryptoManager) {
|
||||
this.cryptoManager = cryptoManager;
|
||||
}
|
||||
|
||||
public void setConversionService(ConversionService conversionService) {
|
||||
this.conversionService = conversionService;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<ConvertiblePair> getConvertibleTypes() {
|
||||
Set<ConvertiblePair> convertiblePairs = new HashSet<>();
|
||||
Class<?>[] clazzes = new Class[] { String.class, Integer.class, Long.class, Float.class, Double.class,
|
||||
BigInteger.class, BigDecimal.class, Boolean.class };
|
||||
for (Class clazz : clazzes) {
|
||||
convertiblePairs.add(new ConvertiblePair(CouchbaseDocument.class, clazz));
|
||||
}
|
||||
return convertiblePairs;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
|
||||
return source == null? null : new String(cryptoManager.decrypt(((CouchbaseDocument) source).getContent()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType) {
|
||||
return targetType.hasAnnotation(Encrypted.class);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* Copyright 2012-2022 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.couchbase.core.convert;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Michael Reiche
|
||||
*/
|
||||
public class Encrypted {
|
||||
Map<Object,Object> encryptionMap;
|
||||
|
||||
public Encrypted(Map<Object,Object> encryptionMap){
|
||||
this.encryptionMap = encryptionMap;
|
||||
}
|
||||
|
||||
Map<Object, Object> getEncryptionMap(){
|
||||
return encryptionMap;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
/*
|
||||
* Copyright 2012-2022 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.couchbase.core.convert;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.math.BigInteger;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.springframework.core.convert.TypeDescriptor;
|
||||
import org.springframework.core.convert.converter.ConditionalGenericConverter;
|
||||
import org.springframework.core.convert.converter.GenericConverter;
|
||||
import org.springframework.data.convert.WritingConverter;
|
||||
|
||||
import com.couchbase.client.core.encryption.CryptoManager;
|
||||
|
||||
/**
|
||||
* Use the cryptomManager to encrypt properties
|
||||
*
|
||||
* @author Michael Reiche
|
||||
*/
|
||||
@WritingConverter
|
||||
public class EncryptingWritingConverter implements ConditionalGenericConverter {
|
||||
|
||||
CryptoManager cryptoManager;
|
||||
|
||||
public EncryptingWritingConverter(CryptoManager cryptoManager) {
|
||||
this.cryptoManager = cryptoManager;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<GenericConverter.ConvertiblePair> getConvertibleTypes() {
|
||||
|
||||
Set<ConvertiblePair> convertiblePairs = new HashSet<>();
|
||||
Class<?>[] clazzes = new Class[] { String.class, Integer.class, Long.class, Float.class, Double.class,
|
||||
BigInteger.class, BigDecimal.class, Boolean.class };
|
||||
for (Class clazz : clazzes) {
|
||||
convertiblePairs.add(new ConvertiblePair(clazz, String.class));
|
||||
}
|
||||
return convertiblePairs;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
|
||||
if (source == null) {
|
||||
return null;
|
||||
}
|
||||
com.couchbase.client.java.encryption.annotation.Encrypted ann = sourceType
|
||||
.getAnnotation(com.couchbase.client.java.encryption.annotation.Encrypted.class);
|
||||
Map<Object, Object> result = new HashMap<>();
|
||||
result.putAll(cryptoManager.encrypt(source.toString().getBytes(), ann.encrypter()));
|
||||
return new Encrypted(result);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType) {
|
||||
return sourceType.hasAnnotation(com.couchbase.client.java.encryption.annotation.Encrypted.class);
|
||||
}
|
||||
}
|
||||
@@ -19,9 +19,11 @@ package org.springframework.data.couchbase.core.convert;
|
||||
import static org.springframework.data.couchbase.core.mapping.id.GenerationStrategy.UNIQUE;
|
||||
import static org.springframework.data.couchbase.core.mapping.id.GenerationStrategy.USE_ATTRIBUTES;
|
||||
|
||||
import java.beans.Transient;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
@@ -33,9 +35,11 @@ import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.ApplicationContextAware;
|
||||
import org.springframework.core.CollectionFactory;
|
||||
import org.springframework.core.convert.ConversionService;
|
||||
import org.springframework.core.convert.TypeDescriptor;
|
||||
import org.springframework.core.convert.support.DefaultConversionService;
|
||||
import org.springframework.data.annotation.Transient;
|
||||
import org.springframework.data.convert.CustomConversions;
|
||||
import org.springframework.data.couchbase.core.convert.translation.JacksonTranslationService;
|
||||
import org.springframework.data.couchbase.core.convert.translation.TranslationService;
|
||||
import org.springframework.data.couchbase.core.mapping.CouchbaseDocument;
|
||||
import org.springframework.data.couchbase.core.mapping.CouchbaseList;
|
||||
import org.springframework.data.couchbase.core.mapping.CouchbaseMappingContext;
|
||||
@@ -72,6 +76,9 @@ import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
|
||||
import com.couchbase.client.core.encryption.CryptoManager;
|
||||
import com.couchbase.client.java.json.JsonObject;
|
||||
|
||||
/**
|
||||
* A mapping converter for Couchbase. The converter is responsible for reading from and writing to entities and
|
||||
* converting it into a consumable database representation.
|
||||
@@ -119,6 +126,11 @@ public class MappingCouchbaseConverter extends AbstractCouchbaseConverter implem
|
||||
*/
|
||||
private @Nullable EntityCallbacks entityCallbacks;
|
||||
|
||||
/**
|
||||
* CryptoManager for en/decryption
|
||||
*/
|
||||
private @Nullable CryptoManager cryptoManager;
|
||||
|
||||
public MappingCouchbaseConverter() {
|
||||
this(new CouchbaseMappingContext(), null);
|
||||
}
|
||||
@@ -156,6 +168,23 @@ public class MappingCouchbaseConverter extends AbstractCouchbaseConverter implem
|
||||
spELContext = new SpELContext(CouchbaseDocumentPropertyAccessor.INSTANCE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the cryptoManager used in conversions. We need to be able to mangle a property name.
|
||||
*/
|
||||
@Override
|
||||
public void setCustomConversions(CustomConversions customConversions) {
|
||||
super.setCustomConversions(customConversions);
|
||||
if (customConversions instanceof CouchbaseCustomConversions) {
|
||||
this.cryptoManager = ((CouchbaseCustomConversions) customConversions).getCryptoManager();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterPropertiesSet() {
|
||||
super.afterPropertiesSet();
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a collection from the given source object.
|
||||
*
|
||||
@@ -278,8 +307,15 @@ public class MappingCouchbaseConverter extends AbstractCouchbaseConverter implem
|
||||
accessor.setProperty(prop, obj);
|
||||
}
|
||||
|
||||
/**
|
||||
* doesPropertyExistInSource. It could have getFieldName() or mangled(getFieldName())
|
||||
*
|
||||
* @param property
|
||||
* @return
|
||||
*/
|
||||
private boolean doesPropertyExistInSource(final CouchbasePersistentProperty property) {
|
||||
return property.isIdProperty() || source.containsKey(property.getFieldName());
|
||||
return property.isIdProperty() || source.containsKey(property.getFieldName())
|
||||
|| (cryptoManager != null && source.containsKey(cryptoManager.mangle(property.getFieldName())));
|
||||
}
|
||||
|
||||
private boolean isIdConstructionProperty(final CouchbasePersistentProperty property) {
|
||||
@@ -398,10 +434,25 @@ public class MappingCouchbaseConverter extends AbstractCouchbaseConverter implem
|
||||
throw new MappingException("Unable to create class from " + value.toString());
|
||||
}
|
||||
}
|
||||
|
||||
return target.isAssignableFrom(value.getClass()) ? value : conversionService.convert(value, target);
|
||||
}
|
||||
|
||||
/**
|
||||
* Potentially convert simple values like ENUMs.
|
||||
*
|
||||
* @param value the value to convert.
|
||||
* @param target the target persistent property which may have an Encrypt annotation
|
||||
* @return the potentially converted object.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
private Object getPotentiallyConvertedSimpleRead(Object value, final CouchbasePersistentProperty target) {
|
||||
if (value == null || target == null) {
|
||||
return value;
|
||||
}
|
||||
// this call to convert takes TypeDescriptors - the target type descriptor may have an Encrypt annotation.
|
||||
return conversionService.convert(value, TypeDescriptor.forObject(value), new TypeDescriptor(target.getField()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(final Object source, final CouchbaseDocument target) {
|
||||
if (source == null) {
|
||||
@@ -415,7 +466,8 @@ public class MappingCouchbaseConverter extends AbstractCouchbaseConverter implem
|
||||
typeMapper.writeType(type, target);
|
||||
}
|
||||
|
||||
writeInternal(source, target, type, true);
|
||||
// CouchbasePersistentEntity entity = mappingContext.getPersistentEntity(source.getClass());
|
||||
writeInternalRoot(source, target, type, true);
|
||||
if (target.getId() == null) {
|
||||
throw new MappingException("An ID property is needed, but not found/could not be generated on this entity.");
|
||||
}
|
||||
@@ -426,10 +478,10 @@ public class MappingCouchbaseConverter extends AbstractCouchbaseConverter implem
|
||||
*
|
||||
* @param source the source object.
|
||||
* @param target the target document.
|
||||
* @param typeHint the type information for the source.
|
||||
* @param withId write out with the id.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
protected void writeInternal(final Object source, CouchbaseDocument target, final TypeInformation<?> typeHint,
|
||||
protected void writeInternalRoot(final Object source, CouchbaseDocument target, TypeInformation<?> typeHint,
|
||||
boolean withId) {
|
||||
if (source == null) {
|
||||
return;
|
||||
@@ -442,7 +494,7 @@ public class MappingCouchbaseConverter extends AbstractCouchbaseConverter implem
|
||||
}
|
||||
|
||||
if (Map.class.isAssignableFrom(source.getClass())) {
|
||||
writeMapInternal((Map<Object, Object>) source, target, ClassTypeInformation.MAP);
|
||||
writeMapInternal((Map<Object, Object>) source, target, ClassTypeInformation.MAP, null);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -451,7 +503,7 @@ public class MappingCouchbaseConverter extends AbstractCouchbaseConverter implem
|
||||
}
|
||||
|
||||
CouchbasePersistentEntity<?> entity = mappingContext.getPersistentEntity(source.getClass());
|
||||
writeInternal(source, target, entity, withId);
|
||||
writeInternalEntity(source, target, entity, withId, null);
|
||||
addCustomTypeKeyIfNecessary(typeHint, source, target);
|
||||
}
|
||||
|
||||
@@ -487,8 +539,8 @@ public class MappingCouchbaseConverter extends AbstractCouchbaseConverter implem
|
||||
* @param entity the persistent entity to convert from.
|
||||
* @param withId one of the top-level properties is the id for the document
|
||||
*/
|
||||
protected void writeInternal(final Object source, final CouchbaseDocument target,
|
||||
final CouchbasePersistentEntity<?> entity, boolean withId) {
|
||||
protected void writeInternalEntity(final Object source, final CouchbaseDocument target,
|
||||
final CouchbasePersistentEntity<?> entity, boolean withId, CouchbasePersistentProperty prop) {
|
||||
if (source == null) {
|
||||
return;
|
||||
}
|
||||
@@ -508,6 +560,7 @@ public class MappingCouchbaseConverter extends AbstractCouchbaseConverter implem
|
||||
|
||||
target.setExpiration((int) (entity.getExpiryDuration().getSeconds()));
|
||||
|
||||
// write all the entity.properties to the target. Does not write the id or version.
|
||||
writeToTargetDocument(target, entity, accessor, idProperty, versionProperty, prefixes, suffixes, idAttributes);
|
||||
|
||||
if (idProperty != null && target.getId() == null) {
|
||||
@@ -521,6 +574,7 @@ public class MappingCouchbaseConverter extends AbstractCouchbaseConverter implem
|
||||
} else {
|
||||
target.setId(id);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
entity.doWithAssociations(new AssociationHandler<CouchbasePersistentProperty>() {
|
||||
@@ -530,7 +584,7 @@ public class MappingCouchbaseConverter extends AbstractCouchbaseConverter implem
|
||||
Class<?> type = inverseProp.getType();
|
||||
Object propertyObj = accessor.getProperty(inverseProp, type);
|
||||
if (null != propertyObj) {
|
||||
writePropertyInternal(propertyObj, target, inverseProp, false);
|
||||
writePropertyInternal(propertyObj, target, inverseProp, accessor);
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -577,9 +631,9 @@ public class MappingCouchbaseConverter extends AbstractCouchbaseConverter implem
|
||||
}
|
||||
|
||||
if (!conversions.isSimpleType(propertyObj.getClass())) {
|
||||
writePropertyInternal(propertyObj, target, prop, false);
|
||||
writePropertyInternal(propertyObj, target, prop, accessor);
|
||||
} else {
|
||||
writeSimpleInternal(propertyObj, target, prop.getFieldName());
|
||||
writeSimpleInternal(prop, accessor, target, prop.getFieldName());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -595,7 +649,7 @@ public class MappingCouchbaseConverter extends AbstractCouchbaseConverter implem
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
private void writePropertyInternal(final Object source, final CouchbaseDocument target,
|
||||
final CouchbasePersistentProperty prop, boolean withId) {
|
||||
final CouchbasePersistentProperty prop, final ConvertingPropertyAccessor accessor) {
|
||||
if (source == null) {
|
||||
return;
|
||||
}
|
||||
@@ -603,28 +657,23 @@ public class MappingCouchbaseConverter extends AbstractCouchbaseConverter implem
|
||||
String name = prop.getFieldName();
|
||||
TypeInformation<?> valueType = ClassTypeInformation.from(source.getClass());
|
||||
TypeInformation<?> type = prop.getTypeInformation();
|
||||
|
||||
if (valueType.isCollectionLike()) {
|
||||
CouchbaseList collectionDoc = createCollection(asCollection(source), prop);
|
||||
CouchbaseList collectionDoc = createCollection(asCollection(source), valueType, prop, accessor);
|
||||
// TODO needs to handle enc
|
||||
target.put(name, collectionDoc);
|
||||
return;
|
||||
}
|
||||
|
||||
if (valueType.isMap()) {
|
||||
CouchbaseDocument mapDoc = createMap((Map<Object, Object>) source, prop);
|
||||
// TODO needs to handle enc
|
||||
target.put(name, mapDoc);
|
||||
return;
|
||||
}
|
||||
|
||||
if (valueType.getType().equals(java.util.Optional.class)) {
|
||||
if (source == null)
|
||||
return;
|
||||
Optional<String> o = (Optional<String>) source;
|
||||
if (o.isPresent()) {
|
||||
writeSimpleInternal(o.get(), target, prop.getFieldName());
|
||||
} else {
|
||||
writeSimpleInternal(null, target, prop.getFieldName());
|
||||
}
|
||||
Optional<?> o = (Optional<?>) source;
|
||||
writeSimpleInternal(o.map(s -> prop).orElse(null), accessor, target, prop.getFieldName());
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -643,9 +692,19 @@ public class MappingCouchbaseConverter extends AbstractCouchbaseConverter implem
|
||||
|
||||
CouchbasePersistentEntity<?> entity = isSubtype(prop.getType(), source.getClass())
|
||||
? mappingContext.getRequiredPersistentEntity(source.getClass())
|
||||
: mappingContext.getRequiredPersistentEntity(type);
|
||||
writeInternal(source, propertyDoc, entity, false);
|
||||
target.put(name, propertyDoc);
|
||||
: mappingContext.getRequiredPersistentEntity(prop);
|
||||
writeInternalEntity(source, propertyDoc, entity, false, prop);
|
||||
com.couchbase.client.java.encryption.annotation.Encrypted ann = prop
|
||||
.findAnnotation(com.couchbase.client.java.encryption.annotation.Encrypted.class);
|
||||
if (ann != null) {
|
||||
JsonObject jo = JsonObject.from(propertyDoc.getContent());
|
||||
Map<Object, Object> encMap = new HashMap();
|
||||
encMap.putAll(cryptoManager.encrypt(jo.toBytes(), ann.encrypter()));
|
||||
CouchbaseDocument mapDoc = writeMapInternal(encMap, new CouchbaseDocument(), prop.getTypeInformation(), prop);
|
||||
target.put(cryptoManager.mangle(name), mapDoc);
|
||||
} else {
|
||||
target.put(name, propertyDoc);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -659,7 +718,7 @@ public class MappingCouchbaseConverter extends AbstractCouchbaseConverter implem
|
||||
Assert.notNull(map, "Given map must not be null!");
|
||||
Assert.notNull(prop, "PersistentProperty must not be null!");
|
||||
|
||||
return writeMapInternal(map, new CouchbaseDocument(), prop.getTypeInformation());
|
||||
return writeMapInternal(map, new CouchbaseDocument(), prop.getTypeInformation(), prop);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -667,11 +726,10 @@ public class MappingCouchbaseConverter extends AbstractCouchbaseConverter implem
|
||||
*
|
||||
* @param source the source object.
|
||||
* @param target the target document.
|
||||
* @param type the type information for the document.
|
||||
* @return the written couchbase document.
|
||||
*/
|
||||
private CouchbaseDocument writeMapInternal(final Map<Object, Object> source, final CouchbaseDocument target,
|
||||
final TypeInformation<?> type) {
|
||||
TypeInformation<?> type, CouchbasePersistentProperty prop) {
|
||||
for (Map.Entry<Object, Object> entry : source.entrySet()) {
|
||||
Object key = entry.getKey();
|
||||
Object val = entry.getValue();
|
||||
@@ -680,14 +738,14 @@ public class MappingCouchbaseConverter extends AbstractCouchbaseConverter implem
|
||||
String simpleKey = key.toString();
|
||||
|
||||
if (val == null || conversions.isSimpleType(val.getClass())) {
|
||||
writeSimpleInternal(val, target, simpleKey);
|
||||
writeSimpleInternal(val, target, simpleKey); // this is an entry in a map, cannot have an annotation
|
||||
} else if (val instanceof Collection || val.getClass().isArray()) {
|
||||
target.put(simpleKey, writeCollectionInternal(asCollection(val),
|
||||
new CouchbaseList(conversions.getSimpleTypeHolder()), type.getMapValueType()));
|
||||
target.put(simpleKey,
|
||||
writeCollectionInternal(asCollection(val), new CouchbaseList(conversions.getSimpleTypeHolder()),
|
||||
prop.getTypeInformation(), prop, getPropertyAccessor(val)));
|
||||
} else {
|
||||
CouchbaseDocument embeddedDoc = new CouchbaseDocument();
|
||||
TypeInformation<?> valueTypeInfo = type.isMap() ? type.getMapValueType() : ClassTypeInformation.OBJECT;
|
||||
writeInternal(val, embeddedDoc, valueTypeInfo, false);
|
||||
writeInternalRoot(val, embeddedDoc, prop.getTypeInformation(), false);
|
||||
target.put(simpleKey, embeddedDoc);
|
||||
}
|
||||
} else {
|
||||
@@ -702,12 +760,12 @@ public class MappingCouchbaseConverter extends AbstractCouchbaseConverter implem
|
||||
* Helper method to create the underlying collection/list.
|
||||
*
|
||||
* @param collection the collection to write.
|
||||
* @param prop the property information.
|
||||
* @return the created couchbase list.
|
||||
*/
|
||||
private CouchbaseList createCollection(final Collection<?> collection, final CouchbasePersistentProperty prop) {
|
||||
return writeCollectionInternal(collection, new CouchbaseList(conversions.getSimpleTypeHolder()),
|
||||
prop.getTypeInformation());
|
||||
private CouchbaseList createCollection(final Collection<?> collection, final TypeInformation<?> type,
|
||||
CouchbasePersistentProperty prop, ConvertingPropertyAccessor accessor) {
|
||||
return writeCollectionInternal(collection, new CouchbaseList(conversions.getSimpleTypeHolder()), type, prop,
|
||||
accessor);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -715,11 +773,11 @@ public class MappingCouchbaseConverter extends AbstractCouchbaseConverter implem
|
||||
*
|
||||
* @param source the source object.
|
||||
* @param target the target document.
|
||||
* @param type the type information for the document.
|
||||
* @return the created couchbase list.
|
||||
*/
|
||||
private CouchbaseList writeCollectionInternal(final Collection<?> source, final CouchbaseList target,
|
||||
final TypeInformation<?> type) {
|
||||
final TypeInformation<?> type, CouchbasePersistentProperty prop, ConvertingPropertyAccessor accessor) {
|
||||
|
||||
TypeInformation<?> componentType = type == null ? null : type.getComponentType();
|
||||
|
||||
for (Object element : source) {
|
||||
@@ -729,11 +787,10 @@ public class MappingCouchbaseConverter extends AbstractCouchbaseConverter implem
|
||||
target.put(getPotentiallyConvertedSimpleWrite(element));
|
||||
} else if (element instanceof Collection || elementType.isArray()) {
|
||||
target.put(writeCollectionInternal(asCollection(element), new CouchbaseList(conversions.getSimpleTypeHolder()),
|
||||
componentType));
|
||||
type, prop, accessor));
|
||||
} else {
|
||||
|
||||
CouchbaseDocument embeddedDoc = new CouchbaseDocument();
|
||||
writeInternal(element, embeddedDoc, componentType, false);
|
||||
writeInternalRoot(element, embeddedDoc, prop.getTypeInformation(), false);
|
||||
target.put(embeddedDoc);
|
||||
}
|
||||
|
||||
@@ -784,7 +841,7 @@ public class MappingCouchbaseConverter extends AbstractCouchbaseConverter implem
|
||||
/**
|
||||
* Write the given source into the couchbase document target.
|
||||
*
|
||||
* @param source the source object.
|
||||
* @param source the source object. This does not have access to annotaions.
|
||||
* @param target the target document.
|
||||
* @param key the key of the object.
|
||||
*/
|
||||
@@ -792,8 +849,41 @@ public class MappingCouchbaseConverter extends AbstractCouchbaseConverter implem
|
||||
target.put(key, getPotentiallyConvertedSimpleWrite(source));
|
||||
}
|
||||
|
||||
/**
|
||||
* Write the given source into the couchbase document target.
|
||||
*
|
||||
* @param source the source persistent property. This has access to annotations.
|
||||
* @param target the target document.
|
||||
* @param key the key of the object.
|
||||
*/
|
||||
private void writeSimpleInternal(final CouchbasePersistentProperty source,
|
||||
final ConvertingPropertyAccessor<Object> accessor, final CouchbaseDocument target, final String key) {
|
||||
Object result = getPotentiallyConvertedSimpleWrite(source, accessor);
|
||||
if (result instanceof Optional) {
|
||||
Optional<Object> optional = (Optional) result;
|
||||
result = optional.orElse(null);
|
||||
}
|
||||
// For FLE, write as "encrypted$"...
|
||||
if (source.findAnnotation(com.couchbase.client.java.encryption.annotation.Encrypted.class) != null) {
|
||||
if (result instanceof Encrypted) {
|
||||
CouchbaseDocument mapDoc = writeMapInternal(((Encrypted) result).getEncryptionMap(), new CouchbaseDocument(),
|
||||
ClassTypeInformation.MAP, null);
|
||||
target.put(cryptoManager.mangle(key), mapDoc);
|
||||
} else {
|
||||
throw new RuntimeException("field annotation with @Encrypted could not be encrypted " + source.getField());
|
||||
}
|
||||
} else {
|
||||
target.put(key, result);
|
||||
}
|
||||
}
|
||||
|
||||
public Object getPotentiallyConvertedSimpleWrite(final Object value) {
|
||||
return convertForWriteIfNeeded(value);
|
||||
return convertForWriteIfNeeded(value); // cannot access annotations
|
||||
}
|
||||
|
||||
public Object getPotentiallyConvertedSimpleWrite(final CouchbasePersistentProperty value,
|
||||
ConvertingPropertyAccessor<Object> accessor) {
|
||||
return convertForWriteIfNeeded(value, accessor); // can access annotations
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -845,7 +935,7 @@ public class MappingCouchbaseConverter extends AbstractCouchbaseConverter implem
|
||||
* @return the converted object.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
private <R> R readValue(Object value, TypeInformation<?> type, Object parent) {
|
||||
private <R> R readValue(Object value, TypeInformation type, Object parent) {
|
||||
Class<?> rawType = type.getType();
|
||||
|
||||
if (conversions.hasCustomReadTarget(value.getClass(), rawType)) {
|
||||
@@ -855,7 +945,43 @@ public class MappingCouchbaseConverter extends AbstractCouchbaseConverter implem
|
||||
} else if (value instanceof CouchbaseList) {
|
||||
return (R) readCollection(type, (CouchbaseList) value, parent);
|
||||
} else {
|
||||
return (R) getPotentiallyConvertedSimpleRead(value, rawType);
|
||||
return (R) getPotentiallyConvertedSimpleRead(value, type.getClass()); // type does not have annotations
|
||||
}
|
||||
}
|
||||
|
||||
TranslationService translationService = new JacksonTranslationService();
|
||||
|
||||
/**
|
||||
* Helper method to read the value based on the PersistentProperty
|
||||
*
|
||||
* @param value the value to convert.
|
||||
* @param prop the persistent property - will have annotations (i.e. Encrypt for FLE)
|
||||
* @param parent the optional parent.
|
||||
* @param <R> the target type.
|
||||
* @return the converted object.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
private <R> R readValue(Object value, CouchbasePersistentProperty prop, Object parent) {
|
||||
Class<?> rawType = prop.getType();
|
||||
|
||||
if (prop.findAnnotation(com.couchbase.client.java.encryption.annotation.Encrypted.class) != null
|
||||
&& !conversions.isSimpleType(rawType)) {
|
||||
TreeMap exported = ((CouchbaseDocument) value).export(); // should be couchbase document (?)
|
||||
byte[] result = cryptoManager.decrypt(exported);
|
||||
CouchbaseDocument converted = new CouchbaseDocument();
|
||||
Object readEntity = read(prop.getTypeInformation().getType(),
|
||||
(CouchbaseDocument) translationService.decode(new String(result), converted));
|
||||
return (R) readEntity;
|
||||
}
|
||||
if (conversions.hasCustomReadTarget(value.getClass(), rawType)) {
|
||||
TypeInformation ti = ClassTypeInformation.from(value.getClass());
|
||||
return (R) conversionService.convert(value, ti.toTypeDescriptor(), new TypeDescriptor(prop.getField()));
|
||||
} else if (value instanceof CouchbaseDocument) {
|
||||
return (R) read(prop.getTypeInformation(), (CouchbaseDocument) value, parent);
|
||||
} else if (value instanceof CouchbaseList) {
|
||||
return (R) readCollection(prop.getTypeInformation(), (CouchbaseList) value, parent);
|
||||
} else {
|
||||
return (R) getPotentiallyConvertedSimpleRead(value, prop);// passes PersistentProperty with annotations
|
||||
}
|
||||
}
|
||||
|
||||
@@ -961,6 +1087,11 @@ public class MappingCouchbaseConverter extends AbstractCouchbaseConverter implem
|
||||
public <R> R getPropertyValue(final CouchbasePersistentProperty property) {
|
||||
String expression = property.getSpelExpression();
|
||||
Object value = expression != null ? evaluator.evaluate(expression) : source.get(property.getFieldName());
|
||||
if (value == null
|
||||
&& property.findAnnotation(com.couchbase.client.java.encryption.annotation.Encrypted.class) != null
|
||||
&& cryptoManager != null) {
|
||||
value = source.get(cryptoManager.mangle(property.getFieldName()));
|
||||
}
|
||||
|
||||
if (property == entity.getIdProperty() && parent == null) {
|
||||
return readValue(source.getId(), property.getTypeInformation(), source);
|
||||
@@ -968,8 +1099,7 @@ public class MappingCouchbaseConverter extends AbstractCouchbaseConverter implem
|
||||
if (value == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return readValue(value, property.getTypeInformation(), source);
|
||||
return readValue(value, property, source);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -23,7 +23,9 @@ import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
import com.couchbase.client.core.encryption.CryptoManager;
|
||||
import org.springframework.core.convert.converter.Converter;
|
||||
import org.springframework.core.convert.converter.GenericConverter;
|
||||
import org.springframework.data.convert.ReadingConverter;
|
||||
import org.springframework.data.convert.WritingConverter;
|
||||
|
||||
@@ -43,7 +45,6 @@ public final class OtherConverters {
|
||||
*/
|
||||
public static Collection<Converter<?, ?>> getConvertersToRegister() {
|
||||
List<Converter<?, ?>> converters = new ArrayList<Converter<?, ?>>();
|
||||
|
||||
converters.add(UuidToString.INSTANCE);
|
||||
converters.add(StringToUuid.INSTANCE);
|
||||
converters.add(BigIntegerToString.INSTANCE);
|
||||
@@ -54,6 +55,19 @@ public final class OtherConverters {
|
||||
return converters;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the crypto converters to be registered.
|
||||
*
|
||||
* @param cryptoManager to use for encrypting and decrypting
|
||||
* @return the list of converters to register.
|
||||
*/
|
||||
public static Collection<GenericConverter> getCryptoConverters(CryptoManager cryptoManager) {
|
||||
List<GenericConverter> converters = new ArrayList<>();
|
||||
converters.add(new EncryptingWritingConverter(cryptoManager));
|
||||
converters.add(new DecryptingReadingConverter(cryptoManager));
|
||||
return converters;
|
||||
}
|
||||
|
||||
@WritingConverter
|
||||
public enum UuidToString implements Converter<UUID, String> {
|
||||
INSTANCE;
|
||||
|
||||
@@ -15,10 +15,16 @@
|
||||
*/
|
||||
package org.springframework.data.couchbase.domain;
|
||||
|
||||
import com.couchbase.client.java.encryption.annotation.Encrypted;
|
||||
import org.springframework.data.couchbase.core.mapping.Document;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
@Document
|
||||
public class Address extends ComparableEntity {
|
||||
public class Address extends ComparableEntity implements Serializable {
|
||||
|
||||
@Encrypted
|
||||
private String encStreet;
|
||||
|
||||
private String street;
|
||||
private String city;
|
||||
@@ -36,6 +42,14 @@ public class Address extends ComparableEntity {
|
||||
this.street = street;
|
||||
}
|
||||
|
||||
public String getEncStreet() {
|
||||
return encStreet;
|
||||
}
|
||||
|
||||
public void setEncStreet(String encStreet) {
|
||||
this.encStreet = encStreet;
|
||||
}
|
||||
|
||||
public String getCity() {
|
||||
return city;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2012-2021 the original author or authors
|
||||
* Copyright 2012-2022 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.
|
||||
@@ -220,6 +220,7 @@ public class Config extends AbstractCouchbaseConfiguration {
|
||||
@Bean(name = "couchbaseTranslationService")
|
||||
public TranslationService couchbaseTranslationService() {
|
||||
final JacksonTranslationService jacksonTranslationService = new JacksonTranslationService();
|
||||
jacksonTranslationService.setObjectMapper(couchbaseObjectMapper(cryptoManager()));
|
||||
jacksonTranslationService.afterPropertiesSet();
|
||||
|
||||
// for sdk3, we need to ask the mapper _it_ uses to ignore extra fields...
|
||||
|
||||
@@ -0,0 +1,158 @@
|
||||
/*
|
||||
* Copyright 2012-2022 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.couchbase.domain;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.UUID;
|
||||
|
||||
import com.couchbase.client.java.encryption.annotation.Encrypted;
|
||||
import org.springframework.data.annotation.CreatedBy;
|
||||
import org.springframework.data.annotation.CreatedDate;
|
||||
import org.springframework.data.annotation.LastModifiedBy;
|
||||
import org.springframework.data.annotation.LastModifiedDate;
|
||||
import org.springframework.data.annotation.PersistenceConstructor;
|
||||
import org.springframework.data.annotation.Transient;
|
||||
import org.springframework.data.annotation.TypeAlias;
|
||||
import org.springframework.data.annotation.Version;
|
||||
import org.springframework.data.couchbase.core.mapping.Document;
|
||||
|
||||
/**
|
||||
* UserEncrypted entity for tests
|
||||
*
|
||||
* @author Michael Nitschinger
|
||||
* @author Michael Reiche
|
||||
*/
|
||||
|
||||
@Document
|
||||
@TypeAlias(AbstractingTypeMapper.Type.ABSTRACTUSER)
|
||||
public class UserEncrypted extends AbstractUser implements Serializable {
|
||||
|
||||
public UserEncrypted(){
|
||||
this.subtype = AbstractingTypeMapper.Type.USER;
|
||||
}
|
||||
|
||||
public UserEncrypted( final String lastname, final String encryptedField) {
|
||||
this();
|
||||
this.id = UUID.randomUUID().toString();
|
||||
this.lastname = lastname;
|
||||
this.encryptedField = encryptedField;
|
||||
}
|
||||
|
||||
@PersistenceConstructor
|
||||
public UserEncrypted(final String id, final String firstname, final String lastname) {
|
||||
this();
|
||||
this.id = id;
|
||||
this.firstname = firstname;
|
||||
this.lastname = lastname;
|
||||
}
|
||||
|
||||
public UserEncrypted(final String id, final String firstname, final String lastname, final String encryptedField) {
|
||||
this();
|
||||
this.id = id;
|
||||
this.firstname = firstname;
|
||||
this.lastname = lastname;
|
||||
this.encryptedField = encryptedField;
|
||||
}
|
||||
|
||||
@Version protected long version;
|
||||
@Transient protected String transientInfo;
|
||||
@CreatedBy protected String createdBy;
|
||||
@CreatedDate protected long createdDate;
|
||||
@LastModifiedBy protected String lastModifiedBy;
|
||||
@LastModifiedDate protected long lastModifiedDate;
|
||||
@Encrypted public String encryptedField;
|
||||
@Encrypted public Integer encInteger=1;
|
||||
@Encrypted public Long encLong=Long.valueOf(1);
|
||||
@Encrypted public Boolean encBoolean = Boolean.TRUE;
|
||||
|
||||
List nicknames = List.of("Happy", "Sleepy");
|
||||
Address homeAddress = new Address();
|
||||
List<Address> addresses = new ArrayList<>();
|
||||
|
||||
@Encrypted
|
||||
Address encAddress = new Address();
|
||||
|
||||
public String getLastname() {
|
||||
return lastname;
|
||||
}
|
||||
|
||||
public long getCreatedDate() {
|
||||
return createdDate;
|
||||
}
|
||||
|
||||
public void setCreatedDate(long createdDate) {
|
||||
this.createdDate = createdDate;
|
||||
}
|
||||
|
||||
public String getCreatedBy() {
|
||||
return createdBy;
|
||||
}
|
||||
|
||||
public void setCreatedBy(String createdBy) {
|
||||
this.createdBy = createdBy;
|
||||
}
|
||||
|
||||
public long getLastModifiedDate() {
|
||||
return lastModifiedDate;
|
||||
}
|
||||
|
||||
public String getLastModifiedBy() {
|
||||
return lastModifiedBy;
|
||||
}
|
||||
|
||||
|
||||
public String getEncryptedField() {
|
||||
return encryptedField;
|
||||
}
|
||||
|
||||
|
||||
public long getVersion() {
|
||||
return version;
|
||||
}
|
||||
|
||||
public void setVersion(long version) {
|
||||
this.version = version;
|
||||
}
|
||||
|
||||
public void setHomeAddress(Address address){
|
||||
this.homeAddress = address;
|
||||
}
|
||||
|
||||
public void setEncAddress(Address address){
|
||||
this.encAddress = address;
|
||||
}
|
||||
|
||||
public void addAddress(Address address){
|
||||
this.addresses.add(address);
|
||||
}
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(getId(), firstname, lastname);
|
||||
}
|
||||
|
||||
public String getTransientInfo() {
|
||||
return transientInfo;
|
||||
}
|
||||
|
||||
public void setTransientInfo(String something) {
|
||||
transientInfo = something;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
/*
|
||||
* Copyright 2012-2022 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.couchbase.domain;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.springframework.cache.annotation.Cacheable;
|
||||
import org.springframework.data.couchbase.repository.Collection;
|
||||
import org.springframework.data.couchbase.repository.CouchbaseRepository;
|
||||
import org.springframework.data.couchbase.repository.Query;
|
||||
import org.springframework.data.couchbase.repository.ScanConsistency;
|
||||
import org.springframework.data.couchbase.repository.Scope;
|
||||
import org.springframework.data.repository.query.Param;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
import com.couchbase.client.java.json.JsonArray;
|
||||
import com.couchbase.client.java.query.QueryScanConsistency;
|
||||
|
||||
/**
|
||||
* User Repository for tests
|
||||
*
|
||||
* @author Michael Nitschinger
|
||||
* @author Michael Reiche
|
||||
*/
|
||||
@Repository
|
||||
@ScanConsistency(query = QueryScanConsistency.REQUEST_PLUS)
|
||||
public interface UserEncryptedRepository extends CouchbaseRepository<UserEncrypted, String> {
|
||||
|
||||
List<User> findByFirstname(String firstname);
|
||||
|
||||
List<User> findByFirstnameIgnoreCase(String firstname);
|
||||
|
||||
Stream<User> findByLastname(String lastname);
|
||||
|
||||
List<User> findByFirstnameIn(String... firstnames);
|
||||
|
||||
List<User> findByFirstnameIn(JsonArray firstnames);
|
||||
|
||||
List<User> findByFirstnameAndLastname(String firstname, String lastname);
|
||||
|
||||
@Query("#{#n1ql.selectEntity} where #{#n1ql.filter} and firstname = $1 and lastname = $2")
|
||||
List<User> getByFirstnameAndLastname(String firstname, String lastname);
|
||||
|
||||
@Query("#{#n1ql.selectEntity} where #{#n1ql.filter} and (firstname = $first or lastname = $last)")
|
||||
List<User> getByFirstnameOrLastname(@Param("first") String firstname, @Param("last") String lastname);
|
||||
|
||||
List<User> findByIdIsNotNullAndFirstnameEquals(String firstname);
|
||||
|
||||
List<User> findByVersionEqualsAndFirstnameEquals(Long version, String firstname);
|
||||
|
||||
@Query("#{#n1ql.selectEntity}|#{#n1ql.filter}|#{#n1ql.bucket}|#{#n1ql.scope}|#{#n1ql.collection}")
|
||||
@Scope("thisScope")
|
||||
@Collection("thisCollection")
|
||||
List<User> spelTests();
|
||||
|
||||
// simulate a slow operation
|
||||
@Cacheable("mySpringCache")
|
||||
default List<User> getByFirstname(String firstname) {
|
||||
try {
|
||||
Thread.sleep(1000 * 5);
|
||||
} catch (InterruptedException ie) {}
|
||||
return findByFirstname(firstname);
|
||||
}
|
||||
|
||||
@Override
|
||||
UserEncrypted save(UserEncrypted user);
|
||||
}
|
||||
@@ -0,0 +1,221 @@
|
||||
/*
|
||||
* Copyright 2012-2022 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.couchbase.repository;
|
||||
|
||||
import static com.couchbase.client.core.util.CbCollections.mapOf;
|
||||
import static com.couchbase.client.java.query.QueryScanConsistency.REQUEST_PLUS;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.lang.reflect.InvocationHandler;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Proxy;
|
||||
import java.security.InvalidKeyException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.Provider;
|
||||
import java.util.Base64;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
||||
import javax.crypto.Mac;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
|
||||
import com.couchbase.client.encryption.AeadAes256CbcHmacSha512Provider;
|
||||
import com.couchbase.client.encryption.Decrypter;
|
||||
import com.couchbase.client.encryption.DefaultCryptoManager;
|
||||
import com.couchbase.client.encryption.Encrypter;
|
||||
import com.couchbase.client.encryption.EncryptionResult;
|
||||
import com.couchbase.client.encryption.Keyring;
|
||||
import com.couchbase.client.java.encryption.annotation.Encrypted;
|
||||
import com.couchbase.client.java.encryption.databind.jackson.EncryptionModule;
|
||||
import com.couchbase.client.java.json.JsonArray;
|
||||
import com.couchbase.client.java.json.JsonObject;
|
||||
import com.couchbase.client.java.json.JsonValue;
|
||||
import com.couchbase.client.java.json.JsonValueModule;
|
||||
import com.couchbase.client.java.query.QueryOptions;
|
||||
import com.couchbase.client.java.query.QueryScanConsistency;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Disabled;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.data.couchbase.config.AbstractCouchbaseConfiguration;
|
||||
import org.springframework.data.couchbase.core.CouchbaseTemplate;
|
||||
import org.springframework.data.couchbase.core.query.N1QLExpression;
|
||||
import org.springframework.data.couchbase.core.query.N1QLQuery;
|
||||
import org.springframework.data.couchbase.domain.Address;
|
||||
import org.springframework.data.couchbase.domain.PersonValueRepository;
|
||||
import org.springframework.data.couchbase.domain.User;
|
||||
import org.springframework.data.couchbase.domain.UserEncrypted;
|
||||
import org.springframework.data.couchbase.domain.UserEncryptedRepository;
|
||||
import org.springframework.data.couchbase.domain.UserRepository;
|
||||
import org.springframework.data.couchbase.repository.config.EnableCouchbaseRepositories;
|
||||
import org.springframework.data.couchbase.util.ClusterAwareIntegrationTests;
|
||||
import org.springframework.data.couchbase.util.ClusterType;
|
||||
import org.springframework.data.couchbase.util.IgnoreWhen;
|
||||
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
|
||||
|
||||
import com.couchbase.client.core.deps.io.netty.handler.ssl.util.InsecureTrustManagerFactory;
|
||||
import com.couchbase.client.core.encryption.CryptoManager;
|
||||
import com.couchbase.client.core.env.SecurityConfig;
|
||||
import com.couchbase.client.java.env.ClusterEnvironment;
|
||||
import org.testcontainers.shaded.org.bouncycastle.asn1.isismtt.x509.AdditionalInformationSyntax;
|
||||
|
||||
/**
|
||||
* Repository KV tests
|
||||
*
|
||||
* @author Michael Nitschinger
|
||||
* @author Michael Reiche
|
||||
*/
|
||||
@SpringJUnitConfig(CouchbaseRepositoryFieldLevelEncryptionIntegrationTests.Config.class)
|
||||
@IgnoreWhen(clusterTypes = ClusterType.MOCKED)
|
||||
public class CouchbaseRepositoryFieldLevelEncryptionIntegrationTests extends ClusterAwareIntegrationTests {
|
||||
|
||||
@Autowired
|
||||
UserEncryptedRepository userEncryptedRepository;
|
||||
|
||||
@Autowired PersonValueRepository personValueRepository;
|
||||
@Autowired CouchbaseTemplate couchbaseTemplate;
|
||||
|
||||
@BeforeEach
|
||||
public void beforeEach() {
|
||||
super.beforeEach();
|
||||
couchbaseTemplate.removeByQuery(UserEncrypted.class).withConsistency(REQUEST_PLUS).all();
|
||||
couchbaseTemplate.findByQuery(UserEncrypted.class).withConsistency(REQUEST_PLUS).all();
|
||||
}
|
||||
|
||||
@Test
|
||||
void javaSDKEncryption() {
|
||||
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
@IgnoreWhen(clusterTypes = ClusterType.MOCKED)
|
||||
void saveAndFindById() {
|
||||
UserEncrypted user = new UserEncrypted(UUID.randomUUID().toString(), "saveAndFindById", "l", "hello");
|
||||
Address address = new Address(); // plaintext address with encrypted street
|
||||
address.setEncStreet("Olcott Street");
|
||||
address.setCity("Santa Clara");
|
||||
user.addAddress(address);
|
||||
user.setHomeAddress(address);
|
||||
// cannot set encrypted fields within encrypted objects (i.e. setEncAddress())
|
||||
Address encAddress = new Address(); // encrypted address with plaintext street.
|
||||
encAddress.setStreet("Castro St");
|
||||
encAddress.setCity("Mountain View");
|
||||
user.setEncAddress(encAddress);
|
||||
assertFalse(userEncryptedRepository.existsById(user.getId()));
|
||||
userEncryptedRepository.save(user);
|
||||
Optional<UserEncrypted> found = userEncryptedRepository.findById(user.getId());
|
||||
assertTrue(found.isPresent());
|
||||
System.err.println(found.get());
|
||||
found.ifPresent(u -> assertEquals(user, u));
|
||||
assertTrue(userEncryptedRepository.existsById(user.getId()));
|
||||
//userEncryptedRepository.delete(user);
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableCouchbaseRepositories("org.springframework.data.couchbase")
|
||||
static class Config extends AbstractCouchbaseConfiguration {
|
||||
|
||||
@Override
|
||||
public String getConnectionString() {
|
||||
return connectionString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUserName() {
|
||||
return config().adminUsername();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getPassword() {
|
||||
return config().adminPassword();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getBucketName() {
|
||||
return bucketName();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void configureEnvironment(ClusterEnvironment.Builder builder) {
|
||||
if (config().isUsingCloud()) {
|
||||
builder.securityConfig(
|
||||
SecurityConfig.builder().trustManagerFactory(InsecureTrustManagerFactory.INSTANCE).enableTls(true));
|
||||
}
|
||||
CryptoManager cryptoManager = cryptoManager();
|
||||
builder.cryptoManager(cryptoManager).build();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected CryptoManager cryptoManager() {
|
||||
|
||||
Decrypter decrypter = new Decrypter() {
|
||||
@Override
|
||||
public String algorithm() {
|
||||
return "myAlg";
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] decrypt(EncryptionResult encrypted) {
|
||||
return Base64.getDecoder().decode(encrypted.getString("ciphertext"));
|
||||
}
|
||||
};
|
||||
|
||||
Encrypter encrypter = new Encrypter() {
|
||||
@Override
|
||||
public EncryptionResult encrypt(byte[] plaintext) {
|
||||
return EncryptionResult
|
||||
.fromMap(mapOf("alg", "myAlg", "ciphertext", Base64.getEncoder().encodeToString(plaintext)));
|
||||
}
|
||||
};
|
||||
Map<String, byte[]> keyMap = new HashMap();
|
||||
keyMap.put("myKey",
|
||||
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,
|
||||
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, });
|
||||
Keyring keyring = Keyring.fromMap(keyMap);
|
||||
// Provider secProvider;
|
||||
AeadAes256CbcHmacSha512Provider provider = AeadAes256CbcHmacSha512Provider.builder().keyring(keyring)
|
||||
/*.securityProvider(secProvider)*/.build();
|
||||
return DefaultCryptoManager.builder().decrypter(provider.decrypter())
|
||||
.defaultEncrypter(provider.encrypterForKey("myKey")).build();
|
||||
}
|
||||
|
||||
byte[] hmacMe(String cbc_secret_key, String cbc_api_message) {
|
||||
try {
|
||||
return hmac("hmacSHA256", cbc_secret_key.getBytes("utf-8"), cbc_api_message.getBytes("utf-8"));
|
||||
} catch (UnsupportedEncodingException | NoSuchAlgorithmException | InvalidKeyException ue) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
static byte[] hmac(String algorithm, byte[] key, byte[] message)
|
||||
throws NoSuchAlgorithmException, InvalidKeyException {
|
||||
Mac mac = Mac.getInstance(algorithm);
|
||||
mac.init(new SecretKeySpec(key, algorithm));
|
||||
return mac.doFinal(message);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user