/* * Copyright (c) 2014 EMC Corporation * All Rights Reserved */ package com.emc.storageos.systemservices.impl.validate; import static com.emc.storageos.model.property.PropertyConstants.*; import java.net.*; import java.util.Arrays; import java.util.List; import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.apache.commons.codec.binary.Base64; import com.emc.storageos.model.property.PropertiesMetadata; import com.emc.storageos.model.property.PropertyMetadata; import com.emc.storageos.svcs.errorhandling.resources.APIException; import com.emc.storageos.db.client.model.EncryptionProvider; import com.google.common.net.InetAddresses; import org.apache.commons.lang.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class PropertiesConfigurationValidator { private static final Logger _log = LoggerFactory.getLogger (PropertiesConfigurationValidator.class); private static List<String> PROPERTIES_ALLOW_EMPTY_VALUE = Arrays.asList("system_update_repo"); private PropertiesMetadata _propertiesMetadata = null; private EncryptionProvider _encryptionProvider; public static final int PORT_MAX = 65535; public static final int PORT_MIN = 1; private static enum PROP_LENGTH { MAX, MIN } private static final Pattern pattern; private static final String EMAIL_PATTERN = "^[_A-Za-z0-9-\\+]+(\\.[_A-Za-z0-9-]+)*@" + "[A-Za-z0-9-]+(\\.[A-Za-z0-9]+)*(\\.[A-Za-z]{2,})$"; // valid pattern for an individual label in a full hostname string. private static String VALID_HOST_NAME_LABEL_PATTERN = "^[a-z0-9-]+"; static { pattern = Pattern.compile(EMAIL_PATTERN); } public void setEncryptionProvider(EncryptionProvider encryptionProvider) { _encryptionProvider = encryptionProvider; } /** * validate property for updating operation. */ public String getValidPropValue(String propertyName, String propertyValue, boolean userMutated) { return getValidPropValue(propertyName, propertyValue, userMutated, false); } /** * Validate the property using the properties metadata, for property * resetting or updating. * * @param propertyName * @param propertyValue * @param userMutated * @return */ public String getValidPropValue(String propertyName, String propertyValue, boolean userMutated, boolean bReset) { Map<String, PropertyMetadata> metadataMap = getMetaData(); PropertyMetadata metaData = metadataMap.get(propertyName); if (metaData == null) { throw APIException.badRequests.propertyIsNotValid(propertyName); } // check to see if a user changed the property..and if that option is // allowed for that property. if (userMutated) { if (metaData.getUserMutable() != null ? metaData.getUserMutable() : false) { return validateProperty(propertyName, propertyValue, metaData, bReset); } else { throw APIException.badRequests.changePropertyIsNotAllowed(propertyName); } } else { return validateProperty(propertyName, propertyValue, metaData, bReset); } } /** * @param propertyName * @param propertyValue * @param metaData * @return */ private String validateProperty(String propertyName, String propertyValue, PropertyMetadata metaData,boolean bReset) { // If the property is not the encrypted string, trip the leading // and trailing whitespaces. That is because, the propertyValue // (type encryptedstring or encryptedtext) contains the plain text // at this point, so trimming it will remove the whitespaces from // what actually user entered. Where that should not be removed // until they are encrypted. The base64.encrypt() will take care // that. if (!(ENCRYPTEDSTRING.equalsIgnoreCase(metaData.getType()) || ENCRYPTEDTEXT.equalsIgnoreCase(metaData.getType()))) { // Remove leading and trailing spaces and newlines propertyValue = propertyValue.trim(); } // Minimum Length Check if (metaData.getMinLen() != null) { if (!validateLength(propertyValue, metaData.getMinLen(), PROP_LENGTH.MIN)) { throw APIException.badRequests.propertyValueLengthLessThanMinLength(propertyName, metaData.getMinLen()); } } // Maximum Length Check if (metaData.getMaxLen() != null) { if (!validateLength(propertyValue, metaData.getMaxLen(), PROP_LENGTH.MAX)) { throw APIException.badRequests.propertyValueLengthExceedMaxLength(propertyName, metaData.getMaxLen()); } } // added to allow nill for properties. If the propertyValue passed the // previous test and is null, we should return it directly if (StringUtils.isEmpty(propertyValue)) { if (bReset || allowEmptyValue(propertyName)) { return propertyValue; } } // allowed values check. If the propertyValue doesn't match the // allowable values, it should throw a exception. Because we might not // explicitly specify null as allowed value, we // have to put this logic after the null test. if (metaData.getAllowedValues() != null && metaData.getAllowedValues().length > 0) { if (!validateAllowedValues(propertyValue, metaData.getAllowedValues())) { throw APIException.badRequests.propertyValueDoesNotMatchAllowedValues(propertyName, Arrays.toString(metaData.getAllowedValues())); } } // Valid Type Check. Same reason as above, we should validate a null // value's type. if (!validateType(propertyValue, metaData)) { throw APIException.badRequests.propertyValueTypeIsInvalid(propertyName, metaData.getType()); } String validatedPropVal; // Prepare property value according to type if (STRING.equalsIgnoreCase(metaData.getType())) { validatedPropVal = propertyValue; } else if (ENCRYPTEDSTRING.equalsIgnoreCase(metaData.getType()) || ENCRYPTEDTEXT.equalsIgnoreCase(metaData.getType())) { validatedPropVal = new String(Base64.encodeBase64(_encryptionProvider.encrypt(propertyValue))); } else if (TEXT.equalsIgnoreCase(metaData.getType())) { validatedPropVal = propertyValue; validatedPropVal = validatedPropVal.replace("\n", "\\\\n").replace("\r", ""); } else if (IPLIST.equalsIgnoreCase(metaData.getType()) || EMAILLIST.equalsIgnoreCase(metaData.getType())) { validatedPropVal = formatList(propertyValue); } else { validatedPropVal = propertyValue; } return validatedPropVal; } /** * check if the property allows empty as its value * * @param propertyName * @return */ private static boolean allowEmptyValue(String propertyName) { return PROPERTIES_ALLOW_EMPTY_VALUE.contains(propertyName); } /** * Validate the property's type. * * @param propertyValue * @param metaData * @return */ private static boolean validateType(String propertyValue, PropertyMetadata metaData) { if (metaData.getType().equalsIgnoreCase(IPADDR)) { return validateIpv4Addr(propertyValue); } else if (metaData.getType().equalsIgnoreCase(IPV6ADDR)) { return validateIpv6Addr(propertyValue); } else if (metaData.getType().equalsIgnoreCase(EMAIL)) { return validateEmail(propertyValue); } else if (metaData.getType().equalsIgnoreCase(EMAILLIST)) { return validateEmailList(propertyValue); } else if (metaData.getType().equalsIgnoreCase(URL)) { return validateUrl(propertyValue); } else if (metaData.getType().equalsIgnoreCase(UINT64)) { return validateUint64(propertyValue); } else if (metaData.getType().equalsIgnoreCase(UINT32)) { return validateUint32(propertyValue); } else if (metaData.getType().equalsIgnoreCase(UINT16)) { return validateUint16(propertyValue); } else if (metaData.getType().equalsIgnoreCase(UINT8)) { return validateUint8(propertyValue); } else if (metaData.getType().equalsIgnoreCase(BOOLEAN)) { return validateBoolean(propertyValue); } else if (metaData.getType().equalsIgnoreCase(PERCENT)) { return validatePercent(propertyValue); } else if (metaData.getType().equalsIgnoreCase(HOSTNAME)) { return validateHostName(propertyValue); } else if (metaData.getType().equalsIgnoreCase(STRICTHOSTNAME)) { return validateStrictHostName(propertyValue); } else if (metaData.getType().equalsIgnoreCase(IPLIST)) { return validateIpList(propertyValue); } else if (metaData.getType().equalsIgnoreCase(IPPORTLIST)) { return validateIpPortList(propertyValue); } else if (metaData.getType().equalsIgnoreCase(ENCRYPTEDSTRING)) { return true; } else if (metaData.getType().equalsIgnoreCase(ENCRYPTEDTEXT)) { return true; } else if (metaData.getType().equalsIgnoreCase(STRING)) { return validateString(propertyValue); } else if (metaData.getType().equalsIgnoreCase(TEXT)) { return true; } return false; } /** * Validate String - return false if contains newlines * * @param string * @return Boolean */ private static boolean validateString(String string) { return !string.contains("\n"); } /** * Remove spaces and newlines from ip list * * @param iplist * @return */ private static String formatList(String list) { String[] ips = list.split(","); StringBuilder sb = new StringBuilder(); String delim = ","; for (String ip : ips) { sb.append(delim).append(ip.trim()); } return sb.substring(delim.length()); } /** * Validate a property's allowable values. * * @param propertyValue * @param acceptableValues * @return boolean */ private static boolean validateAllowedValues(String propertyValue, String[] acceptableValues) { for (String value : acceptableValues) { if (value.equals(propertyValue)) { return true; } } return false; } /** * Validate uint64 values. * * @param value * @return */ public static boolean validateUint64(String value) { try { return Long.parseLong(value) >= 0L; } catch (Exception e) { return false; } } /** * Validate uint16 values. * * @param value * @return */ public static boolean validateUint16(String value) { try { int intValue = Integer.parseInt(value); return intValue >= 0 && intValue <= 65535; } catch (Exception e) { return false; } } /** * Validate uint8 values. * * @param value * @return */ public static boolean validateUint8(String value) { try { int intValue = Integer.parseInt(value); return intValue >= 0 && intValue <= 255; } catch (Exception e) { return false; } } /** * Validate uint32 values. * * @param value * @return */ public static boolean validateUint32(String value) { try { return Integer.parseInt(value) >= 0; } catch (Exception e) { return false; } } /** * Validate boolean values. * * @param value * @return */ public static boolean validateBoolean(String value) { try { Boolean.parseBoolean(value); return true; } catch (Exception e) { return false; } } /** * Validate percent values. * * @param value * @return */ public static boolean validatePercent(String value) { try { int intValue = Integer.parseInt(value); if (intValue < 0 || intValue > 100) { return false; } } catch (Exception e) { return false; } return true; } /** * Validate value is an IpAddr. * * @param value * @return */ private static boolean validateIpAddr(String value) { return InetAddresses.isInetAddress(value); } /** * Validate value is an IPv4 Address * * @param value * @return */ public static boolean validateIpv4Addr(String value) { try { return validateIpAddr(value) && InetAddresses.forString(value) instanceof Inet4Address; } catch (Exception e) { return false; } } /** * Validate value is an IPv6 Address * * @param value * @return */ public static boolean validateIpv6Addr(String value) { try { return validateIpAddr(value) && InetAddresses.forString(value) instanceof Inet6Address; } catch (Exception e) { return false; } } /** * Validate that value is an Email. * * @param value * @return */ public static boolean validateEmail(String value) { return pattern.matcher((String) value).matches(); } /** * Validate that value is an email list. * * @param value comma separated email list * @return */ public static boolean validateEmailList(String value) { String[] emails = value.split(","); for (String email : emails) { if (validateEmail(email.trim()) == false) { return false; } } return true; } /** * Validate the value is a valid URL. * * @param value * @return */ public static boolean validateUrl(String value) { try { URL url = new URL((String) value); } catch (MalformedURLException e) { return false; } return true; } /** * Validate ip list * * @param iplist ip list * @return */ public static boolean validateIpList(String iplist) { String[] ips = iplist.split(","); try { for (String ip : ips) { ip = ip.trim(); if (ip.contains("/")) { // Handle subnet specification String[] ipcomps = ip.split("/"); // We have to trim the each component to handle the situation when there are spaces in the left or in the right of "/" if (ipcomps.length != 2 || !InetAddresses.isInetAddress(ipcomps[0].trim())) { return false; } else { // We have to put the test on maskBits in a separate try block otherwise a non-integer will cause problem. try { int maskBits = Integer.parseInt(ipcomps[1].trim()); if (maskBits > 32 || maskBits < 0) { return false; } } catch (NumberFormatException e) { return false; } } } else if (!InetAddresses.isInetAddress(ip)) { return false; } } } catch (NumberFormatException exc) { return false; } return true; } /** * Validate a property's maxLength and minLength values. * * @param value * @param lengthThreshold * @param lengthCriteria * @return */ public static boolean validateLength(String value, int lengthThreshold, PROP_LENGTH lengthCriteria) { if (value == null) { return false; } else if (lengthCriteria.toString().equals(PROP_LENGTH.MAX.toString())) { if (value.length() > lengthThreshold) { return false; } } else if (lengthCriteria.toString().equals(PROP_LENGTH.MIN.toString())) { if (value.length() < lengthThreshold) { return false; } } else { return false; } return true; } /** * Validate Hostname * * @param hostName * @return */ public static boolean validateHostName(String hostName) { // nothing in hostname, return false. if (hostName == null || hostName.isEmpty()) { return false; } // if length is greater than 255, return false; if (hostName.length() > 255) { return false; } // Testing uses an IP Address as opposed to a hostname. First test if // the // hostname looks like an ip address. if (validateIpAddr(hostName)) { return true; } // hostname doesn't appear to be an ip adress, let's check for hostname. String[] hostLabels = hostName.split("\\.", -1); if (hostLabels.length == 0) { return false; } Pattern labelPattern = Pattern.compile(VALID_HOST_NAME_LABEL_PATTERN); Matcher matcher = null; for (String label : hostLabels) { // check for length. must be between 1 to 63 chars. if (label.length() == 0 || label.length() > 63) { return false; } // check for pattern. a-z0-9- matcher = labelPattern.matcher(label); if (!matcher.matches()) { return false; } } return true; } /** * Validate Strict Hostname * * @param hostName * @return */ public static boolean validateStrictHostName(String hostName) { // nothing in hostname, return false. if (hostName == null || hostName.isEmpty()) { return false; } // if length is greater than 255, return false. if (hostName.length() > 253) { return false; } String[] hostLabels = hostName.split("\\.", -1); if (hostLabels.length == 0) { return false; } Pattern labelPattern = Pattern.compile(VALID_HOST_NAME_LABEL_PATTERN); Matcher matcher = null; for (String label : hostLabels) { // check for length. must be between 1 to 63 chars. if (label.length() == 0 || label.length() > 63) { return false; } // check for pattern. a-z0-9- matcher = labelPattern.matcher(label); if (!matcher.matches()) { return false; } } return true; } /** * Validate the IP-Port List for Syslog * <p/> * Ex - 1.1.1.1:4444,[22:22::222]:3333 * * @param ipPortList * @return */ public static boolean validateIpPortList(String ipPortList) { if (ipPortList == null || ipPortList.isEmpty()) { return true; } String[] serverPortList = ipPortList.split(","); for (String serverPort : serverPortList) { // split on last colon for ip:port String ip = serverPort.substring(0, serverPort.lastIndexOf(":")); String port = serverPort.substring(serverPort.lastIndexOf(":") + 1); if (ip.startsWith("[") && ip.endsWith("]")) { // if it is surrounded by [] then it should be IPV6 only ip = ip.substring(1, ip.length() - 1); if (!validateIpv6Addr(ip)) return false; } else if (validateIpv6Addr(ip)) { // this is an ipv6 address without brackets return false; } else if (!validateHostName(ip)) { // this isn't hostname or ipv4 return false; } if (!validateUint16(port)) return false; } return true; } /** * Return the map of the properties metadata. * * @return */ private Map<String, PropertyMetadata> getMetaData() { return _propertiesMetadata.getGlobalMetadata(); } // Spring injected property. public void setPropertiesMetadata(PropertiesMetadata propertiesMetadata) { _propertiesMetadata = propertiesMetadata; } // display will be like: Property Error for {propName}. Property value {propvalue} {error description} public String getDisplayError(String name, String value, String errDesc) { if (errDesc == null || errDesc.isEmpty()) { return null; } StringBuffer buff = new StringBuffer(); buff.append("Property Error for "); buff.append(name).append(". "); buff.append("Property value ").append(value).append(" "); buff.append(errDesc); return buff.toString(); } }