You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cloudstack.apache.org by GitBox <gi...@apache.org> on 2022/07/25 13:30:31 UTC

[GitHub] [cloudstack] JoaoJandre opened a new pull request, #6567: Create password policies configurations

JoaoJandre opened a new pull request, #6567:
URL: https://github.com/apache/cloudstack/pull/6567

   ## Description
   
   Currently, ACS does not have a feature to manage user password policies. New domain-scoped settings were created to allow operators to define password policies. Configured password policies will be applied whenever a user changes their password or a new user is created. The following settings were created:
   
   | Setting | Purpose |
   | ------ | ------ |
   |`password.policy.minimum.length`| Minimum password length |
   |`password.policy.minimum.lowercase.letters`| Minimum number of lower case letters|
   |`password.policy.minimum.uppercase.letters`| Minimum number of upper case letters|
   |`password.policy.minimum.special.characters`| Minimum number of special characters|
   |`password.policy.minimum.digits`| Minimum number of digits|
   |`password.policy.allowPasswordToContainUsername`| If the password may contain the user's username|
   |`password.policy.regex`| A regular expression that the password must match|
   
   ### Types of changes
   
   - [ ] Breaking change (fix or feature that would cause existing functionality to change)
   - [X] New feature (non-breaking change which adds functionality)
   - [ ] Bug fix (non-breaking change which fixes an issue)
   - [ ] Enhancement (improves an existing feature and functionality)
   - [ ] Cleanup (Code refactoring and cleanup, that may add test cases)
   
   ### Feature/Enhancement Scale or Bug Severity
   
   #### Feature/Enhancement Scale
   
   - [ ] Major
   - [X] Minor
   
   ### How Has This Been Tested?
   
   Unit tests were created for the new methods. Furthermore each configuration was individually tested in the ROOT domain. The creation of a user was tested, to ensure that verification of password policies was being called. All other tests were done by changing the user's password via the UI.
   
   | No. | Test | Result | Expected result? |
   | ------ | ------ | ------ | ------ |
   | 1 | `password.policy.minimum.length` = 0 and password "abc" | Password successfully changed | Yes |
   | 2 | `password.policy.minimum.length` = 5 and password "abc" | Error thrown warning that the length does not reach the minimum expected | Yes |
   | 3 | `password.policy.minimum.length` = 5 and password "abcde" | Password successfully changed | Yes |
   | 4 | `password.policy.minimum.lowercase.letters` = 0 and password "ABC" | Password successfully changed | Yes |
   | 5 | `password.policy.minimum.lowercase.letters` = 1 and password "ABC" | Error thrown warning that the number of lowercase letters does not reach the expected minimum | Yes |
   | 6 | `password.policy.minimum.lowercase.letters` = 1 and password "aBC" | Password successfully changed | Yes |
   | 7 | `password.policy.minimum.uppercase.letters` = 0 and password "abc" | Password successfully changed | Yes |
   | 8 | `password.policy.minimum.uppercase.letters` = 1 and password "abc" | Error thrown warning that the number of uppercase letters does not reach the expected minimum | Yes |
   | 9 | `password.policy.minimum.uppercase.letters` = 1 and password "Abc" | Password successfully changed | Yes |
   | 10 | `password.policy.minimum.special.characters` = 0 and password "abc" | Password successfully changed | Yes |
   | 11 | `password.policy.minimum.special.characters` = 1 and password "abc" | Error thrown warning that the number of special characters does not reach the minimum expected | Yes |
   | 12 | `password.policy.minimum.special.characters` = 1 and password "&bc" | Password successfully changed | Yes |
   | 13 | `password.policy.minimum.digits` = 0 and password "abc" | Password successfully changed | Yes |
   | 14 | `password.policy.minimum.digits` = 1 and password "abc" | Error thrown warning that the number of digits does not reach the minimum expected | Yes |
   | 15 | `password.policy.minimum.digits` = 1 and password "1bc" | Password successfully changed | Yes |
   | 16 | `password.policy.allowPasswordToContainUsername` = true, password "abc" and username "test"| Password successfully changed | Yes |
   | 17 | `password.policy.allowPasswordToContainUsername` = true, password "testabc" and username "test"| Password successfully changed | Yes |
   | 18 | `password.policy.allowPasswordToContainUsername` = false, password "abc" and username "test"| Password successfully changed | Yes |
   | 19 | `password.policy.allowPasswordToContainUsername` = false, password "testabc" and username "test"| Error thrown warning that password cannot contain username | Yes |
   | 20 | `password.policy.regex` = ".+" and password "abc"| Password successfully changed | Yes |
   | 21 | `password.policy.regex` = "[a-z]+" and password "abc"| Password successfully changed | Yes |
   | 22 | `password.policy.regex` = "[a-z]+" and password "abc1"| Error thrown warning that password does not match with regex | Yes |
   | 23 | `password.policy.minimum.length` = 5, `password.policy.minimum.lowercase.letters` = 1, `password.policy.minimum.uppercase.letters` = 1, `password.policy.minimum.special.characters ` = 1, `password.policy.minimum.digits` = 1, `password.policy.allowPasswordToContainUsername` = false, `password.policy.regex` = "[a-zA-Z1-9@]+ " and password " aA1@b"| Password successfully changed | Yes |
   
   Tests were repeated on a subdomain of the ROOT to ensure that the settings taken into account were always from the subdomain, and all tests went as expected.


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@cloudstack.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


