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 bc18dac0bb
commit c2cb2e11ef
10 changed files with 1055 additions and 0 deletions

View File

@@ -0,0 +1,233 @@
/*
* 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 static org.mockito.Matchers.any;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyZeroInteractions;
import static org.mockito.Mockito.when;
import java.util.ArrayList;
import java.util.List;
import org.alfresco.model.ContentModel;
import org.alfresco.module.org_alfresco_module_rm.classification.ContentClassificationService;
import org.alfresco.module.org_alfresco_module_rm.content.cleanser.ContentCleanser;
import org.alfresco.module.org_alfresco_module_rm.test.util.AlfMock;
import org.alfresco.module.org_alfresco_module_rm.test.util.BaseUnitTest;
import org.alfresco.service.cmr.dictionary.DataTypeDefinition;
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.namespace.QName;
import org.junit.Test;
import org.mockito.InjectMocks;
import org.mockito.Mock;
/**
* Content destruction component unit test.
*
* @author Roy Wetherall
* @since 3.0.a
*/
public class ContentDestructionComponentUnitTest extends BaseUnitTest
{
@InjectMocks private ContentDestructionComponent contentDestructionComponent;
@Mock private ContentClassificationService mockedContentClassificationService;
@Mock private ContentCleanser mockedContentCleanser;
@Mock private EagerContentStoreCleaner mockedEagerContentStoreCleaner;
/**
* Given a non-sensitive node
* When it is deleted
* Then nothing happens
*/
@Test
public void deleteNonSensitiveNode()
{
NodeRef nodeRef = generateCmContent("myContent.txt");
when(mockedRecordService.isRecord(nodeRef))
.thenReturn(false);
when(mockedContentClassificationService.isClassified(nodeRef))
.thenReturn(false);
contentDestructionComponent.beforeDeleteNode(nodeRef);
verifyZeroInteractions(mockedEagerContentStoreCleaner, mockedDictionaryService);
}
/**
* Given a record
* When it is deleted
* Then it is sent for immediate destruction
*/
@Test
public void deleteRecord()
{
String contentURL = AlfMock.generateText();
NodeRef nodeRef = generateDeletedNodeRef(contentURL);
when(mockedRecordService.isRecord(nodeRef))
.thenReturn(true);
when(mockedContentClassificationService.isClassified(nodeRef))
.thenReturn(false);
contentDestructionComponent.beforeDeleteNode(nodeRef);
verify(mockedEagerContentStoreCleaner).registerOrphanedContentUrl(contentURL, true);
}
/**
* Given classified content
* When it is deleted
* Then it is send for immediate destruction
*/
@Test
public void deleteClassifiedContent()
{
String contentURL = AlfMock.generateText();
NodeRef nodeRef = generateDeletedNodeRef(contentURL);
when(mockedRecordService.isRecord(nodeRef))
.thenReturn(false);
when(mockedContentClassificationService.isClassified(nodeRef))
.thenReturn(true);
contentDestructionComponent.beforeDeleteNode(nodeRef);
verify(mockedEagerContentStoreCleaner).registerOrphanedContentUrl(contentURL, true);
}
/**
* Given that content cleansing is turned on
* When a sensitive node is deleted
* Then it is not scheduled for cleansing before destruction
*/
@Test
public void contentCleansingOn()
{
String contentURL = AlfMock.generateText();
NodeRef nodeRef = generateDeletedNodeRef(contentURL);
when(mockedRecordService.isRecord(nodeRef))
.thenReturn(false);
when(mockedContentClassificationService.isClassified(nodeRef))
.thenReturn(true);
contentDestructionComponent.setCleansingEnabled(true);
contentDestructionComponent.beforeDeleteNode(nodeRef);
verify(mockedEagerContentStoreCleaner).registerOrphanedContentUrlForCleansing(contentURL);
}
/**
* Given that content cleansing is turned off
* When a sensitive node is deleted
* Then it is scheduled for cleansing before destruction
*/
@Test
public void contentCleansingOff()
{
String contentURL = AlfMock.generateText();
NodeRef nodeRef = generateDeletedNodeRef(contentURL);
when(mockedRecordService.isRecord(nodeRef))
.thenReturn(false);
when(mockedContentClassificationService.isClassified(nodeRef))
.thenReturn(true);
contentDestructionComponent.setCleansingEnabled(false);
contentDestructionComponent.beforeDeleteNode(nodeRef);
verify(mockedEagerContentStoreCleaner).registerOrphanedContentUrl(contentURL, true);
}
/**
* Given that a sensitive node has more than one content property
* When is it deleted
* Then all the content properties are scheduled for destruction
*/
@Test
public void moreThanOneContentProperty()
{
String contentURL = AlfMock.generateText();
NodeRef nodeRef = generateDeletedNodeRef(contentURL, 2);
when(mockedRecordService.isRecord(nodeRef))
.thenReturn(false);
when(mockedContentClassificationService.isClassified(nodeRef))
.thenReturn(true);
contentDestructionComponent.setCleansingEnabled(true);
contentDestructionComponent.beforeDeleteNode(nodeRef);
verify(mockedEagerContentStoreCleaner, times(2)).registerOrphanedContentUrlForCleansing(contentURL);
}
/**
* Helper method that creates deleted node reference
*/
private NodeRef generateDeletedNodeRef(String contentURL)
{
return generateDeletedNodeRef(contentURL, 1);
}
/**
* Helper method that creates deleted node reference
*/
private NodeRef generateDeletedNodeRef(String contentURL, int contentPropertiesCount)
{
NodeRef nodeRef = generateCmContent("myContent.txt");
List<QName> contentProperties = new ArrayList<QName>(contentPropertiesCount);
for (int i = 0; i < contentPropertiesCount; i++)
{
contentProperties.add(AlfMock.generateQName());
}
when(mockedDictionaryService.getAllProperties(ContentModel.TYPE_CONTENT))
.thenReturn(contentProperties);
DataTypeDefinition mockedDataTypeDefinition = mock(DataTypeDefinition.class);
when(mockedDataTypeDefinition.getName())
.thenReturn(DataTypeDefinition.CONTENT);
PropertyDefinition mockedPropertyDefinition = mock(PropertyDefinition.class);
when(mockedPropertyDefinition.getDataType())
.thenReturn(mockedDataTypeDefinition);
when(mockedDictionaryService.getProperty(any(QName.class)))
.thenReturn(mockedPropertyDefinition);
ContentData mockedDataContent = mock(ContentData.class);
when(mockedDataContent.getContentUrl())
.thenReturn(contentURL);
when(mockedNodeService.getProperty(eq(nodeRef), any(QName.class)))
.thenReturn(mockedDataContent);
return nodeRef;
}
}

