blob: bc683cd75a958cf12db7b298fc50387e9b7d182b [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jclouds.util;
import static com.google.common.base.Preconditions.checkArgument;
import java.security.SecureRandom;
import java.util.Collections;
import java.util.List;
import java.util.Random;
import com.google.common.base.Joiner;
import com.google.common.base.Strings;
import com.google.common.primitives.Chars;
/**
* Generates random passwords.
* <p>
* This class allows to configure the password requirements for:
* <ul>
* <li>Number of upper case and lower case letters</li>
* <li>Inclusion of numbers</li>
* <li>Inclusion of special characters</li>
* </ul>
* By default, it will include at least three lower case letters, three upper
* case, three numbers and three special characters, and a maximum of five from
* each set.
* <p>
* It also allows to configure forbidden characters to accommodate the password
* requirements for the different clouds.
* <p>
* Example usage:
* <pre>
* String password = new PasswordGenerator()
* .lower().count(3) // Exactly three lower case characters
* .upper().count(2) // Exactly 2 upper case characters
* .numbers().min(5).exclude("012345".toCharArray()) // At least five numbers, from 6 to 9.
* .symbols().min(6).max(10) // Between 6 and 10 special characters
* .generate();
* </pre>
*
*/
public class PasswordGenerator {
private static final Random RANDOM = new SecureRandom();
private final Config lower = new Config("abcdefghijklmnopqrstuvwxyz").min(3).max(5);
private final Config upper = new Config("ABCDEFGHIJKLMNOPQRSTUVWXYZ").min(3).max(5);
private final Config numbers = new Config("1234567890").min(3).max(5);
// Use a small set of symbols that does not break shell commands
private final Config symbols = new Config("~@#%*()-_=+:,.?").min(3).max(5);
/**
* Returns the lower case configuration. Allows to configure the presence of lower case characters.
*/
public Config lower() {
return lower;
}
/**
* Returns the upper case configuration. Allows to configure the presence of upper case characters.
*/
public Config upper() {
return upper;
}
/**
* Returns the numbers configuration. Allows to configure the presence of numeric characters.
*/
public Config numbers() {
return numbers;
}
/**
* Returns the special character configuration. Allows to configure the presence of special characters.
*/
public Config symbols() {
return symbols;
}
/**
* Generates a random password using the configured spec.
*/
public String generate() {
StringBuilder sb = new StringBuilder();
sb.append(lower.fragment());
sb.append(upper.fragment());
sb.append(numbers.fragment());
sb.append(symbols.fragment());
return shuffleAndJoin(sb.toString().toCharArray());
}
private static String shuffleAndJoin(char[] chars) {
List<Character> result = Chars.asList(chars);
Collections.shuffle(result);
return Joiner.on("").join(result);
}
public class Config {
private final String characters;
private char[] exclusions;
private int minLength;
private int maxLength;
private Config(String characters) {
checkArgument(!Strings.isNullOrEmpty(characters), "charactets must be a non-empty string");
this.characters = characters;
}
public Config exclude(char[] exclusions) {
this.exclusions = exclusions;
return this;
}
public Config min(int num) {
this.minLength = num;
return this;
}
public Config max(int num) {
this.maxLength = num;
return this;
}
public Config count(int num) {
min(num);
max(num);
return this;
}
private String fragment() {
int length = minLength + RANDOM.nextInt((maxLength - minLength) + 1);
return new Generator(characters, length, exclusions).generate();
}
// Delegate to enclosing class for better fluent generators
public Config lower() {
return PasswordGenerator.this.lower();
}
public Config upper() {
return PasswordGenerator.this.upper();
}
public Config numbers() {
return PasswordGenerator.this.numbers();
}
public Config symbols() {
return PasswordGenerator.this.symbols();
}
public String generate() {
return PasswordGenerator.this.generate();
}
}
private static class Generator {
private final char[] characters;
private final int count;
private Generator(String characters, int count, char[] exclusions) {
checkArgument(!Strings.isNullOrEmpty(characters), "charactets must be a non-empty string");
this.count = count;
if (exclusions == null || exclusions.length == 0) {
this.characters = characters.toCharArray();
} else {
this.characters = characters.replaceAll("[" + new String(exclusions) + "]", "").toCharArray();
}
}
public String generate() {
char[] selected = new char[count];
for (int i = 0; i < count; i++) {
selected[i] = characters[RANDOM.nextInt(characters.length)];
}
return shuffleAndJoin(selected);
}
}
}