[GitHub] [cloudstack] harikrishna-patnala commented on pull request #6567: Password policies

Posted by GitBox <gi...@apache.org>.
harikrishna-patnala commented on PR #6567:
URL: https://github.com/apache/cloudstack/pull/6567#issuecomment-1196297473

   @blueorangutan package


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@cloudstack.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


[GitHub] [cloudstack] harikrishna-patnala commented on a diff in pull request #6567: Password policies

Posted by GitBox <gi...@apache.org>.
harikrishna-patnala commented on code in PR #6567:
URL: https://github.com/apache/cloudstack/pull/6567#discussion_r930647669


##########
server/src/main/java/com/cloud/user/PasswordPolicyImpl.java:
##########
@@ -0,0 +1,245 @@
+// 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.log4j.Logger;
+
+public class PasswordPolicyImpl implements PasswordPolicy, Configurable {
+
+    private Logger logger = Logger.getLogger(PasswordPolicyImpl.class);
+
+    public void verifyIfPasswordCompliesWithPasswordPolicies(String password, String username, Long domainId) {
+        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 (passwordPolicyRegex == null){
+            logger.trace(String.format("Regex is null; 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) {

Review Comment:
   These methods may not be required, we can directly get the value from the configuration and it is as readable as these methods.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@cloudstack.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


[GitHub] [cloudstack] sonarcloud[bot] commented on pull request #6567: Password policies

Posted by GitBox <gi...@apache.org>.
sonarcloud[bot] commented on PR #6567:
URL: https://github.com/apache/cloudstack/pull/6567#issuecomment-1194123381

   SonarCloud Quality Gate failed.&nbsp; &nbsp; [![Quality Gate failed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/failed-16px.png 'Quality Gate failed')](https://sonarcloud.io/dashboard?id=apache_cloudstack&pullRequest=6567)
   
   [![Bug](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/bug-16px.png 'Bug')](https://sonarcloud.io/project/issues?id=apache_cloudstack&pullRequest=6567&resolved=false&types=BUG) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/issues?id=apache_cloudstack&pullRequest=6567&resolved=false&types=BUG) [0 Bugs](https://sonarcloud.io/project/issues?id=apache_cloudstack&pullRequest=6567&resolved=false&types=BUG)  
   [![Vulnerability](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/vulnerability-16px.png 'Vulnerability')](https://sonarcloud.io/project/issues?id=apache_cloudstack&pullRequest=6567&resolved=false&types=VULNERABILITY) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/issues?id=apache_cloudstack&pullRequest=6567&resolved=false&types=VULNERABILITY) [0 Vulnerabilities](https://sonarcloud.io/project/issues?id=apache_cloudstack&pullRequest=6567&resolved=false&types=VULNERABILITY)  
   [![Security Hotspot](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/security_hotspot-16px.png 'Security Hotspot')](https://sonarcloud.io/project/security_hotspots?id=apache_cloudstack&pullRequest=6567&resolved=false&types=SECURITY_HOTSPOT) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/security_hotspots?id=apache_cloudstack&pullRequest=6567&resolved=false&types=SECURITY_HOTSPOT) [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=apache_cloudstack&pullRequest=6567&resolved=false&types=SECURITY_HOTSPOT)  
   [![Code Smell](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/code_smell-16px.png 'Code Smell')](https://sonarcloud.io/project/issues?id=apache_cloudstack&pullRequest=6567&resolved=false&types=CODE_SMELL) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/issues?id=apache_cloudstack&pullRequest=6567&resolved=false&types=CODE_SMELL) [8 Code Smells](https://sonarcloud.io/project/issues?id=apache_cloudstack&pullRequest=6567&resolved=false&types=CODE_SMELL)
   
   [![0.0%](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/CoverageChart/0-16px.png '0.0%')](https://sonarcloud.io/component_measures?id=apache_cloudstack&pullRequest=6567&metric=new_coverage&view=list) [0.0% Coverage](https://sonarcloud.io/component_measures?id=apache_cloudstack&pullRequest=6567&metric=new_coverage&view=list)  
   [![0.0%](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/Duplications/3-16px.png '0.0%')](https://sonarcloud.io/component_measures?id=apache_cloudstack&pullRequest=6567&metric=new_duplicated_lines_density&view=list) [0.0% Duplication](https://sonarcloud.io/component_measures?id=apache_cloudstack&pullRequest=6567&metric=new_duplicated_lines_density&view=list)
   
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@cloudstack.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


[GitHub] [cloudstack] JoaoJandre commented on a diff in pull request #6567: Password policies

Posted by GitBox <gi...@apache.org>.
JoaoJandre commented on code in PR #6567:
URL: https://github.com/apache/cloudstack/pull/6567#discussion_r931490279


##########
server/src/main/java/com/cloud/user/PasswordPolicyImpl.java:
##########
@@ -0,0 +1,245 @@
+// 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.log4j.Logger;
+
+public class PasswordPolicyImpl implements PasswordPolicy, Configurable {
+
+    private Logger logger = Logger.getLogger(PasswordPolicyImpl.class);
+
+    public void verifyIfPasswordCompliesWithPasswordPolicies(String password, String username, Long domainId) {
+        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 (passwordPolicyRegex == null){
+            logger.trace(String.format("Regex is null; 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) {

Review Comment:
   Although this is true, using these methods makes unit testing easier and the code more organized.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@cloudstack.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


Re: [PR] Password policies [cloudstack]

Posted by "JoaoJandre (via GitHub)" <gi...@apache.org>.
JoaoJandre commented on PR #6567:
URL: https://github.com/apache/cloudstack/pull/6567#issuecomment-1860353728

   > This looks like a regression of the pr https://lists.apache.org/thread/tqtcwsfb0knmvqct1hlow4ty2nc2w2j2
   > 
   > cc @JoaoJandre @DaanHoogland @GutoVeronezi
   
   @weizhouapache  I've opened a PR to fix this.


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@cloudstack.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


[GitHub] [cloudstack] DaanHoogland merged pull request #6567: Password policies

Posted by GitBox <gi...@apache.org>.
DaanHoogland merged PR #6567:
URL: https://github.com/apache/cloudstack/pull/6567


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@cloudstack.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


[GitHub] [cloudstack] blueorangutan commented on pull request #6567: Password policies

Posted by GitBox <gi...@apache.org>.
blueorangutan commented on PR #6567:
URL: https://github.com/apache/cloudstack/pull/6567#issuecomment-1201826024

   <b>Trillian test result (tid-4587)</b>
   Environment: kvm-centos7 (x2), Advanced Networking with Mgmt server 7
   Total time taken: 43261 seconds
   Marvin logs: https://github.com/blueorangutan/acs-prs/releases/download/trillian/pr6567-t4587-kvm-centos7.zip
   Smoke tests completed. 99 look OK, 1 have errors
   Only failed tests results shown below:
   
   
   Test | Result | Time (s) | Test File
   --- | --- | --- | ---
   test_08_upgrade_kubernetes_ha_cluster | `Failure` | 582.18 | test_kubernetes_clusters.py
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@cloudstack.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


[GitHub] [cloudstack] DaanHoogland commented on pull request #6567: Password policies

Posted by GitBox <gi...@apache.org>.
DaanHoogland commented on PR #6567:
URL: https://github.com/apache/cloudstack/pull/6567#issuecomment-1202099375

   @harikrishna-patnala are your concerns met?


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@cloudstack.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


[GitHub] [cloudstack] blueorangutan commented on pull request #6567: Password policies

Posted by GitBox <gi...@apache.org>.
blueorangutan commented on PR #6567:
URL: https://github.com/apache/cloudstack/pull/6567#issuecomment-1196297679

   @harikrishna-patnala a Jenkins job has been kicked to build packages. It will be bundled with  KVM, XenServer and VMware SystemVM templates. I'll keep you posted as I make progress.


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@cloudstack.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


[GitHub] [cloudstack] blueorangutan commented on pull request #6567: Password policies

Posted by GitBox <gi...@apache.org>.
blueorangutan commented on PR #6567:
URL: https://github.com/apache/cloudstack/pull/6567#issuecomment-1196321370

   Packaging result: :heavy_check_mark: el7 :heavy_check_mark: el8 :heavy_check_mark: debian :heavy_check_mark: suse15. SL-JID 3832


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@cloudstack.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


Re: [PR] Password policies [cloudstack]

Posted by "weizhouapache (via GitHub)" <gi...@apache.org>.
weizhouapache commented on PR #6567:
URL: https://github.com/apache/cloudstack/pull/6567#issuecomment-1858778007

   This looks like a regression of the pr
   https://lists.apache.org/thread/tqtcwsfb0knmvqct1hlow4ty2nc2w2j2
   
   cc @JoaoJandre @DaanHoogland @GutoVeronezi 


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@cloudstack.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


[GitHub] [cloudstack] blueorangutan commented on pull request #6567: Password policies

Posted by GitBox <gi...@apache.org>.
blueorangutan commented on PR #6567:
URL: https://github.com/apache/cloudstack/pull/6567#issuecomment-1201034505

   @DaanHoogland a Trillian-Jenkins test job (centos7 mgmt + kvm-centos7) has been kicked to run smoke tests


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@cloudstack.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


[GitHub] [cloudstack] DaanHoogland commented on pull request #6567: Password policies

Posted by GitBox <gi...@apache.org>.
DaanHoogland commented on PR #6567:
URL: https://github.com/apache/cloudstack/pull/6567#issuecomment-1201033912

   @blueorangutan test


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@cloudstack.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org