keycloak-aplcache

authenticator example updated

9/23/2016 5:50:08 PM

Changes

server-spi/src/main/java/org/keycloak/storage/user/UserCredentialValidatorProvider.java 33(+0 -33)

Details

diff --git a/distribution/demo-dist/src/main/xslt/standalone.xsl b/distribution/demo-dist/src/main/xslt/standalone.xsl
index ca45244..d67ce63 100755
--- a/distribution/demo-dist/src/main/xslt/standalone.xsl
+++ b/distribution/demo-dist/src/main/xslt/standalone.xsl
@@ -43,7 +43,7 @@
         <xsl:copy>
             <xsl:apply-templates select="node()[name(.)='datasource']"/>
             <datasource jndi-name="java:jboss/datasources/KeycloakDS" pool-name="KeycloakDS" use-java-context="true">
-                <connection-url>jdbc:h2:${jboss.server.data.dir}/keycloak;AUTO_SERVER=TRUE</connection-url>
+                <xa-datasource-property name="URL">jdbc:h2:${jboss.server.data.dir}/keycloak;AUTO_SERVER=TRUE</xa-datasource-property>
                 <driver>h2</driver>
                 <security>
                     <user-name>sa</user-name>
diff --git a/examples/providers/authenticator/pom.xml b/examples/providers/authenticator/pom.xml
index 0123a91..16a95d6 100755
--- a/examples/providers/authenticator/pom.xml
+++ b/examples/providers/authenticator/pom.xml
@@ -64,6 +64,13 @@
                     <target>1.8</target>
                 </configuration>
             </plugin>
+            <plugin>
+                <groupId>org.wildfly.plugins</groupId>
+                <artifactId>wildfly-maven-plugin</artifactId>
+                <configuration>
+                    <skip>false</skip>
+                </configuration>
+            </plugin>
         </plugins>
     </build>
 </project>
diff --git a/examples/providers/authenticator/README.md b/examples/providers/authenticator/README.md
index 54dc752..ef612cc 100755
--- a/examples/providers/authenticator/README.md
+++ b/examples/providers/authenticator/README.md
@@ -1,31 +1,23 @@
 Example Custom Authenticator
 ===================================================
 
-This is an example of defining a custom Authenticator and Required action.  This example is explained in the user documentation
-of Keycloak.   To deploy, build this directory then take the jar and copy it to providers directory. Alternatively you can deploy as a module by running:
+1. First, Keycloak must be running.
 
-    KEYCLOAK_HOME/bin/jboss-cli.sh --command="module add --name=org.keycloak.examples.secret-question --resources=target/authenticator-required-action-example.jar --dependencies=org.keycloak.keycloak-core,org.keycloak.keycloak-server-spi,org.keycloak.keycloak-services,org.jboss.resteasy.resteasy-jaxrs,javax.ws.rs.api"
+2. Execute the follow.  This will build the example and deploy it
 
-Then registering the provider by editing `standalone/configuration/standalone.xml` and adding the module to the providers element:
+   $ mvn clean install wildfly:deploy
 
-    <providers>
-        ...
-        <provider>module:org.keycloak.examples.secret-question</provider>
-    </providers>
+3. Copy the secret-question.ftl and secret-question-config.ftl files to the themes/base/login directory.
 
-You then have to copy the secret-question.ftl and secret-question-config.ftl files to the themes/base/login directory.
+4. Login to admin console.  Hit browser refresh if you are already logged in so that the new providers show up.
 
-After you do all this, you then have to reboot keycloak.  When reboot is complete, you will need to log into
-the admin console to create a new flow with your new authenticator.
+5. Go to the Authentication menu item and go to the Flow tab, you will be able to view the currently
+   defined flows.  You cannot modify an built in flows, so, to add the Authenticator you
+   have to copy an existing flow or create your own.  Copy the "Browser" flow.
 
-If you go to the Authentication menu item and go to the Flow tab, you will be able to view the currently
-defined flows.  You cannot modify an built in flows, so, to add the Authenticator you
-have to copy an existing flow or create your own.
+6. In your copy, click the "Actions" menu item and "Add Execution".  Pick Secret Question
 
-Next you have to register your required action.
-Click on the Required Actions tab.  Click on the Register button and choose your new Required Action.
-Your new required action should now be displayed and enabled in the required actions list.
+7. Next you have to register the required action that you created. Click on the Required Actions tab in the Authenticaiton menu.
+   Click on the Register button and choose your new Required Action.
+   Your new required action should now be displayed and enabled in the required actions list.
 
