keycloak-aplcache

Details

diff --git a/forms/common-themes/src/main/resources/theme/base/account/messages/messages_de.properties b/forms/common-themes/src/main/resources/theme/base/account/messages/messages_de.properties
index b3b388f..3dbc156 100644
--- a/forms/common-themes/src/main/resources/theme/base/account/messages/messages_de.properties
+++ b/forms/common-themes/src/main/resources/theme/base/account/messages/messages_de.properties
@@ -89,14 +89,14 @@ identityProviderRemovedMessage=Identity Provider erfolgreich entfernt.
 accountDisabledMessage=Benutzerkonto ist gesperrt, bitte kontaktieren Sie den Admin.
 
 accountTemporarilyDisabledMessage=Benutzerkonto ist tempor\u00E4r gesperrt, bitte kontaktieren Sie den Admin oder versuchen Sie es sp\u00E4ter noch einmal.
-invalidPasswordMinLengthMessage=Ung\u00FCltiges Passwort: minimum l\u00E4nge {0}.
-invalidPasswordMinDigitsMessage=Ung\u00FCltiges Passwort: muss mindestens {0} Zahl(en) beinhalten.
-invalidPasswordMinLowerCaseCharsMessage=Ung\u00FCltiges Passwort: muss mindestens {0} Kleinbuchstaben beinhalten.
-invalidPasswordMinUpperCaseCharsMessage=Ung\u00FCltiges Passwort: muss mindestens {0} Grossbuchstaben beinhalten.
-invalidPasswordMinSpecialCharsMessage=Ung\u00FCltiges Passwort: muss mindestens {0} Spezialzeichen beinhalten.
-invalidPasswordNotUsernameMessage=Ung\u00FCltiges Passwort: darf nicht gleich sein wie Benutzername.
-invalidPasswordRegexPatternMessage=Ung\u00FCltiges Passwort: nicht Regex-Muster (n) entsprechen.
-invalidPasswordHistoryMessage=Ung\u00FCltiges Passwort: muss nicht gleich einem der letzten {0} Kennw\u00F6rter sein.
+invalidPasswordMinLengthMessage=Ung\u00FCltiges Passwort\: minimum l\u00E4nge {0}.
+invalidPasswordMinDigitsMessage=Ung\u00FCltiges Passwort\: muss mindestens {0} Zahl(en) beinhalten.
+invalidPasswordMinLowerCaseCharsMessage=Ung\u00FCltiges Passwort\: muss mindestens {0} Kleinbuchstaben beinhalten.
+invalidPasswordMinUpperCaseCharsMessage=Ung\u00FCltiges Passwort\: muss mindestens {0} Grossbuchstaben beinhalten.
+invalidPasswordMinSpecialCharsMessage=Ung\u00FCltiges Passwort\: muss mindestens {0} Spezialzeichen beinhalten.
+invalidPasswordNotUsernameMessage=Ung\u00FCltiges Passwort\: darf nicht gleich sein wie Benutzername.
+invalidPasswordRegexPatternMessage=Ung\u00FCltiges Passwort\: nicht Regex-Muster (n) entsprechen.
+invalidPasswordHistoryMessage=Ung\u00FCltiges Passwort {0}: darf nicht gleich einem der letzten Passwortgeschichte.
 
 locale_de=Deutsch
 locale_en=Englisch
diff --git a/forms/common-themes/src/main/resources/theme/base/account/messages/messages_en.properties b/forms/common-themes/src/main/resources/theme/base/account/messages/messages_en.properties
index 713904b..248f2ad 100755
--- a/forms/common-themes/src/main/resources/theme/base/account/messages/messages_en.properties
+++ b/forms/common-themes/src/main/resources/theme/base/account/messages/messages_en.properties
@@ -95,7 +95,7 @@ invalidPasswordMinUpperCaseCharsMessage=Invalid password: must contain at least 
 invalidPasswordMinSpecialCharsMessage=Invalid password: must contain at least {0} special characters.
 invalidPasswordNotUsernameMessage=Invalid password: must not be equal to the username.
 invalidPasswordRegexPatternMessage=Invalid password: fails to match regex pattern(s).
-invalidPasswordHistoryMessage=Invalid password: must not be equal to any of last {0} passwords.
+invalidPasswordHistoryMessage=Invalid password {0}: must not be equal to any of last password history.
 
 locale_de=German
 locale_en=English
