diff --git a/rm-server/config/alfresco/module/org_alfresco_module_rm/classified-content-context.xml b/rm-server/config/alfresco/module/org_alfresco_module_rm/classified-content-context.xml index a26fae24c0..6228975ee6 100644 --- a/rm-server/config/alfresco/module/org_alfresco_module_rm/classified-content-context.xml +++ b/rm-server/config/alfresco/module/org_alfresco_module_rm/classified-content-context.xml @@ -39,12 +39,12 @@ - + - - + @@ -196,6 +196,7 @@ + diff --git a/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/classification/ContentClassificationServiceImpl.java b/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/classification/ContentClassificationServiceImpl.java index 9123e6a800..6b82e27605 100644 --- a/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/classification/ContentClassificationServiceImpl.java +++ b/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/classification/ContentClassificationServiceImpl.java @@ -23,6 +23,13 @@ import static org.alfresco.module.org_alfresco_module_rm.util.RMParameterCheck.c import static org.alfresco.util.ParameterCheck.mandatory; import static org.apache.commons.lang3.StringUtils.isNotBlank; +import java.io.Serializable; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +import com.google.common.collect.Sets; import org.alfresco.model.ContentModel; import org.alfresco.model.QuickShareModel; import org.alfresco.module.org_alfresco_module_rm.classification.ClassificationException.InvalidNode; @@ -35,15 +42,14 @@ import org.alfresco.module.org_alfresco_module_rm.util.ServiceBaseImpl; import org.alfresco.repo.security.authentication.AuthenticationUtil; import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork; import org.alfresco.repo.security.permissions.AccessDeniedException; +import org.alfresco.repo.version.common.VersionUtil; import org.alfresco.service.cmr.repository.InvalidNodeRefException; import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.version.Version; +import org.alfresco.service.cmr.version.VersionHistory; +import org.alfresco.service.cmr.version.VersionService; import org.alfresco.service.namespace.QName; -import java.io.Serializable; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Map; - /** * A service to handle the classification of content. * @@ -53,22 +59,24 @@ import java.util.Map; public class ContentClassificationServiceImpl extends ServiceBaseImpl implements ContentClassificationService, ClassifiedContentModel { + /** A set containing the property names reserved for use by the version service. */ + private static final Set RESERVED_PROPERTIES = Sets.newHashSet(VersionUtil.RESERVED_PROPERTY_NAMES); + private ClassificationLevelManager levelManager; private ClassificationReasonManager reasonManager; private SecurityClearanceService securityClearanceService; private ClassificationServiceBootstrap classificationServiceBootstrap; private FreezeService freezeService; private ReferredMetadataService referredMetadataService; + private VersionService versionService; public void setLevelManager(ClassificationLevelManager levelManager) { this.levelManager = levelManager; } public void setReasonManager(ClassificationReasonManager reasonManager) { this.reasonManager = reasonManager; } public void setSecurityClearanceService(SecurityClearanceService securityClearanceService) { this.securityClearanceService = securityClearanceService; } public void setClassificationServiceBootstrap(ClassificationServiceBootstrap classificationServiceBootstrap) { this.classificationServiceBootstrap = classificationServiceBootstrap; } public void setFreezeService(FreezeService service) { this.freezeService = service; } - public void setReferredMetadataService(ReferredMetadataService service) - { - this.referredMetadataService = service; - } + public void setReferredMetadataService(ReferredMetadataService service) { this.referredMetadataService = service; } + public void setVersionService(VersionService service) { this.versionService = service; } public void init() { @@ -130,11 +138,55 @@ public class ContentClassificationServiceImpl extends ServiceBaseImpl } nodeService.addAspect(content, ASPECT_CLASSIFIED, properties); + + // Classify the version history if one exists. + if (versionService.isVersioned(content)) + { + replaceHeadOfVersionHistory(content); + } + return null; } }); } + /** + * Ensure that the latest version in the version store is classified. This it to ensure that if someone + * tries to access the content from the version store directly then they are not able to download it. + * + * @param content The node being classified. + */ + protected void replaceHeadOfVersionHistory(final NodeRef content) + { + VersionHistory versionHistory = versionService.getVersionHistory(content); + Version headVersion = versionHistory.getHeadVersion(); + boolean onlyOneVersion = headVersion.equals(versionHistory.getRootVersion()); + Map headProperties = nodeService.getProperties(headVersion.getVersionedNodeRef()); + Map versionProperties = headVersion.getVersionProperties(); + + // Delete the head version, as we cannot add aspects to historical content. + versionService.deleteVersion(content, headVersion); + + if (onlyOneVersion) + { + // Recreate the initial entry in the version store. + versionService.ensureVersioningEnabled(content, headProperties); + } + else + { + // Recreate the version (without the reserved properties - which are added by the version service). + Map newProperties = new HashMap(); + for (Map.Entry entry : versionProperties.entrySet()) + { + if (!RESERVED_PROPERTIES.contains(entry.getKey())) + { + newProperties.put(entry.getKey(), entry.getValue()); + } + } + versionService.createVersion(content, newProperties); + } + } + /** * Validate the properties contained in the {@link ClassificationAspectProperties}. * @@ -226,14 +278,14 @@ public class ContentClassificationServiceImpl extends ServiceBaseImpl @Override public boolean hasClearance(NodeRef nodeRef) { - boolean result = true; - if (nodeService.exists(nodeRef)) - { - // Get the node's current classification - ClassificationLevel currentClassification = getCurrentClassification(nodeRef); - result = securityClearanceService.isCurrentUserClearedForClassification(currentClassification.getId()); - } - return result; + boolean result = true; + if (nodeService.exists(nodeRef)) + { + // Get the node's current classification + ClassificationLevel currentClassification = getCurrentClassification(nodeRef); + result = securityClearanceService.isCurrentUserClearedForClassification(currentClassification.getId()); + } + return result; } /** diff --git a/rm-server/unit-test/java/org/alfresco/module/org_alfresco_module_rm/classification/ContentClassificationServiceImplUnitTest.java b/rm-server/unit-test/java/org/alfresco/module/org_alfresco_module_rm/classification/ContentClassificationServiceImplUnitTest.java index 86a61ec760..31e8ec0f17 100644 --- a/rm-server/unit-test/java/org/alfresco/module/org_alfresco_module_rm/classification/ContentClassificationServiceImplUnitTest.java +++ b/rm-server/unit-test/java/org/alfresco/module/org_alfresco_module_rm/classification/ContentClassificationServiceImplUnitTest.java @@ -1,5 +1,4 @@ /* - * Copyright (C) 2005-2015 Alfresco Software Limited. * * This file is part of Alfresco * @@ -32,12 +31,14 @@ import static org.mockito.Mockito.when; import java.io.Serializable; import java.util.Arrays; +import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; import com.google.common.collect.Sets; import org.alfresco.model.ContentModel; import org.alfresco.model.QuickShareModel; @@ -49,10 +50,14 @@ import org.alfresco.module.org_alfresco_module_rm.referredmetadata.ReferredMetad import org.alfresco.module.org_alfresco_module_rm.test.util.MockAuthenticationUtilHelper; import org.alfresco.module.org_alfresco_module_rm.util.AuthenticationUtil; import org.alfresco.repo.security.permissions.AccessDeniedException; +import org.alfresco.repo.version.VersionBaseModel; import org.alfresco.service.cmr.dictionary.DictionaryService; import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.repository.NodeService; import org.alfresco.service.cmr.security.PersonService.PersonInfo; +import org.alfresco.service.cmr.version.Version; +import org.alfresco.service.cmr.version.VersionHistory; +import org.alfresco.service.cmr.version.VersionService; import org.alfresco.service.namespace.QName; import org.junit.Before; import org.junit.Test; @@ -82,6 +87,7 @@ public class ContentClassificationServiceImplUnitTest implements ClassifiedConte @Mock SecurityClearanceService mockSecurityClearanceService; @Mock AuthenticationUtil mockAuthenticationUtil; @Mock ReferredMetadataService mockReferredMetadataService; + @Mock VersionService mockVersionService; @Mock ClassificationAspectProperties mockPropertiesDTO; @Captor ArgumentCaptor> propertiesCaptor; @@ -379,4 +385,53 @@ public class ContentClassificationServiceImplUnitTest implements ClassifiedConte assertFalse(contentClassificationServiceImpl.isClassified(nodeRef)); } + + /** Check that replacing the only version will use ensureVersioningEnabled. */ + @Test public void replaceHeadOfVersionHistory_onlyOneVersion() + { + NodeRef content = new NodeRef("fake://content/"); + VersionHistory mockVersionHistory = mock(VersionHistory.class); + when(mockVersionService.getVersionHistory(content)).thenReturn(mockVersionHistory); + Version mockVersion = mock(Version.class); + when(mockVersionHistory.getHeadVersion()).thenReturn(mockVersion); + when(mockVersionHistory.getRootVersion()).thenReturn(mockVersion); + NodeRef versionedNodeRef = new NodeRef("fake://versionedNodeRef/"); + when(mockVersion.getVersionedNodeRef()).thenReturn(versionedNodeRef); + Map properties = new HashMap<>(); + when(mockNodeService.getProperties(versionedNodeRef)).thenReturn(properties); + + // Call the method under test. + contentClassificationServiceImpl.replaceHeadOfVersionHistory(content); + + verify(mockVersionService).deleteVersion(content, mockVersion); + verify(mockVersionService).ensureVersioningEnabled(content, properties); + } + + /** + * Check that when replacing a later version than the initial one, the entry in the version history is deleted and + * recreated. Ensure that properties from {link VersionUtil.RESERVED_PROPERTY_NAMES} are removed from the properties + * before calling {@link VersionService.createVersion}. + */ + @Test public void replaceHeadOfVersionHistory_twoVersions() + { + NodeRef content = new NodeRef("fake://content/"); + VersionHistory mockVersionHistory = mock(VersionHistory.class); + when(mockVersionService.getVersionHistory(content)).thenReturn(mockVersionHistory); + Version mockHeadVersion = mock(Version.class); + when(mockVersionHistory.getHeadVersion()).thenReturn(mockHeadVersion); + Version mockRootVersion = mock(Version.class); + when(mockVersionHistory.getRootVersion()).thenReturn(mockRootVersion); + Map versionProperties = ImmutableMap.of("ClassificationKey", "ClassificationValue", + "VersionNumber", "1.1", + VersionBaseModel.PROP_CREATED_DATE, "DateToBeIgnored"); + when(mockHeadVersion.getVersionProperties()).thenReturn(versionProperties); + + // Call the method under test. + contentClassificationServiceImpl.replaceHeadOfVersionHistory(content); + + verify(mockVersionService).deleteVersion(content, mockHeadVersion); + Map expectedVersionProperties = ImmutableMap.of("ClassificationKey", "ClassificationValue", + "VersionNumber", "1.1"); + verify(mockVersionService).createVersion(content, expectedVersionProperties); + } }