-I'm hoping the UI is intuitive enough so that you
-can figure out for yourself how to create a flow and add the Authenticator and Required Action.  We're looking to add a screencast
-to show this in action.
diff --git a/examples/providers/authenticator/src/main/java/org/keycloak/examples/authenticator/SecretQuestionAuthenticator.java b/examples/providers/authenticator/src/main/java/org/keycloak/examples/authenticator/SecretQuestionAuthenticator.java
index e25ff81..fb71a7c 100755
--- a/examples/providers/authenticator/src/main/java/org/keycloak/examples/authenticator/SecretQuestionAuthenticator.java
+++ b/examples/providers/authenticator/src/main/java/org/keycloak/examples/authenticator/SecretQuestionAuthenticator.java
@@ -17,18 +17,20 @@
 
 package org.keycloak.examples.authenticator;
 
+import org.jboss.resteasy.spi.HttpResponse;
+import org.jboss.resteasy.spi.ResteasyProviderFactory;
 import org.keycloak.authentication.AuthenticationFlowContext;
 import org.keycloak.authentication.AuthenticationFlowError;
 import org.keycloak.authentication.Authenticator;
+import org.keycloak.common.util.ServerCookie;
 import org.keycloak.models.AuthenticatorConfigModel;
 import org.keycloak.models.KeycloakSession;
 import org.keycloak.models.RealmModel;
 import org.keycloak.models.UserCredentialModel;
-import org.keycloak.models.UserCredentialValueModel;
 import org.keycloak.models.UserModel;
-import org.keycloak.services.util.CookieHelper;
 
 import javax.ws.rs.core.Cookie;
+import javax.ws.rs.core.HttpHeaders;
 import javax.ws.rs.core.MultivaluedMap;
 import javax.ws.rs.core.Response;
 import java.net.URI;
@@ -87,13 +89,22 @@ public class SecretQuestionAuthenticator implements Authenticator {
 
         }
         URI uri = context.getUriInfo().getBaseUriBuilder().path("realms").path(context.getRealm().getName()).build();
-        CookieHelper.addCookie("SECRET_QUESTION_ANSWERED", "true",
+        addCookie("SECRET_QUESTION_ANSWERED", "true",
                 uri.getRawPath(),
                 null, null,
                 maxCookieAge,
                 false, true);
     }
 
+    public static void addCookie(String name, String value, String path, String domain, String comment, int maxAge, boolean secure, boolean httpOnly) {
+        HttpResponse response = ResteasyProviderFactory.getContextData(HttpResponse.class);
+        StringBuffer cookieBuf = new StringBuffer();
+        ServerCookie.appendCookieValue(cookieBuf, 1, name, value, path, domain, comment, maxAge, secure, httpOnly);
+        String cookie = cookieBuf.toString();
+        response.getOutputHeaders().add(HttpHeaders.SET_COOKIE, cookie);
+    }
+
+
     protected boolean validateAnswer(AuthenticationFlowContext context) {
         MultivaluedMap<String, String> formData = context.getHttpRequest().getDecodedFormParameters();
         String secret = formData.getFirst("secret_answer");
diff --git a/examples/providers/authenticator/src/main/java/org/keycloak/examples/authenticator/SecretQuestionCredentialProvider.java b/examples/providers/authenticator/src/main/java/org/keycloak/examples/authenticator/SecretQuestionCredentialProvider.java
index 693c0eb..520aeb0 100644
--- a/examples/providers/authenticator/src/main/java/org/keycloak/examples/authenticator/SecretQuestionCredentialProvider.java
+++ b/examples/providers/authenticator/src/main/java/org/keycloak/examples/authenticator/SecretQuestionCredentialProvider.java
@@ -110,7 +110,7 @@ public class SecretQuestionCredentialProvider implements CredentialProvider, Cre
     }
 
     @Override
