cayenne-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From ntimof...@apache.org
Subject [cayenne] branch STABLE-4.0 updated: CAY-2584 Crypto: can't use ColumnSelect with encrypted columns
Date Thu, 06 Jun 2019 15:09:54 GMT
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 <stariy95@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


Mime
View raw message