diff --git a/forms/common-themes/src/main/resources/theme/base/account/messages/messages_pt_BR.properties b/forms/common-themes/src/main/resources/theme/base/account/messages/messages_pt_BR.properties
index 4af2355..27da886 100644
--- a/forms/common-themes/src/main/resources/theme/base/account/messages/messages_pt_BR.properties
+++ b/forms/common-themes/src/main/resources/theme/base/account/messages/messages_pt_BR.properties
@@ -53,7 +53,7 @@ invalidPasswordMinUpperCaseCharsMessage=Senha inv\u00E1lida\: deve conter pelo m
 invalidPasswordMinSpecialCharsMessage=Senha inv\u00E1lida\: deve conter pelo menos {0} caracteres especiais
 invalidPasswordNotUsernameMessage=Senha inv\u00E1lida\: n\u00E3o deve ser igual ao nome de usu\u00E1rio
 invalidPasswordRegexPatternMessage=Senha inv\u00E1lida\: n\u00E3o correspondem ao padr\u00E3o regex(s).
-invalidPasswordHistoryMessage=Senha inv\u00E1lida\: n�o deve ser igual a qualquer um dos �ltimos {0} senhas.
+invalidPasswordHistoryMessage=Senha inv\u00E1lida {0}\: n\u00E3o deve ser igual a qualquer uma \u00FAltima hist\u00F3ria senha.
 
 locale_de=Deutsch
 locale_en=English
diff --git a/forms/common-themes/src/main/resources/theme/base/login/messages/messages_de.properties b/forms/common-themes/src/main/resources/theme/base/login/messages/messages_de.properties
index 53e46a8..3ed399d 100644
--- a/forms/common-themes/src/main/resources/theme/base/login/messages/messages_de.properties
+++ b/forms/common-themes/src/main/resources/theme/base/login/messages/messages_de.properties
@@ -132,7 +132,7 @@ invalidPasswordMinUpperCaseCharsMessage=Ung\u00FCltiges Passwort\: muss mindeste
 invalidPasswordMinSpecialCharsMessage=Ung\u00FCltiges Passwort\: muss mindestens {0} Spezialzeichen beinhalten.
 invalidPasswordNotUsernameMessage=Ung\u00FCltiges Passwort\: darf nicht gleich sein wie Benutzername.
 invalidPasswordRegexPatternMessage=Ung\u00FCltiges Passwort\: nicht Regex-Muster (n) entsprechen.
-invalidPasswordHistoryMessage=Ung\u00FCltiges Passwort\: muss nicht gleich einem der letzten {0} Kennw\u00F6rter sein.
+invalidPasswordHistoryMessage=Ung\u00FCltiges Passwort {0}\: darf nicht gleich einem der letzten Passwortgeschichte.
 
 failedToProcessResponseMessage=Konnte Response nicht verarbeiten.
 httpsRequiredMessage=HTTPS erforderlich.
diff --git a/forms/common-themes/src/main/resources/theme/base/login/messages/messages_en.properties b/forms/common-themes/src/main/resources/theme/base/login/messages/messages_en.properties
index 11833e5..c20e1fa 100755
--- a/forms/common-themes/src/main/resources/theme/base/login/messages/messages_en.properties
+++ b/forms/common-themes/src/main/resources/theme/base/login/messages/messages_en.properties
@@ -129,7 +129,7 @@ invalidPasswordMinUpperCaseCharsMessage=Invalid password: must contain at least 
 invalidPasswordMinSpecialCharsMessage=Invalid password: must contain at least {0} special characters.
 invalidPasswordNotUsernameMessage=Invalid password: must not be equal to the username.
 invalidPasswordRegexPatternMessage=Invalid password: fails to match regex pattern(s).
-invalidPasswordHistoryMessage=Invalid password: must not be equal to any of last {0} passwords.
+invalidPasswordHistoryMessage=Invalid password {0}: must not be equal to any of last password history.
 
 failedToProcessResponseMessage=Failed to process response
 httpsRequiredMessage=HTTPS required
