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 }