keycloak-memoizeit

Changes

server-spi/src/test/java/org/keycloak/models/PasswordPolicyTest.java 168(+0 -168)

Details

diff --git a/core/src/main/java/org/keycloak/representations/idm/PasswordPolicyTypeRepresentation.java b/core/src/main/java/org/keycloak/representations/idm/PasswordPolicyTypeRepresentation.java
new file mode 100644
index 0000000..5a5a1f9
--- /dev/null
+++ b/core/src/main/java/org/keycloak/representations/idm/PasswordPolicyTypeRepresentation.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.keycloak.representations.idm;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class PasswordPolicyTypeRepresentation {
+
+    private String id;
+    private String displayName;
+    private String configType;
+    private String defaultValue;
+    private boolean multipleSupported;
+
+    public String getId() {
+        return id;
+    }
+
+    public void setId(String id) {
+        this.id = id;
+    }
+
+    public String getDisplayName() {
+        return displayName;
+    }
+
+    public void setDisplayName(String displayName) {
+        this.displayName = displayName;
+    }
+
+    public String getConfigType() {
+        return configType;
+    }
+
+    public void setConfigType(String configType) {
+        this.configType = configType;
+    }
+
+    public String getDefaultValue() {
+        return defaultValue;
+    }
+
+    public void setDefaultValue(String defaultValue) {
+        this.defaultValue = defaultValue;
+    }
+
+    public boolean isMultipleSupported() {
+        return multipleSupported;
+    }
+
+    public void setMultipleSupported(boolean multipleSupported) {
+        this.multipleSupported = multipleSupported;
+    }
+}
diff --git a/core/src/main/java/org/keycloak/representations/info/ServerInfoRepresentation.java b/core/src/main/java/org/keycloak/representations/info/ServerInfoRepresentation.java
index 5510bb2..c3aeeeb 100755
--- a/core/src/main/java/org/keycloak/representations/info/ServerInfoRepresentation.java
+++ b/core/src/main/java/org/keycloak/representations/info/ServerInfoRepresentation.java
@@ -17,6 +17,7 @@
 
 package org.keycloak.representations.info;
 
+import org.keycloak.representations.idm.PasswordPolicyTypeRepresentation;
 import org.keycloak.representations.idm.ProtocolMapperRepresentation;
 import org.keycloak.representations.idm.ProtocolMapperTypeRepresentation;
 
