From 0c8f3674c5e89d1f68a150395786c5f2d3f517c9 Mon Sep 17 00:00:00 2001 From: Alex O'Ree Date: Sun, 23 Nov 2025 08:03:04 -0500 Subject: [PATCH 1/2] JSPWIKI-1248 adds a new password complexity check mechanism, measurement of the quantity of characters that have changed --- .../apache/wiki/auth/DefaultUserManager.java | 2 +- .../auth/PasswordComplexityVeriffier.java | 22 +++++++++++- .../main/resources/CoreResources.properties | 3 +- .../resources/CoreResources_de.properties | 1 + .../resources/CoreResources_es.properties | 1 + .../resources/CoreResources_fi.properties | 1 + .../resources/CoreResources_fr.properties | 1 + .../resources/CoreResources_it.properties | 1 + .../resources/CoreResources_nl.properties | 1 + .../resources/CoreResources_pt_BR.properties | 1 + .../resources/CoreResources_ru.properties | 1 + .../resources/CoreResources_zh_CN.properties | 1 + .../auth/PasswordComplexityVeriffierTest.java | 34 +++++++++++++------ 13 files changed, 56 insertions(+), 14 deletions(-) diff --git a/jspwiki-main/src/main/java/org/apache/wiki/auth/DefaultUserManager.java b/jspwiki-main/src/main/java/org/apache/wiki/auth/DefaultUserManager.java index ac76f6471a..d51171d89a 100644 --- a/jspwiki-main/src/main/java/org/apache/wiki/auth/DefaultUserManager.java +++ b/jspwiki-main/src/main/java/org/apache/wiki/auth/DefaultUserManager.java @@ -355,7 +355,7 @@ public void validateProfile( final Context context, final UserProfile profile ) if( !profile.isNew() && !getUserDatabase().validatePassword( profile.getLoginName(), password0 ) ) { session.addMessage( SESSION_MESSAGES, rb.getString( "security.error.passwordnomatch" ) ); } - List msg = PasswordComplexityVeriffier.validate(password2, context); + List msg = PasswordComplexityVeriffier.validate(password2, password0, context); for (String s : msg) { session.addMessage( SESSION_MESSAGES, s ); } diff --git a/jspwiki-main/src/main/java/org/apache/wiki/auth/PasswordComplexityVeriffier.java b/jspwiki-main/src/main/java/org/apache/wiki/auth/PasswordComplexityVeriffier.java index 43f709ee4b..10240aec79 100644 --- a/jspwiki-main/src/main/java/org/apache/wiki/auth/PasswordComplexityVeriffier.java +++ b/jspwiki-main/src/main/java/org/apache/wiki/auth/PasswordComplexityVeriffier.java @@ -44,7 +44,7 @@ private PasswordComplexityVeriffier() { * @param context * @return see above */ - public static List validate(String pwd, Context context) { + public static List validate(String pwd, String previousPwd, Context context) { Properties wikiProps = context.getEngine().getWikiProperties(); final ResourceBundle rb = ResourceBundle.getBundle(InternationalizationManager.CORE_BUNDLE, context.getWikiSession().getLocale()); @@ -55,6 +55,7 @@ public static List validate(String pwd, Context context) { int minDigits = Integer.parseInt(wikiProps.getProperty("jspwiki.credentials.minDigits", "1")); int minSymbols = Integer.parseInt(wikiProps.getProperty("jspwiki.credentials.minSymbols", "1")); int maxRepeats = Integer.parseInt(wikiProps.getProperty("jspwiki.credentials.repeatingCharacters", "1")); + int minChanged = Integer.parseInt(wikiProps.getProperty("jspwiki.credentials.minChanged", "0")); //potential future enhancement, detect common patterns like keyboard walks, asdf, etc //boolean allowCommonPatterns = "true".equalsIgnoreCase(wikiProps.getProperty("jspwiki.credentials.allowCommonPatterns", "false")); //potential future enhancements, detect common numerical patterns, such as 1234 oe 4321 @@ -69,6 +70,7 @@ public static List validate(String pwd, Context context) { problems.add(MessageFormat.format(rb.getString("pwdcheck.tooshort"), minLength)); } char[] cred = pwd.toCharArray(); + int qtyChanged = 0; int upper = 0; int lower = 0; int digits = 0; @@ -108,6 +110,24 @@ public static List validate(String pwd, Context context) { } } } + + //quantity of changed character test + if (minChanged > 0 && previousPwd != null) { + for (int i = 0; i < cred.length; i++) { + if ((i + 1) < previousPwd.length()) { + if (previousPwd.charAt(i) != cred[i]) { + qtyChanged++; + } + } else { + //i.e. the new pass is longer than the old one + break; + } + + } + if (qtyChanged < minChanged) { + problems.add(MessageFormat.format(rb.getString("pwdcheck.minchanged"), minChanged)); + } + } if (repeatCheck) { if (localrepeats > repeats) { diff --git a/jspwiki-main/src/main/resources/CoreResources.properties b/jspwiki-main/src/main/resources/CoreResources.properties index 6f1cee8641..d2116a2ffe 100644 --- a/jspwiki-main/src/main/resources/CoreResources.properties +++ b/jspwiki-main/src/main/resources/CoreResources.properties @@ -235,4 +235,5 @@ pwdcheck.minLower=Not enough lower case (a-z) characters, min={0} pwdcheck.minDigits=Not enough digits (0-9), min={0} pwdcheck.minOther=Not enough symbols or other characters, min={0} pwdcheck.tooshort=Password too short, min={0} -pwdcheck.toolong=Password too long, max={0} \ No newline at end of file +pwdcheck.toolong=Password too long, max={0} +pwdcheck.minchanged=Not enough characters changed, min={0} \ No newline at end of file diff --git a/jspwiki-main/src/main/resources/CoreResources_de.properties b/jspwiki-main/src/main/resources/CoreResources_de.properties index d7caa21352..fa6eef5ed7 100644 --- a/jspwiki-main/src/main/resources/CoreResources_de.properties +++ b/jspwiki-main/src/main/resources/CoreResources_de.properties @@ -253,3 +253,4 @@ pwdcheck.minDigits=Nicht gen\u00fcgend Ziffern (0-9), min={0} pwdcheck.minOther=Nicht gen\u00fcgend Symbole oder andere Zeichen, min={0} pwdcheck.tooshort=Passwort zu kurz, min={0} pwdcheck.toolong=Passwort zu lang, max={0} +pwdcheck.minchanged=Nicht gen\u00fcgend Zeichen ge\u00e4ndert, min={0} diff --git a/jspwiki-main/src/main/resources/CoreResources_es.properties b/jspwiki-main/src/main/resources/CoreResources_es.properties index 4db7afd11b..b9fac12a8c 100644 --- a/jspwiki-main/src/main/resources/CoreResources_es.properties +++ b/jspwiki-main/src/main/resources/CoreResources_es.properties @@ -220,3 +220,4 @@ pwdcheck.minDigits=No hay suficientes d\u00edgitos (0-9), min={0} pwdcheck.minOther=No hay suficientes s\u00edmbolos u otros caracteres, min={0} pwdcheck.tooshort=Contrase\u00f1a demasiado corta, min={0} pwdcheck.toolong=Contrase\u00f1a demasiado larga, m\u00e1ximo={0} +pwdcheck.minchanged=No se cambiaron suficientes caracteres, min={0} diff --git a/jspwiki-main/src/main/resources/CoreResources_fi.properties b/jspwiki-main/src/main/resources/CoreResources_fi.properties index 01e35f6900..0f9c536b3f 100644 --- a/jspwiki-main/src/main/resources/CoreResources_fi.properties +++ b/jspwiki-main/src/main/resources/CoreResources_fi.properties @@ -204,3 +204,4 @@ pwdcheck.minDigits=Ei tarpeeksi numeroita (0-9), min={0} pwdcheck.minOther=Ei tarpeeksi symboleja tai muita merkkej\u00e4, min={0} pwdcheck.tooshort=Salasana liian lyhyt, min={0} pwdcheck.toolong=Salasana liian pitk\u00e4, max={0} +pwdcheck.minchanged=Pas assez de caract\u00e8res modifi\u00e9s, min={0} diff --git a/jspwiki-main/src/main/resources/CoreResources_fr.properties b/jspwiki-main/src/main/resources/CoreResources_fr.properties index a1b3b7c5eb..db2a475b5d 100644 --- a/jspwiki-main/src/main/resources/CoreResources_fr.properties +++ b/jspwiki-main/src/main/resources/CoreResources_fr.properties @@ -254,3 +254,4 @@ pwdcheck.minDigits=Nombre de chiffres insuffisant (0-9), min={0} pwdcheck.minOther=Pas assez de symboles ou d'autres caract\u00e8res, min={0} pwdcheck.tooshort=Mot de passe trop court, min={0} pwdcheck.toolong=Mot de passe trop long, max={0} +pwdcheck.minchanged=Pas assez de caract\u00e8res modifi\u00e9s, min={0} diff --git a/jspwiki-main/src/main/resources/CoreResources_it.properties b/jspwiki-main/src/main/resources/CoreResources_it.properties index c3e80388cb..88fa21e0d8 100644 --- a/jspwiki-main/src/main/resources/CoreResources_it.properties +++ b/jspwiki-main/src/main/resources/CoreResources_it.properties @@ -241,3 +241,4 @@ pwdcheck.minDigits=Cifre insufficienti (0-9), min={0} pwdcheck.minOther=Simboli o altri caratteri non sufficienti, min={0} pwdcheck.tooshort=Password troppo corta, min={0} pwdcheck.toolong=Password troppo lunga, max={0} +pwdcheck.minchanged=Non sono stati modificati abbastanza caratteri, min={0} diff --git a/jspwiki-main/src/main/resources/CoreResources_nl.properties b/jspwiki-main/src/main/resources/CoreResources_nl.properties index 8cf7806802..d4cadc7573 100644 --- a/jspwiki-main/src/main/resources/CoreResources_nl.properties +++ b/jspwiki-main/src/main/resources/CoreResources_nl.properties @@ -239,3 +239,4 @@ pwdcheck.minDigits=Niet genoeg cijfers (0-9), min={0} pwdcheck.minOther=Niet genoeg symbolen of andere tekens, min={0} pwdcheck.tooshort=Wachtwoord te kort, min={0} pwdcheck.toolong=Wachtwoord te lang, max={0} +pwdcheck.minchanged=Er zijn niet genoeg tekens gewijzigd, min={0} diff --git a/jspwiki-main/src/main/resources/CoreResources_pt_BR.properties b/jspwiki-main/src/main/resources/CoreResources_pt_BR.properties index 4e6a7d46d8..e1a955013b 100644 --- a/jspwiki-main/src/main/resources/CoreResources_pt_BR.properties +++ b/jspwiki-main/src/main/resources/CoreResources_pt_BR.properties @@ -242,3 +242,4 @@ pwdcheck.minDigits=D\u00edgitos insuficientes (0-9), m\u00ednimo={0} pwdcheck.minOther=N\u00e3o h\u00e1 s\u00edmbolos ou outros caracteres suficientes, min={0} pwdcheck.tooshort=Senha muito curta, min={0} pwdcheck.toolong=Senha muito longa, m\u00e1ximo={0} +pwdcheck.minchanged=N\u00e3o foram alterados caracteres suficientes, min={0} diff --git a/jspwiki-main/src/main/resources/CoreResources_ru.properties b/jspwiki-main/src/main/resources/CoreResources_ru.properties index 0f0a9b046f..3b7914c332 100644 --- a/jspwiki-main/src/main/resources/CoreResources_ru.properties +++ b/jspwiki-main/src/main/resources/CoreResources_ru.properties @@ -247,3 +247,4 @@ pwdcheck.minDigits=\u041d\u0435\u0434\u043e\u0441\u0442\u0430\u0442\u043e\u0447\ pwdcheck.minOther=\u041d\u0435\u0434\u043e\u0441\u0442\u0430\u0442\u043e\u0447\u043d\u043e \u0441\u0438\u043c\u0432\u043e\u043b\u043e\u0432 \u0438\u043b\u0438 \u0434\u0440\u0443\u0433\u0438\u0445 \u0437\u043d\u0430\u043a\u043e\u0432, min={0} pwdcheck.tooshort=\u041f\u0430\u0440\u043e\u043b\u044c \u0441\u043b\u0438\u0448\u043a\u043e\u043c \u043a\u043e\u0440\u043e\u0442\u043a\u0438\u0439, \u043c\u0438\u043d={0} pwdcheck.toolong=\u041f\u0430\u0440\u043e\u043b\u044c \u0441\u043b\u0438\u0448\u043a\u043e\u043c \u0434\u043b\u0438\u043d\u043d\u044b\u0439, \u043c\u0430\u043a\u0441.={0} +pwdcheck.minchanged=\u041d\u0435\u0434\u043e\u0441\u0442\u0430\u0442\u043e\u0447\u043d\u043e \u0441\u0438\u043c\u0432\u043e\u043b\u043e\u0432 \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u043e, \u043c\u0438\u043d={0} diff --git a/jspwiki-main/src/main/resources/CoreResources_zh_CN.properties b/jspwiki-main/src/main/resources/CoreResources_zh_CN.properties index d26f4aa1da..970ffba66e 100644 --- a/jspwiki-main/src/main/resources/CoreResources_zh_CN.properties +++ b/jspwiki-main/src/main/resources/CoreResources_zh_CN.properties @@ -246,3 +246,4 @@ pwdcheck.minDigits=\u6570\u5b57\u4e0d\u8db3\uff080-9\uff09\uff0c\u6700\u5c0f\u50 pwdcheck.minOther=\u7b26\u53f7\u6216\u5176\u4ed6\u5b57\u7b26\u4e0d\u8db3\uff0c\u6700\u5c0f\u503c\u4e3a{0} pwdcheck.tooshort=\u5bc6\u7801\u8fc7\u77ed\uff0c\u6700\u5c0f\u503c\u4e3a{0} pwdcheck.toolong=\u5bc6\u7801\u8fc7\u957f\uff0c\u6700\u5927\u9650\u5236\u4e3a{0} +pwdcheck.minchanged=\u66f4\u6539\u7684\u5b57\u7b26\u6570\u4e0d\u8db3\uff0c\u6700\u5c0f\u503c\u4e3a0\u3002 diff --git a/jspwiki-main/src/test/java/org/apache/wiki/auth/PasswordComplexityVeriffierTest.java b/jspwiki-main/src/test/java/org/apache/wiki/auth/PasswordComplexityVeriffierTest.java index 84b63ca07c..f27a62bd75 100644 --- a/jspwiki-main/src/test/java/org/apache/wiki/auth/PasswordComplexityVeriffierTest.java +++ b/jspwiki-main/src/test/java/org/apache/wiki/auth/PasswordComplexityVeriffierTest.java @@ -49,6 +49,7 @@ public PasswordComplexityVeriffierTest() throws Exception { // allow X repeating characters but no more. 1 is the default. // i.e. 1 with "password" is ok but "passsword" is not props.setProperty("jspwiki.credentials.repeatingCharacters", "1"); + props.setProperty("jspwiki.credentials.minChanged", "1"); engine = TestEngine.build(props); context = new WikiContext(engine, new WikiPage(engine, "test")); @@ -57,43 +58,54 @@ public PasswordComplexityVeriffierTest() throws Exception { @Test public void testValidate() { - List result = PasswordComplexityVeriffier.validate("e,#Em1KG1!tez8EYmi?,", context); + List result = PasswordComplexityVeriffier.validate("e,#Em1KG1!tez8EYmi?,",null, context); Assertions.assertTrue(result.isEmpty(), StringUtils.join(result)); //too long - result = PasswordComplexityVeriffier.validate("e,#Em1KG1!tez8EYmi?,1234", context); + result = PasswordComplexityVeriffier.validate("e,#Em1KG1!tez8EYmi?,1234",null, context); Assertions.assertTrue(!result.isEmpty(), StringUtils.join(result)); //no upper, digits symbols - result = PasswordComplexityVeriffier.validate("abcedefghi", context); + result = PasswordComplexityVeriffier.validate("abcedefghi",null, context); Assertions.assertTrue(!result.isEmpty(), StringUtils.join(result)); //too short - result = PasswordComplexityVeriffier.validate("abc", context); + result = PasswordComplexityVeriffier.validate("abc",null, context); Assertions.assertTrue(!result.isEmpty(), StringUtils.join(result)); //no lower, digits, symbols - result = PasswordComplexityVeriffier.validate("ABCEDEFGHI", context); + result = PasswordComplexityVeriffier.validate("ABCEDEFGHI",null, context); Assertions.assertTrue(!result.isEmpty(), StringUtils.join(result)); //too many repeating - result = PasswordComplexityVeriffier.validate("a@CEDEFGHI222", context); + result = PasswordComplexityVeriffier.validate("a@CEDEFGHI222",null, context); Assertions.assertTrue(!result.isEmpty(), StringUtils.join(result)); - result = PasswordComplexityVeriffier.validate("a@CEDEFGHI222a", context); + result = PasswordComplexityVeriffier.validate("a@CEDEFGHI222a",null, context); Assertions.assertTrue(!result.isEmpty(), StringUtils.join(result)); - result = PasswordComplexityVeriffier.validate("aaa@CEDEFGHI2a", context); + result = PasswordComplexityVeriffier.validate("aaa@CEDEFGHI2a",null, context); Assertions.assertTrue(!result.isEmpty(), StringUtils.join(result)); - result = PasswordComplexityVeriffier.validate("aaa@CEbbbbGHI2a", context); + result = PasswordComplexityVeriffier.validate("aaa@CEbbbbGHI2a",null, context); Assertions.assertTrue(!result.isEmpty(), StringUtils.join(result)); - result = PasswordComplexityVeriffier.validate("a@CEbbGH4444", context); + result = PasswordComplexityVeriffier.validate("a@CEbbGH4444",null, context); Assertions.assertTrue(!result.isEmpty(), StringUtils.join(result)); - result = PasswordComplexityVeriffier.validate("aaaa@CEbbGH444", context); + result = PasswordComplexityVeriffier.validate("aaaa@CEbbGH444",null, context); Assertions.assertTrue(!result.isEmpty(), StringUtils.join(result)); + + //shorter to longer version + result = PasswordComplexityVeriffier.validate("e,#Em1KG1!tez8EYmi?,","e,#Em1KG1!tez8EYm,", context); + Assertions.assertTrue(!result.isEmpty(), StringUtils.join(result)); + + //longer to shorter + result = PasswordComplexityVeriffier.validate("e,#Em1KG1!tez8EYmi","e,#Em1KG1!tez8EYmi?", context); + Assertions.assertTrue(!result.isEmpty(), StringUtils.join(result)); + + result = PasswordComplexityVeriffier.validate("e,#Em1KG1!tez8EYmi?,","e,#Em1KG1!teAAEYmi?,", context); + Assertions.assertTrue(result.isEmpty(), StringUtils.join(result)); } } From 33d2669e2589cbe97c7f13c8e31bfba65c3165c8 Mon Sep 17 00:00:00 2001 From: Alex O'Ree Date: Sun, 23 Nov 2025 20:48:47 -0500 Subject: [PATCH 2/2] JSPWIKI-1248 after testing on the live server, it was noted that the default setting is 0 characters must be changed when changing passwords. so you can keep reusing the same one over and over again. changed to at least 1. --- .../main/java/org/apache/wiki/auth/DefaultUserManager.java | 4 ++++ jspwiki-main/src/main/resources/ini/jspwiki.properties | 3 ++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/jspwiki-main/src/main/java/org/apache/wiki/auth/DefaultUserManager.java b/jspwiki-main/src/main/java/org/apache/wiki/auth/DefaultUserManager.java index d51171d89a..512196a189 100644 --- a/jspwiki-main/src/main/java/org/apache/wiki/auth/DefaultUserManager.java +++ b/jspwiki-main/src/main/java/org/apache/wiki/auth/DefaultUserManager.java @@ -342,12 +342,16 @@ public void validateProfile( final Context context, final UserProfile profile ) if( !m_engine.getManager( AuthenticationManager.class ).isContainerAuthenticated() ) { // passwords must match and can't be null + + //this is the new password final String password = profile.getPassword(); if( password == null ) { session.addMessage( SESSION_MESSAGES, rb.getString( "security.error.blankpassword" ) ); } else { final HttpServletRequest request = context.getHttpRequest(); + //the existing password final String password0 = ( request == null ) ? null : request.getParameter( "password0" ); + //the new password confirmation final String password2 = ( request == null ) ? null : request.getParameter( "password2" ); if( !password.equals( password2 ) ) { session.addMessage( SESSION_MESSAGES, rb.getString( "security.error.passwordnomatch" ) ); diff --git a/jspwiki-main/src/main/resources/ini/jspwiki.properties b/jspwiki-main/src/main/resources/ini/jspwiki.properties index 7835c67bba..d415c79fe9 100644 --- a/jspwiki-main/src/main/resources/ini/jspwiki.properties +++ b/jspwiki-main/src/main/resources/ini/jspwiki.properties @@ -1132,5 +1132,6 @@ jspwiki.credentials.minSymbols=1 # allow X repeating characters but no more. 1 is the default. # i.e. 1 with "password" is ok but "passsword" is not jspwiki.credentials.repeatingCharacters=1 - +# when changing a password, at least this number of characters must be different +jspwiki.credentials.minChanged=1 ### End of configuration file.