001    /*
002     * Copyright 2011 The Kuali Foundation.
003     * 
004     * Licensed under the Educational Community License, Version 2.0 (the "License");
005     * you may not use this file except in compliance with the License.
006     * You may obtain a copy of the License at
007     * 
008     * http://www.opensource.org/licenses/ecl2.php
009     * 
010     * Unless required by applicable law or agreed to in writing, software
011     * distributed under the License is distributed on an "AS IS" BASIS,
012     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013     * See the License for the specific language governing permissions and
014     * limitations under the License.
015     */
016    package org.kuali.kfs.sec.document;
017    
018    import java.util.ArrayList;
019    import java.util.List;
020    import java.util.Map;
021    
022    import org.apache.commons.lang.StringUtils;
023    import org.kuali.kfs.sec.SecConstants;
024    import org.kuali.kfs.sec.businessobject.SecurityDefinition;
025    import org.kuali.kfs.sec.businessobject.SecurityDefinitionDocumentType;
026    import org.kuali.kfs.sec.identity.SecKimAttributes;
027    import org.kuali.kfs.sec.service.AccessSecurityService;
028    import org.kuali.kfs.sys.KFSConstants;
029    import org.kuali.kfs.sys.context.SpringContext;
030    import org.kuali.kfs.sys.document.FinancialSystemMaintainable;
031    import org.kuali.rice.kew.exception.WorkflowException;
032    import org.kuali.rice.kim.bo.role.dto.KimPermissionInfo;
033    import org.kuali.rice.kim.bo.types.dto.AttributeSet;
034    import org.kuali.rice.kim.service.IdentityManagementService;
035    import org.kuali.rice.kim.service.PermissionService;
036    import org.kuali.rice.kim.service.PermissionUpdateService;
037    import org.kuali.rice.kim.service.impl.RoleManagementServiceImpl;
038    import org.kuali.rice.kns.bo.DocumentHeader;
039    import org.kuali.rice.kns.document.MaintenanceDocument;
040    import org.kuali.rice.kns.service.DocumentService;
041    import org.kuali.rice.kns.util.KNSConstants;
042    
043    
044    /**
045     * Maintainable implementation for the Security Definition maintenance document. Hooks into Post processing to create the KIM permissions from the definition records
046     */
047    public class SecurityDefinitionMaintainableImpl extends FinancialSystemMaintainable {
048        private static final org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(SecurityDefinitionMaintainableImpl.class);
049    
050        /**
051         * @see org.kuali.rice.kns.maintenance.KualiMaintainableImpl#refresh(java.lang.String, java.util.Map, org.kuali.rice.kns.document.MaintenanceDocument)
052         */
053        @Override
054        public void refresh(String refreshCaller, Map fieldValues, MaintenanceDocument document) {
055            super.refresh(refreshCaller, fieldValues, document);
056            this.getBusinessObject().refreshNonUpdateableReferences();
057        }
058    
059        /**
060         * @see org.kuali.rice.kns.maintenance.KualiMaintainableImpl#doRouteStatusChange(org.kuali.rice.kns.bo.DocumentHeader)
061         */
062        @Override
063        public void doRouteStatusChange(DocumentHeader documentHeader) {
064            super.doRouteStatusChange(documentHeader);
065    
066            if (documentHeader.getWorkflowDocument().stateIsProcessed()) {
067                DocumentService documentService = SpringContext.getBean(DocumentService.class);
068                try {
069                    MaintenanceDocument document = (MaintenanceDocument) documentService.getByDocumentHeaderId(documentHeader.getDocumentNumber());
070                    SecurityDefinition oldSecurityDefinition = (SecurityDefinition) document.getOldMaintainableObject().getBusinessObject();
071                    SecurityDefinition newSecurityDefinition = (SecurityDefinition) document.getNewMaintainableObject().getBusinessObject();
072    
073                    oldSecurityDefinition.refreshNonUpdateableReferences();
074                    newSecurityDefinition.refreshNonUpdateableReferences();
075    
076                    boolean newMaintenanceAction = getMaintenanceAction().equalsIgnoreCase(KNSConstants.MAINTENANCE_NEW_ACTION) || getMaintenanceAction().equalsIgnoreCase(KNSConstants.MAINTENANCE_COPY_ACTION);
077    
078                    createOrUpdateDocumentPermissions(oldSecurityDefinition, newSecurityDefinition, newMaintenanceAction);
079                    createOrUpdateLookupPermission(oldSecurityDefinition, newSecurityDefinition, newMaintenanceAction);
080                    createOrUpdateInquiryPermissions(oldSecurityDefinition, newSecurityDefinition, newMaintenanceAction);
081    
082                    createOrUpdateDefinitionRole(oldSecurityDefinition, newSecurityDefinition);
083                    
084                    SpringContext.getBean(IdentityManagementService.class).flushAllCaches();
085                }
086                catch (WorkflowException e) {
087                    LOG.error("caught exception while handling handleRouteStatusChange -> documentService.getByDocumentHeaderId(" + documentHeader.getDocumentNumber() + "). ", e);
088                    throw new RuntimeException("caught exception while handling handleRouteStatusChange -> documentService.getByDocumentHeaderId(" + documentHeader.getDocumentNumber() + "). ", e);
089                }
090            }
091        }
092    
093        /**
094         * 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
095         * if indicator changed values on the definition
096         * 
097         * @param oldSecurityDefinition SecurityDefinition record before updates
098         * @param newSecurityDefinition SecurityDefinition after updates
099         */
100        protected void createOrUpdateDefinitionRole(SecurityDefinition oldSecurityDefinition, SecurityDefinition newSecurityDefinition) {
101            RoleManagementServiceImpl roleService = SpringContext.getBean(RoleManagementServiceImpl.class);
102            PermissionService permissionService = SpringContext.getBean(PermissionService.class);
103            
104            String roleId = oldSecurityDefinition.getRoleId();
105            String roleName = newSecurityDefinition.getName();
106            if (StringUtils.isBlank(roleId)) {
107                // create new role for definition
108                roleId = roleService.getNextAvailableRoleId();
109                newSecurityDefinition.setRoleId(roleId);
110                roleService.saveRole(roleId, roleName, newSecurityDefinition.getDescription(), newSecurityDefinition.isActive(), SecConstants.SecurityTypes.SECURITY_DEFINITION_ROLE_TYPE, SecConstants.ACCESS_SECURITY_NAMESPACE_CODE);
111            }
112            else {
113                // update role active indicator if it has been updated on the definition
114                if ((oldSecurityDefinition.isActive() && !newSecurityDefinition.isActive()) || (!oldSecurityDefinition.isActive() && newSecurityDefinition.isActive())) {
115                    roleService.saveRole(roleId, roleName, newSecurityDefinition.getDescription(), newSecurityDefinition.isActive(), SecConstants.SecurityTypes.SECURITY_DEFINITION_ROLE_TYPE, SecConstants.ACCESS_SECURITY_NAMESPACE_CODE);
116                }
117            }
118    
119            // assign all permissions for definition to role (have same name as role)
120            List<KimPermissionInfo> permissions = permissionService.getPermissionsByName(SecConstants.ACCESS_SECURITY_NAMESPACE_CODE, roleName);
121            for (KimPermissionInfo kimPermissionInfo : permissions) {
122                List<String> permissionRoleIds = permissionService.getRoleIdsForPermissionId(kimPermissionInfo.getPermissionId());
123                if (!permissionRoleIds.contains(roleId)) {
124                    roleService.assignPermissionToRole(kimPermissionInfo.getPermissionId(), roleId);
125                }
126            }
127        }
128    
129        /**
130         * Iterates through the document types and creates any new document permissions necessary or updates old permissions setting inactive if needed
131         * 
132         * @param oldSecurityDefinition SecurityDefiniton record before requested changes (old side of maintenance document)
133         * @param newSecurityDefinition SecurityDefinition record with requested changes (new side of maintenance document)
134         * @param newMaintenanceAction Indicates whether this is a new maintenance record (old side in empty)
135         */
136        protected void createOrUpdateDocumentPermissions(SecurityDefinition oldSecurityDefinition, SecurityDefinition newSecurityDefinition, boolean newMaintenanceAction) {
137            for (SecurityDefinitionDocumentType definitionDocumentType : newSecurityDefinition.getDefinitionDocumentTypes()) {
138                String documentType = definitionDocumentType.getFinancialSystemDocumentTypeCode();
139                boolean documentTypePermissionActive = newSecurityDefinition.isActive() && definitionDocumentType.isActive();
140                boolean isNewDocumentType = newMaintenanceAction || !isDocumentTypeInDefinition(documentType, oldSecurityDefinition);
141    
142                if (isNewDocumentType) {
143                    createNewDocumentTypePermissions(documentType, documentTypePermissionActive, newSecurityDefinition);
144                }
145                else {
146                    createOrUpdateDocumentTypePermissions(documentType, documentTypePermissionActive, oldSecurityDefinition, newSecurityDefinition);
147                }
148            }
149        }
150    
151        /**
152         * 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
153         * 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
154         * then a new permission will be created and will be active if definition is active on new side.
155         * 
156         * @param oldSecurityDefinition SecurityDefiniton record before requested changes (old side of maintenance document)
157         * @param newSecurityDefinition SecurityDefinition record with requested changes (new side of maintenance document)
158         * @param newMaintenanceAction Indicates whether this is a new maintenance record (old side in empty)
159         */
160        protected void createOrUpdateLookupPermission(SecurityDefinition oldSecurityDefinition, SecurityDefinition newSecurityDefinition, boolean newMaintenanceAction) {
161            AttributeSet permissionDetails = populateLookupPermissionDetails(newSecurityDefinition);
162    
163            String permissionId = "";
164            if (!newMaintenanceAction) {
165                // find old Lookup permission
166                List<KimPermissionInfo> permissions = findSecurityPermissionsByNameAndTemplate(oldSecurityDefinition.getName(), SpringContext.getBean(AccessSecurityService.class).getLookupWithFieldValueTemplateId());
167                if (permissions != null && !permissions.isEmpty()) {
168                    KimPermissionInfo oldPermission = permissions.get(0);
169                    permissionId = oldPermission.getPermissionId();
170                }
171            }
172    
173            // need to save lookup permission if new side indicator is true or already has a permission in which case we need to update details and active indicator
174            if (newSecurityDefinition.isRestrictLookup() || StringUtils.isNotBlank(permissionId)) {
175                savePermission(newSecurityDefinition, permissionId, SpringContext.getBean(AccessSecurityService.class).getLookupWithFieldValueTemplateId(), newSecurityDefinition.isActive() && newSecurityDefinition.isRestrictLookup(), permissionDetails);
176            }
177        }
178    
179        /**
180         * 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
181         * 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
182         * 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
183         * indicator set to true if definition is active on new side.
184         * 
185         * @param oldSecurityDefinition SecurityDefiniton record before requested changes (old side of maintenance document)
186         * @param newSecurityDefinition SecurityDefinition record with requested changes (new side of maintenance document)
187         * @param newMaintenanceAction Indicates whether this is a new maintenance record (old side in empty)
188         */
189        protected void createOrUpdateInquiryPermissions(SecurityDefinition oldSecurityDefinition, SecurityDefinition newSecurityDefinition, boolean newMaintenanceAction) {
190            String glPermissionId = "";
191            String ldPermissionId = "";
192            if (!newMaintenanceAction) {
193                // find old inquiry permissions
194                List<KimPermissionInfo> permissions = findSecurityPermissionsByNameAndTemplate(oldSecurityDefinition.getName(),SpringContext.getBean(AccessSecurityService.class).getInquiryWithFieldValueTemplateId());
195                if (permissions != null) {
196                    for (KimPermissionInfo permissionInfo : permissions) {
197                        String namespaceCode = permissionInfo.getDetails().get(SecKimAttributes.NAMESPACE_CODE);
198                        if (StringUtils.equals(KFSConstants.ParameterNamespaces.GL, namespaceCode)) {
199                            glPermissionId = permissionInfo.getPermissionId();
200                        }
201                        else if (StringUtils.equals(SecConstants.LABOR_MODULE_NAMESPACE_CODE, namespaceCode)) {
202                            ldPermissionId = permissionInfo.getPermissionId();
203                        }
204                    }
205                }
206            }
207    
208            // 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
209            if (newSecurityDefinition.isRestrictGLInquiry() || StringUtils.isNotBlank(glPermissionId)) {
210                AttributeSet permissionDetails = populateInquiryPermissionDetails(KFSConstants.ParameterNamespaces.GL, newSecurityDefinition);
211                savePermission(newSecurityDefinition, glPermissionId, SpringContext.getBean(AccessSecurityService.class).getInquiryWithFieldValueTemplateId(), newSecurityDefinition.isActive() && newSecurityDefinition.isRestrictGLInquiry(), permissionDetails);
212            }
213    
214            // need to save ld inquiry permission if new side indicator is true or already has a permission in which case we need to update details and active indicator
215            if (newSecurityDefinition.isRestrictLaborInquiry() || StringUtils.isNotBlank(ldPermissionId)) {
216                AttributeSet permissionDetails = populateInquiryPermissionDetails(SecConstants.LABOR_MODULE_NAMESPACE_CODE, newSecurityDefinition);
217                savePermission(newSecurityDefinition, ldPermissionId, SpringContext.getBean(AccessSecurityService.class).getInquiryWithFieldValueTemplateId(), newSecurityDefinition.isActive() && newSecurityDefinition.isRestrictLaborInquiry(), permissionDetails);
218            }
219        }
220    
221        /**
222         * Checks the document restrict flags on the security definition and if true calls helper method to create a new permission
223         * 
224         * @param documentType workflow document type name for permission detail
225         * @param active boolean indicating whether the permissions should be set to active (true) or non-active (false)
226         * @param newSecurityDefinition SecurityDefintion which contains values for the permissions
227         */
228        protected void createNewDocumentTypePermissions(String documentType, boolean active, SecurityDefinition newSecurityDefinition) {
229            AttributeSet permissionDetails = populateDocumentTypePermissionDetails(documentType, newSecurityDefinition);
230    
231            if (newSecurityDefinition.isRestrictViewDocument()) {
232                savePermission(newSecurityDefinition, "", SpringContext.getBean(AccessSecurityService.class).getViewDocumentWithFieldValueTemplateId(), active, permissionDetails);
233            }
234    
235            if (newSecurityDefinition.isRestrictViewAccountingLine()) {
236                savePermission(newSecurityDefinition, "", SpringContext.getBean(AccessSecurityService.class).getViewAccountingLineWithFieldValueTemplateId(), active, permissionDetails);
237            }
238    
239            if (newSecurityDefinition.isRestrictViewNotesAndAttachments()) {
240                savePermission(newSecurityDefinition, "", SpringContext.getBean(AccessSecurityService.class).getViewNotesAttachmentsWithFieldValueTemplateId(), active, permissionDetails);
241            }
242    
243            if (newSecurityDefinition.isRestrictEditAccountingLine()) {
244                savePermission(newSecurityDefinition, "",SpringContext.getBean(AccessSecurityService.class).getEditAccountingLineWithFieldValueTemplateId(), active, permissionDetails);
245            }
246    
247            if (newSecurityDefinition.isRestrictEditDocument()) {
248                savePermission(newSecurityDefinition, "", SpringContext.getBean(AccessSecurityService.class).getEditDocumentWithFieldValueTemplateId(), active, permissionDetails);
249            }
250        }
251    
252        /**
253         * For each of the document templates ids calls helper method to create or update corresponding permission
254         * 
255         * @param documentType workflow document type name for permission detail
256         * @param active boolean indicating whether the permissions should be set to active (true) or non-active (false)
257         * @param oldSecurityDefinition SecurityDefiniton record before requested changes (old side of maintenance document)
258         * @param newSecurityDefinition SecurityDefinition record with requested changes (new side of maintenance document)
259         */
260        protected void createOrUpdateDocumentTypePermissions(String documentType, boolean active, SecurityDefinition oldSecurityDefinition, SecurityDefinition newSecurityDefinition) {
261            // view document
262            createOrUpdateDocumentTypePermission(documentType, active && newSecurityDefinition.isRestrictViewDocument(), oldSecurityDefinition, newSecurityDefinition, SpringContext.getBean(AccessSecurityService.class).getViewDocumentWithFieldValueTemplateId());
263    
264            // view accounting line
265            createOrUpdateDocumentTypePermission(documentType, active && newSecurityDefinition.isRestrictViewAccountingLine(), oldSecurityDefinition, newSecurityDefinition, SpringContext.getBean(AccessSecurityService.class).getViewAccountingLineWithFieldValueTemplateId());
266    
267            // view notes/attachments
268            createOrUpdateDocumentTypePermission(documentType, active && newSecurityDefinition.isRestrictViewNotesAndAttachments(), oldSecurityDefinition, newSecurityDefinition, SpringContext.getBean(AccessSecurityService.class).getViewNotesAttachmentsWithFieldValueTemplateId());
269    
270            // edit accounting line
271            createOrUpdateDocumentTypePermission(documentType, active && newSecurityDefinition.isRestrictEditAccountingLine(), oldSecurityDefinition, newSecurityDefinition, SpringContext.getBean(AccessSecurityService.class).getEditAccountingLineWithFieldValueTemplateId());
272    
273            // edit document
274            createOrUpdateDocumentTypePermission(documentType, active && newSecurityDefinition.isRestrictEditDocument(), oldSecurityDefinition, newSecurityDefinition, SpringContext.getBean(AccessSecurityService.class).getEditDocumentWithFieldValueTemplateId());
275        }
276    
277        /**
278         * First tries to find an existing permission for the document type, template, and definition. If found the permission will be updated with the new details and the active
279         * indicator will be updated based on the active parameter. If not found and active parameter is true, then a new permission is created for the given doc type, template, and
280         * definition.
281         * 
282         * @param documentType workflow document type name for permission detail
283         * @param active boolean indicating whether the permissions should be set to active (true) or non-active (false)
284         * @param oldSecurityDefinition SecurityDefiniton record before requested changes (old side of maintenance document)
285         * @param newSecurityDefinition SecurityDefinition record with requested changes (new side of maintenance document)
286         * @param templateId KIM template id for the permission record that is should be created or updated
287         */
288        protected void createOrUpdateDocumentTypePermission(String documentType, boolean active, SecurityDefinition oldSecurityDefinition, SecurityDefinition newSecurityDefinition, String templateId) {
289            AttributeSet permissionDetails = populateDocumentTypePermissionDetails(documentType, newSecurityDefinition);
290    
291            KimPermissionInfo oldPermission = findDocumentPermission(oldSecurityDefinition, templateId, documentType);
292            String permissionId = "";
293            if (oldPermission != null) {
294                permissionId = oldPermission.getPermissionId();
295            }
296    
297            savePermission(newSecurityDefinition, permissionId, templateId, active, permissionDetails);
298        }
299    
300        /**
301         * Builds an AttributeSet populated from the given method parameters. Details are set based on the KIM 'Security Document Permission' type.
302         * 
303         * @param documentType workflow document type name
304         * @param securityDefinition SecurityDefiniton record
305         * @return AttributeSet populated with document type name, property name, operator, and property value details
306         */
307        protected AttributeSet populateDocumentTypePermissionDetails(String documentType, SecurityDefinition securityDefinition) {
308            AttributeSet permissionDetails = new AttributeSet();
309            permissionDetails.put(SecKimAttributes.DOCUMENT_TYPE_NAME, documentType);
310            permissionDetails.put(SecKimAttributes.PROPERTY_NAME, securityDefinition.getSecurityAttribute().getName());
311    
312            return permissionDetails;
313        }
314    
315        /**
316         * Builds an AttributeSet populated from the given method parameters. Details are set based on the KIM 'Security Lookup Permission' type.
317         * 
318         * @param securityDefinition SecurityDefiniton record
319         * @return AttributeSet populated with property name, operator, and property value details
320         */
321        protected AttributeSet populateLookupPermissionDetails(SecurityDefinition securityDefinition) {
322            AttributeSet permissionDetails = new AttributeSet();
323            permissionDetails.put(SecKimAttributes.PROPERTY_NAME, securityDefinition.getSecurityAttribute().getName());
324    
325            return permissionDetails;
326        }
327    
328        /**
329         * Builds an AttributeSet populated from the given method parameters. Details are set based on the KIM 'Security Inquiry Permission' type.
330         * 
331         * @param namespaceCode KIM namespace code
332         * @param securityDefinition SecurityDefiniton record
333         * @return AttributeSet populated with namespace, property name, operator, and property value details
334         */
335        protected AttributeSet populateInquiryPermissionDetails(String namespaceCode, SecurityDefinition securityDefinition) {
336            AttributeSet permissionDetails = new AttributeSet();
337            permissionDetails.put(SecKimAttributes.NAMESPACE_CODE, namespaceCode);
338            permissionDetails.put(SecKimAttributes.PROPERTY_NAME, securityDefinition.getSecurityAttribute().getName());
339    
340            return permissionDetails;
341        }
342    
343        /**
344         * Calls helper method to find all permissions for the given template ID and security defintion name (permission name). Iterates through the results to find the permission with
345         * matching document type detail
346         * 
347         * @param securityDefinition SecurityDefiniton record for permission
348         * @param templateId KIM template ID for permission
349         * @param documentType KEW document type name for permission detail
350         * @return KimPermissionInfo provides information on the matching permission
351         */
352        protected KimPermissionInfo findDocumentPermission(SecurityDefinition securityDefinition, String templateId, String documentType) {
353            // get all the permissions for the definition record and template
354            List<KimPermissionInfo> permissions = findSecurityPermissionsByNameAndTemplate(securityDefinition.getName(), templateId);
355    
356            // iterate through permission list finding permissions that have the document type detail
357            KimPermissionInfo foundPermission = null;
358            for (KimPermissionInfo permissionInfo : permissions) {
359                String permissionDocType = permissionInfo.getDetails().get(SecKimAttributes.DOCUMENT_TYPE_NAME);
360                if (StringUtils.equalsIgnoreCase(documentType, permissionDocType)) {
361                    foundPermission = permissionInfo;
362                    break;
363                }
364            }
365    
366            return foundPermission;
367        }
368    
369        /**
370         * Calls permission service to find all permissions for the given name. Iterates through results and finds ones that match given template ID as well
371         * 
372         * @param permissionName name of permission to find
373         * @param templateId KIM template ID of permission to find
374         * @return List<KimPermissionInfo> List of matching permissions
375         * @see org.kuali.rice.kim.service.PermissionService#getPermissionsByName()
376         */
377        protected List<KimPermissionInfo> findSecurityPermissionsByNameAndTemplate(String permissionName, String templateId) {
378            PermissionService permissionService = SpringContext.getBean(PermissionService.class);
379    
380            // get all the permissions for the given name
381            List<KimPermissionInfo> permissions = permissionService.getPermissionsByNameIncludingInactive(SecConstants.ACCESS_SECURITY_NAMESPACE_CODE, permissionName);
382    
383            List<KimPermissionInfo> templatePermissions = new ArrayList<KimPermissionInfo>();
384            for (KimPermissionInfo permissionInfo : permissions) {
385                if (StringUtils.equals(templateId, permissionInfo.getTemplateId())) {
386                    templatePermissions.add(permissionInfo);
387                }
388            }
389    
390            return templatePermissions;
391        }
392    
393        /**
394         * Determines whether a given document type name is included in the document type list for the given security definition
395         * 
396         * @param documentType KEW document type name
397         * @param oldSecurityDefinition SecurityDefinition record
398         * @return boolean indicating whether the document type is associated with the given security definition
399         */
400        protected boolean isDocumentTypeInDefinition(String documentType, SecurityDefinition oldSecurityDefinition) {
401            for (SecurityDefinitionDocumentType definitionDocumentType : oldSecurityDefinition.getDefinitionDocumentTypes()) {
402                String oldDocumentType = definitionDocumentType.getFinancialSystemDocumentTypeCode();
403                if (StringUtils.equalsIgnoreCase(documentType, oldDocumentType)) {
404                    return true;
405                }
406            }
407    
408            return false;
409        }
410    
411        /**
412         * Calls PermissionUpdateService to save a permission.
413         * 
414         * @param securityDefinition SecurityDefinition record
415         * @param permissionId ID for the permission being saved, or empty for new permission
416         * @param permissionTemplateId KIM template ID for permission to save
417         * @param active boolean indicating whether the permission should be set to active (true) or non-active (false)
418         * @param permissionDetails AttributeSet representing the permission details
419         * @see org.kuali.rice.kim.service.PermissionUpdateService#savePermission()
420         */
421        protected void savePermission(SecurityDefinition securityDefinition, String permissionId, String permissionTemplateId, boolean active, AttributeSet permissionDetails) {
422            LOG.info(String.format("saving permission with id: %s, template ID: %s, name: %s, active: %s", permissionId, permissionTemplateId, securityDefinition.getName(), active));
423    
424            PermissionUpdateService permissionUpdateService = SpringContext.getBean(PermissionUpdateService.class);
425    
426            if (StringUtils.isBlank(permissionId)) {
427                permissionId = permissionUpdateService.getNextAvailablePermissionId();
428            }
429    
430            permissionUpdateService.savePermission(permissionId, permissionTemplateId, SecConstants.ACCESS_SECURITY_NAMESPACE_CODE, securityDefinition.getName(), securityDefinition.getDescription(), active, permissionDetails);
431        }
432        
433        /**
434         * Override to clear out KIM role id on copy
435         * 
436         * @see org.kuali.rice.kns.maintenance.KualiMaintainableImpl#processAfterCopy(org.kuali.rice.kns.document.MaintenanceDocument,
437         *      java.util.Map)
438         */
439        @Override
440        public void processAfterCopy(MaintenanceDocument document, Map<String, String[]> parameters) {
441            SecurityDefinition securityDefinition = (SecurityDefinition) document.getNewMaintainableObject().getBusinessObject();
442            securityDefinition.setRoleId("");
443            
444            super.processAfterCopy(document, parameters);
445        }
446    
447    }