/* * 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.sys.document.validation.impl; import java.util.Iterator; import java.util.Map; import java.util.Set; import org.apache.commons.lang.StringUtils; import org.kuali.kfs.sys.KFSConstants; import org.kuali.kfs.sys.KFSKeyConstants; import org.kuali.kfs.sys.KFSPropertyConstants; import org.kuali.kfs.sys.businessobject.AccountingLine; import org.kuali.kfs.sys.businessobject.FinancialSystemDocumentHeader; import org.kuali.kfs.sys.businessobject.SourceAccountingLine; import org.kuali.kfs.sys.businessobject.TargetAccountingLine; import org.kuali.kfs.sys.document.AccountingDocument; import org.kuali.kfs.sys.document.Correctable; import org.kuali.kfs.sys.document.authorization.AccountingLineAuthorizer; import org.kuali.kfs.sys.document.authorization.AccountingLineAuthorizerBase; import org.kuali.kfs.sys.document.datadictionary.AccountingLineGroupDefinition; import org.kuali.kfs.sys.document.datadictionary.FinancialSystemTransactionalDocumentEntry; import org.kuali.kfs.sys.document.validation.GenericValidation; import org.kuali.kfs.sys.document.validation.event.AddAccountingLineEvent; import org.kuali.kfs.sys.document.validation.event.AttributedDocumentEvent; import org.kuali.kfs.sys.document.validation.event.DeleteAccountingLineEvent; import org.kuali.kfs.sys.document.validation.event.UpdateAccountingLineEvent; import org.kuali.rice.kim.api.identity.Person; import org.kuali.rice.krad.rules.rule.event.KualiDocumentEvent; import org.kuali.rice.krad.service.DataDictionaryService; import org.kuali.rice.krad.util.GlobalVariables; /** * A validation that checks whether the given accounting line is accessible to the given user or not */ public class AccountingLineAccessibleValidation extends GenericValidation { protected DataDictionaryService dataDictionaryService; protected AccountingDocument accountingDocumentForValidation; protected AccountingLine accountingLineForValidation; /** * Indicates what is being done to an accounting line. This allows the same method to be used for different actions. */ public enum AccountingLineAction { ADD(KFSKeyConstants.ERROR_ACCOUNTINGLINE_INACCESSIBLE_ADD), DELETE(KFSKeyConstants.ERROR_ACCOUNTINGLINE_INACCESSIBLE_DELETE), UPDATE(KFSKeyConstants.ERROR_ACCOUNTINGLINE_INACCESSIBLE_UPDATE); public final String accessibilityErrorKey; AccountingLineAction(String accessabilityErrorKey) { this.accessibilityErrorKey = accessabilityErrorKey; } } /** * Validates that the given accounting line is accessible for editing by the current user. * <strong>This method expects a document as the first parameter and an accounting line as the second</strong> * @see org.kuali.kfs.sys.document.validation.Validation#validate(java.lang.Object[]) */ @Override public boolean validate(AttributedDocumentEvent event) { final Person currentUser = GlobalVariables.getUserSession().getPerson(); if (accountingDocumentForValidation instanceof Correctable) { final String errorDocumentNumber = ((FinancialSystemDocumentHeader)accountingDocumentForValidation.getDocumentHeader()).getFinancialDocumentInErrorNumber(); if (StringUtils.isNotBlank(errorDocumentNumber)) { return true; } } final AccountingLineAuthorizer accountingLineAuthorizer = lookupAccountingLineAuthorizer(); final boolean lineIsAccessible = accountingLineAuthorizer.hasEditPermissionOnAccountingLine(accountingDocumentForValidation, accountingLineForValidation, getAccountingLineCollectionProperty(), currentUser, true); final boolean isAccessible = accountingLineAuthorizer.hasEditPermissionOnField(accountingDocumentForValidation, accountingLineForValidation, getAccountingLineCollectionProperty(), KFSPropertyConstants.ACCOUNT_NUMBER, lineIsAccessible, true, currentUser); if (!isAccessible) { // if only object code changed and the user has edit permissions on object code, that's ok if (event instanceof UpdateAccountingLineEvent) { final boolean isObjectCodeAccessible = accountingLineAuthorizer.hasEditPermissionOnField(accountingDocumentForValidation, accountingLineForValidation, getAccountingLineCollectionProperty(), KFSPropertyConstants.FINANCIAL_OBJECT_CODE, lineIsAccessible, true, currentUser); final boolean onlyObjectCodeChanged = onlyObjectCodeChanged(((UpdateAccountingLineEvent) event).getAccountingLine(), ((UpdateAccountingLineEvent) event).getUpdatedAccountingLine()); if (isObjectCodeAccessible && onlyObjectCodeChanged) { return true; } } // report errors final String principalName = currentUser.getPrincipalName(); final String[] accountErrorParams = new String[] { getDataDictionaryService().getAttributeLabel(accountingLineForValidation.getClass(), KFSPropertyConstants.ACCOUNT_NUMBER), accountingLineForValidation.getChartOfAccountsCode()+"-"+accountingLineForValidation.getAccountNumber(), principalName }; GlobalVariables.getMessageMap().putError(KFSPropertyConstants.ACCOUNT_NUMBER, convertEventToMessage(event), accountErrorParams); } return isAccessible; } /** * Checks to see if the object code is the only difference between the original accounting line and the updated accounting line. * * @param accountingLine * @param updatedAccountingLine * @return true if only the object code has changed on the accounting line, false otherwise */ private boolean onlyObjectCodeChanged(AccountingLine accountingLine, AccountingLine updatedAccountingLine) { // no changes, return false if (accountingLine.isLike(updatedAccountingLine)) { return false; } // copy the updatedAccountLine so we can set the object code on the copy of the updated accounting line // to be the original value for comparison purposes AccountingLine updatedLine = null; if (updatedAccountingLine.isSourceAccountingLine()) { updatedLine = new SourceAccountingLine(); } else { updatedLine = new TargetAccountingLine(); } updatedLine.copyFrom(updatedAccountingLine); updatedLine.setFinancialObjectCode(accountingLine.getFinancialObjectCode()); // if they're the same, the only change was the object code return (accountingLine.isLike(updatedLine)); } /** * Returns the name of the accounting line group which holds the proper authorizer to do the KIM check * @return the name of the accouting line group to get the authorizer from */ protected String getGroupName() { return (accountingLineForValidation.isSourceAccountingLine() ? KFSConstants.SOURCE_ACCOUNTING_LINES_GROUP_NAME : KFSConstants.TARGET_ACCOUNTING_LINES_GROUP_NAME); } /** * @return hopefully, the best accounting line authorizer implementation to do the KIM check for to see if lines are accessible */ protected AccountingLineAuthorizer lookupAccountingLineAuthorizer() { final String groupName = getGroupName(); final Map<String, AccountingLineGroupDefinition> groups = ((FinancialSystemTransactionalDocumentEntry)dataDictionaryService.getDataDictionary().getDictionaryObjectEntry(accountingDocumentForValidation.getClass().getName())).getAccountingLineGroups(); if (groups.isEmpty()) { return new AccountingLineAuthorizerBase(); // no groups? just use the default... } if (groups.containsKey(groupName)) { return groups.get(groupName).getAccountingLineAuthorizer(); // we've got the group } final Set<String> groupNames = groups.keySet(); // we've got groups, just not the proper name; try our luck and get the first group iterator final Iterator<String> groupNameIterator = groupNames.iterator(); final String firstGroupName = groupNameIterator.next(); return groups.get(firstGroupName).getAccountingLineAuthorizer(); } /** * Determines the property of the accounting line collection from the error prefixes * @return the accounting line collection property */ protected String getAccountingLineCollectionProperty() { String propertyName = null; if (GlobalVariables.getMessageMap().getErrorPath().size() > 0) { propertyName = GlobalVariables.getMessageMap().getErrorPath().get(0).replaceFirst(".*?document\\.", ""); } else { propertyName = accountingLineForValidation.isSourceAccountingLine() ? KFSConstants.PermissionAttributeValue.SOURCE_ACCOUNTING_LINES.value : KFSConstants.PermissionAttributeValue.TARGET_ACCOUNTING_LINES.value; } if (propertyName.equals("newSourceLine")) { return KFSConstants.PermissionAttributeValue.SOURCE_ACCOUNTING_LINES.value; } if (propertyName.equals("newTargetLine")) { return KFSConstants.PermissionAttributeValue.TARGET_ACCOUNTING_LINES.value; } return propertyName; } /** * Determines what error message should be shown based on the event that required this validation * @param event the event to use to determine the error message * @return the key of the error message to display */ protected String convertEventToMessage(KualiDocumentEvent event) { if (event instanceof AddAccountingLineEvent) { return AccountingLineAction.ADD.accessibilityErrorKey; } else if (event instanceof UpdateAccountingLineEvent) { return AccountingLineAction.UPDATE.accessibilityErrorKey; } else if (event instanceof DeleteAccountingLineEvent) { return AccountingLineAction.DELETE.accessibilityErrorKey; } else { return ""; } } /** * Gets the accountingDocumentForValidation attribute. * @return Returns the accountingDocumentForValidation. */ public AccountingDocument getAccountingDocumentForValidation() { return accountingDocumentForValidation; } /** * Sets the accountingDocumentForValidation attribute value. * @param accountingDocumentForValidation The accountingDocumentForValidation to set. */ public void setAccountingDocumentForValidation(AccountingDocument accountingDocumentForValidation) { this.accountingDocumentForValidation = accountingDocumentForValidation; } /** * Gets the accountingLineForValidation attribute. * @return Returns the accountingLineForValidation. */ public AccountingLine getAccountingLineForValidation() { return accountingLineForValidation; } /** * Sets the accountingLineForValidation attribute value. * @param accountingLineForValidation The accountingLineForValidation to set. */ public void setAccountingLineForValidation(AccountingLine accountingLineForValidation) { this.accountingLineForValidation = accountingLineForValidation; } /** * Gets the dataDictionaryService attribute. * @return Returns the dataDictionaryService. */ public DataDictionaryService getDataDictionaryService() { return dataDictionaryService; } /** * Sets the dataDictionaryService attribute value. * @param dataDictionaryService The dataDictionaryService to set. */ public void setDataDictionaryService(DataDictionaryService dataDictionaryService) { this.dataDictionaryService = dataDictionaryService; } }