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.Collection;
020    import java.util.HashMap;
021    import java.util.HashSet;
022    import java.util.Iterator;
023    import java.util.List;
024    import java.util.Map;
025    
026    import org.apache.commons.lang.StringUtils;
027    import org.kuali.kfs.sec.SecConstants;
028    import org.kuali.kfs.sec.businessobject.SecurityDefinition;
029    import org.kuali.kfs.sec.businessobject.SecurityModel;
030    import org.kuali.kfs.sec.businessobject.SecurityModelDefinition;
031    import org.kuali.kfs.sec.businessobject.SecurityModelMember;
032    import org.kuali.kfs.sec.businessobject.SecurityPrincipal;
033    import org.kuali.kfs.sec.identity.SecKimAttributes;
034    import org.kuali.kfs.sec.util.KimUtil;
035    import org.kuali.kfs.sys.KFSPropertyConstants;
036    import org.kuali.kfs.sys.context.SpringContext;
037    import org.kuali.kfs.sys.document.FinancialSystemMaintainable;
038    import org.kuali.rice.kew.exception.WorkflowException;
039    import org.kuali.rice.kim.bo.role.dto.KimRoleInfo;
040    import org.kuali.rice.kim.bo.role.dto.RoleMembershipInfo;
041    import org.kuali.rice.kim.bo.types.dto.AttributeSet;
042    import org.kuali.rice.kim.service.GroupService;
043    import org.kuali.rice.kim.service.IdentityManagementService;
044    import org.kuali.rice.kim.service.RoleManagementService;
045    import org.kuali.rice.kim.util.KIMPropertyConstants;
046    import org.kuali.rice.kim.util.KimConstants;
047    import org.kuali.rice.kns.bo.DocumentHeader;
048    import org.kuali.rice.kns.bo.PersistableBusinessObject;
049    import org.kuali.rice.kns.document.MaintenanceDocument;
050    import org.kuali.rice.kns.service.BusinessObjectService;
051    import org.kuali.rice.kns.service.DocumentService;
052    import org.kuali.rice.kns.util.KNSConstants;
053    
054    
055    /**
056     * Maintainable implementation for the Security Model maintenance document. Hooks into Post processing to create a KIM role from
057     * Model and assigns users/permissions to role based on Model
058     */
059    public class SecurityModelMaintainableImpl extends FinancialSystemMaintainable {
060        private static final org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(SecurityModelMaintainableImpl.class);
061    
062        /**
063         * @see org.kuali.rice.kns.maintenance.KualiMaintainableImpl#refresh(java.lang.String, java.util.Map,
064         *      org.kuali.rice.kns.document.MaintenanceDocument)
065         */
066        @Override
067        public void refresh(String refreshCaller, Map fieldValues, MaintenanceDocument document) {
068            super.refresh(refreshCaller, fieldValues, document);
069    
070            this.getBusinessObject().refreshNonUpdateableReferences();
071            for (Iterator iterator = newCollectionLines.values().iterator(); iterator.hasNext();) {
072                PersistableBusinessObject businessObject = (PersistableBusinessObject) iterator.next();
073                businessObject.refreshNonUpdateableReferences();
074            }
075        }
076    
077        /**
078         * @see org.kuali.rice.kns.maintenance.KualiMaintainableImpl#doRouteStatusChange(org.kuali.rice.kns.bo.DocumentHeader)
079         */
080        @Override
081        public void doRouteStatusChange(DocumentHeader documentHeader) {
082            super.doRouteStatusChange(documentHeader);
083    
084            if (documentHeader.getWorkflowDocument().stateIsProcessed()) {
085                DocumentService documentService = SpringContext.getBean(DocumentService.class);
086                try {
087                    MaintenanceDocument document = (MaintenanceDocument) documentService.getByDocumentHeaderId(documentHeader.getDocumentNumber());
088                    SecurityModel oldSecurityModel = (SecurityModel) document.getOldMaintainableObject().getBusinessObject();
089                    SecurityModel newSecurityModel = (SecurityModel) document.getNewMaintainableObject().getBusinessObject();
090    
091                    boolean newMaintenanceAction = getMaintenanceAction().equalsIgnoreCase(KNSConstants.MAINTENANCE_NEW_ACTION) || getMaintenanceAction().equalsIgnoreCase(KNSConstants.MAINTENANCE_COPY_ACTION);
092    
093                    createOrUpdateModelRole(oldSecurityModel, newSecurityModel);
094                    assignOrUpdateModelMembershipToDefinitionRoles(oldSecurityModel, newSecurityModel, newMaintenanceAction);
095                    assignOrUpdateModelMembers(newSecurityModel);
096    
097                    if (!newSecurityModel.isActive()) {
098                        inactivateModelRole(newSecurityModel);
099                    }
100    
101                    SpringContext.getBean(IdentityManagementService.class).flushAllCaches();
102                }
103                catch (WorkflowException e) {
104                    LOG.error("caught exception while handling handleRouteStatusChange -> documentService.getByDocumentHeaderId(" + documentHeader.getDocumentNumber() + "). ", e);
105                    throw new RuntimeException("caught exception while handling handleRouteStatusChange -> documentService.getByDocumentHeaderId(" + documentHeader.getDocumentNumber() + "). ", e);
106                }
107            }
108        }
109    
110        /**
111         * Creates a new role for the model (if the model is new), otherwise updates the role
112         * 
113         * @param oldSecurityModel SecurityModel record before updates
114         * @param newSecurityModel SecurityModel after updates
115         */
116        protected void createOrUpdateModelRole(SecurityModel oldSecurityModel, SecurityModel newSecurityModel) {
117            RoleManagementService roleService = SpringContext.getBean(RoleManagementService.class);
118    
119            String roleName = newSecurityModel.getName();
120    
121            String roleId = newSecurityModel.getRoleId();
122            if (StringUtils.isBlank(roleId)) {
123                // get new id for role
124                roleId = roleService.getNextAvailableRoleId();
125                newSecurityModel.setRoleId(roleId);
126            }
127    
128            // always set the role as active so we can add members and definitions, after processing the indicator will be updated to
129            // the appropriate value
130            roleService.saveRole(roleId, roleName, newSecurityModel.getDescription(), true, SecConstants.SecurityTypes.DEFAULT_ROLE_TYPE, SecConstants.ACCESS_SECURITY_NAMESPACE_CODE);
131            roleService.flushRoleCaches();
132        }
133    
134        /**
135         * Saves the given security model setting the active indicator to false
136         * 
137         * @param newSecurityModel SecurityModel to inactivate
138         */
139        protected void inactivateModelRole(SecurityModel newSecurityModel) {
140            RoleManagementService roleService = SpringContext.getBean(RoleManagementService.class);
141    
142            KimRoleInfo roleInfo = roleService.getRole(newSecurityModel.getRoleId());
143    
144            roleService.saveRole(roleInfo.getRoleId(), roleInfo.getRoleName(), newSecurityModel.getDescription(), false, SecConstants.SecurityTypes.DEFAULT_ROLE_TYPE, SecConstants.ACCESS_SECURITY_NAMESPACE_CODE);
145        }
146    
147        /**
148         * Iterates through the model definition list and assigns the model role to the definition role if necessary or updates the
149         * current member assignment
150         * 
151         * @param oldSecurityModel SecurityModel record before updates
152         * @param newSecurityModel SecurityModel whose membership should be updated
153         * @param newMaintenanceAction boolean indicating whether this is a new record (old side will not contain data)
154         */
155        protected void assignOrUpdateModelMembershipToDefinitionRoles(SecurityModel oldSecurityModel, SecurityModel newSecurityModel, boolean newMaintenanceAction) {
156            RoleManagementService roleService = SpringContext.getBean(RoleManagementService.class);
157    
158            KimRoleInfo modelRoleInfo = roleService.getRole(newSecurityModel.getRoleId());
159    
160            for (SecurityModelDefinition securityModelDefinition : newSecurityModel.getModelDefinitions()) {
161                SecurityDefinition securityDefinition = securityModelDefinition.getSecurityDefinition();
162    
163                KimRoleInfo definitionRoleInfo = roleService.getRole(securityDefinition.getRoleId());
164    
165                RoleMembershipInfo modelMembershipInfo = null;
166                if (!newMaintenanceAction) {
167                    SecurityModelDefinition oldSecurityModelDefinition = null;
168                    for (SecurityModelDefinition modelDefinition : oldSecurityModel.getModelDefinitions()) {
169                        if (modelDefinition.getModelDefinitionId() != null && securityModelDefinition.getModelDefinitionId() != null && modelDefinition.getModelDefinitionId().equals(securityModelDefinition.getModelDefinitionId())) {
170                            oldSecurityModelDefinition = modelDefinition;
171                        }
172                    }
173    
174                    if (oldSecurityModelDefinition != null) {
175                        AttributeSet membershipQualifications = new AttributeSet();
176                        membershipQualifications.put(SecKimAttributes.CONSTRAINT_CODE, oldSecurityModelDefinition.getConstraintCode());
177                        membershipQualifications.put(SecKimAttributes.OPERATOR, oldSecurityModelDefinition.getOperatorCode());
178                        membershipQualifications.put(SecKimAttributes.PROPERTY_VALUE, oldSecurityModelDefinition.getAttributeValue());
179                        membershipQualifications.put(SecKimAttributes.OVERRIDE_DENY, Boolean.toString(oldSecurityModelDefinition.isOverrideDeny()));
180    
181                        if (modelRoleInfo == null || definitionRoleInfo == null) {
182                            // this should throw an elegant error if either are null
183                            String error = "Apparent data problem with access security. model or definition is null. this should not happen";
184                            LOG.error(error);
185                            throw new RuntimeException(error);
186                        } else {
187                            modelMembershipInfo = KimUtil.getRoleMembershipInfoForMemberType(definitionRoleInfo.getRoleId(), modelRoleInfo.getRoleId(), KimConstants.KimUIConstants.MEMBER_TYPE_ROLE_CODE, membershipQualifications);
188                        }
189                    }
190                }
191    
192                // only create membership if model is active and the model definition record is active
193                boolean membershipActive = newSecurityModel.isActive() && securityModelDefinition.isActive();
194    
195                // if membership already exists, need to remove if the model definition record is now inactive or the qualifications
196                // need updated
197                String modelMembershipId = "";
198                if (modelMembershipInfo != null) {
199                    modelMembershipId = modelMembershipInfo.getRoleMemberId();
200                    if (!membershipActive) {
201                        roleService.removeRoleFromRole(modelMembershipInfo.getMemberId(), definitionRoleInfo.getNamespaceCode(), definitionRoleInfo.getRoleName(), modelMembershipInfo.getQualifier());
202                    }
203                }
204    
205                // create of update role if membership should be active
206                if (membershipActive) {
207                    AttributeSet membershipQualifications = new AttributeSet();
208                    membershipQualifications.put(SecKimAttributes.CONSTRAINT_CODE, securityModelDefinition.getConstraintCode());
209                    membershipQualifications.put(SecKimAttributes.OPERATOR, securityModelDefinition.getOperatorCode());
210                    membershipQualifications.put(SecKimAttributes.PROPERTY_VALUE, securityModelDefinition.getAttributeValue());
211                    membershipQualifications.put(SecKimAttributes.OVERRIDE_DENY, Boolean.toString(securityModelDefinition.isOverrideDeny()));
212    
213                    if (modelRoleInfo == null || definitionRoleInfo == null) {
214                        // this should throw an elegant error if either are null
215                        String error = "Apparent data problem with access security. model or definition is null. this should not happen";
216                        LOG.error(error);
217                        throw new RuntimeException(error);
218                    } else {
219                        roleService.saveRoleMemberForRole(modelMembershipId, modelRoleInfo.getRoleId(), KimConstants.KimUIConstants.MEMBER_TYPE_ROLE_CODE, definitionRoleInfo.getRoleId(), membershipQualifications, null, null);
220                    }
221                }
222            }
223        }
224    
225        /**
226         * Iterates through the model member list and assign members to the model role or updates the membership
227         * 
228         * @param securityModel SecurityModel whose member list should be updated
229         */
230        protected void assignOrUpdateModelMembers(SecurityModel securityModel) {
231            RoleManagementService roleService = SpringContext.getBean(RoleManagementService.class);
232    
233            KimRoleInfo modelRoleInfo = roleService.getRole(securityModel.getRoleId());
234    
235            if (modelRoleInfo == null) {
236                // this should throw an elegant error if either are null
237                String error = "Apparent data problem with access security. model is null. this should not happen";
238                LOG.error(error);
239                throw new RuntimeException(error);
240            } else {
241    
242                for (SecurityModelMember modelMember : securityModel.getModelMembers()) {
243                    RoleMembershipInfo membershipInfo = KimUtil.getRoleMembershipInfoForMemberType(modelRoleInfo.getRoleId(), modelMember.getMemberId(), modelMember.getMemberTypeCode(), null);
244        
245                    String membershipId = "";
246                    if (membershipInfo != null) {
247                        membershipId = membershipInfo.getRoleMemberId();
248                    }
249        
250                    java.sql.Date fromDate = null;
251                    java.sql.Date toDate = null;
252                    if ( modelMember.getActiveFromDate() != null ) {
253                        fromDate = new java.sql.Date( modelMember.getActiveFromDate().getTime() ); 
254                    }
255                    if ( modelMember.getActiveToDate() != null ) {
256                        toDate = new java.sql.Date( modelMember.getActiveToDate().getTime() ); 
257                    }
258                    roleService.saveRoleMemberForRole(membershipId, modelMember.getMemberId(), modelMember.getMemberTypeCode(), modelRoleInfo.getRoleId(), new AttributeSet(), fromDate, toDate);
259        
260                    createPrincipalSecurityRecords(modelMember.getMemberId(), modelMember.getMemberTypeCode());
261                }
262            }
263        }
264    
265        /**
266         * Creates security principal records for model members (if necessary) so that they will appear on security principal lookup for
267         * editing
268         * 
269         * @param memberId String member id of model role
270         * @param memberTypeCode String member type code for member
271         */
272        protected void createPrincipalSecurityRecords(String memberId, String memberTypeCode) {
273            Collection<String> principalIds = new HashSet<String>();
274    
275            if (KimConstants.KimUIConstants.MEMBER_TYPE_PRINCIPAL_CODE.equals(memberTypeCode)) {
276                principalIds.add(memberId);
277            }
278            else if (KimConstants.KimUIConstants.MEMBER_TYPE_ROLE_CODE.equals(memberTypeCode)) {
279                KimRoleInfo roleInfo = SpringContext.getBean(RoleManagementService.class).getRole(memberId);
280                Collection<String> rolePrincipalIds = SpringContext.getBean(RoleManagementService.class).getRoleMemberPrincipalIds(roleInfo.getNamespaceCode(), roleInfo.getRoleName(), new AttributeSet());
281                principalIds.addAll(rolePrincipalIds);
282            }
283            else if (KimConstants.KimUIConstants.MEMBER_TYPE_GROUP_CODE.equals(memberTypeCode)) {
284                List<String> groupPrincipalIds = SpringContext.getBean(GroupService.class).getMemberPrincipalIds(memberId);
285                principalIds.addAll(groupPrincipalIds);
286            }
287    
288            BusinessObjectService businessObjectService = SpringContext.getBean(BusinessObjectService.class);
289            for (String principalId : principalIds) {
290                SecurityPrincipal securityPrincipal = businessObjectService.findBySinglePrimaryKey(SecurityPrincipal.class, principalId);
291                if (securityPrincipal == null) {
292                    SecurityPrincipal newSecurityPrincipal = new SecurityPrincipal();
293                    newSecurityPrincipal.setPrincipalId(principalId);
294    
295                    businessObjectService.save(newSecurityPrincipal);
296                }
297            }
298        }
299    
300        /**
301         * Determines whether the given definition is part of the SecurityModel associated definitions
302         * 
303         * @param definitionName name of definition to look for
304         * @param securityModel SecurityModel to check
305         * @return boolean true if the definition is in the security model, false if not
306         */
307        protected boolean isDefinitionInModel(String definitionName, SecurityModel securityModel) {
308            for (SecurityModelDefinition securityModelDefinition : securityModel.getModelDefinitions()) {
309                if (StringUtils.equalsIgnoreCase(definitionName, securityModelDefinition.getSecurityDefinition().getName())) {
310                    return true;
311                }
312            }
313    
314            return false;
315        }
316    
317        /**
318         * Override to clear out KIM role id on copy
319         * 
320         * @see org.kuali.rice.kns.maintenance.KualiMaintainableImpl#processAfterCopy(org.kuali.rice.kns.document.MaintenanceDocument,
321         *      java.util.Map)
322         */
323        @Override
324        public void processAfterCopy(MaintenanceDocument document, Map<String, String[]> parameters) {
325            SecurityModel securityModel = (SecurityModel) document.getNewMaintainableObject().getBusinessObject();
326            securityModel.setRoleId("");
327            
328            super.processAfterCopy(document, parameters);
329        }
330    
331    
332    }