You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cayenne.apache.org by nt...@apache.org on 2019/06/06 15:09:54 UTC
[cayenne] branch STABLE-4.0 updated: CAY-2584 Crypto: can't use
ColumnSelect with encrypted columns
This is an automated email from the ASF dual-hosted git repository.
ntimofeev pushed a commit to branch STABLE-4.0
in repository https://gitbox.apache.org/repos/asf/cayenne.git
The following commit(s) were added to refs/heads/STABLE-4.0 by this push:
new 806b5ca CAY-2584 Crypto: can't use ColumnSelect with encrypted columns
806b5ca is described below
commit 806b5ca56ed7e6d95f10040534150f11534ade06
Author: Nikita Timofeev <st...@gmail.com>
AuthorDate: Thu Jun 6 18:09:47 2019 +0300
CAY-2584 Crypto: can't use ColumnSelect with encrypted columns
---
RELEASE-NOTES.txt | 3 +-
.../reader/CryptoRowReaderFactoryDecorator.java | 155 ++++++++++++++++-----
.../apache/cayenne/crypto/Runtime_AES128_IT.java | 100 +++++++++++++
.../cayenne/access/jdbc/ColumnDescriptor.java | 4 +
.../jdbc/reader/DefaultRowReaderFactory.java | 14 +-
.../translator/select/DefaultSelectTranslator.java | 26 ++--
6 files changed, 249 insertions(+), 53 deletions(-)
diff --git a/RELEASE-NOTES.txt b/RELEASE-NOTES.txt
index 6fdf056..01804da 100644
--- a/RELEASE-NOTES.txt
+++ b/RELEASE-NOTES.txt
@@ -13,19 +13,20 @@ Date:
----------------------------------
Changes/New Features:
+
CAY-2570 Use MySQL adapter for latest versions of MariaDB
Bug Fixes:
CAY-2550 Modeler: ObjAttribute inspector modifies wrong columns in attribute table
CAY-2559 Modeler: Warning dialog shows wrong information after changing target entity in dbRelationship
+CAY-2584 Crypto: can't use ColumnSelect with encrypted columns
----------------------------------
Release: 4.0.1
Date: December 20, 2018
----------------------------------
-=======
Changes/New Features:
CAY-2473 Modeler: cleanup attributes and relationship editors
diff --git a/cayenne-crypto/src/main/java/org/apache/cayenne/crypto/reader/CryptoRowReaderFactoryDecorator.java b/cayenne-crypto/src/main/java/org/apache/cayenne/crypto/reader/CryptoRowReaderFactoryDecorator.java
index ab2c08d..e60600a 100644
--- a/cayenne-crypto/src/main/java/org/apache/cayenne/crypto/reader/CryptoRowReaderFactoryDecorator.java
+++ b/cayenne-crypto/src/main/java/org/apache/cayenne/crypto/reader/CryptoRowReaderFactoryDecorator.java
@@ -20,6 +20,7 @@ package org.apache.cayenne.crypto.reader;
import org.apache.cayenne.access.jdbc.ColumnDescriptor;
import org.apache.cayenne.access.jdbc.RowDescriptor;
+import org.apache.cayenne.access.jdbc.reader.DefaultRowReaderFactory;
import org.apache.cayenne.access.jdbc.reader.RowReader;
import org.apache.cayenne.access.jdbc.reader.RowReaderFactory;
import org.apache.cayenne.access.types.ExtendedType;
@@ -27,68 +28,70 @@ import org.apache.cayenne.access.types.ExtendedTypeMap;
import org.apache.cayenne.crypto.map.ColumnMapper;
import org.apache.cayenne.crypto.transformer.MapTransformer;
import org.apache.cayenne.crypto.transformer.TransformerFactory;
+import org.apache.cayenne.crypto.transformer.bytes.BytesDecryptor;
+import org.apache.cayenne.crypto.transformer.bytes.BytesTransformerFactory;
+import org.apache.cayenne.crypto.transformer.value.ValueDecryptor;
+import org.apache.cayenne.crypto.transformer.value.ValueTransformerFactory;
import org.apache.cayenne.dba.DbAdapter;
import org.apache.cayenne.dba.TypesMapping;
import org.apache.cayenne.di.Inject;
import org.apache.cayenne.map.DbAttribute;
import org.apache.cayenne.map.ObjAttribute;
+import org.apache.cayenne.query.EntityResultSegment;
import org.apache.cayenne.query.QueryMetadata;
+import org.apache.cayenne.query.ScalarResultSegment;
import java.sql.ResultSet;
import java.util.Map;
-public class CryptoRowReaderFactoryDecorator implements RowReaderFactory {
+public class CryptoRowReaderFactoryDecorator extends DefaultRowReaderFactory {
- private RowReaderFactory delegate;
private TransformerFactory transformerFactory;
private ColumnMapper columnMapper;
+ private BytesTransformerFactory bytesTransformerFactory;
+ private ValueTransformerFactory valueTransformerFactory;
public CryptoRowReaderFactoryDecorator(@Inject RowReaderFactory delegate,
@Inject TransformerFactory transformerFactory,
- @Inject ColumnMapper columnMapper) {
- this.delegate = delegate;
+ @Inject ColumnMapper columnMapper,
+ @Inject BytesTransformerFactory bytesTransformerFactory,
+ @Inject ValueTransformerFactory valueTransformerFactory) {
this.transformerFactory = transformerFactory;
this.columnMapper = columnMapper;
+ this.bytesTransformerFactory = bytesTransformerFactory;
+ this.valueTransformerFactory = valueTransformerFactory;
}
@Override
- public RowReader<?> rowReader(final RowDescriptor descriptor, QueryMetadata queryMetadata, DbAdapter adapter,
+ public RowReader<?> rowReader(RowDescriptor descriptor, QueryMetadata queryMetadata, DbAdapter adapter,
Map<ObjAttribute, ColumnDescriptor> attributeOverrides) {
+ RowDescriptor encryptedRowDescriptor = encryptedRowDescriptor(descriptor, adapter.getExtendedTypes());
+ return super.rowReader(encryptedRowDescriptor, queryMetadata, adapter, attributeOverrides);
+ }
- final RowReader<?> delegateReader = delegate.rowReader(encryptedRowDescriptor(descriptor, adapter.getExtendedTypes()),
- queryMetadata,
- adapter,
- attributeOverrides);
-
- return new RowReader<Object>() {
-
- private boolean decryptorCompiled;
- private MapTransformer decryptor;
-
- private void ensureDecryptorCompiled(Object row) {
- if (!decryptorCompiled) {
- decryptor = transformerFactory.decryptor(descriptor.getColumns(), row);
- decryptorCompiled = true;
- }
- }
-
- @Override
- public Object readRow(ResultSet resultSet) {
- Object row = delegateReader.readRow(resultSet);
-
- ensureDecryptorCompiled(row);
-
- if (decryptor != null) {
-
- @SuppressWarnings({"unchecked", "rawtypes"})
- Map<String, Object> map = (Map) row;
+ @Override
+ protected RowReader<?> createScalarRowReader(RowDescriptor descriptor, QueryMetadata queryMetadata,
+ ScalarResultSegment segment) {
+ RowReader<?> scalarRowReader = super
+ .createScalarRowReader(descriptor, queryMetadata, segment);
+ return new DecoratedScalarRowReader(descriptor.getColumns()[segment.getColumnOffset()], scalarRowReader);
+ }
- decryptor.transform(map);
- }
+ @Override
+ protected RowReader<?> createEntityRowReader(RowDescriptor descriptor, QueryMetadata queryMetadata,
+ EntityResultSegment resultMetadata,
+ PostprocessorFactory postProcessorFactory) {
+ RowReader<?> entityRowReader = super
+ .createEntityRowReader(descriptor, queryMetadata, resultMetadata, postProcessorFactory);
+ return new DecoratedFullRowReader(descriptor, entityRowReader);
+ }
- return row;
- }
- };
+ @Override
+ protected RowReader<?> createFullRowReader(RowDescriptor descriptor, QueryMetadata queryMetadata,
+ PostprocessorFactory postProcessorFactory) {
+ RowReader<?> fullRowReader = super
+ .createFullRowReader(descriptor, queryMetadata, postProcessorFactory);
+ return new DecoratedFullRowReader(descriptor, fullRowReader);
}
protected RowDescriptor encryptedRowDescriptor(RowDescriptor descriptor, ExtendedTypeMap typeMap) {
@@ -121,6 +124,82 @@ public class CryptoRowReaderFactoryDecorator implements RowReaderFactory {
encryptedConverters[i] = t;
}
- return new RowDescriptor(originalColumns, encryptedConverters);
+ return new DecoratedRowDescriptor(descriptor, originalColumns, encryptedConverters);
+ }
+
+ private static class DecoratedRowDescriptor extends RowDescriptor {
+
+ private final RowDescriptor original;
+
+ DecoratedRowDescriptor(RowDescriptor rowDescriptor, ColumnDescriptor[] columns, ExtendedType[] converters) {
+ this.original = rowDescriptor;
+ this.columns = columns;
+ this.converters = converters;
+ }
+
+ public RowDescriptor unwrap() {
+ return original;
+ }
+ }
+
+ private class DecoratedScalarRowReader implements RowReader<Object> {
+ private final RowReader<?> delegateReader;
+ private final ValueDecryptor valueDecryptor;
+ private final BytesDecryptor bytesDecryptor;
+
+ DecoratedScalarRowReader(ColumnDescriptor descriptor, RowReader<?> delegateReader) {
+ this.delegateReader = delegateReader;
+ if(descriptor.getAttribute() != null && columnMapper.isEncrypted(descriptor.getAttribute())) {
+ this.valueDecryptor = valueTransformerFactory.decryptor(descriptor.getAttribute());
+ this.bytesDecryptor = bytesTransformerFactory.decryptor();
+ } else {
+ this.valueDecryptor = null;
+ this.bytesDecryptor = null;
+ }
+ }
+
+ @Override
+ public Object readRow(ResultSet resultSet) {
+ Object value = delegateReader.readRow(resultSet);
+ if(valueDecryptor == null) {
+ return value;
+ }
+ return valueDecryptor.decrypt(bytesDecryptor, value);
+ }
+ }
+
+ private class DecoratedFullRowReader implements RowReader<Object> {
+
+ private final RowDescriptor descriptor;
+ private final RowReader<?> delegateReader;
+ private boolean decryptorCompiled;
+ private MapTransformer decryptor;
+
+ DecoratedFullRowReader(RowDescriptor descriptor, RowReader<?> delegateReader) {
+ this.descriptor = descriptor;
+ this.delegateReader = delegateReader;
+ }
+
+ private void ensureDecryptorCompiled(Object row) {
+ if (!decryptorCompiled) {
+ decryptor = transformerFactory.decryptor(descriptor.getColumns(), row);
+ decryptorCompiled = true;
+ }
+ }
+
+ @Override
+ public Object readRow(ResultSet resultSet) {
+ Object row = delegateReader.readRow(resultSet);
+
+ ensureDecryptorCompiled(row);
+
+ if (decryptor != null) {
+ @SuppressWarnings("unchecked")
+ Map<String, Object> map = (Map<String, Object>) row;
+ decryptor.transform(map);
+ }
+
+ return row;
+ }
}
}
diff --git a/cayenne-crypto/src/test/java/org/apache/cayenne/crypto/Runtime_AES128_IT.java b/cayenne-crypto/src/test/java/org/apache/cayenne/crypto/Runtime_AES128_IT.java
index 1e4b4bc..b6f75d3 100644
--- a/cayenne-crypto/src/test/java/org/apache/cayenne/crypto/Runtime_AES128_IT.java
+++ b/cayenne-crypto/src/test/java/org/apache/cayenne/crypto/Runtime_AES128_IT.java
@@ -23,6 +23,8 @@ import org.apache.cayenne.crypto.db.Table1;
import org.apache.cayenne.crypto.db.Table2;
import org.apache.cayenne.crypto.transformer.value.IntegerConverter;
import org.apache.cayenne.crypto.unit.CryptoUnitUtils;
+import org.apache.cayenne.exp.Property;
+import org.apache.cayenne.query.ObjectSelect;
import org.apache.cayenne.query.SelectQuery;
import org.junit.Before;
import org.junit.Test;
@@ -156,4 +158,102 @@ public class Runtime_AES128_IT extends Runtime_AES128_Base {
assertEquals(61, result.get(0).getCryptoInt());
}
+ @Test
+ public void test_ColumnQueryObject() {
+
+ ObjectContext context = runtime.newContext();
+
+ Table1 t1 = context.newObject(Table1.class);
+ t1.setCryptoInt(1);
+ t1.setCryptoString("test");
+ context.commitChanges();
+
+ List<Table1> result = ObjectSelect
+ .columnQuery(Table1.class, Property.createSelf(Table1.class))
+ .select(context);
+
+ assertEquals(1, result.size());
+ assertEquals(1, result.get(0).getCryptoInt());
+ assertEquals("test", result.get(0).getCryptoString());
+ }
+
+ @Test
+ public void test_ColumnQueryObjectWithPlainScalar() {
+
+ ObjectContext context = runtime.newContext();
+
+ Table1 t1 = context.newObject(Table1.class);
+ t1.setCryptoInt(1);
+ t1.setPlainInt(2);
+ t1.setCryptoString("test");
+ context.commitChanges();
+
+ List<Object[]> result = ObjectSelect
+ .columnQuery(Table1.class, Property.createSelf(Table1.class), Table1.PLAIN_INT)
+ .select(context);
+
+ assertEquals(1, result.size());
+ assertEquals(1, ((Table1)result.get(0)[0]).getCryptoInt());
+ assertEquals("test", ((Table1)result.get(0)[0]).getCryptoString());
+ assertEquals(2, result.get(0)[1]);
+ }
+
+ @Test
+ public void test_ColumnQueryObjectWithEncryptedScalar() {
+
+ ObjectContext context = runtime.newContext();
+
+ Table1 t1 = context.newObject(Table1.class);
+ t1.setCryptoInt(1);
+ t1.setPlainInt(2);
+ t1.setCryptoString("test");
+ context.commitChanges();
+
+ List<Object[]> result = ObjectSelect
+ .columnQuery(Table1.class, Property.createSelf(Table1.class), Table1.CRYPTO_INT)
+ .select(context);
+
+ assertEquals(1, result.size());
+ assertEquals(1, ((Table1)result.get(0)[0]).getCryptoInt());
+ assertEquals("test", ((Table1)result.get(0)[0]).getCryptoString());
+ assertEquals(1, result.get(0)[1]);
+ }
+
+ @Test
+ public void test_ColumnQuerySingleScalar() {
+ ObjectContext context = runtime.newContext();
+
+ Table1 t1 = context.newObject(Table1.class);
+ t1.setCryptoInt(1);
+ t1.setCryptoString("test");
+ context.commitChanges();
+
+ List<String> result = ObjectSelect
+ .columnQuery(Table1.class, Table1.CRYPTO_STRING)
+ .select(context);
+
+ assertEquals(1, result.size());
+ assertEquals("test", result.get(0));
+ }
+
+ @Test
+ public void test_ColumnQueryMultipleScalars() {
+ ObjectContext context = runtime.newContext();
+
+ Table1 t1 = context.newObject(Table1.class);
+ t1.setCryptoInt(1);
+ t1.setCryptoString("test");
+ t1.setPlainInt(2);
+ context.commitChanges();
+
+ List<Object[]> result = ObjectSelect
+ .columnQuery(Table1.class, Table1.CRYPTO_STRING, Table1.CRYPTO_INT, Table1.PLAIN_INT)
+ .select(context);
+
+ assertEquals(1, result.size());
+ assertEquals("test", result.get(0)[0]);
+ assertEquals(1, result.get(0)[1]);
+ assertEquals(2, result.get(0)[2]);
+ }
+
}
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/access/jdbc/ColumnDescriptor.java b/cayenne-server/src/main/java/org/apache/cayenne/access/jdbc/ColumnDescriptor.java
index 632f5e3..cd8d1fc 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/access/jdbc/ColumnDescriptor.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/access/jdbc/ColumnDescriptor.java
@@ -149,6 +149,10 @@ public class ColumnDescriptor {
return name;
}
+ public void setAttribute(DbAttribute attribute) {
+ this.attribute = attribute;
+ }
+
/**
* Returns a DbAttribute for this column. Since columns descriptors can be
* initialized in a context where a DbAttribite is unknown, this method may
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/access/jdbc/reader/DefaultRowReaderFactory.java b/cayenne-server/src/main/java/org/apache/cayenne/access/jdbc/reader/DefaultRowReaderFactory.java
index 02ba45a..35ca6da 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/access/jdbc/reader/DefaultRowReaderFactory.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/access/jdbc/reader/DefaultRowReaderFactory.java
@@ -70,7 +70,7 @@ public class DefaultRowReaderFactory implements RowReaderFactory {
return createEntityRowReader(descriptor, queryMetadata, (EntityResultSegment) segment,
postProcessorFactory);
} else {
- return new ScalarRowReader<Object>(descriptor, (ScalarResultSegment) segment);
+ return createScalarRowReader(descriptor, queryMetadata, (ScalarResultSegment) segment);
}
} else {
CompoundRowReader reader = new CompoundRowReader(resultWidth);
@@ -84,7 +84,7 @@ public class DefaultRowReaderFactory implements RowReaderFactory {
createEntityRowReader(descriptor, queryMetadata, (EntityResultSegment) segment,
postProcessorFactory));
} else {
- reader.addRowReader(i, new ScalarRowReader<>(descriptor, (ScalarResultSegment) segment));
+ reader.addRowReader(i, createScalarRowReader(descriptor, queryMetadata, (ScalarResultSegment) segment));
}
}
@@ -92,7 +92,11 @@ public class DefaultRowReaderFactory implements RowReaderFactory {
}
}
- private RowReader<?> createEntityRowReader(RowDescriptor descriptor, QueryMetadata queryMetadata,
+ protected RowReader<?> createScalarRowReader(RowDescriptor descriptor, QueryMetadata queryMetadata, ScalarResultSegment segment) {
+ return new ScalarRowReader<Object>(descriptor, segment);
+ }
+
+ protected RowReader<?> createEntityRowReader(RowDescriptor descriptor, QueryMetadata queryMetadata,
EntityResultSegment resultMetadata, PostprocessorFactory postProcessorFactory) {
if (queryMetadata.getPageSize() > 0) {
@@ -104,7 +108,7 @@ public class DefaultRowReaderFactory implements RowReaderFactory {
}
}
- private RowReader<?> createFullRowReader(RowDescriptor descriptor, QueryMetadata queryMetadata,
+ protected RowReader<?> createFullRowReader(RowDescriptor descriptor, QueryMetadata queryMetadata,
PostprocessorFactory postProcessorFactory) {
if (queryMetadata.getPageSize() > 0) {
@@ -116,7 +120,7 @@ public class DefaultRowReaderFactory implements RowReaderFactory {
}
}
- private class PostprocessorFactory {
+ protected static class PostprocessorFactory {
private QueryMetadata queryMetadata;
private ExtendedTypeMap extendedTypes;
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/DefaultSelectTranslator.java b/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/DefaultSelectTranslator.java
index 7297fc6..51ec41a 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/DefaultSelectTranslator.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/DefaultSelectTranslator.java
@@ -438,7 +438,8 @@ public class DefaultSelectTranslator extends QueryAssembler implements SelectTra
for(Property<?> property : query.getColumns()) {
joinTableAliasForProperty[0] = null;
- int expressionType = property.getExpression().getType();
+ Expression propertyExpression = property.getExpression();
+ int expressionType = propertyExpression.getType();
// forbid direct selection of toMany relationships columns
if(property.getType() != null && (expressionType == Expression.OBJ_PATH || expressionType == Expression.DB_PATH)
@@ -457,7 +458,7 @@ public class DefaultSelectTranslator extends QueryAssembler implements SelectTra
// Qualifier Translator in case of Object Columns have side effect -
// it will create required joins, that we catch with listener above.
// And we force created join alias for all columns of Object we select.
- qualifierTranslator.setQualifier(property.getExpression());
+ qualifierTranslator.setQualifier(propertyExpression);
qualifierTranslator.setForceJoinForRelations(objectProperty);
StringBuilder builder = qualifierTranslator.appendPart(new StringBuilder());
@@ -482,13 +483,15 @@ public class DefaultSelectTranslator extends QueryAssembler implements SelectTra
builder.append(" AS ").append(alias);
}
- int type = getJdbcTypeForProperty(property);
+ DbAttribute attribute = getAttributeForProperty(propertyExpression);
+ int type = attribute == null ? getJdbcTypeForProperty(property) : attribute.getType();
ColumnDescriptor descriptor;
if(property.getType() != null) {
descriptor = new ColumnDescriptor(builder.toString(), type, property.getType().getCanonicalName());
} else {
descriptor = new ColumnDescriptor(builder.toString(), type);
}
+ descriptor.setAttribute(attribute);
descriptor.setDataRowKey(alias);
descriptor.setIsExpression(true);
columns.add(descriptor);
@@ -509,18 +512,18 @@ public class DefaultSelectTranslator extends QueryAssembler implements SelectTra
return columns;
}
- private int getJdbcTypeForProperty(Property<?> property) {
- int expressionType = property.getExpression().getType();
+ private DbAttribute getAttributeForProperty(Expression propertyExpression) {
+ int expressionType = propertyExpression.getType();
if(expressionType == Expression.OBJ_PATH) {
// Scan obj path, stop as soon as DbAttribute found
for (PathComponent<ObjAttribute, ObjRelationship> component :
- getQueryMetadata().getObjEntity().resolvePath(property.getExpression(), getPathAliases())) {
+ getQueryMetadata().getObjEntity().resolvePath(propertyExpression, getPathAliases())) {
if(component.getAttribute() != null) {
Iterator<CayenneMapEntry> dbPathIterator = component.getAttribute().getDbPathIterator();
while (dbPathIterator.hasNext()) {
Object pathPart = dbPathIterator.next();
if (pathPart instanceof DbAttribute) {
- return ((DbAttribute) pathPart).getType();
+ return ((DbAttribute) pathPart);
}
}
}
@@ -528,12 +531,17 @@ public class DefaultSelectTranslator extends QueryAssembler implements SelectTra
} else if(expressionType == Expression.DB_PATH) {
// Scan db path, stop as soon as DbAttribute found
for (PathComponent<DbAttribute, DbRelationship> component :
- getQueryMetadata().getDbEntity().resolvePath(property.getExpression(), getPathAliases())) {
+ getQueryMetadata().getDbEntity().resolvePath(propertyExpression, getPathAliases())) {
if(component.getAttribute() != null) {
- return component.getAttribute().getType();
+ return component.getAttribute();
}
}
}
+
+ return null;
+ }
+
+ private int getJdbcTypeForProperty(Property<?> property) {
// NOTE: If no attribute found or expression have some other type
// return JDBC type based on Java type of the property.
// This can lead to incorrect behavior in case we deal with some custom type