vt-middleware / passay

Password policy enforcement for Java.
http://www.passay.org
Other
275 stars 63 forks source link

Creating a RepeatCharacterRegexRule generates an exception in certain Locale's #134

Closed ptmarquis closed 1 year ago

ptmarquis commented 1 year ago

Creating an instance of RepeatCharacterRegexRule will generate an exception in certain Locale's. The regular expression used internally by the class will include a repetition count that is non-ASCII. Below is a TestNG test case demonstrating the issue

package org.passay;

import java.util.Locale;
import org.testng.annotations.Test;

public class ArabicTest {
    @Test
    public void test() {
        // Get the current default Locale
        final Locale defaultLocale = Locale.getDefault();
        try {
            // Set the default Locale to ar-US
            final Locale arUS = new Locale.Builder()
                    .setLanguage("ar")
                    .setRegion("US")
                    .build();
            Locale.setDefault(arUS);

            // In this Locale, the generated regular expressions includes
            // a repetition count that is non-ASCII
            final RepeatCharacterRegexRule rule = new RepeatCharacterRegexRule();
        } finally {
            Locale.setDefault(defaultLocale);
        }
    }
}

Stack trace

java.util.regex.PatternSyntaxException: Illegal repetition near index 17
([^\x00-\x1F])\1{٤}
                 ^

    at java.base/java.util.regex.Pattern.error(Pattern.java:2028)
    at java.base/java.util.regex.Pattern.closure(Pattern.java:3309)
    at java.base/java.util.regex.Pattern.sequence(Pattern.java:2214)
    at java.base/java.util.regex.Pattern.expr(Pattern.java:2069)
    at java.base/java.util.regex.Pattern.compile(Pattern.java:1783)
    at java.base/java.util.regex.Pattern.<init>(Pattern.java:1430)
    at java.base/java.util.regex.Pattern.compile(Pattern.java:1069)
    at org.passay.IllegalRegexRule.<init>(IllegalRegexRule.java:61)
    at org.passay.RepeatCharacterRegexRule.<init>(RepeatCharacterRegexRule.java:56)
    at org.passay.RepeatCharacterRegexRule.<init>(RepeatCharacterRegexRule.java:44)
    at org.passay.RepeatCharacterRegexRule.<init>(RepeatCharacterRegexRule.java:33)
    at org.passay.ArabicTest.test(ArabicTest.java:20)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)
    at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.base/java.lang.reflect.Method.invoke(Method.java:568)
    at org.testng.internal.invokers.MethodInvocationHelper.invokeMethod(MethodInvocationHelper.java:135)
    at org.testng.internal.invokers.TestInvoker.invokeMethod(TestInvoker.java:673)
    at org.testng.internal.invokers.TestInvoker.invokeTestMethod(TestInvoker.java:220)
    at org.testng.internal.invokers.MethodRunner.runInSequence(MethodRunner.java:50)
    at org.testng.internal.invokers.TestInvoker$MethodInvocationAgent.invoke(TestInvoker.java:945)
    at org.testng.internal.invokers.TestInvoker.invokeTestMethods(TestInvoker.java:193)
    at org.testng.internal.invokers.TestMethodWorker.invokeTestMethods(TestMethodWorker.java:146)
    at org.testng.internal.invokers.TestMethodWorker.run(TestMethodWorker.java:128)
    at java.base/java.util.ArrayList.forEach(ArrayList.java:1511)
    at org.testng.TestRunner.privateRun(TestRunner.java:808)
    at org.testng.TestRunner.run(TestRunner.java:603)
    at org.testng.SuiteRunner.runTest(SuiteRunner.java:429)
    at org.testng.SuiteRunner.runSequentially(SuiteRunner.java:423)
    at org.testng.SuiteRunner.privateRun(SuiteRunner.java:383)
    at org.testng.SuiteRunner.run(SuiteRunner.java:326)
    at org.testng.SuiteRunnerWorker.runSuite(SuiteRunnerWorker.java:52)
    at org.testng.SuiteRunnerWorker.run(SuiteRunnerWorker.java:95)
    at org.testng.TestNG.runSuitesSequentially(TestNG.java:1249)
    at org.testng.TestNG.runSuitesLocally(TestNG.java:1169)
    at org.testng.TestNG.runSuites(TestNG.java:1092)
    at org.testng.TestNG.run(TestNG.java:1060)
    at com.intellij.rt.testng.IDEARemoteTestNG.run(IDEARemoteTestNG.java:66)
    at com.intellij.rt.testng.RemoteTestNGStarter.main(RemoteTestNGStarter.java:109)
ptmarquis commented 1 year ago

One way to fix this is to specify a Locale in the call to String#format when creating the regular expression. Diff below

diff --git a/src/main/java/org/passay/RepeatCharacterRegexRule.java b/src/main/java/org/passay/RepeatCharacterRegexRule.java
index c0b89e4..4250d39 100644
--- a/src/main/java/org/passay/RepeatCharacterRegexRule.java
+++ b/src/main/java/org/passay/RepeatCharacterRegexRule.java
@@ -1,6 +1,8 @@
 /* See LICENSE for licensing and NOTICE for copyright. */
 package org.passay;

+import java.util.Locale;
+
 /**
  * Rule for determining if a password contains a duplicate ASCII keyboard sequence. See {@link java.util.regex.Pattern}
  * /p{ASCII}. The default sequence length is 5 characters.
@@ -53,7 +55,7 @@ public class RepeatCharacterRegexRule extends IllegalRegexRule
    */
   public RepeatCharacterRegexRule(final int sl, final boolean reportAll)
   {
-    super(String.format(REPEAT_CHAR_REGEX, sl - 1), reportAll);
+    super(String.format(Locale.ENGLISH, REPEAT_CHAR_REGEX, sl - 1), reportAll);
     if (sl < MINIMUM_SEQUENCE_LENGTH) {
       throw new IllegalArgumentException(String.format("sequence length must be >= %s", MINIMUM_SEQUENCE_LENGTH));
     }
ptmarquis commented 1 year ago

Thanks for fixing this so quickly!