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.validation.impl;
017    
018    import java.util.HashMap;
019    import java.util.Map;
020    
021    import org.apache.commons.lang.StringUtils;
022    import org.kuali.kfs.sec.SecConstants;
023    import org.kuali.kfs.sec.SecKeyConstants;
024    import org.kuali.kfs.sec.SecPropertyConstants;
025    import org.kuali.kfs.sec.businessobject.SecurityDefinition;
026    import org.kuali.kfs.sec.businessobject.SecurityModel;
027    import org.kuali.kfs.sec.businessobject.SecurityModelDefinition;
028    import org.kuali.kfs.sec.businessobject.SecurityModelMember;
029    import org.kuali.kfs.sys.KFSPropertyConstants;
030    import org.kuali.kfs.sys.context.SpringContext;
031    import org.kuali.rice.kim.bo.entity.dto.KimPrincipalInfo;
032    import org.kuali.rice.kim.bo.group.dto.GroupInfo;
033    import org.kuali.rice.kim.bo.role.dto.KimRoleInfo;
034    import org.kuali.rice.kim.service.GroupService;
035    import org.kuali.rice.kim.service.IdentityManagementService;
036    import org.kuali.rice.kim.service.RoleManagementService;
037    import org.kuali.rice.kim.util.KimConstants;
038    import org.kuali.rice.kns.bo.PersistableBusinessObject;
039    import org.kuali.rice.kns.document.MaintenanceDocument;
040    import org.kuali.rice.kns.maintenance.rules.MaintenanceDocumentRuleBase;
041    import org.kuali.rice.kns.service.BusinessObjectService;
042    import org.kuali.rice.kns.util.GlobalVariables;
043    import org.kuali.rice.kns.util.KNSConstants;
044    import org.kuali.rice.kns.util.ObjectUtils;
045    
046    
047    /**
048     * Implements business rules checks on the SecurityModel maintenance document
049     */
050    public class SecurityModelRule extends MaintenanceDocumentRuleBase {
051        protected static org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(SecurityModelRule.class);
052    
053        private SecurityModel oldSecurityModel;
054        private SecurityModel newSecurityModel;
055    
056        public SecurityModelRule() {
057            super();
058        }
059    
060        /**
061         * @see org.kuali.rice.kns.maintenance.rules.MaintenanceDocumentRuleBase#processCustomApproveDocumentBusinessRules(org.kuali.rice.kns.document.MaintenanceDocument)
062         */
063        @Override
064        protected boolean processCustomApproveDocumentBusinessRules(MaintenanceDocument document) {
065            boolean isValid = super.processCustomApproveDocumentBusinessRules(document);
066    
067            if (!isValid) {
068                return isValid;
069            }
070    
071            boolean isMaintenanceEdit = document.isEdit();
072    
073            isValid &= validateSecurityModel(isMaintenanceEdit);
074    
075            return isValid;
076        }
077    
078        /**
079         * @see org.kuali.rice.kns.maintenance.rules.MaintenanceDocumentRuleBase#processCustomRouteDocumentBusinessRules(org.kuali.rice.kns.document.MaintenanceDocument)
080         */
081        @Override
082        protected boolean processCustomRouteDocumentBusinessRules(MaintenanceDocument document) {
083            boolean isValid = super.processCustomRouteDocumentBusinessRules(document);
084    
085            if (!isValid) {
086                return isValid;
087            }
088    
089            boolean isMaintenanceEdit = document.isEdit();
090    
091            isValid &= validateSecurityModel(isMaintenanceEdit);
092    
093            return isValid;
094        }
095    
096        /**
097         * @see org.kuali.rice.kns.maintenance.rules.MaintenanceDocumentRuleBase#processCustomAddCollectionLineBusinessRules(org.kuali.rice.kns.document.MaintenanceDocument,
098         *      java.lang.String, org.kuali.rice.kns.bo.PersistableBusinessObject)
099         */
100        @Override
101        public boolean processCustomAddCollectionLineBusinessRules(MaintenanceDocument document, String collectionName, PersistableBusinessObject line) {
102            boolean isValid = super.processCustomAddCollectionLineBusinessRules(document, collectionName, line);
103    
104            if (!isValid) {
105                return isValid;
106            }
107    
108            if (SecPropertyConstants.MODEL_DEFINITIONS.equals(collectionName)) {
109                isValid &= validateModelDefinition((SecurityModelDefinition) line, "");
110            }
111    
112            if (SecPropertyConstants.MODEL_MEMBERS.equals(collectionName)) {
113                isValid &= validateModelMember((SecurityModelMember) line, "");
114            }
115    
116            return isValid;
117        }
118    
119        /**
120         * @see org.kuali.rice.kns.maintenance.rules.MaintenanceDocumentRuleBase#setupConvenienceObjects()
121         */
122        @Override
123        public void setupConvenienceObjects() {
124            oldSecurityModel = (SecurityModel) super.getOldBo();
125            newSecurityModel = (SecurityModel) super.getNewBo();
126        }
127    
128        /**
129         * Validates the new security model record
130         * 
131         * @param isMaintenanceEdit boolean indicating whether the maintenance action is an edit (true), or a new/copy (false)
132         * @return boolean true if validation was successful, false if there are errors
133         */
134        protected boolean validateSecurityModel(boolean isMaintenanceEdit) {
135            boolean isValid = true;
136    
137            if (!isMaintenanceEdit) {
138                boolean validModelName = verifyModelNameIsUnique(newSecurityModel, KNSConstants.MAINTENANCE_NEW_MAINTAINABLE);
139                if (!validModelName) {
140                    isValid = false;
141                }
142            }
143    
144            // check to make sure there is at least one model definition
145            if (newSecurityModel.getModelDefinitions() == null || newSecurityModel.getModelDefinitions().size() == 0) {
146                GlobalVariables.getMessageMap().putError(KNSConstants.GLOBAL_ERRORS, SecKeyConstants.ERROR_MODEL_DEFINITION_MISSING);            
147            }
148    
149            int index = 0;
150            for (SecurityModelDefinition modelDefinition : newSecurityModel.getModelDefinitions()) {
151                String errorKeyPrefix = KNSConstants.MAINTENANCE_NEW_MAINTAINABLE + SecPropertyConstants.MODEL_DEFINITIONS + "[" + index + "].";
152    
153                boolean modelDefinitionValid = validateModelDefinition(modelDefinition, errorKeyPrefix);
154                if (!modelDefinitionValid) {
155                    isValid = false;
156                }
157    
158                index++;
159            }
160    
161            index = 0;
162            for (SecurityModelMember modelMember : newSecurityModel.getModelMembers()) {
163                String errorKeyPrefix = KNSConstants.MAINTENANCE_NEW_MAINTAINABLE + SecPropertyConstants.MODEL_MEMBERS + "[" + index + "].";
164    
165                boolean modelMemberValid = validateModelMember(modelMember, errorKeyPrefix);
166                if (!modelMemberValid) {
167                    isValid = false;
168                }
169    
170                index++;
171            }
172    
173    
174            return isValid;
175        }
176    
177        /**
178         * For new or copy action verifies the name given for the model is not being used by another model or definition
179         * 
180         * @param securityModel SecurityModel with name to check
181         * @param errorKeyPrefix String errorPrefix to use if any errors are found
182         * @return boolean true if name exists, false if not
183         */
184        protected boolean verifyModelNameIsUnique(SecurityModel securityModel, String errorKeyPrefix) {
185            boolean isValid = true;
186    
187            Map<String, String> searchValues = new HashMap<String, String>();
188            searchValues.put(KFSPropertyConstants.NAME, securityModel.getName());
189    
190            int matchCount = SpringContext.getBean(BusinessObjectService.class).countMatching(SecurityModel.class, searchValues);
191            if (matchCount > 0) {
192                GlobalVariables.getMessageMap().putError(errorKeyPrefix + KFSPropertyConstants.NAME, SecKeyConstants.ERROR_MODEL_NAME_NON_UNIQUE, securityModel.getName());
193                isValid = false;
194            }
195            
196            matchCount = SpringContext.getBean(BusinessObjectService.class).countMatching(SecurityDefinition.class, searchValues);
197            if (matchCount > 0) {
198                GlobalVariables.getMessageMap().putError(errorKeyPrefix + KFSPropertyConstants.NAME, SecKeyConstants.ERROR_MODEL_NAME_NON_UNIQUE, securityModel.getName());
199                isValid = false;
200            }
201    
202            return isValid;
203        }
204    
205        /**
206         * Validates a definition assignment to the model
207         * 
208         * @param modelDefinition SecurityModelDefinition to validate
209         * @param errorKeyPrefix String errorPrefix to use if any errors are found
210         * @return boolean true if validation was successful, false if there are errors
211         */
212        protected boolean validateModelDefinition(SecurityModelDefinition modelDefinition, String errorKeyPrefix) {
213            boolean isValid = true;
214    
215            modelDefinition.refreshNonUpdateableReferences();
216            
217            if (ObjectUtils.isNull(modelDefinition.getSecurityDefinition())) {
218                return false;
219            }
220            
221            String attributeName = modelDefinition.getSecurityDefinition().getSecurityAttribute().getName();
222            String attributeValue = modelDefinition.getAttributeValue();
223    
224            // if value is blank (which is allowed) no need to validate
225            if (StringUtils.isBlank(attributeValue)) {
226                return true;
227            }
228            
229            // descend attributes do not allow multiple values or wildcards, and operator must be equal
230            if (SecConstants.SecurityAttributeNames.CHART_DESCEND_HIERARCHY.equals(attributeName) || SecConstants.SecurityAttributeNames.ORGANIZATION_DESCEND_HIERARCHY.equals(attributeName)) {
231                if (StringUtils.contains(attributeValue, SecConstants.SecurityValueSpecialCharacters.MULTI_VALUE_SEPERATION_CHARACTER)) {
232                    GlobalVariables.getMessageMap().putError(errorKeyPrefix + SecPropertyConstants.ATTRIBUTE_VALUE, SecKeyConstants.ERROR_MODEL_DEFINITION_MULTI_ATTR_VALUE, attributeName);
233                    isValid = false;
234                }
235    
236                if (StringUtils.contains(attributeValue, SecConstants.SecurityValueSpecialCharacters.WILDCARD_CHARACTER)) {
237                    GlobalVariables.getMessageMap().putError(errorKeyPrefix + SecPropertyConstants.ATTRIBUTE_VALUE, SecKeyConstants.ERROR_MODEL_DEFINITION_WILDCARD_ATTR_VALUE, attributeName);
238                    isValid = false;
239                }
240    
241                if (!SecConstants.SecurityDefinitionOperatorCodes.EQUAL.equals(modelDefinition.getOperatorCode())) {
242                    GlobalVariables.getMessageMap().putError(errorKeyPrefix + SecPropertyConstants.OPERATOR_CODE, SecKeyConstants.ERROR_MODEL_DEFINITION_OPERATOR_CODE_NOT_EQUAL, attributeName);
243                    isValid = false;
244                }
245            }
246    
247            // validate attribute value for existence
248            isValid = isValid && SecurityValidationUtil.validateAttributeValue(attributeName, attributeValue, errorKeyPrefix);
249    
250            return isValid;
251        }
252    
253        /**
254         * Validates a member assignment to the model
255         * 
256         * @param modelMember SecurityModelMember to validate
257         * @param errorKeyPrefix String errorPrefix to use if any errors are found
258         * @return boolean true if validation was successful, false if there are errors
259         */
260        protected boolean validateModelMember(SecurityModelMember modelMember, String errorKeyPrefix) {
261            boolean isValid = true;
262    
263            String memberId = modelMember.getMemberId();
264            String memberTypeCode = modelMember.getMemberTypeCode();
265            
266            if (StringUtils.isBlank(memberId) || StringUtils.isBlank(memberTypeCode)) {
267                return false;
268            }
269    
270            if (KimConstants.KimUIConstants.MEMBER_TYPE_PRINCIPAL_CODE.equals(memberTypeCode)) {
271                KimPrincipalInfo principalInfo = SpringContext.getBean(IdentityManagementService.class).getPrincipal(memberId);
272                if (principalInfo == null) {
273                    GlobalVariables.getMessageMap().putError(errorKeyPrefix + SecPropertyConstants.MEMBER_ID, SecKeyConstants.ERROR_MODEL_MEMBER_ID_NOT_VALID, memberId, memberTypeCode);
274                    isValid = false;
275                }
276            }
277            else if (KimConstants.KimUIConstants.MEMBER_TYPE_ROLE_CODE.equals(memberTypeCode)) {
278                KimRoleInfo roleInfo = SpringContext.getBean(RoleManagementService.class).getRole(memberId);
279                if (roleInfo == null) {
280                    GlobalVariables.getMessageMap().putError(errorKeyPrefix + SecPropertyConstants.MEMBER_ID, SecKeyConstants.ERROR_MODEL_MEMBER_ID_NOT_VALID, memberId, memberTypeCode);
281                    isValid = false;
282                }
283            }
284            else if (KimConstants.KimUIConstants.MEMBER_TYPE_GROUP_CODE.equals(memberTypeCode)) {
285                GroupInfo groupInfo = SpringContext.getBean(GroupService.class).getGroupInfo(memberId);
286                if (groupInfo == null) {
287                    GlobalVariables.getMessageMap().putError(errorKeyPrefix + SecPropertyConstants.MEMBER_ID, SecKeyConstants.ERROR_MODEL_MEMBER_ID_NOT_VALID, memberId, memberTypeCode);
288                    isValid = false;
289                }
290            }
291    
292            return isValid;
293        }
294    
295    
296    }