alsutton/enterprisepasswordsafe

View on GitHub
src/main/java/com/enterprisepasswordsafe/ui/web/utils/PasswordGenerator.java

Summary

Maintainability
B
4 hrs
Test Coverage
F
0%
/*
 * Copyright (c) 2017 Carbon Security Ltd. <opensource@carbonsecurity.co.uk>
 *
 * Permission to use, copy, modify, and distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */

package com.enterprisepasswordsafe.ui.web.utils;

import com.enterprisepasswordsafe.database.PasswordRestriction;

import javax.servlet.http.HttpServletRequest;
import java.util.Random;

/**
 * Utility class to assist in password generation.
 */
public final class PasswordGenerator implements com.enterprisepasswordsafe.engine.utils.PasswordGenerator {

    /**
     * Empty string used for password generation
     */

    private static final String EMPTY_STRING = "";

    /**
     * The default minimum password length.
     */

    private static final int ABSOLUTE_MINIMUM_LENGTH = 8;

    /**
     * The maximum length of characters to add on the end of any requirement.
     */

    private static final int MAXIMUM_EXTENSION_SIZE = 16;

    /**
     * The random number generator.
     */

    private static final Random RANDOM_NUMBER_GENERATOR = new Random();

    /**
     * Private constructor, to avoid instanciation.
     */

    private PasswordGenerator() { }

    /**
     * Generates a new password.
     *
     * @param request The Servlet request.
     * @param startSpecial Whether or not the password must start with a special character.
     *
     * @return A generated password.
     */
    public String generate(final HttpServletRequest request, boolean startSpecial) {
        int upperCount = extractValue(request, "upper");
        int lowerCount = extractValue(request, "lower");
        int numericCount = extractValue(request, "numeric");
        int specialCount = extractValue(request, "special");
        String chars = request.getParameter("chars");
        if( chars == null ) {
            chars = EMPTY_STRING;
        }

        // Work out the password length
        int minLength;
        try {
            minLength = Integer.parseInt(request.getParameter("min"));
        } catch (NumberFormatException nfe) {
            minLength = ABSOLUTE_MINIMUM_LENGTH;
        }

        int maxLength;
        try {
            maxLength = Integer.parseInt(request.getParameter("max"));
        } catch (NumberFormatException nfe) {
            maxLength = minLength + RANDOM_NUMBER_GENERATOR.nextInt(MAXIMUM_EXTENSION_SIZE);
        }

        return generate(upperCount, lowerCount, numericCount, specialCount, minLength, maxLength, chars, startSpecial);
    }

    /**
     * Generate a password from a password restriction
     *
     * @param restriction The restriction to generate the password from.
     *
     * @return The generated password.
     */

    public String generate(PasswordRestriction restriction) {
        return generate(restriction, false);
    }

    /**
     * Generate a password from a password restriction
     *
     * @param restriction The restriction to generate the password from.
     * @param startSpecial Whether or not the password must start with a special character.
     *
     * @return The generated password.
     */

    public String generate(PasswordRestriction restriction, final boolean startSpecial) {
        int upper = 0,
            lower = 0,
            numeric = 0,
            special = 0,
            minLength = 0,
            maxLength = 0;
        String specialChars = "";

        if(restriction != null) {
            upper = restriction.getMinUpper();
            lower = restriction.getMinLower();
            numeric = restriction.getMinNumeric();
            special = restriction.getMinSpecial();
            minLength = restriction.getMinLength();
            maxLength = restriction.getMaxLength();
            specialChars = restriction.getSpecialCharacters();

        }
        return generate( upper, lower, numeric, special, minLength, maxLength, specialChars, startSpecial );
    }

    /**
     * Generate a random password with a default set of characteristics.
     *
     * @return The generated password.
     */

    public String generate() {
        return generate( 0, 0, 0, 0, 8, 16, "", false);
    }

    /**
     * Generate a random password with a default set of characteristics.
     *
     * @param startSpecial true if the password should start with one of the special characters, false if not.
     *
     * @return The generated password.
     */

    public String generate(boolean startSpecial) {
        return generate( 0, 0, 0, 0, 8, 16, "", startSpecial);
    }

