keycloak-uncached

KEYCLOAK-2333 LDAP/MSAD password policies are not used when

1/16/2017 9:51:16 AM

Changes

Details

diff --git a/federation/ldap/src/main/java/org/keycloak/storage/ldap/idm/query/internal/LDAPQuery.java b/federation/ldap/src/main/java/org/keycloak/storage/ldap/idm/query/internal/LDAPQuery.java
index d86a238..c27b2c6 100644
--- a/federation/ldap/src/main/java/org/keycloak/storage/ldap/idm/query/internal/LDAPQuery.java
+++ b/federation/ldap/src/main/java/org/keycloak/storage/ldap/idm/query/internal/LDAPQuery.java
@@ -152,9 +152,9 @@ public class LDAPQuery {
     public List<LDAPObject> getResultList() {
 
         // Apply mappers now
-        List<ComponentModel> sortedMappers = ldapFedProvider.sortMappersAsc(mappers);
+        List<ComponentModel> sortedMappers = ldapFedProvider.getMapperManager().sortMappersAsc(mappers);
         for (ComponentModel mapperModel : sortedMappers) {
-            LDAPStorageMapper fedMapper = ldapFedProvider.getMapper(mapperModel);
+            LDAPStorageMapper fedMapper = ldapFedProvider.getMapperManager().getMapper(mapperModel);
             fedMapper.beforeLDAPQuery(this);
         }
 
diff --git a/federation/ldap/src/main/java/org/keycloak/storage/ldap/idm/store/IdentityStore.java b/federation/ldap/src/main/java/org/keycloak/storage/ldap/idm/store/IdentityStore.java
index 4b2010b..5a57d28 100644
--- a/federation/ldap/src/main/java/org/keycloak/storage/ldap/idm/store/IdentityStore.java
+++ b/federation/ldap/src/main/java/org/keycloak/storage/ldap/idm/store/IdentityStore.java
@@ -20,6 +20,7 @@ package org.keycloak.storage.ldap.idm.store;
 import org.keycloak.storage.ldap.LDAPConfig;
 import org.keycloak.storage.ldap.idm.model.LDAPObject;
 import org.keycloak.storage.ldap.idm.query.internal.LDAPQuery;
+import org.keycloak.storage.ldap.mappers.LDAPOperationDecorator;
 
 import javax.naming.AuthenticationException;
 import java.util.List;
@@ -92,7 +93,8 @@ public interface IdentityStore {
      *
      * @param user Keycloak user
      * @param password Ldap password
+     * @param passwordUpdateDecorator Callback to be executed before/after password update. Can be null
      */
-    void updatePassword(LDAPObject user, String password);
+    void updatePassword(LDAPObject user, String password, LDAPOperationDecorator passwordUpdateDecorator);
 
 }
diff --git a/federation/ldap/src/main/java/org/keycloak/storage/ldap/idm/store/ldap/LDAPIdentityStore.java b/federation/ldap/src/main/java/org/keycloak/storage/ldap/idm/store/ldap/LDAPIdentityStore.java
index fec84b2..c0e84b0 100644
--- a/federation/ldap/src/main/java/org/keycloak/storage/ldap/idm/store/ldap/LDAPIdentityStore.java
+++ b/federation/ldap/src/main/java/org/keycloak/storage/ldap/idm/store/ldap/LDAPIdentityStore.java
@@ -29,6 +29,7 @@ import org.keycloak.storage.ldap.idm.query.EscapeStrategy;
 import org.keycloak.storage.ldap.idm.query.internal.EqualCondition;
 import org.keycloak.storage.ldap.idm.query.internal.LDAPQuery;
 import org.keycloak.storage.ldap.idm.store.IdentityStore;
+import org.keycloak.storage.ldap.mappers.LDAPOperationDecorator;
 
 import javax.naming.AuthenticationException;
 import javax.naming.NamingEnumeration;
@@ -205,7 +206,7 @@ public class LDAPIdentityStore implements IdentityStore {
     }
 
     @Override
-    public void updatePassword(LDAPObject user, String password) {
+    public void updatePassword(LDAPObject user, String password, LDAPOperationDecorator passwordUpdateDecorator) {
         String userDN = user.getDn().toString();
 
         if (logger.isDebugEnabled()) {
@@ -213,7 +214,7 @@ public class LDAPIdentityStore implements IdentityStore {
         }
 
         if (getConfig().isActiveDirectory()) {
-            updateADPassword(userDN, password);
+            updateADPassword(userDN, password, passwordUpdateDecorator);
         } else {
             ModificationItem[] mods = new ModificationItem[1];
 
@@ -222,7 +223,7 @@ public class LDAPIdentityStore implements IdentityStore {
 
                 mods[0] = new ModificationItem(DirContext.REPLACE_ATTRIBUTE, mod0);
 
-                operationManager.modifyAttribute(userDN, mod0);
+                operationManager.modifyAttributes(userDN, mods, passwordUpdateDecorator);
             } catch (ModelException me) {
                 throw me;
             } catch (Exception e) {
@@ -232,7 +233,7 @@ public class LDAPIdentityStore implements IdentityStore {
     }
 
 
-    private void updateADPassword(String userDN, String password) {
+    private void updateADPassword(String userDN, String password, LDAPOperationDecorator passwordUpdateDecorator) {
         try {
             // Replace the "unicdodePwd" attribute with a new value
             // Password must be both Unicode and a quoted string
@@ -244,7 +245,7 @@ public class LDAPIdentityStore implements IdentityStore {
             List<ModificationItem> modItems = new ArrayList<ModificationItem>();
             modItems.add(new ModificationItem(DirContext.REPLACE_ATTRIBUTE, unicodePwd));
 
-            operationManager.modifyAttributes(userDN, modItems.toArray(new ModificationItem[] {}));
+            operationManager.modifyAttributes(userDN, modItems.toArray(new ModificationItem[] {}), passwordUpdateDecorator);
         } catch (ModelException me) {
             throw me;
         } catch (Exception e) {
diff --git a/federation/ldap/src/main/java/org/keycloak/storage/ldap/idm/store/ldap/LDAPOperationManager.java b/federation/ldap/src/main/java/org/keycloak/storage/ldap/idm/store/ldap/LDAPOperationManager.java
index 2f07254..350b16d 100644
--- a/federation/ldap/src/main/java/org/keycloak/storage/ldap/idm/store/ldap/LDAPOperationManager.java
+++ b/federation/ldap/src/main/java/org/keycloak/storage/ldap/idm/store/ldap/LDAPOperationManager.java
@@ -22,6 +22,7 @@ import org.keycloak.models.LDAPConstants;
 import org.keycloak.models.ModelException;
 import org.keycloak.storage.ldap.LDAPConfig;
 import org.keycloak.storage.ldap.idm.query.internal.LDAPQuery;
+import org.keycloak.storage.ldap.mappers.LDAPOperationDecorator;
 
 import javax.naming.AuthenticationException;
 import javax.naming.Binding;
@@ -81,7 +82,7 @@ public class LDAPOperationManager {
      */
     public void modifyAttribute(String dn, Attribute attribute) {
         ModificationItem[] mods = new ModificationItem[]{new ModificationItem(DirContext.REPLACE_ATTRIBUTE, attribute)};
-        modifyAttributes(dn, mods);
+        modifyAttributes(dn, mods, null);
     }
 
     /**
@@ -101,7 +102,7 @@ public class LDAPOperationManager {
                 modItems.add(modItem);
             }
 
-            modifyAttributes(dn, modItems.toArray(new ModificationItem[] {}));
+            modifyAttributes(dn, modItems.toArray(new ModificationItem[] {}), null);
         } catch (NamingException ne) {
             throw new ModelException("Could not modify attributes on entry from DN [" + dn + "]", ne);
         }
@@ -119,7 +120,7 @@ public class LDAPOperationManager {
      */
     public void removeAttribute(String dn, Attribute attribute) {
         ModificationItem[] mods = new ModificationItem[]{new ModificationItem(DirContext.REMOVE_ATTRIBUTE, attribute)};
-        modifyAttributes(dn, mods);
+        modifyAttributes(dn, mods, null);
     }
 
     /**
@@ -132,7 +133,7 @@ public class LDAPOperationManager {
      */
     public void addAttribute(String dn, Attribute attribute) {
         ModificationItem[] mods = new ModificationItem[]{new ModificationItem(DirContext.ADD_ATTRIBUTE, attribute)};
-        modifyAttributes(dn, mods);
+        modifyAttributes(dn, mods, null);
     }
 
     /**
@@ -379,7 +380,7 @@ public class LDAPOperationManager {
         }
     }
 
-    public void modifyAttributes(final String dn, final ModificationItem[] mods) {
+    public void modifyAttributes(final String dn, final ModificationItem[] mods, LDAPOperationDecorator decorator) {
         try {
             if (logger.isTraceEnabled()) {
                 logger.tracef("Modifying attributes for entry [%s]: [", dn);
@@ -405,7 +406,7 @@ public class LDAPOperationManager {
                     context.modifyAttributes(dn, mods);
                     return null;
                 }
-            });
+            }, decorator);
         } catch (NamingException e) {
             throw new ModelException("Could not modify attribute for DN [" + dn + "]", e);
         }
@@ -546,13 +547,19 @@ public class LDAPOperationManager {
     }
 
     private <R> R execute(LdapOperation<R> operation) throws NamingException {
+        return execute(operation, null);
+    }
+
+    private <R> R execute(LdapOperation<R> operation, LDAPOperationDecorator decorator) throws NamingException {
         LdapContext context = null;
 
         try {
             context = createLdapContext();
+            if (decorator != null) {
+                decorator.beforeLDAPOperation(context, operation);
+            }
+
             return operation.execute(context);
-        } catch (NamingException ne) {
-            throw ne;
         } finally {
             if (context != null) {
                 try {
@@ -564,7 +571,7 @@ public class LDAPOperationManager {
         }
     }
 
-    private interface LdapOperation<R> {
+    public interface LdapOperation<R> {
         R execute(LdapContext context) throws NamingException;
     }
 
diff --git a/federation/ldap/src/main/java/org/keycloak/storage/ldap/LDAPStorageProvider.java b/federation/ldap/src/main/java/org/keycloak/storage/ldap/LDAPStorageProvider.java
index b98d31d..8edb6ca 100755
--- a/federation/ldap/src/main/java/org/keycloak/storage/ldap/LDAPStorageProvider.java
+++ b/federation/ldap/src/main/java/org/keycloak/storage/ldap/LDAPStorageProvider.java
@@ -40,6 +40,7 @@ import org.keycloak.models.UserCredentialModel;
 import org.keycloak.models.UserModel;
 import org.keycloak.models.UserManager;
 import org.keycloak.models.cache.UserCache;
+import org.keycloak.models.credential.PasswordUserCredentialModel;
 import org.keycloak.storage.StorageId;
 import org.keycloak.storage.UserStorageProvider;
 import org.keycloak.storage.ldap.idm.model.LDAPObject;
@@ -49,9 +50,10 @@ import org.keycloak.storage.ldap.idm.query.internal.LDAPQuery;
 import org.keycloak.storage.ldap.idm.query.internal.LDAPQueryConditionsBuilder;
 import org.keycloak.storage.ldap.idm.store.ldap.LDAPIdentityStore;
 import org.keycloak.storage.ldap.kerberos.LDAPProviderKerberosConfig;
-import org.keycloak.storage.ldap.mappers.LDAPMappersComparator;
+import org.keycloak.storage.ldap.mappers.LDAPOperationDecorator;
 import org.keycloak.storage.ldap.mappers.LDAPStorageMapper;
-import org.keycloak.storage.ldap.mappers.PasswordUpdated;
+import org.keycloak.storage.ldap.mappers.LDAPStorageMapperManager;
+import org.keycloak.storage.ldap.mappers.PasswordUpdateCallback;
 import org.keycloak.storage.user.ImportedUserValidation;
 import org.keycloak.storage.user.UserLookupProvider;
 import org.keycloak.storage.user.UserQueryProvider;
@@ -59,7 +61,6 @@ import org.keycloak.storage.user.UserRegistrationProvider;
 
 import javax.naming.AuthenticationException;
 import java.util.ArrayList;
-import java.util.Collection;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
@@ -89,7 +90,8 @@ public class LDAPStorageProvider implements UserStorageProvider,
     protected LDAPIdentityStore ldapIdentityStore;
     protected EditMode editMode;
     protected LDAPProviderKerberosConfig kerberosConfig;
-    protected PasswordUpdated updater;
+    protected PasswordUpdateCallback updater;
+    protected LDAPStorageMapperManager mapperManager;
 
     protected final Set<String> supportedCredentialTypes = new HashSet<>();
 
@@ -100,6 +102,7 @@ public class LDAPStorageProvider implements UserStorageProvider,
         this.ldapIdentityStore = ldapIdentityStore;
         this.kerberosConfig = new LDAPProviderKerberosConfig(model);
         this.editMode = ldapIdentityStore.getConfig().getEditMode();
+        this.mapperManager = new LDAPStorageMapperManager(this);
 
         supportedCredentialTypes.add(UserCredentialModel.PASSWORD);
         if (kerberosConfig.isAllowKerberosAuthentication()) {
@@ -107,7 +110,7 @@ public class LDAPStorageProvider implements UserStorageProvider,
         }
     }
 
-    public void setUpdater(PasswordUpdated updater) {
+    public void setUpdater(PasswordUpdateCallback updater) {
         this.updater = updater;
     }
 
@@ -127,6 +130,10 @@ public class LDAPStorageProvider implements UserStorageProvider,
         return model;
     }
 
+    public LDAPStorageMapperManager getMapperManager() {
+        return mapperManager;
+    }
+
     @Override
     public UserModel validate(RealmModel realm, UserModel local) {
         LDAPObject ldapObject = loadAndValidateUser(realm, local);
@@ -154,9 +161,9 @@ public class LDAPStorageProvider implements UserStorageProvider,
         }
 
         List<ComponentModel> mappers = realm.getComponents(model.getId(), LDAPStorageMapper.class.getName());
-        List<ComponentModel> sortedMappers = sortMappersAsc(mappers);
+        List<ComponentModel> sortedMappers = mapperManager.sortMappersAsc(mappers);
         for (ComponentModel mapperModel : sortedMappers) {
-            LDAPStorageMapper ldapMapper = getMapper(mapperModel);
+            LDAPStorageMapper ldapMapper = mapperManager.getMapper(mapperModel);
             proxied = ldapMapper.proxy(ldapObject, proxied, realm);
         }
 
@@ -299,9 +306,9 @@ public class LDAPStorageProvider implements UserStorageProvider,
     @Override
     public List<UserModel> getGroupMembers(RealmModel realm, GroupModel group, int firstResult, int maxResults) {
         List<ComponentModel> mappers = realm.getComponents(model.getId(), LDAPStorageMapper.class.getName());
-        List<ComponentModel> sortedMappers = sortMappersAsc(mappers);
+        List<ComponentModel> sortedMappers = mapperManager.sortMappersAsc(mappers);
         for (ComponentModel mapperModel : sortedMappers) {
-            LDAPStorageMapper ldapMapper = getMapper(mapperModel);
+            LDAPStorageMapper ldapMapper = mapperManager.getMapper(mapperModel);
             List<UserModel> users = ldapMapper.getGroupMembers(realm, group, firstResult, maxResults);
 
             // Sufficient for now
@@ -410,12 +417,12 @@ public class LDAPStorageProvider implements UserStorageProvider,
         imported.setEnabled(true);
 
         List<ComponentModel> mappers = realm.getComponents(model.getId(), LDAPStorageMapper.class.getName());
-        List<ComponentModel> sortedMappers = sortMappersDesc(mappers);
+        List<ComponentModel> sortedMappers = mapperManager.sortMappersDesc(mappers);
         for (ComponentModel mapperModel : sortedMappers) {
             if (logger.isTraceEnabled()) {
                 logger.tracef("Using mapper %s during import user from LDAP", mapperModel);
             }
-            LDAPStorageMapper ldapMapper = getMapper(mapperModel);
+            LDAPStorageMapper ldapMapper = mapperManager.getMapper(mapperModel);
             ldapMapper.onImportUserFromLDAP(ldapUser, imported, realm, true);
         }
 
@@ -492,12 +499,12 @@ public class LDAPStorageProvider implements UserStorageProvider,
             } catch (AuthenticationException ae) {
                 boolean processed = false;
                 List<ComponentModel> mappers = realm.getComponents(model.getId(), LDAPStorageMapper.class.getName());
-                List<ComponentModel> sortedMappers = sortMappersDesc(mappers);
+                List<ComponentModel> sortedMappers = mapperManager.sortMappersDesc(mappers);
                 for (ComponentModel mapperModel : sortedMappers) {
                     if (logger.isTraceEnabled()) {
                         logger.tracef("Using mapper %s during import user from LDAP", mapperModel);
                     }
-                    LDAPStorageMapper ldapMapper = getMapper(mapperModel);
+                    LDAPStorageMapper ldapMapper = mapperManager.getMapper(mapperModel);
                     processed = processed || ldapMapper.onAuthenticationFailure(ldapUser, user, ae, realm);
                 }
                 return processed;
@@ -508,23 +515,29 @@ public class LDAPStorageProvider implements UserStorageProvider,
 
     @Override
     public boolean updateCredential(RealmModel realm, UserModel user, CredentialInput input) {
-        if (!CredentialModel.PASSWORD.equals(input.getType()) || ! (input instanceof UserCredentialModel)) return false;
+        if (!CredentialModel.PASSWORD.equals(input.getType()) || ! (input instanceof PasswordUserCredentialModel)) return false;
         if (editMode == UserStorageProvider.EditMode.READ_ONLY) {
             throw new ModelReadOnlyException("Federated storage is not writable");
 
         } else if (editMode == UserStorageProvider.EditMode.WRITABLE) {
             LDAPIdentityStore ldapIdentityStore = getLdapIdentityStore();
-            UserCredentialModel cred = (UserCredentialModel)input;
+            PasswordUserCredentialModel cred = (PasswordUserCredentialModel)input;
             String password = cred.getValue();
             LDAPObject ldapUser = loadAndValidateUser(realm, user);
 
             try {
-                ldapIdentityStore.updatePassword(ldapUser, password);
-                if (updater != null) updater.passwordUpdated(user, ldapUser, input);
+                LDAPOperationDecorator operationDecorator = null;
+                if (updater != null) {
+                    operationDecorator = updater.beforePasswordUpdate(user, ldapUser, cred);
+                }
+
+                ldapIdentityStore.updatePassword(ldapUser, password, operationDecorator);
+
+                if (updater != null) updater.passwordUpdated(user, ldapUser, cred);
                 return true;
             } catch (ModelException me) {
                 if (updater != null) {
-                    updater.passwordUpdateFailed(user, ldapUser, input, me);
+                    updater.passwordUpdateFailed(user, ldapUser, cred, me);
                     return false;
                 } else {
                     throw me;
@@ -667,23 +680,5 @@ public class LDAPStorageProvider implements UserStorageProvider,
         return ldapUser;
     }
 
-    public LDAPStorageMapper getMapper(ComponentModel mapperModel) {
-        LDAPStorageMapper ldapMapper = getSession().getProvider(LDAPStorageMapper.class, mapperModel);
-        if (ldapMapper == null) {
-            throw new ModelException("Can't find mapper type with ID: " + mapperModel.getProviderId());
-        }
-
-        return ldapMapper;
-    }
-
-
-    public List<ComponentModel> sortMappersAsc(Collection<ComponentModel> mappers) {
-        return LDAPMappersComparator.sortAsc(getLdapIdentityStore().getConfig(), mappers);
-    }
-
-    protected List<ComponentModel> sortMappersDesc(Collection<ComponentModel> mappers) {
-        return LDAPMappersComparator.sortDesc(getLdapIdentityStore().getConfig(), mappers);
-    }
-
 
 }
diff --git a/federation/ldap/src/main/java/org/keycloak/storage/ldap/LDAPStorageProviderFactory.java b/federation/ldap/src/main/java/org/keycloak/storage/ldap/LDAPStorageProviderFactory.java
index 003d7bf..cd65604 100755
--- a/federation/ldap/src/main/java/org/keycloak/storage/ldap/LDAPStorageProviderFactory.java
+++ b/federation/ldap/src/main/java/org/keycloak/storage/ldap/LDAPStorageProviderFactory.java
@@ -521,9 +521,9 @@ public class LDAPStorageProviderFactory implements UserStorageProviderFactory<LD
 
                                 // Update keycloak user
                                 List<ComponentModel> federationMappers = currentRealm.getComponents(fedModel.getId(), LDAPStorageMapper.class.getName());
-                                List<ComponentModel> sortedMappers = ldapFedProvider.sortMappersDesc(federationMappers);
+                                List<ComponentModel> sortedMappers = ldapFedProvider.getMapperManager().sortMappersDesc(federationMappers);
                                 for (ComponentModel mapperModel : sortedMappers) {
-                                    LDAPStorageMapper ldapMapper = ldapFedProvider.getMapper(mapperModel);
+                                    LDAPStorageMapper ldapMapper = ldapFedProvider.getMapperManager().getMapper(mapperModel);
                                     ldapMapper.onImportUserFromLDAP(ldapUser, currentUser, currentRealm, false);
                                 }
 
diff --git a/federation/ldap/src/main/java/org/keycloak/storage/ldap/LDAPUtils.java b/federation/ldap/src/main/java/org/keycloak/storage/ldap/LDAPUtils.java
index 87754f5..29f561a 100755
--- a/federation/ldap/src/main/java/org/keycloak/storage/ldap/LDAPUtils.java
+++ b/federation/ldap/src/main/java/org/keycloak/storage/ldap/LDAPUtils.java
@@ -61,9 +61,9 @@ public class LDAPUtils {
         ldapUser.setObjectClasses(ldapConfig.getUserObjectClasses());
 
         List<ComponentModel> federationMappers = realm.getComponents(ldapProvider.getModel().getId(), LDAPStorageMapper.class.getName());
-        List<ComponentModel> sortedMappers = ldapProvider.sortMappersAsc(federationMappers);
+        List<ComponentModel> sortedMappers = ldapProvider.getMapperManager().sortMappersAsc(federationMappers);
         for (ComponentModel mapperModel : sortedMappers) {
-            LDAPStorageMapper ldapMapper = ldapProvider.getMapper(mapperModel);
+            LDAPStorageMapper ldapMapper = ldapProvider.getMapperManager().getMapper(mapperModel);
             ldapMapper.onRegisterUserToLDAP(ldapUser, user, realm);
         }
 
diff --git a/federation/ldap/src/main/java/org/keycloak/storage/ldap/mappers/FullNameLDAPStorageMapperFactory.java b/federation/ldap/src/main/java/org/keycloak/storage/ldap/mappers/FullNameLDAPStorageMapperFactory.java
index 12fc079..4b37784 100755
--- a/federation/ldap/src/main/java/org/keycloak/storage/ldap/mappers/FullNameLDAPStorageMapperFactory.java
+++ b/federation/ldap/src/main/java/org/keycloak/storage/ldap/mappers/FullNameLDAPStorageMapperFactory.java
@@ -85,6 +85,11 @@ public class FullNameLDAPStorageMapperFactory extends AbstractLDAPStorageMapperF
     }
 
     @Override
+    public List<ProviderConfigProperty> getConfigProperties(RealmModel realm, ComponentModel parent) {
+        return getConfigProps(parent);
+    }
+
+    @Override
     public String getId() {
         return PROVIDER_ID;
     }
diff --git a/federation/ldap/src/main/java/org/keycloak/storage/ldap/mappers/LDAPOperationDecorator.java b/federation/ldap/src/main/java/org/keycloak/storage/ldap/mappers/LDAPOperationDecorator.java
new file mode 100644
index 0000000..4815206
--- /dev/null
+++ b/federation/ldap/src/main/java/org/keycloak/storage/ldap/mappers/LDAPOperationDecorator.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.storage.ldap.mappers;
+
+import javax.naming.NamingException;
+import javax.naming.ldap.LdapContext;
+
+import org.keycloak.storage.ldap.idm.store.ldap.LDAPOperationManager;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public interface LDAPOperationDecorator {
+
+    void beforeLDAPOperation(LdapContext ldapContext, LDAPOperationManager.LdapOperation ldapOperation) throws NamingException;
+
+}
diff --git a/federation/ldap/src/main/java/org/keycloak/storage/ldap/mappers/LDAPStorageMapperManager.java b/federation/ldap/src/main/java/org/keycloak/storage/ldap/mappers/LDAPStorageMapperManager.java
new file mode 100644
index 0000000..b8e9dc4
--- /dev/null
+++ b/federation/ldap/src/main/java/org/keycloak/storage/ldap/mappers/LDAPStorageMapperManager.java
@@ -0,0 +1,59 @@
+/*
+ * 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.storage.ldap.mappers;
+
+import java.util.Collection;
+import java.util.List;
+
+import org.keycloak.component.ComponentModel;
+import org.keycloak.models.ModelException;
+import org.keycloak.storage.ldap.LDAPStorageProvider;
+
+/**
+ * TODO: LDAPStorageMapper should be divided into more interfaces and let the LDAPStorageMapperManager to check which operation (feature) is supported by which mapper implementation
+ *
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class LDAPStorageMapperManager {
+
+    private final LDAPStorageProvider ldapProvider;
+
+    public LDAPStorageMapperManager(LDAPStorageProvider ldapProvider) {
+        this.ldapProvider = ldapProvider;
+    }
+
+    public LDAPStorageMapper getMapper(ComponentModel mapperModel) {
+        LDAPStorageMapper ldapMapper = ldapProvider.getSession().getProvider(LDAPStorageMapper.class, mapperModel);
+        if (ldapMapper == null) {
+            throw new ModelException("Can't find mapper type with ID: " + mapperModel.getProviderId());
+        }
+
+        return ldapMapper;
+    }
+
+
+    public List<ComponentModel> sortMappersAsc(Collection<ComponentModel> mappers) {
+        return LDAPMappersComparator.sortAsc(ldapProvider.getLdapIdentityStore().getConfig(), mappers);
+    }
+
+    public List<ComponentModel> sortMappersDesc(Collection<ComponentModel> mappers) {
+        return LDAPMappersComparator.sortDesc(ldapProvider.getLdapIdentityStore().getConfig(), mappers);
+    }
+
+
+}
diff --git a/federation/ldap/src/main/java/org/keycloak/storage/ldap/mappers/msad/LDAPServerPolicyHintsDecorator.java b/federation/ldap/src/main/java/org/keycloak/storage/ldap/mappers/msad/LDAPServerPolicyHintsDecorator.java
new file mode 100644
index 0000000..97a8f43
--- /dev/null
+++ b/federation/ldap/src/main/java/org/keycloak/storage/ldap/mappers/msad/LDAPServerPolicyHintsDecorator.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.storage.ldap.mappers.msad;
+
+import javax.naming.NamingException;
+import javax.naming.ldap.BasicControl;
+import javax.naming.ldap.LdapContext;
+
+import org.jboss.logging.Logger;
+import org.keycloak.storage.ldap.idm.store.ldap.LDAPOperationManager;
+import org.keycloak.storage.ldap.mappers.LDAPOperationDecorator;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class LDAPServerPolicyHintsDecorator implements LDAPOperationDecorator {
+
+    private static final Logger logger = Logger.getLogger(LDAPServerPolicyHintsDecorator.class);
+
+    public static final String LDAP_SERVER_POLICY_HINTS_OID = "1.2.840.113556.1.4.2239";
+    public static final String LDAP_SERVER_POLICY_HINTS_DEPRECATED_OID = "1.2.840.113556.1.4.2066";
+
+    @Override
+    public void beforeLDAPOperation(LdapContext ldapContext, LDAPOperationManager.LdapOperation ldapOperation) throws NamingException {
+        logger.debug("Applying LDAP_PASSWORD_POLICY_HINTS_OID before update password");
+
+        final byte[] controlData = {48, (byte) 132, 0, 0, 0, 3, 2, 1, 1};
+
+        // Rather using deprecated OID as it works from MSAD 2008-R2 when the newer works from MSAD 2012
+        BasicControl control = new BasicControl(LDAP_SERVER_POLICY_HINTS_DEPRECATED_OID, true, controlData);
+        BasicControl[] controls = new BasicControl[] { control };
+        ldapContext.setRequestControls(controls);
+    }
+}
diff --git a/federation/ldap/src/main/java/org/keycloak/storage/ldap/mappers/msad/MSADUserAccountControlStorageMapper.java b/federation/ldap/src/main/java/org/keycloak/storage/ldap/mappers/msad/MSADUserAccountControlStorageMapper.java
index 57164e6..4b926bb 100644
--- a/federation/ldap/src/main/java/org/keycloak/storage/ldap/mappers/msad/MSADUserAccountControlStorageMapper.java
+++ b/federation/ldap/src/main/java/org/keycloak/storage/ldap/mappers/msad/MSADUserAccountControlStorageMapper.java
@@ -24,13 +24,15 @@ import org.keycloak.models.LDAPConstants;
 import org.keycloak.models.ModelException;
 import org.keycloak.models.RealmModel;
 import org.keycloak.models.UserModel;
+import org.keycloak.models.credential.PasswordUserCredentialModel;
 import org.keycloak.models.utils.UserModelDelegate;
 import org.keycloak.storage.UserStorageProvider;
 import org.keycloak.storage.ldap.LDAPStorageProvider;
 import org.keycloak.storage.ldap.idm.model.LDAPObject;
 import org.keycloak.storage.ldap.idm.query.internal.LDAPQuery;
 import org.keycloak.storage.ldap.mappers.AbstractLDAPStorageMapper;
-import org.keycloak.storage.ldap.mappers.PasswordUpdated;
+import org.keycloak.storage.ldap.mappers.LDAPOperationDecorator;
+import org.keycloak.storage.ldap.mappers.PasswordUpdateCallback;
 
 import javax.naming.AuthenticationException;
 import java.util.HashSet;
@@ -44,12 +46,14 @@ import java.util.regex.Pattern;
  *
  * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
  */
-public class MSADUserAccountControlStorageMapper extends AbstractLDAPStorageMapper implements PasswordUpdated {
+public class MSADUserAccountControlStorageMapper extends AbstractLDAPStorageMapper implements PasswordUpdateCallback {
+
+    public static final String LDAP_PASSWORD_POLICY_HINTS_ENABLED = "ldap.password.policy.hints.enabled";
 
     private static final Logger logger = Logger.getLogger(MSADUserAccountControlStorageMapper.class);
 
     private static final Pattern AUTH_EXCEPTION_REGEX = Pattern.compile(".*AcceptSecurityContext error, data ([0-9a-f]*), v.*");
-    private static final Pattern AUTH_INVALID_NEW_PASSWORD = Pattern.compile(".*error code ([0-9a-f]+) .*WILL_NOT_PERFORM.*");
+    private static final Pattern AUTH_INVALID_NEW_PASSWORD = Pattern.compile(".*ERROR CODE ([0-9A-F]+) - ([0-9A-F]+): .*WILL_NOT_PERFORM.*");
 
     public MSADUserAccountControlStorageMapper(ComponentModel mapperModel, LDAPStorageProvider ldapProvider) {
         super(mapperModel, ldapProvider);
@@ -70,7 +74,18 @@ public class MSADUserAccountControlStorageMapper extends AbstractLDAPStorageMapp
     }
 
     @Override
-    public void passwordUpdated(UserModel user, LDAPObject ldapUser, CredentialInput input) {
+    public LDAPOperationDecorator beforePasswordUpdate(UserModel user, LDAPObject ldapUser, PasswordUserCredentialModel password) {
+        // Not apply policies if password is reset by admin (not by user himself)
+        if (password.isAdminRequest()) {
+            return null;
+        }
+
+        boolean applyDecorator = mapperModel.get(LDAP_PASSWORD_POLICY_HINTS_ENABLED, false);
+        return applyDecorator ? new LDAPServerPolicyHintsDecorator() : null;
+    }
+
+    @Override
+    public void passwordUpdated(UserModel user, LDAPObject ldapUser, PasswordUserCredentialModel password) {
         logger.debugf("Going to update userAccountControl for ldap user '%s' after successful password update", ldapUser.getDn().toString());
 
         // Normally it's read-only
@@ -90,7 +105,7 @@ public class MSADUserAccountControlStorageMapper extends AbstractLDAPStorageMapp
     }
 
     @Override
-    public void passwordUpdateFailed(UserModel user, LDAPObject ldapUser, CredentialInput input, ModelException exception) {
+    public void passwordUpdateFailed(UserModel user, LDAPObject ldapUser, PasswordUserCredentialModel password, ModelException exception) {
         throw processFailedPasswordUpdateException(exception);
     }
 
@@ -148,12 +163,17 @@ public class MSADUserAccountControlStorageMapper extends AbstractLDAPStorageMapp
         }
 
         String exceptionMessage = e.getCause().getMessage().replace('\n', ' ');
+        logger.debugf("Failed to update password in Active Directory. Exception message: %s", exceptionMessage);
+        exceptionMessage = exceptionMessage.toUpperCase();
+
         Matcher m = AUTH_INVALID_NEW_PASSWORD.matcher(exceptionMessage);
         if (m.matches()) {
             String errorCode = m.group(1);
-            if (errorCode.equals("53")) {
-                ModelException me = new ModelException("invalidPasswordRegexPatternMessage", e);
-                me.setParameters(new Object[]{"passwordConstraintViolation"});
+            String errorCode2 = m.group(2);
+
+            // 52D corresponds to ERROR_PASSWORD_RESTRICTION. See https://msdn.microsoft.com/en-us/library/windows/desktop/ms681385(v=vs.85).aspx
+            if ((errorCode.equals("53")) && errorCode2.endsWith("52D")) {
+                ModelException me = new ModelException("invalidPasswordGenericMessage", e);
                 return me;
             }
         }
diff --git a/federation/ldap/src/main/java/org/keycloak/storage/ldap/mappers/msad/MSADUserAccountControlStorageMapperFactory.java b/federation/ldap/src/main/java/org/keycloak/storage/ldap/mappers/msad/MSADUserAccountControlStorageMapperFactory.java
index 9dc1e5a..0eac7ae 100644
--- a/federation/ldap/src/main/java/org/keycloak/storage/ldap/mappers/msad/MSADUserAccountControlStorageMapperFactory.java
+++ b/federation/ldap/src/main/java/org/keycloak/storage/ldap/mappers/msad/MSADUserAccountControlStorageMapperFactory.java
@@ -21,9 +21,13 @@ import org.keycloak.component.ComponentModel;
 import org.keycloak.models.LDAPConstants;
 import org.keycloak.models.RealmModel;
 import org.keycloak.provider.ProviderConfigProperty;
+import org.keycloak.provider.ProviderConfigurationBuilder;
+import org.keycloak.storage.UserStorageProvider;
+import org.keycloak.storage.ldap.LDAPConfig;
 import org.keycloak.storage.ldap.LDAPStorageProvider;
 import org.keycloak.storage.ldap.mappers.AbstractLDAPStorageMapper;
 import org.keycloak.storage.ldap.mappers.AbstractLDAPStorageMapperFactory;
+import org.keycloak.storage.ldap.mappers.FullNameLDAPStorageMapper;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -34,9 +38,23 @@ import java.util.List;
 public class MSADUserAccountControlStorageMapperFactory extends AbstractLDAPStorageMapperFactory {
 
     public static final String PROVIDER_ID = LDAPConstants.MSAD_USER_ACCOUNT_CONTROL_MAPPER;
-    protected static final List<ProviderConfigProperty> configProperties = new ArrayList<ProviderConfigProperty>();
+    protected static final List<ProviderConfigProperty> configProperties;
 
     static {
+        configProperties = getConfigProps(null);
+    }
+
+    private static List<ProviderConfigProperty> getConfigProps(ComponentModel parent) {
+        return ProviderConfigurationBuilder.create()
+                .property().name(MSADUserAccountControlStorageMapper.LDAP_PASSWORD_POLICY_HINTS_ENABLED)
+                .label("Password Policy Hints Enabled")
+                .helpText("Applicable just for writable MSAD. If on, then updating password in MSAD will use LDAP_SERVER_POLICY_HINTS_OID " +
+                        "extension, which means that advanced MSAD password policies like 'password history' or 'minimal password age' will be applied. This extension works just for MSAD 2008 R2 or newer.")
+                .type(ProviderConfigProperty.BOOLEAN_TYPE)
+                .defaultValue("false")
+                .add()
+                .build();
+
     }
 
     @Override
@@ -51,6 +69,11 @@ public class MSADUserAccountControlStorageMapperFactory extends AbstractLDAPStor
     }
 
     @Override
+    public List<ProviderConfigProperty> getConfigProperties(RealmModel realm, ComponentModel parent) {
+        return getConfigProps(parent);
+    }
+
+    @Override
     public String getId() {
         return PROVIDER_ID;
     }
diff --git a/federation/ldap/src/main/java/org/keycloak/storage/ldap/mappers/msadlds/MSADLDSUserAccountControlStorageMapper.java b/federation/ldap/src/main/java/org/keycloak/storage/ldap/mappers/msadlds/MSADLDSUserAccountControlStorageMapper.java
index 517a3c0..f10ac55 100644
--- a/federation/ldap/src/main/java/org/keycloak/storage/ldap/mappers/msadlds/MSADLDSUserAccountControlStorageMapper.java
+++ b/federation/ldap/src/main/java/org/keycloak/storage/ldap/mappers/msadlds/MSADLDSUserAccountControlStorageMapper.java
@@ -24,13 +24,15 @@ import org.keycloak.models.LDAPConstants;
 import org.keycloak.models.ModelException;
 import org.keycloak.models.RealmModel;
 import org.keycloak.models.UserModel;
+import org.keycloak.models.credential.PasswordUserCredentialModel;
 import org.keycloak.models.utils.UserModelDelegate;
 import org.keycloak.storage.UserStorageProvider;
 import org.keycloak.storage.ldap.LDAPStorageProvider;
 import org.keycloak.storage.ldap.idm.model.LDAPObject;
 import org.keycloak.storage.ldap.idm.query.internal.LDAPQuery;
 import org.keycloak.storage.ldap.mappers.AbstractLDAPStorageMapper;
-import org.keycloak.storage.ldap.mappers.PasswordUpdated;
+import org.keycloak.storage.ldap.mappers.LDAPOperationDecorator;
+import org.keycloak.storage.ldap.mappers.PasswordUpdateCallback;
 
 import javax.naming.AuthenticationException;
 import java.util.HashSet;
@@ -45,7 +47,7 @@ import java.util.regex.Pattern;
  * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
  * @author <a href="mailto:slawomir@dabek.name">Slawomir Dabek</a>
  */
-public class MSADLDSUserAccountControlStorageMapper extends AbstractLDAPStorageMapper implements PasswordUpdated {
+public class MSADLDSUserAccountControlStorageMapper extends AbstractLDAPStorageMapper implements PasswordUpdateCallback {
 
     private static final Logger logger = Logger.getLogger(MSADLDSUserAccountControlStorageMapper.class);
 
@@ -71,7 +73,12 @@ public class MSADLDSUserAccountControlStorageMapper extends AbstractLDAPStorageM
     }
 
     @Override
-    public void passwordUpdated(UserModel user, LDAPObject ldapUser, CredentialInput input) {
+    public LDAPOperationDecorator beforePasswordUpdate(UserModel user, LDAPObject ldapUser, PasswordUserCredentialModel password) {
+        return null; // Not supported for now. Not sure if LDAP_SERVER_POLICY_HINTS_OID works in MSAD LDS
+    }
+
+    @Override
+    public void passwordUpdated(UserModel user, LDAPObject ldapUser, PasswordUserCredentialModel password) {
         logger.debugf("Going to update pwdLastSet for ldap user '%s' after successful password update", ldapUser.getDn().toString());
 
         // Normally it's read-only
@@ -89,7 +96,7 @@ public class MSADLDSUserAccountControlStorageMapper extends AbstractLDAPStorageM
     }
 
     @Override
-    public void passwordUpdateFailed(UserModel user, LDAPObject ldapUser, CredentialInput input, ModelException exception) {
+    public void passwordUpdateFailed(UserModel user, LDAPObject ldapUser, PasswordUserCredentialModel password, ModelException exception) {
         throw processFailedPasswordUpdateException(exception);
     }
 
diff --git a/server-spi/src/main/java/org/keycloak/models/credential/PasswordUserCredentialModel.java b/server-spi/src/main/java/org/keycloak/models/credential/PasswordUserCredentialModel.java
new file mode 100644
index 0000000..a688ea3
--- /dev/null
+++ b/server-spi/src/main/java/org/keycloak/models/credential/PasswordUserCredentialModel.java
@@ -0,0 +1,38 @@
+/*
+ * 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.models.credential;
+
+import org.keycloak.models.UserCredentialModel;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class PasswordUserCredentialModel extends UserCredentialModel {
+
+    // True if we have password-update request triggered by admin, not by user himself
+    private static final String ADMIN_REQUEST = "adminRequest";
+
+    public boolean isAdminRequest() {
+        Boolean b = (Boolean) this.notes.get(ADMIN_REQUEST);
+        return b!=null && b;
+    }
+
+    public void setAdminRequest(boolean adminRequest) {
+        this.notes.put(ADMIN_REQUEST, adminRequest);
+    }
+}
diff --git a/server-spi/src/main/java/org/keycloak/models/UserCredentialModel.java b/server-spi/src/main/java/org/keycloak/models/UserCredentialModel.java
index 4be355d..9b1784c 100755
--- a/server-spi/src/main/java/org/keycloak/models/UserCredentialModel.java
+++ b/server-spi/src/main/java/org/keycloak/models/UserCredentialModel.java
@@ -19,7 +19,10 @@ package org.keycloak.models;
 
 import org.keycloak.credential.CredentialInput;
 import org.keycloak.credential.CredentialModel;
+import org.keycloak.models.credential.PasswordUserCredentialModel;
 
+import java.util.HashMap;
+import java.util.Map;
 import java.util.UUID;
 
 /**
@@ -43,15 +46,24 @@ public class UserCredentialModel implements CredentialInput {
     protected String device;
     protected String algorithm;
 
+    // Additional context informations
+    protected Map<String, Object> notes = new HashMap<>();
+
     public UserCredentialModel() {
     }
 
-    public static UserCredentialModel password(String password) {
-        UserCredentialModel model = new UserCredentialModel();
+    public static PasswordUserCredentialModel password(String password) {
+        return password(password, false);
+    }
+
+    public static PasswordUserCredentialModel password(String password, boolean adminRequest) {
+        PasswordUserCredentialModel model = new PasswordUserCredentialModel();
         model.setType(PASSWORD);
         model.setValue(password);
+        model.setAdminRequest(adminRequest);
         return model;
     }
+
     public static UserCredentialModel passwordToken(String passwordToken) {
         UserCredentialModel model = new UserCredentialModel();
         model.setType(PASSWORD_TOKEN);
@@ -136,4 +148,16 @@ public class UserCredentialModel implements CredentialInput {
     public void setAlgorithm(String algorithm) {
         this.algorithm = algorithm;
     }
+
+    public void setNote(String key, String value) {
+        this.notes.put(key, value);
+    }
+
+    public void removeNote(String key) {
+        this.notes.remove(key);
+    }
+
+    public Object getNote(String key) {
+        return this.notes.get(key);
+    }
 }
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 0094f0a..d2851b2 100755
--- a/services/src/main/java/org/keycloak/authentication/forms/RegistrationPassword.java
+++ b/services/src/main/java/org/keycloak/authentication/forms/RegistrationPassword.java
@@ -96,7 +96,7 @@ public class RegistrationPassword implements FormAction, FormActionFactory {
         credentials.setValue(password);
         UserModel user = context.getUser();
         try {
-            context.getSession().userCredentialManager().updateCredential(context.getRealm(), user, UserCredentialModel.password(formData.getFirst("password")));
+            context.getSession().userCredentialManager().updateCredential(context.getRealm(), user, UserCredentialModel.password(formData.getFirst("password"), false));
         } catch (Exception me) {
             user.addRequiredAction(UserModel.RequiredAction.UPDATE_PASSWORD);
         }
diff --git a/services/src/main/java/org/keycloak/authentication/requiredactions/UpdatePassword.java b/services/src/main/java/org/keycloak/authentication/requiredactions/UpdatePassword.java
index a2c43f7..aa5bf25 100755
--- a/services/src/main/java/org/keycloak/authentication/requiredactions/UpdatePassword.java
+++ b/services/src/main/java/org/keycloak/authentication/requiredactions/UpdatePassword.java
@@ -108,7 +108,7 @@ public class UpdatePassword implements RequiredActionProvider, RequiredActionFac
         }
 
         try {
-            context.getSession().userCredentialManager().updateCredential(context.getRealm(), context.getUser(), UserCredentialModel.password(passwordNew));
+            context.getSession().userCredentialManager().updateCredential(context.getRealm(), context.getUser(), UserCredentialModel.password(passwordNew, false));
             context.success();
         } catch (ModelException me) {
             errorEvent.detail(Details.REASON, me.getMessage()).error(Errors.PASSWORD_REJECTED);
diff --git a/services/src/main/java/org/keycloak/services/resources/AccountService.java b/services/src/main/java/org/keycloak/services/resources/AccountService.java
index cb41012..d754249 100755
--- a/services/src/main/java/org/keycloak/services/resources/AccountService.java
+++ b/services/src/main/java/org/keycloak/services/resources/AccountService.java
@@ -650,7 +650,7 @@ public class AccountService extends AbstractSecuredLocalService {
         }
 
         try {
-            session.userCredentialManager().updateCredential(realm, user, UserCredentialModel.password(passwordNew));
+            session.userCredentialManager().updateCredential(realm, user, UserCredentialModel.password(passwordNew, false));
         } catch (ModelReadOnlyException mre) {
             setReferrerOnPage();
             errorEvent.error(Errors.NOT_ALLOWED);
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/UsersResource.java b/services/src/main/java/org/keycloak/services/resources/admin/UsersResource.java
index 83265cb..6e89650 100755
--- a/services/src/main/java/org/keycloak/services/resources/admin/UsersResource.java
+++ b/services/src/main/java/org/keycloak/services/resources/admin/UsersResource.java
@@ -49,6 +49,7 @@ import org.keycloak.models.UserCredentialModel;
 import org.keycloak.models.UserLoginFailureModel;
 import org.keycloak.models.UserModel;
 import org.keycloak.models.UserSessionModel;
+import org.keycloak.models.credential.PasswordUserCredentialModel;
 import org.keycloak.models.utils.ModelToRepresentation;
 import org.keycloak.models.utils.RepresentationToModel;
 import org.keycloak.protocol.oidc.OIDCLoginProtocol;
@@ -776,7 +777,7 @@ public class UsersResource {
             throw new BadRequestException("Empty password not allowed");
         }
 
-        UserCredentialModel cred = RepresentationToModel.convertCredential(pass);
+        UserCredentialModel cred = UserCredentialModel.password(pass.getValue(), true);
         try {
             session.userCredentialManager().updateCredential(realm, user, cred);
         } catch (IllegalStateException ise) {
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/storage/ldap/LDAPProvidersIntegrationTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/storage/ldap/LDAPProvidersIntegrationTest.java
index fdb73d7..4ea8ed7 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/storage/ldap/LDAPProvidersIntegrationTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/storage/ldap/LDAPProvidersIntegrationTest.java
@@ -715,7 +715,7 @@ public class LDAPProvidersIntegrationTest {
 
             }
             try {
-                UserCredentialModel cred = UserCredentialModel.password("PoopyPoop1");
+                UserCredentialModel cred = UserCredentialModel.password("PoopyPoop1", true);
                 session.userCredentialManager().updateCredential(appRealm, user, cred);
                 Assert.fail("should fail");
             } catch (ModelReadOnlyException e) {
@@ -856,7 +856,7 @@ public class LDAPProvidersIntegrationTest {
             Assert.assertNotNull(user.getFederationLink());
             Assert.assertEquals(user.getFederationLink(), ldapModel.getId());
 
-            UserCredentialModel cred = UserCredentialModel.password("Candycand1");
+            UserCredentialModel cred = UserCredentialModel.password("Candycand1", true);
             session.userCredentialManager().updateCredential(appRealm, user, cred);
             CredentialModel userCredentialValueModel = session.userCredentialManager().getStoredCredentialsByType(appRealm, user, CredentialModel.PASSWORD).get(0);
             Assert.assertEquals(UserCredentialModel.PASSWORD, userCredentialValueModel.getType());
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/storage/ldap/LDAPTestUtils.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/storage/ldap/LDAPTestUtils.java
index 8338c48..941d2e9 100644
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/storage/ldap/LDAPTestUtils.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/storage/ldap/LDAPTestUtils.java
@@ -128,7 +128,7 @@ public class LDAPTestUtils {
     }
 
     public static void updateLDAPPassword(LDAPStorageProvider ldapProvider, LDAPObject ldapUser, String password) {
-        ldapProvider.getLdapIdentityStore().updatePassword(ldapUser, password);
+        ldapProvider.getLdapIdentityStore().updatePassword(ldapUser, password, null);
 
         // Enable MSAD user through userAccountControls
         if (ldapProvider.getLdapIdentityStore().getConfig().isActiveDirectory()) {
diff --git a/themes/src/main/resources/theme/base/account/messages/messages_en.properties b/themes/src/main/resources/theme/base/account/messages/messages_en.properties
index 1f42008..90b10de 100755
--- a/themes/src/main/resources/theme/base/account/messages/messages_en.properties
+++ b/themes/src/main/resources/theme/base/account/messages/messages_en.properties
@@ -150,6 +150,7 @@ invalidPasswordMinSpecialCharsMessage=Invalid password: must contain at least {0
 invalidPasswordNotUsernameMessage=Invalid password: must not be equal to the username.
 invalidPasswordRegexPatternMessage=Invalid password: fails to match regex pattern(s).
 invalidPasswordHistoryMessage=Invalid password: must not be equal to any of last {0} passwords.
+invalidPasswordGenericMessage=Invalid password: new password doesn''t match password policies.
 
 locale_ca=Catal\u00E0
 locale_de=Deutsch
diff --git a/themes/src/main/resources/theme/base/admin/messages/messages_en.properties b/themes/src/main/resources/theme/base/admin/messages/messages_en.properties
index e734c06..82db91e 100644
--- a/themes/src/main/resources/theme/base/admin/messages/messages_en.properties
+++ b/themes/src/main/resources/theme/base/admin/messages/messages_en.properties
@@ -6,6 +6,7 @@ invalidPasswordMinSpecialCharsMessage=Invalid password: must contain at least {0
 invalidPasswordNotUsernameMessage=Invalid password: must not be equal to the username.
 invalidPasswordRegexPatternMessage=Invalid password: fails to match regex pattern(s).
 invalidPasswordHistoryMessage=Invalid password: must not be equal to any of last {0} passwords.
+invalidPasswordGenericMessage=Invalid password: new password doesn''t match password policies.
 
 ldapErrorInvalidCustomFilter=Custom configured LDAP filter does not start with "(" or does not end with ")".
 ldapErrorConnectionTimeoutNotNumber=Connection Timeout must be a number
diff --git a/themes/src/main/resources/theme/base/login/messages/messages_en.properties b/themes/src/main/resources/theme/base/login/messages/messages_en.properties
index 3fc5bc4..823c4af 100755
--- a/themes/src/main/resources/theme/base/login/messages/messages_en.properties
+++ b/themes/src/main/resources/theme/base/login/messages/messages_en.properties
@@ -168,6 +168,7 @@ invalidPasswordMinSpecialCharsMessage=Invalid password: must contain at least {0
 invalidPasswordNotUsernameMessage=Invalid password: must not be equal to the username.
 invalidPasswordRegexPatternMessage=Invalid password: fails to match regex pattern(s).
 invalidPasswordHistoryMessage=Invalid password: must not be equal to any of last {0} passwords.
+invalidPasswordGenericMessage=Invalid password: new password doesn''t match password policies.
 
 failedToProcessResponseMessage=Failed to process response
 httpsRequiredMessage=HTTPS required