/*
* JBoss, Home of Professional Open Source.
* Copyright 2011, Red Hat, Inc., and individual contributors
* as indicated by the @author tags. See the copyright.txt file in the
* distribution for a full listing of individual contributors.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This software 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
package org.jboss.as.domain.management.security.password;
import static org.jboss.as.domain.management.logging.DomainManagementLogger.ROOT_LOGGER;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
import java.util.Properties;
import org.jboss.as.domain.management.security.password.PasswordCheckResult.Result;
import org.jboss.as.domain.management.security.password.simple.SimplePasswordStrengthChecker;
/**
* Simple util which narrows down password checks so there is no hassle in performing those checks in CLI.
*
* @author baranowb
* @author <a href="mailto:darran.lofthouse@jboss.com">Darran Lofthouse</a>
*/
public class PasswordCheckUtil {
public static final String _PROPERTY_CHECKER = "password.restriction.checker";
public static final String _PROPERTY_STRENGTH = "password.restriction.strength";
public static final String _PROPERTY_FORBIDDEN = "password.restriction.forbiddenValue";
public static final String _PROPERTY_RESTRICTION = "password.restriction";
public static final String _PROPERTY_MIN_LENGTH = "password.restriction.minLength";
public static final String _PROPERTY_MIN_ALPHA = "password.restriction.minAlpha";
public static final String _PROPERTY_MIN_DIGIT = "password.restriction.minDigit";
public static final String _PROPERTY_MIN_SYMBOL = "password.restriction.minSymbol";
public static final String _PROPERTY_MATCH_USERNAME = "password.restriction.mustNotMatchUsername";
private PasswordStrengthChecker passwordStrengthChecker;
private PasswordStrength acceptable = PasswordStrength.MODERATE;
private RestrictionLevel level = RestrictionLevel.WARN;
// Something ordered is good so the ordering of messages and validation is consistent across invocations.
public List<PasswordRestriction> passwordValuesRestrictions = new ArrayList<PasswordRestriction>();
private CompoundRestriction compountRestriction = null;
private PasswordCheckUtil(final File configFile, final RestrictionLevel level) {
this.level = level;
if (configFile != null && configFile.exists()) {
try {
Properties configProperties = new Properties();
configProperties.load(new FileInputStream(configFile));
// level
initRestrictionLevel(configProperties);
// strength
initDefaultStrength(configProperties);
// match username
initMustNotMatchUsername(configProperties);
// checker
initStrengthChecker(configProperties);
// name restrictions
initPasswordRestrictions(configProperties);
// length
initMinLength(configProperties);
// alpha
initMinAlpha(configProperties);
// digit
initMinDigit(configProperties);
// symbol
initMinSymbol(configProperties);
} catch (IOException e) {
simple();
}
} else {
simple();
}
}
private void simple() {
// revert to simple
this.passwordStrengthChecker = new SimplePasswordStrengthChecker();
}
public static PasswordCheckUtil create(final File configFile) {
return new PasswordCheckUtil(configFile, null);
}
public static PasswordCheckUtil create(RestrictionLevel level) {
return new PasswordCheckUtil(null, level);
}
private boolean must() {
return RestrictionLevel.REJECT == level;
}
/**
* @param props
*/
private void initPasswordRestrictions(Properties props) {
try {
String forbiddens = props.getProperty(_PROPERTY_FORBIDDEN);
if (forbiddens == null) {
return;
}
String[] values = forbiddens.split(",");
this.passwordValuesRestrictions.add(new ValueRestriction(values, must()));
} catch (Exception e) {
// log?
}
}
/**
* @param props
*/
private void initStrengthChecker(Properties props) {
try {
String stringClassName = props.getProperty(_PROPERTY_CHECKER);
if (stringClassName == null) {
this.simple();
return;
}
Class<PasswordStrengthChecker> clazz = (Class<PasswordStrengthChecker>) PasswordCheckUtil.class
.forName(stringClassName);
this.passwordStrengthChecker = clazz.newInstance();
} catch (Exception e) {
this.simple();
}
}
/**
* @param props
*/
private void initDefaultStrength(Properties props) {
try {
this.acceptable = PasswordStrength.valueOf(props.getProperty(_PROPERTY_STRENGTH).toUpperCase(Locale.ENGLISH));
} catch (Exception e) {
// log
}
}
/**
* @param props
*/
private void initMinAlpha(Properties props) {
try {
int minAlpha = Integer.parseInt(props.getProperty(_PROPERTY_MIN_ALPHA));
createAlphaRestriction(minAlpha);
} catch (Exception e) {
// log
}
}
/**
* @param props
*/
private void initMinSymbol(Properties props) {
try {
int minAlpha = Integer.parseInt(props.getProperty(_PROPERTY_MIN_SYMBOL));
createSymbolRestriction(minAlpha);
} catch (Exception e) {
// log
}
}
/**
* @param props
*/
private void initMinDigit(Properties props) {
try {
int minDigit = Integer.parseInt(props.getProperty(_PROPERTY_MIN_DIGIT));
createDigitRestriction(minDigit);
} catch (Exception e) {
// log
}
}
/**
* @param props
*/
private void initMinLength(Properties props) {
try {
int minLength = Integer.parseInt(props.getProperty(_PROPERTY_MIN_LENGTH));
createLengthRestriction(minLength);
} catch (Exception e) {
// log
}
}
/**
* @param props
*/
private void initMustNotMatchUsername(Properties props) {
try {
if (Boolean.parseBoolean(props.getProperty(_PROPERTY_MATCH_USERNAME))) {
passwordValuesRestrictions.add(new UsernamePasswordMatch(must()));
}
} catch (Exception e) {
// log
}
}
/**
* @param props
*/
private void initRestrictionLevel(Properties props) {
try {
level = RestrictionLevel.valueOf(props.getProperty(_PROPERTY_RESTRICTION));
} catch (Exception e) {
// log
}
}
private boolean assertStrength(PasswordStrength result) {
return result.getStrength() >= this.acceptable.getStrength();
}
/**
* Method which performs strength checks on password. It returns outcome which can be used by CLI.
*
* @param isAdminitrative - administrative checks are less restrictive. This means that weak password or one which violates restrictions is not indicated as failure.
* Administrative checks are usually performed by admin changing/setting default password for user.
* @param userName - the name of user for which password is set.
* @param password - password.
* @return
*/
public PasswordCheckResult check(boolean isAdminitrative, String userName, String password) {
// TODO: allow custom restrictions?
List<PasswordRestriction> passwordValuesRestrictions = getPasswordRestrictions();
final PasswordStrengthCheckResult strengthResult = this.passwordStrengthChecker.check(userName, password, passwordValuesRestrictions);
final int failedRestrictions = strengthResult.getRestrictionFailures().size();
final PasswordStrength strength = strengthResult.getStrength();
final boolean strongEnough = assertStrength(strength);
PasswordCheckResult.Result resultAction;
String resultMessage = null;
if (isAdminitrative) {
if (strongEnough) {
if (failedRestrictions > 0) {
resultAction = Result.WARN;
resultMessage = strengthResult.getRestrictionFailures().get(0).getMessage();
} else {
resultAction = Result.ACCEPT;
}
} else {
resultAction = Result.WARN;
resultMessage = ROOT_LOGGER.passwordNotStrongEnough(strength.toString(), this.acceptable.toString());
}
} else {
if (strongEnough) {
if (failedRestrictions > 0) {
resultAction = Result.REJECT;
resultMessage = strengthResult.getRestrictionFailures().get(0).getMessage();
} else {
resultAction = Result.ACCEPT;
}
} else {
if (failedRestrictions > 0) {
resultAction = Result.REJECT;
resultMessage = strengthResult.getRestrictionFailures().get(0).getMessage();
} else {
resultAction = Result.REJECT;
resultMessage = ROOT_LOGGER.passwordNotStrongEnough(strength.toString(), this.acceptable.toString());
}
}
}
return new PasswordCheckResult(resultAction, resultMessage);
}
public RestrictionLevel getRestrictionLevel() {
return level;
}
public List<PasswordRestriction> getPasswordRestrictions() {
return Collections.unmodifiableList(passwordValuesRestrictions);
}
private void addToCompointRestriction(final PasswordRestriction toWrap) {
if (compountRestriction == null) {
compountRestriction = new CompoundRestriction(level == RestrictionLevel.REJECT);
passwordValuesRestrictions.add(compountRestriction);
}
compountRestriction.add(toWrap);
}
public void createLengthRestriction(int minLength) {
if (minLength > 0) {
addToCompointRestriction(new LengthRestriction(minLength, must()));
}
}
public PasswordRestriction createAlphaRestriction(int minAlpha) {
return createRegExRestriction(minAlpha, SimplePasswordStrengthChecker.REGEX_ALPHA,
ROOT_LOGGER.passwordMustHaveAlphaInfo(minAlpha), must() ? ROOT_LOGGER.passwordMustHaveAlpha(minAlpha)
: ROOT_LOGGER.passwordShouldHaveAlpha(minAlpha));
}
public PasswordRestriction createDigitRestriction(int minDigit) {
return createRegExRestriction(minDigit, SimplePasswordStrengthChecker.REGEX_DIGITS,
ROOT_LOGGER.passwordMustHaveDigitInfo(minDigit), must() ? ROOT_LOGGER.passwordMustHaveDigit(minDigit)
: ROOT_LOGGER.passwordShouldHaveDigit(minDigit));
}
public PasswordRestriction createSymbolRestriction(int minSymbol) {
return createRegExRestriction(minSymbol, SimplePasswordStrengthChecker.REGEX_SYMBOLS,
ROOT_LOGGER.passwordMustHaveSymbolInfo(minSymbol), must() ? ROOT_LOGGER.passwordMustHaveSymbol(minSymbol)
: ROOT_LOGGER.passwordShouldHaveSymbol(minSymbol));
}
private PasswordRestriction createRegExRestriction(int minChar, String regex, String requirementsMessage,
String failureMessage) {
if (minChar > 0) {
PasswordRestriction pr = new RegexRestriction(String.format("(.*%s.*){%d}", regex, minChar), requirementsMessage,
failureMessage);
addToCompointRestriction(pr);
return pr;
}
return null;
}
}