mirror of
https://github.com/Alfresco/alfresco-community-repo.git
synced 2025-08-14 17:58:59 +00:00
Fixed mass encryption/decryption of properties and related tests
- ALF-8646: RINF 38: Text data encryption - ALF-9055: RINF 38: Support encryption against existing data git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/HEAD/root@28508 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261
This commit is contained in:
@@ -73,6 +73,7 @@ import org.alfresco.service.namespace.RegexQNamePattern;
|
||||
import org.alfresco.service.transaction.TransactionService;
|
||||
import org.alfresco.util.BaseSpringTest;
|
||||
import org.alfresco.util.GUID;
|
||||
import org.alfresco.util.TestWithUserUtils;
|
||||
import org.hibernate.dialect.DB2Dialect;
|
||||
import org.hibernate.dialect.Dialect;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
@@ -1743,6 +1744,48 @@ public abstract class BaseNodeServiceTest extends BaseSpringTest
|
||||
assertTrue("Serialization/deserialization failed", checkPropertyQname instanceof QName);
|
||||
}
|
||||
|
||||
public void testEncryptionAndDecryptionOfProperties()
|
||||
{
|
||||
QName valueToEncrypt = PROP_QNAME_CONTENT_VALUE;
|
||||
QName value = PROP_QNAME_CONTENT_VALUE;
|
||||
|
||||
// Test single property encryption/decryption
|
||||
Serializable encryptedProperty = metadataEncryptor.encrypt(PROP_QNAME_ENCRYPTED_VALUE, valueToEncrypt);
|
||||
assertTrue("Not a SealedObject", encryptedProperty instanceof SealedObject);
|
||||
Serializable encryptedPropertyAgain = metadataEncryptor.encrypt(PROP_QNAME_ENCRYPTED_VALUE, encryptedProperty);
|
||||
assertTrue("Re-encryption not expected", encryptedProperty == encryptedPropertyAgain);
|
||||
Serializable decryptedProperty = metadataEncryptor.decrypt(PROP_QNAME_ENCRYPTED_VALUE, encryptedProperty);
|
||||
assertEquals("Value not decrypted correctly", valueToEncrypt, decryptedProperty);
|
||||
|
||||
// Test mass property encryption/decryption
|
||||
Map<QName, Serializable> properties = new HashMap<QName, Serializable>(5);
|
||||
properties.put(PROP_QNAME_ENCRYPTED_VALUE, valueToEncrypt);
|
||||
properties.put(PROP_QNAME_QNAME_VALUE, value);
|
||||
Map<QName, Serializable> encryptedProperties = metadataEncryptor.encrypt(properties);
|
||||
assertTrue("Not a SealedObject", encryptedProperties.get(PROP_QNAME_ENCRYPTED_VALUE) instanceof SealedObject);
|
||||
assertTrue("Should not encrypt", encryptedProperties.get(PROP_QNAME_QNAME_VALUE) instanceof QName);
|
||||
Map<QName, Serializable> encryptedPropertiesAgain = metadataEncryptor.encrypt(encryptedProperties);
|
||||
assertTrue("Map should not change", encryptedProperties == encryptedPropertiesAgain);
|
||||
assertTrue(
|
||||
"Re-encryption not expected",
|
||||
encryptedProperties.get(PROP_QNAME_ENCRYPTED_VALUE) == encryptedPropertiesAgain.get(PROP_QNAME_ENCRYPTED_VALUE));
|
||||
assertTrue("Should not encrypt", encryptedProperties.get(PROP_QNAME_QNAME_VALUE) instanceof QName);
|
||||
Map<QName, Serializable> decryptedProperties = metadataEncryptor.decrypt(encryptedProperties);
|
||||
assertEquals("Values not decrypted correctly", valueToEncrypt, decryptedProperties.get(PROP_QNAME_ENCRYPTED_VALUE));
|
||||
assertEquals("Values not decrypted correctly", value, decryptedProperties.get(PROP_QNAME_QNAME_VALUE));
|
||||
|
||||
// Check that nulls are handled
|
||||
Map<QName, Serializable> propertiesNull = new HashMap<QName, Serializable>(5);
|
||||
propertiesNull.put(PROP_QNAME_ENCRYPTED_VALUE, null);
|
||||
propertiesNull.put(PROP_QNAME_QNAME_VALUE, null);
|
||||
Map<QName, Serializable> encryptedPropertiesNull = metadataEncryptor.encrypt(propertiesNull);
|
||||
assertTrue("Map should not change", encryptedPropertiesNull == propertiesNull);
|
||||
assertNull("Null should remain", encryptedPropertiesNull.get(PROP_QNAME_ENCRYPTED_VALUE));
|
||||
Map<QName, Serializable> decryptedPropertiesNull = metadataEncryptor.decrypt(encryptedPropertiesNull);
|
||||
assertTrue("Map should not change", encryptedPropertiesNull == decryptedPropertiesNull);
|
||||
assertNull("Null should remain", decryptedPropertiesNull.get(PROP_QNAME_ENCRYPTED_VALUE));
|
||||
}
|
||||
|
||||
/**
|
||||
* Check that <b>d:encrypted</b> properties work correctly.
|
||||
*/
|
||||
@@ -1759,7 +1802,7 @@ public abstract class BaseNodeServiceTest extends BaseSpringTest
|
||||
assertTrue("Properties not encrypted", checkProperty instanceof SealedObject);
|
||||
|
||||
// create node
|
||||
NodeRef nodeRef = nodeService.createNode(
|
||||
final NodeRef nodeRef = nodeService.createNode(
|
||||
rootNodeRef,
|
||||
ASSOC_TYPE_QNAME_TEST_CHILDREN,
|
||||
QName.createQName("pathA"),
|
||||
@@ -1774,10 +1817,46 @@ public abstract class BaseNodeServiceTest extends BaseSpringTest
|
||||
checkProperty = checkProperties.get(PROP_QNAME_ENCRYPTED_VALUE);
|
||||
assertTrue("Encrypted property not persisted", checkProperty instanceof SealedObject);
|
||||
|
||||
// Decrypt individual property
|
||||
checkProperty = metadataEncryptor.decrypt(PROP_QNAME_ENCRYPTED_VALUE, checkProperty);
|
||||
assertEquals("Bulk property decryption failed", property, checkProperty);
|
||||
|
||||
// Now decrypt en-masse
|
||||
checkProperties = metadataEncryptor.decrypt(checkProperties);
|
||||
checkProperty = checkProperties.get(PROP_QNAME_ENCRYPTED_VALUE);
|
||||
assertEquals("Bulk property decryption failed", property, checkProperty);
|
||||
|
||||
// Now make sure that the value can be null
|
||||
RetryingTransactionCallback<Void> setNullPropCallback = new RetryingTransactionCallback<Void>()
|
||||
{
|
||||
@Override
|
||||
public Void execute() throws Throwable
|
||||
{
|
||||
nodeService.setProperty(nodeRef, PROP_QNAME_ENCRYPTED_VALUE, null);
|
||||
return null;
|
||||
}
|
||||
};
|
||||
retryingTransactionHelper.doInTransaction(setNullPropCallback);
|
||||
|
||||
// Finally, make sure that it fails if we don't encrypt
|
||||
try
|
||||
{
|
||||
RetryingTransactionCallback<Void> setUnencryptedPropCallback = new RetryingTransactionCallback<Void>()
|
||||
{
|
||||
@Override
|
||||
public Void execute() throws Throwable
|
||||
{
|
||||
nodeService.setProperty(nodeRef, PROP_QNAME_ENCRYPTED_VALUE, "No encrypted");
|
||||
return null;
|
||||
}
|
||||
};
|
||||
retryingTransactionHelper.doInTransaction(setUnencryptedPropCallback);
|
||||
fail("Failed to detect unencrypted property"); // This behaviour may change
|
||||
}
|
||||
catch (RuntimeException e)
|
||||
{
|
||||
// Expected
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
|
@@ -2,7 +2,9 @@ package org.alfresco.repo.node.encryption;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import javax.crypto.SealedObject;
|
||||
|
||||
@@ -76,11 +78,40 @@ public class MetadataEncryptor
|
||||
{
|
||||
return inbound;
|
||||
}
|
||||
if (inbound instanceof SealedObject)
|
||||
{
|
||||
return inbound;
|
||||
}
|
||||
Serializable outbound = encryptor.sealObject(KeyProvider.ALIAS_METADATA, null, inbound);
|
||||
// Done
|
||||
return outbound;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decrypt a property if the data definition (model-specific) requires it.
|
||||
* <p/>
|
||||
* This method can only be called by the 'system' user.
|
||||
*
|
||||
* @param propertyQName the property qualified name
|
||||
* @param inbound the property to decrypt
|
||||
* @return the decrypted property or the original if it wasn't encrypted
|
||||
*/
|
||||
public Serializable decrypt(QName propertyQName, Serializable inbound)
|
||||
{
|
||||
PropertyDefinition propertyDef = dictionaryService.getProperty(propertyQName);
|
||||
if (inbound == null || propertyDef == null || !(propertyDef.getDataType().getName().equals(DataTypeDefinition.ENCRYPTED)))
|
||||
{
|
||||
return inbound;
|
||||
}
|
||||
if (!(inbound instanceof SealedObject))
|
||||
{
|
||||
return inbound;
|
||||
}
|
||||
Serializable outbound = encryptor.unsealObject(KeyProvider.ALIAS_METADATA, inbound);
|
||||
// Done
|
||||
return outbound;
|
||||
}
|
||||
|
||||
/**
|
||||
* Encrypt properties if their data definition (model-specific) requires it.
|
||||
* The values provided can be mixed; values will be encrypted only if required.
|
||||
@@ -93,36 +124,35 @@ public class MetadataEncryptor
|
||||
*/
|
||||
public Map<QName, Serializable> encrypt(Map<QName, Serializable> inbound)
|
||||
{
|
||||
boolean encrypt = false;
|
||||
Set<QName> encryptedProperties = new HashSet<QName>(5);
|
||||
for (Map.Entry<QName, Serializable> entry : inbound.entrySet())
|
||||
{
|
||||
QName key = entry.getKey();
|
||||
PropertyDefinition propertyDef = dictionaryService.getProperty(key);
|
||||
QName qname = entry.getKey();
|
||||
Serializable value = entry.getValue();
|
||||
PropertyDefinition propertyDef = dictionaryService.getProperty(qname);
|
||||
if (propertyDef != null && (propertyDef.getDataType().getName().equals(DataTypeDefinition.ENCRYPTED)))
|
||||
{
|
||||
encrypt = true;
|
||||
break;
|
||||
if (value != null && !(value instanceof SealedObject))
|
||||
{
|
||||
encryptedProperties.add(qname);
|
||||
}
|
||||
}
|
||||
if (!encrypt)
|
||||
}
|
||||
if (encryptedProperties.isEmpty())
|
||||
{
|
||||
// Nothing to do
|
||||
return inbound;
|
||||
}
|
||||
// Encrypt, in place, using a copied map
|
||||
Map<QName, Serializable> outbound = new HashMap<QName, Serializable>(inbound);
|
||||
for (Map.Entry<QName, Serializable> entry : inbound.entrySet())
|
||||
for (QName propertyQName : encryptedProperties)
|
||||
{
|
||||
Serializable value = entry.getValue();
|
||||
if (value != null && (value instanceof SealedObject))
|
||||
{
|
||||
// Straight copy, i.e. do nothing
|
||||
continue;
|
||||
}
|
||||
// Have to decrypt the value
|
||||
// We have already checked for nulls and conversions
|
||||
Serializable value = inbound.get(propertyQName);
|
||||
// Have to encrypt the value
|
||||
Serializable encryptedValue = encryptor.sealObject(KeyProvider.ALIAS_METADATA, null, value);
|
||||
// Store it back
|
||||
outbound.put(entry.getKey(), encryptedValue);
|
||||
outbound.put(propertyQName, encryptedValue);
|
||||
}
|
||||
// Done
|
||||
return outbound;
|
||||
@@ -142,35 +172,35 @@ public class MetadataEncryptor
|
||||
{
|
||||
checkAuthentication();
|
||||
|
||||
boolean decrypt = false;
|
||||
Set<QName> encryptedProperties = new HashSet<QName>(5);
|
||||
for (Map.Entry<QName, Serializable> entry : inbound.entrySet())
|
||||
{
|
||||
QName qname = entry.getKey();
|
||||
Serializable value = entry.getValue();
|
||||
PropertyDefinition propertyDef = dictionaryService.getProperty(qname);
|
||||
if (propertyDef != null && (propertyDef.getDataType().getName().equals(DataTypeDefinition.ENCRYPTED)))
|
||||
{
|
||||
if (value != null && (value instanceof SealedObject))
|
||||
{
|
||||
decrypt = true;
|
||||
break;
|
||||
encryptedProperties.add(qname);
|
||||
}
|
||||
}
|
||||
if (!decrypt)
|
||||
}
|
||||
if (encryptedProperties.isEmpty())
|
||||
{
|
||||
// Nothing to do
|
||||
return inbound;
|
||||
}
|
||||
// Decrypt, in place, using a copied map
|
||||
Map<QName, Serializable> outbound = new HashMap<QName, Serializable>(inbound);
|
||||
for (Map.Entry<QName, Serializable> entry : inbound.entrySet())
|
||||
for (QName propertyQName : encryptedProperties)
|
||||
{
|
||||
Serializable value = entry.getValue();
|
||||
if (value != null && (value instanceof SealedObject))
|
||||
{
|
||||
// Straight copy, i.e. do nothing
|
||||
continue;
|
||||
}
|
||||
// We have already checked for nulls and conversions
|
||||
Serializable value = inbound.get(propertyQName);
|
||||
// Have to decrypt the value
|
||||
Serializable decryptedValue = encryptor.unsealObject(KeyProvider.ALIAS_METADATA, value);
|
||||
Serializable unencryptedValue = encryptor.unsealObject(KeyProvider.ALIAS_METADATA, value);
|
||||
// Store it back
|
||||
outbound.put(entry.getKey(), decryptedValue);
|
||||
outbound.put(propertyQName, unencryptedValue);
|
||||
}
|
||||
// Done
|
||||
return outbound;
|
||||
|
@@ -19,6 +19,8 @@
|
||||
package org.alfresco.repo.node.integrity;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.io.Serializable;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.transaction.UserTransaction;
|
||||
|
||||
@@ -28,6 +30,7 @@ import org.alfresco.model.ContentModel;
|
||||
import org.alfresco.repo.dictionary.DictionaryDAO;
|
||||
import org.alfresco.repo.dictionary.M2Model;
|
||||
import org.alfresco.repo.node.BaseNodeServiceTest;
|
||||
import org.alfresco.repo.node.encryption.MetadataEncryptor;
|
||||
import org.alfresco.repo.security.authentication.AuthenticationComponent;
|
||||
import org.alfresco.service.ServiceRegistry;
|
||||
import org.alfresco.service.cmr.repository.NodeRef;
|
||||
@@ -97,7 +100,7 @@ public class IntegrityTest extends TestCase
|
||||
private ServiceRegistry serviceRegistry;
|
||||
private NodeService nodeService;
|
||||
private NodeRef rootNodeRef;
|
||||
private PropertyMap allProperties;
|
||||
private Map<QName, Serializable> allProperties;
|
||||
private UserTransaction txn;
|
||||
private AuthenticationComponent authenticationComponent;
|
||||
|
||||
@@ -117,6 +120,8 @@ public class IntegrityTest extends TestCase
|
||||
integrityChecker.setTraceOn(true);
|
||||
integrityChecker.setMaxErrorsPerTransaction(100); // we want to count the correct number of errors
|
||||
|
||||
MetadataEncryptor encryptor = (MetadataEncryptor) ctx.getBean("metadataEncryptor");
|
||||
|
||||
serviceRegistry = (ServiceRegistry) ctx.getBean(ServiceRegistry.SERVICE_REGISTRY);
|
||||
nodeService = serviceRegistry.getNodeService();
|
||||
this.authenticationComponent = (AuthenticationComponent)ctx.getBean("authenticationComponent");
|
||||
@@ -141,6 +146,7 @@ public class IntegrityTest extends TestCase
|
||||
allProperties.put(TEST_PROP_INT_B, "456");
|
||||
allProperties.put(TEST_PROP_ENCRYPTED_A, "ABC");
|
||||
allProperties.put(TEST_PROP_ENCRYPTED_B, "DEF");
|
||||
allProperties = encryptor.encrypt(allProperties);
|
||||
}
|
||||
|
||||
public void tearDown() throws Exception
|
||||
@@ -166,7 +172,7 @@ public class IntegrityTest extends TestCase
|
||||
/**
|
||||
* Create a node of the given type, and hanging off the root node
|
||||
*/
|
||||
private NodeRef createNode(String name, QName type, PropertyMap properties)
|
||||
private NodeRef createNode(String name, QName type, Map<QName, Serializable> properties)
|
||||
{
|
||||
return nodeService.createNode(
|
||||
rootNodeRef,
|
||||
@@ -246,9 +252,18 @@ public class IntegrityTest extends TestCase
|
||||
}
|
||||
|
||||
public void testCreateWithoutEncryption() throws Exception
|
||||
{
|
||||
allProperties.put(TEST_PROP_ENCRYPTED_A, "Not encrypted");
|
||||
try
|
||||
{
|
||||
NodeRef nodeRef = createNode("abc", TEST_TYPE_WITH_ENCRYPTED_PROPERTIES, allProperties);
|
||||
checkIntegrityExpectFailure("Failed to detect unencrypted properties", 2);
|
||||
fail("Current detection of unencrypted properties is done by NodeService.");
|
||||
}
|
||||
catch (Throwable e)
|
||||
{
|
||||
// Expected
|
||||
}
|
||||
// checkIntegrityExpectFailure("Failed to detect unencrypted properties", 2);
|
||||
}
|
||||
|
||||
public void testCreateWithoutPropertiesForAspect() throws Exception
|
||||
|
Reference in New Issue
Block a user