-    public void onCache(RealmModel realm, CachedUserModel user) {
+    public void onCache(RealmModel realm, CachedUserModel user, UserModel delegate) {
         List<CredentialModel> creds = session.userCredentialManager().getStoredCredentialsByType(realm, user, SECRET_QUESTION);
         if (!creds.isEmpty()) user.getCachedWith().put(CACHE_KEY, creds.get(0));
     }
diff --git a/examples/providers/user-storage-jpa/src/main/java/org/keycloak/examples/storage/user/EjbExampleUserStorageProvider.java b/examples/providers/user-storage-jpa/src/main/java/org/keycloak/examples/storage/user/EjbExampleUserStorageProvider.java
index a9bbc9b..8f7f147 100644
--- a/examples/providers/user-storage-jpa/src/main/java/org/keycloak/examples/storage/user/EjbExampleUserStorageProvider.java
+++ b/examples/providers/user-storage-jpa/src/main/java/org/keycloak/examples/storage/user/EjbExampleUserStorageProvider.java
@@ -18,16 +18,21 @@ package org.keycloak.examples.storage.user;
 
 import org.jboss.logging.Logger;
 import org.keycloak.component.ComponentModel;
+import org.keycloak.credential.CredentialInput;
+import org.keycloak.credential.CredentialInputUpdater;
+import org.keycloak.credential.CredentialInputValidator;
+import org.keycloak.credential.CredentialModel;
 import org.keycloak.models.GroupModel;
 import org.keycloak.models.KeycloakSession;
 import org.keycloak.models.RealmModel;
 import org.keycloak.models.RoleModel;
 import org.keycloak.models.UserCredentialModel;
 import org.keycloak.models.UserModel;
+import org.keycloak.models.cache.CachedUserModel;
+import org.keycloak.models.cache.OnUserCache;
 import org.keycloak.models.utils.KeycloakModelUtils;
 import org.keycloak.storage.StorageId;
 import org.keycloak.storage.UserStorageProvider;
-import org.keycloak.storage.user.UserCredentialValidatorProvider;
 import org.keycloak.storage.user.UserLookupProvider;
 import org.keycloak.storage.user.UserQueryProvider;
 import org.keycloak.storage.user.UserRegistrationProvider;
@@ -52,9 +57,13 @@ import java.util.Map;
 public class EjbExampleUserStorageProvider implements UserStorageProvider,
         UserLookupProvider,
         UserRegistrationProvider,
-        UserCredentialValidatorProvider,
-        UserQueryProvider {
+        UserQueryProvider,
+        CredentialInputUpdater,
+        CredentialInputValidator,
+        OnUserCache
+{
     private static final Logger logger = Logger.getLogger(EjbExampleUserStorageProvider.class);
+    public static final String PASSWORD_CACHE_KEY = UserAdapter.class.getName() + ".password";
 
     @PersistenceContext
     protected EntityManager em;
@@ -150,18 +159,69 @@ public class EjbExampleUserStorageProvider implements UserStorageProvider,
     }
 
     @Override
-    public boolean validCredentials(KeycloakSession session, RealmModel realm, UserModel user, List<UserCredentialModel> input) {
-        // having a "password" attribute is a workaround so that passwords can be cached.  All done for performance reasons...
-        // If we override getCredentialsDirectly/updateCredentialsDirectly
-        // then the realm passsword policy will/may try and overwrite the plain text password with a hash.
-        // If you don't like this workaround, you can query the database every time to validate the password
-        for (UserCredentialModel cred : input) {
-            if (!UserCredentialModel.PASSWORD.equals(cred.getType())) return false;
-            if (!cred.getValue().equals(user.getFirstAttribute("password"))) return false;
+    public void onCache(RealmModel realm, CachedUserModel user, UserModel delegate) {
+        String password = ((UserAdapter)delegate).getPassword();
+        if (password != null) {
+            user.getCachedWith().put(PASSWORD_CACHE_KEY, password);
         }
+    }
+
+    @Override
+    public boolean supportsCredentialType(String credentialType) {
+        return CredentialModel.PASSWORD.equals(credentialType);
+    }
+
+    @Override
+    public boolean updateCredential(RealmModel realm, UserModel user, CredentialInput input) {
+        if (!supportsCredentialType(input.getType()) || !(input instanceof UserCredentialModel)) return false;
+        UserCredentialModel cred = (UserCredentialModel)input;
+        UserAdapter adapter = getUserAdapter(user);
+        adapter.setPassword(cred.getValue());
+
         return true;
     }
 
+    public UserAdapter getUserAdapter(UserModel user) {
+        UserAdapter adapter = null;
+        if (user instanceof CachedUserModel) {
+            adapter = (UserAdapter)((CachedUserModel)user).getDelegateForUpdate();
+        } else {
+            adapter = (UserAdapter)user;
+        }
+        return adapter;
+    }
+
+    @Override
+    public void disableCredentialType(RealmModel realm, UserModel user, String credentialType) {
+        if (!supportsCredentialType(credentialType)) return;
+
+        getUserAdapter(user).setPassword(null);
+
+    }
+
+    @Override
+    public boolean isConfiguredFor(RealmModel realm, UserModel user, String credentialType) {
+        return supportsCredentialType(credentialType) && getPassword(user) != null;
+    }
+
+    @Override
+    public boolean isValid(RealmModel realm, UserModel user, CredentialInput input) {
+        if (!supportsCredentialType(input.getType()) || !(input instanceof UserCredentialModel)) return false;
+        UserCredentialModel cred = (UserCredentialModel)input;
+        String password = getPassword(user);
+        return password != null && password.equals(cred.getValue());
+    }
+
+    public String getPassword(UserModel user) {
+        String password = null;
+        if (user instanceof CachedUserModel) {
+            password = (String)((CachedUserModel)user).getCachedWith().get(PASSWORD_CACHE_KEY);
+        } else if (user instanceof UserAdapter) {
+            password = ((UserAdapter)user).getPassword();
+        }
+        return password;
+    }
+
     @Override
     public int getUsersCount(RealmModel realm) {
         Object count = em.createNamedQuery("getUserCount")
diff --git a/examples/providers/user-storage-jpa/src/main/java/org/keycloak/examples/storage/user/EjbExampleUserStorageProviderFactory.java b/examples/providers/user-storage-jpa/src/main/java/org/keycloak/examples/storage/user/EjbExampleUserStorageProviderFactory.java
index a1db65d..1e16100 100644
--- a/examples/providers/user-storage-jpa/src/main/java/org/keycloak/examples/storage/user/EjbExampleUserStorageProviderFactory.java
+++ b/examples/providers/user-storage-jpa/src/main/java/org/keycloak/examples/storage/user/EjbExampleUserStorageProviderFactory.java
@@ -16,6 +16,7 @@
  */
 package org.keycloak.examples.storage.user;
 
+import org.jboss.logging.Logger;
 import org.keycloak.Config;
 import org.keycloak.component.ComponentModel;
 import org.keycloak.models.KeycloakSession;
@@ -32,6 +33,7 @@ import java.util.List;
  * @version $Revision: 1 $
  */
 public class EjbExampleUserStorageProviderFactory implements UserStorageProviderFactory<EjbExampleUserStorageProvider> {
+    private static final Logger logger = Logger.getLogger(EjbExampleUserStorageProviderFactory.class);
 
 
     @Override
@@ -56,4 +58,10 @@ public class EjbExampleUserStorageProviderFactory implements UserStorageProvider
     public String getHelpText() {
         return "JPA Example User Storage Provider";
     }
+
+    @Override
+    public void close() {
+        logger.info("<<<<<< Closing factory");
+
+    }
 }
diff --git a/examples/providers/user-storage-jpa/src/main/java/org/keycloak/examples/storage/user/UserAdapter.java b/examples/providers/user-storage-jpa/src/main/java/org/keycloak/examples/storage/user/UserAdapter.java
index 84f4084..8c8bcd6 100644
--- a/examples/providers/user-storage-jpa/src/main/java/org/keycloak/examples/storage/user/UserAdapter.java
+++ b/examples/providers/user-storage-jpa/src/main/java/org/keycloak/examples/storage/user/UserAdapter.java
@@ -34,7 +34,7 @@ import java.util.Map;
  * @version $Revision: 1 $
  */
 public class UserAdapter extends AbstractUserAdapterFederatedStorage {
-    private static final Logger logger = Logger.getLogger(EjbExampleUserStorageProvider.class);
+    private static final Logger logger = Logger.getLogger(UserAdapter.class);
     protected UserEntity entity;
     protected String keycloakId;
 
@@ -44,6 +44,14 @@ public class UserAdapter extends AbstractUserAdapterFederatedStorage {
         keycloakId = StorageId.keycloakId(model, entity.getId());
     }
 
+    public String getPassword() {
+        return entity.getPassword();
+    }
+
+    public void setPassword(String password) {
+        entity.setPassword(password);
+    }
+
     @Override
     public String getUsername() {
         return entity.getUsername();
@@ -74,13 +82,6 @@ public class UserAdapter extends AbstractUserAdapterFederatedStorage {
     public void setSingleAttribute(String name, String value) {
         if (name.equals("phone")) {
             entity.setPhone(value);
-        } else if (name.equals("password")) {
-            // ignore
-
-            // having a "password" attribute is a workaround so that passwords can be cached.  All done for performance reasons...
-            // If we override getCredentialsDirectly/updateCredentialsDirectly
-            // then the realm passsword policy will/may try and overwrite the plain text password with a hash.
-            // If you don't like this workaround, you can query the database every time to validate the password
         } else {
             super.setSingleAttribute(name, value);
         }
@@ -90,13 +91,6 @@ public class UserAdapter extends AbstractUserAdapterFederatedStorage {
     public void removeAttribute(String name) {
         if (name.equals("phone")) {
             entity.setPhone(null);
-        } else if (name.equals("password")) {
-            // ignore
-
-            // having a "password" attribute is a workaround so that passwords can be cached.  All done for performance reasons...
-            // If we override getCredentialsDirectly/updateCredentialsDirectly
-            // then the realm passsword policy will/may try and overwrite the plain text password with a hash.
-            // If you don't like this workaround, you can query the database every time to validate the password
         } else {
             super.removeAttribute(name);
         }
@@ -106,13 +100,6 @@ public class UserAdapter extends AbstractUserAdapterFederatedStorage {
     public void setAttribute(String name, List<String> values) {
         if (name.equals("phone")) {
             entity.setPhone(values.get(0));
-        } else if (name.equals("password")) {
-            // ignore
-
-            // having a "password" attribute is a workaround so that passwords can be cached.  All done for performance reasons...
-            // If we override getCredentialsDirectly/updateCredentialsDirectly
-            // then the realm passsword policy will/may try and overwrite the plain text password with a hash.
-            // If you don't like this workaround, you can query the database every time to validate the password
         } else {
             super.setAttribute(name, values);
         }
@@ -122,12 +109,6 @@ public class UserAdapter extends AbstractUserAdapterFederatedStorage {
     public String getFirstAttribute(String name) {
         if (name.equals("phone")) {
             return entity.getPhone();
-        } else if (name.equals("password")) {
-            // having a "password" attribute is a workaround so that passwords can be cached.  All done for performance reasons...
-            // If we override getCredentialsDirectly/updateCredentialsDirectly
-            // then the realm passsword policy will/may try and overwrite the plain text password with a hash.
-            // If you don't like this workaround, you can query the database every time to validate the password
-            return entity.getPassword();
         } else {
             return super.getFirstAttribute(name);
         }
@@ -139,12 +120,6 @@ public class UserAdapter extends AbstractUserAdapterFederatedStorage {
         MultivaluedHashMap<String, String> all = new MultivaluedHashMap<>();
         all.putAll(attrs);
         all.add("phone", entity.getPhone());
-
-        // having a "password" attribute is a workaround so that passwords can be cached.  All done for performance reasons...
-        // If we override getCredentialsDirectly/updateCredentialsDirectly
-        // then the realm passsword policy will/may try and overwrite the plain text password with a hash.
-        // If you don't like this workaround, you can query the database every time to validate the password
-        all.add("password", entity.getPassword());
         return all;
     }
 
diff --git a/examples/providers/user-storage-jpa/src/main/resources/META-INF/persistence.xml b/examples/providers/user-storage-jpa/src/main/resources/META-INF/persistence.xml
index 9894af4..51082e1 100644
--- a/examples/providers/user-storage-jpa/src/main/resources/META-INF/persistence.xml
+++ b/examples/providers/user-storage-jpa/src/main/resources/META-INF/persistence.xml
@@ -4,7 +4,7 @@
              xsi:schemaLocation="
         http://java.sun.com/xml/ns/persistence
         http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd">
-    <persistence-unit name="user-storage-jpa-example">
+    <persistence-unit name="user-storage-jpa-example" transaction-type="JTA">
         <jta-data-source>java:jboss/datasources/ExampleDS</jta-data-source>
 
         <class>org.keycloak.examples.storage.user.UserEntity</class>
diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/UserAdapter.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/UserAdapter.java
index 6483f2a..002761e 100755
--- a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/UserAdapter.java
+++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/UserAdapter.java
@@ -60,12 +60,14 @@ public class UserAdapter implements CachedUserModel {
         this.realm = realm;
     }
 
-    protected void getDelegateForUpdate() {
+    @Override
+    public UserModel getDelegateForUpdate() {
         if (updated == null) {
             userProviderCache.registerUserInvalidation(realm, cached);
             updated = userProviderCache.getDelegate().getUserById(getId(), realm);
             if (updated == null) throw new IllegalStateException("Not found in database");
         }
+        return updated;
     }
 
     @Override
diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/UserCacheSession.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/UserCacheSession.java
index 9143052..7300768 100755
--- a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/UserCacheSession.java
+++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/UserCacheSession.java
@@ -168,21 +168,22 @@ public class UserCacheSession implements UserCache {
         }
 
         CachedUser cached = cache.get(id, CachedUser.class);
+        UserModel delegate = null;
         boolean wasCached = cached != null;
         if (cached == null) {
             logger.trace("not cached");
             Long loaded = cache.getCurrentRevision(id);
-            UserModel model = getDelegate().getUserById(id, realm);
-            if (model == null) {
+            delegate = getDelegate().getUserById(id, realm);
+            if (delegate == null) {
                 logger.trace("delegate returning null");
                 return null;
             }
-            cached = new CachedUser(loaded, realm, model);
+            cached = new CachedUser(loaded, realm, delegate);
             cache.addRevisioned(cached, startupRevision);
         }
         logger.trace("returning new cache adapter");
         UserAdapter adapter = new UserAdapter(cached, this, session, realm);
-        if (!wasCached) onCache(realm, adapter);
+        if (!wasCached) onCache(realm, adapter, delegate);
         managedUsers.put(id, adapter);
         return adapter;
     }
@@ -251,24 +252,24 @@ public class UserCacheSession implements UserCache {
         }
     }
 
-    protected UserAdapter getUserAdapter(RealmModel realm, String userId, Long loaded, UserModel model) {
+    protected UserAdapter getUserAdapter(RealmModel realm, String userId, Long loaded, UserModel delegate) {
         CachedUser cached = cache.get(userId, CachedUser.class);
         boolean wasCached = cached != null;
         if (cached == null) {
-            cached = new CachedUser(loaded, realm, model);
+            cached = new CachedUser(loaded, realm, delegate);
             cache.addRevisioned(cached, startupRevision);
         }
         UserAdapter adapter = new UserAdapter(cached, this, session, realm);
         if (!wasCached) {
-            onCache(realm, adapter);
+            onCache(realm, adapter, delegate);
         }
         return adapter;
 
     }
 
-    private void onCache(RealmModel realm, UserAdapter adapter) {
-        ((OnUserCache)getDelegate()).onCache(realm, adapter);
-        ((OnUserCache)session.userCredentialManager()).onCache(realm, adapter);
+    private void onCache(RealmModel realm, UserAdapter adapter, UserModel delegate) {
+        ((OnUserCache)getDelegate()).onCache(realm, adapter, delegate);
+        ((OnUserCache)session.userCredentialManager()).onCache(realm, adapter, delegate);
     }
 
     @Override
diff --git a/server-spi/src/main/java/org/keycloak/models/cache/CachedUserModel.java b/server-spi/src/main/java/org/keycloak/models/cache/CachedUserModel.java
index 5d9c7cd..9ee5a6a 100644
--- a/server-spi/src/main/java/org/keycloak/models/cache/CachedUserModel.java
+++ b/server-spi/src/main/java/org/keycloak/models/cache/CachedUserModel.java
@@ -21,10 +21,24 @@ import org.keycloak.models.UserModel;
 import java.util.concurrent.ConcurrentHashMap;
 
 /**
+ * Cached users will implement this interface
+ *
  * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
  * @version $Revision: 1 $
  */
 public interface CachedUserModel extends UserModel {
+
+    /**
+     * Invalidates the cache for this user and returns a delegate that represents the actual data provider
+     *
+     * @return
+     */
+    UserModel getDelegateForUpdate();
+
+    /**
+     * Invalidate the cache for this user
+     *
+     */
     void invalidate();
 
     /**
diff --git a/server-spi/src/main/java/org/keycloak/models/cache/OnUserCache.java b/server-spi/src/main/java/org/keycloak/models/cache/OnUserCache.java
index e676ce1..319b5c5 100644
--- a/server-spi/src/main/java/org/keycloak/models/cache/OnUserCache.java
+++ b/server-spi/src/main/java/org/keycloak/models/cache/OnUserCache.java
@@ -17,11 +17,12 @@
 package org.keycloak.models.cache;
 
 import org.keycloak.models.RealmModel;
+import org.keycloak.models.UserModel;
 
 /**
  * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
  * @version $Revision: 1 $
  */
 public interface OnUserCache {
-    void onCache(RealmModel realm, CachedUserModel user);
+    void onCache(RealmModel realm, CachedUserModel user, UserModel delegate);
 }
diff --git a/server-spi/src/main/java/org/keycloak/models/UserProvider.java b/server-spi/src/main/java/org/keycloak/models/UserProvider.java
index d6267c9..e1b64fa 100755
--- a/server-spi/src/main/java/org/keycloak/models/UserProvider.java
+++ b/server-spi/src/main/java/org/keycloak/models/UserProvider.java
@@ -18,9 +18,7 @@
 package org.keycloak.models;
 
 import org.keycloak.component.ComponentModel;
-import org.keycloak.credential.CredentialInput;
 import org.keycloak.provider.Provider;
-import org.keycloak.storage.user.UserCredentialValidatorProvider;
 import org.keycloak.storage.user.UserLookupProvider;
 import org.keycloak.storage.user.UserQueryProvider;
 import org.keycloak.storage.user.UserRegistrationProvider;
diff --git a/services/src/main/java/org/keycloak/credential/OTPCredentialProvider.java b/services/src/main/java/org/keycloak/credential/OTPCredentialProvider.java
index b6dd489..f5c973a 100644
--- a/services/src/main/java/org/keycloak/credential/OTPCredentialProvider.java
+++ b/services/src/main/java/org/keycloak/credential/OTPCredentialProvider.java
@@ -53,7 +53,7 @@ public class OTPCredentialProvider implements CredentialProvider, CredentialInpu
     }
 
     @Override
-    public void onCache(RealmModel realm, CachedUserModel user) {
+    public void onCache(RealmModel realm, CachedUserModel user, UserModel delegate) {
         List<CredentialModel> creds = getCredentialStore().getStoredCredentialsByType(realm, user, CredentialModel.TOTP);
         user.getCachedWith().put(OTPCredentialProvider.class.getName() + "." + CredentialModel.TOTP, creds);
 
diff --git a/services/src/main/java/org/keycloak/credential/PasswordCredentialProvider.java b/services/src/main/java/org/keycloak/credential/PasswordCredentialProvider.java
index 17803c1..4fc5856 100644
--- a/services/src/main/java/org/keycloak/credential/PasswordCredentialProvider.java
+++ b/services/src/main/java/org/keycloak/credential/PasswordCredentialProvider.java
@@ -206,7 +206,7 @@ public class PasswordCredentialProvider implements CredentialProvider, Credentia
     }
 
     @Override
-    public void onCache(RealmModel realm, CachedUserModel user) {
+    public void onCache(RealmModel realm, CachedUserModel user, UserModel delegate) {
         List<CredentialModel> passwords = getCredentialStore().getStoredCredentialsByType(realm, user, CredentialModel.PASSWORD);
         if (passwords != null) {
             user.getCachedWith().put(PASSWORD_CACHE_KEY, passwords);
diff --git a/services/src/main/java/org/keycloak/credential/UserCredentialStoreManager.java b/services/src/main/java/org/keycloak/credential/UserCredentialStoreManager.java
index 915cbe7..fab3127 100644
--- a/services/src/main/java/org/keycloak/credential/UserCredentialStoreManager.java
+++ b/services/src/main/java/org/keycloak/credential/UserCredentialStoreManager.java
@@ -254,10 +254,10 @@ public class UserCredentialStoreManager implements UserCredentialManager, OnUser
     }
 
     @Override
-    public void onCache(RealmModel realm, CachedUserModel user) {
+    public void onCache(RealmModel realm, CachedUserModel user, UserModel delegate) {
         List<OnUserCache> credentialProviders = getCredentialProviders(realm, OnUserCache.class);
         for (OnUserCache validator : credentialProviders) {
-            validator.onCache(realm, user);
+            validator.onCache(realm, user, delegate);
         }
     }
 
diff --git a/services/src/main/java/org/keycloak/services/DefaultKeycloakSessionFactory.java b/services/src/main/java/org/keycloak/services/DefaultKeycloakSessionFactory.java
index 36f9b7f..808b413 100755
--- a/services/src/main/java/org/keycloak/services/DefaultKeycloakSessionFactory.java
+++ b/services/src/main/java/org/keycloak/services/DefaultKeycloakSessionFactory.java
@@ -136,6 +136,7 @@ public class DefaultKeycloakSessionFactory implements KeycloakSessionFactory, Pr
 
     @Override
     public void undeploy(ProviderManager pm) {
+        logger.debug("undeploy");
         // we make a copy to avoid concurrent access exceptions
         Map<Class<? extends Provider>, Map<String, ProviderFactory>> copy = getFactoriesCopy();
         MultivaluedHashMap<Class<? extends Provider>, ProviderFactory> factories = pm.getLoadedFactories();
@@ -144,6 +145,7 @@ public class DefaultKeycloakSessionFactory implements KeycloakSessionFactory, Pr
             Map<String, ProviderFactory> registered = copy.get(entry.getKey());
             for (ProviderFactory factory : entry.getValue()) {
                 undeployed.add(factory);
+                logger.debugv("undeploying {0} of id {1}", factory.getClass().getName(), factory.getId());
                 if (registered != null) {
                     registered.remove(factory.getId());
                 }
diff --git a/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java b/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java
index aa536f5..b188501 100755
--- a/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java
+++ b/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java
@@ -570,6 +570,10 @@ public class AuthenticationManager {
                                                Set<String> requiredActions) {
         for (String action : requiredActions) {
             RequiredActionProviderModel model = realm.getRequiredActionProviderByAlias(action);
+            if (model == null) {
+                logger.warnv("Could not find configuration for Required Action {0}, did you forget to register it?", action);
+                continue;
+            }
             if (!model.isEnabled()) {
                 continue;
             }
diff --git a/services/src/main/java/org/keycloak/storage/UserStorageManager.java b/services/src/main/java/org/keycloak/storage/UserStorageManager.java
index d682627..8da231e 100755
--- a/services/src/main/java/org/keycloak/storage/UserStorageManager.java
+++ b/services/src/main/java/org/keycloak/storage/UserStorageManager.java
@@ -566,15 +566,15 @@ public class UserStorageManager implements UserProvider, OnUserCache {
     }
 
     @Override
-    public void onCache(RealmModel realm, CachedUserModel user) {
+    public void onCache(RealmModel realm, CachedUserModel user, UserModel delegate) {
         if (StorageId.isLocalStorage(user)) {
             if (session.userLocalStorage() instanceof OnUserCache) {
-                ((OnUserCache)session.userLocalStorage()).onCache(realm, user);
+                ((OnUserCache)session.userLocalStorage()).onCache(realm, user, delegate);
             }
         } else {
             Object provider = getStorageProvider(session, realm, StorageId.resolveProviderId(user));
             if (provider != null && provider instanceof OnUserCache) {
-                ((OnUserCache)provider).onCache(realm, user);
+                ((OnUserCache)provider).onCache(realm, user, delegate);
             }
         }
     }
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/storage/UserMapStorage.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/storage/UserMapStorage.java
index 962262a..a9fe9ab 100644
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/storage/UserMapStorage.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/storage/UserMapStorage.java
@@ -26,12 +26,10 @@ import org.keycloak.models.KeycloakSession;
 import org.keycloak.models.RealmModel;
 import org.keycloak.models.RoleModel;
 import org.keycloak.models.UserCredentialModel;
-import org.keycloak.models.UserCredentialValueModel;
 import org.keycloak.models.UserModel;
 import org.keycloak.storage.StorageId;
 import org.keycloak.storage.UserStorageProvider;
 import org.keycloak.storage.adapter.AbstractUserAdapterFederatedStorage;
-import org.keycloak.storage.user.UserCredentialValidatorProvider;
 import org.keycloak.storage.user.UserLookupProvider;
 import org.keycloak.storage.user.UserRegistrationProvider;
 
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/storage/UserPropertyFileStorage.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/storage/UserPropertyFileStorage.java
index 5a1db7e..bab2c4a 100644
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/storage/UserPropertyFileStorage.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/storage/UserPropertyFileStorage.java
@@ -29,7 +29,6 @@ import org.keycloak.storage.StorageId;
 import org.keycloak.storage.UserStorageProvider;
 import org.keycloak.storage.adapter.AbstractUserAdapter;
 import org.keycloak.storage.adapter.AbstractUserAdapterFederatedStorage;
-import org.keycloak.storage.user.UserCredentialValidatorProvider;
 import org.keycloak.storage.user.UserLookupProvider;
 import org.keycloak.storage.user.UserQueryProvider;
 
diff --git a/wildfly/server-subsystem/src/main/resources/subsystem-templates/keycloak-datasources.xml b/wildfly/server-subsystem/src/main/resources/subsystem-templates/keycloak-datasources.xml
index c823e4f..cd7fc81 100755
--- a/wildfly/server-subsystem/src/main/resources/subsystem-templates/keycloak-datasources.xml
+++ b/wildfly/server-subsystem/src/main/resources/subsystem-templates/keycloak-datasources.xml
@@ -21,22 +21,22 @@
     <extension-module>org.jboss.as.connector</extension-module>
     <subsystem xmlns="urn:jboss:domain:datasources:4.0">
         <datasources>
-            <datasource jndi-name="java:jboss/datasources/ExampleDS" pool-name="ExampleDS" enabled="true" use-java-context="true">
-                <connection-url>jdbc:h2:mem:test;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE</connection-url>
+            <xa-datasource jndi-name="java:jboss/datasources/ExampleDS" pool-name="ExampleDS" enabled="true" use-java-context="true">
+                <xa-datasource-property name="URL">jdbc:h2:mem:test;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE</xa-datasource-property>
                 <driver>h2</driver>
                 <security>
                     <user-name>sa</user-name>
                     <password>sa</password>
                 </security>
-            </datasource>
-            <datasource jndi-name="java:jboss/datasources/KeycloakDS" pool-name="KeycloakDS" enabled="true" use-java-context="true">
-                <?KEYCLOAK_DS_CONNECTION_URL?>
+            </xa-datasource>
+            <xa-datasource jndi-name="java:jboss/datasources/KeycloakDS" pool-name="KeycloakDS" enabled="true" use-java-context="true">
+                <xa-datasource-property name="URL"><?KEYCLOAK_DS_CONNECTION_URL?></xa-datasource-property>
                 <driver>h2</driver>
                 <security>
                     <user-name>sa</user-name>
                     <password>sa</password>
                 </security>
-            </datasource>
+            </xa-datasource>
             <drivers>
                 <driver name="h2" module="com.h2database.h2">
                     <xa-datasource-class>org.h2.jdbcx.JdbcDataSource</xa-datasource-class>
@@ -46,12 +46,12 @@
     </subsystem>
     <supplement name="default">
         <replacement placeholder="KEYCLOAK_DS_CONNECTION_URL">
-            <connection-url>jdbc:h2:${jboss.server.data.dir}/keycloak;AUTO_SERVER=TRUE</connection-url>
+            jdbc:h2:${jboss.server.data.dir}/keycloak;AUTO_SERVER=TRUE
         </replacement>
     </supplement>
     <supplement name="domain">
         <replacement placeholder="KEYCLOAK_DS_CONNECTION_URL">
-            <connection-url>jdbc:h2:${jboss.server.data.dir}/../../shared-database/keycloak;AUTO_SERVER=TRUE</connection-url>
+            jdbc:h2:${jboss.server.data.dir}/../../shared-database/keycloak;AUTO_SERVER=TRUE
         </replacement>
     </supplement>
 </config>