Summary Table
Categories |
Total Count |
PII |
0 |
URL |
0 |
DNS |
0 |
EKL |
0 |
IP |
0 |
PORT |
0 |
VsID |
0 |
CF |
0 |
AI |
0 |
VPD |
0 |
PL |
0 |
Other |
0 |
File Content
/*
* DataSanitizer.java
* Copyright (c) 2017 Veterans Affairs.
*/
package gov.va.security.util;
import java.beans.IntrospectionException;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.log4j.Logger;
import org.owasp.esapi.ESAPI;
import org.owasp.esapi.errors.EncodingException;
import org.owasp.esapi.errors.IntrusionException;
import org.owasp.esapi.errors.ValidationException;
import org.springframework.beans.BeanWrapper;
import org.springframework.beans.PropertyAccessorFactory;
import gov.va.oneconsult.seoc.api.util.EncodeLoggerFactory;
/**
* Provides methods that sanitize the data elements within an object
*
* @author Ablevets
*
*/
public class DataSanitizer
{
private static final Logger logger = EncodeLoggerFactory.getLogger(DataSanitizer.class);
private static final String DATEFORMAT = "mm/dd/yyyy hh:mm:ss";
private static final String PATTERN_URL_SANITIZE_REGEX = "(?:(?:https?):\\/\\/|\\b(?:[a-z\\d]+\\.))(?:(?:[^\\s()<>]+|\\((?:[^\\s()<>]+|(?:\\([^\\s()<>]+\\)))?\\))+(?:\\((?:[^\\s()<>]+|(?:\\(?:[^\\s()<>]+\\)))?\\)|[^\\s`!()\\[\\]{};:'\".,<>?«»“”‘’]))?";;
private static final String PATTERN_SANITIZE_REGEX_LOOP = "[^a-zA-Z0-9()\\[\\]\\-*.?;,\\/:+&_{}\\\"' \n\r\t]*";
private static final String PATTERN_SANITIZE_REGEX = "[^a-zA-Z0-9()\\[\\]\\-*.?;,\\/:+&_{}\\\"' \n\r\t]";
private static final int MAX_FIELD_LENGTH = 5000;
private static final String VALIDATOR_PATTERN = "SafeString";
private static final Pattern sanitizeRegexPatternCaseInsensitive = Pattern.compile(PATTERN_SANITIZE_REGEX, Pattern.CASE_INSENSITIVE);
private static final Pattern sanitizeRegexInLoop = Pattern.compile(PATTERN_SANITIZE_REGEX_LOOP);
private static final Pattern sanitizeRegexSimple = Pattern.compile(PATTERN_SANITIZE_REGEX);
private static final Pattern sanitizeUrlRegex = Pattern.compile(PATTERN_URL_SANITIZE_REGEX);
/**
* Description: Return sanitized String value after applying Url and Html
* Encoders.
*
* @param value
* @return String
*/
public String sanitizeOutputData(String value)
{
if (value != null)
{
value = ESAPI.encoder().canonicalize(value);
value = applyHtmlEncoder(value);
}
return value;
}
/**
* Description: Return String value after applying whitelist sanitization
*
* @param value
* @return String
*/
public String sanitizeInputData(String value)
{
try
{
if(value != null)
{
return ESAPI.validator().getValidInput("Input", value,
VALIDATOR_PATTERN, MAX_FIELD_LENGTH, true);
}
} catch (IntrusionException e)
{
logger.error("IntrusionException from input " + value);
value = replaceUnknownData(value);
} catch (ValidationException e)
{
logger.error("ValidationException from input " + value);
value = replaceUnknownData(value);
}
return value;
}
/**
* Description:
*
* @param value
* @return String
*/
public String replaceUnknownData(String value)
{
Matcher matcher = sanitizeRegexPatternCaseInsensitive.matcher(value);
String outputStr = matcher.replaceAll("");
return outputStr;
}
/**
* Description: Html Decoder
*
* @param value
* @return String
*/
public static String applyHtmlDecoder(String value)
{
if(value != null)
{
return ESAPI.encoder().decodeForHTML(value);
}
return value;
}
/**
* Description: Apply Html Encoder only to the parts of string that doesn't
* comply with the white list characters.
*
* @param value
* @return String
*/
public String applyHtmlEncoder(String value)
{
StringBuffer sb = new StringBuffer();
String matchedText = "";
String replacementText = "";
Matcher m = sanitizeRegexInLoop.matcher(value);
Matcher preMat = sanitizeRegexSimple.matcher(value);
if(preMat.find()) {
while (m.find())
{
matchedText = m.group();
replacementText = ESAPI.encoder().encodeForHTML(matchedText);
m.appendReplacement(sb, replacementText);
}
m.appendTail(sb);
return sb.toString();
}else {
return value;
}
}
/**
* Description: Html Attribute Encoder
*
* @param value
* @return String
*/
public String applyHtmlAttributeEncoder(String value)
{
// Immune characters[,, ., -, _]
if(value!=null) {
return ESAPI.encoder().encodeForHTMLAttribute(value);
}
return value;
}
/**
* Description: Url Encoder
*
* @param value
* @return String
*/
public String applyUrlEncoder(String value)
{
StringBuffer sb = new StringBuffer();
try
{
String matchedText = "";
String replacementText = "";
Matcher m = sanitizeUrlRegex.matcher(value);
while (m.find())
{
matchedText = m.group();
replacementText = ESAPI.encoder().encodeForURL(matchedText);
m.appendReplacement(sb, replacementText);
}
m.appendTail(sb);
} catch (EncodingException e)
{
logger.error("Error occured during encoding data with URL encoder.");
}
return sb.toString();
}
/**
* Description: Cleanup the passed on object
*
* @param object
*/
public void sanitize(Object object)
{
traverseObject(object, new ArrayList<Class<?>>());
}
public Object sanitizeObject(Object object)
{
traverseObject(object, new ArrayList<Class<?>>());
return object;
}
/**
* Description: Recursive method that traverses each field and sanitizes the
* data. Avoid recursive reading of backward reference object
*
* @param obj
* @param invokingClassList - Maintaining the list of invoking classes helps in
* breaking the cyclic reference of objects
* @param marker
*/
private void traverseObject(Object obj, List<Class<?>> invokingClassList)
{
if(obj==null) {
logger.error("Object not available to traverse.");
return;
}
Class<?> cls = obj.getClass();
Field[] fields = cls.getDeclaredFields();
BeanWrapper bw;
for (Field f : fields)
{
Type type = f.getGenericType();
if (type instanceof ParameterizedType)
{
ParameterizedType pType = (ParameterizedType) type;
// Avoid recursive read on parameterized data
// Check if the invoking class happens to be same as the field type which we are
// trying to read.
if (invokingClassList != null
&& (invokingClassList.contains(pType.getActualTypeArguments()[0])))
{
continue;
}
} else
{
// Avoid recursive read
// Check if the invoking class happens to be same as the field type which we are
// trying to read.
if (invokingClassList != null && invokingClassList.contains(f.getType()))
{
continue;
}
}
try
{
PropertyDescriptor pd = new PropertyDescriptor(f.getName(), cls);
Object value = pd.getReadMethod().invoke(obj);
bw = PropertyAccessorFactory.forBeanPropertyAccess(obj);
bw.registerCustomEditor(java.util.Date.class, new DateEditor(DATEFORMAT, true));
if (value == null)
{
continue;
}
if (f.getType().isPrimitive() || value instanceof Number || value instanceof String
|| value instanceof Character || value instanceof Boolean)
{
bw.setPropertyValue(f.getName(), sanitizeOutputData(value.toString()));
} else if (value instanceof Date)
{
Date dateValue = (Date) value;
DateFormat formatter = new SimpleDateFormat(DATEFORMAT);
bw.setPropertyValue(f.getName(),
sanitizeOutputData(formatter.format(dateValue.getTime())));
} else if(value instanceof List<?>)
{
if(invokingClassList!=null) {
invokingClassList.add(cls);
((List<?>) value).forEach(v ->
{
this.traverseObject(v, invokingClassList);
});
invokingClassList.remove(cls);
}
} else if(value instanceof Set<?>)
{
if(invokingClassList!=null) {
invokingClassList.add(cls);
((Set<?>) value).forEach(v ->
{
this.traverseObject(v, invokingClassList);
});
invokingClassList.remove(cls);
}
} else
{
// Traverse fields of any other custom defined object
if(invokingClassList!=null) {
invokingClassList.add(cls);
this.traverseObject(value, invokingClassList);
invokingClassList.remove(cls);
}
}
} catch (IllegalAccessException e1)
{
logger.error("IllegalAccessException occured while retrieving the information.");
} catch (IllegalArgumentException e1)
{
logger.error("IllegalAccessException occured while retrieving the information.");
} catch (InvocationTargetException e1)
{
logger.error("IllegalAccessException occured while retrieving the information.");
} catch (IntrospectionException e1)
{
logger.error("IllegalAccessException occured while retrieving the information.");
}
}
}
}