keycloak-memoizeit

fixes for new user fed spi

7/7/2016 11:35:35 AM

Changes

Details

diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/CacheManager.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/CacheManager.java
index e41913d..d272a5a 100755
--- a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/CacheManager.java
+++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/CacheManager.java
@@ -16,6 +16,40 @@ import java.util.Set;
 import java.util.function.Predicate;
 
 /**
+ *
+ * Some notes on how this works:
+
+ * This implementation manages optimistic locking and version checks itself.  The reason is Infinispan just does behave
+ * the way we need it to.  Not saying Infinispan is bad, just that we have specific caching requirements!
+ *
+ * This is an invalidation cache implementation and requires to caches:
+ * Cache 1 is an Invalidation Cache
+ * Cache 2 is a local-only revision number cache.
+ *
+ *
+ * Each node in the cluster maintains its own revision number cache for each entry in the main invalidation cache.  This revision
+ * cache holds the version counter for each cached entity.
+ *
+ * Cache listeners do not receive a @CacheEntryInvalidated event if that node does not have an entry for that item.  So, consider the following.
+
+ 1. Node 1 gets current counter for user.  There currently isn't one as this user isn't cached.
+ 2. Node 1 reads user from DB
+ 3. Node 2 updates user
+ 4. Node 2 calls cache.remove(user).  This does not result in an invalidation listener event to node 1!
+ 5. node 1 checks version counter, checks pass. Stale entry is cached.
+
+ The issue is that Node 1 doesn't have an entry for the user, so it never receives an invalidation listener event from Node 2 thus it can't bump the version.  So, when node 1 goes to cache the user it is stale as the version number was never bumped.
+
+ So how is this issue fixed?  here is pseudo code:
+
+ 1. Node 1 calls cacheManager.getCurrentRevision() to get the current local version counter of that User
+ 2. Node 1 getCurrentRevision() pulls current counter for that user
+ 3. Node 1 getCurrentRevision() adds a "invalidation.key.userid" to invalidation cache.  Its just a marker. nothing else
+ 4. Node 2 update user
+ 5. Node 2 does a cache.remove(user) cache.remove(invalidation.key.userid)
+ 6. Node 1 receives invalidation event for invalidation.key.userid. Bumps the version counter for that user
+ 7. node 1 version check fails, it doesn't cache the user
+ *
  * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
  * @version $Revision: 1 $
  */