    /**
     * Generate a password.
     *
     * @param upperCount The minimum number of upper case characters.
     * @param lowerCount The minimum number of lower case characters.
     * @param numericCount The minimum number of numeric characters.
     * @param specialCount The minimum number of special characters.
     * @param minLength The minimum length for the password.
     * @param maxLength The maximum length for the password.
     * @param specialChars The special characters to use in the password.
     * @param startSpecial true if the password should start with one of the special characters, false if not.
     *
     * @return The generated password.
     */
    public String generate( final int upperCount, final int lowerCount,
            final int numericCount, int specialCount, int minLength,
            int maxLength, final String specialChars, boolean startSpecial ) {
        if(    specialChars.length() > 0
        &&  specialCount > 0
        &&  startSpecial) {
            specialCount -= 1;
            minLength -= 1;
            maxLength -= 1;
        }

        int trueMinLength = Math.max(minLength, upperCount+lowerCount+numericCount+specialCount);
        int length;
        if( trueMinLength == maxLength ) {
            length = trueMinLength;
        } else {
            synchronized(RANDOM_NUMBER_GENERATOR) {
                length = trueMinLength + RANDOM_NUMBER_GENERATOR.nextInt(maxLength-trueMinLength);
                if( length > maxLength ) {
                    length = maxLength;
                }
            }
        }


        // Construct the password character set
        StringBuffer passwordCharacters = new StringBuffer(length);
        addCharsToBuffer(passwordCharacters, upperCount, PasswordRestriction.UPPER_PASSWORD_CHARS);
        addCharsToBuffer(passwordCharacters, lowerCount, PasswordRestriction.LOWER_PASSWORD_CHARS);
        addCharsToBuffer(passwordCharacters, numericCount, PasswordRestriction.NUMERIC_PASSWORD_CHARS);
        if(specialChars.length() > 0) {
            addCharsToBuffer(passwordCharacters, specialCount, specialChars);
        }
        length -= (upperCount + lowerCount + numericCount + specialCount);

        String allChars =   PasswordRestriction.UPPER_PASSWORD_CHARS +
                            PasswordRestriction.LOWER_PASSWORD_CHARS +
                            PasswordRestriction.NUMERIC_PASSWORD_CHARS +
                            specialChars;

        addCharsToBuffer(passwordCharacters, length, allChars);

        // Now randomise the order
        StringBuilder passwordBuffer = new StringBuilder(passwordCharacters.length());

        if(!specialChars.isEmpty() && specialCount > 0 && startSpecial) {
            // Always start with special. Solaris 10 likes this
            // and it's generally a good idea
            passwordBuffer.append(
                    specialChars.charAt(
                            RANDOM_NUMBER_GENERATOR.nextInt(
                                    specialChars.length()
                            )
                    )
                );
        }

        while (passwordCharacters.length() > 0) {
            int nextCharPosition = RANDOM_NUMBER_GENERATOR
                    .nextInt(passwordCharacters.length());
            passwordBuffer.append(passwordCharacters.charAt(nextCharPosition));
            passwordCharacters.deleteCharAt(nextCharPosition);
        }

        // Return the final password.
        return passwordBuffer.toString();
    }

    /**
     * Extracts a value from the servlet request.
     *
     * @param request
     *            The servlet request to extract the value from.
     * @param parameterName
     *            The name of the parameter to extract the value from.
     * @return The numeric value of a parameter.
     */

    private int extractValue(final HttpServletRequest request,
                             final String parameterName) {
        try {
            return Integer.parseInt(request.getParameter(parameterName));
        } catch (NumberFormatException nfe) {
            return 1;
        }
    }

    /**
     * Adds a given number of characters from a set to the specified buffer.
     *
     * @param buffer The buffer to add the characters to.
     * @param count The number of characters to add
     * @param characters The characters to choose from.
     */
    private void addCharsToBuffer(final StringBuffer buffer,
            final int count, final String characters) {
        int charCount = characters.length();
        for (int i = 0; i < count; i++) {
            buffer.append(characters.charAt(RANDOM_NUMBER_GENERATOR.nextInt(charCount)));
        }
    }

    /**
     * The characters available for use in a login password.
     */

    private static final char[] PASSWORD_CHARS = {'1', '2', '3', '4', '5',
            '6', '7', '8', '9', '0', 'b', 'c', 'd', 'f', 'g', 'h', 'j', 'k',
            'l', 'm', 'n', 'p', 'q', 'r', 's', 't', 'v', 'w', 'x', 'z', 'B',
            'C', 'D', 'F', 'G', 'H', 'J', 'K', 'L', 'M', 'N', 'P', 'Q', 'R',
            'S', 'T', 'V', 'W', 'X', 'Y', 'Z' };

    /**
     * Generate a random 8 character password. Primarily used when creating new
     * application users.
     *
     * @return The eight character password.
     */

    public String getRandomPassword() {
        StringBuilder passwordBuffer = new StringBuilder(ABSOLUTE_MINIMUM_LENGTH);

        for (int i = 0; i < ABSOLUTE_MINIMUM_LENGTH; i++) {
            passwordBuffer.append(PASSWORD_CHARS[RANDOM_NUMBER_GENERATOR
                    .nextInt(PASSWORD_CHARS.length)]);
        }

        return passwordBuffer.toString();
    }

    //------ Singleton ------

    private static final class InstanceHolder {
        static final PasswordGenerator INSTANCE = new PasswordGenerator();
    }

    public static PasswordGenerator getInstance() {
        return InstanceHolder.INSTANCE;
    }
}