/*
* JBoss, Home of Professional Open Source.
* Copyright 2013, 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.adduser;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Locale;
import org.jboss.as.domain.management.logging.DomainManagementLogger;
/**
* State to perform validation of the supplied username.
*
* Checks include: - Valid characters. Easy to guess user names. Duplicate users.
*
* @author <a href="mailto:darran.lofthouse@jboss.com">Darran Lofthouse</a>
*/
public class ValidateUserState extends AbstractValidationState {
private static final String[] BAD_USER_NAMES = {"admin", "administrator", "root"};
private static final char[] VALID_PUNCTUATION;
private static final String VALID_SYMBOLS;
static {
char[] validPunctuation = { '.', '-', ',', '@', '/', '\\', '=' };
Arrays.sort(validPunctuation);
VALID_PUNCTUATION = validPunctuation;
StringBuilder sb = new StringBuilder();
for (int i=0 ; i < VALID_PUNCTUATION.length ; i++) {
sb.append("\"");
sb.append(VALID_PUNCTUATION[i]);
sb.append("\"");
if (i < VALID_PUNCTUATION.length -1) {
sb.append(", ");
}
}
VALID_SYMBOLS = sb.toString();
}
private final StateValues stateValues;
private final ConsoleWrapper theConsole;
public ValidateUserState(ConsoleWrapper theConsole, final StateValues stateValues) {
this.theConsole = theConsole;
this.stateValues = stateValues;
}
@Override
protected Collection<State> getValidationStates() {
List<State> validationStates = new ArrayList<State>(3);
validationStates.add(getValidCharactersState());
validationStates.add(getDuplicateCheckState());
validationStates.add(getCommonNamesCheckState());
return validationStates;
}
@Override
protected State getSuccessState() {
return new PromptPasswordState(theConsole, stateValues, false);
}
private State getRetryState() {
return stateValues.isSilentOrNonInteractive() ? null : new PromptNewUserState(theConsole, stateValues);
}
private State getValidCharactersState() {
return new State() {
@Override
public State execute() {
for (char currentChar : stateValues.getUserName().toCharArray()) {
if ((!isValidPunctuation(currentChar))
&& (Character.isLetter(currentChar) || Character.isDigit(currentChar)) == false) {
return new ErrorState(theConsole, DomainManagementLogger.ROOT_LOGGER.usernameNotAlphaNumeric(VALID_SYMBOLS), getRetryState(), stateValues);
}
}
return ValidateUserState.this;
}
private boolean isValidPunctuation(char currentChar) {
return (Arrays.binarySearch(VALID_PUNCTUATION, currentChar) >= 0);
}
};
}
private State getDuplicateCheckState() {
return new State() {
@Override
public State execute() {
if (stateValues.isExistingDisabledUser() || stateValues.isExistingEnabledUser()) {
State duplicateContinuing = stateValues.isSilentOrNonInteractive() ? null :
new PromptNewUserState(theConsole, stateValues);
stateValues.setExistingUser(true);
if (stateValues.isSilentOrNonInteractive()) {
return ValidateUserState.this;
} else {
final boolean existingDisabledUser = stateValues.isExistingDisabledUser();
if (existingDisabledUser) {
theConsole.printf(DomainManagementLogger.ROOT_LOGGER.aboutToUpdateDisabledUser(stateValues.getUserName()));
} else {
theConsole.printf(DomainManagementLogger.ROOT_LOGGER.aboutToUpdateEnabledUser(stateValues.getUserName()));
}
theConsole.printf(AddUser.NEW_LINE);
String response = theConsole.readLine("(a): ");
if (response == null) {
// This will return user to the command prompt so add a new line to ensure
// the command prompt is on the next line.
theConsole.printf(AddUser.NEW_LINE);
return null;
}
Option option = convertResponse(response, existingDisabledUser);
switch (option) {
case NEW:
return duplicateContinuing;
case UPDATE:
break;
case ENABLE:
stateValues.getOptions().setEnableDisableMode(true);
stateValues.getOptions().setDisable(false);
return new PreModificationState(theConsole, stateValues);
case DISABLE:
stateValues.getOptions().setEnableDisableMode(true);
stateValues.getOptions().setDisable(true);
return new PreModificationState(theConsole, stateValues);
default:
return new ErrorState(theConsole, DomainManagementLogger.ROOT_LOGGER.invalidChoiceUpdateUserResponse(), this, stateValues);
}
return ValidateUserState.this;
}
} else {
stateValues.setExistingUser(false);
return ValidateUserState.this;
}
}
};
}
private State getCommonNamesCheckState() {
return new State() {
@Override
public State execute() {
// If this is updating an existing user then the name is already accepted.
if (stateValues.isExistingUser() == false && stateValues.isSilentOrNonInteractive() == false) {
for (String current : BAD_USER_NAMES) {
if (current.equals(stateValues.getUserName().toLowerCase(Locale.ENGLISH))) {
String message = DomainManagementLogger.ROOT_LOGGER.usernameEasyToGuess(stateValues.getUserName());
String prompt = DomainManagementLogger.ROOT_LOGGER.sureToAddUser(stateValues.getUserName());
return new ConfirmationChoice(theConsole, message, prompt, ValidateUserState.this, getRetryState());
}
}
}
return ValidateUserState.this;
}
};
}
private Option convertResponse(final String response, final boolean existingDisabledUser) {
String responseLowerCase = response.toLowerCase(Locale.ENGLISH);
if ("".equals(responseLowerCase) || "a".equals(responseLowerCase)) {
return Option.UPDATE;
} else if ("b".equals(responseLowerCase)) {
// Opposite option...
if (existingDisabledUser) {
// ... when the existing user is disabled, enable!
return Option.ENABLE;
} else {
// ... when the existing user is enabled, disable!
return Option.DISABLE;
}
} else if ("c".equals(responseLowerCase)) {
return Option.NEW;
}
return Option.INVALID;
}
private enum Option {
UPDATE,
DISABLE,
ENABLE,
NEW,
INVALID
}
}