/* * 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 password.pwm.AppProperty; import password.pwm.PwmConstants; 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.i18n.Display; import password.pwm.util.LocaleHelper; import password.pwm.util.java.JsonUtil; import password.pwm.util.java.StringUtil; import java.io.Serializable; import java.math.BigInteger; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.StringTokenizer; import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.regex.PatternSyntaxException; /** * @author Jason D. Rivard */ public class FormConfiguration implements Serializable { // ------------------------------ FIELDS ------------------------------ public enum Type {text, email, number, password, random, tel, hidden, date, datetime, time, week, month, url, select, userDN, checkbox} private String name; private int minimumLength; private int maximumLength; private Type type = Type.text; private boolean required; private boolean confirmationRequired; private boolean readonly; private boolean unique; private boolean multivalue; private Map<String,String> labels = Collections.singletonMap("", ""); private Map<String,String> regexErrors = Collections.singletonMap("",""); private Map<String,String> description = Collections.singletonMap("",""); private String regex; private String placeholder; private String javascript; private Map<String,String> selectOptions = Collections.emptyMap(); // -------------------------- STATIC METHODS -------------------------- public static FormConfiguration parseOldConfigString(final String config) throws PwmOperationalException { if (config == null) { throw new NullPointerException("config can not be null"); } final FormConfiguration formItem = new FormConfiguration(); final StringTokenizer st = new StringTokenizer(config, ":"); // attribute name formItem.name = st.nextToken(); // label formItem.labels = Collections.singletonMap("",st.nextToken()); // type { final String typeStr = st.nextToken(); try { formItem.type = Type.valueOf(typeStr.toLowerCase()); } catch (IllegalArgumentException e) { throw new PwmOperationalException(new ErrorInformation(PwmError.CONFIG_FORMAT_ERROR,null,new String[]{"unknown type for form config: " + typeStr})); } } //minimum length try { formItem.minimumLength = Integer.parseInt(st.nextToken()); } catch (NumberFormatException e) { throw new PwmOperationalException(new ErrorInformation(PwmError.CONFIG_FORMAT_ERROR,null,new String[]{"invalid minimum length type for form config: " + e.getMessage()})); } //maximum length try { formItem.maximumLength = Integer.parseInt(st.nextToken()); } catch (NumberFormatException e) { throw new PwmOperationalException(new ErrorInformation(PwmError.CONFIG_FORMAT_ERROR,null,new String[]{"invalid maximum length type for form config: " + e.getMessage()})); } //required formItem.required = Boolean.TRUE.toString().equalsIgnoreCase(st.nextToken()); //confirmation formItem.confirmationRequired = Boolean.TRUE.toString().equalsIgnoreCase(st.nextToken()); return formItem; } public void validate() throws PwmOperationalException { if (this.getName() == null || this.getName().length() < 1) { throw new PwmOperationalException(new ErrorInformation(PwmError.CONFIG_FORMAT_ERROR,null,new String[]{" form field name is required"})); } if (this.getType() == null) { throw new PwmOperationalException(new ErrorInformation(PwmError.CONFIG_FORMAT_ERROR,null,new String[]{" type is required for field " + this.getName()})); } if (labels == null || this.labels.isEmpty() || this.getLabel(PwmConstants.DEFAULT_LOCALE) == null || this.getLabel(PwmConstants.DEFAULT_LOCALE).length() < 1) { throw new PwmOperationalException(new ErrorInformation(PwmError.CONFIG_FORMAT_ERROR,null,new String[]{" a default label value is required for " + this.getName()})); } if (this.getRegex() != null && this.getRegex().length() > 0) { try { Pattern.compile(this.getRegex()); } catch (PatternSyntaxException e) { throw new PwmOperationalException(new ErrorInformation(PwmError.CONFIG_FORMAT_ERROR,null,new String[]{" regular expression for '" + this.getName() + " ' is not valid: " + e.getMessage()})); } } if (this.getType() == Type.select) { if (this.getSelectOptions() == null || this.getSelectOptions().isEmpty()) { throw new PwmOperationalException(new ErrorInformation(PwmError.CONFIG_FORMAT_ERROR,null,new String[]{" field '" + this.getName() + " ' is type select, but no select options are defined"})); } } } // --------------------------- CONSTRUCTORS --------------------------- public FormConfiguration() { labels = Collections.singletonMap("",""); regexErrors = Collections.singletonMap("",""); } // --------------------- GETTER / SETTER METHODS --------------------- public String getName() { return name; } public String getLabel(final Locale locale) { return LocaleHelper.resolveStringKeyLocaleMap(locale, labels); } public Map<String,String> getLabelLocaleMap() { return Collections.unmodifiableMap(this.labels); } public String getRegexError(final Locale locale) { return LocaleHelper.resolveStringKeyLocaleMap(locale, regexErrors); } public String getDescription(final Locale locale) { return LocaleHelper.resolveStringKeyLocaleMap(locale, description); } public Map<String,String> getLabelDescriptionLocaleMap() { return Collections.unmodifiableMap(this.description); } public int getMaximumLength() { return maximumLength; } public int getMinimumLength() { return minimumLength; } public Type getType() { return type; } public boolean isConfirmationRequired() { return confirmationRequired; } public boolean isRequired() { return required; } public boolean isReadonly() { return readonly; } public boolean isUnique() { return unique; } public boolean isMultivalue() { return multivalue; } public String getRegex() { return regex; } public String getPlaceholder() { return placeholder; } public String getJavascript() { return javascript; } public Map<String,String> getSelectOptions() { return Collections.unmodifiableMap(selectOptions); } // ------------------------ CANONICAL METHODS ------------------------ public boolean equals(final Object o) { if (this == o) { return true; } if (!(o instanceof FormConfiguration)) { return false; } final FormConfiguration parameterConfig = (FormConfiguration) o; return !(name != null ? !name.equals(parameterConfig.name) : parameterConfig.name != null); } public int hashCode() { return (name != null ? name.hashCode() : 0); } public String toString() { final StringBuilder sb = new StringBuilder(); sb.append("FormItem: "); sb.append(JsonUtil.serialize(this)); return sb.toString(); } // -------------------------- OTHER METHODS -------------------------- public void checkValue(final Configuration config, final String value, final Locale locale) throws PwmDataValidationException, PwmUnrecoverableException { //check if value is missing and required. if (required && (value == null || value.length() < 1)) { final ErrorInformation error = new ErrorInformation(PwmError.ERROR_FIELD_REQUIRED, null, new String[]{getLabel(locale)}); throw new PwmDataValidationException(error); } switch (type) { case number: if (value != null && value.length() > 0) { try { new BigInteger(value); } catch (NumberFormatException e) { final ErrorInformation error = new ErrorInformation(PwmError.ERROR_FIELD_NOT_A_NUMBER, null, new String[]{getLabel(locale)}); throw new PwmDataValidationException(error); } } break; case email: if (value != null && value.length() > 0) { if (!testEmailAddress(config, value)) { final ErrorInformation error = new ErrorInformation(PwmError.ERROR_FIELD_INVALID_EMAIL, null, new String[]{getLabel(locale)}); throw new PwmDataValidationException(error); } } break; default: // continue for other types break; } if (value != null && (this.getMinimumLength() > 0) && (value.length() > 0) && (value.length() < this.getMinimumLength())) { final ErrorInformation error = new ErrorInformation(PwmError.ERROR_FIELD_TOO_SHORT, null, new String[]{getLabel(locale)}); throw new PwmDataValidationException(error); } if (value != null && value.length() > this.getMaximumLength()) { final ErrorInformation error = new ErrorInformation(PwmError.ERROR_FIELD_TOO_LONG, null, new String[]{getLabel(locale)}); throw new PwmDataValidationException(error); } if (value != null && value.length() > 0 && this.getRegex() != null && this.getRegex().length() > 0) { if (!value.matches(this.getRegex())) { final String configuredErrorMessage = this.getRegexError(locale); final ErrorInformation error = new ErrorInformation(PwmError.ERROR_FIELD_REGEX_NOMATCH, null, configuredErrorMessage, new String[]{getLabel(locale)}); throw new PwmDataValidationException(error); } } } public static List<String> convertToListOfNames(final Collection<FormConfiguration> formConfigurations) { if (formConfigurations == null) { return Collections.emptyList(); } final ArrayList<String> returnList = new ArrayList<>(); for (final FormConfiguration formConfiguration : formConfigurations) { returnList.add(formConfiguration.getName()); } return returnList; } /** * Return false if an invalid email address is issued. * @param config * @param address * @return */ public static boolean testEmailAddress(final Configuration config, final String address) { final String patternStr; if (config != null) { patternStr = config.readAppProperty(AppProperty.FORM_EMAIL_REGEX); } else { patternStr = AppProperty.FORM_EMAIL_REGEX.getDefaultValue(); } final Pattern pattern = Pattern.compile(patternStr); final Matcher matcher = pattern.matcher(address); return matcher.matches(); } public String displayValue(final String value, final Locale locale, final Configuration config) { if (value == null) { return LocaleHelper.getLocalizedMessage(locale, Display.Value_NotApplicable, config); } if (this.getType() == Type.select) { if (this.getSelectOptions() != null) { for (final String key : selectOptions.keySet()) { if (value.equals(key)) { final String displayValue = selectOptions.get(key); if (!StringUtil.isEmpty(displayValue)) { return displayValue; } } } } } return value; } }