RM-2502 Create new constraint for initial classification.

Previously we were restricting the initial classification to be a level
that the current user could access, but this is not always the case for
downgraded content.

Also add new integration test for initial classification constraint.

I tried adding a test that extended our BaseRMTestCase, but the
transactions all happened as system (or admin maybe?), and so the level 2
user is always allowed to reclassify level 1 content (by the constraint).
Consequently I ended up creating a stand-alone test for this.

+review RM

git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/modules/recordsmanagement/HEAD@110144 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261
This commit is contained in:
Tom Page
2015-08-14 13:34:56 +00:00
parent 3c4becdd85
commit 2e0c28fe1b
6 changed files with 363 additions and 1 deletions

View File

@@ -108,6 +108,7 @@
<property name="objectDefinitionSource"> <property name="objectDefinitionSource">
<value> <value>
org.alfresco.module.org_alfresco_module_rm.classification.ClassificationSchemeService.getClassificationLevels=ACL_ALLOW org.alfresco.module.org_alfresco_module_rm.classification.ClassificationSchemeService.getClassificationLevels=ACL_ALLOW
org.alfresco.module.org_alfresco_module_rm.classification.ClassificationSchemeService.getAllClassificationLevels=ACL_ALLOW
org.alfresco.module.org_alfresco_module_rm.classification.ClassificationSchemeService.getClassificationReasons=ACL_ALLOW org.alfresco.module.org_alfresco_module_rm.classification.ClassificationSchemeService.getClassificationReasons=ACL_ALLOW
org.alfresco.module.org_alfresco_module_rm.classification.ClassificationSchemeService.getClassificationLevelById=ACL_ALLOW org.alfresco.module.org_alfresco_module_rm.classification.ClassificationSchemeService.getClassificationLevelById=ACL_ALLOW
org.alfresco.module.org_alfresco_module_rm.classification.ClassificationSchemeService.getClassificationReasonById=ACL_ALLOW org.alfresco.module.org_alfresco_module_rm.classification.ClassificationSchemeService.getClassificationReasonById=ACL_ALLOW

View File

@@ -26,6 +26,8 @@
<!-- Model Constraints --> <!-- Model Constraints -->
<constraints> <constraints>
<constraint name="clf:initialClassificationLevelRestriction"
type="org.alfresco.module.org_alfresco_module_rm.classification.InitialClassificationLevelConstraint" />
<constraint name="clf:classificationLevelRestriction" <constraint name="clf:classificationLevelRestriction"
type="org.alfresco.module.org_alfresco_module_rm.classification.ClassificationLevelConstraint" /> type="org.alfresco.module.org_alfresco_module_rm.classification.ClassificationLevelConstraint" />
<constraint name="clf:classificationReasonRestriction" <constraint name="clf:classificationReasonRestriction"
@@ -53,7 +55,7 @@
<protected>true</protected> <protected>true</protected>
<mandatory>true</mandatory> <mandatory>true</mandatory>
<constraints> <constraints>
<constraint ref="clf:classificationLevelRestriction" /> <constraint ref="clf:initialClassificationLevelRestriction" />
</constraints> </constraints>
</property> </property>
<property name="clf:currentClassification"> <property name="clf:currentClassification">

View File

