RM-2123 Create a new service for content classification.

Move methods to do with content from the ClassificationService and the
SecurityClearanceService into the ContentClassificationService.

Remove the dependency of SecurityClearanceService on ClassificationService
which will allow us to reverse this dependency in the next commit.  This is
needed in order to filter classifications by the current user's clearance.
Nb. This included adding a method in the SecurityClearanceService called
isClearedForClassification, which looks quite similar to a new API Roy
created hasClearance (see ContentClassificationService).  In the future we
should look to see if we can consolidate these.

Remove dependency of ClassificationServiceBootstrap on the services, so that
it can be passed into them. This allows us to provide access to the POJO
managers in the services (this is made harder as the POJO managers aren't
Spring beans).  In order to initialise these objects, change the POJO
managers to use setters rather than constructor arguments. This allows us
to store a reference to the manager before the data has been loaded.

Move the attribute service keys for classification levels and reasons into
the ClassifiedContentModel.

Expect NO_CLEARANCE to be passed into the ClearanceLevelManager, as
otherwise we have to have logic to exclude it (see initialise in the old
SecurityClearanceService) and then more logic to include it again (see the
old constructor for ClearanceLevelManager).

git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/modules/recordsmanagement/HEAD@104375 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261
This commit is contained in:
Tom Page
2015-05-18 15:03:57 +00:00
parent 5c66e55735
commit 24780dc472
26 changed files with 1326 additions and 978 deletions

View File

@@ -39,9 +39,8 @@
<bean id="classificationService" <bean id="classificationService"
class="org.alfresco.module.org_alfresco_module_rm.classification.ClassificationServiceImpl" class="org.alfresco.module.org_alfresco_module_rm.classification.ClassificationServiceImpl"
parent="baseService"> parent="baseService" init-method="init">
<property name="attributeService" ref="AttributeService"/> <property name="classificationServiceBootstrap" ref="classificationServiceBootstrap"/>
<property name="classificationServiceDAO" ref="classificationServiceDAO"/>
</bean> </bean>
<bean id="ClassificationService" class="org.springframework.aop.framework.ProxyFactoryBean"> <bean id="ClassificationService" class="org.springframework.aop.framework.ProxyFactoryBean">
@@ -76,9 +75,7 @@
<property name="objectDefinitionSource"> <property name="objectDefinitionSource">
<value> <value>
org.alfresco.module.org_alfresco_module_rm.classification.ClassificationService.getClassificationLevels=ACL_ALLOW 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.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.getClassificationLevelById=ACL_ALLOW
org.alfresco.module.org_alfresco_module_rm.classification.ClassificationService.getClassificationReasonById=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 org.alfresco.module.org_alfresco_module_rm.classification.ClassificationService.getUnclassifiedClassificationLevel=ACL_ALLOW
@@ -94,9 +91,9 @@
<bean id="classificationServiceBootstrap" <bean id="classificationServiceBootstrap"
class="org.alfresco.module.org_alfresco_module_rm.classification.ClassificationServiceBootstrap"> class="org.alfresco.module.org_alfresco_module_rm.classification.ClassificationServiceBootstrap">
<constructor-arg ref="rm.authenticationUtil"/> <constructor-arg ref="rm.authenticationUtil"/>
<constructor-arg ref="classificationService"/> <!-- Intentionally using the small 'c' here -->
<constructor-arg ref="securityClearanceService"/> <!-- Intentionally using the small 's' here -->
<constructor-arg ref="TransactionService"/> <constructor-arg ref="TransactionService"/>
<constructor-arg ref="attributeService"/>
<constructor-arg ref="classificationServiceDAO"/>
</bean> </bean>
@@ -104,9 +101,9 @@
<bean id="securityClearanceService" <bean id="securityClearanceService"
class="org.alfresco.module.org_alfresco_module_rm.classification.SecurityClearanceServiceImpl" class="org.alfresco.module.org_alfresco_module_rm.classification.SecurityClearanceServiceImpl"
parent="baseService"> parent="baseService" init-method="init">
<property name="classificationService" ref="ClassificationService"/> <!-- Intentionally using capital 'C' -->
<property name="personService" ref="PersonService"/> <property name="personService" ref="PersonService"/>
<property name="classificationServiceBootstrap" ref="classificationServiceBootstrap"/>
</bean> </bean>
<bean id="SecurityClearanceService" class="org.springframework.aop.framework.ProxyFactoryBean"> <bean id="SecurityClearanceService" class="org.springframework.aop.framework.ProxyFactoryBean">
@@ -140,7 +137,6 @@
<bean id="SecurityClearanceService_security" parent="baseSecurity"> <bean id="SecurityClearanceService_security" parent="baseSecurity">
<property name="objectDefinitionSource"> <property name="objectDefinitionSource">
<value> <value>
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.getUserSecurityClearance=ACL_ALLOW
org.alfresco.module.org_alfresco_module_rm.classification.SecurityClearanceService.getUsersSecurityClearance=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 org.alfresco.module.org_alfresco_module_rm.classification.SecurityClearanceService.setUserSecurityClearance=ACL_ALLOW
@@ -149,4 +145,52 @@
</value> </value>
</property> </property>
</bean> </bean>
<!-- Content Classification Service -->
<bean id="contentClassificationService"
class="org.alfresco.module.org_alfresco_module_rm.classification.ContentClassificationServiceImpl"
parent="baseService" init-method="init">
<property name="securityClearanceService" ref="SecurityClearanceService"/>
<property name="classificationServiceBootstrap" ref="classificationServiceBootstrap"/>
</bean>
<bean id="ContentClassificationService" class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="proxyInterfaces">
<value>org.alfresco.module.org_alfresco_module_rm.classification.ContentClassificationService</value>
</property>
<property name="target">
<ref bean="contentClassificationService"/>
</property>
<property name="interceptorNames">
<list>
<idref local="ContentClassificationService_transaction"/>
<idref bean="exceptionTranslator"/>
<idref local="ContentClassificationService_security"/>
</list>
</property>
</bean>
<bean id="ContentClassificationService_transaction" class="org.springframework.transaction.interceptor.TransactionInterceptor">
<property name="transactionManager">
<ref bean="transactionManager"/>
</property>
<property name="transactionAttributes">
<props>
<prop key="*">${server.transaction.mode.default}</prop>
</props>
</property>
</bean>
<!-- FIXME: We have to restrict methods in the classification service (with capabilities, etc.) -->
<bean id="ContentClassificationService_security" parent="baseSecurity">
<property name="objectDefinitionSource">
<value>
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
</value>
</property>
</bean>
</beans> </beans>

View File

@@ -7,7 +7,7 @@
<!-- extending bean definition --> <!-- extending bean definition -->
<bean id="rm.jsonConversionComponent" <bean id="rm.jsonConversionComponent"
class="org.alfresco.module.org_alfresco_module_rm.jscript.app.JSONConversionComponent" class="org.alfresco.module.org_alfresco_module_rm.jscript.app.JSONConversionComponent"
parent="baseJsonConversionComponent" parent="baseJsonConversionComponent"
init-method="init"> init-method="init">
<property name="recordContributorsGroupEnabled" value="${rm.record.contributors.group.enabled}"/> <property name="recordContributorsGroupEnabled" value="${rm.record.contributors.group.enabled}"/>

View File

@@ -674,7 +674,7 @@
<bean id="webscript.org.alfresco.rma.classification.classifycontent.post" <bean id="webscript.org.alfresco.rma.classification.classifycontent.post"
class="org.alfresco.module.org_alfresco_module_rm.script.classification.ClassifyContentPost" class="org.alfresco.module.org_alfresco_module_rm.script.classification.ClassifyContentPost"
parent="rmBaseWebscript"> parent="rmBaseWebscript">
<property name="classificationService" ref="ClassificationService" /> <property name="contentClassificationService" ref="contentClassificationService" />
</bean> </bean>
<!-- REST impl for GET user security clearance --> <!-- REST impl for GET user security clearance -->

