From ad715371a32629a5742102faadc3c804ca58cb20 Mon Sep 17 00:00:00 2001 From: girirajsharma Date: Tue, 14 Apr 2015 22:46:42 +0530 Subject: [PATCH] Added password policy test cases for regex and password history. --- .../account/messages/messages_de.properties | 16 +-- .../account/messages/messages_en.properties | 2 +- .../messages/messages_pt_BR.properties | 2 +- .../login/messages/messages_de.properties | 2 +- .../login/messages/messages_en.properties | 2 +- .../login/messages/messages_pt_BR.properties | 4 +- .../keycloak/models/PasswordPolicyTest.java | 46 ++++++++ .../testsuite/account/AccountTest.java | 51 ++++++++- .../testsuite/forms/ResetPasswordTest.java | 103 +++++++++++++++++- 9 files changed, 209 insertions(+), 19 deletions(-) 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 b3b388f45f..3dbc156513 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 713904bc9a..248f2ad252 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 4af23551d5..27da8869bd 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 53e46a8d63..3ed399dfcb 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 11833e580f..c20e1fae4d 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 bd0c37c33d..245d8cb0b4 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 3bde361113..14f28fb0de 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 999fdb875f..77a4687a4e 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 55e6ed3711..fb2a3fe569 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 Stian Thorgersen @@ -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) { @@ -462,6 +498,65 @@ public class ResetPasswordTest { events.expectLogin().user(userId).detail(Details.USERNAME, "login-test").assertEvent(); } + @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";