@@ -44,6 +44,15 @@ public interface ClassificationSchemeService
*/ */
List<ClassificationLevel> getClassificationLevels(); List<ClassificationLevel> getClassificationLevels();
/**
* Returns an immutable list of all the classification levels defined in the system.
*
* @return classification levels in descending order from highest to lowest
* (where fewer users have access to the highest classification levels
* and therefore access to the most restricted documents).
*/
List<ClassificationLevel> getAllClassificationLevels();
/** /**
* Returns an immutable list of the defined classification reasons. * Returns an immutable list of the defined classification reasons.
* @return classification reasons in the order that they are defined. * @return classification reasons in the order that they are defined.

View File

@@ -90,6 +90,16 @@ public class ClassificationSchemeServiceImpl extends ServiceBaseImpl implements
return restrictList(levelManager.getClassificationLevels(), usersLevel); return restrictList(levelManager.getClassificationLevels(), usersLevel);
} }
@Override
public List<ClassificationLevel> getAllClassificationLevels()
{
if (levelManager == null)
{
return Collections.emptyList();
}
return levelManager.getClassificationLevels();
}
@Override public List<ClassificationReason> getClassificationReasons() @Override public List<ClassificationReason> getClassificationReasons()
{ {
return reasonManager == null ? Collections.<ClassificationReason>emptyList() : return reasonManager == null ? Collections.<ClassificationReason>emptyList() :

View File

@@ -0,0 +1,51 @@
/*
* 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.ArrayList;
import java.util.List;
/**
* Check that a value is a valid initial {@link ClassificationLevel} by checking the {@link ClassificationSchemeService}.
* The initial classification level is allowed to be any level, regardless of the clearance of the current user.
*
* @author tpage
* @since 3.0.a
*/
public class InitialClassificationLevelConstraint extends ClassificationSchemeEntityConstraint
{
/**
* Get the allowed values. Note that these are <tt>String</tt> instances, but may
* represent non-<tt>String</tt> values. It is up to the caller to distinguish.
*
* @return the values allowed.
*/
@Override
protected List<String> getAllowedValues()
{
List<ClassificationLevel> classificationLevels = classificationSchemeService.getAllClassificationLevels();
List<String> values = new ArrayList<String>();
for (ClassificationLevel classificationLevel : classificationLevels)
{
values.add(classificationLevel.getId());
}
return values;
}
}

View File

