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 cd75660c97..7a2d3a228e 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,9 +39,8 @@ - - + parent="baseService" init-method="init"> + @@ -76,9 +75,7 @@ org.alfresco.module.org_alfresco_module_rm.classification.ClassificationService.getClassificationLevels=ACL_ALLOW - org.alfresco.module.org_alfresco_module_rm.classification.ClassificationService.getCurrentClassification=ACL_ALLOW org.alfresco.module.org_alfresco_module_rm.classification.ClassificationService.getClassificationReasons=ACL_ALLOW - org.alfresco.module.org_alfresco_module_rm.classification.ClassificationService.classifyContent=ACL_ALLOW org.alfresco.module.org_alfresco_module_rm.classification.ClassificationService.getClassificationLevelById=ACL_ALLOW org.alfresco.module.org_alfresco_module_rm.classification.ClassificationService.getClassificationReasonById=ACL_ALLOW org.alfresco.module.org_alfresco_module_rm.classification.ClassificationService.getUnclassifiedClassificationLevel=ACL_ALLOW @@ -94,9 +91,9 @@ - - + + @@ -104,9 +101,9 @@ - + parent="baseService" init-method="init"> + @@ -140,7 +137,6 @@ - org.alfresco.module.org_alfresco_module_rm.classification.SecurityClearanceService.hasClearance=ACL_ALLOW org.alfresco.module.org_alfresco_module_rm.classification.SecurityClearanceService.getUserSecurityClearance=ACL_ALLOW org.alfresco.module.org_alfresco_module_rm.classification.SecurityClearanceService.getUsersSecurityClearance=ACL_ALLOW org.alfresco.module.org_alfresco_module_rm.classification.SecurityClearanceService.setUserSecurityClearance=ACL_ALLOW @@ -149,4 +145,52 @@ + + + + + + + + + + + org.alfresco.module.org_alfresco_module_rm.classification.ContentClassificationService + + + + + + + + + + + + + + + + + + + + ${server.transaction.mode.default} + + + + + + + + + org.alfresco.module.org_alfresco_module_rm.classification.ContentClassificationService.getCurrentClassification=ACL_ALLOW + org.alfresco.module.org_alfresco_module_rm.classification.ContentClassificationService.classifyContent=ACL_ALLOW + org.alfresco.module.org_alfresco_module_rm.classification.ContentClassificationService.hasClearance=ACL_ALLOW + org.alfresco.module.org_alfresco_module_rm.classification.ContentClassificationService.*=ACL_DENY + + + diff --git a/rm-server/config/alfresco/module/org_alfresco_module_rm/rm-ui-evaluators-context.xml b/rm-server/config/alfresco/module/org_alfresco_module_rm/rm-ui-evaluators-context.xml index 7d9365adde..7580c546df 100644 --- a/rm-server/config/alfresco/module/org_alfresco_module_rm/rm-ui-evaluators-context.xml +++ b/rm-server/config/alfresco/module/org_alfresco_module_rm/rm-ui-evaluators-context.xml @@ -7,7 +7,7 @@ diff --git a/rm-server/config/alfresco/module/org_alfresco_module_rm/rm-webscript-context.xml b/rm-server/config/alfresco/module/org_alfresco_module_rm/rm-webscript-context.xml index 14a49c0885..a9da37410e 100644 --- a/rm-server/config/alfresco/module/org_alfresco_module_rm/rm-webscript-context.xml +++ b/rm-server/config/alfresco/module/org_alfresco_module_rm/rm-webscript-context.xml @@ -674,7 +674,7 @@ - + diff --git a/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/classification/ClassificationLevelManager.java b/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/classification/ClassificationLevelManager.java index 9c148b67e3..26f92dce3a 100644 --- a/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/classification/ClassificationLevelManager.java +++ b/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/classification/ClassificationLevelManager.java @@ -21,34 +21,33 @@ package org.alfresco.module.org_alfresco_module_rm.classification; import java.util.ArrayList; import java.util.List; -import org.alfresco.module.org_alfresco_module_rm.classification.ClassificationServiceException.LevelIdNotFound; - import com.google.common.collect.ImmutableList; +import org.alfresco.module.org_alfresco_module_rm.classification.ClassificationServiceException.LevelIdNotFound; /** * Container for the configured {@link ClassificationLevel} objects. - * + * * @author tpage */ public class ClassificationLevelManager { - /** Unclassified classificaiton level */ - public static final String UNCLASSIFIED_ID = "Unclassified"; - private static final String UNCLASSIFIED_MSG = "rm.classification.unclassified"; - public static final ClassificationLevel UNCLASSIFIED = new ClassificationLevel(UNCLASSIFIED_ID, UNCLASSIFIED_MSG); - + /** Unclassified classification level */ + public static final String UNCLASSIFIED_ID = "Unclassified"; + private static final String UNCLASSIFIED_MSG = "rm.classification.unclassified"; + public static final ClassificationLevel UNCLASSIFIED = new ClassificationLevel(UNCLASSIFIED_ID, UNCLASSIFIED_MSG); + /** An immutable list of classification levels ordered from most to least secure. */ private ImmutableList classificationLevels; /** - * Constructor that stores an immutable copy of the given levels. - * + * Store an immutable copy of the given classification levels. + * * @param classificationLevels A list of classification levels ordered from most to least secure. */ - public ClassificationLevelManager(List classificationLevels) + public void setClassificationLevels(List classificationLevels) { - List temp = new ArrayList(classificationLevels); - temp.add(temp.size(), UNCLASSIFIED); + List temp = new ArrayList(classificationLevels); + temp.add(temp.size(), UNCLASSIFIED); this.classificationLevels = ImmutableList.copyOf(temp); } @@ -66,7 +65,7 @@ public class ClassificationLevelManager /** * Get a ClassificationLevel using its id. - * + * * @param id The id of a classification level. * @return The classification level. * @throws LevelIdNotFound If the classification level cannot be found. diff --git a/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/classification/ClassificationReasonManager.java b/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/classification/ClassificationReasonManager.java index 76b19269e1..ab4972e982 100644 --- a/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/classification/ClassificationReasonManager.java +++ b/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/classification/ClassificationReasonManager.java @@ -25,7 +25,7 @@ import org.alfresco.module.org_alfresco_module_rm.classification.ClassificationS /** * Container for the configured {@link ClassificationReason} objects. - * + * * @author tpage */ public class ClassificationReasonManager @@ -34,11 +34,11 @@ public class ClassificationReasonManager private ImmutableList classificationReasons; /** - * Constructor that stores an immutable copy of the given reasons. - * + * Store an immutable copy of the given reasons. + * * @param classificationReasons The classification reasons. */ - public ClassificationReasonManager(Collection classificationReasons) + public void setClassificationReasons(Collection classificationReasons) { this.classificationReasons = ImmutableList.copyOf(classificationReasons); } @@ -51,7 +51,7 @@ public class ClassificationReasonManager /** * Get a ClassificationReason using its id. - * + * * @param id The id of a classification reason. * @return The classification reason. * @throws ReasonIdNotFound If the classification reason cannot be found. diff --git a/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/classification/ClassificationService.java b/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/classification/ClassificationService.java index 67eb7d922e..52e50a7858 100644 --- a/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/classification/ClassificationService.java +++ b/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/classification/ClassificationService.java @@ -19,13 +19,9 @@ package org.alfresco.module.org_alfresco_module_rm.classification; import java.util.List; -import java.util.Set; -import org.alfresco.module.org_alfresco_module_rm.classification.ClassificationServiceException.InvalidNode; import org.alfresco.module.org_alfresco_module_rm.classification.ClassificationServiceException.LevelIdNotFound; import org.alfresco.module.org_alfresco_module_rm.classification.ClassificationServiceException.ReasonIdNotFound; -import org.alfresco.service.cmr.repository.InvalidNodeRefException; -import org.alfresco.service.cmr.repository.NodeRef; /** * The Classification Service supports the 'Classified Records' feature, whereby Alfresco @@ -44,14 +40,6 @@ public interface ClassificationService * and therefore access to the most restricted documents). */ List getClassificationLevels(); - - /** - * Returns the current classification level of a given node. - * - * @param nodeRef node reference - * @return {@link ClassificationLevel} classification level, unclassified if none - */ - ClassificationLevel getCurrentClassification(NodeRef nodeRef); /** * Returns an immutable list of the defined classification reasons. @@ -59,21 +47,6 @@ public interface ClassificationService */ List getClassificationReasons(); - /** - * Classify a piece of content. - * - * @param classificationLevelId The security clearance needed to access the content. - * @param classificationAuthority The name of the authority responsible for the classification of this content. - * @param classificationReasonIds A non-empty set of ids of reasons for classifying the content in this way. - * @param content The node to classify. - * @throws LevelIdNotFound If the supplied level id is not found. - * @throws ReasonIdNotFound If any of the supplied reason ids are not found. - * @throws InvalidNodeRefException If the node could not be found. - * @throws InvalidNode If the supplied node is not a content node. - */ - void classifyContent(String classificationLevelId, String classificationAuthority, Set classificationReasonIds, NodeRef content) - throws LevelIdNotFound, ReasonIdNotFound, InvalidNodeRefException, InvalidNode; - /** * Gets the unclassified {@link ClassificationLevel}. * @return the unclassified classification level diff --git a/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/classification/ClassificationServiceBootstrap.java b/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/classification/ClassificationServiceBootstrap.java index 04951f1967..3e4363ac2e 100644 --- a/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/classification/ClassificationServiceBootstrap.java +++ b/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/classification/ClassificationServiceBootstrap.java @@ -18,9 +18,20 @@ */ package org.alfresco.module.org_alfresco_module_rm.classification; +import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; + +import com.google.common.collect.ImmutableList; +import org.alfresco.module.org_alfresco_module_rm.classification.ClassificationServiceException.MissingConfiguration; +import org.alfresco.module.org_alfresco_module_rm.classification.model.ClassifiedContentModel; import org.alfresco.module.org_alfresco_module_rm.util.AuthenticationUtil; +import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork; import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback; +import org.alfresco.service.cmr.attributes.AttributeService; import org.alfresco.service.transaction.TransactionService; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.context.ApplicationEvent; import org.springframework.extensions.surf.util.AbstractLifecycleBean; @@ -30,25 +41,42 @@ import org.springframework.extensions.surf.util.AbstractLifecycleBean; * @author Neil Mc Erlean * @since 3.0 */ -public class ClassificationServiceBootstrap extends AbstractLifecycleBean +public class ClassificationServiceBootstrap extends AbstractLifecycleBean implements ClassifiedContentModel { + /** Logging utility for the class. */ + private static final Logger LOGGER = LoggerFactory.getLogger(ClassificationServiceBootstrap.class); + private final AuthenticationUtil authenticationUtil; - private final ClassificationServiceImpl classificationServiceImpl; - private final SecurityClearanceServiceImpl securityClearanceServiceImpl; private final TransactionService transactionService; + private AttributeService attributeService; + /** The classification levels currently configured in this server. */ + private ClassificationLevelManager classificationLevelManager = new ClassificationLevelManager(); + /** The classification reasons currently configured in this server. */ + private ClassificationReasonManager classificationReasonManager = new ClassificationReasonManager(); + /** The clearance levels currently configured in this server. */ + private ClearanceLevelManager clearanceLevelManager = new ClearanceLevelManager(); + private ClassificationServiceDAO classificationServiceDAO; public ClassificationServiceBootstrap(AuthenticationUtil authUtil, - ClassificationServiceImpl cService, - SecurityClearanceServiceImpl securityClearanceServiceImpl, - TransactionService txService) + TransactionService txService, + AttributeService attributeService, + ClassificationServiceDAO classificationServiceDAO) { - this.authenticationUtil = authUtil; - this.classificationServiceImpl = cService; - this.securityClearanceServiceImpl = securityClearanceServiceImpl; - this.transactionService = txService; + this.authenticationUtil = authUtil; + this.transactionService = txService; + this.attributeService = attributeService; + this.classificationServiceDAO = classificationServiceDAO; } - @Override protected void onBootstrap(ApplicationEvent event) + /** Set the object from which configuration options will be read. */ + public void setClassificationServiceDAO(ClassificationServiceDAO classificationServiceDAO) { this.classificationServiceDAO = classificationServiceDAO; } + public void setAttributeService(AttributeService attributeService) { this.attributeService = attributeService; } + + public ClassificationLevelManager getClassificationLevelManager() { return classificationLevelManager; } + public ClassificationReasonManager getClassificationReasonManager() { return classificationReasonManager; } + public ClearanceLevelManager getClearanceLevelManager() { return clearanceLevelManager; } + + @Override public void onBootstrap(ApplicationEvent event) { authenticationUtil.runAsSystem(new org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork() { @@ -58,8 +86,9 @@ public class ClassificationServiceBootstrap extends AbstractLifecycleBean { public Void execute() { - classificationServiceImpl.initialise(); - securityClearanceServiceImpl.initialise(); + initConfiguredClassificationLevels(); + initConfiguredClassificationReasons(); + initConfiguredClearanceLevels(classificationLevelManager.getClassificationLevels()); return null; } }; @@ -69,6 +98,144 @@ public class ClassificationServiceBootstrap extends AbstractLifecycleBean }); } + /** + * Initialise the system's classification levels by loading the values from a configuration file and storing them + * in the attribute service and the {@link ClassificationLevelManager}. + */ + protected void initConfiguredClassificationLevels() + { + final List allPersistedLevels = getPersistedLevels(); + final List configurationLevels = getConfigurationLevels(); + + // Note! We cannot log the level names or even the size of these lists for security reasons. + LOGGER.debug("Persisted classification levels: {}", loggableStatusOf(allPersistedLevels)); + LOGGER.debug("Classpath classification levels: {}", loggableStatusOf(configurationLevels)); + + if (configurationLevels == null || configurationLevels.isEmpty()) + { + throw new MissingConfiguration("Classification level configuration is missing."); + } + else if (!configurationLevels.equals(allPersistedLevels)) + { + attributeService.setAttribute((Serializable) configurationLevels, LEVELS_KEY); + this.classificationLevelManager.setClassificationLevels(configurationLevels); + } + else + { + this.classificationLevelManager.setClassificationLevels(allPersistedLevels); + } + } + + /** + * Gets the list (in descending order) of classification levels - as persisted in the system. + * @return the list of classification levels if they have been persisted, else {@code null}. + */ + private List getPersistedLevels() + { + return authenticationUtil.runAsSystem(new RunAsWork>() + { + @Override + @SuppressWarnings("unchecked") + public List doWork() throws Exception + { + return (List) attributeService.getAttribute(LEVELS_KEY); + } + }); + } + + /** Gets the list (in descending order) of classification levels - as defined in the system configuration. */ + private List getConfigurationLevels() + { + return classificationServiceDAO.getConfiguredLevels(); + } + + private static boolean isEmpty(List l) { return l == null || l.isEmpty(); } + + /** Helper method for debug-logging of sensitive lists. */ + private String loggableStatusOf(List l) + { + if (l == null) { return "null"; } + else if (l.isEmpty()) { return "empty"; } + else { return "non-empty"; } + } + + /** + * Initialise the system's classification reasons by loading the values from a configuration file and storing them + * in the attribute service and the {@link ClassificationReasonManager}. + */ + protected void initConfiguredClassificationReasons() + { + final List persistedReasons = getPersistedReasons(); + final List classpathReasons = getConfigurationReasons(); + + // Note! We cannot log the reasons or even the size of these lists for security reasons. + LOGGER.debug("Persisted classification reasons: {}", loggableStatusOf(persistedReasons)); + LOGGER.debug("Classpath classification reasons: {}", loggableStatusOf(classpathReasons)); + + if (isEmpty(persistedReasons)) + { + if (isEmpty(classpathReasons)) + { + throw new MissingConfiguration("Classification reason configuration is missing."); + } + attributeService.setAttribute((Serializable) classpathReasons, REASONS_KEY); + this.classificationReasonManager.setClassificationReasons(classpathReasons); + } + else + { + if (isEmpty(classpathReasons) || !classpathReasons.equals(persistedReasons)) + { + LOGGER.warn("Classification reasons configured in classpath do not match those stored in Alfresco. " + + "Alfresco will use the unchanged values stored in the database."); + // RM-2073 says that we should log a warning and proceed normally. + } + this.classificationReasonManager.setClassificationReasons(persistedReasons); + } + } + + /** + * Gets the list of classification reasons as persisted in the system. + * @return the list of classification reasons if they have been persisted, else {@code null}. + */ + private List getPersistedReasons() + { + return authenticationUtil.runAsSystem(new RunAsWork>() + { + @Override + @SuppressWarnings("unchecked") + public List doWork() throws Exception + { + return (List) attributeService.getAttribute(REASONS_KEY); + } + }); + } + + /** Gets the list of classification reasons - as defined and ordered in the system configuration. */ + private List getConfigurationReasons() + { + return classificationServiceDAO.getConfiguredReasons(); + } + + /** + * Initialise and create a {@link ClearanceLevelManager}. + * + * @param classificationLevels The list of classification levels to use to create clearance levels from. + */ + protected void initConfiguredClearanceLevels(ImmutableList classificationLevels) + { + List clearanceLevels = new ArrayList<>(); + for (ClassificationLevel classificationLevel : classificationLevels) + { + String displayLabelKey = classificationLevel.getDisplayLabelKey(); + if (classificationLevel.equals(ClassificationLevelManager.UNCLASSIFIED)) + { + displayLabelKey = "rm.classification.noClearance"; + } + clearanceLevels.add(new ClearanceLevel(classificationLevel, displayLabelKey)); + } + this.clearanceLevelManager.setClearanceLevels(clearanceLevels); + } + @Override protected void onShutdown(ApplicationEvent event) { // Intentionally empty. diff --git a/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/classification/ClassificationServiceImpl.java b/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/classification/ClassificationServiceImpl.java index 927c881a8a..b75d8c174d 100644 --- a/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/classification/ClassificationServiceImpl.java +++ b/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/classification/ClassificationServiceImpl.java @@ -19,30 +19,15 @@ package org.alfresco.module.org_alfresco_module_rm.classification; import static org.alfresco.module.org_alfresco_module_rm.util.RMParameterCheck.checkNotBlank; -import static org.alfresco.util.ParameterCheck.mandatory; -import java.io.Serializable; import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; import java.util.List; -import java.util.Map; -import java.util.Set; -import org.alfresco.model.ContentModel; -import org.alfresco.module.org_alfresco_module_rm.classification.ClassificationServiceException.InvalidNode; import org.alfresco.module.org_alfresco_module_rm.classification.ClassificationServiceException.LevelIdNotFound; -import org.alfresco.module.org_alfresco_module_rm.classification.ClassificationServiceException.MissingConfiguration; import org.alfresco.module.org_alfresco_module_rm.classification.ClassificationServiceException.ReasonIdNotFound; import org.alfresco.module.org_alfresco_module_rm.classification.model.ClassifiedContentModel; import org.alfresco.module.org_alfresco_module_rm.util.ServiceBaseImpl; -import org.alfresco.repo.security.authentication.AuthenticationUtil; -import org.alfresco.service.cmr.attributes.AttributeService; -import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.repository.NodeService; -import org.alfresco.service.namespace.QName; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; /** * @author Neil Mc Erlean @@ -51,160 +36,23 @@ import org.slf4j.LoggerFactory; public class ClassificationServiceImpl extends ServiceBaseImpl implements ClassificationService, ClassifiedContentModel { - private static final Serializable[] LEVELS_KEY = new String[] { "org.alfresco", - "module.org_alfresco_module_rm", - "classification.levels" }; - private static final Serializable[] REASONS_KEY = new String[] { "org.alfresco", - "module.org_alfresco_module_rm", - "classification.reasons" }; - private static final Logger LOGGER = LoggerFactory.getLogger(ClassificationServiceImpl.class); - - private AttributeService attributeService; // TODO What about other code (e.g. REST API) accessing the AttrService? - private NodeService nodeService; - private ClassificationServiceDAO classificationServiceDao; - /** The classification levels currently configured in this server. */ private ClassificationLevelManager levelManager; /** The classification reasons currently configured in this server. */ private ClassificationReasonManager reasonManager; + private ClassificationServiceBootstrap classificationServiceBootstrap; - public void setAttributeService(AttributeService service) { this.attributeService = service; } public void setNodeService(NodeService service) { this.nodeService = service; } - - /** Set the object from which configuration options will be read. */ - public void setClassificationServiceDAO(ClassificationServiceDAO classificationServiceDao) { this.classificationServiceDao = classificationServiceDao; } - - void initialise() + public void setClassificationServiceBootstrap(ClassificationServiceBootstrap classificationServiceBootstrap) { - initConfiguredClassificationLevels(); - initConfiguredClassificationReasons(); + this.classificationServiceBootstrap = classificationServiceBootstrap; } - protected void initConfiguredClassificationLevels() + /** Store the references to the classification level and reason managers in this class. */ + public void init() { - final List allPersistedLevels = getPersistedLevels(); - final List configurationLevels = getConfigurationLevels(); - - // Note! We cannot log the level names or even the size of these lists for security reasons. - LOGGER.debug("Persisted classification levels: {}", loggableStatusOf(allPersistedLevels)); - LOGGER.debug("Classpath classification levels: {}", loggableStatusOf(configurationLevels)); - - if (configurationLevels == null || configurationLevels.isEmpty()) - { - throw new MissingConfiguration("Classification level configuration is missing."); - } - else if (!configurationLevels.equals(allPersistedLevels)) - { - attributeService.setAttribute((Serializable) configurationLevels, LEVELS_KEY); - this.levelManager = new ClassificationLevelManager(configurationLevels); - } - else - { - this.levelManager = new ClassificationLevelManager(allPersistedLevels); - } - } - - protected void initConfiguredClassificationReasons() - { - final List persistedReasons = getPersistedReasons(); - final List classpathReasons = getConfigurationReasons(); - - // Note! We cannot log the reasons or even the size of these lists for security reasons. - LOGGER.debug("Persisted classification reasons: {}", loggableStatusOf(persistedReasons)); - LOGGER.debug("Classpath classification reasons: {}", loggableStatusOf(classpathReasons)); - - if (isEmpty(persistedReasons)) - { - if (isEmpty(classpathReasons)) - { - throw new MissingConfiguration("Classification reason configuration is missing."); - } - attributeService.setAttribute((Serializable) classpathReasons, REASONS_KEY); - this.reasonManager = new ClassificationReasonManager(classpathReasons); - } - else - { - if (isEmpty(classpathReasons) || !classpathReasons.equals(persistedReasons)) - { - LOGGER.warn("Classification reasons configured in classpath do not match those stored in Alfresco. " - + "Alfresco will use the unchanged values stored in the database."); - // RM-2073 says that we should log a warning and proceed normally. - } - this.reasonManager = new ClassificationReasonManager(persistedReasons); - } - } - - private static boolean isEmpty(List l) { return l == null || l.isEmpty(); } - - /** Helper method for debug-logging of sensitive lists. */ - private String loggableStatusOf(List l) - { - if (l == null) { return "null"; } - else if (l.isEmpty()) { return "empty"; } - else { return "non-empty"; } - } - - /** - * Gets the list (in descending order) of classification levels - as persisted in the system. - * @return the list of classification levels if they have been persisted, else {@code null}. - */ - List getPersistedLevels() - { - return authenticationUtil.runAsSystem(new AuthenticationUtil.RunAsWork>() - { - @Override - @SuppressWarnings("unchecked") - public List doWork() throws Exception - { - return (List) attributeService.getAttribute(LEVELS_KEY); - } - }); - } - - /** Gets the list (in descending order) of classification levels - as defined in the system configuration. */ - List getConfigurationLevels() - { - return classificationServiceDao.getConfiguredLevels(); - } - - /** - * @see org.alfresco.module.org_alfresco_module_rm.classification.ClassificationService#getCurrentClassification(org.alfresco.service.cmr.repository.NodeRef) - */ - public ClassificationLevel getCurrentClassification(NodeRef nodeRef) - { - // by default everything is unclassified - ClassificationLevel result = ClassificationLevelManager.UNCLASSIFIED; - - if (nodeService.hasAspect(nodeRef, ASPECT_CLASSIFIED)) - { - String classificationId = (String)nodeService.getProperty(nodeRef, PROP_CURRENT_CLASSIFICATION); - result = levelManager.findLevelById(classificationId); - } - - return result; - }; - - /** - * Gets the list of classification reasons as persisted in the system. - * @return the list of classification reasons if they have been persisted, else {@code null}. - */ - List getPersistedReasons() - { - return authenticationUtil.runAsSystem(new AuthenticationUtil.RunAsWork>() - { - @Override - @SuppressWarnings("unchecked") - public List doWork() throws Exception - { - return (List) attributeService.getAttribute(REASONS_KEY); - } - }); - } - - /** Gets the list of classification reasons - as defined and ordered in the system configuration. */ - List getConfigurationReasons() - { - return classificationServiceDao.getConfiguredReasons(); + levelManager = classificationServiceBootstrap.getClassificationLevelManager(); + reasonManager = classificationServiceBootstrap.getClassificationReasonManager(); } /** @@ -241,58 +89,9 @@ public class ClassificationServiceImpl extends ServiceBaseImpl Collections.unmodifiableList(reasonManager.getClassificationReasons()); } - @Override - public void classifyContent(String classificationLevelId, String classificationAuthority, - Set classificationReasonIds, NodeRef content) - { - checkNotBlank("classificationLevelId", classificationLevelId); - checkNotBlank("classificationAuthority", classificationAuthority); - mandatory("classificationReasonIds", classificationReasonIds); - mandatory("content", content); - - if (!dictionaryService.isSubClass(nodeService.getType(content), ContentModel.TYPE_CONTENT)) - { - throw new InvalidNode(content, "The supplied node is not a content node."); - } - if (nodeService.hasAspect(content, ASPECT_CLASSIFIED)) - { - throw new UnsupportedOperationException( - "The content has already been classified. Reclassification is currently not supported."); - } - - Map properties = new HashMap(); - // Check the classification level id - an exception will be thrown if the id cannot be found - getClassificationLevelById(classificationLevelId); - - // Initial classification id - if (nodeService.getProperty(content, PROP_INITIAL_CLASSIFICATION) == null) - { - properties.put(PROP_INITIAL_CLASSIFICATION, classificationLevelId); - } - - // Current classification id - properties.put(PROP_CURRENT_CLASSIFICATION, classificationLevelId); - - // Classification authority - properties.put(PROP_CLASSIFICATION_AUTHORITY, classificationAuthority); - - // Classification reason ids - HashSet classificationReasons = new HashSet<>(); - for (String classificationReasonId : classificationReasonIds) - { - // Check the classification reason id - an exception will be thrown if the id cannot be found - getClassificationReasonById(classificationReasonId); - classificationReasons.add(classificationReasonId); - } - properties.put(PROP_CLASSIFICATION_REASONS, classificationReasons); - - // Add aspect - nodeService.addAspect(content, ASPECT_CLASSIFIED, properties); - } - @Override public ClassificationLevel getUnclassifiedClassificationLevel() { - return ClassificationLevelManager.UNCLASSIFIED; + return ClassificationLevelManager.UNCLASSIFIED; } /** diff --git a/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/classification/ClearanceLevelManager.java b/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/classification/ClearanceLevelManager.java index c9610dc199..5ab1092fcc 100644 --- a/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/classification/ClearanceLevelManager.java +++ b/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/classification/ClearanceLevelManager.java @@ -18,12 +18,10 @@ */ package org.alfresco.module.org_alfresco_module_rm.classification; -import java.util.ArrayList; import java.util.List; -import org.alfresco.module.org_alfresco_module_rm.classification.ClassificationServiceException.LevelIdNotFound; - import com.google.common.collect.ImmutableList; +import org.alfresco.module.org_alfresco_module_rm.classification.ClassificationServiceException.LevelIdNotFound; /** * Container for the configured {@link ClearanceLevel} objects. @@ -32,22 +30,20 @@ import com.google.common.collect.ImmutableList; */ public class ClearanceLevelManager { - private static String NO_CLEARANCE_MSG = "rm.classification.noClearance"; - public static final ClearanceLevel NO_CLEARANCE = new ClearanceLevel(ClassificationLevelManager.UNCLASSIFIED, NO_CLEARANCE_MSG); - + private static String NO_CLEARANCE_MSG = "rm.classification.noClearance"; + public static final ClearanceLevel NO_CLEARANCE = new ClearanceLevel(ClassificationLevelManager.UNCLASSIFIED, NO_CLEARANCE_MSG); + /** An immutable list of clearance levels ordered from most to least secure. */ private ImmutableList clearanceLevels; /** - * Constructor that stores an immutable copy of the given levels. + * Store an immutable copy of the given levels. * * @param clearanceLevels A list of clearance levels ordered from most to least secure. */ - public ClearanceLevelManager(List clearanceLevels) + public void setClearanceLevels(List clearanceLevels) { - List temp = new ArrayList(clearanceLevels); - temp.add(temp.size(), NO_CLEARANCE); - this.clearanceLevels = ImmutableList.copyOf(temp); + this.clearanceLevels = ImmutableList.copyOf(clearanceLevels); } /** @return An immutable list of clearance levels ordered from most to least secure. */ diff --git a/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/classification/ContentClassificationService.java b/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/classification/ContentClassificationService.java new file mode 100644 index 0000000000..f0813bc758 --- /dev/null +++ b/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/classification/ContentClassificationService.java @@ -0,0 +1,70 @@ +/* + * 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.classification; + +import java.util.Set; + +import org.alfresco.module.org_alfresco_module_rm.classification.ClassificationServiceException.InvalidNode; +import org.alfresco.module.org_alfresco_module_rm.classification.ClassificationServiceException.LevelIdNotFound; +import org.alfresco.module.org_alfresco_module_rm.classification.ClassificationServiceException.ReasonIdNotFound; +import org.alfresco.service.cmr.repository.InvalidNodeRefException; +import org.alfresco.service.cmr.repository.NodeRef; + +/** + * A service to handle the classification of content. + * + * @author tpage + */ +public interface ContentClassificationService +{ + /** + * Returns the current classification level of a given node. + * + * @param nodeRef node reference + * @return {@link ClassificationLevel} classification level, unclassified if none + */ + ClassificationLevel getCurrentClassification(NodeRef nodeRef); + + /** + * Classify a piece of content. + * + * @param classificationLevelId The security clearance needed to access the content. + * @param classificationAuthority The name of the authority responsible for the classification of this content. + * @param classificationReasonIds A non-empty set of ids of reasons for classifying the content in this way. + * @param content The node to classify. + * @throws LevelIdNotFound If the supplied level id is not found. + * @throws ReasonIdNotFound If any of the supplied reason ids are not found. + * @throws InvalidNodeRefException If the node could not be found. + * @throws InvalidNode If the supplied node is not a content node. + */ + void classifyContent(String classificationLevelId, String classificationAuthority, Set classificationReasonIds, NodeRef content) + throws LevelIdNotFound, ReasonIdNotFound, InvalidNodeRefException, InvalidNode; + + /** + * Indicates whether the currently authenticated user has clearance to see the + * provided node. + *

+ * Note that users, regardless of their clearance level, are always cleared to see a node that has no classification + * applied. + * + * @param nodeRef node reference + * @return boolean true if cleared to see node, false otherwise + */ + boolean hasClearance(NodeRef nodeRef); +} 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 new file mode 100644 index 0000000000..3ffd6d31f8 --- /dev/null +++ b/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/classification/ContentClassificationServiceImpl.java @@ -0,0 +1,168 @@ +/* + * 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.classification; + +import static org.alfresco.module.org_alfresco_module_rm.util.RMParameterCheck.checkNotBlank; +import static org.alfresco.util.ParameterCheck.mandatory; + +import java.io.Serializable; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.alfresco.model.ContentModel; +import org.alfresco.module.org_alfresco_module_rm.classification.ClassificationServiceException.InvalidNode; +import org.alfresco.module.org_alfresco_module_rm.classification.model.ClassifiedContentModel; +import org.alfresco.module.org_alfresco_module_rm.util.ServiceBaseImpl; +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.namespace.QName; + +/** + * A service to handle the classification of content. + * + * @author tpage + */ +public class ContentClassificationServiceImpl extends ServiceBaseImpl implements ContentClassificationService, + ClassifiedContentModel +{ + private ClassificationLevelManager levelManager; + private ClassificationReasonManager reasonManager; + private NodeService nodeService; + private DictionaryService dictionaryService; + private SecurityClearanceService securityClearanceService; + private ClassificationServiceBootstrap classificationServiceBootstrap; + + public void setLevelManager(ClassificationLevelManager levelManager) { this.levelManager = levelManager; } + public void setReasonManager(ClassificationReasonManager reasonManager) { this.reasonManager = reasonManager; } + public void setNodeService(NodeService nodeService) { this.nodeService = nodeService; } + public void setDictionaryService(DictionaryService dictionaryService) { this.dictionaryService = dictionaryService; } + public void setSecurityClearanceService(SecurityClearanceService securityClearanceService) { this.securityClearanceService = securityClearanceService; } + public void setClassificationServiceBootstrap(ClassificationServiceBootstrap classificationServiceBootstrap) { this.classificationServiceBootstrap = classificationServiceBootstrap; } + + public void init() + { + this.levelManager = classificationServiceBootstrap.getClassificationLevelManager(); + this.reasonManager = classificationServiceBootstrap.getClassificationReasonManager(); + } + + @Override + public ClassificationLevel getCurrentClassification(NodeRef nodeRef) + { + // by default everything is unclassified + ClassificationLevel result = ClassificationLevelManager.UNCLASSIFIED; + + if (nodeService.hasAspect(nodeRef, ASPECT_CLASSIFIED)) + { + String classificationId = (String)nodeService.getProperty(nodeRef, PROP_CURRENT_CLASSIFICATION); + result = levelManager.findLevelById(classificationId); + } + + return result; + }; + + @Override + public void classifyContent(String classificationLevelId, String classificationAuthority, + Set classificationReasonIds, NodeRef content) + { + checkNotBlank("classificationLevelId", classificationLevelId); + checkNotBlank("classificationAuthority", classificationAuthority); + mandatory("classificationReasonIds", classificationReasonIds); + mandatory("content", content); + + if (!dictionaryService.isSubClass(nodeService.getType(content), ContentModel.TYPE_CONTENT)) + { + throw new InvalidNode(content, "The supplied node is not a content node."); + } + if (nodeService.hasAspect(content, ASPECT_CLASSIFIED)) + { + throw new UnsupportedOperationException( + "The content has already been classified. Reclassification is currently not supported."); + } + + Map properties = new HashMap(); + // Check the classification level id - an exception will be thrown if the id cannot be found + levelManager.findLevelById(classificationLevelId); + + // Initial classification id + if (nodeService.getProperty(content, PROP_INITIAL_CLASSIFICATION) == null) + { + properties.put(PROP_INITIAL_CLASSIFICATION, classificationLevelId); + } + + // Current classification id + properties.put(PROP_CURRENT_CLASSIFICATION, classificationLevelId); + + // Classification authority + properties.put(PROP_CLASSIFICATION_AUTHORITY, classificationAuthority); + + // Classification reason ids + HashSet classificationReasons = new HashSet<>(); + for (String classificationReasonId : classificationReasonIds) + { + // Check the classification reason id - an exception will be thrown if the id cannot be found + reasonManager.findReasonById(classificationReasonId); + classificationReasons.add(classificationReasonId); + } + properties.put(PROP_CLASSIFICATION_REASONS, classificationReasons); + + // Add aspect + nodeService.addAspect(content, ASPECT_CLASSIFIED, properties); + } + + @Override + public boolean hasClearance(NodeRef nodeRef) + { + boolean result = false; + + // Get the node's current classification + ClassificationLevel currentClassification = getCurrentClassification(nodeRef); + if (ClassificationLevelManager.UNCLASSIFIED.equals(currentClassification)) + { + // since the node is not classified user has clearance + result = true; + } + else + { + // Get the user's security clearance + SecurityClearance securityClearance = securityClearanceService.getUserSecurityClearance(); + if (!ClearanceLevelManager.NO_CLEARANCE.equals(securityClearance.getClearanceLevel())) + { + // get the users highest classification clearance + ClassificationLevel highestClassification = securityClearance.getClearanceLevel().getHighestClassificationLevel(); + + // if classification is less than or equal to highest classification then user has clearance + List allClassificationLevels = levelManager.getClassificationLevels(); + int highestIndex = allClassificationLevels.indexOf(highestClassification); + int currentIndex = allClassificationLevels.indexOf(currentClassification); + + if (highestIndex <= currentIndex) + { + // user has clearance + result = true; + } + } + } + + return result; + } +} diff --git a/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/classification/SecurityClearanceService.java b/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/classification/SecurityClearanceService.java index 7599687434..82a2251d2e 100644 --- a/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/classification/SecurityClearanceService.java +++ b/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/classification/SecurityClearanceService.java @@ -19,7 +19,6 @@ package org.alfresco.module.org_alfresco_module_rm.classification; import org.alfresco.query.PagingResults; -import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.security.NoSuchPersonException; import java.util.List; @@ -33,18 +32,6 @@ import java.util.List; */ public interface SecurityClearanceService { - /** - * Indicates whether the currently authenticated user has clearance to see the - * provided node. - *

- * Note that users, regardless of their clearance level, are always cleared to see a node that has no classification - * applied. - * - * @param nodeRef node reference - * @return boolean true if cleared to see node, false otherwise - */ - boolean hasClearance(NodeRef nodeRef); - /** * Get the currently authenticated user's security clearance. * diff --git a/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/classification/SecurityClearanceServiceImpl.java b/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/classification/SecurityClearanceServiceImpl.java index 017a449934..dbc69fdf4a 100644 --- a/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/classification/SecurityClearanceServiceImpl.java +++ b/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/classification/SecurityClearanceServiceImpl.java @@ -25,6 +25,8 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; +import com.google.common.collect.ImmutableList; +import org.alfresco.module.org_alfresco_module_rm.classification.ClassificationServiceException.LevelIdNotFound; import org.alfresco.module.org_alfresco_module_rm.util.ServiceBaseImpl; import org.alfresco.query.PagingRequest; import org.alfresco.query.PagingResults; @@ -43,75 +45,23 @@ public class SecurityClearanceServiceImpl extends ServiceBaseImpl implements Sec { /** The clearance levels currently configured in this server. */ private ClearanceLevelManager clearanceManager; - - private ClassificationService classificationService; - private PersonService personService; + /** The object containing the {@link ClassificationLevel}s in the system. */ + private ClassificationLevelManager classificationLevelManager; + private PersonService personService; + private ClassificationServiceBootstrap classificationServiceBootstrap; public void setClearanceManager(ClearanceLevelManager clearanceManager) { this.clearanceManager = clearanceManager; } - public void setClassificationService(ClassificationService service) { this.classificationService = service; } + public void setClassificationLevelManager(ClassificationLevelManager classificationLevelManager) { this.classificationLevelManager = classificationLevelManager; } public void setPersonService(PersonService service) { this.personService = service; } + public void setClassificationServiceBootstrap(ClassificationServiceBootstrap classificationServiceBootstrap) { this.classificationServiceBootstrap = classificationServiceBootstrap; } - /** - * Initialise and create a {@link ClearanceLevelManager}. This assumes that the {@link ClassificationService} has - * already been initialised. - */ - void initialise() + /** Store the references to the classification and clearance level managers in this class. */ + public void init() { - ArrayList clearanceLevels = new ArrayList(); - List classificationLevels = classificationService.getClassificationLevels(); - for (ClassificationLevel classificationLevel : classificationLevels) - { - if (!ClassificationLevelManager.UNCLASSIFIED.equals(classificationLevel)) - { - clearanceLevels.add(new ClearanceLevel(classificationLevel, classificationLevel.getDisplayLabelKey())); - } - } - this.clearanceManager = new ClearanceLevelManager(clearanceLevels); + this.classificationLevelManager = classificationServiceBootstrap.getClassificationLevelManager(); + this.clearanceManager = classificationServiceBootstrap.getClearanceLevelManager(); } - /** Get the clearance manager (for use in unit testing). */ - protected ClearanceLevelManager getClearanceManager() { return clearanceManager; } - - /** - * @see org.alfresco.module.org_alfresco_module_rm.classification.SecurityClearanceService#hasClearance(org.alfresco.service.cmr.repository.NodeRef) - */ - @Override - public boolean hasClearance(NodeRef nodeRef) - { - boolean result = false; - - // get the nodes current classification - ClassificationLevel currentClassification = classificationService.getCurrentClassification(nodeRef); - if (ClassificationLevelManager.UNCLASSIFIED.equals(currentClassification)) - { - // since the node is not classified user has clearance - result = true; - } - else - { - // get the users security clearance - SecurityClearance securityClearance = getUserSecurityClearance(); - if (!ClearanceLevelManager.NO_CLEARANCE.equals(securityClearance.getClearanceLevel())) - { - // get the users highest classification clearance - ClassificationLevel highestClassification = securityClearance.getClearanceLevel().getHighestClassificationLevel(); - - // if classification is less than or equal to highest classification then user has clearance - List allClassificationLevels = classificationService.getClassificationLevels(); - int highestIndex = allClassificationLevels.indexOf(highestClassification); - int currentIndex = allClassificationLevels.indexOf(currentClassification); - - if (highestIndex <= currentIndex) - { - // user has clearance - result = true; - } - } - } - - return result; - } - @Override public SecurityClearance getUserSecurityClearance() { @@ -123,7 +73,7 @@ public class SecurityClearanceServiceImpl extends ServiceBaseImpl implements Sec /** * Gets the users security clearnace. - * + * * @param userName user name * @return {@link SecurityClearance} provides information about the user and their clearance level */ @@ -132,21 +82,14 @@ public class SecurityClearanceServiceImpl extends ServiceBaseImpl implements Sec final NodeRef personNode = personService.getPerson(userName, false); final PersonInfo personInfo = personService.getPerson(personNode); - final ClassificationLevel classificationLevel; - + ClearanceLevel clearanceLevel = ClearanceLevelManager.NO_CLEARANCE; if (nodeService.hasAspect(personNode, ASPECT_SECURITY_CLEARANCE)) { - final String clearanceLevelValue = (String)nodeService.getProperty(personNode, PROP_CLEARANCE_LEVEL); - - classificationLevel = clearanceLevelValue == null ? classificationService.getUnclassifiedClassificationLevel() : - classificationService.getClassificationLevelById(clearanceLevelValue); - } - else - { - classificationLevel = classificationService.getUnclassifiedClassificationLevel(); + final String clearanceLevelId = (String)nodeService.getProperty(personNode, PROP_CLEARANCE_LEVEL); + clearanceLevel = (clearanceLevelId == null ? ClearanceLevelManager.NO_CLEARANCE + : clearanceManager.findLevelByClassificationLevelId(clearanceLevelId)); } - ClearanceLevel clearanceLevel = clearanceManager.findLevelByClassificationLevelId(classificationLevel.getId()); return new SecurityClearance(personInfo, clearanceLevel); } @@ -182,6 +125,33 @@ public class SecurityClearanceServiceImpl extends ServiceBaseImpl implements Sec }; } + /** + * Check if a classification can be accessed by a user with a given clearance. + * + * @param clearance The clearance of the user. + * @param classificationId The classification level to look for. + * @return {@code true} if the user can access the classification level. + */ + protected boolean isClearedForClassification(SecurityClearance clearance, String classificationId) + { + ImmutableList classificationLevels = classificationLevelManager.getClassificationLevels(); + + String clearanceId = clearance.getClearanceLevel().getHighestClassificationLevel().getId(); + for (ClassificationLevel classificationLevel : classificationLevels) + { + if (classificationLevel.getId().equals(clearanceId)) + { + return true; + } + else if (classificationLevel.getId().equals(classificationId)) + { + return false; + } + } + // Neither the clearance id nor the classification id were found - something's gone wrong. + throw new LevelIdNotFound(classificationId); + } + @Override public SecurityClearance setUserSecurityClearance(String userName, String clearanceId) { @@ -189,9 +159,13 @@ public class SecurityClearanceServiceImpl extends ServiceBaseImpl implements Sec ParameterCheck.mandatoryString("clearanceId", clearanceId); final NodeRef personNode = personService.getPerson(userName, false); - // This is just used to check the current user has clearance to see the specified level; it will throw a - // LevelIdNotFound exception if not. - classificationService.getClassificationLevelById(clearanceId); + + // Check the current user has clearance to see the specified level. + SecurityClearance userSecurityClearance = getUserSecurityClearance(); + if (!isClearedForClassification(userSecurityClearance, clearanceId)) + { + throw new LevelIdNotFound(clearanceId); + } nodeService.setProperty(personNode, PROP_CLEARANCE_LEVEL, clearanceId); diff --git a/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/classification/model/ClassifiedContentModel.java b/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/classification/model/ClassifiedContentModel.java index d463657c96..a8c6996e3b 100644 --- a/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/classification/model/ClassifiedContentModel.java +++ b/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/classification/model/ClassifiedContentModel.java @@ -18,6 +18,8 @@ */ package org.alfresco.module.org_alfresco_module_rm.classification.model; +import java.io.Serializable; + import org.alfresco.service.namespace.QName; /** @@ -34,6 +36,9 @@ public interface ClassifiedContentModel String CLF_URI = "http://www.alfresco.org/model/classifiedcontent/1.0"; String CLF_PREFIX = "clf"; + Serializable[] LEVELS_KEY = new String[] { "org.alfresco", "module.org_alfresco_module_rm", "classification.levels" }; + Serializable[] REASONS_KEY = new String[] { "org.alfresco", "module.org_alfresco_module_rm", "classification.reasons" }; + /** Classified aspect */ QName ASPECT_CLASSIFIED = QName.createQName(CLF_URI, "classified"); QName PROP_INITIAL_CLASSIFICATION = QName.createQName(CLF_URI, "initialClassification"); diff --git a/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/script/classification/ClassifyContentPost.java b/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/script/classification/ClassifyContentPost.java index dd8f423354..89dff922b9 100644 --- a/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/script/classification/ClassifyContentPost.java +++ b/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/script/classification/ClassifyContentPost.java @@ -1,19 +1,19 @@ /* - * Copyright (C) 2005-2014 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 + * Copyright (C) 2005-2014 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.script.classification; @@ -28,7 +28,7 @@ import java.util.HashSet; import java.util.Map; import java.util.Set; -import org.alfresco.module.org_alfresco_module_rm.classification.ClassificationService; +import org.alfresco.module.org_alfresco_module_rm.classification.ContentClassificationService; import org.alfresco.module.org_alfresco_module_rm.script.AbstractRmWebScript; import org.alfresco.service.cmr.repository.NodeRef; import org.json.JSONArray; @@ -50,23 +50,17 @@ public class ClassifyContentPost extends AbstractRmWebScript public static final String CLASSIFICATION_AUTHORITY = "classificationAuthority"; public static final String CLASSIFICATION_REASONS = "classificationReasons"; - /** Classification service */ - private ClassificationService classificationService; + /** The service responsible for classifying content. */ + private ContentClassificationService contentClassificationService; /** - * @return the classificationService + * Set the service responsible for classifying content. + * + * @param contentClassificationService The service responsible for classifying content. */ - protected ClassificationService getClassificationService() + public void setContentClassificationService(ContentClassificationService contentClassificationService) { - return this.classificationService; - } - - /** - * @param classificationService the classificationService to set - */ - public void setClassificationService(ClassificationService classificationService) - { - this.classificationService = classificationService; + this.contentClassificationService = contentClassificationService; } /** @@ -83,7 +77,7 @@ public class ClassifyContentPost extends AbstractRmWebScript Set classificationReasonIds = getClassificationReasonIds(jsonObject); NodeRef document = parseRequestForNodeRef(req); - getClassificationService().classifyContent(classificationLevelId, classificationAuthority, classificationReasonIds, document); + contentClassificationService.classifyContent(classificationLevelId, classificationAuthority, classificationReasonIds, document); Map model = new HashMap(1); model.put(SUCCESS, true); diff --git a/rm-server/test/java/org/alfresco/module/org_alfresco_module_rm/test/integration/classification/ClassificationLevelsTest.java b/rm-server/test/java/org/alfresco/module/org_alfresco_module_rm/test/integration/classification/ClassificationLevelsTest.java index 4a3a13ed85..367d832edd 100644 --- a/rm-server/test/java/org/alfresco/module/org_alfresco_module_rm/test/integration/classification/ClassificationLevelsTest.java +++ b/rm-server/test/java/org/alfresco/module/org_alfresco_module_rm/test/integration/classification/ClassificationLevelsTest.java @@ -64,7 +64,7 @@ public class ClassificationLevelsTest extends BaseRMTestCase { List levels = classificationService.getClassificationLevels(); assertNotNull(levels); - assertEquals(5, levels.size()); + assertEquals("Expected 5 levels to be configured (4 defined in the test plus Unclassified)", 5, levels.size()); ClassificationLevel level1 = levels.get(0); ClassificationLevel level2 = levels.get(1); diff --git a/rm-server/test/java/org/alfresco/module/org_alfresco_module_rm/test/integration/classification/ClassifyTest.java b/rm-server/test/java/org/alfresco/module/org_alfresco_module_rm/test/integration/classification/ClassifyTest.java index 9f79cd9b6c..57f64521a9 100644 --- a/rm-server/test/java/org/alfresco/module/org_alfresco_module_rm/test/integration/classification/ClassifyTest.java +++ b/rm-server/test/java/org/alfresco/module/org_alfresco_module_rm/test/integration/classification/ClassifyTest.java @@ -34,12 +34,12 @@ import org.alfresco.service.cmr.repository.NodeRef; */ public class ClassifyTest extends BaseRMTestCase { - /** test data */ - private static final String CLASSIFICATION_LEVEL = "level1"; - private static final String CLASSIFICATION_REASON = "Test Reason 1"; - private static final String CLASSIFICATION_AUTHORITY = "classification authority"; - private static final String RECORD_NAME = "recordname.txt"; - + /** test data */ + private static final String CLASSIFICATION_LEVEL = "level1"; + private static final String CLASSIFICATION_REASON = "Test Reason 1"; + private static final String CLASSIFICATION_AUTHORITY = "classification authority"; + private static final String RECORD_NAME = "recordname.txt"; + /** * Given that a record is frozen * And unclassified @@ -50,27 +50,27 @@ public class ClassifyTest extends BaseRMTestCase { doBehaviourDrivenTest(new BehaviourDrivenTest(AccessDeniedException.class) { - private NodeRef record; - + private NodeRef record; + public void given() throws Exception { - record = utils.createRecord(rmFolder, RECORD_NAME); - - NodeRef hold = holdService.createHold(filePlan, "my hold", "for test", "for test"); - holdService.addToHold(hold, record); + record = utils.createRecord(rmFolder, RECORD_NAME); + + NodeRef hold = holdService.createHold(filePlan, "my hold", "for test", "for test"); + holdService.addToHold(hold, record); } public void when() throws Exception { - classificationService.classifyContent( - CLASSIFICATION_LEVEL, - CLASSIFICATION_AUTHORITY, - Collections.singleton(CLASSIFICATION_REASON), - record); + contentClassificationService.classifyContent( + CLASSIFICATION_LEVEL, + CLASSIFICATION_AUTHORITY, + Collections.singleton(CLASSIFICATION_REASON), + record); } }); } - + /** * Given that a record is complete * And unclassified @@ -80,31 +80,31 @@ public class ClassifyTest extends BaseRMTestCase { doBehaviourDrivenTest(new BehaviourDrivenTest() { - private NodeRef record; - + private NodeRef record; + public void given() throws Exception { - record = utils.createRecord(rmFolder, RECORD_NAME); - utils.completeRecord(record); + record = utils.createRecord(rmFolder, RECORD_NAME); + utils.completeRecord(record); } public void when() throws Exception { - classificationService.classifyContent( - CLASSIFICATION_LEVEL, - CLASSIFICATION_AUTHORITY, - Collections.singleton(CLASSIFICATION_REASON), - record); + contentClassificationService.classifyContent( + CLASSIFICATION_LEVEL, + CLASSIFICATION_AUTHORITY, + Collections.singleton(CLASSIFICATION_REASON), + record); } @SuppressWarnings("unchecked") - public void then() throws Exception + public void then() throws Exception { - assertTrue(nodeService.hasAspect(record, ClassifiedContentModel.ASPECT_CLASSIFIED)); - assertEquals(CLASSIFICATION_LEVEL, (String)nodeService.getProperty(record, ClassifiedContentModel.PROP_INITIAL_CLASSIFICATION)); - assertEquals(CLASSIFICATION_LEVEL, (String)nodeService.getProperty(record, ClassifiedContentModel.PROP_CURRENT_CLASSIFICATION)); - assertEquals(CLASSIFICATION_AUTHORITY, (String)nodeService.getProperty(record, ClassifiedContentModel.PROP_CLASSIFICATION_AUTHORITY)); - assertEquals(Collections.singletonList(CLASSIFICATION_REASON), (List)nodeService.getProperty(record, ClassifiedContentModel.PROP_CLASSIFICATION_REASONS)); + assertTrue(nodeService.hasAspect(record, ClassifiedContentModel.ASPECT_CLASSIFIED)); + assertEquals(CLASSIFICATION_LEVEL, (String)nodeService.getProperty(record, ClassifiedContentModel.PROP_INITIAL_CLASSIFICATION)); + assertEquals(CLASSIFICATION_LEVEL, (String)nodeService.getProperty(record, ClassifiedContentModel.PROP_CURRENT_CLASSIFICATION)); + assertEquals(CLASSIFICATION_AUTHORITY, (String)nodeService.getProperty(record, ClassifiedContentModel.PROP_CLASSIFICATION_AUTHORITY)); + assertEquals(Collections.singletonList(CLASSIFICATION_REASON), (List)nodeService.getProperty(record, ClassifiedContentModel.PROP_CLASSIFICATION_REASONS)); } }); } diff --git a/rm-server/test/java/org/alfresco/module/org_alfresco_module_rm/test/util/BaseRMTestCase.java b/rm-server/test/java/org/alfresco/module/org_alfresco_module_rm/test/util/BaseRMTestCase.java index c304645469..36b66555ef 100644 --- a/rm-server/test/java/org/alfresco/module/org_alfresco_module_rm/test/util/BaseRMTestCase.java +++ b/rm-server/test/java/org/alfresco/module/org_alfresco_module_rm/test/util/BaseRMTestCase.java @@ -30,6 +30,7 @@ import org.alfresco.module.org_alfresco_module_rm.audit.RecordsManagementAuditSe import org.alfresco.module.org_alfresco_module_rm.capability.CapabilityService; import org.alfresco.module.org_alfresco_module_rm.capability.RMPermissionModel; import org.alfresco.module.org_alfresco_module_rm.classification.ClassificationService; +import org.alfresco.module.org_alfresco_module_rm.classification.ContentClassificationService; import org.alfresco.module.org_alfresco_module_rm.dataset.DataSetService; import org.alfresco.module.org_alfresco_module_rm.disposition.DispositionSchedule; import org.alfresco.module.org_alfresco_module_rm.disposition.DispositionService; @@ -162,6 +163,7 @@ public abstract class BaseRMTestCase extends RetryingTransactionHelperTestCase protected InplaceRecordService inplaceRecordService; protected RelationshipService relationshipService; protected ClassificationService classificationService; + protected ContentClassificationService contentClassificationService; /** test data */ protected String siteId; @@ -273,7 +275,7 @@ public abstract class BaseRMTestCase extends RetryingTransactionHelperTestCase */ protected boolean isRMSiteTest() { - return true; + return true; } /** @@ -402,6 +404,7 @@ public abstract class BaseRMTestCase extends RetryingTransactionHelperTestCase inplaceRecordService = (InplaceRecordService) applicationContext.getBean("InplaceRecordService"); relationshipService = (RelationshipService) applicationContext.getBean("RelationshipService"); classificationService = (ClassificationService) applicationContext.getBean("ClassificationService"); + contentClassificationService = (ContentClassificationService) applicationContext.getBean("ContentClassificationService"); } /** @@ -482,44 +485,44 @@ public abstract class BaseRMTestCase extends RetryingTransactionHelperTestCase */ protected void setupTestData() { - doTestInTransaction(new Test() + doTestInTransaction(new Test() { public Void run() { - setupTestDataImpl(); + setupTestDataImpl(); - if (isRecordTest() && isRMSiteTest()) - { - setupTestRecords(); - } + if (isRecordTest() && isRMSiteTest()) + { + setupTestRecords(); + } - return null; + return null; } @Override public void test(Void result) throws Exception { - if (isRMSiteTest()) - { - if (isRecordTest()) - { - // declare a record - utils.completeRecord(recordDeclaredOne); - utils.completeRecord(recordDeclaredTwo); - } + if (isRMSiteTest()) + { + if (isRecordTest()) + { + // declare a record + utils.completeRecord(recordDeclaredOne); + utils.completeRecord(recordDeclaredTwo); + } - // unfiled container - unfiledContainer = filePlanService.getUnfiledContainer(filePlan); - assertNotNull(unfiledContainer); + // unfiled container + unfiledContainer = filePlanService.getUnfiledContainer(filePlan); + assertNotNull(unfiledContainer); - // holds container - holdsContainer = filePlanService.getHoldContainer(filePlan); - assertNotNull(holdsContainer); + // holds container + holdsContainer = filePlanService.getHoldContainer(filePlan); + assertNotNull(holdsContainer); - // transfers container - transfersContainer = filePlanService.getTransferContainer(filePlan); - assertNotNull(transfersContainer); - } + // transfers container + transfersContainer = filePlanService.getTransferContainer(filePlan); + assertNotNull(transfersContainer); + } } }, AuthenticationUtil.getSystemUserName()); } @@ -556,28 +559,28 @@ public abstract class BaseRMTestCase extends RetryingTransactionHelperTestCase if (isRMSiteTest()) { - siteId = GUID.generate(); - siteInfo = siteService.createSite( - "rm-site-dashboard", - siteId, - "title", - "descrition", - SiteVisibility.PUBLIC, - RecordsManagementModel.TYPE_RM_SITE); + siteId = GUID.generate(); + siteInfo = siteService.createSite( + "rm-site-dashboard", + siteId, + "title", + "descrition", + SiteVisibility.PUBLIC, + RecordsManagementModel.TYPE_RM_SITE); - filePlan = siteService.getContainer(siteId, RmSiteType.COMPONENT_DOCUMENT_LIBRARY); - assertNotNull("Site document library container was not created successfully.", filePlan); + filePlan = siteService.getContainer(siteId, RmSiteType.COMPONENT_DOCUMENT_LIBRARY); + assertNotNull("Site document library container was not created successfully.", filePlan); - // Create RM container - rmContainer = filePlanService.createRecordCategory(filePlan, "rmContainer"); - assertNotNull("Could not create rm container", rmContainer); + // Create RM container + rmContainer = filePlanService.createRecordCategory(filePlan, "rmContainer"); + assertNotNull("Could not create rm container", rmContainer); - // Create disposition schedule - dispositionSchedule = utils.createBasicDispositionSchedule(rmContainer); + // Create disposition schedule + dispositionSchedule = utils.createBasicDispositionSchedule(rmContainer); - // Create RM folder - rmFolder = recordFolderService.createRecordFolder(rmContainer, "rmFolder"); - assertNotNull("Could not create rm folder", rmFolder); + // Create RM folder + rmFolder = recordFolderService.createRecordFolder(rmContainer, "rmFolder"); + assertNotNull("Could not create rm folder", rmFolder); } } diff --git a/rm-server/unit-test/java/org/alfresco/module/org_alfresco_module_rm/classification/ClassificationLevelManagerUnitTest.java b/rm-server/unit-test/java/org/alfresco/module/org_alfresco_module_rm/classification/ClassificationLevelManagerUnitTest.java index f9c8f6c7fe..29d8febfc6 100644 --- a/rm-server/unit-test/java/org/alfresco/module/org_alfresco_module_rm/classification/ClassificationLevelManagerUnitTest.java +++ b/rm-server/unit-test/java/org/alfresco/module/org_alfresco_module_rm/classification/ClassificationLevelManagerUnitTest.java @@ -29,7 +29,7 @@ import org.junit.Test; /** * Unit tests for the {@link ClassificationLevelManager}. - * + * * @author tpage */ public class ClassificationLevelManagerUnitTest @@ -43,7 +43,8 @@ public class ClassificationLevelManagerUnitTest @Before public void setup() { - classificationLevelManager = new ClassificationLevelManager(LEVELS); + classificationLevelManager = new ClassificationLevelManager(); + classificationLevelManager.setClassificationLevels(LEVELS); } @Test public void findClassificationById_found() diff --git a/rm-server/unit-test/java/org/alfresco/module/org_alfresco_module_rm/classification/ClassificationReasonManagerUnitTest.java b/rm-server/unit-test/java/org/alfresco/module/org_alfresco_module_rm/classification/ClassificationReasonManagerUnitTest.java index 682e4a8843..16cdcf1168 100644 --- a/rm-server/unit-test/java/org/alfresco/module/org_alfresco_module_rm/classification/ClassificationReasonManagerUnitTest.java +++ b/rm-server/unit-test/java/org/alfresco/module/org_alfresco_module_rm/classification/ClassificationReasonManagerUnitTest.java @@ -29,7 +29,7 @@ import org.junit.Test; /** * Unit tests for the {@link ClassificationReasonManager}. - * + * * @author tpage */ public class ClassificationReasonManagerUnitTest @@ -43,7 +43,8 @@ public class ClassificationReasonManagerUnitTest @Before public void setup() { - classificationReasonManager = new ClassificationReasonManager(REASONS); + classificationReasonManager = new ClassificationReasonManager(); + classificationReasonManager.setClassificationReasons(REASONS); } @Test public void findClassificationById_found() diff --git a/rm-server/unit-test/java/org/alfresco/module/org_alfresco_module_rm/classification/ClassificationServiceBootstrapUnitTest.java b/rm-server/unit-test/java/org/alfresco/module/org_alfresco_module_rm/classification/ClassificationServiceBootstrapUnitTest.java new file mode 100644 index 0000000000..0ae13faf8d --- /dev/null +++ b/rm-server/unit-test/java/org/alfresco/module/org_alfresco_module_rm/classification/ClassificationServiceBootstrapUnitTest.java @@ -0,0 +1,236 @@ +/* + * 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.classification; + +import static java.util.Arrays.asList; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.anyString; +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Stream; + +import com.google.common.collect.ImmutableList; +import org.alfresco.module.org_alfresco_module_rm.classification.ClassificationServiceException.MissingConfiguration; +import org.alfresco.module.org_alfresco_module_rm.test.util.MockAuthenticationUtilHelper; +import org.alfresco.module.org_alfresco_module_rm.util.AuthenticationUtil; +import org.alfresco.service.cmr.attributes.AttributeService; +import org.apache.log4j.Appender; +import org.apache.log4j.Level; +import org.apache.log4j.spi.LoggingEvent; +import org.junit.Before; +import org.junit.Test; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +/** + * Unit tests for the {@link ClassificationServiceBootstrap}. + * + * @author tpage + */ +public class ClassificationServiceBootstrapUnitTest +{ + private static final List DEFAULT_CLASSIFICATION_LEVELS = asLevelList("Top Secret", "rm.classification.topSecret", + "Secret", "rm.classification.secret", + "Confidential", "rm.classification.confidential", + "No Clearance", "rm.classification.noClearance"); + private static final List ALT_CLASSIFICATION_LEVELS = asLevelList("Board", "B", + "Executive Management", "EM", + "Employee", "E", + "Public", "P"); + private static final List PLACEHOLDER_CLASSIFICATION_REASONS = asList(new ClassificationReason("id1", "label1"), + new ClassificationReason("id2", "label2")); + private static final List ALTERNATIVE_CLASSIFICATION_REASONS = asList(new ClassificationReason("id8", "label8"), + new ClassificationReason("id9", "label9")); + + /** + * A convenience method for turning lists of level id Strings into lists + * of {@code ClassificationLevel} objects. + * + * @param idsAndLabels A varargs/array of Strings like so: [ id0, label0, id1, label1... ] + * @throws IllegalArgumentException if {@code idsAndLabels} has a non-even length. + */ + public static List asLevelList(String ... idsAndLabels) + { + if (idsAndLabels.length % 2 != 0) + { + throw new IllegalArgumentException(String.format("Cannot create %s objects with %d args.", + ClassificationLevel.class.getSimpleName(), idsAndLabels.length)); + } + + final List levels = new ArrayList<>(idsAndLabels.length / 2); + + for (int i = 0; i < idsAndLabels.length; i += 2) + { + levels.add(new ClassificationLevel(idsAndLabels[i], idsAndLabels[i+1])); + } + + return levels; + } + + @InjectMocks ClassificationServiceBootstrap classificationServiceBootstrap; + @Mock ClassificationServiceDAO mockClassificationServiceDAO; + @Mock AttributeService mockAttributeService; + @Mock AuthenticationUtil mockAuthenticationUtil; + /** Using a mock appender in the class logger so that we can verify some of the logging requirements. */ + @Mock Appender mockAppender; + @Captor ArgumentCaptor loggingEventCaptor; + + @Before public void setUp() + { + MockitoAnnotations.initMocks(this); + MockAuthenticationUtilHelper.setup(mockAuthenticationUtil); + } + + @Test public void defaultLevelsConfigurationVanillaSystem() + { + when(mockClassificationServiceDAO.getConfiguredLevels()).thenReturn(DEFAULT_CLASSIFICATION_LEVELS); + when(mockAttributeService.getAttribute(anyString(), anyString(), anyString())).thenReturn(null); + + classificationServiceBootstrap.initConfiguredClassificationLevels(); + + verify(mockAttributeService).setAttribute(eq((Serializable) DEFAULT_CLASSIFICATION_LEVELS), + anyString(), anyString(), anyString()); + } + + @Test public void alternativeLevelsConfigurationPreviouslyStartedSystem() + { + when(mockClassificationServiceDAO.getConfiguredLevels()).thenReturn(ALT_CLASSIFICATION_LEVELS); + when(mockAttributeService.getAttribute(anyString(), anyString(), anyString())) + .thenReturn((Serializable) DEFAULT_CLASSIFICATION_LEVELS); + + classificationServiceBootstrap.initConfiguredClassificationLevels(); + + verify(mockAttributeService).setAttribute(eq((Serializable) ALT_CLASSIFICATION_LEVELS), + anyString(), anyString(), anyString()); + } + + @Test (expected=MissingConfiguration.class) + public void missingLevelsConfigurationVanillaSystemShouldFail() throws Exception + { + when(mockAttributeService.getAttribute(anyString(), anyString(), anyString())).thenReturn(null); + + classificationServiceBootstrap.initConfiguredClassificationLevels(); + } + + @Test public void pristineSystemShouldBootstrapReasonsConfiguration() + { + // There are no classification reasons stored in the AttributeService. + when(mockAttributeService.getAttribute(anyString(), anyString(), anyString())).thenReturn(null); + + // We'll use a small set of placeholder classification reasons. + when(mockClassificationServiceDAO.getConfiguredReasons()).thenReturn(PLACEHOLDER_CLASSIFICATION_REASONS); + + classificationServiceBootstrap.initConfiguredClassificationReasons(); + + verify(mockAttributeService).setAttribute(eq((Serializable)PLACEHOLDER_CLASSIFICATION_REASONS), + anyString(), anyString(), anyString()); + } + + @Test public void checkAttributesNotTouchedIfConfiguredReasonsHaveNotChanged() + { + // The classification reasons stored are the same values that are found on the classpath. + when(mockAttributeService.getAttribute(anyString(), anyString(), anyString())).thenReturn((Serializable)PLACEHOLDER_CLASSIFICATION_REASONS); + when(mockClassificationServiceDAO.getConfiguredReasons()).thenReturn(PLACEHOLDER_CLASSIFICATION_REASONS); + + classificationServiceBootstrap.initConfiguredClassificationReasons(); + + verify(mockAttributeService, never()).setAttribute(any(Serializable.class), + anyString(), anyString(), anyString()); + } + + /** + * Check that if the reasons supplied on the classpath differ from those already persisted then a warning is logged + * and no change is made to the persisted reasons. + *

+ * This test uses the underlying log4j implementation to insert a mock Appender, and tests this for the warning + * message. If the underlying logging framework is changed then this unit test will fail, and it may not be + * possible to/worth fixing. + */ + @Test public void previouslyStartedSystemShouldWarnIfConfiguredReasonsHaveChanged() + { + // The classification reasons stored are different from those found on the classpath. + when(mockAttributeService.getAttribute(anyString(), anyString(), anyString())).thenReturn( + (Serializable) PLACEHOLDER_CLASSIFICATION_REASONS); + when(mockClassificationServiceDAO.getConfiguredReasons()).thenReturn(ALTERNATIVE_CLASSIFICATION_REASONS); + + // Put the mock Appender into the log4j logger and allow warning messages to be received. + org.apache.log4j.Logger log4jLogger = org.apache.log4j.Logger.getLogger(ClassificationServiceBootstrap.class); + log4jLogger.addAppender(mockAppender); + Level normalLevel = log4jLogger.getLevel(); + log4jLogger.setLevel(Level.WARN); + + // Call the method under test. + classificationServiceBootstrap.initConfiguredClassificationReasons(); + + // Reset the logging level for other tests. + log4jLogger.setLevel(normalLevel); + + // Check the persisted values weren't changed. + verify(mockAttributeService, never()).setAttribute(any(Serializable.class), + anyString(), anyString(), anyString()); + + // Check that the warning message was logged. + verify(mockAppender).doAppend(loggingEventCaptor.capture()); + List loggingEvents = loggingEventCaptor.getAllValues(); + Stream messages = loggingEvents.stream().map(event -> event.getRenderedMessage()); + String expectedMessage = "Classification reasons configured in classpath do not match those stored in Alfresco. Alfresco will use the unchanged values stored in the database."; + assertTrue("Warning message not found in log.", messages.anyMatch(message -> message == expectedMessage)); + } + + @Test(expected = MissingConfiguration.class) + public void noReasonsFoundCausesException() + { + when(mockAttributeService.getAttribute(anyString(), anyString(), anyString())) + .thenReturn((Serializable) null); + when(mockClassificationServiceDAO.getConfiguredReasons()).thenReturn(null); + + classificationServiceBootstrap.initConfiguredClassificationReasons(); + } + + /** + * Check that the initialise method creates a clearance level corresponding to each classification level and that + * the display label for the lowest clearance level is "No Clearance" (rather than "Unclassified"). + */ + @Test public void initConfiguredClearanceLevels() + { + ClassificationLevel topSecret = new ClassificationLevel("1", "TopSecret"); + ClassificationLevel secret = new ClassificationLevel("2", "Secret"); + ImmutableList classificationLevels = ImmutableList.of(topSecret, secret, ClassificationLevelManager.UNCLASSIFIED); + + // Call the method under test. + classificationServiceBootstrap.initConfiguredClearanceLevels(classificationLevels); + + List clearanceLevels = classificationServiceBootstrap.getClearanceLevelManager().getClearanceLevels(); + assertEquals("There should be one clearance level for each classification level.", classificationLevels.size(), clearanceLevels.size()); + assertEquals("TopSecret", clearanceLevels.get(0).getDisplayLabel()); + assertEquals("Secret", clearanceLevels.get(1).getDisplayLabel()); + assertEquals("rm.classification.noClearance", clearanceLevels.get(2).getDisplayLabel()); + } +} diff --git a/rm-server/unit-test/java/org/alfresco/module/org_alfresco_module_rm/classification/ClassificationServiceImplUnitTest.java b/rm-server/unit-test/java/org/alfresco/module/org_alfresco_module_rm/classification/ClassificationServiceImplUnitTest.java index 460110497f..07be295efd 100644 --- a/rm-server/unit-test/java/org/alfresco/module/org_alfresco_module_rm/classification/ClassificationServiceImplUnitTest.java +++ b/rm-server/unit-test/java/org/alfresco/module/org_alfresco_module_rm/classification/ClassificationServiceImplUnitTest.java @@ -18,45 +18,21 @@ */ package org.alfresco.module.org_alfresco_module_rm.classification; -import static java.util.Arrays.asList; import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; -import static org.mockito.Matchers.any; -import static org.mockito.Matchers.anyString; -import static org.mockito.Matchers.eq; import static org.mockito.Mockito.doThrow; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; -import static org.mockito.Mockito.verifyNoMoreInteractions; -import static org.alfresco.module.org_alfresco_module_rm.test.util.AlfMock.generateNodeRef; -import static org.alfresco.module.org_alfresco_module_rm.test.util.AlfMock.generateText; import java.io.Serializable; import java.util.ArrayList; -import java.util.HashSet; import java.util.List; import java.util.Map; -import java.util.Set; -import java.util.stream.Stream; -import org.alfresco.model.ContentModel; -import org.alfresco.module.org_alfresco_module_rm.classification.ClassificationServiceException.InvalidNode; import org.alfresco.module.org_alfresco_module_rm.classification.ClassificationServiceException.LevelIdNotFound; -import org.alfresco.module.org_alfresco_module_rm.classification.ClassificationServiceException.MissingConfiguration; import org.alfresco.module.org_alfresco_module_rm.classification.ClassificationServiceException.ReasonIdNotFound; -import org.alfresco.module.org_alfresco_module_rm.classification.model.ClassifiedContentModel; import org.alfresco.module.org_alfresco_module_rm.test.util.ExceptionUtils; -import org.alfresco.module.org_alfresco_module_rm.test.util.MockAuthenticationUtilHelper; -import org.alfresco.module.org_alfresco_module_rm.util.AuthenticationUtil; -import org.alfresco.service.cmr.attributes.AttributeService; 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.namespace.QName; -import org.apache.log4j.Appender; -import org.apache.log4j.Level; -import org.apache.log4j.spi.LoggingEvent; import org.junit.Before; import org.junit.Test; import org.mockito.ArgumentCaptor; @@ -65,8 +41,6 @@ import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.MockitoAnnotations; -import com.google.common.collect.Sets; - /** * Unit tests for {@link ClassificationServiceImpl}. * @@ -79,18 +53,7 @@ public class ClassificationServiceImplUnitTest "Secret", "rm.classification.secret", "Confidential", "rm.classification.confidential", "No Clearance", "rm.classification.noClearance"); - private static final List ALT_CLASSIFICATION_LEVELS = asLevelList("Board", "B", - "Executive Management", "EM", - "Employee", "E", - "Public", "P"); - private static final List PLACEHOLDER_CLASSIFICATION_REASONS = asList(new ClassificationReason("id1", "label1"), - new ClassificationReason("id2", "label2")); - private static final List ALTERNATIVE_CLASSIFICATION_REASONS = asList(new ClassificationReason("id8", "label8"), - new ClassificationReason("id9", "label9")); - - private static final String CLASSIFICATION_LEVEL_ID = "classificationLevelId"; - private static final ClassificationLevel CLASSIFICATION_LEVEL = new ClassificationLevel(CLASSIFICATION_LEVEL_ID, generateText()); - + /** * A convenience method for turning lists of level id Strings into lists * of {@code ClassificationLevel} objects. @@ -117,128 +80,15 @@ public class ClassificationServiceImplUnitTest @InjectMocks private ClassificationServiceImpl classificationServiceImpl; - @Mock private AttributeService mockedAttributeService; - @Mock private AuthenticationUtil mockedAuthenticationUtil; - @Mock private ClassificationServiceDAO mockClassificationServiceDAO; @Mock private NodeService mockNodeService; @Mock private DictionaryService mockDictionaryService; - /** Using a mock appender in the class logger so that we can verify some of the logging requirements. */ - @Mock private Appender mockAppender; @Mock private ClassificationLevelManager mockLevelManager; @Mock private ClassificationReasonManager mockReasonManager; - @Captor private ArgumentCaptor loggingEventCaptor; @Captor private ArgumentCaptor> propertiesCaptor; @Before public void setUp() { MockitoAnnotations.initMocks(this); - MockAuthenticationUtilHelper.setup(mockedAuthenticationUtil); - } - - @Test public void defaultLevelsConfigurationVanillaSystem() - { - when(mockClassificationServiceDAO.getConfiguredLevels()).thenReturn(DEFAULT_CLASSIFICATION_LEVELS); - when(mockedAttributeService.getAttribute(anyString(), anyString(), anyString())).thenReturn(null); - - classificationServiceImpl.initConfiguredClassificationLevels(); - - verify(mockedAttributeService).setAttribute(eq((Serializable) DEFAULT_CLASSIFICATION_LEVELS), - anyString(), anyString(), anyString()); - } - - @Test public void alternativeLevelsConfigurationPreviouslyStartedSystem() - { - when(mockClassificationServiceDAO.getConfiguredLevels()).thenReturn(ALT_CLASSIFICATION_LEVELS); - when(mockedAttributeService.getAttribute(anyString(), anyString(), anyString())) - .thenReturn((Serializable) DEFAULT_CLASSIFICATION_LEVELS); - - classificationServiceImpl.initConfiguredClassificationLevels(); - - verify(mockedAttributeService).setAttribute(eq((Serializable) ALT_CLASSIFICATION_LEVELS), - anyString(), anyString(), anyString()); - } - - @Test (expected=MissingConfiguration.class) - public void missingLevelsConfigurationVanillaSystemShouldFail() throws Exception - { - when(mockedAttributeService.getAttribute(anyString(), anyString(), anyString())).thenReturn(null); - - classificationServiceImpl.initConfiguredClassificationLevels(); - } - - @Test public void pristineSystemShouldBootstrapReasonsConfiguration() - { - // There are no classification reasons stored in the AttributeService. - when(mockedAttributeService.getAttribute(anyString(), anyString(), anyString())).thenReturn(null); - - // We'll use a small set of placeholder classification reasons. - when(mockClassificationServiceDAO.getConfiguredReasons()).thenReturn(PLACEHOLDER_CLASSIFICATION_REASONS); - - classificationServiceImpl.initConfiguredClassificationReasons(); - - verify(mockedAttributeService).setAttribute(eq((Serializable)PLACEHOLDER_CLASSIFICATION_REASONS), - anyString(), anyString(), anyString()); - } - - @Test public void checkAttributesNotTouchedIfConfiguredReasonsHaveNotChanged() - { - // The classification reasons stored are the same values that are found on the classpath. - when(mockedAttributeService.getAttribute(anyString(), anyString(), anyString())).thenReturn((Serializable)PLACEHOLDER_CLASSIFICATION_REASONS); - when(mockClassificationServiceDAO.getConfiguredReasons()).thenReturn(PLACEHOLDER_CLASSIFICATION_REASONS); - - classificationServiceImpl.initConfiguredClassificationReasons(); - - verify(mockedAttributeService, never()).setAttribute(any(Serializable.class), - anyString(), anyString(), anyString()); - } - - /** - * Check that if the reasons supplied on the classpath differ from those already persisted then a warning is logged - * and no change is made to the persisted reasons. - *

- * This test uses the underlying log4j implementation to insert a mock Appender, and tests this for the warning - * message. If the underlying logging framework is changed then this unit test will fail, and it may not be - * possible to/worth fixing. - */ - @Test public void previouslyStartedSystemShouldWarnIfConfiguredReasonsHaveChanged() - { - // The classification reasons stored are different from those found on the classpath. - when(mockedAttributeService.getAttribute(anyString(), anyString(), anyString())).thenReturn( - (Serializable) PLACEHOLDER_CLASSIFICATION_REASONS); - when(mockClassificationServiceDAO.getConfiguredReasons()).thenReturn(ALTERNATIVE_CLASSIFICATION_REASONS); - - // Put the mock Appender into the log4j logger and allow warning messages to be received. - org.apache.log4j.Logger log4jLogger = org.apache.log4j.Logger.getLogger(ClassificationServiceImpl.class); - log4jLogger.addAppender(mockAppender); - Level normalLevel = log4jLogger.getLevel(); - log4jLogger.setLevel(Level.WARN); - - // Call the method under test. - classificationServiceImpl.initConfiguredClassificationReasons(); - - // Reset the logging level for other tests. - log4jLogger.setLevel(normalLevel); - - // Check the persisted values weren't changed. - verify(mockedAttributeService, never()).setAttribute(any(Serializable.class), - anyString(), anyString(), anyString()); - - // Check that the warning message was logged. - verify(mockAppender).doAppend(loggingEventCaptor.capture()); - List loggingEvents = loggingEventCaptor.getAllValues(); - Stream messages = loggingEvents.stream().map(event -> event.getRenderedMessage()); - String expectedMessage = "Classification reasons configured in classpath do not match those stored in Alfresco. Alfresco will use the unchanged values stored in the database."; - assertTrue("Warning message not found in log.", messages.anyMatch(message -> message == expectedMessage)); - } - - @Test(expected = MissingConfiguration.class) - public void noReasonsFoundCausesException() - { - when(mockedAttributeService.getAttribute(anyString(), anyString(), anyString())) - .thenReturn((Serializable) null); - when(mockClassificationServiceDAO.getConfiguredReasons()).thenReturn(null); - - classificationServiceImpl.initConfiguredClassificationReasons(); } /** @@ -271,69 +121,6 @@ public class ClassificationServiceImplUnitTest assertEquals("Expected an empty list when the target level is not found.", 0, actual.size()); } - /** Classify a piece of content with a couple of reasons and check the NodeService is called correctly. */ - @Test public void classifyContent_success() - { - // Create a level and two reasons. - ClassificationLevel level = new ClassificationLevel("levelId1", "displayLabelKey"); - ClassificationReason reason1 = new ClassificationReason("reasonId1", "displayLabelKey1"); - ClassificationReason reason2 = new ClassificationReason("reasonId2", "displayLabelKey2"); - // Set up the managers to return these objects when the ids are provided. - when(mockLevelManager.findLevelById("levelId1")).thenReturn(level); - when(mockReasonManager.findReasonById("reasonId1")).thenReturn(reason1); - when(mockReasonManager.findReasonById("reasonId2")).thenReturn(reason2); - // Create a content node. - NodeRef content = new NodeRef("fake://content/"); - when(mockDictionaryService.isSubClass(mockNodeService.getType(content), ContentModel.TYPE_CONTENT)).thenReturn(true); - when(mockNodeService.hasAspect(content, ClassifiedContentModel.ASPECT_CLASSIFIED)).thenReturn(false); - - // Call the method under test. - classificationServiceImpl.classifyContent("levelId1", "classificationAuthority", - Sets.newHashSet("reasonId1", "reasonId2"), content); - - verify(mockNodeService).addAspect(eq(content), eq(ClassifiedContentModel.ASPECT_CLASSIFIED), - propertiesCaptor.capture()); - // Check the properties that were received. - Map properties = propertiesCaptor.getValue(); - HashSet expectedPropertyKeys = Sets.newHashSet(ClassifiedContentModel.PROP_INITIAL_CLASSIFICATION, - ClassifiedContentModel.PROP_CURRENT_CLASSIFICATION, - ClassifiedContentModel.PROP_CLASSIFICATION_AUTHORITY, - ClassifiedContentModel.PROP_CLASSIFICATION_REASONS); - assertEquals("Aspect created with unexpected set of keys.", expectedPropertyKeys, properties.keySet()); - assertEquals("Unexpected initial classification.", level.getId(), properties.get(ClassifiedContentModel.PROP_INITIAL_CLASSIFICATION)); - assertEquals("Unexpected current classification.", level.getId(), properties.get(ClassifiedContentModel.PROP_CURRENT_CLASSIFICATION)); - assertEquals("Unexpected authority.", "classificationAuthority", properties.get(ClassifiedContentModel.PROP_CLASSIFICATION_AUTHORITY)); - Set expectedReasonIds = Sets.newHashSet("reasonId1", "reasonId2"); - assertEquals("Unexpected set of reasons.", expectedReasonIds, properties.get(ClassifiedContentModel.PROP_CLASSIFICATION_REASONS)); - } - - /** Classify a folder using the classifyContent method and check that an exception is raised. */ - @Test(expected = InvalidNode.class) - public void classifyContent_notContent() - { - // Create a folder node. - NodeRef notAPieceOfContent = new NodeRef("not://a/piece/of/content/"); - when(mockNodeService.getType(notAPieceOfContent)).thenReturn(ContentModel.TYPE_FOLDER); - - // Call the method under test. - classificationServiceImpl.classifyContent("levelId1", "classificationAuthority", - Sets.newHashSet("reasonId1", "reasonId2"), notAPieceOfContent); - } - - /** Classify a piece of content that has already been classified. */ - @Test(expected = UnsupportedOperationException.class) - public void classifyContent_alreadyClassified() - { - // Create a classified piece of content. - NodeRef classifiedContent = new NodeRef("classified://content/"); - when(mockDictionaryService.isSubClass(mockNodeService.getType(classifiedContent), ContentModel.TYPE_CONTENT)).thenReturn(true); - when(mockNodeService.hasAspect(classifiedContent, ClassifiedContentModel.ASPECT_CLASSIFIED)).thenReturn(true); - - // Call the method under test. - classificationServiceImpl.classifyContent("levelId1", "classificationAuthority", - Sets.newHashSet("reasonId1", "reasonId2"), classifiedContent); - } - @Test public void getClassificationLevelById() { @@ -369,48 +156,4 @@ public class ClassificationServiceImplUnitTest doThrow(new ReasonIdNotFound("Id not found!")).when(mockReasonManager).findReasonById(classificationReasonId); classificationServiceImpl.getClassificationReasonById(classificationReasonId); } - - /** - * Given that a node does not have the classify aspect applied - * When I ask for the nodes classification - * Then 'Unclassified' is returned - */ - @Test - public void getCurrentClassificationWithoutAspectApplied() - { - NodeRef nodeRef = generateNodeRef(mockNodeService); - when(mockNodeService.hasAspect(nodeRef, ClassifiedContentModel.ASPECT_CLASSIFIED)) - .thenReturn(false); - - ClassificationLevel classificationLevel = classificationServiceImpl.getCurrentClassification(nodeRef); - - assertEquals(ClassificationLevelManager.UNCLASSIFIED, classificationLevel); - verify(mockNodeService).hasAspect(nodeRef, ClassifiedContentModel.ASPECT_CLASSIFIED); - verifyNoMoreInteractions(mockNodeService); - } - - /** - * Given that a node is classified - * When I ask for the node classification - * Then I get the correct classificationlevel - */ - @Test - public void getCurrentClassification() - { - NodeRef nodeRef = generateNodeRef(mockNodeService); - when(mockNodeService.hasAspect(nodeRef, ClassifiedContentModel.ASPECT_CLASSIFIED)) - .thenReturn(true); - when(mockNodeService.getProperty(nodeRef, ClassifiedContentModel.PROP_CURRENT_CLASSIFICATION)) - .thenReturn(CLASSIFICATION_LEVEL_ID); - when(mockLevelManager.findLevelById(CLASSIFICATION_LEVEL_ID)) - .thenReturn(CLASSIFICATION_LEVEL); - - ClassificationLevel classificationLevel = classificationServiceImpl.getCurrentClassification(nodeRef); - - assertEquals(CLASSIFICATION_LEVEL, classificationLevel); - verify(mockNodeService).hasAspect(nodeRef, ClassifiedContentModel.ASPECT_CLASSIFIED); - verify(mockNodeService).getProperty(nodeRef, ClassifiedContentModel.PROP_CURRENT_CLASSIFICATION); - verify(mockLevelManager).findLevelById(CLASSIFICATION_LEVEL_ID); - verifyNoMoreInteractions(mockNodeService, mockLevelManager); - } } diff --git a/rm-server/unit-test/java/org/alfresco/module/org_alfresco_module_rm/classification/ClearanceLevelManagerUnitTest.java b/rm-server/unit-test/java/org/alfresco/module/org_alfresco_module_rm/classification/ClearanceLevelManagerUnitTest.java index 547d771e4b..63dac33a97 100644 --- a/rm-server/unit-test/java/org/alfresco/module/org_alfresco_module_rm/classification/ClearanceLevelManagerUnitTest.java +++ b/rm-server/unit-test/java/org/alfresco/module/org_alfresco_module_rm/classification/ClearanceLevelManagerUnitTest.java @@ -47,8 +47,11 @@ public class ClearanceLevelManagerUnitTest @Before public void setup() { - List clearanceLevels = ImmutableList.of(TOP_SECRET_CLEARANCE, SECRET_CLEARANCE); - clearanceLevelManager = new ClearanceLevelManager(clearanceLevels); + List clearanceLevels = ImmutableList.of(TOP_SECRET_CLEARANCE, + SECRET_CLEARANCE, + ClearanceLevelManager.NO_CLEARANCE); + clearanceLevelManager = new ClearanceLevelManager(); + clearanceLevelManager.setClearanceLevels(clearanceLevels); } /** Check that the secret clearance can be found from the classification level id "S". */ @@ -65,16 +68,17 @@ public class ClearanceLevelManagerUnitTest { clearanceLevelManager.findLevelByClassificationLevelId("UNKNOWN ID"); } - + /** - * Given that I have created a clearance level manager from a list of clearance levels + * Given that I have created a clearance level manager from a list of clearance levels (including the no clearance level) * Then the no clearance level is available + * + * ...(and not added in the same way as for {@link ClassificationLevelManager#setClassificationLevels(List)}). */ @Test public void noClearanceLevel() { - assertEquals(3, clearanceLevelManager.getClearanceLevels().size()); - ClearanceLevel noClearance = clearanceLevelManager.findLevelByClassificationLevelId(ClassificationLevelManager.UNCLASSIFIED_ID); - assertEquals(ClearanceLevelManager.NO_CLEARANCE, noClearance); - assertEquals(ClassificationLevelManager.UNCLASSIFIED, noClearance.getHighestClassificationLevel()); + assertEquals("The three clearance levels should be available.", 3, clearanceLevelManager.getClearanceLevels().size()); + ClearanceLevel noClearance = clearanceLevelManager.findLevelByClassificationLevelId(ClassificationLevelManager.UNCLASSIFIED_ID); + assertEquals("'No clearance' could not be found.", ClearanceLevelManager.NO_CLEARANCE, noClearance); } } 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 new file mode 100644 index 0000000000..1554fe9da5 --- /dev/null +++ b/rm-server/unit-test/java/org/alfresco/module/org_alfresco_module_rm/classification/ContentClassificationServiceImplUnitTest.java @@ -0,0 +1,313 @@ +/* + * 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.classification; + +import static org.alfresco.module.org_alfresco_module_rm.test.util.AlfMock.generateNodeRef; +import static org.alfresco.module.org_alfresco_module_rm.test.util.AlfMock.generateText; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.when; + +import java.io.Serializable; +import java.util.Arrays; +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.Sets; +import org.alfresco.model.ContentModel; +import org.alfresco.module.org_alfresco_module_rm.classification.ClassificationServiceException.InvalidNode; +import org.alfresco.module.org_alfresco_module_rm.classification.model.ClassifiedContentModel; +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.namespace.QName; +import org.junit.Before; +import org.junit.Test; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +/** + * Unit tests for {@link ContentClassificationServiceImpl}. + * + * @author tpage + */ +public class ContentClassificationServiceImplUnitTest implements ClassifiedContentModel +{ + private static final String CLASSIFICATION_LEVEL_ID = "classificationLevelId"; + private static final ClassificationLevel CLASSIFICATION_LEVEL = new ClassificationLevel(CLASSIFICATION_LEVEL_ID, generateText()); + + @InjectMocks ContentClassificationServiceImpl contentClassificationServiceImpl; + @Mock ClassificationLevelManager mockLevelManager; + @Mock ClassificationReasonManager mockReasonManager; + @Mock NodeService mockNodeService; + @Mock DictionaryService mockDictionaryService; + @Mock SecurityClearanceService mockSecurityClearanceService; + @Captor ArgumentCaptor> propertiesCaptor; + + @Before public void setUp() + { + MockitoAnnotations.initMocks(this); + } + + /** Classify a piece of content with a couple of reasons and check the NodeService is called correctly. */ + @Test public void classifyContent_success() + { + // Create a level and two reasons. + ClassificationLevel level = new ClassificationLevel("levelId1", "displayLabelKey"); + ClassificationReason reason1 = new ClassificationReason("reasonId1", "displayLabelKey1"); + ClassificationReason reason2 = new ClassificationReason("reasonId2", "displayLabelKey2"); + // Set up the managers to return these objects when the ids are provided. + when(mockLevelManager.findLevelById("levelId1")).thenReturn(level); + when(mockReasonManager.findReasonById("reasonId1")).thenReturn(reason1); + when(mockReasonManager.findReasonById("reasonId2")).thenReturn(reason2); + // Create a content node. + NodeRef content = new NodeRef("fake://content/"); + when(mockDictionaryService.isSubClass(mockNodeService.getType(content), ContentModel.TYPE_CONTENT)).thenReturn(true); + when(mockNodeService.hasAspect(content, ClassifiedContentModel.ASPECT_CLASSIFIED)).thenReturn(false); + + // Call the method under test. + contentClassificationServiceImpl.classifyContent("levelId1", "classificationAuthority", + Sets.newHashSet("reasonId1", "reasonId2"), content); + + verify(mockNodeService).addAspect(eq(content), eq(ClassifiedContentModel.ASPECT_CLASSIFIED), + propertiesCaptor.capture()); + // Check the properties that were received. + Map properties = propertiesCaptor.getValue(); + HashSet expectedPropertyKeys = Sets.newHashSet(ClassifiedContentModel.PROP_INITIAL_CLASSIFICATION, + ClassifiedContentModel.PROP_CURRENT_CLASSIFICATION, + ClassifiedContentModel.PROP_CLASSIFICATION_AUTHORITY, + ClassifiedContentModel.PROP_CLASSIFICATION_REASONS); + assertEquals("Aspect created with unexpected set of keys.", expectedPropertyKeys, properties.keySet()); + assertEquals("Unexpected initial classification.", level.getId(), properties.get(ClassifiedContentModel.PROP_INITIAL_CLASSIFICATION)); + assertEquals("Unexpected current classification.", level.getId(), properties.get(ClassifiedContentModel.PROP_CURRENT_CLASSIFICATION)); + assertEquals("Unexpected authority.", "classificationAuthority", properties.get(ClassifiedContentModel.PROP_CLASSIFICATION_AUTHORITY)); + Set expectedReasonIds = Sets.newHashSet("reasonId1", "reasonId2"); + assertEquals("Unexpected set of reasons.", expectedReasonIds, properties.get(ClassifiedContentModel.PROP_CLASSIFICATION_REASONS)); + } + + /** Classify a folder using the classifyContent method and check that an exception is raised. */ + @Test(expected = InvalidNode.class) + public void classifyContent_notContent() + { + // Create a folder node. + NodeRef notAPieceOfContent = new NodeRef("not://a/piece/of/content/"); + when(mockNodeService.getType(notAPieceOfContent)).thenReturn(ContentModel.TYPE_FOLDER); + + // Call the method under test. + contentClassificationServiceImpl.classifyContent("levelId1", "classificationAuthority", + Sets.newHashSet("reasonId1", "reasonId2"), notAPieceOfContent); + } + + /** Classify a piece of content that has already been classified. */ + @Test(expected = UnsupportedOperationException.class) + public void classifyContent_alreadyClassified() + { + // Create a classified piece of content. + NodeRef classifiedContent = new NodeRef("classified://content/"); + when(mockDictionaryService.isSubClass(mockNodeService.getType(classifiedContent), ContentModel.TYPE_CONTENT)).thenReturn(true); + when(mockNodeService.hasAspect(classifiedContent, ClassifiedContentModel.ASPECT_CLASSIFIED)).thenReturn(true); + + // Call the method under test. + contentClassificationServiceImpl.classifyContent("levelId1", "classificationAuthority", + Sets.newHashSet("reasonId1", "reasonId2"), classifiedContent); + } + + /** + * Given that a node does not have the classify aspect applied + * When I ask for the nodes classification + * Then 'Unclassified' is returned + */ + @Test + public void getCurrentClassificationWithoutAspectApplied() + { + NodeRef nodeRef = generateNodeRef(mockNodeService); + when(mockNodeService.hasAspect(nodeRef, ClassifiedContentModel.ASPECT_CLASSIFIED)) + .thenReturn(false); + + ClassificationLevel classificationLevel = contentClassificationServiceImpl.getCurrentClassification(nodeRef); + + assertEquals(ClassificationLevelManager.UNCLASSIFIED, classificationLevel); + verify(mockNodeService).hasAspect(nodeRef, ClassifiedContentModel.ASPECT_CLASSIFIED); + verifyNoMoreInteractions(mockNodeService); + } + + /** + * Given that a node is classified + * When I ask for the node classification + * Then I get the correct classificationlevel + */ + @Test + public void getCurrentClassification() + { + NodeRef nodeRef = generateNodeRef(mockNodeService); + when(mockNodeService.hasAspect(nodeRef, ClassifiedContentModel.ASPECT_CLASSIFIED)) + .thenReturn(true); + when(mockNodeService.getProperty(nodeRef, ClassifiedContentModel.PROP_CURRENT_CLASSIFICATION)) + .thenReturn(CLASSIFICATION_LEVEL_ID); + when(mockLevelManager.findLevelById(CLASSIFICATION_LEVEL_ID)) + .thenReturn(CLASSIFICATION_LEVEL); + + ClassificationLevel classificationLevel = contentClassificationServiceImpl.getCurrentClassification(nodeRef); + + assertEquals(CLASSIFICATION_LEVEL, classificationLevel); + verify(mockNodeService).hasAspect(nodeRef, ClassifiedContentModel.ASPECT_CLASSIFIED); + verify(mockNodeService).getProperty(nodeRef, ClassifiedContentModel.PROP_CURRENT_CLASSIFICATION); + verify(mockLevelManager).findLevelById(CLASSIFICATION_LEVEL_ID); + verifyNoMoreInteractions(mockNodeService, mockLevelManager); + } + + + /** + * Given that the node is unclassified + * When I ask if the current user has clearance + * Then true + */ + @Test public void clearedForUnclassifiedNode() + { + // Content is unclassified by default. + NodeRef nodeRef = generateNodeRef(mockNodeService); + + assertTrue(contentClassificationServiceImpl.hasClearance(nodeRef)); + } + + /** + * Given that the node is classified + * And the user has no security clearance + * When I ask if the current user has clearance + * Then false + */ + @Test public void userWithNoClearanceIsntClearedOnClassifiedNode() + { + // assign test classification to node + NodeRef nodeRef = generateNodeRef(mockNodeService); + when(mockNodeService.hasAspect(nodeRef, ASPECT_CLASSIFIED)).thenReturn(true); + String classificationLevelId = generateText(); + when(mockNodeService.getProperty(nodeRef, PROP_CURRENT_CLASSIFICATION)).thenReturn(classificationLevelId); + ClassificationLevel classificationLevel = new ClassificationLevel(classificationLevelId, generateText()); + when(mockLevelManager.findLevelById(classificationLevelId)).thenReturn(classificationLevel); + + // create user with no clearance + SecurityClearance clearance = new SecurityClearance(mock(PersonInfo.class), ClearanceLevelManager.NO_CLEARANCE); + when(mockSecurityClearanceService.getUserSecurityClearance()).thenReturn(clearance); + + assertFalse(contentClassificationServiceImpl.hasClearance(nodeRef)); + } + + /** + * Given that the node is classified + * And the user has clearance grater than the classification + * When I ask if the user has clearance + * Then true + */ + @Test public void classifiedNodeUserClearanceGreater() + { + // init classification levels + ClassificationLevel topSecret = new ClassificationLevel("TopSecret", generateText()); + String secretId = "Secret"; + ClassificationLevel secret = new ClassificationLevel(secretId, generateText()); + ClassificationLevel confidential = new ClassificationLevel("Confidential", generateText()); + List classificationLevels = Arrays.asList(topSecret, secret, confidential, ClassificationLevelManager.UNCLASSIFIED); + when(mockLevelManager.getClassificationLevels()).thenReturn(ImmutableList.copyOf(classificationLevels)); + + // set nodes classification + NodeRef nodeRef = generateNodeRef(mockNodeService); + when(mockNodeService.hasAspect(nodeRef, ASPECT_CLASSIFIED)).thenReturn(true); + when(mockNodeService.getProperty(nodeRef, PROP_CURRENT_CLASSIFICATION)).thenReturn(secretId); + when(mockLevelManager.findLevelById(secretId)).thenReturn(secret); + + // set users security clearance + ClearanceLevel topSecretClearance = new ClearanceLevel(topSecret, "Top Secret"); + SecurityClearance clearance = new SecurityClearance(mock(PersonInfo.class), topSecretClearance); + when(mockSecurityClearanceService.getUserSecurityClearance()).thenReturn(clearance); + + assertTrue(contentClassificationServiceImpl.hasClearance(nodeRef)); + } + + /** + * Given that the node is classified + * And the user has clearance equal to the the classification + * When I ask if the user has clearance + * Then true + */ + @Test public void classifiedNodeUserClearanceEqual() + { + // init classification levels + ClassificationLevel topSecret = new ClassificationLevel("TopSecret", generateText()); + String secretId = "Secret"; + ClassificationLevel secret = new ClassificationLevel(secretId, generateText()); + ClassificationLevel confidential = new ClassificationLevel("Confidential", generateText()); + List classificationLevels = Arrays.asList(topSecret, secret, confidential, ClassificationLevelManager.UNCLASSIFIED); + when(mockLevelManager.getClassificationLevels()).thenReturn(ImmutableList.copyOf(classificationLevels)); + + // set nodes classification + NodeRef nodeRef = generateNodeRef(mockNodeService); + when(mockNodeService.hasAspect(nodeRef, ASPECT_CLASSIFIED)).thenReturn(true); + when(mockNodeService.getProperty(nodeRef, PROP_CURRENT_CLASSIFICATION)).thenReturn(secretId); + when(mockLevelManager.findLevelById(secretId)).thenReturn(secret); + + // set users security clearance + ClearanceLevel secretClearance = new ClearanceLevel(secret, "Secret"); + SecurityClearance clearance = new SecurityClearance(mock(PersonInfo.class), secretClearance); + when(mockSecurityClearanceService.getUserSecurityClearance()).thenReturn(clearance); + + assertTrue(contentClassificationServiceImpl.hasClearance(nodeRef)); + } + + /** + * Given that the node is classified + * And the user has clearance less than the classification + * When I ask if the user has clearance + * Then true + */ + @Test public void classifiedNodeUserClearanceLess() + { + // init classification levels + ClassificationLevel topSecret = new ClassificationLevel("TopSecret", generateText()); + String secretId = "Secret"; + ClassificationLevel secret = new ClassificationLevel(secretId, generateText()); + ClassificationLevel confidential = new ClassificationLevel("Confidential", generateText()); + List classificationLevels = Arrays.asList(topSecret, secret, confidential, ClassificationLevelManager.UNCLASSIFIED); + when(mockLevelManager.getClassificationLevels()).thenReturn(ImmutableList.copyOf(classificationLevels)); + + // set nodes classification + NodeRef nodeRef = generateNodeRef(mockNodeService); + when(mockNodeService.hasAspect(nodeRef, ASPECT_CLASSIFIED)).thenReturn(true); + when(mockNodeService.getProperty(nodeRef, PROP_CURRENT_CLASSIFICATION)).thenReturn(secretId); + when(mockLevelManager.findLevelById(secretId)).thenReturn(secret); + + // set users security clearance + ClearanceLevel confidentialClearance = new ClearanceLevel(confidential, "Confidential"); + SecurityClearance clearance = new SecurityClearance(mock(PersonInfo.class), confidentialClearance); + when(mockSecurityClearanceService.getUserSecurityClearance()).thenReturn(clearance); + + assertFalse(contentClassificationServiceImpl.hasClearance(nodeRef)); + } +} diff --git a/rm-server/unit-test/java/org/alfresco/module/org_alfresco_module_rm/classification/SecurityClearanceServiceImplUnitTest.java b/rm-server/unit-test/java/org/alfresco/module/org_alfresco_module_rm/classification/SecurityClearanceServiceImplUnitTest.java index 85528df09b..3b29bf6eea 100644 --- a/rm-server/unit-test/java/org/alfresco/module/org_alfresco_module_rm/classification/SecurityClearanceServiceImplUnitTest.java +++ b/rm-server/unit-test/java/org/alfresco/module/org_alfresco_module_rm/classification/SecurityClearanceServiceImplUnitTest.java @@ -20,14 +20,15 @@ package org.alfresco.module.org_alfresco_module_rm.classification; import static org.alfresco.module.org_alfresco_module_rm.classification.model.ClassifiedContentModel.ASPECT_SECURITY_CLEARANCE; import static org.alfresco.module.org_alfresco_module_rm.classification.model.ClassifiedContentModel.PROP_CLEARANCE_LEVEL; -import static org.junit.Assert.*; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; import static org.mockito.Matchers.anyBoolean; import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import static org.alfresco.module.org_alfresco_module_rm.test.util.AlfMock.*; -import java.util.ArrayList; import java.util.Arrays; import java.util.List; @@ -58,12 +59,13 @@ public class SecurityClearanceServiceImplUnitTest { @InjectMocks private SecurityClearanceServiceImpl securityClearanceServiceImpl; - @Mock private AuthenticationUtil mockedAuthenticationUtil; - @Mock private ClassificationService mockClassificationService; - @Mock private DictionaryService mockDictionaryService; - @Mock private NodeService mockNodeService; - @Mock private PersonService mockPersonService; - @Mock private ClearanceLevelManager mockClearanceLevelManager; + @Mock private AuthenticationUtil mockAuthenticationUtil; + @Mock private ClassificationLevelManager mockClassificationLevelManager; + @Mock private DictionaryService mockDictionaryService; + @Mock private NodeService mockNodeService; + @Mock private PersonService mockPersonService; + @Mock private ClassificationService mockClassificationService; + @Mock private ClearanceLevelManager mockClearanceLevelManager; @Before public void setUp() { @@ -76,15 +78,15 @@ public class SecurityClearanceServiceImplUnitTest final PersonInfo info = new PersonInfo(userNode, userName, firstName, lastName); when(mockPersonService.getPerson(eq(userName), anyBoolean())).thenReturn(userNode); - when(mockPersonService.getPerson(eq(userNode))).thenReturn(info); + when(mockPersonService.getPerson(userNode)).thenReturn(info); - when(mockNodeService.hasAspect(eq(userNode), eq(ASPECT_SECURITY_CLEARANCE))).thenReturn(clearanceLevel != null); - when(mockNodeService.getProperty(eq(userNode), eq(PROP_CLEARANCE_LEVEL))).thenReturn(clearanceLevel); + when(mockNodeService.hasAspect(userNode, ASPECT_SECURITY_CLEARANCE)).thenReturn(clearanceLevel != null); + when(mockNodeService.getProperty(userNode, PROP_CLEARANCE_LEVEL)).thenReturn(clearanceLevel); if (clearanceLevel != null) { final ClassificationLevel dummyValue = new ClassificationLevel(clearanceLevel, clearanceLevel); - when(mockClassificationService.getClassificationLevelById(eq(clearanceLevel))).thenReturn(dummyValue); + when(mockClassificationLevelManager.findLevelById(clearanceLevel)).thenReturn(dummyValue); } return info; @@ -93,246 +95,115 @@ public class SecurityClearanceServiceImplUnitTest @Test public void userWithNoClearanceGetsDefaultClearance() { final PersonInfo user1 = createMockPerson("user1", "User", "One", null); - MockAuthenticationUtilHelper.setup(mockedAuthenticationUtil, user1.getUserName()); - + MockAuthenticationUtilHelper.setup(mockAuthenticationUtil, user1.getUserName()); when(mockClassificationService.getUnclassifiedClassificationLevel()) - .thenReturn(ClassificationLevelManager.UNCLASSIFIED); + .thenReturn(ClassificationLevelManager.UNCLASSIFIED); when(mockClearanceLevelManager.findLevelByClassificationLevelId(ClassificationLevelManager.UNCLASSIFIED_ID)) - .thenReturn(ClearanceLevelManager.NO_CLEARANCE); + .thenReturn(ClearanceLevelManager.NO_CLEARANCE); final SecurityClearance clearance = securityClearanceServiceImpl.getUserSecurityClearance(); - + assertEquals(ClassificationLevelManager.UNCLASSIFIED, clearance.getClearanceLevel().getHighestClassificationLevel()); } - /** Check that a user can have their clearance set. */ + /** Check that a user can have their clearance set by an authorised user. */ @Test public void setUserSecurityClearance_setClearance() { - // Create the user. + // Create the clearance. + String topSecretId = "ClearanceId"; + ClassificationLevel level = new ClassificationLevel(topSecretId, "TopSecretKey"); + ClearanceLevel clearanceLevel = new ClearanceLevel(level, "TopSecretKey"); + when(mockClearanceLevelManager.findLevelByClassificationLevelId(topSecretId)).thenReturn(clearanceLevel); + when(mockClassificationLevelManager.getClassificationLevels()).thenReturn(ImmutableList.of(level)); + + // Create the authorised user. + String authorisedUserName = "authorisedUser"; + when(mockAuthenticationUtil.getFullyAuthenticatedUser()).thenReturn(authorisedUserName); + NodeRef authorisedPersonNode = new NodeRef(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE, authorisedUserName); + PersonInfo authorisedPersonInfo = new PersonInfo(authorisedPersonNode, authorisedUserName, "first", "last"); + when(mockPersonService.getPerson(authorisedUserName, false)).thenReturn(authorisedPersonNode); + when(mockPersonService.getPerson(authorisedPersonNode)).thenReturn(authorisedPersonInfo); + + // The authorised user is cleared to use this clearance. + when(mockNodeService.hasAspect(authorisedPersonNode, ASPECT_SECURITY_CLEARANCE)).thenReturn(true); + when(mockNodeService.getProperty(authorisedPersonNode, PROP_CLEARANCE_LEVEL)).thenReturn(topSecretId); + + // Create the user who will have their clearance set. String userName = "User 1"; - NodeRef personNode = new NodeRef(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE, userName); - PersonInfo personInfo = new PersonInfo(personNode, userName, "user", "two"); - + PersonInfo personInfo = new PersonInfo(personNode, userName, "first", "last"); when(mockPersonService.getPerson(userName, false)).thenReturn(personNode); when(mockPersonService.getPerson(personNode)).thenReturn(personInfo); - // Create the clearance. - String clearanceId = "ClearanceId"; - ClassificationLevel level = new ClassificationLevel(clearanceId, "TopSecretKey"); - when(mockClassificationService.getClassificationLevelById(clearanceId)).thenReturn(level); - ClearanceLevel clearanceLevel = new ClearanceLevel(level, "TopSecretKey"); - when(mockClearanceLevelManager.findLevelByClassificationLevelId(clearanceId)).thenReturn(clearanceLevel); - + // Once the user's clearance has been set then the node service is queried about it. when(mockNodeService.hasAspect(personNode, ASPECT_SECURITY_CLEARANCE)).thenReturn(true); - when(mockNodeService.getProperty(personNode, PROP_CLEARANCE_LEVEL)).thenReturn(clearanceId); - + when(mockNodeService.getProperty(personNode, PROP_CLEARANCE_LEVEL)).thenReturn(topSecretId); // Call the method under test. - SecurityClearance securityClearance = securityClearanceServiceImpl.setUserSecurityClearance(userName, clearanceId); + SecurityClearance securityClearance = securityClearanceServiceImpl.setUserSecurityClearance(userName, topSecretId); + // Check the returned value. assertEquals(personInfo, securityClearance.getPersonInfo()); assertEquals(clearanceLevel, securityClearance.getClearanceLevel()); - - verify(mockNodeService).setProperty(personNode, PROP_CLEARANCE_LEVEL, clearanceId); + // Check the value stored in the node service. + verify(mockNodeService).setProperty(personNode, PROP_CLEARANCE_LEVEL, topSecretId); } - /** - * Check that a user cannot raise someone's clearance above their own. Here we check that an exception thrown by the - * classification service is passed through. - */ + /** Check that a user cannot raise someone else's clearance above their own. */ @Test(expected = LevelIdNotFound.class) public void setUserSecurityClearance_insufficientClearance() { - String userName = "User 1"; - NodeRef personNode = new NodeRef(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE, userName); - when(mockPersonService.getPerson(userName, false)).thenReturn(personNode); - String clearanceId = "ClearanceId"; - // If the user has insufficient clearance then they cannot access the level. - when(mockClassificationService.getClassificationLevelById(clearanceId)).thenThrow(new LevelIdNotFound(clearanceId)); + // Create the "Top Secret" and "Confidential" clearances. + String topSecretId = "TopSecretClearanceId"; + ClassificationLevel topSecret = new ClassificationLevel(topSecretId, "TopSecretKey"); + ClearanceLevel topSecretClearance = new ClearanceLevel(topSecret, "TopSecretKey"); + when(mockClearanceLevelManager.findLevelByClassificationLevelId(topSecretId)).thenReturn(topSecretClearance); + String confidentialId = "ConfidentialClearanceId"; + ClassificationLevel confidential = new ClassificationLevel(confidentialId, "ConfidentialKey"); + ClearanceLevel confidentialClearance = new ClearanceLevel(confidential, "ConfidentialKey"); + when(mockClearanceLevelManager.findLevelByClassificationLevelId(confidentialId)).thenReturn(confidentialClearance); + when(mockClassificationLevelManager.getClassificationLevels()).thenReturn(ImmutableList.of(topSecret, confidential)); - securityClearanceServiceImpl.setUserSecurityClearance(userName, clearanceId); + // Create the user attempting to use the API with "Confidential" clearance. + String userName = "unauthorisedUser"; + when(mockAuthenticationUtil.getFullyAuthenticatedUser()).thenReturn(userName); + NodeRef personNode = new NodeRef(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE, userName); + PersonInfo personInfo = new PersonInfo(personNode, userName, "first", "last"); + when(mockPersonService.getPerson(userName, false)).thenReturn(personNode); + when(mockPersonService.getPerson(personNode)).thenReturn(personInfo); + + // The authorised user is cleared to use this clearance. + when(mockNodeService.hasAspect(personNode, ASPECT_SECURITY_CLEARANCE)).thenReturn(true); + when(mockNodeService.getProperty(personNode, PROP_CLEARANCE_LEVEL)).thenReturn(confidentialId); + + // Create the user who will have their clearance set. + String targetUserName = "Target User"; + NodeRef targetPersonNode = new NodeRef(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE, targetUserName); + PersonInfo targetPersonInfo = new PersonInfo(targetPersonNode, targetUserName, "first", "last"); + when(mockPersonService.getPerson(targetUserName, false)).thenReturn(targetPersonNode); + when(mockPersonService.getPerson(targetPersonNode)).thenReturn(targetPersonInfo); + + // Call the method under test and expect an exception. + securityClearanceServiceImpl.setUserSecurityClearance(targetUserName, topSecretId); } /** - * Check that the initialise method creates a clearance level corresponding to each classification level and that - * the display label for the lowest clearance level is "No Clearance" (rather than "Unclassified"). + * Check that a user with no clearance is not cleared to use the "Secret" classification. */ - @Test public void initialise() + @Test public void isClearedForClassification() { ClassificationLevel topSecret = new ClassificationLevel("1", "TopSecret"); ClassificationLevel secret = new ClassificationLevel("2", "Secret"); - List classificationLevels = Arrays.asList(topSecret, secret, ClassificationLevelManager.UNCLASSIFIED); - when(mockClassificationService.getClassificationLevels()).thenReturn(classificationLevels ); + ImmutableList classificationLevels = ImmutableList.of(topSecret, secret); + when(mockClassificationLevelManager.getClassificationLevels()).thenReturn(classificationLevels); + + SecurityClearance clearance = new SecurityClearance(mock(PersonInfo.class), ClearanceLevelManager.NO_CLEARANCE); // Call the method under test. - securityClearanceServiceImpl.initialise(); + boolean result = securityClearanceServiceImpl.isClearedForClassification(clearance, "2"); - List clearanceLevels = securityClearanceServiceImpl.getClearanceManager().getClearanceLevels(); - assertEquals("There should be one clearance level for each classification level.", classificationLevels.size(), clearanceLevels.size()); - assertEquals("TopSecret", clearanceLevels.get(0).getDisplayLabel()); - assertEquals("Secret", clearanceLevels.get(1).getDisplayLabel()); - assertEquals("rm.classification.noClearance", clearanceLevels.get(2).getDisplayLabel()); - } - - /** - * Given that the node is unclassified - * When I ask if the current user has clearance - * Then true - */ - @Test public void clearedForUnclassifiedNode() - { - NodeRef nodeRef = generateNodeRef(mockNodeService); - when(mockClassificationService.getCurrentClassification(nodeRef)) - .thenReturn(ClassificationLevelManager.UNCLASSIFIED); - - assertTrue(securityClearanceServiceImpl.hasClearance(nodeRef)); - } - - /** - * Given that the node is classified - * And the user has no security clearance - * When I ask if the current user has clearance - * Then false - */ - @Test public void userWithNoClearanceIsntClearedOnClassifiedNode() - { - // assign test classification to node - String classificationLevelId = generateText(); - ClassificationLevel classificationLevel = new ClassificationLevel(classificationLevelId, generateText()); - NodeRef nodeRef = generateNodeRef(mockNodeService); - when(mockClassificationService.getCurrentClassification(nodeRef)) - .thenReturn(classificationLevel); - when(mockClearanceLevelManager.findLevelByClassificationLevelId(classificationLevelId)) - .thenReturn(new ClearanceLevel(classificationLevel, generateText())); - - // create user with no clearance - final PersonInfo user1 = createMockPerson(generateText(), generateText(), generateText(), null); - MockAuthenticationUtilHelper.setup(mockedAuthenticationUtil, user1.getUserName()); - when(mockClassificationService.getUnclassifiedClassificationLevel()) - .thenReturn(ClassificationLevelManager.UNCLASSIFIED); - when(mockClearanceLevelManager.findLevelByClassificationLevelId(ClassificationLevelManager.UNCLASSIFIED_ID)) - .thenReturn(ClearanceLevelManager.NO_CLEARANCE); - - assertFalse(securityClearanceServiceImpl.hasClearance(nodeRef)); - } - - /** - * Given that the node is classified - * And the user has clearance grater than the classification - * When I ask if the user has clearance - * Then true - */ - @Test public void classifiedNodeUserClearanceGreater() - { - // init classification levels - ClassificationLevel topSecret = new ClassificationLevel("TopSecret", generateText()); - ClassificationLevel secret = new ClassificationLevel("Secret", generateText()); - ClassificationLevel confidential = new ClassificationLevel("Confidential", generateText()); - List classificationLevels = Arrays.asList(topSecret, secret, confidential, ClassificationLevelManager.UNCLASSIFIED); - when(mockClassificationService.getClassificationLevels()).thenReturn(classificationLevels); - - // init classification levels - when(mockClearanceLevelManager.findLevelByClassificationLevelId("TopSecret")) - .thenReturn(new ClearanceLevel(topSecret, generateText())); - when(mockClearanceLevelManager.findLevelByClassificationLevelId("Secret")) - .thenReturn(new ClearanceLevel(secret, generateText())); - when(mockClearanceLevelManager.findLevelByClassificationLevelId("Confidential")) - .thenReturn(new ClearanceLevel(confidential, generateText())); - when(mockClearanceLevelManager.findLevelByClassificationLevelId(ClassificationLevelManager.UNCLASSIFIED_ID)) - .thenReturn(ClearanceLevelManager.NO_CLEARANCE); - when(mockClassificationService.getUnclassifiedClassificationLevel()) - .thenReturn(ClassificationLevelManager.UNCLASSIFIED); - - // set nodes classification - NodeRef nodeRef = generateNodeRef(mockNodeService); - when(mockClassificationService.getCurrentClassification(nodeRef)) - .thenReturn(secret); - - // set users security clearance - final PersonInfo user1 = createMockPerson(generateText(), generateText(), generateText(), "TopSecret"); - MockAuthenticationUtilHelper.setup(mockedAuthenticationUtil, user1.getUserName()); - - assertTrue(securityClearanceServiceImpl.hasClearance(nodeRef)); - } - - /** - * Given that the node is classified - * And the user has clearance equal to the the classification - * When I ask if the user has clearance - * Then true - */ - @Test public void classifiedNodeUserClearanceEqual() - { - // init classification levels - ClassificationLevel topSecret = new ClassificationLevel("TopSecret", generateText()); - ClassificationLevel secret = new ClassificationLevel("Secret", generateText()); - ClassificationLevel confidential = new ClassificationLevel("Confidential", generateText()); - List classificationLevels = Arrays.asList(topSecret, secret, confidential, ClassificationLevelManager.UNCLASSIFIED); - when(mockClassificationService.getClassificationLevels()).thenReturn(classificationLevels); - - // init classification levels - when(mockClearanceLevelManager.findLevelByClassificationLevelId("TopSecret")) - .thenReturn(new ClearanceLevel(topSecret, generateText())); - when(mockClearanceLevelManager.findLevelByClassificationLevelId("Secret")) - .thenReturn(new ClearanceLevel(secret, generateText())); - when(mockClearanceLevelManager.findLevelByClassificationLevelId("Confidential")) - .thenReturn(new ClearanceLevel(confidential, generateText())); - when(mockClearanceLevelManager.findLevelByClassificationLevelId(ClassificationLevelManager.UNCLASSIFIED_ID)) - .thenReturn(ClearanceLevelManager.NO_CLEARANCE); - when(mockClassificationService.getUnclassifiedClassificationLevel()) - .thenReturn(ClassificationLevelManager.UNCLASSIFIED); - - // set nodes classification - NodeRef nodeRef = generateNodeRef(mockNodeService); - when(mockClassificationService.getCurrentClassification(nodeRef)) - .thenReturn(secret); - - // set users security clearance - final PersonInfo user1 = createMockPerson(generateText(), generateText(), generateText(), "Secret"); - MockAuthenticationUtilHelper.setup(mockedAuthenticationUtil, user1.getUserName()); - - assertTrue(securityClearanceServiceImpl.hasClearance(nodeRef)); - } - - /** - * Given that the node is classified - * And the user has clearance less than the classification - * When I ask if the user has clearance - * Then true - */ - @Test public void classifiedNodeUserClearanceLess() - { - // init classification levels - ClassificationLevel topSecret = new ClassificationLevel("TopSecret", generateText()); - ClassificationLevel secret = new ClassificationLevel("Secret", generateText()); - ClassificationLevel confidential = new ClassificationLevel("Confidential", generateText()); - List classificationLevels = Arrays.asList(topSecret, secret, confidential, ClassificationLevelManager.UNCLASSIFIED); - when(mockClassificationService.getClassificationLevels()).thenReturn(classificationLevels); - - // init classification levels - when(mockClearanceLevelManager.findLevelByClassificationLevelId("TopSecret")) - .thenReturn(new ClearanceLevel(topSecret, generateText())); - when(mockClearanceLevelManager.findLevelByClassificationLevelId("Secret")) - .thenReturn(new ClearanceLevel(secret, generateText())); - when(mockClearanceLevelManager.findLevelByClassificationLevelId("Confidential")) - .thenReturn(new ClearanceLevel(confidential, generateText())); - when(mockClearanceLevelManager.findLevelByClassificationLevelId(ClassificationLevelManager.UNCLASSIFIED_ID)) - .thenReturn(ClearanceLevelManager.NO_CLEARANCE); - when(mockClassificationService.getUnclassifiedClassificationLevel()) - .thenReturn(ClassificationLevelManager.UNCLASSIFIED); - - // set nodes classification - NodeRef nodeRef = generateNodeRef(mockNodeService); - when(mockClassificationService.getCurrentClassification(nodeRef)) - .thenReturn(secret); - - // set users security clearance - final PersonInfo user1 = createMockPerson(generateText(), generateText(), generateText(), "Confidential"); - MockAuthenticationUtilHelper.setup(mockedAuthenticationUtil, user1.getUserName()); - - assertFalse(securityClearanceServiceImpl.hasClearance(nodeRef)); + assertFalse("A user with no clearance should not be able to access the 'Secret' classification.", result); } /** diff --git a/rm-server/unit-test/java/org/alfresco/module/org_alfresco_module_rm/script/classification/ClassifyContentPostUnitTest.java b/rm-server/unit-test/java/org/alfresco/module/org_alfresco_module_rm/script/classification/ClassifyContentPostUnitTest.java index bc9d0cc2ba..593286dbdb 100644 --- a/rm-server/unit-test/java/org/alfresco/module/org_alfresco_module_rm/script/classification/ClassifyContentPostUnitTest.java +++ b/rm-server/unit-test/java/org/alfresco/module/org_alfresco_module_rm/script/classification/ClassifyContentPostUnitTest.java @@ -34,7 +34,7 @@ import java.util.Map; import org.alfresco.module.org_alfresco_module_rm.classification.ClassificationLevelManager; import org.alfresco.module.org_alfresco_module_rm.classification.ClassificationReasonManager; -import org.alfresco.module.org_alfresco_module_rm.classification.ClassificationService; +import org.alfresco.module.org_alfresco_module_rm.classification.ContentClassificationService; import org.alfresco.module.org_alfresco_module_rm.test.util.BaseWebScriptUnitTest; import org.json.JSONArray; import org.json.JSONObject; @@ -68,8 +68,8 @@ public class ClassifyContentPostUnitTest extends BaseWebScriptUnitTest /** ClassifyContentPost webscript instance */ private @Spy @InjectMocks ClassifyContentPost webScript; - /** Mocked classification service */ - private @Mock ClassificationService mockedClassificationService; + /** Mocked content classification service */ + private @Mock ContentClassificationService mockedContentClassificationService; /** Mocked classification level manager */ private @Mock ClassificationLevelManager mockedClassificationLevelManager; @@ -116,7 +116,7 @@ public class ClassifyContentPostUnitTest extends BaseWebScriptUnitTest assertEquals(getStringValueFromJSONObject(json, SUCCESS), Boolean.TRUE.toString()); // Verify that the classify content method was called - verify(mockedClassificationService, times(1)).classifyContent(LEVEL_ID, AUTHORITY, newHashSet(REASON1_ID, REASON2_ID), record); + verify(mockedContentClassificationService, times(1)).classifyContent(LEVEL_ID, AUTHORITY, newHashSet(REASON1_ID, REASON2_ID), record); } /**