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