diff --git a/forms/common-themes/src/main/resources/theme/base/login/messages/messages_pt_BR.properties b/forms/common-themes/src/main/resources/theme/base/login/messages/messages_pt_BR.properties
index bd0c37c..245d8cb 100644
--- a/forms/common-themes/src/main/resources/theme/base/login/messages/messages_pt_BR.properties
+++ b/forms/common-themes/src/main/resources/theme/base/login/messages/messages_pt_BR.properties
@@ -119,14 +119,14 @@ accountPasswordUpdatedMessage=Sua senha foi atualizada
 
 noAccessMessage=Sem acesso
 
-invalidPasswordMinLengthMessage=Senha inv\u00E1lida: comprimento m\u00EDnimo {0}
+invalidPasswordMinLengthMessage=Senha inv\u00E1lida\: comprimento m\u00EDnimo {0}
 invalidPasswordMinDigitsMessage=Senha inv\u00E1lida\: deve conter pelo menos {0} d\u00EDgitos num\u00E9ricos
 invalidPasswordMinLowerCaseCharsMessage=Senha inv\u00E1lida\: deve conter pelo menos {0} caracteres min\u00FAsculos
 invalidPasswordMinUpperCaseCharsMessage=Senha inv\u00E1lida\: deve conter pelo menos {0} caracteres mai\u00FAsculos
 invalidPasswordMinSpecialCharsMessage=Senha inv\u00E1lida\: deve conter pelo menos {0} caracteres especiais
 invalidPasswordNotUsernameMessage=Senha inv\u00E1lida\: n\u00E3o deve ser igual ao nome de usu\u00E1rio
 invalidPasswordRegexPatternMessage=Senha inv\u00E1lida\: n\u00E3o correspondem ao padr\u00E3o regex(s).
-invalidPasswordHistoryMessage=Senha inv\u00E1lida\: n�o deve ser igual a qualquer um dos �ltimos {0} senhas.
+invalidPasswordHistoryMessage=Senha inv\u00E1lida {0}\: n\u00E3o deve ser igual a qualquer uma \u00FAltima hist\u00F3ria senha.
 
 failedToProcessResponseMessage=Falha ao processar a resposta
 httpsRequiredMessage=HTTPS requerido
diff --git a/model/api/src/test/java/org/keycloak/models/PasswordPolicyTest.java b/model/api/src/test/java/org/keycloak/models/PasswordPolicyTest.java
index 3bde361..14f28fb 100644
--- a/model/api/src/test/java/org/keycloak/models/PasswordPolicyTest.java
+++ b/model/api/src/test/java/org/keycloak/models/PasswordPolicyTest.java
@@ -1,5 +1,9 @@
 package org.keycloak.models;
 
+import static org.junit.Assert.fail;
+
+import java.util.regex.PatternSyntaxException;
+
 import org.junit.Assert;
 import org.junit.Test;
 
@@ -79,6 +83,48 @@ public class PasswordPolicyTest {
         Assert.assertEquals("invalidPasswordNotUsernameMessage", policy.validate("jdoe", "jdoe").getMessage());
         Assert.assertNull(policy.validate("jdoe", "ab&d1234"));
     }
