From 24b6655eeb4bc988b117243d1613cfabe31606bb Mon Sep 17 00:00:00 2001 From: Roy Wetherall Date: Wed, 29 Jul 2015 01:26:49 +0000 Subject: [PATCH] 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 --- .../alfresco-global.properties | 6 + .../content-context.xml | 38 +++ .../org_alfresco_module_rm/module-context.xml | 3 + .../content/ContentDestructionComponent.java | 210 ++++++++++++++++ .../content/EagerContentStoreCleaner.java | 163 ++++++++++++ .../content/cleanser/ContentCleanser.java | 122 +++++++++ .../cleanser/ContentCleanser522022M.java | 50 ++++ .../ContentDestructionComponentUnitTest.java | 233 ++++++++++++++++++ .../EagerContentStoreCleanerUnitTest.java | 121 +++++++++ .../ContentCleanser522022MUnitTest.java | 109 ++++++++ 10 files changed, 1055 insertions(+) create mode 100644 rm-server/config/alfresco/module/org_alfresco_module_rm/content-context.xml create mode 100644 rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/content/ContentDestructionComponent.java create mode 100644 rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/content/EagerContentStoreCleaner.java create mode 100644 rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/content/cleanser/ContentCleanser.java create mode 100644 rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/content/cleanser/ContentCleanser522022M.java create mode 100644 rm-server/unit-test/java/org/alfresco/module/org_alfresco_module_rm/content/ContentDestructionComponentUnitTest.java create mode 100644 rm-server/unit-test/java/org/alfresco/module/org_alfresco_module_rm/content/EagerContentStoreCleanerUnitTest.java create mode 100644 rm-server/unit-test/java/org/alfresco/module/org_alfresco_module_rm/content/cleanser/ContentCleanser522022MUnitTest.java diff --git a/rm-server/config/alfresco/module/org_alfresco_module_rm/alfresco-global.properties b/rm-server/config/alfresco/module/org_alfresco_module_rm/alfresco-global.properties index d1b78c753a..6a22542489 100644 --- a/rm-server/config/alfresco/module/org_alfresco_module_rm/alfresco-global.properties +++ b/rm-server/config/alfresco/module/org_alfresco_module_rm/alfresco-global.properties @@ -67,3 +67,9 @@ rm.classification.levelsFile=/alfresco/module/org_alfresco_module_rm/classificat rm.classification.reasonsFile=/alfresco/module/org_alfresco_module_rm/classification/rm-classification-reasons.json # The location of the exemption categories configuration file (relative to the classpath). rm.classification.exemptionCategoriesFile=/alfresco/module/org_alfresco_module_rm/classification/rm-exemption-categories.json + +# +# Content cleansing +# +rm.content.cleansing.enabled=false +rm.content.cleaner=contentCleanser.522022M diff --git a/rm-server/config/alfresco/module/org_alfresco_module_rm/content-context.xml b/rm-server/config/alfresco/module/org_alfresco_module_rm/content-context.xml new file mode 100644 index 0000000000..4e888f2697 --- /dev/null +++ b/rm-server/config/alfresco/module/org_alfresco_module_rm/content-context.xml @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/rm-server/config/alfresco/module/org_alfresco_module_rm/module-context.xml b/rm-server/config/alfresco/module/org_alfresco_module_rm/module-context.xml index 8df31092a8..9e2d78befd 100644 --- a/rm-server/config/alfresco/module/org_alfresco_module_rm/module-context.xml +++ b/rm-server/config/alfresco/module/org_alfresco_module_rm/module-context.xml @@ -258,5 +258,8 @@ + + + diff --git a/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/content/ContentDestructionComponent.java b/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/content/ContentDestructionComponent.java new file mode 100644 index 0000000000..e840007005 --- /dev/null +++ b/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/content/ContentDestructionComponent.java @@ -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 . + */ +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. + *

+ * Listens for the destruction of sensitive nodes (classified content and records) and schedules + * all their content for immediate destruction. + *

+ * 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. + *

+ * 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() + { + 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 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); + } + } + } + } +} diff --git a/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/content/EagerContentStoreCleaner.java b/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/content/EagerContentStoreCleaner.java new file mode 100644 index 0000000000..d1c5648bb1 --- /dev/null +++ b/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/content/EagerContentStoreCleaner.java @@ -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 . + */ +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 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 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); + } + } + } + +} diff --git a/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/content/cleanser/ContentCleanser.java b/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/content/cleanser/ContentCleanser.java new file mode 100644 index 0000000000..61750cf2d0 --- /dev/null +++ b/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/content/cleanser/ContentCleanser.java @@ -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 . + */ +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]); + } + }; +} diff --git a/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/content/cleanser/ContentCleanser522022M.java b/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/content/cleanser/ContentCleanser522022M.java new file mode 100644 index 0000000000..128ac507f0 --- /dev/null +++ b/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/content/cleanser/ContentCleanser522022M.java @@ -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 . + */ +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); + } +} diff --git a/rm-server/unit-test/java/org/alfresco/module/org_alfresco_module_rm/content/ContentDestructionComponentUnitTest.java b/rm-server/unit-test/java/org/alfresco/module/org_alfresco_module_rm/content/ContentDestructionComponentUnitTest.java new file mode 100644 index 0000000000..2abe883419 --- /dev/null +++ b/rm-server/unit-test/java/org/alfresco/module/org_alfresco_module_rm/content/ContentDestructionComponentUnitTest.java @@ -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 . + */ +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 contentProperties = new ArrayList(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; + } +} diff --git a/rm-server/unit-test/java/org/alfresco/module/org_alfresco_module_rm/content/EagerContentStoreCleanerUnitTest.java b/rm-server/unit-test/java/org/alfresco/module/org_alfresco_module_rm/content/EagerContentStoreCleanerUnitTest.java new file mode 100644 index 0000000000..4ed98fb865 --- /dev/null +++ b/rm-server/unit-test/java/org/alfresco/module/org_alfresco_module_rm/content/EagerContentStoreCleanerUnitTest.java @@ -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 . + */ +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 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 mockedSet = new HashSet(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 mockedSet = new HashSet(Arrays.asList(contentURL)); + when(mockedTransactionalResourceHelper.getSet(EagerContentStoreCleaner.KEY_POST_COMMIT_CLEANSING_URLS)) + .thenReturn(mockedSet); + + eagerContentStoreCleaner.deleteFromStore(AlfMock.generateText(), mock(ContentStore.class)); + + verifyZeroInteractions(mockedContentCleanser); + } +} diff --git a/rm-server/unit-test/java/org/alfresco/module/org_alfresco_module_rm/content/cleanser/ContentCleanser522022MUnitTest.java b/rm-server/unit-test/java/org/alfresco/module/org_alfresco_module_rm/content/cleanser/ContentCleanser522022MUnitTest.java new file mode 100644 index 0000000000..e8098475c5 --- /dev/null +++ b/rm-server/unit-test/java/org/alfresco/module/org_alfresco_module_rm/content/cleanser/ContentCleanser522022MUnitTest.java @@ -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 . + */ +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); + } +}