diff --git a/model/infinispan/src/test/java/org/keycloak/models/sessions/infinispan/initializer/ClusteredCacheBehaviorTest.java b/model/infinispan/src/test/java/org/keycloak/models/sessions/infinispan/initializer/ClusteredCacheBehaviorTest.java
index 3dcc913..de6e587 100755
--- a/model/infinispan/src/test/java/org/keycloak/models/sessions/infinispan/initializer/ClusteredCacheBehaviorTest.java
+++ b/model/infinispan/src/test/java/org/keycloak/models/sessions/infinispan/initializer/ClusteredCacheBehaviorTest.java
@@ -29,6 +29,8 @@ import org.keycloak.connections.infinispan.InfinispanConnectionProvider;
 @Ignore
 public class ClusteredCacheBehaviorTest {
     public EmbeddedCacheManager createManager() {
+        System.setProperty("java.net.preferIPv4Stack", "true");
+        System.setProperty("jgroups.tcp.port", "53715");
         GlobalConfigurationBuilder gcb = new GlobalConfigurationBuilder();
 
         boolean clustered = true;
@@ -36,7 +38,8 @@ public class ClusteredCacheBehaviorTest {
         boolean allowDuplicateJMXDomains = true;
 
         if (clustered) {
-            gcb.transport().defaultTransport();
+            gcb = gcb.clusteredDefault();
+            gcb.transport().clusterName("test-clustering");
         }
         gcb.globalJmxStatistics().allowDuplicateDomains(allowDuplicateJMXDomains);
 
diff --git a/model/jpa/src/main/java/org/keycloak/storage/jpa/entity/BrokerLinkEntity.java b/model/jpa/src/main/java/org/keycloak/storage/jpa/entity/BrokerLinkEntity.java
index 90ca5f1..a6b05cd 100755
--- a/model/jpa/src/main/java/org/keycloak/storage/jpa/entity/BrokerLinkEntity.java
+++ b/model/jpa/src/main/java/org/keycloak/storage/jpa/entity/BrokerLinkEntity.java
@@ -21,6 +21,7 @@ import javax.persistence.Column;
 import javax.persistence.Entity;
 import javax.persistence.Id;
 import javax.persistence.IdClass;
+import javax.persistence.JoinColumn;
 import javax.persistence.NamedQueries;
 import javax.persistence.NamedQuery;
 import javax.persistence.Table;
@@ -32,7 +33,7 @@ import java.io.Serializable;
  */
 @NamedQueries({
         @NamedQuery(name= "findBrokerLinkByUser", query="select link from BrokerLinkEntity link where link.userId = :userId"),
-        @NamedQuery(name= "findBrokerLinkByUserAndProvider", query="select link from BrokerLinkEntity link where link.userId = :userId and link.identityProvider = :identityProvider"),
+        @NamedQuery(name= "findBrokerLinkByUserAndProvider", query="select link from BrokerLinkEntity link where link.userId = :userId and link.identityProvider = :identityProvider and link.realmId = :realmId"),
         @NamedQuery(name= "findUserByBrokerLinkAndRealm", query="select link.userId from BrokerLinkEntity link where link.realmId = :realmId and link.identityProvider = :identityProvider and link.brokerUserId = :brokerUserId"),
         @NamedQuery(name= "deleteBrokerLinkByStorageProvider", query="delete from BrokerLinkEntity social where social.storageProviderId = :storageProviderId"),
         @NamedQuery(name= "deleteBrokerLinkByRealm", query="delete from BrokerLinkEntity social where social.realmId = :realmId"),
@@ -45,6 +46,7 @@ import java.io.Serializable;
 public class BrokerLinkEntity {
 
     @Id
+    @Column(name = "USER_ID")
     private String userId;
 
     @Id
diff --git a/model/jpa/src/main/java/org/keycloak/storage/jpa/entity/FederatedUserConsentEntity.java b/model/jpa/src/main/java/org/keycloak/storage/jpa/entity/FederatedUserConsentEntity.java
index 18aab7f..8066310 100755
--- a/model/jpa/src/main/java/org/keycloak/storage/jpa/entity/FederatedUserConsentEntity.java
+++ b/model/jpa/src/main/java/org/keycloak/storage/jpa/entity/FederatedUserConsentEntity.java
@@ -39,12 +39,12 @@ import java.util.Collection;
         @UniqueConstraint(columnNames = {"USER_ID", "CLIENT_ID"})
 })
 @NamedQueries({
-        @NamedQuery(name="userFederatedConsentByUserAndClient", query="select consent from UserConsentEntity consent where consent.userId = :userId and consent.clientId = :clientId"),
-        @NamedQuery(name="userFederatedConsentsByUser", query="select consent from UserConsentEntity consent where consent.userId = :userId"),
-        @NamedQuery(name="deleteFederatedUserConsentsByRealm", query="delete from UserConsentEntity consent where consent.realmId=:realmId"),
+        @NamedQuery(name="userFederatedConsentByUserAndClient", query="select consent from FederatedUserConsentEntity consent where consent.userId = :userId and consent.clientId = :clientId"),
+        @NamedQuery(name="userFederatedConsentsByUser", query="select consent from FederatedUserConsentEntity consent where consent.userId = :userId"),
+        @NamedQuery(name="deleteFederatedUserConsentsByRealm", query="delete from FederatedUserConsentEntity consent where consent.realmId=:realmId"),
         @NamedQuery(name="deleteFederatedUserConsentsByStorageProvider", query="delete from FederatedUserConsentEntity e where e.storageProviderId=:storageProviderId"),
-        @NamedQuery(name="deleteFederatedUserConsentsByUser", query="delete from UserConsentEntity consent where consent.userId = :userId and consent.realmId = :realmId"),
-        @NamedQuery(name="deleteFederatedUserConsentsByClient", query="delete from UserConsentEntity consent where consent.clientId = :clientId"),
+        @NamedQuery(name="deleteFederatedUserConsentsByUser", query="delete from FederatedUserConsentEntity consent where consent.userId = :userId and consent.realmId = :realmId"),
+        @NamedQuery(name="deleteFederatedUserConsentsByClient", query="delete from FederatedUserConsentEntity consent where consent.clientId = :clientId"),
 })
 public class FederatedUserConsentEntity {
 
diff --git a/model/jpa/src/main/java/org/keycloak/storage/jpa/entity/FederatedUserGroupMembershipEntity.java b/model/jpa/src/main/java/org/keycloak/storage/jpa/entity/FederatedUserGroupMembershipEntity.java
index cbbdf4e..ecfaa49 100755
--- a/model/jpa/src/main/java/org/keycloak/storage/jpa/entity/FederatedUserGroupMembershipEntity.java
+++ b/model/jpa/src/main/java/org/keycloak/storage/jpa/entity/FederatedUserGroupMembershipEntity.java
@@ -35,7 +35,7 @@ import java.io.Serializable;
 @NamedQueries({
         @NamedQuery(name="feduserMemberOf", query="select m from FederatedUserGroupMembershipEntity m where m.userId = :userId and m.groupId = :groupId"),
         @NamedQuery(name="feduserGroupMembership", query="select m from FederatedUserGroupMembershipEntity m where m.userId = :userId"),
-        @NamedQuery(name="fedgroupMembership", query="select g.user from FederatedUserGroupMembershipEntity g where g.groupId = :groupId"),
+        @NamedQuery(name="fedgroupMembership", query="select g.userId from FederatedUserGroupMembershipEntity g where g.groupId = :groupId"),
         @NamedQuery(name="feduserGroupIds", query="select m.groupId from FederatedUserGroupMembershipEntity m where m.userId = :userId"),
         @NamedQuery(name="deleteFederatedUserGroupMembershipByRealm", query="delete from  FederatedUserGroupMembershipEntity mapping where mapping.realmId=:realmId"),
         @NamedQuery(name="deleteFederatedUserGroupMembershipByStorageProvider", query="delete from FederatedUserGroupMembershipEntity e where e.storageProviderId=:storageProviderId"),
@@ -50,6 +50,7 @@ import java.io.Serializable;
 public class FederatedUserGroupMembershipEntity {
 
     @Id
+    @Column(name = "USER_ID")
     protected String userId;
 
     @Id
diff --git a/model/jpa/src/main/java/org/keycloak/storage/jpa/entity/FederatedUserRequiredActionEntity.java b/model/jpa/src/main/java/org/keycloak/storage/jpa/entity/FederatedUserRequiredActionEntity.java
index c39a311..f555628 100755
--- a/model/jpa/src/main/java/org/keycloak/storage/jpa/entity/FederatedUserRequiredActionEntity.java
+++ b/model/jpa/src/main/java/org/keycloak/storage/jpa/entity/FederatedUserRequiredActionEntity.java
@@ -36,7 +36,7 @@ import java.io.Serializable;
  * @version $Revision: 1 $
  */
 @NamedQueries({
-        @NamedQuery(name="getFederatedUserRequiredActionsByUser", query="select action from FederatedUserRequiredActionEntity action where action.userId = :userId and attr.realmId=:realmId"),
+        @NamedQuery(name="getFederatedUserRequiredActionsByUser", query="select action from FederatedUserRequiredActionEntity action where action.userId = :userId and action.realmId=:realmId"),
         @NamedQuery(name="deleteFederatedUserRequiredActionsByRealm", query="delete from FederatedUserRequiredActionEntity action where action.realmId=:realmId"),
         @NamedQuery(name="deleteFederatedUserRequiredActionsByStorageProvider", query="delete from FederatedUserRequiredActionEntity e where e.storageProviderId=:storageProviderId"),
         @NamedQuery(name="deleteFederatedUserRequiredActionsByRealmAndLink", query="delete from FederatedUserRequiredActionEntity action where action.userId IN (select u.id from UserEntity u where u.realmId=:realmId and u.federationLink=:link)")
diff --git a/model/jpa/src/main/java/org/keycloak/storage/jpa/entity/FederatedUserRoleMappingEntity.java b/model/jpa/src/main/java/org/keycloak/storage/jpa/entity/FederatedUserRoleMappingEntity.java
index 78ef299..719fce6 100755
--- a/model/jpa/src/main/java/org/keycloak/storage/jpa/entity/FederatedUserRoleMappingEntity.java
+++ b/model/jpa/src/main/java/org/keycloak/storage/jpa/entity/FederatedUserRoleMappingEntity.java
@@ -48,6 +48,7 @@ import java.io.Serializable;
 public class FederatedUserRoleMappingEntity {
 
     @Id
+    @Column(name = "USER_ID")
     protected String userId;
 
     @Id
diff --git a/model/jpa/src/main/java/org/keycloak/storage/jpa/JpaUserFederatedStorageProvider.java b/model/jpa/src/main/java/org/keycloak/storage/jpa/JpaUserFederatedStorageProvider.java
index b206cc5..4ec58bd 100644
--- a/model/jpa/src/main/java/org/keycloak/storage/jpa/JpaUserFederatedStorageProvider.java
+++ b/model/jpa/src/main/java/org/keycloak/storage/jpa/JpaUserFederatedStorageProvider.java
@@ -149,8 +149,8 @@ public class JpaUserFederatedStorageProvider implements
 
     @Override
     public String getUserByFederatedIdentity(FederatedIdentityModel link, RealmModel realm) {
-        TypedQuery<String> query = em.createNamedQuery("findBrokerLinkByUserAndProvider", String.class)
-                .setParameter("realmid", realm.getId())
+        TypedQuery<String> query = em.createNamedQuery("findUserByBrokerLinkAndRealm", String.class)
+                .setParameter("realmId", realm.getId())
                 .setParameter("identityProvider", link.getIdentityProvider())
                 .setParameter("brokerUserId", link.getUserId());
         List<String> results = query.getResultList();
@@ -180,15 +180,16 @@ public class JpaUserFederatedStorageProvider implements
 
     @Override
     public boolean removeFederatedIdentity(RealmModel realm, UserModel user, String socialProvider) {
-        BrokerLinkEntity entity = getBrokerLinkEntity(user, socialProvider);
+        BrokerLinkEntity entity = getBrokerLinkEntity(realm, user, socialProvider);
         if (entity == null) return false;
         em.remove(entity);
         return true;
     }
 
-    private BrokerLinkEntity getBrokerLinkEntity(UserModel user, String socialProvider) {
+    private BrokerLinkEntity getBrokerLinkEntity(RealmModel realm, UserModel user, String socialProvider) {
         TypedQuery<BrokerLinkEntity> query = em.createNamedQuery("findBrokerLinkByUserAndProvider", BrokerLinkEntity.class)
                 .setParameter("userId", user.getId())
+                .setParameter("realmId", realm.getId())
                 .setParameter("identityProvider", socialProvider);
         List<BrokerLinkEntity> results = query.getResultList();
         return results.size() > 0 ? results.get(0) : null;
@@ -196,7 +197,7 @@ public class JpaUserFederatedStorageProvider implements
 
     @Override
     public void updateFederatedIdentity(RealmModel realm, UserModel user, FederatedIdentityModel model) {
-        BrokerLinkEntity entity = getBrokerLinkEntity(user, model.getIdentityProvider());
+        BrokerLinkEntity entity = getBrokerLinkEntity(realm, user, model.getIdentityProvider());
         if (entity == null) return;
         entity.setBrokerUserName(model.getUserName());
         entity.setBrokerUserId(model.getUserId());
@@ -221,7 +222,7 @@ public class JpaUserFederatedStorageProvider implements
 
     @Override
     public FederatedIdentityModel getFederatedIdentity(UserModel user, String socialProvider, RealmModel realm) {
-        BrokerLinkEntity entity = getBrokerLinkEntity(user, socialProvider);
+        BrokerLinkEntity entity = getBrokerLinkEntity(realm, user, socialProvider);
         if (entity == null) return null;
         return new FederatedIdentityModel(entity.getIdentityProvider(), entity.getBrokerUserId(), entity.getBrokerUserName(), entity.getToken());
     }
@@ -239,6 +240,7 @@ public class JpaUserFederatedStorageProvider implements
         consentEntity.setId(KeycloakModelUtils.generateId());
         consentEntity.setUserId(user.getId());
         consentEntity.setClientId(clientId);
+        consentEntity.setRealmId(realm.getId());
         consentEntity.setStorageProviderId(StorageId.resolveProviderId(user));
         em.persist(consentEntity);
         em.flush();
@@ -588,7 +590,7 @@ public class JpaUserFederatedStorageProvider implements
 
     @Override
     public void preRemove(RealmModel realm) {
-        int num = em.createNamedQuery("deleteUserConsentRolesByRealm")
+        int num = em.createNamedQuery("deleteFederatedUserConsentRolesByRealm")
                 .setParameter("realmId", realm.getId()).executeUpdate();
         num = em.createNamedQuery("deleteFederatedUserConsentProtMappersByRealm")
                 .setParameter("realmId", realm.getId()).executeUpdate();
diff --git a/model/jpa/src/main/resources/META-INF/jpa-changelog-2.1.0.xml b/model/jpa/src/main/resources/META-INF/jpa-changelog-2.1.0.xml
index 79ba2fc..79c3e9e 100755
--- a/model/jpa/src/main/resources/META-INF/jpa-changelog-2.1.0.xml
+++ b/model/jpa/src/main/resources/META-INF/jpa-changelog-2.1.0.xml
@@ -17,131 +17,178 @@
   -->
 
 <databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.1.xsd">
-    <changeSet author="mposolda@redhat.com" id="1.8.0">
+    <changeSet author="bburke@redhat.com" id="2.1.0">
 
-        <addColumn tableName="IDENTITY_PROVIDER">
-            <column name="POST_BROKER_LOGIN_FLOW_ID" type="VARCHAR(36)">
-                <constraints nullable="true"/>
+        <createTable tableName="BROKER_LINK">
+            <column name="IDENTITY_PROVIDER" type="VARCHAR(255)">
+                <constraints nullable="false" />
+            </column>
+            <column name="STORAGE_PROVIDER_ID" type="VARCHAR(255)">
+            </column>
+            <column name="REALM_ID" type="VARCHAR(36)">
+                <constraints nullable="false" />
             </column>
-        </addColumn>
-        <createTable tableName="CLIENT_TEMPLATE">
+            <column name="BROKER_USER_ID" type="VARCHAR(255)" />
+            <column name="BROKER_USERNAME" type="VARCHAR(255)" />
+            <column name="TOKEN" type="TEXT" />
+            <column name="USER_ID" type="VARCHAR(255)">
+                <constraints nullable="false" />
+            </column>
+        </createTable>
+        <createTable tableName="FED_USER_ATTRIBUTE">
             <column name="ID" type="VARCHAR(36)">
-                <constraints nullable="false"/>
+                <constraints nullable="false" />
             </column>
-            <column name="NAME" type="VARCHAR(255)"/>
-            <column name="REALM_ID" type="VARCHAR(36)"/>
-            <column name="DESCRIPTION" type="VARCHAR(255)"/>
-            <column name="PROTOCOL" type="VARCHAR(255)"/>
-            <column name="FULL_SCOPE_ALLOWED" type="BOOLEAN" defaultValueBoolean="false">
+            <column name="NAME" type="VARCHAR(255)">
                 <constraints nullable="false"/>
             </column>
-            <column name="CONSENT_REQUIRED" type="BOOLEAN" defaultValueBoolean="false">
+            <column name="USER_ID" type="VARCHAR(255)">
                 <constraints nullable="false"/>
             </column>
-            <column name="STANDARD_FLOW_ENABLED" type="BOOLEAN" defaultValueBoolean="true">
+            <column name="REALM_ID" type="VARCHAR(36)">
                 <constraints nullable="false"/>
             </column>
-            <column name="IMPLICIT_FLOW_ENABLED" type="BOOLEAN" defaultValueBoolean="false">
+            <column name="STORAGE_PROVIDER_ID" type="VARCHAR(36)"/>
+            <column name="VALUE" type="VARCHAR(2024)"/>
+        </createTable>
+        <createTable tableName="FED_USER_CONSENT">
+            <column name="ID" type="VARCHAR(36)">
                 <constraints nullable="false"/>
             </column>
-            <column name="DIRECT_ACCESS_GRANTS_ENABLED" type="BOOLEAN" defaultValueBoolean="false">
+            <column name="CLIENT_ID" type="VARCHAR(36)">
                 <constraints nullable="false"/>
             </column>
-            <column name="SERVICE_ACCOUNTS_ENABLED" type="BOOLEAN" defaultValueBoolean="false">
+            <column name="USER_ID" type="VARCHAR(255)">
                 <constraints nullable="false"/>
             </column>
-            <column name="FRONTCHANNEL_LOGOUT" type="BOOLEAN" defaultValueBoolean="false">
+            <column name="REALM_ID" type="VARCHAR(36)">
                 <constraints nullable="false"/>
             </column>
-            <column name="BEARER_ONLY" type="BOOLEAN" defaultValueBoolean="false">
+            <column name="STORAGE_PROVIDER_ID" type="VARCHAR(36)"/>
+        </createTable>
+        <createTable tableName="FED_USER_CONSENT_ROLE">
+            <column name="USER_CONSENT_ID" type="VARCHAR(36)">
                 <constraints nullable="false"/>
             </column>
-            <column name="PUBLIC_CLIENT" type="BOOLEAN" defaultValueBoolean="false">
+            <column name="ROLE_ID" type="VARCHAR(36)">
                 <constraints nullable="false"/>
             </column>
         </createTable>
-        <createTable tableName="CLIENT_TEMPLATE_ATTRIBUTES">
-            <column name="TEMPLATE_ID" type="VARCHAR(36)">
+        <createTable tableName="FED_USER_CONSENT_PROT_MAPPER">
+            <column name="USER_CONSENT_ID" type="VARCHAR(36)">
                 <constraints nullable="false"/>
             </column>
-            <column name="VALUE" type="VARCHAR(2048)"/>
-            <column name="NAME" type="VARCHAR(255)">
+            <column name="PROTOCOL_MAPPER_ID" type="VARCHAR(36)">
                 <constraints nullable="false"/>
             </column>
         </createTable>
-        <createTable tableName="TEMPLATE_SCOPE_MAPPING">
-            <column name="TEMPLATE_ID" type="VARCHAR(36)">
+        <createTable tableName="FED_USER_CREDENTIAL">
+            <column name="ID" type="VARCHAR(36)">
                 <constraints nullable="false"/>
             </column>
-            <column name="ROLE_ID" type="VARCHAR(36)">
+            <column name="DEVICE" type="VARCHAR(255)"/>
+            <column name="HASH_ITERATIONS" type="INT"/>
+            <column name="SALT" type="BLOB(16)"/>
+            <column name="TYPE" type="VARCHAR(255)"/>
+            <column name="VALUE" type="VARCHAR(255)"/>
+            <column name="CREATED_DATE" type="BIGINT"/>
+            <column name="COUNTER" type="INT" defaultValueNumeric="0">
+                <constraints nullable="true"/>
+            </column>
+            <column name="DIGITS" type="INT" defaultValueNumeric="6">
+                <constraints nullable="true"/>
+            </column>
+            <column name="PERIOD" type="INT" defaultValueNumeric="30">
+                <constraints nullable="true"/>
+            </column>
+            <column name="ALGORITHM" type="VARCHAR(36)" defaultValue="HmacSHA1">
+                <constraints nullable="true"/>
+            </column>
+            <column name="USER_ID" type="VARCHAR(255)">
                 <constraints nullable="false"/>
             </column>
+            <column name="REALM_ID" type="VARCHAR(36)">
+                <constraints nullable="false"/>
+            </column>
+            <column name="STORAGE_PROVIDER_ID" type="VARCHAR(36)"/>
         </createTable>
-
-
-
-        <dropNotNullConstraint tableName="PROTOCOL_MAPPER" columnName="CLIENT_ID" columnDataType="VARCHAR(36)"/>
-        <addColumn tableName="CLIENT">
-            <column name="CLIENT_TEMPLATE_ID" type="VARCHAR(36)">
-                <constraints nullable="true"/>
+        <createTable tableName="FED_USER_GROUP_MEMBERSHIP">
+            <column name="GROUP_ID" type="VARCHAR(36)">
+                <constraints nullable="false"/>
             </column>
-            <column name="USE_TEMPLATE_CONFIG" type="BOOLEAN" defaultValueBoolean="false">
+            <column name="USER_ID" type="VARCHAR(255)">
                 <constraints nullable="false"/>
             </column>
-            <column name="USE_TEMPLATE_SCOPE" type="BOOLEAN" defaultValueBoolean="false">
+            <column name="REALM_ID" type="VARCHAR(36)">
                 <constraints nullable="false"/>
             </column>
-            <column name="USE_TEMPLATE_MAPPERS" type="BOOLEAN" defaultValueBoolean="false">
+            <column name="STORAGE_PROVIDER_ID" type="VARCHAR(36)"/>
+        </createTable>
+        <createTable tableName="FED_USER_REQUIRED_ACTION">
+            <column name="REQUIRED_ACTION" type="VARCHAR(255)" defaultValue=" ">
                 <constraints nullable="false"/>
             </column>
-        </addColumn>
-        <addColumn tableName="PROTOCOL_MAPPER">
-            <column name="CLIENT_TEMPLATE_ID" type="VARCHAR(36)">
-                <constraints nullable="true"/>
+            <column name="USER_ID" type="VARCHAR(255)">
+                <constraints nullable="false"/>
+            </column>
+            <column name="REALM_ID" type="VARCHAR(36)">
+                <constraints nullable="false"/>
+            </column>
+            <column name="STORAGE_PROVIDER_ID" type="VARCHAR(36)"/>
+        </createTable>
+        <createTable tableName="FED_USER_ROLE_MAPPING">
+            <column name="ROLE_ID" type="VARCHAR(36)">
+                <constraints nullable="false"/>
             </column>
-         </addColumn>
-        <createTable tableName="REALM_CLIENT_TEMPLATE">
-            <column name="CLIENT_TEMPLATE_ID" type="VARCHAR(36)">
+            <column name="USER_ID" type="VARCHAR(255)">
                 <constraints nullable="false"/>
             </column>
             <column name="REALM_ID" type="VARCHAR(36)">
                 <constraints nullable="false"/>
             </column>
+            <column name="STORAGE_PROVIDER_ID" type="VARCHAR(36)"/>
         </createTable>
 
-        <addPrimaryKey columnNames="ID" constraintName="PK_CLI_TEMPLATE" tableName="CLIENT_TEMPLATE"/>
-        <addUniqueConstraint columnNames="REALM_ID,NAME" constraintName="UK_CLI_TEMPLATE" tableName="CLIENT_TEMPLATE"/>
-        <addForeignKeyConstraint baseColumnNames="REALM_ID" baseTableName="CLIENT_TEMPLATE" constraintName="FK_REALM_CLI_TMPLT" referencedColumnNames="ID" referencedTableName="REALM"/>
-        <addForeignKeyConstraint baseColumnNames="CLIENT_TEMPLATE_ID" baseTableName="PROTOCOL_MAPPER" constraintName="FK_CLI_TMPLT_MAPPER" referencedColumnNames="ID" referencedTableName="CLIENT_TEMPLATE"/>
-        <addForeignKeyConstraint baseColumnNames="CLIENT_TEMPLATE_ID" baseTableName="CLIENT" constraintName="FK_CLI_TMPLT_CLIENT" referencedColumnNames="ID" referencedTableName="CLIENT_TEMPLATE"/>
-        <addForeignKeyConstraint baseColumnNames="REALM_ID" baseTableName="REALM_CLIENT_TEMPLATE" constraintName="FK_RLM_CLI_TMPLT_RLM" referencedColumnNames="ID" referencedTableName="REALM"/>
-        <addForeignKeyConstraint baseColumnNames="CLIENT_TEMPLATE_ID" baseTableName="REALM_CLIENT_TEMPLATE" constraintName="FK_RLM_CLI_TMPLT_CLI" referencedColumnNames="ID" referencedTableName="CLIENT_TEMPLATE"/>
-        <addPrimaryKey columnNames="TEMPLATE_ID, ROLE_ID" constraintName="PK_TEMPLATE_SCOPE" tableName="TEMPLATE_SCOPE_MAPPING"/>
-        <addForeignKeyConstraint baseColumnNames="TEMPLATE_ID" baseTableName="TEMPLATE_SCOPE_MAPPING" constraintName="FK_TEMPL_SCOPE_TEMPL" referencedColumnNames="ID" referencedTableName="CLIENT_TEMPLATE"/>
-        <addForeignKeyConstraint baseColumnNames="ROLE_ID" baseTableName="TEMPLATE_SCOPE_MAPPING" constraintName="FK_TEMPL_SCOPE_ROLE" referencedColumnNames="ID" referencedTableName="KEYCLOAK_ROLE"/>
-        <addPrimaryKey columnNames="TEMPLATE_ID, NAME" constraintName="PK_CL_TMPL_ATTR" tableName="CLIENT_TEMPLATE_ATTRIBUTES"/>
-        <addForeignKeyConstraint baseColumnNames="TEMPLATE_ID" baseTableName="CLIENT_TEMPLATE_ATTRIBUTES" constraintName="FK_CL_TEMPL_ATTR_TEMPL" referencedColumnNames="ID" referencedTableName="CLIENT_TEMPLATE"/>
-
-        <update tableName="CREDENTIAL">
-            <column name="ALGORITHM" type="VARCHAR(36)" value="pbkdf2" />
-            <where>TYPE in ('password-history', 'password') AND ALGORITHM is NULL</where>
-        </update>
+        <createTable tableName="STORAGE_PROVIDER_CONFIG">
+            <column name="STORAGE_PROVIDER_ID" type="VARCHAR(36)">
+                <constraints nullable="false"/>
+            </column>
+            <column name="VALUE" type="VARCHAR(255)"/>
+            <column name="NAME" type="VARCHAR(255)">
+                <constraints nullable="false"/>
+            </column>
+        </createTable>
+        <createTable tableName="STORAGE_PROVIDER">
+            <column name="ID" type="VARCHAR(36)">
+                <constraints nullable="false"/>
+            </column>
+            <column name="DISPLAY_NAME" type="VARCHAR(255)"/>
+            <column name="PRIORITY" type="INT"/>
+            <column name="PROVIDER_NAME" type="VARCHAR(255)"/>
+            <column name="REALM_ID" type="VARCHAR(36)"/>
+        </createTable>
 
-    </changeSet>
 
-    <changeSet id="1.8.0-2" author="keycloak">
-        <dropDefaultValue tableName="CREDENTIAL" columnName="ALGORITHM" columnDataType="VARCHAR(36)"/>
 
-        <update tableName="CREDENTIAL">
-            <column name="ALGORITHM" type="VARCHAR(36)" value="pbkdf2" />
-            <where>TYPE in ('password-history', 'password') AND ALGORITHM = 'HmacSHA1'</where>
-        </update>
 
-        <!-- Sybase specific hacks -->
-        <modifySql dbms="sybase">
-            <regExpReplace replace=".*(SET DEFAULT NULL)" with="SELECT 1" />
-        </modifySql>
+        <addPrimaryKey columnNames="IDENTITY_PROVIDER, USER_ID" constraintName="CONSTR_BROKER_LINK_PK" tableName="BROKER_LINK" />
+        <addPrimaryKey columnNames="ID" constraintName="CONSTR_FED_USER_ATTR_PK" tableName="FED_USER_ATTRIBUTE"/>
+        <addPrimaryKey columnNames="ID" constraintName="CONSTR_FED_USER_CONSENT_PK" tableName="FED_USER_CONSENT"/>
+        <addPrimaryKey columnNames="USER_CONSENT_ID, ROLE_ID" constraintName="CONSTR_USER_CONSENT_ROLE_PK" tableName="FED_USER_CONSENT_ROLE"/>
+        <addPrimaryKey columnNames="USER_CONSENT_ID, PROTOCOL_MAPPER_ID" constraintName="CONSTR_USER_CONSENT_PROT_MAP_PK" tableName="FED_USER_CONSENT_PROT_MAPPER"/>
+        <!--
+        <addForeignKeyConstraint baseColumnNames="USER_CONSENT_ID" baseTableName="FED_USER_CONSENT_ROLE" constraintName="FK_FED_GRNTCSNT_ROLE_GR" referencedColumnNames="ID" referencedTableName="FED_USER_CONSENT"/>
+        <addForeignKeyConstraint baseColumnNames="USER_CONSENT_ID" baseTableName="FED_USER_CONSENT_PROT_MAPPER" constraintName="FK_FED_GRNTCSNT_PRM_GR" referencedColumnNames="ID" referencedTableName="FED_USER_CONSENT"/>
+ -->
+        <addPrimaryKey columnNames="ID" constraintName="CONSTR_FED_USER_CRED_PK" tableName="FED_USER_CREDENTIAL"/>
+        <addPrimaryKey columnNames="GROUP_ID, USER_ID" constraintName="CONSTR_FED_USER_GROUP" tableName="FED_USER_GROUP_MEMBERSHIP"/>
+        <addPrimaryKey columnNames="ROLE_ID, USER_ID" constraintName="CONSTR_FED_USER_ROLE" tableName="FED_USER_ROLE_MAPPING"/>
+        <addPrimaryKey columnNames="REQUIRED_ACTION, USER_ID" constraintName="CONSTR_FED_REQUIRED_ACTION" tableName="FED_USER_REQUIRED_ACTION"/>
 
+        <addPrimaryKey columnNames="ID" constraintName="CONSTR_STORAGE_PROVIDER_PK" tableName="STORAGE_PROVIDER"/>
+        <addPrimaryKey columnNames="STORAGE_PROVIDER_ID, NAME" constraintName="CONSTR_STORAGE_CONFIG" tableName="STORAGE_PROVIDER_CONFIG"/>
+        <!--
+        <addForeignKeyConstraint baseColumnNames="REALM_ID" baseTableName="STORAGE_PROVIDER" constraintName="FK_STORAGE_PROVIDER_REALM" referencedColumnNames="ID" referencedTableName="REALM"/>
+        -->
     </changeSet>
-
 </databaseChangeLog>
\ No newline at end of file
diff --git a/model/jpa/src/main/resources/META-INF/jpa-changelog-master.xml b/model/jpa/src/main/resources/META-INF/jpa-changelog-master.xml
index c8abe49..4dbc50b 100755
--- a/model/jpa/src/main/resources/META-INF/jpa-changelog-master.xml
+++ b/model/jpa/src/main/resources/META-INF/jpa-changelog-master.xml
@@ -32,6 +32,7 @@
     <include file="META-INF/jpa-changelog-1.9.0.xml"/>
     <include file="META-INF/jpa-changelog-1.9.1.xml"/>
     <include file="META-INF/jpa-changelog-1.9.2.xml"/>
+    <include file="META-INF/jpa-changelog-2.1.0.xml"/>
 
     <include file="META-INF/jpa-changelog-authz-master.xml"/>
 </databaseChangeLog>
diff --git a/model/jpa/src/main/resources/META-INF/persistence.xml b/model/jpa/src/main/resources/META-INF/persistence.xml
index b90c0fc..d912033 100755
--- a/model/jpa/src/main/resources/META-INF/persistence.xml
+++ b/model/jpa/src/main/resources/META-INF/persistence.xml
@@ -25,6 +25,7 @@
         <class>org.keycloak.models.jpa.entities.RealmEntity</class>
         <class>org.keycloak.models.jpa.entities.RealmAttributeEntity</class>
         <class>org.keycloak.models.jpa.entities.RequiredCredentialEntity</class>
+        <class>org.keycloak.models.jpa.entities.StorageProviderEntity</class>
         <class>org.keycloak.models.jpa.entities.UserFederationProviderEntity</class>
         <class>org.keycloak.models.jpa.entities.UserFederationMapperEntity</class>
         <class>org.keycloak.models.jpa.entities.RoleEntity</class>
@@ -64,6 +65,17 @@
         <class>org.keycloak.authorization.jpa.entities.ResourceEntity</class>
         <class>org.keycloak.authorization.jpa.entities.ScopeEntity</class>
         <class>org.keycloak.authorization.jpa.entities.PolicyEntity</class>
+
+        <!-- User Federation Storage -->
+        <class>org.keycloak.storage.jpa.entity.BrokerLinkEntity</class>
+        <class>org.keycloak.storage.jpa.entity.FederatedUserAttributeEntity</class>
+        <class>org.keycloak.storage.jpa.entity.FederatedUserConsentEntity</class>
+        <class>org.keycloak.storage.jpa.entity.FederatedUserConsentRoleEntity</class>
+        <class>org.keycloak.storage.jpa.entity.FederatedUserConsentProtocolMapperEntity</class>
+        <class>org.keycloak.storage.jpa.entity.FederatedUserCredentialEntity</class>
+        <class>org.keycloak.storage.jpa.entity.FederatedUserGroupMembershipEntity</class>
+        <class>org.keycloak.storage.jpa.entity.FederatedUserRequiredActionEntity</class>
+        <class>org.keycloak.storage.jpa.entity.FederatedUserRoleMappingEntity</class>
         
         <exclude-unlisted-classes>true</exclude-unlisted-classes>
 
diff --git a/model/jpa/src/main/resources/META-INF/services/org.keycloak.storage.federated.UserFederatedStorageProviderFactory b/model/jpa/src/main/resources/META-INF/services/org.keycloak.storage.federated.UserFederatedStorageProviderFactory
new file mode 100644
index 0000000..c67277c
--- /dev/null
+++ b/model/jpa/src/main/resources/META-INF/services/org.keycloak.storage.federated.UserFederatedStorageProviderFactory
@@ -0,0 +1 @@
+org.keycloak.storage.jpa.JpaUserFederatedStorageProviderFactory
\ No newline at end of file
diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/MongoUserProvider.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/MongoUserProvider.java
index 4f20a50..ad811d4 100755
--- a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/MongoUserProvider.java
+++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/MongoUserProvider.java
@@ -209,7 +209,8 @@ public class MongoUserProvider implements UserProvider {
     }
 
     @Override
-    public List<UserModel> searchForUser(String search, RealmModel realm, int firstResult, int maxResults) {
+    public List<UserModel>
+    searchForUser(String search, RealmModel realm, int firstResult, int maxResults) {
         search = search.trim();
         Pattern caseInsensitivePattern = Pattern.compile("(?i:" + search + ")");
 
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 16e6634..d903799 100755
--- a/server-spi/src/main/java/org/keycloak/models/UserProvider.java
+++ b/server-spi/src/main/java/org/keycloak/models/UserProvider.java
@@ -47,10 +47,6 @@ public interface UserProvider extends Provider, UserLookupProvider, UserQueryPro
     UserModel getServiceAccount(ClientModel client);
     List<UserModel> getUsers(RealmModel realm, boolean includeServiceAccounts);
     List<UserModel> getUsers(RealmModel realm, int firstResult, int maxResults, boolean includeServiceAccounts);
-    List<UserModel> searchForUser(String search, RealmModel realm);
-
-    // Searching by UserModel.attribute (not property)
-    List<UserModel> searchForUserByUserAttribute(String attrName, String attrValue, RealmModel realm);
 
     void preRemove(RealmModel realm);
 
diff --git a/server-spi/src/main/java/org/keycloak/models/UserQueryProvider.java b/server-spi/src/main/java/org/keycloak/models/UserQueryProvider.java
index 3f476bf..57a69ca 100644
--- a/server-spi/src/main/java/org/keycloak/models/UserQueryProvider.java
+++ b/server-spi/src/main/java/org/keycloak/models/UserQueryProvider.java
@@ -41,4 +41,9 @@ public interface UserQueryProvider {
 
     List<UserModel> getGroupMembers(RealmModel realm, GroupModel group, int firstResult, int maxResults);
     List<UserModel> getGroupMembers(RealmModel realm, GroupModel group);
+
+    List<UserModel> searchForUser(String search, RealmModel realm);
+
+    // Searching by UserModel.attribute (not property)
+    List<UserModel> searchForUserByUserAttribute(String attrName, String attrValue, RealmModel realm);
 }
diff --git a/server-spi/src/main/java/org/keycloak/storage/StorageProviderSpi.java b/server-spi/src/main/java/org/keycloak/storage/StorageProviderSpi.java
index 42a9060..5d6d0da 100755
--- a/server-spi/src/main/java/org/keycloak/storage/StorageProviderSpi.java
+++ b/server-spi/src/main/java/org/keycloak/storage/StorageProviderSpi.java
@@ -33,7 +33,7 @@ public class StorageProviderSpi implements Spi {
 
     @Override
     public String getName() {
-        return "userFederation";
+        return "storage";
     }
 
     @Override
diff --git a/server-spi/src/main/java/org/keycloak/storage/UserStorageManager.java b/server-spi/src/main/java/org/keycloak/storage/UserStorageManager.java
index aa438a3..f740f76 100755
--- a/server-spi/src/main/java/org/keycloak/storage/UserStorageManager.java
+++ b/server-spi/src/main/java/org/keycloak/storage/UserStorageManager.java
@@ -43,6 +43,7 @@ import org.keycloak.storage.federated.UserFederatedStorageProvider;
 
 import java.util.Arrays;
 import java.util.HashMap;
+import java.util.HashSet;
 import java.util.Iterator;
 import java.util.LinkedList;
 import java.util.List;
@@ -152,43 +153,77 @@ public class UserStorageManager implements UserProvider {
 
     @Override
     public void addFederatedIdentity(RealmModel realm, UserModel user, FederatedIdentityModel socialLink) {
-        getFederatedStorage().addFederatedIdentity(realm, user, socialLink);
+        if (StorageId.isLocalStorage(user)) {
+            localStorage().addFederatedIdentity(realm, user, socialLink);
+        } else {
+            getFederatedStorage().addFederatedIdentity(realm, user, socialLink);
+        }
     }
 
     public void updateFederatedIdentity(RealmModel realm, UserModel federatedUser, FederatedIdentityModel federatedIdentityModel) {
-        getFederatedStorage().updateFederatedIdentity(realm, federatedUser, federatedIdentityModel);
+        if (StorageId.isLocalStorage(federatedUser)) {
+            localStorage().updateFederatedIdentity(realm, federatedUser, federatedIdentityModel);
+
+        } else {
+            getFederatedStorage().updateFederatedIdentity(realm, federatedUser, federatedIdentityModel);
+        }
     }
 
     @Override
     public boolean removeFederatedIdentity(RealmModel realm, UserModel user, String socialProvider) {
-        return getFederatedStorage().removeFederatedIdentity(realm, user, socialProvider);
+        if (StorageId.isLocalStorage(user)) {
+            return localStorage().removeFederatedIdentity(realm, user, socialProvider);
+        } else {
+            return getFederatedStorage().removeFederatedIdentity(realm, user, socialProvider);
+        }
     }
 
     @Override
     public void addConsent(RealmModel realm, UserModel user, UserConsentModel consent) {
-        getFederatedStorage().addConsent(realm, user, consent);
+        if (StorageId.isLocalStorage(user)) {
+            localStorage().addConsent(realm, user, consent);
+        } else {
+            getFederatedStorage().addConsent(realm, user, consent);
+        }
 
     }
 
     @Override
     public UserConsentModel getConsentByClient(RealmModel realm, UserModel user, String clientInternalId) {
-        return getFederatedStorage().getConsentByClient(realm, user, clientInternalId);
+        if (StorageId.isLocalStorage(user)) {
+            return localStorage().getConsentByClient(realm, user, clientInternalId);
+        } else {
+            return getFederatedStorage().getConsentByClient(realm, user, clientInternalId);
+        }
     }
 
     @Override
     public List<UserConsentModel> getConsents(RealmModel realm, UserModel user) {
-        return getFederatedStorage().getConsents(realm, user);
+        if (StorageId.isLocalStorage(user)) {
+            return localStorage().getConsents(realm, user);
+
+        } else {
+            return getFederatedStorage().getConsents(realm, user);
+        }
     }
 
     @Override
     public void updateConsent(RealmModel realm, UserModel user, UserConsentModel consent) {
-        getFederatedStorage().updateConsent(realm, user, consent);
+        if (StorageId.isLocalStorage(user)) {
+            localStorage().updateConsent(realm, user, consent);
+        } else {
+            getFederatedStorage().updateConsent(realm, user, consent);
+        }
 
     }
 
     @Override
     public boolean revokeConsentForClient(RealmModel realm, UserModel user, String clientInternalId) {
-        return getFederatedStorage().revokeConsentForClient(realm, user, clientInternalId);
+        if (StorageId.isLocalStorage(user)) {
+            return localStorage().revokeConsentForClient(realm, user, clientInternalId);
+        } else {
+            return getFederatedStorage().revokeConsentForClient(realm, user, clientInternalId);
+        }
     }
 
     @Override
@@ -334,24 +369,10 @@ public class UserStorageManager implements UserProvider {
 
     @Override
     public List<UserModel> searchForUser(final String search, final RealmModel realm, int firstResult, int maxResults) {
-        final Map<String, String> attributes = new HashMap<String, String>();
-        int spaceIndex = search.lastIndexOf(' ');
-        if (spaceIndex > -1) {
-            String firstName = search.substring(0, spaceIndex).trim();
-            String lastName = search.substring(spaceIndex).trim();
-            attributes.put(UserModel.FIRST_NAME, firstName);
-            attributes.put(UserModel.LAST_NAME, lastName);
-        } else if (search.indexOf('@') > -1) {
-            attributes.put(UserModel.USERNAME, search.trim().toLowerCase());
-            attributes.put(UserModel.EMAIL, search.trim().toLowerCase());
-        } else {
-            attributes.put(UserModel.LAST_NAME, search.trim());
-            attributes.put(UserModel.USERNAME, search.trim().toLowerCase());
-        }
-        return query(new PaginatedQuery() {
+         return query(new PaginatedQuery() {
             @Override
             public List<UserModel> query(UserQueryProvider provider, int first, int max) {
-                return provider.searchForUserByAttributes(attributes, realm, first, max);
+                return provider.searchForUser(search, realm, first, max);
             }
         }, realm, firstResult, maxResults);
     }
@@ -372,26 +393,32 @@ public class UserStorageManager implements UserProvider {
     }
 
     @Override
-    public List<UserModel> searchForUserByUserAttribute(String attrName, String attrValue, RealmModel realm) {
-        Map<String, String> attributes = new HashMap<>();
-        attributes.put(attrName, attrValue);
-        return searchForUserByAttributes(attributes, realm);
+    public List<UserModel> searchForUserByUserAttribute(final String attrName, final String attrValue, RealmModel realm) {
+        return query(new PaginatedQuery() {
+            @Override
+            public List<UserModel> query(UserQueryProvider provider, int first, int max) {
+                return provider.searchForUserByUserAttribute(attrName, attrValue, realm);
+            }
+        }, realm,0, Integer.MAX_VALUE - 1);
     }
 
     @Override
     public Set<FederatedIdentityModel> getFederatedIdentities(UserModel user, RealmModel realm) {
         if (user == null) throw new IllegalStateException("Federated user no longer valid");
+        Set<FederatedIdentityModel> set = new HashSet<>();
         if (StorageId.isLocalStorage(user)) {
-            return localStorage().getFederatedIdentities(user, realm);
+            set.addAll(localStorage().getFederatedIdentities(user, realm));
         }
-        return getFederatedStorage().getFederatedIdentities(user, realm);
+        set.addAll(getFederatedStorage().getFederatedIdentities(user, realm));
+        return set;
     }
 
     @Override
     public FederatedIdentityModel getFederatedIdentity(UserModel user, String socialProvider, RealmModel realm) {
         if (user == null) throw new IllegalStateException("Federated user no longer valid");
         if (StorageId.isLocalStorage(user)) {
-            return localStorage().getFederatedIdentity(user, socialProvider, realm);
+            FederatedIdentityModel model = localStorage().getFederatedIdentity(user, socialProvider, realm);
+            if (model != null) return model;
         }
         return getFederatedStorage().getFederatedIdentity(user, socialProvider, realm);
     }
@@ -430,6 +457,7 @@ public class UserStorageManager implements UserProvider {
 
     @Override
     public void preRemove(RealmModel realm, UserFederationProviderModel model) {
+        getFederatedStorage().preRemove(realm, model);
         localStorage().preRemove(realm, model);
     }
 
@@ -454,11 +482,14 @@ public class UserStorageManager implements UserProvider {
     @Override
     public void preRemove(RealmModel realm, ClientModel client) {
         localStorage().preRemove(realm, client);
+        getFederatedStorage().preRemove(realm, client);
+
     }
 
     @Override
     public void preRemove(ProtocolMapperModel protocolMapper) {
         localStorage().preRemove(protocolMapper);
+        getFederatedStorage().preRemove(protocolMapper);
     }
 
     @Override
diff --git a/server-spi/src/main/resources/META-INF/services/org.keycloak.provider.Spi b/server-spi/src/main/resources/META-INF/services/org.keycloak.provider.Spi
index 89c6e2d..c5a5ebb 100755
--- a/server-spi/src/main/resources/META-INF/services/org.keycloak.provider.Spi
+++ b/server-spi/src/main/resources/META-INF/services/org.keycloak.provider.Spi
@@ -16,6 +16,7 @@
 #
 
 org.keycloak.models.UserFederationSpi
+org.keycloak.storage.StorageProviderSpi
 org.keycloak.storage.federated.UserFederatedStorageProviderSpi
 org.keycloak.mappers.UserFederationMapperSpi
 org.keycloak.models.RealmSpi
diff --git a/services/src/main/java/org/keycloak/services/DefaultKeycloakSession.java b/services/src/main/java/org/keycloak/services/DefaultKeycloakSession.java
index 3e5529e..8eb2a56 100644
--- a/services/src/main/java/org/keycloak/services/DefaultKeycloakSession.java
+++ b/services/src/main/java/org/keycloak/services/DefaultKeycloakSession.java
@@ -42,6 +42,7 @@ public class DefaultKeycloakSession implements KeycloakSession {
     private ScriptingProvider scriptingProvider;
     private UserSessionProvider sessionProvider;
     private UserFederationManager federationManager;
+    private UserFederatedStorageProvider userFederatedStorageProvider;
     private KeycloakContext context;
 
     public DefaultKeycloakSession(DefaultKeycloakSessionFactory factory) {
@@ -91,7 +92,10 @@ public class DefaultKeycloakSession implements KeycloakSession {
 
     @Override
     public UserFederatedStorageProvider userFederatedStorage() {
-        return null;
+        if (userFederatedStorageProvider == null) {
+            userFederatedStorageProvider = getProvider(UserFederatedStorageProvider.class);
+        }
+        return userFederatedStorageProvider;
     }
 
     @Override
diff --git a/services/src/main/java/org/keycloak/services/DefaultKeycloakSessionFactory.java b/services/src/main/java/org/keycloak/services/DefaultKeycloakSessionFactory.java
index 172de6e..b56af8d 100755
--- a/services/src/main/java/org/keycloak/services/DefaultKeycloakSessionFactory.java
+++ b/services/src/main/java/org/keycloak/services/DefaultKeycloakSessionFactory.java
@@ -154,7 +154,11 @@ public class DefaultKeycloakSessionFactory implements KeycloakSessionFactory {
 
     @Override
     public <T extends Provider> ProviderFactory<T> getProviderFactory(Class<T> clazz, String id) {
-         return factoriesMap.get(clazz).get(id);
+        Map<String, ProviderFactory> map = factoriesMap.get(clazz);
+        if (map == null) {
+            return null;
+        }
+        return map.get(id);
     }
 
     @Override
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/AbstractKeycloakIdentityProviderTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/AbstractKeycloakIdentityProviderTest.java
index 1af21f5..7683d0c 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/AbstractKeycloakIdentityProviderTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/AbstractKeycloakIdentityProviderTest.java
@@ -72,7 +72,9 @@ public abstract class AbstractKeycloakIdentityProviderTest extends AbstractIdent
 
     @Test
     public void testDisabledUser() {
+        KeycloakSession session = brokerServerRule.startSession();
         setUpdateProfileFirstLogin(session.realms().getRealmByName("realm-with-broker"), IdentityProviderRepresentation.UPFLM_OFF);
+        brokerServerRule.stopSession(session, true);
 
         driver.navigate().to("http://localhost:8081/test-app");
         loginPage.clickSocial(getProviderId());
@@ -81,7 +83,7 @@ public abstract class AbstractKeycloakIdentityProviderTest extends AbstractIdent
         driver.navigate().to("http://localhost:8081/test-app/logout");
 
         try {
-            KeycloakSession session = brokerServerRule.startSession();
+            session = brokerServerRule.startSession();
             session.users().getUserByUsername("test-user", session.realms().getRealmByName("realm-with-broker")).setEnabled(false);
             brokerServerRule.stopSession(session, true);
 
@@ -93,7 +95,7 @@ public abstract class AbstractKeycloakIdentityProviderTest extends AbstractIdent
             assertTrue(errorPage.isCurrent());
             assertEquals("Account is disabled, contact admin.", errorPage.getError());
         } finally {
-            KeycloakSession session = brokerServerRule.startSession();
+            session = brokerServerRule.startSession();
             session.users().getUserByUsername("test-user", session.realms().getRealmByName("realm-with-broker")).setEnabled(true);
             brokerServerRule.stopSession(session, true);
         }
@@ -101,7 +103,9 @@ public abstract class AbstractKeycloakIdentityProviderTest extends AbstractIdent
 
     @Test
     public void testTemporarilyDisabledUser() {
+        KeycloakSession session = brokerServerRule.startSession();
         setUpdateProfileFirstLogin(session.realms().getRealmByName("realm-with-broker"), IdentityProviderRepresentation.UPFLM_OFF);
+        brokerServerRule.stopSession(session, true);
 
         driver.navigate().to("http://localhost:8081/test-app");
         loginPage.clickSocial(getProviderId());
@@ -109,7 +113,7 @@ public abstract class AbstractKeycloakIdentityProviderTest extends AbstractIdent
         driver.navigate().to("http://localhost:8081/test-app/logout");
 
         try {
-            KeycloakSession session = brokerServerRule.startSession();
+            session = brokerServerRule.startSession();
             RealmModel brokerRealm = session.realms().getRealmByName("realm-with-broker");
             brokerRealm.setBruteForceProtected(true);
             brokerRealm.setFailureFactor(2);
@@ -129,7 +133,7 @@ public abstract class AbstractKeycloakIdentityProviderTest extends AbstractIdent
             assertTrue(errorPage.isCurrent());
             assertEquals("Account is disabled, contact admin.", errorPage.getError());
         } finally {
-            KeycloakSession session = brokerServerRule.startSession();
+            session = brokerServerRule.startSession();
             RealmModel brokerRealm = session.realms().getRealmByName("realm-with-broker");
             brokerRealm.setBruteForceProtected(false);
             brokerRealm.setFailureFactor(0);
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/OIDCKeyCloakServerBrokerBasicTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/OIDCKeyCloakServerBrokerBasicTest.java
index 28663ff..d79d85f 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/OIDCKeyCloakServerBrokerBasicTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/OIDCKeyCloakServerBrokerBasicTest.java
@@ -170,4 +170,14 @@ public class OIDCKeyCloakServerBrokerBasicTest extends AbstractKeycloakIdentityP
     public void testAccountManagementLinkIdentity() {
         super.testAccountManagementLinkIdentity();
     }
+
+    @Test
+    public void testWithLinkedFederationProvider() throws Exception {
+        super.testWithLinkedFederationProvider();
+    }
+
+    @Test
+    public void testAccountManagementLinkedIdentityAlreadyExists() {
+        super.testAccountManagementLinkedIdentityAlreadyExists();
+    }
 }
diff --git a/testsuite/integration/src/test/resources/log4j.properties b/testsuite/integration/src/test/resources/log4j.properties
index c2bc22c..306d48e 100755
--- a/testsuite/integration/src/test/resources/log4j.properties
+++ b/testsuite/integration/src/test/resources/log4j.properties
@@ -21,7 +21,7 @@ log4j.appender.stdout=org.apache.log4j.ConsoleAppender
 log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
 log4j.appender.stdout.layout.ConversionPattern=%d{HH:mm:ss,SSS} %-5p %t [%c] %m%n
 
-log4j.logger.org.keycloak=info
+log4j.logger.org.keycloak=debug
 
 # Enable to view events
 # log4j.logger.org.keycloak.events=debug
diff --git a/testsuite/integration-arquillian/tests/base/src/test/resources/META-INF/keycloak-server.json b/testsuite/integration-arquillian/tests/base/src/test/resources/META-INF/keycloak-server.json
index c37291d..c42d254 100755
--- a/testsuite/integration-arquillian/tests/base/src/test/resources/META-INF/keycloak-server.json
+++ b/testsuite/integration-arquillian/tests/base/src/test/resources/META-INF/keycloak-server.json
@@ -23,6 +23,10 @@
         "provider": "${keycloak.user.provider:jpa}"
     },
 
+    "userFederatedStorage": {
+        "provider": "${keycloak.userFederatedStorage.provider:jpa}"
+    },
+
     "userSessionPersister": {
         "provider": "${keycloak.userSessionPersister.provider:jpa}"
     },