+    
+    @Test
+    public void testRegexPatterns() {
+        PasswordPolicy policy = null;
+        try {
+            policy = new PasswordPolicy("regexPatterns");
+            fail("Expected NullPointerEXception: Regex Pattern cannot be null.");
+        } catch (NullPointerException e) {
+            // Expected NPE as regex pattern is null.
+        }
+        
+        try {
+            policy = new PasswordPolicy("regexPatterns(*)");
+            fail("Expected PatternSyntaxException: Regex Pattern cannot be null.");
+        } catch (PatternSyntaxException e) {
+            // Expected PSE as regex pattern(or any of its token) is not quantifiable.
+        }
+        
+        try {
+            policy = new PasswordPolicy("regexPatterns(*,**)");
+            fail("Expected PatternSyntaxException: Regex Pattern cannot be null.");
+        } catch (PatternSyntaxException e) {
+            // Expected PSE as regex pattern(or any of its token) is not quantifiable.
+        }
+        
+        //Fails to match one of the regex pattern
+        policy = new PasswordPolicy("regexPatterns(jdoe,j*d)");
+        Assert.assertEquals("invalidPasswordRegexPatternMessage", policy.validate("jdoe", "jdoe").getMessage());
+        
+        ////Fails to match all of the regex patterns
+        policy = new PasswordPolicy("regexPatterns(j*p,j*d,adoe)");
+        Assert.assertEquals("invalidPasswordRegexPatternMessage", policy.validate("jdoe", "jdoe").getMessage());
+        
+        policy = new PasswordPolicy("regexPatterns([a-z][a-z][a-z][a-z][0-9])");
+        Assert.assertEquals("invalidPasswordRegexPatternMessage", policy.validate("jdoe", "jdoe").getMessage());
+        
+        policy = new PasswordPolicy("regexPatterns(jdoe)");
+        Assert.assertNull(policy.validate("jdoe", "jdoe"));
+        
+        policy = new PasswordPolicy("regexPatterns([a-z][a-z][a-z][a-z][0-9])");
+        Assert.assertNull(policy.validate("jdoe", "jdoe0"));
+    }
 
     @Test
     public void testComplex() {
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/account/AccountTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/account/AccountTest.java
index 999fdb8..77a4687 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/account/AccountTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/account/AccountTest.java
@@ -229,7 +229,7 @@ public class AccountTest {
     }
 
     @Test
-    public void changePasswordWithPasswordPolicy() {
+    public void changePasswordWithLengthPasswordPolicy() {
         keycloakRule.update(new KeycloakRule.KeycloakSetup() {
             @Override
             public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) {
@@ -262,6 +262,55 @@ public class AccountTest {
             });
         }
     }
+    
+    @Test
+    public void changePasswordWithPasswordHistoryPolicy() {
+        keycloakRule.update(new KeycloakRule.KeycloakSetup() {
+            @Override
+            public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) {
+                appRealm.setPasswordPolicy(new PasswordPolicy("passwordHistory(2)"));
+            }
+        });
+
+        try {
+            changePasswordPage.open();
+            loginPage.login("test-user@localhost", "password");
+
+            events.expectLogin().client("account").detail(Details.REDIRECT_URI, ACCOUNT_REDIRECT + "?path=password").assertEvent();
+
+            changePasswordPage.changePassword("password", "password", "password");
+
+            Assert.assertEquals("Invalid password password: must not be equal to any of last password history.", profilePage.getError());
+
+            changePasswordPage.changePassword("password", "password1", "password1");
+
+            Assert.assertEquals("Your password has been updated.", profilePage.getSuccess());
+            
+            events.expectAccount(EventType.UPDATE_PASSWORD).assertEvent();
+            
+            changePasswordPage.changePassword("password1", "password", "password");
+
+            Assert.assertEquals("Invalid password password: must not be equal to any of last password history.", profilePage.getError());
+            
+            changePasswordPage.changePassword("password1", "password1", "password1");
+
+            Assert.assertEquals("Invalid password password1: must not be equal to any of last password history.", profilePage.getError());
+            
+            changePasswordPage.changePassword("password1", "password2", "password2");
+
+            Assert.assertEquals("Your password has been updated.", profilePage.getSuccess());
+
+            events.expectAccount(EventType.UPDATE_PASSWORD).assertEvent();
+            
+        } finally {
+            keycloakRule.update(new KeycloakRule.KeycloakSetup() {
+                @Override
+                public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) {
+                    appRealm.setPasswordPolicy(new PasswordPolicy(null));
+                }
+            });
+        }
+    }
 
     @Test
     public void changeProfile() {
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/ResetPasswordTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/ResetPasswordTest.java
index 55e6ed3..fb2a3fe 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/ResetPasswordTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/ResetPasswordTest.java
@@ -37,7 +37,6 @@ import org.keycloak.services.managers.RealmManager;
 import org.keycloak.testsuite.AssertEvents;
 import org.keycloak.testsuite.MailUtil;
 import org.keycloak.testsuite.OAuthClient;
-import org.keycloak.testsuite.Retry;
 import org.keycloak.testsuite.pages.AppPage;
 import org.keycloak.testsuite.pages.AppPage.RequestType;
 import org.keycloak.testsuite.pages.ErrorPage;
@@ -57,8 +56,7 @@ import javax.mail.internet.MimeMessage;
 import java.io.IOException;
 import java.util.Collections;
 
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.*;
 
 /**
  * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
@@ -241,6 +239,44 @@ public class ResetPasswordTest {
         assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType());
     }
 
+    private void resetPassword(String username, String password) throws IOException, MessagingException {
+        loginPage.open();
+        loginPage.resetPassword();
+
+        resetPasswordPage.assertCurrent();
+
+        resetPasswordPage.changePassword(username);
+
+        resetPasswordPage.assertCurrent();
+
+        String sessionId = events.expectRequiredAction(EventType.SEND_RESET_PASSWORD).user(userId)
+                .detail(Details.USERNAME, username).detail(Details.EMAIL, "login@test.com").assertEvent().getSessionId();
+
+        assertEquals("You should receive an email shortly with further instructions.", resetPasswordPage.getSuccessMessage());
+
+        MimeMessage message = greenMail.getReceivedMessages()[greenMail.getReceivedMessages().length - 1];
+
+        String body = (String) message.getContent();
+        String changePasswordUrl = MailUtil.getLink(body);
+
+        driver.navigate().to(changePasswordUrl.trim());
+
+        updatePasswordPage.assertCurrent();
+
+        updatePasswordPage.changePassword(password, password);
+
+        events.expectRequiredAction(EventType.UPDATE_PASSWORD).user(userId).session(sessionId)
+                .detail(Details.USERNAME, username).assertEvent();
+
+        assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType());
+
+        events.expectLogin().user(userId).detail(Details.USERNAME, username).session(sessionId).assertEvent();
+
+        oauth.openLogout();
+
+        events.expectLogout(sessionId).user(userId).session(sessionId).assertEvent();
+    }
+
     @Test
     public void resetPasswordWrongEmail() throws IOException, MessagingException, InterruptedException {
         loginPage.open();
@@ -405,7 +441,7 @@ public class ResetPasswordTest {
     }
 
     @Test
-    public void resetPasswordWithPasswordPolicy() throws IOException, MessagingException {
+    public void resetPasswordWithLengthPasswordPolicy() throws IOException, MessagingException {
         keycloakRule.update(new KeycloakRule.KeycloakSetup() {
             @Override
             public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) {
@@ -463,6 +499,65 @@ public class ResetPasswordTest {
     }
 
     @Test
+    public void resetPasswordWithPasswordHisoryPolicy() throws IOException, MessagingException {
+        keycloakRule.update(new KeycloakRule.KeycloakSetup() {
+            @Override
+            public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) {
+                //Block passwords that are equal to previous passwords. Default value is 3.
+                appRealm.setPasswordPolicy(new PasswordPolicy("passwordHistory"));
+            }
+        });
+        
+        // try-catch blocks have been commented out to reduce execution time for this test case(30s->15s).
+        // TODO : Comment out any other piece of code, if applicable, in order to reduce execution time.
+
+        resetPassword("login-test", "password1");
+        /*try {
+            resetPassword("login-test", "password1");
+            fail("Expected NullPointerException: Block passwords that are equal to previous passwords.");
+        } catch (Exception e) {
+            // Expected NPE as "password1" matches with password history
+        }*/
+        
+        resetPassword("login-test", "password2");
+        /*try {
+            resetPassword("login-test", "password1");
+            fail("Expected NullPointerException: Block passwords that are equal to previous passwords.");
+        } catch (Exception e) {
+            // Expected NPE as "password1" matches with password history
+        }
+        try {
+            resetPassword("login-test", "password2");
+            fail("Expected NullPointerException: Block passwords that are equal to previous passwords.");
+        } catch (Exception e) {
+            // Expected NPE as "password2" matches with password history
+        }*/
+        
+        resetPassword("login-test", "password3");
+        try {
+            resetPassword("login-test", "password1");
+            fail("Expected NullPointerException: Block passwords that are equal to previous passwords.");
+        } catch (Exception e) {
+            // Expected NPE as "password1" matches with password history
+        }
+        try {
+            resetPassword("login-test", "password2");
+            fail("Expected NullPointerException: Block passwords that are equal to previous passwords.");
+        } catch (Exception e) {
+            // Expected NPE as "password2" matches with password history
+        }
+        try {
+            resetPassword("login-test", "password3");
+            fail("Expected NullPointerException: Block passwords that are equal to previous passwords.");
+        } catch (Exception e) {
+            // Expected NPE as "password3" matches with password history
+        }
+        
+        resetPassword("login-test", "password");
+
+    }
+
+    @Test
     public void resetPasswordNewBrowserSession() throws IOException, MessagingException {
         String username = "login-test";