Data destruction and cleansing

* added content destruction component which ensures all records and classified content are immediately destroyed and optionally cleansed
 * extension to eager content cleaner to allow cleansing to take place just before the content is deleted from the content store
 * base content cleanser
 * simple implementation of DoD 5220-22M cleansing algoritm
 * data cleansing enabled global configuration
 * data cleansing bean configuration
 * unit tests
 * see RM-2463 and RM-2464

+review RM 



git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/modules/recordsmanagement/HEAD@109121 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261
This commit is contained in:
Roy Wetherall
2015-07-29 01:26:49 +00:00
parent 8c441c8835
commit 24b6655eeb
10 changed files with 1055 additions and 0 deletions

View File

@@ -0,0 +1,210 @@
/*
* Copyright (C) 2005-2015 Alfresco Software Limited.
*
* This file is part of Alfresco
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
package org.alfresco.module.org_alfresco_module_rm.content;
import java.util.Collection;
import org.alfresco.module.org_alfresco_module_rm.classification.ContentClassificationService;
import org.alfresco.module.org_alfresco_module_rm.record.RecordService;
import org.alfresco.module.org_alfresco_module_rm.util.AuthenticationUtil;
import org.alfresco.repo.node.NodeServicePolicies;
import org.alfresco.repo.policy.annotation.Behaviour;
import org.alfresco.repo.policy.annotation.BehaviourBean;
import org.alfresco.repo.policy.annotation.BehaviourKind;
import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork;
import org.alfresco.service.cmr.dictionary.DataTypeDefinition;
import org.alfresco.service.cmr.dictionary.DictionaryService;
import org.alfresco.service.cmr.dictionary.PropertyDefinition;
import org.alfresco.service.cmr.repository.ContentData;
import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.cmr.repository.NodeService;
import org.alfresco.service.namespace.QName;
/**
* Content destruction component.
* <p>
* Listens for the destruction of sensitive nodes (classified content and records) and schedules
* all their content for immediate destruction.
* <p>
* If enabled, the content is also cleansed before destruction.
*
* @author Roy Wetherall
* @since 3.0.a
*/
@BehaviourBean
public class ContentDestructionComponent implements NodeServicePolicies.BeforeDeleteNodePolicy
{
/** authentication utils */
private AuthenticationUtil authenticationUtil;
/** content classification service */
private ContentClassificationService contentClassificationService;
/** record service */
private RecordService recordService;
/** eager content store cleaner */
private EagerContentStoreCleaner eagerContentStoreCleaner;
/** dictionary service */
private DictionaryService dictionaryService;
/** node service */
private NodeService nodeService;
/** indicates whether cleansing is enabled or not */
private boolean cleansingEnabled = false;
/**
* @param authenticationUtil authentication utils
*/
public void setAuthenticationUtil(AuthenticationUtil authenticationUtil)
{
this.authenticationUtil = authenticationUtil;
}
/**
* @param contentClassificationService content classification service
*/
public void setContentClassificationService(ContentClassificationService contentClassificationService)
{
this.contentClassificationService = contentClassificationService;
}
/**
* @param recordService record service
*/
public void setRecordService(RecordService recordService)
{
this.recordService = recordService;
}
/**
* @param eagerContentStoreCleaner eager content store cleaner
*/
public void setEagerContentStoreCleaner(EagerContentStoreCleaner eagerContentStoreCleaner)
{
this.eagerContentStoreCleaner = eagerContentStoreCleaner;
}
/**
* @param dictionaryService dictionary service
*/
public void setDictionaryService(DictionaryService dictionaryService)
{
this.dictionaryService = dictionaryService;
}
/**
* @param nodeService node service
*/
public void setNodeService(NodeService nodeService)
{
this.nodeService = nodeService;
}
/**
* @param cleansingEnabled true if cleansing enabled, false otherwise
*/
public void setCleansingEnabled(boolean cleansingEnabled)
{
this.cleansingEnabled = cleansingEnabled;
}
/**
* @return true if cleansing is enabled, false otherwise
*/
public boolean isCleansingEnabled()
{
return cleansingEnabled;
}
/**
* System behaviour implementation that listens for sensitive nodes
* and schedules them for immediate destruction.
* <p>
* Note that the content destruction and cleansing takes place on transaction
* commit. If the transaction is rolled back after this behaviour is encountered
* then the content will not be destroyed or cleansed.
*
* @param nodeRef node reference about to be deleted
*/
@Override
@Behaviour
(
isService = true,
kind = BehaviourKind.CLASS
)
public void beforeDeleteNode(final NodeRef nodeRef)
{
authenticationUtil.runAsSystem(new RunAsWork<Void>()
{
public Void doWork() throws Exception
{
// if enable and content is classified or a record
if (contentClassificationService.isClassified(nodeRef) ||
recordService.isRecord(nodeRef))
{
// then register all content for destruction
registerAllContentForDestruction(nodeRef);
}
return null;
}
});
}
/**
* Registers all content on the given node for destruction.
*
* @param nodeRef node reference
*/
private void registerAllContentForDestruction(NodeRef nodeRef)
{
// get node type
QName nodeType = nodeService.getType(nodeRef);
// get type properties
Collection<QName> nodeProperties = dictionaryService.getAllProperties(nodeType);
for (QName nodeProperty : nodeProperties)
{
// get property definition
PropertyDefinition propertyDefinition = dictionaryService.getProperty(nodeProperty);
// if content property
if (propertyDefinition != null &&
DataTypeDefinition.CONTENT.equals(propertyDefinition.getDataType().getName()))
{
// get content data
ContentData dataContent = (ContentData)nodeService.getProperty(nodeRef, nodeProperty);
// if enabled cleanse content
if (isCleansingEnabled())
{
// register for cleanse then immediate destruction
eagerContentStoreCleaner.registerOrphanedContentUrlForCleansing(dataContent.getContentUrl());
}
else
{
// register for immediate destruction
eagerContentStoreCleaner.registerOrphanedContentUrl(dataContent.getContentUrl(), true);
}
}
}
}
}