View File

@@ -0,0 +1,121 @@
/*
* 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 static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import static org.mockito.Mockito.verifyZeroInteractions;
import java.io.File;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
import org.alfresco.module.org_alfresco_module_rm.content.cleanser.ContentCleanser;
import org.alfresco.module.org_alfresco_module_rm.test.util.AlfMock;
import org.alfresco.module.org_alfresco_module_rm.test.util.BaseUnitTest;
import org.alfresco.repo.content.ContentStore;
import org.alfresco.repo.content.filestore.FileContentReader;
import org.junit.Test;
import org.mockito.InjectMocks;
import org.mockito.Mock;
/**
* Eager content store cleaner unit test.
*
* @author Roy Wetherall
* @since 3.0.a
*/
public class EagerContentStoreCleanerUnitTest extends BaseUnitTest
{
@InjectMocks private EagerContentStoreCleaner eagerContentStoreCleaner = new EagerContentStoreCleaner()
{
/** dummy implementation */
public boolean registerOrphanedContentUrl(String contentUrl, boolean force) {return true;};
};
@Mock private ContentCleanser mockedContentCleanser;
/**
* When content is registered for cleansing
* Then the content URL is recorded for use later
*/
@SuppressWarnings("unchecked")
@Test
public void registerContentURL()
{
String contentURL = AlfMock.generateText();
Set<Object> mockedSet = mock(Set.class);
when(mockedTransactionalResourceHelper.getSet(EagerContentStoreCleaner.KEY_POST_COMMIT_CLEANSING_URLS))
.thenReturn(mockedSet);
eagerContentStoreCleaner.registerOrphanedContentUrlForCleansing(contentURL);
verify(mockedSet).add(contentURL);
}
/**
* Given that the content requires cleansing
* When the content is deleted from the store
* Then the content is cleansed first
*/
@Test
public void contentRequiresCleaning()
{
String contentURL = AlfMock.generateText();
Set<Object> mockedSet = new HashSet<Object>(Arrays.asList(contentURL));
when(mockedTransactionalResourceHelper.getSet(EagerContentStoreCleaner.KEY_POST_COMMIT_CLEANSING_URLS))
.thenReturn(mockedSet);
FileContentReader mockedReader = mock(FileContentReader.class);
when(mockedReader.exists())
.thenReturn(true);
File mockedFile = mock(File.class);
when(mockedReader.getFile())
.thenReturn(mockedFile);
ContentStore mockedContentStore = mock(ContentStore.class);
when(mockedContentStore.getReader(contentURL))
.thenReturn(mockedReader);
eagerContentStoreCleaner.deleteFromStore(contentURL, mockedContentStore);
verify(mockedContentCleanser).cleanse(mockedFile);
}
/**
* Given that the content does not require cleansing
* When the content is deleted from the store
* Then the content is not cleansed
*/
@Test
public void contentDoesntRequireCleaning()
{
String contentURL = AlfMock.generateText();
Set<Object> mockedSet = new HashSet<Object>(Arrays.asList(contentURL));
when(mockedTransactionalResourceHelper.getSet(EagerContentStoreCleaner.KEY_POST_COMMIT_CLEANSING_URLS))
.thenReturn(mockedSet);
eagerContentStoreCleaner.deleteFromStore(AlfMock.generateText(), mock(ContentStore.class));
verifyZeroInteractions(mockedContentCleanser);
}
}

