diff --git a/rm-community/rm-community-repo/config/alfresco/module/org_alfresco_module_rm/extended-repository-context.xml b/rm-community/rm-community-repo/config/alfresco/module/org_alfresco_module_rm/extended-repository-context.xml index 123ff3f912..27551507da 100644 --- a/rm-community/rm-community-repo/config/alfresco/module/org_alfresco_module_rm/extended-repository-context.xml +++ b/rm-community/rm-community-repo/config/alfresco/module/org_alfresco_module_rm/extended-repository-context.xml @@ -60,6 +60,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-community/rm-community-repo/config/alfresco/module/org_alfresco_module_rm/log4j.properties b/rm-community/rm-community-repo/config/alfresco/module/org_alfresco_module_rm/log4j.properties index 7a14ca27f5..83e724394e 100644 --- a/rm-community/rm-community-repo/config/alfresco/module/org_alfresco_module_rm/log4j.properties +++ b/rm-community/rm-community-repo/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-community/rm-community-repo/config/alfresco/module/org_alfresco_module_rm/rm-service-context.xml b/rm-community/rm-community-repo/config/alfresco/module/org_alfresco_module_rm/rm-service-context.xml index d1d575c4d2..5b25686727 100644 --- a/rm-community/rm-community-repo/config/alfresco/module/org_alfresco_module_rm/rm-service-context.xml +++ b/rm-community/rm-community-repo/config/alfresco/module/org_alfresco_module_rm/rm-service-context.xml @@ -573,7 +573,10 @@ parent="baseService"> - + + + + diff --git a/rm-community/rm-community-repo/config/alfresco/module/org_alfresco_module_rm/rm-version-context.xml b/rm-community/rm-community-repo/config/alfresco/module/org_alfresco_module_rm/rm-version-context.xml index bffe192a53..d9de79a796 100644 --- a/rm-community/rm-community-repo/config/alfresco/module/org_alfresco_module_rm/rm-version-context.xml +++ b/rm-community/rm-community-repo/config/alfresco/module/org_alfresco_module_rm/rm-version-context.xml @@ -28,7 +28,6 @@ - diff --git a/rm-community/rm-community-repo/config/alfresco/module/org_alfresco_module_rm/rm-webscript-context.xml b/rm-community/rm-community-repo/config/alfresco/module/org_alfresco_module_rm/rm-webscript-context.xml index 58628e9921..728ac66fc5 100644 --- a/rm-community/rm-community-repo/config/alfresco/module/org_alfresco_module_rm/rm-webscript-context.xml +++ b/rm-community/rm-community-repo/config/alfresco/module/org_alfresco_module_rm/rm-webscript-context.xml @@ -587,6 +587,19 @@ + + + + + + + + + + + + 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 + required + \ No newline at end of file diff --git a/rm-community/rm-community-repo/config/alfresco/templates/webscripts/org/alfresco/repository/roles/rm-dynamicauthorities.get.json.ftl b/rm-community/rm-community-repo/config/alfresco/templates/webscripts/org/alfresco/repository/roles/rm-dynamicauthorities.get.json.ftl new file mode 100644 index 0000000000..8929b9a33c --- /dev/null +++ b/rm-community/rm-community-repo/config/alfresco/templates/webscripts/org/alfresco/repository/roles/rm-dynamicauthorities.get.json.ftl @@ -0,0 +1,30 @@ +<#-- + #%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% +--> +{ + "responsestatus" : "${responsestatus?json_string}", + "message" : "${message?json_string}" +} \ No newline at end of file diff --git a/rm-community/rm-community-repo/source/compatibility/org/alfresco/module/org_alfresco_module_rm/security/DeprecatedExtendedSecurityService.java b/rm-community/rm-community-repo/source/compatibility/org/alfresco/module/org_alfresco_module_rm/security/DeprecatedExtendedSecurityService.java new file mode 100644 index 0000000000..7be4c3382f --- /dev/null +++ b/rm-community/rm-community-repo/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-community/rm-community-repo/source/java/org/alfresco/module/org_alfresco_module_rm/security/ExtendedReaderDynamicAuthority.java b/rm-community/rm-community-repo/source/compatibility/org/alfresco/module/org_alfresco_module_rm/security/ExtendedReaderDynamicAuthority.java similarity index 96% rename from rm-community/rm-community-repo/source/java/org/alfresco/module/org_alfresco_module_rm/security/ExtendedReaderDynamicAuthority.java rename to rm-community/rm-community-repo/source/compatibility/org/alfresco/module/org_alfresco_module_rm/security/ExtendedReaderDynamicAuthority.java index 198139ff11..4b166ba686 100644 --- a/rm-community/rm-community-repo/source/java/org/alfresco/module/org_alfresco_module_rm/security/ExtendedReaderDynamicAuthority.java +++ b/rm-community/rm-community-repo/source/compatibility/org/alfresco/module/org_alfresco_module_rm/security/ExtendedReaderDynamicAuthority.java @@ -1,97 +1,98 @@ -/* - * #%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; -import java.util.Map; -import java.util.Set; - -import org.alfresco.module.org_alfresco_module_rm.capability.RMPermissionModel; -import org.alfresco.repo.security.permissions.PermissionReference; -import org.alfresco.service.cmr.repository.NodeRef; - -/** - * Extended readers dynamic authority implementation. - * - * @author Roy Wetherall - * @since 2.1 - */ -public class ExtendedReaderDynamicAuthority extends ExtendedSecurityBaseDynamicAuthority -{ - /** Extended reader role */ - public static final String EXTENDED_READER = "ROLE_EXTENDED_READER"; - - /** - * @see org.alfresco.repo.security.permissions.DynamicAuthority#getAuthority() - */ - @Override - public String getAuthority() - { - return EXTENDED_READER; - } - - /** - * @see org.alfresco.repo.security.permissions.DynamicAuthority#requiredFor() - */ - @Override - public Set requiredFor() - { - if (requiredFor == null) - { - requiredFor = Collections.singleton(getModelDAO().getPermissionReference(null, RMPermissionModel.READ_RECORDS)); - } - - return requiredFor; - } - - /** - * @see org.alfresco.module.org_alfresco_module_rm.security.ExtendedSecurityBaseDynamicAuthority#getAuthorites(org.alfresco.service.cmr.repository.NodeRef) - */ - @SuppressWarnings("unchecked") - protected Set getAuthorites(NodeRef nodeRef) - { - Set result = null; - - Map readerMap = (Map)getNodeService().getProperty(nodeRef, PROP_READERS); - if (readerMap != null) - { - result = readerMap.keySet(); - } - - return result; - } - - /** - * @see org.alfresco.module.org_alfresco_module_rm.security.ExtendedSecurityBaseDynamicAuthority#getTransactionCacheName() - */ - @Override - protected String getTransactionCacheName() - { - return "rm.extendedreaderdynamicauthority"; - } -} +/* + * #%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; +import java.util.Map; +import java.util.Set; + +import org.alfresco.module.org_alfresco_module_rm.capability.RMPermissionModel; +import org.alfresco.repo.security.permissions.PermissionReference; +import org.alfresco.service.cmr.repository.NodeRef; + +/** + * Extended readers dynamic authority implementation. + * + * @author Roy Wetherall + * @since 2.1 + */ +@Deprecated +public class ExtendedReaderDynamicAuthority extends ExtendedSecurityBaseDynamicAuthority +{ + /** Extended reader role */ + public static final String EXTENDED_READER = "ROLE_EXTENDED_READER"; + + /** + * @see org.alfresco.repo.security.permissions.DynamicAuthority#getAuthority() + */ + @Override + public String getAuthority() + { + return EXTENDED_READER; + } + + /** + * @see org.alfresco.repo.security.permissions.DynamicAuthority#requiredFor() + */ + @Override + public Set requiredFor() + { + if (requiredFor == null) + { + requiredFor = Collections.singleton(getModelDAO().getPermissionReference(null, RMPermissionModel.READ_RECORDS)); + } + + return requiredFor; + } + + /** + * @see org.alfresco.module.org_alfresco_module_rm.security.ExtendedSecurityBaseDynamicAuthority#getAuthorites(org.alfresco.service.cmr.repository.NodeRef) + */ + @SuppressWarnings("unchecked") + protected Set getAuthorites(NodeRef nodeRef) + { + Set result = null; + + Map readerMap = (Map)getNodeService().getProperty(nodeRef, PROP_READERS); + if (readerMap != null) + { + result = readerMap.keySet(); + } + + return result; + } + + /** + * @see org.alfresco.module.org_alfresco_module_rm.security.ExtendedSecurityBaseDynamicAuthority#getTransactionCacheName() + */ + @Override + protected String getTransactionCacheName() + { + return "rm.extendedreaderdynamicauthority"; + } +} diff --git a/rm-community/rm-community-repo/source/java/org/alfresco/module/org_alfresco_module_rm/security/ExtendedSecurityBaseDynamicAuthority.java b/rm-community/rm-community-repo/source/compatibility/org/alfresco/module/org_alfresco_module_rm/security/ExtendedSecurityBaseDynamicAuthority.java similarity index 97% rename from rm-community/rm-community-repo/source/java/org/alfresco/module/org_alfresco_module_rm/security/ExtendedSecurityBaseDynamicAuthority.java rename to rm-community/rm-community-repo/source/compatibility/org/alfresco/module/org_alfresco_module_rm/security/ExtendedSecurityBaseDynamicAuthority.java index 620cede221..d48150633b 100644 --- a/rm-community/rm-community-repo/source/java/org/alfresco/module/org_alfresco_module_rm/security/ExtendedSecurityBaseDynamicAuthority.java +++ b/rm-community/rm-community-repo/source/compatibility/org/alfresco/module/org_alfresco_module_rm/security/ExtendedSecurityBaseDynamicAuthority.java @@ -1,191 +1,192 @@ -/* - * #%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; -import java.util.Set; - -import org.alfresco.module.org_alfresco_module_rm.model.RecordsManagementModel; -import org.alfresco.repo.security.permissions.DynamicAuthority; -import org.alfresco.repo.security.permissions.PermissionReference; -import org.alfresco.repo.security.permissions.impl.ModelDAO; -import org.alfresco.repo.transaction.TransactionalResourceHelper; -import org.alfresco.service.cmr.repository.NodeRef; -import org.alfresco.service.cmr.repository.NodeService; -import org.alfresco.service.cmr.security.AuthorityService; -import org.alfresco.util.Pair; -import org.springframework.context.ApplicationContext; -import org.springframework.context.ApplicationContextAware; - -/** - * Extended readers dynamic authority implementation. - * - * @author Roy Wetherall - * @since 2.1 - */ -public abstract class ExtendedSecurityBaseDynamicAuthority implements DynamicAuthority, - RecordsManagementModel, - ApplicationContextAware -{ - /** Authority service */ - private AuthorityService authorityService; - - /** Extended security service */ - private ExtendedSecurityService extendedSecurityService; - - /** Node service */ - private NodeService nodeService; - - /** Application context */ - protected ApplicationContext applicationContext; - - /** model DAO */ - protected ModelDAO modelDAO; - - /** permission reference */ - protected Set requiredFor; - - // NOTE: we get the services directly from the application context in this way to avoid - // cyclic relationships and issues when loading the application context - - /** - * @return authority service - */ - protected AuthorityService getAuthorityService() - { - if (authorityService == null) - { - authorityService = (AuthorityService)applicationContext.getBean("authorityService"); - } - return authorityService; - } - - /** - * @return extended security service - */ - protected ExtendedSecurityService getExtendedSecurityService() - { - if (extendedSecurityService == null) - { - extendedSecurityService = (ExtendedSecurityService)applicationContext.getBean("extendedSecurityService"); - } - return extendedSecurityService; - } - - /** - * @return node service - */ - protected NodeService getNodeService() - { - if (nodeService == null) - { - nodeService = (NodeService)applicationContext.getBean("dbNodeService"); - } - return nodeService; - } - - /** - * @return model DAO - */ - protected ModelDAO getModelDAO() - { - if (modelDAO == null) - { - modelDAO = (ModelDAO)applicationContext.getBean("permissionsModelDAO"); - } - return modelDAO; - } - - /** - * @return String transaction cache name - */ - protected abstract String getTransactionCacheName(); - - /** - * @see org.springframework.context.ApplicationContextAware#setApplicationContext(org.springframework.context.ApplicationContext) - */ - @Override - public void setApplicationContext(ApplicationContext applicationContext) - { - this.applicationContext = applicationContext; - } - - /** - * Gets a list of the authorities from the extended security aspect that this dynamic - * authority is checking against. - * - * @param nodeRef - * @return - */ - protected abstract Set getAuthorites(NodeRef nodeRef); - - /** - * @see org.alfresco.repo.security.permissions.DynamicAuthority#hasAuthority(org.alfresco.service.cmr.repository.NodeRef, java.lang.String) - */ - @Override - public boolean hasAuthority(NodeRef nodeRef, String userName) - { - boolean result = false; - - Map, Boolean> transactionCache = TransactionalResourceHelper.getMap(getTransactionCacheName()); - Pair key = new Pair(nodeRef, userName); - - if (transactionCache.containsKey(key)) - { - result = transactionCache.get(key); - } - else - { - if (getNodeService().hasAspect(nodeRef, ASPECT_EXTENDED_SECURITY)) - { - Set authorities = getAuthorites(nodeRef); - if (authorities != null) - { - // check for everyone or the user - if (authorities.contains("GROUP_EVEYONE") || - authorities.contains(userName)) - { - result = true; - } - else - { - // determine whether any of the users groups are in the extended security - Set contained = getAuthorityService().getAuthoritiesForUser(userName); - authorities.retainAll(contained); - result = (authorities.size() != 0); - } - } - } - - // cache result - transactionCache.put(key, result); - } - - return result; - } -} +/* + * #%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; +import java.util.Set; + +import org.alfresco.module.org_alfresco_module_rm.model.RecordsManagementModel; +import org.alfresco.repo.security.permissions.DynamicAuthority; +import org.alfresco.repo.security.permissions.PermissionReference; +import org.alfresco.repo.security.permissions.impl.ModelDAO; +import org.alfresco.repo.transaction.TransactionalResourceHelper; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.NodeService; +import org.alfresco.service.cmr.security.AuthorityService; +import org.alfresco.util.Pair; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; + +/** + * Extended readers dynamic authority implementation. + * + * @author Roy Wetherall + * @since 2.1 + */ +@Deprecated +public abstract class ExtendedSecurityBaseDynamicAuthority implements DynamicAuthority, + RecordsManagementModel, + ApplicationContextAware +{ + /** Authority service */ + private AuthorityService authorityService; + + /** Extended security service */ + private ExtendedSecurityService extendedSecurityService; + + /** Node service */ + private NodeService nodeService; + + /** Application context */ + protected ApplicationContext applicationContext; + + /** model DAO */ + protected ModelDAO modelDAO; + + /** permission reference */ + protected Set requiredFor; + + // NOTE: we get the services directly from the application context in this way to avoid + // cyclic relationships and issues when loading the application context + + /** + * @return authority service + */ + protected AuthorityService getAuthorityService() + { + if (authorityService == null) + { + authorityService = (AuthorityService)applicationContext.getBean("authorityService"); + } + return authorityService; + } + + /** + * @return extended security service + */ + protected ExtendedSecurityService getExtendedSecurityService() + { + if (extendedSecurityService == null) + { + extendedSecurityService = (ExtendedSecurityService)applicationContext.getBean("extendedSecurityService"); + } + return extendedSecurityService; + } + + /** + * @return node service + */ + protected NodeService getNodeService() + { + if (nodeService == null) + { + nodeService = (NodeService)applicationContext.getBean("dbNodeService"); + } + return nodeService; + } + + /** + * @return model DAO + */ + protected ModelDAO getModelDAO() + { + if (modelDAO == null) + { + modelDAO = (ModelDAO)applicationContext.getBean("permissionsModelDAO"); + } + return modelDAO; + } + + /** + * @return String transaction cache name + */ + protected abstract String getTransactionCacheName(); + + /** + * @see org.springframework.context.ApplicationContextAware#setApplicationContext(org.springframework.context.ApplicationContext) + */ + @Override + public void setApplicationContext(ApplicationContext applicationContext) + { + this.applicationContext = applicationContext; + } + + /** + * Gets a list of the authorities from the extended security aspect that this dynamic + * authority is checking against. + * + * @param nodeRef + * @return + */ + protected abstract Set getAuthorites(NodeRef nodeRef); + + /** + * @see org.alfresco.repo.security.permissions.DynamicAuthority#hasAuthority(org.alfresco.service.cmr.repository.NodeRef, java.lang.String) + */ + @Override + public boolean hasAuthority(NodeRef nodeRef, String userName) + { + boolean result = false; + + Map, Boolean> transactionCache = TransactionalResourceHelper.getMap(getTransactionCacheName()); + Pair key = new Pair(nodeRef, userName); + + if (transactionCache.containsKey(key)) + { + result = transactionCache.get(key); + } + else + { + if (getNodeService().hasAspect(nodeRef, ASPECT_EXTENDED_SECURITY)) + { + Set authorities = getAuthorites(nodeRef); + if (authorities != null) + { + // check for everyone or the user + if (authorities.contains("GROUP_EVEYONE") || + authorities.contains(userName)) + { + result = true; + } + else + { + // determine whether any of the users groups are in the extended security + Set contained = getAuthorityService().getAuthoritiesForUser(userName); + authorities.retainAll(contained); + result = (authorities.size() != 0); + } + } + } + + // cache result + transactionCache.put(key, result); + } + + return result; + } +} diff --git a/rm-community/rm-community-repo/source/java/org/alfresco/module/org_alfresco_module_rm/security/ExtendedWriterDynamicAuthority.java b/rm-community/rm-community-repo/source/compatibility/org/alfresco/module/org_alfresco_module_rm/security/ExtendedWriterDynamicAuthority.java similarity index 97% rename from rm-community/rm-community-repo/source/java/org/alfresco/module/org_alfresco_module_rm/security/ExtendedWriterDynamicAuthority.java rename to rm-community/rm-community-repo/source/compatibility/org/alfresco/module/org_alfresco_module_rm/security/ExtendedWriterDynamicAuthority.java index 14ca006206..2ce3c6a8f1 100644 --- a/rm-community/rm-community-repo/source/java/org/alfresco/module/org_alfresco_module_rm/security/ExtendedWriterDynamicAuthority.java +++ b/rm-community/rm-community-repo/source/compatibility/org/alfresco/module/org_alfresco_module_rm/security/ExtendedWriterDynamicAuthority.java @@ -1,102 +1,103 @@ -/* - * #%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; -import java.util.HashSet; -import java.util.Map; -import java.util.Set; - -import org.alfresco.module.org_alfresco_module_rm.capability.RMPermissionModel; -import org.alfresco.repo.security.permissions.PermissionReference; -import org.alfresco.service.cmr.repository.NodeRef; - -/** - * Extended writers dynamic authority implementation. - * - * @author Roy Wetherall - * @since 2.1 - */ -public class ExtendedWriterDynamicAuthority extends ExtendedSecurityBaseDynamicAuthority -{ - /** Extended writer role */ - public static final String EXTENDED_WRITER = "ROLE_EXTENDED_WRITER"; - - /** - * @see org.alfresco.repo.security.permissions.DynamicAuthority#getAuthority() - */ - @Override - public String getAuthority() - { - return EXTENDED_WRITER; - } - - /** - * @see org.alfresco.repo.security.permissions.DynamicAuthority#requiredFor() - */ - @Override - public Set requiredFor() - { - if (requiredFor == null) - { - requiredFor = new HashSet(3); - Collections.addAll(requiredFor, - getModelDAO().getPermissionReference(null, RMPermissionModel.READ_RECORDS), - getModelDAO().getPermissionReference(null, RMPermissionModel.FILING), - getModelDAO().getPermissionReference(null, RMPermissionModel.FILE_RECORDS)); - } - - return requiredFor; - } - - /** - * @see org.alfresco.module.org_alfresco_module_rm.security.ExtendedSecurityBaseDynamicAuthority#getAuthorites(org.alfresco.service.cmr.repository.NodeRef) - */ - @SuppressWarnings("unchecked") - protected Set getAuthorites(NodeRef nodeRef) - { - Set result = null; - - Map map = (Map)getNodeService().getProperty(nodeRef, PROP_WRITERS); - if (map != null) - { - result = map.keySet(); - } - - return result; - } - - /** - * @see org.alfresco.module.org_alfresco_module_rm.security.ExtendedSecurityBaseDynamicAuthority#getTransactionCacheName() - */ - @Override - protected String getTransactionCacheName() - { - return "rm.extendedwriterdynamicauthority"; - } -} +/* + * #%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; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +import org.alfresco.module.org_alfresco_module_rm.capability.RMPermissionModel; +import org.alfresco.repo.security.permissions.PermissionReference; +import org.alfresco.service.cmr.repository.NodeRef; + +/** + * Extended writers dynamic authority implementation. + * + * @author Roy Wetherall + * @since 2.1 + */ +@Deprecated +public class ExtendedWriterDynamicAuthority extends ExtendedSecurityBaseDynamicAuthority +{ + /** Extended writer role */ + public static final String EXTENDED_WRITER = "ROLE_EXTENDED_WRITER"; + + /** + * @see org.alfresco.repo.security.permissions.DynamicAuthority#getAuthority() + */ + @Override + public String getAuthority() + { + return EXTENDED_WRITER; + } + + /** + * @see org.alfresco.repo.security.permissions.DynamicAuthority#requiredFor() + */ + @Override + public Set requiredFor() + { + if (requiredFor == null) + { + requiredFor = new HashSet(3); + Collections.addAll(requiredFor, + getModelDAO().getPermissionReference(null, RMPermissionModel.READ_RECORDS), + getModelDAO().getPermissionReference(null, RMPermissionModel.FILING), + getModelDAO().getPermissionReference(null, RMPermissionModel.FILE_RECORDS)); + } + + return requiredFor; + } + + /** + * @see org.alfresco.module.org_alfresco_module_rm.security.ExtendedSecurityBaseDynamicAuthority#getAuthorites(org.alfresco.service.cmr.repository.NodeRef) + */ + @SuppressWarnings("unchecked") + protected Set getAuthorites(NodeRef nodeRef) + { + Set result = null; + + Map map = (Map)getNodeService().getProperty(nodeRef, PROP_WRITERS); + if (map != null) + { + result = map.keySet(); + } + + return result; + } + + /** + * @see org.alfresco.module.org_alfresco_module_rm.security.ExtendedSecurityBaseDynamicAuthority#getTransactionCacheName() + */ + @Override + protected String getTransactionCacheName() + { + return "rm.extendedwriterdynamicauthority"; + } +} diff --git a/rm-community/rm-community-repo/source/java/org/alfresco/module/org_alfresco_module_rm/security/FilePlanAuthenticationService.java b/rm-community/rm-community-repo/source/compatibility/org/alfresco/module/org_alfresco_module_rm/security/FilePlanAuthenticationService.java similarity index 97% rename from rm-community/rm-community-repo/source/java/org/alfresco/module/org_alfresco_module_rm/security/FilePlanAuthenticationService.java rename to rm-community/rm-community-repo/source/compatibility/org/alfresco/module/org_alfresco_module_rm/security/FilePlanAuthenticationService.java index a2737bdc79..88914c0563 100644 --- a/rm-community/rm-community-repo/source/java/org/alfresco/module/org_alfresco_module_rm/security/FilePlanAuthenticationService.java +++ b/rm-community/rm-community-repo/source/compatibility/org/alfresco/module/org_alfresco_module_rm/security/FilePlanAuthenticationService.java @@ -1,58 +1,59 @@ -/* - * #%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; -import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork; - -/** - * File plan authentication service. - * - * @author Roy Wetherall - * @since 2.1 - */ -public interface FilePlanAuthenticationService -{ - /** - * @return rm admin user name - * - * @deprecated as of 2.2, use {@link AuthenticationUtil#getAdminUserName()} - */ - String getRmAdminUserName(); - - /** - * Run provided work as the global rm admin user. - * - * @param return type - * @param runAsWork work to execute as the rm admin user - * @return R result of work execution - * - * @deprecated as of 2.2, use {@link AuthenticationUtil#runAs(RunAsWork, AuthenticationUtil#getAdminUserName())} - */ - R runAsRmAdmin(RunAsWork runAsWork); -} +/* + * #%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; +import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork; + +/** + * File plan authentication service. + * + * @author Roy Wetherall + * @since 2.1 + * @deprecated as of 2.2, use {@link AuthenticationUtil}. + */ +public interface FilePlanAuthenticationService +{ + /** + * @return rm admin user name + * + * @deprecated as of 2.2, use {@link AuthenticationUtil#getAdminUserName()} + */ + String getRmAdminUserName(); + + /** + * Run provided work as the global rm admin user. + * + * @param return type + * @param runAsWork work to execute as the rm admin user + * @return R result of work execution + * + * @deprecated as of 2.2, use {@link AuthenticationUtil#runAs(RunAsWork, AuthenticationUtil#getAdminUserName())} + */ + R runAsRmAdmin(RunAsWork runAsWork); +} diff --git a/rm-community/rm-community-repo/source/java/org/alfresco/module/org_alfresco_module_rm/security/FilePlanAuthenticationServiceImpl.java b/rm-community/rm-community-repo/source/compatibility/org/alfresco/module/org_alfresco_module_rm/security/FilePlanAuthenticationServiceImpl.java similarity index 97% rename from rm-community/rm-community-repo/source/java/org/alfresco/module/org_alfresco_module_rm/security/FilePlanAuthenticationServiceImpl.java rename to rm-community/rm-community-repo/source/compatibility/org/alfresco/module/org_alfresco_module_rm/security/FilePlanAuthenticationServiceImpl.java index 9a3d84aeff..5c0a5f8ff5 100644 --- a/rm-community/rm-community-repo/source/java/org/alfresco/module/org_alfresco_module_rm/security/FilePlanAuthenticationServiceImpl.java +++ b/rm-community/rm-community-repo/source/compatibility/org/alfresco/module/org_alfresco_module_rm/security/FilePlanAuthenticationServiceImpl.java @@ -1,62 +1,63 @@ -/* - * #%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; -import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork; - -/** - * @author Roy Wetherall - * @since 2.1 - */ -public class FilePlanAuthenticationServiceImpl implements FilePlanAuthenticationService -{ - /** Default rm admin user values */ - @Deprecated - public static final String DEFAULT_RM_ADMIN_USER = "rmadmin"; - - /** - * @see org.alfresco.module.org_alfresco_module_rm.security.FilePlanAuthenticationService#getRMAdminUserName() - */ - @Override - @Deprecated - public String getRmAdminUserName() - { - return AuthenticationUtil.getAdminUserName(); - } - - /** - * @see org.alfresco.module.org_alfresco_module_rm.security.FilePlanAuthenticationService#runAsRMAdmin(org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork) - */ - @Override - @Deprecated - public R runAsRmAdmin(RunAsWork runAsWork) - { - return AuthenticationUtil.runAs(runAsWork, AuthenticationUtil.getAdminUserName()); - } -} +/* + * #%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; +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 */ + @Deprecated + public static final String DEFAULT_RM_ADMIN_USER = "rmadmin"; + + /** + * @see org.alfresco.module.org_alfresco_module_rm.security.FilePlanAuthenticationService#getRMAdminUserName() + */ + @Override + @Deprecated + public String getRmAdminUserName() + { + return AuthenticationUtil.getAdminUserName(); + } + + /** + * @see org.alfresco.module.org_alfresco_module_rm.security.FilePlanAuthenticationService#runAsRMAdmin(org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork) + */ + @Override + @Deprecated + public R runAsRmAdmin(RunAsWork runAsWork) + { + return AuthenticationUtil.runAs(runAsWork, AuthenticationUtil.getAdminUserName()); + } +} diff --git a/rm-community/rm-community-repo/source/java/org/alfresco/module/org_alfresco_module_rm/action/impl/CopyMoveLinkFileToBaseAction.java b/rm-community/rm-community-repo/source/java/org/alfresco/module/org_alfresco_module_rm/action/impl/CopyMoveLinkFileToBaseAction.java index 9a1bbe3d8f..1c0d10ea83 100644 --- a/rm-community/rm-community-repo/source/java/org/alfresco/module/org_alfresco_module_rm/action/impl/CopyMoveLinkFileToBaseAction.java +++ b/rm-community/rm-community-repo/source/java/org/alfresco/module/org_alfresco_module_rm/action/impl/CopyMoveLinkFileToBaseAction.java @@ -143,7 +143,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 synchronized void executeImpl(Action action, final NodeRef actionedUponNodeRef) + protected synchronized void executeImpl(final Action action, final NodeRef actionedUponNodeRef) { String actionName = action.getActionDefinitionName(); if (isOkToProceedWithAction(actionedUponNodeRef, actionName)) @@ -165,8 +165,25 @@ public abstract class CopyMoveLinkFileToBaseAction extends RMActionExecuterAbstr NodeRef recordFolder = (NodeRef)action.getParameterValue(PARAM_DESTINATION_RECORD_FOLDER); if (recordFolder == null) { - final boolean finaltargetIsUnfiledRecords = targetIsUnfiledRecords; - recordFolder = createOrResolvePath(action, actionedUponNodeRef, finaltargetIsUnfiledRecords); + final boolean finaltargetIsUnfiledRecords = targetIsUnfiledRecords; + recordFolder = retryingTransactionHelper.doInTransaction(new RetryingTransactionHelper.RetryingTransactionCallback() + { + public NodeRef execute() throws Throwable + { + NodeRef result = null; + try + { + // get the reference to the record folder based on the relative path + result = createOrResolvePath(action, actionedUponNodeRef, finaltargetIsUnfiledRecords); + } + catch (DuplicateChildNodeNameException ex) + { + throw new ConcurrencyFailureException("Cannot create or resolve path.", ex); + } + + return result; + } + }, false, true); } // now we have the reference to the target folder we can do some final checks to see if the action is valid @@ -180,29 +197,30 @@ public abstract class CopyMoveLinkFileToBaseAction extends RMActionExecuterAbstr { try { - if(getMode() == CopyMoveLinkFileToActionMode.MOVE) + synchronized (this) { - fileFolderService.move(actionedUponNodeRef, finalRecordFolder, null); - } - else if(getMode() == CopyMoveLinkFileToActionMode.COPY) - { - fileFolderService.copy(actionedUponNodeRef, finalRecordFolder, null); - } - else if(getMode() == CopyMoveLinkFileToActionMode.LINK) - { - getRecordService().link(actionedUponNodeRef, finalRecordFolder); + if (getMode() == CopyMoveLinkFileToActionMode.MOVE) + { + fileFolderService.move(actionedUponNodeRef, finalRecordFolder, null); + } + else if (getMode() == CopyMoveLinkFileToActionMode.COPY) + { + fileFolderService.copy(actionedUponNodeRef, finalRecordFolder, null); + } + else if (getMode() == CopyMoveLinkFileToActionMode.LINK) + { + getRecordService().link(actionedUponNodeRef, finalRecordFolder); + } } } catch (FileNotFoundException fileNotFound) { - throw new AlfrescoRuntimeException( - "Unable to execute file to action, because the " + (mode == CopyMoveLinkFileToActionMode.MOVE ? "move" : "copy") + " operation failed.", - fileNotFound - ); + throw new AlfrescoRuntimeException("Unable to execute file to action, because the " + (mode == CopyMoveLinkFileToActionMode.MOVE ? "move" : "copy") + " operation failed.", fileNotFound); } - + return null; } + }); } } @@ -389,7 +407,7 @@ public abstract class CopyMoveLinkFileToBaseAction extends RMActionExecuterAbstr */ private NodeRef getChild(NodeRef parent, String childName) { - return getNodeService().getChildByName(parent, ContentModel.ASSOC_CONTAINS, childName); + return getNodeService().getChildByName(parent, ContentModel.ASSOC_CONTAINS, childName); } /** diff --git a/rm-community/rm-community-repo/source/java/org/alfresco/module/org_alfresco_module_rm/fileplan/FilePlanServiceImpl.java b/rm-community/rm-community-repo/source/java/org/alfresco/module/org_alfresco_module_rm/fileplan/FilePlanServiceImpl.java index 51884641a6..a8429536ba 100644 --- a/rm-community/rm-community-repo/source/java/org/alfresco/module/org_alfresco_module_rm/fileplan/FilePlanServiceImpl.java +++ b/rm-community/rm-community-repo/source/java/org/alfresco/module/org_alfresco_module_rm/fileplan/FilePlanServiceImpl.java @@ -42,8 +42,6 @@ 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; @@ -360,8 +358,6 @@ public class FilePlanServiceImpl extends ServiceBaseImpl // 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); // prevent inheritance of rules nodeService.addAspect(container, RuleModel.ASPECT_IGNORE_INHERITED_RULES, null); diff --git a/rm-community/rm-community-repo/source/java/org/alfresco/module/org_alfresco_module_rm/model/RecordsManagementModel.java b/rm-community/rm-community-repo/source/java/org/alfresco/module/org_alfresco_module_rm/model/RecordsManagementModel.java index 52abff0010..acc25e2992 100644 --- a/rm-community/rm-community-repo/source/java/org/alfresco/module/org_alfresco_module_rm/model/RecordsManagementModel.java +++ b/rm-community/rm-community-repo/source/java/org/alfresco/module/org_alfresco_module_rm/model/RecordsManagementModel.java @@ -251,9 +251,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-community/rm-community-repo/source/java/org/alfresco/module/org_alfresco_module_rm/model/rma/aspect/RecordAspect.java b/rm-community/rm-community-repo/source/java/org/alfresco/module/org_alfresco_module_rm/model/rma/aspect/RecordAspect.java index 2d266a39ea..23c58fdcd9 100644 --- a/rm-community/rm-community-repo/source/java/org/alfresco/module/org_alfresco_module_rm/model/rma/aspect/RecordAspect.java +++ b/rm-community/rm-community-repo/source/java/org/alfresco/module/org_alfresco_module_rm/model/rma/aspect/RecordAspect.java @@ -39,6 +39,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; @@ -65,7 +66,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 @@ -132,11 +134,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); } } @@ -305,4 +307,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-community/rm-community-repo/source/java/org/alfresco/module/org_alfresco_module_rm/patch/v21/RMv21InPlacePatch.java b/rm-community/rm-community-repo/source/java/org/alfresco/module/org_alfresco_module_rm/patch/v21/RMv21InPlacePatch.java index 5eaf3c79f2..5eb2b2a992 100644 --- a/rm-community/rm-community-repo/source/java/org/alfresco/module/org_alfresco_module_rm/patch/v21/RMv21InPlacePatch.java +++ b/rm-community/rm-community-repo/source/java/org/alfresco/module/org_alfresco_module_rm/patch/v21/RMv21InPlacePatch.java @@ -34,13 +34,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; @@ -164,10 +161,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-community/rm-community-repo/source/java/org/alfresco/module/org_alfresco_module_rm/record/InplaceRecordServiceImpl.java b/rm-community/rm-community-repo/source/java/org/alfresco/module/org_alfresco_module_rm/record/InplaceRecordServiceImpl.java index 4a009c07f1..02431a8ea9 100644 --- a/rm-community/rm-community-repo/source/java/org/alfresco/module/org_alfresco_module_rm/record/InplaceRecordServiceImpl.java +++ b/rm-community/rm-community-repo/source/java/org/alfresco/module/org_alfresco_module_rm/record/InplaceRecordServiceImpl.java @@ -30,7 +30,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; @@ -104,22 +103,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; } }); @@ -173,18 +176,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-community/rm-community-repo/source/java/org/alfresco/module/org_alfresco_module_rm/record/RecordServiceImpl.java b/rm-community/rm-community-repo/source/java/org/alfresco/module/org_alfresco_module_rm/record/RecordServiceImpl.java index 688802014c..2a2bb04f8b 100644 --- a/rm-community/rm-community-repo/source/java/org/alfresco/module/org_alfresco_module_rm/record/RecordServiceImpl.java +++ b/rm-community/rm-community-repo/source/java/org/alfresco/module/org_alfresco_module_rm/record/RecordServiceImpl.java @@ -107,6 +107,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; @@ -253,10 +254,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 @@ -417,11 +414,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); } /** @@ -592,27 +584,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() */ @@ -914,12 +885,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 @@ -971,13 +940,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 { @@ -1009,24 +972,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; @@ -1095,7 +1042,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-community/rm-community-repo/source/java/org/alfresco/module/org_alfresco_module_rm/role/FilePlanRoleServiceImpl.java b/rm-community/rm-community-repo/source/java/org/alfresco/module/org_alfresco_module_rm/role/FilePlanRoleServiceImpl.java index 542cae9eb5..e4357d3d24 100644 --- a/rm-community/rm-community-repo/source/java/org/alfresco/module/org_alfresco_module_rm/role/FilePlanRoleServiceImpl.java +++ b/rm-community/rm-community-repo/source/java/org/alfresco/module/org_alfresco_module_rm/role/FilePlanRoleServiceImpl.java @@ -45,8 +45,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.DuplicateChildNodeNameException; @@ -195,8 +193,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-community/rm-community-repo/source/java/org/alfresco/module/org_alfresco_module_rm/security/ExtendedSecurityService.java b/rm-community/rm-community-repo/source/java/org/alfresco/module/org_alfresco_module_rm/security/ExtendedSecurityService.java index 6c3af1a400..afa06f4fdb 100644 --- a/rm-community/rm-community-repo/source/java/org/alfresco/module/org_alfresco_module_rm/security/ExtendedSecurityService.java +++ b/rm-community/rm-community-repo/source/java/org/alfresco/module/org_alfresco_module_rm/security/ExtendedSecurityService.java @@ -29,98 +29,77 @@ 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-community/rm-community-repo/source/java/org/alfresco/module/org_alfresco_module_rm/security/ExtendedSecurityServiceImpl.java b/rm-community/rm-community-repo/source/java/org/alfresco/module/org_alfresco_module_rm/security/ExtendedSecurityServiceImpl.java index f1db301030..fda0573794 100644 --- a/rm-community/rm-community-repo/source/java/org/alfresco/module/org_alfresco_module_rm/security/ExtendedSecurityServiceImpl.java +++ b/rm-community/rm-community-repo/source/java/org/alfresco/module/org_alfresco_module_rm/security/ExtendedSecurityServiceImpl.java @@ -27,23 +27,37 @@ 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. @@ -53,13 +67,31 @@ import org.alfresco.util.ParameterCheck; */ public class ExtendedSecurityServiceImpl extends ServiceBaseImpl implements ExtendedSecurityService, - RecordsManagementModel + RecordsManagementModel, + ApplicationListener { + /** 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 @@ -76,118 +108,152 @@ 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); - - // add to the extended security roles - addExtendedSecurityRoles(nodeRef, readers, writers); + 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(final NodeRef nodeRef, Set readers, Set writers, boolean applyToParents) + @Override + public Set getWriters(NodeRef nodeRef) { ParameterCheck.mandatory("nodeRef", nodeRef); - ParameterCheck.mandatory("applyToParents", applyToParents); - - // get the properties - final Map properties = nodeService.getProperties(nodeRef); - - // update the readers map - if (readers != null && readers.size() != 0) + + Set result = Collections.EMPTY_SET; + Pair iprGroups = getIPRGroups(nodeRef); + if (iprGroups != null) { - // get reader map - Map readersMap = (Map)properties.get(PROP_READERS); - - // set the readers property (this will in turn apply the aspect if required) - properties.put(PROP_READERS, (Serializable)addToMap(readersMap, readers)); + result = getAuthorities(iprGroups.getSecond()); } - - // update the writers map - if (writers != null && writers.size() != 0) - { - // get writer map - Map writersMap = (Map)properties.get(PROP_WRITERS); - - // set the writers property (this will in turn apply the aspect if required) - properties.put(PROP_WRITERS, (Serializable)addToMap(writersMap, writers)); - } - - // set properties - nodeService.setProperties(nodeRef, properties); - + + 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)) { @@ -195,101 +261,300 @@ public class ExtendedSecurityServiceImpl extends ServiceBaseImpl for (ChildAssociationRef assoc : assocs) { NodeRef child = assoc.getChildRef(); - addExtendedSecurityImpl(child, readers, writers, false); + assignIPRGroupsToNode(iprGroups, child); } - } + } } - + /** - * - * @param nodeRef - * @param readers - * @param writers + * 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 void addExtendedSecurityRoles(NodeRef nodeRef, Set readers, Set writers) + private Pair getIPRGroups(NodeRef nodeRef) { - NodeRef filePlan = filePlanService.getFilePlan(nodeRef); - - addExtendedSecurityRolesImpl(filePlan, readers, FilePlanRoleService.ROLE_EXTENDED_READERS); - addExtendedSecurityRolesImpl(filePlan, writers, FilePlanRoleService.ROLE_EXTENDED_WRITERS); - } - - /** - * Add extended security roles implementation - * - * @param filePlan file plan - * @param authorities authorities - * @param roleName role name - */ - private void addExtendedSecurityRolesImpl(NodeRef filePlan, Set authorities, String roleName) - { - if (authorities != null) + Pair result = null; + String iprReaderGroup = null; + String iprWriterGroup = null; + + // get all the set permissions + Set permissions = permissionService.getAllSetPermissions(nodeRef); + for (AccessPermission permission : permissions) { - for (String authority : authorities) + // look for the presence of the reader group + if (permission.getAuthority().startsWith(GROUP_PREFIX + READER_GROUP_PREFIX)) { - if ((!authority.equals(PermissionService.ALL_AUTHORITIES) && !authority.equals(PermissionService.OWNER_AUTHORITY))) - { - // add the authority to the role - filePlanRoleService.assignRoleToAuthority(filePlan, roleName, authority); - } + iprReaderGroup = permission.getAuthority(); + } + // look for the presence of the writer group + else if (permission.getAuthority().startsWith(GROUP_PREFIX + WRITER_GROUP_PREFIX)) + { + iprWriterGroup = permission.getAuthority(); } } + + // assuming the are both present then return + if (iprReaderGroup != null && iprWriterGroup != null) + { + result = new Pair(iprReaderGroup, iprWriterGroup); + } + + 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)) { @@ -297,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-community/rm-community-repo/source/java/org/alfresco/module/org_alfresco_module_rm/security/FilePlanPermissionServiceImpl.java b/rm-community/rm-community-repo/source/java/org/alfresco/module/org_alfresco_module_rm/security/FilePlanPermissionServiceImpl.java index 43068da49a..5fab978a41 100644 --- a/rm-community/rm-community-repo/source/java/org/alfresco/module/org_alfresco_module_rm/security/FilePlanPermissionServiceImpl.java +++ b/rm-community/rm-community-repo/source/java/org/alfresco/module/org_alfresco_module_rm/security/FilePlanPermissionServiceImpl.java @@ -27,8 +27,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; @@ -383,13 +381,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); } @@ -435,9 +449,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))); } /** @@ -494,20 +519,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); @@ -589,7 +608,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-community/rm-community-repo/source/java/org/alfresco/module/org_alfresco_module_rm/version/RecordableVersionServiceImpl.java b/rm-community/rm-community-repo/source/java/org/alfresco/module/org_alfresco_module_rm/version/RecordableVersionServiceImpl.java index 4ebfc07cf8..0b714bc9da 100644 --- a/rm-community/rm-community-repo/source/java/org/alfresco/module/org_alfresco_module_rm/version/RecordableVersionServiceImpl.java +++ b/rm-community/rm-community-repo/source/java/org/alfresco/module/org_alfresco_module_rm/version/RecordableVersionServiceImpl.java @@ -34,7 +34,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; @@ -56,12 +55,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.lang.StringUtils; @@ -129,9 +128,6 @@ public class RecordableVersionServiceImpl extends Version2ServiceImpl /** extended security service */ private ExtendedSecurityService extendedSecurityService; - - /** ownable service */ - private OwnableService ownableService; /** * @param filePlanService file plan service @@ -196,14 +192,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) @@ -706,13 +694,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(); @@ -752,13 +735,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-community/rm-community-repo/source/java/org/alfresco/repo/security/permissions/impl/ExtendedPermissionService.java b/rm-community/rm-community-repo/source/java/org/alfresco/repo/security/permissions/impl/ExtendedPermissionService.java index 038df7e2f0..a7f1e5a5d2 100644 --- a/rm-community/rm-community-repo/source/java/org/alfresco/repo/security/permissions/impl/ExtendedPermissionService.java +++ b/rm-community/rm-community-repo/source/java/org/alfresco/repo/security/permissions/impl/ExtendedPermissionService.java @@ -29,7 +29,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. @@ -46,4 +48,18 @@ public interface ExtendedPermissionService extends PermissionService * @return {@link Set}<{@link String}> set of authorities with write access */ 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-community/rm-community-repo/source/java/org/alfresco/repo/security/permissions/impl/ExtendedPermissionServiceImpl.java b/rm-community/rm-community-repo/source/java/org/alfresco/repo/security/permissions/impl/ExtendedPermissionServiceImpl.java index 74e9e818ce..31246b35f1 100644 --- a/rm-community/rm-community-repo/source/java/org/alfresco/repo/security/permissions/impl/ExtendedPermissionServiceImpl.java +++ b/rm-community/rm-community-repo/source/java/org/alfresco/repo/security/permissions/impl/ExtendedPermissionServiceImpl.java @@ -39,8 +39,6 @@ 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; @@ -50,8 +48,11 @@ import org.alfresco.repo.security.permissions.processor.PermissionProcessorRegis 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; /** @@ -359,13 +360,17 @@ public class ExtendedPermissionServiceImpl 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; @@ -376,4 +381,28 @@ public class ExtendedPermissionServiceImpl 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-community/rm-community-repo/source/java/org/alfresco/repo/web/scripts/roles/DynamicAuthoritiesGet.java b/rm-community/rm-community-repo/source/java/org/alfresco/repo/web/scripts/roles/DynamicAuthoritiesGet.java new file mode 100644 index 0000000000..5e3020e9aa --- /dev/null +++ b/rm-community/rm-community-repo/source/java/org/alfresco/repo/web/scripts/roles/DynamicAuthoritiesGet.java @@ -0,0 +1,267 @@ +/* + * #%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% + */ +/* + * 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.lang.StringUtils; +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_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"; + 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 (StringUtils.isBlank(batchSizeStr)) + { + model.put(MODEL_STATUS, FAILED_STATUS); + model.put(MODEL_MESSAGE, MESSAGE_BATCHSIZE_IS_MANDATORY); + logger.info(MESSAGE_BATCHSIZE_IS_MANDATORY); + return model; + } + 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) + { + model.put(MODEL_STATUS, FAILED_STATUS); + model.put(MODEL_MESSAGE, MESSAGE_BATCHSIZE_IS_INVALID); + logger.info(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); + logger.info(MESSAGE_NO_RECORDS_TO_PROCESS); + return model; + } + + //default total number of records to be processed to batch size value + Long totalNumberOfRecordsToProcess = batchSize; + if (StringUtils.isNotBlank(totalToBeProcessedRecordsStr)) + { + 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(); + logger.info(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(); + String recordName = (String) nodeService.getProperty(record, ContentModel.PROP_NAME); + logger.info(MessageFormat.format(MESSAGE_PROCESSING_RECORD_BEGIN_TEMPLATE, recordName)); + processNode(record); + logger.info(MessageFormat.format(MESSAGE_PROCESSING_RECORD_END_TEMPLATE, recordName)); + processedNodes.add(record); + } + + return null; + } + }, + false, // read only + true); // requires new + } + logger.info(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); + logger.info(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()); + } + } +} diff --git a/rm-community/rm-community-repo/test/java/org/alfresco/module/org_alfresco_module_rm/test/integration/issue/RM1429Test.java b/rm-community/rm-community-repo/test/java/org/alfresco/module/org_alfresco_module_rm/test/integration/issue/RM1429Test.java index 0795d314b2..6c7560402b 100644 --- a/rm-community/rm-community-repo/test/java/org/alfresco/module/org_alfresco_module_rm/test/integration/issue/RM1429Test.java +++ b/rm-community/rm-community-repo/test/java/org/alfresco/module/org_alfresco_module_rm/test/integration/issue/RM1429Test.java @@ -44,14 +44,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); @@ -64,7 +64,7 @@ public class RM1429Test extends DeleteHoldTest // Add record folder to the hold holdService.addToHold(hold, rmFolder); - return null; + return hold; } }); diff --git a/rm-community/rm-community-repo/test/java/org/alfresco/module/org_alfresco_module_rm/test/integration/issue/RM1463Test.java b/rm-community/rm-community-repo/test/java/org/alfresco/module/org_alfresco_module_rm/test/integration/issue/RM1463Test.java index c3993d090d..3ed864a61f 100644 --- a/rm-community/rm-community-repo/test/java/org/alfresco/module/org_alfresco_module_rm/test/integration/issue/RM1463Test.java +++ b/rm-community/rm-community-repo/test/java/org/alfresco/module/org_alfresco_module_rm/test/integration/issue/RM1463Test.java @@ -44,14 +44,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); @@ -61,7 +61,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-community/rm-community-repo/test/java/org/alfresco/module/org_alfresco_module_rm/test/integration/issue/RM1464Test.java b/rm-community/rm-community-repo/test/java/org/alfresco/module/org_alfresco_module_rm/test/integration/issue/RM1464Test.java index 9f603b23b9..4a9dfa3ccf 100644 --- a/rm-community/rm-community-repo/test/java/org/alfresco/module/org_alfresco_module_rm/test/integration/issue/RM1464Test.java +++ b/rm-community/rm-community-repo/test/java/org/alfresco/module/org_alfresco_module_rm/test/integration/issue/RM1464Test.java @@ -44,14 +44,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); @@ -61,7 +61,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-community/rm-community-repo/test/java/org/alfresco/module/org_alfresco_module_rm/test/integration/issue/RM3993Test.java b/rm-community/rm-community-repo/test/java/org/alfresco/module/org_alfresco_module_rm/test/integration/issue/RM3993Test.java new file mode 100644 index 0000000000..20c6dc3e4d --- /dev/null +++ b/rm-community/rm-community-repo/test/java/org/alfresco/module/org_alfresco_module_rm/test/integration/issue/RM3993Test.java @@ -0,0 +1,253 @@ +/* + * #%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% + */ +/* + * 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.repo.security.authentication.AuthenticationUtil; +import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork; +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; + } + + Integer numberOfRecords = AuthenticationUtil.runAsSystem(new RunAsWork() + { + + @Override + public Integer doWork() throws Exception + { + List containedRecordFolders = filePlanService.getContainedRecordFolders(nodeRefCategory1); + int numberOfRecords = 0; + for(NodeRef recordFolder : containedRecordFolders) + { + numberOfRecords = numberOfRecords + fileFolderService.list(recordFolder).size(); + } + return numberOfRecords; + } + }); + assertTrue(numberOfRecords == NUMBER_OF_BATCHES * NUMBER_IN_BATCH); + } + + 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 diff --git a/rm-community/rm-community-repo/test/java/org/alfresco/module/org_alfresco_module_rm/test/integration/record/CreateInplaceRecordTest.java b/rm-community/rm-community-repo/test/java/org/alfresco/module/org_alfresco_module_rm/test/integration/record/CreateInplaceRecordTest.java new file mode 100644 index 0000000000..51159550ac --- /dev/null +++ b/rm-community/rm-community-repo/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-community/rm-community-repo/test/java/org/alfresco/module/org_alfresco_module_rm/test/integration/record/InplaceRecordPermissionTest.java b/rm-community/rm-community-repo/test/java/org/alfresco/module/org_alfresco_module_rm/test/integration/record/InplaceRecordPermissionTest.java new file mode 100644 index 0000000000..ef086d2bc8 --- /dev/null +++ b/rm-community/rm-community-repo/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-community/rm-community-repo/test/java/org/alfresco/module/org_alfresco_module_rm/test/integration/record/MoveInplaceRecordTest.java b/rm-community/rm-community-repo/test/java/org/alfresco/module/org_alfresco_module_rm/test/integration/record/MoveInplaceRecordTest.java index 01aea14033..bee09d18df 100644 --- a/rm-community/rm-community-repo/test/java/org/alfresco/module/org_alfresco_module_rm/test/integration/record/MoveInplaceRecordTest.java +++ b/rm-community/rm-community-repo/test/java/org/alfresco/module/org_alfresco_module_rm/test/integration/record/MoveInplaceRecordTest.java @@ -69,6 +69,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() { @@ -93,8 +96,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() @@ -114,15 +123,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-community/rm-community-repo/test/java/org/alfresco/module/org_alfresco_module_rm/test/legacy/action/FileReportActionTest.java b/rm-community/rm-community-repo/test/java/org/alfresco/module/org_alfresco_module_rm/test/legacy/action/FileReportActionTest.java index 26c46eaab0..31ae795a76 100644 --- a/rm-community/rm-community-repo/test/java/org/alfresco/module/org_alfresco_module_rm/test/legacy/action/FileReportActionTest.java +++ b/rm-community/rm-community-repo/test/java/org/alfresco/module/org_alfresco_module_rm/test/legacy/action/FileReportActionTest.java @@ -30,6 +30,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; @@ -62,6 +63,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-community/rm-community-repo/test/java/org/alfresco/module/org_alfresco_module_rm/test/legacy/action/FileToActionTest.java b/rm-community/rm-community-repo/test/java/org/alfresco/module/org_alfresco_module_rm/test/legacy/action/FileToActionTest.java index 54660ef58c..825287dab9 100644 --- a/rm-community/rm-community-repo/test/java/org/alfresco/module/org_alfresco_module_rm/test/legacy/action/FileToActionTest.java +++ b/rm-community/rm-community-repo/test/java/org/alfresco/module/org_alfresco_module_rm/test/legacy/action/FileToActionTest.java @@ -119,24 +119,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)); - - AuthenticationUtil.runAsSystem(new AuthenticationUtil.RunAsWork() - { - public Void doWork() throws Exception - { - // is the unfiled container the primary parent of the filed record - NodeRef parent = nodeService.getPrimaryParent(dmDocument).getParentRef(); - assertEquals(filePlanService.getUnfiledContainer(filePlan), parent); - - // TODO Auto-generated method stub - return null; - }}); - 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-community/rm-community-repo/test/java/org/alfresco/module/org_alfresco_module_rm/test/legacy/action/RejectActionTest.java b/rm-community/rm-community-repo/test/java/org/alfresco/module/org_alfresco_module_rm/test/legacy/action/RejectActionTest.java index e302801910..90a6157024 100644 --- a/rm-community/rm-community-repo/test/java/org/alfresco/module/org_alfresco_module_rm/test/legacy/action/RejectActionTest.java +++ b/rm-community/rm-community-repo/test/java/org/alfresco/module/org_alfresco_module_rm/test/legacy/action/RejectActionTest.java @@ -113,7 +113,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-community/rm-community-repo/test/java/org/alfresco/module/org_alfresco_module_rm/test/legacy/service/ExtendedSecurityServiceImplTest.java b/rm-community/rm-community-repo/test/java/org/alfresco/module/org_alfresco_module_rm/test/legacy/service/ExtendedSecurityServiceImplTest.java index f1ddc47a14..2629edb0de 100644 --- a/rm-community/rm-community-repo/test/java/org/alfresco/module/org_alfresco_module_rm/test/legacy/service/ExtendedSecurityServiceImplTest.java +++ b/rm-community/rm-community-repo/test/java/org/alfresco/module/org_alfresco_module_rm/test/legacy/service/ExtendedSecurityServiceImplTest.java @@ -27,9 +27,7 @@ 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; @@ -101,64 +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(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(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(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(recordToo, testMapToo); + // test remove + extendedSecurityService.remove(recordToo); + + assertFalse(extendedSecurityService.hasExtendedSecurity(recordToo)); + assertTrue(extendedSecurityService.getReaders(recordToo).isEmpty()); + assertTrue(extendedSecurityService.getWriters(recordToo).isEmpty()); return null; } @@ -172,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)); @@ -186,15 +149,12 @@ 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(record, extendedReaders); - checkExtendedReaders(record, testMap); assertFalse(extendedSecurityService.hasExtendedSecurity(moveRecordCategory)); assertFalse(extendedSecurityService.hasExtendedSecurity(moveRecordFolder)); @@ -206,31 +166,21 @@ public class ExtendedSecurityServiceImplTest extends BaseRMTestCase @Override public void test(Void result) throws Exception { - 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-community/rm-community-repo/test/java/org/alfresco/module/org_alfresco_module_rm/test/legacy/service/FilePlanPermissionServiceImplTest.java b/rm-community/rm-community-repo/test/java/org/alfresco/module/org_alfresco_module_rm/test/legacy/service/FilePlanPermissionServiceImplTest.java index 0064691ac7..b36b0c87a8 100644 --- a/rm-community/rm-community-repo/test/java/org/alfresco/module/org_alfresco_module_rm/test/legacy/service/FilePlanPermissionServiceImplTest.java +++ b/rm-community/rm-community-repo/test/java/org/alfresco/module/org_alfresco_module_rm/test/legacy/service/FilePlanPermissionServiceImplTest.java @@ -33,8 +33,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; @@ -1241,10 +1239,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-community/rm-community-repo/test/java/org/alfresco/module/org_alfresco_module_rm/test/legacy/service/RecordServiceImplTest.java b/rm-community/rm-community-repo/test/java/org/alfresco/module/org_alfresco_module_rm/test/legacy/service/RecordServiceImplTest.java index 28ea6cc461..7f80b18167 100644 --- a/rm-community/rm-community-repo/test/java/org/alfresco/module/org_alfresco_module_rm/test/legacy/service/RecordServiceImplTest.java +++ b/rm-community/rm-community-repo/test/java/org/alfresco/module/org_alfresco_module_rm/test/legacy/service/RecordServiceImplTest.java @@ -38,8 +38,6 @@ 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.record.RecordService; 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; @@ -181,22 +179,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; } @@ -209,16 +198,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); @@ -230,10 +212,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; } @@ -247,9 +229,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)); @@ -311,7 +291,8 @@ public class RecordServiceImplTest extends BaseRMTestCase public void test(Void result) { - checkPermissions(READ_RECORDS, AccessStatus.DENIED, // file plan + checkPermissions(READ_RECORDS, + AccessStatus.DENIED, // file plan AccessStatus.DENIED, // unfiled container AccessStatus.DENIED, // record category AccessStatus.DENIED, // record folder @@ -320,7 +301,8 @@ public class RecordServiceImplTest extends BaseRMTestCase 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 diff --git a/rm-community/rm-community-repo/test/java/org/alfresco/module/org_alfresco_module_rm/test/util/BaseRMTestCase.java b/rm-community/rm-community-repo/test/java/org/alfresco/module/org_alfresco_module_rm/test/util/BaseRMTestCase.java index 4365d3339b..cf72a52da9 100644 --- a/rm-community/rm-community-repo/test/java/org/alfresco/module/org_alfresco_module_rm/test/util/BaseRMTestCase.java +++ b/rm-community/rm-community-repo/test/java/org/alfresco/module/org_alfresco_module_rm/test/util/BaseRMTestCase.java @@ -423,11 +423,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; } @@ -602,16 +603,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()); } /** @@ -687,20 +693,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; + }); + }); } /** diff --git a/rm-community/rm-community-repo/test/java/org/alfresco/module/org_alfresco_module_rm/test/util/BehaviourTest.java b/rm-community/rm-community-repo/test/java/org/alfresco/module/org_alfresco_module_rm/test/util/BehaviourTest.java new file mode 100644 index 0000000000..e77dc8a92f --- /dev/null +++ b/rm-community/rm-community-repo/test/java/org/alfresco/module/org_alfresco_module_rm/test/util/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-community/rm-community-repo/test/java/org/alfresco/module/org_alfresco_module_rm/test/util/ExpectedFailure.java b/rm-community/rm-community-repo/test/java/org/alfresco/module/org_alfresco_module_rm/test/util/ExpectedFailure.java new file mode 100644 index 0000000000..9377402aaf --- /dev/null +++ b/rm-community/rm-community-repo/test/java/org/alfresco/module/org_alfresco_module_rm/test/util/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-community/rm-community-repo/test/java/org/alfresco/module/org_alfresco_module_rm/test/util/ExpectedValue.java b/rm-community/rm-community-repo/test/java/org/alfresco/module/org_alfresco_module_rm/test/util/ExpectedValue.java new file mode 100644 index 0000000000..dd72955d2c --- /dev/null +++ b/rm-community/rm-community-repo/test/java/org/alfresco/module/org_alfresco_module_rm/test/util/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-community/rm-community-repo/unit-test/java/org/alfresco/module/org_alfresco_module_rm/security/ExtendedSecurityServiceImplUnitTest.java b/rm-community/rm-community-repo/unit-test/java/org/alfresco/module/org_alfresco_module_rm/security/ExtendedSecurityServiceImplUnitTest.java new file mode 100644 index 0000000000..05d691186b --- /dev/null +++ b/rm-community/rm-community-repo/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-community/rm-community-repo/unit-test/java/org/alfresco/module/org_alfresco_module_rm/security/FilePlanPermissionServiceImplUnitTest.java b/rm-community/rm-community-repo/unit-test/java/org/alfresco/module/org_alfresco_module_rm/security/FilePlanPermissionServiceImplUnitTest.java index f54ab8423b..cdcab4f4a4 100644 --- a/rm-community/rm-community-repo/unit-test/java/org/alfresco/module/org_alfresco_module_rm/security/FilePlanPermissionServiceImplUnitTest.java +++ b/rm-community/rm-community-repo/unit-test/java/org/alfresco/module/org_alfresco_module_rm/security/FilePlanPermissionServiceImplUnitTest.java @@ -27,13 +27,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; @@ -172,16 +169,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); } /** @@ -194,15 +182,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); } /** @@ -216,14 +196,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); } /** @@ -237,16 +209,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)); - } /** @@ -259,17 +221,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); } /** @@ -283,13 +235,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)); } /** @@ -302,14 +247,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); } /** @@ -322,10 +260,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); } @@ -333,13 +267,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); } /** @@ -350,21 +283,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); + } } /** @@ -380,7 +312,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); } /** @@ -396,7 +328,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); } /** @@ -412,7 +344,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); } @@ -429,7 +361,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); } /** @@ -445,7 +377,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); } /** @@ -461,7 +393,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); } @@ -478,7 +410,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); } /** @@ -494,7 +426,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); } @@ -511,7 +443,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-community/rm-community-repo/unit-test/java/org/alfresco/repo/web/scripts/roles/DynamicAuthoritiesGetUnitTest.java b/rm-community/rm-community-repo/unit-test/java/org/alfresco/repo/web/scripts/roles/DynamicAuthoritiesGetUnitTest.java new file mode 100644 index 0000000000..70304fb172 --- /dev/null +++ b/rm-community/rm-community-repo/unit-test/java/org/alfresco/repo/web/scripts/roles/DynamicAuthoritiesGetUnitTest.java @@ -0,0 +1,418 @@ +/* + * #%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% + */ +/* + * 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