diff --git a/libraries-security/pom.xml b/libraries-security/pom.xml
index a57f029702..3077abc29c 100644
--- a/libraries-security/pom.xml
+++ b/libraries-security/pom.xml
@@ -38,6 +38,16 @@
${junit.version}
test
+
+ org.passay
+ passay
+ 1.3.1
+
+
+ org.cryptacular
+ cryptacular
+ 1.2.2
+
diff --git a/libraries-security/src/test/java/com/baeldung/passay/NegativeMatchingRulesUnitTest.java b/libraries-security/src/test/java/com/baeldung/passay/NegativeMatchingRulesUnitTest.java
new file mode 100644
index 0000000000..5054a5880e
--- /dev/null
+++ b/libraries-security/src/test/java/com/baeldung/passay/NegativeMatchingRulesUnitTest.java
@@ -0,0 +1,149 @@
+package com.baeldung.passay;
+
+import org.cryptacular.bean.EncodingHashBean;
+import org.cryptacular.spec.CodecSpec;
+import org.cryptacular.spec.DigestSpec;
+import org.junit.Assert;
+import org.junit.Test;
+import org.passay.DictionaryRule;
+import org.passay.DictionarySubstringRule;
+import org.passay.DigestHistoryRule;
+import org.passay.EnglishSequenceData;
+import org.passay.HistoryRule;
+import org.passay.IllegalCharacterRule;
+import org.passay.IllegalRegexRule;
+import org.passay.IllegalSequenceRule;
+import org.passay.NumberRangeRule;
+import org.passay.PasswordData;
+import org.passay.PasswordValidator;
+import org.passay.RepeatCharacterRegexRule;
+import org.passay.RuleResult;
+import org.passay.SourceRule;
+import org.passay.UsernameRule;
+import org.passay.WhitespaceRule;
+import org.passay.dictionary.ArrayWordList;
+import org.passay.dictionary.WordListDictionary;
+
+import java.util.Arrays;
+import java.util.List;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+
+public class NegativeMatchingRulesUnitTest {
+
+ @Test
+ public void givenDictionaryRules_whenValidatePassword_thenFoundIllegalWordsFromDictionary() {
+ ArrayWordList arrayWordList = new ArrayWordList(new String[] { "bar", "foobar" });
+
+ WordListDictionary wordListDictionary = new WordListDictionary(arrayWordList);
+
+ DictionaryRule dictionaryRule = new DictionaryRule(wordListDictionary);
+ DictionarySubstringRule dictionarySubstringRule = new DictionarySubstringRule(wordListDictionary);
+
+ PasswordValidator passwordValidator = new PasswordValidator(dictionaryRule, dictionarySubstringRule);
+ RuleResult validate = passwordValidator.validate(new PasswordData("foobar"));
+
+ assertFalse(validate.isValid());
+ assertEquals("ILLEGAL_WORD:{matchingWord=foobar}", getDetail(validate, 0));
+ assertEquals("ILLEGAL_WORD:{matchingWord=bar}", getDetail(validate, 1));
+ }
+
+ @Test
+ public void givenHistoryRule_whenValidatePassword_thenFoundIllegalWordsFromHistory() {
+ HistoryRule historyRule = new HistoryRule();
+
+ PasswordData passwordData = new PasswordData("123");
+ passwordData.setPasswordReferences(new PasswordData.HistoricalReference("12345"), new PasswordData.HistoricalReference("1234"), new PasswordData.HistoricalReference("123"));
+
+ PasswordValidator passwordValidator = new PasswordValidator(historyRule);
+
+ RuleResult validate = passwordValidator.validate(passwordData);
+
+ assertFalse(validate.isValid());
+ assertEquals("HISTORY_VIOLATION:{historySize=3}", getDetail(validate, 0));
+ }
+
+ @Test
+ public void givenSeveralIllegalRules_whenValidatePassword_thenFoundSeveralIllegalPatterns() {
+ IllegalCharacterRule illegalCharacterRule = new IllegalCharacterRule(new char[] { 'a' });
+ IllegalRegexRule illegalRegexRule = new IllegalRegexRule("\\w{2}\\d{2}");
+ IllegalSequenceRule illegalSequenceRule = new IllegalSequenceRule(EnglishSequenceData.Alphabetical, 3, true);
+ NumberRangeRule numberRangeRule = new NumberRangeRule(1, 10);
+ WhitespaceRule whitespaceRule = new WhitespaceRule();
+
+ PasswordValidator passwordValidator = new PasswordValidator(illegalCharacterRule, illegalRegexRule, illegalSequenceRule, numberRangeRule, whitespaceRule);
+
+ RuleResult validate = passwordValidator.validate(new PasswordData("abcd22 "));
+
+ assertFalse(validate.isValid());
+ assertEquals("ILLEGAL_CHAR:{illegalCharacter=a, matchBehavior=contains}", getDetail(validate, 0));
+ assertEquals("ILLEGAL_MATCH:{match=cd22, pattern=\\w{2}\\d{2}}", getDetail(validate, 1));
+ assertEquals("ILLEGAL_ALPHABETICAL_SEQUENCE:{sequence=abc}", getDetail(validate, 2));
+ assertEquals("ILLEGAL_ALPHABETICAL_SEQUENCE:{sequence=bcd}", getDetail(validate, 3));
+ assertEquals("ILLEGAL_NUMBER_RANGE:{number=2, matchBehavior=contains}", getDetail(validate, 4));
+ assertEquals("ILLEGAL_WHITESPACE:{whitespaceCharacter= , matchBehavior=contains}", getDetail(validate, 5));
+ }
+
+ @Test
+ public void givenSourceRule_whenValidatePassword_thenFoundIllegalWordsFromSource() {
+ SourceRule sourceRule = new SourceRule();
+
+ PasswordData passwordData = new PasswordData("password");
+ passwordData.setPasswordReferences(new PasswordData.SourceReference("source", "password"));
+
+ PasswordValidator passwordValidator = new PasswordValidator(sourceRule);
+ RuleResult validate = passwordValidator.validate(passwordData);
+
+ assertFalse(validate.isValid());
+ assertEquals("SOURCE_VIOLATION:{source=source}", getDetail(validate, 0));
+ }
+
+ @Test
+ public void givenRepeatCharacterRegexRuleRule_whenValidatePassword_thenFoundIllegalPatternMatches() {
+ RepeatCharacterRegexRule repeatCharacterRegexRule = new RepeatCharacterRegexRule(3);
+
+ PasswordValidator passwordValidator = new PasswordValidator(repeatCharacterRegexRule);
+
+ RuleResult validate = passwordValidator.validate(new PasswordData("aaabbb"));
+
+ assertFalse(validate.isValid());
+ assertEquals("ILLEGAL_MATCH:{match=aaa, pattern=([^\\x00-\\x1F])\\1{2}}", getDetail(validate, 0));
+ assertEquals("ILLEGAL_MATCH:{match=bbb, pattern=([^\\x00-\\x1F])\\1{2}}", getDetail(validate, 1));
+ }
+
+ @Test
+ public void givenUserNameRule_whenValidatePassword_thenFoundUserNameInPassword() {
+ PasswordValidator passwordValidator = new PasswordValidator(new UsernameRule());
+
+ PasswordData passwordData = new PasswordData("testuser1234");
+ passwordData.setUsername("testuser");
+
+ RuleResult validate = passwordValidator.validate(passwordData);
+
+ assertFalse(validate.isValid());
+ assertEquals("ILLEGAL_USERNAME:{username=testuser, matchBehavior=contains}", getDetail(validate, 0));
+ }
+
+ @Test
+ public void givenPasswordAndHashBeanAndEncryptedReferences_whenValidate_thenPasswordValidationShouldPass() {
+ List historicalReferences = Arrays.asList(new PasswordData.HistoricalReference("SHA256", "2e4551de804e27aacf20f9df5be3e8cd384ed64488b21ab079fb58e8c90068ab"));
+ PasswordData passwordData = new PasswordData("example!");
+ passwordData.setPasswordReferences(historicalReferences);
+
+ EncodingHashBean encodingHashBean = new EncodingHashBean(new CodecSpec("Base64"), new DigestSpec("SHA256"), 1, false);
+
+ PasswordValidator passwordValidator = new PasswordValidator(new DigestHistoryRule(encodingHashBean));
+
+ RuleResult validate = passwordValidator.validate(passwordData);
+
+ Assert.assertTrue(validate.isValid());
+ }
+
+ private String getDetail(RuleResult validate, int i) {
+ return validate.getDetails()
+ .get(i)
+ .toString();
+ }
+
+}
diff --git a/libraries-security/src/test/java/com/baeldung/passay/PasswordGeneratorUnitTest.java b/libraries-security/src/test/java/com/baeldung/passay/PasswordGeneratorUnitTest.java
new file mode 100644
index 0000000000..ff279e9317
--- /dev/null
+++ b/libraries-security/src/test/java/com/baeldung/passay/PasswordGeneratorUnitTest.java
@@ -0,0 +1,50 @@
+package com.baeldung.passay;
+
+import org.junit.Assert;
+import org.junit.Test;
+import org.passay.CharacterData;
+import org.passay.CharacterRule;
+import org.passay.EnglishCharacterData;
+import org.passay.PasswordGenerator;
+
+import java.util.stream.Stream;
+
+public class PasswordGeneratorUnitTest {
+
+ @Test
+ public void givenDigitsGenerator_whenGeneratingPassword_thenPasswordContainsDigitsHasLength10() {
+ CharacterRule digits = new CharacterRule(EnglishCharacterData.Digit);
+
+ PasswordGenerator passwordGenerator = new PasswordGenerator();
+ String password = passwordGenerator.generatePassword(10, digits);
+
+ Assert.assertTrue(password.length() == 10);
+ Assert.assertTrue(containsOnlyCharactersFromSet(password, "0123456789"));
+ }
+
+ @Test
+ public void givenCustomizedRule_whenGenerating_thenGeneratedPasswordContainsCustomizedCharacters() {
+ CharacterRule specialCharacterRule = new CharacterRule(new CharacterData() {
+ @Override
+ public String getErrorCode() {
+ return "SAMPLE_ERROR_CODE";
+ }
+
+ @Override
+ public String getCharacters() {
+ return "ABCxyz123!@#";
+ }
+ });
+
+ PasswordGenerator passwordGenerator = new PasswordGenerator();
+ String password = passwordGenerator.generatePassword(10, specialCharacterRule);
+
+ Assert.assertTrue(containsOnlyCharactersFromSet(password, "ABCxyz123!@#"));
+ }
+
+ private boolean containsOnlyCharactersFromSet(String password, String setOfCharacters) {
+ return Stream.of(password.split(""))
+ .allMatch(it -> setOfCharacters.contains(it));
+ }
+
+}
diff --git a/libraries-security/src/test/java/com/baeldung/passay/PasswordValidatorUnitTest.java b/libraries-security/src/test/java/com/baeldung/passay/PasswordValidatorUnitTest.java
new file mode 100644
index 0000000000..3fc59a82d5
--- /dev/null
+++ b/libraries-security/src/test/java/com/baeldung/passay/PasswordValidatorUnitTest.java
@@ -0,0 +1,81 @@
+package com.baeldung.passay;
+
+import org.junit.Assert;
+import org.junit.Test;
+import org.passay.LengthRule;
+import org.passay.MessageResolver;
+import org.passay.PasswordData;
+import org.passay.PasswordValidator;
+import org.passay.PropertiesMessageResolver;
+import org.passay.RuleResult;
+import org.passay.RuleResultDetail;
+import org.passay.RuleResultMetadata;
+import org.passay.WhitespaceRule;
+
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.net.URL;
+import java.util.Properties;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+
+public class PasswordValidatorUnitTest {
+
+ @Test
+ public void givenPasswordValidatorWithLengthRule_whenValidation_thenTooShortPassword() {
+ PasswordData passwordData = new PasswordData("1234");
+
+ PasswordValidator passwordValidator = new PasswordValidator(new LengthRule(5));
+
+ RuleResult validate = passwordValidator.validate(passwordData);
+ assertEquals(false, validate.isValid());
+
+ RuleResultDetail ruleResultDetail = validate.getDetails()
+ .get(0);
+ assertEquals("TOO_SHORT", ruleResultDetail.getErrorCode());
+ assertEquals(5, ruleResultDetail.getParameters()
+ .get("minimumLength"));
+ assertEquals(5, ruleResultDetail.getParameters()
+ .get("maximumLength"));
+
+ Integer lengthCount = validate.getMetadata()
+ .getCounts()
+ .get(RuleResultMetadata.CountCategory.Length);
+ assertEquals(Integer.valueOf(4), lengthCount);
+ }
+
+ @Test
+ public void givenPasswordValidatorWithLengthRule_whenValidation_thenTooLongPassword() {
+ PasswordData passwordData = new PasswordData("123456");
+
+ PasswordValidator passwordValidator = new PasswordValidator(new LengthRule(5));
+
+ RuleResult validate = passwordValidator.validate(passwordData);
+ assertFalse(validate.isValid());
+ Assert.assertEquals("TOO_LONG", validate.getDetails()
+ .get(0)
+ .getErrorCode());
+ }
+
+ @Test
+ public void givenPasswordValidatorWithLengthRule_whenValidation_thenCustomizedMeesagesAvailable() throws IOException {
+ URL resource = this.getClass()
+ .getClassLoader()
+ .getResource("messages.properties");
+ Properties props = new Properties();
+ props.load(new FileInputStream(resource.getPath()));
+
+ MessageResolver resolver = new PropertiesMessageResolver(props);
+ PasswordValidator validator = new PasswordValidator(resolver, new LengthRule(8, 16), new WhitespaceRule());
+
+ RuleResult tooShort = validator.validate(new PasswordData("XXXX"));
+ RuleResult tooLong = validator.validate(new PasswordData("ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ"));
+
+ assertEquals("Password must not contain less characters than 16.", validator.getMessages(tooShort)
+ .get(0));
+ assertEquals("Password must not have more characters than 16.", validator.getMessages(tooLong)
+ .get(0));
+ }
+
+}
diff --git a/libraries-security/src/test/java/com/baeldung/passay/PositiveMatchingRulesUnitTest.java b/libraries-security/src/test/java/com/baeldung/passay/PositiveMatchingRulesUnitTest.java
new file mode 100644
index 0000000000..0da1b43335
--- /dev/null
+++ b/libraries-security/src/test/java/com/baeldung/passay/PositiveMatchingRulesUnitTest.java
@@ -0,0 +1,76 @@
+package com.baeldung.passay;
+
+import org.junit.Test;
+import org.passay.AllowedCharacterRule;
+import org.passay.AllowedRegexRule;
+import org.passay.CharacterCharacteristicsRule;
+import org.passay.CharacterRule;
+import org.passay.EnglishCharacterData;
+import org.passay.LengthComplexityRule;
+import org.passay.LengthRule;
+import org.passay.PasswordData;
+import org.passay.PasswordValidator;
+import org.passay.RuleResult;
+
+import static org.junit.Assert.*;
+
+public class PositiveMatchingRulesUnitTest {
+
+ @Test
+ public void givenPasswordValidationRules_whenValidatingPassword_thenPosswordIsNotValidWithSeveralErrors() {
+ PasswordValidator passwordValidator = new PasswordValidator(new AllowedCharacterRule(new char[] { 'a', 'b', 'c' }), new AllowedRegexRule("\\d{2}\\w{10}"), new CharacterRule(EnglishCharacterData.LowerCase, 5), new LengthRule(8, 10));
+
+ RuleResult validate = passwordValidator.validate(new PasswordData("12abc"));
+
+ assertFalse(validate.isValid());
+ assertEquals("ALLOWED_CHAR:{illegalCharacter=1, matchBehavior=contains}", getDetail(validate, 0));
+ assertEquals("ALLOWED_CHAR:{illegalCharacter=2, matchBehavior=contains}", getDetail(validate, 1));
+ assertEquals("ALLOWED_MATCH:{pattern=\\d{2}\\w{10}}", getDetail(validate, 2));
+ assertEquals("INSUFFICIENT_LOWERCASE:{" + "minimumRequired=5, matchingCharacterCount=3, " + "validCharacters=abcdefghijklmnopqrstuvwxyz, " + "matchingCharacters=abc}", getDetail(validate, 3));
+ assertEquals("TOO_SHORT:{minimumLength=8, maximumLength=10}", getDetail(validate, 4));
+ }
+
+ @Test
+ public void givenRulesForDifferentPasswordLength_whenValidatingTwoDifferentPassword_thenBothOfThemAreInvalid() {
+ PasswordData shortPassword = new PasswordData("12ab");
+ PasswordData longPassword = new PasswordData("1234abcde");
+
+ LengthComplexityRule lengthComplexityRule = new LengthComplexityRule();
+ lengthComplexityRule.addRules("[1,5]", new CharacterRule(EnglishCharacterData.LowerCase, 5));
+ lengthComplexityRule.addRules("[6,10]", new AllowedCharacterRule(new char[] { 'a', 'b', 'c', 'd' }));
+
+ PasswordValidator passwordValidator = new PasswordValidator(lengthComplexityRule);
+
+ RuleResult validateShort = passwordValidator.validate(shortPassword);
+ RuleResult validateLong = passwordValidator.validate(longPassword);
+
+ assertFalse(validateShort.isValid());
+ assertFalse(validateLong.isValid());
+
+ assertEquals("INSUFFICIENT_LOWERCASE:{" + "minimumRequired=5, " + "matchingCharacterCount=2, " + "validCharacters=abcdefghijklmnopqrstuvwxyz, " + "matchingCharacters=ab}", getDetail(validateShort, 0));
+ assertEquals("ALLOWED_CHAR:{illegalCharacter=1, matchBehavior=contains}", getDetail(validateLong, 0));
+ }
+
+ @Test
+ public void givenCharacterCharacteristicsRule_whenValidatingPassword_thenItIsInvalidAsItBreaksToManyRules() {
+ PasswordData shortPassword = new PasswordData();
+ shortPassword.setPassword("12345abcde!");
+
+ CharacterCharacteristicsRule characterCharacteristicsRule = new CharacterCharacteristicsRule(4, new CharacterRule(EnglishCharacterData.LowerCase, 5), new CharacterRule(EnglishCharacterData.UpperCase, 5), new CharacterRule(EnglishCharacterData.Digit),
+ new CharacterRule(EnglishCharacterData.Special));
+
+ PasswordValidator passwordValidator = new PasswordValidator(characterCharacteristicsRule);
+
+ RuleResult validate = passwordValidator.validate(shortPassword);
+ assertFalse(validate.isValid());
+
+ assertEquals("INSUFFICIENT_UPPERCASE:{" + "minimumRequired=5, " + "matchingCharacterCount=0, " + "validCharacters=ABCDEFGHIJKLMNOPQRSTUVWXYZ, " + "matchingCharacters=}", getDetail(validate, 0));
+ assertEquals("INSUFFICIENT_CHARACTERISTICS:{" + "successCount=3, " + "minimumRequired=4, " + "ruleCount=4}", getDetail(validate, 1));
+ }
+
+ private String getDetail(RuleResult validate, int i) {
+ return validate.getDetails()
+ .get(i)
+ .toString();
+ }
+}
diff --git a/libraries-security/src/test/resources/messages.properties b/libraries-security/src/test/resources/messages.properties
new file mode 100644
index 0000000000..ad0039d71d
--- /dev/null
+++ b/libraries-security/src/test/resources/messages.properties
@@ -0,0 +1,2 @@
+TOO_LONG=Password must not have more characters than %2$s.
+TOO_SHORT=Password must not contain less characters than %2$s.
\ No newline at end of file