diff --git a/rm-server/config/alfresco/module/org_alfresco_module_rm/extended-repository-context.xml b/rm-server/config/alfresco/module/org_alfresco_module_rm/extended-repository-context.xml index b284a10522..9a741ca3d0 100644 --- a/rm-server/config/alfresco/module/org_alfresco_module_rm/extended-repository-context.xml +++ b/rm-server/config/alfresco/module/org_alfresco_module_rm/extended-repository-context.xml @@ -3,29 +3,12 @@ - - + + + + + + diff --git a/rm-server/config/alfresco/module/org_alfresco_module_rm/model/recordsModel.xml b/rm-server/config/alfresco/module/org_alfresco_module_rm/model/recordsModel.xml index 62883b7f5c..d3329582f4 100644 --- a/rm-server/config/alfresco/module/org_alfresco_module_rm/model/recordsModel.xml +++ b/rm-server/config/alfresco/module/org_alfresco_module_rm/model/recordsModel.xml @@ -1102,7 +1102,7 @@ true - d:any + d:noderef true diff --git a/rm-server/config/alfresco/module/org_alfresco_module_rm/rm-service-context.xml b/rm-server/config/alfresco/module/org_alfresco_module_rm/rm-service-context.xml index 05a8cecd58..9902ed0040 100644 --- a/rm-server/config/alfresco/module/org_alfresco_module_rm/rm-service-context.xml +++ b/rm-server/config/alfresco/module/org_alfresco_module_rm/rm-service-context.xml @@ -1088,18 +1088,24 @@ @@ -1421,56 +1427,6 @@ - - () @@ -775,10 +773,10 @@ public class RecordServiceImpl extends BaseBehaviourBean // save the information about the originating details Map aspectProperties = new HashMap(3); aspectProperties.put(PROP_RECORD_ORIGINATING_LOCATION, parentAssoc.getParentRef()); - aspectProperties.put(PROP_RECORD_ORIGINATING_USER_ID, userId); + aspectProperties.put(PROP_RECORD_ORIGINATING_USER_ID, owner); aspectProperties.put(PROP_RECORD_ORIGINATING_CREATION_DATE, new Date()); nodeService.addAspect(nodeRef, ASPECT_RECORD_ORIGINATING_DETAILS, aspectProperties); - + // make the document a record makeRecord(nodeRef); @@ -1110,6 +1108,12 @@ public class RecordServiceImpl extends BaseBehaviourBean throw new AlfrescoRuntimeException("Unable to find the creator of document."); } ownableService.setOwner(nodeRef, documentOwner); + + // clear the existing permissions + permissionService.clearPermission(nodeRef, null); + + // restore permission inheritance + permissionService.setInheritParentPermissions(nodeRef, true); // send an email to the record creator notificationHelper.recordRejectedEmailNotification(nodeRef, recordId, documentOwner); diff --git a/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/security/RMMethodSecurityInterceptor.java b/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/security/RMMethodSecurityInterceptor.java index 2108c2bd66..36a301e186 100644 --- a/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/security/RMMethodSecurityInterceptor.java +++ b/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/security/RMMethodSecurityInterceptor.java @@ -1,15 +1,18 @@ package org.alfresco.module.org_alfresco_module_rm.security; +import java.text.MessageFormat; +import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; +import java.util.List; import java.util.Map; import net.sf.acegisecurity.AccessDeniedException; import net.sf.acegisecurity.intercept.InterceptorStatusToken; -import net.sf.acegisecurity.intercept.method.aopalliance.MethodSecurityInterceptor; import net.sf.acegisecurity.vote.AccessDecisionVoter; +import org.alfresco.repo.security.permissions.impl.acegi.MethodSecurityInterceptor; import org.alfresco.service.cmr.security.AccessStatus; import org.aopalliance.intercept.MethodInvocation; import org.apache.commons.logging.Log; @@ -63,7 +66,7 @@ public class RMMethodSecurityInterceptor extends MethodSecurityInterceptor /** * Current capability report details. *

