|
@@ -20,14 +20,12 @@
|
|
|
|
|
|
package password.pwm.util.password;
|
|
package password.pwm.util.password;
|
|
|
|
|
|
-import com.novell.ldapchai.exception.ImpossiblePasswordPolicyException;
|
|
|
|
-import lombok.Value;
|
|
|
|
|
|
+import org.apache.commons.lang3.mutable.MutableInt;
|
|
import password.pwm.PwmDomain;
|
|
import password.pwm.PwmDomain;
|
|
import password.pwm.bean.SessionLabel;
|
|
import password.pwm.bean.SessionLabel;
|
|
import password.pwm.config.DomainConfig;
|
|
import password.pwm.config.DomainConfig;
|
|
import password.pwm.config.PwmSetting;
|
|
import password.pwm.config.PwmSetting;
|
|
import password.pwm.config.profile.PwmPasswordPolicy;
|
|
import password.pwm.config.profile.PwmPasswordPolicy;
|
|
-import password.pwm.config.profile.PwmPasswordRule;
|
|
|
|
import password.pwm.error.ErrorInformation;
|
|
import password.pwm.error.ErrorInformation;
|
|
import password.pwm.error.PwmError;
|
|
import password.pwm.error.PwmError;
|
|
import password.pwm.error.PwmUnrecoverableException;
|
|
import password.pwm.error.PwmUnrecoverableException;
|
|
@@ -41,405 +39,242 @@ import password.pwm.util.secure.PwmRandom;
|
|
|
|
|
|
import java.time.Instant;
|
|
import java.time.Instant;
|
|
import java.util.ArrayList;
|
|
import java.util.ArrayList;
|
|
-import java.util.Arrays;
|
|
|
|
-import java.util.Collection;
|
|
|
|
-import java.util.Collections;
|
|
|
|
import java.util.EnumSet;
|
|
import java.util.EnumSet;
|
|
-import java.util.HashMap;
|
|
|
|
-import java.util.HashSet;
|
|
|
|
import java.util.List;
|
|
import java.util.List;
|
|
import java.util.Map;
|
|
import java.util.Map;
|
|
|
|
+import java.util.Optional;
|
|
import java.util.Set;
|
|
import java.util.Set;
|
|
|
|
+import java.util.function.Supplier;
|
|
|
|
+import java.util.stream.Collectors;
|
|
|
|
|
|
/**
|
|
/**
|
|
* Random password generator.
|
|
* Random password generator.
|
|
*
|
|
*
|
|
* @author Jason D. Rivard
|
|
* @author Jason D. Rivard
|
|
*/
|
|
*/
|
|
-public class RandomPasswordGenerator
|
|
|
|
|
|
+final class RandomPasswordGenerator
|
|
{
|
|
{
|
|
- /**
|
|
|
|
- * Default seed phrases. Most basic ASCII chars, except those that are visually ambiguous are
|
|
|
|
- * represented here. No multi-character phrases are included.
|
|
|
|
- */
|
|
|
|
- public static final Set<String> DEFAULT_SEED_PHRASES = Collections.unmodifiableSet( new HashSet<>( Arrays.asList(
|
|
|
|
- "a", "b", "c", "d", "e", "f", "g", "h", "j", "k", "m", "n", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z",
|
|
|
|
- "a", "b", "c", "d", "e", "f", "g", "h", "j", "k", "m", "n", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z",
|
|
|
|
- "a", "b", "c", "d", "e", "f", "g", "h", "j", "k", "m", "n", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z",
|
|
|
|
- "A", "B", "C", "D", "E", "F", "G", "H", "J", "K", "L", "M", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z",
|
|
|
|
- "2", "3", "4", "5", "6", "7", "8", "9",
|
|
|
|
- "@", "&", "!", "?", "%", "$", "#", "^", ")", "(", "+", "-", "=", ".", ",", "/", "\\"
|
|
|
|
- ) ) );
|
|
|
|
-
|
|
|
|
private static final PwmLogger LOGGER = PwmLogger.forClass( RandomPasswordGenerator.class );
|
|
private static final PwmLogger LOGGER = PwmLogger.forClass( RandomPasswordGenerator.class );
|
|
|
|
|
|
- public static PasswordData createRandomPassword(
|
|
|
|
- final SessionLabel sessionLabel,
|
|
|
|
- final PwmPasswordPolicy passwordPolicy,
|
|
|
|
- final PwmDomain pwmDomain
|
|
|
|
|
|
+ private record MutatorResult(
|
|
|
|
+ String password,
|
|
|
|
+ boolean validPassword,
|
|
|
|
+ int rounds
|
|
)
|
|
)
|
|
- throws PwmUnrecoverableException
|
|
|
|
{
|
|
{
|
|
- final RandomGeneratorConfig randomGeneratorConfig = RandomGeneratorConfig.make( pwmDomain, passwordPolicy );
|
|
|
|
-
|
|
|
|
- return createRandomPassword(
|
|
|
|
- sessionLabel,
|
|
|
|
- randomGeneratorConfig,
|
|
|
|
- pwmDomain
|
|
|
|
- );
|
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+ private RandomPasswordGenerator( )
|
|
|
|
+ {
|
|
|
|
+ }
|
|
|
|
|
|
- /**
|
|
|
|
- * <p>Creates a new password that satisfies the password rules. All rules are checked for. If for some
|
|
|
|
- * reason the pwmRandom algorithm can not generate a valid password, null will be returned.</p>
|
|
|
|
- *
|
|
|
|
- * <p>If there is an identifiable reason the password can not be created (such as mis-configured rules) then
|
|
|
|
- * an {@link com.novell.ldapchai.exception.ImpossiblePasswordPolicyException} will be thrown.</p>
|
|
|
|
- *
|
|
|
|
- * @param sessionLabel A valid pwmSession
|
|
|
|
- * @param randomGeneratorConfig Policy to be used during generation
|
|
|
|
- * @param pwmDomain Used to read configuration, seedmanager and other services.
|
|
|
|
- * @return A randomly generated password value that meets the requirements of this {@code PasswordPolicy}
|
|
|
|
- * @throws ImpossiblePasswordPolicyException If there is no way to create a password using the configured rules and
|
|
|
|
- * default seed phrase
|
|
|
|
- * @throws PwmUnrecoverableException if the operation can not be completed
|
|
|
|
- */
|
|
|
|
- public static PasswordData createRandomPassword(
|
|
|
|
- final SessionLabel sessionLabel,
|
|
|
|
- final RandomGeneratorConfig randomGeneratorConfig,
|
|
|
|
- final PwmDomain pwmDomain
|
|
|
|
|
|
+ public static PasswordData generate(
|
|
|
|
+ final RandomGeneratorRequest request
|
|
)
|
|
)
|
|
throws PwmUnrecoverableException
|
|
throws PwmUnrecoverableException
|
|
{
|
|
{
|
|
|
|
+ final SessionLabel sessionLabel = request.sessionLabel();
|
|
|
|
+ final PwmPasswordPolicy randomGenPolicy = request.randomGenPolicy();
|
|
|
|
+ final RandomGeneratorConfig randomGeneratorConfig = request.randomGeneratorConfig();
|
|
|
|
+ final PwmDomain pwmDomain = request.pwmDomain();
|
|
|
|
+
|
|
final Instant startTime = Instant.now();
|
|
final Instant startTime = Instant.now();
|
|
|
|
|
|
randomGeneratorConfig.validateSettings( pwmDomain );
|
|
randomGeneratorConfig.validateSettings( pwmDomain );
|
|
|
|
|
|
- final PwmRandom pwmRandom = pwmDomain.getSecureService().pwmRandom();
|
|
|
|
- final SeedMachine seedMachine = new SeedMachine( pwmRandom, normalizeSeeds( randomGeneratorConfig.getSeedlistPhrases() ) );
|
|
|
|
-
|
|
|
|
- // determine the password policy to use for random generation
|
|
|
|
- final PwmPasswordPolicy randomGenPolicy = makeRandomGenPwdPolicy( randomGeneratorConfig, pwmDomain );
|
|
|
|
-
|
|
|
|
// read a rule validator
|
|
// read a rule validator
|
|
// modify until it passes all the rules
|
|
// modify until it passes all the rules
|
|
- final MutatorResult mutatorResult = passwordMutator( sessionLabel, pwmDomain, seedMachine, randomGeneratorConfig, randomGenPolicy );
|
|
|
|
|
|
+ final MutatorResult mutatorResult = mutatePassword( request );
|
|
|
|
|
|
// report outcome
|
|
// report outcome
|
|
-
|
|
|
|
- if ( mutatorResult.isValidPassword() )
|
|
|
|
|
|
+ if ( mutatorResult.validPassword() )
|
|
{
|
|
{
|
|
- LOGGER.trace( sessionLabel, () -> "finished random password generation after " + mutatorResult.getRounds()
|
|
|
|
- + " rounds.", TimeDuration.fromCurrent( startTime ) );
|
|
|
|
|
|
+ final Supplier<CharSequence> logMsg = () -> "finished random password generation after "
|
|
|
|
+ + mutatorResult.rounds() + " rounds.";
|
|
|
|
+ LOGGER.trace( sessionLabel, logMsg, TimeDuration.fromCurrent( startTime ) );
|
|
|
|
+ //System.out.println( logMsg.get() );
|
|
}
|
|
}
|
|
else
|
|
else
|
|
{
|
|
{
|
|
if ( LOGGER.isInterestingLevel( PwmLogLevel.ERROR ) )
|
|
if ( LOGGER.isInterestingLevel( PwmLogLevel.ERROR ) )
|
|
{
|
|
{
|
|
final PwmPasswordRuleValidator pwmPasswordRuleValidator = PwmPasswordRuleValidator.create( sessionLabel, pwmDomain, randomGenPolicy );
|
|
final PwmPasswordRuleValidator pwmPasswordRuleValidator = PwmPasswordRuleValidator.create( sessionLabel, pwmDomain, randomGenPolicy );
|
|
- final int errors = pwmPasswordRuleValidator.internalPwmPolicyValidator( mutatorResult.getPassword(), null, null ).size();
|
|
|
|
- final int judgeLevel = PasswordUtility.judgePasswordStrength( pwmDomain.getConfig(), mutatorResult.getPassword() );
|
|
|
|
- LOGGER.error( sessionLabel, () -> "failed random password generation after "
|
|
|
|
- + mutatorResult.getRounds() + " rounds. " + "(errors=" + errors + ", judgeLevel=" + judgeLevel,
|
|
|
|
- TimeDuration.fromCurrent( startTime ) );
|
|
|
|
|
|
+ final int errors = pwmPasswordRuleValidator.internalPwmPolicyValidator( mutatorResult.password(), null, null ).size();
|
|
|
|
+ final int judgeLevel = PasswordUtility.judgePasswordStrength( pwmDomain.getConfig(), mutatorResult.password() );
|
|
|
|
+ final Supplier<CharSequence> logMsg = () -> "failed random password generation after "
|
|
|
|
+ + mutatorResult.rounds() + " rounds. " + "(errors=" + errors + ", judgeLevel=" + judgeLevel;
|
|
|
|
+ LOGGER.error( sessionLabel, logMsg, TimeDuration.fromCurrent( startTime ) );
|
|
|
|
+ //System.out.println( logMsg.get() );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
StatisticsClient.incrementStat( pwmDomain, Statistic.GENERATED_PASSWORDS );
|
|
StatisticsClient.incrementStat( pwmDomain, Statistic.GENERATED_PASSWORDS );
|
|
|
|
|
|
- LOGGER.trace( sessionLabel, () -> "real-time random password generator called"
|
|
|
|
- + " (" + TimeDuration.compactFromCurrent( startTime ) + ")" );
|
|
|
|
|
|
+ LOGGER.trace( sessionLabel, () -> "real-time random password generator called", TimeDuration.fromCurrent( startTime ) );
|
|
|
|
|
|
- return new PasswordData( mutatorResult.getPassword() );
|
|
|
|
|
|
+ //System.out.println( "total: " + TimeDuration.compactFromCurrent( startTime ) );
|
|
|
|
+ return new PasswordData( mutatorResult.password() );
|
|
}
|
|
}
|
|
|
|
|
|
- @Value
|
|
|
|
- private static class MutatorResult
|
|
|
|
- {
|
|
|
|
- private final String password;
|
|
|
|
- private final boolean validPassword;
|
|
|
|
- private final int rounds;
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- private static PwmPasswordPolicy makeRandomGenPwdPolicy(
|
|
|
|
- final RandomGeneratorConfig effectiveConfig,
|
|
|
|
- final PwmDomain pwmDomain
|
|
|
|
- )
|
|
|
|
- {
|
|
|
|
- final PwmPasswordPolicy defaultPolicy = PwmPasswordPolicy.defaultPolicy();
|
|
|
|
- final Map<String, String> newPolicyMap = new HashMap<>( defaultPolicy.getPolicyMap() );
|
|
|
|
-
|
|
|
|
- newPolicyMap.put( PwmPasswordRule.MaximumLength.getKey(), String.valueOf( effectiveConfig.getMaximumLength() ) );
|
|
|
|
- if ( effectiveConfig.getMinimumLength() > defaultPolicy.ruleHelper().readIntValue( PwmPasswordRule.MinimumLength ) )
|
|
|
|
- {
|
|
|
|
- newPolicyMap.put( PwmPasswordRule.MinimumLength.getKey(), String.valueOf( effectiveConfig.getMinimumLength() ) );
|
|
|
|
- }
|
|
|
|
- if ( effectiveConfig.getMaximumLength() < defaultPolicy.ruleHelper().readIntValue( PwmPasswordRule.MaximumLength ) )
|
|
|
|
- {
|
|
|
|
- newPolicyMap.put( PwmPasswordRule.MaximumLength.getKey(), String.valueOf( effectiveConfig.getMaximumLength() ) );
|
|
|
|
- }
|
|
|
|
- if ( effectiveConfig.getMinimumStrength() > defaultPolicy.ruleHelper().readIntValue( PwmPasswordRule.MinimumStrength ) )
|
|
|
|
- {
|
|
|
|
- newPolicyMap.put( PwmPasswordRule.MinimumStrength.getKey(), String.valueOf( effectiveConfig.getMinimumStrength() ) );
|
|
|
|
- }
|
|
|
|
- return PwmPasswordPolicy.createPwmPasswordPolicy( pwmDomain.getDomainID(), newPolicyMap );
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- private static MutatorResult passwordMutator(
|
|
|
|
- final SessionLabel sessionLabel,
|
|
|
|
- final PwmDomain pwmDomain,
|
|
|
|
- final SeedMachine seedMachine,
|
|
|
|
- final RandomGeneratorConfig effectiveConfig,
|
|
|
|
- final PwmPasswordPolicy randomGenPolicy
|
|
|
|
|
|
|
|
|
|
+ private static MutatorResult mutatePassword(
|
|
|
|
+ final RandomGeneratorRequest request
|
|
)
|
|
)
|
|
throws PwmUnrecoverableException
|
|
throws PwmUnrecoverableException
|
|
{
|
|
{
|
|
|
|
+ final RandomGeneratorConfig effectiveConfig = request.randomGeneratorConfig();
|
|
|
|
+ final PwmPasswordPolicy randomGenPolicy = request.randomGenPolicy();
|
|
|
|
|
|
- final int maxTryCount = effectiveConfig.getMaximumAttempts();
|
|
|
|
- final int jitterCount = effectiveConfig.getJitter();
|
|
|
|
- final PwmRandom pwmRandom = pwmDomain.getSecureService().pwmRandom();
|
|
|
|
|
|
+ final int maxTryCount = effectiveConfig.maximumAttempts();
|
|
|
|
|
|
- final StringBuilder password = new StringBuilder();
|
|
|
|
- password.append( generateNewPassword( pwmRandom, seedMachine, effectiveConfig.getMinimumLength() ) );
|
|
|
|
|
|
+ final PwmPasswordRuleValidator pwmPasswordRuleValidator = PwmPasswordRuleValidator.create(
|
|
|
|
+ request.sessionLabel(), request.pwmDomain(), randomGenPolicy, PwmPasswordRuleValidator.Flag.FailFast );
|
|
|
|
|
|
|
|
+ final String newPassword = generateNewPassword( request );
|
|
|
|
|
|
- final PwmPasswordRuleValidator pwmPasswordRuleValidator = PwmPasswordRuleValidator.create(
|
|
|
|
- sessionLabel, pwmDomain, randomGenPolicy, PwmPasswordRuleValidator.Flag.FailFast );
|
|
|
|
|
|
|
|
int tryCount = 0;
|
|
int tryCount = 0;
|
|
boolean validPassword = false;
|
|
boolean validPassword = false;
|
|
|
|
+
|
|
|
|
+ final MutablePassword mutablePassword = new MutablePassword( request, request.randomGeneratorConfig().seedMachine(), request.pwmRandom(), newPassword );
|
|
|
|
+
|
|
while ( !validPassword && tryCount < maxTryCount )
|
|
while ( !validPassword && tryCount < maxTryCount )
|
|
{
|
|
{
|
|
tryCount++;
|
|
tryCount++;
|
|
validPassword = true;
|
|
validPassword = true;
|
|
|
|
|
|
- if ( tryCount % jitterCount == 0 )
|
|
|
|
- {
|
|
|
|
- password.delete( 0, password.length() );
|
|
|
|
- password.append( generateNewPassword( pwmRandom, seedMachine, effectiveConfig.getMinimumLength() ) );
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
final List<ErrorInformation> errors = pwmPasswordRuleValidator.internalPwmPolicyValidator(
|
|
final List<ErrorInformation> errors = pwmPasswordRuleValidator.internalPwmPolicyValidator(
|
|
- password.toString(), null, null );
|
|
|
|
|
|
+ mutablePassword.value(), null, null );
|
|
|
|
+
|
|
if ( errors != null && !errors.isEmpty() )
|
|
if ( errors != null && !errors.isEmpty() )
|
|
{
|
|
{
|
|
validPassword = false;
|
|
validPassword = false;
|
|
- modifyPasswordBasedOnErrors( pwmRandom, password, errors, seedMachine );
|
|
|
|
|
|
+ modifyPasswordBasedOnErrors( mutablePassword, errors );
|
|
}
|
|
}
|
|
- else if ( checkPasswordAgainstDisallowedHttpValues( pwmDomain.getConfig(), password.toString() ) )
|
|
|
|
|
|
+ else if ( checkPasswordAgainstDisallowedHttpValues( request.pwmDomain().getConfig(), mutablePassword.value() ) )
|
|
{
|
|
{
|
|
validPassword = false;
|
|
validPassword = false;
|
|
- password.delete( 0, password.length() );
|
|
|
|
- password.append( generateNewPassword( pwmRandom, seedMachine, effectiveConfig.getMinimumLength() ) );
|
|
|
|
|
|
+ mutablePassword.reset( generateNewPassword( request ) );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
- return new MutatorResult( password.toString(), validPassword, tryCount );
|
|
|
|
|
|
+ return new MutatorResult( mutablePassword.value(), validPassword, tryCount );
|
|
}
|
|
}
|
|
|
|
|
|
private static void modifyPasswordBasedOnErrors(
|
|
private static void modifyPasswordBasedOnErrors(
|
|
- final PwmRandom pwmRandom,
|
|
|
|
- final StringBuilder password,
|
|
|
|
- final List<ErrorInformation> errors,
|
|
|
|
- final SeedMachine seedMachine
|
|
|
|
|
|
+ final MutablePassword mutablePassword,
|
|
|
|
+ final List<ErrorInformation> errors
|
|
)
|
|
)
|
|
{
|
|
{
|
|
- if ( password == null || errors == null || errors.isEmpty() )
|
|
|
|
|
|
+ if ( errors == null || errors.isEmpty() )
|
|
{
|
|
{
|
|
return;
|
|
return;
|
|
}
|
|
}
|
|
|
|
|
|
final Set<PwmError> errorMessages = EnumSet.noneOf( PwmError.class );
|
|
final Set<PwmError> errorMessages = EnumSet.noneOf( PwmError.class );
|
|
- for ( final ErrorInformation errorInfo : errors )
|
|
|
|
- {
|
|
|
|
- errorMessages.add( errorInfo.getError() );
|
|
|
|
- }
|
|
|
|
|
|
+ errors.forEach( errorInfo -> errorMessages.add( errorInfo.getError() ) );
|
|
|
|
|
|
boolean touched = false;
|
|
boolean touched = false;
|
|
|
|
|
|
if ( errorMessages.contains( PwmError.PASSWORD_TOO_SHORT ) )
|
|
if ( errorMessages.contains( PwmError.PASSWORD_TOO_SHORT ) )
|
|
{
|
|
{
|
|
- addRandChar( pwmRandom, password, seedMachine.getAllChars() );
|
|
|
|
|
|
+ mutablePassword.addRandChar();
|
|
touched = true;
|
|
touched = true;
|
|
}
|
|
}
|
|
|
|
|
|
if ( errorMessages.contains( PwmError.PASSWORD_TOO_LONG ) )
|
|
if ( errorMessages.contains( PwmError.PASSWORD_TOO_LONG ) )
|
|
{
|
|
{
|
|
- password.deleteCharAt( pwmRandom.nextInt( password.length() ) );
|
|
|
|
- touched = true;
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- if ( errorMessages.contains( PwmError.PASSWORD_FIRST_IS_NUMERIC ) || errorMessages.contains( PwmError.PASSWORD_FIRST_IS_SPECIAL ) )
|
|
|
|
- {
|
|
|
|
- password.deleteCharAt( 0 );
|
|
|
|
- touched = true;
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- if ( errorMessages.contains( PwmError.PASSWORD_LAST_IS_NUMERIC ) || errorMessages.contains( PwmError.PASSWORD_LAST_IS_SPECIAL ) )
|
|
|
|
- {
|
|
|
|
- password.deleteCharAt( password.length() - 1 );
|
|
|
|
- touched = true;
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- if ( errorMessages.contains( PwmError.PASSWORD_NOT_ENOUGH_NUM ) )
|
|
|
|
- {
|
|
|
|
- addRandChar( pwmRandom, password, seedMachine.getNumChars() );
|
|
|
|
- touched = true;
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- if ( errorMessages.contains( PwmError.PASSWORD_NOT_ENOUGH_SPECIAL ) )
|
|
|
|
- {
|
|
|
|
- addRandChar( pwmRandom, password, seedMachine.getSpecialChars() );
|
|
|
|
- touched = true;
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- if ( errorMessages.contains( PwmError.PASSWORD_NOT_ENOUGH_UPPER ) )
|
|
|
|
- {
|
|
|
|
- addRandChar( pwmRandom, password, seedMachine.getUpperChars() );
|
|
|
|
|
|
+ mutablePassword.deleteRandChar();
|
|
touched = true;
|
|
touched = true;
|
|
}
|
|
}
|
|
|
|
|
|
- if ( errorMessages.contains( PwmError.PASSWORD_NOT_ENOUGH_LOWER ) )
|
|
|
|
|
|
+ if ( errorMessages.contains( PwmError.PASSWORD_FIRST_IS_NUMERIC )
|
|
|
|
+ || errorMessages.contains( PwmError.PASSWORD_FIRST_IS_SPECIAL ) )
|
|
{
|
|
{
|
|
- addRandChar( pwmRandom, password, seedMachine.getLowerChars() );
|
|
|
|
|
|
+ mutablePassword.deleteFirstChar();
|
|
touched = true;
|
|
touched = true;
|
|
}
|
|
}
|
|
|
|
|
|
- PasswordCharCounter passwordCharCounter = new PasswordCharCounter( password.toString() );
|
|
|
|
- if ( errorMessages.contains( PwmError.PASSWORD_TOO_MANY_NUMERIC ) && passwordCharCounter.getNumericCharCount() > 0 )
|
|
|
|
|
|
+ if ( errorMessages.contains( PwmError.PASSWORD_LAST_IS_NUMERIC )
|
|
|
|
+ || errorMessages.contains( PwmError.PASSWORD_LAST_IS_SPECIAL ) )
|
|
{
|
|
{
|
|
- deleteRandChar( pwmRandom, password, passwordCharCounter.getNumericChars() );
|
|
|
|
|
|
+ mutablePassword.deleteLastChar();
|
|
touched = true;
|
|
touched = true;
|
|
- passwordCharCounter = new PasswordCharCounter( password.toString() );
|
|
|
|
}
|
|
}
|
|
|
|
|
|
- if ( errorMessages.contains( PwmError.PASSWORD_TOO_MANY_SPECIAL ) && passwordCharCounter.getSpecialCharsCount() > 0 )
|
|
|
|
|
|
+ if ( errorMessages.contains( PwmError.PASSWORD_TOO_WEAK ) )
|
|
{
|
|
{
|
|
- deleteRandChar( pwmRandom, password, passwordCharCounter.getSpecialChars() );
|
|
|
|
|
|
+ mutablePassword.randomPasswordCharModifier();
|
|
touched = true;
|
|
touched = true;
|
|
- passwordCharCounter = new PasswordCharCounter( password.toString() );
|
|
|
|
}
|
|
}
|
|
|
|
|
|
- if ( errorMessages.contains( PwmError.PASSWORD_TOO_MANY_UPPER ) && passwordCharCounter.getUpperCharCount() > 0 )
|
|
|
|
|
|
+ if ( checkForTooFewErrors( mutablePassword, errorMessages ) )
|
|
{
|
|
{
|
|
- deleteRandChar( pwmRandom, password, passwordCharCounter.getUpperChars() );
|
|
|
|
touched = true;
|
|
touched = true;
|
|
- passwordCharCounter = new PasswordCharCounter( password.toString() );
|
|
|
|
}
|
|
}
|
|
|
|
|
|
- if ( errorMessages.contains( PwmError.PASSWORD_TOO_MANY_LOWER ) && passwordCharCounter.getLowerCharCount() > 0 )
|
|
|
|
|
|
+ if ( checkForTooManyErrors( mutablePassword, errorMessages ) )
|
|
{
|
|
{
|
|
- deleteRandChar( pwmRandom, password, passwordCharCounter.getLowerChars() );
|
|
|
|
- touched = true;
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- if ( errorMessages.contains( PwmError.PASSWORD_TOO_WEAK ) )
|
|
|
|
- {
|
|
|
|
- randomPasswordModifier( pwmRandom, password, seedMachine );
|
|
|
|
touched = true;
|
|
touched = true;
|
|
}
|
|
}
|
|
|
|
|
|
if ( !touched )
|
|
if ( !touched )
|
|
{
|
|
{
|
|
- // dunno whats wrong, try just deleting a pwmRandom char, and hope a re-insert will add another.
|
|
|
|
- randomPasswordModifier( pwmRandom, password, seedMachine );
|
|
|
|
|
|
+ // dunno what is wrong, try just deleting a pwmRandom char, and hope a re-insert will add another.
|
|
|
|
+ mutablePassword.randomPasswordCharModifier();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
- private static void deleteRandChar(
|
|
|
|
- final PwmRandom pwmRandom,
|
|
|
|
- final StringBuilder password,
|
|
|
|
- final String charsToRemove
|
|
|
|
|
|
+ private static boolean checkForTooFewErrors(
|
|
|
|
+ final MutablePassword mutablePassword,
|
|
|
|
+ final Set<PwmError> errorMessages
|
|
)
|
|
)
|
|
- throws ImpossiblePasswordPolicyException
|
|
|
|
{
|
|
{
|
|
- final List<Integer> removePossibilities = new ArrayList<>();
|
|
|
|
- for ( int i = 0; i < password.length(); i++ )
|
|
|
|
|
|
+ boolean touched = false;
|
|
|
|
+
|
|
|
|
+ for ( final PasswordCharType passwordCharType : PasswordCharType.values() )
|
|
{
|
|
{
|
|
- final char loopChar = password.charAt( i );
|
|
|
|
- final int index = charsToRemove.indexOf( loopChar );
|
|
|
|
- if ( index != -1 )
|
|
|
|
|
|
+ final Optional<PwmError> tooFewError = passwordCharType.getTooFewError();
|
|
|
|
+ if ( tooFewError.isPresent() && errorMessages.contains( tooFewError.get() ) )
|
|
{
|
|
{
|
|
- removePossibilities.add( i );
|
|
|
|
|
|
+ if ( mutablePassword.getPwmRandom().nextBoolean() )
|
|
|
|
+ {
|
|
|
|
+ mutablePassword.deleteRandCharExceptType( passwordCharType );
|
|
|
|
+ }
|
|
|
|
+ mutablePassword.addRandChar( passwordCharType );
|
|
|
|
+ touched = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
- if ( removePossibilities.isEmpty() )
|
|
|
|
- {
|
|
|
|
- throw new ImpossiblePasswordPolicyException( ImpossiblePasswordPolicyException.ErrorEnum.UNEXPECTED_ERROR );
|
|
|
|
- }
|
|
|
|
- final Integer charToDelete = removePossibilities.get( pwmRandom.nextInt( removePossibilities.size() ) );
|
|
|
|
- password.deleteCharAt( charToDelete );
|
|
|
|
- }
|
|
|
|
|
|
|
|
- private static void randomPasswordModifier(
|
|
|
|
- final PwmRandom pwmRandom,
|
|
|
|
- final StringBuilder password,
|
|
|
|
- final SeedMachine seedMachine
|
|
|
|
- )
|
|
|
|
- {
|
|
|
|
- switch ( pwmRandom.nextInt( 6 ) )
|
|
|
|
- {
|
|
|
|
- case 0:
|
|
|
|
- case 1:
|
|
|
|
- addRandChar( pwmRandom, password, seedMachine.getSpecialChars() );
|
|
|
|
- break;
|
|
|
|
- case 2:
|
|
|
|
- case 3:
|
|
|
|
- addRandChar( pwmRandom, password, seedMachine.getNumChars() );
|
|
|
|
- break;
|
|
|
|
- case 4:
|
|
|
|
- addRandChar( pwmRandom, password, seedMachine.getUpperChars() );
|
|
|
|
- break;
|
|
|
|
- case 5:
|
|
|
|
- addRandChar( pwmRandom, password, seedMachine.getLowerChars() );
|
|
|
|
- break;
|
|
|
|
- default:
|
|
|
|
- switchRandomCase( pwmRandom, password );
|
|
|
|
- break;
|
|
|
|
- }
|
|
|
|
|
|
+ return touched;
|
|
}
|
|
}
|
|
|
|
|
|
- private static void switchRandomCase(
|
|
|
|
- final PwmRandom pwmRandom,
|
|
|
|
- final StringBuilder password
|
|
|
|
|
|
+ private static boolean checkForTooManyErrors(
|
|
|
|
+ final MutablePassword mutablePassword,
|
|
|
|
+ final Set<PwmError> errorMessages
|
|
)
|
|
)
|
|
{
|
|
{
|
|
- for ( int i = 0; i < password.length(); i++ )
|
|
|
|
|
|
+ boolean touched = false;
|
|
|
|
+
|
|
|
|
+ for ( final PasswordCharType passwordCharType : PasswordCharType.values() )
|
|
{
|
|
{
|
|
- final int randspot = pwmRandom.nextInt( password.length() );
|
|
|
|
- final char oldChar = password.charAt( randspot );
|
|
|
|
- if ( Character.isLetter( oldChar ) )
|
|
|
|
|
|
+ final Optional<PwmError> tooManyError = passwordCharType.getTooManyError();
|
|
|
|
+ if ( tooManyError.isPresent() && errorMessages.contains( tooManyError.get() ) )
|
|
{
|
|
{
|
|
- final char newChar = Character.isUpperCase( oldChar ) ? Character.toLowerCase( oldChar ) : Character.toUpperCase( oldChar );
|
|
|
|
- password.deleteCharAt( randspot );
|
|
|
|
- password.insert( randspot, newChar );
|
|
|
|
- return;
|
|
|
|
|
|
+ final PasswordCharCounter passwordCharCounter = mutablePassword.getPasswordCharCounter();
|
|
|
|
+ if ( passwordCharCounter.hasCharsOfType( passwordCharType ) )
|
|
|
|
+ {
|
|
|
|
+ mutablePassword.deleteRandChar( passwordCharType );
|
|
|
|
+ if ( mutablePassword.getPwmRandom().nextBoolean() )
|
|
|
|
+ {
|
|
|
|
+ mutablePassword.addRandCharExceptType( passwordCharType );
|
|
|
|
+ }
|
|
|
|
+ touched = true;
|
|
|
|
+ }
|
|
}
|
|
}
|
|
}
|
|
}
|
|
- }
|
|
|
|
|
|
|
|
- private static void addRandChar( final PwmRandom pwmRandom, final StringBuilder password, final String allowedChars )
|
|
|
|
- throws ImpossiblePasswordPolicyException
|
|
|
|
- {
|
|
|
|
- final int insertPosition = password.length() < 1 ? 0 : pwmRandom.nextInt( password.length() );
|
|
|
|
- addRandChar( pwmRandom, password, allowedChars, insertPosition );
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- private static void addRandChar( final PwmRandom pwmRandom, final StringBuilder password, final String allowedChars, final int insertPosition )
|
|
|
|
- throws ImpossiblePasswordPolicyException
|
|
|
|
- {
|
|
|
|
- if ( allowedChars.length() < 1 )
|
|
|
|
- {
|
|
|
|
- throw new ImpossiblePasswordPolicyException( ImpossiblePasswordPolicyException.ErrorEnum.REQUIRED_CHAR_NOT_ALLOWED );
|
|
|
|
- }
|
|
|
|
- else
|
|
|
|
- {
|
|
|
|
- final int newCharPosition = pwmRandom.nextInt( allowedChars.length() );
|
|
|
|
- final char charToAdd = allowedChars.charAt( newCharPosition );
|
|
|
|
- password.insert( insertPosition, charToAdd );
|
|
|
|
- }
|
|
|
|
|
|
+ return touched;
|
|
}
|
|
}
|
|
|
|
|
|
private static boolean checkPasswordAgainstDisallowedHttpValues( final DomainConfig config, final String password )
|
|
private static boolean checkPasswordAgainstDisallowedHttpValues( final DomainConfig config, final String password )
|
|
@@ -458,160 +293,50 @@ public class RandomPasswordGenerator
|
|
return false;
|
|
return false;
|
|
}
|
|
}
|
|
|
|
|
|
- private RandomPasswordGenerator( )
|
|
|
|
|
|
+ private static String generateNewPassword(
|
|
|
|
+ final RandomGeneratorRequest request
|
|
|
|
+ )
|
|
{
|
|
{
|
|
- }
|
|
|
|
|
|
+ final RandomGeneratorConfig randomGeneratorConfig = request.randomGeneratorConfig();
|
|
|
|
+ final PwmRandom pwmRandom = request.pwmRandom();
|
|
|
|
+ final SeedMachine seedMachine = request.randomGeneratorConfig().seedMachine();
|
|
|
|
|
|
- protected static class SeedMachine
|
|
|
|
- {
|
|
|
|
- private final Collection<String> seeds;
|
|
|
|
- private final PwmRandom pwmRandom;
|
|
|
|
|
|
+ final int effectiveLengthRange = randomGeneratorConfig.maximumLength() - randomGeneratorConfig.minimumLength();
|
|
|
|
+ final int desiredLength = effectiveLengthRange > 1
|
|
|
|
+ ? randomGeneratorConfig.minimumLength() + pwmRandom.nextInt( effectiveLengthRange )
|
|
|
|
+ : randomGeneratorConfig.maximumLength();
|
|
|
|
|
|
- private String allChars;
|
|
|
|
- private String numChars;
|
|
|
|
- private String specialChars;
|
|
|
|
- private String upperChars;
|
|
|
|
- private String lowerChars;
|
|
|
|
|
|
+ final Map<PasswordCharType, MutableInt> charTypeCounter = request.maxCharsPerType().entrySet()
|
|
|
|
+ .stream()
|
|
|
|
+ .filter( entry -> entry.getValue() > 0 )
|
|
|
|
+ .collect( Collectors.toMap(
|
|
|
|
+ Map.Entry::getKey,
|
|
|
|
+ entry -> new MutableInt( entry.getValue() ) ) );
|
|
|
|
|
|
- public SeedMachine( final PwmRandom pwmRandom, final Collection<String> seeds )
|
|
|
|
- {
|
|
|
|
- this.pwmRandom = pwmRandom;
|
|
|
|
- this.seeds = seeds;
|
|
|
|
- }
|
|
|
|
|
|
+ final StringBuilder password = new StringBuilder( desiredLength );
|
|
|
|
|
|
- public String getRandomSeed( )
|
|
|
|
- {
|
|
|
|
- return new ArrayList<>( seeds ).get( pwmRandom.nextInt( seeds.size() ) );
|
|
|
|
- }
|
|
|
|
|
|
+ // list copy of charTypeCounter.keySet() required because cannot pick random value from set/map. SequencedMap would be a better fit if it existed.
|
|
|
|
+ final List<PasswordCharType> list = new ArrayList<>( charTypeCounter.keySet() );
|
|
|
|
|
|
- public String getAllChars( )
|
|
|
|
|
|
+ while ( password.length() < desiredLength && !charTypeCounter.isEmpty() )
|
|
{
|
|
{
|
|
- if ( allChars == null )
|
|
|
|
|
|
+ final PasswordCharType type = list.get( pwmRandom.nextInt( list.size() ) );
|
|
|
|
+ if ( charTypeCounter.get( type ).decrementAndGet() == 0 )
|
|
{
|
|
{
|
|
- final StringBuilder sb = new StringBuilder();
|
|
|
|
- for ( final String s : seeds )
|
|
|
|
- {
|
|
|
|
- for ( final Character c : s.toCharArray() )
|
|
|
|
- {
|
|
|
|
- if ( sb.indexOf( c.toString() ) == -1 )
|
|
|
|
- {
|
|
|
|
- sb.append( c );
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
- allChars = sb.length() > 2 ? sb.toString() : ( new SeedMachine( pwmRandom, DEFAULT_SEED_PHRASES ) ).getAllChars();
|
|
|
|
|
|
+ charTypeCounter.remove( type );
|
|
|
|
+ list.remove( type );
|
|
}
|
|
}
|
|
|
|
|
|
- return allChars;
|
|
|
|
|
|
+ final String seedChars = seedMachine.charsOfType( type );
|
|
|
|
+ final char nextChar = seedChars.charAt( pwmRandom.nextInt( seedChars.length() ) );
|
|
|
|
+ password.append( nextChar );
|
|
}
|
|
}
|
|
|
|
|
|
- public String getNumChars( )
|
|
|
|
|
|
+ while ( password.length() < desiredLength )
|
|
{
|
|
{
|
|
- if ( numChars == null )
|
|
|
|
- {
|
|
|
|
- final StringBuilder sb = new StringBuilder();
|
|
|
|
- for ( final Character c : getAllChars().toCharArray() )
|
|
|
|
- {
|
|
|
|
- if ( Character.isDigit( c ) )
|
|
|
|
- {
|
|
|
|
- sb.append( c );
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
- numChars = sb.length() > 2 ? sb.toString() : ( new SeedMachine( pwmRandom, DEFAULT_SEED_PHRASES ) ).getNumChars();
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- return numChars;
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- public String getSpecialChars( )
|
|
|
|
- {
|
|
|
|
- if ( specialChars == null )
|
|
|
|
- {
|
|
|
|
- final StringBuilder sb = new StringBuilder();
|
|
|
|
- for ( final Character c : getAllChars().toCharArray() )
|
|
|
|
- {
|
|
|
|
- if ( !Character.isLetterOrDigit( c ) )
|
|
|
|
- {
|
|
|
|
- sb.append( c );
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
- specialChars = sb.length() > 2 ? sb.toString() : ( new SeedMachine( pwmRandom, DEFAULT_SEED_PHRASES ) ).getSpecialChars();
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- return specialChars;
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- public String getUpperChars( )
|
|
|
|
- {
|
|
|
|
- if ( upperChars == null )
|
|
|
|
- {
|
|
|
|
- final StringBuilder sb = new StringBuilder();
|
|
|
|
- for ( final Character c : getAllChars().toCharArray() )
|
|
|
|
- {
|
|
|
|
- if ( Character.isUpperCase( c ) )
|
|
|
|
- {
|
|
|
|
- sb.append( c );
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
- upperChars = sb.length() > 0 ? sb.toString() : ( new SeedMachine( pwmRandom, DEFAULT_SEED_PHRASES ) ).getUpperChars();
|
|
|
|
- }
|
|
|
|
- return upperChars;
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- public String getLowerChars( )
|
|
|
|
- {
|
|
|
|
- if ( lowerChars == null )
|
|
|
|
- {
|
|
|
|
- final StringBuilder sb = new StringBuilder();
|
|
|
|
- for ( final Character c : getAllChars().toCharArray() )
|
|
|
|
- {
|
|
|
|
- if ( Character.isLowerCase( c ) )
|
|
|
|
- {
|
|
|
|
- sb.append( c );
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
- lowerChars = sb.length() > 0 ? sb.toString() : ( new SeedMachine( pwmRandom, DEFAULT_SEED_PHRASES ) ).getLowerChars();
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- return lowerChars;
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- private static String generateNewPassword( final PwmRandom pwmRandom, final SeedMachine seedMachine, final int desiredLength )
|
|
|
|
- {
|
|
|
|
- final StringBuilder password = new StringBuilder();
|
|
|
|
-
|
|
|
|
- while ( password.length() < ( desiredLength - 1 ) )
|
|
|
|
- {
|
|
|
|
- //loop around until we're long enough
|
|
|
|
password.append( seedMachine.getRandomSeed() );
|
|
password.append( seedMachine.getRandomSeed() );
|
|
}
|
|
}
|
|
|
|
|
|
- if ( pwmRandom.nextInt( 3 ) == 0 )
|
|
|
|
- {
|
|
|
|
- final SeedMachine defaultSeedMachine = new SeedMachine( pwmRandom, DEFAULT_SEED_PHRASES );
|
|
|
|
- addRandChar( pwmRandom, password, defaultSeedMachine.getNumChars(), pwmRandom.nextInt( password.length() ) );
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- if ( pwmRandom.nextBoolean() )
|
|
|
|
- {
|
|
|
|
- switchRandomCase( pwmRandom, password );
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
return password.toString();
|
|
return password.toString();
|
|
}
|
|
}
|
|
-
|
|
|
|
- private static Collection<String> normalizeSeeds( final Collection<String> inputSeeds )
|
|
|
|
- {
|
|
|
|
- if ( inputSeeds == null )
|
|
|
|
- {
|
|
|
|
- return DEFAULT_SEED_PHRASES;
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- final Collection<String> newSeeds = new HashSet<>( inputSeeds );
|
|
|
|
- newSeeds.removeIf( s -> s == null || s.length() < 1 );
|
|
|
|
-
|
|
|
|
- return newSeeds.isEmpty() ? DEFAULT_SEED_PHRASES : newSeeds;
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
}
|
|
}
|