View File

@@ -0,0 +1,163 @@
/*
* Copyright (C) 2005-2015 Alfresco Software Limited.
*
* This file is part of Alfresco
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
package org.alfresco.module.org_alfresco_module_rm.content;
import java.io.File;
import java.util.Set;
import org.alfresco.module.org_alfresco_module_rm.content.cleanser.ContentCleanser;
import org.alfresco.module.org_alfresco_module_rm.util.TransactionalResourceHelper;
import org.alfresco.repo.content.ContentStore;
import org.alfresco.repo.content.filestore.FileContentReader;
import org.alfresco.service.cmr.repository.ContentReader;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
/**
* Eager content store cleaner that allows content to be registered for cleansing before
* destruction.
*
* @author Roy Wetherall
* @since 3.0.a
*/
public class EagerContentStoreCleaner extends org.alfresco.repo.content.cleanup.EagerContentStoreCleaner
{
/** transaction resource key */
protected static final String KEY_POST_COMMIT_CLEANSING_URLS = "postCommitCleansingUrls";
/** logger */
private static Log logger = LogFactory.getLog(EagerContentStoreCleaner.class);
/** transactional resource helper */
private TransactionalResourceHelper transactionalResourceHelper;
/** content cleanser */
private ContentCleanser contentCleanser;
/**
* @param transactionResourceHelper transactional resource helper
*/
public void setTransactionalResourceHelper(TransactionalResourceHelper transactionalResourceHelper)
{
this.transactionalResourceHelper = transactionalResourceHelper;
}
/**
* @param contentCleanser content cleanser
*/
public void setContentCleanser(ContentCleanser contentCleanser)
{
this.contentCleanser = contentCleanser;
}
/**
* Registers orphaned content URLs for cleansing
*
* @param contentUrl content url
*/
public void registerOrphanedContentUrlForCleansing(String contentUrl)
{
// make note of content that needs cleansing
Set<String> cleansingUrls = transactionalResourceHelper.getSet(KEY_POST_COMMIT_CLEANSING_URLS);
cleansingUrls.add(contentUrl);
// register as usual
registerOrphanedContentUrl(contentUrl, true);
}
/**
* @see org.alfresco.repo.content.cleanup.EagerContentStoreCleaner#deleteFromStore(java.lang.String, org.alfresco.repo.content.ContentStore)
*/
@Override
protected boolean deleteFromStore(String contentUrl, ContentStore store)
{
// determine if the content requires cleansing or not
Set<String> cleansingUrls = transactionalResourceHelper.getSet(KEY_POST_COMMIT_CLEANSING_URLS);
if (cleansingUrls.contains(contentUrl))
{
// cleanse content before delete
cleanseContent(contentUrl, store);
}
// delete from store
return super.deleteFromStore(contentUrl, store);
}
/**
* Cleanse content
*
* @param contentUrl content url
* @param store content store
*/
private void cleanseContent(String contentUrl, ContentStore store)
{
if (contentCleanser == null)
{
logger.error(
"No content cleanser specified. Unable to cleanse: \n" +
" URL: " + contentUrl + "\n" +
" Source: " + store);
}
else
{
// First check if the content is present at all
ContentReader reader = store.getReader(contentUrl);
if (reader != null && reader.exists())
{
// Call to implementation's shred
if (logger.isDebugEnabled())
{
logger.debug(
"About to cleanse: \n" +
" URL: " + contentUrl + "\n" +
" Source: " + store);
}
try
{
if (reader instanceof FileContentReader)
{
// get file content
FileContentReader fileReader = (FileContentReader) reader;
File file = fileReader.getFile();
// cleanse content
contentCleanser.cleanse(file);
}
}
catch (Throwable e)
{
logger.error(
"Content cleansing failed: \n" +
" URL: " + contentUrl + "\n" +
" Source: " + store + "\n" +
" Reader: " + reader,
e);
}
}
else
{
logger.error(
"Content no longer exists. Unable to cleanse: \n" +
" URL: " + contentUrl + "\n" +
" Source: " + store);
}
}
}
}

