From f9ef2cd5856905388e7efaf917ad72b819f6cc19 Mon Sep 17 00:00:00 2001 From: Roy Wetherall Date: Fri, 29 Jul 2016 13:04:23 +1000 Subject: [PATCH 01/11] RM-3074: Initial refactoring of the extended security service implementation RM-3074: Added root group and zones * added root inplace group, created on start-up if not already there * all groups created in RM zone * fixed up file plan permission unit test and removed ignore from test suite * removed delimeters from groups names to keep length to a min RM-3074: Find groups and handle clashes * refactor how existing groups are found * name with index * handle hash clahses RM-3074: Add exact group match * ensure that hash match is backed up with exact match * fill out JDoc RM-3074: unit testing of extended security service impl RM-3074: groups assigned to RM roles, not authorities * IPR groups are now assigned to RM roles rather than the individual authorities * more unit tests RM-3074: Remove unnecessary files RM-3074: review comments RM-3074: Refactor so read and write groups are reused independantly based on review comments RM-3074: Inplace move no longer needs to modify extended security * inplace move no longer needs to store and reset extended security * no need to clear extended security when assoc is removed * a couple of extra checks to inplace move integration test RM-3074: place common logic to get readers and writers of a node in one place * added getReadersAndWriters method to extended permission service * refactored calling services, consolidating code into one location * extended unit test Note: getFullAuthenticatedUser has been removed from writers list as it makes no logical sense since you must be a writer in order to perform these operations in the first place. RM-3074: Rename set/remove methods on extended security interface * create deprecated service interface to tidy things up * create set method and deprecate exisiting * crate remove method and deprecate exisiting * remove deprecation warnings in code RM-3074: missing file Change in project structure RM-3074: Rename getReader and getWriter methods RM-3074: Unit test for records with renditions RM-3074: Integration Tests, including BDT test helpers to simplify code RM-3074: Integration tests * including fix for ghosted records being visible in collab sites Add completeEvent method RM-3074: Review comments RM-3074: Contributor didn't get write in-place access as expected * owner derived from cm:creator was not added to writer list * fully authenticated user in base test was being set incorrectly * transaction executed outside authentication in BDT classes RM-3074: missing file RM-3074: Fixup community integration tests RM-3074: Fixed failing test RM-3074: Test group reuse RM-3074: Deleted creators cause failures when declaring records RM-3074: Fix unit tests RM-3074: Remove extended security when a record is copied Add AlfMock to help backport unit tests RM-3074: Compile tests with 1.8 RM-3074: Fix integration test RM-3074: Fix integration tests --- pom.xml | 14 + .../extended-repository-context.xml | 1 + .../rm-service-context.xml | 5 +- .../rm-version-context.xml | 1 - .../DeprecatedExtendedSecurityService.java | 148 +++ .../ExtendedReaderDynamicAuthority.java | 22 +- .../ExtendedSecurityBaseDynamicAuthority.java | 22 +- .../ExtendedWriterDynamicAuthority.java | 22 +- .../FilePlanAuthenticationService.java | 22 +- .../FilePlanAuthenticationServiceImpl.java | 22 +- .../action/impl/DeclareRecordAction.java | 3 + .../fileplan/FilePlanServiceImpl.java | 26 +- .../model/RecordsManagementModel.java | 7 +- .../model/rma/aspect/RecordAspect.java | 39 +- .../patch/v21/RMv21InPlacePatch.java | 7 - .../record/InplaceRecordServiceImpl.java | 34 +- .../record/RecordServiceImpl.java | 69 +- .../role/FilePlanRoleServiceImpl.java | 4 - .../security/ExtendedSecurityService.java | 118 +-- .../security/ExtendedSecurityServiceImpl.java | 785 +++++++++------ .../FilePlanPermissionServiceImpl.java | 47 +- .../version/RecordableVersionServiceImpl.java | 31 +- .../impl/ExtendedPermissionService.java | 16 + .../impl/RMPermissionServiceImpl.java | 39 +- .../test/integration/issue/RM1429Test.java | 12 +- .../test/integration/issue/RM1463Test.java | 12 +- .../test/integration/issue/RM1464Test.java | 12 +- .../record/CreateInplaceRecordTest.java | 188 ++++ .../record/InplaceRecordPermissionTest.java | 951 ++++++++++++++++++ .../record/MoveInplaceRecordTest.java | 31 +- .../legacy/action/FileReportActionTest.java | 3 + .../test/legacy/action/FileToActionTest.java | 28 +- .../test/legacy/action/RejectActionTest.java | 3 +- .../ExtendedSecurityServiceImplTest.java | 142 +-- .../FilePlanPermissionServiceImplTest.java | 6 - .../legacy/service/RecordServiceImplTest.java | 80 +- .../test/util/BaseRMTestCase.java | 60 +- .../test/util/CommonRMTestUtils.java | 17 + .../test/util/bdt/BehaviourTest.java | 276 +++++ .../test/util/bdt/ExpectedFailure.java | 92 ++ .../test/util/bdt/ExpectedValue.java | 86 ++ .../ExtendedSecurityServiceImplUnitTest.java | 928 +++++++++++++++++ ...FilePlanPermissionServiceImplUnitTest.java | 126 +-- .../test/util/AlfMock.java | 130 +++ 44 files changed, 3813 insertions(+), 874 deletions(-) create mode 100644 rm-server/source/compatibility/org/alfresco/module/org_alfresco_module_rm/security/DeprecatedExtendedSecurityService.java rename rm-server/source/{java => compatibility}/org/alfresco/module/org_alfresco_module_rm/security/ExtendedReaderDynamicAuthority.java (84%) rename rm-server/source/{java => compatibility}/org/alfresco/module/org_alfresco_module_rm/security/ExtendedSecurityBaseDynamicAuthority.java (90%) rename rm-server/source/{java => compatibility}/org/alfresco/module/org_alfresco_module_rm/security/ExtendedWriterDynamicAuthority.java (85%) rename rm-server/source/{java => compatibility}/org/alfresco/module/org_alfresco_module_rm/security/FilePlanAuthenticationService.java (75%) rename rm-server/source/{java => compatibility}/org/alfresco/module/org_alfresco_module_rm/security/FilePlanAuthenticationServiceImpl.java (79%) create mode 100644 rm-server/test/java/org/alfresco/module/org_alfresco_module_rm/test/integration/record/CreateInplaceRecordTest.java create mode 100644 rm-server/test/java/org/alfresco/module/org_alfresco_module_rm/test/integration/record/InplaceRecordPermissionTest.java create mode 100644 rm-server/test/java/org/alfresco/module/org_alfresco_module_rm/test/util/bdt/BehaviourTest.java create mode 100644 rm-server/test/java/org/alfresco/module/org_alfresco_module_rm/test/util/bdt/ExpectedFailure.java create mode 100644 rm-server/test/java/org/alfresco/module/org_alfresco_module_rm/test/util/bdt/ExpectedValue.java create mode 100644 rm-server/unit-test/java/org/alfresco/module/org_alfresco_module_rm/security/ExtendedSecurityServiceImplUnitTest.java create mode 100644 rm-server/unit-test/java/org/alfresco/module/org_alfresco_module_rm/test/util/AlfMock.java diff --git a/pom.xml b/pom.xml index 06cc4d693c..2487228b3c 100644 --- a/pom.xml +++ b/pom.xml @@ -78,6 +78,7 @@ false 1.7 + 1.8 UTF-8 -Xmx1024m -XX:MaxPermSize=256m -Duser.language=en -Dcom.sun.management.jmxremote @@ -148,6 +149,19 @@ ${maven.build.sourceVersion} ${maven.build.sourceVersion} + + + default-testCompile + process-test-sources + + testCompile + + + ${maven.build.testSourceVersion} + ${maven.build.testSourceVersion} + + + maven-deploy-plugin 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 6f589e52b4..7e0ed6021b 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 @@ -58,6 +58,7 @@ org.alfresco.service.cmr.security.PermissionService.hasPermission=ACL_ALLOW org.alfresco.service.cmr.security.PermissionService.getReaders=ACL_METHOD.ROLE_ADMINISTRATOR org.alfresco.repo.security.permissions.impl.ExtendedPermissionService.getWriters=ACL_METHOD.ROLE_ADMINISTRATOR + org.alfresco.repo.security.permissions.impl.ExtendedPermissionService.getReadersAndWriters=ACL_METHOD.ROLE_ADMINISTRATOR org.alfresco.service.cmr.security.PermissionService.deletePermissions=ACL_NODE.0.sys:base.ChangePermissions org.alfresco.service.cmr.security.PermissionService.deletePermission=ACL_NODE.0.sys:base.ChangePermissions org.alfresco.service.cmr.security.PermissionService.setPermission=ACL_NODE.0.sys:base.ChangePermissions 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 d42f1b3bda..6afcedfb33 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 @@ -569,7 +569,10 @@ parent="baseService"> - + + + + diff --git a/rm-server/config/alfresco/module/org_alfresco_module_rm/rm-version-context.xml b/rm-server/config/alfresco/module/org_alfresco_module_rm/rm-version-context.xml index ce01a26573..d8ad239912 100644 --- a/rm-server/config/alfresco/module/org_alfresco_module_rm/rm-version-context.xml +++ b/rm-server/config/alfresco/module/org_alfresco_module_rm/rm-version-context.xml @@ -22,7 +22,6 @@ - diff --git a/rm-server/source/compatibility/org/alfresco/module/org_alfresco_module_rm/security/DeprecatedExtendedSecurityService.java b/rm-server/source/compatibility/org/alfresco/module/org_alfresco_module_rm/security/DeprecatedExtendedSecurityService.java new file mode 100644 index 0000000000..b844594bf0 --- /dev/null +++ b/rm-server/source/compatibility/org/alfresco/module/org_alfresco_module_rm/security/DeprecatedExtendedSecurityService.java @@ -0,0 +1,148 @@ +/* + * #%L + * Alfresco Records Management Module + * %% + * Copyright (C) 2005 - 2016 Alfresco Software Limited + * %% + * This file is part of the Alfresco software. + * - + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * - + * 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 . + * #L% + */ + +package org.alfresco.module.org_alfresco_module_rm.security; + +import java.util.Set; + +import org.alfresco.service.cmr.repository.NodeRef; + +/** + * Deprecated extended security service for compatibility. + * + * @author Roy Wetherall + */ +public interface DeprecatedExtendedSecurityService +{ + /** + * Gets the set of authorities that are extended readers for the given node. + * + * @param nodeRef node reference + * @return {@link Set}<{@link String}> set of extended readers + * + * @deprecated as of 2.5, use {@link ExtendedSecurityService#getReaders(NodeRef)} + */ + Set getExtendedReaders(NodeRef nodeRef); + + /** + * Get the set of authorities that are extended writers for the given node. + * + * @param nodeRef node reference + * @return {@link Set}<{@link String}> set of extended writers + * + * @deprecated as of 2.5, use {@link ExtendedSecurityService#getWriters(NodeRef)} + */ + Set getExtendedWriters(NodeRef nodeRef); + + /** + * Add extended security for the specified authorities to a node. + * + * As of, 2.5 this method no longer applies the extended security to parents. + * + * @param nodeRef node reference + * @param readers set of authorities to add extended read permissions + * @param writers set of authorities to add extended write permissions + * + * @deprecated as of 2.5, use {@link ExtendedSecurityService#set(NodeRef, Set, Set)} + */ + @Deprecated + void addExtendedSecurity(NodeRef nodeRef, Set readers, Set writers); + + /** + * Add extended security for the specified authorities to a node. + *

+ * If specified, the read and write extended permissions are applied to all parents up to the file plan as + * extended read. This ensures parental read, but not parental write. + * + * @param nodeRef node reference + * @param readers set of authorities to add extended read permissions + * @param writers set of authorities to add extended write permissions + * @param applyToParents true if extended security applied to parents (read only) false otherwise. + * + * @deprecated as of 2.5, because extended security is no longer applied to parents. Note that calling this method will + * only apply the extended security to the node and the applyToParents parameter value will be ignored. + * + * @see ExtendedSecurityService#set(NodeRef, Set, Set) + */ + @Deprecated void addExtendedSecurity(NodeRef nodeRef, Set readers, Set writers, boolean applyToParents); + + /** + * Remove all extended readers and writers from the given node reference. + * + * @param nodeRef node reference + * + * @deprecated as of 2.5, see {@link ExtendedSecurityService#remove(NodeRef)} + */ + @Deprecated void removeAllExtendedSecurity(NodeRef nodeRef); + + /** + * Remove the extended security for the specified authorities from a node. + * + * @param nodeRef node reference + * @param readers set of authorities to remove as extended readers + * @param writers set of authorities to remove as extended writers + * + * @deprecated as of 2.5, because partial removal of readers and writers from node or parents is no longer supported. + * Note that calling this method will now remove all extended security from the node and never applied to parents. + * + * @see {@link ExtendedSecurityService#remove(NodeRef)} + */ + @Deprecated void removeExtendedSecurity(NodeRef nodeRef, Set readers, Set writers); + + /** + * Remove the extended security for the specified authorities from a node. + *