View File

@@ -0,0 +1,109 @@
/*
* 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 static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import java.io.File;
import org.alfresco.module.org_alfresco_module_rm.test.util.BaseUnitTest;
import org.alfresco.service.cmr.repository.ContentIOException;
import org.junit.Test;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.Spy;
/**
* Eager content store cleaner unit test.
*
* @author Roy Wetherall
* @since 3.0.a
*/
public class ContentCleanser522022MUnitTest extends BaseUnitTest
{
@InjectMocks @Spy private ContentCleanser522022M contentCleanser522022M = new ContentCleanser522022M()
{
/** dummy implementations */
protected void overwrite(File file, OverwriteOperation overwriteOperation) {};
};
@Mock private File mockedFile;
/**
* Given that a file exists
* When I cleanse it
* Then the content is overwritten
*/
@Test
public void cleanseFile()
{
when(mockedFile.exists())
.thenReturn(true);
when(mockedFile.canWrite())
.thenReturn(true);
contentCleanser522022M.cleanse(mockedFile);
verify(contentCleanser522022M)
.overwrite(mockedFile, contentCleanser522022M.overwriteOnes);
verify(contentCleanser522022M)
.overwrite(mockedFile, contentCleanser522022M.overwriteZeros);
verify(contentCleanser522022M)
.overwrite(mockedFile, contentCleanser522022M.overwriteOnes);
}
/**
* Given that the file does not exist
* When I cleanse it
* Then an exception is thrown
*/
@Test
(
expected=ContentIOException.class
)
public void fileDoesNotExist()
{
when(mockedFile.exists())
.thenReturn(false);
when(mockedFile.canWrite())
.thenReturn(true);
contentCleanser522022M.cleanse(mockedFile);
}
/**
* Given that I can not write to the file
* When I cleanse it
* Then an exception is thrown
*/
@Test
(
expected=ContentIOException.class
)
public void cantWriteToFile()
{
when(mockedFile.exists())
.thenReturn(true);
when(mockedFile.canWrite())
.thenReturn(false);
contentCleanser522022M.cleanse(mockedFile);
}
}