/* * Password Management Servlets (PWM) * http://www.pwm-project.org * * Copyright (c) 2006-2009 Novell, Inc. * Copyright (c) 2009-2017 The PWM Project * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ package password.pwm.config; import com.novell.ldapchai.ChaiUser; import com.novell.ldapchai.exception.ChaiException; import com.novell.ldapchai.exception.ChaiOperationException; import com.novell.ldapchai.exception.ChaiUnavailableException; import com.novell.ldapchai.util.SearchHelper; import password.pwm.AppProperty; import password.pwm.PwmApplication; import password.pwm.bean.SessionLabel; import password.pwm.bean.UserIdentity; import password.pwm.error.ErrorInformation; import password.pwm.error.PwmDataValidationException; import password.pwm.error.PwmError; import password.pwm.error.PwmOperationalException; import password.pwm.error.PwmUnrecoverableException; import password.pwm.http.PwmRequest; import password.pwm.ldap.search.SearchConfiguration; import password.pwm.ldap.UserDataReader; import password.pwm.ldap.search.UserSearchEngine; import password.pwm.svc.cache.CacheKey; import password.pwm.svc.cache.CachePolicy; import password.pwm.svc.cache.CacheService; import password.pwm.util.java.JavaHelper; import password.pwm.util.java.JsonUtil; import password.pwm.util.java.StringUtil; import password.pwm.util.Validator; import password.pwm.util.logging.PwmLogger; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.List; import java.util.Locale; import java.util.Map; public class FormUtility { private static final PwmLogger LOGGER = PwmLogger.forClass(FormUtility.class); public enum Flag { ReturnEmptyValues } private static final String NEGATIVE_CACHE_HIT = "NEGATIVE_CACHE_HIT"; public static Map<FormConfiguration, String> readFormValuesFromMap( final Map<String,String> inputMap, final Collection<FormConfiguration> formItems, final Locale locale ) throws PwmDataValidationException, PwmUnrecoverableException { if (formItems == null || formItems.isEmpty()) { return Collections.emptyMap(); } final Map<FormConfiguration, String> returnMap = new LinkedHashMap<>(); if (inputMap == null) { return returnMap; } for (final FormConfiguration formItem : formItems) { final String keyName = formItem.getName(); final String value = inputMap.get(keyName); if (formItem.isRequired()) { if (value == null || value.length() < 0) { final String errorMsg = "missing required value for field '" + formItem.getName() + "'"; final ErrorInformation error = new ErrorInformation(PwmError.ERROR_FIELD_REQUIRED, errorMsg, new String[]{formItem.getLabel(locale)}); throw new PwmDataValidationException(error); } } if (formItem.isConfirmationRequired()) { final String confirmValue = inputMap.get(keyName + Validator.PARAM_CONFIRM_SUFFIX); if (confirmValue == null || !confirmValue.equals(value)) { final String errorMsg = "incorrect confirmation value for field '" + formItem.getName() + "'"; final ErrorInformation error = new ErrorInformation(PwmError.ERROR_FIELD_BAD_CONFIRM, errorMsg, new String[]{formItem.getLabel(locale)}); throw new PwmDataValidationException(error); } } if (formItem.getType() == FormConfiguration.Type.checkbox) { final String parsedValue = parseInputValueToFormValue(formItem, value); returnMap.put(formItem, parsedValue); } else if (value != null && !formItem.isReadonly()) { final String parsedValue = parseInputValueToFormValue(formItem, value); returnMap.put(formItem, parsedValue); } } return returnMap; } private static String parseInputValueToFormValue(final FormConfiguration formConfiguration, final String input) { if (formConfiguration.getType() == FormConfiguration.Type.checkbox) { final boolean bValue = checkboxValueIsChecked(input); return bValue ? "TRUE" : "FALSE"; } return input; } public static boolean checkboxValueIsChecked(final String value) { boolean booleanValue = false; if (value != null) { if (Boolean.parseBoolean(value)) { booleanValue = true; } else if ("on".equalsIgnoreCase(value)) { booleanValue = true; } else if ("checked".equalsIgnoreCase(value)) { booleanValue = true; } } return booleanValue; } public static Map<String,String> asStringMap(final Map<FormConfiguration, String> input) { final Map<String,String> returnObj = new HashMap<>(); for (final FormConfiguration formConfiguration : input.keySet()) { returnObj.put(formConfiguration.getName(), input.get(formConfiguration)); if (formConfiguration.isConfirmationRequired()) { final String confirmFieldName = formConfiguration.getName() + Validator.PARAM_CONFIRM_SUFFIX; returnObj.put(confirmFieldName, input.get(formConfiguration)); } } return returnObj; } public static Map<FormConfiguration, String> readFormValuesFromRequest( final PwmRequest pwmRequest, final Collection<FormConfiguration> formItems, final Locale locale ) throws PwmDataValidationException, PwmUnrecoverableException { final Map<String,String> tempMap = pwmRequest.readParametersAsMap(); return readFormValuesFromMap(tempMap, formItems, locale); } public static void validateFormValueUniqueness( final PwmApplication pwmApplication, final Map<FormConfiguration, String> formValues, final Locale locale, final Collection<UserIdentity> excludeDN, final boolean allowResultCaching ) throws PwmDataValidationException, PwmUnrecoverableException { final Map<String, String> filterClauses = new HashMap<>(); final Map<String,String> labelMap = new HashMap<>(); for (final FormConfiguration formItem : formValues.keySet()) { if (formItem.isUnique() && !formItem.isReadonly()) { if (formItem.getType() != FormConfiguration.Type.hidden) { final String value = formValues.get(formItem); if (value != null && value.length() > 0) { filterClauses.put(formItem.getName(), value); labelMap.put(formItem.getName(), formItem.getLabel(locale)); } } } } if (filterClauses.isEmpty()) { // nothing to search return; } final StringBuilder filter = new StringBuilder(); { filter.append("(&"); // outer; // object classes; filter.append("(|"); for (final String objectClass : pwmApplication.getConfig().readSettingAsStringArray(PwmSetting.DEFAULT_OBJECT_CLASSES)) { filter.append("(objectClass=").append(objectClass).append(")"); } filter.append(")"); // attributes filter.append("(|"); for (final String name : filterClauses.keySet()) { final String value = filterClauses.get(name); filter.append("(").append(name).append("=").append(StringUtil.escapeLdapFilter(value)).append(")"); } filter.append(")"); filter.append(")"); } final CacheService cacheService = pwmApplication.getCacheService(); final CacheKey cacheKey = CacheKey.makeCacheKey( Validator.class, null, "attr_unique_check_" + filter.toString() ); if (allowResultCaching && cacheService != null) { final String cacheValue = cacheService.get(cacheKey); if (cacheValue != null) { if (NEGATIVE_CACHE_HIT.equals(cacheValue)) { return; } else { final ErrorInformation errorInformation = JsonUtil.deserialize(cacheValue,ErrorInformation.class); throw new PwmDataValidationException(errorInformation); } } } final SearchHelper searchHelper = new SearchHelper(); searchHelper.setFilterAnd(filterClauses); final SearchConfiguration searchConfiguration = SearchConfiguration.builder() .filter(filter.toString()) .build(); final int resultSearchSizeLimit = 1 + (excludeDN == null ? 0 : excludeDN.size()); final long cacheLifetimeMS = Long.parseLong(pwmApplication.getConfig().readAppProperty(AppProperty.CACHE_FORM_UNIQUE_VALUE_LIFETIME_MS)); final CachePolicy cachePolicy = CachePolicy.makePolicyWithExpirationMS(cacheLifetimeMS); try { final UserSearchEngine userSearchEngine = pwmApplication.getUserSearchEngine(); final Map<UserIdentity,Map<String,String>> results = new LinkedHashMap<>(userSearchEngine.performMultiUserSearch( searchConfiguration, resultSearchSizeLimit, Collections.emptyList(), SessionLabel.SYSTEM_LABEL )); if (excludeDN != null && !excludeDN.isEmpty()) { for (final UserIdentity loopIgnoreIdentity : excludeDN) { for (final Iterator<UserIdentity> iterator = results.keySet().iterator(); iterator.hasNext(); ) { final UserIdentity loopUser = iterator.next(); if (loopIgnoreIdentity.equals(loopUser)) { iterator.remove(); } } } } if (!results.isEmpty()) { final UserIdentity userIdentity = results.keySet().iterator().next(); if (labelMap.size() == 1) { // since only one value searched, it must be that one value final String attributeName = labelMap.values().iterator().next(); LOGGER.trace("found duplicate value for attribute '" + attributeName + "' on entry " + userIdentity); final ErrorInformation error = new ErrorInformation(PwmError.ERROR_FIELD_DUPLICATE, null, new String[]{attributeName}); throw new PwmDataValidationException(error); } // do a compare on a user values to find one that matches. for (final String name : filterClauses.keySet()) { final String value = filterClauses.get(name); final boolean compareResult; try { final ChaiUser theUser = pwmApplication.getProxiedChaiUser(userIdentity); compareResult = theUser.compareStringAttribute(name, value); } catch (ChaiOperationException | ChaiUnavailableException e) { final PwmError error = PwmError.forChaiError(e.getErrorCode()); throw new PwmUnrecoverableException(error.toInfo()); } if (compareResult) { final String label = labelMap.get(name); LOGGER.trace("found duplicate value for attribute '" + label + "' on entry " + userIdentity); final ErrorInformation error = new ErrorInformation(PwmError.ERROR_FIELD_DUPLICATE, null, new String[]{label}); throw new PwmDataValidationException(error); } } // user didn't match on the compare.. shouldn't read here but just in case final ErrorInformation error = new ErrorInformation(PwmError.ERROR_FIELD_DUPLICATE, null); throw new PwmDataValidationException(error); } } catch (PwmOperationalException e) { if (cacheService != null) { final String jsonPayload = JsonUtil.serialize(e.getErrorInformation()); cacheService.put(cacheKey, cachePolicy, jsonPayload); } throw new PwmDataValidationException(e.getErrorInformation()); } if (allowResultCaching && cacheService != null) { cacheService.put(cacheKey, cachePolicy, NEGATIVE_CACHE_HIT); } } /** * Validates each of the parameters in the supplied map against the vales in the embedded config * and checks to make sure the ParamConfig value meets the requirements of the ParamConfig itself. * * * @param formValues - a Map containing String keys of parameter names and ParamConfigs as values * @throws password.pwm.error.PwmDataValidationException - If there is a problem with any of the fields * @throws password.pwm.error.PwmUnrecoverableException * if an unexpected error occurs */ public static void validateFormValues( final Configuration configuration, final Map<FormConfiguration, String> formValues, final Locale locale ) throws PwmUnrecoverableException, PwmDataValidationException { for (final FormConfiguration formItem : formValues.keySet()) { final String value = formValues.get(formItem); formItem.checkValue(configuration,value,locale); } } public static String ldapSearchFilterForForm(final PwmApplication pwmApplication, final Collection<FormConfiguration> formElements) throws PwmUnrecoverableException { if (formElements == null || formElements.isEmpty()) { final String errorMsg = "can not auto-generate ldap search filter for form with no required form items"; final ErrorInformation errorInformation = new ErrorInformation(PwmError.CONFIG_FORMAT_ERROR,null,new String[]{errorMsg}); throw new PwmUnrecoverableException(errorInformation); } final StringBuilder sb = new StringBuilder(); sb.append("(&"); final List<String> objectClasses = pwmApplication.getConfig().readSettingAsStringArray(PwmSetting.DEFAULT_OBJECT_CLASSES); if (objectClasses != null && !objectClasses.isEmpty()) { if (objectClasses.size() == 1) { sb.append("(objectclass="); sb.append(objectClasses.iterator().next()); sb.append(")"); } else { sb.append("(|"); for (final String objectClassValue : objectClasses) { sb.append("(objectclass="); sb.append(objectClassValue); sb.append(")"); } sb.append(")"); } } for (final FormConfiguration formConfiguration : formElements) { final String formElementName = formConfiguration.getName(); sb.append("("); sb.append(formElementName); sb.append("="); sb.append("%").append(formElementName).append("%"); sb.append(")"); } sb.append(")"); return sb.toString(); } public static void populateFormMapFromLdap( final List<FormConfiguration> formFields, final SessionLabel sessionLabel, final Map<FormConfiguration, String> formMap, final UserDataReader userDataReader ) throws PwmUnrecoverableException { final Map<FormConfiguration, List<String>> valueMap = populateFormMapFromLdap(formFields, sessionLabel, userDataReader); for (final FormConfiguration formConfiguration : formFields) { if (valueMap.containsKey(formConfiguration)) { final List<String> values = valueMap.get(formConfiguration); if (values != null && !values.isEmpty()) { final String value = values.iterator().next(); formMap.put(formConfiguration, value); } } } } public static Map<FormConfiguration, List<String>> populateFormMapFromLdap( final List<FormConfiguration> formFields, final SessionLabel sessionLabel, final UserDataReader userDataReader, final Flag... flags ) throws PwmUnrecoverableException { final boolean includeNulls = JavaHelper.enumArrayContainsValue(flags, Flag.ReturnEmptyValues); final List<String> formFieldNames = FormConfiguration.convertToListOfNames(formFields); LOGGER.trace(sessionLabel, "preparing to load form data from ldap for fields " + JsonUtil.serializeCollection(formFieldNames)); final Map<String,List<String>> dataFromLdap = new LinkedHashMap<>(); try { for (final FormConfiguration formConfiguration : formFields) { final String attribute = formConfiguration.getName(); if (formConfiguration.isMultivalue()) { final List<String> values = userDataReader.readMultiStringAttribute(attribute, UserDataReader.Flag.ignoreCache); if (includeNulls || (values != null && !values.isEmpty())) { dataFromLdap.put(attribute, values); } } else { final String value = userDataReader.readStringAttribute(attribute); if (includeNulls || (value != null)) { dataFromLdap.put(attribute, Collections.singletonList(value)); } } } } catch (Exception e) { PwmError error = null; if (e instanceof ChaiException) { error = PwmError.forChaiError(((ChaiException) e).getErrorCode()); } if (error == null || error == PwmError.ERROR_UNKNOWN) { error = PwmError.ERROR_LDAP_DATA_ERROR; } final ErrorInformation errorInformation = new ErrorInformation(error,"error reading current profile values: " + e.getMessage()); LOGGER.error(sessionLabel,errorInformation.getDetailedErrorMsg()); throw new PwmUnrecoverableException(errorInformation); } final Map<FormConfiguration, List<String>> returnMap = new LinkedHashMap<>(); for (final FormConfiguration formItem : formFields) { final String attrName = formItem.getName(); if (dataFromLdap.containsKey(attrName)) { final List<String> values = new ArrayList<>(); for (final String value : dataFromLdap.get(attrName)) { final String parsedValue = parseInputValueToFormValue(formItem, value); values.add(parsedValue); LOGGER.trace(sessionLabel, "loaded value for form item '" + attrName + "' with value=" + value); } returnMap.put(formItem, values); } } return returnMap; } public static Map<FormConfiguration, String> multiValueMapToSingleValue(final Map<FormConfiguration, List<String>> input) { final Map<FormConfiguration, String> returnMap = new LinkedHashMap<>(); for (final FormConfiguration formConfiguration : input.keySet()) { final List<String> listValue = input.get(formConfiguration); final String value = listValue != null && !listValue.isEmpty() ? listValue.iterator().next() : null; returnMap.put(formConfiguration, value); } return returnMap; } }