keycloak-aplcache

Merge pull request #1043 from jpkrohling/KEYCLOAK-1101-PasswordPolicyNotUsername KEYCLOAK-1101

3/13/2015 5:58:44 PM

Details

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 b3b0edf..53bcf1f 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 6dff85c..608a2b6 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 @@
                             <input class="form-control disabled" type="text" value="{{p.name|capitalize}}" readonly>
                         </td>
                         <td>
-                            <input class="form-control" ng-model="p.value" type="number" placeholder="No value assigned"  min="1">
+                            <input class="form-control" ng-model="p.value" ng-show="p.name != 'notUsername' "
+                                   placeholder="No value assigned"
+                                   min="1">
                         </td>
                         <td class="actions">
                             <div class="action-div"><i class="pficon pficon-delete" ng-click="removePolicy($index)" tooltip-placement="right" tooltip="Remove Policy"></i></div>
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 dc82bcc..3651e9d 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 fe96e5a..a360f95 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 3481498..7242c55 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"));
-
-        Assert.assertNull(policy.validate("12aaBB&-"));
+        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("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 254cee1..d306dc0 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<String, String> formData, PasswordPolicy policy) {
-        return policy.validate(formData.getFirst("password"));
+        return policy.validate(formData.getFirst("username"), formData.getFirst("password"));
     }
 
     public static String validateUpdateProfileForm(MultivaluedMap<String, String> formData) {