- * Used to getnerate the capability error report. + * Used to generate the capability error report. */ private static final ThreadLocal> CAPABILITIES = new ThreadLocal>() { @@ -74,6 +77,22 @@ public class RMMethodSecurityInterceptor extends MethodSecurityInterceptor }; }; + /** + * Indicates whether this is an RM security check or not + */ + private static final ThreadLocal IS_RM_SECURITY_CHECK = new ThreadLocal() + { + protected Boolean initialValue() {return false;}; + }; + + /** + * Messages to display in error report. + */ + private static final ThreadLocal> MESSAGES = new ThreadLocal>() + { + protected List initialValue() {return new ArrayList();}; + }; + /** * Get capability report object from the thread local, creating one for * the given capability name if one does not already exist. @@ -95,6 +114,41 @@ public class RMMethodSecurityInterceptor extends MethodSecurityInterceptor return capability; } + /** + * Indicates whether this is a RM security check or not + * + * @param newValue true if RM security check, false otherwise + */ + public static void isRMSecurityChecked(boolean newValue) + { + if (logger.isDebugEnabled()) + { + RMMethodSecurityInterceptor.IS_RM_SECURITY_CHECK.set(newValue); + } + } + + /** + * Add a message to be displayed in the error report. + * + * @param message error message + */ + public static void addMessage(String message) + { + if (logger.isDebugEnabled()) + { + List messages = RMMethodSecurityInterceptor.MESSAGES.get(); + messages.add(message); + } + } + + public static void addMessage(String message, Object ... params) + { + if (logger.isDebugEnabled()) + { + addMessage(MessageFormat.format(message, params)); + } + } + /** * Report capability status. * @@ -186,20 +240,73 @@ public class RMMethodSecurityInterceptor extends MethodSecurityInterceptor { // clear the capability report information RMMethodSecurityInterceptor.CAPABILITIES.remove(); + RMMethodSecurityInterceptor.IS_RM_SECURITY_CHECK.remove(); + RMMethodSecurityInterceptor.MESSAGES.remove(); + // before invocation (where method security check takes place) result = super.beforeInvocation(object); } catch (AccessDeniedException exception) { - String failureReport = getFailureReport(); - if (failureReport == null) + if (logger.isDebugEnabled()) { - throw exception; + MethodInvocation mi = (MethodInvocation)object; + + StringBuilder methodDetails = new StringBuilder("\n"); + if (RMMethodSecurityInterceptor.IS_RM_SECURITY_CHECK.get()) + { + methodDetails.append("RM method security check was performed.\n"); + } + else + { + methodDetails.append("Standard DM method security check was performed.\n"); + } + + boolean first = true; + methodDetails.append("Failed on method: ").append(mi.getMethod().getName()).append("("); + for (Object arg : mi.getArguments()) + { + if (first) + { + first = false; + } + else + { + methodDetails.append(", "); + } + + if (arg != null) + { + methodDetails.append(arg.toString()); + } + else + { + methodDetails.append("null"); + } + } + methodDetails.append(")\n"); + + List messages = RMMethodSecurityInterceptor.MESSAGES.get(); + for (String message : messages) + { + methodDetails.append(message).append("\n"); + } + + String failureReport = getFailureReport(); + if (failureReport == null) + { + // rethrow with additional information + throw new AccessDeniedException(exception.getMessage() + methodDetails, exception); + } + else + { + // rethrow with additional information + throw new AccessDeniedException(exception.getMessage() + methodDetails + getFailureReport(), exception); + } } else { - // rethrow with additional information - throw new AccessDeniedException(exception.getMessage() + getFailureReport(), exception); + throw exception; } } return result; diff --git a/rm-server/test/java/org/alfresco/module/org_alfresco_module_rm/test/integration/IntegrationTestSuite.java b/rm-server/test/java/org/alfresco/module/org_alfresco_module_rm/test/integration/IntegrationTestSuite.java index 4d4ebc637e..f697408229 100755 --- a/rm-server/test/java/org/alfresco/module/org_alfresco_module_rm/test/integration/IntegrationTestSuite.java +++ b/rm-server/test/java/org/alfresco/module/org_alfresco_module_rm/test/integration/IntegrationTestSuite.java @@ -22,6 +22,7 @@ import org.alfresco.module.org_alfresco_module_rm.test.integration.disposition.D import org.alfresco.module.org_alfresco_module_rm.test.integration.dod.DoD5015TestSuite; import org.alfresco.module.org_alfresco_module_rm.test.integration.event.EventTestSuite; import org.alfresco.module.org_alfresco_module_rm.test.integration.issue.IssueTestSuite; +import org.alfresco.module.org_alfresco_module_rm.test.integration.record.RejectRecordTest; import org.alfresco.module.org_alfresco_module_rm.test.integration.report.ReportTestSuite; import org.junit.runner.RunWith; import org.junit.runners.Suite; @@ -41,7 +42,8 @@ import org.junit.runners.Suite.SuiteClasses; IssueTestSuite.class, EventTestSuite.class, ReportTestSuite.class, - DispositionTestSuite.class + DispositionTestSuite.class, + RejectRecordTest.class }) public class IntegrationTestSuite { diff --git a/rm-server/test/java/org/alfresco/module/org_alfresco_module_rm/test/integration/record/RecordTestSuite.java b/rm-server/test/java/org/alfresco/module/org_alfresco_module_rm/test/integration/record/RecordTestSuite.java new file mode 100644 index 0000000000..064882b873 --- /dev/null +++ b/rm-server/test/java/org/alfresco/module/org_alfresco_module_rm/test/integration/record/RecordTestSuite.java @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2005-2014 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ +package org.alfresco.module.org_alfresco_module_rm.test.integration.record; + +import org.junit.runner.RunWith; +import org.junit.runners.Suite; +import org.junit.runners.Suite.SuiteClasses; + +/** + * Record integration test suite + * + * @author Roy Wetherall + * @since 2.2 + */ +@RunWith(Suite.class) +@SuiteClasses( +{ + RejectRecordTest.class +}) +public class RecordTestSuite +{ +} diff --git a/rm-server/test/java/org/alfresco/module/org_alfresco_module_rm/test/integration/record/RejectRecordTest.java b/rm-server/test/java/org/alfresco/module/org_alfresco_module_rm/test/integration/record/RejectRecordTest.java new file mode 100644 index 0000000000..3bee784152 --- /dev/null +++ b/rm-server/test/java/org/alfresco/module/org_alfresco_module_rm/test/integration/record/RejectRecordTest.java @@ -0,0 +1,192 @@ +/* + * Copyright (C) 2005-2014 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ +package org.alfresco.module.org_alfresco_module_rm.test.integration.record; + +import org.alfresco.model.ContentModel; +import org.alfresco.module.org_alfresco_module_rm.test.util.BaseRMTestCase; +import org.alfresco.repo.security.authentication.AuthenticationUtil; +import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork; +import org.alfresco.service.cmr.repository.ContentWriter; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.version.Version; +import org.alfresco.service.cmr.version.VersionHistory; +import org.alfresco.service.cmr.version.VersionService; +import org.springframework.extensions.webscripts.GUID; + +/** + * reject record tests. + * + * @author Roy Wetherall + * @since 2.2 + */ +public class RejectRecordTest extends BaseRMTestCase +{ + private VersionService versionService; + + private static final String REASON = GUID.generate(); + + @Override + protected boolean isUserTest() + { + return true; + } + + @Override + protected boolean isCollaborationSiteTest() + { + return true; + } + + @Override + protected void initServices() + { + super.initServices(); + + versionService = (VersionService)applicationContext.getBean("VersionService"); + } + + /** + * + */ + public void testRejectedRecordInCorrectState() + { + doBehaviourDrivenTest(new BehaviourDrivenTest() + { + public void given() + { + assertFalse(recordService.isRecord(dmDocument)); + ownableService.setOwner(dmDocument, userName); + + // document is declared as a record by user + AuthenticationUtil.runAs(new RunAsWork() + { + public Void doWork() throws Exception + { + // declare record + recordService.createRecord(filePlan, dmDocument); + return null; + } + }, userName); + } + + public void when() + { + // sanity checks + assertTrue(recordService.isRecord(dmDocument)); + assertFalse(permissionService.getInheritParentPermissions(dmDocument)); + + // declare record + recordService.rejectRecord(dmDocument, REASON); + } + + public void then() + { + // document is no longer a record + assertFalse(recordService.isRecord(dmDocument)); + + // expected owner has be re-set + assertEquals(userName, ownableService.getOwner(dmDocument)); + assertTrue(permissionService.getInheritParentPermissions(dmDocument)); + assertFalse(nodeService.hasAspect(dmDocument, ASPECT_FILE_PLAN_COMPONENT)); + } + }); + } + + /** + * + */ + public void testRevertAfterReject() + { + doBehaviourDrivenTest(new BehaviourDrivenTest() + {; + private NodeRef document; + + public void given() + { + NodeRef folder = fileFolderService.create(documentLibrary, GUID.generate(), TYPE_FOLDER).getNodeRef(); + document = fileFolderService.create(folder, GUID.generate(), TYPE_CONTENT).getNodeRef(); + + assertFalse(recordService.isRecord(document)); + ownableService.setOwner(document, userName); + versionService.ensureVersioningEnabled(document, null); + + // document is declared as a record by user + AuthenticationUtil.runAs(new RunAsWork() + { + public Void doWork() throws Exception + { + // declare record + recordService.createRecord(filePlan, document); + return null; + } + }, userName); + + assertTrue(nodeService.hasAspect(document, ASPECT_FILE_PLAN_COMPONENT)); + } + + public void when() + { + // reject the record + recordService.rejectRecord(document, REASON); + assertFalse(nodeService.hasAspect(document, ASPECT_FILE_PLAN_COMPONENT)); + + // upload a new version of the document + AuthenticationUtil.runAs(new RunAsWork() + { + public Void doWork() throws Exception + { + ContentWriter writer = contentService.getWriter(document, ContentModel.PROP_CONTENT, true); + writer.putContent("This is a change to the content and should force a new version"); + versionService.createVersion(document, null); + + return null; + } + }, userName); + + assertFalse(nodeService.hasAspect(document, ASPECT_FILE_PLAN_COMPONENT)); + + VersionHistory history = versionService.getVersionHistory(document); + assertEquals(2, history.getAllVersions().size()); + final Version initial = history.getRootVersion(); + + assertFalse(nodeService.hasAspect(initial.getFrozenStateNodeRef(), ASPECT_FILE_PLAN_COMPONENT)); + + AuthenticationUtil.runAs(new RunAsWork() + { + public Void doWork() throws Exception + { + // revert the document to a previous version + versionService.revert(document, initial); + + return null; + } + }, userName); + } + + public void then() + { + // document is no longer a record + assertFalse(recordService.isRecord(document)); + + // expected owner has be re-set + assertEquals(userName, ownableService.getOwner(document)); + } + }); + } +} diff --git a/rm-server/test/java/org/alfresco/module/org_alfresco_module_rm/test/util/BaseRMTestCase.java b/rm-server/test/java/org/alfresco/module/org_alfresco_module_rm/test/util/BaseRMTestCase.java index 5f5df891f1..f5a366eba4 100644 --- a/rm-server/test/java/org/alfresco/module/org_alfresco_module_rm/test/util/BaseRMTestCase.java +++ b/rm-server/test/java/org/alfresco/module/org_alfresco_module_rm/test/util/BaseRMTestCase.java @@ -66,6 +66,7 @@ import org.alfresco.service.cmr.repository.StoreRef; import org.alfresco.service.cmr.search.SearchService; import org.alfresco.service.cmr.security.AuthorityService; import org.alfresco.service.cmr.security.MutableAuthenticationService; +import org.alfresco.service.cmr.security.OwnableService; import org.alfresco.service.cmr.security.PermissionService; import org.alfresco.service.cmr.security.PersonService; import org.alfresco.service.cmr.site.SiteInfo; @@ -126,6 +127,7 @@ public abstract class BaseRMTestCase extends RetryingTransactionHelperTestCase protected PermissionService permissionService; protected TaggingService taggingService; protected ActionService actionService; + protected OwnableService ownableService; /** RM Services */ protected DispositionService dispositionService; @@ -376,6 +378,7 @@ public abstract class BaseRMTestCase extends RetryingTransactionHelperTestCase permissionService = (PermissionService)applicationContext.getBean("PermissionService"); taggingService = (TaggingService)applicationContext.getBean("TaggingService"); actionService = (ActionService)applicationContext.getBean("ActionService"); + ownableService = (OwnableService)applicationContext.getBean("OwnableService"); // Get RM services dispositionService = (DispositionService)applicationContext.getBean("DispositionService"); @@ -772,13 +775,13 @@ public abstract class BaseRMTestCase extends RetryingTransactionHelperTestCase @Override protected A doTestInTransaction(Test test) { - return super.doTestInTransaction(test, rmAdminUserName); + return super.doTestInTransaction(test, AuthenticationUtil.getAdminUserName()); } @Override protected void doTestInTransaction(FailureTest test) { - super.doTestInTransaction(test, rmAdminUserName); + super.doTestInTransaction(test, AuthenticationUtil.getAdminUserName()); } /**