From c8e32253a5a1c733efa883e45643455f1322e134 Mon Sep 17 00:00:00 2001 From: Roland Praml Date: Wed, 18 Jul 2018 11:41:46 +0200 Subject: [PATCH] FIX: support for ScalarTypeConverter and @Lob (#1454) --- .../server/deploy/parse/DeployUtil.java | 27 +++--- .../java/org/tests/types/EncryptedBinary.java | 36 ++++++++ .../java/org/tests/types/EncryptedString.java | 41 +++++++++ .../org/tests/types/PasswordStoreModel.java | 83 +++++++++++++++++++ .../ScalarTypeEncryptedBinaryConverter.java | 23 +++++ .../ScalarTypeEncryptedStringConverter.java | 22 +++++ .../org/tests/types/TestEncryptedString.java | 48 +++++++++++ 7 files changed, 270 insertions(+), 10 deletions(-) create mode 100644 src/test/java/org/tests/types/EncryptedBinary.java create mode 100644 src/test/java/org/tests/types/EncryptedString.java create mode 100644 src/test/java/org/tests/types/PasswordStoreModel.java create mode 100644 src/test/java/org/tests/types/ScalarTypeEncryptedBinaryConverter.java create mode 100644 src/test/java/org/tests/types/ScalarTypeEncryptedStringConverter.java create mode 100644 src/test/java/org/tests/types/TestEncryptedString.java diff --git a/src/main/java/io/ebeaninternal/server/deploy/parse/DeployUtil.java b/src/main/java/io/ebeaninternal/server/deploy/parse/DeployUtil.java index ccb44db9d..e740bf616 100644 --- a/src/main/java/io/ebeaninternal/server/deploy/parse/DeployUtil.java +++ b/src/main/java/io/ebeaninternal/server/deploy/parse/DeployUtil.java @@ -18,6 +18,7 @@ import io.ebeaninternal.server.deploy.meta.DeployBeanProperty; import io.ebeaninternal.server.type.DataEncryptSupport; import io.ebeaninternal.server.type.ScalarType; import io.ebeaninternal.server.type.ScalarTypeArray; +import io.ebeaninternal.server.type.ScalarTypeWrapper; import io.ebeaninternal.server.type.SimpleAesEncryptor; import io.ebeaninternal.server.type.TypeManager; import org.slf4j.Logger; @@ -278,19 +279,25 @@ public class DeployUtil { */ public void setLobType(DeployBeanProperty prop) { - // is String or byte[] ? used to determine if its a CLOB or BLOB - Class type = prop.getPropertyType(); + ScalarType scalarType = prop.getScalarType(); - // this also sets the lob flag on DeployBeanProperty - int lobType = isClobType(type) ? dbCLOBType : dbBLOBType; + if (scalarType instanceof ScalarTypeWrapper) { + int lobType = scalarType.getJdbcType() == Types.VARCHAR ? dbCLOBType : dbBLOBType; + prop.setDbType(lobType); + } else { + // is String or byte[] ? used to determine if its a CLOB or BLOB + Class type = prop.getPropertyType(); + // this also sets the lob flag on DeployBeanProperty + int lobType = isClobType(type) ? dbCLOBType : dbBLOBType; - ScalarType scalarType = typeManager.getScalarType(type, lobType); - if (scalarType == null) { - // this should never occur actually - throw new RuntimeException("No ScalarType for LOB type [" + type + "] [" + lobType + "]"); + scalarType = typeManager.getScalarType(type, lobType); + if (scalarType == null) { + // this should never occur actually + throw new RuntimeException("No ScalarType for LOB type [" + type + "] [" + lobType + "]"); + } + prop.setDbType(lobType); + prop.setScalarType(scalarType); } - prop.setDbType(lobType); - prop.setScalarType(scalarType); } private boolean isClobType(Class type) { diff --git a/src/test/java/org/tests/types/EncryptedBinary.java b/src/test/java/org/tests/types/EncryptedBinary.java new file mode 100644 index 000000000..c0db92544 --- /dev/null +++ b/src/test/java/org/tests/types/EncryptedBinary.java @@ -0,0 +1,36 @@ +package org.tests.types; + +/** + * Encrypted string. + * + * @author Roland Praml, FOCONIS AG + * + */ +public class EncryptedBinary { + + private final byte[] encryptedData; + + EncryptedBinary(byte[] encryptedData) { + this.encryptedData = encryptedData; + } + + public byte[] getEncryptedData() { + return encryptedData; + } + + public byte[] decrypt() { + return xor(encryptedData); + } + + public static EncryptedBinary encrypt(final byte[] s) { + return new EncryptedBinary(xor(s)); + } + + private static byte[] xor(byte[] s) { + byte[] ret = new byte[s.length]; + for (int i = 0; i < s.length; i++) { + ret[i] = (byte) (s[i] ^ i); + } + return ret; + } +} diff --git a/src/test/java/org/tests/types/EncryptedString.java b/src/test/java/org/tests/types/EncryptedString.java new file mode 100644 index 000000000..c2c0cd05e --- /dev/null +++ b/src/test/java/org/tests/types/EncryptedString.java @@ -0,0 +1,41 @@ +package org.tests.types; + +/** + * Encrypted string. + * + * @author Roland Praml, FOCONIS AG + * + */ +public class EncryptedString { + + private final String encryptedData; + + EncryptedString(String encryptedData) { + this.encryptedData = encryptedData; + } + + public String getEncryptedData() { + return encryptedData; + } + + public String decrypt() { + return rot13(encryptedData); + } + + public static EncryptedString encrypt(final String s) { + return new EncryptedString(rot13(s)); + } + + private static String rot13(String s) { + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < s.length(); i++) { + char c = s.charAt(i); + if (c >= 'a' && c <= 'm') c += 13; + else if (c >= 'A' && c <= 'M') c += 13; + else if (c >= 'n' && c <= 'z') c -= 13; + else if (c >= 'N' && c <= 'Z') c -= 13; + sb.append(c); + } + return sb.toString(); + } +} diff --git a/src/test/java/org/tests/types/PasswordStoreModel.java b/src/test/java/org/tests/types/PasswordStoreModel.java new file mode 100644 index 000000000..07578ed6c --- /dev/null +++ b/src/test/java/org/tests/types/PasswordStoreModel.java @@ -0,0 +1,83 @@ +package org.tests.types; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Lob; +import javax.validation.constraints.Size; + +import org.tests.model.BaseModel; + +@Entity +public class PasswordStoreModel extends BaseModel { + private static final long serialVersionUID = 1L; + // PasswordStoreModel should have the following column definitions in DDL + // enc1 varchar(30), + // enc2 varchar(40), + // enc3 clob, + // enc4 varbinary(30), + // enc5 varbinary(40), + // enc6 blob, + + @Size(max = 30) + private EncryptedString enc1; + @Column(length = 40) + private EncryptedString enc2; + @Lob + private EncryptedString enc3; + + @Size(max = 30) + private EncryptedBinary enc4; + @Column(length = 40) + private EncryptedBinary enc5; + @Lob + private EncryptedBinary enc6; + + public EncryptedString getEnc1() { + return enc1; + } + + public void setEnc1(EncryptedString enc1) { + this.enc1 = enc1; + } + + public EncryptedString getEnc2() { + return enc2; + } + + public void setEnc2(EncryptedString enc2) { + this.enc2 = enc2; + } + + public EncryptedString getEnc3() { + return enc3; + } + + public void setEnc3(EncryptedString enc3) { + this.enc3 = enc3; + } + + public EncryptedBinary getEnc4() { + return enc4; + } + + public void setEnc4(EncryptedBinary enc4) { + this.enc4 = enc4; + } + + public EncryptedBinary getEnc5() { + return enc5; + } + + public void setEnc5(EncryptedBinary enc5) { + this.enc5 = enc5; + } + + public EncryptedBinary getEnc6() { + return enc6; + } + + public void setEnc6(EncryptedBinary enc6) { + this.enc6 = enc6; + } + +} diff --git a/src/test/java/org/tests/types/ScalarTypeEncryptedBinaryConverter.java b/src/test/java/org/tests/types/ScalarTypeEncryptedBinaryConverter.java new file mode 100644 index 000000000..18f86ae6e --- /dev/null +++ b/src/test/java/org/tests/types/ScalarTypeEncryptedBinaryConverter.java @@ -0,0 +1,23 @@ +package org.tests.types; + +import io.ebean.config.ScalarTypeConverter; + +public class ScalarTypeEncryptedBinaryConverter implements ScalarTypeConverter { + + + @Override + public EncryptedBinary getNullValue() { + return null; + } + + @Override + public EncryptedBinary wrapValue(final byte[] scalarType) { + return new EncryptedBinary(scalarType); + } + + @Override + public byte[] unwrapValue(final EncryptedBinary beanType) { + return beanType.getEncryptedData(); + } + +} diff --git a/src/test/java/org/tests/types/ScalarTypeEncryptedStringConverter.java b/src/test/java/org/tests/types/ScalarTypeEncryptedStringConverter.java new file mode 100644 index 000000000..16f3deeed --- /dev/null +++ b/src/test/java/org/tests/types/ScalarTypeEncryptedStringConverter.java @@ -0,0 +1,22 @@ +package org.tests.types; + +import io.ebean.config.ScalarTypeConverter; + +public class ScalarTypeEncryptedStringConverter implements ScalarTypeConverter { + + @Override + public EncryptedString getNullValue() { + return null; + } + + @Override + public EncryptedString wrapValue(final String scalarType) { + return new EncryptedString(scalarType); + } + + @Override + public String unwrapValue(final EncryptedString beanType) { + return beanType.getEncryptedData(); + } + +} diff --git a/src/test/java/org/tests/types/TestEncryptedString.java b/src/test/java/org/tests/types/TestEncryptedString.java new file mode 100644 index 000000000..d54642a20 --- /dev/null +++ b/src/test/java/org/tests/types/TestEncryptedString.java @@ -0,0 +1,48 @@ +package org.tests.types; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.Assert.*; + +import java.nio.charset.StandardCharsets; + +import org.junit.Test; + +import io.ebean.BaseTestCase; +import io.ebean.Ebean; + +public class TestEncryptedString extends BaseTestCase{ + + @Test + public void testName() throws Exception { + PasswordStoreModel model = new PasswordStoreModel(); + + model.setEnc1(EncryptedString.encrypt("Hello")); + model.setEnc2(EncryptedString.encrypt("World")); + model.setEnc3(EncryptedString.encrypt("Test")); + + model.setEnc4(EncryptedBinary.encrypt("Hello".getBytes(StandardCharsets.UTF_8))); + model.setEnc5(EncryptedBinary.encrypt("World".getBytes(StandardCharsets.UTF_8))); + model.setEnc6(EncryptedBinary.encrypt("Test".getBytes(StandardCharsets.UTF_8))); + + + model.save(); + + model = Ebean.find(PasswordStoreModel.class, model.getId()); + + assertThat(model.getEnc1().getEncryptedData()).isNotEqualTo("Hello"); + assertThat(model.getEnc2().getEncryptedData()).isNotEqualTo("World"); + assertThat(model.getEnc3().getEncryptedData()).isNotEqualTo("Test"); + assertThat(model.getEnc4().getEncryptedData()).isNotEqualTo("Hello".getBytes(StandardCharsets.UTF_8)); + assertThat(model.getEnc5().getEncryptedData()).isNotEqualTo("World".getBytes(StandardCharsets.UTF_8)); + assertThat(model.getEnc6().getEncryptedData()).isNotEqualTo("Test".getBytes(StandardCharsets.UTF_8)); + + + assertThat(model.getEnc1().decrypt()).isEqualTo("Hello"); + assertThat(model.getEnc2().decrypt()).isEqualTo("World"); + assertThat(model.getEnc3().decrypt()).isEqualTo("Test"); + assertThat(model.getEnc4().decrypt()).isEqualTo("Hello".getBytes(StandardCharsets.UTF_8)); + assertThat(model.getEnc5().decrypt()).isEqualTo("World".getBytes(StandardCharsets.UTF_8)); + assertThat(model.getEnc6().decrypt()).isEqualTo("Test".getBytes(StandardCharsets.UTF_8)); + + } +}