diff --git a/forms/common-themes/src/main/resources/theme/admin/base/resources/js/services.js b/forms/common-themes/src/main/resources/theme/admin/base/resources/js/services.js index b3b0edfcee..53bcf1f1fa 100755 --- a/forms/common-themes/src/main/resources/theme/admin/base/resources/js/services.js +++ b/forms/common-themes/src/main/resources/theme/admin/base/resources/js/services.js @@ -1042,7 +1042,8 @@ module.factory('PasswordPolicy', function() { digits: "Minimal number (integer type) of digits in password. Default value is 1.", lowerCase: "Minimal number (integer type) of lowercase characters in password. Default value is 1.", upperCase: "Minimal number (integer type) of uppercase characters in password. Default value is 1.", - specialChars: "Minimal number (integer type) of special characters in password. Default value is 1." + specialChars: "Minimal number (integer type) of special characters in password. Default value is 1.", + notUsername: "Block passwords that are equal to the username" } p.allPolicies = [ @@ -1051,7 +1052,8 @@ module.factory('PasswordPolicy', function() { { name: 'digits', value: 1 }, { name: 'lowerCase', value: 1 }, { name: 'upperCase', value: 1 }, - { name: 'specialChars', value: 1 } + { name: 'specialChars', value: 1 }, + { name: 'notUsername', value: 1 } ]; p.parse = function(policyString) { @@ -1068,9 +1070,9 @@ module.factory('PasswordPolicy', function() { var re = /(\w+)\(*(\d*)\)*/; var policyEntry = re.exec(policyToken); - - policies.push({ name: policyEntry[1], value: parseInt(policyEntry[2]) }); - + if (null !== policyEntry) { + policies.push({ name: policyEntry[1], value: parseInt(policyEntry[2]) }); + } } return policies; diff --git a/forms/common-themes/src/main/resources/theme/admin/base/resources/partials/realm-credentials.html b/forms/common-themes/src/main/resources/theme/admin/base/resources/partials/realm-credentials.html index 6dff85cba7..608a2b622f 100755 --- a/forms/common-themes/src/main/resources/theme/admin/base/resources/partials/realm-credentials.html +++ b/forms/common-themes/src/main/resources/theme/admin/base/resources/partials/realm-credentials.html @@ -46,7 +46,9 @@ - +
diff --git a/model/api/src/main/java/org/keycloak/models/PasswordPolicy.java b/model/api/src/main/java/org/keycloak/models/PasswordPolicy.java index dc82bcc5f5..3651e9d3f4 100755 --- a/model/api/src/main/java/org/keycloak/models/PasswordPolicy.java +++ b/model/api/src/main/java/org/keycloak/models/PasswordPolicy.java @@ -52,6 +52,8 @@ public class PasswordPolicy { list.add(new UpperCase(args)); } else if (name.equals(SpecialChars.NAME)) { list.add(new SpecialChars(args)); + } else if (name.equals(NotUsername.NAME)) { + list.add(new NotUsername(args)); } else if (name.equals(HashIterations.NAME)) { list.add(new HashIterations(args)); } @@ -74,9 +76,9 @@ public class PasswordPolicy { return -1; } - public String validate(String password) { + public String validate(String username, String password) { for (Policy p : policies) { - String error = p.validate(password); + String error = p.validate(username, password); if (error != null) { return error; } @@ -85,7 +87,7 @@ public class PasswordPolicy { } private static interface Policy { - public String validate(String password); + public String validate(String username, String password); } private static class HashIterations implements Policy { @@ -97,11 +99,23 @@ public class PasswordPolicy { } @Override - public String validate(String password) { + public String validate(String username, String password) { return null; } } + private static class NotUsername implements Policy { + private static final String NAME = "notUsername"; + + public NotUsername(String[] args) { + } + + @Override + public String validate(String username, String password) { + return username.equals(password) ? "Invalid password: must not be equal to the username" : null; + } + } + private static class Length implements Policy { private static final String NAME = "length"; private int min; @@ -111,7 +125,7 @@ public class PasswordPolicy { } @Override - public String validate(String password) { + public String validate(String username, String password) { return password.length() < min ? "Invalid password: minimum length " + min : null; } } @@ -125,7 +139,7 @@ public class PasswordPolicy { } @Override - public String validate(String password) { + public String validate(String username, String password) { int count = 0; for (char c : password.toCharArray()) { if (Character.isDigit(c)) { @@ -145,7 +159,7 @@ public class PasswordPolicy { } @Override - public String validate(String password) { + public String validate(String username, String password) { int count = 0; for (char c : password.toCharArray()) { if (Character.isLowerCase(c)) { @@ -165,7 +179,7 @@ public class PasswordPolicy { } @Override - public String validate(String password) { + public String validate(String username, String password) { int count = 0; for (char c : password.toCharArray()) { if (Character.isUpperCase(c)) { @@ -185,7 +199,7 @@ public class PasswordPolicy { } @Override - public String validate(String password) { + public String validate(String username, String password) { int count = 0; for (char c : password.toCharArray()) { if (!Character.isLetterOrDigit(c)) { diff --git a/model/api/src/main/java/org/keycloak/models/UserFederationManager.java b/model/api/src/main/java/org/keycloak/models/UserFederationManager.java index fe96e5af16..a360f958a4 100755 --- a/model/api/src/main/java/org/keycloak/models/UserFederationManager.java +++ b/model/api/src/main/java/org/keycloak/models/UserFederationManager.java @@ -323,7 +323,7 @@ public class UserFederationManager implements UserProvider { public void updateCredential(RealmModel realm, UserModel user, UserCredentialModel credential) { if (credential.getType().equals(UserCredentialModel.PASSWORD)) { if (realm.getPasswordPolicy() != null) { - String error = realm.getPasswordPolicy().validate(credential.getValue()); + String error = realm.getPasswordPolicy().validate(user.getUsername(), credential.getValue()); if (error != null) throw new ModelException(error); } } 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 3481498884..7242c554df 100644 --- a/model/api/src/test/java/org/keycloak/models/PasswordPolicyTest.java +++ b/model/api/src/test/java/org/keycloak/models/PasswordPolicyTest.java @@ -11,68 +11,76 @@ public class PasswordPolicyTest { @Test public void testLength() { PasswordPolicy policy = new PasswordPolicy("length"); - Assert.assertEquals("Invalid password: minimum length 8", policy.validate("1234567")); - Assert.assertNull(policy.validate("12345678")); + Assert.assertEquals("Invalid password: minimum length 8", policy.validate("jdoe", "1234567")); + Assert.assertNull(policy.validate("jdoe", "12345678")); policy = new PasswordPolicy("length(4)"); - Assert.assertEquals("Invalid password: minimum length 4", policy.validate("123")); - Assert.assertNull(policy.validate("1234")); + Assert.assertEquals("Invalid password: minimum length 4", policy.validate("jdoe", "123")); + Assert.assertNull(policy.validate("jdoe", "1234")); } @Test public void testDigits() { PasswordPolicy policy = new PasswordPolicy("digits"); - Assert.assertEquals("Invalid password: must contain at least 1 numerical digits", policy.validate("abcd")); - Assert.assertNull(policy.validate("abcd1")); + Assert.assertEquals("Invalid password: must contain at least 1 numerical digits", policy.validate("jdoe", "abcd")); + Assert.assertNull(policy.validate("jdoe", "abcd1")); policy = new PasswordPolicy("digits(2)"); - Assert.assertEquals("Invalid password: must contain at least 2 numerical digits", policy.validate("abcd1")); - Assert.assertNull(policy.validate("abcd12")); + Assert.assertEquals("Invalid password: must contain at least 2 numerical digits", policy.validate("jdoe", "abcd1")); + Assert.assertNull(policy.validate("jdoe", "abcd12")); } @Test public void testLowerCase() { PasswordPolicy policy = new PasswordPolicy("lowerCase"); - Assert.assertEquals("Invalid password: must contain at least 1 lower case characters", policy.validate("ABCD1234")); - Assert.assertNull(policy.validate("ABcD1234")); + Assert.assertEquals("Invalid password: must contain at least 1 lower case characters", policy.validate("jdoe", "ABCD1234")); + Assert.assertNull(policy.validate("jdoe", "ABcD1234")); policy = new PasswordPolicy("lowerCase(2)"); - Assert.assertEquals("Invalid password: must contain at least 2 lower case characters", policy.validate("ABcD1234")); - Assert.assertNull(policy.validate("aBcD1234")); + Assert.assertEquals("Invalid password: must contain at least 2 lower case characters", policy.validate("jdoe", "ABcD1234")); + Assert.assertNull(policy.validate("jdoe", "aBcD1234")); } @Test public void testUpperCase() { PasswordPolicy policy = new PasswordPolicy("upperCase"); - Assert.assertEquals("Invalid password: must contain at least 1 upper case characters", policy.validate("abcd1234")); - Assert.assertNull(policy.validate("abCd1234")); + Assert.assertEquals("Invalid password: must contain at least 1 upper case characters", policy.validate("jdoe", "abcd1234")); + Assert.assertNull(policy.validate("jdoe", "abCd1234")); policy = new PasswordPolicy("upperCase(2)"); - Assert.assertEquals("Invalid password: must contain at least 2 upper case characters", policy.validate("abCd1234")); - Assert.assertNull(policy.validate("AbCd1234")); + Assert.assertEquals("Invalid password: must contain at least 2 upper case characters", policy.validate("jdoe", "abCd1234")); + Assert.assertNull(policy.validate("jdoe", "AbCd1234")); } @Test public void testSpecialChars() { PasswordPolicy policy = new PasswordPolicy("specialChars"); - Assert.assertEquals("Invalid password: must contain at least 1 special characters", policy.validate("abcd1234")); - Assert.assertNull(policy.validate("ab&d1234")); + Assert.assertEquals("Invalid password: must contain at least 1 special characters", policy.validate("jdoe", "abcd1234")); + Assert.assertNull(policy.validate("jdoe", "ab&d1234")); policy = new PasswordPolicy("specialChars(2)"); - Assert.assertEquals("Invalid password: must contain at least 2 special characters", policy.validate("ab&d1234")); - Assert.assertNull(policy.validate("ab&d-234")); + Assert.assertEquals("Invalid password: must contain at least 2 special characters", policy.validate("jdoe", "ab&d1234")); + Assert.assertNull(policy.validate("jdoe", "ab&d-234")); + } + + @Test + public void testNotUsername() { + PasswordPolicy policy = new PasswordPolicy("notUsername"); + Assert.assertEquals("Invalid password: must not be equal to the username", policy.validate("jdoe", "jdoe")); + Assert.assertNull(policy.validate("jdoe", "ab&d1234")); } @Test public void testComplex() { - PasswordPolicy policy = new PasswordPolicy("length(8) and digits(2) and lowerCase(2) and upperCase(2) and specialChars(2)"); - Assert.assertNotNull(policy.validate("12aaBB&")); - Assert.assertNotNull(policy.validate("aaaaBB&-")); - Assert.assertNotNull(policy.validate("12AABB&-")); - Assert.assertNotNull(policy.validate("12aabb&-")); - Assert.assertNotNull(policy.validate("12aaBBcc")); + PasswordPolicy policy = new PasswordPolicy("length(8) and digits(2) and lowerCase(2) and upperCase(2) and specialChars(2) and notUsername()"); + Assert.assertNotNull(policy.validate("jdoe", "12aaBB&")); + Assert.assertNotNull(policy.validate("jdoe", "aaaaBB&-")); + Assert.assertNotNull(policy.validate("jdoe", "12AABB&-")); + Assert.assertNotNull(policy.validate("jdoe", "12aabb&-")); + Assert.assertNotNull(policy.validate("jdoe", "12aaBBcc")); + Assert.assertNotNull(policy.validate("12aaBB&-", "12aaBB&-")); - Assert.assertNull(policy.validate("12aaBB&-")); + Assert.assertNull(policy.validate("jdoe", "12aaBB&-")); } } diff --git a/services/src/main/java/org/keycloak/services/validation/Validation.java b/services/src/main/java/org/keycloak/services/validation/Validation.java index 254cee1e8c..d306dc050d 100755 --- a/services/src/main/java/org/keycloak/services/validation/Validation.java +++ b/services/src/main/java/org/keycloak/services/validation/Validation.java @@ -49,7 +49,7 @@ public class Validation { } public static String validatePassword(MultivaluedMap formData, PasswordPolicy policy) { - return policy.validate(formData.getFirst("password")); + return policy.validate(formData.getFirst("username"), formData.getFirst("password")); } public static String validateUpdateProfileForm(MultivaluedMap formData) {