| // 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 com.cloud.user; | |
| import com.cloud.exception.InvalidParameterValueException; | |
| import org.apache.cloudstack.framework.config.ConfigKey; | |
| import org.apache.cloudstack.framework.config.Configurable; | |
| import org.apache.commons.lang3.StringUtils; | |
| import org.apache.logging.log4j.Logger; | |
| import org.apache.logging.log4j.LogManager; | |
| public class PasswordPolicyImpl implements PasswordPolicy, Configurable { | |
| private Logger logger = LogManager.getLogger(PasswordPolicyImpl.class); | |
| public void verifyIfPasswordCompliesWithPasswordPolicies(String password, String username, Long domainId) { | |
| if (StringUtils.isEmpty(password)) { | |
| logger.warn(String.format("User [%s] has an empty password, skipping password policy checks. " + | |
| "If this is not a LDAP user, there is something wrong.", username)); | |
| return; | |
| } | |
| int numberOfSpecialCharactersInPassword = 0; | |
| int numberOfUppercaseLettersInPassword = 0; | |
| int numberOfLowercaseLettersInPassword = 0; | |
| int numberOfDigitsInPassword = 0; | |
| char[] splitPassword = password.toCharArray(); | |
| for (char character: splitPassword) { | |
| if (!Character.isLetterOrDigit(character)) { | |
| numberOfSpecialCharactersInPassword++; | |
| } else if (Character.isUpperCase(character)) { | |
| numberOfUppercaseLettersInPassword++; | |
| } else if (Character.isLowerCase(character)) { | |
| numberOfLowercaseLettersInPassword++; | |
| } else if (Character.isDigit(character)) { | |
| numberOfDigitsInPassword++; | |
| } | |
| } | |
| validateIfPasswordContainsTheMinimumNumberOfSpecialCharacters(numberOfSpecialCharactersInPassword, username, domainId); | |
| validateIfPasswordContainsTheMinimumNumberOfUpperCaseLetters(numberOfUppercaseLettersInPassword, username, domainId); | |
| validateIfPasswordContainsTheMinimumNumberOfLowerCaseLetters(numberOfLowercaseLettersInPassword, username, domainId); | |
| validateIfPasswordContainsTheMinimumNumberOfDigits(numberOfDigitsInPassword, username, domainId); | |
| validateIfPasswordContainsTheMinimumLength(password, username, domainId); | |
| validateIfPasswordContainsTheUsername(password, username, domainId); | |
| validateIfPasswordMatchesRegex(password, username, domainId); | |
| } | |
| protected void validateIfPasswordContainsTheMinimumNumberOfSpecialCharacters(int numberOfSpecialCharactersInPassword, String username, Long domainId) { | |
| Integer passwordPolicyMinimumSpecialCharacters = getPasswordPolicyMinimumSpecialCharacters(domainId); | |
| logger.trace(String.format("Validating if the new password for user [%s] contains the minimum number of special characters [%s] defined in the configuration [%s].", | |
| username, passwordPolicyMinimumSpecialCharacters, PasswordPolicyMinimumSpecialCharacters.key())); | |
| if (passwordPolicyMinimumSpecialCharacters == 0) { | |
| logger.trace(String.format("The minimum number of special characters for a user's password is 0; therefore, we will not validate the number of special characters for" | |
| + " the new password of user [%s].", username)); | |
| return; | |
| } | |
| if (numberOfSpecialCharactersInPassword < passwordPolicyMinimumSpecialCharacters) { | |
| logger.error(String.format("User [%s] informed [%d] special characters for their new password; however, the minimum number of special characters is [%d]. " | |
| + "Refusing the user's new password.", username, numberOfSpecialCharactersInPassword, passwordPolicyMinimumSpecialCharacters)); | |
| throw new InvalidParameterValueException(String.format("User password should contain at least [%d] special characters.", passwordPolicyMinimumSpecialCharacters)); | |
| } | |
| logger.trace(String.format("The new password for user [%s] complies with the policy of minimum special characters [%s].", username, | |
| PasswordPolicyMinimumSpecialCharacters.key())); | |
| } | |
| protected void validateIfPasswordContainsTheMinimumNumberOfUpperCaseLetters(int numberOfUppercaseLettersInPassword, String username, Long domainId) { | |
| Integer passwordPolicyMinimumUpperCaseLetters = getPasswordPolicyMinimumUpperCaseLetters(domainId); | |
| logger.trace(String.format("Validating if the new password for user [%s] contains the minimum number of upper case letters [%s] defined in the configuration [%s].", | |
| username, passwordPolicyMinimumUpperCaseLetters, PasswordPolicyMinimumUppercaseLetters.key())); | |
| if (passwordPolicyMinimumUpperCaseLetters == 0) { | |
| logger.trace(String.format("The minimum number of upper case letters for a user's password is 0; therefore, we will not validate the number of upper case letters for" | |
| + " the new password of user [%s].", username)); | |
| return; | |
| } | |
| if (numberOfUppercaseLettersInPassword < passwordPolicyMinimumUpperCaseLetters) { | |
| logger.error(String.format("User [%s] informed [%d] upper case letters for their new password; however, the minimum number of upper case letters is [%d]. " | |
| + "Refusing the user's new password.", username, numberOfUppercaseLettersInPassword, passwordPolicyMinimumUpperCaseLetters)); | |
| throw new InvalidParameterValueException(String.format("User password should contain at least [%d] upper case letters.", passwordPolicyMinimumUpperCaseLetters)); | |
| } | |
| logger.trace(String.format("The new password for user [%s] complies with the policy of minimum upper case letters [%s].", username, | |
| PasswordPolicyMinimumUppercaseLetters.key())); | |
| } | |
| protected void validateIfPasswordContainsTheMinimumNumberOfLowerCaseLetters(int numberOfLowercaseLettersInPassword, String username, Long domainId) { | |
| Integer passwordPolicyMinimumLowerCaseLetters = getPasswordPolicyMinimumLowerCaseLetters(domainId); | |
| logger.trace(String.format("Validating if the new password for user [%s] contains the minimum number of lower case letters [%s] defined in the configuration [%s].", | |
| username, passwordPolicyMinimumLowerCaseLetters, PasswordPolicyMinimumLowercaseLetters.key())); | |
| if (passwordPolicyMinimumLowerCaseLetters == 0) { | |
| logger.trace(String.format("The minimum number of lower case letters for a user's password is 0; therefore, we will not validate the number of lower case letters for" | |
| + " the new password of user [%s].", username)); | |
| return; | |
| } | |
| if (numberOfLowercaseLettersInPassword < passwordPolicyMinimumLowerCaseLetters) { | |
| logger.error(String.format("User [%s] informed [%d] lower case letters for their new password; however, the minimum number of lower case letters is [%d]. " | |
| + "Refusing the user's new password.", username, numberOfLowercaseLettersInPassword, passwordPolicyMinimumLowerCaseLetters)); | |
| throw new InvalidParameterValueException(String.format("User password should contain at least [%d] lower case letters.", passwordPolicyMinimumLowerCaseLetters)); | |
| } | |
| logger.trace(String.format("The new password for user [%s] complies with the policy of minimum lower case letters [%s].", username, | |
| PasswordPolicyMinimumLowercaseLetters.key())); | |
| } | |
| protected void validateIfPasswordContainsTheMinimumNumberOfDigits(int numberOfDigitsInPassword, String username, Long domainId) { | |
| Integer passwordPolicyMinimumDigits = getPasswordPolicyMinimumDigits(domainId); | |
| logger.trace(String.format("Validating if the new password for user [%s] contains the minimum number of digits [%s] defined in the configuration [%s].", | |
| username, passwordPolicyMinimumDigits, PasswordPolicyMinimumDigits.key())); | |
| if (passwordPolicyMinimumDigits == 0) { | |
| logger.trace(String.format("The minimum number of digits for a user's password is 0; therefore, we will not validate the number of digits for the new password of" | |
| + " user [%s].", username)); | |
| return; | |
| } | |
| if (numberOfDigitsInPassword < passwordPolicyMinimumDigits) { | |
| logger.error(String.format("User [%s] informed [%d] digits for their new password; however, the minimum number of digits is [%d]. " | |
| + "Refusing the user's new password.", username, numberOfDigitsInPassword, passwordPolicyMinimumDigits)); | |
| throw new InvalidParameterValueException(String.format("User password should contain at least [%d] digits.", passwordPolicyMinimumDigits)); | |
| } | |
| logger.trace(String.format("The new password for user [%s] complies with the policy of minimum digits [%s].", username, PasswordPolicyMinimumDigits.key())); | |
| } | |
| protected void validateIfPasswordContainsTheMinimumLength(String password, String username, Long domainId) { | |
| Integer passwordPolicyMinimumLength = getPasswordPolicyMinimumLength(domainId); | |
| logger.trace(String.format("Validating if the new password for user [%s] contains the minimum length [%s] defined in the configuration [%s].", username, | |
| passwordPolicyMinimumLength, PasswordPolicyMinimumLength.key())); | |
| if (passwordPolicyMinimumLength == 0) { | |
| logger.trace(String.format("The minimum length of a user's password is 0; therefore, we will not validate the length of the new password of user [%s].", username)); | |
| return; | |
| } | |
| Integer passwordLength = password.length(); | |
| if (passwordLength < passwordPolicyMinimumLength) { | |
| logger.error(String.format("User [%s] informed [%d] characters for their new password; however, the minimum password length is [%d]. Refusing the user's new password.", | |
| username, passwordLength, passwordPolicyMinimumLength)); | |
| throw new InvalidParameterValueException(String.format("User password should contain at least [%d] characters.", passwordPolicyMinimumLength)); | |
| } | |
| logger.trace(String.format("The new password for user [%s] complies with the policy of minimum length [%s].", username, PasswordPolicyMinimumLength.key())); | |
| } | |
| protected void validateIfPasswordContainsTheUsername(String password, String username, Long domainId) { | |
| logger.trace(String.format("Validating if the new password for user [%s] contains their username.", username)); | |
| if (getPasswordPolicyAllowPasswordToContainUsername(domainId)) { | |
| logger.trace(String.format("Allow password to contain username is true; therefore, we will not validate if the password contains the username of user [%s].", username)); | |
| return; | |
| } | |
| if (StringUtils.containsIgnoreCase(password, username)) { | |
| logger.error(String.format("User [%s] informed a new password that contains their username; however, the this is not allowed as configured in [%s]. " | |
| + "Refusing the user's new password.", username, PasswordPolicyAllowPasswordToContainUsername.key())); | |
| throw new InvalidParameterValueException("User password should not contain their username."); | |
| } | |
| logger.trace(String.format("The new password for user [%s] complies with the policy of allowing passwords to contain username [%s].", username, | |
| PasswordPolicyAllowPasswordToContainUsername.key())); | |
| } | |
| protected void validateIfPasswordMatchesRegex(String password, String username, Long domainId) { | |
| String passwordPolicyRegex = getPasswordPolicyRegex(domainId); | |
| logger.trace(String.format("Validating if the new password for user [%s] matches regex [%s] defined in the configuration [%s].", | |
| username, passwordPolicyRegex, PasswordPolicyRegex.key())); | |
| if (StringUtils.isEmpty(passwordPolicyRegex)) { | |
| logger.trace(String.format("Regex is empty; therefore, we will not validate if the new password matches with regex for user [%s].", username)); | |
| return; | |
| } | |
| if (!password.matches(passwordPolicyRegex)) { | |
| logger.error(String.format("User [%s] informed a new password that does not match with regex [%s]. Refusing the user's new password.", username, passwordPolicyRegex)); | |
| throw new InvalidParameterValueException("User password does not match with password policy regex."); | |
| } | |
| logger.trace(String.format("The new password for user [%s] complies with the policy of matching regex [%s].", username, | |
| PasswordPolicyRegex.key())); | |
| } | |
| @Override | |
| public String getConfigComponentName() { | |
| return PasswordPolicyImpl.class.getSimpleName(); | |
| } | |
| @Override | |
| public ConfigKey<?>[] getConfigKeys() { | |
| return new ConfigKey<?>[]{PasswordPolicyMinimumLength, PasswordPolicyMinimumSpecialCharacters, PasswordPolicyMinimumUppercaseLetters, PasswordPolicyMinimumLowercaseLetters, | |
| PasswordPolicyMinimumDigits, PasswordPolicyAllowPasswordToContainUsername, PasswordPolicyRegex | |
| }; | |
| } | |
| public Integer getPasswordPolicyMinimumLength(Long domainId) { | |
| return PasswordPolicyMinimumLength.valueIn(domainId); | |
| } | |
| public Integer getPasswordPolicyMinimumSpecialCharacters(Long domainId) { | |
| return PasswordPolicyMinimumSpecialCharacters.valueIn(domainId); | |
| } | |
| public Integer getPasswordPolicyMinimumUpperCaseLetters(Long domainId) { | |
| return PasswordPolicyMinimumUppercaseLetters.valueIn(domainId); | |
| } | |
| public Integer getPasswordPolicyMinimumLowerCaseLetters(Long domainId) { | |
| return PasswordPolicyMinimumLowercaseLetters.valueIn(domainId); | |
| } | |
| public Integer getPasswordPolicyMinimumDigits(Long domainId) { | |
| return PasswordPolicyMinimumDigits.valueIn(domainId); | |
| } | |
| public Boolean getPasswordPolicyAllowPasswordToContainUsername(Long domainId) { | |
| return PasswordPolicyAllowPasswordToContainUsername.valueIn(domainId); | |
| } | |
| public String getPasswordPolicyRegex(Long domainId) { | |
| return PasswordPolicyRegex.valueIn(domainId); | |
| } | |
| } |