001 /** 002 * Copyright 2005-2014 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.rice.krad.util; 017 018 import org.apache.commons.codec.EncoderException; 019 import org.apache.commons.codec.net.URLCodec; 020 import org.apache.commons.lang.StringUtils; 021 import org.kuali.rice.core.api.CoreApiServiceLocator; 022 import org.kuali.rice.core.api.encryption.EncryptionService; 023 import org.kuali.rice.core.api.util.Truth; 024 import org.kuali.rice.core.api.util.type.KualiDecimal; 025 import org.kuali.rice.core.web.format.BooleanFormatter; 026 import org.kuali.rice.coreservice.framework.CoreFrameworkServiceLocator; 027 import org.kuali.rice.coreservice.framework.parameter.ParameterConstants; 028 import org.kuali.rice.coreservice.framework.parameter.ParameterService; 029 import org.kuali.rice.krad.UserSession; 030 import org.kuali.rice.krad.messages.Message; 031 import org.kuali.rice.krad.messages.MessageService; 032 import org.kuali.rice.krad.service.DataObjectMetaDataService; 033 import org.kuali.rice.krad.service.KRADServiceLocatorWeb; 034 import org.kuali.rice.krad.service.KualiModuleService; 035 import org.kuali.rice.krad.service.ModuleService; 036 import org.kuali.rice.krad.uif.UifConstants; 037 import org.kuali.rice.krad.uif.UifParameters; 038 import org.kuali.rice.krad.uif.component.Component; 039 import org.kuali.rice.krad.uif.element.Action; 040 import org.kuali.rice.krad.uif.element.Image; 041 import org.kuali.rice.krad.uif.element.Link; 042 import org.kuali.rice.krad.uif.field.ActionField; 043 import org.kuali.rice.krad.uif.field.DataField; 044 import org.kuali.rice.krad.uif.field.Field; 045 import org.kuali.rice.krad.uif.field.FieldGroup; 046 import org.kuali.rice.krad.uif.field.ImageField; 047 import org.kuali.rice.krad.uif.field.LinkField; 048 import org.kuali.rice.krad.uif.field.MessageField; 049 import org.kuali.rice.krad.uif.field.SpaceField; 050 import org.kuali.rice.krad.uif.util.ObjectPropertyUtils; 051 import org.kuali.rice.krad.uif.util.ViewModelUtils; 052 import org.kuali.rice.krad.uif.view.ExpressionEvaluator; 053 import org.kuali.rice.krad.uif.view.View; 054 import org.kuali.rice.krad.web.form.UifFormBase; 055 import org.springframework.util.Assert; 056 057 import javax.servlet.ServletRequest; 058 import javax.servlet.http.HttpServletRequest; 059 import java.lang.reflect.Constructor; 060 import java.lang.reflect.Method; 061 import java.security.GeneralSecurityException; 062 import java.text.MessageFormat; 063 import java.text.NumberFormat; 064 import java.util.ArrayList; 065 import java.util.Arrays; 066 import java.util.Collection; 067 import java.util.Collections; 068 import java.util.HashMap; 069 import java.util.Iterator; 070 import java.util.List; 071 import java.util.Map; 072 import java.util.Properties; 073 import java.util.regex.Pattern; 074 075 /** 076 * Miscellaneous Utility Methods 077 * 078 * @author Kuali Rice Team (rice.collab@kuali.org) 079 */ 080 public final class KRADUtils { 081 private static KualiModuleService kualiModuleService; 082 083 private static final KualiDecimal ONE_HUNDRED = new KualiDecimal("100.00"); 084 085 /** 086 * Prevent instantiation of the class. 087 */ 088 private KRADUtils() { 089 throw new UnsupportedOperationException("do not call"); 090 } 091 092 /** 093 * Retrieve the title for a business object class 094 * 095 * <p> 096 * The title is a nicely formatted version of the simple class name. 097 * </p> 098 * 099 * @param clazz business object class 100 * @return title of the business object class 101 */ 102 public final static String getBusinessTitleForClass(Class<? extends Object> clazz) { 103 if (clazz == null) { 104 throw new IllegalArgumentException( 105 "The getBusinessTitleForClass method of KRADUtils requires a non-null class"); 106 } 107 String className = clazz.getSimpleName(); 108 109 StringBuffer label = new StringBuffer(className.substring(0, 1)); 110 for (int i = 1; i < className.length(); i++) { 111 if (Character.isLowerCase(className.charAt(i))) { 112 label.append(className.charAt(i)); 113 } else { 114 label.append(" ").append(className.charAt(i)); 115 } 116 } 117 return label.toString().trim(); 118 } 119 120 /** 121 * Picks off the filename from the full path 122 * 123 * <p> 124 * The different OS path separators are being taken into consideration. 125 * </p> 126 * 127 * @param fullFileNames file name with path 128 * @return file name 129 */ 130 public final static List<String> getFileNameFromPath(List<String> fullFileNames) { 131 List<String> fileNameList = new ArrayList<String>(); 132 133 for (String fullFileName : fullFileNames) { 134 if (StringUtils.contains(fullFileName, "/")) { 135 fileNameList.add(StringUtils.substringAfterLast(fullFileName, "/")); 136 } else { 137 fileNameList.add(StringUtils.substringAfterLast(fullFileName, "\\")); 138 } 139 } 140 141 return fileNameList; 142 } 143 144 /** 145 * Convert the given money amount into an integer string. 146 * 147 * <p> 148 * Since the return string cannot have decimal point, multiplies the amount by 100 so the decimal places 149 * are not lost, for example, 320.15 is converted into 32015. 150 * </p> 151 * 152 * @param decimalNumber decimal number to be converted 153 * @return an integer string of the given money amount through multiplying by 100 and removing the fraction 154 * portion. 155 */ 156 public final static String convertDecimalIntoInteger(KualiDecimal decimalNumber) { 157 KualiDecimal decimalAmount = decimalNumber.multiply(ONE_HUNDRED); 158 NumberFormat formatter = NumberFormat.getIntegerInstance(); 159 String formattedAmount = formatter.format(decimalAmount); 160 161 return StringUtils.replace(formattedAmount, ",", ""); 162 } 163 164 /** 165 * Return the integer value of a string 166 * 167 * <p> 168 * If the string contains a decimal value everything after the decimal point is dropped. 169 * </p> 170 * 171 * @param numberStr string 172 * @return integer representation of the given string 173 */ 174 public static Integer getIntegerValue(String numberStr) { 175 Integer numberInt = null; 176 try { 177 numberInt = new Integer(numberStr); 178 } catch (NumberFormatException nfe) { 179 Double numberDbl = new Double(numberStr); 180 numberInt = new Integer(numberDbl.intValue()); 181 } 182 return numberInt; 183 } 184 185 /** 186 * Attempt to coerce a String attribute value to the given propertyType. If the transformation can't be made, 187 * either because the propertyType is null or because the transformation required exceeds this method's very small 188 * bag of tricks, then null is returned. 189 * 190 * @param propertyType the Class to coerce the attributeValue to 191 * @param attributeValue the String value to coerce 192 * @return an instance of the propertyType class, or null the transformation can't be made. 193 */ 194 public static Object hydrateAttributeValue(Class<?> propertyType, String attributeValue) { 195 Object attributeValueObject = null; 196 if (propertyType != null && attributeValue != null) { 197 if (String.class.equals(propertyType)) { 198 // it's already a String 199 attributeValueObject = attributeValue; 200 } // KULRICE-6808: Kim Role Maintenance - Custom boolean role qualifier values are not being converted properly 201 else if (Boolean.class.equals(propertyType) || Boolean.TYPE.equals(propertyType)) { 202 attributeValueObject = Truth.strToBooleanIgnoreCase(attributeValue); 203 } else { 204 // try to create one with KRADUtils for other misc data types 205 attributeValueObject = KRADUtils.createObject(propertyType, new Class[]{String.class}, 206 new Object[]{attributeValue}); 207 // if that didn't work, we'll get a null back 208 } 209 } 210 return attributeValueObject; 211 } 212 213 public static Object createObject(Class<?> clazz, Class<?>[] argumentClasses, Object[] argumentValues) { 214 if (clazz == null) { 215 return null; 216 } 217 if (argumentClasses.length == 1 && argumentClasses[0] == String.class) { 218 if (argumentValues.length == 1 && argumentValues[0] != null) { 219 if (clazz == String.class) { 220 // this means we're trying to create a String from a String 221 // don't new up Strings, it's a bad idea 222 return argumentValues[0]; 223 } else { 224 // maybe it's a type that supports valueOf? 225 Method valueOfMethod = null; 226 try { 227 valueOfMethod = clazz.getMethod("valueOf", String.class); 228 } catch (NoSuchMethodException e) { 229 // ignored 230 } 231 if (valueOfMethod != null) { 232 try { 233 return valueOfMethod.invoke(null, argumentValues[0]); 234 } catch (Exception e) { 235 // ignored 236 } 237 } 238 } 239 } 240 } 241 try { 242 Constructor<?> constructor = clazz.getConstructor(argumentClasses); 243 return constructor.newInstance(argumentValues); 244 } catch (Exception e) { 245 // ignored 246 } 247 return null; 248 } 249 250 /** 251 * Creates a comma separated String representation of the given list. 252 * 253 * <p> 254 * For example 'a','b',c'. 255 * </p> 256 * 257 * @param list 258 * @return the joined String, empty if the list is null or has no elements 259 */ 260 public static String joinWithQuotes(List<String> list) { 261 if (list == null || list.size() == 0) { 262 return ""; 263 } 264 265 return KRADConstants.SINGLE_QUOTE + 266 StringUtils.join(list.iterator(), KRADConstants.SINGLE_QUOTE + "," + KRADConstants.SINGLE_QUOTE) + 267 KRADConstants.SINGLE_QUOTE; 268 } 269 270 private static KualiModuleService getKualiModuleService() { 271 if (kualiModuleService == null) { 272 kualiModuleService = KRADServiceLocatorWeb.getKualiModuleService(); 273 } 274 return kualiModuleService; 275 } 276 277 /** 278 * TODO this method will probably need to be exposed in a public KRADUtils class as it is used 279 * by several different modules. That will have to wait until ModuleService and KualiModuleService are moved 280 * to core though. 281 */ 282 public static String getNamespaceCode(Class<? extends Object> clazz) { 283 ModuleService moduleService = getKualiModuleService().getResponsibleModuleService(clazz); 284 if (moduleService == null) { 285 return KRADConstants.DEFAULT_NAMESPACE; 286 } 287 return moduleService.getModuleConfiguration().getNamespaceCode(); 288 } 289 290 public static Map<String, String> getNamespaceAndComponentSimpleName(Class<? extends Object> clazz) { 291 Map<String, String> map = new HashMap<String, String>(); 292 map.put(KRADConstants.NAMESPACE_CODE, getNamespaceCode(clazz)); 293 map.put(KRADConstants.COMPONENT_NAME, getComponentSimpleName(clazz)); 294 return map; 295 } 296 297 public static Map<String, String> getNamespaceAndComponentFullName(Class<? extends Object> clazz) { 298 Map<String, String> map = new HashMap<String, String>(); 299 map.put(KRADConstants.NAMESPACE_CODE, getNamespaceCode(clazz)); 300 map.put(KRADConstants.COMPONENT_NAME, getComponentFullName(clazz)); 301 return map; 302 } 303 304 public static Map<String, String> getNamespaceAndActionClass(Class<? extends Object> clazz) { 305 Map<String, String> map = new HashMap<String, String>(); 306 map.put(KRADConstants.NAMESPACE_CODE, getNamespaceCode(clazz)); 307 map.put(KRADConstants.ACTION_CLASS, clazz.getName()); 308 return map; 309 } 310 311 private static String getComponentSimpleName(Class<? extends Object> clazz) { 312 return clazz.getSimpleName(); 313 } 314 315 private static String getComponentFullName(Class<? extends Object> clazz) { 316 return clazz.getName(); 317 } 318 319 /** 320 * Parses a string that is in map format (commas separating map entries, colon separates 321 * map key/value) to a new map instance 322 * 323 * @param parameter - string parameter to parse 324 * @return Map<String, String> instance populated from string parameter 325 */ 326 public static Map<String, String> convertStringParameterToMap(String parameter) { 327 Map<String, String> map = new HashMap<String, String>(); 328 329 if (StringUtils.isNotBlank(parameter)) { 330 if (StringUtils.contains(parameter, ",")) { 331 String[] fieldConversions = StringUtils.split(parameter, ","); 332 333 for (int i = 0; i < fieldConversions.length; i++) { 334 String fieldConversionStr = fieldConversions[i]; 335 if (StringUtils.isNotBlank(fieldConversionStr)) { 336 if (StringUtils.contains(fieldConversionStr, ":")) { 337 String[] fieldConversion = StringUtils.split(fieldConversionStr, ":"); 338 map.put(fieldConversion[0], fieldConversion[1]); 339 } else { 340 map.put(fieldConversionStr, fieldConversionStr); 341 } 342 } 343 } 344 } else if (StringUtils.contains(parameter, ":")) { 345 String[] fieldConversion = StringUtils.split(parameter, ":"); 346 map.put(fieldConversion[0], fieldConversion[1]); 347 } else { 348 map.put(parameter, parameter); 349 } 350 } 351 352 return map; 353 } 354 355 /** 356 * Parses a string that is in list format (commas separating list entries) to a new List instance 357 * 358 * @param parameter - string parameter to parse 359 * @return List<String> instance populated from string parameter 360 */ 361 public static List<String> convertStringParameterToList(String parameter) { 362 List<String> list = new ArrayList<String>(); 363 364 if (StringUtils.isNotBlank(parameter)) { 365 if (StringUtils.contains(parameter, ",")) { 366 String[] parameters = StringUtils.split(parameter, ","); 367 List arraysList = Arrays.asList(parameters); 368 list.addAll(arraysList); 369 } else { 370 list.add(parameter); 371 } 372 } 373 374 return list; 375 } 376 377 /** 378 * Translates characters in the given string like brackets that will cause 379 * problems with binding to characters that do not affect the binding 380 * 381 * @param key - string to translate 382 * @return String translated string 383 */ 384 public static String translateToMapSafeKey(String key) { 385 String safeKey = key; 386 387 safeKey = StringUtils.replace(safeKey, "[", "_"); 388 safeKey = StringUtils.replace(safeKey, "]", "_"); 389 390 return safeKey; 391 } 392 393 /** 394 * Builds a string from the given map by joining each entry with a comma and 395 * each key/value pair with a colon 396 * 397 * @param map - map instance to build string for 398 * @return String of map entries 399 */ 400 public static String buildMapParameterString(Map<String, String> map) { 401 String parameterString = ""; 402 403 for (Map.Entry<String, String> entry : map.entrySet()) { 404 if (StringUtils.isNotBlank(parameterString)) { 405 parameterString += ","; 406 } 407 408 parameterString += entry.getKey() + ":" + entry.getValue(); 409 } 410 411 return parameterString; 412 } 413 414 /** 415 * Parses the given string into a Map by splitting on the comma to get the 416 * map entries and within each entry splitting by colon to get the key/value 417 * pairs 418 * 419 * @param parameterString - string to parse into map 420 * @return Map<String, String> map from string 421 */ 422 public static Map<String, String> getMapFromParameterString(String parameterString) { 423 Map<String, String> map = new HashMap<String, String>(); 424 425 String[] entries = parameterString.split(","); 426 for (int i = 0; i < entries.length; i++) { 427 String[] keyValue = entries[i].split(":"); 428 if (keyValue.length != 2) { 429 throw new RuntimeException("malformed field conversion pair: " + Arrays.toString(keyValue)); 430 } 431 432 map.put(keyValue[0], keyValue[1]); 433 } 434 435 return map; 436 } 437 438 /** 439 * Retrieves value for the given parameter name in the request and attempts to convert to a Boolean using 440 * the <code>BooleanFormatter</code> 441 * 442 * @param request - servlet request containing parameters 443 * @param parameterName - name of parameter to retrieve value for 444 * @return Boolean set to value of parameter, or null if parameter was not found in request 445 */ 446 public static Boolean getRequestParameterAsBoolean(ServletRequest request, String parameterName) { 447 Boolean parameterValue = null; 448 449 String parameterValueStr = request.getParameter(parameterName); 450 if (StringUtils.isNotBlank(parameterValueStr)) { 451 parameterValue = (Boolean) new BooleanFormatter().convertFromPresentationFormat(parameterValueStr); 452 } 453 454 return parameterValue; 455 } 456 457 /** 458 * Translates the given Map of String keys and String array values to a Map 459 * of String key and values. If the String array contains more than one 460 * value, the single string is built by joining the values with the vertical 461 * bar character 462 * 463 * @param requestParameters - Map of request parameters to translate 464 * @return Map<String, String> translated Map 465 */ 466 public static Map<String, String> translateRequestParameterMap(Map<String, String[]> requestParameters) { 467 Map<String, String> parameters = new HashMap<String, String>(); 468 469 for (Map.Entry<String, String[]> parameter : requestParameters.entrySet()) { 470 String parameterValue = ""; 471 if (parameter.getValue().length > 1) { 472 parameterValue = StringUtils.join(parameter.getValue(), "|"); 473 } else { 474 parameterValue = parameter.getValue()[0]; 475 } 476 parameters.put(parameter.getKey(), parameterValue); 477 } 478 479 return parameters; 480 } 481 482 /** 483 * Retrieves parameter values from the request that match the requested 484 * names. In addition, based on the object class an authorization check is 485 * performed to determine if the values are secure and should be decrypted. 486 * If true, the value is decrypted before returning 487 * 488 * @param parameterNames - names of the parameters whose values should be retrieved 489 * from the request 490 * @param parentObjectClass - object class that contains the parameter names as properties 491 * and should be consulted for security checks 492 * @param requestParameters - all request parameters to pull from 493 * @return Map<String, String> populated with parameter name/value pairs 494 * pulled from the request 495 */ 496 public static Map<String, String> getParametersFromRequest(List<String> parameterNames, Class<?> parentObjectClass, 497 Map<String, String> requestParameters) { 498 Map<String, String> parameterValues = new HashMap<String, String>(); 499 500 for (Iterator<String> iter = parameterNames.iterator(); iter.hasNext(); ) { 501 String keyPropertyName = iter.next(); 502 503 if (requestParameters.get(keyPropertyName) != null) { 504 String keyValue = requestParameters.get(keyPropertyName); 505 506 // Check if this element was encrypted, if it was decrypt it 507 if (KRADServiceLocatorWeb.getDataObjectAuthorizationService() 508 .attributeValueNeedsToBeEncryptedOnFormsAndLinks(parentObjectClass, keyPropertyName)) { 509 try { 510 keyValue = StringUtils.removeEnd(keyValue, EncryptionService.ENCRYPTION_POST_PREFIX); 511 keyValue = CoreApiServiceLocator.getEncryptionService().decrypt(keyValue); 512 } catch (GeneralSecurityException e) { 513 throw new RuntimeException(e); 514 } 515 } 516 517 parameterValues.put(keyPropertyName, keyValue); 518 } 519 } 520 521 return parameterValues; 522 } 523 524 /** 525 * Builds a Map containing a key/value pair for each property given in the property names list, general 526 * security is checked to determine if the value needs to be encrypted along with applying formatting to 527 * the value 528 * 529 * @param propertyNames - list of property names to get key/value pairs for 530 * @param dataObject - object instance containing the properties for which the values will be pulled 531 * @return Map<String, String> containing entry for each property name with the property name as the map key 532 * and the property value as the value 533 */ 534 public static Map<String, String> getPropertyKeyValuesFromDataObject(List<String> propertyNames, 535 Object dataObject) { 536 return getPropertyKeyValuesFromDataObject(propertyNames, Collections.<String>emptyList(), dataObject); 537 } 538 539 /** 540 * Builds a Map containing a key/value pair for each property given in the property names list, general 541 * security is checked to determine if the value needs to be encrypted along with applying formatting to 542 * the value 543 * 544 * @param propertyNames - list of property names to get key/value pairs for 545 * @param securePropertyNames - list of secure property names to match for encryption 546 * @param dataObject - object instance containing the properties for which the values will be pulled 547 * @return Map<String, String> containing entry for each property name with the property name as the map key 548 * and the property value as the value 549 */ 550 public static Map<String, String> getPropertyKeyValuesFromDataObject(List<String> propertyNames, 551 List<String> securePropertyNames, Object dataObject) { 552 Map<String, String> propertyKeyValues = new HashMap<String, String>(); 553 554 if (dataObject == null) { 555 return propertyKeyValues; 556 } 557 558 // iterate through properties and add a map entry for each 559 for (String propertyName : propertyNames) { 560 Object propertyValue = ObjectPropertyUtils.getPropertyValue(dataObject, propertyName); 561 if (propertyValue == null) { 562 propertyValue = StringUtils.EMPTY; 563 } 564 565 // secure values are not returned 566 if (!isSecure(propertyName, securePropertyNames, dataObject, propertyValue)) { 567 propertyKeyValues.put(propertyName, propertyValue.toString()); 568 } 569 570 } 571 572 return propertyKeyValues; 573 } 574 575 /** 576 * Determines whether a property name should be secured, either based on installed sensitive data patterns, a list 577 * of secure property name patterns, or attributes in the Data Dictionary. 578 * 579 * @param propertyName The property name to check for security 580 * @param securePropertyNames The secure property name patterns to check 581 * @param dataObject The object containing this property 582 * @param propertyValue The value of the property 583 * @return true if the property needs to be secure, false otherwise 584 */ 585 private static boolean isSecure(String propertyName, List<String> securePropertyNames, Object dataObject, Object propertyValue) { 586 if (propertyValue instanceof String && containsSensitiveDataPatternMatch((String) propertyValue)) { 587 return true; 588 } 589 590 if (containsSecurePropertyName(propertyName, securePropertyNames)) { 591 return true; 592 } 593 594 return KRADServiceLocatorWeb.getDataObjectAuthorizationService() 595 .attributeValueNeedsToBeEncryptedOnFormsAndLinks(dataObject.getClass(), propertyName); 596 } 597 598 /** 599 * Helper method to identify if propertyName contains a secure property name element. 600 * Check handles simple or compound names and ignores partial matches. 601 * 602 * @param propertyName property name as a single term or compound term (i.e. items[0].propertyName) 603 * @param securePropertyNames list of secure property names to match 604 * @return true if any of the secure property names are found in the property name, false otherwise 605 */ 606 private static boolean containsSecurePropertyName(String propertyName, List<String> securePropertyNames) { 607 if (securePropertyNames == null) { 608 return false; 609 } 610 611 for (String securePropertyName : securePropertyNames) { 612 // pattern prefix and suffix used to handle compound names and ignore partial name matches 613 if (Pattern.compile("(?:\\.|^)" + Pattern.quote(securePropertyName) + "(?:\\.|\\[|$)").matcher(propertyName) 614 .find()) { 615 return true; 616 } 617 } 618 619 return false; 620 } 621 622 /** 623 * Utility method to convert a Map to a Properties object 624 * 625 * @param parameters - map to convert 626 * @return Properties object containing all the map entries 627 */ 628 public static Properties convertMapToProperties(Map<String, String> parameters) { 629 Properties properties = new Properties(); 630 631 if (parameters != null) { 632 for (Map.Entry<String, String> parameter : parameters.entrySet()) { 633 properties.put(parameter.getKey(), parameter.getValue()); 634 } 635 } 636 637 return properties; 638 } 639 640 /** 641 * Utility method to convert a Request Parameters Map to a Properties object 642 * 643 * <p> 644 * Multiple values for a parameter are joined together with comma delimiter 645 * </p> 646 * 647 * @param requestParameters - map to convert 648 * @return Properties object containing all the map entries 649 */ 650 public static Properties convertRequestMapToProperties(Map<String, String[]> requestParameters) { 651 Properties properties = new Properties(); 652 653 if (requestParameters != null) { 654 for (Map.Entry<String, String[]> parameter : requestParameters.entrySet()) { 655 String[] parameterValue = parameter.getValue(); 656 String parameterValueString = StringUtils.join(parameterValue, ","); 657 658 properties.put(parameter.getKey(), parameterValueString); 659 } 660 } 661 662 return properties; 663 } 664 665 /** 666 * Check if data might be sensitive 667 * 668 * <p> 669 * The sensitivity of the data is checked by matching it against the sensitive data patterns that are specified 670 * in the system parameter table. 671 * </p> 672 * 673 * @param fieldValue data to be checked for sensitivity 674 * @return true if the data matches the sensitive data pattern, false otherwise 675 */ 676 public static boolean containsSensitiveDataPatternMatch(String fieldValue) { 677 if (StringUtils.isBlank(fieldValue)) { 678 return false; 679 } 680 681 ParameterService parameterService = CoreFrameworkServiceLocator.getParameterService(); 682 Collection<String> sensitiveDataPatterns = parameterService.getParameterValuesAsString( 683 KRADConstants.KNS_NAMESPACE, ParameterConstants.ALL_COMPONENT, 684 KRADConstants.SystemGroupParameterNames.SENSITIVE_DATA_PATTERNS); 685 686 for (String pattern : sensitiveDataPatterns) { 687 if (Pattern.compile(pattern).matcher(fieldValue).find()) { 688 return true; 689 } 690 } 691 692 return false; 693 } 694 695 /** 696 * Strips out common patterns used in cross side scripting 697 * 698 * @param value string to strip patterns from 699 * @return cleaned string 700 */ 701 public static String stripXSSPatterns(String value) { 702 if (value == null) { 703 return null; 704 } 705 706 // Avoid null characters 707 value = value.replaceAll("", ""); 708 709 // Avoid anything between script tags 710 Pattern scriptPattern = Pattern.compile("<script>(.*?)</script>", Pattern.CASE_INSENSITIVE); 711 value = scriptPattern.matcher(value).replaceAll(""); 712 713 // Avoid anything in a src='...' type of expression 714 scriptPattern = Pattern.compile("src[\r\n]*=[\r\n]*\\\'(.*?)\\\'", 715 Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL); 716 value = scriptPattern.matcher(value).replaceAll(""); 717 718 scriptPattern = Pattern.compile("src[\r\n]*=[\r\n]*\\\"(.*?)\\\"", 719 Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL); 720 value = scriptPattern.matcher(value).replaceAll(""); 721 722 // Remove any lonesome </script> tag 723 scriptPattern = Pattern.compile("</script>", Pattern.CASE_INSENSITIVE); 724 value = scriptPattern.matcher(value).replaceAll(""); 725 726 // Remove any lonesome <script ...> tag 727 scriptPattern = Pattern.compile("<script(.*?)>", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL); 728 value = scriptPattern.matcher(value).replaceAll(""); 729 730 // Avoid eval(...) expressions 731 scriptPattern = Pattern.compile("eval\\((.*?)\\)", 732 Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL); 733 value = scriptPattern.matcher(value).replaceAll(""); 734 735 // Avoid expression(...) expressions 736 scriptPattern = Pattern.compile("expression\\((.*?)\\)", 737 Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL); 738 value = scriptPattern.matcher(value).replaceAll(""); 739 740 // Avoid javascript:... expressions 741 scriptPattern = Pattern.compile("javascript:", Pattern.CASE_INSENSITIVE); 742 value = scriptPattern.matcher(value).replaceAll(""); 743 744 // Avoid vbscript:... expressions 745 scriptPattern = Pattern.compile("vbscript:", Pattern.CASE_INSENSITIVE); 746 value = scriptPattern.matcher(value).replaceAll(""); 747 748 // Avoid onload= expressions 749 scriptPattern = Pattern.compile("onload(.*?)=", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL); 750 value = scriptPattern.matcher(value).replaceAll(""); 751 752 return value; 753 } 754 755 /** 756 * Gets the UserSession object from the HttpServletRequest object's 757 * associated session. 758 * 759 * <p> 760 * In some cases (different threads) the UserSession cannot be retrieved 761 * from GlobalVariables but can still be accessed via the session object 762 * </p> 763 */ 764 public static final UserSession getUserSessionFromRequest(HttpServletRequest request) { 765 return (UserSession) request.getSession().getAttribute(KRADConstants.USER_SESSION_KEY); 766 } 767 768 /** 769 * Check if current deployment is the production environment 770 * 771 * @return true if the deploy environment is production, false otherwise 772 */ 773 public static boolean isProductionEnvironment() { 774 return CoreApiServiceLocator.getKualiConfigurationService().getPropertyValueAsString( 775 KRADConstants.PROD_ENVIRONMENT_CODE_KEY).equalsIgnoreCase( 776 CoreApiServiceLocator.getKualiConfigurationService().getPropertyValueAsString( 777 KRADConstants.ENVIRONMENT_KEY)); 778 } 779 780 /** 781 * Gets the message associated with ErrorMessage object passed in, using message service. 782 * The prefix and suffix will be appended to the retrieved message if processPrefixSuffix is true and if those 783 * settings are set on the ErrorMessage passed in. 784 * 785 * @param errorMessage the ErrorMessage object containing the message key(s) 786 * @param processPrefixSuffix if true appends the prefix and suffix to the message if they exist on ErrorMessage 787 * @return the converted/retrieved message 788 */ 789 public static String getMessageText(ErrorMessage errorMessage, boolean processPrefixSuffix) { 790 String message = ""; 791 if (errorMessage != null && errorMessage.getErrorKey() != null) { 792 MessageService messageService = KRADServiceLocatorWeb.getMessageService(); 793 794 // find message by key 795 message = messageService.getMessageText(errorMessage.getNamespaceCode(), errorMessage.getComponentCode(), 796 errorMessage.getErrorKey()); 797 if (message == null) { 798 message = "Intended message with key: " + errorMessage.getErrorKey() + " not found."; 799 } 800 801 if (errorMessage.getMessageParameters() != null && StringUtils.isNotBlank(message)) { 802 message = message.replace("'", "''"); 803 message = MessageFormat.format(message, (Object[]) errorMessage.getMessageParameters()); 804 } 805 806 // add prefix 807 if (StringUtils.isNotBlank(errorMessage.getMessagePrefixKey()) && processPrefixSuffix) { 808 String prefix = messageService.getMessageText(errorMessage.getNamespaceCode(), 809 errorMessage.getComponentCode(), errorMessage.getMessagePrefixKey()); 810 811 if (errorMessage.getMessagePrefixParameters() != null && StringUtils.isNotBlank(prefix)) { 812 prefix = prefix.replace("'", "''"); 813 prefix = MessageFormat.format(prefix, (Object[]) errorMessage.getMessagePrefixParameters()); 814 } 815 816 if (StringUtils.isNotBlank(prefix)) { 817 message = prefix + " " + message; 818 } 819 } 820 821 // add suffix 822 if (StringUtils.isNotBlank(errorMessage.getMessageSuffixKey()) && processPrefixSuffix) { 823 String suffix = messageService.getMessageText(errorMessage.getNamespaceCode(), 824 errorMessage.getComponentCode(), errorMessage.getMessageSuffixKey()); 825 826 if (errorMessage.getMessageSuffixParameters() != null && StringUtils.isNotBlank(suffix)) { 827 suffix = suffix.replace("'", "''"); 828 suffix = MessageFormat.format(suffix, (Object[]) errorMessage.getMessageSuffixParameters()); 829 } 830 831 if (StringUtils.isNotBlank(suffix)) { 832 message = message + " " + suffix; 833 } 834 } 835 } 836 837 return message; 838 } 839 840 /** 841 * Generate the request parameter portion of the url based on the map of key value pairs passed in 842 * 843 * @param requestParameters the request parameters to use in the string 844 * @return a request parameter string starting with "?" with "&" separators, or blank if the mapped passed in is 845 * blank 846 */ 847 public static String getRequestStringFromMap(Map<String, String> requestParameters) { 848 String requestString = ""; 849 850 if (requestParameters.isEmpty()) { 851 return requestString; 852 } 853 854 URLCodec urlCodec = new URLCodec(KRADConstants.DEFAULT_ENCODING); 855 856 for (String key : requestParameters.keySet()) { 857 String value = null; 858 try { 859 value = urlCodec.encode(requestParameters.get(key)); 860 } catch (EncoderException e) { 861 throw new RuntimeException("Unable to encode parameter name or value: " + key + "=" + value, e); 862 } 863 864 if (StringUtils.isNotBlank(requestString)) { 865 requestString = requestString + "&"; 866 } 867 868 requestString = requestString + key + "=" + value; 869 } 870 871 return "?" + requestString; 872 } 873 874 /** 875 * Helper method for building a URL that will invoke the given controller and render the given 876 * KRAD view 877 * 878 * @param baseUrl base url (domain, port) 879 * @param controllerMapping mapping for the controller that should be invoked 880 * @param viewId id for the view that should be rendered 881 * @return url for invoking the view 882 */ 883 public static String buildViewUrl(String baseUrl, String controllerMapping, String viewId) { 884 Assert.hasLength(baseUrl, "base url is null or empty"); 885 Assert.hasLength(controllerMapping, "controller mapping is null or empty"); 886 Assert.hasLength(viewId, "view id is null or empty"); 887 888 StringBuffer url = new StringBuffer(); 889 890 url.append(baseUrl); 891 892 if (!baseUrl.endsWith("/")) { 893 url.append("/"); 894 } 895 896 url.append(controllerMapping); 897 898 url.append("?"); 899 url.append(UifParameters.VIEW_ID); 900 url.append("="); 901 url.append(viewId); 902 903 return url.toString(); 904 } 905 906 /** 907 * Removes parameters from the given properties object that are request specific (useful when manupulating the 908 * current URL to invoke something else) 909 * 910 * @param requestParameters properties instance containing the parameters to clean 911 */ 912 public static void cleanRequestParameters(Properties requestParameters) { 913 requestParameters.remove(UifParameters.SESSION_ID); 914 requestParameters.remove(UifParameters.AJAX_REQUEST); 915 requestParameters.remove(UifParameters.AJAX_RETURN_TYPE); 916 requestParameters.remove(UifParameters.FORM_KEY); 917 requestParameters.remove(UifParameters.JUMP_TO_ID); 918 requestParameters.remove(UifParameters.FOCUS_ID); 919 } 920 921 /** 922 * Get the full url for a request (requestURL + queryString) 923 * 924 * @param request the request 925 * @return the fullUrl 926 */ 927 public static String getFullURL(HttpServletRequest request) { 928 if (request == null) { 929 return null; 930 } 931 932 StringBuffer requestURL = request.getRequestURL(); 933 String queryString = request.getQueryString(); 934 935 if (queryString == null) { 936 return requestURL.toString(); 937 } else { 938 return requestURL.append('?').append(queryString).toString(); 939 } 940 } 941 942 /** 943 * Attempts to generate a unique view title by combining the View's headerText with the title attribute for the 944 * dataObjectClass found through the DataObjectMetaDataService. If the title attribute cannot be found, just the 945 * headerText is returned. 946 * 947 * @param form the form 948 * @param view the view 949 * @return the headerText with the title attribute in parenthesis or just the headerText if it title attribute 950 * cannot be determined 951 */ 952 public static String generateUniqueViewTitle(UifFormBase form, View view) { 953 String title = view.getHeader().getHeaderText(); 954 955 String viewLabelPropertyName = ""; 956 957 Class<?> dataObjectClass; 958 if (StringUtils.isNotBlank(view.getDefaultBindingObjectPath())) { 959 dataObjectClass = ObjectPropertyUtils.getPropertyType(form, view.getDefaultBindingObjectPath()); 960 } else { 961 dataObjectClass = view.getFormClass(); 962 } 963 964 DataObjectMetaDataService mds = KRADServiceLocatorWeb.getDataObjectMetaDataService(); 965 if (dataObjectClass != null) { 966 viewLabelPropertyName = mds.getTitleAttribute(dataObjectClass); 967 } 968 969 String viewLabelPropertyPath = ""; 970 if (StringUtils.isNotBlank(viewLabelPropertyName)) { 971 // adjust binding prefix 972 if (!viewLabelPropertyName.startsWith(UifConstants.NO_BIND_ADJUST_PREFIX)) { 973 if (StringUtils.isNotBlank(view.getDefaultBindingObjectPath())) { 974 viewLabelPropertyPath = view.getDefaultBindingObjectPath() + "." + viewLabelPropertyName; 975 } 976 } else { 977 viewLabelPropertyPath = StringUtils.removeStart(viewLabelPropertyName, 978 UifConstants.NO_BIND_ADJUST_PREFIX); 979 } 980 } else { 981 // attempt to get title attribute 982 if (StringUtils.isNotBlank(view.getDefaultBindingObjectPath())) { 983 dataObjectClass = ViewModelUtils.getObjectClassForMetadata(view, form, 984 view.getDefaultBindingObjectPath()); 985 } else { 986 dataObjectClass = view.getFormClass(); 987 } 988 989 if (dataObjectClass != null) { 990 String titleAttribute = mds.getTitleAttribute(dataObjectClass); 991 if (StringUtils.isNotBlank(titleAttribute)) { 992 viewLabelPropertyPath = view.getDefaultBindingObjectPath() + "." + titleAttribute; 993 } 994 } 995 } 996 997 Object viewLabelPropertyValue = null; 998 if (StringUtils.isNotBlank(viewLabelPropertyPath) && ObjectPropertyUtils.isReadableProperty(form, 999 viewLabelPropertyPath)) { 1000 viewLabelPropertyValue = ObjectPropertyUtils.getPropertyValue(form, viewLabelPropertyPath); 1001 } 1002 1003 if (viewLabelPropertyValue != null && StringUtils.isNotBlank(viewLabelPropertyValue.toString()) && StringUtils 1004 .isNotBlank(title)) { 1005 return title + " (" + viewLabelPropertyValue.toString() + ")"; 1006 } else { 1007 return title; 1008 } 1009 } 1010 1011 /** 1012 * Attempts to extract a string value out of the field passed in, varies depending on field type 1013 * 1014 * <p>If the field is a dataField, it will use its propertyName to retrieve a value, otherwise it will try to 1015 * retrieve textual content out of various component types. If the field is a FieldGroup, only the first 1016 * component's determined value will be used. This function is used for sorting.</p> 1017 * 1018 * @param model the current model 1019 * @param field the field to get a value from 1020 * @return the field's String value, false if it cant be determined 1021 */ 1022 public static String getSimpleFieldValue(Object model, Field field) { 1023 if (field == null) { 1024 return null; 1025 } 1026 1027 String value = null; 1028 // check for what type of field this is 1029 if (field instanceof DataField) { 1030 String propertyPath = ((DataField) field).getBindingInfo().getBindingPath(); 1031 Object valueObject = null; 1032 1033 if (field.isHidden()) { 1034 return ""; 1035 } 1036 1037 // check if readable 1038 if (ObjectPropertyUtils.isReadableProperty(model, propertyPath)) { 1039 valueObject = ObjectPropertyUtils.getPropertyValue(model, propertyPath); 1040 } 1041 1042 // use object's string value 1043 if (valueObject != null && !((DataField) field).isApplyMask()) { 1044 value = valueObject.toString(); 1045 } else if (valueObject != null && ((DataField) field).isApplyMask()) { 1046 value = ((DataField) field).getMaskFormatter().maskValue(valueObject); 1047 } 1048 } else if (field instanceof ActionField) { 1049 value = ((ActionField) field).getActionLabel(); 1050 1051 // use image alt text if any 1052 if (StringUtils.isBlank(value) && ((ActionField) field).getActionImage() != null) { 1053 value = ((ActionField) field).getActionImage().getAltText(); 1054 } 1055 } else if (field instanceof LinkField) { 1056 value = ((LinkField) field).getLinkText(); 1057 } else if (field instanceof ImageField) { 1058 value = ((ImageField) field).getAltText(); 1059 } else if (field instanceof MessageField && ((MessageField) field).getMessage() != null) { 1060 value = ((MessageField) field).getMessage().getMessageText(); 1061 } else if (field instanceof SpaceField) { 1062 value = ""; 1063 } else if (field instanceof FieldGroup 1064 && ((FieldGroup) field).getGroup() != null 1065 && ((FieldGroup) field).getGroup().getItems() != null 1066 && !((FieldGroup) field).getGroup().getItems().isEmpty()) { 1067 // using first components type for assumed value 1068 Component firstComponent = ((FieldGroup) field).getGroup().getItems().get(0); 1069 1070 // check first component type to extract value 1071 if (firstComponent != null && firstComponent instanceof Field) { 1072 value = getSimpleFieldValue(model, field); 1073 } else if (firstComponent instanceof Action && StringUtils.isNotBlank( 1074 ((Action) firstComponent).getActionLabel())) { 1075 value = ((Action) firstComponent).getActionLabel(); 1076 } else if (firstComponent instanceof Action && ((Action) firstComponent).getActionImage() != null) { 1077 value = ((Action) firstComponent).getActionImage().getAltText(); 1078 } else if (firstComponent instanceof Link) { 1079 value = ((Link) firstComponent).getLinkText(); 1080 } else if (firstComponent instanceof Image) { 1081 value = ((Image) firstComponent).getAltText(); 1082 } else if (firstComponent instanceof org.kuali.rice.krad.uif.element.Message) { 1083 value = ((org.kuali.rice.krad.uif.element.Message) firstComponent).getMessageText(); 1084 } else { 1085 value = null; 1086 } 1087 } 1088 1089 return value; 1090 } 1091 1092 /** 1093 * Helper method to change common characters into HTML attribute safe characters 1094 * 1095 * @param message the string to convert 1096 * @return the converted string with quotes, sing quotes, and slash replaced 1097 */ 1098 public static String convertToHTMLAttributeSafeString(String message) { 1099 if (StringUtils.isBlank(message)) { 1100 return message; 1101 } 1102 1103 if (message.contains("\"")) { 1104 message = message.replace("\"", """); 1105 } 1106 if (message.contains("'")) { 1107 message = message.replace("'", "'"); 1108 } 1109 if (message.contains("\\")) { 1110 message = message.replace("\\", "\"); 1111 } 1112 1113 return message; 1114 } 1115 1116 /** 1117 * Get the rowCss for the line specified, by evaluating the conditionalRowCssClasses map for that row 1118 * 1119 * @param conditionalRowCssClasses the conditionalRowCssClass map, where key is the condition and value is 1120 * the class(es) 1121 * @param lineIndex the line/row index 1122 * @param isOdd true if the row is considered odd 1123 * @param lineContext the lineContext for expressions, pass null if not applicable 1124 * @param expressionEvaluator the expressionEvaluator, pass null if not applicable 1125 * @return row csss class String for the class attribute of this row 1126 */ 1127 public static String generateRowCssClassString(Map<String, String> conditionalRowCssClasses, int lineIndex, 1128 boolean isOdd, Map<String, Object> lineContext, ExpressionEvaluator expressionEvaluator) { 1129 String rowCss = ""; 1130 if (conditionalRowCssClasses == null || conditionalRowCssClasses.isEmpty()) { 1131 return rowCss; 1132 } 1133 1134 for (String cssRule : conditionalRowCssClasses.keySet()) { 1135 if (cssRule.startsWith(UifConstants.EL_PLACEHOLDER_PREFIX) && lineContext != null && 1136 expressionEvaluator != null) { 1137 String outcome = expressionEvaluator.evaluateExpressionTemplate(lineContext, cssRule); 1138 if (outcome != null && Boolean.parseBoolean(outcome)) { 1139 rowCss = rowCss + " " + conditionalRowCssClasses.get(cssRule); 1140 } 1141 } else if (cssRule.equals(UifConstants.RowSelection.ALL)) { 1142 rowCss = rowCss + " " + conditionalRowCssClasses.get(cssRule); 1143 } else if (cssRule.equals(UifConstants.RowSelection.EVEN) && !isOdd) { 1144 rowCss = rowCss + " " + conditionalRowCssClasses.get(cssRule); 1145 } else if (cssRule.equals(UifConstants.RowSelection.ODD) && isOdd) { 1146 rowCss = rowCss + " " + conditionalRowCssClasses.get(cssRule); 1147 } else if (StringUtils.isNumeric(cssRule) && (lineIndex + 1) == Integer.parseInt(cssRule)) { 1148 rowCss = rowCss + " " + conditionalRowCssClasses.get(cssRule); 1149 } 1150 } 1151 1152 rowCss = StringUtils.removeStart(rowCss, " "); 1153 1154 return rowCss; 1155 } 1156 }