/* * The Kuali Financial System, a comprehensive financial management system for higher education. * * Copyright 2005-2014 The Kuali Foundation * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version. * * This program 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 Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package org.kuali.kfs.sec.document; import java.util.HashMap; import java.util.Map; import org.apache.commons.lang.StringUtils; import org.kuali.kfs.sec.businessobject.SecurityDefinition; import org.kuali.kfs.sec.businessobject.SecurityDefinitionDocumentType; import org.kuali.kfs.sec.service.AccessSecurityService; import org.kuali.kfs.sys.KFSConstants; import org.kuali.kfs.sys.context.SpringContext; import org.kuali.rice.kew.api.exception.WorkflowException; import org.kuali.rice.kim.api.KimConstants; import org.kuali.rice.kim.api.common.template.Template; import org.kuali.rice.kim.api.permission.Permission; import org.kuali.rice.kim.api.role.Role; import org.kuali.rice.kim.api.services.KimApiServiceLocator; import org.kuali.rice.kns.document.MaintenanceDocument; import org.kuali.rice.krad.bo.DocumentHeader; import org.kuali.rice.krad.service.DocumentService; import org.kuali.rice.krad.util.KRADConstants; /** * Maintainable implementation for the Security Definition maintenance document. Hooks into Post processing to create the KIM permissions from the definition records */ public class SecurityDefinitionMaintainableImpl extends AbstractSecurityModuleMaintainable { private static final org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(SecurityDefinitionMaintainableImpl.class); /** * @see org.kuali.rice.kns.maintenance.KualiMaintainableImpl#doRouteStatusChange(org.kuali.rice.krad.bo.DocumentHeader) */ @Override public void doRouteStatusChange(DocumentHeader documentHeader) { super.doRouteStatusChange(documentHeader); if (documentHeader.getWorkflowDocument().isProcessed()) { DocumentService documentService = SpringContext.getBean(DocumentService.class); try { MaintenanceDocument document = (MaintenanceDocument) documentService.getByDocumentHeaderId(documentHeader.getDocumentNumber()); SecurityDefinition oldSecurityDefinition = (SecurityDefinition) document.getOldMaintainableObject().getBusinessObject(); SecurityDefinition newSecurityDefinition = (SecurityDefinition) document.getNewMaintainableObject().getBusinessObject(); oldSecurityDefinition.refreshNonUpdateableReferences(); newSecurityDefinition.refreshNonUpdateableReferences(); boolean newMaintenanceAction = getMaintenanceAction().equalsIgnoreCase(KRADConstants.MAINTENANCE_NEW_ACTION) || getMaintenanceAction().equalsIgnoreCase(KRADConstants.MAINTENANCE_COPY_ACTION); createOrUpdateDefinitionRole(oldSecurityDefinition, newSecurityDefinition); createOrUpdateDocumentPermissions(newSecurityDefinition); createOrUpdateLookupPermission(newSecurityDefinition); createOrUpdateInquiryPermissions(newSecurityDefinition); } catch (WorkflowException e) { LOG.error("caught exception while handling handleRouteStatusChange -> documentService.getByDocumentHeaderId(" + documentHeader.getDocumentNumber() + "). ", e); throw new RuntimeException("caught exception while handling handleRouteStatusChange -> documentService.getByDocumentHeaderId(" + documentHeader.getDocumentNumber() + "). ", e); } } } /** * Creates a new role for the definition (if the definition is new), then grants to the role any new permissions granted for the definition. Also update role active indicator * if indicator changed values on the definition * * @param oldSecurityDefinition SecurityDefinition record before updates * @param newSecurityDefinition SecurityDefinition after updates */ protected void createOrUpdateDefinitionRole(SecurityDefinition oldSecurityDefinition, SecurityDefinition newSecurityDefinition ) { //, List<Permission> permissionsToAssign ) { Role oldRole = null; if ( StringUtils.isNotBlank(oldSecurityDefinition.getRoleId()) ) { oldRole = KimApiServiceLocator.getRoleService().getRole(oldSecurityDefinition.getRoleId()); } if ( oldRole == null ) { Role.Builder newRole = Role.Builder.create(); newRole.setNamespaceCode(KFSConstants.CoreModuleNamespaces.ACCESS_SECURITY); newRole.setName(newSecurityDefinition.getName()); newRole.setDescription(newSecurityDefinition.getDescription()); newRole.setActive(newSecurityDefinition.isActive()); newRole.setKimTypeId(getDefaultRoleTypeId()); Role createdRole = KimApiServiceLocator.getRoleService().createRole(newRole.build()); newSecurityDefinition.setRoleId(createdRole.getId()); } else { // update role active indicator if it has been updated on the definition if ( oldSecurityDefinition.isActive() != newSecurityDefinition.isActive() ) { Role.Builder updatedRole = Role.Builder.create(oldRole); updatedRole.setActive(newSecurityDefinition.isActive()); KimApiServiceLocator.getRoleService().updateRole(updatedRole.build()); } } } /** * Iterates through the document types and creates any new document permissions necessary or updates old permissions setting inactive if needed * * @param oldSecurityDefinition SecurityDefiniton record before requested changes (old side of maintenance document) * @param newSecurityDefinition SecurityDefinition record with requested changes (new side of maintenance document) * @param newMaintenanceAction Indicates whether this is a new maintenance record (old side in empty) */ protected void createOrUpdateDocumentPermissions(SecurityDefinition securityDefinition) { for (SecurityDefinitionDocumentType definitionDocumentType : securityDefinition.getDefinitionDocumentTypes()) { String documentType = definitionDocumentType.getFinancialSystemDocumentTypeCode(); boolean documentTypePermissionActive = securityDefinition.isActive() && definitionDocumentType.isActive(); createOrUpdateDocumentTypePermissions(documentType, documentTypePermissionActive, securityDefinition); } } /** * First tries to retrieve a lookup permission previously setup for this definition. If old permission found it will be updated with the new details and its active indicator * will be set based on the definition active indicator and restrict lookup indicator value. If old permission does not exist but restrict lookup indicator is true on new side * then a new permission will be created and will be active if definition is active on new side. * * @param oldSecurityDefinition SecurityDefiniton record before requested changes (old side of maintenance document) * @param newSecurityDefinition SecurityDefinition record with requested changes (new side of maintenance document) * @param newMaintenanceAction Indicates whether this is a new maintenance record (old side in empty) */ protected void createOrUpdateLookupPermission(SecurityDefinition securityDefinition) { Template lookupTemplate = getAccessSecurityService().getLookupWithFieldValueTemplate(); String permissionName = securityDefinition.getName() + "/" + lookupTemplate.getName(); createOrUpdatePermissionAndAssignToRole(permissionName, securityDefinition.getRoleId(), securityDefinition.getDescription(), securityDefinition.isRestrictLookup(), lookupTemplate, getLookupPermissionDetails(securityDefinition)); } /** * First tries to find inquiry permissions for GL namespace and LD namespace. If old permissions are found they will be updated with the new details and active indicator will * be set based on the definition active indicator and restrict gl indicator (for gl inqury permission) and restrict ld inquiry (for ld inquiry permission). If an old * permission does not exist for one or both of the namespaces and the corresponding indicators are set to true on new side then new permissions will be created with active * indicator set to true if definition is active on new side. * * @param oldSecurityDefinition SecurityDefiniton record before requested changes (old side of maintenance document) * @param newSecurityDefinition SecurityDefinition record with requested changes (new side of maintenance document) * @param newMaintenanceAction Indicates whether this is a new maintenance record (old side in empty) */ protected void createOrUpdateInquiryPermissions(SecurityDefinition securityDefinition) { // find old inquiry permissions Template inquiryTemplate = getAccessSecurityService().getInquiryWithFieldValueTemplate(); String glPermissionName = securityDefinition.getName() + "/" + inquiryTemplate.getName() + "/" + KFSConstants.CoreModuleNamespaces.GL; String ldPermissionName = securityDefinition.getName() + "/" + inquiryTemplate.getName() + "/" + KFSConstants.OptionalModuleNamespaces.LABOR_DISTRIBUTION; Permission glPermission = KimApiServiceLocator.getPermissionService().findPermByNamespaceCodeAndName(KFSConstants.CoreModuleNamespaces.ACCESS_SECURITY, glPermissionName ); Permission ldPermission = KimApiServiceLocator.getPermissionService().findPermByNamespaceCodeAndName(KFSConstants.CoreModuleNamespaces.ACCESS_SECURITY, ldPermissionName ); // need to save gl inquiry permission if new side indicator is true or already has a permission in which case we need to update details and active indicator createOrUpdatePermissionAndAssignToRole(glPermissionName, securityDefinition.getRoleId(), securityDefinition.getDescription(), securityDefinition.isRestrictGLInquiry(), inquiryTemplate, getInquiryPermissionDetails(KFSConstants.CoreModuleNamespaces.GL,securityDefinition)); createOrUpdatePermissionAndAssignToRole(ldPermissionName, securityDefinition.getRoleId(), securityDefinition.getDescription(), securityDefinition.isRestrictLaborInquiry(), inquiryTemplate, getInquiryPermissionDetails(KFSConstants.OptionalModuleNamespaces.LABOR_DISTRIBUTION,securityDefinition)); } /** * For each of the document templates ids calls helper method to create or update corresponding permission * * @param documentType workflow document type name for permission detail * @param active boolean indicating whether the permissions should be set to active (true) or non-active (false) * @param oldSecurityDefinition SecurityDefiniton record before requested changes (old side of maintenance document) * @param newSecurityDefinition SecurityDefinition record with requested changes (new side of maintenance document) */ protected void createOrUpdateDocumentTypePermissions(String documentType, boolean active, SecurityDefinition securityDefinition) { Map<String,String> permissionDetails = populateDocumentTypePermissionDetails(documentType, securityDefinition); // Permission Names must be unique // So - Security Definition Name/template name/document type // view document Template permissionTemplate = getAccessSecurityService().getViewDocumentWithFieldValueTemplate(); String permissionName = securityDefinition.getName() + "/" + permissionTemplate.getName() + "/" + documentType; createOrUpdatePermissionAndAssignToRole(permissionName, securityDefinition.getRoleId(), securityDefinition.getDescription(), active && securityDefinition.isRestrictViewDocument(), permissionTemplate, permissionDetails); // view accounting line permissionTemplate = getAccessSecurityService().getViewAccountingLineWithFieldValueTemplate(); permissionName = securityDefinition.getName() + "/" + permissionTemplate.getName() + "/" + documentType; createOrUpdatePermissionAndAssignToRole(permissionName, securityDefinition.getRoleId(), securityDefinition.getDescription(), active && securityDefinition.isRestrictViewAccountingLine(), permissionTemplate, permissionDetails); // view notes/attachments permissionTemplate = getAccessSecurityService().getViewNotesAttachmentsWithFieldValueTemplate(); permissionName = securityDefinition.getName() + "/" + permissionTemplate.getName() + "/" + documentType; createOrUpdatePermissionAndAssignToRole(permissionName, securityDefinition.getRoleId(), securityDefinition.getDescription(), active && securityDefinition.isRestrictViewNotesAndAttachments(), permissionTemplate, permissionDetails); // edit accounting line permissionTemplate = getAccessSecurityService().getEditAccountingLineWithFieldValueTemplate(); permissionName = securityDefinition.getName() + "/" + permissionTemplate.getName() + "/" + documentType; createOrUpdatePermissionAndAssignToRole(permissionName, securityDefinition.getRoleId(), securityDefinition.getDescription(), active && securityDefinition.isRestrictEditAccountingLine(), permissionTemplate, permissionDetails); // edit document permissionTemplate = getAccessSecurityService().getEditDocumentWithFieldValueTemplate(); permissionName = securityDefinition.getName() + "/" + permissionTemplate.getName() + "/" + documentType; createOrUpdatePermissionAndAssignToRole(permissionName, securityDefinition.getRoleId(), securityDefinition.getDescription(), active && securityDefinition.isRestrictEditDocument(), permissionTemplate, permissionDetails); } /** * Builds an Map<String,String> populated from the given method parameters. Details are set based on the KIM 'Security Document Permission' type. * * @param documentType workflow document type name * @param securityDefinition SecurityDefiniton record * @return Map<String,String> populated with document type name, property name, operator, and property value details */ protected Map<String,String> populateDocumentTypePermissionDetails(String documentType, SecurityDefinition securityDefinition) { Map<String,String> permissionDetails = new HashMap<String,String>(); permissionDetails.put(KimConstants.AttributeConstants.DOCUMENT_TYPE_NAME, documentType); permissionDetails.put(KimConstants.AttributeConstants.PROPERTY_NAME, securityDefinition.getSecurityAttribute().getName()); return permissionDetails; } /** * Builds an Map<String,String> populated from the given method parameters. Details are set based on the KIM 'Security Lookup Permission' type. * * @param securityDefinition SecurityDefiniton record * @return Map<String,String> populated with property name, operator, and property value details */ protected Map<String,String> getLookupPermissionDetails(SecurityDefinition securityDefinition) { Map<String,String> permissionDetails = new HashMap<String,String>(); permissionDetails.put(KimConstants.AttributeConstants.PROPERTY_NAME, securityDefinition.getSecurityAttribute().getName()); return permissionDetails; } /** * Builds an Map<String,String> populated from the given method parameters. Details are set based on the KIM 'Security Inquiry Permission' type. * * @param namespaceCode KIM namespace code * @param securityDefinition SecurityDefiniton record * @return Map<String,String> populated with namespace, property name, operator, and property value details */ protected Map<String,String> getInquiryPermissionDetails(String namespaceCode, SecurityDefinition securityDefinition) { Map<String,String> permissionDetails = new HashMap<String,String>(); permissionDetails.put(KimConstants.AttributeConstants.NAMESPACE_CODE, namespaceCode); permissionDetails.put(KimConstants.AttributeConstants.PROPERTY_NAME, securityDefinition.getSecurityAttribute().getName()); return permissionDetails; } /** * Determines whether a given document type name is included in the document type list for the given security definition * * @param documentType KEW document type name * @param oldSecurityDefinition SecurityDefinition record * @return boolean indicating whether the document type is associated with the given security definition */ protected boolean isDocumentTypeInDefinition(String documentType, SecurityDefinition oldSecurityDefinition) { for (SecurityDefinitionDocumentType definitionDocumentType : oldSecurityDefinition.getDefinitionDocumentTypes()) { String oldDocumentType = definitionDocumentType.getFinancialSystemDocumentTypeCode(); if (StringUtils.equals(documentType, oldDocumentType)) { return true; } } return false; } /** * Calls PermissionUpdateService to save a permission. * * @param securityDefinition SecurityDefinition record * @param permissionId ID for the permission being saved, or empty for new permission * @param permissionTemplateId KIM template ID for permission to save * @param active boolean indicating whether the permission should be set to active (true) or non-active (false) * @param permissionDetails Map<String,String> representing the permission details * @see org.kuali.rice.kim.service.PermissionUpdateService#savePermission() */ protected void createOrUpdatePermissionAndAssignToRole(String permissionName, String roleId, String permissionDescription, boolean active, Template permissionTemplate, Map<String,String> permissionDetails) { // Get the existing permission Permission perm = KimApiServiceLocator.getPermissionService().findPermByNamespaceCodeAndName(KFSConstants.CoreModuleNamespaces.ACCESS_SECURITY, permissionName); if ( perm == null ) { if ( active ) { Permission.Builder newPerm = Permission.Builder.create(KFSConstants.CoreModuleNamespaces.ACCESS_SECURITY, permissionName); newPerm.setTemplate( Template.Builder.create(permissionTemplate) ); newPerm.setDescription(permissionDescription ); newPerm.setAttributes(permissionDetails); newPerm.setActive(true); if ( LOG.isDebugEnabled() ) { LOG.debug( "About to save new permission: " + newPerm); } perm = KimApiServiceLocator.getPermissionService().createPermission(newPerm.build()); } } else { if ( perm.isActive() != active ) { Permission.Builder updatedPerm = Permission.Builder.create(perm); updatedPerm.setActive(active); perm = KimApiServiceLocator.getPermissionService().updatePermission(updatedPerm.build()); } } assignPermissionToRole(perm, roleId); } protected void assignPermissionToRole( Permission perm, String roleId ) { if ( perm != null ) { if ( perm.isActive() ) { KimApiServiceLocator.getRoleService().assignPermissionToRole(perm.getId(), roleId ); } else { KimApiServiceLocator.getRoleService().revokePermissionFromRole(perm.getId(), roleId ); } } } /** * Override to clear out KIM role id on copy * * @see org.kuali.rice.kns.maintenance.KualiMaintainableImpl#processAfterCopy(org.kuali.rice.kns.document.MaintenanceDocument, * java.util.Map) */ @Override public void processAfterCopy(MaintenanceDocument document, Map<String, String[]> parameters) { SecurityDefinition securityDefinition = (SecurityDefinition) document.getNewMaintainableObject().getBusinessObject(); securityDefinition.setRoleId(""); super.processAfterCopy(document, parameters); } private static AccessSecurityService accessSecurityService; public static AccessSecurityService getAccessSecurityService() { if ( accessSecurityService == null ) { accessSecurityService = SpringContext.getBean(AccessSecurityService.class); } return accessSecurityService; } }