/* * #%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 <http://www.gnu.org/licenses/>. * #L% */ package org.alfresco.module.org_alfresco_module_rm.security; 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; import static org.alfresco.service.cmr.security.OwnableService.NO_OWNER; import static org.alfresco.util.ParameterCheck.mandatory; import static org.apache.commons.lang.BooleanUtils.isTrue; import java.util.HashSet; import java.util.Set; import org.alfresco.error.AlfrescoRuntimeException; 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.role.FilePlanRoleService; import org.alfresco.module.org_alfresco_module_rm.util.ServiceBaseImpl; import org.alfresco.repo.node.NodeServicePolicies; import org.alfresco.repo.policy.JavaBehaviour; import org.alfresco.repo.policy.PolicyComponent; import org.alfresco.repo.policy.annotation.Behaviour; import org.alfresco.repo.policy.annotation.BehaviourBean; import org.alfresco.repo.security.authentication.AuthenticationUtil; 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.AccessStatus; import org.alfresco.service.cmr.security.AuthorityService; import org.alfresco.service.cmr.security.AuthorityType; import org.alfresco.service.cmr.security.OwnableService; import org.alfresco.service.cmr.security.PermissionService; import org.alfresco.service.namespace.QName; import org.alfresco.util.ParameterCheck; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; /** * File plan permission service. * * @author Roy Wetherall * @since 2.1 */ @BehaviourBean public class FilePlanPermissionServiceImpl extends ServiceBaseImpl implements FilePlanPermissionService, RMPermissionModel, NodeServicePolicies.OnMoveNodePolicy { /** Permission service */ private PermissionService permissionService; /** Ownable service */ private OwnableService ownableService; /** Policy component */ private PolicyComponent policyComponent; /** Authority service */ private AuthorityService authorityService; /** File plan role service */ private FilePlanRoleService filePlanRoleService; /** File plan service */ private FilePlanService filePlanService; /** Logger */ private static final Log LOGGER = LogFactory.getLog(FilePlanPermissionServiceImpl.class); /** * Initialisation method */ public void init() { getPolicyComponent().bindClassBehaviour( NodeServicePolicies.OnAddAspectPolicy.QNAME, ASPECT_RECORD, new JavaBehaviour(this, "onAddRecord", TRANSACTION_COMMIT)); getPolicyComponent().bindClassBehaviour( NodeServicePolicies.OnMoveNodePolicy.QNAME, ASPECT_RECORD, new JavaBehaviour(this, "onMoveRecord", TRANSACTION_COMMIT)); getPolicyComponent().bindClassBehaviour( NodeServicePolicies.OnMoveNodePolicy.QNAME, TYPE_RECORD_CATEGORY, new JavaBehaviour(this, "onMoveNode", TRANSACTION_COMMIT)); } /** * Gets the permission service * * @return The permission service */ protected PermissionService getPermissionService() { return this.permissionService; } /** * @param permissionService permission service */ public void setPermissionService(PermissionService permissionService) { this.permissionService = permissionService; } /** * Gets the policy component * * @return The policy component */ protected PolicyComponent getPolicyComponent() { return this.policyComponent; } /** * @param policyComponent policy component */ public void setPolicyComponent(PolicyComponent policyComponent) { this.policyComponent = policyComponent; } /** * Gets the ownable service * * @return The ownable service */ protected OwnableService getOwnableService() { return this.ownableService; } /** * @param ownableService ownable service */ public void setOwnableService(OwnableService ownableService) { this.ownableService = ownableService; } /** * Gets the authority service * * @return The authority service */ public AuthorityService getAuthorityService() { return this.authorityService; } /** * Sets the authority service * * @param authorityService The authority service */ public void setAuthorityService(AuthorityService authorityService) { this.authorityService = authorityService; } /** * Gets the file plan role service * * @return The file plan role service */ public FilePlanRoleService getFilePlanRoleService() { return this.filePlanRoleService; } /** * Sets the file plan role service * * @param filePlanRoleService The file plan role service to set */ public void setFilePlanRoleService(FilePlanRoleService filePlanRoleService) { this.filePlanRoleService = filePlanRoleService; } /** * Gets the file plan service * * @return The file plan service */ public FilePlanService getFilePlanService() { return this.filePlanService; } /** * Sets the file plan service * * @param filePlanService The file plan service to set */ public void setFilePlanService(FilePlanService filePlanService) { this.filePlanService = filePlanService; } /** * @see org.alfresco.module.org_alfresco_module_rm.security.FilePlanPermissionService#setupRecordCategoryPermissions(org.alfresco.service.cmr.repository.NodeRef) */ @Override public void setupRecordCategoryPermissions(final NodeRef recordCategory) { mandatory("recordCategory", recordCategory); // assert that we have a record category in our hands if (!instanceOf(recordCategory, TYPE_RECORD_CATEGORY)) { throw new AlfrescoRuntimeException("Unable to setup record category permissions, because node is not a record category."); } // setup category permissions NodeRef parentNodeRef = nodeService.getPrimaryParent(recordCategory).getParentRef(); setupPermissions(parentNodeRef, recordCategory); } /** * Setup permissions on new unfiled record folder * * @param childAssocRef child association reference */ @Behaviour ( type = "rma:unfiledRecordFolder", kind = CLASS, policy = "alf:onCreateNode", notificationFrequency = TRANSACTION_COMMIT ) public void onCreateUnfiledRecordFolder(ChildAssociationRef childAssocRef) { mandatory("childAssocRef", childAssocRef); setupPermissions(childAssocRef.getParentRef(), childAssocRef.getChildRef()); } /** * Setup permissions on new record folder * * @param childAssocRef child association reference */ @Behaviour ( type = "rma:recordFolder", kind = CLASS, policy = "alf:onCreateNode", notificationFrequency = TRANSACTION_COMMIT ) public void onCreateRecordFolder(ChildAssociationRef childAssocRef) { mandatory("childAssocRef", childAssocRef); setupPermissions(childAssocRef.getParentRef(), childAssocRef.getChildRef()); } /** * Setup permissions on newly created hold. * * @param childAssocRef child association reference */ @Behaviour ( type = "rma:hold", kind = CLASS, policy = "alf:onCreateNode", notificationFrequency = TRANSACTION_COMMIT ) public void onCreateHold(final ChildAssociationRef childAssocRef) { createContainerElement(childAssocRef); } /** * Setup permissions on newly created transfer. * * @param childAssocRef child association reference */ @Behaviour ( type = "rma:transfer", kind = CLASS, policy = "alf:onCreateNode", notificationFrequency = TRANSACTION_COMMIT ) public void onCreateTransfer(final ChildAssociationRef childAssocRef) { createContainerElement(childAssocRef); } /** * Helper method to create a container element, e.g. transfer folder or hold * * @param childAssocRef */ private void createContainerElement(final ChildAssociationRef childAssocRef) { mandatory("childAssocRef", childAssocRef); NodeRef childRef = childAssocRef.getChildRef(); setupPermissions(childAssocRef.getParentRef(), childRef); grantFilingPermissionToCreator(childRef); } /** * Helper method to give filing permissions to the currently logged in user who creates the node (transfer folder, hold, etc.) * * @param nodeRef The node reference of the created object */ private void grantFilingPermissionToCreator(final NodeRef nodeRef) { final String user = AuthenticationUtil.getFullyAuthenticatedUser(); final boolean hasUserPermission = authenticationUtil.runAs(new AuthenticationUtil.RunAsWork<Boolean>() { public Boolean doWork() { return getPermissionService().hasPermission(nodeRef, RMPermissionModel.FILING) == AccessStatus.ALLOWED; } }, user); if (!hasUserPermission) { authenticationUtil.runAsSystem(new AuthenticationUtil.RunAsWork<Void>() { public Void doWork() { getPermissionService().setPermission(nodeRef, user, RMPermissionModel.FILING, true); return null; } }); } } /** * Helper method to setup permissions. * * @param parent parent node reference * @param nodeRef child node reference */ public void setupPermissions(final NodeRef parent, final NodeRef nodeRef) { mandatory("parent", parent); mandatory("nodeRef", nodeRef); if (nodeService.exists(nodeRef) && nodeService.exists(parent)) { authenticationUtil.runAsSystem(new AuthenticationUtil.RunAsWork<Object>() { public Object doWork() { // set inheritance boolean isParentNodeFilePlan = isRecordCategory(nodeRef) && isFilePlan(parent); boolean inheritanceAllowed = isInheritanceAllowed(nodeRef, isParentNodeFilePlan); getPermissionService().setInheritParentPermissions(nodeRef, inheritanceAllowed); Set<AccessPermission> keepPerms = new HashSet<AccessPermission>(5); Set<AccessPermission> 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) { String adminRole = getAdminRole(nodeRef); getPermissionService().setPermission(nodeRef, adminRole, RMPermissionModel.FILING, true); } // remove owner getOwnableService().setOwner(nodeRef, NO_OWNER); if (isParentNodeFilePlan) { Set<AccessPermission> perms = permissionService.getAllSetPermissions(parent); for (AccessPermission perm : perms) { if (RMPermissionModel.FILING.equals(perm.getPermission())) { AccessStatus accessStatus = perm.getAccessStatus(); boolean allow = false; if (AccessStatus.ALLOWED.equals(accessStatus)) { allow = true; } permissionService.setPermission( nodeRef, perm.getAuthority(), perm.getPermission(), allow); } } } return null; } }); } } private String getAdminRole(NodeRef nodeRef) { NodeRef filePlan = getFilePlan(nodeRef); if (filePlan == null) { throw new AlfrescoRuntimeException("The file plan could not be found for the give node: '" + nodeRef + "'."); } 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))); } /** * Sets ups records permission when aspect is added. * * @see NodeServicePolicies.OnAddAspectPolicy#onAddAspect(NodeRef, QName) * * @param record * @param aspectTypeQName */ public void onAddRecord(final NodeRef record, final QName aspectTypeQName) { mandatory("childAssocRef", record); mandatory("childAssocRef", aspectTypeQName); authenticationUtil.runAsSystem(new AuthenticationUtil.RunAsWork<Object>() { public Object doWork() { if (nodeService.exists(record) && nodeService.hasAspect(record, aspectTypeQName)) { NodeRef recordFolder = nodeService.getPrimaryParent(record).getParentRef(); setupPermissions(recordFolder, record); } return null; } }); } /** * onMoveRecord behaviour * * @param sourceAssocRef source association reference * @param destinationAssocRef destination association reference */ public void onMoveRecord(final ChildAssociationRef sourceAssocRef, final ChildAssociationRef destinationAssocRef) { mandatory("sourceAssocRef", sourceAssocRef); mandatory("destinationAssocRef", destinationAssocRef); authenticationUtil.runAs(new AuthenticationUtil.RunAsWork<Void>() { public Void doWork() { NodeRef record = sourceAssocRef.getChildRef(); if (nodeService.exists(record) && nodeService.hasAspect(record, ASPECT_RECORD)) { boolean inheritParentPermissions = permissionService.getInheritParentPermissions(record); Set<AccessPermission> keepPerms = new HashSet<AccessPermission>(5); Set<AccessPermission> origionalRecordPerms= permissionService.getAllSetPermissions(record); for (AccessPermission recordPermission : origionalRecordPerms) { String permission = recordPermission.getPermission(); if ((RMPermissionModel.FILING.equals(permission) || RMPermissionModel.READ_RECORDS.equals(permission)) && recordPermission.isSetDirectly()) { // then we can assume this is a permission we want to preserve keepPerms.add(recordPermission); } } // re-setup the records permissions setupPermissions(destinationAssocRef.getParentRef(), record); // re-add keep'er permissions for (AccessPermission keeper : keepPerms) { setPermission(record, keeper.getAuthority(), keeper.getPermission()); } permissionService.setInheritParentPermissions(record, inheritParentPermissions); } return null; } }, getSystemUserName()); } /** * @see org.alfresco.module.org_alfresco_module_rm.security.RecordsManagementSecurityService#setPermission(org.alfresco.service.cmr.repository.NodeRef, java.lang.String, java.lang.String, boolean) */ public void setPermission(final NodeRef nodeRef, final String authority, final String permission) { ParameterCheck.mandatory("nodeRef", nodeRef); ParameterCheck.mandatory("authority", authority); ParameterCheck.mandatory("permission", permission); authenticationUtil.runAsSystem(new AuthenticationUtil.RunAsWork<Object>() { public Void doWork() { if (canPerformPermissionAction(nodeRef)) { // Set the permission on the node getPermissionService().setPermission(nodeRef, authority, permission, true); } else { if (LOGGER.isWarnEnabled()) { LOGGER.warn("Setting permissions for this node is not supported. (nodeRef=" + nodeRef + ", authority=" + authority + ", permission=" + permission + ")"); } } return null; } }); } /** * @see org.alfresco.module.org_alfresco_module_rm.security.RecordsManagementSecurityService#deletePermission(org.alfresco.service.cmr.repository.NodeRef, java.lang.String, java.lang.String) */ public void deletePermission(final NodeRef nodeRef, final String authority, final String permission) { ParameterCheck.mandatory("nodeRef", nodeRef); ParameterCheck.mandatory("authority", authority); ParameterCheck.mandatory("permission", permission); authenticationUtil.runAsSystem(new AuthenticationUtil.RunAsWork<Object>() { public Void doWork() { if (canPerformPermissionAction(nodeRef)) { // Delete permission on this node getPermissionService().deletePermission(nodeRef, authority, permission); } else { if (LOGGER.isWarnEnabled()) { LOGGER.warn("Deleting permissions for this node is not supported. (nodeRef=" + nodeRef + ", authority=" + authority + ", permission=" + permission + ")"); } } return null; } }); } private boolean canPerformPermissionAction(NodeRef nodeRef) { return isFilePlanContainer(nodeRef) || isRecordFolder(nodeRef) || isRecord(nodeRef) || isTransfer(nodeRef) || isHold(nodeRef); } /** * @see org.alfresco.repo.node.NodeServicePolicies.OnMoveNodePolicy#onMoveNode(org.alfresco.service.cmr.repository.ChildAssociationRef, org.alfresco.service.cmr.repository.ChildAssociationRef) */ @Override public void onMoveNode(ChildAssociationRef oldChildAssocRef, ChildAssociationRef newChildAssocRef) { if (isFilePlan(newChildAssocRef.getParentRef())) { permissionService.setInheritParentPermissions(oldChildAssocRef.getChildRef(), false); } } }