@@ -43,6 +44,8 @@ public class ServerInfoRepresentation {
     private Map<String, List<ProtocolMapperRepresentation>> builtinProtocolMappers;
     private Map<String, List<ClientInstallationRepresentation>> clientInstallations;
 
+    private List<PasswordPolicyTypeRepresentation> passwordPolicies;
+
     private Map<String, List<String>> enums;
 
     public SystemInfoRepresentation getSystemInfo() {
@@ -132,4 +135,13 @@ public class ServerInfoRepresentation {
     public void setClientInstallations(Map<String, List<ClientInstallationRepresentation>> clientInstallations) {
         this.clientInstallations = clientInstallations;
     }
+
+    public List<PasswordPolicyTypeRepresentation> getPasswordPolicies() {
+        return passwordPolicies;
+    }
+
+    public void setPasswordPolicies(List<PasswordPolicyTypeRepresentation> passwordPolicies) {
+        this.passwordPolicies = passwordPolicies;
+    }
+
 }
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java b/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java
index 40112cd..6d7ea98 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java
@@ -1223,7 +1223,7 @@ public class RealmAdapter implements RealmModel, JpaModel<RealmEntity> {
     @Override
     public PasswordPolicy getPasswordPolicy() {
         if (passwordPolicy == null) {
-            passwordPolicy = new PasswordPolicy(realm.getPasswordPolicy());
+            passwordPolicy = PasswordPolicy.parse(session, realm.getPasswordPolicy());
         }
         return passwordPolicy;
     }
diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/RealmAdapter.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/RealmAdapter.java
index c532cbd..5c9c589 100755
--- a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/RealmAdapter.java
+++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/RealmAdapter.java
@@ -308,7 +308,7 @@ public class RealmAdapter extends AbstractMongoAdapter<MongoRealmEntity> impleme
     @Override
     public PasswordPolicy getPasswordPolicy() {
         if (passwordPolicy == null) {
-            passwordPolicy = new PasswordPolicy(realm.getPasswordPolicy());
+            passwordPolicy = PasswordPolicy.parse(session, realm.getPasswordPolicy());
         }
         return passwordPolicy;
     }
diff --git a/server-spi/src/main/java/org/keycloak/hash/PasswordHashManager.java b/server-spi/src/main/java/org/keycloak/hash/PasswordHashManager.java
index 8f77d60..9c5afa8 100644
--- a/server-spi/src/main/java/org/keycloak/hash/PasswordHashManager.java
+++ b/server-spi/src/main/java/org/keycloak/hash/PasswordHashManager.java
@@ -18,7 +18,11 @@
 package org.keycloak.hash;
 
 import org.jboss.logging.Logger;
-import org.keycloak.models.*;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.PasswordPolicy;
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.UserCredentialValueModel;
+import org.keycloak.policy.HashAlgorithmPasswordPolicyProviderFactory;
 
 /**
  * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
@@ -32,17 +36,12 @@ public class PasswordHashManager {
     }
 
     public static UserCredentialValueModel encode(KeycloakSession session, PasswordPolicy passwordPolicy, String rawPassword) {
-        String algorithm = passwordPolicy.getHashAlgorithm();
-        int iterations = passwordPolicy.getHashIterations();
-        if (iterations < 1) {
-            iterations = 1;
-        }
         PasswordHashProvider provider = session.getProvider(PasswordHashProvider.class, passwordPolicy.getHashAlgorithm());
         if (provider == null) {
-            log.warnv("Could not find hash provider {0} from password policy, using default provider {1}", algorithm, Constants.DEFAULT_HASH_ALGORITHM);
-            provider = session.getProvider(PasswordHashProvider.class, Constants.DEFAULT_HASH_ALGORITHM);
+            log.warnv("Could not find hash provider {0} from password policy, using default provider {1}", passwordPolicy.getHashAlgorithm(), HashAlgorithmPasswordPolicyProviderFactory.DEFAULT_VALUE);
+            provider = session.getProvider(PasswordHashProvider.class, HashAlgorithmPasswordPolicyProviderFactory.DEFAULT_VALUE);
         }
-        return provider.encode(rawPassword, iterations);
+        return provider.encode(rawPassword, passwordPolicy.getHashIterations());
     }
 
     public static boolean verify(KeycloakSession session, RealmModel realm, String password, UserCredentialValueModel credential) {
diff --git a/server-spi/src/main/java/org/keycloak/models/Constants.java b/server-spi/src/main/java/org/keycloak/models/Constants.java
index 7f998df..916565a 100755
--- a/server-spi/src/main/java/org/keycloak/models/Constants.java
+++ b/server-spi/src/main/java/org/keycloak/models/Constants.java
@@ -41,8 +41,6 @@ public interface Constants {
     String AUTHZ_UMA_AUTHORIZATION = "uma_authorization";
     String[] AUTHZ_DEFAULT_AUTHORIZATION_ROLES = {AUTHZ_UMA_AUTHORIZATION};
 
-    String DEFAULT_HASH_ALGORITHM = "pbkdf2";
-
     // 15 minutes
     int DEFAULT_ACCESS_TOKEN_LIFESPAN_FOR_IMPLICIT_FLOW_TIMEOUT = 900;
     // 30 days
diff --git a/server-spi/src/main/java/org/keycloak/models/PasswordPolicy.java b/server-spi/src/main/java/org/keycloak/models/PasswordPolicy.java
index 0b22b4e..95dfc53 100755
--- a/server-spi/src/main/java/org/keycloak/models/PasswordPolicy.java
+++ b/server-spi/src/main/java/org/keycloak/models/PasswordPolicy.java
@@ -17,498 +17,101 @@
 
 package org.keycloak.models;
 
-import org.keycloak.hash.PasswordHashManager;
+import org.keycloak.policy.ForceExpiredPasswordPolicyProviderFactory;
+import org.keycloak.policy.HashAlgorithmPasswordPolicyProviderFactory;
+import org.keycloak.policy.HashIterationsPasswordPolicyProviderFactory;
+import org.keycloak.policy.HistoryPasswordPolicyProviderFactory;
+import org.keycloak.policy.PasswordPolicyProvider;
 
 import java.io.Serializable;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Comparator;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
 
 /**
  * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
  */
 public class PasswordPolicy implements Serializable {
 
-    public static final String INVALID_PASSWORD_MIN_LENGTH_MESSAGE = "invalidPasswordMinLengthMessage";
-    public static final String INVALID_PASSWORD_MIN_DIGITS_MESSAGE = "invalidPasswordMinDigitsMessage";
-    public static final String INVALID_PASSWORD_MIN_LOWER_CASE_CHARS_MESSAGE = "invalidPasswordMinLowerCaseCharsMessage";
-    public static final String INVALID_PASSWORD_MIN_UPPER_CASE_CHARS_MESSAGE = "invalidPasswordMinUpperCaseCharsMessage";
-    public static final String INVALID_PASSWORD_MIN_SPECIAL_CHARS_MESSAGE = "invalidPasswordMinSpecialCharsMessage";
-    public static final String INVALID_PASSWORD_NOT_USERNAME = "invalidPasswordNotUsernameMessage";
-    public static final String INVALID_PASSWORD_REGEX_PATTERN = "invalidPasswordRegexPatternMessage";
-    public static final String INVALID_PASSWORD_HISTORY = "invalidPasswordHistoryMessage";
-
-    private List<Policy> policies;
     private String policyString;
+    private Map<String, Object> policyConfig;
 
-    public PasswordPolicy(String policyString) {
-        this.policyString = policyString;
-        this.policies = new LinkedList<>();
+    public static PasswordPolicy empty() {
+        return new PasswordPolicy(null, new HashMap<>());
+    }
+
+    public static PasswordPolicy parse(KeycloakSession session, String policyString) {
+        Map<String, Object> policyConfig = new HashMap<>();
 
         if (policyString != null && !policyString.trim().isEmpty()) {
             for (String policy : policyString.split(" and ")) {
                 policy = policy.trim();
 
-                String name;
-                String arg = null;
+                String key;
+                String config = null;
 
                 int i = policy.indexOf('(');
                 if (i == -1) {
-                    name = policy.trim();
+                    key = policy.trim();
                 } else {
-                    name = policy.substring(0, i).trim();
-                    arg = policy.substring(i + 1, policy.length() - 1);
+                    key = policy.substring(0, i).trim();
+                    config = policy.substring(i + 1, policy.length() - 1);
                 }
 
-                if (name.equals(Length.NAME)) {
-                    policies.add(new Length(arg));
-                } else if (name.equals(Digits.NAME)) {
-                    policies.add(new Digits(arg));
-                } else if (name.equals(LowerCase.NAME)) {
-                    policies.add(new LowerCase(arg));
-                } else if (name.equals(UpperCase.NAME)) {
-                    policies.add(new UpperCase(arg));
-                } else if (name.equals(SpecialChars.NAME)) {
-                    policies.add(new SpecialChars(arg));
-                } else if (name.equals(NotUsername.NAME)) {
-                    policies.add(new NotUsername(arg));
-                } else if (name.equals(HashAlgorithm.NAME)) {
-                    policies.add(new HashAlgorithm(arg));
-                } else if (name.equals(HashIterations.NAME)) {
-                    policies.add(new HashIterations(arg));
-                } else if (name.equals(RegexPatterns.NAME)) {
-                    Pattern.compile(arg);
-                    policies.add(new RegexPatterns(arg));
-                } else if (name.equals(PasswordHistory.NAME)) {
-                    policies.add(new PasswordHistory(arg, this));
-                } else if (name.equals(ForceExpiredPasswordChange.NAME)) {
-                    policies.add(new ForceExpiredPasswordChange(arg));
-                } else {
+                PasswordPolicyProvider provider = session.getProvider(PasswordPolicyProvider.class, key);
+                if (provider == null) {
                     throw new IllegalArgumentException("Unsupported policy");
                 }
-            }
-        }
-    }
-
-    public String getHashAlgorithm() {
-        if (policies == null)
-            return Constants.DEFAULT_HASH_ALGORITHM;
-        for (Policy p : policies) {
-            if (p instanceof HashAlgorithm) {
-                return ((HashAlgorithm) p).algorithm;
-            }
-
-        }
-        return Constants.DEFAULT_HASH_ALGORITHM;
-    }
 
-    /**
-     *
-     * @return -1 if no hash iterations setting
-     */
-    public int getHashIterations() {
-        if (policies == null)
-            return -1;
-        for (Policy p : policies) {
-            if (p instanceof HashIterations) {
-                return ((HashIterations) p).iterations;
+                policyConfig.put(key, provider.parseConfig(config));
             }
-
-        }
-        return -1;
-    }
-
-    /**
-     *
-     * @return -1 if no expired passwords setting
-     */
-    public int getExpiredPasswords() {
-        if (policies == null)
-            return -1;
-        for (Policy p : policies) {
-            if (p instanceof PasswordHistory) {
-                return ((PasswordHistory) p).passwordHistoryPolicyValue;
-            }
-
-        }
-        return -1;
-    }
-    
-    /**
-    *
-    * @return -1 if no force expired password change setting
-    */
-   public int getDaysToExpirePassword() {
-       if (policies == null)
-           return -1;
-       for (Policy p : policies) {
-           if (p instanceof ForceExpiredPasswordChange) {
-               return ((ForceExpiredPasswordChange) p).daysToExpirePassword;
-           }
-
-       }
-       return -1;
-   }
-
-    public Error validate(KeycloakSession session, UserModel user, String password) {
-        for (Policy p : policies) {
-            Error error = p.validate(session, user, password);
-            if (error != null) {
-                return error;
-            }
-        }
-        return null;
-    }
-    
-    public Error validate(KeycloakSession session, String user, String password) {
-        for (Policy p : policies) {
-            Error error = p.validate(session, user, password);
-            if (error != null) {
-                return error;
-            }
-        }
-        return null;
-    }
-
-    private static interface Policy extends Serializable {
-        public Error validate(KeycloakSession session, UserModel user, String password);
-        public Error validate(KeycloakSession session, String user, String password);
-    }
-
-    public static class Error {
-        private String message;
-        private Object[] parameters;
-
-        private Error(String message, Object... parameters) {
-            this.message = message;
-            this.parameters = parameters;
-        }
-
-        public String getMessage() {
-            return message;
         }
 
-        public Object[] getParameters() {
-            return parameters;
-        }
-    }
-
-    private static class HashAlgorithm implements Policy {
-        private static final String NAME = "hashAlgorithm";
-        private String algorithm;
-
-        public HashAlgorithm(String arg) {
-            algorithm = stringArg(NAME, Constants.DEFAULT_HASH_ALGORITHM, arg);
-        }
-        
-        @Override
-        public Error validate(KeycloakSession session, String user, String password) {
-            return null;
-        }
-        
-        @Override
-        public Error validate(KeycloakSession session, UserModel user, String password) {
-            return null;
-        }
+        return new PasswordPolicy(policyString, policyConfig);
     }
 
-    private static class HashIterations implements Policy {
-        private static final String NAME = "hashIterations";
-        private int iterations;
-
-        public HashIterations(String arg) {
-            iterations = intArg(NAME, 1, arg);
-        }
-
-        @Override
-        public Error validate(KeycloakSession session, String user, String password) {
-            return null;
-        }
-
-        @Override
-        public Error validate(KeycloakSession session, UserModel user, String password) {
-            return null;
-        }
-    }
-
-    private static class NotUsername implements Policy {
-        private static final String NAME = "notUsername";
-
-        public NotUsername(String arg) {
-        }
-
-        @Override
-        public Error validate(KeycloakSession session, String username, String password) {
-            return username.equals(password) ? new Error(INVALID_PASSWORD_NOT_USERNAME) : null;
-        }
-        
-        @Override
-        public Error validate(KeycloakSession session, UserModel user, String password) {
-            return validate(session, user.getUsername(), password);
-        }
-    }
-
-    private static class Length implements Policy {
-        private static final String NAME = "length";
-        private int min;
-
-        public Length(String arg)
-        {
-            min = intArg(NAME, 8, arg);
-        }
-        
-
-        @Override
-        public Error validate(KeycloakSession session, String username, String password) {
-            return password.length() < min ? new Error(INVALID_PASSWORD_MIN_LENGTH_MESSAGE, min) : null;
-        }
-        
-        @Override
-        public Error validate(KeycloakSession session, UserModel user, String password) {
-            return validate(session, user.getUsername(), password);
-        }
-    }
-
-    private static class Digits implements Policy {
-        private static final String NAME = "digits";
-        private int min;
-
-        public Digits(String arg)
-        {
-            min = intArg(NAME, 1, arg);
-        }
-        
-
-        @Override
-        public Error validate(KeycloakSession session, String username, String password) {
-            int count = 0;
-            for (char c : password.toCharArray()) {
-                if (Character.isDigit(c)) {
-                    count++;
-                }
-            }
-            return count < min ? new Error(INVALID_PASSWORD_MIN_DIGITS_MESSAGE, min) : null;
-        }
-        
-        @Override
-        public Error validate(KeycloakSession session, UserModel user, String password) {
-            return validate(session, user.getUsername(), password);
-        }
+    private PasswordPolicy(String policyString, Map<String, Object> policyConfig) {
+        this.policyString = policyString;
+        this.policyConfig = policyConfig;
     }
 
-    private static class LowerCase implements Policy {
-        private static final String NAME = "lowerCase";
-        private int min;
-
-        public LowerCase(String arg)
-        {
-            min = intArg(NAME, 1, arg);
-        }
-        
-        @Override
-        public Error validate(KeycloakSession session, String username, String password) {
-            int count = 0;
-            for (char c : password.toCharArray()) {
-                if (Character.isLowerCase(c)) {
-                    count++;
-                }
-            }
-            return count < min ? new Error(INVALID_PASSWORD_MIN_LOWER_CASE_CHARS_MESSAGE, min) : null;
-        }
-        
-        @Override
-        public Error validate(KeycloakSession session, UserModel user, String password) {
-            return validate(session, user.getUsername(), password);
-        }
+    public Set<String> getPolicies() {
+        return policyConfig.keySet();
     }
 
-    private static class UpperCase implements Policy {
-        private static final String NAME = "upperCase";
-        private int min;
-
-        public UpperCase(String arg) {
-            min = intArg(NAME, 1, arg);
-        }
-
-        @Override
-        public Error validate(KeycloakSession session, String username, String password) {
-            int count = 0;
-            for (char c : password.toCharArray()) {
-                if (Character.isUpperCase(c)) {
-                    count++;
-                }
-            }
-            return count < min ? new Error(INVALID_PASSWORD_MIN_UPPER_CASE_CHARS_MESSAGE, min) : null;
-        }
-        
-        @Override
-        public Error validate(KeycloakSession session, UserModel user, String password) {
-            return validate(session, user.getUsername(), password);
-        }
+    public <T> T getPolicyConfig(String key) {
+        return (T) policyConfig.get(key);
     }
 
-    private static class SpecialChars implements Policy {
-        private static final String NAME = "specialChars";
-        private int min;
-
-        public SpecialChars(String arg)
-        {
-            min = intArg(NAME, 1, arg);
-        }
-        
-        @Override
-        public Error validate(KeycloakSession session, String username, String password) {
-            int count = 0;
-            for (char c : password.toCharArray()) {
-                if (!Character.isLetterOrDigit(c)) {
-                    count++;
-                }
-            }
-            return count < min ? new Error(INVALID_PASSWORD_MIN_SPECIAL_CHARS_MESSAGE, min) : null;
-        }
-        
-        @Override
-        public Error validate(KeycloakSession session, UserModel user, String password) {
-            return validate(session, user.getUsername(), password);
-        }
-    }
-
-    private static class RegexPatterns implements Policy {
-        private static final String NAME = "regexPattern";
-        private String regexPattern;
-
-        public RegexPatterns(String arg)
-        {
-            regexPattern = arg;
-        }
-
-        @Override
-        public Error validate(KeycloakSession session, String username, String password) {
-            Pattern pattern = Pattern.compile(regexPattern);
-            Matcher matcher = pattern.matcher(password);
-            if (!matcher.matches()) {
-                return new Error(INVALID_PASSWORD_REGEX_PATTERN, (Object) regexPattern);
-            }
-            return null;
-        }
-
-        @Override
-        public Error validate(KeycloakSession session, UserModel user, String password) {
-            return validate(session, user.getUsername(), password);
+    public String getHashAlgorithm() {
+        if (policyConfig.containsKey(HashAlgorithmPasswordPolicyProviderFactory.ID)) {
+            return getPolicyConfig(HashAlgorithmPasswordPolicyProviderFactory.ID);
+        } else {
+            return HashAlgorithmPasswordPolicyProviderFactory.DEFAULT_VALUE;
         }
     }
 
-    private static class PasswordHistory implements Policy {
-        private static final String NAME = "passwordHistory";
-        private final PasswordPolicy passwordPolicy;
-        private int passwordHistoryPolicyValue;
-
-        public PasswordHistory(String arg, PasswordPolicy passwordPolicy)
-        {
-            this.passwordPolicy = passwordPolicy;
-            passwordHistoryPolicyValue = intArg(NAME, 3, arg);
-        }
-        
-        @Override
-        public Error validate(KeycloakSession session, String user, String password) {
-            return null;
-        }
-
-        @Override
-        public Error validate(KeycloakSession session, UserModel user, String password) {
-            if (passwordHistoryPolicyValue != -1) {
-                UserCredentialValueModel cred = getCredentialValueModel(user, UserCredentialModel.PASSWORD);
-                if (cred != null) {
-                    if(PasswordHashManager.verify(session, passwordPolicy, password, cred)) {
-                        return new Error(INVALID_PASSWORD_HISTORY, passwordHistoryPolicyValue);
-                    }
-                }
-
-                List<UserCredentialValueModel> passwordExpiredCredentials = getCredentialValueModels(user, passwordHistoryPolicyValue - 1,
-                        UserCredentialModel.PASSWORD_HISTORY);
-                for (UserCredentialValueModel credential : passwordExpiredCredentials) {
-                    if (PasswordHashManager.verify(session, passwordPolicy, password, credential)) {
-                        return new Error(INVALID_PASSWORD_HISTORY, passwordHistoryPolicyValue);
-                    }
-                }
-            }
-            return null;
-        }
-
-        private UserCredentialValueModel getCredentialValueModel(UserModel user, String credType) {
-            for (UserCredentialValueModel model : user.getCredentialsDirectly()) {
-                if (model.getType().equals(credType)) {
-                    return model;
-                }
-            }
-
-            return null;
-        }
-
-        private List<UserCredentialValueModel> getCredentialValueModels(UserModel user, int expiredPasswordsPolicyValue,
-                String credType) {
-            List<UserCredentialValueModel> credentialModels = new ArrayList<UserCredentialValueModel>();
-            for (UserCredentialValueModel model : user.getCredentialsDirectly()) {
-                if (model.getType().equals(credType)) {
-                    credentialModels.add(model);
-                }
-            }
-
-            Collections.sort(credentialModels, new Comparator<UserCredentialValueModel>() {
-                public int compare(UserCredentialValueModel credFirst, UserCredentialValueModel credSecond) {
-                    if (credFirst.getCreatedDate() > credSecond.getCreatedDate()) {
-                        return -1;
-                    } else if (credFirst.getCreatedDate() < credSecond.getCreatedDate()) {
-                        return 1;
-                    } else {
-                        return 0;
-                    }
-                }
-            });
-
-            if (credentialModels.size() > expiredPasswordsPolicyValue) {
-                return credentialModels.subList(0, expiredPasswordsPolicyValue);
-            }
-            return credentialModels;
+    public int getHashIterations() {
+        if (policyConfig.containsKey(HashIterationsPasswordPolicyProviderFactory.ID)) {
+            return getPolicyConfig(HashIterationsPasswordPolicyProviderFactory.ID);
+        } else {
+            return HashIterationsPasswordPolicyProviderFactory.DEFAULT_VALUE;
         }
     }
-    
-    private static class ForceExpiredPasswordChange implements Policy {
-        private static final String NAME = "forceExpiredPasswordChange";
-        private int daysToExpirePassword;
-
-        public ForceExpiredPasswordChange(String arg) {
-            daysToExpirePassword = intArg(NAME, 365, arg);
-        }
 
-        @Override
-        public Error validate(KeycloakSession session, String username, String password) {
-            return null;
-        }
-
-        @Override
-        public Error validate(KeycloakSession session, UserModel user, String password) {
-            return null;
-        }
-    }
-    
-    private static int intArg(String policy, int defaultValue, String arg) {
-        if (arg == null) {
-            return defaultValue;
+    public int getExpiredPasswords() {
+        if (policyConfig.containsKey(HistoryPasswordPolicyProviderFactory.ID)) {
+            return getPolicyConfig(HistoryPasswordPolicyProviderFactory.ID);
         } else {
-            return Integer.parseInt(arg);
+            return -1;
         }
     }
 
-    private static String stringArg(String policy, String defaultValue, String arg) {
-        if (arg == null) {
-            return defaultValue;
+    public int getDaysToExpirePassword() {
+        if (policyConfig.containsKey(ForceExpiredPasswordPolicyProviderFactory.ID)) {
+            return getPolicyConfig(ForceExpiredPasswordPolicyProviderFactory.ID);
         } else {
-            return arg;
+            return -1;
         }
     }
 
@@ -516,4 +119,5 @@ public class PasswordPolicy implements Serializable {
     public String toString() {
         return policyString;
     }
+
 }
diff --git a/server-spi/src/main/java/org/keycloak/models/UserFederationManager.java b/server-spi/src/main/java/org/keycloak/models/UserFederationManager.java
index 08a070d..19db2e1 100755
--- a/server-spi/src/main/java/org/keycloak/models/UserFederationManager.java
+++ b/server-spi/src/main/java/org/keycloak/models/UserFederationManager.java
@@ -19,6 +19,8 @@ package org.keycloak.models;
 
 import org.jboss.logging.Logger;
 import org.keycloak.models.utils.KeycloakModelUtils;
+import org.keycloak.policy.PasswordPolicyManagerProvider;
+import org.keycloak.policy.PolicyError;
 import org.keycloak.services.managers.UserManager;
 import org.keycloak.storage.StorageProviderModel;
 
@@ -493,7 +495,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) {
-                PasswordPolicy.Error error = realm.getPasswordPolicy().validate(session, user, credential.getValue());
+                PolicyError error = session.getProvider(PasswordPolicyManagerProvider.class).validate(user, credential.getValue());
                 if (error != null) throw new ModelException(error.getMessage(), error.getParameters());
             }
         }
diff --git a/server-spi/src/main/java/org/keycloak/models/utils/CredentialValidation.java b/server-spi/src/main/java/org/keycloak/models/utils/CredentialValidation.java
index c6b3762..85d52c0 100755
--- a/server-spi/src/main/java/org/keycloak/models/utils/CredentialValidation.java
+++ b/server-spi/src/main/java/org/keycloak/models/utils/CredentialValidation.java
@@ -17,19 +17,18 @@
 
 package org.keycloak.models.utils;
 
+import org.keycloak.common.util.Time;
 import org.keycloak.hash.PasswordHashManager;
 import org.keycloak.jose.jws.JWSInput;
 import org.keycloak.jose.jws.JWSInputException;
 import org.keycloak.jose.jws.crypto.RSAProvider;
 import org.keycloak.models.KeycloakSession;
 import org.keycloak.models.OTPPolicy;
-import org.keycloak.models.PasswordPolicy;
 import org.keycloak.models.RealmModel;
 import org.keycloak.models.UserCredentialModel;
 import org.keycloak.models.UserCredentialValueModel;
 import org.keycloak.models.UserModel;
 import org.keycloak.representations.PasswordToken;
-import org.keycloak.common.util.Time;
 
 import java.util.List;
 
@@ -39,15 +38,6 @@ import java.util.List;
  */
 public class CredentialValidation {
 
-    private static int hashIterations(RealmModel realm) {
-        PasswordPolicy policy = realm.getPasswordPolicy();
-        if (policy != null) {
-            return policy.getHashIterations();
-        }
-        return -1;
-
-    }
-
    /**
      * Will update password if hash iteration policy has changed
      *
@@ -78,8 +68,7 @@ public class CredentialValidation {
         boolean validated = PasswordHashManager.verify(session, realm, unhashedCredValue, credential);
 
         if (validated) {
-            int iterations = hashIterations(realm);
-            if (iterations > -1 && iterations != credential.getHashIterations()) {
+            if (realm.getPasswordPolicy().getHashIterations() != credential.getHashIterations()) {
 
                 UserCredentialValueModel newCred = PasswordHashManager.encode(session, realm, unhashedCredValue);
                 user.updateCredentialDirectly(newCred);
diff --git a/server-spi/src/main/java/org/keycloak/models/utils/RepresentationToModel.java b/server-spi/src/main/java/org/keycloak/models/utils/RepresentationToModel.java
index b36b96c..3efca79 100755
--- a/server-spi/src/main/java/org/keycloak/models/utils/RepresentationToModel.java
+++ b/server-spi/src/main/java/org/keycloak/models/utils/RepresentationToModel.java
@@ -196,7 +196,7 @@ public class RepresentationToModel {
             newRealm.addRequiredCredential(CredentialRepresentation.PASSWORD);
         }
 
-        if (rep.getPasswordPolicy() != null) newRealm.setPasswordPolicy(new PasswordPolicy(rep.getPasswordPolicy()));
+        if (rep.getPasswordPolicy() != null) newRealm.setPasswordPolicy(PasswordPolicy.parse(session, rep.getPasswordPolicy()));
         if (rep.getOtpPolicyType() != null) newRealm.setOTPPolicy(toPolicy(rep));
         else newRealm.setOTPPolicy(OTPPolicy.DEFAULT_POLICY);
 
@@ -661,7 +661,7 @@ public class RepresentationToModel {
         return url != null ? url.replace(target, replacement) : null;
     }
 
-    public static void updateRealm(RealmRepresentation rep, RealmModel realm) {
+    public static void updateRealm(RealmRepresentation rep, RealmModel realm, KeycloakSession session) {
         if (rep.getRealm() != null) {
             renameRealm(realm, rep.getRealm());
         }
@@ -709,7 +709,7 @@ public class RepresentationToModel {
         if (rep.isAdminEventsDetailsEnabled() != null) realm.setAdminEventsDetailsEnabled(rep.isAdminEventsDetailsEnabled());
 
 
-        if (rep.getPasswordPolicy() != null) realm.setPasswordPolicy(new PasswordPolicy(rep.getPasswordPolicy()));
+        if (rep.getPasswordPolicy() != null) realm.setPasswordPolicy(PasswordPolicy.parse(session, rep.getPasswordPolicy()));
         if (rep.getOtpPolicyType() != null) realm.setOTPPolicy(toPolicy(rep));
 
         if (rep.getDefaultRoles() != null) {
diff --git a/server-spi/src/main/java/org/keycloak/policy/DefaultPasswordPolicyManagerProvider.java b/server-spi/src/main/java/org/keycloak/policy/DefaultPasswordPolicyManagerProvider.java
new file mode 100644
index 0000000..c380f80
--- /dev/null
+++ b/server-spi/src/main/java/org/keycloak/policy/DefaultPasswordPolicyManagerProvider.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.keycloak.policy;
+
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.PasswordPolicy;
+import org.keycloak.models.UserModel;
+
+import java.util.LinkedList;
+import java.util.List;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class DefaultPasswordPolicyManagerProvider implements PasswordPolicyManagerProvider {
+
+    private KeycloakSession session;
+
+    public DefaultPasswordPolicyManagerProvider(KeycloakSession session) {
+        this.session = session;
+    }
+
+    @Override
+    public PolicyError validate(UserModel user, String password) {
+        for (PasswordPolicyProvider p : getProviders(session)) {
+            PolicyError policyError = p.validate(user, password);
+            if (policyError != null) {
+                return policyError;
+            }
+        }
+        return null;
+    }
+
+    @Override
+    public PolicyError validate(String user, String password) {
+        for (PasswordPolicyProvider p : getProviders(session)) {
+            PolicyError policyError = p.validate(user, password);
+            if (policyError != null) {
+                return policyError;
+            }
+        }
+        return null;
+    }
+
+    @Override
+    public void close() {
+    }
+
+    private List<PasswordPolicyProvider> getProviders(KeycloakSession session) {
+        LinkedList<PasswordPolicyProvider> list = new LinkedList<>();
+        PasswordPolicy policy = session.getContext().getRealm().getPasswordPolicy();
+        for (String id : policy.getPolicies()) {
+            PasswordPolicyProvider provider = session.getProvider(PasswordPolicyProvider.class, id);
+            list.add(provider);
+        }
+        return list;
+    }
+
+}
diff --git a/server-spi/src/main/java/org/keycloak/policy/DefaultPasswordPolicyManagerProviderFactory.java b/server-spi/src/main/java/org/keycloak/policy/DefaultPasswordPolicyManagerProviderFactory.java
new file mode 100644
index 0000000..b8aabd4
--- /dev/null
+++ b/server-spi/src/main/java/org/keycloak/policy/DefaultPasswordPolicyManagerProviderFactory.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.keycloak.policy;
+
+import org.keycloak.Config;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.KeycloakSessionFactory;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class DefaultPasswordPolicyManagerProviderFactory implements PasswordPolicyManagerProviderFactory {
+
+    @Override
+    public PasswordPolicyManagerProvider create(KeycloakSession session) {
+        return new DefaultPasswordPolicyManagerProvider(session);
+    }
+
+    @Override
+    public void init(Config.Scope config) {
+    }
+
+    @Override
+    public void postInit(KeycloakSessionFactory factory) {
+    }
+
+    @Override
+    public void close() {
+    }
+
+    @Override
+    public String getId() {
+        return "default";
+    }
+
+}
diff --git a/server-spi/src/main/java/org/keycloak/policy/DigitsPasswordPolicyProvider.java b/server-spi/src/main/java/org/keycloak/policy/DigitsPasswordPolicyProvider.java
new file mode 100644
index 0000000..d3ca22d
--- /dev/null
+++ b/server-spi/src/main/java/org/keycloak/policy/DigitsPasswordPolicyProvider.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.keycloak.policy;
+
+import org.keycloak.models.KeycloakContext;
+import org.keycloak.models.UserModel;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class DigitsPasswordPolicyProvider implements PasswordPolicyProvider {
+
+    private static final String ERROR_MESSAGE = "invalidPasswordMinDigitsMessage";
+
+    private KeycloakContext context;
+
+    public DigitsPasswordPolicyProvider(KeycloakContext context) {
+        this.context = context;
+    }
+
+    @Override
+    public PolicyError validate(String username, String password) {
+        int min = context.getRealm().getPasswordPolicy().getPolicyConfig(DigitsPasswordPolicyProviderFactory.ID);
+        int count = 0;
+        for (char c : password.toCharArray()) {
+            if (Character.isDigit(c)) {
+                count++;
+            }
+        }
+        return count < min ? new PolicyError(ERROR_MESSAGE, min) : null;
+    }
+
+    @Override
+    public PolicyError validate(UserModel user, String password) {
+        return validate(user.getUsername(), password);
+    }
+
+    @Override
+    public Object parseConfig(String value) {
+        return value != null ? Integer.parseInt(value) : 1;
+    }
+
+    @Override
+    public void close() {
+    }
+
+}
diff --git a/server-spi/src/main/java/org/keycloak/policy/DigitsPasswordPolicyProviderFactory.java b/server-spi/src/main/java/org/keycloak/policy/DigitsPasswordPolicyProviderFactory.java
new file mode 100644
index 0000000..d7fce9c
--- /dev/null
+++ b/server-spi/src/main/java/org/keycloak/policy/DigitsPasswordPolicyProviderFactory.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.keycloak.policy;
+
+import org.keycloak.Config;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.KeycloakSessionFactory;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class DigitsPasswordPolicyProviderFactory implements PasswordPolicyProviderFactory {
+
+    static final String ID = "digits";
+
+    @Override
+    public String getId() {
+        return ID;
+    }
+
+    @Override
+    public PasswordPolicyProvider create(KeycloakSession session) {
+        return new DigitsPasswordPolicyProvider(session.getContext());
+    }
+
+    @Override
+    public void init(Config.Scope config) {
+    }
+
+    @Override
+    public void postInit(KeycloakSessionFactory factory) {
+    }
+
+    @Override
+    public String getDisplayName() {
+        return "Digits";
+    }
+
+    @Override
+    public String getConfigType() {
+        return PasswordPolicyProvider.INT_CONFIG_TYPE;
+    }
+
+    @Override
+    public String getDefaultConfigValue() {
+        return "1";
+    }
+
+    @Override
+    public boolean isMultiplSupported() {
+        return false;
+    }
+
+    @Override
+    public void close() {
+    }
+
+}
diff --git a/server-spi/src/main/java/org/keycloak/policy/ForceExpiredPasswordPolicyProviderFactory.java b/server-spi/src/main/java/org/keycloak/policy/ForceExpiredPasswordPolicyProviderFactory.java
new file mode 100644
index 0000000..ecefffb
--- /dev/null
+++ b/server-spi/src/main/java/org/keycloak/policy/ForceExpiredPasswordPolicyProviderFactory.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.keycloak.policy;
+
+import org.keycloak.Config;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.KeycloakSessionFactory;
+import org.keycloak.models.UserModel;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class ForceExpiredPasswordPolicyProviderFactory implements PasswordPolicyProviderFactory, PasswordPolicyProvider {
+
+    public static final String ID = "forceExpiredPasswordChange";
+    public static final int DEFAULT_VALUE = 365;
+
+    @Override
+    public PasswordPolicyProvider create(KeycloakSession session) {
+        return this;
+    }
+
+    @Override
+    public void init(Config.Scope config) {
+    }
+
+    @Override
+    public void postInit(KeycloakSessionFactory factory) {
+    }
+
+    @Override
+    public void close() {
+    }
+
+    @Override
+    public String getId() {
+        return ID;
+    }
+
+    @Override
+    public PolicyError validate(UserModel user, String password) {
+        return null;
+    }
+
+    @Override
+    public PolicyError validate(String user, String password) {
+        return null;
+    }
+
+    @Override
+    public String getDisplayName() {
+        return "Expire Password";
+    }
+
+    @Override
+    public String getConfigType() {
+        return PasswordPolicyProvider.STRING_CONFIG_TYPE;
+    }
+
+    @Override
+    public String getDefaultConfigValue() {
+        return String.valueOf(DEFAULT_VALUE);
+    }
+
+    @Override
+    public boolean isMultiplSupported() {
+        return false;
+    }
+
+    @Override
+    public Object parseConfig(String value) {
+        return value != null ? Integer.parseInt(value) : DEFAULT_VALUE;
+    }
+
+}
diff --git a/server-spi/src/main/java/org/keycloak/policy/HashAlgorithmPasswordPolicyProviderFactory.java b/server-spi/src/main/java/org/keycloak/policy/HashAlgorithmPasswordPolicyProviderFactory.java
new file mode 100644
index 0000000..add3b03
--- /dev/null
+++ b/server-spi/src/main/java/org/keycloak/policy/HashAlgorithmPasswordPolicyProviderFactory.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.keycloak.policy;
+
+import org.keycloak.Config;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.KeycloakSessionFactory;
+import org.keycloak.models.UserModel;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class HashAlgorithmPasswordPolicyProviderFactory implements PasswordPolicyProviderFactory, PasswordPolicyProvider {
+
+    public static final String DEFAULT_VALUE = "pbkdf2";
+
+    public static final String ID = "hashAlgorithm";
+
+    @Override
+    public PasswordPolicyProvider create(KeycloakSession session) {
+        return this;
+    }
+
+    @Override
+    public void init(Config.Scope config) {
+    }
+
+    @Override
+    public void postInit(KeycloakSessionFactory factory) {
+    }
+
+    @Override
+    public void close() {
+    }
+
+    @Override
+    public String getId() {
+        return ID;
+    }
+
+    @Override
+    public PolicyError validate(UserModel user, String password) {
+        return null;
+    }
+
+    @Override
+    public PolicyError validate(String user, String password) {
+        return null;
+    }
+
+    @Override
+    public String getDisplayName() {
+        return "Hashing Algorithm";
+    }
+
+    @Override
+    public String getConfigType() {
+        return PasswordPolicyProvider.STRING_CONFIG_TYPE;
+    }
+
+    @Override
+    public String getDefaultConfigValue() {
+        return DEFAULT_VALUE;
+    }
+
+    @Override
+    public boolean isMultiplSupported() {
+        return false;
+    }
+
+    @Override
+    public Object parseConfig(String value) {
+        return value != null ? value : DEFAULT_VALUE;
+    }
+
+}
diff --git a/server-spi/src/main/java/org/keycloak/policy/HashIterationsPasswordPolicyProviderFactory.java b/server-spi/src/main/java/org/keycloak/policy/HashIterationsPasswordPolicyProviderFactory.java
new file mode 100644
index 0000000..64a77d5
--- /dev/null
+++ b/server-spi/src/main/java/org/keycloak/policy/HashIterationsPasswordPolicyProviderFactory.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.keycloak.policy;
+
+import org.keycloak.Config;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.KeycloakSessionFactory;
+import org.keycloak.models.UserModel;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class HashIterationsPasswordPolicyProviderFactory implements PasswordPolicyProvider, PasswordPolicyProviderFactory {
+
+    public static final int DEFAULT_VALUE = 20000;
+
+    public static final String ID = "hashIterations";
+
+    @Override
+    public PasswordPolicyProvider create(KeycloakSession session) {
+        return this;
+    }
+
+    @Override
+    public void init(Config.Scope config) {
+    }
+
+    @Override
+    public void postInit(KeycloakSessionFactory factory) {
+    }
+
+    @Override
+    public String getId() {
+        return ID;
+    }
+
+    @Override
+    public PolicyError validate(UserModel user, String password) {
+        return null;
+    }
+
+    @Override
+    public PolicyError validate(String user, String password) {
+        return null;
+    }
+
+    @Override
+    public Object parseConfig(String value) {
+        return value != null ? Integer.parseInt(value) : DEFAULT_VALUE;
+    }
+
+    @Override
+    public String getDisplayName() {
+        return "Hashing Iterations";
+    }
+
+    @Override
+    public String getConfigType() {
+        return PasswordPolicyProvider.INT_CONFIG_TYPE;
+    }
+
+    @Override
+    public String getDefaultConfigValue() {
+        return String.valueOf(DEFAULT_VALUE);
+    }
+
+    @Override
+    public boolean isMultiplSupported() {
+        return false;
+    }
+
+    @Override
+    public void close() {
+    }
+
+}
diff --git a/server-spi/src/main/java/org/keycloak/policy/HistoryPasswordPolicyProvider.java b/server-spi/src/main/java/org/keycloak/policy/HistoryPasswordPolicyProvider.java
new file mode 100644
index 0000000..3cfe5fb
--- /dev/null
+++ b/server-spi/src/main/java/org/keycloak/policy/HistoryPasswordPolicyProvider.java
@@ -0,0 +1,117 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.keycloak.policy;
+
+import org.keycloak.hash.PasswordHashManager;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.PasswordPolicy;
+import org.keycloak.models.UserCredentialModel;
+import org.keycloak.models.UserCredentialValueModel;
+import org.keycloak.models.UserModel;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class HistoryPasswordPolicyProvider implements PasswordPolicyProvider {
+
+    private static final String ERROR_MESSAGE = "invalidPasswordHistoryMessage";
+
+    private KeycloakSession session;
+
+    public HistoryPasswordPolicyProvider(KeycloakSession session) {
+        this.session = session;
+    }
+
+    @Override
+    public PolicyError validate(String username, String password) {
+        return null;
+    }
+
+    @Override
+    public PolicyError validate(UserModel user, String password) {
+        PasswordPolicy policy = session.getContext().getRealm().getPasswordPolicy();
+        int passwordHistoryPolicyValue = policy.getPolicyConfig(HistoryPasswordPolicyProviderFactory.ID);
+        if (passwordHistoryPolicyValue != -1) {
+            UserCredentialValueModel cred = getCredentialValueModel(user, UserCredentialModel.PASSWORD);
+            if (cred != null) {
+                if(PasswordHashManager.verify(session, policy, password, cred)) {
+                    return new PolicyError(ERROR_MESSAGE, passwordHistoryPolicyValue);
+                }
+            }
+
+            List<UserCredentialValueModel> passwordExpiredCredentials = getCredentialValueModels(user, passwordHistoryPolicyValue - 1,
+                    UserCredentialModel.PASSWORD_HISTORY);
+            for (UserCredentialValueModel credential : passwordExpiredCredentials) {
+                if (PasswordHashManager.verify(session, policy, password, credential)) {
+                    return new PolicyError(ERROR_MESSAGE, passwordHistoryPolicyValue);
+                }
+            }
+        }
+        return null;
+    }
+
+    private UserCredentialValueModel getCredentialValueModel(UserModel user, String credType) {
+        for (UserCredentialValueModel model : user.getCredentialsDirectly()) {
+            if (model.getType().equals(credType)) {
+                return model;
+            }
+        }
+        return null;
+    }
+
+    private List<UserCredentialValueModel> getCredentialValueModels(UserModel user, int expiredPasswordsPolicyValue, String credType) {
+        List<UserCredentialValueModel> credentialModels = new ArrayList<UserCredentialValueModel>();
+        for (UserCredentialValueModel model : user.getCredentialsDirectly()) {
+            if (model.getType().equals(credType)) {
+                credentialModels.add(model);
+            }
+        }
+
+        Collections.sort(credentialModels, new Comparator<UserCredentialValueModel>() {
+            public int compare(UserCredentialValueModel credFirst, UserCredentialValueModel credSecond) {
+                if (credFirst.getCreatedDate() > credSecond.getCreatedDate()) {
+                    return -1;
+                } else if (credFirst.getCreatedDate() < credSecond.getCreatedDate()) {
+                    return 1;
+                } else {
+                    return 0;
+                }
+            }
+        });
+
+        if (credentialModels.size() > expiredPasswordsPolicyValue) {
+            return credentialModels.subList(0, expiredPasswordsPolicyValue);
+        }
+        return credentialModels;
+    }
+
+    @Override
+    public Object parseConfig(String value) {
+        return value != null ? Integer.parseInt(value) : HistoryPasswordPolicyProviderFactory.DEFAULT_VALUE;
+    }
+
+    @Override
+    public void close() {
+    }
+
+}
diff --git a/server-spi/src/main/java/org/keycloak/policy/HistoryPasswordPolicyProviderFactory.java b/server-spi/src/main/java/org/keycloak/policy/HistoryPasswordPolicyProviderFactory.java
new file mode 100644
index 0000000..c2c180a
--- /dev/null
+++ b/server-spi/src/main/java/org/keycloak/policy/HistoryPasswordPolicyProviderFactory.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.keycloak.policy;
+
+import org.keycloak.Config;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.KeycloakSessionFactory;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class HistoryPasswordPolicyProviderFactory implements PasswordPolicyProviderFactory {
+
+    public static final String ID = "passwordHistory";
+    public static final Integer DEFAULT_VALUE = 3;
+
+    @Override
+    public String getId() {
+        return ID;
+    }
+
+    @Override
+    public PasswordPolicyProvider create(KeycloakSession session) {
+        return new HistoryPasswordPolicyProvider(session);
+    }
+
+    @Override
+    public void init(Config.Scope config) {
+    }
+
+    @Override
+    public void postInit(KeycloakSessionFactory factory) {
+    }
+
+    @Override
+    public String getDisplayName() {
+        return "Not Recently Used";
+    }
+
+    @Override
+    public String getConfigType() {
+        return PasswordPolicyProvider.INT_CONFIG_TYPE;
+    }
+
+    @Override
+    public String getDefaultConfigValue() {
+        return String.valueOf(HistoryPasswordPolicyProviderFactory.DEFAULT_VALUE);
+    }
+
+    @Override
+    public boolean isMultiplSupported() {
+        return false;
+    }
+
+    @Override
+    public void close() {
+    }
+
+}
diff --git a/server-spi/src/main/java/org/keycloak/policy/LengthPasswordPolicyProvider.java b/server-spi/src/main/java/org/keycloak/policy/LengthPasswordPolicyProvider.java
new file mode 100644
index 0000000..bdafdc9
--- /dev/null
+++ b/server-spi/src/main/java/org/keycloak/policy/LengthPasswordPolicyProvider.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.keycloak.policy;
+
+import org.keycloak.models.KeycloakContext;
+import org.keycloak.models.UserModel;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class LengthPasswordPolicyProvider implements PasswordPolicyProvider {
+
+    private static final String ERROR_MESSAGE = "invalidPasswordMinLengthMessage";
+
+    private KeycloakContext context;
+
+    public LengthPasswordPolicyProvider(KeycloakContext context) {
+        this.context = context;
+    }
+
+    @Override
+    public PolicyError validate(String username, String password) {
+        int min = context.getRealm().getPasswordPolicy().getPolicyConfig(LengthPasswordPolicyProviderFactory.ID);
+        return password.length() < min ? new PolicyError(ERROR_MESSAGE, min) : null;
+    }
+
+    @Override
+    public PolicyError validate(UserModel user, String password) {
+        return validate(user.getUsername(), password);
+    }
+
+    @Override
+    public Object parseConfig(String value) {
+        return value != null ? Integer.parseInt(value) : 8;
+    }
+
+    @Override
+    public void close() {
+    }
+
+}
diff --git a/server-spi/src/main/java/org/keycloak/policy/LengthPasswordPolicyProviderFactory.java b/server-spi/src/main/java/org/keycloak/policy/LengthPasswordPolicyProviderFactory.java
new file mode 100644
index 0000000..a60c250
--- /dev/null
+++ b/server-spi/src/main/java/org/keycloak/policy/LengthPasswordPolicyProviderFactory.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.keycloak.policy;
+
+import org.keycloak.Config;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.KeycloakSessionFactory;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class LengthPasswordPolicyProviderFactory implements PasswordPolicyProviderFactory {
+
+    static final String ID = "length";
+
+    @Override
+    public String getId() {
+        return ID;
+    }
+
+    @Override
+    public PasswordPolicyProvider create(KeycloakSession session) {
+        return new LengthPasswordPolicyProvider(session.getContext());
+    }
+
+    @Override
+    public void init(Config.Scope config) {
+    }
+
+    @Override
+    public void postInit(KeycloakSessionFactory factory) {
+    }
+
+    @Override
+    public String getDisplayName() {
+        return "Minimum Length";
+    }
+
+    @Override
+    public String getConfigType() {
+        return PasswordPolicyProvider.INT_CONFIG_TYPE;
+    }
+
+    @Override
+    public String getDefaultConfigValue() {
+        return "8";
+    }
+
+    @Override
+    public boolean isMultiplSupported() {
+        return false;
+    }
+
+    @Override
+    public void close() {
+    }
+
+}
diff --git a/server-spi/src/main/java/org/keycloak/policy/LowerCasePasswordPolicyProvider.java b/server-spi/src/main/java/org/keycloak/policy/LowerCasePasswordPolicyProvider.java
new file mode 100644
index 0000000..3312e2d
--- /dev/null
+++ b/server-spi/src/main/java/org/keycloak/policy/LowerCasePasswordPolicyProvider.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.keycloak.policy;
+
+import org.keycloak.models.KeycloakContext;
+import org.keycloak.models.UserModel;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class LowerCasePasswordPolicyProvider implements PasswordPolicyProvider {
+
+    private static final String ERROR_MESSAGE = "invalidPasswordMinLowerCaseCharsMessage";
+
+    private KeycloakContext context;
+
+    public LowerCasePasswordPolicyProvider(KeycloakContext context) {
+        this.context = context;
+    }
+
+    @Override
+    public PolicyError validate(String username, String password) {
+        int min = context.getRealm().getPasswordPolicy().getPolicyConfig(LowerCasePasswordPolicyProviderFactory.ID);
+        int count = 0;
+        for (char c : password.toCharArray()) {
+            if (Character.isLowerCase(c)) {
+                count++;
+            }
+        }
+        return count < min ? new PolicyError(ERROR_MESSAGE, min) : null;
+    }
+
+    @Override
+    public PolicyError validate(UserModel user, String password) {
+        return validate(user.getUsername(), password);
+    }
+
+    @Override
+    public Object parseConfig(String value) {
+        return value != null ? Integer.parseInt(value) : 1;
+    }
+
+    @Override
+    public void close() {
+    }
+
+}
diff --git a/server-spi/src/main/java/org/keycloak/policy/LowerCasePasswordPolicyProviderFactory.java b/server-spi/src/main/java/org/keycloak/policy/LowerCasePasswordPolicyProviderFactory.java
new file mode 100644
index 0000000..7e96dcf
--- /dev/null
+++ b/server-spi/src/main/java/org/keycloak/policy/LowerCasePasswordPolicyProviderFactory.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.keycloak.policy;
+
+import org.keycloak.Config;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.KeycloakSessionFactory;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class LowerCasePasswordPolicyProviderFactory implements PasswordPolicyProviderFactory {
+
+    public static final String ID = "lowerCase";
+
+    @Override
+    public PasswordPolicyProvider create(KeycloakSession session) {
+        return new LowerCasePasswordPolicyProvider(session.getContext());
+    }
+
+    @Override
+    public void init(Config.Scope config) {
+    }
+
+    @Override
+    public void postInit(KeycloakSessionFactory factory) {
+    }
+
+    @Override
+    public String getDisplayName() {
+        return "Lowercase Characters";
+    }
+
+    @Override
+    public String getConfigType() {
+        return PasswordPolicyProvider.INT_CONFIG_TYPE;
+    }
+
+    @Override
+    public String getDefaultConfigValue() {
+        return "1";
+    }
+
+    @Override
+    public void close() {
+    }
+
+    @Override
+    public boolean isMultiplSupported() {
+        return false;
+    }
+
+    @Override
+    public String getId() {
+        return ID;
+    }
+
+}
diff --git a/server-spi/src/main/java/org/keycloak/policy/NotUsernamePasswordPolicyProvider.java b/server-spi/src/main/java/org/keycloak/policy/NotUsernamePasswordPolicyProvider.java
new file mode 100644
index 0000000..82fa7b6
--- /dev/null
+++ b/server-spi/src/main/java/org/keycloak/policy/NotUsernamePasswordPolicyProvider.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.keycloak.policy;
+
+import org.keycloak.models.KeycloakContext;
+import org.keycloak.models.UserModel;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class NotUsernamePasswordPolicyProvider implements PasswordPolicyProvider {
+
+    private static final String ERROR_MESSAGE = "invalidPasswordNotUsernameMessage";
+
+    private KeycloakContext context;
+
+    public NotUsernamePasswordPolicyProvider(KeycloakContext context) {
+        this.context = context;
+    }
+
+    @Override
+    public PolicyError validate(String username, String password) {
+        return username.equals(password) ? new PolicyError(ERROR_MESSAGE) : null;
+    }
+
+    @Override
+    public PolicyError validate(UserModel user, String password) {
+        return validate(user.getUsername(), password);
+    }
+
+    @Override
+    public Object parseConfig(String value) {
+        return null;
+    }
+
+    @Override
+    public void close() {
+    }
+
+}
diff --git a/server-spi/src/main/java/org/keycloak/policy/NotUsernamePasswordPolicyProviderFactory.java b/server-spi/src/main/java/org/keycloak/policy/NotUsernamePasswordPolicyProviderFactory.java
new file mode 100644
index 0000000..30ebbff
--- /dev/null
+++ b/server-spi/src/main/java/org/keycloak/policy/NotUsernamePasswordPolicyProviderFactory.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.keycloak.policy;
+
+import org.keycloak.Config;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.KeycloakSessionFactory;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class NotUsernamePasswordPolicyProviderFactory implements PasswordPolicyProviderFactory {
+
+    static final String ID = "notUsername";
+
+    @Override
+    public String getId() {
+        return ID;
+    }
+
+    @Override
+    public PasswordPolicyProvider create(KeycloakSession session) {
+        return new NotUsernamePasswordPolicyProvider(session.getContext());
+    }
+
+    @Override
+    public void init(Config.Scope config) {
+    }
+
+    @Override
+    public void postInit(KeycloakSessionFactory factory) {
+    }
+
+    @Override
+    public String getDisplayName() {
+        return "Not Username";
+    }
+
+    @Override
+    public String getConfigType() {
+        return null;
+    }
+
+    @Override
+    public String getDefaultConfigValue() {
+        return null;
+    }
+
+    @Override
+    public boolean isMultiplSupported() {
+        return false;
+    }
+
+    @Override
+    public void close() {
+    }
+
+}
diff --git a/server-spi/src/main/java/org/keycloak/policy/PasswordPolicyManagerProvider.java b/server-spi/src/main/java/org/keycloak/policy/PasswordPolicyManagerProvider.java
new file mode 100644
index 0000000..3039c95
--- /dev/null
+++ b/server-spi/src/main/java/org/keycloak/policy/PasswordPolicyManagerProvider.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.keycloak.policy;
+
+import org.keycloak.models.UserModel;
+import org.keycloak.provider.Provider;
+
+/**
+ * @author <a href="mailto:roelof.naude@epiuse.com">Roelof Naude</a>
+ */
+public interface PasswordPolicyManagerProvider extends Provider {
+
+    PolicyError validate(UserModel user, String password);
+    PolicyError validate(String user, String password);
+
+}
diff --git a/server-spi/src/main/java/org/keycloak/policy/PasswordPolicyManagerProviderFactory.java b/server-spi/src/main/java/org/keycloak/policy/PasswordPolicyManagerProviderFactory.java
new file mode 100644
index 0000000..f68701e
--- /dev/null
+++ b/server-spi/src/main/java/org/keycloak/policy/PasswordPolicyManagerProviderFactory.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.keycloak.policy;
+
+import org.keycloak.provider.ProviderFactory;
+
+/**
+ * @author <a href="mailto:roelof.naude@epiuse.com">Roelof Naude</a>
+ */
+public interface PasswordPolicyManagerProviderFactory extends ProviderFactory<PasswordPolicyManagerProvider> {
+
+}
diff --git a/server-spi/src/main/java/org/keycloak/policy/PasswordPolicyManagerSpi.java b/server-spi/src/main/java/org/keycloak/policy/PasswordPolicyManagerSpi.java
new file mode 100644
index 0000000..266cf1f
--- /dev/null
+++ b/server-spi/src/main/java/org/keycloak/policy/PasswordPolicyManagerSpi.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.keycloak.policy;
+
+import org.keycloak.provider.Provider;
+import org.keycloak.provider.ProviderFactory;
+import org.keycloak.provider.Spi;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class PasswordPolicyManagerSpi implements Spi {
+
+    @Override
+    public boolean isInternal() {
+        return true;
+    }
+
+    @Override
+    public String getName() {
+        return "password-policy-manager";
+    }
+
+    @Override
+    public Class<? extends Provider> getProviderClass() {
+        return PasswordPolicyManagerProvider.class;
+    }
+
+    @Override
+    public Class<? extends ProviderFactory> getProviderFactoryClass() {
+        return PasswordPolicyManagerProviderFactory.class;
+    }
+
+}
diff --git a/server-spi/src/main/java/org/keycloak/policy/PasswordPolicyProvider.java b/server-spi/src/main/java/org/keycloak/policy/PasswordPolicyProvider.java
new file mode 100644
index 0000000..96b1803
--- /dev/null
+++ b/server-spi/src/main/java/org/keycloak/policy/PasswordPolicyProvider.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.keycloak.policy;
+
+import org.keycloak.models.UserModel;
+import org.keycloak.provider.Provider;
+
+/**
+ * @author <a href="mailto:roelof.naude@epiuse.com">Roelof Naude</a>
+ */
+public interface PasswordPolicyProvider extends Provider {
+
+    String STRING_CONFIG_TYPE = "String";
+    String INT_CONFIG_TYPE = "int";
+
+    PolicyError validate(UserModel user, String password);
+    PolicyError validate(String user, String password);
+    Object parseConfig(String value);
+
+}
diff --git a/server-spi/src/main/java/org/keycloak/policy/PasswordPolicyProviderFactory.java b/server-spi/src/main/java/org/keycloak/policy/PasswordPolicyProviderFactory.java
new file mode 100644
index 0000000..44714e3
--- /dev/null
+++ b/server-spi/src/main/java/org/keycloak/policy/PasswordPolicyProviderFactory.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.keycloak.policy;
+
+import org.keycloak.provider.ProviderFactory;
+
+/**
+ * @author <a href="mailto:roelof.naude@epiuse.com">Roelof Naude</a>
+ */
+public interface PasswordPolicyProviderFactory extends ProviderFactory<PasswordPolicyProvider> {
+
+    String getDisplayName();
+    String getConfigType();
+    String getDefaultConfigValue();
+    boolean isMultiplSupported();
+
+}
diff --git a/server-spi/src/main/java/org/keycloak/policy/PasswordPolicySpi.java b/server-spi/src/main/java/org/keycloak/policy/PasswordPolicySpi.java
new file mode 100644
index 0000000..97ad19a
--- /dev/null
+++ b/server-spi/src/main/java/org/keycloak/policy/PasswordPolicySpi.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.keycloak.policy;
+
+import org.keycloak.provider.Provider;
+import org.keycloak.provider.ProviderFactory;
+import org.keycloak.provider.Spi;
+
+/**
+ * @author <a href="mailto:roelof.naude@epiuse.com">Roelof Naude</a>
+ */
+public class PasswordPolicySpi implements Spi {
+
+    @Override
+    public boolean isInternal() {
+        return false;
+    }
+
+    @Override
+    public String getName() {
+        return "password-policy";
+    }
+
+    @Override
+    public Class<? extends Provider> getProviderClass() {
+        return PasswordPolicyProvider.class;
+    }
+
+    @Override
+    public Class<? extends ProviderFactory> getProviderFactoryClass() {
+        return PasswordPolicyProviderFactory.class;
+    }
+}
diff --git a/server-spi/src/main/java/org/keycloak/policy/PolicyError.java b/server-spi/src/main/java/org/keycloak/policy/PolicyError.java
new file mode 100644
index 0000000..d3a84f0
--- /dev/null
+++ b/server-spi/src/main/java/org/keycloak/policy/PolicyError.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.keycloak.policy;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public final class PolicyError {
+    private String message;
+    private Object[] parameters;
+
+    public PolicyError(String message, Object... parameters) {
+        this.message = message;
+        this.parameters = parameters;
+    }
+
+    public String getMessage() {
+        return message;
+    }
+
+    public Object[] getParameters() {
+        return parameters;
+    }
+}
diff --git a/server-spi/src/main/java/org/keycloak/policy/RegexPatternsPasswordPolicyProvider.java b/server-spi/src/main/java/org/keycloak/policy/RegexPatternsPasswordPolicyProvider.java
new file mode 100644
index 0000000..7d4dbcc
--- /dev/null
+++ b/server-spi/src/main/java/org/keycloak/policy/RegexPatternsPasswordPolicyProvider.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.keycloak.policy;
+
+import org.keycloak.models.KeycloakContext;
+import org.keycloak.models.UserModel;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class RegexPatternsPasswordPolicyProvider implements PasswordPolicyProvider {
+
+    private static final String ERROR_MESSAGE = "invalidPasswordRegexPatternMessage";
+
+    private KeycloakContext context;
+
+    public RegexPatternsPasswordPolicyProvider(KeycloakContext context) {
+        this.context = context;
+    }
+
+    @Override
+    public PolicyError validate(String username, String password) {
+        Pattern pattern = context.getRealm().getPasswordPolicy().getPolicyConfig(RegexPatternsPasswordPolicyProviderFactory.ID);
+        Matcher matcher = pattern.matcher(password);
+        if (!matcher.matches()) {
+            return new PolicyError(ERROR_MESSAGE, pattern.pattern());
+        }
+        return null;
+    }
+
+    @Override
+    public PolicyError validate(UserModel user, String password) {
+        return validate(user.getUsername(), password);
+    }
+
+    @Override
+    public Object parseConfig(String value) {
+        if (value == null) {
+            throw new IllegalArgumentException("Config required");
+        }
+        return Pattern.compile(value);
+    }
+
+    @Override
+    public void close() {
+    }
+
+}
diff --git a/server-spi/src/main/java/org/keycloak/policy/RegexPatternsPasswordPolicyProviderFactory.java b/server-spi/src/main/java/org/keycloak/policy/RegexPatternsPasswordPolicyProviderFactory.java
new file mode 100644
index 0000000..c0ce732
--- /dev/null
+++ b/server-spi/src/main/java/org/keycloak/policy/RegexPatternsPasswordPolicyProviderFactory.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.keycloak.policy;
+
+import org.keycloak.Config;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.KeycloakSessionFactory;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class RegexPatternsPasswordPolicyProviderFactory implements PasswordPolicyProviderFactory {
+
+    static final String ID = "regexPattern";
+
+    @Override
+    public String getId() {
+        return ID;
+    }
+
+    @Override
+    public PasswordPolicyProvider create(KeycloakSession session) {
+        return new RegexPatternsPasswordPolicyProvider(session.getContext());
+    }
+
+    @Override
+    public void init(Config.Scope config) {
+    }
+
+    @Override
+    public void postInit(KeycloakSessionFactory factory) {
+    }
+
+    @Override
+    public String getDisplayName() {
+        return "Regular Expression";
+    }
+
+    @Override
+    public String getConfigType() {
+        return PasswordPolicyProvider.STRING_CONFIG_TYPE;
+    }
+
+    @Override
+    public String getDefaultConfigValue() {
+        return "";
+    }
+
+    @Override
+    public boolean isMultiplSupported() {
+        return true;
+    }
+
+    @Override
+    public void close() {
+    }
+
+}
diff --git a/server-spi/src/main/java/org/keycloak/policy/SpecialCharsPasswordPolicyProvider.java b/server-spi/src/main/java/org/keycloak/policy/SpecialCharsPasswordPolicyProvider.java
new file mode 100644
index 0000000..694d81e
--- /dev/null
+++ b/server-spi/src/main/java/org/keycloak/policy/SpecialCharsPasswordPolicyProvider.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.keycloak.policy;
+
+import org.keycloak.models.KeycloakContext;
+import org.keycloak.models.UserModel;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class SpecialCharsPasswordPolicyProvider implements PasswordPolicyProvider {
+
+    private static final String ERROR_MESSAGE = "invalidPasswordMinSpecialCharsMessage";
+
+    private KeycloakContext context;
+
+    public SpecialCharsPasswordPolicyProvider(KeycloakContext context) {
+        this.context = context;
+    }
+
+    @Override
+    public PolicyError validate(String username, String password) {
+        int min = context.getRealm().getPasswordPolicy().getPolicyConfig(SpecialCharsPasswordPolicyProviderFactory.ID);
+        int count = 0;
+        for (char c : password.toCharArray()) {
+            if (!Character.isLetterOrDigit(c)) {
+                count++;
+            }
+        }
+        return count < min ? new PolicyError(ERROR_MESSAGE, min) : null;
+    }
+
+    @Override
+    public PolicyError validate(UserModel user, String password) {
+        return validate(user.getUsername(), password);
+    }
+
+    @Override
+    public Object parseConfig(String value) {
+        return value != null ? Integer.parseInt(value) : 1;
+    }
+
+    @Override
+    public void close() {
+    }
+
+}
diff --git a/server-spi/src/main/java/org/keycloak/policy/SpecialCharsPasswordPolicyProviderFactory.java b/server-spi/src/main/java/org/keycloak/policy/SpecialCharsPasswordPolicyProviderFactory.java
new file mode 100644
index 0000000..908cbee
--- /dev/null
+++ b/server-spi/src/main/java/org/keycloak/policy/SpecialCharsPasswordPolicyProviderFactory.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.keycloak.policy;
+
+import org.keycloak.Config;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.KeycloakSessionFactory;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class SpecialCharsPasswordPolicyProviderFactory implements PasswordPolicyProviderFactory {
+
+    public static final String ID = "specialChars";
+
+    @Override
+    public PasswordPolicyProvider create(KeycloakSession session) {
+        return new SpecialCharsPasswordPolicyProvider(session.getContext());
+    }
+
+    @Override
+    public void init(Config.Scope config) {
+    }
+
+    @Override
+    public void postInit(KeycloakSessionFactory factory) {
+    }
+
+    @Override
+    public void close() {
+    }
+
+    @Override
+    public String getDisplayName() {
+        return "Special Characters";
+    }
+
+    @Override
+    public String getConfigType() {
+        return PasswordPolicyProvider.INT_CONFIG_TYPE;
+    }
+
+    @Override
+    public String getDefaultConfigValue() {
+        return "1";
+    }
+
+    @Override
+    public boolean isMultiplSupported() {
+        return false;
+    }
+
+    @Override
+    public String getId() {
+        return ID;
+    }
+
+}
diff --git a/server-spi/src/main/java/org/keycloak/policy/UpperCasePasswordPolicyProvider.java b/server-spi/src/main/java/org/keycloak/policy/UpperCasePasswordPolicyProvider.java
new file mode 100644
index 0000000..d8a570b
--- /dev/null
+++ b/server-spi/src/main/java/org/keycloak/policy/UpperCasePasswordPolicyProvider.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.keycloak.policy;
+
+import org.keycloak.models.KeycloakContext;
+import org.keycloak.models.UserModel;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class UpperCasePasswordPolicyProvider implements PasswordPolicyProvider {
+
+    private static final String ERROR_MESSAGE = "invalidPasswordMinUpperCaseCharsMessage";
+
+    private KeycloakContext context;
+
+    public UpperCasePasswordPolicyProvider(KeycloakContext context) {
+        this.context = context;
+    }
+
+    @Override
+    public PolicyError validate(String username, String password) {
+        int min = context.getRealm().getPasswordPolicy().getPolicyConfig(UpperCasePasswordPolicyProviderFactory.ID);
+        int count = 0;
+        for (char c : password.toCharArray()) {
+            if (Character.isUpperCase(c)) {
+                count++;
+            }
+        }
+        return count < min ? new PolicyError(ERROR_MESSAGE, min) : null;
+    }
+
+    @Override
+    public PolicyError validate(UserModel user, String password) {
+        return validate(user.getUsername(), password);
+    }
+
+    @Override
+    public Object parseConfig(String value) {
+        return value != null ? Integer.parseInt(value) : 1;
+    }
+
+    @Override
+    public void close() {
+    }
+
+}
diff --git a/server-spi/src/main/java/org/keycloak/policy/UpperCasePasswordPolicyProviderFactory.java b/server-spi/src/main/java/org/keycloak/policy/UpperCasePasswordPolicyProviderFactory.java
new file mode 100644
index 0000000..8dce247
--- /dev/null
+++ b/server-spi/src/main/java/org/keycloak/policy/UpperCasePasswordPolicyProviderFactory.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.keycloak.policy;
+
+import org.keycloak.Config;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.KeycloakSessionFactory;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class UpperCasePasswordPolicyProviderFactory implements PasswordPolicyProviderFactory {
+
+    public static final String ID = "upperCase";
+
+    @Override
+    public PasswordPolicyProvider create(KeycloakSession session) {
+        return new UpperCasePasswordPolicyProvider(session.getContext());
+    }
+
+    @Override
+    public void init(Config.Scope config) {
+    }
+
+    @Override
+    public void postInit(KeycloakSessionFactory factory) {
+    }
+
+    @Override
+    public void close() {
+    }
+
+    @Override
+    public String getDisplayName() {
+        return "Uppercase Characters";
+    }
+
+    @Override
+    public String getConfigType() {
+        return PasswordPolicyProvider.INT_CONFIG_TYPE;
+    }
+
+    @Override
+    public String getDefaultConfigValue() {
+        return "1";
+    }
+
+    @Override
+    public boolean isMultiplSupported() {
+        return false;
+    }
+
+    @Override
+    public String getId() {
+        return ID;
+    }
+
+}
diff --git a/server-spi/src/main/java/org/keycloak/provider/ProviderConfigProperty.java b/server-spi/src/main/java/org/keycloak/provider/ProviderConfigProperty.java
index 93f7aad..5ddfb4c 100755
--- a/server-spi/src/main/java/org/keycloak/provider/ProviderConfigProperty.java
+++ b/server-spi/src/main/java/org/keycloak/provider/ProviderConfigProperty.java
@@ -36,6 +36,17 @@ public class ProviderConfigProperty {
     protected String type;
     protected Object defaultValue;
 
+    public ProviderConfigProperty() {
+    }
+
+    public ProviderConfigProperty(String name, String label, String helpText, String type, Object defaultValue) {
+        this.name = name;
+        this.label = label;
+        this.helpText = helpText;
+        this.type = type;
+        this.defaultValue = defaultValue;
+    }
+
     public String getName() {
         return name;
     }
diff --git a/server-spi/src/main/resources/META-INF/services/org.keycloak.policy.PasswordPolicyManagerProviderFactory b/server-spi/src/main/resources/META-INF/services/org.keycloak.policy.PasswordPolicyManagerProviderFactory
new file mode 100644
index 0000000..128272d
--- /dev/null
+++ b/server-spi/src/main/resources/META-INF/services/org.keycloak.policy.PasswordPolicyManagerProviderFactory
@@ -0,0 +1,18 @@
+#
+# Copyright 2016 Red Hat, Inc. and/or its affiliates
+# and other contributors as indicated by the @author tags.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+org.keycloak.policy.DefaultPasswordPolicyManagerProviderFactory
\ No newline at end of file
diff --git a/server-spi/src/main/resources/META-INF/services/org.keycloak.policy.PasswordPolicyProviderFactory b/server-spi/src/main/resources/META-INF/services/org.keycloak.policy.PasswordPolicyProviderFactory
new file mode 100644
index 0000000..a436fe9
--- /dev/null
+++ b/server-spi/src/main/resources/META-INF/services/org.keycloak.policy.PasswordPolicyProviderFactory
@@ -0,0 +1,28 @@
+#
+# Copyright 2016 Red Hat, Inc. and/or its affiliates
+# and other contributors as indicated by the @author tags.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+org.keycloak.policy.DigitsPasswordPolicyProviderFactory
+org.keycloak.policy.ForceExpiredPasswordPolicyProviderFactory
+org.keycloak.policy.HashAlgorithmPasswordPolicyProviderFactory
+org.keycloak.policy.HashIterationsPasswordPolicyProviderFactory
+org.keycloak.policy.HistoryPasswordPolicyProviderFactory
+org.keycloak.policy.LengthPasswordPolicyProviderFactory
+org.keycloak.policy.LowerCasePasswordPolicyProviderFactory
+org.keycloak.policy.NotUsernamePasswordPolicyProviderFactory
+org.keycloak.policy.RegexPatternsPasswordPolicyProviderFactory
+org.keycloak.policy.SpecialCharsPasswordPolicyProviderFactory
+org.keycloak.policy.UpperCasePasswordPolicyProviderFactory
diff --git a/server-spi/src/main/resources/META-INF/services/org.keycloak.provider.Spi b/server-spi/src/main/resources/META-INF/services/org.keycloak.provider.Spi
index c5a5ebb..696bb4d 100755
--- a/server-spi/src/main/resources/META-INF/services/org.keycloak.provider.Spi
+++ b/server-spi/src/main/resources/META-INF/services/org.keycloak.provider.Spi
@@ -60,4 +60,5 @@ org.keycloak.authorization.store.StoreFactorySpi
 org.keycloak.authorization.AuthorizationSpi
 org.keycloak.models.cache.authorization.CachedStoreFactorySpi
 org.keycloak.protocol.oidc.TokenIntrospectionSpi
-
+org.keycloak.policy.PasswordPolicySpi
+org.keycloak.policy.PasswordPolicyManagerSpi
\ No newline at end of file
diff --git a/services/src/main/java/org/keycloak/authentication/forms/RegistrationPassword.java b/services/src/main/java/org/keycloak/authentication/forms/RegistrationPassword.java
index 7f2b0c8..a42a073 100755
--- a/services/src/main/java/org/keycloak/authentication/forms/RegistrationPassword.java
+++ b/services/src/main/java/org/keycloak/authentication/forms/RegistrationPassword.java
@@ -33,6 +33,8 @@ import org.keycloak.models.RealmModel;
 import org.keycloak.models.UserCredentialModel;
 import org.keycloak.models.UserModel;
 import org.keycloak.models.utils.FormMessage;
+import org.keycloak.policy.PasswordPolicyManagerProvider;
+import org.keycloak.policy.PolicyError;
 import org.keycloak.provider.ProviderConfigProperty;
 import org.keycloak.representations.idm.CredentialRepresentation;
 import org.keycloak.services.messages.Messages;
@@ -70,7 +72,7 @@ public class RegistrationPassword implements FormAction, FormActionFactory {
             errors.add(new FormMessage(RegistrationPage.FIELD_PASSWORD_CONFIRM, Messages.INVALID_PASSWORD_CONFIRM));
         }
         if (formData.getFirst(RegistrationPage.FIELD_PASSWORD) != null) {
-            PasswordPolicy.Error err = context.getRealm().getPasswordPolicy().validate(context.getSession(), context.getRealm().isRegistrationEmailAsUsername() ? formData.getFirst(RegistrationPage.FIELD_EMAIL) : formData.getFirst(RegistrationPage.FIELD_USERNAME), formData.getFirst(RegistrationPage.FIELD_PASSWORD));
+            PolicyError err = context.getSession().getProvider(PasswordPolicyManagerProvider.class).validate(context.getRealm().isRegistrationEmailAsUsername() ? formData.getFirst(RegistrationPage.FIELD_EMAIL) : formData.getFirst(RegistrationPage.FIELD_USERNAME), formData.getFirst(RegistrationPage.FIELD_PASSWORD));
             if (err != null)
                 errors.add(new FormMessage(RegistrationPage.FIELD_PASSWORD, err.getMessage(), err.getParameters()));
         }
diff --git a/services/src/main/java/org/keycloak/services/managers/ApplianceBootstrap.java b/services/src/main/java/org/keycloak/services/managers/ApplianceBootstrap.java
index 99276d0..356922e 100755
--- a/services/src/main/java/org/keycloak/services/managers/ApplianceBootstrap.java
+++ b/services/src/main/java/org/keycloak/services/managers/ApplianceBootstrap.java
@@ -84,6 +84,8 @@ public class ApplianceBootstrap {
 
     public void createMasterRealmUser(String username, String password) {
         RealmModel realm = session.realms().getRealm(Config.getAdminRealm());
+        session.getContext().setRealm(realm);
+
         if (session.users().getUsersCount(realm) > 0) {
             throw new IllegalStateException("Can't create initial user as users already exists");
         }
diff --git a/services/src/main/java/org/keycloak/services/managers/RealmManager.java b/services/src/main/java/org/keycloak/services/managers/RealmManager.java
index b38e245..214abb3 100755
--- a/services/src/main/java/org/keycloak/services/managers/RealmManager.java
+++ b/services/src/main/java/org/keycloak/services/managers/RealmManager.java
@@ -220,7 +220,7 @@ public class RealmManager implements RealmImporter {
 
         realm.setEventsListeners(Collections.singleton("jboss-logging"));
 
-        realm.setPasswordPolicy(new PasswordPolicy("hashIterations(20000)"));
+        realm.setPasswordPolicy(PasswordPolicy.parse(session, "hashIterations(20000)"));
     }
 
     public boolean removeRealm(RealmModel realm) {
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/info/ServerInfoAdminResource.java b/services/src/main/java/org/keycloak/services/resources/admin/info/ServerInfoAdminResource.java
index 3ea8fd1..1a67fb0 100755
--- a/services/src/main/java/org/keycloak/services/resources/admin/info/ServerInfoAdminResource.java
+++ b/services/src/main/java/org/keycloak/services/resources/admin/info/ServerInfoAdminResource.java
@@ -35,6 +35,11 @@ import org.keycloak.broker.provider.IdentityProvider;
 import org.keycloak.broker.provider.IdentityProviderFactory;
 import org.keycloak.events.EventType;
 import org.keycloak.events.admin.OperationType;
+import org.keycloak.models.PasswordPolicy;
+import org.keycloak.policy.PasswordPolicyProvider;
+import org.keycloak.policy.PasswordPolicyProviderFactory;
+import org.keycloak.provider.*;
+import org.keycloak.representations.idm.PasswordPolicyTypeRepresentation;
 import org.keycloak.theme.Theme;
 import org.keycloak.theme.ThemeProvider;
 import org.keycloak.models.KeycloakSession;
@@ -44,10 +49,6 @@ import org.keycloak.protocol.ClientInstallationProvider;
 import org.keycloak.protocol.LoginProtocol;
 import org.keycloak.protocol.LoginProtocolFactory;
 import org.keycloak.protocol.ProtocolMapper;
-import org.keycloak.provider.ProviderConfigProperty;
-import org.keycloak.provider.ProviderFactory;
-import org.keycloak.provider.ServerInfoAwareProviderFactory;
-import org.keycloak.provider.Spi;
 import org.keycloak.representations.idm.ConfigPropertyRepresentation;
 import org.keycloak.representations.idm.ProtocolMapperRepresentation;
 import org.keycloak.representations.idm.ProtocolMapperTypeRepresentation;
@@ -88,6 +89,7 @@ public class ServerInfoAdminResource {
         setProtocolMapperTypes(info);
         setBuiltinProtocolMappers(info);
         setClientInstallations(info);
+        setPasswordPolicies(info);
         info.setEnums(ENUMS);
         return info;
     }
@@ -248,6 +250,20 @@ public class ServerInfoAdminResource {
         }
     }
 
+    private void setPasswordPolicies(ServerInfoRepresentation info) {
+        info.setPasswordPolicies(new LinkedList<>());
+        for (ProviderFactory f : session.getKeycloakSessionFactory().getProviderFactories(PasswordPolicyProvider.class)) {
+            PasswordPolicyProviderFactory factory = (PasswordPolicyProviderFactory) f;
+            PasswordPolicyTypeRepresentation rep = new PasswordPolicyTypeRepresentation();
+            rep.setId(factory.getId());
+            rep.setDisplayName(factory.getDisplayName());
+            rep.setConfigType(factory.getConfigType());
+            rep.setDefaultValue(factory.getDefaultConfigValue());
+            rep.setMultipleSupported(factory.isMultiplSupported());
+            info.getPasswordPolicies().add(rep);
+        }
+    }
+
     private static Map<String, List<String>> createEnumsMap(Class... enums) {
         Map<String, List<String>> m = new HashMap<>();
         for (Class e : enums) {
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/RealmAdminResource.java b/services/src/main/java/org/keycloak/services/resources/admin/RealmAdminResource.java
index 1eee46b..4594bdd 100644
--- a/services/src/main/java/org/keycloak/services/resources/admin/RealmAdminResource.java
+++ b/services/src/main/java/org/keycloak/services/resources/admin/RealmAdminResource.java
@@ -280,7 +280,7 @@ public class RealmAdminResource {
                 }
             }
 
-            RepresentationToModel.updateRealm(rep, realm);
+            RepresentationToModel.updateRealm(rep, realm, session);
 
             // Refresh periodic sync tasks for configured federationProviders
             List<UserFederationProviderModel> federationProviders = realm.getUserFederationProviders();
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 b5a39bb..eacfa8f 100755
--- a/services/src/main/java/org/keycloak/services/validation/Validation.java
+++ b/services/src/main/java/org/keycloak/services/validation/Validation.java
@@ -22,6 +22,8 @@ import org.keycloak.models.KeycloakSession;
 import org.keycloak.models.PasswordPolicy;
 import org.keycloak.models.RealmModel;
 import org.keycloak.models.utils.FormMessage;
+import org.keycloak.policy.PasswordPolicyManagerProvider;
+import org.keycloak.policy.PolicyError;
 import org.keycloak.representations.idm.CredentialRepresentation;
 import org.keycloak.services.messages.Messages;
 
@@ -73,7 +75,7 @@ public class Validation {
         }
 
         if (formData.getFirst(FIELD_PASSWORD) != null) {
-            PasswordPolicy.Error err = policy.validate(session, realm.isRegistrationEmailAsUsername()?formData.getFirst(FIELD_EMAIL):formData.getFirst(FIELD_USERNAME), formData.getFirst(FIELD_PASSWORD));
+            PolicyError err = session.getProvider(PasswordPolicyManagerProvider.class).validate(realm.isRegistrationEmailAsUsername() ? formData.getFirst(FIELD_EMAIL) : formData.getFirst(FIELD_USERNAME), formData.getFirst(FIELD_PASSWORD));
             if (err != null)
                 errors.add(new FormMessage(FIELD_PASSWORD, err.getMessage(), err.getParameters()));
         }
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/model/AdapterTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/model/AdapterTest.java
index f340379..a28af56 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/model/AdapterTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/model/AdapterTest.java
@@ -164,11 +164,11 @@ public class AdapterTest extends AbstractModelTest {
         Assert.assertTrue(userProvider.validCredentials(session, realmModel, user, UserCredentialModel.password("geheim")));
         List<UserCredentialValueModel> creds = user.getCredentialsDirectly();
         Assert.assertEquals(creds.get(0).getHashIterations(), 20000);
-        realmModel.setPasswordPolicy(new PasswordPolicy("hashIterations(200)"));
+        realmModel.setPasswordPolicy(PasswordPolicy.parse(realmManager.getSession(), "hashIterations(200)"));
         Assert.assertTrue(userProvider.validCredentials(session, realmModel, user, UserCredentialModel.password("geheim")));
         creds = user.getCredentialsDirectly();
         Assert.assertEquals(creds.get(0).getHashIterations(), 200);
-        realmModel.setPasswordPolicy(new PasswordPolicy("hashIterations(1)"));
+        realmModel.setPasswordPolicy(PasswordPolicy.parse(realmManager.getSession(), "hashIterations(1)"));
     }
 
     @Test
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/model/ModelTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/model/ModelTest.java
index c356eb5..a34f460 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/model/ModelTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/model/ModelTest.java
@@ -42,7 +42,7 @@ public class ModelTest extends AbstractModelTest {
         realm.setSslRequired(SslRequired.EXTERNAL);
         realm.setVerifyEmail(true);
         realm.setAccessTokenLifespan(1000);
-        realm.setPasswordPolicy(new PasswordPolicy("length"));
+        realm.setPasswordPolicy(PasswordPolicy.parse(realmManager.getSession(), "length"));
         realm.setAccessCodeLifespan(1001);
         realm.setAccessCodeLifespanUserAction(1002);
         KeycloakModelUtils.generateRealmKeys(realm);
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/model/PasswordPolicyTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/model/PasswordPolicyTest.java
new file mode 100755
index 0000000..e21fb0b
--- /dev/null
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/model/PasswordPolicyTest.java
@@ -0,0 +1,185 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.keycloak.testsuite.model;
+
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.keycloak.models.PasswordPolicy;
+import org.keycloak.models.RealmModel;
+import org.keycloak.policy.PasswordPolicyManagerProvider;
+
+import java.util.regex.PatternSyntaxException;
+
+import static org.junit.Assert.fail;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class PasswordPolicyTest extends AbstractModelTest {
+
+    private RealmModel realmModel;
+    private PasswordPolicyManagerProvider policyManager;
+
+    @Before
+    public void before() throws Exception {
+        super.before();
+        realmModel = realmManager.createRealm("JUGGLER");
+        session.getContext().setRealm(realmModel);
+        policyManager = session.getProvider(PasswordPolicyManagerProvider.class);
+    }
+
+    @Test
+    public void testLength() {
+        realmModel.setPasswordPolicy(PasswordPolicy.parse(session, "length"));
+
+        Assert.assertEquals("invalidPasswordMinLengthMessage", policyManager.validate("jdoe", "1234567").getMessage());
+        Assert.assertArrayEquals(new Object[]{8}, policyManager.validate("jdoe", "1234567").getParameters());
+        Assert.assertNull(policyManager.validate("jdoe", "12345678"));
+
+        realmModel.setPasswordPolicy(PasswordPolicy.parse(session, "length(4)"));
+
+        Assert.assertEquals("invalidPasswordMinLengthMessage", policyManager.validate("jdoe", "123").getMessage());
+        Assert.assertArrayEquals(new Object[]{4}, policyManager.validate("jdoe", "123").getParameters());
+        Assert.assertNull(policyManager.validate("jdoe", "1234"));
+    }
+
+    @Test
+    public void testDigits() {
+        realmModel.setPasswordPolicy(PasswordPolicy.parse(session, "digits"));
+        Assert.assertEquals("invalidPasswordMinDigitsMessage", policyManager.validate("jdoe", "abcd").getMessage());
+        Assert.assertArrayEquals(new Object[]{1}, policyManager.validate("jdoe", "abcd").getParameters());
+        Assert.assertNull(policyManager.validate("jdoe", "abcd1"));
+
+        realmModel.setPasswordPolicy(PasswordPolicy.parse(session, "digits(2)"));
+        Assert.assertEquals("invalidPasswordMinDigitsMessage", policyManager.validate("jdoe", "abcd1").getMessage());
+        Assert.assertArrayEquals(new Object[]{2}, policyManager.validate("jdoe", "abcd1").getParameters());
+        Assert.assertNull(policyManager.validate("jdoe", "abcd12"));
+    }
+
+    @Test
+    public void testLowerCase() {
+        realmModel.setPasswordPolicy(PasswordPolicy.parse(session, "lowerCase"));
+        Assert.assertEquals("invalidPasswordMinLowerCaseCharsMessage", policyManager.validate("jdoe", "ABCD1234").getMessage());
+        Assert.assertArrayEquals(new Object[]{1}, policyManager.validate("jdoe", "ABCD1234").getParameters());
+        Assert.assertNull(policyManager.validate("jdoe", "ABcD1234"));
+
+        realmModel.setPasswordPolicy(PasswordPolicy.parse(session, "lowerCase(2)"));
+        Assert.assertEquals("invalidPasswordMinLowerCaseCharsMessage", policyManager.validate("jdoe", "ABcD1234").getMessage());
+        Assert.assertArrayEquals(new Object[]{2}, policyManager.validate("jdoe", "ABcD1234").getParameters());
+        Assert.assertNull(policyManager.validate("jdoe", "aBcD1234"));
+    }
+
+    @Test
+    public void testUpperCase() {
+        realmModel.setPasswordPolicy(PasswordPolicy.parse(session, "upperCase"));
+        Assert.assertEquals("invalidPasswordMinUpperCaseCharsMessage", policyManager.validate("jdoe", "abcd1234").getMessage());
+        Assert.assertArrayEquals(new Object[]{1}, policyManager.validate("jdoe", "abcd1234").getParameters());
+        Assert.assertNull(policyManager.validate("jdoe", "abCd1234"));
+
+        realmModel.setPasswordPolicy(PasswordPolicy.parse(session, "upperCase(2)"));
+        Assert.assertEquals("invalidPasswordMinUpperCaseCharsMessage", policyManager.validate("jdoe", "abCd1234").getMessage());
+        Assert.assertArrayEquals(new Object[]{2}, policyManager.validate("jdoe", "abCd1234").getParameters());
+        Assert.assertNull(policyManager.validate("jdoe", "AbCd1234"));
+    }
+
+    @Test
+    public void testSpecialChars() {
+        realmModel.setPasswordPolicy(PasswordPolicy.parse(session, "specialChars"));
+        Assert.assertEquals("invalidPasswordMinSpecialCharsMessage", policyManager.validate("jdoe", "abcd1234").getMessage());
+        Assert.assertArrayEquals(new Object[]{1}, policyManager.validate("jdoe", "abcd1234").getParameters());
+        Assert.assertNull(policyManager.validate("jdoe", "ab&d1234"));
+
+        realmModel.setPasswordPolicy(PasswordPolicy.parse(session, "specialChars(2)"));
+        Assert.assertEquals("invalidPasswordMinSpecialCharsMessage", policyManager.validate("jdoe", "ab&d1234").getMessage());
+        Assert.assertArrayEquals(new Object[]{2}, policyManager.validate("jdoe", "ab&d1234").getParameters());
+        Assert.assertNull(policyManager.validate("jdoe", "ab&d-234"));
+    }
+
+    @Test
+    public void testNotUsername() {
+        realmModel.setPasswordPolicy(PasswordPolicy.parse(session, "notUsername"));
+        Assert.assertEquals("invalidPasswordNotUsernameMessage", policyManager.validate("jdoe", "jdoe").getMessage());
+        Assert.assertNull(policyManager.validate("jdoe", "ab&d1234"));
+    }
+
+    @Test
+    public void testInvalidPolicyName() {
+        try {
+            realmModel.setPasswordPolicy(PasswordPolicy.parse(session, "noSuchPolicy"));
+            Assert.fail("Expected exception");
+        } catch (IllegalArgumentException e) {
+        }
+    }
+
+    @Test
+    public void testRegexPatterns() {
+        PasswordPolicy policy = null;
+        try {
+            realmModel.setPasswordPolicy(PasswordPolicy.parse(session, "regexPattern"));
+            fail("Expected NullPointerException: Regex Pattern cannot be null.");
+        } catch (IllegalArgumentException e) {
+            // Expected NPE as regex pattern is null.
+        }
+
+        try {
+            realmModel.setPasswordPolicy(PasswordPolicy.parse(session, "regexPattern(*)"));
+            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 {
+            realmModel.setPasswordPolicy(PasswordPolicy.parse(session, "regexPattern(*,**)"));
+            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
+        realmModel.setPasswordPolicy(PasswordPolicy.parse(session, "regexPattern(jdoe) and regexPattern(j*d)"));
+        Assert.assertEquals("invalidPasswordRegexPatternMessage", policyManager.validate("jdoe", "jdoe").getMessage());
+
+        ////Fails to match all of the regex patterns
+        realmModel.setPasswordPolicy(PasswordPolicy.parse(session, "regexPattern(j*p) and regexPattern(j*d) and regexPattern(adoe)"));
+        Assert.assertEquals("invalidPasswordRegexPatternMessage", policyManager.validate("jdoe", "jdoe").getMessage());
+
+        realmModel.setPasswordPolicy(PasswordPolicy.parse(session, "regexPattern([a-z][a-z][a-z][a-z][0-9])"));
+        Assert.assertEquals("invalidPasswordRegexPatternMessage", policyManager.validate("jdoe", "jdoe").getMessage());
+
+        realmModel.setPasswordPolicy(PasswordPolicy.parse(session, "regexPattern(jdoe)"));
+        Assert.assertNull(policyManager.validate("jdoe", "jdoe"));
+
+        realmModel.setPasswordPolicy(PasswordPolicy.parse(session, "regexPattern([a-z][a-z][a-z][a-z][0-9])"));
+        Assert.assertNull(policyManager.validate("jdoe", "jdoe0"));
+    }
+
+    @Test
+    public void testComplex() {
+        realmModel.setPasswordPolicy(PasswordPolicy.parse(session, "length(8) and digits(2) and lowerCase(2) and upperCase(2) and specialChars(2) and notUsername()"));
+        Assert.assertNotNull(policyManager.validate("jdoe", "12aaBB&"));
+        Assert.assertNotNull(policyManager.validate("jdoe", "aaaaBB&-"));
+        Assert.assertNotNull(policyManager.validate("jdoe", "12AABB&-"));
+        Assert.assertNotNull(policyManager.validate("jdoe", "12aabb&-"));
+        Assert.assertNotNull(policyManager.validate("jdoe", "12aaBBcc"));
+        Assert.assertNotNull(policyManager.validate("12aaBB&-", "12aaBB&-"));
+
+        Assert.assertNull(policyManager.validate("jdoe", "12aaBB&-"));
+    }
+
+}
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/ResourceOwnerPasswordCredentialsGrantTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/ResourceOwnerPasswordCredentialsGrantTest.java
index ec76062..6796d12 100755
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/ResourceOwnerPasswordCredentialsGrantTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/ResourceOwnerPasswordCredentialsGrantTest.java
@@ -351,8 +351,7 @@ public class ResourceOwnerPasswordCredentialsGrantTest extends AbstractKeycloakT
     public void grantAccessTokenExpiredPassword() throws Exception {
 
         RealmResource realmResource = adminClient.realm("test");
-        RealmManager.realm(realmResource).passwordPolicy(
-                new PasswordPolicy("forceExpiredPasswordChange(1)").toString());
+        RealmManager.realm(realmResource).passwordPolicy("forceExpiredPasswordChange(1)");
 
         try {
             setTimeOffset(60 * 60 * 48);
@@ -376,7 +375,7 @@ public class ResourceOwnerPasswordCredentialsGrantTest extends AbstractKeycloakT
                     .user((String) null)
                     .assertEvent();
         } finally {
-            RealmManager.realm(realmResource).passwordPolicy(new PasswordPolicy("").toString());
+            RealmManager.realm(realmResource).passwordPolicy("");
             UserManager.realm(realmResource).username("test-user@localhost")
                     .removeRequiredAction(UserModel.RequiredAction.UPDATE_PASSWORD.toString());
         }
diff --git a/themes/src/main/resources/theme/base/admin/messages/admin-messages_en.properties b/themes/src/main/resources/theme/base/admin/messages/admin-messages_en.properties
index 4e23f6c..af199e3 100644
--- a/themes/src/main/resources/theme/base/admin/messages/admin-messages_en.properties
+++ b/themes/src/main/resources/theme/base/admin/messages/admin-messages_en.properties
@@ -919,4 +919,4 @@ clear-events=Clear events
 saved-types=Saved Types
 clear-admin-events=Clear admin events
 clear-changes=Clear changes
-error=Error
+error=Error
\ No newline at end of file
diff --git a/themes/src/main/resources/theme/base/admin/resources/js/app.js b/themes/src/main/resources/theme/base/admin/resources/js/app.js
index 2591bbf..e5cd7c1 100755
--- a/themes/src/main/resources/theme/base/admin/resources/js/app.js
+++ b/themes/src/main/resources/theme/base/admin/resources/js/app.js
@@ -1639,6 +1639,9 @@ module.config([ '$routeProvider', function($routeProvider) {
             resolve : {
                 realm : function(RealmLoader) {
                     return RealmLoader();
+                },
+                serverInfo : function(ServerInfoLoader) {
+                    return ServerInfoLoader();
                 }
             },
             controller : 'RealmPasswordPolicyCtrl'
diff --git a/themes/src/main/resources/theme/base/admin/resources/js/controllers/realm.js b/themes/src/main/resources/theme/base/admin/resources/js/controllers/realm.js
index 65f45b6..68a4049 100755
--- a/themes/src/main/resources/theme/base/admin/resources/js/controllers/realm.js
+++ b/themes/src/main/resources/theme/base/admin/resources/js/controllers/realm.js
@@ -429,68 +429,84 @@ module.controller('RealmCacheCtrl', function($scope, realm, RealmClearUserCache,
 
 });
 
-module.controller('RealmPasswordPolicyCtrl', function($scope, Realm, realm, $http, $location, Dialog, Notifications, PasswordPolicy) {
-    console.log('RealmPasswordPolicyCtrl');
+module.controller('RealmPasswordPolicyCtrl', function($scope, Realm, realm, $http, $location, $route, Dialog, Notifications, serverInfo) {
+    var parse = function(policyString) {
+        var policies = [];
+        if (!policyString || policyString.length == 0){
+            return policies;
+        }
 
-    $scope.realm = realm;
+        var policyArray = policyString.split(" and ");
 
-    var oldCopy = angular.copy($scope.realm);
+        for (var i = 0; i < policyArray.length; i ++){
+            var policyToken = policyArray[i];
+            var id;
+            var value;
+            if (policyToken.indexOf('(') == -1) {
+                id = policyToken.trim();
+            } else {
+                id = policyToken.substring(0, policyToken.indexOf('('));
+                value = policyToken.substring(policyToken.indexOf('(') + 1, policyToken.indexOf(')')).trim();
+            }
+
+            for (var j = 0; j < serverInfo.passwordPolicies.length; j++) {
+                if (serverInfo.passwordPolicies[j].id == id) {
+                    var p = serverInfo.passwordPolicies[j];
+                    p.value = value && value || p.defaultValue;
+                    policies.push(p);
+                }
+            }
+        }
+        return policies;
+    };
 
-    $scope.allPolicies = PasswordPolicy.allPolicies;
-    $scope.policyMessages = PasswordPolicy.policyMessages;
+    var toString = function(policies) {
+        if (!policies || policies.length == 0) {
+            return "";
+        }
+        var policyString = "";
+        for (var i = 0; i < policies.length; i++) {
+            policyString += policies[i].id;
+            if (policies[i].value && policies[i].value != policies[i].defaultValue) {
+                policyString += '(' + policies[i].value + ')';
+            }
+            policyString += " and ";
+        }
+        policyString = policyString.substring(0, policyString.length - 5);
+        return policyString;
+    }
 
-    $scope.policy = PasswordPolicy.parse(realm.passwordPolicy);
-    var oldPolicy = angular.copy($scope.policy);
+    $scope.realm = realm;
+    $scope.serverInfo = serverInfo;
+    $scope.changed = false; $scope.policy = parse(realm.passwordPolicy);
 
     $scope.addPolicy = function(policy){
+        policy.value = policy.defaultValue;
         if (!$scope.policy) {
             $scope.policy = [];
         }
-        if (policy.name === 'regexPattern') {
-            for (var i in $scope.allPolicies) {
-                var p = $scope.allPolicies[i];
-                if (p.name === 'regexPattern') {
-                    $scope.allPolicies[i] = { name: 'regexPattern', value: '' };
-                }
-            }
-        }
         $scope.policy.push(policy);
+        $scope.changed = true;
     }
 
     $scope.removePolicy = function(index){
         $scope.policy.splice(index, 1);
+        $scope.changed = true;
     }
 
-    $scope.changed = false;
-
-    $scope.$watch('realm', function() {
-        if (!angular.equals($scope.realm, oldCopy)) {
-            $scope.changed = true;
-        }
-    }, true);
-
-    $scope.$watch('policy', function(oldVal, newVal) {
-        if (!angular.equals($scope.policy, oldPolicy)) {
-            $scope.realm.passwordPolicy = PasswordPolicy.toString($scope.policy);
-            $scope.changed = true;
-        }
-    }, true);
-
     $scope.save = function() {
         $scope.changed = false;
+        $scope.realm.passwordPolicy = toString($scope.policy);
+        console.debug($scope.realm.passwordPolicy);
 
         Realm.update($scope.realm, function () {
             $location.url("/realms/" + realm.realm + "/authentication/password-policy");
             Notifications.success("Your changes have been saved to the realm.");
-            oldCopy = angular.copy($scope.realm);
-            oldPolicy = angular.copy($scope.policy);
         });
     };
 
     $scope.reset = function() {
-        $scope.realm = angular.copy(oldCopy);
-        $scope.policy = angular.copy(oldPolicy);
-        $scope.changed = false;
+        $route.reload();
     };
 });
 
diff --git a/themes/src/main/resources/theme/base/admin/resources/js/services.js b/themes/src/main/resources/theme/base/admin/resources/js/services.js
index 6630c25..c49a8d5 100755
--- a/themes/src/main/resources/theme/base/admin/resources/js/services.js
+++ b/themes/src/main/resources/theme/base/admin/resources/js/services.js
@@ -1238,90 +1238,6 @@ module.factory('TimeUnit2', function() {
     return t;
 });
 
-
-module.factory('PasswordPolicy', function() {
-    var p = {};
-
-    p.policyMessages = {
-        hashAlgorithm: 	"Default hashing algorithm.  Default is 'pbkdf2'.",
-        hashIterations: 	"Number of hashing iterations.  Default is 1.  Recommended is 50000.",
-        length:         	"Minimal password length (integer type). Default value is 8.",
-        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.",
-        notUsername:    	"Block passwords that are equal to the username",
-        regexPattern:  	    "Block passwords that do not match the regex pattern (string type).",
-        passwordHistory:  	"Block passwords that are equal to previous passwords. Default value is 3.",
-        forceExpiredPasswordChange:  	"Force password change when password credential is expired. Default value is 365 days."
-    }
-
-    p.allPolicies = [
-        { name: 'hashAlgorithm', value: 'pbkdf2' },
-        { name: 'hashIterations', value: 1 },
-        { name: 'length', value: 8 },
-        { name: 'digits', value: 1 },
-        { name: 'lowerCase', value: 1 },
-        { name: 'upperCase', value: 1 },
-        { name: 'specialChars', value: 1 },
-        { name: 'notUsername', value: 1 },
-        { name: 'regexPattern', value: ''},
-        { name: 'passwordHistory', value: 3 },
-        { name: 'forceExpiredPasswordChange', value: 365 }
-    ];
-
-    p.parse = function(policyString) {
-        var policies = [];
-        var re, policyEntry;
-
-        if (!policyString || policyString.length == 0){
-            return policies;
-        }
-
-        var policyArray = policyString.split(" and ");
-
-        for (var i = 0; i < policyArray.length; i ++){
-            var policyToken = policyArray[i];
-            
-            if(policyToken.indexOf('hashAlgorithm') === 0 || policyToken.indexOf('regexPattern') === 0) {
-            	re = /(\w+)\((.*)\)/;
-            	policyEntry = re.exec(policyToken);
-                if (null !== policyEntry) {
-                	policies.push({ name: policyEntry[1], value: policyEntry[2] });
-                }
-            } else {
-            	re = /(\w+)\(*(\d*)\)*/;
-            	policyEntry = re.exec(policyToken);
-                if (null !== policyEntry) {
-                	policies.push({ name: policyEntry[1], value: parseInt(policyEntry[2]) });
-                }
-            }
-        }
-        return policies;
-    };
-
-    p.toString = function(policies) {
-        if (!policies || policies.length == 0) {
-            return "";
-        }
-        var policyString = "";
-
-        for (var i = 0; i < policies.length; i++) {
-            policyString += policies[i].name;
-            if ( policies[i].value ){
-                policyString += '(' + policies[i].value + ')';
-            }
-            policyString += " and ";
-        }
-
-        policyString = policyString.substring(0, policyString.length - 5);
-
-        return policyString;
-    };
-
-    return p;
-});
-
 module.filter('removeSelectedPolicies', function() {
     return function(policies, selectedPolicies) {
         var result = [];
@@ -1329,7 +1245,7 @@ module.filter('removeSelectedPolicies', function() {
             var policy = policies[i];
             var policyAvailable = true;
             for(var j in selectedPolicies) {
-                if(policy.name === selectedPolicies[j].name && policy.name !== 'regexPattern') {
+                if(policy.id === selectedPolicies[j].id && !policy.multipleSupported) {
                     policyAvailable = false;
                 }
             }
diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/password-policy.html b/themes/src/main/resources/theme/base/admin/resources/partials/password-policy.html
index 926ee80..e929cad 100755
--- a/themes/src/main/resources/theme/base/admin/resources/partials/password-policy.html
+++ b/themes/src/main/resources/theme/base/admin/resources/partials/password-policy.html
@@ -7,12 +7,12 @@
         <table class="table table-striped table-bordered">
             <caption class="hidden">{{:: 'table-of-password-policies' | translate}}</caption>
             <thead>
-            <tr ng-show="(allPolicies|removeSelectedPolicies:policy).length > 0">
+            <tr ng-show="(serverInfo.passwordPolicies|removeSelectedPolicies:policy).length > 0">
                 <th colspan="5" class="kc-table-actions">
                     <div class="pull-right">
                         <div>
                             <select class="form-control" ng-model="selectedPolicy"
-                                    ng-options="(p.name|capitalize) for p in (allPolicies|removeSelectedPolicies:policy)"
+                                    ng-options="policy as policy.displayName for policy in (serverInfo.passwordPolicies|removeSelectedPolicies:policy) track by policy.id"
                                     data-ng-change="addPolicy(selectedPolicy); selectedPolicy = null">
                                 <option value="" disabled selected>{{:: 'add-policy.placeholder' | translate}}</option>
                             </select>
@@ -28,10 +28,9 @@
             </thead>
             <tbody>
             <tr ng-repeat="p in policy">
-                <td>{{p.name|capitalize}}</td>
+                <td>{{p.displayName}}</td>
                 <td>
-                    <input class="form-control" ng-model="p.value" ng-show="p.name != 'notUsername' "
-                           placeholder="{{:: 'no-value-assigned.placeholder' | translate}}" min="1" required>
+                    <input type="text" class="form-control" ng-model="p.value" ng-show="p.configType" data-ng-required="!p.configType && !p.defaultValue">
                 </td>
                 <td class="kc-action-cell" ng-click="removePolicy($index)">{{:: 'delete' | translate}}</td>
             </tr>