From d359e6f1ff07cbb9c83e1c08c4847e1d52352259 Mon Sep 17 00:00:00 2001 From: Tom Page Date: Fri, 14 Aug 2015 13:34:56 +0000 Subject: [PATCH] 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 --- .../classified-content-context.xml | 1 + .../model/classifiedContentModel.xml | 4 +- .../ClassificationSchemeService.java | 9 + .../ClassificationSchemeServiceImpl.java | 10 + .../InitialClassificationLevelConstraint.java | 51 ++++ .../ClassificationConstraintTest.java | 289 ++++++++++++++++++ 6 files changed, 363 insertions(+), 1 deletion(-) create mode 100644 rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/classification/InitialClassificationLevelConstraint.java create mode 100644 rm-server/test/java/org/alfresco/module/org_alfresco_module_rm/test/integration/classification/ClassificationConstraintTest.java diff --git a/rm-server/config/alfresco/module/org_alfresco_module_rm/classified-content-context.xml b/rm-server/config/alfresco/module/org_alfresco_module_rm/classified-content-context.xml index b36727ef8f..ad6f3419a3 100644 --- a/rm-server/config/alfresco/module/org_alfresco_module_rm/classified-content-context.xml +++ b/rm-server/config/alfresco/module/org_alfresco_module_rm/classified-content-context.xml @@ -108,6 +108,7 @@ 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.getClassificationLevelById=ACL_ALLOW org.alfresco.module.org_alfresco_module_rm.classification.ClassificationSchemeService.getClassificationReasonById=ACL_ALLOW diff --git a/rm-server/config/alfresco/module/org_alfresco_module_rm/model/classifiedContentModel.xml b/rm-server/config/alfresco/module/org_alfresco_module_rm/model/classifiedContentModel.xml index 3c4896789e..54f1207c70 100644 --- a/rm-server/config/alfresco/module/org_alfresco_module_rm/model/classifiedContentModel.xml +++ b/rm-server/config/alfresco/module/org_alfresco_module_rm/model/classifiedContentModel.xml @@ -26,6 +26,8 @@ + true true - + diff --git a/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/classification/ClassificationSchemeService.java b/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/classification/ClassificationSchemeService.java index 093e208140..8e49a56d16 100644 --- a/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/classification/ClassificationSchemeService.java +++ b/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/classification/ClassificationSchemeService.java @@ -44,6 +44,15 @@ public interface ClassificationSchemeService */ List 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 getAllClassificationLevels(); + /** * Returns an immutable list of the defined classification reasons. * @return classification reasons in the order that they are defined. diff --git a/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/classification/ClassificationSchemeServiceImpl.java b/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/classification/ClassificationSchemeServiceImpl.java index abbcd8fc49..d5849b293f 100644 --- a/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/classification/ClassificationSchemeServiceImpl.java +++ b/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/classification/ClassificationSchemeServiceImpl.java @@ -90,6 +90,16 @@ public class ClassificationSchemeServiceImpl extends ServiceBaseImpl implements return restrictList(levelManager.getClassificationLevels(), usersLevel); } + @Override + public List getAllClassificationLevels() + { + if (levelManager == null) + { + return Collections.emptyList(); + } + return levelManager.getClassificationLevels(); + } + @Override public List getClassificationReasons() { return reasonManager == null ? Collections.emptyList() : diff --git a/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/classification/InitialClassificationLevelConstraint.java b/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/classification/InitialClassificationLevelConstraint.java new file mode 100644 index 0000000000..8499cd8e59 --- /dev/null +++ b/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/classification/InitialClassificationLevelConstraint.java @@ -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 . + */ + +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 String instances, but may + * represent non-String values. It is up to the caller to distinguish. + * + * @return the values allowed. + */ + @Override + protected List getAllowedValues() + { + List classificationLevels = classificationSchemeService.getAllClassificationLevels(); + List values = new ArrayList(); + for (ClassificationLevel classificationLevel : classificationLevels) + { + values.add(classificationLevel.getId()); + } + return values; + } +} diff --git a/rm-server/test/java/org/alfresco/module/org_alfresco_module_rm/test/integration/classification/ClassificationConstraintTest.java b/rm-server/test/java/org/alfresco/module/org_alfresco_module_rm/test/integration/classification/ClassificationConstraintTest.java new file mode 100644 index 0000000000..8d6501937a --- /dev/null +++ b/rm-server/test/java/org/alfresco/module/org_alfresco_module_rm/test/integration/classification/ClassificationConstraintTest.java @@ -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 . + */ +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() + { + 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. + *

+ * RM-2502

+     * 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.
+     * 
+ */ + @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() + { + 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() + { + 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() + { + 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() + { + 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 properties = new HashMap(); + 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); + } +}