/* * The Kuali Financial System, a comprehensive financial management system for higher education. * * Copyright 2005-2014 The Kuali Foundation * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package org.kuali.kfs.sec.businessobject.lookup; import java.beans.PropertyDescriptor; import java.sql.Date; import java.util.Collection; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import org.apache.commons.beanutils.PropertyUtils; import org.apache.commons.lang.StringUtils; import org.kuali.kfs.integration.ld.SegmentedBusinessObject; import org.kuali.kfs.sec.SecKeyConstants; import org.kuali.kfs.sec.service.AccessSecurityService; import org.kuali.kfs.sec.util.SecUtil; import org.kuali.kfs.sys.KFSConstants; import org.kuali.rice.core.web.format.BooleanFormatter; import org.kuali.rice.core.web.format.CollectionFormatter; import org.kuali.rice.core.web.format.DateFormatter; import org.kuali.rice.core.web.format.Formatter; import org.kuali.rice.kim.api.identity.Person; import org.kuali.rice.kns.document.authorization.BusinessObjectRestrictions; import org.kuali.rice.kns.document.authorization.FieldRestriction; import org.kuali.rice.kns.lookup.HtmlData; import org.kuali.rice.kns.lookup.LookupableHelperService; import org.kuali.rice.kns.service.BusinessObjectAuthorizationService; import org.kuali.rice.kns.service.BusinessObjectDictionaryService; import org.kuali.rice.kns.service.BusinessObjectMetaDataService; import org.kuali.rice.kns.web.comparator.CellComparatorHelper; import org.kuali.rice.kns.web.struts.form.LookupForm; import org.kuali.rice.kns.web.ui.Column; import org.kuali.rice.kns.web.ui.Field; import org.kuali.rice.kns.web.ui.ResultRow; import org.kuali.rice.kns.web.ui.Row; import org.kuali.rice.krad.bo.BusinessObject; import org.kuali.rice.krad.bo.PersistableBusinessObject; import org.kuali.rice.krad.service.DataDictionaryService; import org.kuali.rice.krad.service.PersistenceStructureService; import org.kuali.rice.krad.util.GlobalVariables; import org.kuali.rice.krad.util.KRADConstants; import org.kuali.rice.krad.util.ObjectUtils; /** * Wraps balance inquiry lookupables so that access security can be applied to the results */ public class AccessSecurityBalanceLookupableHelperServiceImpl implements LookupableHelperService { protected static final String ACTION_URLS_EMPTY = " "; protected AccessSecurityService accessSecurityService; protected LookupableHelperService lookupableHelperService; protected BusinessObjectMetaDataService businessObjectMetaDataService; protected BusinessObjectAuthorizationService businessObjectAuthorizationService; protected PersistenceStructureService persistenceStructureService; protected boolean glInquiry; protected boolean laborInquiry; public AccessSecurityBalanceLookupableHelperServiceImpl() { glInquiry = false; laborInquiry = false; } @Override public boolean allowsMaintenanceNewOrCopyAction() { return lookupableHelperService.allowsMaintenanceNewOrCopyAction(); } @Override public boolean allowsNewOrCopyAction(String documentTypeName) { return lookupableHelperService.allowsNewOrCopyAction(documentTypeName); } @Override public void applyFieldAuthorizationsFromNestedLookups(Field field) { lookupableHelperService.applyFieldAuthorizationsFromNestedLookups(field); } @Override public boolean checkForAdditionalFields(Map fieldValues) { return lookupableHelperService.checkForAdditionalFields(fieldValues); } @Override public String getActionUrls(BusinessObject businessObject, List pkNames, BusinessObjectRestrictions businessObjectRestrictions) { return lookupableHelperService.getActionUrls(businessObject, pkNames, businessObjectRestrictions); } @Override public String getBackLocation() { return lookupableHelperService.getBackLocation(); } @Override public Class getBusinessObjectClass() { return lookupableHelperService.getBusinessObjectClass(); } @Override public BusinessObjectDictionaryService getBusinessObjectDictionaryService() { return lookupableHelperService.getBusinessObjectDictionaryService(); } @Override public List getColumns() { return lookupableHelperService.getColumns(); } @Override public List<HtmlData> getCustomActionUrls(BusinessObject businessObject, List pkNames) { return lookupableHelperService.getCustomActionUrls(businessObject, pkNames); } @Override public DataDictionaryService getDataDictionaryService() { return lookupableHelperService.getDataDictionaryService(); } @Override public List getDefaultSortColumns() { return lookupableHelperService.getDefaultSortColumns(); } @Override public String getDocFormKey() { return lookupableHelperService.getDocFormKey(); } @Override public String getDocNum() { return lookupableHelperService.getDocNum(); } @Override public Field getExtraField() { return lookupableHelperService.getExtraField(); } @Override public HtmlData getInquiryUrl(BusinessObject businessObject, String propertyName) { return lookupableHelperService.getInquiryUrl(businessObject, propertyName); } @Override public String getMaintenanceUrl(BusinessObject businessObject, HtmlData htmlData, List pkNames, BusinessObjectRestrictions businessObjectRestrictions) { return lookupableHelperService.getMaintenanceUrl(businessObject, htmlData, pkNames, businessObjectRestrictions); } @Override public Map getParameters() { return lookupableHelperService.getParameters(); } @Override public String getPrimaryKeyFieldLabels() { return lookupableHelperService.getPrimaryKeyFieldLabels(); } @Override public List<String> getReadOnlyFieldsList() { return lookupableHelperService.getReadOnlyFieldsList(); } @Override public List getReturnKeys() { return lookupableHelperService.getReturnKeys(); } @Override public String getReturnLocation() { return lookupableHelperService.getReturnLocation(); } @Override public HtmlData getReturnUrl(BusinessObject businessObject, LookupForm lookupForm, List returnKeys, BusinessObjectRestrictions businessObjectRestrictions) { return lookupableHelperService.getReturnUrl(businessObject, lookupForm, returnKeys, businessObjectRestrictions); } @Override public HtmlData getReturnUrl(BusinessObject businessObject, Map fieldConversions, String lookupImpl, List returnKeys, BusinessObjectRestrictions businessObjectRestrictions) { return lookupableHelperService.getReturnUrl(businessObject, fieldConversions, lookupImpl, returnKeys, businessObjectRestrictions); } @Override public List<Row> getRows() { return lookupableHelperService.getRows(); } /** * Gets search results and passes to access security service to apply access restrictions * * @see org.kuali.rice.kns.lookup.LookupableHelperService#getSearchResults(java.util.Map) */ @Override public List getSearchResults(Map<String, String> fieldValues) { List results = lookupableHelperService.getSearchResults(fieldValues); int resultSizeBeforeRestrictions = results.size(); if (glInquiry) { accessSecurityService.applySecurityRestrictionsForGLInquiry(results, GlobalVariables.getUserSession().getPerson()); } if (laborInquiry) { accessSecurityService.applySecurityRestrictionsForLaborInquiry(results, GlobalVariables.getUserSession().getPerson()); } accessSecurityService.compareListSizeAndAddMessageIfChanged(resultSizeBeforeRestrictions, results, SecKeyConstants.MESSAGE_BALANCE_INQUIRY_RESULTS_RESTRICTED); return results; } /** * Gets search results and passes to access security service to apply access restrictions * * @see org.kuali.rice.kns.lookup.LookupableHelperService#getSearchResultsUnbounded(java.util.Map) */ @Override public List getSearchResultsUnbounded(Map<String, String> fieldValues) { List results = lookupableHelperService.getSearchResultsUnbounded(fieldValues); int resultSizeBeforeRestrictions = results.size(); if (glInquiry) { accessSecurityService.applySecurityRestrictionsForGLInquiry(results, GlobalVariables.getUserSession().getPerson()); } if (laborInquiry) { accessSecurityService.applySecurityRestrictionsForLaborInquiry(results, GlobalVariables.getUserSession().getPerson()); } accessSecurityService.compareListSizeAndAddMessageIfChanged(resultSizeBeforeRestrictions, results, SecKeyConstants.MESSAGE_BALANCE_INQUIRY_RESULTS_RESTRICTED); return results; } @Override public String getSupplementalMenuBar() { return lookupableHelperService.getSupplementalMenuBar(); } @Override public String getTitle() { return lookupableHelperService.getTitle(); } @Override public boolean isResultReturnable(BusinessObject object) { return lookupableHelperService.isResultReturnable(object); } @Override public boolean isSearchUsingOnlyPrimaryKeyValues() { return lookupableHelperService.isSearchUsingOnlyPrimaryKeyValues(); } @Override public void performClear(LookupForm lookupForm) { lookupableHelperService.performClear(lookupForm); } @Override public boolean performCustomAction(boolean ignoreErrors) { return lookupableHelperService.performCustomAction(ignoreErrors); } /** * Need to duplicate the logic of performLookup so that getSearchResults will be called on this class and not the nested lookup helper service * * @see org.kuali.rice.kns.lookup.LookupableHelperService#performLookup(org.kuali.rice.kns.web.struts.form.LookupForm, java.util.Collection, boolean) */ @Override public Collection performLookup(LookupForm lookupForm, Collection resultTable, boolean bounded) { Map lookupFormFields = lookupForm.getFieldsForLookup(); setBackLocation((String) lookupForm.getFieldsForLookup().get(KRADConstants.BACK_LOCATION)); setDocFormKey((String) lookupForm.getFieldsForLookup().get(KRADConstants.DOC_FORM_KEY)); Collection displayList; preprocessDateFields(lookupFormFields); Map fieldsForLookup = new HashMap(lookupForm.getFieldsForLookup()); // call search method to get results if (bounded) { displayList = getSearchResults(lookupForm.getFieldsForLookup()); } else { displayList = getSearchResultsUnbounded(lookupForm.getFieldsForLookup()); } HashMap<String, Class> propertyTypes = new HashMap<String, Class>(); boolean hasReturnableRow = false; List returnKeys = getReturnKeys(); List pkNames = businessObjectMetaDataService.listPrimaryKeyFieldNames(getBusinessObjectClass()); Person user = GlobalVariables.getUserSession().getPerson(); // iterate through result list and wrap rows with return url and action urls for (Iterator iter = displayList.iterator(); iter.hasNext();) { BusinessObject element = (BusinessObject) iter.next(); if (element instanceof PersistableBusinessObject) { lookupForm.setLookupObjectId(((PersistableBusinessObject) element).getObjectId()); } BusinessObjectRestrictions businessObjectRestrictions = businessObjectAuthorizationService.getLookupResultRestrictions(element, user); HtmlData returnUrl = getReturnUrl(element, lookupForm, returnKeys, businessObjectRestrictions); String actionUrls = getActionUrls(element, pkNames, businessObjectRestrictions); // Fix for JIRA - KFSMI-2417 if ("".equals(actionUrls)) { actionUrls = ACTION_URLS_EMPTY; } List<Column> columns = getColumns(); for (Iterator iterator = columns.iterator(); iterator.hasNext();) { Column col = (Column) iterator.next(); Formatter formatter = col.getFormatter(); // pick off result column from result list, do formatting String propValue = KRADConstants.EMPTY_STRING; Object prop = ObjectUtils.getPropertyValue(element, col.getPropertyName()); // set comparator and formatter based on property type Class propClass = propertyTypes.get(col.getPropertyName()); if (propClass == null) { try { propClass = ObjectUtils.getPropertyType(element, col.getPropertyName(), persistenceStructureService); propertyTypes.put(col.getPropertyName(), propClass); } catch (Exception e) { throw new RuntimeException("Cannot access PropertyType for property " + "'" + col.getPropertyName() + "' " + " on an instance of '" + element.getClass().getName() + "'.", e); } } // formatters if (prop != null) { // for Booleans, always use BooleanFormatter if (prop instanceof Boolean) { formatter = new BooleanFormatter(); } // for Dates, always use DateFormatter if (prop instanceof Date) { formatter = new DateFormatter(); } // for collection, use the list formatter if a formatter hasn't been defined yet if (prop instanceof Collection && formatter == null) { formatter = new CollectionFormatter(); } if (formatter != null) { propValue = (String) formatter.format(prop); } else { propValue = prop.toString(); } } // comparator col.setComparator(CellComparatorHelper.getAppropriateComparatorForPropertyClass(propClass)); col.setValueComparator(CellComparatorHelper.getAppropriateValueComparatorForPropertyClass(propClass)); propValue = maskValueIfNecessary(element.getClass(), col.getPropertyName(), propValue, businessObjectRestrictions); col.setPropertyValue(propValue); if (StringUtils.isNotBlank(propValue)) { col.setColumnAnchor(getInquiryUrl(element, col.getPropertyName())); } } ResultRow row = new ResultRow(columns, returnUrl.constructCompleteHtmlTag(), actionUrls); row.setRowId(returnUrl.getName()); row.setReturnUrlHtmlData(returnUrl); // because of concerns of the BO being cached in session on the ResultRow, // let's only attach it when needed (currently in the case of export) if (getBusinessObjectDictionaryService().isExportable(getBusinessObjectClass())) { row.setBusinessObject(element); } if(element instanceof SegmentedBusinessObject) { for (String propertyName : ((SegmentedBusinessObject) element).getSegmentedPropertyNames()) { columns.add(setupResultsColumn(element, propertyName, businessObjectRestrictions)); } } if (element instanceof PersistableBusinessObject) { row.setObjectId((((PersistableBusinessObject) element).getObjectId())); } boolean rowReturnable = isResultReturnable(element); row.setRowReturnable(rowReturnable); if (rowReturnable) { hasReturnableRow = true; } resultTable.add(row); } lookupForm.setHasReturnableRow(hasReturnableRow); return displayList; } /** * @param element * @param attributeName * @return Column */ protected Column setupResultsColumn(BusinessObject element, String attributeName, BusinessObjectRestrictions businessObjectRestrictions) { Column col = new Column(); col.setPropertyName(attributeName); String columnTitle = getDataDictionaryService().getAttributeLabel(getBusinessObjectClass(), attributeName); if (StringUtils.isBlank(columnTitle)) { columnTitle = getDataDictionaryService().getCollectionLabel(getBusinessObjectClass(), attributeName); } col.setColumnTitle(columnTitle); col.setMaxLength(getDataDictionaryService().getAttributeMaxLength(getBusinessObjectClass(), attributeName)); Class formatterClass = getDataDictionaryService().getAttributeFormatter(getBusinessObjectClass(), attributeName); Formatter formatter = null; if (formatterClass != null) { try { formatter = (Formatter) formatterClass.newInstance(); col.setFormatter(formatter); } catch (InstantiationException e) { throw new RuntimeException("Unable to get new instance of formatter class: " + formatterClass.getName()); } catch (IllegalAccessException e) { throw new RuntimeException("Unable to get new instance of formatter class: " + formatterClass.getName()); } } // pick off result column from result list, do formatting String propValue = KFSConstants.EMPTY_STRING; Object prop = ObjectUtils.getPropertyValue(element, attributeName); // set comparator and formatter based on property type Class propClass = null; try { PropertyDescriptor propDescriptor = PropertyUtils.getPropertyDescriptor(element, col.getPropertyName()); if (propDescriptor != null) { propClass = propDescriptor.getPropertyType(); } } catch (Exception e) { throw new RuntimeException("Cannot access PropertyType for property " + "'" + col.getPropertyName() + "' " + " on an instance of '" + element.getClass().getName() + "'.", e); } // formatters if (prop != null) { // for Booleans, always use BooleanFormatter if (prop instanceof Boolean) { formatter = new BooleanFormatter(); } if (formatter != null) { propValue = (String) formatter.format(prop); } else { propValue = prop.toString(); } } // comparator col.setComparator(CellComparatorHelper.getAppropriateComparatorForPropertyClass(propClass)); col.setValueComparator(CellComparatorHelper.getAppropriateValueComparatorForPropertyClass(propClass)); propValue = maskValueIfNecessary(element.getClass(), col.getPropertyName(), propValue, businessObjectRestrictions); col.setPropertyValue(propValue); if (StringUtils.isNotBlank(propValue)) { col.setColumnAnchor(getInquiryUrl(element, col.getPropertyName())); } return col; } /** * changes from/to dates into the range operators the lookupable dao expects ("..",">" etc) this method modifies the passed in map and returns a list containing only the * modified fields * * @param lookupFormFields */ protected Map<String, String> preprocessDateFields(Map lookupFormFields) { Map<String, String> fieldsToUpdate = new HashMap<String, String>(); Set<String> fieldsForLookup = lookupFormFields.keySet(); for (String propName : fieldsForLookup) { if (propName.startsWith(KRADConstants.LOOKUP_RANGE_LOWER_BOUND_PROPERTY_PREFIX)) { String fromDateValue = (String) lookupFormFields.get(propName); String dateFieldName = StringUtils.remove(propName, KRADConstants.LOOKUP_RANGE_LOWER_BOUND_PROPERTY_PREFIX); String dateValue = (String) lookupFormFields.get(dateFieldName); String newPropValue = dateValue;// maybe clean above with ObjectUtils.clean(propertyValue) if (StringUtils.isNotEmpty(fromDateValue) && StringUtils.isNotEmpty(dateValue)) { newPropValue = fromDateValue + ".." + dateValue; } else if (StringUtils.isNotEmpty(fromDateValue) && StringUtils.isEmpty(dateValue)) { newPropValue = ">=" + fromDateValue; } else if (StringUtils.isNotEmpty(dateValue) && StringUtils.isEmpty(fromDateValue)) { newPropValue = "<=" + dateValue; } // could optionally continue on else here fieldsToUpdate.put(dateFieldName, newPropValue); } } // update lookup values from found date values to update Set<String> keysToUpdate = fieldsToUpdate.keySet(); for (String updateKey : keysToUpdate) { lookupFormFields.put(updateKey, fieldsToUpdate.get(updateKey)); } return fieldsToUpdate; } protected String maskValueIfNecessary(Class businessObjectClass, String propertyName, String propertyValue, BusinessObjectRestrictions businessObjectRestrictions) { String maskedPropertyValue = propertyValue; if (businessObjectRestrictions != null) { FieldRestriction fieldRestriction = businessObjectRestrictions.getFieldRestriction(propertyName); if (fieldRestriction != null && (fieldRestriction.isMasked() || fieldRestriction.isPartiallyMasked())) { maskedPropertyValue = fieldRestriction.getMaskFormatter().maskValue(propertyValue); } } return maskedPropertyValue; } @Override public void setBackLocation(String backLocation) { lookupableHelperService.setBackLocation(backLocation); } @Override public void setBusinessObjectClass(Class businessObjectClass) { lookupableHelperService.setBusinessObjectClass(businessObjectClass); } @Override public void setDocFormKey(String docFormKey) { lookupableHelperService.setDocFormKey(docFormKey); } @Override public void setDocNum(String docNum) { lookupableHelperService.setDocNum(docNum); } @Override public void setFieldConversions(Map fieldConversions) { lookupableHelperService.setFieldConversions(fieldConversions); } @Override public void setParameters(Map parameters) { lookupableHelperService.setParameters(parameters); } @Override public void setReadOnlyFieldsList(List<String> readOnlyFieldsList) { lookupableHelperService.setReadOnlyFieldsList(readOnlyFieldsList); } @Override public boolean shouldDisplayHeaderNonMaintActions() { return lookupableHelperService.shouldDisplayHeaderNonMaintActions(); } @Override public boolean shouldDisplayLookupCriteria() { return lookupableHelperService.shouldDisplayLookupCriteria(); } @Override public void validateSearchParameters(Map fieldValues) { lookupableHelperService.validateSearchParameters(fieldValues); } /** * Sets the accessSecurityService attribute value. * * @param accessSecurityService The accessSecurityService to set. */ public void setAccessSecurityService(AccessSecurityService accessSecurityService) { this.accessSecurityService = accessSecurityService; } /** * Sets the lookupableHelperService attribute value. * * @param lookupableHelperService The lookupableHelperService to set. */ public void setLookupableHelperService(LookupableHelperService lookupableHelperService) { this.lookupableHelperService = lookupableHelperService; } /** * Sets the businessObjectMetaDataService attribute value. * * @param businessObjectMetaDataService The businessObjectMetaDataService to set. */ public void setBusinessObjectMetaDataService(BusinessObjectMetaDataService businessObjectMetaDataService) { this.businessObjectMetaDataService = businessObjectMetaDataService; } /** * Sets the businessObjectAuthorizationService attribute value. * * @param businessObjectAuthorizationService The businessObjectAuthorizationService to set. */ public void setBusinessObjectAuthorizationService(BusinessObjectAuthorizationService businessObjectAuthorizationService) { this.businessObjectAuthorizationService = businessObjectAuthorizationService; } /** * Sets the persistenceStructureService attribute value. * * @param persistenceStructureService The persistenceStructureService to set. */ public void setPersistenceStructureService(PersistenceStructureService persistenceStructureService) { this.persistenceStructureService = persistenceStructureService; } /** * Sets the glInquiry attribute value. * * @param glInquiry The glInquiry to set. */ public void setGlInquiry(boolean glInquiry) { this.glInquiry = glInquiry; } /** * Sets the laborInquiry attribute value. * * @param laborInquiry The laborInquiry to set. */ public void setLaborInquiry(boolean laborInquiry) { this.laborInquiry = laborInquiry; } //@Override @Override public void applyConditionalLogicForFieldDisplay() { // TODO Auto-generated method stub } }