diff --git a/src/main/java/io/ebeaninternal/server/deploy/BeanFkeyProperty.java b/src/main/java/io/ebeaninternal/server/deploy/BeanFkeyProperty.java index 52bb3b4d4..a6908d315 100644 --- a/src/main/java/io/ebeaninternal/server/deploy/BeanFkeyProperty.java +++ b/src/main/java/io/ebeaninternal/server/deploy/BeanFkeyProperty.java @@ -78,6 +78,11 @@ public final class BeanFkeyProperty implements ElPropertyValue { return false; } + @Override + public Object localEncrypt(Object value) { + throw new IllegalArgumentException("Should not get here?"); + } + @Override public boolean containsFormulaWithJoin() { return false; diff --git a/src/main/java/io/ebeaninternal/server/deploy/BeanProperty.java b/src/main/java/io/ebeaninternal/server/deploy/BeanProperty.java index 8fa2b9af8..7473e01aa 100644 --- a/src/main/java/io/ebeaninternal/server/deploy/BeanProperty.java +++ b/src/main/java/io/ebeaninternal/server/deploy/BeanProperty.java @@ -28,6 +28,7 @@ import io.ebeaninternal.server.query.STreeProperty; import io.ebeaninternal.server.query.SqlBeanLoad; import io.ebeaninternal.server.query.SqlJoinType; import io.ebeaninternal.server.type.DataBind; +import io.ebeaninternal.server.type.LocalEncryptedType; import io.ebeaninternal.server.type.ScalarType; import io.ebeaninternal.server.type.ScalarTypeBoolean; import io.ebeaninternal.server.type.ScalarTypeEnum; @@ -1246,6 +1247,11 @@ public class BeanProperty implements ElPropertyValue, Property, STreeProperty { return dbBind; } + @Override + public Object localEncrypt(Object value) { + return ((LocalEncryptedType)scalarType).localEncrypt(value); + } + /** * Returns true if DB encrypted. */ diff --git a/src/main/java/io/ebeaninternal/server/el/ElPropertyChain.java b/src/main/java/io/ebeaninternal/server/el/ElPropertyChain.java index 5d441391c..7afd21a1f 100644 --- a/src/main/java/io/ebeaninternal/server/el/ElPropertyChain.java +++ b/src/main/java/io/ebeaninternal/server/el/ElPropertyChain.java @@ -174,6 +174,11 @@ public class ElPropertyChain implements ElPropertyValue { return lastElPropertyValue.isLocalEncrypted(); } + @Override + public Object localEncrypt(Object value) { + return lastElPropertyValue.localEncrypt(value); + } + @Override public String getAssocIsEmpty(SpiExpressionRequest request, String path) { return lastElPropertyValue.getAssocIsEmpty(request, path); diff --git a/src/main/java/io/ebeaninternal/server/el/ElPropertyValue.java b/src/main/java/io/ebeaninternal/server/el/ElPropertyValue.java index bbae84850..f69b33040 100644 --- a/src/main/java/io/ebeaninternal/server/el/ElPropertyValue.java +++ b/src/main/java/io/ebeaninternal/server/el/ElPropertyValue.java @@ -52,6 +52,11 @@ public interface ElPropertyValue extends ElPropertyDeploy, ExpressionPath { */ boolean isDbEncrypted(); + /** + * Encrypt the input value return the encrypted value. + */ + Object localEncrypt(Object value); + /** * Return the value ensuring objects prior to the top scalar property are * automatically populated. diff --git a/src/main/java/io/ebeaninternal/server/expression/SimpleExpression.java b/src/main/java/io/ebeaninternal/server/expression/SimpleExpression.java index 4069999c3..f694c78d0 100644 --- a/src/main/java/io/ebeaninternal/server/expression/SimpleExpression.java +++ b/src/main/java/io/ebeaninternal/server/expression/SimpleExpression.java @@ -84,10 +84,11 @@ public class SimpleExpression extends AbstractValueExpression { // bind the key as well as the value String encryptKey = prop.getBeanProperty().getEncryptKey().getStringValue(); request.addBindEncryptKey(encryptKey); + } else if (prop.isLocalEncrypted()) { + Object bindVal = prop.localEncrypt(value()); + request.addBindEncryptKey(bindVal); + return; } - //else if (prop.isLocalEncrypted()) { - // not supporting this for equals (but probably could) - // prop.getBeanProperty().getScalarType(); } request.addBindValue(value()); diff --git a/src/main/java/io/ebeaninternal/server/type/LocalEncryptedType.java b/src/main/java/io/ebeaninternal/server/type/LocalEncryptedType.java new file mode 100644 index 000000000..950bb10a1 --- /dev/null +++ b/src/main/java/io/ebeaninternal/server/type/LocalEncryptedType.java @@ -0,0 +1,12 @@ +package io.ebeaninternal.server.type; + +/** + * Scalar type that wraps a local/client side encrypted value. + */ +public interface LocalEncryptedType { + + /** + * Encrypt and return the un-encrypted value. + */ + Object localEncrypt(Object value); +} diff --git a/src/main/java/io/ebeaninternal/server/type/ScalarTypeEncryptedWrapper.java b/src/main/java/io/ebeaninternal/server/type/ScalarTypeEncryptedWrapper.java index 1b15dbc11..ae0b75be3 100644 --- a/src/main/java/io/ebeaninternal/server/type/ScalarTypeEncryptedWrapper.java +++ b/src/main/java/io/ebeaninternal/server/type/ScalarTypeEncryptedWrapper.java @@ -9,7 +9,7 @@ import java.io.DataOutput; import java.io.IOException; import java.sql.SQLException; -public class ScalarTypeEncryptedWrapper implements ScalarType { +public class ScalarTypeEncryptedWrapper implements ScalarType, LocalEncryptedType { private final ScalarType wrapped; @@ -23,6 +23,12 @@ public class ScalarTypeEncryptedWrapper implements ScalarType { this.dataEncryptSupport = dataEncryptSupport; } + @Override + public Object localEncrypt(Object value) { + String formatValue = wrapped.format(value); + return dataEncryptSupport.encryptObject(formatValue); + } + @Override public long asVersion(T value) { throw new RuntimeException("not supported"); diff --git a/src/test/java/org/tests/basic/encrypt/TestEncryptClientSide.java b/src/test/java/org/tests/basic/encrypt/TestEncryptClientSide.java new file mode 100644 index 000000000..e5d59a07b --- /dev/null +++ b/src/test/java/org/tests/basic/encrypt/TestEncryptClientSide.java @@ -0,0 +1,55 @@ +package org.tests.basic.encrypt; + +import io.ebean.BaseTestCase; +import io.ebean.Ebean; +import org.junit.Test; +import org.tests.model.basic.EBasicEncryptClient; + +import java.time.LocalDate; + +import static org.assertj.core.api.Assertions.assertThat; + +public class TestEncryptClientSide extends BaseTestCase { + + @Test + public void insertUpdate() { + + LocalDate today = LocalDate.now(); + + EBasicEncryptClient bean = new EBasicEncryptClient(); + bean.setDescription("hello"); + bean.setStatus(EBasicEncryptClient.Status.ONE); + bean.setDob(today); + + Ebean.save(bean); + + EBasicEncryptClient found = Ebean.find(EBasicEncryptClient.class) + .where() + .eq("description", "hello") + .eq("status", EBasicEncryptClient.Status.ONE) + .eq("dob", today) + .findOne(); + + assertThat(found).isNotNull(); + assertThat(found.getDescription()).isEqualTo("hello"); + assertThat(found.getStatus()).isEqualTo(EBasicEncryptClient.Status.ONE); + assertThat(found.getDob()).isEqualTo(today); + + + found.setDescription("goodbye"); + found.setStatus(EBasicEncryptClient.Status.TWO); + Ebean.save(found); + + found = Ebean.find(EBasicEncryptClient.class) + .where() + .eq("description", "goodbye") + .eq("status", EBasicEncryptClient.Status.TWO) + .eq("dob", today) + .findOne(); + + assertThat(found).isNotNull(); + assertThat(found.getDescription()).isEqualTo("goodbye"); + + Ebean.delete(found); + } +} diff --git a/src/test/java/org/tests/model/basic/EBasicEncryptClient.java b/src/test/java/org/tests/model/basic/EBasicEncryptClient.java new file mode 100644 index 000000000..64426c075 --- /dev/null +++ b/src/test/java/org/tests/model/basic/EBasicEncryptClient.java @@ -0,0 +1,87 @@ +package org.tests.model.basic; + +import io.ebean.annotation.Encrypted; + +import javax.persistence.Entity; +import javax.persistence.EnumType; +import javax.persistence.Enumerated; +import javax.persistence.Id; +import javax.persistence.Table; +import javax.persistence.Version; +import java.time.LocalDate; + +@Entity +@Table(name = "e_basicenc_client") +public class EBasicEncryptClient { + + public enum Status { + ONE, + TWO + } + + @Id + long id; + + String name; + + @Encrypted(dbLength = 80, dbEncryption = false) + String description; + + @Encrypted(dbLength = 20, dbEncryption = false) + LocalDate dob; + + @Enumerated(EnumType.ORDINAL) + @Encrypted(dbLength = 20, dbEncryption = false) + Status status; + + @Version + long version; + + public long getId() { + return id; + } + + public void setId(long id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public LocalDate getDob() { + return dob; + } + + public void setDob(LocalDate dob) { + this.dob = dob; + } + + public Status getStatus() { + return status; + } + + public void setStatus(Status status) { + this.status = status; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public long getVersion() { + return version; + } + + public void setVersion(long version) { + this.version = version; + } +}