View File

@@ -0,0 +1,122 @@
/*
* Copyright (C) 2005-2015 Alfresco Software Limited.
*
* This file is part of Alfresco
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
package org.alfresco.module.org_alfresco_module_rm.content.cleanser;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.Random;
/**
* Content cleanser base implementation.
*
* @author Roy Wetherall
* @since 3.0.a
*/
public abstract class ContentCleanser
{
/**
* Cleanse file
*
* @param file file to cleanse
*/
public abstract void cleanse(File file);
/**
* Overwrite files bytes with provided overwrite operation
*
* @param file file
* @param overwriteOperation overwrite operation
*/
protected void overwrite(File file, OverwriteOperation overwriteOperation)
{
// get the number of bytes
long bytes = file.length();
try
{
// get an output stream
OutputStream os = new BufferedOutputStream(new FileOutputStream(file));
try
{
for (int i = 0; i < bytes; i++)
{
// overwrite byte
overwriteOperation.operation(os);
}
}
finally
{
// close ouput stream
try {os.close(); } catch (Throwable e) {}
}
}
catch (IOException ioException)
{
// re-throw
throw new RuntimeException("Unable to overwrite file", ioException);
}
}
/**
* Overwrite operation
*/
protected abstract class OverwriteOperation
{
public abstract void operation(OutputStream os) throws IOException;
}
/**
* Overwrite with zeros operation
*/
protected OverwriteOperation overwriteZeros = new OverwriteOperation()
{
public void operation(OutputStream os) throws IOException
{
os.write(0);
}
};
/**
* Overwrite with ones operation
*/
protected OverwriteOperation overwriteOnes = new OverwriteOperation()
{
public void operation(OutputStream os) throws IOException
{
os.write(1);
}
};
/**
* Overwrite with random operation
*/
protected OverwriteOperation overwriteRandom = new OverwriteOperation()
{
private Random random = new Random();
public void operation(OutputStream os) throws IOException
{
byte[] randomByte = new byte[1];
random.nextBytes(randomByte);
os.write(randomByte[0]);
}
};
}

View File

@@ -0,0 +1,50 @@
/*
* Copyright (C) 2005-2015 Alfresco Software Limited.
*
* This file is part of Alfresco
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
package org.alfresco.module.org_alfresco_module_rm.content.cleanser;
import java.io.File;
import org.alfresco.service.cmr.repository.ContentIOException;
/**
* DoD 5220-22M data cleansing implementation.
*
* @author Roy Wetherall
* @since 3.0.a
*/
public class ContentCleanser522022M extends ContentCleanser
{
/**
* @see org.alfresco.module.org_alfresco_module_rm.content.cleanser.ContentCleanser#cleanse(java.io.File)
*/
@Override
public void cleanse(File file)
{
// Double check
if (!file.exists() || !file.canWrite())
{
throw new ContentIOException("Unable to write to file: " + file);
}
// Overwite file
overwrite(file, overwriteOnes);
overwrite(file, overwriteZeros);
overwrite(file, overwriteRandom);
}
}