/*
* 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.util;
import com.google.gson.reflect.TypeToken;
import com.novell.ldapchai.ChaiUser;
import com.novell.ldapchai.exception.ChaiError;
import com.novell.ldapchai.exception.ChaiPasswordPolicyException;
import com.novell.ldapchai.exception.ChaiUnavailableException;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.math.NumberUtils;
import password.pwm.AppProperty;
import password.pwm.PwmApplication;
import password.pwm.PwmConstants;
import password.pwm.bean.SessionLabel;
import password.pwm.bean.UserInfoBean;
import password.pwm.bean.pub.PublicUserInfoBean;
import password.pwm.config.Configuration;
import password.pwm.config.PwmSetting;
import password.pwm.config.option.ADPolicyComplexity;
import password.pwm.config.profile.PwmPasswordPolicy;
import password.pwm.config.profile.PwmPasswordPolicy.RuleHelper;
import password.pwm.config.profile.PwmPasswordRule;
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.svc.PwmService;
import password.pwm.svc.stats.Statistic;
import password.pwm.util.java.JavaHelper;
import password.pwm.util.java.JsonUtil;
import password.pwm.util.java.StringUtil;
import password.pwm.util.logging.PwmLogger;
import password.pwm.util.macro.MacroMachine;
import password.pwm.util.operations.PasswordUtility;
import password.pwm.ws.client.rest.RestClientHelper;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.regex.Pattern;
public class PwmPasswordRuleValidator {
private static final PwmLogger LOGGER = PwmLogger.forClass(PwmPasswordRuleValidator.class);
private final PwmApplication pwmApplication;
private final PwmPasswordPolicy policy;
private final Locale locale;
public enum Flag {
FailFast,
}
public PwmPasswordRuleValidator(final PwmApplication pwmApplication, final PwmPasswordPolicy policy) {
this.pwmApplication = pwmApplication;
this.policy = policy;
this.locale = PwmConstants.DEFAULT_LOCALE;
}
public PwmPasswordRuleValidator(
final PwmApplication pwmApplication,
final PwmPasswordPolicy policy,
final Locale locale
)
{
this.pwmApplication = pwmApplication;
this.policy = policy;
this.locale = locale;
}
public boolean testPassword(
final PasswordData password,
final PasswordData oldPassword,
final UserInfoBean userInfoBean,
final ChaiUser user
)
throws PwmDataValidationException, ChaiUnavailableException, PwmUnrecoverableException {
final List<ErrorInformation> errorResults = validate(password, oldPassword, userInfoBean);
if (!errorResults.isEmpty()) {
throw new PwmDataValidationException(errorResults.iterator().next());
}
if (user != null) {
try {
LOGGER.trace("calling chai directory password validation checker");
user.testPasswordPolicy(password.getStringValue());
} catch (UnsupportedOperationException e) {
LOGGER.trace("Unsupported operation was thrown while validating password: " + e.toString());
} catch (ChaiUnavailableException e) {
pwmApplication.getStatisticsManager().incrementValue(Statistic.LDAP_UNAVAILABLE_COUNT);
LOGGER.warn("ChaiUnavailableException was thrown while validating password: " + e.toString());
throw e;
} catch (ChaiPasswordPolicyException e) {
final ChaiError passwordError = e.getErrorCode();
final PwmError pwmError = PwmError.forChaiError(passwordError);
final ErrorInformation info = new ErrorInformation(pwmError == null ? PwmError.PASSWORD_UNKNOWN_VALIDATION : pwmError);
LOGGER.trace("ChaiPasswordPolicyException was thrown while validating password: " + e.toString());
errorResults.add(info);
}
}
if (!errorResults.isEmpty()) {
throw new PwmDataValidationException(errorResults.iterator().next());
}
return true;
}
/**
* Validates a password against the configured rules of PWM. No directory operations
* are performed here.
*
* @param password desired new password
* @return true if the password is okay, never returns false.
*/
private List<ErrorInformation> validate(
final PasswordData password,
final PasswordData oldPassword,
final UserInfoBean uiBean
)
throws PwmUnrecoverableException
{
final List<ErrorInformation> internalResults = internalPwmPolicyValidator(password, oldPassword, uiBean);
if (pwmApplication != null) {
final List<ErrorInformation> externalResults = invokeExternalRuleMethods(pwmApplication.getConfig(), policy, password, uiBean);
internalResults.addAll(externalResults);
}
return internalResults;
}
public List<ErrorInformation> internalPwmPolicyValidator(
final PasswordData password,
final PasswordData oldPassword,
final UserInfoBean uiBean,
final Flag... flags
)
throws PwmUnrecoverableException
{
final String passwordString = password == null ? "" : password.getStringValue();
final String oldPasswordString = oldPassword == null ? null : oldPassword.getStringValue();
return internalPwmPolicyValidator(passwordString, oldPasswordString, uiBean, flags);
}
public List<ErrorInformation> internalPwmPolicyValidator(
final String passwordString,
final String oldPasswordString,
final UserInfoBean uiBean,
final Flag... flags
)
throws PwmUnrecoverableException
{
final boolean failFast = flags != null && Arrays.asList(flags).contains(Flag.FailFast);
// null check
if (passwordString == null) {
return Collections.singletonList(new ErrorInformation(PwmError.ERROR_UNKNOWN, "empty (null) new password"));
}
final List<ErrorInformation> errorList = new ArrayList<>();
final PwmPasswordPolicy.RuleHelper ruleHelper = policy.getRuleHelper();
final MacroMachine macroMachine = uiBean == null || uiBean.getUserIdentity() == null
? MacroMachine.forNonUserSpecific(pwmApplication, SessionLabel.SYSTEM_LABEL)
: MacroMachine.forUser(pwmApplication, PwmConstants.DEFAULT_LOCALE, SessionLabel.SYSTEM_LABEL, uiBean.getUserIdentity());
//check against old password
if (oldPasswordString != null && oldPasswordString.length() > 0 && ruleHelper.readBooleanValue(PwmPasswordRule.DisallowCurrent)) {
if (oldPasswordString.length() > 0) {
if (oldPasswordString.equalsIgnoreCase(passwordString)) {
errorList.add(new ErrorInformation(PwmError.PASSWORD_SAMEASOLD));
}
}
//check chars from old password
final int maxOldAllowed = ruleHelper.readIntValue(PwmPasswordRule.MaximumOldChars);
if (maxOldAllowed > 0) {
if (oldPasswordString.length() > 0) {
final String lPassword = passwordString.toLowerCase();
final Set<Character> dupeChars = new HashSet<>();
//add all dupes to the set.
for (final char loopChar : oldPasswordString.toLowerCase().toCharArray()) {
if (lPassword.indexOf(loopChar) != -1) {
dupeChars.add(loopChar);
}
}
//count the number of (unique) set elements.
if (dupeChars.size() >= maxOldAllowed) {
errorList.add(new ErrorInformation(PwmError.PASSWORD_TOO_MANY_OLD_CHARS));
}
}
}
}
if (failFast && errorList.size() > 1) {
return errorList;
}
errorList.addAll(basicSyntaxRuleChecks(passwordString,policy,uiBean));
if (failFast && errorList.size() > 1) {
return errorList;
}
// check against disallowed values;
if (!ruleHelper.getDisallowedValues().isEmpty()) {
final String lcasePwd = passwordString.toLowerCase();
final Set<String> paramValues = new HashSet<>(ruleHelper.getDisallowedValues());
for (final String loopValue : paramValues) {
if (loopValue != null && loopValue.length() > 0) {
final String expandedValue = macroMachine.expandMacros(loopValue);
if (StringUtils.isNotBlank(expandedValue)) {
final String loweredLoop = expandedValue.toLowerCase();
if (lcasePwd.contains(loweredLoop)) {
errorList.add(new ErrorInformation(PwmError.PASSWORD_USING_DISALLOWED));
}
}
}
}
}
if (failFast && errorList.size() > 1) {
return errorList;
}
// check disallowed attributes.
if (!policy.getRuleHelper().getDisallowedAttributes().isEmpty()) {
final List<String> paramConfigs = policy.getRuleHelper().getDisallowedAttributes(RuleHelper.Flag.KeepThresholds);
if (uiBean != null) {
final Map<String,String> userValues = uiBean.getCachedPasswordRuleAttributes();
for (final String paramConfig : paramConfigs) {
final String[] parts = paramConfig.split(":");
final String attrName = parts[0];
final String disallowedValue = StringUtils.defaultString(userValues.get(attrName));
final int threshold = parts.length > 1 ? NumberUtils.toInt(parts[1]) : 0;
if (containsDisallowedValue(passwordString, disallowedValue, threshold)) {
LOGGER.trace("password rejected, same as user attr " + attrName);
errorList.add(new ErrorInformation(PwmError.PASSWORD_SAMEASATTR));
}
}
}
}
if (failFast && errorList.size() > 1) {
return errorList;
}
{ // check password strength
final int requiredPasswordStrength = ruleHelper.readIntValue(PwmPasswordRule.MinimumStrength);
if (requiredPasswordStrength > 0) {
if (pwmApplication != null) {
final int passwordStrength = PasswordUtility.judgePasswordStrength(passwordString);
if (passwordStrength < requiredPasswordStrength) {
errorList.add(new ErrorInformation(PwmError.PASSWORD_TOO_WEAK));
//LOGGER.trace(pwmSession, "password rejected, password strength of " + passwordStrength + " is lower than policy requirement of " + requiredPasswordStrength);
}
}
}
}
if (failFast && errorList.size() > 1) {
return errorList;
}
// check regex matches.
for (final Pattern pattern : ruleHelper.getRegExMatch(macroMachine)) {
if (!pattern.matcher(passwordString).matches()) {
errorList.add(new ErrorInformation(PwmError.PASSWORD_INVALID_CHAR));
//LOGGER.trace(pwmSession, "password rejected, does not match configured regex pattern: " + pattern.toString());
}
}
if (failFast && errorList.size() > 1) {
return errorList;
}
// check no-regex matches.
for (final Pattern pattern : ruleHelper.getRegExNoMatch(macroMachine)) {
if (pattern.matcher(passwordString).matches()) {
errorList.add(new ErrorInformation(PwmError.PASSWORD_INVALID_CHAR));
//LOGGER.trace(pwmSession, "password rejected, matches configured no-regex pattern: " + pattern.toString());
}
}
if (failFast && errorList.size() > 1) {
return errorList;
}
// check char group matches
if (ruleHelper.readIntValue(PwmPasswordRule.CharGroupsMinMatch) > 0) {
final List<Pattern> ruleGroups = ruleHelper.getCharGroupValues();
if (ruleGroups != null && !ruleGroups.isEmpty()) {
final int requiredMatches = ruleHelper.readIntValue(PwmPasswordRule.CharGroupsMinMatch);
int matches = 0;
for (final Pattern pattern : ruleGroups) {
if (pattern.matcher(passwordString).find()) {
matches++;
}
}
if (matches < requiredMatches) {
errorList.add(new ErrorInformation(PwmError.PASSWORD_NOT_ENOUGH_GROUPS));
}
}
if (failFast && errorList.size() > 1) {
return errorList;
}
}
if (failFast && errorList.size() > 1) {
return errorList;
}
// check if the password is in the dictionary.
if (ruleHelper.readBooleanValue(PwmPasswordRule.EnableWordlist)) {
if (pwmApplication != null) {
if (pwmApplication.getWordlistManager() != null && pwmApplication.getWordlistManager().status() == PwmService.STATUS.OPEN) {
final boolean found = pwmApplication.getWordlistManager().containsWord(passwordString);
if (found) {
//LOGGER.trace(pwmSession, "password rejected, in wordlist file");
errorList.add(new ErrorInformation(PwmError.PASSWORD_INWORDLIST));
}
} else {
/* noop */
//LOGGER.warn(pwmSession, "password wordlist checking enabled, but wordlist is not available, skipping wordlist check");
}
}
if (failFast && errorList.size() > 1) {
return errorList;
}
}
if (failFast && errorList.size() > 1) {
return errorList;
}
// check for shared (global) password history
if (pwmApplication != null) {
if (pwmApplication.getConfig().readSettingAsBoolean(PwmSetting.PASSWORD_SHAREDHISTORY_ENABLE) && pwmApplication.getSharedHistoryManager().status() == PwmService.STATUS.OPEN) {
final boolean found = pwmApplication.getSharedHistoryManager().containsWord(passwordString);
if (found) {
//LOGGER.trace(pwmSession, "password rejected, in global shared history");
errorList.add(new ErrorInformation(PwmError.PASSWORD_INWORDLIST));
}
}
if (failFast && errorList.size() > 1) {
return errorList;
}
}
return errorList;
}
static boolean containsDisallowedValue(final String password, final String disallowedValue, final int threshold) {
if (StringUtils.isNotBlank(disallowedValue)) {
if (threshold > 0) {
if (disallowedValue.length() >= threshold) {
final String[] disallowedValueChunks = StringUtil.createStringChunks(disallowedValue, threshold);
for (final String chunk : disallowedValueChunks) {
if (StringUtils.containsIgnoreCase(password, chunk)) {
return true;
}
}
}
} else {
// No threshold? Then the password can't contain the whole disallowed value
return StringUtils.containsIgnoreCase(password, disallowedValue);
}
}
return false;
}
/**
* Check a supplied password for it's validity according to AD complexity rules.
* - Not contain the user's account name or parts of the user's full name that exceed two consecutive characters
* - Be at least six characters in length
* - Contain characters from three of the following five categories:
* - English uppercase characters (A through Z)
* - English lowercase characters (a through z)
* - Base 10 digits (0 through 9)
* - Non-alphabetic characters (for example, !, $, #, %)
* - Any character categorized as an alphabetic but is not uppercase or lowercase.
* <p/>
* See this article: http://technet.microsoft.com/en-us/library/cc786468%28WS.10%29.aspx
*
* @param userInfoBean userInfoBean
* @param password password to test
* @param charCounter associated charCounter for the password.
* @return list of errors if the password does not meet requirements, or an empty list if the password complies
* with AD requirements
*/
private static List<ErrorInformation> checkPasswordForADComplexity(
final ADPolicyComplexity complexityLevel,
final UserInfoBean userInfoBean,
final String password,
final PasswordCharCounter charCounter,
final int maxGroupViolationCount
) {
final List<ErrorInformation> errorList = new ArrayList<>();
if (password == null || password.length() < 6) {
errorList.add(new ErrorInformation(PwmError.PASSWORD_TOO_SHORT));
return errorList;
}
final int maxLength = complexityLevel == ADPolicyComplexity.AD2003 ? 128 : 512;
if (password.length() > maxLength) {
errorList.add(new ErrorInformation(PwmError.PASSWORD_TOO_LONG));
return errorList;
}
if (userInfoBean != null && userInfoBean.getCachedPasswordRuleAttributes() != null) {
final Map<String,String> userAttrs = userInfoBean.getCachedPasswordRuleAttributes();
final String samAccountName = userAttrs.get("sAMAccountName");
if (samAccountName != null
&& samAccountName.length() > 2
&& samAccountName.length() >= password.length()) {
if (password.toLowerCase().contains(samAccountName.toLowerCase())) {
errorList.add(new ErrorInformation(PwmError.PASSWORD_INWORDLIST));
LOGGER.trace("Password violation due to ADComplexity check: Password contains sAMAccountName");
}
}
final String displayName = userAttrs.get("displayName");
if (displayName != null && displayName.length() > 2) {
if (checkContainsTokens(password, displayName)) {
errorList.add(new ErrorInformation(PwmError.PASSWORD_INWORDLIST));
LOGGER.trace("Password violation due to ADComplexity check: Tokens from displayName used in password");
}
}
}
int complexityPoints = 0;
if (charCounter.getUpperCharCount() > 0) {
complexityPoints++;
}
if (charCounter.getLowerCharCount() > 0) {
complexityPoints++;
}
if (charCounter.getNumericCharCount() > 0) {
complexityPoints++;
}
switch (complexityLevel) {
case AD2003:
if (charCounter.getSpecialCharsCount() > 0 || charCounter.getOtherLetterCharCount() > 0) {
complexityPoints++;
}
break;
case AD2008:
if (charCounter.getSpecialCharsCount() > 0) {
complexityPoints++;
}
if (charCounter.getOtherLetterCharCount() > 0) {
complexityPoints++;
}
break;
default:
JavaHelper.unhandledSwitchStatement(complexityLevel);
}
switch (complexityLevel) {
case AD2008:
final int totalGroups = 5;
final int violations = totalGroups - complexityPoints;
if (violations <= maxGroupViolationCount) {
return errorList;
}
break;
case AD2003:
if (complexityPoints >= 3) {
return errorList;
}
break;
default:
JavaHelper.unhandledSwitchStatement(complexityLevel);
}
if (charCounter.getUpperCharCount() < 1) {
errorList.add(new ErrorInformation(PwmError.PASSWORD_NOT_ENOUGH_UPPER));
}
if (charCounter.getLowerCharCount() < 1) {
errorList.add(new ErrorInformation(PwmError.PASSWORD_NOT_ENOUGH_LOWER));
}
if (charCounter.getNumericCharCount() < 1) {
errorList.add(new ErrorInformation(PwmError.PASSWORD_NOT_ENOUGH_NUM));
}
if (charCounter.getSpecialCharsCount() < 1) {
errorList.add(new ErrorInformation(PwmError.PASSWORD_NOT_ENOUGH_SPECIAL));
}
if (charCounter.getOtherLetterCharCount() < 1) {
errorList.add(new ErrorInformation(PwmError.PASSWORD_UNKNOWN_VALIDATION));
}
return errorList;
}
// escape characters permitted because they match the exact AD specification
@SuppressWarnings("checkstyle:avoidescapedunicodecharacters")
private static boolean checkContainsTokens(final String baseValue, final String checkPattern) {
if (baseValue == null || baseValue.length() == 0) {
return false;
}
if (checkPattern == null || checkPattern.length() == 0) {
return false;
}
final String baseValueLower = baseValue.toLowerCase();
final String[] tokens = checkPattern.toLowerCase().split("[,\\.\\-\u2013\u2014_ \u00a3\\t]+");
if (tokens != null && tokens.length > 0) {
for (final String token : tokens) {
if (token.length() > 2) {
if (baseValueLower.contains(token)) {
return true;
}
}
}
}
return false;
}
private static final String REST_RESPONSE_KEY_ERROR = "error";
private static final String REST_RESPONSE_KEY_ERROR_MSG = "errorMessage";
public List<ErrorInformation> invokeExternalRuleMethods(
final Configuration config,
final PwmPasswordPolicy pwmPasswordPolicy,
final PasswordData password,
final UserInfoBean uiBean
)
throws PwmUnrecoverableException
{
final List<ErrorInformation> returnedErrors = new ArrayList<>();
final String restURL = config.readSettingAsString(PwmSetting.EXTERNAL_PWCHECK_REST_URLS);
final boolean haltOnError = Boolean.parseBoolean(config.readAppProperty(AppProperty.WS_REST_CLIENT_PWRULE_HALTONERROR));
final Map<String,Object> sendData = new LinkedHashMap<>();
if (restURL == null || restURL.isEmpty()) {
return Collections.emptyList();
}
{
final String passwordStr = password == null ? "" : password.getStringValue();
sendData.put("password", passwordStr);
}
if (pwmPasswordPolicy != null) {
final LinkedHashMap<String,Object> policyData = new LinkedHashMap<>();
for (final PwmPasswordRule rule : PwmPasswordRule.values()) {
policyData.put(rule.name(),pwmPasswordPolicy.getValue(rule));
}
sendData.put("policy",policyData);
}
if (uiBean != null) {
final MacroMachine macroMachine = MacroMachine.forUser(pwmApplication, PwmConstants.DEFAULT_LOCALE, SessionLabel.SYSTEM_LABEL, uiBean.getUserIdentity());
final PublicUserInfoBean publicUserInfoBean = PublicUserInfoBean.fromUserInfoBean(uiBean, pwmApplication.getConfig(), locale, macroMachine);
sendData.put("userInfo", publicUserInfoBean);
}
final String jsonRequestBody = JsonUtil.serializeMap(sendData);
try {
final String responseBody = RestClientHelper.makeOutboundRestWSCall(pwmApplication, locale, restURL,
jsonRequestBody);
final Map<String,Object> responseMap = JsonUtil.deserialize(responseBody,
new TypeToken<Map<String, Object>>() {}
);
if (responseMap.containsKey(REST_RESPONSE_KEY_ERROR) && Boolean.parseBoolean(responseMap.get(
REST_RESPONSE_KEY_ERROR).toString())) {
if (responseMap.containsKey(REST_RESPONSE_KEY_ERROR_MSG)) {
final String errorMessage = responseMap.get(REST_RESPONSE_KEY_ERROR_MSG).toString();
LOGGER.trace("external web service reported error: " + errorMessage);
returnedErrors.add(new ErrorInformation(PwmError.PASSWORD_CUSTOM_ERROR,errorMessage,errorMessage,null));
} else {
LOGGER.trace("external web service reported error without specifying an errorMessage");
returnedErrors.add(new ErrorInformation(PwmError.PASSWORD_CUSTOM_ERROR));
}
} else {
LOGGER.trace("external web service did not report an error");
}
} catch (PwmOperationalException e) {
final String errorMsg = "error executing external rule REST call: " + e.getMessage();
LOGGER.error(errorMsg);
if (haltOnError) {
throw new PwmUnrecoverableException(e.getErrorInformation(),e);
}
throw new IllegalStateException("http response error code: " + e.getMessage());
}
return returnedErrors;
}
private static List<ErrorInformation> basicSyntaxRuleChecks(
final String password,
final PwmPasswordPolicy policy,
final UserInfoBean uiBean
) {
final List<ErrorInformation> errorList = new ArrayList<>();
final PwmPasswordPolicy.RuleHelper ruleHelper = policy.getRuleHelper();
final PasswordCharCounter charCounter = new PasswordCharCounter(password);
final int passwordLength = password.length();
//Check minimum length
if (passwordLength < ruleHelper.readIntValue(PwmPasswordRule.MinimumLength)) {
errorList.add(new ErrorInformation(PwmError.PASSWORD_TOO_SHORT));
}
//Check maximum length
{
final int passwordMaximumLength = ruleHelper.readIntValue(PwmPasswordRule.MaximumLength);
if (passwordMaximumLength > 0 && passwordLength > passwordMaximumLength) {
errorList.add(new ErrorInformation(PwmError.PASSWORD_TOO_LONG));
}
}
//check number of numeric characters
{
final int numberOfNumericChars = charCounter.getNumericCharCount();
if (ruleHelper.readBooleanValue(PwmPasswordRule.AllowNumeric)) {
if (numberOfNumericChars < ruleHelper.readIntValue(PwmPasswordRule.MinimumNumeric)) {
errorList.add(new ErrorInformation(PwmError.PASSWORD_NOT_ENOUGH_NUM));
}
final int maxNumeric = ruleHelper.readIntValue(PwmPasswordRule.MaximumNumeric);
if (maxNumeric > 0 && numberOfNumericChars > maxNumeric) {
errorList.add(new ErrorInformation(PwmError.PASSWORD_TOO_MANY_NUMERIC));
}
if (!ruleHelper.readBooleanValue(
PwmPasswordRule.AllowFirstCharNumeric) && charCounter.isFirstNumeric()) {
errorList.add(new ErrorInformation(PwmError.PASSWORD_FIRST_IS_NUMERIC));
}
if (!ruleHelper.readBooleanValue(
PwmPasswordRule.AllowLastCharNumeric) && charCounter.isLastNumeric()) {
errorList.add(new ErrorInformation(PwmError.PASSWORD_LAST_IS_NUMERIC));
}
} else {
if (numberOfNumericChars > 0) {
errorList.add(new ErrorInformation(PwmError.PASSWORD_TOO_MANY_NUMERIC));
}
}
}
//check number of upper characters
{
final int numberOfUpperChars = charCounter.getUpperCharCount();
if (numberOfUpperChars < ruleHelper.readIntValue(PwmPasswordRule.MinimumUpperCase)) {
errorList.add(new ErrorInformation(PwmError.PASSWORD_NOT_ENOUGH_UPPER));
}
final int maxUpper = ruleHelper.readIntValue(PwmPasswordRule.MaximumUpperCase);
if (maxUpper > 0 && numberOfUpperChars > maxUpper) {
errorList.add(new ErrorInformation(PwmError.PASSWORD_TOO_MANY_UPPER));
}
}
//check number of alpha characters
{
final int numberOfAlphaChars = charCounter.getAlphaCharCount();
if (numberOfAlphaChars < ruleHelper.readIntValue(PwmPasswordRule.MinimumAlpha)) {
errorList.add(new ErrorInformation(PwmError.PASSWORD_NOT_ENOUGH_ALPHA));
}
final int maxAlpha = ruleHelper.readIntValue(PwmPasswordRule.MaximumAlpha);
if (maxAlpha > 0 && numberOfAlphaChars > maxAlpha) {
errorList.add(new ErrorInformation(PwmError.PASSWORD_TOO_MANY_ALPHA));
}
}
//check number of non-alpha characters
{
final int numberOfNonAlphaChars = charCounter.getNonAlphaCharCount();
if (numberOfNonAlphaChars < ruleHelper.readIntValue(PwmPasswordRule.MinimumNonAlpha)) {
errorList.add(new ErrorInformation(PwmError.PASSWORD_NOT_ENOUGH_NONALPHA));
}
final int maxNonAlpha = ruleHelper.readIntValue(PwmPasswordRule.MaximumNonAlpha);
if (maxNonAlpha > 0 && numberOfNonAlphaChars > maxNonAlpha) {
errorList.add(new ErrorInformation(PwmError.PASSWORD_TOO_MANY_NONALPHA));
}
}
//check number of lower characters
{
final int numberOfLowerChars = charCounter.getLowerCharCount();
if (numberOfLowerChars < ruleHelper.readIntValue(PwmPasswordRule.MinimumLowerCase)) {
errorList.add(new ErrorInformation(PwmError.PASSWORD_NOT_ENOUGH_LOWER));
}
final int maxLower = ruleHelper.readIntValue(PwmPasswordRule.MaximumLowerCase);
if (maxLower > 0 && numberOfLowerChars > maxLower) {
errorList.add(new ErrorInformation(PwmError.PASSWORD_TOO_MANY_UPPER));
}
}
//check number of special characters
{
final int numberOfSpecialChars = charCounter.getSpecialCharsCount();
if (ruleHelper.readBooleanValue(PwmPasswordRule.AllowSpecial)) {
if (numberOfSpecialChars < ruleHelper.readIntValue(PwmPasswordRule.MinimumSpecial)) {
errorList.add(new ErrorInformation(PwmError.PASSWORD_NOT_ENOUGH_SPECIAL));
}
final int maxSpecial = ruleHelper.readIntValue(PwmPasswordRule.MaximumSpecial);
if (maxSpecial > 0 && numberOfSpecialChars > maxSpecial) {
errorList.add(new ErrorInformation(PwmError.PASSWORD_TOO_MANY_SPECIAL));
}
if (!ruleHelper.readBooleanValue(
PwmPasswordRule.AllowFirstCharSpecial) && charCounter.isFirstSpecial()) {
errorList.add(new ErrorInformation(PwmError.PASSWORD_FIRST_IS_SPECIAL));
}
if (!ruleHelper.readBooleanValue(
PwmPasswordRule.AllowLastCharSpecial) && charCounter.isLastSpecial()) {
errorList.add(new ErrorInformation(PwmError.PASSWORD_LAST_IS_SPECIAL));
}
} else {
if (numberOfSpecialChars > 0) {
errorList.add(new ErrorInformation(PwmError.PASSWORD_TOO_MANY_SPECIAL));
}
}
}
//Check maximum character repeats (sequential)
{
final int maxSequentialRepeat = ruleHelper.readIntValue(PwmPasswordRule.MaximumSequentialRepeat);
if (maxSequentialRepeat > 0 && charCounter.getSequentialRepeatedChars() > maxSequentialRepeat) {
errorList.add(new ErrorInformation(PwmError.PASSWORD_TOO_MANY_REPEAT));
}
//Check maximum character repeats (overall)
final int maxRepeat = ruleHelper.readIntValue(PwmPasswordRule.MaximumRepeat);
if (maxRepeat > 0 && charCounter.getRepeatedChars() > maxRepeat) {
errorList.add(new ErrorInformation(PwmError.PASSWORD_TOO_MANY_REPEAT));
}
}
//Check minimum unique character
{
final int minUnique = ruleHelper.readIntValue(PwmPasswordRule.MinimumUnique);
if (minUnique > 0 && charCounter.getUniqueChars() < minUnique) {
errorList.add(new ErrorInformation(PwmError.PASSWORD_NOT_ENOUGH_UNIQUE));
}
}
// check ad-complexity
{
final ADPolicyComplexity complexityLevel = ruleHelper.getADComplexityLevel();
if (complexityLevel == ADPolicyComplexity.AD2003 || complexityLevel == ADPolicyComplexity.AD2008) {
final int maxGroupViolations = ruleHelper.readIntValue(PwmPasswordRule.ADComplexityMaxViolations);
errorList.addAll(checkPasswordForADComplexity(complexityLevel, uiBean, password, charCounter,
maxGroupViolations));
}
}
// check consecutive characters
{
final int maximumConsecutive = ruleHelper.readIntValue(PwmPasswordRule.MaximumConsecutive);
if (tooManyConsecutiveChars(password, maximumConsecutive)) {
errorList.add(new ErrorInformation(PwmError.PASSWORD_TOO_MANY_CONSECUTIVE));
}
}
return errorList;
}
public static boolean tooManyConsecutiveChars(final String str, final int maximumConsecutive) {
if (str != null && maximumConsecutive > 1 && str.length() >= maximumConsecutive) {
final int[] codePoints = StringUtil.toCodePointArray(str.toLowerCase());
int lastCodePoint = -1;
int consecutiveCharCount = 1;
for (int i=0; i<codePoints.length; i++) {
if (codePoints[i] == lastCodePoint+1) {
consecutiveCharCount++;
} else {
consecutiveCharCount = 1;
}
lastCodePoint = codePoints[i];
if (consecutiveCharCount == maximumConsecutive) {
return true;
}
}
}
return false;
}
}