@@ -0,0 +1,289 @@
/*
* 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.test.integration.classification;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import java.io.Serializable;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import org.alfresco.model.ContentModel;
import org.alfresco.module.org_alfresco_module_rm.capability.RMPermissionModel;
import org.alfresco.module.org_alfresco_module_rm.classification.ClassificationAspectProperties;
import org.alfresco.module.org_alfresco_module_rm.classification.ContentClassificationService;
import org.alfresco.module.org_alfresco_module_rm.classification.SecurityClearanceService;
import org.alfresco.module.org_alfresco_module_rm.classification.model.ClassifiedContentModel;
import org.alfresco.module.org_alfresco_module_rm.fileplan.FilePlanService;
import org.alfresco.module.org_alfresco_module_rm.model.RecordsManagementModel;
import org.alfresco.module.org_alfresco_module_rm.model.rma.type.RmSiteType;
import org.alfresco.module.org_alfresco_module_rm.recordfolder.RecordFolderService;
import org.alfresco.module.org_alfresco_module_rm.role.FilePlanRoleService;
import org.alfresco.module.org_alfresco_module_rm.security.FilePlanPermissionService;
import org.alfresco.module.org_alfresco_module_rm.test.util.CommonRMTestUtils;
import org.alfresco.repo.security.authentication.AuthenticationUtil;
import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork;
import org.alfresco.repo.security.authority.AuthorityDAO;
import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.cmr.repository.NodeService;
import org.alfresco.service.cmr.security.MutableAuthenticationService;
import org.alfresco.service.cmr.security.PersonService;
import org.alfresco.service.cmr.site.SiteService;
import org.alfresco.service.cmr.site.SiteVisibility;
import org.alfresco.service.namespace.QName;
import org.alfresco.util.ApplicationContextHelper;
import org.alfresco.util.GUID;
import org.junit.Before;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
/**
* Integration tests that require the constraints to be executed by a user other than Admin.
*
* @author Tom Page
* @since 3.0.a
*/
public class ClassificationConstraintTest implements RMPermissionModel
{
/* test data */
private static final String CLASSIFICATION_REASON = "Test Reason 1";
private static final String CLASSIFIED_BY = "classified by text";
private static final String RECORD_NAME = "recordname.txt";
/* Application context */
protected String[] getConfigLocations()
{
return new String[]
{
"classpath:alfresco/application-context.xml",
"classpath:test-context.xml"
};
}
private ApplicationContext applicationContext;
/** Common test utils */
private CommonRMTestUtils utils;
/* Services */
private NodeService nodeService;
private MutableAuthenticationService authenticationService;
private SiteService siteService;
private PersonService personService;
/* RM Services */
private FilePlanRoleService filePlanRoleService;
private FilePlanPermissionService filePlanPermissionService;
private FilePlanService filePlanService;
private RecordFolderService recordFolderService;
private SecurityClearanceService securityClearanceService;
private ContentClassificationService contentClassificationService;
/* test data */
private String siteId;
private NodeRef filePlan;
private NodeRef rmContainer;
private NodeRef rmFolder;
/** Load the application context and create the test data. */
@Before
public void setUp() throws Exception
{
// Get the application context
applicationContext = ApplicationContextHelper.getApplicationContext(getConfigLocations());
utils = new CommonRMTestUtils(applicationContext);
// Initialise the service beans
initServices();
// Setup test data
setupTestData();
}
/** Initialise the service beans. */
protected void initServices()
{
// Get services
nodeService = (NodeService)applicationContext.getBean("NodeService");
siteService = (SiteService)this.applicationContext.getBean("SiteService");
authenticationService = (MutableAuthenticationService)this.applicationContext.getBean("AuthenticationService");
personService = (PersonService)this.applicationContext.getBean("PersonService");
// Get RM services
filePlanRoleService = (FilePlanRoleService)this.applicationContext.getBean("FilePlanRoleService");
filePlanPermissionService = (FilePlanPermissionService)this.applicationContext.getBean("FilePlanPermissionService");
filePlanService = (FilePlanService) applicationContext.getBean("FilePlanService");
recordFolderService = (RecordFolderService) applicationContext.getBean("RecordFolderService");
securityClearanceService = (SecurityClearanceService) applicationContext.getBean("SecurityClearanceService");
contentClassificationService = (ContentClassificationService) applicationContext.getBean("ContentClassificationService");
}
/** Setup test data for tests */
protected void setupTestData()
{
AuthenticationUtil.runAsSystem(new RunAsWork<Void>()
{
public Void doWork()
{
AuthorityDAO authDao = (AuthorityDAO)applicationContext.getBean("authorityDAO");
if (!authDao.authorityExists(AuthenticationUtil.getSystemUserName()))
{
createPerson(AuthenticationUtil.getSystemUserName(), false);
}
assertTrue("No person object for System available.", authDao.authorityExists(AuthenticationUtil.getSystemUserName()));
siteId = GUID.generate();
siteService.createSite(
"rm-site-dashboard",
siteId,
"title",
"descrition",
SiteVisibility.PUBLIC,
RecordsManagementModel.TYPE_RM_SITE);
filePlan = siteService.getContainer(siteId, RmSiteType.COMPONENT_DOCUMENT_LIBRARY);
assertNotNull("Site document library container was not created successfully.", filePlan);
// Create RM container
rmContainer = filePlanService.createRecordCategory(filePlan, "rmContainer");
assertNotNull("Could not create rm container", rmContainer);
// Create RM folder
rmFolder = recordFolderService.createRecordFolder(rmContainer, "rmFolder");
assertNotNull("Could not create rm folder", rmFolder);
return null;
}
});
}
/**
* Mid-level user further downgrading a downgraded record.
* <p>
* <a href="https://issues.alfresco.com/jira/browse/RM-2502">RM-2502</a><pre>
* Given I have "level2" clearance
* And a record has an initial classification of "level1"
* And the record has a current classification of "level2"
* When I try to downgrade the record to "level3"
* Then I am successful.
* </pre>
*/
@Test
public void testInitialClassificationConstraint()
{
// Given I set up some test data (admin at level 1, midLevelUser at level 2 and a new record).
final String midLevelUser = GUID.generate();
final NodeRef record = AuthenticationUtil.runAsSystem(new RunAsWork<NodeRef>()
{
public NodeRef doWork()
{
// Ensure admin is level 1 cleared.
securityClearanceService.setUserSecurityClearance(AuthenticationUtil.getAdminUserName(), "level1");
// Create user with level 2 clearance.
createPerson(midLevelUser, true);
filePlanRoleService.assignRoleToAuthority(filePlan, FilePlanRoleService.ROLE_RECORDS_MANAGER, midLevelUser);
filePlanPermissionService.setPermission(rmContainer, midLevelUser, FILING);
securityClearanceService.setUserSecurityClearance(midLevelUser, "level2");
// Create a record to be classified during the test.
return utils.createRecord(rmFolder, RECORD_NAME);
}
});
// And admin creates a downgraded record.
AuthenticationUtil.runAs(new RunAsWork<Void>()
{
public Void doWork()
{
// Create a level 1 record and downgrade it to level 2.
classifyAs(record, "level1");
classifyAs(record, "level2");
assertTrue("Record should have been classified.",
nodeService.hasAspect(record, ClassifiedContentModel.ASPECT_CLASSIFIED));
assertEquals("Record have initial classification of 'level1'.", "level1",
nodeService.getProperty(record, ClassifiedContentModel.PROP_INITIAL_CLASSIFICATION));
assertEquals("Record should be 'level2' classified.", "level2",
nodeService.getProperty(record, ClassifiedContentModel.PROP_CURRENT_CLASSIFICATION));
return null;
}
}, AuthenticationUtil.getAdminUserName());
// When the mid-level user downgrades the record to level 3.
AuthenticationUtil.runAs(new RunAsWork<Void>()
{
public Void doWork()
{
// Check that the mid-clearance user can further downgrade the classification (even though the initial
// classification was above their clearance).
classifyAs(record, "level3");
return null;
}
}, midLevelUser);
// Then the record is classified at level 3 (with initial classification level 1).
AuthenticationUtil.runAs(new RunAsWork<Void>()
{
public Void doWork()
{
assertTrue("Record should still be classified.",
nodeService.hasAspect(record, ClassifiedContentModel.ASPECT_CLASSIFIED));
assertEquals("Record have initial classification of 'level1'.", "level1",
nodeService.getProperty(record, ClassifiedContentModel.PROP_INITIAL_CLASSIFICATION));
assertEquals("Record should be 'level3' classified.", "level3",
nodeService.getProperty(record, ClassifiedContentModel.PROP_CURRENT_CLASSIFICATION));
return null;
}
}, AuthenticationUtil.getAdminUserName());
}
/**
* Util method to create a person.
*
* @param userName user name
* @param createAuth Whether to give the user a password or not.
* @return NodeRef user node reference
*/
private NodeRef createPerson(String userName, boolean createAuth)
{
if (createAuth)
{
authenticationService.createAuthentication(userName, "password".toCharArray());
}
Map<QName, Serializable> properties = new HashMap<QName, Serializable>();
properties.put(ContentModel.PROP_USERNAME, userName);
return personService.createPerson(properties);
}
/**
* Classify the given node.
*
* @param node The node to classify.
* @param level The id of the classification level to use.
*/
private void classifyAs(final NodeRef node, final String level)
{
ClassificationAspectProperties propertiesDTO = new ClassificationAspectProperties();
propertiesDTO.setClassificationLevelId(level);
propertiesDTO.setClassifiedBy(CLASSIFIED_BY);
propertiesDTO.setClassificationReasonIds(Collections.singleton(CLASSIFICATION_REASON));
contentClassificationService.editClassifiedContent(propertiesDTO, node);
}
}