View File

@@ -21,34 +21,33 @@ package org.alfresco.module.org_alfresco_module_rm.classification;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import org.alfresco.module.org_alfresco_module_rm.classification.ClassificationServiceException.LevelIdNotFound;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
import org.alfresco.module.org_alfresco_module_rm.classification.ClassificationServiceException.LevelIdNotFound;
/** /**
* Container for the configured {@link ClassificationLevel} objects. * Container for the configured {@link ClassificationLevel} objects.
* *
* @author tpage * @author tpage
*/ */
public class ClassificationLevelManager public class ClassificationLevelManager
{ {
/** Unclassified classificaiton level */ /** Unclassified classification level */
public static final String UNCLASSIFIED_ID = "Unclassified"; public static final String UNCLASSIFIED_ID = "Unclassified";
private static final String UNCLASSIFIED_MSG = "rm.classification.unclassified"; private static final String UNCLASSIFIED_MSG = "rm.classification.unclassified";
public static final ClassificationLevel UNCLASSIFIED = new ClassificationLevel(UNCLASSIFIED_ID, UNCLASSIFIED_MSG); public static final ClassificationLevel UNCLASSIFIED = new ClassificationLevel(UNCLASSIFIED_ID, UNCLASSIFIED_MSG);
/** An immutable list of classification levels ordered from most to least secure. */ /** An immutable list of classification levels ordered from most to least secure. */
private ImmutableList<ClassificationLevel> classificationLevels; private ImmutableList<ClassificationLevel> 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. * @param classificationLevels A list of classification levels ordered from most to least secure.
*/ */
public ClassificationLevelManager(List<ClassificationLevel> classificationLevels) public void setClassificationLevels(List<ClassificationLevel> classificationLevels)
{ {
List<ClassificationLevel> temp = new ArrayList<ClassificationLevel>(classificationLevels); List<ClassificationLevel> temp = new ArrayList<ClassificationLevel>(classificationLevels);
temp.add(temp.size(), UNCLASSIFIED); temp.add(temp.size(), UNCLASSIFIED);
this.classificationLevels = ImmutableList.copyOf(temp); this.classificationLevels = ImmutableList.copyOf(temp);
} }
@@ -66,7 +65,7 @@ public class ClassificationLevelManager
/** /**
* Get a <code>ClassificationLevel</code> using its id. * Get a <code>ClassificationLevel</code> using its id.
* *
* @param id The id of a classification level. * @param id The id of a classification level.
* @return The classification level. * @return The classification level.
* @throws LevelIdNotFound If the classification level cannot be found. * @throws LevelIdNotFound If the classification level cannot be found.

View File

@@ -25,7 +25,7 @@ import org.alfresco.module.org_alfresco_module_rm.classification.ClassificationS
/** /**
* Container for the configured {@link ClassificationReason} objects. * Container for the configured {@link ClassificationReason} objects.
* *
* @author tpage * @author tpage
*/ */
public class ClassificationReasonManager public class ClassificationReasonManager
@@ -34,11 +34,11 @@ public class ClassificationReasonManager
private ImmutableList<ClassificationReason> classificationReasons; private ImmutableList<ClassificationReason> classificationReasons;
/** /**
* Constructor that stores an immutable copy of the given reasons. * Store an immutable copy of the given reasons.
* *
* @param classificationReasons The classification reasons. * @param classificationReasons The classification reasons.
*/ */
public ClassificationReasonManager(Collection<ClassificationReason> classificationReasons) public void setClassificationReasons(Collection<ClassificationReason> classificationReasons)
{ {
this.classificationReasons = ImmutableList.copyOf(classificationReasons); this.classificationReasons = ImmutableList.copyOf(classificationReasons);
} }
@@ -51,7 +51,7 @@ public class ClassificationReasonManager
/** /**
* Get a <code>ClassificationReason</code> using its id. * Get a <code>ClassificationReason</code> using its id.
* *
* @param id The id of a classification reason. * @param id The id of a classification reason.
* @return The classification reason. * @return The classification reason.
* @throws ReasonIdNotFound If the classification reason cannot be found. * @throws ReasonIdNotFound If the classification reason cannot be found.

View File

@@ -19,13 +19,9 @@
package org.alfresco.module.org_alfresco_module_rm.classification; package org.alfresco.module.org_alfresco_module_rm.classification;
import java.util.List; 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.LevelIdNotFound;
import org.alfresco.module.org_alfresco_module_rm.classification.ClassificationServiceException.ReasonIdNotFound; 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 * 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). * and therefore access to the most restricted documents).
*/ */
List<ClassificationLevel> getClassificationLevels(); List<ClassificationLevel> 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. * Returns an immutable list of the defined classification reasons.
@@ -59,21 +47,6 @@ public interface ClassificationService
*/ */
List<ClassificationReason> getClassificationReasons(); List<ClassificationReason> 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<String> classificationReasonIds, NodeRef content)
throws LevelIdNotFound, ReasonIdNotFound, InvalidNodeRefException, InvalidNode;
/** /**
* Gets the unclassified {@link ClassificationLevel}. * Gets the unclassified {@link ClassificationLevel}.
* @return the unclassified classification level * @return the unclassified classification level

View File

@@ -18,9 +18,20 @@
*/ */
package org.alfresco.module.org_alfresco_module_rm.classification; 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.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.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback;
import org.alfresco.service.cmr.attributes.AttributeService;
import org.alfresco.service.transaction.TransactionService; import org.alfresco.service.transaction.TransactionService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.ApplicationEvent; import org.springframework.context.ApplicationEvent;
import org.springframework.extensions.surf.util.AbstractLifecycleBean; import org.springframework.extensions.surf.util.AbstractLifecycleBean;
@@ -30,25 +41,42 @@ import org.springframework.extensions.surf.util.AbstractLifecycleBean;
* @author Neil Mc Erlean * @author Neil Mc Erlean
* @since 3.0 * @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 AuthenticationUtil authenticationUtil;
private final ClassificationServiceImpl classificationServiceImpl;
private final SecurityClearanceServiceImpl securityClearanceServiceImpl;
private final TransactionService transactionService; 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, public ClassificationServiceBootstrap(AuthenticationUtil authUtil,
ClassificationServiceImpl cService, TransactionService txService,
SecurityClearanceServiceImpl securityClearanceServiceImpl, AttributeService attributeService,
TransactionService txService) ClassificationServiceDAO classificationServiceDAO)
{ {
this.authenticationUtil = authUtil; this.authenticationUtil = authUtil;
this.classificationServiceImpl = cService; this.transactionService = txService;
this.securityClearanceServiceImpl = securityClearanceServiceImpl; this.attributeService = attributeService;
this.transactionService = txService; 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<Void>() authenticationUtil.runAsSystem(new org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork<Void>()
{ {
@@ -58,8 +86,9 @@ public class ClassificationServiceBootstrap extends AbstractLifecycleBean
{ {
public Void execute() public Void execute()
{ {
classificationServiceImpl.initialise(); initConfiguredClassificationLevels();
securityClearanceServiceImpl.initialise(); initConfiguredClassificationReasons();
initConfiguredClearanceLevels(classificationLevelManager.getClassificationLevels());
return null; 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<ClassificationLevel> allPersistedLevels = getPersistedLevels();
final List<ClassificationLevel> 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<ClassificationLevel> getPersistedLevels()
{
return authenticationUtil.runAsSystem(new RunAsWork<List<ClassificationLevel>>()
{
@Override
@SuppressWarnings("unchecked")
public List<ClassificationLevel> doWork() throws Exception
{
return (List<ClassificationLevel>) attributeService.getAttribute(LEVELS_KEY);
}
});
}
/** Gets the list (in descending order) of classification levels - as defined in the system configuration. */
private List<ClassificationLevel> 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<ClassificationReason> persistedReasons = getPersistedReasons();
final List<ClassificationReason> 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<ClassificationReason> getPersistedReasons()
{
return authenticationUtil.runAsSystem(new RunAsWork<List<ClassificationReason>>()
{
@Override
@SuppressWarnings("unchecked")
public List<ClassificationReason> doWork() throws Exception
{
return (List<ClassificationReason>) attributeService.getAttribute(REASONS_KEY);
}
});
}
/** Gets the list of classification reasons - as defined and ordered in the system configuration. */
private List<ClassificationReason> 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<ClassificationLevel> classificationLevels)
{
List<ClearanceLevel> 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) @Override protected void onShutdown(ApplicationEvent event)
{ {
// Intentionally empty. // Intentionally empty.

View File

@@ -19,30 +19,15 @@
package org.alfresco.module.org_alfresco_module_rm.classification; 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.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.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List; 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.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.ClassificationServiceException.ReasonIdNotFound;
import org.alfresco.module.org_alfresco_module_rm.classification.model.ClassifiedContentModel; import org.alfresco.module.org_alfresco_module_rm.classification.model.ClassifiedContentModel;
import org.alfresco.module.org_alfresco_module_rm.util.ServiceBaseImpl; 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.cmr.repository.NodeService;
import org.alfresco.service.namespace.QName;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/** /**
* @author Neil Mc Erlean * @author Neil Mc Erlean
@@ -51,160 +36,23 @@ import org.slf4j.LoggerFactory;
public class ClassificationServiceImpl extends ServiceBaseImpl public class ClassificationServiceImpl extends ServiceBaseImpl
implements ClassificationService, ClassifiedContentModel 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. */ /** The classification levels currently configured in this server. */
private ClassificationLevelManager levelManager; private ClassificationLevelManager levelManager;
/** The classification reasons currently configured in this server. */ /** The classification reasons currently configured in this server. */
private ClassificationReasonManager reasonManager; private ClassificationReasonManager reasonManager;
private ClassificationServiceBootstrap classificationServiceBootstrap;
public void setAttributeService(AttributeService service) { this.attributeService = service; }
public void setNodeService(NodeService service) { this.nodeService = service; } public void setNodeService(NodeService service) { this.nodeService = service; }
public void setClassificationServiceBootstrap(ClassificationServiceBootstrap classificationServiceBootstrap)
/** Set the object from which configuration options will be read. */
public void setClassificationServiceDAO(ClassificationServiceDAO classificationServiceDao) { this.classificationServiceDao = classificationServiceDao; }
void initialise()
{ {
initConfiguredClassificationLevels(); this.classificationServiceBootstrap = classificationServiceBootstrap;
initConfiguredClassificationReasons();
} }
protected void initConfiguredClassificationLevels() /** Store the references to the classification level and reason managers in this class. */
public void init()
{ {
final List<ClassificationLevel> allPersistedLevels = getPersistedLevels(); levelManager = classificationServiceBootstrap.getClassificationLevelManager();
final List<ClassificationLevel> configurationLevels = getConfigurationLevels(); reasonManager = classificationServiceBootstrap.getClassificationReasonManager();
// 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<ClassificationReason> persistedReasons = getPersistedReasons();
final List<ClassificationReason> 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<ClassificationLevel> getPersistedLevels()
{
return authenticationUtil.runAsSystem(new AuthenticationUtil.RunAsWork<List<ClassificationLevel>>()
{
@Override
@SuppressWarnings("unchecked")
public List<ClassificationLevel> doWork() throws Exception
{
return (List<ClassificationLevel>) attributeService.getAttribute(LEVELS_KEY);
}
});
}
/** Gets the list (in descending order) of classification levels - as defined in the system configuration. */
List<ClassificationLevel> 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<ClassificationReason> getPersistedReasons()
{
return authenticationUtil.runAsSystem(new AuthenticationUtil.RunAsWork<List<ClassificationReason>>()
{
@Override
@SuppressWarnings("unchecked")
public List<ClassificationReason> doWork() throws Exception
{
return (List<ClassificationReason>) attributeService.getAttribute(REASONS_KEY);
}
});
}
/** Gets the list of classification reasons - as defined and ordered in the system configuration. */
List<ClassificationReason> getConfigurationReasons()
{
return classificationServiceDao.getConfiguredReasons();
} }
/** /**
@@ -241,58 +89,9 @@ public class ClassificationServiceImpl extends ServiceBaseImpl
Collections.unmodifiableList(reasonManager.getClassificationReasons()); Collections.unmodifiableList(reasonManager.getClassificationReasons());
} }
@Override
public void classifyContent(String classificationLevelId, String classificationAuthority,
Set<String> 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<QName, Serializable> properties = new HashMap<QName, Serializable>();
// 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<String> 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() @Override public ClassificationLevel getUnclassifiedClassificationLevel()
{ {
return ClassificationLevelManager.UNCLASSIFIED; return ClassificationLevelManager.UNCLASSIFIED;
} }
/** /**

View File

@@ -18,12 +18,10 @@
*/ */
package org.alfresco.module.org_alfresco_module_rm.classification; package org.alfresco.module.org_alfresco_module_rm.classification;
import java.util.ArrayList;
import java.util.List; import java.util.List;
import org.alfresco.module.org_alfresco_module_rm.classification.ClassificationServiceException.LevelIdNotFound;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
import org.alfresco.module.org_alfresco_module_rm.classification.ClassificationServiceException.LevelIdNotFound;
/** /**
* Container for the configured {@link ClearanceLevel} objects. * Container for the configured {@link ClearanceLevel} objects.
@@ -32,22 +30,20 @@ import com.google.common.collect.ImmutableList;
*/ */
public class ClearanceLevelManager public class ClearanceLevelManager
{ {
private static String NO_CLEARANCE_MSG = "rm.classification.noClearance"; private static String NO_CLEARANCE_MSG = "rm.classification.noClearance";
public static final ClearanceLevel NO_CLEARANCE = new ClearanceLevel(ClassificationLevelManager.UNCLASSIFIED, NO_CLEARANCE_MSG); 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. */ /** An immutable list of clearance levels ordered from most to least secure. */
private ImmutableList<ClearanceLevel> clearanceLevels; private ImmutableList<ClearanceLevel> 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. * @param clearanceLevels A list of clearance levels ordered from most to least secure.
*/ */
public ClearanceLevelManager(List<ClearanceLevel> clearanceLevels) public void setClearanceLevels(List<ClearanceLevel> clearanceLevels)
{ {
List<ClearanceLevel> temp = new ArrayList<ClearanceLevel>(clearanceLevels); this.clearanceLevels = ImmutableList.copyOf(clearanceLevels);
temp.add(temp.size(), NO_CLEARANCE);
this.clearanceLevels = ImmutableList.copyOf(temp);
} }
/** @return An immutable list of clearance levels ordered from most to least secure. */ /** @return An immutable list of clearance levels ordered from most to least secure. */

View File

@@ -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 <http://www.gnu.org/licenses/>.
*/
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<String> classificationReasonIds, NodeRef content)
throws LevelIdNotFound, ReasonIdNotFound, InvalidNodeRefException, InvalidNode;
/**
* Indicates whether the currently authenticated user has clearance to see the
* provided node.
* <p>
* 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);
}

View File

@@ -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 <http://www.gnu.org/licenses/>.
*/
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<String> 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<QName, Serializable> properties = new HashMap<QName, Serializable>();
// 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<String> 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<ClassificationLevel> allClassificationLevels = levelManager.getClassificationLevels();
int highestIndex = allClassificationLevels.indexOf(highestClassification);
int currentIndex = allClassificationLevels.indexOf(currentClassification);
if (highestIndex <= currentIndex)
{
// user has clearance
result = true;
}
}
}
return result;
}
}

View File

@@ -19,7 +19,6 @@
package org.alfresco.module.org_alfresco_module_rm.classification; package org.alfresco.module.org_alfresco_module_rm.classification;
import org.alfresco.query.PagingResults; import org.alfresco.query.PagingResults;
import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.cmr.security.NoSuchPersonException; import org.alfresco.service.cmr.security.NoSuchPersonException;
import java.util.List; import java.util.List;
@@ -33,18 +32,6 @@ import java.util.List;
*/ */
public interface SecurityClearanceService public interface SecurityClearanceService
{ {
/**
* Indicates whether the currently authenticated user has clearance to see the
* provided node.
* <p>
* 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. * Get the currently authenticated user's security clearance.
* *

View File

@@ -25,6 +25,8 @@ import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.List; 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.module.org_alfresco_module_rm.util.ServiceBaseImpl;
import org.alfresco.query.PagingRequest; import org.alfresco.query.PagingRequest;
import org.alfresco.query.PagingResults; import org.alfresco.query.PagingResults;
@@ -43,75 +45,23 @@ public class SecurityClearanceServiceImpl extends ServiceBaseImpl implements Sec
{ {
/** The clearance levels currently configured in this server. */ /** The clearance levels currently configured in this server. */
private ClearanceLevelManager clearanceManager; private ClearanceLevelManager clearanceManager;
/** The object containing the {@link ClassificationLevel}s in the system. */
private ClassificationService classificationService; private ClassificationLevelManager classificationLevelManager;
private PersonService personService; private PersonService personService;
private ClassificationServiceBootstrap classificationServiceBootstrap;
public void setClearanceManager(ClearanceLevelManager clearanceManager) { this.clearanceManager = clearanceManager; } 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 setPersonService(PersonService service) { this.personService = service; }
public void setClassificationServiceBootstrap(ClassificationServiceBootstrap classificationServiceBootstrap) { this.classificationServiceBootstrap = classificationServiceBootstrap; }
/** /** Store the references to the classification and clearance level managers in this class. */
* Initialise and create a {@link ClearanceLevelManager}. This assumes that the {@link ClassificationService} has public void init()
* already been initialised.
*/
void initialise()
{ {
ArrayList<ClearanceLevel> clearanceLevels = new ArrayList<ClearanceLevel>(); this.classificationLevelManager = classificationServiceBootstrap.getClassificationLevelManager();
List<ClassificationLevel> classificationLevels = classificationService.getClassificationLevels(); this.clearanceManager = classificationServiceBootstrap.getClearanceLevelManager();
for (ClassificationLevel classificationLevel : classificationLevels)
{
if (!ClassificationLevelManager.UNCLASSIFIED.equals(classificationLevel))
{
clearanceLevels.add(new ClearanceLevel(classificationLevel, classificationLevel.getDisplayLabelKey()));
}
}
this.clearanceManager = new ClearanceLevelManager(clearanceLevels);
} }
/** 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<ClassificationLevel> allClassificationLevels = classificationService.getClassificationLevels();
int highestIndex = allClassificationLevels.indexOf(highestClassification);
int currentIndex = allClassificationLevels.indexOf(currentClassification);
if (highestIndex <= currentIndex)
{
// user has clearance
result = true;
}
}
}
return result;
}
@Override @Override
public SecurityClearance getUserSecurityClearance() public SecurityClearance getUserSecurityClearance()
{ {
@@ -123,7 +73,7 @@ public class SecurityClearanceServiceImpl extends ServiceBaseImpl implements Sec
/** /**
* Gets the users security clearnace. * Gets the users security clearnace.
* *
* @param userName user name * @param userName user name
* @return {@link SecurityClearance} provides information about the user and their clearance level * @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 NodeRef personNode = personService.getPerson(userName, false);
final PersonInfo personInfo = personService.getPerson(personNode); final PersonInfo personInfo = personService.getPerson(personNode);
final ClassificationLevel classificationLevel; ClearanceLevel clearanceLevel = ClearanceLevelManager.NO_CLEARANCE;
if (nodeService.hasAspect(personNode, ASPECT_SECURITY_CLEARANCE)) if (nodeService.hasAspect(personNode, ASPECT_SECURITY_CLEARANCE))
{ {
final String clearanceLevelValue = (String)nodeService.getProperty(personNode, PROP_CLEARANCE_LEVEL); final String clearanceLevelId = (String)nodeService.getProperty(personNode, PROP_CLEARANCE_LEVEL);
clearanceLevel = (clearanceLevelId == null ? ClearanceLevelManager.NO_CLEARANCE
classificationLevel = clearanceLevelValue == null ? classificationService.getUnclassifiedClassificationLevel() : : clearanceManager.findLevelByClassificationLevelId(clearanceLevelId));
classificationService.getClassificationLevelById(clearanceLevelValue);
}
else
{
classificationLevel = classificationService.getUnclassifiedClassificationLevel();
} }
ClearanceLevel clearanceLevel = clearanceManager.findLevelByClassificationLevelId(classificationLevel.getId());
return new SecurityClearance(personInfo, clearanceLevel); 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<ClassificationLevel> 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 @Override
public SecurityClearance setUserSecurityClearance(String userName, String clearanceId) public SecurityClearance setUserSecurityClearance(String userName, String clearanceId)
{ {
@@ -189,9 +159,13 @@ public class SecurityClearanceServiceImpl extends ServiceBaseImpl implements Sec
ParameterCheck.mandatoryString("clearanceId", clearanceId); ParameterCheck.mandatoryString("clearanceId", clearanceId);
final NodeRef personNode = personService.getPerson(userName, false); 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. // Check the current user has clearance to see the specified level.
classificationService.getClassificationLevelById(clearanceId); SecurityClearance userSecurityClearance = getUserSecurityClearance();
if (!isClearedForClassification(userSecurityClearance, clearanceId))
{
throw new LevelIdNotFound(clearanceId);
}
nodeService.setProperty(personNode, PROP_CLEARANCE_LEVEL, clearanceId); nodeService.setProperty(personNode, PROP_CLEARANCE_LEVEL, clearanceId);

View File

@@ -18,6 +18,8 @@
*/ */
package org.alfresco.module.org_alfresco_module_rm.classification.model; package org.alfresco.module.org_alfresco_module_rm.classification.model;
import java.io.Serializable;
import org.alfresco.service.namespace.QName; 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_URI = "http://www.alfresco.org/model/classifiedcontent/1.0";
String CLF_PREFIX = "clf"; 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 */ /** Classified aspect */
QName ASPECT_CLASSIFIED = QName.createQName(CLF_URI, "classified"); QName ASPECT_CLASSIFIED = QName.createQName(CLF_URI, "classified");
QName PROP_INITIAL_CLASSIFICATION = QName.createQName(CLF_URI, "initialClassification"); QName PROP_INITIAL_CLASSIFICATION = QName.createQName(CLF_URI, "initialClassification");

View File

@@ -1,19 +1,19 @@
/* /*
* Copyright (C) 2005-2014 Alfresco Software Limited. * Copyright (C) 2005-2014 Alfresco Software Limited.
* *
* This file is part of Alfresco * This file is part of Alfresco
* *
* Alfresco is free software: you can redistribute it and/or modify * 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 * 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 * the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version. * (at your option) any later version.
* *
* Alfresco is distributed in the hope that it will be useful, * Alfresco is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details. * GNU Lesser General Public License for more details.
* *
* You should have received a copy of the GNU Lesser General Public License * You should have received a copy of the GNU Lesser General Public License
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>. * along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/ */
package org.alfresco.module.org_alfresco_module_rm.script.classification; 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.Map;
import java.util.Set; 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.module.org_alfresco_module_rm.script.AbstractRmWebScript;
import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.repository.NodeRef;
import org.json.JSONArray; 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_AUTHORITY = "classificationAuthority";
public static final String CLASSIFICATION_REASONS = "classificationReasons"; public static final String CLASSIFICATION_REASONS = "classificationReasons";
/** Classification service */ /** The service responsible for classifying content. */
private ClassificationService classificationService; 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; this.contentClassificationService = contentClassificationService;
}
/**
* @param classificationService the classificationService to set
*/
public void setClassificationService(ClassificationService classificationService)
{
this.classificationService = classificationService;
} }
/** /**
@@ -83,7 +77,7 @@ public class ClassifyContentPost extends AbstractRmWebScript
Set<String> classificationReasonIds = getClassificationReasonIds(jsonObject); Set<String> classificationReasonIds = getClassificationReasonIds(jsonObject);
NodeRef document = parseRequestForNodeRef(req); NodeRef document = parseRequestForNodeRef(req);
getClassificationService().classifyContent(classificationLevelId, classificationAuthority, classificationReasonIds, document); contentClassificationService.classifyContent(classificationLevelId, classificationAuthority, classificationReasonIds, document);
Map<String, Object> model = new HashMap<String, Object>(1); Map<String, Object> model = new HashMap<String, Object>(1);
model.put(SUCCESS, true); model.put(SUCCESS, true);

View File

@@ -64,7 +64,7 @@ public class ClassificationLevelsTest extends BaseRMTestCase
{ {
List<ClassificationLevel> levels = classificationService.getClassificationLevels(); List<ClassificationLevel> levels = classificationService.getClassificationLevels();
assertNotNull(levels); 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 level1 = levels.get(0);
ClassificationLevel level2 = levels.get(1); ClassificationLevel level2 = levels.get(1);

View File

@@ -34,12 +34,12 @@ import org.alfresco.service.cmr.repository.NodeRef;
*/ */
public class ClassifyTest extends BaseRMTestCase public class ClassifyTest extends BaseRMTestCase
{ {
/** test data */ /** test data */
private static final String CLASSIFICATION_LEVEL = "level1"; private static final String CLASSIFICATION_LEVEL = "level1";
private static final String CLASSIFICATION_REASON = "Test Reason 1"; private static final String CLASSIFICATION_REASON = "Test Reason 1";
private static final String CLASSIFICATION_AUTHORITY = "classification authority"; private static final String CLASSIFICATION_AUTHORITY = "classification authority";
private static final String RECORD_NAME = "recordname.txt"; private static final String RECORD_NAME = "recordname.txt";
/** /**
* Given that a record is frozen * Given that a record is frozen
* And unclassified * And unclassified
@@ -50,27 +50,27 @@ public class ClassifyTest extends BaseRMTestCase
{ {
doBehaviourDrivenTest(new BehaviourDrivenTest(AccessDeniedException.class) doBehaviourDrivenTest(new BehaviourDrivenTest(AccessDeniedException.class)
{ {
private NodeRef record; private NodeRef record;
public void given() throws Exception public void given() throws Exception
{ {
record = utils.createRecord(rmFolder, RECORD_NAME); record = utils.createRecord(rmFolder, RECORD_NAME);
NodeRef hold = holdService.createHold(filePlan, "my hold", "for test", "for test"); NodeRef hold = holdService.createHold(filePlan, "my hold", "for test", "for test");
holdService.addToHold(hold, record); holdService.addToHold(hold, record);
} }
public void when() throws Exception public void when() throws Exception
{ {
classificationService.classifyContent( contentClassificationService.classifyContent(
CLASSIFICATION_LEVEL, CLASSIFICATION_LEVEL,
CLASSIFICATION_AUTHORITY, CLASSIFICATION_AUTHORITY,
Collections.singleton(CLASSIFICATION_REASON), Collections.singleton(CLASSIFICATION_REASON),
record); record);
} }
}); });
} }
/** /**
* Given that a record is complete * Given that a record is complete
* And unclassified * And unclassified
@@ -80,31 +80,31 @@ public class ClassifyTest extends BaseRMTestCase
{ {
doBehaviourDrivenTest(new BehaviourDrivenTest() doBehaviourDrivenTest(new BehaviourDrivenTest()
{ {
private NodeRef record; private NodeRef record;
public void given() throws Exception public void given() throws Exception
{ {
record = utils.createRecord(rmFolder, RECORD_NAME); record = utils.createRecord(rmFolder, RECORD_NAME);
utils.completeRecord(record); utils.completeRecord(record);
} }
public void when() throws Exception public void when() throws Exception
{ {
classificationService.classifyContent( contentClassificationService.classifyContent(
CLASSIFICATION_LEVEL, CLASSIFICATION_LEVEL,
CLASSIFICATION_AUTHORITY, CLASSIFICATION_AUTHORITY,
Collections.singleton(CLASSIFICATION_REASON), Collections.singleton(CLASSIFICATION_REASON),
record); record);
} }
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public void then() throws Exception public void then() throws Exception
{ {
assertTrue(nodeService.hasAspect(record, ClassifiedContentModel.ASPECT_CLASSIFIED)); 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_INITIAL_CLASSIFICATION));
assertEquals(CLASSIFICATION_LEVEL, (String)nodeService.getProperty(record, ClassifiedContentModel.PROP_CURRENT_CLASSIFICATION)); assertEquals(CLASSIFICATION_LEVEL, (String)nodeService.getProperty(record, ClassifiedContentModel.PROP_CURRENT_CLASSIFICATION));
assertEquals(CLASSIFICATION_AUTHORITY, (String)nodeService.getProperty(record, ClassifiedContentModel.PROP_CLASSIFICATION_AUTHORITY)); assertEquals(CLASSIFICATION_AUTHORITY, (String)nodeService.getProperty(record, ClassifiedContentModel.PROP_CLASSIFICATION_AUTHORITY));
assertEquals(Collections.singletonList(CLASSIFICATION_REASON), (List<String>)nodeService.getProperty(record, ClassifiedContentModel.PROP_CLASSIFICATION_REASONS)); assertEquals(Collections.singletonList(CLASSIFICATION_REASON), (List<String>)nodeService.getProperty(record, ClassifiedContentModel.PROP_CLASSIFICATION_REASONS));
} }
}); });
} }

View File

@@ -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.CapabilityService;
import org.alfresco.module.org_alfresco_module_rm.capability.RMPermissionModel; 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.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.dataset.DataSetService;
import org.alfresco.module.org_alfresco_module_rm.disposition.DispositionSchedule; import org.alfresco.module.org_alfresco_module_rm.disposition.DispositionSchedule;
import org.alfresco.module.org_alfresco_module_rm.disposition.DispositionService; import org.alfresco.module.org_alfresco_module_rm.disposition.DispositionService;
@@ -162,6 +163,7 @@ public abstract class BaseRMTestCase extends RetryingTransactionHelperTestCase
protected InplaceRecordService inplaceRecordService; protected InplaceRecordService inplaceRecordService;
protected RelationshipService relationshipService; protected RelationshipService relationshipService;
protected ClassificationService classificationService; protected ClassificationService classificationService;
protected ContentClassificationService contentClassificationService;
/** test data */ /** test data */
protected String siteId; protected String siteId;
@@ -273,7 +275,7 @@ public abstract class BaseRMTestCase extends RetryingTransactionHelperTestCase
*/ */
protected boolean isRMSiteTest() protected boolean isRMSiteTest()
{ {
return true; return true;
} }
/** /**
@@ -402,6 +404,7 @@ public abstract class BaseRMTestCase extends RetryingTransactionHelperTestCase
inplaceRecordService = (InplaceRecordService) applicationContext.getBean("InplaceRecordService"); inplaceRecordService = (InplaceRecordService) applicationContext.getBean("InplaceRecordService");
relationshipService = (RelationshipService) applicationContext.getBean("RelationshipService"); relationshipService = (RelationshipService) applicationContext.getBean("RelationshipService");
classificationService = (ClassificationService) applicationContext.getBean("ClassificationService"); classificationService = (ClassificationService) applicationContext.getBean("ClassificationService");
contentClassificationService = (ContentClassificationService) applicationContext.getBean("ContentClassificationService");
} }
/** /**
@@ -482,44 +485,44 @@ public abstract class BaseRMTestCase extends RetryingTransactionHelperTestCase
*/ */
protected void setupTestData() protected void setupTestData()
{ {
doTestInTransaction(new Test<Void>() doTestInTransaction(new Test<Void>()
{ {
public Void run() public Void run()
{ {
setupTestDataImpl(); setupTestDataImpl();
if (isRecordTest() && isRMSiteTest()) if (isRecordTest() && isRMSiteTest())
{ {
setupTestRecords(); setupTestRecords();
} }
return null; return null;
} }
@Override @Override
public void test(Void result) throws Exception public void test(Void result) throws Exception
{ {
if (isRMSiteTest()) if (isRMSiteTest())
{ {
if (isRecordTest()) if (isRecordTest())
{ {
// declare a record // declare a record
utils.completeRecord(recordDeclaredOne); utils.completeRecord(recordDeclaredOne);
utils.completeRecord(recordDeclaredTwo); utils.completeRecord(recordDeclaredTwo);
} }
// unfiled container // unfiled container
unfiledContainer = filePlanService.getUnfiledContainer(filePlan); unfiledContainer = filePlanService.getUnfiledContainer(filePlan);
assertNotNull(unfiledContainer); assertNotNull(unfiledContainer);
// holds container // holds container
holdsContainer = filePlanService.getHoldContainer(filePlan); holdsContainer = filePlanService.getHoldContainer(filePlan);
assertNotNull(holdsContainer); assertNotNull(holdsContainer);
// transfers container // transfers container
transfersContainer = filePlanService.getTransferContainer(filePlan); transfersContainer = filePlanService.getTransferContainer(filePlan);
assertNotNull(transfersContainer); assertNotNull(transfersContainer);
} }
} }
}, AuthenticationUtil.getSystemUserName()); }, AuthenticationUtil.getSystemUserName());
} }
@@ -556,28 +559,28 @@ public abstract class BaseRMTestCase extends RetryingTransactionHelperTestCase
if (isRMSiteTest()) if (isRMSiteTest())
{ {
siteId = GUID.generate(); siteId = GUID.generate();
siteInfo = siteService.createSite( siteInfo = siteService.createSite(
"rm-site-dashboard", "rm-site-dashboard",
siteId, siteId,
"title", "title",
"descrition", "descrition",
SiteVisibility.PUBLIC, SiteVisibility.PUBLIC,
RecordsManagementModel.TYPE_RM_SITE); RecordsManagementModel.TYPE_RM_SITE);
filePlan = siteService.getContainer(siteId, RmSiteType.COMPONENT_DOCUMENT_LIBRARY); filePlan = siteService.getContainer(siteId, RmSiteType.COMPONENT_DOCUMENT_LIBRARY);
assertNotNull("Site document library container was not created successfully.", filePlan); assertNotNull("Site document library container was not created successfully.", filePlan);
// Create RM container // Create RM container
rmContainer = filePlanService.createRecordCategory(filePlan, "rmContainer"); rmContainer = filePlanService.createRecordCategory(filePlan, "rmContainer");
assertNotNull("Could not create rm container", rmContainer); assertNotNull("Could not create rm container", rmContainer);
// Create disposition schedule // Create disposition schedule
dispositionSchedule = utils.createBasicDispositionSchedule(rmContainer); dispositionSchedule = utils.createBasicDispositionSchedule(rmContainer);
// Create RM folder // Create RM folder
rmFolder = recordFolderService.createRecordFolder(rmContainer, "rmFolder"); rmFolder = recordFolderService.createRecordFolder(rmContainer, "rmFolder");
assertNotNull("Could not create rm folder", rmFolder); assertNotNull("Could not create rm folder", rmFolder);
} }
} }

View File

@@ -29,7 +29,7 @@ import org.junit.Test;
/** /**
* Unit tests for the {@link ClassificationLevelManager}. * Unit tests for the {@link ClassificationLevelManager}.
* *
* @author tpage * @author tpage
*/ */
public class ClassificationLevelManagerUnitTest public class ClassificationLevelManagerUnitTest
@@ -43,7 +43,8 @@ public class ClassificationLevelManagerUnitTest
@Before public void setup() @Before public void setup()
{ {
classificationLevelManager = new ClassificationLevelManager(LEVELS); classificationLevelManager = new ClassificationLevelManager();
classificationLevelManager.setClassificationLevels(LEVELS);
} }
@Test public void findClassificationById_found() @Test public void findClassificationById_found()

View File

@@ -29,7 +29,7 @@ import org.junit.Test;
/** /**
* Unit tests for the {@link ClassificationReasonManager}. * Unit tests for the {@link ClassificationReasonManager}.
* *
* @author tpage * @author tpage
*/ */
public class ClassificationReasonManagerUnitTest public class ClassificationReasonManagerUnitTest
@@ -43,7 +43,8 @@ public class ClassificationReasonManagerUnitTest
@Before public void setup() @Before public void setup()
{ {
classificationReasonManager = new ClassificationReasonManager(REASONS); classificationReasonManager = new ClassificationReasonManager();
classificationReasonManager.setClassificationReasons(REASONS);
} }
@Test public void findClassificationById_found() @Test public void findClassificationById_found()

View File

@@ -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 <http://www.gnu.org/licenses/>.
*/
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<ClassificationLevel> 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<ClassificationLevel> ALT_CLASSIFICATION_LEVELS = asLevelList("Board", "B",
"Executive Management", "EM",
"Employee", "E",
"Public", "P");
private static final List<ClassificationReason> PLACEHOLDER_CLASSIFICATION_REASONS = asList(new ClassificationReason("id1", "label1"),
new ClassificationReason("id2", "label2"));
private static final List<ClassificationReason> 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<ClassificationLevel> 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<ClassificationLevel> 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<LoggingEvent> 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.
* <p>
* 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<LoggingEvent> loggingEvents = loggingEventCaptor.getAllValues();
Stream<String> 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<ClassificationLevel> classificationLevels = ImmutableList.of(topSecret, secret, ClassificationLevelManager.UNCLASSIFIED);
// Call the method under test.
classificationServiceBootstrap.initConfiguredClearanceLevels(classificationLevels);
List<ClearanceLevel> 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());
}
}

View File

@@ -18,45 +18,21 @@
*/ */
package org.alfresco.module.org_alfresco_module_rm.classification; 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.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.doThrow;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when; 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.io.Serializable;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Map; 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.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.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.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.dictionary.DictionaryService;
import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.cmr.repository.NodeService; import org.alfresco.service.cmr.repository.NodeService;
import org.alfresco.service.namespace.QName; 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.Before;
import org.junit.Test; import org.junit.Test;
import org.mockito.ArgumentCaptor; import org.mockito.ArgumentCaptor;
@@ -65,8 +41,6 @@ import org.mockito.InjectMocks;
import org.mockito.Mock; import org.mockito.Mock;
import org.mockito.MockitoAnnotations; import org.mockito.MockitoAnnotations;
import com.google.common.collect.Sets;
/** /**
* Unit tests for {@link ClassificationServiceImpl}. * Unit tests for {@link ClassificationServiceImpl}.
* *
@@ -79,18 +53,7 @@ public class ClassificationServiceImplUnitTest
"Secret", "rm.classification.secret", "Secret", "rm.classification.secret",
"Confidential", "rm.classification.confidential", "Confidential", "rm.classification.confidential",
"No Clearance", "rm.classification.noClearance"); "No Clearance", "rm.classification.noClearance");
private static final List<ClassificationLevel> ALT_CLASSIFICATION_LEVELS = asLevelList("Board", "B",
"Executive Management", "EM",
"Employee", "E",
"Public", "P");
private static final List<ClassificationReason> PLACEHOLDER_CLASSIFICATION_REASONS = asList(new ClassificationReason("id1", "label1"),
new ClassificationReason("id2", "label2"));
private static final List<ClassificationReason> 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 * A convenience method for turning lists of level id Strings into lists
* of {@code ClassificationLevel} objects. * of {@code ClassificationLevel} objects.
@@ -117,128 +80,15 @@ public class ClassificationServiceImplUnitTest
@InjectMocks private ClassificationServiceImpl classificationServiceImpl; @InjectMocks private ClassificationServiceImpl classificationServiceImpl;
@Mock private AttributeService mockedAttributeService;
@Mock private AuthenticationUtil mockedAuthenticationUtil;
@Mock private ClassificationServiceDAO mockClassificationServiceDAO;
@Mock private NodeService mockNodeService; @Mock private NodeService mockNodeService;
@Mock private DictionaryService mockDictionaryService; @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 ClassificationLevelManager mockLevelManager;
@Mock private ClassificationReasonManager mockReasonManager; @Mock private ClassificationReasonManager mockReasonManager;
@Captor private ArgumentCaptor<LoggingEvent> loggingEventCaptor;
@Captor private ArgumentCaptor<Map<QName, Serializable>> propertiesCaptor; @Captor private ArgumentCaptor<Map<QName, Serializable>> propertiesCaptor;
@Before public void setUp() @Before public void setUp()
{ {
MockitoAnnotations.initMocks(this); 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.
* <p>
* 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<LoggingEvent> loggingEvents = loggingEventCaptor.getAllValues();
Stream<String> 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()); 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<QName, Serializable> properties = propertiesCaptor.getValue();
HashSet<QName> 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<String> expectedReasonIds = Sets.newHashSet("reasonId1", "reasonId2");
assertEquals("Unexpected set of reasons.", expectedReasonIds, properties.get(ClassifiedContentModel.PROP_CLASSIFICATION_REASONS));
}
/** Classify a folder using the <code>classifyContent</code> 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 @Test
public void getClassificationLevelById() public void getClassificationLevelById()
{ {
@@ -369,48 +156,4 @@ public class ClassificationServiceImplUnitTest
doThrow(new ReasonIdNotFound("Id not found!")).when(mockReasonManager).findReasonById(classificationReasonId); doThrow(new ReasonIdNotFound("Id not found!")).when(mockReasonManager).findReasonById(classificationReasonId);
classificationServiceImpl.getClassificationReasonById(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);
}
} }

View File

@@ -47,8 +47,11 @@ public class ClearanceLevelManagerUnitTest
@Before @Before
public void setup() public void setup()
{ {
List<ClearanceLevel> clearanceLevels = ImmutableList.of(TOP_SECRET_CLEARANCE, SECRET_CLEARANCE); List<ClearanceLevel> clearanceLevels = ImmutableList.of(TOP_SECRET_CLEARANCE,
clearanceLevelManager = new ClearanceLevelManager(clearanceLevels); 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". */ /** 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"); 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 * Then the no clearance level is available
*
* ...(and not added in the same way as for {@link ClassificationLevelManager#setClassificationLevels(List)}).
*/ */
@Test public void noClearanceLevel() @Test public void noClearanceLevel()
{ {
assertEquals(3, clearanceLevelManager.getClearanceLevels().size()); assertEquals("The three clearance levels should be available.", 3, clearanceLevelManager.getClearanceLevels().size());
ClearanceLevel noClearance = clearanceLevelManager.findLevelByClassificationLevelId(ClassificationLevelManager.UNCLASSIFIED_ID); ClearanceLevel noClearance = clearanceLevelManager.findLevelByClassificationLevelId(ClassificationLevelManager.UNCLASSIFIED_ID);
assertEquals(ClearanceLevelManager.NO_CLEARANCE, noClearance); assertEquals("'No clearance' could not be found.", ClearanceLevelManager.NO_CLEARANCE, noClearance);
assertEquals(ClassificationLevelManager.UNCLASSIFIED, noClearance.getHighestClassificationLevel());
} }
} }

View File

@@ -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 <http://www.gnu.org/licenses/>.
*/
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<Map<QName, Serializable>> 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<QName, Serializable> properties = propertiesCaptor.getValue();
HashSet<QName> 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<String> expectedReasonIds = Sets.newHashSet("reasonId1", "reasonId2");
assertEquals("Unexpected set of reasons.", expectedReasonIds, properties.get(ClassifiedContentModel.PROP_CLASSIFICATION_REASONS));
}
/** Classify a folder using the <code>classifyContent</code> 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<ClassificationLevel> 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<ClassificationLevel> 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<ClassificationLevel> 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));
}
}

View File

@@ -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.ASPECT_SECURITY_CLEARANCE;
import static org.alfresco.module.org_alfresco_module_rm.classification.model.ClassifiedContentModel.PROP_CLEARANCE_LEVEL; 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.anyBoolean;
import static org.mockito.Matchers.eq; import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when; import static org.mockito.Mockito.when;
import static org.alfresco.module.org_alfresco_module_rm.test.util.AlfMock.*; import static org.alfresco.module.org_alfresco_module_rm.test.util.AlfMock.*;
import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;
@@ -58,12 +59,13 @@ public class SecurityClearanceServiceImplUnitTest
{ {
@InjectMocks private SecurityClearanceServiceImpl securityClearanceServiceImpl; @InjectMocks private SecurityClearanceServiceImpl securityClearanceServiceImpl;
@Mock private AuthenticationUtil mockedAuthenticationUtil; @Mock private AuthenticationUtil mockAuthenticationUtil;
@Mock private ClassificationService mockClassificationService; @Mock private ClassificationLevelManager mockClassificationLevelManager;
@Mock private DictionaryService mockDictionaryService; @Mock private DictionaryService mockDictionaryService;
@Mock private NodeService mockNodeService; @Mock private NodeService mockNodeService;
@Mock private PersonService mockPersonService; @Mock private PersonService mockPersonService;
@Mock private ClearanceLevelManager mockClearanceLevelManager; @Mock private ClassificationService mockClassificationService;
@Mock private ClearanceLevelManager mockClearanceLevelManager;
@Before public void setUp() @Before public void setUp()
{ {
@@ -76,15 +78,15 @@ public class SecurityClearanceServiceImplUnitTest
final PersonInfo info = new PersonInfo(userNode, userName, firstName, lastName); final PersonInfo info = new PersonInfo(userNode, userName, firstName, lastName);
when(mockPersonService.getPerson(eq(userName), anyBoolean())).thenReturn(userNode); 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.hasAspect(userNode, ASPECT_SECURITY_CLEARANCE)).thenReturn(clearanceLevel != null);
when(mockNodeService.getProperty(eq(userNode), eq(PROP_CLEARANCE_LEVEL))).thenReturn(clearanceLevel); when(mockNodeService.getProperty(userNode, PROP_CLEARANCE_LEVEL)).thenReturn(clearanceLevel);
if (clearanceLevel != null) if (clearanceLevel != null)
{ {
final ClassificationLevel dummyValue = new ClassificationLevel(clearanceLevel, clearanceLevel); final ClassificationLevel dummyValue = new ClassificationLevel(clearanceLevel, clearanceLevel);
when(mockClassificationService.getClassificationLevelById(eq(clearanceLevel))).thenReturn(dummyValue); when(mockClassificationLevelManager.findLevelById(clearanceLevel)).thenReturn(dummyValue);
} }
return info; return info;
@@ -93,246 +95,115 @@ public class SecurityClearanceServiceImplUnitTest
@Test public void userWithNoClearanceGetsDefaultClearance() @Test public void userWithNoClearanceGetsDefaultClearance()
{ {
final PersonInfo user1 = createMockPerson("user1", "User", "One", null); final PersonInfo user1 = createMockPerson("user1", "User", "One", null);
MockAuthenticationUtilHelper.setup(mockedAuthenticationUtil, user1.getUserName()); MockAuthenticationUtilHelper.setup(mockAuthenticationUtil, user1.getUserName());
when(mockClassificationService.getUnclassifiedClassificationLevel()) when(mockClassificationService.getUnclassifiedClassificationLevel())
.thenReturn(ClassificationLevelManager.UNCLASSIFIED); .thenReturn(ClassificationLevelManager.UNCLASSIFIED);
when(mockClearanceLevelManager.findLevelByClassificationLevelId(ClassificationLevelManager.UNCLASSIFIED_ID)) when(mockClearanceLevelManager.findLevelByClassificationLevelId(ClassificationLevelManager.UNCLASSIFIED_ID))
.thenReturn(ClearanceLevelManager.NO_CLEARANCE); .thenReturn(ClearanceLevelManager.NO_CLEARANCE);
final SecurityClearance clearance = securityClearanceServiceImpl.getUserSecurityClearance(); final SecurityClearance clearance = securityClearanceServiceImpl.getUserSecurityClearance();
assertEquals(ClassificationLevelManager.UNCLASSIFIED, clearance.getClearanceLevel().getHighestClassificationLevel()); 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() @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"; String userName = "User 1";
NodeRef personNode = new NodeRef(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE, userName); 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(userName, false)).thenReturn(personNode);
when(mockPersonService.getPerson(personNode)).thenReturn(personInfo); when(mockPersonService.getPerson(personNode)).thenReturn(personInfo);
// Create the clearance. // Once the user's clearance has been set then the node service is queried about it.
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);
when(mockNodeService.hasAspect(personNode, ASPECT_SECURITY_CLEARANCE)).thenReturn(true); 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. // 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(personInfo, securityClearance.getPersonInfo());
assertEquals(clearanceLevel, securityClearance.getClearanceLevel()); assertEquals(clearanceLevel, securityClearance.getClearanceLevel());
// Check the value stored in the node service.
verify(mockNodeService).setProperty(personNode, PROP_CLEARANCE_LEVEL, clearanceId); verify(mockNodeService).setProperty(personNode, PROP_CLEARANCE_LEVEL, topSecretId);
} }
/** /** Check that a user cannot raise someone else's clearance above their own. */
* 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.
*/
@Test(expected = LevelIdNotFound.class) @Test(expected = LevelIdNotFound.class)
public void setUserSecurityClearance_insufficientClearance() public void setUserSecurityClearance_insufficientClearance()
{ {
String userName = "User 1"; // Create the "Top Secret" and "Confidential" clearances.
NodeRef personNode = new NodeRef(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE, userName); String topSecretId = "TopSecretClearanceId";
when(mockPersonService.getPerson(userName, false)).thenReturn(personNode); ClassificationLevel topSecret = new ClassificationLevel(topSecretId, "TopSecretKey");
String clearanceId = "ClearanceId"; ClearanceLevel topSecretClearance = new ClearanceLevel(topSecret, "TopSecretKey");
// If the user has insufficient clearance then they cannot access the level. when(mockClearanceLevelManager.findLevelByClassificationLevelId(topSecretId)).thenReturn(topSecretClearance);
when(mockClassificationService.getClassificationLevelById(clearanceId)).thenThrow(new LevelIdNotFound(clearanceId)); 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 * Check that a user with no clearance is not cleared to use the "Secret" classification.
* the display label for the lowest clearance level is "No Clearance" (rather than "Unclassified").
*/ */
@Test public void initialise() @Test public void isClearedForClassification()
{ {
ClassificationLevel topSecret = new ClassificationLevel("1", "TopSecret"); ClassificationLevel topSecret = new ClassificationLevel("1", "TopSecret");
ClassificationLevel secret = new ClassificationLevel("2", "Secret"); ClassificationLevel secret = new ClassificationLevel("2", "Secret");
List<ClassificationLevel> classificationLevels = Arrays.asList(topSecret, secret, ClassificationLevelManager.UNCLASSIFIED); ImmutableList<ClassificationLevel> classificationLevels = ImmutableList.of(topSecret, secret);
when(mockClassificationService.getClassificationLevels()).thenReturn(classificationLevels ); when(mockClassificationLevelManager.getClassificationLevels()).thenReturn(classificationLevels);
SecurityClearance clearance = new SecurityClearance(mock(PersonInfo.class), ClearanceLevelManager.NO_CLEARANCE);
// Call the method under test. // Call the method under test.
securityClearanceServiceImpl.initialise(); boolean result = securityClearanceServiceImpl.isClearedForClassification(clearance, "2");
List<ClearanceLevel> clearanceLevels = securityClearanceServiceImpl.getClearanceManager().getClearanceLevels(); assertFalse("A user with no clearance should not be able to access the 'Secret' classification.", result);
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<ClassificationLevel> 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<ClassificationLevel> 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<ClassificationLevel> 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));
} }
/** /**

View File

@@ -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.ClassificationLevelManager;
import org.alfresco.module.org_alfresco_module_rm.classification.ClassificationReasonManager; 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.alfresco.module.org_alfresco_module_rm.test.util.BaseWebScriptUnitTest;
import org.json.JSONArray; import org.json.JSONArray;
import org.json.JSONObject; import org.json.JSONObject;
@@ -68,8 +68,8 @@ public class ClassifyContentPostUnitTest extends BaseWebScriptUnitTest
/** ClassifyContentPost webscript instance */ /** ClassifyContentPost webscript instance */
private @Spy @InjectMocks ClassifyContentPost webScript; private @Spy @InjectMocks ClassifyContentPost webScript;
/** Mocked classification service */ /** Mocked content classification service */
private @Mock ClassificationService mockedClassificationService; private @Mock ContentClassificationService mockedContentClassificationService;
/** Mocked classification level manager */ /** Mocked classification level manager */
private @Mock ClassificationLevelManager mockedClassificationLevelManager; private @Mock ClassificationLevelManager mockedClassificationLevelManager;
@@ -116,7 +116,7 @@ public class ClassifyContentPostUnitTest extends BaseWebScriptUnitTest
assertEquals(getStringValueFromJSONObject(json, SUCCESS), Boolean.TRUE.toString()); assertEquals(getStringValueFromJSONObject(json, SUCCESS), Boolean.TRUE.toString());
// Verify that the classify content method was called // 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);
} }
/** /**