001/** 002 * Copyright 2005-2016 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 */ 016package org.kuali.rice.kns.util; 017 018import org.apache.commons.lang.StringEscapeUtils; 019import org.apache.commons.lang.StringUtils; 020import org.apache.log4j.Level; 021import org.apache.log4j.Logger; 022import org.apache.struts.Globals; 023import org.apache.struts.action.ActionForm; 024import org.apache.struts.action.ActionMapping; 025import org.apache.struts.action.ActionServletWrapper; 026import org.apache.struts.upload.CommonsMultipartRequestHandler; 027import org.apache.struts.upload.FormFile; 028import org.apache.struts.upload.MultipartRequestHandler; 029import org.apache.struts.upload.MultipartRequestWrapper; 030import org.kuali.rice.core.api.CoreApiServiceLocator; 031import org.kuali.rice.core.api.config.property.ConfigContext; 032import org.kuali.rice.core.api.config.property.ConfigurationService; 033import org.kuali.rice.core.api.util.RiceKeyConstants; 034import org.kuali.rice.kew.api.action.ActionRequest; 035import org.kuali.rice.kew.api.action.RecipientType; 036import org.kuali.rice.kim.api.role.Role; 037import org.kuali.rice.kim.api.services.KimApiServiceLocator; 038import org.kuali.rice.kns.datadictionary.KNSDocumentEntry; 039import org.kuali.rice.kns.datadictionary.MaintenanceDocumentEntry; 040import org.kuali.rice.kns.document.authorization.DocumentAuthorizer; 041import org.kuali.rice.kns.service.KNSServiceLocator; 042import org.kuali.rice.kns.web.struts.action.KualiMultipartRequestHandler; 043import org.kuali.rice.kns.web.struts.form.KualiDocumentFormBase; 044import org.kuali.rice.kns.web.struts.form.KualiForm; 045import org.kuali.rice.kns.web.struts.form.KualiMaintenanceForm; 046import org.kuali.rice.kns.web.struts.form.pojo.PojoFormBase; 047import org.kuali.rice.kns.web.ui.Field; 048import org.kuali.rice.kns.web.ui.Row; 049import org.kuali.rice.kns.web.ui.Section; 050import org.kuali.rice.krad.datadictionary.AttributeDefinition; 051import org.kuali.rice.krad.datadictionary.AttributeSecurity; 052import org.kuali.rice.krad.datadictionary.DataDictionary; 053import org.kuali.rice.krad.datadictionary.DataDictionaryEntryBase; 054import org.kuali.rice.krad.datadictionary.mask.MaskFormatter; 055import org.kuali.rice.krad.document.Document; 056import org.kuali.rice.krad.exception.ValidationException; 057import org.kuali.rice.krad.service.KRADServiceLocatorWeb; 058import org.kuali.rice.krad.util.GlobalVariables; 059import org.kuali.rice.krad.util.KRADConstants; 060import org.kuali.rice.krad.util.MessageMap; 061import org.kuali.rice.krad.util.ObjectUtils; 062 063import javax.servlet.ServletException; 064import javax.servlet.http.HttpServletRequest; 065import javax.servlet.http.HttpServletResponse; 066import javax.servlet.http.HttpSession; 067import javax.servlet.jsp.PageContext; 068import java.io.ByteArrayOutputStream; 069import java.io.IOException; 070import java.io.InputStream; 071import java.io.OutputStream; 072import java.net.URI; 073import java.net.URISyntaxException; 074import java.util.Arrays; 075import java.util.Collection; 076import java.util.Enumeration; 077import java.util.HashMap; 078import java.util.Hashtable; 079import java.util.Iterator; 080import java.util.LinkedHashMap; 081import java.util.List; 082import java.util.Map; 083import java.util.Set; 084import java.util.regex.Matcher; 085import java.util.regex.Pattern; 086 087/** 088 * General helper methods for handling requests. 089 */ 090public class WebUtils { 091 private static final Logger LOG = Logger.getLogger(WebUtils.class); 092 093 private static final String IMAGE_COORDINATE_CLICKED_X_EXTENSION = ".x"; 094 private static final String IMAGE_COORDINATE_CLICKED_Y_EXTENSION = ".y"; 095 096 private static final String APPLICATION_IMAGE_URL_PROPERTY_PREFIX = "application.custom.image.url"; 097 private static final String DEFAULT_IMAGE_URL_PROPERTY_NAME = "kr.externalizable.images.url"; 098 099 /** 100 * Prefixes indicating an absolute url 101 */ 102 private static final String[] SCHEMES = { "http://", "https://" }; 103 104 /** 105 * A request attribute name that indicates that a 106 * {@link org.kuali.rice.kns.exception.FileUploadLimitExceededException} has already been thrown for the 107 * request. 108 */ 109 public static final String FILE_UPLOAD_LIMIT_EXCEEDED_EXCEPTION_ALREADY_THROWN = "fileUploadLimitExceededExceptionAlreadyThrown"; 110 111 private static ConfigurationService configurationService; 112 113 /** 114 * Checks for methodToCall parameter, and picks off the value using set dot 115 * notation. Handles the problem of image submits. 116 * 117 * @param request 118 * @return methodToCall String 119 */ 120 public static String parseMethodToCall(ActionForm form, HttpServletRequest request) { 121 String methodToCall = null; 122 123 // check if is specified cleanly 124 if (StringUtils.isNotBlank(request.getParameter(KRADConstants.DISPATCH_REQUEST_PARAMETER))) { 125 if (form instanceof KualiForm 126 && !((KualiForm) form).shouldMethodToCallParameterBeUsed(KRADConstants.DISPATCH_REQUEST_PARAMETER, 127 request.getParameter(KRADConstants.DISPATCH_REQUEST_PARAMETER), request)) { 128 throw new RuntimeException("Cannot verify that the methodToCall should be " 129 + request.getParameter(KRADConstants.DISPATCH_REQUEST_PARAMETER)); 130 } 131 methodToCall = request.getParameter(KRADConstants.DISPATCH_REQUEST_PARAMETER); 132 // include .x at the end of the parameter to make it consistent w/ 133 // other parameters 134 request.setAttribute(KRADConstants.METHOD_TO_CALL_ATTRIBUTE, KRADConstants.DISPATCH_REQUEST_PARAMETER + "." 135 + methodToCall + IMAGE_COORDINATE_CLICKED_X_EXTENSION); 136 } 137 138 /** 139 * The reason why we are checking for a ".x" at the end of the parameter 140 * name: It is for the image names that in addition to sending the form 141 * data, the web browser sends the x,y coordinate of where the user 142 * clicked on the image. If the image input is not given a name then the 143 * browser sends the x and y coordinates as the "x" and "y" input 144 * fields. If the input image does have a name, the x and y coordinates 145 * are sent using the format name.x and name.y. 146 */ 147 if (methodToCall == null) { 148 // iterate through parameters looking for methodToCall 149 for (Enumeration i = request.getParameterNames(); i.hasMoreElements();) { 150 String parameterName = (String) i.nextElement(); 151 152 // check if the parameter name is a specifying the methodToCall 153 if (isMethodToCall(parameterName)) { 154 methodToCall = getMethodToCallSettingAttribute(form, request, parameterName); 155 break; 156 } 157 else { 158 // KULRICE-1218: Check if the parameter's values match (not 159 // just the name) 160 for (String value : request.getParameterValues(parameterName)) { 161 // adding period to startsWith check - don't want to get 162 // confused with methodToCallFoobar 163 if (isMethodToCall(value)) { 164 methodToCall = getMethodToCallSettingAttribute(form, request, value); 165 // why is there not a break outer loop here? 166 } 167 } 168 } 169 } 170 } 171 172 return methodToCall; 173 } 174 175 /** 176 * Checks if a string signifies a methodToCall string 177 * 178 * @param string 179 * the string to check 180 * @return true if is a methodToCall 181 */ 182 private static boolean isMethodToCall(String string) { 183 // adding period to startsWith check - don't want to get confused with 184 // methodToCallFoobar 185 return string.startsWith(KRADConstants.DISPATCH_REQUEST_PARAMETER + "."); 186 } 187 188 /** 189 * Parses out the methodToCall command and also sets the request attribute 190 * for the methodToCall. 191 * 192 * @param form 193 * the ActionForm 194 * @param request 195 * the request to set the attribute on 196 * @param string 197 * the methodToCall string 198 * @return the methodToCall command 199 */ 200 private static String getMethodToCallSettingAttribute(ActionForm form, HttpServletRequest request, String string) { 201 202 if (form instanceof KualiForm 203 && !((KualiForm) form).shouldMethodToCallParameterBeUsed(string, request.getParameter(string), request)) { 204 throw new RuntimeException("Cannot verify that the methodToCall should be " + string); 205 } 206 // always adding a coordinate even if not an image 207 final String attributeValue = endsWithCoordinates(string) ? string : string 208 + IMAGE_COORDINATE_CLICKED_X_EXTENSION; 209 final String methodToCall = StringUtils.substringBetween(attributeValue, 210 KRADConstants.DISPATCH_REQUEST_PARAMETER + ".", "."); 211 request.setAttribute(KRADConstants.METHOD_TO_CALL_ATTRIBUTE, attributeValue); 212 return methodToCall; 213 } 214 215 /** 216 * Iterates through and logs (at the given level) all attributes and 217 * parameters of the given request onto the given Logger 218 * 219 * @param request 220 * @param logger 221 */ 222 public static void logRequestContents(Logger logger, Level level, HttpServletRequest request) { 223 if (logger.isEnabledFor(level)) { 224 logger.log(level, "--------------------"); 225 logger.log(level, "HttpRequest attributes:"); 226 for (Enumeration e = request.getAttributeNames(); e.hasMoreElements();) { 227 String attrName = (String) e.nextElement(); 228 Object attrValue = request.getAttribute(attrName); 229 230 if (attrValue.getClass().isArray()) { 231 logCollection(logger, level, attrName, Arrays.asList((Object[]) attrValue)); 232 } 233 else if (attrValue instanceof Collection) { 234 logCollection(logger, level, attrName, (Collection) attrValue); 235 } 236 else if (attrValue instanceof Map) { 237 logMap(logger, level, attrName, (Map) attrValue); 238 } 239 else { 240 logObject(logger, level, attrName, attrValue); 241 } 242 } 243 244 logger.log(level, "--------------------"); 245 logger.log(level, "HttpRequest parameters:"); 246 for (Enumeration i = request.getParameterNames(); i.hasMoreElements();) { 247 String paramName = (String) i.nextElement(); 248 String[] paramValues = (String[]) request.getParameterValues(paramName); 249 250 logArray(logger, level, paramName, paramValues); 251 } 252 253 logger.log(level, "--------------------"); 254 } 255 } 256 257 private static void logArray(Logger logger, Level level, String arrayName, Object[] array) { 258 StringBuffer value = new StringBuffer("["); 259 for (int i = 0; i < array.length; ++i) { 260 if (i > 0) { 261 value.append(","); 262 } 263 value.append(array[i]); 264 } 265 value.append("]"); 266 267 logThing(logger, level, arrayName, value); 268 } 269 270 private static void logCollection(Logger logger, Level level, String collectionName, Collection c) { 271 StringBuffer value = new StringBuffer("{"); 272 for (Iterator i = c.iterator(); i.hasNext();) { 273 value.append(i.next()); 274 if (i.hasNext()) { 275 value.append(","); 276 } 277 } 278 value.append("}"); 279 280 logThing(logger, level, collectionName, value); 281 } 282 283 private static void logMap(Logger logger, Level level, String mapName, Map m) { 284 StringBuffer value = new StringBuffer("{"); 285 for (Iterator i = m.entrySet().iterator(); i.hasNext();) { 286 Map.Entry e = (Map.Entry) i.next(); 287 value.append("('" + e.getKey() + "','" + e.getValue() + "')"); 288 } 289 value.append("}"); 290 291 logThing(logger, level, mapName, value); 292 } 293 294 private static void logObject(Logger logger, Level level, String objectName, Object o) { 295 logThing(logger, level, objectName, "'" + o + "'"); 296 } 297 298 private static void logThing(Logger logger, Level level, String thingName, Object thing) { 299 logger.log(level, " '" + thingName + "' => " + thing); 300 } 301 302 /** 303 * A file that is not of type text/plain or text/html can be output through 304 * the response using this method. 305 * 306 * @param response 307 * @param contentType 308 * @param byteArrayOutputStream 309 * @param fileName 310 */ 311 public static void saveMimeOutputStreamAsFile(HttpServletResponse response, String contentType, 312 ByteArrayOutputStream byteArrayOutputStream, String fileName) throws IOException { 313 314 // If there are quotes in the name, we should replace them to avoid issues. 315 // The filename will be wrapped with quotes below when it is set in the header 316 String updateFileName; 317 if(fileName.contains("\"")) { 318 updateFileName = fileName.replaceAll("\"", ""); 319 } else { 320 updateFileName = fileName; 321 } 322 323 // set response 324 response.setContentType(contentType); 325 response.setHeader("Content-disposition", "attachment; filename=\"" + updateFileName + "\""); 326 response.setHeader("Expires", "0"); 327 response.setHeader("Cache-Control", "must-revalidate, post-check=0, pre-check=0"); 328 response.setHeader("Pragma", "public"); 329 response.setContentLength(byteArrayOutputStream.size()); 330 331 // write to output 332 OutputStream outputStream = response.getOutputStream(); 333 byteArrayOutputStream.writeTo(response.getOutputStream()); 334 outputStream.flush(); 335 outputStream.close(); 336 } 337 338 /** 339 * A file that is not of type text/plain or text/html can be output through 340 * the response using this method. 341 * 342 * @param response 343 * @param contentType 344 * @param inStream 345 * @param fileName 346 */ 347 public static void saveMimeInputStreamAsFile(HttpServletResponse response, String contentType, 348 InputStream inStream, String fileName, int fileSize) throws IOException { 349 350 // If there are quotes in the name, we should replace them to avoid issues. 351 // The filename will be wrapped with quotes below when it is set in the header 352 String updateFileName; 353 if(fileName.contains("\"")) { 354 updateFileName = fileName.replaceAll("\"", ""); 355 } else { 356 updateFileName = fileName; 357 } 358 359 // set response 360 response.setContentType(contentType); 361 response.setHeader("Content-disposition", "attachment; filename=\"" + updateFileName + "\""); 362 response.setHeader("Expires", "0"); 363 response.setHeader("Cache-Control", "must-revalidate, post-check=0, pre-check=0"); 364 response.setHeader("Pragma", "public"); 365 response.setContentLength(fileSize); 366 367 // write to output 368 OutputStream out = response.getOutputStream(); 369 while (inStream.available() > 0) { 370 out.write(inStream.read()); 371 } 372 out.flush(); 373 } 374 375 /** 376 * JSTL function to return the tab state of the tab from the form. 377 * 378 * @param form 379 * @param tabKey 380 * @return 381 */ 382 public static String getTabState(KualiForm form, String tabKey) { 383 return form.getTabState(tabKey); 384 } 385 386 public static void incrementTabIndex(KualiForm form, String tabKey) { 387 form.incrementTabIndex(); 388 } 389 390 /** 391 * Attempts to reopen sub tabs which would have been closed for inactive records 392 * 393 * @param sections the list of Sections whose rows and fields to set the open tab state on 394 * @param tabStates the map of tabKey->tabState. This map will be modified to set entries to "OPEN" 395 * @param collectionName the name of the collection reopening 396 */ 397 public static void reopenInactiveRecords(List<Section> sections, Map<String, String> tabStates, String collectionName) { 398 for (Section section : sections) { 399 for (Row row: section.getRows()) { 400 for (Field field : row.getFields()) { 401 if (field != null) { 402 if (Field.CONTAINER.equals(field.getFieldType()) && StringUtils.startsWith(field.getContainerName(), collectionName)) { 403 final String tabKey = WebUtils.generateTabKey(FieldUtils.generateCollectionSubTabName(field)); 404 tabStates.put(tabKey, KualiForm.TabState.OPEN.name()); 405 } 406 } 407 } 408 } 409 } 410 } 411 412 /** 413 * Generates a String from the title that can be used as a Map key. 414 * 415 * @param tabTitle 416 * @return 417 */ 418 public static String generateTabKey(String tabTitle) { 419 String key = ""; 420 if (!StringUtils.isBlank(tabTitle)) { 421 key = tabTitle.replaceAll("\\W", ""); 422 // if (key.length() > 25) { 423 // key = key.substring(0, 24); 424 // } 425 } 426 427 return key; 428 } 429 430 public static void getMultipartParameters(HttpServletRequest request, ActionServletWrapper servletWrapper, 431 ActionForm form, ActionMapping mapping) { 432 Map params = new HashMap(); 433 434 // Get the ActionServletWrapper from the form bean 435 // ActionServletWrapper servletWrapper = getServletWrapper(); 436 437 try { 438 CommonsMultipartRequestHandler multipartHandler = new CommonsMultipartRequestHandler(); 439 if (multipartHandler != null) { 440 // Set servlet and mapping info 441 if (servletWrapper != null) { 442 // from pojoformbase 443 // servlet only affects tempdir on local disk 444 servletWrapper.setServletFor(multipartHandler); 445 } 446 multipartHandler.setMapping((ActionMapping) request.getAttribute(Globals.MAPPING_KEY)); 447 // Initialize multipart request class handler 448 multipartHandler.handleRequest(request); 449 450 Collection<FormFile> files = multipartHandler.getFileElements().values(); 451 Enumeration keys = multipartHandler.getFileElements().keys(); 452 453 while (keys.hasMoreElements()) { 454 Object key = keys.nextElement(); 455 FormFile file = (FormFile) multipartHandler.getFileElements().get(key); 456 long maxSize = WebUtils.getMaxUploadSize(form); 457 if (LOG.isDebugEnabled()) { 458 LOG.debug(file.getFileSize()); 459 } 460 if (maxSize > 0 && Long.parseLong(file.getFileSize() + "") > maxSize) { 461 462 GlobalVariables.getMessageMap().putError(key.toString(), 463 RiceKeyConstants.ERROR_UPLOADFILE_SIZE, 464 new String[] { file.getFileName(), Long.toString(maxSize) }); 465 466 } 467 } 468 469 // get file elements for kualirequestprocessor 470 if (servletWrapper == null) { 471 request.setAttribute(KRADConstants.UPLOADED_FILE_REQUEST_ATTRIBUTE_KEY, 472 getFileParametersForMultipartRequest(request, multipartHandler)); 473 } 474 } 475 } 476 catch (ServletException e) { 477 throw new ValidationException("unable to handle multipart request " + e.getMessage(), e); 478 } 479 } 480 481 public static long getMaxUploadSize(ActionForm form) { 482 long max = 0L; 483 KualiMultipartRequestHandler multipartHandler = new KualiMultipartRequestHandler(); 484 if (form instanceof PojoFormBase) { 485 max = multipartHandler.calculateMaxUploadSizeToMaxOfList(((PojoFormBase) form).getMaxUploadSizes()); 486 } 487 if (LOG.isDebugEnabled()) { 488 LOG.debug("Max File Upload Size: " + max); 489 } 490 return max; 491 } 492 493 private static Map getFileParametersForMultipartRequest(HttpServletRequest request, 494 MultipartRequestHandler multipartHandler) { 495 Map parameters = new HashMap(); 496 Hashtable elements = multipartHandler.getFileElements(); 497 Enumeration e = elements.keys(); 498 while (e.hasMoreElements()) { 499 String key = (String) e.nextElement(); 500 parameters.put(key, elements.get(key)); 501 } 502 503 if (request instanceof MultipartRequestWrapper) { 504 request = (HttpServletRequest) ((MultipartRequestWrapper) request).getRequest(); 505 e = request.getParameterNames(); 506 while (e.hasMoreElements()) { 507 String key = (String) e.nextElement(); 508 parameters.put(key, request.getParameterValues(key)); 509 } 510 } 511 else { 512 LOG.debug("Gathering multipart parameters for unwrapped request"); 513 } 514 return parameters; 515 } 516 517 // end multipart 518 519 public static void registerEditableProperty(PojoFormBase form, String editablePropertyName) { 520 form.registerEditableProperty(editablePropertyName); 521 } 522 523 public static boolean isDocumentSession(Document document, PojoFormBase docForm) { 524 boolean sessionDoc = document instanceof org.kuali.rice.krad.document.SessionDocument; 525 boolean dataDictionarySessionDoc = false; 526 if (!sessionDoc) { 527 DataDictionary dataDictionary = KRADServiceLocatorWeb.getDataDictionaryService().getDataDictionary(); 528 if (docForm instanceof KualiMaintenanceForm) { 529 KualiMaintenanceForm maintenanceForm = (KualiMaintenanceForm) docForm; 530 if (dataDictionary != null) { 531 if (maintenanceForm.getDocTypeName() != null) { 532 MaintenanceDocumentEntry maintenanceDocumentEntry = (MaintenanceDocumentEntry) dataDictionary.getDocumentEntry(maintenanceForm.getDocTypeName()); 533 dataDictionarySessionDoc = maintenanceDocumentEntry.isSessionDocument(); 534 } 535 } 536 } 537 else { 538 if (document != null && dataDictionary != null) { 539 KNSDocumentEntry documentEntry = (KNSDocumentEntry) dataDictionary.getDocumentEntry(document.getClass().getName()); 540 dataDictionarySessionDoc = documentEntry.isSessionDocument(); 541 } 542 } 543 } 544 return sessionDoc || dataDictionarySessionDoc; 545 } 546 547 public static boolean isFormSessionDocument(PojoFormBase form) { 548 Document document = null; 549 if (KualiDocumentFormBase.class.isAssignableFrom(form.getClass())) { 550 KualiDocumentFormBase docForm = (KualiDocumentFormBase) form; 551 document = docForm.getDocument(); 552 } 553 return isDocumentSession(document, form); 554 } 555 556 public static String KEY_KUALI_FORM_IN_SESSION = "KualiForm"; 557 558 public static ActionForm getKualiForm(PageContext pageContext) { 559 return getKualiForm((HttpServletRequest) pageContext.getRequest()); 560 } 561 562 public static ActionForm getKualiForm(HttpServletRequest request) { 563 if (request.getAttribute(KEY_KUALI_FORM_IN_SESSION) != null) { 564 return (ActionForm) request.getAttribute(KEY_KUALI_FORM_IN_SESSION); 565 } 566 else { 567 final HttpSession session = request.getSession(false); 568 return session != null ? (ActionForm) session.getAttribute(KEY_KUALI_FORM_IN_SESSION) : null; 569 } 570 } 571 572 public static boolean isPropertyEditable(Set<String> editableProperties, String propertyName) { 573 if (LOG.isDebugEnabled()) { 574 LOG.debug("isPropertyEditable(" + propertyName + ")"); 575 } 576 577 boolean returnVal = editableProperties == null 578 || editableProperties.contains(propertyName) 579 || (getIndexOfCoordinateExtension(propertyName) == -1 ? false : editableProperties 580 .contains(propertyName.substring(0, getIndexOfCoordinateExtension(propertyName)))); 581 if (!returnVal) { 582 if (LOG.isDebugEnabled()) { 583 LOG.debug("isPropertyEditable(" + propertyName + ") == false / editableProperties: " 584 + editableProperties); 585 } 586 } 587 return returnVal; 588 } 589 590 public static boolean endsWithCoordinates(String parameter) { 591 return parameter.endsWith(WebUtils.IMAGE_COORDINATE_CLICKED_X_EXTENSION) 592 || parameter.endsWith(WebUtils.IMAGE_COORDINATE_CLICKED_Y_EXTENSION); 593 } 594 595 public static int getIndexOfCoordinateExtension(String parameter) { 596 int indexOfCoordinateExtension = parameter.lastIndexOf(WebUtils.IMAGE_COORDINATE_CLICKED_X_EXTENSION); 597 if (indexOfCoordinateExtension == -1) { 598 indexOfCoordinateExtension = parameter.lastIndexOf(WebUtils.IMAGE_COORDINATE_CLICKED_Y_EXTENSION); 599 } 600 return indexOfCoordinateExtension; 601 } 602 603 public static boolean isInquiryHiddenField(String className, String fieldName, Object formObject, String propertyName) { 604 boolean isHidden = false; 605 String hiddenInquiryFields = getKualiConfigurationService().getPropertyValueAsString(className + ".hidden"); 606 if (StringUtils.isEmpty(hiddenInquiryFields)) { 607 return isHidden; 608 } 609 List hiddenFields = Arrays.asList(hiddenInquiryFields.replaceAll(" ", "").split(",")); 610 if (hiddenFields.contains(fieldName.trim())) { 611 isHidden = true; 612 } 613 return isHidden; 614 } 615 616 public static boolean isHiddenKimObjectType(String type, String configParameter) { 617 boolean hideType = false; 618 String hiddenTypes = getKualiConfigurationService().getPropertyValueAsString(configParameter); 619 if (StringUtils.isEmpty(hiddenTypes)) { 620 return hideType; 621 } 622 List hiddenTypeValues = Arrays.asList(hiddenTypes.replaceAll(" ", "").split(",")); 623 if (hiddenTypeValues.contains(type.trim())) { 624 hideType = true; 625 } 626 return hideType; 627 } 628 629 public static String getFullyMaskedValue(String className, String fieldName, Object formObject, String propertyName) { 630 String displayMaskValue = null; 631 Object propertyValue = ObjectUtils.getPropertyValue(formObject, propertyName); 632 633 DataDictionaryEntryBase entry = (DataDictionaryEntryBase) KRADServiceLocatorWeb.getDataDictionaryService() 634 .getDataDictionary().getDictionaryObjectEntry(className); 635 AttributeDefinition a = entry.getAttributeDefinition(fieldName); 636 637 AttributeSecurity attributeSecurity = a.getAttributeSecurity(); 638 if (attributeSecurity != null && attributeSecurity.isMask()) { 639 MaskFormatter maskFormatter = attributeSecurity.getMaskFormatter(); 640 displayMaskValue = maskFormatter.maskValue(propertyValue); 641 642 } 643 return displayMaskValue; 644 } 645 646 public static String getPartiallyMaskedValue(String className, String fieldName, Object formObject, 647 String propertyName) { 648 String displayMaskValue = null; 649 Object propertyValue = ObjectUtils.getPropertyValue(formObject, propertyName); 650 651 DataDictionaryEntryBase entry = (DataDictionaryEntryBase) KRADServiceLocatorWeb.getDataDictionaryService() 652 .getDataDictionary().getDictionaryObjectEntry(className); 653 AttributeDefinition a = entry.getAttributeDefinition(fieldName); 654 655 AttributeSecurity attributeSecurity = a.getAttributeSecurity(); 656 if (attributeSecurity != null && attributeSecurity.isPartialMask()) { 657 MaskFormatter partialMaskFormatter = attributeSecurity.getPartialMaskFormatter(); 658 displayMaskValue = partialMaskFormatter.maskValue(propertyValue); 659 660 } 661 return displayMaskValue; 662 } 663 664 public static boolean canFullyUnmaskField(String businessObjectClassName, String fieldName, KualiForm form) { 665 Class businessObjClass = null; 666 try { 667 businessObjClass = Class.forName(businessObjectClassName); 668 } 669 catch (Exception e) { 670 throw new RuntimeException("Unable to resolve class name: " + businessObjectClassName); 671 } 672 if (form instanceof KualiDocumentFormBase) { 673 return KNSServiceLocator.getBusinessObjectAuthorizationService().canFullyUnmaskField( 674 GlobalVariables.getUserSession().getPerson(), businessObjClass, fieldName, 675 ((KualiDocumentFormBase) form).getDocument()); 676 } 677 else { 678 return KNSServiceLocator.getBusinessObjectAuthorizationService().canFullyUnmaskField( 679 GlobalVariables.getUserSession().getPerson(), businessObjClass, fieldName, null); 680 } 681 } 682 683 public static boolean canPartiallyUnmaskField(String businessObjectClassName, String fieldName, KualiForm form) { 684 Class businessObjClass = null; 685 try { 686 businessObjClass = Class.forName(businessObjectClassName); 687 } 688 catch (Exception e) { 689 throw new RuntimeException("Unable to resolve class name: " + businessObjectClassName); 690 } 691 if (form instanceof KualiDocumentFormBase) { 692 return KNSServiceLocator.getBusinessObjectAuthorizationService().canPartiallyUnmaskField( 693 GlobalVariables.getUserSession().getPerson(), businessObjClass, fieldName, 694 ((KualiDocumentFormBase) form).getDocument()); 695 } 696 else { 697 return KNSServiceLocator.getBusinessObjectAuthorizationService().canPartiallyUnmaskField( 698 GlobalVariables.getUserSession().getPerson(), businessObjClass, fieldName, null); 699 } 700 } 701 702 public static boolean canAddNoteAttachment(Document document) { 703 boolean canViewNoteAttachment = false; 704 DocumentAuthorizer documentAuthorizer = KNSServiceLocator.getDocumentHelperService().getDocumentAuthorizer( 705 document); 706 canViewNoteAttachment = documentAuthorizer.canAddNoteAttachment(document, null, GlobalVariables 707 .getUserSession().getPerson()); 708 return canViewNoteAttachment; 709 } 710 711 public static boolean canViewNoteAttachment(Document document, String attachmentTypeCode) { 712 boolean canViewNoteAttachment = false; 713 DocumentAuthorizer documentAuthorizer = KNSServiceLocator.getDocumentHelperService().getDocumentAuthorizer( 714 document); 715 canViewNoteAttachment = documentAuthorizer.canViewNoteAttachment(document, attachmentTypeCode, GlobalVariables 716 .getUserSession().getPerson()); 717 return canViewNoteAttachment; 718 } 719 720 public static boolean canDeleteNoteAttachment(Document document, String attachmentTypeCode, 721 String authorUniversalIdentifier) { 722 boolean canDeleteNoteAttachment = false; 723 DocumentAuthorizer documentAuthorizer = KNSServiceLocator.getDocumentHelperService().getDocumentAuthorizer( 724 document); 725 canDeleteNoteAttachment = documentAuthorizer.canDeleteNoteAttachment(document, attachmentTypeCode, "false", 726 GlobalVariables.getUserSession().getPerson()); 727 if (canDeleteNoteAttachment) { 728 return canDeleteNoteAttachment; 729 } 730 else { 731 canDeleteNoteAttachment = documentAuthorizer.canDeleteNoteAttachment(document, attachmentTypeCode, "true", 732 GlobalVariables.getUserSession().getPerson()); 733 if (canDeleteNoteAttachment 734 && !authorUniversalIdentifier.equals(GlobalVariables.getUserSession().getPerson().getPrincipalId())) { 735 canDeleteNoteAttachment = false; 736 } 737 } 738 return canDeleteNoteAttachment; 739 } 740 741 public static void reuseErrorMapFromPreviousRequest(KualiDocumentFormBase kualiDocumentFormBase) { 742 if (kualiDocumentFormBase.getMessageMapFromPreviousRequest() == null) { 743 LOG.error("Error map from previous request is null!"); 744 return; 745 } 746 MessageMap errorMapFromGlobalVariables = GlobalVariables.getMessageMap(); 747 if (kualiDocumentFormBase.getMessageMapFromPreviousRequest() == errorMapFromGlobalVariables) { 748 // if we've switched them already, then return early and do nothing 749 return; 750 } 751 if (!errorMapFromGlobalVariables.hasNoErrors()) { 752 throw new RuntimeException("Cannot replace error map because it is not empty"); 753 } 754 GlobalVariables.setMessageMap(kualiDocumentFormBase.getMessageMapFromPreviousRequest()); 755 GlobalVariables.getMessageMap().clearErrorPath(); 756 } 757 758 /** 759 * Excapes out HTML to prevent XSS attacks, and replaces the following 760 * strings to allow for a limited set of HTML tags 761 * 762 * <li>[X] and [/X], where X represents any 1 or 2 letter string may be used 763 * to specify the equivalent tag in HTML (i.e. <X> and </X>) <li> 764 * [font COLOR], where COLOR represents any valid html color (i.e. color 765 * name or hexcode preceeded by #) will be filtered into <font 766 * color="COLOR"/> <li>[/font] will be filtered into </font> <li> 767 * [table CLASS], where CLASS gives the style class to use, will be filter 768 * into <table class="CLASS"/> <li>[/table] will be filtered into 769 * </table> <li>[td CLASS], where CLASS gives the style class to use, 770 * will be filter into <td class="CLASS"/> 771 * 772 * @param inputString 773 * @return 774 */ 775 public static String filterHtmlAndReplaceRiceMarkup(String inputString) { 776 String outputString = StringEscapeUtils.escapeHtml(inputString); 777 // string has been escaped of all <, >, and & (and other characters) 778 779 Map<String, String> findAndReplacePatterns = new LinkedHashMap<String, String>(); 780 781 // now replace our rice custom markup into html 782 783 // DON'T ALLOW THE SCRIPT TAG OR ARBITRARY IMAGES/URLS/ETC. THROUGH 784 785 //strip out instances where javascript precedes a URL 786 findAndReplacePatterns.put("\\[a ((javascript|JAVASCRIPT|JavaScript).+)\\]", ""); 787 //turn passed a href value into appropriate tag 788 findAndReplacePatterns.put("\\[a (.+)\\]", "<a href=\"$1\">"); 789 findAndReplacePatterns.put("\\[/a\\]", "</a>"); 790 791 // filter any one character tags 792 findAndReplacePatterns.put("\\[([A-Za-z])\\]", "<$1>"); 793 findAndReplacePatterns.put("\\[/([A-Za-z])\\]", "</$1>"); 794 // filter any two character tags 795 findAndReplacePatterns.put("\\[([A-Za-z]{2})\\]", "<$1>"); 796 findAndReplacePatterns.put("\\[/([A-Za-z]{2})\\]", "</$1>"); 797 // filter the font tag 798 findAndReplacePatterns.put("\\[font (#[0-9A-Fa-f]{1,6}|[A-Za-z]+)\\]", "<font color=\"$1\">"); 799 findAndReplacePatterns.put("\\[/font\\]", "</font>"); 800 // filter the table tag 801 findAndReplacePatterns.put("\\[table\\]", "<table>"); 802 findAndReplacePatterns.put("\\[table ([A-Za-z]+)\\]", "<table class=\"$1\">"); 803 findAndReplacePatterns.put("\\[/table\\]", "</table>"); 804 // fiter td with class 805 findAndReplacePatterns.put("\\[td ([A-Za-z]+)\\]", "<td class=\"$1\">"); 806 807 for (String findPattern : findAndReplacePatterns.keySet()) { 808 Pattern p = Pattern.compile(findPattern); 809 Matcher m = p.matcher(outputString); 810 if (m.find()) { 811 String replacePattern = findAndReplacePatterns.get(findPattern); 812 outputString = m.replaceAll(replacePattern); 813 } 814 } 815 816 return outputString; 817 } 818 819 /** 820 * Determines and returns the URL for question button images; looks first 821 * for a property "application.custom.image.url", and if that is missing, 822 * uses the image url returned by getDefaultButtonImageUrl() 823 * 824 * @param imageName 825 * the name of the image to find a button for 826 * @return the URL where question button images are located 827 */ 828 public static String getButtonImageUrl(String imageName) { 829 String buttonImageUrl = getKualiConfigurationService().getPropertyValueAsString( 830 WebUtils.APPLICATION_IMAGE_URL_PROPERTY_PREFIX + "." + imageName); 831 if (StringUtils.isBlank(buttonImageUrl)) { 832 buttonImageUrl = getDefaultButtonImageUrl(imageName); 833 } 834 return buttonImageUrl; 835 } 836 837 public static String getAttachmentImageForUrl(String contentType) { 838 String image = getKualiConfigurationService().getPropertyValueAsString(KRADConstants.ATTACHMENT_IMAGE_PREFIX + contentType); 839 if (StringUtils.isEmpty(image)) { 840 return getKualiConfigurationService().getPropertyValueAsString(KRADConstants.ATTACHMENT_IMAGE_DEFAULT); 841 } 842 return image; 843 } 844 845 /** 846 * Generates a default button image URL, in the form of: 847 * ${kr.externalizable.images.url}buttonsmall_${imageName}.gif 848 * 849 * @param imageName 850 * the image name to generate a default button name for 851 * @return the default button image url 852 */ 853 public static String getDefaultButtonImageUrl(String imageName) { 854 return getKualiConfigurationService().getPropertyValueAsString(WebUtils.DEFAULT_IMAGE_URL_PROPERTY_NAME) 855 + "buttonsmall_" + imageName + ".gif"; 856 } 857 858 /** 859 * @return an implementation of the KualiConfigurationService 860 */ 861 public static ConfigurationService getKualiConfigurationService() { 862 if (configurationService == null) { 863 configurationService = CoreApiServiceLocator.getKualiConfigurationService(); 864 } 865 return configurationService; 866 } 867 868 /** 869 * Takes a string an converts the whitespace which would be ignored in an 870 * HTML document into HTML elements so the whitespace is preserved 871 * 872 * @param startingString The string to preserve whitespace in 873 * @return A string whose whitespace has been converted to HTML elements to preserve the whitespace in an HTML document 874 */ 875 public static String preserveWhitespace(String startingString) { 876 String convertedString = startingString.replaceAll("\n", "<br />"); 877 convertedString = convertedString.replaceAll(" ", " ").replaceAll("( | )", " "); 878 return convertedString; 879 } 880 881 public static String getKimGroupDisplayName(String groupId) { 882 if(StringUtils.isBlank(groupId)) { 883 throw new IllegalArgumentException("Group ID must have a value"); 884 } 885 return KimApiServiceLocator.getGroupService().getGroup(groupId).getName(); 886 } 887 888 public static String getPrincipalDisplayName(String principalId) { 889 if(StringUtils.isBlank(principalId)) { 890 throw new IllegalArgumentException("Principal ID must have a value"); 891 } 892 if (KimApiServiceLocator.getIdentityService().getDefaultNamesForPrincipalId(principalId) == null){ 893 return ""; 894 } 895 else { 896 return KimApiServiceLocator.getIdentityService().getDefaultNamesForPrincipalId(principalId).getDefaultName().getCompositeName(); 897 } 898 } 899 900 /** 901 * Takes an {@link org.kuali.rice.kew.api.action.ActionRequest} with a recipient type of 902 * {@link org.kuali.rice.kew.api.action.RecipientType#ROLE} and returns the display name for the role. 903 * 904 * @param actionRequest the action request 905 * @return the display name for the role 906 * @throws IllegalArgumentException if the action request is null, or the recipient type is not ROLE 907 */ 908 public static String getRoleDisplayName(ActionRequest actionRequest) { 909 String result; 910 911 if(actionRequest == null) { 912 throw new IllegalArgumentException("actionRequest must be non-null"); 913 } 914 if (RecipientType.ROLE != actionRequest.getRecipientType()) { 915 throw new IllegalArgumentException("actionRequest recipient must be a Role"); 916 } 917 918 Role role = KimApiServiceLocator.getRoleService().getRole(actionRequest.getRoleName()); 919 920 if (role != null) { 921 result = role.getName(); 922 } else if (!StringUtils.isBlank(actionRequest.getQualifiedRoleNameLabel())) { 923 result = actionRequest.getQualifiedRoleNameLabel(); 924 } else { 925 result = actionRequest.getRoleName(); 926 } 927 928 return result; 929 } 930 931 /** 932 * Returns an absolute URL which is a combination of a base part plus path, 933 * or in the case that the path is already an absolute URL, the path alone 934 * @param base the url base path 935 * @param path the path to append to base 936 * @return an absolute URL representing the combination of base+path, or path alone if it is already absolute 937 */ 938 public static String toAbsoluteURL(String base, String path) { 939 boolean abs = false; 940 if (StringUtils.isBlank(path)) { 941 path = ""; 942 } else { 943 for (String scheme: SCHEMES) { 944 if (path.startsWith(scheme)) { 945 abs = true; 946 break; 947 } 948 } 949 } 950 if (abs) { 951 return path; 952 } 953 return base + path; 954 } 955 956 public static String sanitizeBackLocation(String backLocation) { 957 if(StringUtils.isBlank(backLocation)) { 958 return backLocation; 959 } 960 // if it's a relative URL then we should be good regardless 961 try { 962 URI uri = new URI(backLocation); 963 if (!uri.isAbsolute()) { 964 return backLocation; 965 } 966 } catch (URISyntaxException e) { 967 // move on to the other checks if it doesn't parse up as a URI for some reason 968 } 969 Pattern pattern = Pattern.compile(ConfigContext.getCurrentContextConfig().getProperty(KRADConstants.BACK_LOCATION_ALLOWED_REGEX)); 970 if(pattern.matcher(backLocation).matches()) { 971 return backLocation; 972 } else { 973 return ConfigContext.getCurrentContextConfig().getProperty(KRADConstants.BACK_LOCATION_DEFAULT_URL); 974 } 975 } 976 977}