+ * If specified, extended security will also be removed from the parent hierarchy.(read only). Note that + * extended security is records as a reference count, so security will only be utterly removed from the parent + * hierarchy if all references to the authority are removed. + * + * @param nodeRef node reference + * @param readers set of authorities to remove as extended readers + * @param writers set of authorities to remove as extedned writers + * @param applyToParents true if removal of extended security is applied to parent hierarchy (read only), false + * otherwise + * + * @deprecated as of 2.5, because partial removal of readers and writers from node or parents is no longer supported. + * Note that calling this method will now remove all extended security from the node and never applied to parents. + * + * @see {@link ExtendedSecurityService#remove(NodeRef)} + */ + @Deprecated void removeExtendedSecurity(NodeRef nodeRef, Set readers, Set writers, boolean applyToParents); + + /** + * Remove all extended readers and writers from the given node reference. + * + * @param nodeRef node reference + * @param applyToParents if true then apply removal to parent hierarchy (read only) false otherwise. + * + * @deprecated as of 2.5, because partial removal of readers and writers from node or parents is no longer supported. + * Note that calling this method will now remove all extended security from the node and never applied to parents. + * + * @see {@link ExtendedSecurityService#remove(NodeRef)} + */ + @Deprecated void removeAllExtendedSecurity(NodeRef nodeRef, boolean applyToParents); +} diff --git a/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/security/ExtendedReaderDynamicAuthority.java b/rm-server/source/compatibility/org/alfresco/module/org_alfresco_module_rm/security/ExtendedReaderDynamicAuthority.java similarity index 84% rename from rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/security/ExtendedReaderDynamicAuthority.java rename to rm-server/source/compatibility/org/alfresco/module/org_alfresco_module_rm/security/ExtendedReaderDynamicAuthority.java index 443414e66d..27b6c97292 100644 --- a/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/security/ExtendedReaderDynamicAuthority.java +++ b/rm-server/source/compatibility/org/alfresco/module/org_alfresco_module_rm/security/ExtendedReaderDynamicAuthority.java @@ -1,21 +1,30 @@ /* - * Copyright (C) 2005-2014 Alfresco Software Limited. - * - * This file is part of Alfresco - * + * #%L + * Alfresco Records Management Module + * %% + * Copyright (C) 2005 - 2016 Alfresco Software Limited + * %% + * This file is part of the Alfresco software. + * - + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * - * 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 . + * #L% */ + package org.alfresco.module.org_alfresco_module_rm.security; import java.util.Collections; @@ -32,6 +41,7 @@ import org.alfresco.service.cmr.repository.NodeRef; * @author Roy Wetherall * @since 2.1 */ +@Deprecated public class ExtendedReaderDynamicAuthority extends ExtendedSecurityBaseDynamicAuthority { /** Extended reader role */ diff --git a/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/security/ExtendedSecurityBaseDynamicAuthority.java b/rm-server/source/compatibility/org/alfresco/module/org_alfresco_module_rm/security/ExtendedSecurityBaseDynamicAuthority.java similarity index 90% rename from rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/security/ExtendedSecurityBaseDynamicAuthority.java rename to rm-server/source/compatibility/org/alfresco/module/org_alfresco_module_rm/security/ExtendedSecurityBaseDynamicAuthority.java index 8f3cb2f44d..c6b676a230 100644 --- a/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/security/ExtendedSecurityBaseDynamicAuthority.java +++ b/rm-server/source/compatibility/org/alfresco/module/org_alfresco_module_rm/security/ExtendedSecurityBaseDynamicAuthority.java @@ -1,21 +1,30 @@ /* - * Copyright (C) 2005-2014 Alfresco Software Limited. - * - * This file is part of Alfresco - * + * #%L + * Alfresco Records Management Module + * %% + * Copyright (C) 2005 - 2016 Alfresco Software Limited + * %% + * This file is part of the Alfresco software. + * - + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * - * 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 . + * #L% */ + package org.alfresco.module.org_alfresco_module_rm.security; import java.util.Map; @@ -39,6 +48,7 @@ import org.springframework.context.ApplicationContextAware; * @author Roy Wetherall * @since 2.1 */ +@Deprecated public abstract class ExtendedSecurityBaseDynamicAuthority implements DynamicAuthority, RecordsManagementModel, ApplicationContextAware diff --git a/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/security/ExtendedWriterDynamicAuthority.java b/rm-server/source/compatibility/org/alfresco/module/org_alfresco_module_rm/security/ExtendedWriterDynamicAuthority.java similarity index 85% rename from rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/security/ExtendedWriterDynamicAuthority.java rename to rm-server/source/compatibility/org/alfresco/module/org_alfresco_module_rm/security/ExtendedWriterDynamicAuthority.java index c27faa6edf..787e3c7eb8 100644 --- a/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/security/ExtendedWriterDynamicAuthority.java +++ b/rm-server/source/compatibility/org/alfresco/module/org_alfresco_module_rm/security/ExtendedWriterDynamicAuthority.java @@ -1,21 +1,30 @@ /* - * Copyright (C) 2005-2014 Alfresco Software Limited. - * - * This file is part of Alfresco - * + * #%L + * Alfresco Records Management Module + * %% + * Copyright (C) 2005 - 2016 Alfresco Software Limited + * %% + * This file is part of the Alfresco software. + * - + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * - * 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 . + * #L% */ + package org.alfresco.module.org_alfresco_module_rm.security; import java.util.Collections; @@ -33,6 +42,7 @@ import org.alfresco.service.cmr.repository.NodeRef; * @author Roy Wetherall * @since 2.1 */ +@Deprecated public class ExtendedWriterDynamicAuthority extends ExtendedSecurityBaseDynamicAuthority { /** Extended writer role */ diff --git a/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/security/FilePlanAuthenticationService.java b/rm-server/source/compatibility/org/alfresco/module/org_alfresco_module_rm/security/FilePlanAuthenticationService.java similarity index 75% rename from rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/security/FilePlanAuthenticationService.java rename to rm-server/source/compatibility/org/alfresco/module/org_alfresco_module_rm/security/FilePlanAuthenticationService.java index 72bcc8adaa..af9f9f22ed 100644 --- a/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/security/FilePlanAuthenticationService.java +++ b/rm-server/source/compatibility/org/alfresco/module/org_alfresco_module_rm/security/FilePlanAuthenticationService.java @@ -1,21 +1,30 @@ /* - * Copyright (C) 2005-2014 Alfresco Software Limited. - * - * This file is part of Alfresco - * + * #%L + * Alfresco Records Management Module + * %% + * Copyright (C) 2005 - 2016 Alfresco Software Limited + * %% + * This file is part of the Alfresco software. + * - + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * - * 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 . + * #L% */ + package org.alfresco.module.org_alfresco_module_rm.security; import org.alfresco.repo.security.authentication.AuthenticationUtil; @@ -26,6 +35,7 @@ import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork; * * @author Roy Wetherall * @since 2.1 + * @deprecated as of 2.2, use {@link AuthenticationUtil}. */ public interface FilePlanAuthenticationService { diff --git a/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/security/FilePlanAuthenticationServiceImpl.java b/rm-server/source/compatibility/org/alfresco/module/org_alfresco_module_rm/security/FilePlanAuthenticationServiceImpl.java similarity index 79% rename from rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/security/FilePlanAuthenticationServiceImpl.java rename to rm-server/source/compatibility/org/alfresco/module/org_alfresco_module_rm/security/FilePlanAuthenticationServiceImpl.java index 76f036022e..8055fa5301 100644 --- a/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/security/FilePlanAuthenticationServiceImpl.java +++ b/rm-server/source/compatibility/org/alfresco/module/org_alfresco_module_rm/security/FilePlanAuthenticationServiceImpl.java @@ -1,21 +1,30 @@ /* - * Copyright (C) 2005-2014 Alfresco Software Limited. - * - * This file is part of Alfresco - * + * #%L + * Alfresco Records Management Module + * %% + * Copyright (C) 2005 - 2016 Alfresco Software Limited + * %% + * This file is part of the Alfresco software. + * - + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * - * 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 . + * #L% */ + package org.alfresco.module.org_alfresco_module_rm.security; import org.alfresco.repo.security.authentication.AuthenticationUtil; @@ -25,6 +34,7 @@ import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork; * @author Roy Wetherall * @since 2.1 */ +@Deprecated public class FilePlanAuthenticationServiceImpl implements FilePlanAuthenticationService { /** Default rm admin user values */ diff --git a/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/action/impl/DeclareRecordAction.java b/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/action/impl/DeclareRecordAction.java index 1d946695fb..d9565bc074 100644 --- a/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/action/impl/DeclareRecordAction.java +++ b/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/action/impl/DeclareRecordAction.java @@ -48,6 +48,9 @@ import org.springframework.extensions.surf.util.I18NUtil; */ public class DeclareRecordAction extends RMActionExecuterAbstractBase { + /** action name */ + public static final String NAME = "declareRecord"; + /** I18N */ private static final String MSG_UNDECLARED_ONLY_RECORDS = "rm.action.undeclared-only-records"; private static final String MSG_NO_DECLARE_MAND_PROP = "rm.action.no-declare-mand-prop"; diff --git a/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/fileplan/FilePlanServiceImpl.java b/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/fileplan/FilePlanServiceImpl.java index 111212b7a4..7bc57fb327 100644 --- a/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/fileplan/FilePlanServiceImpl.java +++ b/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/fileplan/FilePlanServiceImpl.java @@ -33,11 +33,10 @@ import org.alfresco.model.ContentModel; import org.alfresco.module.org_alfresco_module_rm.capability.RMPermissionModel; import org.alfresco.module.org_alfresco_module_rm.model.RecordsManagementModel; import org.alfresco.module.org_alfresco_module_rm.role.FilePlanRoleService; -import org.alfresco.module.org_alfresco_module_rm.security.ExtendedReaderDynamicAuthority; -import org.alfresco.module.org_alfresco_module_rm.security.ExtendedWriterDynamicAuthority; import org.alfresco.module.org_alfresco_module_rm.util.ServiceBaseImpl; import org.alfresco.repo.cache.SimpleCache; import org.alfresco.repo.domain.node.NodeDAO; +import org.alfresco.repo.rule.RuleModel; import org.alfresco.service.cmr.repository.ChildAssociationRef; import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.repository.StoreRef; @@ -347,23 +346,12 @@ public class FilePlanServiceImpl extends ServiceBaseImpl containerType, properties).getChildRef(); - // if (!inheritPermissions) - // { - // set inheritance to false - getPermissionService().setInheritParentPermissions(container, false); - getPermissionService().setPermission(container, allRoles, RMPermissionModel.READ_RECORDS, true); - getPermissionService().setPermission(container, ExtendedReaderDynamicAuthority.EXTENDED_READER, RMPermissionModel.READ_RECORDS, true); - getPermissionService().setPermission(container, ExtendedWriterDynamicAuthority.EXTENDED_WRITER, RMPermissionModel.FILING, true); - - // TODO set the admin users to have filing permissions on the unfiled container!!! - // TODO we will need to be able to get a list of the admin roles from the service - // } - // else - // { - // just inherit eveything - // TODO will change this when we are able to set permissions on holds and transfers! - // getPermissionService().setInheritParentPermissions(container, true); - // } + // set inheritance to false + getPermissionService().setInheritParentPermissions(container, false); + getPermissionService().setPermission(container, allRoles, RMPermissionModel.READ_RECORDS, true); + + // prevent inheritance of rules + nodeService.addAspect(container, RuleModel.ASPECT_IGNORE_INHERITED_RULES, null); return container; } diff --git a/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/model/RecordsManagementModel.java b/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/model/RecordsManagementModel.java index 481afd29cd..2736a86f08 100644 --- a/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/model/RecordsManagementModel.java +++ b/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/model/RecordsManagementModel.java @@ -242,9 +242,10 @@ public interface RecordsManagementModel extends RecordsManagementCustomModel QName PROP_LOADED_DATA_SET_IDS = QName.createQName(RM_URI, "loadedDataSetIds"); // Extended security aspect - QName ASPECT_EXTENDED_SECURITY = QName.createQName(RM_URI, "extendedSecurity"); - QName PROP_READERS = QName.createQName(RM_URI, "readers"); - QName PROP_WRITERS = QName.createQName(RM_URI, "writers"); + // @deprecated as of 2.5, because of performance issues + @Deprecated QName ASPECT_EXTENDED_SECURITY = QName.createQName(RM_URI, "extendedSecurity"); + @Deprecated QName PROP_READERS = QName.createQName(RM_URI, "readers"); + @Deprecated QName PROP_WRITERS = QName.createQName(RM_URI, "writers"); // Originating details of a record QName ASPECT_RECORD_ORIGINATING_DETAILS = QName.createQName(RM_URI, "recordOriginatingDetails"); diff --git a/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/model/rma/aspect/RecordAspect.java b/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/model/rma/aspect/RecordAspect.java index 8f8f8019c9..ece8737a67 100644 --- a/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/model/rma/aspect/RecordAspect.java +++ b/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/model/rma/aspect/RecordAspect.java @@ -30,6 +30,7 @@ import org.alfresco.module.org_alfresco_module_rm.record.RecordService; import org.alfresco.module.org_alfresco_module_rm.security.ExtendedSecurityService; import org.alfresco.repo.copy.CopyBehaviourCallback; import org.alfresco.repo.copy.CopyDetails; +import org.alfresco.repo.copy.CopyServicePolicies; import org.alfresco.repo.copy.DefaultCopyBehaviourCallback; import org.alfresco.repo.node.NodeServicePolicies; import org.alfresco.repo.policy.Behaviour.NotificationFrequency; @@ -57,7 +58,8 @@ public class RecordAspect extends AbstractDisposableItem implements NodeServicePolicies.OnCreateChildAssociationPolicy, RecordsManagementPolicies.OnCreateReference, RecordsManagementPolicies.OnRemoveReference, - NodeServicePolicies.OnMoveNodePolicy + NodeServicePolicies.OnMoveNodePolicy, + CopyServicePolicies.OnCopyCompletePolicy { /** Well-known location of the scripts folder. */ // TODO make configurable @@ -124,11 +126,11 @@ public class RecordAspect extends AbstractDisposableItem // manage any extended readers NodeRef parent = childAssocRef.getParentRef(); - Set readers = extendedSecurityService.getExtendedReaders(parent); - Set writers = extendedSecurityService.getExtendedWriters(parent); + Set readers = extendedSecurityService.getReaders(parent); + Set writers = extendedSecurityService.getWriters(parent); if (readers != null && readers.size() != 0) { - extendedSecurityService.addExtendedSecurity(thumbnail, readers, writers, false); + extendedSecurityService.set(thumbnail, readers, writers); } } @@ -288,4 +290,33 @@ public class RecordAspect extends AbstractDisposableItem scriptService.executeScript(scriptNodeRef, null, objectModel); } } + + /** + * On copy complete behaviour for record aspect. + * + * @param classRef + * @param sourceNodeRef + * @param targetNodeRef + * @param copyToNewNode + * @param copyMap + */ + @Override + @Behaviour + ( + kind = BehaviourKind.CLASS + ) + public void onCopyComplete(QName classRef, + NodeRef sourceNodeRef, + NodeRef targetNodeRef, + boolean copyToNewNode, + Map copyMap) + { + // given the node exists and is a record + if (nodeService.exists(targetNodeRef) && + nodeService.hasAspect(targetNodeRef, ASPECT_RECORD)) + { + // then remove any extended security from the newly copied record + extendedSecurityService.remove(targetNodeRef); + } + } } diff --git a/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/patch/v21/RMv21InPlacePatch.java b/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/patch/v21/RMv21InPlacePatch.java index 5ec24cdee3..d3a02070cb 100644 --- a/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/patch/v21/RMv21InPlacePatch.java +++ b/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/patch/v21/RMv21InPlacePatch.java @@ -25,13 +25,10 @@ import java.util.Set; import org.alfresco.model.ContentModel; import org.alfresco.module.org_alfresco_module_rm.capability.Capability; 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.dod5015.DOD5015Model; 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.role.FilePlanRoleService; -import org.alfresco.module.org_alfresco_module_rm.security.ExtendedReaderDynamicAuthority; -import org.alfresco.module.org_alfresco_module_rm.security.ExtendedWriterDynamicAuthority; import org.alfresco.module.org_alfresco_module_rm.security.FilePlanPermissionService; import org.alfresco.service.cmr.repository.ChildAssociationRef; import org.alfresco.service.cmr.repository.NodeRef; @@ -155,10 +152,6 @@ public class RMv21InPlacePatch extends RMv21PatchComponent ruleService.disableRules(); try { - // set permissions - filePlanPermissionService.setPermission(filePlan, ExtendedReaderDynamicAuthority.EXTENDED_READER, RMPermissionModel.READ_RECORDS); - filePlanPermissionService.setPermission(filePlan, ExtendedWriterDynamicAuthority.EXTENDED_WRITER, RMPermissionModel.FILING); - // create fileplan containers filePlanService.createHoldContainer(filePlan); filePlanService.createTransferContainer(filePlan); diff --git a/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/record/InplaceRecordServiceImpl.java b/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/record/InplaceRecordServiceImpl.java index 16bd7effb9..8132f465f7 100644 --- a/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/record/InplaceRecordServiceImpl.java +++ b/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/record/InplaceRecordServiceImpl.java @@ -21,7 +21,6 @@ package org.alfresco.module.org_alfresco_module_rm.record; import static org.alfresco.model.ContentModel.ASPECT_PENDING_DELETE; import java.util.List; -import java.util.Set; import org.alfresco.error.AlfrescoRuntimeException; import org.alfresco.module.org_alfresco_module_rm.model.RecordsManagementModel; @@ -95,22 +94,26 @@ public class InplaceRecordServiceImpl extends ServiceBaseImpl implements Inplace { // remove the child association NodeRef originatingLocation = (NodeRef) nodeService.getProperty(nodeRef, PROP_RECORD_ORIGINATING_LOCATION); - List parentAssocs = nodeService.getParentAssocs(nodeRef); - for (ChildAssociationRef childAssociationRef : parentAssocs) + + if (originatingLocation != null) { - if (!childAssociationRef.isPrimary() && - childAssociationRef.getParentRef().equals(originatingLocation) && - !nodeService.hasAspect(childAssociationRef.getChildRef(), ASPECT_PENDING_DELETE)) + List parentAssocs = nodeService.getParentAssocs(nodeRef); + for (ChildAssociationRef childAssociationRef : parentAssocs) { - nodeService.removeChildAssociation(childAssociationRef); - break; + if (!childAssociationRef.isPrimary() && + childAssociationRef.getParentRef().equals(originatingLocation) && + !nodeService.hasAspect(childAssociationRef.getChildRef(), ASPECT_PENDING_DELETE)) + { + nodeService.removeChildAssociation(childAssociationRef); + break; + } } + + // remove the extended security from the node + // this prevents the users from continuing to see the record in searchs and other linked locations + extendedSecurityService.remove(nodeRef); } - // remove the extended security from the node - // this prevents the users from continuing to see the record in searchs and other linked locations - extendedSecurityService.removeAllExtendedSecurity(nodeRef); - return null; } }); @@ -164,18 +167,11 @@ public class InplaceRecordServiceImpl extends ServiceBaseImpl implements Inplace { try { - // Get the extended readers/writers - Set extendedReaders = extendedSecurityService.getExtendedReaders(nodeRef); - Set extendedWriters = extendedSecurityService.getExtendedWriters(nodeRef); - // Move the record fileFolderService.moveFrom(nodeRef, source, targetNodeRef, null); // Update the originating location property nodeService.setProperty(nodeRef, PROP_RECORD_ORIGINATING_LOCATION, targetNodeRef); - - // Set the extended readers/writers - extendedSecurityService.addExtendedSecurity(nodeRef, extendedReaders, extendedWriters); } catch (FileExistsException | FileNotFoundException ex) { diff --git a/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/record/RecordServiceImpl.java b/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/record/RecordServiceImpl.java index 0432c1c086..1536c6f942 100644 --- a/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/record/RecordServiceImpl.java +++ b/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/record/RecordServiceImpl.java @@ -100,6 +100,7 @@ import org.alfresco.service.namespace.NamespaceService; import org.alfresco.service.namespace.QName; import org.alfresco.service.namespace.RegexQNamePattern; import org.alfresco.util.EqualsHelper; +import org.alfresco.util.Pair; import org.alfresco.util.ParameterCheck; import org.alfresco.util.PropertyMap; import org.apache.commons.lang.ArrayUtils; @@ -242,10 +243,6 @@ public class RecordServiceImpl extends BaseBehaviourBean this, "onCreateChildAssociation", NotificationFrequency.FIRST_EVENT); - private JavaBehaviour onDeleteDeclaredRecordLink = new JavaBehaviour( - this, - "onDeleteDeclaredRecordLink", - NotificationFrequency.FIRST_EVENT); /** * @param identifierService identifier service @@ -398,11 +395,6 @@ public class RecordServiceImpl extends BaseBehaviourBean TYPE_RECORD_FOLDER, ContentModel.ASSOC_CONTAINS, onCreateChildAssociation); - policyComponent.bindAssociationBehaviour( - NodeServicePolicies.BeforeDeleteChildAssociationPolicy.QNAME, - ContentModel.TYPE_FOLDER, - ContentModel.ASSOC_CONTAINS, - onDeleteDeclaredRecordLink); } /** @@ -573,27 +565,6 @@ public class RecordServiceImpl extends BaseBehaviourBean }, AuthenticationUtil.getSystemUserName()); } - /** - * Looking specifically at linked content that was declared a record from a non-rm site. - * When the site or the folder that the link was declared in is deleted we need to remove - * the extended security property accounts in the tree - * - * @param childAssocRef - */ - public void onDeleteDeclaredRecordLink(ChildAssociationRef childAssocRef) - { - // Is the deleted child association not a primary association? - // Does the deleted child association have the rma:recordOriginatingDetails aspect? - // Is the parent of the deleted child association a folder (cm:folder)? - if (!childAssocRef.isPrimary() && - nodeService.hasAspect(childAssocRef.getChildRef(), ASPECT_RECORD_ORIGINATING_DETAILS) && - nodeService.getType(childAssocRef.getParentRef()).equals(ContentModel.TYPE_FOLDER)) - { - // ..then remove the extended readers and writers up the tree for this remaining node - extendedSecurityService.removeExtendedSecurity(childAssocRef.getChildRef(), extendedSecurityService.getExtendedReaders(childAssocRef.getChildRef()), extendedSecurityService.getExtendedWriters(childAssocRef.getChildRef()), true); - } - } - /** * @see org.alfresco.module.org_alfresco_module_rm.record.RecordService#disablePropertyEditableCheck() */ @@ -887,12 +858,10 @@ public class RecordServiceImpl extends BaseBehaviourBean throw new AlfrescoRuntimeException("Unable to create record, because new record container could not be found."); } - // get the documents readers - Long aclId = nodeService.getNodeAclId(nodeRef); - Set readers = extendedPermissionService.getReaders(aclId); - Set writers = extendedPermissionService.getWriters(aclId); + // get the documents readers and writers + Pair, Set> readersAndWriters = extendedPermissionService.getReadersAndWriters(nodeRef); - // add the current owner to the list of extended writers + // get the current owner String owner = ownableService.getOwner(nodeRef); // get the documents primary parent assoc @@ -944,13 +913,7 @@ public class RecordServiceImpl extends BaseBehaviourBean nodeService.addChild(parentAssoc.getParentRef(), nodeRef, parentAssoc.getTypeQName(), parentAssoc.getQName()); // set the extended security - Set combinedWriters = new HashSet(writers); - if (owner != null && !owner.isEmpty() && !owner.equals(OwnableService.NO_OWNER)) - { - combinedWriters.add(owner); - } - combinedWriters.add(AuthenticationUtil.getFullyAuthenticatedUser()); - extendedSecurityService.addExtendedSecurity(nodeRef, readers, combinedWriters); + extendedSecurityService.set(nodeRef, readersAndWriters); } finally { @@ -982,24 +945,8 @@ public class RecordServiceImpl extends BaseBehaviourBean // get the unfiled record folder final NodeRef unfiledRecordFolder = filePlanService.getUnfiledContainer(filePlan); - // get the documents readers - Long aclId = nodeService.getNodeAclId(nodeRef); - Set readers = extendedPermissionService.getReaders(aclId); - Set writers = extendedPermissionService.getWriters(aclId); - - // add the current owner to the list of extended writers - Set modifiedWrtiers = new HashSet(writers); - if (nodeService.hasAspect(nodeRef, ContentModel.ASPECT_OWNABLE)) - { - String owner = ownableService.getOwner(nodeRef); - if (owner != null && !owner.isEmpty() && !owner.equals(OwnableService.NO_OWNER)) - { - modifiedWrtiers.add(owner); - } - } - - // add the current user as extended writer - modifiedWrtiers.add(authenticationUtil.getFullyAuthenticatedUser()); + // get the documents readers and writers + Pair, Set> readersAndWriters = extendedPermissionService.getReadersAndWriters(nodeRef); // copy version state and create record NodeRef record = null; @@ -1056,7 +1003,7 @@ public class RecordServiceImpl extends BaseBehaviourBean } // set extended security on record - extendedSecurityService.addExtendedSecurity(record, readers, writers); + extendedSecurityService.set(record, readersAndWriters); return record; } diff --git a/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/role/FilePlanRoleServiceImpl.java b/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/role/FilePlanRoleServiceImpl.java index b9c65bcf43..1cb0b151c3 100644 --- a/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/role/FilePlanRoleServiceImpl.java +++ b/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/role/FilePlanRoleServiceImpl.java @@ -36,8 +36,6 @@ 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.fileplan.FilePlanService; import org.alfresco.module.org_alfresco_module_rm.model.RecordsManagementModel; -import org.alfresco.module.org_alfresco_module_rm.security.ExtendedReaderDynamicAuthority; -import org.alfresco.module.org_alfresco_module_rm.security.ExtendedWriterDynamicAuthority; import org.alfresco.repo.security.authentication.AuthenticationUtil; import org.alfresco.repo.security.authority.RMAuthority; import org.alfresco.service.cmr.repository.NodeRef; @@ -185,8 +183,6 @@ public class FilePlanRoleServiceImpl implements FilePlanRoleService, // Set the permissions permissionService.setInheritParentPermissions(filePlan, false); permissionService.setPermission(filePlan, allRoles, RMPermissionModel.READ_RECORDS, true); - permissionService.setPermission(filePlan, ExtendedReaderDynamicAuthority.EXTENDED_READER, RMPermissionModel.READ_RECORDS, true); - permissionService.setPermission(filePlan, ExtendedWriterDynamicAuthority.EXTENDED_WRITER, RMPermissionModel.FILING, true); // Create the transfer and hold containers systemContainers.add(filePlanService.createHoldContainer(filePlan)); diff --git a/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/security/ExtendedSecurityService.java b/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/security/ExtendedSecurityService.java index a28d66fe53..afa06f4fdb 100644 --- a/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/security/ExtendedSecurityService.java +++ b/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/security/ExtendedSecurityService.java @@ -1,117 +1,105 @@ /* - * Copyright (C) 2005-2014 Alfresco Software Limited. - * - * This file is part of Alfresco - * + * #%L + * Alfresco Records Management Module + * %% + * Copyright (C) 2005 - 2016 Alfresco Software Limited + * %% + * This file is part of the Alfresco software. + * - + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * - * 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 . + * #L% */ + package org.alfresco.module.org_alfresco_module_rm.security; import java.util.Set; +import org.alfresco.api.AlfrescoPublicApi; import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.util.Pair; /** * Extended security service. - * + * * @author Roy Wetherall * @since 2.1 */ -public interface ExtendedSecurityService +@AlfrescoPublicApi +public interface ExtendedSecurityService extends DeprecatedExtendedSecurityService { + /** IPR group prefix */ + static final String IPR_GROUP_PREFIX = "IPR"; + /** * Indicates whether a node has extended security. - * + * * @param nodeRef node reference - * @return boolean true if the node has extedned security, false otherwise + * @return boolean true if the node has extended security, false otherwise */ boolean hasExtendedSecurity(NodeRef nodeRef); - + /** * Gets the set of authorities that are extended readers for the given node. - * + * * @param nodeRef node reference * @return {@link Set}<{@link String}> set of extended readers */ - Set getExtendedReaders(NodeRef nodeRef); - + Set getReaders(NodeRef nodeRef); + /** * Get the set of authorities that are extended writers for the given node. - * + * * @param nodeRef node reference * @return {@link Set}<{@link String}> set of extended writers */ - Set getExtendedWriters(NodeRef nodeRef); - - /** - * Add extended security for the specified authorities to a node. - * - * @param nodeRef node reference - * @param readers set of authorities to add extended read permissions - * @param writers set of authorities to add extended write permissions - */ - void addExtendedSecurity(NodeRef nodeRef, Set readers, Set writers); + Set getWriters(NodeRef nodeRef); /** - * Add extended security for the specified authorities to a node. + * Helper to allow caller to provide authority sets as a pair where the + * first is the readers and the second is the writers. + * + * @see #set(NodeRef, Set, Set) + * + * @param nodeRef node reference + * @param readersAndWriters pair where first is the set of readers and the + * second is the set of writers + */ + void set(NodeRef nodeRef, Pair, Set> readersAndWriters); + + /** + * Set extended security for a node, where the readers will be granted ReadRecord + * permission and ViewRecord capability to the node and where the writers will be + * granted Filling permission and Filling capability to the node. *

- * If specified, the read and write extended permissions are applied to all parents up to the file plan as - * extended read. This ensures parental read, but not parental write. + * Note it is vaild to provide 'null' values for readers and/or writers. * * @param nodeRef node reference - * @param readers set of authorities to add extended read permissions - * @param writers set of authorities to add extended write permissions - * @param applyToParents true if extended security applied to parents (read only) false otherwise. - */ - void addExtendedSecurity(NodeRef nodeRef, Set readers, Set writers, boolean applyToParents); - - /** - * Remove the extended security for the specified authorities from a node. + * @param readers set of readers + * @param writers set of writers * - * @param nodeRef node reference - * @param readers set of authorities to remove as extended readers - * @param writers set of authorities to remove as extended writers + * @since 2.5 */ - void removeExtendedSecurity(NodeRef nodeRef, Set readers, Set writers); + void set(NodeRef nodeRef, Set readers, Set writers); /** - * Remove the extended security for the specified authorities from a node. - *

- * If specified, extended security will also be removed from the parent hierarchy.(read only). Note that - * extended security is records as a reference count, so security will only be utterly removed from the parent - * hierarchy if all references to the authority are removed. - * - * @param nodeRef node reference - * @param readers set of authorities to remove as extended readers - * @param writers set of authorities to remove as extedned writers - * @param applyToParents true if removal of extended security is applied to parent hierarchy (read only), false - * otherwise - */ - void removeExtendedSecurity(NodeRef nodeRef, Set readers, Set writers, boolean applyToParents); - - /** - * Remove all extended readers and writers from the given node reference. + * Removes all extended security from a node. * * @param nodeRef node reference */ - void removeAllExtendedSecurity(NodeRef nodeRef); - - /** - * Remove all extended readers and writers from the given node reference. - * - * @param nodeRef node reference - * @param applyToParents if true then apply removal to parent hierarchy (read only) false otherwise. - */ - void removeAllExtendedSecurity(NodeRef nodeRef, boolean applyToParents); + void remove(NodeRef nodeRef); } diff --git a/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/security/ExtendedSecurityServiceImpl.java b/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/security/ExtendedSecurityServiceImpl.java index 099904fa9b..08ddbd2cd5 100644 --- a/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/security/ExtendedSecurityServiceImpl.java +++ b/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/security/ExtendedSecurityServiceImpl.java @@ -1,42 +1,62 @@ /* - * Copyright (C) 2005-2014 Alfresco Software Limited. - * - * This file is part of Alfresco - * + * #%L + * Alfresco Records Management Module + * %% + * Copyright (C) 2005 - 2016 Alfresco Software Limited + * %% + * This file is part of the Alfresco software. + * - + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * - * 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 . + * #L% */ + package org.alfresco.module.org_alfresco_module_rm.security; -import java.io.Serializable; -import java.util.HashMap; +import static org.alfresco.service.cmr.security.PermissionService.GROUP_PREFIX; + +import java.util.Collections; import java.util.HashSet; import java.util.List; -import java.util.Map; import java.util.Set; import org.alfresco.model.RenditionModel; +import org.alfresco.module.org_alfresco_module_rm.capability.RMPermissionModel; 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.role.FilePlanRoleService; import org.alfresco.module.org_alfresco_module_rm.util.ServiceBaseImpl; +import org.alfresco.query.PagingRequest; +import org.alfresco.query.PagingResults; +import org.alfresco.repo.security.authority.RMAuthority; +import org.alfresco.repo.transaction.RetryingTransactionHelper; import org.alfresco.service.cmr.repository.ChildAssociationRef; import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.security.AccessPermission; +import org.alfresco.service.cmr.security.AuthorityService; import org.alfresco.service.cmr.security.AuthorityType; import org.alfresco.service.cmr.security.PermissionService; -import org.alfresco.service.namespace.QName; import org.alfresco.service.namespace.RegexQNamePattern; +import org.alfresco.service.transaction.TransactionService; +import org.alfresco.util.Pair; import org.alfresco.util.ParameterCheck; +import org.springframework.context.ApplicationListener; +import org.springframework.context.event.ContextRefreshedEvent; +import org.springframework.extensions.webscripts.ui.common.StringUtils; /** * Extended security service implementation. @@ -46,17 +66,31 @@ import org.alfresco.util.ParameterCheck; */ public class ExtendedSecurityServiceImpl extends ServiceBaseImpl implements ExtendedSecurityService, - RecordsManagementModel + RecordsManagementModel, + ApplicationListener { - /** Ad hoc properties used for reference counting */ - private static final QName PROP_EXTENDED_READER_ROLE = QName.createQName(RM_URI, "extendedReaderRole"); - private static final QName PROP_EXTENDED_WRITER_ROLE = QName.createQName(RM_URI, "extendedWriterRole"); - + /** ipr group names */ + static final String ROOT_IPR_GROUP = "INPLACE_RECORD_MANAGEMENT"; + static final String READER_GROUP_PREFIX = ExtendedSecurityService.IPR_GROUP_PREFIX + "R"; + static final String WRITER_GROUP_PREFIX = ExtendedSecurityService.IPR_GROUP_PREFIX + "W"; + + /** max page size for authority query */ + private static final int MAX_ITEMS = 50; + /** File plan service */ private FilePlanService filePlanService; /** File plan role service */ private FilePlanRoleService filePlanRoleService; + + /** authority service */ + private AuthorityService authorityService; + + /** permission service */ + private PermissionService permissionService; + + /** transaction service */ + private TransactionService transactionService; /** * @param filePlanService file plan service @@ -73,115 +107,153 @@ public class ExtendedSecurityServiceImpl extends ServiceBaseImpl { this.filePlanRoleService = filePlanRoleService; } - + /** + * @param authorityService authority service + */ + public void setAuthorityService(AuthorityService authorityService) + { + this.authorityService = authorityService; + } + + /** + * @param permissionService permission service + */ + public void setPermissionService(PermissionService permissionService) + { + this.permissionService = permissionService; + } + + /** + * @param transactionService transaction service + */ + public void setTransactionService(TransactionService transactionService) + { + this.transactionService = transactionService; + } + + /** + * Application context refresh event handler + */ + @Override + public void onApplicationEvent(ContextRefreshedEvent contextRefreshedEvent) + { + transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionHelper.RetryingTransactionCallback() + { + public Void execute() throws Throwable + { + // if the root group doesn't exist then create it + if (!authorityService.authorityExists(getRootIRPGroup())) + { + authorityService.createAuthority(AuthorityType.GROUP, ROOT_IPR_GROUP, ROOT_IPR_GROUP, Collections.singleton(RMAuthority.ZONE_APP_RM)); + } + + return null; + } + }); + } + + /** + * Get root IPR group name + */ + private String getRootIRPGroup() + { + return GROUP_PREFIX + ROOT_IPR_GROUP; + } + + /** * @see org.alfresco.module.org_alfresco_module_rm.security.ExtendedSecurityService#hasExtendedSecurity(org.alfresco.service.cmr.repository.NodeRef) */ + @Override public boolean hasExtendedSecurity(NodeRef nodeRef) { - return nodeService.hasAspect(nodeRef, ASPECT_EXTENDED_SECURITY); + return (getIPRGroups(nodeRef) != null); } /** - * @see org.alfresco.module.org_alfresco_module_rm.security.RecordsManagementSecurityService#getExtendedReaders(org.alfresco.service.cmr.repository.NodeRef) + * @see org.alfresco.module.org_alfresco_module_rm.security.RecordsManagementSecurityService#getReaders(org.alfresco.service.cmr.repository.NodeRef) */ @SuppressWarnings("unchecked") @Override - public Set getExtendedReaders(NodeRef nodeRef) - { - Set result = null; - - Map readerMap = (Map)nodeService.getProperty(nodeRef, PROP_READERS); - if (readerMap != null) - { - result = readerMap.keySet(); - } - - return result; - } - - /** - * @see org.alfresco.module.org_alfresco_module_rm.security.ExtendedSecurityService#getExtendedWriters(org.alfresco.service.cmr.repository.NodeRef) - */ - @SuppressWarnings("unchecked") - @Override - public Set getExtendedWriters(NodeRef nodeRef) - { - Set result = null; - - Map map = (Map)nodeService.getProperty(nodeRef, PROP_WRITERS); - if (map != null) - { - result = map.keySet(); - } - - return result; - } - - /** - * @see org.alfresco.module.org_alfresco_module_rm.security.ExtendedSecurityService#addExtendedSecurity(org.alfresco.service.cmr.repository.NodeRef, java.util.Set, java.util.Set) - */ - @Override - public void addExtendedSecurity(NodeRef nodeRef, Set readers, Set writers) - { - addExtendedSecurity(nodeRef, readers, writers, true); - } - - /** - * @see org.alfresco.module.org_alfresco_module_rm.security.ExtendedSecurityService#addExtendedSecurity(org.alfresco.service.cmr.repository.NodeRef, java.util.Set, java.util.Set, boolean) - */ - @Override - public void addExtendedSecurity(NodeRef nodeRef, Set readers, Set writers, boolean applyToParents) + public Set getReaders(NodeRef nodeRef) { ParameterCheck.mandatory("nodeRef", nodeRef); - ParameterCheck.mandatory("applyToParents", applyToParents); - - if (nodeRef != null) + + Set result = Collections.EMPTY_SET; + Pair iprGroups = getIPRGroups(nodeRef); + if (iprGroups != null) { - addExtendedSecurityImpl(nodeRef, readers, writers, applyToParents); + result = getAuthorities(iprGroups.getFirst()); } + + return result; } /** - * Add extended security implementation method - * - * @param nodeRef - * @param readers - * @param writers - * @param applyToParents + * @see org.alfresco.module.org_alfresco_module_rm.security.ExtendedSecurityService#getWriters(org.alfresco.service.cmr.repository.NodeRef) */ @SuppressWarnings("unchecked") - private void addExtendedSecurityImpl(NodeRef nodeRef, Set readers, Set writers, boolean applyToParents) + @Override + public Set getWriters(NodeRef nodeRef) { ParameterCheck.mandatory("nodeRef", nodeRef); - ParameterCheck.mandatory("applyToParents", applyToParents); - - // add the aspect if missing - if (!nodeService.hasAspect(nodeRef, ASPECT_EXTENDED_SECURITY)) + + Set result = Collections.EMPTY_SET; + Pair iprGroups = getIPRGroups(nodeRef); + if (iprGroups != null) { - nodeService.addAspect(nodeRef, ASPECT_EXTENDED_SECURITY, null); + result = getAuthorities(iprGroups.getSecond()); } - - // update the readers map - if (readers != null && readers.size() != 0) - { - // get reader map - Map readersMap = (Map)nodeService.getProperty(nodeRef, PROP_READERS); - - // set the readers property (this will in turn apply the aspect if required) - nodeService.setProperty(nodeRef, PROP_READERS, (Serializable)addToMap(readersMap, readers)); - } - - // update the writers map - if (writers != null && writers.size() != 0) - { - // get writer map - Map writersMap = (Map)nodeService.getProperty(nodeRef, PROP_WRITERS); - - // set the writers property (this will in turn apply the aspect if required) - nodeService.setProperty(nodeRef, PROP_WRITERS, (Serializable)addToMap(writersMap, writers)); - } - + + return result; + } + + /** + * Helper to get authorities for a given group + * + * @param group group name + * @return Set immediate authorities + */ + private Set getAuthorities(String group) + { + Set result = new HashSet(); + result.addAll(authorityService.getContainedAuthorities(null, group, true)); + return result; + } + + /** + * @see org.alfresco.module.org_alfresco_module_rm.security.ExtendedSecurityService#set(org.alfresco.service.cmr.repository.NodeRef, org.alfresco.util.Pair) + */ + @Override + public void set(NodeRef nodeRef, Pair, Set> readersAndWriters) + { + ParameterCheck.mandatory("nodeRef", nodeRef); + + set(nodeRef, readersAndWriters.getFirst(), readersAndWriters.getSecond()); + } + + /** + * @see org.alfresco.module.org_alfresco_module_rm.security.ExtendedSecurityService#set(org.alfresco.service.cmr.repository.NodeRef, java.util.Set, java.util.Set) + */ + @Override + public void set(NodeRef nodeRef, Set readers, Set writers) + { + ParameterCheck.mandatory("nodeRef", nodeRef); + + // remove existing extended security, assuming there is any + remove(nodeRef); + + // find groups + Pair iprGroups = createOrFindIPRGroups(readers, writers); + + // assign groups to correct fileplan roles + NodeRef filePlan = filePlanService.getFilePlan(nodeRef); + filePlanRoleService.assignRoleToAuthority(filePlan, FilePlanRoleService.ROLE_EXTENDED_READERS, iprGroups.getFirst()); + filePlanRoleService.assignRoleToAuthority(filePlan, FilePlanRoleService.ROLE_EXTENDED_WRITERS, iprGroups.getSecond()); + + // assign groups to node + assignIPRGroupsToNode(iprGroups, nodeRef); + // apply the readers to any renditions of the content if (isRecord(nodeRef)) { @@ -189,130 +261,300 @@ public class ExtendedSecurityServiceImpl extends ServiceBaseImpl for (ChildAssociationRef assoc : assocs) { NodeRef child = assoc.getChildRef(); - addExtendedSecurityImpl(child, readers, writers, false); + assignIPRGroupsToNode(iprGroups, child); } - } - - // add to the extended security roles - addExtendedSecurityRoles(nodeRef, readers, writers); - - if (applyToParents) + } + } + + /** + * Get the IPR groups associated with a given node reference. + *

+ * Return null if none found. + * + * @param nodeRef node reference + * @return Pair where first is the read group and second if the write group, null if none found + */ + private Pair getIPRGroups(NodeRef nodeRef) + { + Pair result = null; + String iprReaderGroup = null; + String iprWriterGroup = null; + + // get all the set permissions + Set permissions = permissionService.getAllSetPermissions(nodeRef); + for (AccessPermission permission : permissions) { - // apply the extended readers up the file plan primary hierarchy - NodeRef parent = nodeService.getPrimaryParent(nodeRef).getParentRef(); - if (parent != null && - filePlanService.isFilePlanComponent(parent)) + // look for the presence of the reader group + if (permission.getAuthority().startsWith(GROUP_PREFIX + READER_GROUP_PREFIX)) { - addExtendedSecurityImpl(parent, readers, null, applyToParents); - addExtendedSecurityImpl(parent, writers, null, applyToParents); + iprReaderGroup = permission.getAuthority(); + } + // look for the presence of the writer group + else if (permission.getAuthority().startsWith(GROUP_PREFIX + WRITER_GROUP_PREFIX)) + { + iprWriterGroup = permission.getAuthority(); } } - } - - /** - * - * @param nodeRef - * @param readers - * @param writers - */ - private void addExtendedSecurityRoles(NodeRef nodeRef, Set readers, Set writers) - { - NodeRef filePlan = filePlanService.getFilePlan(nodeRef); - - addExtendedSecurityRolesImpl(filePlan, readers, PROP_EXTENDED_READER_ROLE, FilePlanRoleService.ROLE_EXTENDED_READERS); - addExtendedSecurityRolesImpl(filePlan, writers, PROP_EXTENDED_WRITER_ROLE, FilePlanRoleService.ROLE_EXTENDED_WRITERS); - } - - /** - * - * @param filePlan - * @param authorities - * @param propertyName - * @param roleName - */ - @SuppressWarnings("unchecked") - private void addExtendedSecurityRolesImpl(NodeRef filePlan, Set authorities, QName propertyName, String roleName) - { - if (authorities != null) + + // assuming the are both present then return + if (iprReaderGroup != null && iprWriterGroup != null) { - // get the reference count - Map referenceCountMap = (Map)nodeService.getProperty(filePlan, propertyName); - - // set of assigned authorities - Set assignedAuthorities = new HashSet(authorities.size()); + result = new Pair(iprReaderGroup, iprWriterGroup); + } - for (String authority : authorities) - { - if ((!authority.equals(PermissionService.ALL_AUTHORITIES) && - !authority.equals(PermissionService.OWNER_AUTHORITY)) && - !AuthorityType.ROLE.equals(AuthorityType.getAuthorityType(authority)) && - (referenceCountMap == null || !referenceCountMap.containsKey(authority))) - { - // add the authority to the role - filePlanRoleService.assignRoleToAuthority(filePlan, roleName, authority); - assignedAuthorities.add(authority); - } - } - - // update the reference count - nodeService.setProperty(filePlan, propertyName, (Serializable)addToMap(referenceCountMap, assignedAuthorities)); - } + return result; } - + /** - * - * @param map - * @param keys + * Given a set of readers and writers find or create the appropriate IPR groups. + *

+ * The IPR groups are named with hashes of the authority lists in order to reduce + * the set of groups that require exact match. A further index is used to handle + * a situation where there is a hash clash, but a difference in the authority lists. + *

+ * When no match is found the groups are created. Once created + * + * @param filePlan file plan + * @param readers authorities with read + * @param writers authorities with write + * @return Pair where first is the full name of the read group and + * second is the full name of the write group + */ + private Pair createOrFindIPRGroups(Set readers, Set writers) + { + return new Pair( + createOrFindIPRGroup(READER_GROUP_PREFIX, readers), + createOrFindIPRGroup(WRITER_GROUP_PREFIX, writers)); + } + + /** + * Create or find an IPR group based on the provided prefix and authorities. + * + * @param groupPrefix group prefix + * @param authorities authorities + * @return String full group name + */ + private String createOrFindIPRGroup(String groupPrefix, Set authorities) + { + String group = null; + + // find group or determine what the next index is if no group exists or there is a clash + Pair groupResult = findIPRGroup(groupPrefix, authorities); + + if (groupResult.getFirst() == null) + { + group = createIPRGroup(groupPrefix, authorities, groupResult.getSecond()); + } + else + { + group = groupResult.getFirst(); + } + + return group; + } + + /** + * Given a group name prefix and the authorities, finds the exact match existing group. + *

+ * If the group does not exist then the group returned is null and the index shows the next available + * group index for creation. + * + * @param groupPrefix group name prefix + * @param authorities authorities + * @return Pair where first is the name of the found group, null if none found and second + * if the next available create index + */ + private Pair findIPRGroup(String groupPrefix, Set authorities) + { + String iprGroup = null; + int nextGroupIndex = 0; + boolean hasMoreItems = true; + int pageCount = 0; + + // determine the short name prefix + String groupShortNamePrefix = getIPRGroupPrefixShortName(groupPrefix, authorities); + + // iterate over the authorities to find a match + while (hasMoreItems == true) + { + // get matching authorities + PagingResults results = authorityService.getAuthorities(AuthorityType.GROUP, + RMAuthority.ZONE_APP_RM, + groupShortNamePrefix, + false, + false, + new PagingRequest(MAX_ITEMS*pageCount, MAX_ITEMS)); + + // record the total count + nextGroupIndex = nextGroupIndex + results.getPage().size(); + + // see if any of the matching groups exactly match + for (String group : results.getPage()) + { + // if exists and matches we have found our group + if (isIPRGroupTrueMatch(group, authorities)) + { + iprGroup = group; + break; + } + } + + // determine if there are any more pages to inspect + hasMoreItems = results.hasMoreItems(); + pageCount ++; + } + + return new Pair(iprGroup, nextGroupIndex); + } + + /** + * Determines whether a group exactly matches a list of authorities. + * + * @param authorities list of authorities + * @param group group * @return */ - private Map addToMap(Map map, Set keys) + private boolean isIPRGroupTrueMatch(String group, Set authorities) + { + Set contained = authorityService.getContainedAuthorities(null, group, true); + return contained.equals(authorities); + } + + /** + * Get IPR group prefix short name. + *

+ * 'package' scope to help testing. + * + * @param prefix prefix + * @param authorities authorities + * @return String group prefix short name + */ + /*package*/ String getIPRGroupPrefixShortName(String prefix, Set authorities) { - if (map == null) + StringBuilder builder = new StringBuilder(128) + .append(prefix) + .append(getAuthoritySetHashCode(authorities)); + + return builder.toString(); + } + + /** + * Get IPR group short name. + *

+ * Note this excludes the "GROUP_" prefix. + *

+ * 'package' scope to help testing. + * + * @param prefix prefix + * @param readers read authorities + * @param writers write authorities + * @param index group index + * @return String group short name + */ + /*package*/ String getIPRGroupShortName(String prefix, Set authorities, int index) + { + return getIPRGroupShortName(prefix, authorities, Integer.toString(index)); + } + + /** + * Get IPR group short name. + *

+ * Note this excludes the "GROUP_" prefix. + * + * @param prefix prefix + * @param readers read authorities + * @param writers write authorities + * @param index group index + * @return String group short name + */ + private String getIPRGroupShortName(String prefix, Set authorities, String index) + { + StringBuilder builder = new StringBuilder(128) + .append(getIPRGroupPrefixShortName(prefix, authorities)) + .append(index); + + return builder.toString(); + } + + /** + * Gets the hashcode value of a set of authorities. + * + * @param authorities set of authorities + * @return int hash code + */ + private int getAuthoritySetHashCode(Set authorities) + { + int result = 0; + if (authorities != null && !authorities.isEmpty()) { - // create map - map = new HashMap(7); + result = StringUtils.join(authorities.toArray(), "").hashCode(); } - - for (String key : keys) + return result; + } + + /** + * Creates a new IPR group. + * + * @param groupNamePrefix group name prefix + * @param children child authorities + * @param index group index + * @return String full name of created group + */ + private String createIPRGroup(String groupNamePrefix, Set children, int index) + { + ParameterCheck.mandatory("groupNamePrefix", groupNamePrefix); + + // get the group name + String groupShortName = getIPRGroupShortName(groupNamePrefix, children, index); + + // create group + String group = authorityService.createAuthority(AuthorityType.GROUP, groupShortName, groupShortName, Collections.singleton(RMAuthority.ZONE_APP_RM)); + + // add root parent + authorityService.addAuthority(getRootIRPGroup(), group); + + // add children if provided + if (children != null) { - if (!key.equals(PermissionService.ALL_AUTHORITIES)) + for (String child : children) { - if (map.containsKey(key)) + if (authorityService.authorityExists(child) && + !PermissionService.ALL_AUTHORITIES.equals(child)) { - // increment reference count - Integer count = map.get(key); - map.put(key, Integer.valueOf(count.intValue()+1)); - } - else - { - // add key with initial count - map.put(key, Integer.valueOf(1)); + authorityService.addAuthority(group, child); } } } - - return map; + + return group; } - + /** - * @see org.alfresco.module.org_alfresco_module_rm.security.ExtendedSecurityService#removeExtendedSecurity(org.alfresco.service.cmr.repository.NodeRef, java.util.Set, java.util.Set) + * Assign IPR groups to a node reference with the correct permissions. + * + * @param iprGroups iprGroups, first read and second write + * @param nodeRef node reference + */ + private void assignIPRGroupsToNode(Pair iprGroups, NodeRef nodeRef) + { + permissionService.setPermission(nodeRef, iprGroups.getFirst(), RMPermissionModel.READ_RECORDS, true); + permissionService.setPermission(nodeRef, iprGroups.getSecond(), RMPermissionModel.FILING, true); + } + + /** + * @see org.alfresco.module.org_alfresco_module_rm.security.ExtendedSecurityService#remove(org.alfresco.service.cmr.repository.NodeRef) */ @Override - public void removeExtendedSecurity(NodeRef nodeRef, Set readers, Set writers) + public void remove(NodeRef nodeRef) { - removeExtendedSecurity(nodeRef, readers, writers, true); - } - - /** - * @see org.alfresco.module.org_alfresco_module_rm.security.ExtendedSecurityService#removeExtendedSecurity(org.alfresco.service.cmr.repository.NodeRef, java.util.Set, java.util.Set, boolean) - */ - @Override - public void removeExtendedSecurity(NodeRef nodeRef, Set readers, Setwriters, boolean applyToParents) - { - if (hasExtendedSecurity(nodeRef)) + ParameterCheck.mandatory("nodeRef", nodeRef); + + Pair iprGroups = getIPRGroups(nodeRef); + if (iprGroups != null) { - removeExtendedSecurityImpl(nodeRef, readers, writers); - + // remove any extended security that might be present + clearPermissions(nodeRef, iprGroups); + // remove the readers from any renditions of the content if (isRecord(nodeRef)) { @@ -320,110 +562,85 @@ public class ExtendedSecurityServiceImpl extends ServiceBaseImpl for (ChildAssociationRef assoc : assocs) { NodeRef child = assoc.getChildRef(); - removeExtendedSecurityImpl(child, readers, writers); - } - } - - if (applyToParents) - { - // apply the extended readers up the file plan primary hierarchy - NodeRef parent = nodeService.getPrimaryParent(nodeRef).getParentRef(); - if (parent != null && - filePlanService.isFilePlanComponent(parent)) - { - removeExtendedSecurity(parent, readers, null, applyToParents); - removeExtendedSecurity(parent, writers, null, applyToParents); + clearPermissions(child, iprGroups); } } } } /** - * Removes a set of readers and writers from a node reference. - *

- * Removes the aspect and resets the property to null if all readers and writers are removed. - * + * Clear the nodes IPR permissions + * * @param nodeRef node reference - * @param readers {@link Set} of readers - * @param writers {@link Set} of writers */ - @SuppressWarnings("unchecked") - private void removeExtendedSecurityImpl(NodeRef nodeRef, Set readers, Set writers) + private void clearPermissions(NodeRef nodeRef, Pair iprGroups) { - Map readersMap = (Map)nodeService.getProperty(nodeRef, PROP_READERS); - nodeService.setProperty(nodeRef, PROP_READERS, (Serializable)removeFromMap(readersMap, readers)); - - Map writersMap = (Map)nodeService.getProperty(nodeRef, PROP_WRITERS); - nodeService.setProperty(nodeRef, PROP_WRITERS, (Serializable)removeFromMap(writersMap, writers)); - - if (readersMap == null && writersMap == null) - { - // remove the aspect - nodeService.removeAspect(nodeRef, ASPECT_EXTENDED_SECURITY); - } + // remove group permissions from node + permissionService.clearPermission(nodeRef, iprGroups.getFirst()); + permissionService.clearPermission(nodeRef, iprGroups.getSecond()); + } + + /** + * @see org.alfresco.module.org_alfresco_module_rm.security.DeprecatedExtendedSecurityService#getExtendedReaders(org.alfresco.service.cmr.repository.NodeRef) + */ + @Override @Deprecated public Set getExtendedReaders(NodeRef nodeRef) + { + return getReaders(nodeRef); + } + + /** + * @see org.alfresco.module.org_alfresco_module_rm.security.DeprecatedExtendedSecurityService#getExtendedWriters(org.alfresco.service.cmr.repository.NodeRef) + */ + @Override @Deprecated public Set getExtendedWriters(NodeRef nodeRef) + { + return getWriters(nodeRef); + } + + /** + * @see org.alfresco.module.org_alfresco_module_rm.security.DeprecatedExtendedSecurityService#addExtendedSecurity(org.alfresco.service.cmr.repository.NodeRef, java.util.Set, java.util.Set) + */ + @Override @Deprecated public void addExtendedSecurity(NodeRef nodeRef, Set readers, Set writers) + { + set(nodeRef, readers, writers); + } + + /** + * @see org.alfresco.module.org_alfresco_module_rm.security.DeprecatedExtendedSecurityService#addExtendedSecurity(org.alfresco.service.cmr.repository.NodeRef, java.util.Set, java.util.Set, boolean) + */ + @Override @Deprecated public void addExtendedSecurity(NodeRef nodeRef, Set readers, Set writers, boolean applyToParents) + { + set(nodeRef, readers, writers); + } + + /** + * @see org.alfresco.module.org_alfresco_module_rm.security.DeprecatedExtendedSecurityService#removeAllExtendedSecurity(org.alfresco.service.cmr.repository.NodeRef) + */ + @Override @Deprecated public void removeAllExtendedSecurity(NodeRef nodeRef) + { + remove(nodeRef); + } + + /** + * @see org.alfresco.module.org_alfresco_module_rm.security.DeprecatedExtendedSecurityService#removeExtendedSecurity(org.alfresco.service.cmr.repository.NodeRef, java.util.Set, java.util.Set) + */ + @Override @Deprecated public void removeExtendedSecurity(NodeRef nodeRef, Set readers, Set writers) + { + remove(nodeRef); } /** - * Helper method to remove items from map or reduce reference count - * - * @param map ref count map - * @param keys keys - * @return Map ref count map + * @see org.alfresco.module.org_alfresco_module_rm.security.DeprecatedExtendedSecurityService#removeExtendedSecurity(org.alfresco.service.cmr.repository.NodeRef, java.util.Set, java.util.Set, boolean) */ - private Map removeFromMap(Map map, Set keys) + @Override @Deprecated public void removeExtendedSecurity(NodeRef nodeRef, Set readers, Setwriters, boolean applyToParents) { - if (map != null && keys != null && keys.size() != 0) - { - // remove the keys - for (String key : keys) - { - if (!key.equals(PermissionService.ALL_AUTHORITIES)) - { - Integer count = map.get(key); - if (count != null) - { - if (count == 1) - { - // remove entry all together if the reference count is now 0 - map.remove(key); - } - else - { - // decrement the reference count by 1 - map.put(key, Integer.valueOf(count.intValue()-1)); - } - } - } - } - } - - // reset the map to null if now empty - if (map != null && map.isEmpty()) - { - map = null; - } - - return map; + remove(nodeRef); } /** - * @see org.alfresco.module.org_alfresco_module_rm.security.ExtendedSecurityService#removeAllExtendedSecurity(org.alfresco.service.cmr.repository.NodeRef) + * @see org.alfresco.module.org_alfresco_module_rm.security.DeprecatedExtendedSecurityService#removeAllExtendedSecurity(org.alfresco.service.cmr.repository.NodeRef, boolean) */ - @Override - public void removeAllExtendedSecurity(NodeRef nodeRef) + @Override @Deprecated public void removeAllExtendedSecurity(NodeRef nodeRef, boolean applyToParents) { - removeAllExtendedSecurity(nodeRef, true); - } - - /** - * @see org.alfresco.module.org_alfresco_module_rm.security.ExtendedSecurityService#removeAllExtendedSecurity(org.alfresco.service.cmr.repository.NodeRef, boolean) - */ - @Override - public void removeAllExtendedSecurity(NodeRef nodeRef, boolean applyToParents) - { - if (hasExtendedSecurity(nodeRef)) - { - removeExtendedSecurity(nodeRef, getExtendedReaders(nodeRef), getExtendedWriters(nodeRef)); - } + remove(nodeRef); } } diff --git a/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/security/FilePlanPermissionServiceImpl.java b/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/security/FilePlanPermissionServiceImpl.java index 030fa5cd05..17f40991ad 100644 --- a/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/security/FilePlanPermissionServiceImpl.java +++ b/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/security/FilePlanPermissionServiceImpl.java @@ -18,8 +18,6 @@ */ package org.alfresco.module.org_alfresco_module_rm.security; -import static org.alfresco.module.org_alfresco_module_rm.security.ExtendedReaderDynamicAuthority.EXTENDED_READER; -import static org.alfresco.module.org_alfresco_module_rm.security.ExtendedWriterDynamicAuthority.EXTENDED_WRITER; import static org.alfresco.repo.policy.Behaviour.NotificationFrequency.TRANSACTION_COMMIT; import static org.alfresco.repo.policy.annotation.BehaviourKind.CLASS; import static org.alfresco.repo.security.authentication.AuthenticationUtil.getSystemUserName; @@ -374,13 +372,29 @@ public class FilePlanPermissionServiceImpl extends ServiceBaseImpl boolean inheritanceAllowed = isInheritanceAllowed(nodeRef, isParentNodeFilePlan); getPermissionService().setInheritParentPermissions(nodeRef, inheritanceAllowed); - // clear all existing permissions + Set keepPerms = new HashSet(5); + Set origionalPerms= getPermissionService().getAllSetPermissions(nodeRef); + + for (AccessPermission perm : origionalPerms) + { + if (perm.getAuthority().startsWith(PermissionService.GROUP_PREFIX + ExtendedSecurityService.IPR_GROUP_PREFIX)) + { + // then we can assume this is a permission we want to preserve + keepPerms.add(perm); + } + } + + // clear all existing permissions and start again getPermissionService().clearPermission(nodeRef, null); + // re-add keep'er permissions + for (AccessPermission keeper : keepPerms) + { + setPermission(nodeRef, keeper.getAuthority(), keeper.getPermission()); + } + if (!inheritanceAllowed) { - getPermissionService().setPermission(nodeRef, EXTENDED_READER, READ_RECORDS, true); - getPermissionService().setPermission(nodeRef, EXTENDED_WRITER, FILING, true); String adminRole = getAdminRole(nodeRef); getPermissionService().setPermission(nodeRef, adminRole, RMPermissionModel.FILING, true); } @@ -426,9 +440,20 @@ public class FilePlanPermissionServiceImpl extends ServiceBaseImpl return authorityService.getName(AuthorityType.GROUP, FilePlanRoleService.ROLE_ADMIN + filePlan.getId()); } + /** + * Indicates whether the default behaviour is to inherit permissions or not. + * + * @param nodeRef node reference + * @param isParentNodeFilePlan true if parent node is a file plan, false otherwise + * @return boolean true if inheritance true, false otherwise + */ private boolean isInheritanceAllowed(NodeRef nodeRef, Boolean isParentNodeFilePlan) { - return !(isFilePlan(nodeRef) || isTransfer(nodeRef) || isHold(nodeRef) || isUnfiledRecordsContainer(nodeRef) || (isRecordCategory(nodeRef) && isTrue(isParentNodeFilePlan))); + return !(isFilePlan(nodeRef) || + isTransfer(nodeRef) || + isHold(nodeRef) || + isUnfiledRecordsContainer(nodeRef) || + (isRecordCategory(nodeRef) && isTrue(isParentNodeFilePlan))); } /** @@ -485,20 +510,14 @@ public class FilePlanPermissionServiceImpl extends ServiceBaseImpl for (AccessPermission recordPermission : origionalRecordPerms) { String permission = recordPermission.getPermission(); - String authority = recordPermission.getAuthority(); if ((RMPermissionModel.FILING.equals(permission) || RMPermissionModel.READ_RECORDS.equals(permission)) && - recordPermission.isSetDirectly() && - !ExtendedReaderDynamicAuthority.EXTENDED_READER.equals(authority) && - !ExtendedWriterDynamicAuthority.EXTENDED_WRITER.equals(authority)) + recordPermission.isSetDirectly()) { // then we can assume this is a permission we want to preserve keepPerms.add(recordPermission); } } - // clear all existing permissions and start again - permissionService.deletePermissions(record); - // re-setup the records permissions setupPermissions(destinationAssocRef.getParentRef(), record); @@ -580,7 +599,7 @@ public class FilePlanPermissionServiceImpl extends ServiceBaseImpl private boolean canPerformPermissionAction(NodeRef nodeRef) { - return isFilePlanContainer(nodeRef) || isRecordFolder(nodeRef) || isRecord(nodeRef) || isTransfer(nodeRef); + return isFilePlanContainer(nodeRef) || isRecordFolder(nodeRef) || isRecord(nodeRef) || isTransfer(nodeRef) || isHold(nodeRef); } /** diff --git a/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/version/RecordableVersionServiceImpl.java b/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/version/RecordableVersionServiceImpl.java index a148fd3016..880ce2bfb8 100644 --- a/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/version/RecordableVersionServiceImpl.java +++ b/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/version/RecordableVersionServiceImpl.java @@ -25,7 +25,6 @@ import java.io.Serializable; import java.util.Collection; import java.util.Collections; import java.util.HashMap; -import java.util.HashSet; import java.util.Map; import java.util.Set; @@ -47,12 +46,12 @@ import org.alfresco.repo.version.VersionModel; import org.alfresco.service.cmr.repository.ChildAssociationRef; import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.repository.StoreRef; -import org.alfresco.service.cmr.security.OwnableService; import org.alfresco.service.cmr.version.ReservedVersionNameException; import org.alfresco.service.cmr.version.Version; import org.alfresco.service.cmr.version.VersionHistory; import org.alfresco.service.cmr.version.VersionType; import org.alfresco.service.namespace.QName; +import org.alfresco.util.Pair; import org.alfresco.util.ParameterCheck; import org.alfresco.util.PropertyMap; import org.apache.commons.logging.Log; @@ -118,9 +117,6 @@ public class RecordableVersionServiceImpl extends Version2ServiceImpl /** extended security service */ private ExtendedSecurityService extendedSecurityService; - - /** ownable service */ - private OwnableService ownableService; /** * @param filePlanService file plan service @@ -185,14 +181,6 @@ public class RecordableVersionServiceImpl extends Version2ServiceImpl { this.extendedSecurityService = extendedSecurityService; } - - /** - * @param ownableService ownable service - */ - public void setOwnableService(OwnableService ownableService) - { - this.ownableService = ownableService; - } /** * @see org.alfresco.repo.version.Version2ServiceImpl#createVersion(org.alfresco.service.cmr.repository.NodeRef, java.util.Map, int) @@ -635,13 +623,8 @@ public class RecordableVersionServiceImpl extends Version2ServiceImpl { public NodeRef doWork() throws Exception { - // get the documents readers - Long aclId = nodeService.getNodeAclId(nodeRef); - Set readers = extendedPermissionService.getReaders(aclId); - Set writers = extendedPermissionService.getWriters(aclId); - - // add the current owner to the list of extended writers - String owner = ownableService.getOwner(nodeRef); + // get the documents readers and writers + Pair, Set> readersAndWriters = extendedPermissionService.getReadersAndWriters(nodeRef); // grab the frozen state NodeRef currentFrozenState = currentVersion.getFrozenStateNodeRef(); @@ -681,13 +664,7 @@ public class RecordableVersionServiceImpl extends Version2ServiceImpl linkToPreviousVersionRecord(nodeRef, record); // set the extended security - Set combinedWriters = new HashSet(writers); - if (owner != null && !owner.isEmpty() && !owner.equals(OwnableService.NO_OWNER)) - { - combinedWriters.add(owner); - } - combinedWriters.add(authenticationUtil.getFullyAuthenticatedUser()); - extendedSecurityService.addExtendedSecurity(record, readers, combinedWriters); + extendedSecurityService.set(record, readersAndWriters); return record; } diff --git a/rm-server/source/java/org/alfresco/repo/security/permissions/impl/ExtendedPermissionService.java b/rm-server/source/java/org/alfresco/repo/security/permissions/impl/ExtendedPermissionService.java index 779301e83d..6fcce8196d 100644 --- a/rm-server/source/java/org/alfresco/repo/security/permissions/impl/ExtendedPermissionService.java +++ b/rm-server/source/java/org/alfresco/repo/security/permissions/impl/ExtendedPermissionService.java @@ -20,7 +20,9 @@ package org.alfresco.repo.security.permissions.impl; import java.util.Set; +import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.security.PermissionService; +import org.alfresco.util.Pair; /** * Extended Permission Service Interface used in RM. @@ -31,4 +33,18 @@ import org.alfresco.service.cmr.security.PermissionService; public interface ExtendedPermissionService extends PermissionService { Set getWriters(Long aclId); + + /** + * Get the readers and writers for a given node. + *

+ * The writers list includes the owner for the node. + * + * @param nodeRef node reference + * @return Pair, Set> first is a set containing all the authorities that have read permission on the + * document and second is a set containing all the authorities that have write + * permission on the document, including the owner. + * + * @since 2.5 + */ + Pair, Set> getReadersAndWriters(NodeRef nodeRef); } diff --git a/rm-server/source/java/org/alfresco/repo/security/permissions/impl/RMPermissionServiceImpl.java b/rm-server/source/java/org/alfresco/repo/security/permissions/impl/RMPermissionServiceImpl.java index e01c1be439..c2b7e30cbb 100644 --- a/rm-server/source/java/org/alfresco/repo/security/permissions/impl/RMPermissionServiceImpl.java +++ b/rm-server/source/java/org/alfresco/repo/security/permissions/impl/RMPermissionServiceImpl.java @@ -29,16 +29,17 @@ import org.alfresco.module.org_alfresco_module_rm.capability.RMPermissionModel; 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.role.FilePlanRoleService; -import org.alfresco.module.org_alfresco_module_rm.security.ExtendedReaderDynamicAuthority; -import org.alfresco.module.org_alfresco_module_rm.security.ExtendedWriterDynamicAuthority; import org.alfresco.repo.cache.SimpleCache; import org.alfresco.repo.security.permissions.AccessControlEntry; import org.alfresco.repo.security.permissions.AccessControlList; import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.security.AccessStatus; import org.alfresco.service.cmr.security.AuthorityType; +import org.alfresco.service.cmr.security.OwnableService; import org.alfresco.service.cmr.security.PermissionService; +import org.alfresco.util.Pair; import org.alfresco.util.PropertyCheck; +import org.apache.commons.lang.StringUtils; import org.springframework.context.ApplicationEvent; /** @@ -303,13 +304,17 @@ public class RMPermissionServiceImpl extends PermissionServiceImpl final String adminRole = getAdminRole(nodeRef); if (nodeService.hasAspect(nodeRef, RecordsManagementModel.ASPECT_FILE_PLAN_COMPONENT) && isNotBlank(adminRole) && !inheritParentPermissions) { - setPermission(nodeRef, ExtendedReaderDynamicAuthority.EXTENDED_READER, RMPermissionModel.READ_RECORDS, true); - setPermission(nodeRef, ExtendedWriterDynamicAuthority.EXTENDED_WRITER, RMPermissionModel.FILING, true); - setPermission(nodeRef, adminRole, RMPermissionModel.FILING, true); + setPermission(nodeRef, adminRole, RMPermissionModel.FILING, true); } super.setInheritParentPermissions(nodeRef, inheritParentPermissions); } + /** + * Helper method to the RM admin role scoped by the correct file plan. + * + * @param nodeRef node reference + * @return String RM admin role + */ private String getAdminRole(NodeRef nodeRef) { String adminRole = null; @@ -320,4 +325,28 @@ public class RMPermissionServiceImpl extends PermissionServiceImpl } return adminRole; } + + /** + * @see org.alfresco.repo.security.permissions.impl.ExtendedPermissionService#getReadersAndWriters(org.alfresco.service.cmr.repository.NodeRef) + */ + @Override + public Pair, Set> getReadersAndWriters(NodeRef nodeRef) + { + // get the documents readers + Long aclId = nodeService.getNodeAclId(nodeRef); + Set readers = getReaders(aclId); + Set writers = getWriters(aclId); + + // add the current owner to the list of extended writers + Set modifiedWrtiers = new HashSet(writers); + String owner = ownableService.getOwner(nodeRef); + if (StringUtils.isNotBlank(owner) && + !owner.equals(OwnableService.NO_OWNER) && + authorityService.authorityExists(owner)) + { + modifiedWrtiers.add(owner); + } + + return new Pair, Set> (readers, modifiedWrtiers); + } } diff --git a/rm-server/test/java/org/alfresco/module/org_alfresco_module_rm/test/integration/issue/RM1429Test.java b/rm-server/test/java/org/alfresco/module/org_alfresco_module_rm/test/integration/issue/RM1429Test.java index 96e835729d..f35e2f10e9 100644 --- a/rm-server/test/java/org/alfresco/module/org_alfresco_module_rm/test/integration/issue/RM1429Test.java +++ b/rm-server/test/java/org/alfresco/module/org_alfresco_module_rm/test/integration/issue/RM1429Test.java @@ -35,14 +35,14 @@ public class RM1429Test extends DeleteHoldTest { public void testDeleteHoldWithoutPermissionsOnChildren() { - // Create the test hold - final NodeRef hold = createAndCheckHold(); - - doTestInTransaction(new Test() + final NodeRef hold = doTestInTransaction(new Test() { @Override - public Void run() + public NodeRef run() { + // Create the test hold + NodeRef hold = createAndCheckHold(); + // Add the user to the RM Manager role filePlanRoleService.assignRoleToAuthority(filePlan, FilePlanRoleService.ROLE_RECORDS_MANAGER, userName); @@ -55,7 +55,7 @@ public class RM1429Test extends DeleteHoldTest // Add record folder to the hold holdService.addToHold(hold, rmFolder); - return null; + return hold; } }); diff --git a/rm-server/test/java/org/alfresco/module/org_alfresco_module_rm/test/integration/issue/RM1463Test.java b/rm-server/test/java/org/alfresco/module/org_alfresco_module_rm/test/integration/issue/RM1463Test.java index 074eb27e83..517b316ab1 100644 --- a/rm-server/test/java/org/alfresco/module/org_alfresco_module_rm/test/integration/issue/RM1463Test.java +++ b/rm-server/test/java/org/alfresco/module/org_alfresco_module_rm/test/integration/issue/RM1463Test.java @@ -35,14 +35,14 @@ public class RM1463Test extends DeleteHoldTest { public void testAddRecordFolderToHoldWithoutFilingPermissionOnRecordFolder() { - // Create hold - final NodeRef hold = createAndCheckHold(); - - doTestInTransaction(new Test() + final NodeRef hold = doTestInTransaction(new Test() { @Override - public Void run() + public NodeRef run() { + // Create hold + NodeRef hold = createAndCheckHold(); + // Add the user to the RM Manager role filePlanRoleService.assignRoleToAuthority(filePlan, FilePlanRoleService.ROLE_RECORDS_MANAGER, userName); @@ -52,7 +52,7 @@ public class RM1463Test extends DeleteHoldTest // Give the user only read permissions on the record folder permissionService.setPermission(rmFolder, userName, RMPermissionModel.READ_RECORDS, true); - return null; + return hold; } }); diff --git a/rm-server/test/java/org/alfresco/module/org_alfresco_module_rm/test/integration/issue/RM1464Test.java b/rm-server/test/java/org/alfresco/module/org_alfresco_module_rm/test/integration/issue/RM1464Test.java index 5a009bb620..83ef6cbf6d 100644 --- a/rm-server/test/java/org/alfresco/module/org_alfresco_module_rm/test/integration/issue/RM1464Test.java +++ b/rm-server/test/java/org/alfresco/module/org_alfresco_module_rm/test/integration/issue/RM1464Test.java @@ -35,14 +35,14 @@ public class RM1464Test extends DeleteHoldTest { public void testAddRecordFolderToHoldWithoutFilingPermissionOnHold() { - // Create hold - final NodeRef hold = createAndCheckHold(); - - doTestInTransaction(new Test() + final NodeRef hold = doTestInTransaction(new Test() { @Override - public Void run() + public NodeRef run() { + // Create hold + NodeRef hold = createAndCheckHold(); + // Add the user to the RM Manager role filePlanRoleService.assignRoleToAuthority(filePlan, FilePlanRoleService.ROLE_RECORDS_MANAGER, userName); @@ -52,7 +52,7 @@ public class RM1464Test extends DeleteHoldTest // Give the user filing permissions on the record folder permissionService.setPermission(rmFolder, userName, RMPermissionModel.FILING, true); - return null; + return hold; } }); diff --git a/rm-server/test/java/org/alfresco/module/org_alfresco_module_rm/test/integration/record/CreateInplaceRecordTest.java b/rm-server/test/java/org/alfresco/module/org_alfresco_module_rm/test/integration/record/CreateInplaceRecordTest.java new file mode 100644 index 0000000000..0394e67af0 --- /dev/null +++ b/rm-server/test/java/org/alfresco/module/org_alfresco_module_rm/test/integration/record/CreateInplaceRecordTest.java @@ -0,0 +1,188 @@ +/* + * #%L + * Alfresco Records Management Module + * %% + * Copyright (C) 2005 - 2016 Alfresco Software Limited + * %% + * This file is part of the Alfresco software. + * - + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * - + * 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 . + * #L% + */ + +package org.alfresco.module.org_alfresco_module_rm.test.integration.record; + +import org.alfresco.module.org_alfresco_module_rm.capability.RMPermissionModel; +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.model.FileExistsException; +import org.alfresco.service.cmr.model.FileNotFoundException; +import org.alfresco.service.cmr.security.AccessStatus; + +/** + * Create Inplace Record Test + * + * @author Roy Wetherall + */ +public class CreateInplaceRecordTest extends BaseRMTestCase +{ + @Override + protected boolean isCollaborationSiteTest() + { + return true; + } + + /** + * Given a document in a collaboration site + * When the document is declared by a site collaborator + * Then the document becomes a record + * And the site users have the appropriate in-place permissions on the record + */ + public void testCreateInplaceRecordFromCollabSite() + { + doBehaviourDrivenTest(new BehaviourDrivenTest() + { + public void given() + { + // Check that the document is not a record + assertFalse("The document should not be a record", recordService.isRecord(dmDocument)); + } + + public void when() + { + // Declare the document as a record + AuthenticationUtil.runAs(new RunAsWork() + { + public Void doWork() throws Exception + { + // Declare record + recordService.createRecord(filePlan, dmDocument); + + return null; + } + }, dmCollaborator); + } + + public void then() + { + // Check that the document is a record now + assertTrue("The document should now be a record", recordService.isRecord(dmDocument)); + + // Check that the record is in the unfiled container + + // Check that the record is still a child of the collaboration folder + + // Check that the collaborator has filling permissions on the record + AuthenticationUtil.runAs(new RunAsWork() + { + public Void doWork() throws Exception + { + assertEquals(AccessStatus.ALLOWED, permissionService.hasPermission(dmDocument, RMPermissionModel.FILING)); + assertEquals(AccessStatus.ALLOWED, permissionService.hasPermission(dmDocument, RMPermissionModel.READ_RECORDS)); + return null; + } + }, dmCollaborator); + + + // Check that the consumer has read permissions on the record + AuthenticationUtil.runAs(new RunAsWork() + { + public Void doWork() throws Exception + { + assertEquals(AccessStatus.DENIED, permissionService.hasPermission(dmDocument, RMPermissionModel.FILING)); + assertEquals(AccessStatus.ALLOWED, permissionService.hasPermission(dmDocument, RMPermissionModel.READ_RECORDS)); + return null; + } + }, dmConsumer); + + } + }); + } + + public void testFileInplaceRecordFromCollabSite() + { + doBehaviourDrivenTest(new BehaviourDrivenTest() + { + public void given() + { + // Check that the document is not a record + assertFalse("The document should not be a record", recordService.isRecord(dmDocument)); + + // Declare the document as a record + AuthenticationUtil.runAs(new RunAsWork() + { + public Void doWork() throws Exception + { + // Declare record + recordService.createRecord(filePlan, dmDocument); + + return null; + } + }, dmCollaborator); + + // Check that the document is a record + assertTrue("The document should be a record", recordService.isRecord(dmDocument)); + assertFalse("The record should not be filed", recordService.isFiled(dmDocument)); + } + + public void when() throws FileExistsException, FileNotFoundException + { + // file the document to a location in the file plan + fileFolderService.move(dmDocument, rmFolder, null); + } + + public void then() + { + // Check that the document is a record now + assertTrue("The document should be a record", recordService.isRecord(dmDocument)); + assertTrue("The record hsould be filed", recordService.isFiled(dmDocument)); + + // Check that the record is in the unfiled container + // TODO + + // Check that the record is still a child of the collaboration folder + // TODO + + // Check that the collaborator has filling permissions on the record + AuthenticationUtil.runAs(new RunAsWork() + { + public Void doWork() throws Exception + { + assertEquals(AccessStatus.ALLOWED, permissionService.hasPermission(dmDocument, RMPermissionModel.FILING)); + assertEquals(AccessStatus.ALLOWED, permissionService.hasPermission(dmDocument, RMPermissionModel.READ_RECORDS)); + return null; + } + }, dmCollaborator); + + + // Check that the consumer has read permissions on the record + AuthenticationUtil.runAs(new RunAsWork() + { + public Void doWork() throws Exception + { + assertEquals(AccessStatus.DENIED, permissionService.hasPermission(dmDocument, RMPermissionModel.FILING)); + assertEquals(AccessStatus.ALLOWED, permissionService.hasPermission(dmDocument, RMPermissionModel.READ_RECORDS)); + return null; + } + }, dmConsumer); + + } + }); + } +} diff --git a/rm-server/test/java/org/alfresco/module/org_alfresco_module_rm/test/integration/record/InplaceRecordPermissionTest.java b/rm-server/test/java/org/alfresco/module/org_alfresco_module_rm/test/integration/record/InplaceRecordPermissionTest.java new file mode 100644 index 0000000000..b972d7823a --- /dev/null +++ b/rm-server/test/java/org/alfresco/module/org_alfresco_module_rm/test/integration/record/InplaceRecordPermissionTest.java @@ -0,0 +1,951 @@ +/* + * #%L + * Alfresco Records Management Module + * %% + * Copyright (C) 2005 - 2016 Alfresco Software Limited + * %% + * This file is part of the Alfresco software. + * - + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * - + * 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 . + * #L% + */ + +package org.alfresco.module.org_alfresco_module_rm.test.integration.record; + +import static org.alfresco.module.org_alfresco_module_rm.test.util.bdt.BehaviourTest.test; + +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import org.alfresco.model.ContentModel; +import org.alfresco.module.org_alfresco_module_rm.action.impl.CutOffAction; +import org.alfresco.module.org_alfresco_module_rm.action.impl.DeclareRecordAction; +import org.alfresco.module.org_alfresco_module_rm.action.impl.DestroyAction; +import org.alfresco.module.org_alfresco_module_rm.capability.Capability; +import org.alfresco.module.org_alfresco_module_rm.capability.RMPermissionModel; +import org.alfresco.module.org_alfresco_module_rm.role.FilePlanRoleService; +import org.alfresco.module.org_alfresco_module_rm.test.util.BaseRMTestCase; +import org.alfresco.module.org_alfresco_module_rm.test.util.CommonRMTestUtils; +import org.alfresco.module.org_alfresco_module_rm.test.util.bdt.BehaviourTest; +import org.alfresco.repo.security.permissions.AccessDeniedException; +import org.alfresco.repo.site.SiteModel; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.NodeService; +import org.alfresco.service.cmr.security.AccessStatus; +import org.alfresco.service.cmr.security.PermissionService; +import org.alfresco.util.GUID; + +/** + * In-place record permission integration test. + * + * @author Roy Wetherall + * @since 2.5 + */ +public class InplaceRecordPermissionTest extends BaseRMTestCase +{ + /** capability list */ + private static final List CAPABILITIES = Stream + .of(RMPermissionModel.VIEW_RECORDS, + RMPermissionModel.EDIT_NON_RECORD_METADATA, + RMPermissionModel.EDIT_RECORD_METADATA) + .collect(Collectors.toList()); + + /** test data */ + private NodeRef contribDoc; + private NodeRef deleteUserDoc; + private NodeRef copiedDoc; + private NodeRef copyDoc; + private String deletedUser; + + /** services */ + private NodeService dbNodeService; + + /** capabilities */ + private Capability viewRecordsCapability; + private Capability editNonRecordMetadataCapability; + private Capability editRecordMetadataCapability; + + /** test characteristics */ + @Override protected boolean isCollaborationSiteTest() { return true; } + @Override protected boolean isUserTest() { return true; } + + /** + * @see org.alfresco.module.org_alfresco_module_rm.test.util.BaseRMTestCase#initServices() + */ + @Override + protected void initServices() + { + super.initServices(); + + // initialise behaviour tests + BehaviourTest.initBehaviourTests(retryingTransactionHelper); + + // get services + dbNodeService = (NodeService)applicationContext.getBean("dbNodeService"); + + // get capability references + viewRecordsCapability = capabilityService.getCapability(RMPermissionModel.VIEW_RECORDS); + editNonRecordMetadataCapability = capabilityService.getCapability(RMPermissionModel.EDIT_NON_RECORD_METADATA); + editRecordMetadataCapability = capabilityService.getCapability(RMPermissionModel.EDIT_RECORD_METADATA); + } + + /** + * Given a document in a collaboration site + * When a user without write permissions on the document tries to declare it as a record + * Then the declaration fails + * And the document does not become a record + */ + public void testUserWithOutWriteCantDeclareInPlaceRecord() + { + test() + .given() + + // Given a document in a collaboration site that is not a record + .expect(false) + .from(() -> recordService.isRecord(dmDocument)) + .because("The document is not a record.") + + // And a user with no write permission on the document + .as(userName) + .expect(AccessStatus.DENIED.toString()) + .from(() -> permissionService.hasPermission(dmDocument, PermissionService.WRITE).toString()) + .because("User does not have write access to document.") + + // When the user tries to declare the record + // Then we expect this to fail + .when() + .as(userName) + .expectException(AccessDeniedException.class) + .from(() -> recordService.createRecord(filePlan, dmDocument)) + .because("The user does not have write permission on the document."); + } + + /** + * Given a document in a collaboration site that is not a record + * And a contributor the didn't create the document + * When the contributor tries to declare the document as a record + * Then the document does not become a record + */ + public void testContributorThatIsntOwnerDeclareInPlaceRecord() + { + test() + .given() + + // Given a document in a collaboration site that is not a record + .expect(false) + .from(() -> recordService.isRecord(dmDocument)) + .because("The document is not a record.") + + // And a contributor the didn't create the document + .as(dmContributor) + .expect(AccessStatus.DENIED.toString()) + .from(() -> permissionService.hasPermission(dmDocument, PermissionService.WRITE).toString()) + .because("Contributor does not have write access to document.") + + // When the user tries to declare the record + // When the contributor tries to declare the document as a record + .when() + .as(dmContributor) + .expectException(AccessDeniedException.class) + .from(() -> recordService.createRecord(filePlan, dmDocument)) + .because("The contributor does not have write permission on the document."); + } + + /** + * Given a document in a collaboration site is not a record + * When the document is declared by a site collaborator + * Then the document becomes a record + * And the site users have the appropriate in-place permissions on the record + */ + public void testCreateInplaceRecordFromCollabSite() + { + test() + + // Given that a document in a collaboration site is not a record + .given() + .asAdmin() + .expect(false) + .from(() -> recordService.isRecord(dmDocument)) + .because("The document is not a record") + + // When it is declared as an inplace record + .when() + .as(dmCollaborator) + .perform(() -> recordService.createRecord(filePlan, dmDocument)) + + .then() + .asAdmin() + // Then it becomes a record + .expect(true) + .from(() -> recordService.isRecord(dmDocument)) + .because("The document is a record") + + // And it isn't filed + .expect(false) + .from(() -> recordService.isFiled(dmDocument)) + .because("The record is not filed") + + // And a site collaborator has filling permissions and filling capability on the record + .as(dmCollaborator) + .perform(() -> + checkInPlaceAccess(dmDocument, + AccessStatus.ALLOWED, // read record permission + AccessStatus.ALLOWED, // filing permission + AccessStatus.ALLOWED, // view record capability + AccessStatus.ALLOWED, // edit non record metadata capability + AccessStatus.DENIED)) // edit record metadata capability + + // And a site contributor has read and view + .as(dmContributor) + .perform(() -> + checkInPlaceAccess(dmDocument, + AccessStatus.ALLOWED, // read record permission + AccessStatus.DENIED, // filing permission + AccessStatus.ALLOWED, // view record capability + AccessStatus.DENIED, // edit non record metadata capability + AccessStatus.DENIED)) // edit record metadata capability + + // And a site consumer has read permissions and view record capability on the record + .as(dmConsumer) + .perform(() -> + checkInPlaceAccess(dmDocument, + AccessStatus.ALLOWED, // read record permission + AccessStatus.DENIED, // filing permission + AccessStatus.ALLOWED, // view record capability + AccessStatus.DENIED, // edit non record metadata capability + AccessStatus.DENIED)) // edit record metadata capability + + // And a user that is not a member of the site has no access to the inplace record + .as(userName) + .perform(() -> + checkInPlaceAccess(dmDocument, + AccessStatus.DENIED, // read record permission + AccessStatus.DENIED, // filing permission + AccessStatus.DENIED, // view record capability + AccessStatus.DENIED, // edit non record metadata capability + AccessStatus.DENIED)); // edit record metadata capability + } + + /** + * Helper method to check in place access for a user on a record. + */ + private void checkInPlaceAccess(NodeRef nodeRef, AccessStatus ... accessStatus) + { + // check permission access + assertEquals("Incorrect read record permission access.", accessStatus[0], permissionService.hasPermission(nodeRef, RMPermissionModel.READ_RECORDS)); + assertEquals("Incorrect filling permission access.", accessStatus[1], permissionService.hasPermission(nodeRef, RMPermissionModel.FILING)); + + // check capability access + Map access = capabilityService.getCapabilitiesAccessState(nodeRef, CAPABILITIES); + assertEquals("Incorrect view records capability access", accessStatus[2], access.get(viewRecordsCapability)); + assertEquals("Incorrect edit non record metadata capability access", accessStatus[3], access.get(editNonRecordMetadataCapability)); + assertEquals("Incorrect edit record metadata capability access", accessStatus[4], access.get(editRecordMetadataCapability)); + } + + /** + * Given that a document is created by contributor + * When it is declared as an inplace record + * Then it becomes a record + * And it isn't filed + * And a site collaborator has filling permissions and filling capability on the record + * And a site contributor has filling capability and permissions + * And a site consumer has read permissions and view record capability on the record + * And a user that is not a member of the site has no access to the inplace record + */ + public void testCreateInplaceRecordFromCollabSiteWhenContribIsCreatorOfDocument() + { + test() + + // Given that a document is created by contributor + .given() + .as(dmContributor) + .perform(() -> + { + contribDoc = fileFolderService.create(dmFolder, "contrib.txt" , ContentModel.TYPE_CONTENT).getNodeRef(); + dbNodeService.addAspect(contribDoc, ContentModel.ASPECT_AUDITABLE, null); + }) + .expect(false) + .from(() -> recordService.isRecord(contribDoc)) + .because("It is not a record.") + .asAdmin() + .expect(dmContributor) + .from(() -> ownableService.getOwner(contribDoc)) + .because("As the creator of the document the contributor is also the owner") + .as(dmContributor) + .expect(AccessStatus.ALLOWED.toString()) + .from(() -> permissionService.hasPermission(contribDoc, PermissionService.WRITE).toString()) + .because("Contrib user has write permissions on created document as the owner.") + + // When it is declared as an inplace record + .when() + .as(dmContributor) + .perform(() -> recordService.createRecord(filePlan, contribDoc)) + + .then() + .asAdmin() + // Then it becomes a record + .expect(true) + .from(() -> recordService.isRecord(contribDoc)) + .because("The document is a record") + + // And it isn't filed + .expect(false) + .from(() -> recordService.isFiled(contribDoc)) + .because("The record is not filed") + + // And a site collaborator has filling permissions and filling capability on the record + .as(dmCollaborator) + .perform(() -> + checkInPlaceAccess(contribDoc, + AccessStatus.ALLOWED, // read record permission + AccessStatus.ALLOWED, // filing permission + AccessStatus.ALLOWED, // view record capability + AccessStatus.ALLOWED, // edit non record metadata capability + AccessStatus.DENIED)) // edit record metadata capability + + // And a site contributor has filling capability and permissions + .as(dmContributor) + .perform(() -> + checkInPlaceAccess(contribDoc, + AccessStatus.ALLOWED, // read record permission + AccessStatus.ALLOWED, // filing permission + AccessStatus.ALLOWED, // view record capability + AccessStatus.ALLOWED, // edit non record metadata capability + AccessStatus.DENIED)) // edit record metadata capability + + // And a site consumer has read permissions and view record capability on the record + .as(dmConsumer) + .perform(() -> + checkInPlaceAccess(contribDoc, + AccessStatus.ALLOWED, // read record permission + AccessStatus.DENIED, // filing permission + AccessStatus.ALLOWED, // view record capability + AccessStatus.DENIED, // edit non record metadata capability + AccessStatus.DENIED)) // edit record metadata capability + + // And a user that is not a member of the site has no access to the inplace record + .as(userName) + .perform(() -> + checkInPlaceAccess(contribDoc, + AccessStatus.DENIED, // read record permission + AccessStatus.DENIED, // filing permission + AccessStatus.DENIED, // view record capability + AccessStatus.DENIED, // edit non record metadata capability + AccessStatus.DENIED)); // edit record metadata capability + } + + /** + * Given an unfiled in-place record + * When the record is moved to the file plan (ie filed) + * Then the site users still have the appropriate in-place permissions on the record + */ + public void testFileInplaceRecordFromCollabSite() throws Exception + { + test() + + // Given an unfiled inplace record + .given() + .as(dmCollaborator) + .perform(() -> recordService.createRecord(filePlan, dmDocument)) + .expect(true) + .from(() -> recordService.isRecord(dmDocument)) + .because("The document is a record.") + .expect(false) + .from(() -> recordService.isFiled(dmDocument)) + .because("The record is not filed") + + // When the record is filed + .when() + .asAdmin() + .perform(() -> fileFolderService.move(dmDocument, rmFolder, null)) + + .then() + + // Then the record is filed + .asAdmin() + .expect(true) + .from(() -> recordService.isFiled(dmDocument)) + .because("The record is filed.") + + // And the collaborator has filling permissions and filling capability on the record + .as(dmCollaborator) + .perform(() -> + checkInPlaceAccess(dmDocument, + AccessStatus.ALLOWED, // read record permission + AccessStatus.ALLOWED, // filing permission + AccessStatus.ALLOWED, // view record capability + AccessStatus.ALLOWED, // edit non record metadata capability + AccessStatus.DENIED)) // edit record metadata capability + + // And a site contributor has read and view + .as(dmContributor) + .perform(() -> + checkInPlaceAccess(dmDocument, + AccessStatus.ALLOWED, // read record permission + AccessStatus.DENIED, // filing permission + AccessStatus.ALLOWED, // view record capability + AccessStatus.DENIED, // edit non record metadata capability + AccessStatus.DENIED)) // edit record metadata capability + + // And the consumer has read permissions and view record capability on the record + .as(dmConsumer) + .perform(() -> + checkInPlaceAccess(dmDocument, + AccessStatus.ALLOWED, // read record permission + AccessStatus.DENIED, // filing permission + AccessStatus.ALLOWED, // view record capability + AccessStatus.DENIED, // edit non record metadata capability + AccessStatus.DENIED)) // edit record metadata capability + + // And a user that is not in the site has no permissions on the record + .as(userName) + .perform(() -> + checkInPlaceAccess(dmDocument, + AccessStatus.DENIED, // read record permission + AccessStatus.DENIED, // filing permission + AccessStatus.DENIED, // view record capability + AccessStatus.DENIED, // edit non record metadata capability + AccessStatus.DENIED)); // edit record metadata capability + } + + /** + * Given an incomplete inplace record + * When it is completed + * Then the inplace users still have access to the record + * And can't edit the records meta-data + */ + public void testCompletedInPlaceRecord() + { + test() + + // Given an incomplete record + .given() + .as(dmCollaborator) + .perform(() -> recordService.createRecord(filePlan, dmDocument)) + .expect(false) + .from(() -> recordService.isDeclared(dmDocument)) + .because("Record is not complete.") + + // When it is completed + .when() + .asAdmin() + .perform(() -> rmActionService.executeRecordsManagementAction(dmDocument, DeclareRecordAction.NAME)) + .expect(true) + .from(() -> recordService.isDeclared(dmDocument)) + .because("Record is complete.") + + .then() + + // Then the collaborator has filling permissions, view record capability, but not edit non-record metadata + .as(dmCollaborator) + .perform(() -> + checkInPlaceAccess(dmDocument, + AccessStatus.ALLOWED, // read record permission + AccessStatus.ALLOWED, // filing permission + AccessStatus.ALLOWED, // view record capability + AccessStatus.DENIED, // edit non record metadata capability + AccessStatus.DENIED)) // edit record metadata capability + + // And a site contributor has read and view + .as(dmContributor) + .perform(() -> + checkInPlaceAccess(dmDocument, + AccessStatus.ALLOWED, // read record permission + AccessStatus.DENIED, // filing permission + AccessStatus.ALLOWED, // view record capability + AccessStatus.DENIED, // edit non record metadata capability + AccessStatus.DENIED)) // edit record metadata capability + + // And the consumer has read permissions and view record capability on the record + .as(dmConsumer) + .perform(() -> + checkInPlaceAccess(dmDocument, + AccessStatus.ALLOWED, // read record permission + AccessStatus.DENIED, // filing permission + AccessStatus.ALLOWED, // view record capability + AccessStatus.DENIED, // edit non record metadata capability + AccessStatus.DENIED)) // edit record metadata capability + + // And a user that is not in the site has no permissions on the record + .as(userName) + .perform(() -> + checkInPlaceAccess(dmDocument, + AccessStatus.DENIED, // read record permission + AccessStatus.DENIED, // filing permission + AccessStatus.DENIED, // view record capability + AccessStatus.DENIED, // edit non record metadata capability + AccessStatus.DENIED)); // edit record metadata capability + } + + /** + * Given an inplace record ready for destruction + * When it is destroyed + * And it's metadata is maintained + * Then the inplace users will no longer see the record + */ + public void testDestroyedRecordInplacePermissions() + { + test() + .given() + + // Given that a record is declared by a collaborator + .as(dmCollaborator) + .perform(() -> recordService.createRecord(filePlan, dmDocument)) + .expect(true) + .from(() -> recordService.isRecord(dmDocument)) + .because("Document is a record.") + + // And it is filed into the file plan + // And eligible for destruction + .asAdmin() + .perform(() -> + { + // create record category and disposition schedule + NodeRef recordCategory = filePlanService.createRecordCategory(filePlan, GUID.generate()); + utils.createBasicDispositionSchedule(recordCategory, GUID.generate(), GUID.generate(), true, true); + + // create record folder and file record + NodeRef recordFolder = recordFolderService.createRecordFolder(recordCategory, GUID.generate()); + fileFolderService.move(dmDocument, recordFolder, null); + + // cut off record + rmActionService.executeRecordsManagementAction(dmDocument, DeclareRecordAction.NAME); + utils.completeEvent(dmDocument, CommonRMTestUtils.DEFAULT_EVENT_NAME); + rmActionService.executeRecordsManagementAction(dmDocument, CutOffAction.NAME); + }) + .expect("destroy") + .from(() -> dispositionService.getNextDispositionAction(dmDocument).getName()) + .because("The next action is destroy.") + .expect(true) + .from(() -> dispositionService.isNextDispositionActionEligible(dmDocument)) + .because("The next action is eligible.") + + // When the record is destroyed + .when(() -> rmActionService.executeRecordsManagementAction(dmDocument, DestroyAction.NAME)) + + .then() + .expect(true) + .from(() -> recordService.isMetadataStub(dmDocument)) + .because("The record has been destroyed and the meta-stub remains.") + + // Then the collaborator has no permissions or capabilities + .as(dmCollaborator) + .perform(() -> + checkInPlaceAccess(dmDocument, + AccessStatus.DENIED, // read record permission + AccessStatus.DENIED, // filing permission + AccessStatus.DENIED, // view record capability + AccessStatus.DENIED, // edit non record metadata capability + AccessStatus.DENIED)) // edit record metadata capability + + // And a site contributor has no permissions or capabilities + .as(dmContributor) + .perform(() -> + checkInPlaceAccess(dmDocument, + AccessStatus.DENIED, // read record permission + AccessStatus.DENIED, // filing permission + AccessStatus.DENIED, // view record capability + AccessStatus.DENIED, // edit non record metadata capability + AccessStatus.DENIED)) // edit record metadata capability + + // And the consumer has no permissions or capabilities + .as(dmConsumer) + .perform(() -> + checkInPlaceAccess(dmDocument, + AccessStatus.DENIED, // read record permission + AccessStatus.DENIED, // filing permission + AccessStatus.DENIED, // view record capability + AccessStatus.DENIED, // edit non record metadata capability + AccessStatus.DENIED)) // edit record metadata capability + + // And a user that is not in the site has no permissions or capabilities + .as(userName) + .perform(() -> + checkInPlaceAccess(dmDocument, + AccessStatus.DENIED, // read record permission + AccessStatus.DENIED, // filing permission + AccessStatus.DENIED, // view record capability + AccessStatus.DENIED, // edit non record metadata capability + AccessStatus.DENIED)); // edit record metadata capability + } + + /** + * Given an inplace record + * And the collaborator has view and edit non-record capability + * And doesn't have edit record capability + * When we add edit record metadata capability to the extended writer role + * Then the collaborator now has edit record metadata capability + */ + public void testAddUserToRole() + { + test() + .given() + .as(dmCollaborator) + + // Given an inplace record + .perform(() -> recordService.createRecord(filePlan, dmDocument)) + .expect(true) + .from(() -> recordService.isRecord(dmDocument)) + .because("Document is a record.") + + // And the collaborator has view and edit non-record capability + // And doesn't have edit record capability + .perform(() -> + checkInPlaceAccess(dmDocument, + AccessStatus.ALLOWED, // read record permission + AccessStatus.ALLOWED, // filing permission + AccessStatus.ALLOWED, // view record capability + AccessStatus.ALLOWED, // edit non record metadata capability + AccessStatus.DENIED)) // edit record metadata capability + .when() + .asAdmin() + + // When we add edit record metadata capability to the extended writer role + .perform(() -> filePlanRoleService.updateRole(filePlan, + FilePlanRoleService.ROLE_EXTENDED_WRITERS, + "", + Stream + .of(viewRecordsCapability, editNonRecordMetadataCapability, editRecordMetadataCapability) + .collect(Collectors.toSet()))) + + .then() + .as(dmCollaborator) + + // Then the collaborator now has edit record metadata capability + .perform(() -> + checkInPlaceAccess(dmDocument, + AccessStatus.ALLOWED, // read record permission + AccessStatus.ALLOWED, // filing permission + AccessStatus.ALLOWED, // view record capability + AccessStatus.ALLOWED, // edit non record metadata capability + AccessStatus.ALLOWED)) // edit record metadata capability + ; + } + + /** + * Given an inplace record + * When the record is hidden + * Then the collaborator has no access to the record + * And the consumer has no access to the record + * And a user that is not in the site has no permissions or capabilities + */ + public void testNoPermissionsAfterHide() + { + test() + .given() + .as(dmCollaborator) + + // Given an inplace record + .perform(() -> recordService.createRecord(filePlan, dmDocument)) + .expect(true) + .from(() -> recordService.isRecord(dmDocument)) + .because("Document is a record.") + .when() + .asAdmin() + + // When the record is hidden + .perform(() -> inplaceRecordService.hideRecord(dmDocument)) + + .then() + + // Then the collaborator has no access to the record + .as(dmCollaborator) + .perform(() -> + checkInPlaceAccess(dmDocument, + AccessStatus.DENIED, // read record permission + AccessStatus.DENIED, // filing permission + AccessStatus.DENIED, // view record capability + AccessStatus.DENIED, // edit non record metadata capability + AccessStatus.DENIED)) // edit record metadata capability + + // And a site contributor has read and view + .as(dmContributor) + .perform(() -> + checkInPlaceAccess(dmDocument, + AccessStatus.DENIED, // read record permission + AccessStatus.DENIED, // filing permission + AccessStatus.DENIED, // view record capability + AccessStatus.DENIED, // edit non record metadata capability + AccessStatus.DENIED)) // edit record metadata capability + + // And the consumer has no access to the record + .as(dmConsumer) + .perform(() -> + checkInPlaceAccess(dmDocument, + AccessStatus.DENIED, // read record permission + AccessStatus.DENIED, // filing permission + AccessStatus.DENIED, // view record capability + AccessStatus.DENIED, // edit non record metadata capability + AccessStatus.DENIED)) // edit record metadata capability + + // And a user that is not in the site has no permissions or capabilities + .as(userName) + .perform(() -> + checkInPlaceAccess(dmDocument, + AccessStatus.DENIED, // read record permission + AccessStatus.DENIED, // filing permission + AccessStatus.DENIED, // view record capability + AccessStatus.DENIED, // edit non record metadata capability + AccessStatus.DENIED)); // edit record metadata capability + ; + } + + /** + * Given an inplace record + * When the record is rejected + * Then the collaborator has no access to the record + * And the consumer has no access to the record + * And a user that is not in the site has no permissions or capabilities + */ + public void testNoPermissionsAfterReject() + { + test() + .given() + .as(dmCollaborator) + + // Given an inplace record + .perform(() -> recordService.createRecord(filePlan, dmDocument)) + .expect(true) + .from(() -> recordService.isRecord(dmDocument)) + .because("Document is a record.") + .when() + .asAdmin() + + // When the record is rejected + .perform(() -> recordService.rejectRecord(dmDocument, GUID.generate())) + + .then() + + // Then the collaborator has no access to the record + .as(dmCollaborator) + .perform(() -> + checkInPlaceAccess(dmDocument, + AccessStatus.DENIED, // read record permission + AccessStatus.DENIED, // filing permission + AccessStatus.DENIED, // view record capability + AccessStatus.DENIED, // edit non record metadata capability + AccessStatus.DENIED)) // edit record metadata capability + + // And a site contributor has read and view + .as(dmContributor) + .perform(() -> + checkInPlaceAccess(dmDocument, + AccessStatus.DENIED, // read record permission + AccessStatus.DENIED, // filing permission + AccessStatus.DENIED, // view record capability + AccessStatus.DENIED, // edit non record metadata capability + AccessStatus.DENIED)) // edit record metadata capability + + // And the consumer has no access to the record + .as(dmConsumer) + .perform(() -> + checkInPlaceAccess(dmDocument, + AccessStatus.DENIED, // read record permission + AccessStatus.DENIED, // filing permission + AccessStatus.DENIED, // view record capability + AccessStatus.DENIED, // edit non record metadata capability + AccessStatus.DENIED)) // edit record metadata capability + + // And a user that is not in the site has no permissions or capabilities + .as(userName) + .perform(() -> + checkInPlaceAccess(dmDocument, + AccessStatus.DENIED, // read record permission + AccessStatus.DENIED, // filing permission + AccessStatus.DENIED, // view record capability + AccessStatus.DENIED, // edit non record metadata capability + AccessStatus.DENIED)); // edit record metadata capability + ; + } + + /** + * Given a user is the cm:creator of a document + * And the user is deleted + * When the document is declared as a record by a manager + * Then it successfully becomes a record + */ + public void testCmCreatorDeletedBeforeRecordDeclaration() + { + test() + .given() + .asAdmin() + .perform(() -> + { + deletedUser = GUID.generate(); + createPerson(deletedUser); + siteService.setMembership(collabSiteId, deletedUser, SiteModel.SITE_CONTRIBUTOR); + }) + .as(deletedUser) + .perform(() -> + { + deleteUserDoc = fileFolderService.create(dmFolder, "deleteUserDoc.txt" , ContentModel.TYPE_CONTENT).getNodeRef(); + dbNodeService.addAspect(deleteUserDoc, ContentModel.ASPECT_AUDITABLE, null); + }) + .asAdmin() + .perform(() -> personService.deletePerson(deletedUser)) + .when() + .as(dmCollaborator) + .perform(() -> recordService.createRecord(filePlan, deleteUserDoc)) + .then() + .expect(true) + .from(() -> recordService.isRecord(deleteUserDoc)) + .because("The document is now a record.") + ; + } + + /** + * Given a document created by the collaborator + * And declared as a record by the collaborator + * And filed by the records manager + * When the records manager copies the record + * Then the collaborator has no access to the record copy + * And a site contributor has no access to the record copy + * And the consumer has no access to the record copy + * And a user that is not in the site has no access to the record copy + */ + public void testNoPermissionsOnCopy() + { + test() + .given() + .as(dmCollaborator) + .perform(() -> + { + // Given a document created by the collaborator + copiedDoc = fileFolderService.create(dmFolder, "copiedDoc.txt" , ContentModel.TYPE_CONTENT).getNodeRef(); + dbNodeService.addAspect(copiedDoc, ContentModel.ASPECT_AUDITABLE, null); + + // And declared as a record by the collaborator + recordService.createRecord(filePlan, copiedDoc); + }) + .asAdmin() + + // And filed by the records manager + .perform(() -> fileFolderService.move(copiedDoc, rmFolder, null)) + + .as(dmCollaborator) + .perform(() -> + checkInPlaceAccess(copiedDoc, + AccessStatus.ALLOWED, // read record permission + AccessStatus.ALLOWED, // filing permission + AccessStatus.ALLOWED, // view record capability + AccessStatus.ALLOWED, // edit non record metadata capability + AccessStatus.DENIED)) // edit record metadata capability + + .when() + .asAdmin() + + // When the records manager copies the record + .perform(() -> copyDoc = fileFolderService.copy(copiedDoc, rmFolder, "newRecord.txt").getNodeRef()) + + .then() + + // Then the collaborator has no access to the record copy + .as(dmCollaborator) + .perform(() -> + checkInPlaceAccess(copyDoc, + AccessStatus.DENIED, // read record permission + AccessStatus.DENIED, // filing permission + AccessStatus.DENIED, // view record capability + AccessStatus.DENIED, // edit non record metadata capability + AccessStatus.DENIED)) // edit record metadata capability + .perform(() -> + checkInPlaceAccess(copiedDoc, + AccessStatus.ALLOWED, // read record permission + AccessStatus.ALLOWED, // filing permission + AccessStatus.ALLOWED, // view record capability + AccessStatus.ALLOWED, // edit non record metadata capability + AccessStatus.DENIED)) // edit record metadata capability + + // And a site contributor has no access to the record copy + .as(dmContributor) + .perform(() -> + checkInPlaceAccess(copyDoc, + AccessStatus.DENIED, // read record permission + AccessStatus.DENIED, // filing permission + AccessStatus.DENIED, // view record capability + AccessStatus.DENIED, // edit non record metadata capability + AccessStatus.DENIED)) // edit record metadata capability + + // And the consumer has no access to the record copy + .as(dmConsumer) + .perform(() -> + checkInPlaceAccess(copyDoc, + AccessStatus.DENIED, // read record permission + AccessStatus.DENIED, // filing permission + AccessStatus.DENIED, // view record capability + AccessStatus.DENIED, // edit non record metadata capability + AccessStatus.DENIED)) // edit record metadata capability + + // And a user that is not in the site has no access to the record copy + .as(userName) + .perform(() -> + checkInPlaceAccess(copyDoc, + AccessStatus.DENIED, // read record permission + AccessStatus.DENIED, // filing permission + AccessStatus.DENIED, // view record capability + AccessStatus.DENIED, // edit non record metadata capability + AccessStatus.DENIED)); // edit record metadata capability + ; + } + + /** + * Test group reuse + */ + public void testGroupReuse() + { + test() + .when() + .as(dmCollaborator) + .perform(50, () -> + { + NodeRef newDocument = fileFolderService.create(dmFolder, GUID.generate(), ContentModel.TYPE_CONTENT).getNodeRef(); + recordService.createRecord(filePlan, newDocument); + }) + .as(dmContributor) + .perform(50, () -> + { + NodeRef newDocument = fileFolderService.create(dmFolder, GUID.generate(), ContentModel.TYPE_CONTENT).getNodeRef(); + recordService.createRecord(filePlan, newDocument); + }) + .then() + .asAdmin() + .expect(101) + .from(() -> nodeService.getChildAssocs(dmFolder).size()) + .because("One hundred inplace records have been created.") + .expect(3) + .from(() -> authorityService.getContainedAuthorities(null, "GROUP_INPLACE_RECORD_MANAGEMENT", true).size()) + .because("The read and write groups are reused."); + ; + } + + /** + * Test tear down + */ + @Override + protected void tearDownImpl() + { + super.tearDownImpl(); + + // clear up groups + authorityService.getContainedAuthorities(null, "GROUP_INPLACE_RECORD_MANAGEMENT", true) + .stream() + .forEach((group) -> authorityService.deleteAuthority(group)); + } +} diff --git a/rm-server/test/java/org/alfresco/module/org_alfresco_module_rm/test/integration/record/MoveInplaceRecordTest.java b/rm-server/test/java/org/alfresco/module/org_alfresco_module_rm/test/integration/record/MoveInplaceRecordTest.java index 9c05652660..d647def3e5 100644 --- a/rm-server/test/java/org/alfresco/module/org_alfresco_module_rm/test/integration/record/MoveInplaceRecordTest.java +++ b/rm-server/test/java/org/alfresco/module/org_alfresco_module_rm/test/integration/record/MoveInplaceRecordTest.java @@ -60,6 +60,9 @@ public class MoveInplaceRecordTest extends BaseRMTestCase // Extended Readers/Writers private Set extendedReadersBeforeMove; private Set extendedWritersBeforeMove; + + // primary parent of record + private NodeRef primaryParentBeforeMove; public void given() { @@ -84,8 +87,14 @@ public class MoveInplaceRecordTest extends BaseRMTestCase // Check that the document is a record now assertTrue(recordService.isRecord(dmDocument)); - extendedReadersBeforeMove = extendedSecurityService.getExtendedReaders(dmDocument); - extendedWritersBeforeMove = extendedSecurityService.getExtendedWriters(dmDocument); + extendedReadersBeforeMove = extendedSecurityService.getReaders(dmDocument); + extendedWritersBeforeMove = extendedSecurityService.getWriters(dmDocument); + + // get the primary parent and assert that it's a record management artifact + primaryParentBeforeMove = nodeService.getPrimaryParent(dmDocument).getParentRef(); + assertTrue("Primary parent of newly created should be a records management artifact.", + filePlanService.isFilePlanComponent(primaryParentBeforeMove)); + } public void when() @@ -105,15 +114,27 @@ public class MoveInplaceRecordTest extends BaseRMTestCase public void then() { + // assert that the document is still a record + assertTrue("After move the document should still be a record.", + recordService.isRecord(dmDocument)); + // Check that the source folder is empty now and the destination folder has the document assertEquals(0, nodeService.getChildAssocs(dmFolder).size()); List destinationFolderChildAssocs = nodeService.getChildAssocs(destinationDmFolder); assertEquals(1, destinationFolderChildAssocs.size()); assertEquals(dmDocument, destinationFolderChildAssocs.get(0).getChildRef()); - + + // Check that the primary parent of the record has remained unchanged + NodeRef primaryParentAfterMove = nodeService.getPrimaryParent(dmDocument).getParentRef(); + assertTrue("Primary parent of record after inplace move should be a records management artifact.", + filePlanService.isFilePlanComponent(primaryParentAfterMove)); + assertEquals("Primary parent of record after inplace move should remain the same.", + primaryParentBeforeMove, + primaryParentAfterMove); + // Check extended readers/writers - Set extendedReadersAfterMove = extendedSecurityService.getExtendedReaders(dmDocument); - Set extendedWritersAfterMove = extendedSecurityService.getExtendedWriters(dmDocument); + Set extendedReadersAfterMove = extendedSecurityService.getReaders(dmDocument); + Set extendedWritersAfterMove = extendedSecurityService.getWriters(dmDocument); assertEquals(extendedReadersBeforeMove.size(), extendedReadersAfterMove.size()); assertEquals(extendedWritersBeforeMove.size(), extendedWritersAfterMove.size()); diff --git a/rm-server/test/java/org/alfresco/module/org_alfresco_module_rm/test/legacy/action/FileReportActionTest.java b/rm-server/test/java/org/alfresco/module/org_alfresco_module_rm/test/legacy/action/FileReportActionTest.java index 5b0c8a11b0..0a0573aae7 100644 --- a/rm-server/test/java/org/alfresco/module/org_alfresco_module_rm/test/legacy/action/FileReportActionTest.java +++ b/rm-server/test/java/org/alfresco/module/org_alfresco_module_rm/test/legacy/action/FileReportActionTest.java @@ -21,6 +21,7 @@ package org.alfresco.module.org_alfresco_module_rm.test.legacy.action; import org.alfresco.module.org_alfresco_module_rm.action.impl.FileReportAction; import org.alfresco.module.org_alfresco_module_rm.test.util.BaseRMTestCase; import org.alfresco.repo.content.MimetypeMap; +import org.alfresco.repo.security.authentication.AuthenticationUtil; import org.alfresco.service.cmr.action.Action; import org.alfresco.service.cmr.repository.NodeRef; import org.apache.commons.lang.StringUtils; @@ -53,6 +54,8 @@ public class FileReportActionTest extends BaseRMTestCase private void fileReport(final String mimeType) { + AuthenticationUtil.setAdminUserAsFullyAuthenticatedUser(); + // create record folder final NodeRef recordFolder = recordFolderService.createRecordFolder(rmContainer, GUID.generate()); diff --git a/rm-server/test/java/org/alfresco/module/org_alfresco_module_rm/test/legacy/action/FileToActionTest.java b/rm-server/test/java/org/alfresco/module/org_alfresco_module_rm/test/legacy/action/FileToActionTest.java index f1d3c9d8a9..24c26bd7ad 100644 --- a/rm-server/test/java/org/alfresco/module/org_alfresco_module_rm/test/legacy/action/FileToActionTest.java +++ b/rm-server/test/java/org/alfresco/module/org_alfresco_module_rm/test/legacy/action/FileToActionTest.java @@ -29,6 +29,7 @@ import org.alfresco.module.org_alfresco_module_rm.action.impl.FileToAction; import org.alfresco.module.org_alfresco_module_rm.capability.Capability; import org.alfresco.module.org_alfresco_module_rm.capability.RMPermissionModel; import org.alfresco.module.org_alfresco_module_rm.test.util.BaseRMTestCase; +import org.alfresco.repo.security.authentication.AuthenticationUtil; import org.alfresco.service.cmr.model.FileInfo; import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.security.AccessStatus; @@ -109,16 +110,27 @@ public class FileToActionTest extends BaseRMTestCase // create record from document recordService.createRecord(filePlan, dmDocument); - // check things have gone according to plan - assertTrue(recordService.isRecord(dmDocument)); - assertFalse(recordService.isFiled(dmDocument)); - - // is the unfiled container the primary parent of the filed record - NodeRef parent = nodeService.getPrimaryParent(dmDocument).getParentRef(); - assertEquals(filePlanService.getUnfiledContainer(filePlan), parent); - return null; } + + @Override + public void test(Void result) throws Exception + { + AuthenticationUtil.runAs(() -> + { + // check things have gone according to plan + assertTrue(recordService.isRecord(dmDocument)); + assertFalse(recordService.isFiled(dmDocument)); + + // is the unfiled container the primary parent of the filed record + NodeRef parent = nodeService.getPrimaryParent(dmDocument).getParentRef(); + assertEquals(filePlanService.getUnfiledContainer(filePlan), parent); + + return null; + }, + AuthenticationUtil.getAdminUserName()); + } + }, dmCollaborator); } diff --git a/rm-server/test/java/org/alfresco/module/org_alfresco_module_rm/test/legacy/action/RejectActionTest.java b/rm-server/test/java/org/alfresco/module/org_alfresco_module_rm/test/legacy/action/RejectActionTest.java index 9b84cd3876..adee075ed1 100644 --- a/rm-server/test/java/org/alfresco/module/org_alfresco_module_rm/test/legacy/action/RejectActionTest.java +++ b/rm-server/test/java/org/alfresco/module/org_alfresco_module_rm/test/legacy/action/RejectActionTest.java @@ -104,7 +104,8 @@ public class RejectActionTest extends BaseRMTestCase assertTrue(nodeService.getParentAssocs(dmDocument).size() == 1); // The extended reader information should be removed - assertNull(extendedSecurityService.getExtendedReaders(dmDocument)); + assertFalse(extendedSecurityService.hasExtendedSecurity(dmDocument)); + assertTrue(extendedSecurityService.getReaders(dmDocument).isEmpty()); return null; } diff --git a/rm-server/test/java/org/alfresco/module/org_alfresco_module_rm/test/legacy/service/ExtendedSecurityServiceImplTest.java b/rm-server/test/java/org/alfresco/module/org_alfresco_module_rm/test/legacy/service/ExtendedSecurityServiceImplTest.java index 324b0ce759..6bf3110943 100644 --- a/rm-server/test/java/org/alfresco/module/org_alfresco_module_rm/test/legacy/service/ExtendedSecurityServiceImplTest.java +++ b/rm-server/test/java/org/alfresco/module/org_alfresco_module_rm/test/legacy/service/ExtendedSecurityServiceImplTest.java @@ -1,26 +1,33 @@ /* - * Copyright (C) 2005-2014 Alfresco Software Limited. - * - * This file is part of Alfresco - * +it st * #%L + * Alfresco Records Management Module + * %% + * Copyright (C) 2005 - 2016 Alfresco Software Limited + * %% + * This file is part of the Alfresco software. + * - + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * - * 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 . + * #L% */ + package org.alfresco.module.org_alfresco_module_rm.test.legacy.service; -import java.util.HashMap; import java.util.HashSet; -import java.util.Map; import java.util.Set; import org.alfresco.model.ContentModel; @@ -92,76 +99,29 @@ public class ExtendedSecurityServiceImplTest extends BaseRMTestCase assertFalse(extendedSecurityService.hasExtendedSecurity(rmFolder)); assertFalse(extendedSecurityService.hasExtendedSecurity(record)); - assertNull(extendedSecurityService.getExtendedReaders(record)); - assertNull(extendedSecurityService.getExtendedWriters(record)); + assertTrue(extendedSecurityService.getReaders(record).isEmpty()); + assertTrue(extendedSecurityService.getWriters(record).isEmpty()); Set extendedReaders = new HashSet(2); extendedReaders.add(monkey); extendedReaders.add(elephant); - extendedSecurityService.addExtendedSecurity(record, extendedReaders, null); - - Map testMap = new HashMap(2); - testMap.put(monkey, Integer.valueOf(1)); - testMap.put(elephant, Integer.valueOf(1)); - - checkExtendedReaders(filePlan, testMap); - checkExtendedReaders(rmContainer, testMap); - checkExtendedReaders(rmFolder, testMap); - checkExtendedReaders(record, testMap); + extendedSecurityService.set(record, extendedReaders, null); + checkExtendedReaders(record, extendedReaders); Set extendedReadersToo = new HashSet(2); extendedReadersToo.add(monkey); extendedReadersToo.add(snake); - extendedSecurityService.addExtendedSecurity(recordToo, extendedReadersToo, null); + extendedSecurityService.set(recordToo, extendedReadersToo, null); + checkExtendedReaders(recordToo, extendedReadersToo); - Map testMapToo = new HashMap(2); - testMapToo.put(monkey, Integer.valueOf(1)); - testMapToo.put(snake, Integer.valueOf(1)); - - Map testMapThree = new HashMap(3); - testMapThree.put(monkey, Integer.valueOf(2)); - testMapThree.put(elephant, Integer.valueOf(1)); - testMapThree.put(snake, Integer.valueOf(1)); - - checkExtendedReaders(filePlan, testMapThree); - checkExtendedReaders(rmContainer, testMapThree); - checkExtendedReaders(rmFolder, testMapThree); - checkExtendedReaders(recordToo, testMapToo); - - // test remove (with no parent inheritance) - - Set removeMap1 = new HashSet(2); - removeMap1.add(elephant); - removeMap1.add(monkey); - - extendedSecurityService.removeExtendedSecurity(rmFolder, removeMap1, null, false); - - Map testMapFour = new HashMap(2); - testMapFour.put(monkey, Integer.valueOf(1)); - testMapFour.put(snake, Integer.valueOf(1)); - - checkExtendedReaders(filePlan, testMapThree); - checkExtendedReaders(rmContainer, testMapThree); - checkExtendedReaders(rmFolder, testMapFour); - checkExtendedReaders(recordToo, testMapToo); - - // test remove (apply to parents) - - Set removeMap2 = new HashSet(1); - removeMap2.add(snake); - - extendedSecurityService.removeExtendedSecurity(recordToo, removeMap2, null, true); - - testMapThree.remove(snake); - testMapFour.remove(snake); - testMapToo.remove(snake); - - checkExtendedReaders(filePlan, testMapThree); - checkExtendedReaders(rmContainer, testMapThree); - checkExtendedReaders(rmFolder, testMapFour); - checkExtendedReaders(recordToo, testMapToo); + // test remove + extendedSecurityService.remove(recordToo); + + assertFalse(extendedSecurityService.hasExtendedSecurity(recordToo)); + assertTrue(extendedSecurityService.getReaders(recordToo).isEmpty()); + assertTrue(extendedSecurityService.getWriters(recordToo).isEmpty()); return null; } @@ -175,12 +135,12 @@ public class ExtendedSecurityServiceImplTest extends BaseRMTestCase doTestInTransaction(new Test() { - Map testMap = new HashMap(2); + Set extendedReaders = new HashSet(2);; public Void run() throws Exception { - testMap.put(monkey, Integer.valueOf(1)); - testMap.put(elephant, Integer.valueOf(1)); + extendedReaders.add(monkey); + extendedReaders.add(elephant); assertFalse(extendedSecurityService.hasExtendedSecurity(filePlan)); assertFalse(extendedSecurityService.hasExtendedSecurity(rmContainer)); @@ -189,18 +149,11 @@ public class ExtendedSecurityServiceImplTest extends BaseRMTestCase assertFalse(extendedSecurityService.hasExtendedSecurity(moveRecordCategory)); assertFalse(extendedSecurityService.hasExtendedSecurity(moveRecordFolder)); - assertNull(extendedSecurityService.getExtendedReaders(record)); + assertTrue(extendedSecurityService.getReaders(record).isEmpty()); - Set extendedReaders = new HashSet(2); - extendedReaders.add(monkey); - extendedReaders.add(elephant); + extendedSecurityService.set(record, extendedReaders, null); - extendedSecurityService.addExtendedSecurity(record, extendedReaders, null); - - checkExtendedReaders(filePlan, testMap); - checkExtendedReaders(rmContainer, testMap); - checkExtendedReaders(rmFolder, testMap); - checkExtendedReaders(record, testMap); + checkExtendedReaders(record, extendedReaders); assertFalse(extendedSecurityService.hasExtendedSecurity(moveRecordCategory)); assertFalse(extendedSecurityService.hasExtendedSecurity(moveRecordFolder)); @@ -212,36 +165,21 @@ public class ExtendedSecurityServiceImplTest extends BaseRMTestCase @Override public void test(Void result) throws Exception { - checkExtendedReaders(filePlan, testMap); - assertFalse(extendedSecurityService.hasExtendedSecurity(rmContainer)); - // assertEquals(0, extendedSecurityService.getExtendedReaders(rmFolder).size()); - checkExtendedReaders(moveRecordCategory, testMap); - checkExtendedReaders(moveRecordFolder, testMap); - checkExtendedReaders(record, testMap); + checkExtendedReaders(record, extendedReaders); } }); } - - @SuppressWarnings("unchecked") - private void checkExtendedReaders(NodeRef nodeRef, Map testMap) + /** + * Check extended readers helper method + */ + private void checkExtendedReaders(NodeRef nodeRef, Set testReaders) { assertTrue(extendedSecurityService.hasExtendedSecurity(nodeRef)); - Map readersMap = (Map)nodeService.getProperty(nodeRef, PROP_READERS); - assertNotNull(readersMap); - assertEquals(testMap.size(), readersMap.size()); - - for (Map.Entry entry: testMap.entrySet()) - { - assertTrue(readersMap.containsKey(entry.getKey())); - assertEquals(entry.getKey(), entry.getValue(), readersMap.get(entry.getKey())); - - } - - Set readers = extendedSecurityService.getExtendedReaders(nodeRef); + Set readers = extendedSecurityService.getReaders(nodeRef); assertNotNull(readers); - assertEquals(testMap.size(), readers.size()); + assertEquals(testReaders, readers); } public void testDifferentUsersDifferentPermissions() diff --git a/rm-server/test/java/org/alfresco/module/org_alfresco_module_rm/test/legacy/service/FilePlanPermissionServiceImplTest.java b/rm-server/test/java/org/alfresco/module/org_alfresco_module_rm/test/legacy/service/FilePlanPermissionServiceImplTest.java index eea15bc001..365cf1db86 100644 --- a/rm-server/test/java/org/alfresco/module/org_alfresco_module_rm/test/legacy/service/FilePlanPermissionServiceImplTest.java +++ b/rm-server/test/java/org/alfresco/module/org_alfresco_module_rm/test/legacy/service/FilePlanPermissionServiceImplTest.java @@ -24,8 +24,6 @@ import java.util.Set; import org.alfresco.module.org_alfresco_module_rm.capability.RMPermissionModel; import org.alfresco.module.org_alfresco_module_rm.role.FilePlanRoleService; -import org.alfresco.module.org_alfresco_module_rm.security.ExtendedReaderDynamicAuthority; -import org.alfresco.module.org_alfresco_module_rm.security.ExtendedWriterDynamicAuthority; import org.alfresco.module.org_alfresco_module_rm.test.util.BaseRMTestCase; import org.alfresco.repo.security.authentication.AuthenticationUtil; import org.alfresco.service.cmr.repository.NodeRef; @@ -1232,10 +1230,6 @@ public class FilePlanPermissionServiceImplTest extends BaseRMTestCase accessPermissions.put(permission.getAuthority(), permission.getPermission()); } - assertTrue(accessPermissions.containsKey(ExtendedReaderDynamicAuthority.EXTENDED_READER)); - assertEquals(RMPermissionModel.READ_RECORDS, accessPermissions.get(ExtendedReaderDynamicAuthority.EXTENDED_READER)); - assertTrue(accessPermissions.containsKey(ExtendedWriterDynamicAuthority.EXTENDED_WRITER)); - assertEquals(RMPermissionModel.FILING, accessPermissions.get(ExtendedWriterDynamicAuthority.EXTENDED_WRITER)); String adminRole = authorityService.getName(AuthorityType.GROUP, FilePlanRoleService.ROLE_ADMIN + filePlan.getId()); assertTrue(accessPermissions.containsKey(adminRole)); assertEquals(RMPermissionModel.FILING, accessPermissions.get(adminRole)); diff --git a/rm-server/test/java/org/alfresco/module/org_alfresco_module_rm/test/legacy/service/RecordServiceImplTest.java b/rm-server/test/java/org/alfresco/module/org_alfresco_module_rm/test/legacy/service/RecordServiceImplTest.java index 14f5ffa379..36304ac076 100644 --- a/rm-server/test/java/org/alfresco/module/org_alfresco_module_rm/test/legacy/service/RecordServiceImplTest.java +++ b/rm-server/test/java/org/alfresco/module/org_alfresco_module_rm/test/legacy/service/RecordServiceImplTest.java @@ -30,8 +30,6 @@ import org.alfresco.module.org_alfresco_module_rm.model.RecordsManagementModel; import org.alfresco.module.org_alfresco_module_rm.record.RecordService; import org.alfresco.module.org_alfresco_module_rm.role.FilePlanRoleService; import org.alfresco.module.org_alfresco_module_rm.role.Role; -import org.alfresco.module.org_alfresco_module_rm.security.ExtendedReaderDynamicAuthority; -import org.alfresco.module.org_alfresco_module_rm.security.ExtendedWriterDynamicAuthority; import org.alfresco.module.org_alfresco_module_rm.test.util.BaseRMTestCase; import org.alfresco.repo.content.MimetypeMap; import org.alfresco.repo.security.authentication.AuthenticationUtil; @@ -174,22 +172,13 @@ public class RecordServiceImplTest extends BaseRMTestCase public void testExtendedWriters() throws Exception { - final ExtendedReaderDynamicAuthority readerDy = (ExtendedReaderDynamicAuthority)applicationContext.getBean("extendedReaderDynamicAuthority"); - final ExtendedWriterDynamicAuthority writerDy = (ExtendedWriterDynamicAuthority)applicationContext.getBean("extendedWriterDynamicAuthority"); - doTestInTransaction(new Test() { @Override public Void run() { - assertNull(extendedSecurityService.getExtendedReaders(recordOne)); - assertNull(extendedSecurityService.getExtendedWriters(recordOne)); - - assertFalse(readerDy.hasAuthority(recordOne, dmCollaborator)); - assertFalse(writerDy.hasAuthority(recordOne, dmCollaborator)); - - assertFalse(readerDy.hasAuthority(filePlan, dmCollaborator)); - assertFalse(writerDy.hasAuthority(filePlan, dmCollaborator)); + assertTrue(extendedSecurityService.getReaders(recordOne).isEmpty()); + assertTrue(extendedSecurityService.getWriters(recordOne).isEmpty()); return null; } @@ -202,16 +191,9 @@ public class RecordServiceImplTest extends BaseRMTestCase { assertEquals(AccessStatus.DENIED, permissionService.hasPermission(recordOne, RMPermissionModel.READ_RECORDS)); assertEquals(AccessStatus.DENIED, permissionService.hasPermission(recordOne, RMPermissionModel.FILING)); - - assertFalse(readerDy.hasAuthority(recordOne, dmCollaborator)); - assertFalse(writerDy.hasAuthority(recordOne, dmCollaborator)); - assertEquals(AccessStatus.DENIED, permissionService.hasPermission(filePlan, RMPermissionModel.VIEW_RECORDS)); assertEquals(AccessStatus.DENIED, permissionService.hasPermission(filePlan, RMPermissionModel.EDIT_NON_RECORD_METADATA)); - assertFalse(readerDy.hasAuthority(filePlan, dmCollaborator)); - assertFalse(writerDy.hasAuthority(filePlan, dmCollaborator)); - return null; } }, dmCollaborator); @@ -223,10 +205,10 @@ public class RecordServiceImplTest extends BaseRMTestCase { Set writers = new HashSet(1); writers.add(dmCollaborator); - extendedSecurityService.addExtendedSecurity(recordOne, null, writers); + extendedSecurityService.set(recordOne, null, writers); - assertNull(extendedSecurityService.getExtendedReaders(recordOne)); - assertFalse(extendedSecurityService.getExtendedWriters(recordOne).isEmpty()); + assertTrue(extendedSecurityService.getReaders(recordOne).isEmpty()); + assertFalse(extendedSecurityService.getWriters(recordOne).isEmpty()); return null; } @@ -240,9 +222,7 @@ public class RecordServiceImplTest extends BaseRMTestCase assertEquals(AccessStatus.ALLOWED, permissionService.hasPermission(recordOne, RMPermissionModel.READ_RECORDS)); assertEquals(AccessStatus.ALLOWED, permissionService.hasPermission(recordOne, RMPermissionModel.FILING)); - assertFalse(readerDy.hasAuthority(recordOne, dmCollaborator)); - assertTrue(writerDy.hasAuthority(recordOne, dmCollaborator)); - + // ALLOWED, becuase users have been added to the in-place roles assertEquals(AccessStatus.ALLOWED, permissionService.hasPermission(filePlan, RMPermissionModel.VIEW_RECORDS)); assertEquals(AccessStatus.ALLOWED, permissionService.hasPermission(filePlan, RMPermissionModel.EDIT_NON_RECORD_METADATA)); @@ -304,19 +284,18 @@ public class RecordServiceImplTest extends BaseRMTestCase public void test(Void result) { - checkPermissions(READ_RECORDS, AccessStatus.ALLOWED, // file - // plan - AccessStatus.ALLOWED, // unfiled container + checkPermissions(READ_RECORDS, + AccessStatus.DENIED, // file + AccessStatus.DENIED, // unfiled container AccessStatus.DENIED, // record category AccessStatus.DENIED, // record folder AccessStatus.ALLOWED); // doc/record - permissionReport(); - assertEquals(AccessStatus.ALLOWED, permissionService.hasPermission(filePlan, RMPermissionModel.VIEW_RECORDS)); - checkPermissions(FILING, AccessStatus.DENIED, // file plan + checkPermissions(FILING, + AccessStatus.DENIED, // file plan AccessStatus.DENIED, // unfiled container AccessStatus.DENIED, // record category AccessStatus.DENIED, // record folder @@ -362,9 +341,8 @@ public class RecordServiceImplTest extends BaseRMTestCase @Override public Void run() { - checkPermissions(READ_RECORDS, AccessStatus.ALLOWED, // file - // plan - AccessStatus.ALLOWED, // unfiled container + checkPermissions(READ_RECORDS, AccessStatus.DENIED, // file plan + AccessStatus.DENIED, // unfiled container AccessStatus.DENIED, // record category AccessStatus.DENIED, // record folder AccessStatus.ALLOWED); // doc/record @@ -394,38 +372,6 @@ public class RecordServiceImplTest extends BaseRMTestCase }, dmConsumer); } - private void permissionReport() - { - Set writers = extendedSecurityService.getExtendedWriters(dmDocument); - for (String writer : writers) - { - System.out.println("writer: " + writer); - } - - System.out.println("Users assigned to extended writers role:"); - Set assignedUsers = filePlanRoleService.getUsersAssignedToRole(filePlan, FilePlanRoleService.ROLE_EXTENDED_WRITERS); - for (String assignedUser : assignedUsers) - { - System.out.println(" ... " + assignedUser); - } - - Set perms = permissionService.getAllSetPermissions(filePlan); - for (AccessPermission perm : perms) - { - if (perm.getPermission().contains(RMPermissionModel.EDIT_NON_RECORD_METADATA)) - { - System.out.println(" ... " + perm.getAuthority() + " - " + perm.getPermission() + " - " + perm.getAccessStatus().toString()); - } - } - for (AccessPermission perm : perms) - { - if (perm.getPermission().contains(RMPermissionModel.VIEW_RECORDS)) - { - System.out.println(" ... " + perm.getAuthority() + " - " + perm.getPermission() + " - " + perm.getAccessStatus().toString()); - } - } - } - public void testCreateRecordNoLink() throws Exception { // show that users without WRITE can not create a record from a 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 ab2da29b56..d549b1ffa6 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 @@ -262,6 +262,8 @@ public abstract class BaseRMTestCase extends RetryingTransactionHelperTestCase /** collaboration site users */ protected String dmConsumer; protected NodeRef dmConsumerNodeRef; + protected String dmContributor; + protected NodeRef dmContributorNodeRef; protected String dmCollaborator; protected NodeRef dmCollaboratorNodeRef; @@ -412,11 +414,12 @@ public abstract class BaseRMTestCase extends RetryingTransactionHelperTestCase @Override public Object execute() throws Throwable { - // As system user - AuthenticationUtil.setFullyAuthenticatedUser(AuthenticationUtil.getSystemUserName()); - // Do the tear down - tearDownImpl(); + AuthenticationUtil.runAsSystem(() -> + { + tearDownImpl(); + return null; + }); return null; } @@ -591,16 +594,21 @@ public abstract class BaseRMTestCase extends RetryingTransactionHelperTestCase protected void setupTestUsers(final NodeRef filePlan) { - retryingTransactionHelper.doInTransaction(new RetryingTransactionCallback() + AuthenticationUtil.runAs(() -> { - @Override - public Object execute() throws Throwable + retryingTransactionHelper.doInTransaction(new RetryingTransactionCallback() { - AuthenticationUtil.setFullyAuthenticatedUser(AuthenticationUtil.getAdminUserName()); - setupTestUsersImpl(filePlan); - return null; - } - }); + @Override + public Object execute() throws Throwable + { + setupTestUsersImpl(filePlan); + return null; + } + }); + + return null; + }, + AuthenticationUtil.getAdminUserName()); } /** @@ -676,20 +684,16 @@ public abstract class BaseRMTestCase extends RetryingTransactionHelperTestCase */ protected void setupMultiHierarchyTestData() { - retryingTransactionHelper.doInTransaction(new RetryingTransactionCallback() - { - @Override - public Object execute() throws Throwable - { - // As system user - AuthenticationUtil.setFullyAuthenticatedUser(AuthenticationUtil.getSystemUserName()); - - // Do setup - setupMultiHierarchyTestDataImpl(); - - return null; - } - }); + AuthenticationUtil.runAsSystem(() -> + { + return retryingTransactionHelper.doInTransaction(() -> + { + // Do setup + setupMultiHierarchyTestDataImpl(); + + return null; + }); + }); } /** @@ -763,6 +767,10 @@ public abstract class BaseRMTestCase extends RetryingTransactionHelperTestCase dmConsumer = GUID.generate(); dmConsumerNodeRef = createPerson(dmConsumer); siteService.setMembership(collabSiteId, dmConsumer, SiteModel.SITE_CONSUMER); + + dmContributor = GUID.generate(); + dmContributorNodeRef = createPerson(dmContributor); + siteService.setMembership(collabSiteId, dmContributor, SiteModel.SITE_CONTRIBUTOR); dmCollaborator = GUID.generate(); dmCollaboratorNodeRef = createPerson(dmCollaborator); diff --git a/rm-server/test/java/org/alfresco/module/org_alfresco_module_rm/test/util/CommonRMTestUtils.java b/rm-server/test/java/org/alfresco/module/org_alfresco_module_rm/test/util/CommonRMTestUtils.java index 51d8f9dfa8..c9abd8935e 100644 --- a/rm-server/test/java/org/alfresco/module/org_alfresco_module_rm/test/util/CommonRMTestUtils.java +++ b/rm-server/test/java/org/alfresco/module/org_alfresco_module_rm/test/util/CommonRMTestUtils.java @@ -30,6 +30,7 @@ import java.util.Set; import org.alfresco.error.AlfrescoRuntimeException; import org.alfresco.model.ContentModel; import org.alfresco.module.org_alfresco_module_rm.action.RecordsManagementActionService; +import org.alfresco.module.org_alfresco_module_rm.action.impl.CompleteEventAction; import org.alfresco.module.org_alfresco_module_rm.action.impl.CutOffAction; import org.alfresco.module.org_alfresco_module_rm.action.impl.DestroyAction; import org.alfresco.module.org_alfresco_module_rm.action.impl.TransferAction; @@ -292,4 +293,20 @@ public class CommonRMTestUtils implements RecordsManagementModel return filePlanRoleService.createRole(filePlan, roleName, roleName, capabilities); } + + /** + * Helper method to complete event on disposable item + * + * @param disposableItem disposable item (record or record folder) + * @param eventName event name + */ + public void completeEvent(NodeRef disposableItem, String eventName) + { + // build action properties + Map params = new HashMap(1); + params.put(CompleteEventAction.PARAM_EVENT_NAME, eventName); + + // complete event + actionService.executeRecordsManagementAction(disposableItem, CompleteEventAction.NAME, params); + } } diff --git a/rm-server/test/java/org/alfresco/module/org_alfresco_module_rm/test/util/bdt/BehaviourTest.java b/rm-server/test/java/org/alfresco/module/org_alfresco_module_rm/test/util/bdt/BehaviourTest.java new file mode 100644 index 0000000000..7ba8091d4a --- /dev/null +++ b/rm-server/test/java/org/alfresco/module/org_alfresco_module_rm/test/util/bdt/BehaviourTest.java @@ -0,0 +1,276 @@ +/* + * #%L + * Alfresco Records Management Module + * %% + * Copyright (C) 2005 - 2016 Alfresco Software Limited + * %% + * This file is part of the Alfresco software. + * - + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * - + * 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 . + * #L% + */ +package org.alfresco.module.org_alfresco_module_rm.test.util.bdt; + +import org.alfresco.repo.security.authentication.AuthenticationUtil; +import org.alfresco.repo.transaction.RetryingTransactionHelper; + +/** + * Helper class that provides an simple way to write behaviour integration tests. + *

+ * Note that initBehaviourTest() must be called before given() is called. + * + * @author Roy Wetherall + * @since 2.5 + */ +public class BehaviourTest +{ + /** retrying transaction helper */ + private static RetryingTransactionHelper retryingTransactionHelper; + + /** current execution user */ + private String asUser = AuthenticationUtil.getAdminUserName(); + + /** + * Initialise behaviour tests for execution with retrying transaction helper + * + * @param retryingTransactionHelper retrying transaction helper + */ + public static void initBehaviourTests(RetryingTransactionHelper retryingTransactionHelper) + { + BehaviourTest.retryingTransactionHelper = retryingTransactionHelper; + } + + /** + * Start a test + * + * @return BehaviourTest new test instance + */ + public static BehaviourTest test() + { + return new BehaviourTest(); + } + + /** + * Helper method to get the retrying transaction helper + * + * @return RetryingTransactionHelper retrying transaction helper + */ + /*package*/ RetryingTransactionHelper getRetryingTransactionHelper() + { + return retryingTransactionHelper; + } + + /** + * Helper method to get the execution user + * + * @return String execution user + */ + /* package*/ String getAsUser() + { + return asUser; + } + + /** + * Helper method to switch the current execution user to admin. + * + * @return BehaviourTest test instance + */ + public BehaviourTest asAdmin() + { + return as(AuthenticationUtil.getAdminUserName()); + } + + /** + * Set execution user + * + * @param asUser execution user + * @return BehaviourTest test instance + */ + public BehaviourTest as(String asUser) + { + this.asUser = asUser; + return this; + } + + /** + * Given. + *

+ * Used to group together given conditions. + * + * @return BehaviourTest test instance + */ + public BehaviourTest given() + { + return this; + } + + /** + * Given. + *

+ * Performs work. + * + * @param given work to do + * @return BehaviourTest test instance + */ + public BehaviourTest given(Work given) + { + return perform(given); + } + + /** + * When. + *

+ * Used to group together when actions. + * + * @return BehaviourTest test instance + */ + public BehaviourTest when() + { + return this; + } + + /** + * When. + *

+ * Performs work. + * + * @param when work to do + * @return BehaviourTest test instance + */ + public BehaviourTest when(Work when) + { + return perform(when); + } + + /** + * Then. + *

+ * Used to group together then actions. + * + * @return BehaviourTest test instance + */ + public BehaviourTest then() + { + return this; + } + + /** + * Then. + *

+ * Performs work. + * + * @param then work to do + * @return BehaviourTest test instance + */ + public BehaviourTest then(Work then) + { + return perform(then); + } + + /** + * Expect a value. + * + * @param value value + * @return ExpectedValue expected value evaluator + */ + public ExpectedValue expect(boolean value) + { + return new ExpectedValue(this, value); + } + + /** + * Expect a value. + * + * @param value value + * @return ExpectedValue expected value evaluator + */ + public ExpectedValue expect(String value) + { + return new ExpectedValue(this, value); + } + + /** + * Expect a value. + * + * @param value value + * @return ExpectedValue expected value evaluator + */ + public ExpectedValue expect(Object value) + { + return new ExpectedValue(this, value); + } + + + /** + * Expect a failure. + * + * @param exceptionClass expected exception + * @return ExpectedFailure expected failure evaluator + */ + public ExpectedFailure expectException(Class exceptionClass) + { + return new ExpectedFailure(this, exceptionClass); + } + + /** + * Perform work a number of times + * + * @param count number of times to perform the work + * @param work work to perform + * @return BehaviourTest test instance + */ + public BehaviourTest perform(int count, Work work) + { + for (int i = 0; i < count; i++) + { + perform(work); + } + + return this; + } + + /** + * Perform work + * + * @param work work to perform + * @return BehaviourTest test instance + */ + public BehaviourTest perform(Work work) + { + return AuthenticationUtil.runAs(() -> + { + return retryingTransactionHelper.doInTransaction(() -> + { + work.doIt(); + return this; + }); + }, + this.asUser); + } + + /** + * Work Interface + */ + public interface Work + { + /** + * Do the work. + */ + void doIt() throws Exception; + } +} diff --git a/rm-server/test/java/org/alfresco/module/org_alfresco_module_rm/test/util/bdt/ExpectedFailure.java b/rm-server/test/java/org/alfresco/module/org_alfresco_module_rm/test/util/bdt/ExpectedFailure.java new file mode 100644 index 0000000000..33c5662d98 --- /dev/null +++ b/rm-server/test/java/org/alfresco/module/org_alfresco_module_rm/test/util/bdt/ExpectedFailure.java @@ -0,0 +1,92 @@ +/* + * #%L + * Alfresco Records Management Module + * %% + * Copyright (C) 2005 - 2016 Alfresco Software Limited + * %% + * This file is part of the Alfresco software. + * - + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * - + * 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 . + * #L% + */ +package org.alfresco.module.org_alfresco_module_rm.test.util.bdt; + +import static org.junit.Assert.fail; + +import java.text.MessageFormat; +import java.util.Arrays; +import java.util.Set; +import java.util.stream.Collectors; + +import org.alfresco.module.org_alfresco_module_rm.test.util.bdt.BehaviourTest.Work; + +/** + * Expected failure. + * + * @author Roy Wetherall + * @since 2.5 + */ +public class ExpectedFailure +{ + private static final String MESSAGE = "Expected failure \"{0}\" was not observed."; + + private BehaviourTest test; + private Set> exceptionClasses; + private Work work; + + @SafeVarargs + public ExpectedFailure(BehaviourTest test, Class ...exceptionClasses) + { + this.test = test; + this.exceptionClasses = Arrays.stream(exceptionClasses).collect(Collectors.toSet()); + } + + public ExpectedFailure from(Work work) + { + this.work = work; + return this; + } + + public BehaviourTest because(String message) + { + try + { + test.perform(work); + } + catch(Exception actualException) + { + boolean found = false; + + for (Class exceptionClass : exceptionClasses) + { + if (exceptionClass.isAssignableFrom(actualException.getClass())) + { + found = true; + } + } + + if (!found) + { + fail(MessageFormat.format(MESSAGE, message)); + } + } + + return test; + } + +} diff --git a/rm-server/test/java/org/alfresco/module/org_alfresco_module_rm/test/util/bdt/ExpectedValue.java b/rm-server/test/java/org/alfresco/module/org_alfresco_module_rm/test/util/bdt/ExpectedValue.java new file mode 100644 index 0000000000..8935696c23 --- /dev/null +++ b/rm-server/test/java/org/alfresco/module/org_alfresco_module_rm/test/util/bdt/ExpectedValue.java @@ -0,0 +1,86 @@ +/* + * #%L + * Alfresco Records Management Module + * %% + * Copyright (C) 2005 - 2016 Alfresco Software Limited + * %% + * This file is part of the Alfresco software. + * - + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * - + * 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 . + * #L% + */ +package org.alfresco.module.org_alfresco_module_rm.test.util.bdt; + +import static org.junit.Assert.assertEquals; + +import java.text.MessageFormat; + +import org.alfresco.repo.security.authentication.AuthenticationUtil; + +/** + * Expected value. + * + * @author Roy Wetherall + * @since 2.5 + */ +public class ExpectedValue +{ + private static final String MESSAGE = "Expected value outcome \"{0}\" was not observed."; + + private T expectedValue; + private Evaluation evaluation; + private BehaviourTest test; + + public ExpectedValue(BehaviourTest test, T value) + { + this.expectedValue = value; + this.test = test; + } + + public ExpectedValue from(Evaluation evaluation) + { + this.evaluation = evaluation; + return this; + } + + public BehaviourTest because(String message) + { + T actualValue = (T)AuthenticationUtil.runAs(() -> + { + return test.getRetryingTransactionHelper().doInTransaction(() -> + { + return evaluation.eval(); + }); + }, + test.getAsUser()); + + if (message != null) + { + message = MessageFormat.format(MESSAGE, message); + } + + assertEquals(message, expectedValue, actualValue); + + return test; + } + + public interface Evaluation + { + T eval() throws Exception; + } +} diff --git a/rm-server/unit-test/java/org/alfresco/module/org_alfresco_module_rm/security/ExtendedSecurityServiceImplUnitTest.java b/rm-server/unit-test/java/org/alfresco/module/org_alfresco_module_rm/security/ExtendedSecurityServiceImplUnitTest.java new file mode 100644 index 0000000000..9bda06d5db --- /dev/null +++ b/rm-server/unit-test/java/org/alfresco/module/org_alfresco_module_rm/security/ExtendedSecurityServiceImplUnitTest.java @@ -0,0 +1,928 @@ +/* + * #%L + * Alfresco Records Management Module + * %% + * Copyright (C) 2005 - 2016 Alfresco Software Limited + * %% + * This file is part of the Alfresco software. + * - + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * - + * 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 . + * #L% + */ +package org.alfresco.module.org_alfresco_module_rm.security; + +import static org.alfresco.module.org_alfresco_module_rm.security.ExtendedSecurityServiceImpl.READER_GROUP_PREFIX; +import static org.alfresco.module.org_alfresco_module_rm.security.ExtendedSecurityServiceImpl.ROOT_IPR_GROUP; +import static org.alfresco.module.org_alfresco_module_rm.security.ExtendedSecurityServiceImpl.WRITER_GROUP_PREFIX; +import static org.alfresco.module.org_alfresco_module_rm.test.util.AlfMock.generateText; +import static org.alfresco.service.cmr.security.PermissionService.GROUP_PREFIX; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.anySet; +import static org.mockito.Matchers.anyString; +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import org.alfresco.model.RenditionModel; +import org.alfresco.module.org_alfresco_module_rm.capability.RMPermissionModel; +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.role.FilePlanRoleService; +import org.alfresco.module.org_alfresco_module_rm.test.util.AlfMock; +import org.alfresco.query.PagingRequest; +import org.alfresco.query.PagingResults; +import org.alfresco.repo.security.authority.RMAuthority; +import org.alfresco.repo.security.permissions.impl.AccessPermissionImpl; +import org.alfresco.repo.transaction.RetryingTransactionHelper; +import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback; +import org.alfresco.service.cmr.repository.ChildAssociationRef; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.NodeService; +import org.alfresco.service.cmr.security.AccessPermission; +import org.alfresco.service.cmr.security.AccessStatus; +import org.alfresco.service.cmr.security.AuthorityService; +import org.alfresco.service.cmr.security.AuthorityType; +import org.alfresco.service.cmr.security.PermissionService; +import org.alfresco.service.namespace.RegexQNamePattern; +import org.alfresco.service.transaction.TransactionService; +import org.junit.Before; +import org.junit.Test; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.mockito.invocation.InvocationOnMock; +import org.mockito.stubbing.Answer; +import org.springframework.context.ApplicationContext; +import org.springframework.context.event.ContextRefreshedEvent; + +/** + * Extended security service implementation unit test. + * + * @author Roy Wetherall + * @since 2.5 + */ +public class ExtendedSecurityServiceImplUnitTest +{ + /** service mocks*/ + @Mock private FilePlanService mockedFilePlanService; + @Mock private FilePlanRoleService mockedFilePlanRoleService; + @Mock private AuthorityService mockedAuthorityService; + @Mock private PermissionService mockedPermissionService; + @Mock private TransactionService mockedTransactionService; + @Mock private RetryingTransactionHelper mockedRetryingTransactionHelper; + @Mock private NodeService mockedNodeService; + @Mock private PagingResults mockedReadPagingResults; + @Mock private PagingResults mockedWritePagingResults; + @Mock private ApplicationContext mockedApplicationContext; + @Mock private ChildAssociationRef mockedChildAssociationRef; + + /** test component */ + @InjectMocks private ExtendedSecurityServiceImpl extendedSecurityService; + + /** read/write group full names */ + private static final String READER_GROUP_FULL_PREFIX = GROUP_PREFIX + READER_GROUP_PREFIX; + private static final String WRITER_GROUP_FULL_PREFIX = GROUP_PREFIX + WRITER_GROUP_PREFIX; + + /** test authorities */ + private static final String USER = "USER"; + private static final String GROUP = GROUP_PREFIX + "GROUP"; + private static final String USER_W = "USER_W"; + private static final String GROUP_W = GROUP_PREFIX + "GROUP_W"; + private static final Set READERS = Stream.of(USER, GROUP).collect(Collectors.toSet()); + private static final Set WRITERS = Stream.of(USER_W, GROUP_W).collect(Collectors.toSet()); + + /** has extended security permission set */ + private static final Set HAS_EXTENDED_SECURITY = Stream + .of(new AccessPermissionImpl(AlfMock.generateText(), AccessStatus.ALLOWED, READER_GROUP_FULL_PREFIX, 0), + new AccessPermissionImpl(AlfMock.generateText(), AccessStatus.ALLOWED, AlfMock.generateText(), 1), + new AccessPermissionImpl(AlfMock.generateText(), AccessStatus.ALLOWED, WRITER_GROUP_FULL_PREFIX, 2)) + .collect(Collectors.toSet()); + + /** has no extended security permission set */ + private static final Set HAS_NO_EXTENDED_SECURITY = Stream + .of(new AccessPermissionImpl(AlfMock.generateText(), AccessStatus.ALLOWED, AlfMock.generateText(), 0), + new AccessPermissionImpl(AlfMock.generateText(), AccessStatus.ALLOWED, AlfMock.generateText(), 1), + new AccessPermissionImpl(AlfMock.generateText(), AccessStatus.ALLOWED, AlfMock.generateText(), 2)) + .collect(Collectors.toSet()); + + /** test data */ + private NodeRef nodeRef; + private NodeRef filePlan; + private String readGroupPrefix; + private String writeGroupPrefix; + + /** + * Before tests + */ + @SuppressWarnings("unchecked") + @Before public void before() + { + // initialise mocks + MockitoAnnotations.initMocks(this); + + // setup node + nodeRef = AlfMock.generateNodeRef(mockedNodeService); + + // setup file plan + filePlan = AlfMock.generateNodeRef(mockedNodeService); + when(mockedFilePlanService.getFilePlan(any(NodeRef.class))) + .thenReturn(filePlan); + + // set-up application context + when(mockedApplicationContext.getBean("dbNodeService")) + .thenReturn(mockedNodeService); + + // setup retrying transaction helper + Answer doInTransactionAnswer = new Answer() + { + @SuppressWarnings("rawtypes") + @Override + public Object answer(InvocationOnMock invocation) throws Throwable + { + RetryingTransactionCallback callback = (RetryingTransactionCallback)invocation.getArguments()[0]; + return callback.execute(); + } + }; + doAnswer(doInTransactionAnswer) + .when(mockedRetryingTransactionHelper) + .doInTransaction(any(RetryingTransactionCallback.class)); + when(mockedTransactionService.getRetryingTransactionHelper()) + .thenReturn(mockedRetryingTransactionHelper); + + // setup create authority + Answer createAuthorityAnswer = new Answer() + { + public String answer(InvocationOnMock invocation) throws Throwable + { + return PermissionService.GROUP_PREFIX + (String)invocation.getArguments()[1]; + } + + }; + when(mockedAuthorityService.createAuthority(any(AuthorityType.class), anyString(), anyString(), anySet())) + .thenAnswer(createAuthorityAnswer); + + // setup group prefixes + readGroupPrefix = extendedSecurityService.getIPRGroupPrefixShortName(READER_GROUP_PREFIX, READERS); + writeGroupPrefix = extendedSecurityService.getIPRGroupPrefixShortName(WRITER_GROUP_PREFIX, WRITERS); + + // make sure the users and groups exist + Stream + .of(USER, USER_W, GROUP, GROUP_W) + .forEach((a) -> + when(mockedAuthorityService.authorityExists(a)) + .thenReturn(true)); + } + + /** + * Given that the root authority does not exist + * When the application context is refreshed + * Then the root authority is created + */ + @Test public void rootAuthorityDoesNotExist() + { + // group doesn't exist + when(mockedAuthorityService.authorityExists(GROUP_PREFIX + ExtendedSecurityServiceImpl.ROOT_IPR_GROUP)) + .thenReturn(false); + + // refresh context + extendedSecurityService.onApplicationEvent(mock(ContextRefreshedEvent.class)); + + // verify group is created + verify(mockedAuthorityService).createAuthority(AuthorityType.GROUP, ROOT_IPR_GROUP, ROOT_IPR_GROUP, Collections.singleton(RMAuthority.ZONE_APP_RM)); + } + + /** + * Given that the root authority exists + * When the application context is refreshed + * Then nothing happens + */ + @Test public void rootAuthorityDoesExist() + { + // group doesn't exist + when(mockedAuthorityService.authorityExists(GROUP_PREFIX + ROOT_IPR_GROUP)) + .thenReturn(true); + + // refresh context + extendedSecurityService.onApplicationEvent(mock(ContextRefreshedEvent.class)); + + // verify group is created + verify(mockedAuthorityService, never()).createAuthority(AuthorityType.GROUP, ROOT_IPR_GROUP, ROOT_IPR_GROUP, Collections.singleton(RMAuthority.ZONE_APP_RM)); + } + + /** + * Given that an IPR read group has read on a node + * And that an IPR write group has filling on a node + * When checking for the existence of extended permissions on that node + * Then it will be confirmed + */ + @Test public void hasExtendedSecurityWithReadAndWriteGroups() + { + // setup permissions + when(mockedPermissionService.getAllSetPermissions(nodeRef)) + .thenReturn(HAS_EXTENDED_SECURITY); + + // has extended security + assertTrue(extendedSecurityService.hasExtendedSecurity(nodeRef)); + } + + /** + * Given that there is no IPR read group has read on a node + * When checking for the existence of extended permissions on that node + * Then it will be denied + */ + @Test public void hasExtendedSecurityWithNoReadGroup() + { + // setup permissions + Set permissions = Stream + .of(new AccessPermissionImpl(AlfMock.generateText(), AccessStatus.ALLOWED, AlfMock.generateText(), 0), + new AccessPermissionImpl(AlfMock.generateText(), AccessStatus.ALLOWED, AlfMock.generateText(), 1), + new AccessPermissionImpl(AlfMock.generateText(), AccessStatus.ALLOWED, GROUP_PREFIX + WRITER_GROUP_PREFIX, 2)) + .collect(Collectors.toSet()); + + when(mockedPermissionService.getAllSetPermissions(nodeRef)) + .thenReturn(permissions); + + // has extended security + assertFalse(extendedSecurityService.hasExtendedSecurity(nodeRef)); + } + + /** + * Given that there is no IPR write group has read on a node + * When checking for the existence of extended permissions on that node + * Then it will be denied + */ + @Test public void hasExtendedSecurityWithNoWriteGroup() + { + // setup permissions + Set permissions = Stream + .of(new AccessPermissionImpl(AlfMock.generateText(), AccessStatus.ALLOWED, GROUP_PREFIX + READER_GROUP_PREFIX, 0), + new AccessPermissionImpl(AlfMock.generateText(), AccessStatus.ALLOWED, AlfMock.generateText(), 1), + new AccessPermissionImpl(AlfMock.generateText(), AccessStatus.ALLOWED, AlfMock.generateText(), 2)) + .collect(Collectors.toSet()); + + when(mockedPermissionService.getAllSetPermissions(nodeRef)) + .thenReturn(permissions); + + // has extended security + assertFalse(extendedSecurityService.hasExtendedSecurity(nodeRef)); + } + + /** + * Given that an IPR read group has no groups assigned permission + * When checking for the existence of extended permissions on that node + * Then it will be denied + */ + @Test public void hasExtendedSecurityWithNoAssignedGroups() + { + // setup permissions + when(mockedPermissionService.getAllSetPermissions(nodeRef)) + .thenReturn(HAS_NO_EXTENDED_SECURITY); + + // has extended security + assertFalse(extendedSecurityService.hasExtendedSecurity(nodeRef)); + } + + /** + * Given that there are no IPR groups assigned + * When I try and get the extended readers + * The I will get an empty set + */ + @Test public void getExtendedReadersNoIPRGroupsAssigned() + { + // setup permissions + when(mockedPermissionService.getAllSetPermissions(nodeRef)) + .thenReturn(HAS_NO_EXTENDED_SECURITY); + + // get extended readers + assertTrue(extendedSecurityService.getReaders(nodeRef).isEmpty()); + } + + /** + * Given that there are IPR groups assigned + * When I try and get the extended readers + * The I will get the set of readers + */ + @Test public void getExtendedReaders() + { + // setup permissions + when(mockedPermissionService.getAllSetPermissions(nodeRef)) + .thenReturn(HAS_EXTENDED_SECURITY); + + when(mockedAuthorityService.getContainedAuthorities(null, READER_GROUP_FULL_PREFIX, true)) + .thenReturn(Stream + .of(USER, GROUP) + .collect(Collectors.toSet())); + + // get extended readers + Set extendedReaders = extendedSecurityService.getReaders(nodeRef); + assertEquals(Stream + .of(USER, GROUP) + .collect(Collectors.toSet()), + extendedReaders); + } + + /** + * Given that there are no IPR groups assigned + * When I try and get the extended writers + * The I will get an empty set + */ + @Test public void getExtendedWritersNoIPRGroupsAssigned() + { + // setup permissions + when(mockedPermissionService.getAllSetPermissions(nodeRef)) + .thenReturn(HAS_NO_EXTENDED_SECURITY); + + // get extended writers + assertTrue(extendedSecurityService.getWriters(nodeRef).isEmpty()); + } + + /** + * Given that there are IPR groups assigned + * When I try and get the extended writers + * The I will get the set of writers + */ + @Test public void getExtendedWriters() + { + // setup permissions + when(mockedPermissionService.getAllSetPermissions(nodeRef)) + .thenReturn(HAS_EXTENDED_SECURITY); + + when(mockedAuthorityService.getContainedAuthorities(null, WRITER_GROUP_FULL_PREFIX, true)) + .thenReturn(Stream + .of(USER, GROUP) + .collect(Collectors.toSet())); + + // get extended writers + Set extendedWriters = extendedSecurityService.getWriters(nodeRef); + assertEquals(Stream + .of(USER, GROUP) + .collect(Collectors.toSet()), + extendedWriters); + } + + /** + * Given a node with no previous IPR groups assigned + * And no IPR group matching authorities + * When I add some read and write authorities + * Then new IPR groups are created + * And they are assigned to the node + * And they are added to the RM roles + */ + @Test public void addExtendedSecurityForTheFirstTimeAndCreateGroups() + { + // group names + String readGroup = extendedSecurityService.getIPRGroupShortName(READER_GROUP_PREFIX, READERS, 0); + String writeGroup = extendedSecurityService.getIPRGroupShortName(WRITER_GROUP_PREFIX, WRITERS, 0); + + // setup query results + when(mockedReadPagingResults.getPage()) + .thenReturn(Collections.emptyList()); + when(mockedAuthorityService.getAuthorities( + eq(AuthorityType.GROUP), + eq(RMAuthority.ZONE_APP_RM), + any(String.class), + eq(false), + eq(false), + any(PagingRequest.class))) + .thenReturn(mockedReadPagingResults); + + // add extended security + extendedSecurityService.set(nodeRef, READERS, WRITERS); + + // verify no old permissions needing to be cleared + verify(mockedPermissionService, never()).clearPermission(eq(nodeRef), anyString()); + + // verify read group created correctly + verify(mockedAuthorityService).createAuthority(AuthorityType.GROUP, readGroup, readGroup, Collections.singleton(RMAuthority.ZONE_APP_RM)); + readGroup = GROUP_PREFIX + readGroup; + verify(mockedAuthorityService).addAuthority(GROUP_PREFIX + ROOT_IPR_GROUP, readGroup); + verify(mockedAuthorityService).addAuthority(readGroup, USER); + verify(mockedAuthorityService).addAuthority(readGroup, GROUP); + + // verify write group created correctly + verify(mockedAuthorityService).createAuthority(AuthorityType.GROUP, writeGroup, writeGroup, Collections.singleton(RMAuthority.ZONE_APP_RM)); + writeGroup = GROUP_PREFIX + writeGroup; + verify(mockedAuthorityService).addAuthority(GROUP_PREFIX + ROOT_IPR_GROUP, writeGroup); + verify(mockedAuthorityService).addAuthority(writeGroup, USER_W); + verify(mockedAuthorityService).addAuthority(writeGroup, GROUP_W); + + // verify groups assigned to RM roles + verify(mockedFilePlanRoleService).assignRoleToAuthority(filePlan, FilePlanRoleService.ROLE_EXTENDED_READERS, readGroup); + verify(mockedFilePlanRoleService).assignRoleToAuthority(filePlan, FilePlanRoleService.ROLE_EXTENDED_WRITERS, writeGroup); + + // verify permissions are assigned to node + verify(mockedPermissionService).setPermission(nodeRef, readGroup, RMPermissionModel.READ_RECORDS, true); + verify(mockedPermissionService).setPermission(nodeRef, writeGroup, RMPermissionModel.FILING, true); + } + + /** + * Given a node with no previous IPR groups assigned + * And existing IPR groups matching + * When I add some read and write authorities + * Then the existing IPR groups are used + * And they are assigned to the node + * And do not not need to be re-added to the RM roles + */ + @Test public void addExtendedSecurityForTheFirstTimeAndReuseGroups() + { + // group names + String readGroup = readGroupPrefix + "0"; + String writeGroup = writeGroupPrefix + "0"; + + // setup query results + when(mockedReadPagingResults.getPage()) + .thenReturn(Stream.of(GROUP_PREFIX + readGroup).collect(Collectors.toList())); + when(mockedAuthorityService.getAuthorities( + eq(AuthorityType.GROUP), + eq(RMAuthority.ZONE_APP_RM), + eq(readGroupPrefix), + eq(false), + eq(false), + any(PagingRequest.class))) + .thenReturn(mockedReadPagingResults); + + when(mockedWritePagingResults.getPage()) + .thenReturn(Stream.of(GROUP_PREFIX + writeGroup).collect(Collectors.toList())); + when(mockedAuthorityService.getAuthorities( + eq(AuthorityType.GROUP), + eq(RMAuthority.ZONE_APP_RM), + eq(writeGroupPrefix), + eq(false), + eq(false), + any(PagingRequest.class))) + .thenReturn(mockedWritePagingResults); + + // setup exact match + when(mockedAuthorityService.authorityExists(GROUP_PREFIX + writeGroup)) + .thenReturn(true); + when(mockedAuthorityService.getContainedAuthorities(null, GROUP_PREFIX + readGroup, true)) + .thenReturn(Stream + .of(USER, GROUP) + .collect(Collectors.toSet())); + when(mockedAuthorityService.getContainedAuthorities(null, GROUP_PREFIX + writeGroup, true)) + .thenReturn(Stream + .of(USER_W, GROUP_W) + .collect(Collectors.toSet())); + + // add extended security + extendedSecurityService.set(nodeRef, READERS, WRITERS); + + // verify no old permissions needing to be cleared + verify(mockedPermissionService, never()).clearPermission(eq(nodeRef), anyString()); + + // verify read group is not recreated + verify(mockedAuthorityService, never()).createAuthority(AuthorityType.GROUP, readGroup, readGroup, Collections.singleton(RMAuthority.ZONE_APP_RM)); + readGroup = GROUP_PREFIX + readGroup; + verify(mockedAuthorityService, never()).addAuthority(GROUP_PREFIX + ROOT_IPR_GROUP, readGroup); + verify(mockedAuthorityService, never()).addAuthority(readGroup, USER); + verify(mockedAuthorityService, never()).addAuthority(readGroup, GROUP); + + // verify write group is not recreated + verify(mockedAuthorityService, never()).createAuthority(AuthorityType.GROUP, writeGroup, writeGroup, Collections.singleton(RMAuthority.ZONE_APP_RM)); + writeGroup = GROUP_PREFIX + writeGroup; + verify(mockedAuthorityService, never()).addAuthority(readGroup, writeGroup); + verify(mockedAuthorityService, never()).addAuthority(writeGroup, USER_W); + verify(mockedAuthorityService, never()).addAuthority(writeGroup, GROUP_W); + + // verify groups assigned to RM roles + verify(mockedFilePlanRoleService).assignRoleToAuthority(filePlan, FilePlanRoleService.ROLE_EXTENDED_READERS, readGroup); + verify(mockedFilePlanRoleService).assignRoleToAuthority(filePlan, FilePlanRoleService.ROLE_EXTENDED_WRITERS, writeGroup); + + // verify permissions are assigned to node + verify(mockedPermissionService).setPermission(nodeRef, readGroup, RMPermissionModel.READ_RECORDS, true); + verify(mockedPermissionService).setPermission(nodeRef, writeGroup, RMPermissionModel.FILING, true); + + } + + /** + * Given a node with no previous IPR groups assigned + * And existing IPR groups matches existing has, but not exact match + * When I add some read and write authorities + * Then new groups are created + * And they are assigned to the node + * And added to the RM roles + */ + @Test public void addExtendedSecurityForTheFirstTimeAndCreateGroupsAfterClash() + { + // group names + String readGroup = readGroupPrefix + "0"; + String writeGroup = writeGroupPrefix + "0"; + + // setup query results + when(mockedReadPagingResults.getPage()) + .thenReturn(Stream.of(GROUP_PREFIX + readGroup).collect(Collectors.toList())); + when(mockedAuthorityService.getAuthorities( + eq(AuthorityType.GROUP), + eq(RMAuthority.ZONE_APP_RM), + eq(readGroupPrefix), + eq(false), + eq(false), + any(PagingRequest.class))) + .thenReturn(mockedReadPagingResults); + + when(mockedWritePagingResults.getPage()) + .thenReturn(Stream.of(GROUP_PREFIX + writeGroup).collect(Collectors.toList())); + when(mockedAuthorityService.getAuthorities( + eq(AuthorityType.GROUP), + eq(RMAuthority.ZONE_APP_RM), + eq(writeGroupPrefix), + eq(false), + eq(false), + any(PagingRequest.class))) + .thenReturn(mockedWritePagingResults); + + // setup exact match + when(mockedAuthorityService.authorityExists(GROUP_PREFIX + writeGroup)) + .thenReturn(true); + when(mockedAuthorityService.getContainedAuthorities(null, GROUP_PREFIX + readGroup, true)) + .thenReturn(Stream + .of(USER, GROUP, AlfMock.generateText()) + .collect(Collectors.toSet())); + when(mockedAuthorityService.getContainedAuthorities(null, GROUP_PREFIX + writeGroup, true)) + .thenReturn(Stream + .of(USER_W, AlfMock.generateText()) + .collect(Collectors.toSet())); + + // add extended security + extendedSecurityService.set(nodeRef, READERS, WRITERS); + + // verify no old permissions needing to be cleared + verify(mockedPermissionService, never()).clearPermission(eq(nodeRef), anyString()); + + // new group names + readGroup = extendedSecurityService.getIPRGroupShortName(READER_GROUP_PREFIX, READERS, 1); + writeGroup = extendedSecurityService.getIPRGroupShortName(WRITER_GROUP_PREFIX, WRITERS, 1); + + // verify read group created correctly + verify(mockedAuthorityService).createAuthority(AuthorityType.GROUP, readGroup, readGroup, Collections.singleton(RMAuthority.ZONE_APP_RM)); + readGroup = GROUP_PREFIX + readGroup; + verify(mockedAuthorityService).addAuthority(GROUP_PREFIX + ROOT_IPR_GROUP, readGroup); + verify(mockedAuthorityService).addAuthority(readGroup, USER); + verify(mockedAuthorityService).addAuthority(readGroup, GROUP); + + // verify write group created correctly + verify(mockedAuthorityService).createAuthority(AuthorityType.GROUP, writeGroup, writeGroup, Collections.singleton(RMAuthority.ZONE_APP_RM)); + writeGroup = GROUP_PREFIX + writeGroup; + verify(mockedAuthorityService).addAuthority(GROUP_PREFIX + ROOT_IPR_GROUP, writeGroup); + verify(mockedAuthorityService).addAuthority(writeGroup, USER_W); + verify(mockedAuthorityService).addAuthority(writeGroup, GROUP_W); + + // verify groups assigned to RM roles + verify(mockedFilePlanRoleService).assignRoleToAuthority(filePlan, FilePlanRoleService.ROLE_EXTENDED_READERS, readGroup); + verify(mockedFilePlanRoleService).assignRoleToAuthority(filePlan, FilePlanRoleService.ROLE_EXTENDED_WRITERS, writeGroup); + + // verify permissions are assigned to node + verify(mockedPermissionService).setPermission(nodeRef, readGroup, RMPermissionModel.READ_RECORDS, true); + verify(mockedPermissionService).setPermission(nodeRef, writeGroup, RMPermissionModel.FILING, true); + } + + /** + * Given a node with no previous IPR groups assigned + * And existing IPR groups matching, but found on second page of find results + * When I add some read and write authorities + * Then the existing IPR groups are used + * And they are assigned to the node + * And do not not need to be re-added to the RM roles + */ + @Test public void addExtendedSecurityWithResultPaging() + { + // group names + String readGroup = readGroupPrefix + "0"; + String writeGroup = writeGroupPrefix + "0"; + + // create fity results + List fiftyResults = new ArrayList(50); + for (int i = 0; i < 50; i++) + { + fiftyResults.add(AlfMock.generateText()); + } + + // setup query results + when(mockedReadPagingResults.getPage()) + .thenReturn(fiftyResults) + .thenReturn(Stream.of(GROUP_PREFIX + readGroup).collect(Collectors.toList())); + when(mockedAuthorityService.getAuthorities( + eq(AuthorityType.GROUP), + eq(RMAuthority.ZONE_APP_RM), + eq(readGroupPrefix), + eq(false), + eq(false), + any(PagingRequest.class))) + .thenReturn(mockedReadPagingResults); + + when(mockedWritePagingResults.getPage()) + .thenReturn(fiftyResults) + .thenReturn(Stream.of(GROUP_PREFIX + writeGroup).collect(Collectors.toList())); + when(mockedAuthorityService.getAuthorities( + eq(AuthorityType.GROUP), + eq(RMAuthority.ZONE_APP_RM), + eq(writeGroupPrefix), + eq(false), + eq(false), + any(PagingRequest.class))) + .thenReturn(mockedWritePagingResults); + + // setup exact match + when(mockedAuthorityService.authorityExists(GROUP_PREFIX + writeGroup)) + .thenReturn(true); + when(mockedAuthorityService.getContainedAuthorities(null, GROUP_PREFIX + readGroup, true)) + .thenReturn(Stream + .of(USER, GROUP) + .collect(Collectors.toSet())); + when(mockedAuthorityService.getContainedAuthorities(null, GROUP_PREFIX + writeGroup, true)) + .thenReturn(Stream + .of(USER_W, GROUP_W) + .collect(Collectors.toSet())); + + // add extended security + extendedSecurityService.set(nodeRef, READERS, WRITERS); + + // verify no old permissions needing to be cleared + verify(mockedPermissionService, never()).clearPermission(eq(nodeRef), anyString()); + + // verify read group is not recreated + verify(mockedAuthorityService, never()).createAuthority(AuthorityType.GROUP, readGroup, readGroup, Collections.singleton(RMAuthority.ZONE_APP_RM)); + readGroup = GROUP_PREFIX + readGroup; + verify(mockedAuthorityService, never()).addAuthority(GROUP_PREFIX + ROOT_IPR_GROUP, readGroup); + verify(mockedAuthorityService, never()).addAuthority(readGroup, USER); + verify(mockedAuthorityService, never()).addAuthority(readGroup, GROUP); + + // verify write group is not recreated + verify(mockedAuthorityService, never()).createAuthority(AuthorityType.GROUP, writeGroup, writeGroup, Collections.singleton(RMAuthority.ZONE_APP_RM)); + writeGroup = GROUP_PREFIX + writeGroup; + verify(mockedAuthorityService, never()).addAuthority(readGroup, writeGroup); + verify(mockedAuthorityService, never()).addAuthority(writeGroup, USER_W); + verify(mockedAuthorityService, never()).addAuthority(writeGroup, GROUP_W); + + // verify groups assigned to RM roles + verify(mockedFilePlanRoleService).assignRoleToAuthority(filePlan, FilePlanRoleService.ROLE_EXTENDED_READERS, readGroup); + verify(mockedFilePlanRoleService).assignRoleToAuthority(filePlan, FilePlanRoleService.ROLE_EXTENDED_WRITERS, writeGroup); + + // verify permissions are assigned to node + verify(mockedPermissionService).setPermission(nodeRef, readGroup, RMPermissionModel.READ_RECORDS, true); + verify(mockedPermissionService).setPermission(nodeRef, writeGroup, RMPermissionModel.FILING, true); + + } + + /** + * Given that a node already has extended security + * When I add extended security + * Then the existing extended security is replaced with the new extended security + */ + @Test public void addExtendedSecurityToNodeWithExtendedSecurity() + { + // group names + String readGroup = extendedSecurityService.getIPRGroupShortName(READER_GROUP_FULL_PREFIX, READERS, 0); + String writeGroup = extendedSecurityService.getIPRGroupShortName(WRITER_GROUP_FULL_PREFIX, WRITERS, 0); + + // setup permissions + Set permissions = Stream + .of(new AccessPermissionImpl(AlfMock.generateText(), AccessStatus.ALLOWED, readGroup, 0), + new AccessPermissionImpl(AlfMock.generateText(), AccessStatus.ALLOWED, AlfMock.generateText(), 1), + new AccessPermissionImpl(AlfMock.generateText(), AccessStatus.ALLOWED, writeGroup, 2)) + .collect(Collectors.toSet()); + when(mockedPermissionService.getAllSetPermissions(nodeRef)) + .thenReturn(permissions); + + // set revised reader and writers + String user = generateText(); + String group = generateText(); + String userW = generateText(); + String groupW = generateText(); + Set newReaders = Stream.of(user, group).collect(Collectors.toSet()); + Set newWriters = Stream.of(userW, groupW).collect(Collectors.toSet()); + + // new group names + String newReadGroup = extendedSecurityService.getIPRGroupShortName(READER_GROUP_PREFIX, newReaders, 0); + String newWriteGroup = extendedSecurityService.getIPRGroupShortName(WRITER_GROUP_PREFIX, newWriters, 0); + + // make sure users and groups exist + Stream + .of(user, group, userW, groupW) + .forEach((a) -> + when(mockedAuthorityService.authorityExists(a)) + .thenReturn(true)); + + // setup query results for no group matches + when(mockedReadPagingResults.getPage()) + .thenReturn(Collections.emptyList()); + when(mockedAuthorityService.getAuthorities( + eq(AuthorityType.GROUP), + eq(RMAuthority.ZONE_APP_RM), + any(String.class), + eq(false), + eq(false), + any(PagingRequest.class))) + .thenReturn(mockedReadPagingResults); + + // set extended security + extendedSecurityService.set(nodeRef, newReaders, newWriters); + + // verify that the old permissions are cleared + verify(mockedPermissionService).clearPermission(nodeRef, readGroup); + verify(mockedPermissionService).clearPermission(nodeRef, writeGroup); + + // verify read group created correctly + verify(mockedAuthorityService).createAuthority(AuthorityType.GROUP, newReadGroup, newReadGroup, Collections.singleton(RMAuthority.ZONE_APP_RM)); + newReadGroup = GROUP_PREFIX + newReadGroup; + verify(mockedAuthorityService).addAuthority(GROUP_PREFIX + ROOT_IPR_GROUP, newReadGroup); + verify(mockedAuthorityService).addAuthority(newReadGroup, user); + verify(mockedAuthorityService).addAuthority(newReadGroup, group); + + // verify write group created correctly + verify(mockedAuthorityService).createAuthority(AuthorityType.GROUP, newWriteGroup, newWriteGroup, Collections.singleton(RMAuthority.ZONE_APP_RM)); + newWriteGroup = GROUP_PREFIX + newWriteGroup; + verify(mockedAuthorityService).addAuthority(GROUP_PREFIX + ROOT_IPR_GROUP, newWriteGroup); + verify(mockedAuthorityService).addAuthority(newWriteGroup, userW); + verify(mockedAuthorityService).addAuthority(newWriteGroup, groupW); + + // verify groups assigned to RM roles + verify(mockedFilePlanRoleService).assignRoleToAuthority(filePlan, FilePlanRoleService.ROLE_EXTENDED_READERS, newReadGroup); + verify(mockedFilePlanRoleService).assignRoleToAuthority(filePlan, FilePlanRoleService.ROLE_EXTENDED_WRITERS, newWriteGroup); + + // verify permissions are assigned to node + verify(mockedPermissionService).setPermission(nodeRef, newReadGroup, RMPermissionModel.READ_RECORDS, true); + verify(mockedPermissionService).setPermission(nodeRef, newWriteGroup, RMPermissionModel.FILING, true); + + } + + /** + * Given that a node has renditions + * When I add extended security + * Then they are applied to the renditions + */ + @Test public void extendedSecurityAddedToRenditions() + { + // group names + String readGroup = extendedSecurityService.getIPRGroupShortName(READER_GROUP_PREFIX, READERS, 0); + String writeGroup = extendedSecurityService.getIPRGroupShortName(WRITER_GROUP_PREFIX, WRITERS, 0); + + // setup query results + when(mockedReadPagingResults.getPage()) + .thenReturn(Collections.emptyList()); + when(mockedAuthorityService.getAuthorities( + eq(AuthorityType.GROUP), + eq(RMAuthority.ZONE_APP_RM), + any(String.class), + eq(false), + eq(false), + any(PagingRequest.class))) + .thenReturn(mockedReadPagingResults); + + // setup renditions + NodeRef renditionNodeRef = AlfMock.generateNodeRef(mockedNodeService); + when(mockedNodeService.hasAspect(nodeRef, RecordsManagementModel.ASPECT_RECORD)) + .thenReturn(true); + when(mockedChildAssociationRef.getChildRef()) + .thenReturn(renditionNodeRef); + when(mockedNodeService.getChildAssocs(nodeRef, RenditionModel.ASSOC_RENDITION, RegexQNamePattern.MATCH_ALL)) + .thenReturn(Collections.singletonList(mockedChildAssociationRef)); + + // add extended security + extendedSecurityService.set(nodeRef, READERS, WRITERS); + + // verify no old permissions needing to be cleared + verify(mockedPermissionService, never()).clearPermission(eq(nodeRef), anyString()); + + // verify read group created correctly + verify(mockedAuthorityService).createAuthority(AuthorityType.GROUP, readGroup, readGroup, Collections.singleton(RMAuthority.ZONE_APP_RM)); + readGroup = GROUP_PREFIX + readGroup; + verify(mockedAuthorityService).addAuthority(GROUP_PREFIX + ROOT_IPR_GROUP, readGroup); + verify(mockedAuthorityService).addAuthority(readGroup, USER); + verify(mockedAuthorityService).addAuthority(readGroup, GROUP); + + // verify write group created correctly + verify(mockedAuthorityService).createAuthority(AuthorityType.GROUP, writeGroup, writeGroup, Collections.singleton(RMAuthority.ZONE_APP_RM)); + writeGroup = GROUP_PREFIX + writeGroup; + verify(mockedAuthorityService).addAuthority(GROUP_PREFIX + ROOT_IPR_GROUP, writeGroup); + verify(mockedAuthorityService).addAuthority(writeGroup, USER_W); + verify(mockedAuthorityService).addAuthority(writeGroup, GROUP_W); + + // verify groups assigned to RM roles + verify(mockedFilePlanRoleService).assignRoleToAuthority(filePlan, FilePlanRoleService.ROLE_EXTENDED_READERS, readGroup); + verify(mockedFilePlanRoleService).assignRoleToAuthority(filePlan, FilePlanRoleService.ROLE_EXTENDED_WRITERS, writeGroup); + + // verify permissions are assigned to node + verify(mockedPermissionService).setPermission(nodeRef, readGroup, RMPermissionModel.READ_RECORDS, true); + verify(mockedPermissionService).setPermission(nodeRef, writeGroup, RMPermissionModel.FILING, true); + + // verify permissions are assigned to the rendition + verify(mockedPermissionService).setPermission(renditionNodeRef, readGroup, RMPermissionModel.READ_RECORDS, true); + verify(mockedPermissionService).setPermission(renditionNodeRef, writeGroup, RMPermissionModel.FILING, true); + } + + /** + * Given that a node has extended security + * When I remove the extended security + * Then the inplace groups permissions are removed + */ + @Test public void removeAllExtendedSecurity() + { + // group names + String readGroup = extendedSecurityService.getIPRGroupShortName(READER_GROUP_FULL_PREFIX, READERS, 0); + String writeGroup = extendedSecurityService.getIPRGroupShortName(WRITER_GROUP_FULL_PREFIX, WRITERS, 0); + + // setup permissions + Set permissions = Stream + .of(new AccessPermissionImpl(AlfMock.generateText(), AccessStatus.ALLOWED, readGroup, 0), + new AccessPermissionImpl(AlfMock.generateText(), AccessStatus.ALLOWED, AlfMock.generateText(), 1), + new AccessPermissionImpl(AlfMock.generateText(), AccessStatus.ALLOWED, writeGroup, 2)) + .collect(Collectors.toSet()); + when(mockedPermissionService.getAllSetPermissions(nodeRef)) + .thenReturn(permissions); + + // remove extended security + extendedSecurityService.remove(nodeRef); + + // verify that the groups permissions have been removed + verify(mockedPermissionService).clearPermission(nodeRef, readGroup); + verify(mockedPermissionService).clearPermission(nodeRef, writeGroup); + } + + /** + * Given that a node has no extended security + * When I remove the extended security + * Then nothing happens + */ + @Test public void noExtendedSecurityToRemove() + { + when(mockedPermissionService.getAllSetPermissions(nodeRef)) + .thenReturn(HAS_NO_EXTENDED_SECURITY); + + // remove extended security + extendedSecurityService.remove(nodeRef); + + // verify that the groups permissions have been removed + verify(mockedPermissionService, never()).clearPermission(eq(nodeRef), anyString()); + } + + /** + * Given that node has renditions + * When I remove the extended security for a node + * Then the extended security is also removed from the renditions + */ + @Test public void removeExtendedSecurityFromRenditions() + { + // group names + String readGroup = extendedSecurityService.getIPRGroupShortName(READER_GROUP_FULL_PREFIX, READERS, 0); + String writeGroup = extendedSecurityService.getIPRGroupShortName(WRITER_GROUP_FULL_PREFIX, WRITERS, 0); + + // setup renditions + NodeRef renditionNodeRef = AlfMock.generateNodeRef(mockedNodeService); + when(mockedNodeService.hasAspect(nodeRef, RecordsManagementModel.ASPECT_RECORD)) + .thenReturn(true); + when(mockedChildAssociationRef.getChildRef()) + .thenReturn(renditionNodeRef); + when(mockedNodeService.getChildAssocs(nodeRef, RenditionModel.ASSOC_RENDITION, RegexQNamePattern.MATCH_ALL)) + .thenReturn(Collections.singletonList(mockedChildAssociationRef)); + + // setup permissions + Set permissions = Stream + .of(new AccessPermissionImpl(AlfMock.generateText(), AccessStatus.ALLOWED, readGroup, 0), + new AccessPermissionImpl(AlfMock.generateText(), AccessStatus.ALLOWED, AlfMock.generateText(), 1), + new AccessPermissionImpl(AlfMock.generateText(), AccessStatus.ALLOWED, writeGroup, 2)) + .collect(Collectors.toSet()); + when(mockedPermissionService.getAllSetPermissions(nodeRef)) + .thenReturn(permissions); + + // remove extended security + extendedSecurityService.remove(nodeRef); + + // verify that the groups permissions have been removed + verify(mockedPermissionService).clearPermission(nodeRef, readGroup); + verify(mockedPermissionService).clearPermission(nodeRef, writeGroup); + + // verify that the groups permissions have been removed from the rendition + verify(mockedPermissionService).clearPermission(renditionNodeRef, readGroup); + verify(mockedPermissionService).clearPermission(renditionNodeRef, writeGroup); + + } +} diff --git a/rm-server/unit-test/java/org/alfresco/module/org_alfresco_module_rm/security/FilePlanPermissionServiceImplUnitTest.java b/rm-server/unit-test/java/org/alfresco/module/org_alfresco_module_rm/security/FilePlanPermissionServiceImplUnitTest.java index 430999961f..3414321c3e 100644 --- a/rm-server/unit-test/java/org/alfresco/module/org_alfresco_module_rm/security/FilePlanPermissionServiceImplUnitTest.java +++ b/rm-server/unit-test/java/org/alfresco/module/org_alfresco_module_rm/security/FilePlanPermissionServiceImplUnitTest.java @@ -18,13 +18,10 @@ */ package org.alfresco.module.org_alfresco_module_rm.security; -import static org.mockito.Matchers.anyString; -import static org.mockito.Matchers.eq; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyNoMoreInteractions; import java.util.HashSet; import java.util.Set; @@ -163,16 +160,7 @@ public class FilePlanPermissionServiceImplUnitTest extends BaseUnitTest filePlanPermissionService.setPermission(unfiledRecordFolder, AUTHORITY, RMPermissionModel.READ_RECORDS); // verify permission set on target node - verify(mockedPermissionService, times(1)).setPermission(unfiledRecordFolder, AUTHORITY, RMPermissionModel.READ_RECORDS, true); - - // verify READ permission set up hierarchy - verify(mockedPermissionService, times(1)).setPermission(unfiledRecordContainer, AUTHORITY, RMPermissionModel.READ_RECORDS, true); - verify(mockedPermissionService, times(1)).setPermission(filePlan, AUTHORITY, RMPermissionModel.READ_RECORDS, true); - - // verify READ permission set down hierarchy - verify(mockedPermissionService, times(1)).setPermission(unfiledRecordFolderChild, AUTHORITY, RMPermissionModel.READ_RECORDS, true); - verify(mockedPermissionService, times(1)).setPermission(unfiledRecord, AUTHORITY, RMPermissionModel.READ_RECORDS, true); - + verify(mockedPermissionService, times(1)).setPermission(unfiledRecordFolder, AUTHORITY, RMPermissionModel.READ_RECORDS, true); } /** @@ -185,15 +173,7 @@ public class FilePlanPermissionServiceImplUnitTest extends BaseUnitTest filePlanPermissionService.setPermission(unfiledRecordFolder, AUTHORITY, RMPermissionModel.FILING); // verify permission set on target node - verify(mockedPermissionService, times(1)).setPermission(unfiledRecordFolder, AUTHORITY, RMPermissionModel.FILING, true); - - // verify READ permission set up hierarchy - verify(mockedPermissionService, times(1)).setPermission(unfiledRecordContainer, AUTHORITY, RMPermissionModel.READ_RECORDS, true); - verify(mockedPermissionService, times(1)).setPermission(filePlan, AUTHORITY, RMPermissionModel.READ_RECORDS, true); - - // verify FILING permission set down hierarchy - verify(mockedPermissionService, times(1)).setPermission(unfiledRecordFolderChild, AUTHORITY, RMPermissionModel.FILING, true); - verify(mockedPermissionService, times(1)).setPermission(unfiledRecord, AUTHORITY, RMPermissionModel.FILING, true); + verify(mockedPermissionService, times(1)).setPermission(unfiledRecordFolder, AUTHORITY, RMPermissionModel.FILING, true); } /** @@ -207,14 +187,6 @@ public class FilePlanPermissionServiceImplUnitTest extends BaseUnitTest // verify permission deleted on target node verify(mockedPermissionService, times(1)).deletePermission(unfiledRecordFolder, AUTHORITY, RMPermissionModel.READ_RECORDS); - - // verify no permissions deleted up the hierarchy - verify(mockedPermissionService, never()).deletePermission(eq(unfiledRecordContainer), eq(AUTHORITY), anyString()); - verify(mockedPermissionService, never()).deletePermission(eq(filePlan), eq(AUTHORITY), anyString()); - - // verify READ permission removed down hierarchy - verify(mockedPermissionService, times(1)).deletePermission(unfiledRecordFolderChild, AUTHORITY, RMPermissionModel.READ_RECORDS); - verify(mockedPermissionService, times(1)).deletePermission(unfiledRecord, AUTHORITY, RMPermissionModel.READ_RECORDS); } /** @@ -228,16 +200,6 @@ public class FilePlanPermissionServiceImplUnitTest extends BaseUnitTest // verify permission set on target node verify(mockedPermissionService, times(1)).setPermission(holdContainer, AUTHORITY, RMPermissionModel.READ_RECORDS, true); - - // verify READ permission set up hierarchy - verify(mockedPermissionService, times(1)).setPermission(filePlan, AUTHORITY, RMPermissionModel.READ_RECORDS, true); - - // verify READ permission set on hold - verify(mockedPermissionService, times(1)).setPermission(hold, AUTHORITY, RMPermissionModel.READ_RECORDS, true); - - // verify permission not set on child of hold - verify(mockedPermissionService, never()).setPermission(eq(heldRecord), eq(AUTHORITY), anyString(), eq(true)); - } /** @@ -250,17 +212,7 @@ public class FilePlanPermissionServiceImplUnitTest extends BaseUnitTest filePlanPermissionService.setPermission(holdContainer, AUTHORITY, RMPermissionModel.FILING); // verify permission set on target node - verify(mockedPermissionService, times(1)).setPermission(holdContainer, AUTHORITY, RMPermissionModel.FILING, true); - - // verify READ permission set up hierarchy - verify(mockedPermissionService, times(1)).setPermission(filePlan, AUTHORITY, RMPermissionModel.READ_RECORDS, true); - - // verify FILING permission set on hold - verify(mockedPermissionService, times(1)).setPermission(hold, AUTHORITY, RMPermissionModel.FILING, true); - - // verify permission not set on child of hold - verify(mockedPermissionService, never()).setPermission(eq(heldRecord), eq(AUTHORITY), anyString(), eq(true)); - + verify(mockedPermissionService, times(1)).setPermission(holdContainer, AUTHORITY, RMPermissionModel.FILING, true); } /** @@ -274,13 +226,6 @@ public class FilePlanPermissionServiceImplUnitTest extends BaseUnitTest // verify permission set on target node verify(mockedPermissionService, times(1)).setPermission(hold, AUTHORITY, RMPermissionModel.READ_RECORDS, true); - - // verify READ permission set up hierarchy - verify(mockedPermissionService, times(1)).setPermission(holdContainer, AUTHORITY, RMPermissionModel.READ_RECORDS, true); - verify(mockedPermissionService, times(1)).setPermission(filePlan, AUTHORITY, RMPermissionModel.READ_RECORDS, true); - - // verify permission not set on child of hold - verify(mockedPermissionService, never()).setPermission(eq(heldRecord), eq(AUTHORITY), anyString(), eq(true)); } /** @@ -293,14 +238,7 @@ public class FilePlanPermissionServiceImplUnitTest extends BaseUnitTest filePlanPermissionService.setPermission(hold, AUTHORITY, RMPermissionModel.FILING); // verify permission set on target node - verify(mockedPermissionService, times(1)).setPermission(hold, AUTHORITY, RMPermissionModel.FILING, true); - - // verify READ permission set up hierarchy - verify(mockedPermissionService, times(1)).setPermission(holdContainer, AUTHORITY, RMPermissionModel.READ_RECORDS, true); - verify(mockedPermissionService, times(1)).setPermission(filePlan, AUTHORITY, RMPermissionModel.READ_RECORDS, true); - - // verify permission not set on child of hold - verify(mockedPermissionService, never()).setPermission(eq(heldRecord), eq(AUTHORITY), anyString(), eq(true)); + verify(mockedPermissionService, times(1)).setPermission(hold, AUTHORITY, RMPermissionModel.FILING, true); } /** @@ -313,10 +251,6 @@ public class FilePlanPermissionServiceImplUnitTest extends BaseUnitTest // setup basic file and read for authorities perms.add(new AccessPermissionImpl(RMPermissionModel.READ_RECORDS, AccessStatus.ALLOWED, AUTHORITY, 0)); perms.add(new AccessPermissionImpl(RMPermissionModel.FILING, AccessStatus.ALLOWED, AUTHORITY2, 1)); - - // setup in-place readers and writers - perms.add(new AccessPermissionImpl(RMPermissionModel.READ_RECORDS, AccessStatus.ALLOWED, ExtendedReaderDynamicAuthority.EXTENDED_READER, 2)); - perms.add(new AccessPermissionImpl(RMPermissionModel.FILING, AccessStatus.ALLOWED, ExtendedWriterDynamicAuthority.EXTENDED_WRITER, 3)); doReturn(perms).when(mockedPermissionService).getAllSetPermissions(nodeRef); } @@ -324,13 +258,12 @@ public class FilePlanPermissionServiceImplUnitTest extends BaseUnitTest /** * Helper to verify the core permissions have been initialized correctly */ - private void verifyInitPermissions(NodeRef nodeRef) + private void verifyInitPermissions(NodeRef nodeRef, boolean isInherited) { - verify(mockedPermissionService, times(1)).setInheritParentPermissions(nodeRef, false); - verify(mockedPermissionService, times(1)).clearPermission(nodeRef, null); - verify(mockedPermissionService, times(1)).setPermission(nodeRef, ExtendedReaderDynamicAuthority.EXTENDED_READER, RMPermissionModel.READ_RECORDS, true); - verify(mockedPermissionService, times(1)).setPermission(nodeRef, ExtendedWriterDynamicAuthority.EXTENDED_WRITER, RMPermissionModel.FILING, true); - verify(mockedOwnableService, times(1)).setOwner(nodeRef, OwnableService.NO_OWNER); + verify(mockedPermissionService).getAllSetPermissions(nodeRef); + verify(mockedPermissionService).setInheritParentPermissions(nodeRef, isInherited); + verify(mockedPermissionService).clearPermission(nodeRef, null); + verify(mockedOwnableService).setOwner(nodeRef, OwnableService.NO_OWNER); } /** @@ -341,21 +274,20 @@ public class FilePlanPermissionServiceImplUnitTest extends BaseUnitTest * @param read verification mode relating to setting read on the child * @param filling verification mode relating to setting filling on the child */ - private void verifyInitPermissions(NodeRef parent, NodeRef child, VerificationMode read, VerificationMode filling) + private void verifyInitPermissions(NodeRef parent, NodeRef child, VerificationMode read, VerificationMode filling, boolean isParentFilePlan, boolean isInherited) { // verify the core permissions are set-up correctly - verifyInitPermissions(child); - - // verify the permissions came from the correct parent - verify(mockedPermissionService).getAllSetPermissions(parent); - - // verify all the inherited permissions are set correctly (note read are not inherited from fileplan) - verify(mockedPermissionService, filling).setPermission(child, AUTHORITY2, RMPermissionModel.FILING, true); - verify(mockedPermissionService, read).setPermission(child, AUTHORITY, RMPermissionModel.READ_RECORDS, true); - - // verify that there are no unaccounted for interactions with the permission service - verifyNoMoreInteractions(mockedPermissionService); + verifyInitPermissions(child, isInherited); + if (isParentFilePlan) + { + // verify the permissions came from the correct parent + verify(mockedPermissionService).getAllSetPermissions(parent); + + // verify all the inherited permissions are set correctly (note read are not inherited from fileplan) + verify(mockedPermissionService, filling).setPermission(child, AUTHORITY2, RMPermissionModel.FILING, true); + verify(mockedPermissionService, read).setPermission(child, AUTHORITY, RMPermissionModel.READ_RECORDS, true); + } } /** @@ -371,7 +303,7 @@ public class FilePlanPermissionServiceImplUnitTest extends BaseUnitTest filePlanPermissionService.setupRecordCategoryPermissions(rootRecordCategory); // verify permission init - verifyInitPermissions(filePlan, rootRecordCategory, never(), times(1)); + verifyInitPermissions(filePlan, rootRecordCategory, never(), times(1), true, false); } /** @@ -387,7 +319,7 @@ public class FilePlanPermissionServiceImplUnitTest extends BaseUnitTest filePlanPermissionService.setupRecordCategoryPermissions(recordCategory); // verify permission init - verifyInitPermissions(rootRecordCategory, recordCategory, times(1), times(1)); + verifyInitPermissions(rootRecordCategory, recordCategory, times(1), times(1), false, true); } /** @@ -403,7 +335,7 @@ public class FilePlanPermissionServiceImplUnitTest extends BaseUnitTest filePlanPermissionService.setupPermissions(recordCategory, newRecordFolder); // verify permission init - verifyInitPermissions(recordCategory, newRecordFolder, times(1), times(1)); + verifyInitPermissions(recordCategory, newRecordFolder, times(1), times(1), false, true); } @@ -420,7 +352,7 @@ public class FilePlanPermissionServiceImplUnitTest extends BaseUnitTest filePlanPermissionService.setupPermissions(newRecordFolder, newRecord); // verify permission init - verifyInitPermissions(newRecordFolder, newRecord, times(1), times(1)); + verifyInitPermissions(newRecordFolder, newRecord, times(1), times(1), false, true); } /** @@ -436,7 +368,7 @@ public class FilePlanPermissionServiceImplUnitTest extends BaseUnitTest filePlanPermissionService.setupPermissions(filePlan, holdContainer); // verify permissions are set-up correctly - verifyInitPermissions(filePlan, holdContainer, times(1), times(1)); + verifyInitPermissions(filePlan, holdContainer, times(1), times(1), false, true); } /** @@ -452,7 +384,7 @@ public class FilePlanPermissionServiceImplUnitTest extends BaseUnitTest filePlanPermissionService.setupPermissions(holdContainer, hold); // verify permissions are set-up correctly - verifyInitPermissions(holdContainer, hold, never(), times(1)); + verifyInitPermissions(holdContainer, hold, never(), times(1), false, false); } @@ -469,7 +401,7 @@ public class FilePlanPermissionServiceImplUnitTest extends BaseUnitTest filePlanPermissionService.setupPermissions(filePlan, unfiledRecordContainer); // verify permissions are set-up correctly - verifyInitPermissions(filePlan, unfiledRecordContainer, times(1), times(1)); + verifyInitPermissions(filePlan, unfiledRecordContainer, times(1), times(1), false, false); } /** @@ -485,7 +417,7 @@ public class FilePlanPermissionServiceImplUnitTest extends BaseUnitTest filePlanPermissionService.setupPermissions(unfiledRecordContainer, unfiledRecordFolder); // verify permissions are set-up correctly - verifyInitPermissions(unfiledRecordContainer, unfiledRecordFolder, never(), times(1)); + verifyInitPermissions(unfiledRecordContainer, unfiledRecordFolder, never(), times(1), false, true); } @@ -502,7 +434,7 @@ public class FilePlanPermissionServiceImplUnitTest extends BaseUnitTest filePlanPermissionService.setupPermissions(unfiledRecordFolder, unfiledRecord); // verify permission init - verifyInitPermissions(unfiledRecordFolder, unfiledRecord, times(1), times(1)); + verifyInitPermissions(unfiledRecordFolder, unfiledRecord, times(1), times(1), false, true); } } diff --git a/rm-server/unit-test/java/org/alfresco/module/org_alfresco_module_rm/test/util/AlfMock.java b/rm-server/unit-test/java/org/alfresco/module/org_alfresco_module_rm/test/util/AlfMock.java new file mode 100644 index 0000000000..5cf8e7b9dc --- /dev/null +++ b/rm-server/unit-test/java/org/alfresco/module/org_alfresco_module_rm/test/util/AlfMock.java @@ -0,0 +1,130 @@ +/* + * #%L + * Alfresco Records Management Module + * %% + * Copyright (C) 2005 - 2016 Alfresco Software Limited + * %% + * This file is part of the Alfresco software. + * - + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * - + * 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 . + * #L% + */ + +package org.alfresco.module.org_alfresco_module_rm.test.util; + +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.when; + +import java.util.UUID; + +import org.alfresco.model.ContentModel; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.NodeService; +import org.alfresco.service.cmr.repository.StoreRef; +import org.alfresco.service.namespace.QName; +import org.alfresco.util.GUID; + +/** + * Utilities helpful when mocking Alfresco constructs. + * + * @author Roy Wetherall + * @since 2.4.a + */ +public class AlfMock +{ + /** + * Helper to generate random text value suitable for a property + * value or node name + */ + public static String generateText() + { + return UUID.randomUUID().toString(); + } + + /** + * Helper method to generate a qname. + */ + public static QName generateQName() + { + return generateQName(GUID.generate()); + } + + /** + * Helper method to generate a qname. + */ + public static QName generateQName(String uri) + { + return QName.createQName(uri, GUID.generate()); + } + + /** + * Helper method to generate a node reference. + * + * @return {@link NodeRef} node reference that behaves like a node that exists in the spaces store + */ + public static NodeRef generateNodeRef(NodeService mockedNodeService) + { + return generateNodeRef(mockedNodeService, null); + } + + /** + * Helper method to generate a node reference of a particular type. + * + * @param type content type qualified name + * @return {@link NodeRef} node reference that behaves like a node that exists in the spaces store with + * the content type provided + */ + public static NodeRef generateNodeRef(NodeService mockedNodeService, QName type) + { + return generateNodeRef(mockedNodeService, type, true); + } + + /** + * Helper method to generate a cm:content node reference with a given name. + * + * @param name content name + * @return NodeRef node reference + */ + public static NodeRef generateCmContent(NodeService mockedNodeService, String name) + { + NodeRef nodeRef = generateNodeRef(mockedNodeService, ContentModel.TYPE_CONTENT, true); + doReturn(name).when(mockedNodeService).getProperty(nodeRef, ContentModel.PROP_NAME); + return nodeRef; + } + + /** + * Helper method to generate a node reference of a particular type with a given existence characteristic. + * + * @param type content type qualified name + * @param exists indicates whether this node should behave like a node that exists or not + * @return {@link NodeRef} node reference that behaves like a node that exists (or not) in the spaces store with + * the content type provided + */ + public static NodeRef generateNodeRef(NodeService mockedNodeService, QName type, boolean exists) + { + NodeRef nodeRef = new NodeRef(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE, GUID.generate()); + when(mockedNodeService.exists(eq(nodeRef))).thenReturn(exists); + if (type != null) + { + when(mockedNodeService.getType(eq(nodeRef))).thenReturn(type); + } + return nodeRef; + } + +} From 194e075cc680bdb5e3f9fe7006f440698b966537 Mon Sep 17 00:00:00 2001 From: Silviu Dinuta Date: Tue, 13 Sep 2016 15:58:43 +0300 Subject: [PATCH 02/11] RM-3906: Implemented webscript --- .../rm-webscript-context.xml | 13 + .../roles/rm-dynamicauthorities.get.desc.xml | 8 + .../roles/rm-dynamicauthorities.get.json.ftl | 4 + .../scripts/roles/DynamicAuthoritiesGet.java | 255 ++++++++++++++++++ 4 files changed, 280 insertions(+) create mode 100644 rm-server/config/alfresco/templates/webscripts/org/alfresco/repository/roles/rm-dynamicauthorities.get.desc.xml create mode 100644 rm-server/config/alfresco/templates/webscripts/org/alfresco/repository/roles/rm-dynamicauthorities.get.json.ftl create mode 100644 rm-server/source/java/org/alfresco/repo/web/scripts/roles/DynamicAuthoritiesGet.java diff --git a/rm-server/config/alfresco/module/org_alfresco_module_rm/rm-webscript-context.xml b/rm-server/config/alfresco/module/org_alfresco_module_rm/rm-webscript-context.xml index 7a31fd1b76..da41dd3d0e 100644 --- a/rm-server/config/alfresco/module/org_alfresco_module_rm/rm-webscript-context.xml +++ b/rm-server/config/alfresco/module/org_alfresco_module_rm/rm-webscript-context.xml @@ -585,6 +585,19 @@ + + + + + + + + + + + + Get webscript for removing dynamic authorities from RM + Gets the result after removing dynamic authorities from RM. + /api/rm/rm-dynamicauthorities?batchsize={batchsize} + argument + user + required + \ No newline at end of file diff --git a/rm-server/config/alfresco/templates/webscripts/org/alfresco/repository/roles/rm-dynamicauthorities.get.json.ftl b/rm-server/config/alfresco/templates/webscripts/org/alfresco/repository/roles/rm-dynamicauthorities.get.json.ftl new file mode 100644 index 0000000000..10971542cb --- /dev/null +++ b/rm-server/config/alfresco/templates/webscripts/org/alfresco/repository/roles/rm-dynamicauthorities.get.json.ftl @@ -0,0 +1,4 @@ +{ + "responsestatus" : "${responsestatus?json_string}", + "message" : "${message?json_string}" +} \ No newline at end of file diff --git a/rm-server/source/java/org/alfresco/repo/web/scripts/roles/DynamicAuthoritiesGet.java b/rm-server/source/java/org/alfresco/repo/web/scripts/roles/DynamicAuthoritiesGet.java new file mode 100644 index 0000000000..67e77045c9 --- /dev/null +++ b/rm-server/source/java/org/alfresco/repo/web/scripts/roles/DynamicAuthoritiesGet.java @@ -0,0 +1,255 @@ +/* + * 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.repo.web.scripts.roles; + +import java.text.MessageFormat; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.alfresco.model.ContentModel; +import org.alfresco.module.org_alfresco_module_rm.model.RecordsManagementModel; +import org.alfresco.module.org_alfresco_module_rm.security.ExtendedReaderDynamicAuthority; +import org.alfresco.module.org_alfresco_module_rm.security.ExtendedSecurityService; +import org.alfresco.module.org_alfresco_module_rm.security.ExtendedWriterDynamicAuthority; +import org.alfresco.repo.domain.node.NodeDAO; +import org.alfresco.repo.domain.patch.PatchDAO; +import org.alfresco.repo.domain.qname.QNameDAO; +import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.NodeService; +import org.alfresco.service.cmr.security.PermissionService; +import org.alfresco.service.namespace.QName; +import org.alfresco.service.transaction.TransactionService; +import org.alfresco.util.Pair; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.extensions.webscripts.Cache; +import org.springframework.extensions.webscripts.DeclarativeWebScript; +import org.springframework.extensions.webscripts.Status; +import org.springframework.extensions.webscripts.WebScriptRequest; + +/** + * Webscript used for removing dynamic authorities from the records. + * + * @author Silviu Dinuta + * @since 2.3.0.7 + */ +@SuppressWarnings("deprecation") +public class DynamicAuthoritiesGet extends DeclarativeWebScript implements RecordsManagementModel +{ + + private static final String MESSAGE_PROCESSING_BEGIN = "Processing - BEGIN"; + private static final String MESSAGE_PROCESSING_END = "Processing - END"; + private static final String MESSAGE_PROCESSING_RECORD_END_TEMPLATE = "Processing record {0} - END"; + private static final String MESSAGE_PROCESSING_RECORD_BEGIN_TEMPLATE = "Processing record {0} - BEGIN"; + private static final String MESSAGE_BATCHSIZE_IS_INVALID = "Parameter batchsize is invalid."; + private static final String MESSAGE_BATCHSIZE_IS_MANDATORY = "Parameter batchsize is mandatory"; + private static final String SUCCESS_STATUS = "success"; + private static final String FAILED_STATUS = "failed"; + /** + * The logger + */ + private static Log logger = LogFactory.getLog(DynamicAuthoritiesGet.class); + private static final String BATCH_SIZE = "batchsize"; + private static final String TOTAL_NUMBER_TO_PROCESS = "maxProcessedRecords"; + private static final String MODEL_STATUS = "responsestatus"; + private static final String MODEL_MESSAGE = "message"; + private static final String MESSAGE_ALL_TEMPLATE = "Processed {0} records."; + private static final String MESSAGE_PARTIAL_TEMPLATE = "Processed first {0} records."; + private static final String MESSAGE_NO_RECORDS_TO_PROCESS = "There where no records to be processed."; + + + /** services */ + private PatchDAO patchDAO; + private NodeDAO nodeDAO; + private QNameDAO qnameDAO; + private NodeService nodeService; + private PermissionService permissionService; + private ExtendedSecurityService extendedSecurityService; + private TransactionService transactionService; + + /** service setters */ + public void setPatchDAO(PatchDAO patchDAO) { this.patchDAO = patchDAO; } + public void setNodeDAO(NodeDAO nodeDAO) { this.nodeDAO = nodeDAO; } + public void setQnameDAO(QNameDAO qnameDAO) { this.qnameDAO = qnameDAO; } + public void setNodeService(NodeService nodeService) { this.nodeService = nodeService; } + public void setPermissionService(PermissionService permissionService) { this.permissionService = permissionService; } + public void setExtendedSecurityService(ExtendedSecurityService extendedSecurityService) { this.extendedSecurityService = extendedSecurityService; } + public void setTransactionService(TransactionService transactionService) { this.transactionService = transactionService; } + + @Override + protected Map executeImpl(WebScriptRequest req, Status status, Cache cache) + { + Map model = new HashMap(); + String batchSizeStr = req.getParameter(BATCH_SIZE); + String totalToBeProcessedRecordsStr = req.getParameter(TOTAL_NUMBER_TO_PROCESS); + + Long size = 0L; + if (batchSizeStr == null || batchSizeStr.length() == 0) + { + model.put(MODEL_STATUS, FAILED_STATUS); + model.put(MODEL_MESSAGE, MESSAGE_BATCHSIZE_IS_MANDATORY); + if (logger.isDebugEnabled()) + { + logger.debug(MESSAGE_BATCHSIZE_IS_MANDATORY); + } + return model; + } + try + { + size = Long.parseLong(batchSizeStr); + } + catch(NumberFormatException ex) + { + model.put(MODEL_STATUS, FAILED_STATUS); + model.put(MODEL_MESSAGE, MESSAGE_BATCHSIZE_IS_INVALID); + if (logger.isDebugEnabled()) + { + logger.debug(MESSAGE_BATCHSIZE_IS_INVALID); + } + return model; + } + final Long batchSize = size; + // get the max node id and the extended security aspect + Long maxNodeId = patchDAO.getMaxAdmNodeID(); + final Pair recordAspectPair = qnameDAO.getQName(ASPECT_EXTENDED_SECURITY); + if(recordAspectPair == null) + { + model.put(MODEL_STATUS, SUCCESS_STATUS); + model.put(MODEL_MESSAGE, MESSAGE_NO_RECORDS_TO_PROCESS); + if (logger.isDebugEnabled()) + { + logger.debug(MESSAGE_NO_RECORDS_TO_PROCESS); + } + return model; + } + + Long totalNumberOfRecordsToProcess = 0L; + if (totalToBeProcessedRecordsStr != null && totalToBeProcessedRecordsStr.length() != 0) + { + try + { + totalNumberOfRecordsToProcess = Long.parseLong(totalToBeProcessedRecordsStr); + } + catch(NumberFormatException ex) + { + //do nothing here, the value will remain 0L in this case + } + } + + final Long maxRecordsToProcess = totalNumberOfRecordsToProcess; + final List processedNodes = new ArrayList(); + if (logger.isDebugEnabled()) + { + logger.debug(MESSAGE_PROCESSING_BEGIN); + } + // by batch size + for (Long i = 0L; i < maxNodeId; i+=batchSize) + { + if(maxRecordsToProcess != 0 && processedNodes.size() >= maxRecordsToProcess) + { + break; + } + final Long currentIndex = i; + + transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback() + { + public Void execute() throws Throwable + { + // get the nodes with the extended security aspect applied + List nodeIds = patchDAO.getNodesByAspectQNameId(recordAspectPair.getFirst(), currentIndex, currentIndex + batchSize); + + // process each one + for (Long nodeId : nodeIds) + { + if(maxRecordsToProcess != 0 && processedNodes.size() >= maxRecordsToProcess) + { + break; + } + NodeRef record = nodeDAO.getNodePair(nodeId).getSecond(); + if (logger.isDebugEnabled()) + { + logger.debug(MessageFormat.format(MESSAGE_PROCESSING_RECORD_BEGIN_TEMPLATE, (String) nodeService.getProperty(record, ContentModel.PROP_NAME))); + } + processNode(record); + if (logger.isDebugEnabled()) + { + logger.debug(MessageFormat.format(MESSAGE_PROCESSING_RECORD_END_TEMPLATE, (String) nodeService.getProperty(record, ContentModel.PROP_NAME))); + } + processedNodes.add(record); + } + + return null; + } + }, + false, // read only + true); // requires new + } + if (logger.isDebugEnabled()) + { + logger.debug(MESSAGE_PROCESSING_END); + } + int processedNodesSize = processedNodes.size(); + String message = ""; + if(totalNumberOfRecordsToProcess == 0 || (totalNumberOfRecordsToProcess > 0 && processedNodesSize < totalNumberOfRecordsToProcess)) + { + message = MessageFormat.format(MESSAGE_ALL_TEMPLATE, processedNodesSize); + } + if (totalNumberOfRecordsToProcess > 0 && totalNumberOfRecordsToProcess == processedNodesSize) + { + message = MessageFormat.format(MESSAGE_PARTIAL_TEMPLATE, totalNumberOfRecordsToProcess); + } + model.put(MODEL_STATUS, SUCCESS_STATUS); + model.put(MODEL_MESSAGE, message); + if (logger.isDebugEnabled()) + { + logger.debug(message); + } + return model; + } + + /** + * Process each node + * + * @param nodeRef + */ + @SuppressWarnings({ "unchecked"}) + private void processNode(NodeRef nodeRef) + { + // get the reader/writer data + Map readers = (Map)nodeService.getProperty(nodeRef, PROP_READERS); + Map writers = (Map)nodeService.getProperty(nodeRef, PROP_WRITERS); + + // remove extended security aspect + nodeService.removeAspect(nodeRef, ASPECT_EXTENDED_SECURITY); + + // remove dynamic authority permissions + permissionService.clearPermission(nodeRef, ExtendedReaderDynamicAuthority.EXTENDED_READER); + permissionService.clearPermission(nodeRef, ExtendedWriterDynamicAuthority.EXTENDED_WRITER); + + // if record then ... + if (nodeService.hasAspect(nodeRef, ASPECT_RECORD)) + { + // re-set extended security via API + extendedSecurityService.set(nodeRef, readers.keySet(), writers.keySet()); + } + } +} From 280a3656be6df2a84b27817c79ac147fbab4ccad Mon Sep 17 00:00:00 2001 From: Silviu Dinuta Date: Tue, 13 Sep 2016 18:04:39 +0300 Subject: [PATCH 03/11] RM-3906: added max records parameter in webscript url as obtional and added aurthentication to admin --- .../repository/roles/rm-dynamicauthorities.get.desc.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rm-server/config/alfresco/templates/webscripts/org/alfresco/repository/roles/rm-dynamicauthorities.get.desc.xml b/rm-server/config/alfresco/templates/webscripts/org/alfresco/repository/roles/rm-dynamicauthorities.get.desc.xml index 5ad672ac73..c044ea12b8 100644 --- a/rm-server/config/alfresco/templates/webscripts/org/alfresco/repository/roles/rm-dynamicauthorities.get.desc.xml +++ b/rm-server/config/alfresco/templates/webscripts/org/alfresco/repository/roles/rm-dynamicauthorities.get.desc.xml @@ -1,8 +1,8 @@ Get webscript for removing dynamic authorities from RM Gets the result after removing dynamic authorities from RM. - /api/rm/rm-dynamicauthorities?batchsize={batchsize} + /api/rm/rm-dynamicauthorities?batchsize={batchsize}&maxProcessedRecords={maxProcessedRecords?} argument - user + admin required \ No newline at end of file From 9b5e3119367a38c812e328c376b3d2508440d0c3 Mon Sep 17 00:00:00 2001 From: Silviu Dinuta Date: Tue, 13 Sep 2016 21:12:15 +0300 Subject: [PATCH 04/11] RM-3960: did most of the review mentioned improvements --- .../org_alfresco_module_rm/log4j.properties | 3 +- .../scripts/roles/DynamicAuthoritiesGet.java | 46 +++++-------------- 2 files changed, 14 insertions(+), 35 deletions(-) diff --git a/rm-server/config/alfresco/module/org_alfresco_module_rm/log4j.properties b/rm-server/config/alfresco/module/org_alfresco_module_rm/log4j.properties index 7a14ca27f5..83e724394e 100644 --- a/rm-server/config/alfresco/module/org_alfresco_module_rm/log4j.properties +++ b/rm-server/config/alfresco/module/org_alfresco_module_rm/log4j.properties @@ -55,4 +55,5 @@ log4j.logger.org.alfresco.module.org_alfresco_module_rm.patch=info # # Job debug # -#log4j.logger.org.alfresco.module.org_alfresco_module_rm.job=debug \ No newline at end of file +#log4j.logger.org.alfresco.module.org_alfresco_module_rm.job=debug +log4j.logger.org.alfresco.repo.web.scripts.roles.DynamicAuthoritiesGet=info \ No newline at end of file diff --git a/rm-server/source/java/org/alfresco/repo/web/scripts/roles/DynamicAuthoritiesGet.java b/rm-server/source/java/org/alfresco/repo/web/scripts/roles/DynamicAuthoritiesGet.java index 67e77045c9..73a11774f7 100644 --- a/rm-server/source/java/org/alfresco/repo/web/scripts/roles/DynamicAuthoritiesGet.java +++ b/rm-server/source/java/org/alfresco/repo/web/scripts/roles/DynamicAuthoritiesGet.java @@ -39,6 +39,7 @@ import org.alfresco.service.cmr.security.PermissionService; import org.alfresco.service.namespace.QName; import org.alfresco.service.transaction.TransactionService; import org.alfresco.util.Pair; +import org.apache.commons.lang.StringUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.extensions.webscripts.Cache; @@ -103,14 +104,11 @@ public class DynamicAuthoritiesGet extends DeclarativeWebScript implements Recor String totalToBeProcessedRecordsStr = req.getParameter(TOTAL_NUMBER_TO_PROCESS); Long size = 0L; - if (batchSizeStr == null || batchSizeStr.length() == 0) + if (StringUtils.isBlank(batchSizeStr)) { model.put(MODEL_STATUS, FAILED_STATUS); model.put(MODEL_MESSAGE, MESSAGE_BATCHSIZE_IS_MANDATORY); - if (logger.isDebugEnabled()) - { - logger.debug(MESSAGE_BATCHSIZE_IS_MANDATORY); - } + logger.info(MESSAGE_BATCHSIZE_IS_MANDATORY); return model; } try @@ -121,10 +119,7 @@ public class DynamicAuthoritiesGet extends DeclarativeWebScript implements Recor { model.put(MODEL_STATUS, FAILED_STATUS); model.put(MODEL_MESSAGE, MESSAGE_BATCHSIZE_IS_INVALID); - if (logger.isDebugEnabled()) - { - logger.debug(MESSAGE_BATCHSIZE_IS_INVALID); - } + logger.info(MESSAGE_BATCHSIZE_IS_INVALID); return model; } final Long batchSize = size; @@ -135,15 +130,12 @@ public class DynamicAuthoritiesGet extends DeclarativeWebScript implements Recor { model.put(MODEL_STATUS, SUCCESS_STATUS); model.put(MODEL_MESSAGE, MESSAGE_NO_RECORDS_TO_PROCESS); - if (logger.isDebugEnabled()) - { - logger.debug(MESSAGE_NO_RECORDS_TO_PROCESS); - } + logger.info(MESSAGE_NO_RECORDS_TO_PROCESS); return model; } Long totalNumberOfRecordsToProcess = 0L; - if (totalToBeProcessedRecordsStr != null && totalToBeProcessedRecordsStr.length() != 0) + if (StringUtils.isNotBlank(totalToBeProcessedRecordsStr)) { try { @@ -157,10 +149,7 @@ public class DynamicAuthoritiesGet extends DeclarativeWebScript implements Recor final Long maxRecordsToProcess = totalNumberOfRecordsToProcess; final List processedNodes = new ArrayList(); - if (logger.isDebugEnabled()) - { - logger.debug(MESSAGE_PROCESSING_BEGIN); - } + logger.info(MESSAGE_PROCESSING_BEGIN); // by batch size for (Long i = 0L; i < maxNodeId; i+=batchSize) { @@ -185,15 +174,10 @@ public class DynamicAuthoritiesGet extends DeclarativeWebScript implements Recor break; } NodeRef record = nodeDAO.getNodePair(nodeId).getSecond(); - if (logger.isDebugEnabled()) - { - logger.debug(MessageFormat.format(MESSAGE_PROCESSING_RECORD_BEGIN_TEMPLATE, (String) nodeService.getProperty(record, ContentModel.PROP_NAME))); - } + String recordName = (String) nodeService.getProperty(record, ContentModel.PROP_NAME); + logger.info(MessageFormat.format(MESSAGE_PROCESSING_RECORD_BEGIN_TEMPLATE, recordName)); processNode(record); - if (logger.isDebugEnabled()) - { - logger.debug(MessageFormat.format(MESSAGE_PROCESSING_RECORD_END_TEMPLATE, (String) nodeService.getProperty(record, ContentModel.PROP_NAME))); - } + logger.info(MessageFormat.format(MESSAGE_PROCESSING_RECORD_END_TEMPLATE, recordName)); processedNodes.add(record); } @@ -203,10 +187,7 @@ public class DynamicAuthoritiesGet extends DeclarativeWebScript implements Recor false, // read only true); // requires new } - if (logger.isDebugEnabled()) - { - logger.debug(MESSAGE_PROCESSING_END); - } + logger.info(MESSAGE_PROCESSING_END); int processedNodesSize = processedNodes.size(); String message = ""; if(totalNumberOfRecordsToProcess == 0 || (totalNumberOfRecordsToProcess > 0 && processedNodesSize < totalNumberOfRecordsToProcess)) @@ -219,10 +200,7 @@ public class DynamicAuthoritiesGet extends DeclarativeWebScript implements Recor } model.put(MODEL_STATUS, SUCCESS_STATUS); model.put(MODEL_MESSAGE, message); - if (logger.isDebugEnabled()) - { - logger.debug(message); - } + logger.info(message); return model; } From cc78febd0cfec39c7a72f13ae80ab0e4373ceb39 Mon Sep 17 00:00:00 2001 From: Silviu Dinuta Date: Wed, 14 Sep 2016 05:19:15 +0300 Subject: [PATCH 05/11] RM-3906: added short description for url parameters --- .../repository/roles/rm-dynamicauthorities.get.desc.xml | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/rm-server/config/alfresco/templates/webscripts/org/alfresco/repository/roles/rm-dynamicauthorities.get.desc.xml b/rm-server/config/alfresco/templates/webscripts/org/alfresco/repository/roles/rm-dynamicauthorities.get.desc.xml index c044ea12b8..bd4aee74a0 100644 --- a/rm-server/config/alfresco/templates/webscripts/org/alfresco/repository/roles/rm-dynamicauthorities.get.desc.xml +++ b/rm-server/config/alfresco/templates/webscripts/org/alfresco/repository/roles/rm-dynamicauthorities.get.desc.xml @@ -1,6 +1,11 @@ - Get webscript for removing dynamic authorities from RM - Gets the result after removing dynamic authorities from RM. + Removes dynamic authorities + + URL parameter batchsize is mandatory, and represents the number of records that are processed in one transaction.
+ URL parameter maxProcessedRecords is optional, and represents the maximum number of records that will be processed in one request.
+ ]]> +
/api/rm/rm-dynamicauthorities?batchsize={batchsize}&maxProcessedRecords={maxProcessedRecords?} argument admin From 7d7d10768d1b18ecbc22e3d5283b85df9bbfc7cc Mon Sep 17 00:00:00 2001 From: Silviu Dinuta Date: Wed, 14 Sep 2016 10:32:42 +0300 Subject: [PATCH 06/11] RM-3906: set maxProcessedRecords to batchsize value if it is not set from the request --- .../alfresco/repo/web/scripts/roles/DynamicAuthoritiesGet.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/rm-server/source/java/org/alfresco/repo/web/scripts/roles/DynamicAuthoritiesGet.java b/rm-server/source/java/org/alfresco/repo/web/scripts/roles/DynamicAuthoritiesGet.java index 73a11774f7..6ce3574d6d 100644 --- a/rm-server/source/java/org/alfresco/repo/web/scripts/roles/DynamicAuthoritiesGet.java +++ b/rm-server/source/java/org/alfresco/repo/web/scripts/roles/DynamicAuthoritiesGet.java @@ -134,7 +134,8 @@ public class DynamicAuthoritiesGet extends DeclarativeWebScript implements Recor return model; } - Long totalNumberOfRecordsToProcess = 0L; + //default total number of records to be processed to batch size value + Long totalNumberOfRecordsToProcess = batchSize; if (StringUtils.isNotBlank(totalToBeProcessedRecordsStr)) { try From d3628be26b5e6205927146302ce5879e8501addf Mon Sep 17 00:00:00 2001 From: Silviu Dinuta Date: Thu, 15 Sep 2016 14:04:06 +0300 Subject: [PATCH 07/11] RM-3996: added unit tests and fixed the bug with infinite loop when batchsize=0 --- .../repo/web/scripts/roles/DynamicAuthoritiesGet.java | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/rm-server/source/java/org/alfresco/repo/web/scripts/roles/DynamicAuthoritiesGet.java b/rm-server/source/java/org/alfresco/repo/web/scripts/roles/DynamicAuthoritiesGet.java index 6ce3574d6d..b023dcf45e 100644 --- a/rm-server/source/java/org/alfresco/repo/web/scripts/roles/DynamicAuthoritiesGet.java +++ b/rm-server/source/java/org/alfresco/repo/web/scripts/roles/DynamicAuthoritiesGet.java @@ -56,7 +56,7 @@ import org.springframework.extensions.webscripts.WebScriptRequest; @SuppressWarnings("deprecation") public class DynamicAuthoritiesGet extends DeclarativeWebScript implements RecordsManagementModel { - + private static final String MESSAGE_PARAMETER_BATCHSIZE_GREATER_THAN_ZERO = "Parameter batchsize should be a number greater than 0."; private static final String MESSAGE_PROCESSING_BEGIN = "Processing - BEGIN"; private static final String MESSAGE_PROCESSING_END = "Processing - END"; private static final String MESSAGE_PROCESSING_RECORD_END_TEMPLATE = "Processing record {0} - END"; @@ -114,6 +114,13 @@ public class DynamicAuthoritiesGet extends DeclarativeWebScript implements Recor try { size = Long.parseLong(batchSizeStr); + if(size <= 0) + { + model.put(MODEL_STATUS, FAILED_STATUS); + model.put(MODEL_MESSAGE, MESSAGE_PARAMETER_BATCHSIZE_GREATER_THAN_ZERO); + logger.info(MESSAGE_PARAMETER_BATCHSIZE_GREATER_THAN_ZERO); + return model; + } } catch(NumberFormatException ex) { From 1b7fd10f66ad789e35fb5afd5e41eac1c0a9aab0 Mon Sep 17 00:00:00 2001 From: Silviu Dinuta Date: Thu, 15 Sep 2016 14:05:31 +0300 Subject: [PATCH 08/11] RM-3996: added unit tests --- .../roles/DynamicAuthoritiesGetUnitTest.java | 392 ++++++++++++++++++ 1 file changed, 392 insertions(+) create mode 100644 rm-server/unit-test/java/org/alfresco/repo/web/scripts/roles/DynamicAuthoritiesGetUnitTest.java diff --git a/rm-server/unit-test/java/org/alfresco/repo/web/scripts/roles/DynamicAuthoritiesGetUnitTest.java b/rm-server/unit-test/java/org/alfresco/repo/web/scripts/roles/DynamicAuthoritiesGetUnitTest.java new file mode 100644 index 0000000000..c18ed9c9cf --- /dev/null +++ b/rm-server/unit-test/java/org/alfresco/repo/web/scripts/roles/DynamicAuthoritiesGetUnitTest.java @@ -0,0 +1,392 @@ +/* + * 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.repo.web.scripts.roles; + +import static java.util.Collections.emptyMap; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.anyBoolean; +import static org.mockito.Matchers.anyLong; +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.io.Serializable; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.google.common.collect.ImmutableMap; + +import org.alfresco.module.org_alfresco_module_rm.model.RecordsManagementModel; +import org.alfresco.module.org_alfresco_module_rm.security.ExtendedReaderDynamicAuthority; +import org.alfresco.module.org_alfresco_module_rm.security.ExtendedSecurityService; +import org.alfresco.module.org_alfresco_module_rm.security.ExtendedWriterDynamicAuthority; +import org.alfresco.module.org_alfresco_module_rm.test.util.AlfMock; +import org.alfresco.module.org_alfresco_module_rm.test.util.BaseWebScriptUnitTest; +import org.alfresco.repo.domain.node.NodeDAO; +import org.alfresco.repo.domain.patch.PatchDAO; +import org.alfresco.repo.domain.qname.QNameDAO; +import org.alfresco.repo.transaction.RetryingTransactionHelper; +import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.NodeService; +import org.alfresco.service.cmr.security.PermissionService; +import org.alfresco.service.namespace.QName; +import org.alfresco.service.transaction.TransactionService; +import org.alfresco.util.Pair; +import org.json.JSONObject; +import org.junit.Before; +import org.junit.Test; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.mockito.invocation.InvocationOnMock; +import org.mockito.stubbing.Answer; +import org.springframework.extensions.webscripts.DeclarativeWebScript; + +/** + * DynamicAuthoritiesGet Unit Test + * + * @author Silviu Dinuta + */ +@SuppressWarnings("deprecation") +public class DynamicAuthoritiesGetUnitTest extends BaseWebScriptUnitTest implements RecordsManagementModel +{ + /** test data */ + private static final Long ASPECT_ID = 123l; + private static final QName ASPECT = AlfMock.generateQName(); + + /** mocks */ + @Mock + private PatchDAO mockedPatchDAO; + @Mock + private NodeDAO mockedNodeDAO; + @Mock + private QNameDAO mockedQnameDAO; + @Mock + private NodeService mockedNodeService; + @Mock + private PermissionService mockedPermissionService; + @Mock + private ExtendedSecurityService mockedExtendedSecurityService; + @Mock + private TransactionService mockedTransactionService; + @Mock + private RetryingTransactionHelper mockedRetryingTransactionHelper; + + /** test component */ + @InjectMocks + private DynamicAuthoritiesGet webScript; + + @Override + protected DeclarativeWebScript getWebScript() + { + return webScript; + } + + @Override + protected String getWebScriptTemplate() + { + return "alfresco/templates/webscripts/org/alfresco/repository/roles/rm-dynamicauthorities.get.json.ftl"; + } + + /** + * Before test + */ + @SuppressWarnings("unchecked") + @Before + public void before() + { + MockitoAnnotations.initMocks(this); + webScript.setNodeService(mockedNodeService); + webScript.setPermissionService(mockedPermissionService); + webScript.setExtendedSecurityService(mockedExtendedSecurityService); + // setup retrying transaction helper + Answer doInTransactionAnswer = new Answer() + { + @SuppressWarnings("rawtypes") + @Override + public Object answer(InvocationOnMock invocation) throws Throwable + { + RetryingTransactionCallback callback = (RetryingTransactionCallback) invocation.getArguments()[0]; + return callback.execute(); + } + }; + + doAnswer(doInTransactionAnswer).when(mockedRetryingTransactionHelper) + . doInTransaction(any(RetryingTransactionCallback.class), anyBoolean(), anyBoolean()); + + when(mockedTransactionService.getRetryingTransactionHelper()).thenReturn(mockedRetryingTransactionHelper); + + // max node id + when(mockedPatchDAO.getMaxAdmNodeID()).thenReturn(500000L); + + // aspect + when(mockedQnameDAO.getQName(ASPECT_EXTENDED_SECURITY)).thenReturn(new Pair(ASPECT_ID, ASPECT)); + } + + /** + * Given that there are no nodes with the extended security aspect When the action is executed Nothing happens + * @throws Exception + */ + @SuppressWarnings({ "unchecked" }) + @Test + public void noNodesWithExtendedSecurity() throws Exception + { + when(mockedPatchDAO.getNodesByAspectQNameId(eq(ASPECT_ID), anyLong(), anyLong())) + .thenReturn(Collections.emptyList()); + + // Set up parameters. + Map parameters = ImmutableMap.of("batchsize", "10", "maxProcessedRecords", "3"); + JSONObject json = executeJSONWebScript(parameters); + assertNotNull(json); + String actualJSONString = json.toString(); + + // Check the JSON result using Jackson to allow easy equality testing. + ObjectMapper mapper = new ObjectMapper(); + String expectedJSONString = "{\"responsestatus\":\"success\",\"message\":\"Processed 0 records.\"}"; + assertEquals(mapper.readTree(expectedJSONString), mapper.readTree(actualJSONString)); + + + verify(mockedNodeService, never()).getProperty(any(NodeRef.class), eq(PROP_READERS)); + verify(mockedNodeService, never()).getProperty(any(NodeRef.class), eq(PROP_WRITERS)); + verify(mockedNodeService, never()).removeAspect(any(NodeRef.class), eq(ASPECT_EXTENDED_SECURITY)); + verify(mockedPermissionService, never()).clearPermission(any(NodeRef.class), + eq(ExtendedReaderDynamicAuthority.EXTENDED_READER)); + verify(mockedPermissionService, never()).clearPermission(any(NodeRef.class), + eq(ExtendedWriterDynamicAuthority.EXTENDED_WRITER)); + verify(mockedExtendedSecurityService, never()).set(any(NodeRef.class), any(Set.class), any(Set.class)); + } + + /** + * Given that there are records with the extended security aspect When the action is executed Then the aspect is + * removed And the dynamic authorities permissions are cleared And extended security is set via the updated API + * @throws Exception + */ + @SuppressWarnings("unchecked") + @Test + public void recordsWithExtendedSecurityAspect() throws Exception + { + List ids = Stream.of(1l, 2l, 3l).collect(Collectors.toList()); + + when(mockedPatchDAO.getNodesByAspectQNameId(eq(ASPECT_ID), anyLong(), anyLong())) + .thenReturn(ids) + .thenReturn(Collections.emptyList()); + + ids.stream().forEach((i) -> { + NodeRef nodeRef = AlfMock.generateNodeRef(mockedNodeService); + when(mockedNodeDAO.getNodePair(i)).thenReturn(new Pair(i, nodeRef)); + when(mockedNodeService.hasAspect(nodeRef, ASPECT_RECORD)).thenReturn(true); + when(mockedNodeService.getProperty(nodeRef, PROP_READERS)) + .thenReturn((Serializable) Collections.emptyMap()); + when(mockedNodeService.getProperty(nodeRef, PROP_WRITERS)) + .thenReturn((Serializable) Collections.emptyMap()); + + }); + + // Set up parameters. + Map parameters = ImmutableMap.of("batchsize", "10", "maxProcessedRecords", "4"); + JSONObject json = executeJSONWebScript(parameters); + assertNotNull(json); + String actualJSONString = json.toString(); + ObjectMapper mapper = new ObjectMapper(); + String expectedJSONString = "{\"responsestatus\":\"success\",\"message\":\"Processed 3 records.\"}"; + assertEquals(mapper.readTree(expectedJSONString), mapper.readTree(actualJSONString)); + + + verify(mockedNodeService, times(3)).getProperty(any(NodeRef.class), eq(PROP_READERS)); + verify(mockedNodeService, times(3)).getProperty(any(NodeRef.class), eq(PROP_WRITERS)); + verify(mockedNodeService, times(3)).removeAspect(any(NodeRef.class), eq(ASPECT_EXTENDED_SECURITY)); + verify(mockedPermissionService, times(3)).clearPermission(any(NodeRef.class), + eq(ExtendedReaderDynamicAuthority.EXTENDED_READER)); + verify(mockedPermissionService, times(3)).clearPermission(any(NodeRef.class), + eq(ExtendedWriterDynamicAuthority.EXTENDED_WRITER)); + verify(mockedExtendedSecurityService, times(3)).set(any(NodeRef.class), any(Set.class), any(Set.class)); + + } + + /** + * Given that there are non-records with the extended security aspect When the web script is executed Then the aspect is + * removed And the dynamic authorities permissions are cleared + * @throws Exception + */ + @SuppressWarnings("unchecked") + @Test + public void nonRecordsWithExtendedSecurityAspect() throws Exception + { + List ids = Stream.of(1l, 2l, 3l).collect(Collectors.toList()); + + when(mockedPatchDAO.getNodesByAspectQNameId(eq(ASPECT_ID), anyLong(), anyLong())) + .thenReturn(ids) + .thenReturn(Collections.emptyList()); + + ids.stream().forEach((i) -> { + NodeRef nodeRef = AlfMock.generateNodeRef(mockedNodeService); + when(mockedNodeDAO.getNodePair(i)).thenReturn(new Pair(i, nodeRef)); + when(mockedNodeService.hasAspect(nodeRef, ASPECT_RECORD)).thenReturn(false); + when(mockedNodeService.getProperty(nodeRef, PROP_READERS)) + .thenReturn((Serializable) Collections.emptyMap()); + when(mockedNodeService.getProperty(nodeRef, PROP_WRITERS)) + .thenReturn((Serializable) Collections.emptyMap()); + + }); + + // Set up parameters. + Map parameters = ImmutableMap.of("batchsize", "10", "maxProcessedRecords", "4"); + JSONObject json = executeJSONWebScript(parameters); + assertNotNull(json); + String actualJSONString = json.toString(); + ObjectMapper mapper = new ObjectMapper(); + String expectedJSONString = "{\"responsestatus\":\"success\",\"message\":\"Processed 3 records.\"}"; + assertEquals(mapper.readTree(expectedJSONString), mapper.readTree(actualJSONString)); + + + verify(mockedNodeService, times(3)).getProperty(any(NodeRef.class), eq(PROP_READERS)); + verify(mockedNodeService, times(3)).getProperty(any(NodeRef.class), eq(PROP_WRITERS)); + verify(mockedNodeService, times(3)).removeAspect(any(NodeRef.class), eq(ASPECT_EXTENDED_SECURITY)); + verify(mockedPermissionService, times(3)).clearPermission(any(NodeRef.class), + eq(ExtendedReaderDynamicAuthority.EXTENDED_READER)); + verify(mockedPermissionService, times(3)).clearPermission(any(NodeRef.class), + eq(ExtendedWriterDynamicAuthority.EXTENDED_WRITER)); + verify(mockedExtendedSecurityService, never()).set(any(NodeRef.class), any(Set.class), any(Set.class)); + } + + @Test + public void missingBatchSizeParameter() throws Exception + { + JSONObject json = executeJSONWebScript(emptyMap()); + assertNotNull(json); + String actualJSONString = json.toString(); + ObjectMapper mapper = new ObjectMapper(); + String expectedJSONString = "{\"responsestatus\":\"failed\",\"message\":\"Parameter batchsize is mandatory\"}"; + assertEquals(mapper.readTree(expectedJSONString), mapper.readTree(actualJSONString)); + } + + @Test + public void invalidBatchSizeParameter() throws Exception + { + // Set up parameters. + Map parameters = ImmutableMap.of("batchsize", "dd"); + JSONObject json = executeJSONWebScript(parameters); + assertNotNull(json); + String actualJSONString = json.toString(); + ObjectMapper mapper = new ObjectMapper(); + String expectedJSONString = "{\"responsestatus\":\"failed\",\"message\":\"Parameter batchsize is invalid.\"}"; + assertEquals(mapper.readTree(expectedJSONString), mapper.readTree(actualJSONString)); + } + + @Test + public void batchSizeShouldBeGraterThanZero() throws Exception + { + when(mockedQnameDAO.getQName(ASPECT_EXTENDED_SECURITY)).thenReturn(null); + // Set up parameters. + Map parameters = ImmutableMap.of("batchsize", "0"); + JSONObject json = executeJSONWebScript(parameters); + assertNotNull(json); + String actualJSONString = json.toString(); + ObjectMapper mapper = new ObjectMapper(); + String expectedJSONString = "{\"responsestatus\":\"failed\",\"message\":\"Parameter batchsize should be a number greater than 0.\"}"; + assertEquals(mapper.readTree(expectedJSONString), mapper.readTree(actualJSONString)); + } + + @Test + public void extendedSecurityAspectNotCreated() throws Exception + { + when(mockedQnameDAO.getQName(ASPECT_EXTENDED_SECURITY)).thenReturn(null); + // Set up parameters. + Map parameters = ImmutableMap.of("batchsize", "3"); + JSONObject json = executeJSONWebScript(parameters); + assertNotNull(json); + String actualJSONString = json.toString(); + ObjectMapper mapper = new ObjectMapper(); + String expectedJSONString = "{\"responsestatus\":\"success\",\"message\":\"There where no records to be processed.\"}"; + assertEquals(mapper.readTree(expectedJSONString), mapper.readTree(actualJSONString)); + } + + @Test + public void processAllRecordsWhenMaxProcessedRecordsIsZero() throws Exception + { + List ids = Stream.of(1l, 2l, 3l,4l).collect(Collectors.toList()); + + when(mockedPatchDAO.getNodesByAspectQNameId(eq(ASPECT_ID), anyLong(), anyLong())) + .thenReturn(ids) + .thenReturn(Collections.emptyList()); + + ids.stream().forEach((i) -> { + NodeRef nodeRef = AlfMock.generateNodeRef(mockedNodeService); + when(mockedNodeDAO.getNodePair(i)).thenReturn(new Pair(i, nodeRef)); + when(mockedNodeService.hasAspect(nodeRef, ASPECT_RECORD)).thenReturn(false); + when(mockedNodeService.getProperty(nodeRef, PROP_READERS)) + .thenReturn((Serializable) Collections.emptyMap()); + when(mockedNodeService.getProperty(nodeRef, PROP_WRITERS)) + .thenReturn((Serializable) Collections.emptyMap()); + + }); + + // Set up parameters. + Map parameters = ImmutableMap.of("batchsize", "10", "maxProcessedRecords", "0"); + JSONObject json = executeJSONWebScript(parameters); + assertNotNull(json); + String actualJSONString = json.toString(); + ObjectMapper mapper = new ObjectMapper(); + String expectedJSONString = "{\"responsestatus\":\"success\",\"message\":\"Processed 4 records.\"}"; + assertEquals(mapper.readTree(expectedJSONString), mapper.readTree(actualJSONString)); + } + + @Test + public void whenMaxProcessedRecordsIsMissingItDefaultsToBatchSize() throws Exception + { + List ids = Stream.of(1l, 2l, 3l, 4l, 5l).collect(Collectors.toList()); + + when(mockedPatchDAO.getNodesByAspectQNameId(eq(ASPECT_ID), anyLong(), anyLong())) + .thenReturn(ids) + .thenReturn(Collections.emptyList()); + + ids.stream().forEach((i) -> { + NodeRef nodeRef = AlfMock.generateNodeRef(mockedNodeService); + when(mockedNodeDAO.getNodePair(i)).thenReturn(new Pair(i, nodeRef)); + when(mockedNodeService.hasAspect(nodeRef, ASPECT_RECORD)).thenReturn(false); + when(mockedNodeService.getProperty(nodeRef, PROP_READERS)) + .thenReturn((Serializable) Collections.emptyMap()); + when(mockedNodeService.getProperty(nodeRef, PROP_WRITERS)) + .thenReturn((Serializable) Collections.emptyMap()); + + }); + + // Set up parameters. + Map parameters = ImmutableMap.of("batchsize", "4"); + JSONObject json = executeJSONWebScript(parameters); + assertNotNull(json); + String actualJSONString = json.toString(); + ObjectMapper mapper = new ObjectMapper(); + String expectedJSONString = "{\"responsestatus\":\"success\",\"message\":\"Processed first 4 records.\"}"; + assertEquals(mapper.readTree(expectedJSONString), mapper.readTree(actualJSONString)); + } +} \ No newline at end of file From bf3bd710411d213eb21da690841ee979ee7deb0e Mon Sep 17 00:00:00 2001 From: Silviu Dinuta Date: Mon, 19 Sep 2016 16:45:22 +0300 Subject: [PATCH 09/11] RM-3993: added integration test --- .../test/integration/issue/RM3993Test.java | 211 ++++++++++++++++++ 1 file changed, 211 insertions(+) create mode 100644 rm-server/test/java/org/alfresco/module/org_alfresco_module_rm/test/integration/issue/RM3993Test.java diff --git a/rm-server/test/java/org/alfresco/module/org_alfresco_module_rm/test/integration/issue/RM3993Test.java b/rm-server/test/java/org/alfresco/module/org_alfresco_module_rm/test/integration/issue/RM3993Test.java new file mode 100644 index 0000000000..cefa23a8a0 --- /dev/null +++ b/rm-server/test/java/org/alfresco/module/org_alfresco_module_rm/test/integration/issue/RM3993Test.java @@ -0,0 +1,211 @@ +/* + * 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.issue; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Random; + +import org.alfresco.model.ContentModel; +import org.alfresco.module.org_alfresco_module_rm.action.dm.CreateRecordAction; +import org.alfresco.module.org_alfresco_module_rm.action.impl.FileToAction; +import org.alfresco.module.org_alfresco_module_rm.test.util.BaseRMTestCase; +import org.alfresco.service.cmr.action.Action; +import org.alfresco.service.cmr.repository.ChildAssociationRef; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.rule.Rule; +import org.alfresco.service.cmr.rule.RuleService; +import org.alfresco.service.cmr.rule.RuleType; +import org.alfresco.service.namespace.NamespaceService; +import org.alfresco.service.namespace.QName; + +/** + * System test for RM-3993: Exceptions thrown when concurrently creating identical folder structure + * + * @author Silviu Dinuta + * @since 2.3.0.7 + */ +public class RM3993Test extends BaseRMTestCase +{ + private static final int NUMBER_OF_BATCHES = 4; + private static final int NUMBER_IN_BATCH = 500; + + private RuleService ruleService; + private NodeRef ruleFolder; + private NodeRef nodeRefCategory1; + + @Override + protected void initServices() + { + super.initServices(); + + ruleService = (RuleService) applicationContext.getBean("RuleService"); + } + + @Override + protected boolean isCollaborationSiteTest() + { + return true; + } + + @Override + protected boolean isRecordTest() + { + return true; + } + + /** + * Given that I have auto declare configured And that I have auto file configured to a path where only the record + * folder needs to be created When I add lots of documents in the same transaction Then the rules should fire And + * the documents should be filed in the new record folder + */ + public void testAutoDeclareAutoFileCreateRecordFolderOnly() throws Exception + { + doTestInTransaction(new Test() + { + @Override + public Void run() + { + // create the folder + ruleFolder = fileFolderService.create(documentLibrary, "mytestfolder", ContentModel.TYPE_FOLDER) + .getNodeRef(); + + // create record category + nodeRefCategory1 = filePlanService.createRecordCategory(filePlan, "category1"); + + Action action = actionService.createAction(CreateRecordAction.NAME); + action.setParameterValue(CreateRecordAction.PARAM_FILE_PLAN, filePlan); + + Rule rule = new Rule(); + rule.setRuleType(RuleType.INBOUND); + rule.setTitle("my rule"); + rule.setAction(action); + rule.setExecuteAsynchronously(true); + ruleService.saveRule(ruleFolder, rule); + + Action fileAction = actionService.createAction(FileToAction.NAME); + fileAction.setParameterValue(FileToAction.PARAM_PATH, + "/category1/{node.cm:description}"); + fileAction.setParameterValue(FileToAction.PARAM_CREATE_RECORD_PATH, true); + + Rule fileRule = new Rule(); + fileRule.setRuleType(RuleType.INBOUND); + fileRule.setTitle("my rule"); + fileRule.setAction(fileAction); + fileRule.setExecuteAsynchronously(true); + ruleService.saveRule(filePlanService.getUnfiledContainer(filePlan), fileRule); + + return null; + } + + @Override + public void test(Void result) throws Exception + { + assertFalse(ruleService.getRules(ruleFolder).isEmpty()); + } + }); + + List records = new ArrayList(NUMBER_OF_BATCHES * NUMBER_IN_BATCH); + + for (int i = 0; i < NUMBER_OF_BATCHES; i++) + { + final int finali = i; + records.addAll(doTestInTransaction(new Test>() + { + @Override + public List run() throws Exception + { + List records = new ArrayList(NUMBER_IN_BATCH); + for (int j = 0; j < NUMBER_IN_BATCH; j++) + { + int count = (finali)* NUMBER_IN_BATCH + (j + 1); + String name = "content" + count + ".txt"; + System.out.println(name + " - creating"); + + Random rand = new Random(); + int descInt = rand.nextInt(2)+1; + NodeRef record = createFile(ruleFolder, name, Integer.toString(descInt), ContentModel.TYPE_CONTENT); + records.add(record); + } + return records; + } + })); + } + + try + { + while (!records.isEmpty()) + { + Thread.sleep(1000); + + final Iterator temp = records.iterator(); + doTestInTransaction(new Test() + { + @Override + public Void run() throws Exception + { + while (temp.hasNext()) + { + NodeRef record = temp.next(); + if (nodeService.hasAspect(record, ASPECT_RECORD) && recordService.isFiled(record)) + { + String name = (String) nodeService.getProperty(record, ContentModel.PROP_NAME); + System.out.println(name + " - complete"); + temp.remove(); + } + } + + return null; + } + }); + } + } + catch (Exception exception) + { + exception.printStackTrace(); + throw exception; + } + + List allContained = filePlanService.getAllContained(nodeRefCategory1, true); + assertTrue(allContained.size() == 2002); + } + + private NodeRef createFile(NodeRef parentNodeRef, String name, String descrption, QName typeQName) + { + Map properties = new HashMap(11); + properties.put(ContentModel.PROP_NAME, (Serializable) name); + properties.put(ContentModel.PROP_DESCRIPTION, (Serializable) descrption); + QName assocQName = QName.createQName( + NamespaceService.CONTENT_MODEL_1_0_URI, + QName.createValidLocalName(name)); + ChildAssociationRef assocRef = nodeService.createNode( + parentNodeRef, + ContentModel.ASSOC_CONTAINS, + assocQName, + typeQName, + properties); + NodeRef nodeRef = assocRef.getChildRef(); + return nodeRef; + } +} \ No newline at end of file From ca5316b6e2bc923763c160ed0c26ae9f109c12af Mon Sep 17 00:00:00 2001 From: Tuna Aksoy Date: Tue, 20 Sep 2016 00:54:14 +0100 Subject: [PATCH 10/11] RM-3993 (Exceptions thrown when concurrently creating identical folder structure) --- .../impl/CopyMoveLinkFileToBaseAction.java | 59 +++++++++---------- 1 file changed, 29 insertions(+), 30 deletions(-) diff --git a/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/action/impl/CopyMoveLinkFileToBaseAction.java b/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/action/impl/CopyMoveLinkFileToBaseAction.java index b879c733a0..eea7aab301 100644 --- a/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/action/impl/CopyMoveLinkFileToBaseAction.java +++ b/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/action/impl/CopyMoveLinkFileToBaseAction.java @@ -11,12 +11,12 @@ import org.alfresco.module.org_alfresco_module_rm.model.RecordsManagementModel; import org.alfresco.repo.action.ParameterDefinitionImpl; import org.alfresco.repo.security.authentication.AuthenticationUtil; import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork; +import org.alfresco.repo.transaction.RetryingTransactionHelper; import org.alfresco.service.cmr.action.Action; import org.alfresco.service.cmr.action.ParameterDefinition; import org.alfresco.service.cmr.dictionary.DataTypeDefinition; import org.alfresco.service.cmr.model.FileFolderService; import org.alfresco.service.cmr.model.FileNotFoundException; -import org.alfresco.service.cmr.repository.ChildAssociationRef; import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.namespace.QName; import org.apache.commons.logging.Log; @@ -103,7 +103,7 @@ public abstract class CopyMoveLinkFileToBaseAction extends RMActionExecuterAbstr * @see org.alfresco.repo.action.executer.ActionExecuterAbstractBase#executeImpl(org.alfresco.service.cmr.action.Action, org.alfresco.service.cmr.repository.NodeRef) */ @Override - protected void executeImpl(final Action action, final NodeRef actionedUponNodeRef) + protected synchronized void executeImpl(final Action action, final NodeRef actionedUponNodeRef) { String actionName = action.getActionDefinitionName(); if (isOkToProceedWithAction(actionedUponNodeRef, actionName)) @@ -125,8 +125,15 @@ public abstract class CopyMoveLinkFileToBaseAction extends RMActionExecuterAbstr NodeRef recordFolder = (NodeRef)action.getParameterValue(PARAM_DESTINATION_RECORD_FOLDER); if (recordFolder == null) { - // get the reference to the record folder based on the relative path - recordFolder = createOrResolvePath(action, actionedUponNodeRef, targetIsUnfiledRecords); + final boolean finaltargetIsUnfiledRecords = targetIsUnfiledRecords; + recordFolder = getTransactionService().getRetryingTransactionHelper().doInTransaction(new RetryingTransactionHelper.RetryingTransactionCallback() + { + public NodeRef execute() throws Throwable + { + // get the reference to the record folder based on the relative path + return createOrResolvePath(action, actionedUponNodeRef, finaltargetIsUnfiledRecords); + } + }, false, true); } // now we have the reference to the target folder we can do some final checks to see if the action is valid @@ -333,19 +340,7 @@ public abstract class CopyMoveLinkFileToBaseAction extends RMActionExecuterAbstr */ private NodeRef getChild(NodeRef parent, String childName) { - NodeRef child = null; - List children = getNodeService().getChildAssocs(parent); - for (ChildAssociationRef childAssoc : children) - { - NodeRef childNodeRef = childAssoc.getChildRef(); - String existingChildName = (String)getNodeService().getProperty(childNodeRef, ContentModel.PROP_NAME); - if(existingChildName.equals(childName)) - { - child = childNodeRef; - break; - } - } - return child; + return getNodeService().getChildByName(parent, ContentModel.ASSOC_CONTAINS, childName); } /** @@ -365,22 +360,26 @@ public abstract class CopyMoveLinkFileToBaseAction extends RMActionExecuterAbstr @Override public NodeRef doWork() { - NodeRef child = null; - if(targetisUnfiledRecords) + // double check that the child hasn't been created by another thread + NodeRef child = getChild(parent, childName); + if (child == null) { - child = fileFolderService.create(parent, childName, RecordsManagementModel.TYPE_UNFILED_RECORD_FOLDER).getNodeRef(); - } - else if(lastAsFolder) - { - child = getRecordFolderService().createRecordFolder(parent, childName); - } - else - { - if(RecordsManagementModel.TYPE_RECORD_FOLDER.equals(getNodeService().getType(parent))) + if (targetisUnfiledRecords) { - throw new AlfrescoRuntimeException("Unable to execute " + action.getActionDefinitionName() + " action, because the destination path could not be created."); + child = fileFolderService.create(parent, childName, RecordsManagementModel.TYPE_UNFILED_RECORD_FOLDER).getNodeRef(); + } + else if(lastAsFolder) + { + child = getRecordFolderService().createRecordFolder(parent, childName); + } + else + { + if(RecordsManagementModel.TYPE_RECORD_FOLDER.equals(getNodeService().getType(parent))) + { + throw new AlfrescoRuntimeException("Unable to execute " + action.getActionDefinitionName() + " action, because the destination path could not be created."); + } + child = filePlanService.createRecordCategory(parent, childName); } - child = filePlanService.createRecordCategory(parent, childName); } return child; } From 08855c6d2f38bbbc2b64e9b90d0d95a28e1adda0 Mon Sep 17 00:00:00 2001 From: Silviu Dinuta Date: Tue, 20 Sep 2016 13:07:44 +0300 Subject: [PATCH 11/11] removed SNAPSHOR from version of all pom files --- pom.xml | 2 +- rm-automation/pom.xml | 622 +++++++++++++++++++++--------------------- rm-server/pom.xml | 2 +- 3 files changed, 313 insertions(+), 313 deletions(-) diff --git a/pom.xml b/pom.xml index 2487228b3c..0fc977df9e 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ org.alfresco alfresco-rm-parent pom - 2.3.0.7-SNAPSHOT + 2.3.0.7 Alfresco Records Management http://www.alfresco.org/ diff --git a/rm-automation/pom.xml b/rm-automation/pom.xml index f9f625308f..7576da3c2a 100644 --- a/rm-automation/pom.xml +++ b/rm-automation/pom.xml @@ -1,312 +1,312 @@ - - - - org.alfresco - alfresco-rm-parent - 2.3.0.7-SNAPSHOT - - 4.0.0 - alfresco-rm-automation - - 2.43.1 - 4.0.5.RELEASE - - - - - - org.codehaus.mojo - build-helper-maven-plugin - - - add-test-source - - add-test-source - - - - src/unit-test/java - - - - - - - maven-surefire-plugin - - false - - - usedefaultlisteners - false - - - listener - org.uncommons.reportng.HTMLReporter, org.uncommons.reportng.JUnitXMLReporter - - - - ${project.build.testOutputDirectory}/testng.xml - - - - - - maven-antrun-plugin - - - default-cli - - - Stopping Alfresco... - - - - - - - - - - - - - org.alfresco - webdrone - 2.6.1 - - - org.seleniumhq.selenium - selenium-java - ${selenium.version} - - - org.seleniumhq.selenium - selenium-server - ${selenium.version} - test - - - org.springframework - spring-beans - ${spring.version} - - - org.springframework - spring-context - ${spring.version} - - - org.springframework - spring-tx - ${spring.version} - test - - - org.springframework - spring-test - ${spring.version} - test - - - org.testng - testng - 6.8.8 - test - - - org.uncommons - reportng - 1.1.4 - test - - - ru.yandex.qatools.htmlelements - htmlelements-all - 1.12 - - - ru.yandex.qatools.properties - properties-loader - 1.5 - test - - - - - install-alfresco - - - - - maven-antrun-plugin - - - fetch-installer - generate-test-resources - - run - - - - Recreating database... - drop database if exists alfresco; create database alfresco - Downloading Alfresco installer... - - - - Installing Alfresco... - - - - - - - - - - org.apache.ant - ant-jsch - 1.8.2 - - - postgresql - postgresql - 9.1-901-1.jdbc4 - - - - - maven-dependency-plugin - - - fetch-amps - process-test-resources - - copy - - - - - org.alfresco - alfresco-rm-share - ${project.version} - amp - amp - - - org.alfresco - alfresco-rm-server - ${project.version} - amp - amp - - - ${project.build.directory}/amps - true - - - - - - org.alfresco.maven.plugin - alfresco-maven-plugin - true - - - install-server-amp - - install - - process-test-resources - - true - ${project.build.directory}/amps/alfresco-rm-server-${project.version}-amp.amp - ${project.build.directory}/alf-installation/tomcat/webapps/alfresco.war - amp - - - - install-share-amp - - install - - process-test-resources - - true - ${project.build.directory}/amps/alfresco-rm-share-${project.version}-amp.amp - ${project.build.directory}/alf-installation/tomcat/webapps/share.war - amp - - - - - - - - - run-alfresco - - - - - org.jacoco - jacoco-maven-plugin - 0.6.3.201306030806 - - - prepare-jacoco - - prepare-agent - - - - - - org.alfresco.* - - - - - - maven-antrun-plugin - - - start-alfresco - process-test-classes - - run - - - - Starting Alfresco... - - - - - - - - - - stop-alfresco - post-integration-test - - run - - - - Stopping Alfresco... - - - - - - - - - - - - + + + + org.alfresco + alfresco-rm-parent + 2.3.0.7 + + 4.0.0 + alfresco-rm-automation + + 2.43.1 + 4.0.5.RELEASE + + + + + + org.codehaus.mojo + build-helper-maven-plugin + + + add-test-source + + add-test-source + + + + src/unit-test/java + + + + + + + maven-surefire-plugin + + false + + + usedefaultlisteners + false + + + listener + org.uncommons.reportng.HTMLReporter, org.uncommons.reportng.JUnitXMLReporter + + + + ${project.build.testOutputDirectory}/testng.xml + + + + + + maven-antrun-plugin + + + default-cli + + + Stopping Alfresco... + + + + + + + + + + + + + org.alfresco + webdrone + 2.6.1 + + + org.seleniumhq.selenium + selenium-java + ${selenium.version} + + + org.seleniumhq.selenium + selenium-server + ${selenium.version} + test + + + org.springframework + spring-beans + ${spring.version} + + + org.springframework + spring-context + ${spring.version} + + + org.springframework + spring-tx + ${spring.version} + test + + + org.springframework + spring-test + ${spring.version} + test + + + org.testng + testng + 6.8.8 + test + + + org.uncommons + reportng + 1.1.4 + test + + + ru.yandex.qatools.htmlelements + htmlelements-all + 1.12 + + + ru.yandex.qatools.properties + properties-loader + 1.5 + test + + + + + install-alfresco + + + + + maven-antrun-plugin + + + fetch-installer + generate-test-resources + + run + + + + Recreating database... + drop database if exists alfresco; create database alfresco + Downloading Alfresco installer... + + + + Installing Alfresco... + + + + + + + + + + org.apache.ant + ant-jsch + 1.8.2 + + + postgresql + postgresql + 9.1-901-1.jdbc4 + + + + + maven-dependency-plugin + + + fetch-amps + process-test-resources + + copy + + + + + org.alfresco + alfresco-rm-share + ${project.version} + amp + amp + + + org.alfresco + alfresco-rm-server + ${project.version} + amp + amp + + + ${project.build.directory}/amps + true + + + + + + org.alfresco.maven.plugin + alfresco-maven-plugin + true + + + install-server-amp + + install + + process-test-resources + + true + ${project.build.directory}/amps/alfresco-rm-server-${project.version}-amp.amp + ${project.build.directory}/alf-installation/tomcat/webapps/alfresco.war + amp + + + + install-share-amp + + install + + process-test-resources + + true + ${project.build.directory}/amps/alfresco-rm-share-${project.version}-amp.amp + ${project.build.directory}/alf-installation/tomcat/webapps/share.war + amp + + + + + + + + + run-alfresco + + + + + org.jacoco + jacoco-maven-plugin + 0.6.3.201306030806 + + + prepare-jacoco + + prepare-agent + + + + + + org.alfresco.* + + + + + + maven-antrun-plugin + + + start-alfresco + process-test-classes + + run + + + + Starting Alfresco... + + + + + + + + + + stop-alfresco + post-integration-test + + run + + + + Stopping Alfresco... + + + + + + + + + + + + \ No newline at end of file diff --git a/rm-server/pom.xml b/rm-server/pom.xml index 15f37cc054..e18bb21f07 100644 --- a/rm-server/pom.xml +++ b/rm-server/pom.xml @@ -5,7 +5,7 @@ org.alfresco alfresco-rm-parent - 2.3.0.7-SNAPSHOT + 2.3.0.7 4.0.0 alfresco-rm-server